/**
   * Draw a single data item.
   *
   * @param g2 the graphics device.
   * @param state the renderer state.
   * @param dataArea the area in which the data is drawn.
   * @param plot the plot.
   * @param domainAxis the domain axis.
   * @param rangeAxis the range axis.
   * @param dataset the dataset (a {@link StatisticalCategoryDataset} is required).
   * @param row the row index (zero-based).
   * @param column the column index (zero-based).
   * @param pass the pass.
   */
  @Override
  public void drawItem(
      Graphics2D g2,
      CategoryItemRendererState state,
      Rectangle2D dataArea,
      CategoryPlot plot,
      CategoryAxis domainAxis,
      ValueAxis rangeAxis,
      CategoryDataset dataset,
      int row,
      int column,
      int pass) {

    // do nothing if item is not visible
    if (!getItemVisible(row, column)) {
      return;
    }

    // if the dataset is not a StatisticalCategoryDataset then just revert
    // to the superclass (LineAndShapeRenderer) behaviour...
    if (!(dataset instanceof StatisticalCategoryDataset)) {
      super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, dataset, row, column, pass);
      return;
    }

    int visibleRow = state.getVisibleSeriesIndex(row);
    if (visibleRow < 0) {
      return;
    }
    int visibleRowCount = state.getVisibleSeriesCount();

    StatisticalCategoryDataset statDataset = (StatisticalCategoryDataset) dataset;
    Number meanValue = statDataset.getMeanValue(row, column);
    if (meanValue == null) {
      return;
    }
    PlotOrientation orientation = plot.getOrientation();

    // current data point...
    double x1;
    if (getUseSeriesOffset()) {
      x1 =
          domainAxis.getCategorySeriesMiddle(
              column,
              dataset.getColumnCount(),
              visibleRow,
              visibleRowCount,
              getItemMargin(),
              dataArea,
              plot.getDomainAxisEdge());
    } else {
      x1 =
          domainAxis.getCategoryMiddle(
              column, getColumnCount(), dataArea, plot.getDomainAxisEdge());
    }
    double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea, plot.getRangeAxisEdge());

    // draw the standard deviation lines *before* the shapes (if they're
    // visible) - it looks better if the shape fill colour is different to
    // the line colour
    Number sdv = statDataset.getStdDevValue(row, column);
    if (pass == 1 && sdv != null) {
      // standard deviation lines
      RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
      double valueDelta = sdv.doubleValue();
      double highVal, lowVal;
      if ((meanValue.doubleValue() + valueDelta) > rangeAxis.getRange().getUpperBound()) {
        highVal =
            rangeAxis.valueToJava2D(rangeAxis.getRange().getUpperBound(), dataArea, yAxisLocation);
      } else {
        highVal =
            rangeAxis.valueToJava2D(meanValue.doubleValue() + valueDelta, dataArea, yAxisLocation);
      }

      if ((meanValue.doubleValue() + valueDelta) < rangeAxis.getRange().getLowerBound()) {
        lowVal =
            rangeAxis.valueToJava2D(rangeAxis.getRange().getLowerBound(), dataArea, yAxisLocation);
      } else {
        lowVal =
            rangeAxis.valueToJava2D(meanValue.doubleValue() - valueDelta, dataArea, yAxisLocation);
      }

      if (this.errorIndicatorPaint != null) {
        g2.setPaint(this.errorIndicatorPaint);
      } else {
        g2.setPaint(getItemPaint(row, column));
      }
      if (this.errorIndicatorStroke != null) {
        g2.setStroke(this.errorIndicatorStroke);
      } else {
        g2.setStroke(getItemOutlineStroke(row, column));
      }
      Line2D line = new Line2D.Double();
      if (orientation == PlotOrientation.HORIZONTAL) {
        line.setLine(lowVal, x1, highVal, x1);
        g2.draw(line);
        line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d);
        g2.draw(line);
        line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d);
        g2.draw(line);
      } else { // PlotOrientation.VERTICAL
        line.setLine(x1, lowVal, x1, highVal);
        g2.draw(line);
        line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal);
        g2.draw(line);
        line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal);
        g2.draw(line);
      }
    }

    Shape hotspot = null;
    if (pass == 1 && getItemShapeVisible(row, column)) {
      Shape shape = getItemShape(row, column);
      if (orientation == PlotOrientation.HORIZONTAL) {
        shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
      } else if (orientation == PlotOrientation.VERTICAL) {
        shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
      }
      hotspot = shape;

      if (getItemShapeFilled(row, column)) {
        if (getUseFillPaint()) {
          g2.setPaint(getItemFillPaint(row, column));
        } else {
          g2.setPaint(getItemPaint(row, column));
        }
        g2.fill(shape);
      }
      if (getDrawOutlines()) {
        if (getUseOutlinePaint()) {
          g2.setPaint(getItemOutlinePaint(row, column));
        } else {
          g2.setPaint(getItemPaint(row, column));
        }
        g2.setStroke(getItemOutlineStroke(row, column));
        g2.draw(shape);
      }
      // draw the item label if there is one...
      if (isItemLabelVisible(row, column)) {
        if (orientation == PlotOrientation.HORIZONTAL) {
          drawItemLabel(
              g2, orientation, dataset, row, column, y1, x1, (meanValue.doubleValue() < 0.0));
        } else if (orientation == PlotOrientation.VERTICAL) {
          drawItemLabel(
              g2, orientation, dataset, row, column, x1, y1, (meanValue.doubleValue() < 0.0));
        }
      }
    }

    if (pass == 0 && getItemLineVisible(row, column)) {
      if (column != 0) {

        Number previousValue = statDataset.getValue(row, column - 1);
        if (previousValue != null) {

          // previous data point...
          double previous = previousValue.doubleValue();
          double x0;
          if (getUseSeriesOffset()) {
            x0 =
                domainAxis.getCategorySeriesMiddle(
                    column - 1,
                    dataset.getColumnCount(),
                    visibleRow,
                    visibleRowCount,
                    getItemMargin(),
                    dataArea,
                    plot.getDomainAxisEdge());
          } else {
            x0 =
                domainAxis.getCategoryMiddle(
                    column - 1, getColumnCount(), dataArea, plot.getDomainAxisEdge());
          }
          double y0 = rangeAxis.valueToJava2D(previous, dataArea, plot.getRangeAxisEdge());

          Line2D line = null;
          if (orientation == PlotOrientation.HORIZONTAL) {
            line = new Line2D.Double(y0, x0, y1, x1);
          } else if (orientation == PlotOrientation.VERTICAL) {
            line = new Line2D.Double(x0, y0, x1, y1);
          }
          g2.setPaint(getItemPaint(row, column));
          g2.setStroke(getItemStroke(row, column));
          g2.draw(line);
        }
      }
    }

    if (pass == 1) {
      // add an item entity, if this information is being collected
      EntityCollection entities = state.getEntityCollection();
      if (entities != null) {
        addEntity(entities, hotspot, dataset, row, column, x1, y1);
      }
    }
  }
  /**
   * Draws an item for a plot with a vertical orientation.
   *
   * @param g2 the graphics device.
   * @param state the renderer state.
   * @param dataArea the data area.
   * @param plot the plot.
   * @param domainAxis the domain axis.
   * @param rangeAxis the range axis.
   * @param dataset the data.
   * @param visibleRow the visible row index.
   * @param row the row index (zero-based).
   * @param column the column index (zero-based).
   */
  protected void drawVerticalItem(
      Graphics2D g2,
      CategoryItemRendererState state,
      Rectangle2D dataArea,
      CategoryPlot plot,
      CategoryAxis domainAxis,
      ValueAxis rangeAxis,
      StatisticalCategoryDataset dataset,
      int visibleRow,
      int row,
      int column) {

    // BAR X
    double rectX =
        calculateBarW0(
            plot, PlotOrientation.VERTICAL, dataArea, domainAxis, state, visibleRow, column);

    // BAR Y
    Number meanValue = dataset.getMeanValue(row, column);
    if (meanValue == null) {
      return;
    }

    double value = meanValue.doubleValue();
    double base = 0.0;
    double lclip = getLowerClip();
    double uclip = getUpperClip();

    if (uclip <= 0.0) { // cases 1, 2, 3 and 4
      if (value >= uclip) {
        return; // bar is not visible
      }
      base = uclip;
      if (value <= lclip) {
        value = lclip;
      }
    } else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
      if (value >= uclip) {
        value = uclip;
      } else {
        if (value <= lclip) {
          value = lclip;
        }
      }
    } else { // cases 9, 10, 11 and 12
      if (value <= lclip) {
        return; // bar is not visible
      }
      base = getLowerClip();
      if (value >= uclip) {
        value = uclip;
      }
    }

    RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
    double transY2 = rangeAxis.valueToJava2D(value, dataArea, yAxisLocation);
    double rectY = Math.min(transY2, transY1);

    double rectWidth = state.getBarWidth();
    double rectHeight = Math.abs(transY2 - transY1);

    Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, rectHeight);
    Paint itemPaint = getItemPaint(row, column);
    GradientPaintTransformer t = getGradientPaintTransformer();
    if (t != null && itemPaint instanceof GradientPaint) {
      itemPaint = t.transform((GradientPaint) itemPaint, bar);
    }
    g2.setPaint(itemPaint);
    g2.fill(bar);
    // draw the outline...
    if (isDrawBarOutline() && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
      Stroke stroke = getItemOutlineStroke(row, column);
      Paint paint = getItemOutlinePaint(row, column);
      if (stroke != null && paint != null) {
        g2.setStroke(stroke);
        g2.setPaint(paint);
        g2.draw(bar);
      }
    }

    // standard deviation lines
    Number n = dataset.getStdDevValue(row, column);
    if (n != null) {
      double valueDelta = n.doubleValue();
      double highVal =
          rangeAxis.valueToJava2D(meanValue.doubleValue() + valueDelta, dataArea, yAxisLocation);
      double lowVal =
          rangeAxis.valueToJava2D(meanValue.doubleValue() - valueDelta, dataArea, yAxisLocation);

      if (this.errorIndicatorPaint != null) {
        g2.setPaint(this.errorIndicatorPaint);
      } else {
        g2.setPaint(getItemOutlinePaint(row, column));
      }
      if (this.errorIndicatorStroke != null) {
        g2.setStroke(this.errorIndicatorStroke);
      } else {
        g2.setStroke(getItemOutlineStroke(row, column));
      }

      Line2D line;
      line =
          new Line2D.Double(
              rectX + rectWidth / 2.0d, lowVal,
              rectX + rectWidth / 2.0d, highVal);
      g2.draw(line);
      line =
          new Line2D.Double(
              rectX + rectWidth / 2.0d - 5.0d, highVal, rectX + rectWidth / 2.0d + 5.0d, highVal);
      g2.draw(line);
      line =
          new Line2D.Double(
              rectX + rectWidth / 2.0d - 5.0d, lowVal, rectX + rectWidth / 2.0d + 5.0d, lowVal);
      g2.draw(line);
    }

    CategoryItemLabelGenerator generator = getItemLabelGenerator(row, column);
    if (generator != null && isItemLabelVisible(row, column)) {
      drawItemLabel(g2, dataset, row, column, plot, generator, bar, (value < 0.0));
    }

    // add an item entity, if this information is being collected
    EntityCollection entities = state.getEntityCollection();
    if (entities != null) {
      addItemEntity(entities, dataset, row, column, bar);
    }
  }
  /**
   * Draws an item for a plot with a vertical orientation.
   *
   * @param g2 the graphics device.
   * @param state the renderer state.
   * @param dataArea the data area.
   * @param plot the plot.
   * @param domainAxis the domain axis.
   * @param rangeAxis the range axis.
   * @param dataset the data.
   * @param row the row index (zero-based).
   * @param column the column index (zero-based).
   */
  protected void drawVerticalItem(
      Graphics2D g2,
      CategoryItemRendererState state,
      Rectangle2D dataArea,
      CategoryPlot plot,
      CategoryAxis domainAxis,
      ValueAxis rangeAxis,
      StatisticalCategoryDataset dataset,
      int row,
      int column) {

    RectangleEdge xAxisLocation = plot.getDomainAxisEdge();

    // BAR X
    double rectX = domainAxis.getCategoryStart(column, getColumnCount(), dataArea, xAxisLocation);

    int seriesCount = getRowCount();
    int categoryCount = getColumnCount();
    if (seriesCount > 1) {
      double seriesGap =
          dataArea.getWidth() * getItemMargin() / (categoryCount * (seriesCount - 1));
      rectX = rectX + row * (state.getBarWidth() + seriesGap);
    } else {
      rectX = rectX + row * state.getBarWidth();
    }

    // BAR Y
    Number meanValue = dataset.getMeanValue(row, column);

    double value = meanValue.doubleValue();
    double base = 0.0;
    double lclip = getLowerClip();
    double uclip = getUpperClip();

    if (uclip <= 0.0) { // cases 1, 2, 3 and 4
      if (value >= uclip) {
        return; // bar is not visible
      }
      base = uclip;
      if (value <= lclip) {
        value = lclip;
      }
    } else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
      if (value >= uclip) {
        value = uclip;
      } else {
        if (value <= lclip) {
          value = lclip;
        }
      }
    } else { // cases 9, 10, 11 and 12
      if (value <= lclip) {
        return; // bar is not visible
      }
      base = getLowerClip();
      if (value >= uclip) {
        value = uclip;
      }
    }

    RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
    double transY2 = rangeAxis.valueToJava2D(value, dataArea, yAxisLocation);
    double rectY = Math.min(transY2, transY1);

    double rectWidth = state.getBarWidth();
    double rectHeight = Math.abs(transY2 - transY1);

    Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, rectHeight);
    Paint seriesPaint = getItemPaint(row, column);
    g2.setPaint(seriesPaint);
    g2.fill(bar);
    if (state.getBarWidth() > 3) {
      g2.setStroke(getItemStroke(row, column));
      g2.setPaint(getItemOutlinePaint(row, column));
      g2.draw(bar);
    }

    // standard deviation lines
    double valueDelta = dataset.getStdDevValue(row, column).doubleValue();
    double highVal =
        rangeAxis.valueToJava2D(meanValue.doubleValue() + valueDelta, dataArea, yAxisLocation);
    double lowVal =
        rangeAxis.valueToJava2D(meanValue.doubleValue() - valueDelta, dataArea, yAxisLocation);

    if (this.errorIndicatorPaint != null) {
      g2.setPaint(this.errorIndicatorPaint);
    } else {
      g2.setPaint(getItemOutlinePaint(row, column));
    }
    Line2D line = null;
    line =
        new Line2D.Double(
            rectX + rectWidth / 2.0d, lowVal,
            rectX + rectWidth / 2.0d, highVal);
    g2.draw(line);
    line =
        new Line2D.Double(
            rectX + rectWidth / 2.0d - 5.0d, highVal, rectX + rectWidth / 2.0d + 5.0d, highVal);
    g2.draw(line);
    line =
        new Line2D.Double(
            rectX + rectWidth / 2.0d - 5.0d, lowVal, rectX + rectWidth / 2.0d + 5.0d, lowVal);
    g2.draw(line);

    CategoryItemLabelGenerator generator = getItemLabelGenerator(row, column);
    if (generator != null && isItemLabelVisible(row, column)) {
      drawItemLabel(g2, dataset, row, column, plot, generator, bar, (value < 0.0));
    }

    // add an item entity, if this information is being collected
    EntityCollection entities = state.getEntityCollection();
    if (entities != null) {
      addItemEntity(entities, dataset, row, column, bar);
    }
  }