/**
   * Initialises the renderer. This method gets called once at the start of the process of drawing a
   * chart.
   *
   * @param g2 the graphics device.
   * @param dataArea the area in which the data is to be plotted.
   * @param plot the plot.
   * @param rendererIndex the renderer index.
   * @param info collects chart rendering information for return to caller.
   * @return The renderer state.
   */
  public CategoryItemRendererState initialise(
      Graphics2D g2,
      Rectangle2D dataArea,
      CategoryPlot plot,
      int rendererIndex,
      PlotRenderingInfo info) {

    CategoryItemRendererState state = super.initialise(g2, dataArea, plot, rendererIndex, info);

    // calculate the box width
    CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    CategoryDataset dataset = plot.getDataset(rendererIndex);
    if (dataset != null) {
      int columns = dataset.getColumnCount();
      int rows = dataset.getRowCount();
      double space = 0.0;
      PlotOrientation orientation = plot.getOrientation();
      if (orientation == PlotOrientation.HORIZONTAL) {
        space = dataArea.getHeight();
      } else if (orientation == PlotOrientation.VERTICAL) {
        space = dataArea.getWidth();
      }
      double maxWidth = space * getMaximumBarWidth();
      double categoryMargin = 0.0;
      double currentItemMargin = 0.0;
      if (columns > 1) {
        categoryMargin = domainAxis.getCategoryMargin();
      }
      if (rows > 1) {
        currentItemMargin = getItemMargin();
      }
      double used =
          space
              * (1
                  - domainAxis.getLowerMargin()
                  - domainAxis.getUpperMargin()
                  - categoryMargin
                  - currentItemMargin);
      if ((rows * columns) > 0) {
        state.setBarWidth(
            Math.min(used / (dataset.getColumnCount() * dataset.getRowCount()), maxWidth));
      } else {
        state.setBarWidth(Math.min(used, maxWidth));
      }
    }

    return state;
  }
 /**
  * Returns the range of values the renderer requires to display all the items from the specified
  * dataset.
  *
  * @param dataset the dataset (<code>null</code> not permitted).
  * @return The range (or <code>null</code> if the dataset is empty).
  */
 public Range findRangeBounds(CategoryDataset dataset) {
   if (dataset == null) {
     return null;
   }
   boolean allItemsNull = true; // we'll set this to false if there is at
   // least one non-null data item...
   double minimum = 0.0;
   double maximum = 0.0;
   int columnCount = dataset.getColumnCount();
   for (int row = 0; row < dataset.getRowCount(); row++) {
     double runningTotal = 0.0;
     for (int column = 0; column <= columnCount - 1; column++) {
       Number n = dataset.getValue(row, column);
       if (n != null) {
         allItemsNull = false;
         double value = n.doubleValue();
         if (column == columnCount - 1) {
           // treat the last column value as an absolute
           runningTotal = value;
         } else {
           runningTotal = runningTotal + value;
         }
         minimum = Math.min(minimum, runningTotal);
         maximum = Math.max(maximum, runningTotal);
       }
     }
   }
   if (!allItemsNull) {
     return new Range(minimum, maximum);
   } else {
     return null;
   }
 }
  public void createChart() {

    CategoryDataset categorydataset = (CategoryDataset) this.datasetStrategy.getDataset();
    report =
        ChartFactory.createStackedBarChart(
            this.datasetStrategy.getTitle(),
            this.datasetStrategy.getYAxisLabel(),
            this.datasetStrategy.getXAxisLabel(),
            categorydataset,
            PlotOrientation.HORIZONTAL,
            true,
            true,
            false);
    // report.setBackgroundPaint( Color.lightGray );
    report.setAntiAlias(false);
    report.setPadding(new RectangleInsets(5.0d, 5.0d, 5.0d, 5.0d));
    CategoryPlot categoryplot = (CategoryPlot) report.getPlot();
    categoryplot.setBackgroundPaint(Color.white);
    categoryplot.setRangeGridlinePaint(Color.lightGray);
    NumberAxis numberaxis = (NumberAxis) categoryplot.getRangeAxis();
    if (datasetStrategy instanceof CloverBarChartStrategy
        || datasetStrategy instanceof MultiCloverBarChartStrategy) {
      numberaxis.setRange(0.0D, StackedBarChartRenderer.NUMBER_AXIS_RANGE);
      numberaxis.setNumberFormatOverride(NumberFormat.getPercentInstance());
    } else {
      numberaxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
    }
    numberaxis.setLowerMargin(0.0D);
    StackedBarRenderer stackedbarrenderer = (StackedBarRenderer) categoryplot.getRenderer();
    stackedbarrenderer.setDrawBarOutline(false);
    stackedbarrenderer.setItemLabelsVisible(true);
    stackedbarrenderer.setItemLabelGenerator(new StandardCategoryItemLabelGenerator());
    stackedbarrenderer.setItemLabelFont(
        StackedBarRenderer.DEFAULT_VALUE_LABEL_FONT.deriveFont(Font.BOLD));
    int height =
        (categorydataset.getColumnCount() * ChartUtils.STANDARD_BARCHART_ENTRY_HEIGHT * 2)
            + ChartUtils.STANDARD_BARCHART_ADDITIONAL_HEIGHT
            + 10;
    if (height > ChartUtils.MINIMUM_HEIGHT) {
      super.setHeight(height);
    } else {
      super.setHeight(ChartUtils.MINIMUM_HEIGHT);
    }
    Paint[] paints = this.datasetStrategy.getPaintColor();

    for (int i = 0; i < categorydataset.getRowCount() && i < paints.length; i++) {
      stackedbarrenderer.setSeriesPaint(i, paints[i]);
    }
  }
  /**
   * Initialises the renderer and returns a state object that will be used for the remainder of the
   * drawing process for a single chart. The state object allows for the fact that the renderer may
   * be used simultaneously by multiple threads (each thread will work with a separate state
   * object).
   *
   * <p>Stores a reference to the {@link PlotRenderingInfo} object (which might be <code>null</code>
   * ), and then sets the useCategoriesPaint flag according to the special case conditions a) there
   * is only one series and b) the categoriesPaint array is not null.
   *
   * @param g2 the graphics device.
   * @param dataArea the data area.
   * @param plot the plot.
   * @param rendererIndex the renderer index.
   * @param info an object for returning information about the structure of the plot (<code>null
   *     </code> permitted).
   * @return The renderer state.
   */
  public CategoryItemRendererState initialise(
      Graphics2D g2,
      Rectangle2D dataArea,
      CategoryPlot plot,
      int rendererIndex,
      PlotRenderingInfo info) {

    setPlot(plot);
    CategoryDataset data = plot.getDataset(rendererIndex);
    if (data != null) {
      this.rowCount = data.getRowCount();
      this.columnCount = data.getColumnCount();
    } else {
      this.rowCount = 0;
      this.columnCount = 0;
    }
    return new CategoryItemRendererState(info);
  }
  /**
   * Returns the middle coordinate (in Java2D space) for a series within a category.
   *
   * @param category the category (<code>null</code> not permitted).
   * @param seriesKey the series key (<code>null</code> not permitted).
   * @param dataset the dataset (<code>null</code> not permitted).
   * @param itemMargin the item margin (0.0 <= itemMargin < 1.0);
   * @param area the area (<code>null</code> not permitted).
   * @param edge the edge (<code>null</code> not permitted).
   * @return The coordinate in Java2D space.
   * @since 1.0.7
   */
  public double getCategorySeriesMiddle(
      Comparable category,
      Comparable seriesKey,
      CategoryDataset dataset,
      double itemMargin,
      Rectangle2D area,
      RectangleEdge edge) {

    int categoryIndex = dataset.getColumnIndex(category);
    int categoryCount = dataset.getColumnCount();
    int seriesIndex = dataset.getRowIndex(seriesKey);
    int seriesCount = dataset.getRowCount();
    double start = getCategoryStart(categoryIndex, categoryCount, area, edge);
    double end = getCategoryEnd(categoryIndex, categoryCount, area, edge);
    double width = end - start;
    if (seriesCount == 1) {
      return start + width / 2.0;
    }
    double gap = (width * itemMargin) / (seriesCount - 1);
    double ww = (width * (1 - itemMargin)) / seriesCount;
    return start + (seriesIndex * (ww + gap)) + ww / 2.0;
  }
  /**
   * 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 the bar for a single (series, category) data item.
   *
   * @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 dataset.
   * @param row the row index (zero-based).
   * @param column the column index (zero-based).
   * @param pass the pass index.
   */
  public void drawItem(
      Graphics2D g2,
      CategoryItemRendererState state,
      Rectangle2D dataArea,
      CategoryPlot plot,
      CategoryAxis domainAxis,
      ValueAxis rangeAxis,
      CategoryDataset dataset,
      int row,
      int column,
      int pass) {

    double previous = state.getSeriesRunningTotal();
    if (column == dataset.getColumnCount() - 1) {
      previous = 0.0;
    }
    double current = 0.0;
    Number n = dataset.getValue(row, column);
    if (n != null) {
      current = previous + n.doubleValue();
    }
    state.setSeriesRunningTotal(current);

    int categoryCount = getColumnCount();
    PlotOrientation orientation = plot.getOrientation();

    double rectX = 0.0;
    double rectY = 0.0;

    RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();

    // Y0
    double j2dy0 = rangeAxis.valueToJava2D(previous, dataArea, rangeAxisLocation);

    // Y1
    double j2dy1 = rangeAxis.valueToJava2D(current, dataArea, rangeAxisLocation);

    double valDiff = current - previous;
    if (j2dy1 < j2dy0) {
      double temp = j2dy1;
      j2dy1 = j2dy0;
      j2dy0 = temp;
    }

    // BAR WIDTH
    double rectWidth = state.getBarWidth();

    // BAR HEIGHT
    double rectHeight = Math.max(getMinimumBarLength(), Math.abs(j2dy1 - j2dy0));

    Comparable seriesKey = dataset.getRowKey(row);
    Comparable categoryKey = dataset.getColumnKey(column);
    if (orientation == PlotOrientation.HORIZONTAL) {
      rectY =
          domainAxis.getCategorySeriesMiddle(
              categoryKey, seriesKey, dataset, getItemMargin(), dataArea, RectangleEdge.LEFT);

      rectX = j2dy0;
      rectHeight = state.getBarWidth();
      rectY = rectY - rectHeight / 2.0;
      rectWidth = Math.max(getMinimumBarLength(), Math.abs(j2dy1 - j2dy0));

    } else if (orientation == PlotOrientation.VERTICAL) {
      rectX =
          domainAxis.getCategorySeriesMiddle(
              categoryKey, seriesKey, dataset, getItemMargin(), dataArea, RectangleEdge.TOP);
      rectX = rectX - rectWidth / 2.0;
      rectY = j2dy0;
    }
    Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, rectHeight);
    Paint seriesPaint = getFirstBarPaint();
    if (column == 0) {
      seriesPaint = getFirstBarPaint();
    } else if (column == categoryCount - 1) {
      seriesPaint = getLastBarPaint();
    } else {
      if (valDiff < 0.0) {
        seriesPaint = getNegativeBarPaint();
      } else if (valDiff > 0.0) {
        seriesPaint = getPositiveBarPaint();
      } else {
        seriesPaint = getLastBarPaint();
      }
    }
    if (getGradientPaintTransformer() != null && seriesPaint instanceof GradientPaint) {
      GradientPaint gp = (GradientPaint) seriesPaint;
      seriesPaint = getGradientPaintTransformer().transform(gp, bar);
    }
    g2.setPaint(seriesPaint);
    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);
      }
    }

    CategoryItemLabelGenerator generator = getItemLabelGenerator(row, column);
    if (generator != null && isItemLabelVisible(row, column)) {
      drawItemLabel(g2, dataset, row, column, plot, generator, bar, (valDiff < 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);
    }
  }
  /**
   * Draw a single data item.
   *
   * @param g2 the graphics device.
   * @param state the renderer state.
   * @param dataArea the data plot area.
   * @param plot the plot.
   * @param domainAxis the domain axis.
   * @param rangeAxis the range axis.
   * @param dataset the dataset.
   * @param row the row index (zero-based).
   * @param column the column index (zero-based).
   * @param pass the pass index.
   */
  @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 or null
    if (!getItemVisible(row, column)) {
      return;
    }
    Number value = dataset.getValue(row, column);
    if (value == null) {
      return;
    }
    PlotOrientation orientation = plot.getOrientation();
    RectangleEdge axisEdge = plot.getDomainAxisEdge();
    int count = dataset.getColumnCount();
    float x0 = (float) domainAxis.getCategoryStart(column, count, dataArea, axisEdge);
    float x1 = (float) domainAxis.getCategoryMiddle(column, count, dataArea, axisEdge);
    float x2 = (float) domainAxis.getCategoryEnd(column, count, dataArea, axisEdge);

    x0 = Math.round(x0);
    x1 = Math.round(x1);
    x2 = Math.round(x2);

    if (this.endType == AreaRendererEndType.TRUNCATE) {
      if (column == 0) {
        x0 = x1;
      } else if (column == getColumnCount() - 1) {
        x2 = x1;
      }
    }

    double yy1 = value.doubleValue();

    double yy0 = 0.0;
    if (this.endType == AreaRendererEndType.LEVEL) {
      yy0 = yy1;
    }
    if (column > 0) {
      Number n0 = dataset.getValue(row, column - 1);
      if (n0 != null) {
        yy0 = (n0.doubleValue() + yy1) / 2.0;
      }
    }

    double yy2 = 0.0;
    if (column < dataset.getColumnCount() - 1) {
      Number n2 = dataset.getValue(row, column + 1);
      if (n2 != null) {
        yy2 = (n2.doubleValue() + yy1) / 2.0;
      }
    } else if (this.endType == AreaRendererEndType.LEVEL) {
      yy2 = yy1;
    }

    RectangleEdge edge = plot.getRangeAxisEdge();
    float y0 = (float) rangeAxis.valueToJava2D(yy0, dataArea, edge);
    float y1 = (float) rangeAxis.valueToJava2D(yy1, dataArea, edge);
    float y2 = (float) rangeAxis.valueToJava2D(yy2, dataArea, edge);
    float yz = (float) rangeAxis.valueToJava2D(0.0, dataArea, edge);
    double labelXX = x1;
    double labelYY = y1;
    g2.setPaint(getItemPaint(row, column));
    g2.setStroke(getItemStroke(row, column));

    GeneralPath area = new GeneralPath();

    if (orientation == PlotOrientation.VERTICAL) {
      area.moveTo(x0, yz);
      area.lineTo(x0, y0);
      area.lineTo(x1, y1);
      area.lineTo(x2, y2);
      area.lineTo(x2, yz);
    } else if (orientation == PlotOrientation.HORIZONTAL) {
      area.moveTo(yz, x0);
      area.lineTo(y0, x0);
      area.lineTo(y1, x1);
      area.lineTo(y2, x2);
      area.lineTo(yz, x2);
      double temp = labelXX;
      labelXX = labelYY;
      labelYY = temp;
    }
    area.closePath();

    g2.setPaint(getItemPaint(row, column));
    g2.fill(area);

    // draw the item labels if there are any...
    if (isItemLabelVisible(row, column)) {
      drawItemLabel(
          g2, orientation, dataset, row, column, labelXX, labelYY, (value.doubleValue() < 0.0));
    }

    // submit the current data point as a crosshair candidate
    int datasetIndex = plot.indexOf(dataset);
    updateCrosshairValues(
        state.getCrosshairState(),
        dataset.getRowKey(row),
        dataset.getColumnKey(column),
        yy1,
        datasetIndex,
        x1,
        y1,
        orientation);

    // add an item entity, if this information is being collected
    EntityCollection entities = state.getEntityCollection();
    if (entities != null) {
      addItemEntity(entities, dataset, row, column, area);
    }
  }
