/**
   * Draws the visual representation of a single data item when the plot has a vertical orientation.
   *
   * @param g2 the graphics device.
   * @param state the renderer state.
   * @param dataArea the area within which the plot is being drawn.
   * @param plot the plot (can be used to obtain standard color information etc).
   * @param domainAxis the domain axis.
   * @param rangeAxis the range axis.
   * @param dataset the dataset (must be an instance of {@link BoxAndWhiskerCategoryDataset}).
   * @param row the row index (zero-based).
   * @param column the column index (zero-based).
   */
  public void drawVerticalItem(
      Graphics2D g2,
      CategoryItemRendererState state,
      Rectangle2D dataArea,
      CategoryPlot plot,
      CategoryAxis domainAxis,
      ValueAxis rangeAxis,
      CategoryDataset dataset,
      int row,
      int column) {

    BoxAndWhiskerCategoryDataset bawDataset = (BoxAndWhiskerCategoryDataset) dataset;

    double categoryEnd =
        domainAxis.getCategoryEnd(column, getColumnCount(), dataArea, plot.getDomainAxisEdge());
    double categoryStart =
        domainAxis.getCategoryStart(column, getColumnCount(), dataArea, plot.getDomainAxisEdge());
    double categoryWidth = categoryEnd - categoryStart;

    double xx = categoryStart;
    int seriesCount = getRowCount();
    int categoryCount = getColumnCount();

    if (seriesCount > 1) {
      double seriesGap =
          dataArea.getWidth() * getItemMargin() / (categoryCount * (seriesCount - 1));
      double usedWidth = (state.getBarWidth() * seriesCount) + (seriesGap * (seriesCount - 1));
      // offset the start of the boxes if the total width used is smaller
      // than the category width
      double offset = (categoryWidth - usedWidth) / 2;
      xx = xx + offset + (row * (state.getBarWidth() + seriesGap));
    } else {
      // offset the start of the box if the box width is smaller than the
      // category width
      double offset = (categoryWidth - state.getBarWidth()) / 2;
      xx = xx + offset;
    }

    double yyAverage = 0.0;
    double yyOutlier;

    Paint itemPaint = getItemPaint(row, column);
    g2.setPaint(itemPaint);
    Stroke s = getItemStroke(row, column);
    g2.setStroke(s);

    double aRadius = 0; // average radius

    RectangleEdge location = plot.getRangeAxisEdge();

    Number yQ1 = bawDataset.getQ1Value(row, column);
    Number yQ3 = bawDataset.getQ3Value(row, column);
    Number yMax = bawDataset.getMaxRegularValue(row, column);
    Number yMin = bawDataset.getMinRegularValue(row, column);
    Shape box = null;
    if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) {

      double yyQ1 = rangeAxis.valueToJava2D(yQ1.doubleValue(), dataArea, location);
      double yyQ3 = rangeAxis.valueToJava2D(yQ3.doubleValue(), dataArea, location);
      double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea, location);
      double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea, location);
      double xxmid = xx + state.getBarWidth() / 2.0;

      // draw the upper shadow...
      g2.draw(new Line2D.Double(xxmid, yyMax, xxmid, yyQ3));
      g2.draw(new Line2D.Double(xx, yyMax, xx + state.getBarWidth(), yyMax));

      // draw the lower shadow...
      g2.draw(new Line2D.Double(xxmid, yyMin, xxmid, yyQ1));
      g2.draw(new Line2D.Double(xx, yyMin, xx + state.getBarWidth(), yyMin));

      // draw the body...
      box =
          new Rectangle2D.Double(
              xx, Math.min(yyQ1, yyQ3), state.getBarWidth(), Math.abs(yyQ1 - yyQ3));
      if (this.fillBox) {
        g2.fill(box);
      }
      g2.setStroke(getItemOutlineStroke(row, column));
      g2.setPaint(getItemOutlinePaint(row, column));
      g2.draw(box);
    }

    g2.setPaint(this.artifactPaint);

    // draw mean - SPECIAL AIMS REQUIREMENT...
    Number yMean = bawDataset.getMeanValue(row, column);
    if (yMean != null) {
      yyAverage = rangeAxis.valueToJava2D(yMean.doubleValue(), dataArea, location);
      aRadius = state.getBarWidth() / 4;
      // here we check that the average marker will in fact be visible
      // before drawing it...
      if ((yyAverage > (dataArea.getMinY() - aRadius))
          && (yyAverage < (dataArea.getMaxY() + aRadius))) {
        Ellipse2D.Double avgEllipse =
            new Ellipse2D.Double(xx + aRadius, yyAverage - aRadius, aRadius * 2, aRadius * 2);
        g2.fill(avgEllipse);
        g2.draw(avgEllipse);
      }
    }

    // draw median...
    Number yMedian = bawDataset.getMedianValue(row, column);
    if (yMedian != null) {
      double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(), dataArea, location);
      g2.draw(new Line2D.Double(xx, yyMedian, xx + state.getBarWidth(), yyMedian));
    }

    // draw yOutliers...
    double maxAxisValue =
        rangeAxis.valueToJava2D(rangeAxis.getUpperBound(), dataArea, location) + aRadius;
    double minAxisValue =
        rangeAxis.valueToJava2D(rangeAxis.getLowerBound(), dataArea, location) - aRadius;

    g2.setPaint(itemPaint);

    // draw outliers
    double oRadius = state.getBarWidth() / 3; // outlier radius
    List outliers = new ArrayList();
    OutlierListCollection outlierListCollection = new OutlierListCollection();

    // From outlier array sort out which are outliers and put these into a
    // list If there are any farouts, set the flag on the
    // OutlierListCollection
    List yOutliers = bawDataset.getOutliers(row, column);
    if (yOutliers != null) {
      for (int i = 0; i < yOutliers.size(); i++) {
        double outlier = ((Number) yOutliers.get(i)).doubleValue();
        Number minOutlier = bawDataset.getMinOutlier(row, column);
        Number maxOutlier = bawDataset.getMaxOutlier(row, column);
        Number minRegular = bawDataset.getMinRegularValue(row, column);
        Number maxRegular = bawDataset.getMaxRegularValue(row, column);
        if (outlier > maxOutlier.doubleValue()) {
          outlierListCollection.setHighFarOut(true);
        } else if (outlier < minOutlier.doubleValue()) {
          outlierListCollection.setLowFarOut(true);
        } else if (outlier > maxRegular.doubleValue()) {
          yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, location);
          outliers.add(new Outlier(xx + state.getBarWidth() / 2.0, yyOutlier, oRadius));
        } else if (outlier < minRegular.doubleValue()) {
          yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, location);
          outliers.add(new Outlier(xx + state.getBarWidth() / 2.0, yyOutlier, oRadius));
        }
        Collections.sort(outliers);
      }

      // Process outliers. Each outlier is either added to the
      // appropriate outlier list or a new outlier list is made
      for (Iterator iterator = outliers.iterator(); iterator.hasNext(); ) {
        Outlier outlier = (Outlier) iterator.next();
        outlierListCollection.add(outlier);
      }

      for (Iterator iterator = outlierListCollection.iterator(); iterator.hasNext(); ) {
        OutlierList list = (OutlierList) iterator.next();
        Outlier outlier = list.getAveragedOutlier();
        Point2D point = outlier.getPoint();

        if (list.isMultiple()) {
          drawMultipleEllipse(point, state.getBarWidth(), oRadius, g2);
        } else {
          drawEllipse(point, oRadius, g2);
        }
      }

      // draw farout indicators
      if (outlierListCollection.isHighFarOut()) {
        drawHighFarOut(aRadius / 2.0, g2, xx + state.getBarWidth() / 2.0, maxAxisValue);
      }

      if (outlierListCollection.isLowFarOut()) {
        drawLowFarOut(aRadius / 2.0, g2, xx + state.getBarWidth() / 2.0, minAxisValue);
      }
    }
    // collect entity and tool tip information...
    if (state.getInfo() != null && box != null) {
      EntityCollection entities = state.getEntityCollection();
      if (entities != null) {
        addItemEntity(entities, dataset, row, column, box);
      }
    }
  }
  /**
   * Draws the visual representation of a single data item when the plot has a horizontal
   * orientation.
   *
   * @param g2 the graphics device.
   * @param state the renderer state.
   * @param dataArea the area within which the plot is being drawn.
   * @param plot the plot (can be used to obtain standard color information etc).
   * @param domainAxis the domain axis.
   * @param rangeAxis the range axis.
   * @param dataset the dataset (must be an instance of {@link BoxAndWhiskerCategoryDataset}).
   * @param row the row index (zero-based).
   * @param column the column index (zero-based).
   */
  public void drawHorizontalItem(
      Graphics2D g2,
      CategoryItemRendererState state,
      Rectangle2D dataArea,
      CategoryPlot plot,
      CategoryAxis domainAxis,
      ValueAxis rangeAxis,
      CategoryDataset dataset,
      int row,
      int column) {

    BoxAndWhiskerCategoryDataset bawDataset = (BoxAndWhiskerCategoryDataset) dataset;

    double categoryEnd =
        domainAxis.getCategoryEnd(column, getColumnCount(), dataArea, plot.getDomainAxisEdge());
    double categoryStart =
        domainAxis.getCategoryStart(column, getColumnCount(), dataArea, plot.getDomainAxisEdge());
    double categoryWidth = Math.abs(categoryEnd - categoryStart);

    double yy = categoryStart;
    int seriesCount = getRowCount();
    int categoryCount = getColumnCount();

    if (seriesCount > 1) {
      double seriesGap =
          dataArea.getHeight() * getItemMargin() / (categoryCount * (seriesCount - 1));
      double usedWidth = (state.getBarWidth() * seriesCount) + (seriesGap * (seriesCount - 1));
      // offset the start of the boxes if the total width used is smaller
      // than the category width
      double offset = (categoryWidth - usedWidth) / 2;
      yy = yy + offset + (row * (state.getBarWidth() + seriesGap));
    } else {
      // offset the start of the box if the box width is smaller than
      // the category width
      double offset = (categoryWidth - state.getBarWidth()) / 2;
      yy = yy + offset;
    }

    g2.setPaint(getItemPaint(row, column));
    Stroke s = getItemStroke(row, column);
    g2.setStroke(s);

    RectangleEdge location = plot.getRangeAxisEdge();

    Number xQ1 = bawDataset.getQ1Value(row, column);
    Number xQ3 = bawDataset.getQ3Value(row, column);
    Number xMax = bawDataset.getMaxRegularValue(row, column);
    Number xMin = bawDataset.getMinRegularValue(row, column);

    Shape box = null;
    if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) {

      double xxQ1 = rangeAxis.valueToJava2D(xQ1.doubleValue(), dataArea, location);
      double xxQ3 = rangeAxis.valueToJava2D(xQ3.doubleValue(), dataArea, location);
      double xxMax = rangeAxis.valueToJava2D(xMax.doubleValue(), dataArea, location);
      double xxMin = rangeAxis.valueToJava2D(xMin.doubleValue(), dataArea, location);
      double yymid = yy + state.getBarWidth() / 2.0;

      // draw the upper shadow...
      g2.draw(new Line2D.Double(xxMax, yymid, xxQ3, yymid));
      g2.draw(new Line2D.Double(xxMax, yy, xxMax, yy + state.getBarWidth()));

      // draw the lower shadow...
      g2.draw(new Line2D.Double(xxMin, yymid, xxQ1, yymid));
      g2.draw(new Line2D.Double(xxMin, yy, xxMin, yy + state.getBarWidth()));

      // draw the box...
      box =
          new Rectangle2D.Double(
              Math.min(xxQ1, xxQ3), yy, Math.abs(xxQ1 - xxQ3), state.getBarWidth());
      if (this.fillBox) {
        g2.fill(box);
      }
      g2.setStroke(getItemOutlineStroke(row, column));
      g2.setPaint(getItemOutlinePaint(row, column));
      g2.draw(box);
    }

    g2.setPaint(this.artifactPaint);
    double aRadius = 0; // average radius

    // draw mean - SPECIAL AIMS REQUIREMENT...
    Number xMean = bawDataset.getMeanValue(row, column);
    if (xMean != null) {
      double xxMean = rangeAxis.valueToJava2D(xMean.doubleValue(), dataArea, location);
      aRadius = state.getBarWidth() / 4;
      // here we check that the average marker will in fact be visible
      // before drawing it...
      if ((xxMean > (dataArea.getMinX() - aRadius)) && (xxMean < (dataArea.getMaxX() + aRadius))) {
        Ellipse2D.Double avgEllipse =
            new Ellipse2D.Double(xxMean - aRadius, yy + aRadius, aRadius * 2, aRadius * 2);
        g2.fill(avgEllipse);
        g2.draw(avgEllipse);
      }
    }

    // draw median...
    Number xMedian = bawDataset.getMedianValue(row, column);
    if (xMedian != null) {
      double xxMedian = rangeAxis.valueToJava2D(xMedian.doubleValue(), dataArea, location);
      g2.draw(new Line2D.Double(xxMedian, yy, xxMedian, yy + state.getBarWidth()));
    }

    // collect entity and tool tip information...
    if (state.getInfo() != null && box != null) {
      EntityCollection entities = state.getEntityCollection();
      if (entities != null) {
        addItemEntity(entities, dataset, row, column, box);
      }
    }
  }