/**
   * Constructor
   *
   * @param points an array of points
   */
  public GPointsArray(GPointsArray points) {
    this.points = new ArrayList<GPoint>(points.getNPoints());

    for (int i = 0; i < points.getNPoints(); i++) {
      this.points.add(new GPoint(points.get(i)));
    }
  }
  /** Draws the histogram labels */
  protected void drawHistLabels() {
    parent.pushStyle();
    parent.textMode(MODEL);
    parent.textFont(font);
    parent.textSize(fontSize);
    parent.fill(fontColor);
    parent.noStroke();

    if (type == GPlot.VERTICAL) {
      if (rotateLabels) {
        parent.textAlign(RIGHT, CENTER);

        for (int i = 0; i < plotPoints.getNPoints(); i++) {
          if (plotPoints.isValid(i) && plotPoints.getX(i) >= 0 && plotPoints.getX(i) <= dim[0]) {
            parent.pushMatrix();
            parent.translate(plotPoints.getX(i), labelsOffset);
            parent.rotate(-HALF_PI);
            parent.text(plotPoints.getLabel(i), 0, 0);
            parent.popMatrix();
          }
        }
      } else {
        parent.textAlign(CENTER, TOP);

        for (int i = 0; i < plotPoints.getNPoints(); i++) {
          if (plotPoints.isValid(i) && plotPoints.getX(i) >= 0 && plotPoints.getX(i) <= dim[0]) {
            parent.text(plotPoints.getLabel(i), plotPoints.getX(i), labelsOffset);
          }
        }
      }
    } else {
      if (rotateLabels) {
        parent.textAlign(CENTER, BOTTOM);

        for (int i = 0; i < plotPoints.getNPoints(); i++) {
          if (plotPoints.isValid(i) && -plotPoints.getY(i) >= 0 && -plotPoints.getY(i) <= dim[1]) {
            parent.pushMatrix();
            parent.translate(-labelsOffset, plotPoints.getY(i));
            parent.rotate(-HALF_PI);
            parent.text(plotPoints.getLabel(i), 0, 0);
            parent.popMatrix();
          }
        }
      } else {
        parent.textAlign(RIGHT, CENTER);

        for (int i = 0; i < plotPoints.getNPoints(); i++) {
          if (plotPoints.isValid(i) && -plotPoints.getY(i) >= 0 && -plotPoints.getY(i) <= dim[1]) {
            parent.text(plotPoints.getLabel(i), -labelsOffset, plotPoints.getY(i));
          }
        }
      }
    }

    parent.popStyle();
  }
  /**
   * Constructor
   *
   * @param parent the parent Processing applet
   * @param type the histogram type. It can be GPlot.VERTICAL or GPlot.HORIZONTAL
   * @param dim the plot box dimensions in pixels
   * @param plotPoints the points positions in the plot reference system
   */
  public GHistogram(PApplet parent, int type, float[] dim, GPointsArray plotPoints) {
    this.parent = parent;

    this.type = (type == GPlot.VERTICAL || type == GPlot.HORIZONTAL) ? type : GPlot.VERTICAL;
    this.dim = dim.clone();
    this.plotPoints = new GPointsArray(plotPoints);
    visible = true;
    separations = new float[] {2};
    bgColors = new int[] {this.parent.color(150, 150, 255)};
    lineColors = new int[] {this.parent.color(100, 100, 255)};
    lineWidths = new float[] {1};

    int nPoints = plotPoints.getNPoints();
    differences = new ArrayList<Float>(nPoints);
    leftSides = new ArrayList<Float>(nPoints);
    rightSides = new ArrayList<Float>(nPoints);
    initializeArrays(nPoints);
    updateArrays();

    labelsOffset = 8;
    drawLabels = false;
    rotateLabels = false;
    fontName = "SansSerif.plain";
    fontColor = this.parent.color(0);
    fontSize = 11;
    font = this.parent.createFont(fontName, fontSize);
  }
  /**
   * Sets all the points in the array
   *
   * @param pts the new points. The number of points could differ from the original.
   */
  public void set(GPointsArray pts) {
    if (pts.getNPoints() == points.size()) {
      for (int i = 0; i < points.size(); i++) {
        points.get(i).set(pts.get(i));
      }
    } else if (pts.getNPoints() > points.size()) {
      for (int i = 0; i < points.size(); i++) {
        points.get(i).set(pts.get(i));
      }

      for (int i = points.size(); i < pts.getNPoints(); i++) {
        points.add(new GPoint(pts.get(i)));
      }
    } else {
      for (int i = 0; i < pts.getNPoints(); i++) {
        points.get(i).set(pts.get(i));
      }

      points.subList(pts.getNPoints(), points.size()).clear();
    }
  }
  /** Updates the differences, leftSides and rightSides arrays */
  protected void updateArrays() {
    int nPoints = plotPoints.getNPoints();

    if (nPoints == 1) {
      leftSides.set(0, (type == GPlot.VERTICAL) ? 0.2f * dim[0] : 0.2f * dim[1]);
      rightSides.set(0, leftSides.get(0));
    } else if (nPoints > 1) {
      // Calculate the differences between consecutive points
      for (int i = 0; i < nPoints - 1; i++) {
        if (plotPoints.isValid(i) && plotPoints.isValid(i + 1)) {
          float separation = separations[i % separations.length];
          float diff;

          if (type == GPlot.VERTICAL) {
            diff = plotPoints.getX(i + 1) - plotPoints.getX(i);
          } else {
            diff = plotPoints.getY(i + 1) - plotPoints.getY(i);
          }

          if (diff > 0) {
            differences.set(i, (diff - separation) / 2f);
          } else {
            differences.set(i, (diff + separation) / 2f);
          }
        } else {
          differences.set(i, 0f);
        }
      }

      // Fill the leftSides and rightSides arrays
      leftSides.set(0, differences.get(0));
      rightSides.set(0, differences.get(0));

      for (int i = 1; i < nPoints - 1; i++) {
        leftSides.set(i, differences.get(i - 1));
        rightSides.set(i, differences.get(i));
      }

      leftSides.set(nPoints - 1, differences.get(nPoints - 2));
      rightSides.set(nPoints - 1, differences.get(nPoints - 2));
    }
  }
 /**
  * Removes one of the points from the histogram
  *
  * @param index the point position
  */
 public void removePlotPoint(int index) {
   plotPoints.remove(index);
   initializeArrays(plotPoints.getNPoints());
   updateArrays();
 }
 /**
  * Adds a new plot points to the histogram
  *
  * @param newPlotPoints the new points positions in the plot reference system
  */
 public void addPlotPoints(GPointsArray newPlotPoints) {
   plotPoints.add(newPlotPoints);
   initializeArrays(plotPoints.getNPoints());
   updateArrays();
 }
 /**
  * Adds a new plot point to the histogram
  *
  * @param index the position to add the point
  * @param newPlotPoint the new point position in the plot reference system
  */
 public void addPlotPoint(int index, GPoint newPlotPoint) {
   plotPoints.add(index, newPlotPoint);
   initializeArrays(plotPoints.getNPoints());
   updateArrays();
 }
  /**
   * Draws the histogram
   *
   * @param plotBasePoint the histogram base point in the plot reference system
   */
  public void draw(GPoint plotBasePoint) {
    if (visible) {
      // Calculate the baseline for the histogram
      float baseline = 0;

      if (plotBasePoint.isValid()) {
        baseline = (type == GPlot.VERTICAL) ? plotBasePoint.getY() : plotBasePoint.getX();
      }

      // Draw the rectangles
      parent.pushStyle();
      parent.rectMode(CORNERS);
      parent.strokeCap(SQUARE);

      for (int i = 0; i < plotPoints.getNPoints(); i++) {
        if (plotPoints.isValid(i)) {
          // Obtain the corners
          float x1, x2, y1, y2;

          if (type == GPlot.VERTICAL) {
            x1 = plotPoints.getX(i) - leftSides.get(i);
            x2 = plotPoints.getX(i) + rightSides.get(i);
            y1 = plotPoints.getY(i);
            y2 = baseline;
          } else {
            x1 = baseline;
            x2 = plotPoints.getX(i);
            y1 = plotPoints.getY(i) - leftSides.get(i);
            y2 = plotPoints.getY(i) + rightSides.get(i);
          }

          if (x1 < 0) {
            x1 = 0;
          } else if (x1 > dim[0]) {
            x1 = dim[0];
          }

          if (-y1 < 0) {
            y1 = 0;
          } else if (-y1 > dim[1]) {
            y1 = -dim[1];
          }

          if (x2 < 0) {
            x2 = 0;
          } else if (x2 > dim[0]) {
            x2 = dim[0];
          }

          if (-y2 < 0) {
            y2 = 0;
          } else if (-y2 > dim[1]) {
            y2 = -dim[1];
          }

          // Draw the rectangle
          float lw = lineWidths[i % lineWidths.length];
          parent.fill(bgColors[i % bgColors.length]);
          parent.stroke(lineColors[i % lineColors.length]);
          parent.strokeWeight(lw);

          if (Math.abs(x2 - x1) > 2 * lw && Math.abs(y2 - y1) > 2 * lw) {
            parent.rect(x1, y1, x2, y2);
          } else if ((type == GPlot.VERTICAL
                  && x2 != x1
                  && !(y1 == y2 && (y1 == 0 || y1 == -dim[1])))
              || (type == GPlot.HORIZONTAL
                  && y2 != y1
                  && !(x1 == x2 && (x1 == 0 || x1 == dim[0])))) {
            parent.rect(x1, y1, x2, y2);
            parent.line(x1, y1, x1, y2);
            parent.line(x2, y1, x2, y2);
            parent.line(x1, y1, x2, y1);
            parent.line(x1, y2, x2, y2);
          }
        }
      }

      parent.popStyle();

      // Draw the labels
      if (drawLabels) {
        drawHistLabels();
      }
    }
  }
 /**
  * Adds a new set of points to the array
  *
  * @param pts the new set of points
  */
 public void add(GPointsArray pts) {
   for (int i = 0; i < pts.getNPoints(); i++) {
     points.add(new GPoint(pts.get(i)));
   }
 }