Beispiel #9
0
  /**
   * Draws the category labels and returns the updated axis state.
   *
   * @param g2 the graphics device (<code>null</code> not permitted).
   * @param plotArea the plot area (<code>null</code> not permitted).
   * @param dataArea the area inside the axes (<code>null</code> not permitted).
   * @param edge the axis location (<code>null</code> not permitted).
   * @param state the axis state (<code>null</code> not permitted).
   * @param plotState collects information about the plot (<code>null</code> permitted).
   * @return The updated axis state (never <code>null</code>).
   */
  protected AxisState drawSubCategoryLabels(
      Graphics2D g2,
      Rectangle2D plotArea,
      Rectangle2D dataArea,
      RectangleEdge edge,
      AxisState state,
      PlotRenderingInfo plotState) {

    if (state == null) {
      throw new IllegalArgumentException("Null 'state' argument.");
    }

    g2.setFont(this.subLabelFont);
    g2.setPaint(this.subLabelPaint);
    CategoryPlot plot = (CategoryPlot) getPlot();
    CategoryDataset dataset = plot.getDataset();
    int categoryCount = dataset.getColumnCount();

    double maxdim = getMaxDim(g2, edge);
    for (int categoryIndex = 0; categoryIndex < categoryCount; categoryIndex++) {

      double x0 = 0.0;
      double x1 = 0.0;
      double y0 = 0.0;
      double y1 = 0.0;
      if (edge == RectangleEdge.TOP) {
        x0 = getCategoryStart(categoryIndex, categoryCount, dataArea, edge);
        x1 = getCategoryEnd(categoryIndex, categoryCount, dataArea, edge);
        y1 = state.getCursor();
        y0 = y1 - maxdim;
      } else if (edge == RectangleEdge.BOTTOM) {
        x0 = getCategoryStart(categoryIndex, categoryCount, dataArea, edge);
        x1 = getCategoryEnd(categoryIndex, categoryCount, dataArea, edge);
        y0 = state.getCursor();
        y1 = y0 + maxdim;
      } else if (edge == RectangleEdge.LEFT) {
        y0 = getCategoryStart(categoryIndex, categoryCount, dataArea, edge);
        y1 = getCategoryEnd(categoryIndex, categoryCount, dataArea, edge);
        x1 = state.getCursor();
        x0 = x1 - maxdim;
      } else if (edge == RectangleEdge.RIGHT) {
        y0 = getCategoryStart(categoryIndex, categoryCount, dataArea, edge);
        y1 = getCategoryEnd(categoryIndex, categoryCount, dataArea, edge);
        x0 = state.getCursor();
        x1 = x0 + maxdim;
      }
      Rectangle2D area = new Rectangle2D.Double(x0, y0, (x1 - x0), (y1 - y0));
      int subCategoryCount = this.subCategories.size();
      float width = (float) ((x1 - x0) / subCategoryCount);
      float height = (float) ((y1 - y0) / subCategoryCount);
      float xx = 0.0f;
      float yy = 0.0f;
      for (int i = 0; i < subCategoryCount; i++) {
        if (RectangleEdge.isTopOrBottom(edge)) {
          xx = (float) (x0 + (i + 0.5) * width);
          yy = (float) area.getCenterY();
        } else {
          xx = (float) area.getCenterX();
          yy = (float) (y0 + (i + 0.5) * height);
        }
        String label = this.subCategories.get(i).toString();
        TextUtilities.drawRotatedString(
            label, g2, xx, yy, TextAnchor.CENTER, 0.0, TextAnchor.CENTER);
      }
    }

    if (edge.equals(RectangleEdge.TOP)) {
      double h = maxdim;
      state.cursorUp(h);
    } else if (edge.equals(RectangleEdge.BOTTOM)) {
      double h = maxdim;
      state.cursorDown(h);
    } else if (edge == RectangleEdge.LEFT) {
      double w = maxdim;
      state.cursorLeft(w);
    } else if (edge == RectangleEdge.RIGHT) {
      double w = maxdim;
      state.cursorRight(w);
    }
    return state;
  }
  /**
   * Draws a marker for the domain axis.
   *
   * @param g2 the graphics device (not <code>null</code>).
   * @param plot the plot (not <code>null</code>).
   * @param axis the range axis (not <code>null</code>).
   * @param marker the marker to be drawn (not <code>null</code>).
   * @param dataArea the area inside the axes (not <code>null</code>).
   */
  public void drawDomainMarker(
      Graphics2D g2,
      CategoryPlot plot,
      CategoryAxis axis,
      CategoryMarker marker,
      Rectangle2D dataArea) {

    Comparable category = marker.getKey();
    CategoryDataset dataset = plot.getDataset(plot.getIndexOf(this));
    int columnIndex = dataset.getColumnIndex(category);
    if (columnIndex < 0) {
      return;
    }
    PlotOrientation orientation = plot.getOrientation();
    Rectangle2D bounds = null;
    if (marker.getDrawAsLine()) {
      double v =
          axis.getCategoryMiddle(
              columnIndex, dataset.getColumnCount(),
              dataArea, plot.getDomainAxisEdge());
      Line2D line = null;
      if (orientation == PlotOrientation.HORIZONTAL) {
        line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
      } else if (orientation == PlotOrientation.VERTICAL) {
        line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
      }

      g2.setPaint(marker.getPaint());
      g2.setStroke(marker.getStroke());
      g2.draw(line);
      bounds = line.getBounds2D();
    } else {
      double v0 =
          axis.getCategoryStart(
              columnIndex, dataset.getColumnCount(),
              dataArea, plot.getDomainAxisEdge());
      double v1 =
          axis.getCategoryEnd(
              columnIndex, dataset.getColumnCount(),
              dataArea, plot.getDomainAxisEdge());
      Rectangle2D area = null;
      if (orientation == PlotOrientation.HORIZONTAL) {
        area = new Rectangle2D.Double(dataArea.getMinX(), v0, dataArea.getWidth(), (v1 - v0));
      } else if (orientation == PlotOrientation.VERTICAL) {
        area = new Rectangle2D.Double(v0, dataArea.getMinY(), (v1 - v0), dataArea.getHeight());
      }
      g2.setPaint(marker.getPaint());
      g2.fill(area);
      bounds = area;
    }

    String label = marker.getLabel();
    RectangleAnchor anchor = marker.getLabelAnchor();
    if (label != null) {
      Font labelFont = marker.getLabelFont();
      g2.setFont(labelFont);
      g2.setPaint(marker.getLabelPaint());
      Point2D coordinates =
          calculateDomainMarkerTextAnchorPoint(
              g2,
              orientation,
              dataArea,
              bounds,
              marker.getLabelOffset(),
              marker.getLabelOffsetType(),
              anchor);
      TextUtilities.drawAlignedString(
          label,
          g2,
          (float) coordinates.getX(),
          (float) coordinates.getY(),
          marker.getLabelTextAnchor());
    }
  }