content: /* ===========================================================
   * JFreeChart : a free chart library for the Java(tm) platform
   * ===========================================================
   *
   * (C) Copyright 2000-2012, by Object Refinery Limited and Contributors.
   *
   * Project Info:  http://www.jfree.org/jfreechart/index.html
   *
   * This library is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
   * USA.
   *
   * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   * Other names may be trademarks of their respective owners.]
   *
   * -------------------------
   * StackedBarRenderer3D.java
   * -------------------------
   * (C) Copyright 2000-2012, by Serge V. Grachov and Contributors.
   *
   * Original Author:  Serge V. Grachov;
   * Contributor(s):   David Gilbert (for Object Refinery Limited);
   *                   Richard Atkinson;
   *                   Christian W. Zuckschwerdt;
   *                   Max Herfort (patch 1459313);
   *
   * Changes
   * -------
   * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG);
   * 15-Nov-2001 : Modified to allow for null data values (DG);
   * 13-Dec-2001 : Added tooltips (DG);
   * 15-Feb-2002 : Added isStacked() method (DG);
   * 24-May-2002 : Incorporated tooltips into chart entities (DG);
   * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG);
   * 25-Jun-2002 : Removed redundant imports (DG);
   * 26-Jun-2002 : Small change to entity (DG);
   * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
   *               for HTML image maps (RA);
   * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
   * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
   *               CategoryToolTipGenerator interface (DG);
   * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
   * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
   * 17-Jan-2003 : Moved plot classes to a separate package (DG);
   * 25-Mar-2003 : Implemented Serializable (DG);
   * 01-May-2003 : Added default constructor (bug 726235) and fixed bug
   *               726260) (DG);
   * 13-May-2003 : Renamed StackedVerticalBarRenderer3D
   *               --> StackedBarRenderer3D (DG);
   * 30-Jul-2003 : Modified entity constructor (CZ);
   * 07-Oct-2003 : Added renderer state (DG);
   * 21-Nov-2003 : Added a new constructor (DG);
   * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG);
   * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG);
   * 05-Nov-2004 : Modified drawItem() signature (DG);
   * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
   * 18-Mar-2005 : Override for getPassCount() method (DG);
   * 20-Apr-2005 : Renamed CategoryLabelGenerator
   *               --> CategoryItemLabelGenerator (DG);
   * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
   * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
   * ------------- JFREECHART 1.0.x ---------------------------------------------
   * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted
   *               by Max Herfort (DG);
   * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG);
   * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList()
   *               method (DG);
   * 18-Jan-2007 : Updated block drawing code to take account of inverted axes,
   *               see bug report 1599652 (DG);
   * 08-May-2007 : Fixed bugs 1713401 (drawBarOutlines flag) and  1713474
   *               (shading) (DG);
   * 15-Aug-2008 : Fixed bug 2031407 - no negative zero for stack encoding (DG);
   * 03-Feb-2009 : Fixed regression in findRangeBounds() method for null
   *               dataset (DG);
   * 04-Feb-2009 : Handle seriesVisible flag (DG);
   * 07-Jul-2009 : Added flag for handling zero values (DG);
   * 15-Jun-2012 : Removed JCommon dependencies (DG);
   *
   */
  
  package org.jfree.chart.renderer.category;
  
  import java.awt.Color;
  import java.awt.Graphics2D;
  import java.awt.Paint;
  import java.awt.Shape;
  import java.awt.geom.GeneralPath;
  import java.awt.geom.Point2D;
  import java.awt.geom.Rectangle2D;
  import java.io.Serializable;
  import java.util.ArrayList;
  import java.util.List;
  
  import org.jfree.chart.HashUtilities;
  import org.jfree.chart.axis.CategoryAxis;
  import org.jfree.chart.axis.ValueAxis;
  import org.jfree.chart.util.PublicCloneable;
  import org.jfree.chart.entity.EntityCollection;
  import org.jfree.chart.event.RendererChangeEvent;
  import org.jfree.chart.labels.CategoryItemLabelGenerator;
  import org.jfree.chart.plot.CategoryPlot;
  import org.jfree.chart.plot.PlotOrientation;
  import org.jfree.data.DataUtilities;
  import org.jfree.data.Range;
  import org.jfree.data.category.CategoryDataset;
  import org.jfree.data.general.DatasetUtilities;
  
  /**
   * Renders stacked bars with 3D-effect, for use with the {@link CategoryPlot}
   * class.  The example shown here is generated by the
   * <code>StackedBarChart3DDemo1.java</code> program included in the
   * JFreeChart Demo Collection:
   * <br><br>
   * <img src="../../../../../images/StackedBarRenderer3DSample.png"
   * alt="StackedBarRenderer3DSample.png" />
   */
  public class StackedBarRenderer3D extends BarRenderer3D
          implements Cloneable, PublicCloneable, Serializable {
  
      /** For serialization. */
      private static final long serialVersionUID = -5832945916493247123L;
  
      /** A flag that controls whether the bars display values or percentages. */
      private boolean renderAsPercentages;
  
      /**
       * A flag that controls whether or not zero values are drawn by the
       * renderer.
       *
       * @since 1.0.14
       */
      private boolean ignoreZeroValues;
  
      /**
       * Creates a new renderer with no tool tip generator and no URL generator.
       * <P>
       * The defaults (no tool tip or URL generators) have been chosen to
       * minimise the processing required to generate a default chart.  If you
       * require tool tips or URLs, then you can easily add the required
       * generators.
       */
      public StackedBarRenderer3D() {
          this(false);
      }
  
      /**
       * Constructs a new renderer with the specified '3D effect'.
       *
       * @param xOffset  the x-offset for the 3D effect.
       * @param yOffset  the y-offset for the 3D effect.
       */
      public StackedBarRenderer3D(double xOffset, double yOffset) {
          super(xOffset, yOffset);
      }
  
      /**
       * Creates a new renderer.
       *
       * @param renderAsPercentages  a flag that controls whether the data values
       *                             are rendered as percentages.
       *
       * @since 1.0.2
       */
      public StackedBarRenderer3D(boolean renderAsPercentages) {
          super();
          this.renderAsPercentages = renderAsPercentages;
      }
  
      /**
       * Constructs a new renderer with the specified '3D effect'.
       *
       * @param xOffset  the x-offset for the 3D effect.
       * @param yOffset  the y-offset for the 3D effect.
       * @param renderAsPercentages  a flag that controls whether the data values
       *                             are rendered as percentages.
       *
       * @since 1.0.2
       */
      public StackedBarRenderer3D(double xOffset, double yOffset,
              boolean renderAsPercentages) {
          super(xOffset, yOffset);
          this.renderAsPercentages = renderAsPercentages;
      }
  
      /**
       * Returns <code>true</code> if the renderer displays each item value as
       * a percentage (so that the stacked bars add to 100%), and
       * <code>false</code> otherwise.
       *
       * @return A boolean.
       *
       * @since 1.0.2
       */
      public boolean getRenderAsPercentages() {
          return this.renderAsPercentages;
      }
  
      /**
       * Sets the flag that controls whether the renderer displays each item
       * value as a percentage (so that the stacked bars add to 100%), and sends
       * a {@link RendererChangeEvent} to all registered listeners.
       *
       * @param asPercentages  the flag.
       *
       * @since 1.0.2
       */
      public void setRenderAsPercentages(boolean asPercentages) {
          this.renderAsPercentages = asPercentages;
          fireChangeEvent();
      }
  
      /**
       * Returns the flag that controls whether or not zero values are drawn
       * by the renderer.
       *
       * @return A boolean.
       *
       * @since 1.0.14
       */
      public boolean getIgnoreZeroValues() {
          return this.ignoreZeroValues;
      }
  
      /**
       * Sets a flag that controls whether or not zero values are drawn by the
       * renderer, and sends a {@link RendererChangeEvent} to all registered
       * listeners.
       *
       * @param ignore  the new flag value.
       *
       * @since 1.0.14
       */
      public void setIgnoreZeroValues(boolean ignore) {
          this.ignoreZeroValues = ignore;
          notifyListeners(new RendererChangeEvent(this));
      }
  
      /**
       * 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).
       */
      @Override
      public Range findRangeBounds(CategoryDataset dataset) {
          if (dataset == null) {
              return null;
          }
          if (this.renderAsPercentages) {
              return new Range(0.0, 1.0);
          }
          else {
              return DatasetUtilities.findStackedRangeBounds(dataset);
          }
      }
  
      /**
       * Calculates the bar width and stores it in the renderer state.
       *
       * @param plot  the plot.
       * @param dataArea  the data area.
       * @param rendererIndex  the renderer index.
       * @param state  the renderer state.
       */
      @Override
      protected void calculateBarWidth(CategoryPlot plot,
                                       Rectangle2D dataArea,
                                       int rendererIndex,
                                       CategoryItemRendererState state) {
  
          // calculate the bar width
          CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
          CategoryDataset data = plot.getDataset(rendererIndex);
          if (data != null) {
              PlotOrientation orientation = plot.getOrientation();
              double space = 0.0;
              if (orientation == PlotOrientation.HORIZONTAL) {
                  space = dataArea.getHeight();
              }
              else if (orientation == PlotOrientation.VERTICAL) {
                  space = dataArea.getWidth();
              }
              double maxWidth = space * getMaximumBarWidth();
              int columns = data.getColumnCount();
              double categoryMargin = 0.0;
              if (columns > 1) {
                  categoryMargin = domainAxis.getCategoryMargin();
              }
  
              double used = space * (1 - domainAxis.getLowerMargin()
                                       - domainAxis.getUpperMargin()
                                       - categoryMargin);
              if (columns > 0) {
                  state.setBarWidth(Math.min(used / columns, maxWidth));
              }
              else {
                  state.setBarWidth(Math.min(used, maxWidth));
              }
          }
  
      }
  
      /**
       * Returns a list containing the stacked values for the specified series
       * in the given dataset, plus the supplied base value.
       *
       * @param dataset  the dataset (<code>null</code> not permitted).
       * @param category  the category key (<code>null</code> not permitted).
       * @param includedRows  the included rows.
       * @param base  the base value.
       * @param asPercentages  a flag that controls whether the values in the
       *     list are converted to percentages of the total.
       *
       * @return The value list.
       *
       * @since 1.0.13
       */
      protected List<Object[]> createStackedValueList(CategoryDataset dataset,
              Comparable category, int[] includedRows, double base,
              boolean asPercentages) {
  
          List<Object[]> result = new ArrayList<Object[]>();
          double posBase = base;
          double negBase = base;
          double total = 0.0;
          if (asPercentages) {
              total = DataUtilities.calculateColumnTotal(dataset,
                      dataset.getColumnIndex(category), includedRows);
          }
  
          int baseIndex = -1;
          for (int r : includedRows) {
              Number n = dataset.getValue(dataset.getRowKey(r), category);
              if (n == null) {
                  continue;
              }
              double v = n.doubleValue();
              if (asPercentages) {
                  v = v / total;
              }
              if ((v > 0.0) || (!this.ignoreZeroValues && v >= 0.0)) {
                  if (baseIndex < 0) {
                      result.add(new Object[]{null, base});
                      baseIndex = 0;
                  }
                  posBase = posBase + v;
                  result.add(new Object[]{r, posBase});
              } else if (v < 0.0) {
                  if (baseIndex < 0) {
                      result.add(new Object[]{null, base});
                      baseIndex = 0;
                  }
                  negBase = negBase + v; // '+' because v is negative
                  result.add(0, new Object[]{-r - 1,
                          negBase});
                  baseIndex++;
              }
          }
          return result;
  
      }
  
      /**
       * Draws the visual representation of one data item from the chart (in
       * fact, this method does nothing until it reaches the last item for each
       * category, at which point it draws all the items for that category).
       *
       * @param g2  the graphics device.
       * @param state  the renderer state.
       * @param dataArea  the plot area.
       * @param plot  the plot.
       * @param domainAxis  the domain (category) axis.
       * @param rangeAxis  the range (value) axis.
       * @param dataset  the data.
       * @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) {
  
          // wait till we are at the last item for the row then draw the
          // whole stack at once
          if (row < dataset.getRowCount() - 1) {
              return;
          }
          Comparable category = dataset.getColumnKey(column);
  
          List<Object[]> values = createStackedValueList(dataset,
                  dataset.getColumnKey(column), state.getVisibleSeriesArray(),
                  getBase(), this.renderAsPercentages);
  
          Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
                  dataArea.getY() + getYOffset(),
                  dataArea.getWidth() - getXOffset(),
                  dataArea.getHeight() - getYOffset());
  
  
          PlotOrientation orientation = plot.getOrientation();
  
          // handle rendering separately for the two plot orientations...
          if (orientation == PlotOrientation.HORIZONTAL) {
              drawStackHorizontal(values, category, g2, state, adjusted, plot,
                      domainAxis, rangeAxis, dataset);
          }
          else {
              drawStackVertical(values, category, g2, state, adjusted, plot,
                      domainAxis, rangeAxis, dataset);
          }
  
      }
  
      /**
       * Draws a stack of bars for one category, with a horizontal orientation.
       *
       * @param values  the value list.
       * @param category  the category.
       * @param g2  the graphics device.
       * @param state  the state.
       * @param dataArea  the data area (adjusted for the 3D effect).
       * @param plot  the plot.
       * @param domainAxis  the domain axis.
       * @param rangeAxis  the range axis.
       * @param dataset  the dataset.
       *
       * @since 1.0.4
       */
      protected void drawStackHorizontal(List values, Comparable category,
              Graphics2D g2, CategoryItemRendererState state,
              Rectangle2D dataArea, CategoryPlot plot,
              CategoryAxis domainAxis, ValueAxis rangeAxis,
              CategoryDataset dataset) {
  
          int column = dataset.getColumnIndex(category);
          double barX0 = domainAxis.getCategoryMiddle(column,
                  dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
                  - state.getBarWidth() / 2.0;
          double barW = state.getBarWidth();
  
          // a list to store the series index and bar region, so we can draw
          // all the labels at the end...
          List<Object[]> itemLabelList = new ArrayList<Object[]>(); //FIXME MMC create an internal class to handle the data
  
          // draw the blocks
          boolean inverted = rangeAxis.isInverted();
          int blockCount = values.size() - 1;
          for (int k = 0; k < blockCount; k++) {
              int index = (inverted ? blockCount - k - 1 : k);
              Object[] prev = (Object[]) values.get(index);
              Object[] curr = (Object[]) values.get(index + 1);
              int series;
              if (curr[0] == null) {
                  series = -(Integer) prev[0] - 1;
              }
              else {
                  series = (Integer) curr[0];
                  if (series < 0) {
                      series = -(Integer) prev[0] - 1;
                  }
              }
              double v0 = (Double) prev[1];
              double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
                      plot.getRangeAxisEdge());
  
              double v1 = (Double) curr[1];
              double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
                      plot.getRangeAxisEdge());
  
              Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1,
                      inverted);
              Paint fillPaint = getItemPaint(series, column);
              Paint fillPaintDark = fillPaint;
              if (fillPaintDark instanceof Color) {
                  fillPaintDark = ((Color) fillPaint).darker();
              }
              boolean drawOutlines = isDrawBarOutline();
              Paint outlinePaint = fillPaint;
              if (drawOutlines) {
                  outlinePaint = getItemOutlinePaint(series, column);
                  g2.setStroke(getItemOutlineStroke(series, column));
              }
              for (int f = 0; f < 6; f++) {
                  if (f == 5) {
                      g2.setPaint(fillPaint);
                  }
                  else {
                      g2.setPaint(fillPaintDark);
                  }
                  g2.fill(faces[f]);
                  if (drawOutlines) {
                      g2.setPaint(outlinePaint);
                      g2.draw(faces[f]);
                  }
              }
  
              itemLabelList.add(new Object[] {series,
                      faces[5].getBounds2D(), v0 < getBase()});
  
              // add an item entity, if this information is being collected
              EntityCollection entities = state.getEntityCollection();
              if (entities != null) {
                  addItemEntity(entities, dataset, series, column, faces[5]);
              }
  
          }
  
          for (Object[] record : itemLabelList) {
              int series = (Integer) record[0];
              Rectangle2D bar = (Rectangle2D) record[1];
              boolean neg = (Boolean) record[2];
              CategoryItemLabelGenerator generator
                      = getItemLabelGenerator(series, column);
              if (generator != null && isItemLabelVisible(series, column)) {
                  drawItemLabel(g2, dataset, series, column, plot, generator,
                          bar, neg);
              }
  
          }
      }
  
      /**
       * Creates an array of shapes representing the six sides of a block in a
       * horizontal stack.
       *
       * @param x0  left edge of bar (in Java2D space).
       * @param width  the width of the bar (in Java2D units).
       * @param y0  the base of the block (in Java2D space).
       * @param y1  the top of the block (in Java2D space).
       * @param inverted  a flag indicating whether or not the block is inverted
       *     (this changes the order of the faces of the block).
       *
       * @return The sides of the block.
       */
      private Shape[] createHorizontalBlock(double x0, double width, double y0,
              double y1, boolean inverted) {
          Shape[] result = new Shape[6];
          Point2D p00 = new Point2D.Double(y0, x0);
          Point2D p01 = new Point2D.Double(y0, x0 + width);
          Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
                  p01.getY() - getYOffset());
          Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
                  p00.getY() - getYOffset());
  
          Point2D p0 = new Point2D.Double(y1, x0);
          Point2D p1 = new Point2D.Double(y1, x0 + width);
          Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
                  p1.getY() - getYOffset());
          Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
                  p0.getY() - getYOffset());
  
          GeneralPath bottom = new GeneralPath();
          bottom.moveTo((float) p1.getX(), (float) p1.getY());
          bottom.lineTo((float) p01.getX(), (float) p01.getY());
          bottom.lineTo((float) p02.getX(), (float) p02.getY());
          bottom.lineTo((float) p2.getX(), (float) p2.getY());
          bottom.closePath();
  
          GeneralPath top = new GeneralPath();
          top.moveTo((float) p0.getX(), (float) p0.getY());
          top.lineTo((float) p00.getX(), (float) p00.getY());
          top.lineTo((float) p03.getX(), (float) p03.getY());
          top.lineTo((float) p3.getX(), (float) p3.getY());
          top.closePath();
  
          GeneralPath back = new GeneralPath();
          back.moveTo((float) p2.getX(), (float) p2.getY());
          back.lineTo((float) p02.getX(), (float) p02.getY());
          back.lineTo((float) p03.getX(), (float) p03.getY());
          back.lineTo((float) p3.getX(), (float) p3.getY());
          back.closePath();
  
          GeneralPath front = new GeneralPath();
          front.moveTo((float) p0.getX(), (float) p0.getY());
          front.lineTo((float) p1.getX(), (float) p1.getY());
          front.lineTo((float) p01.getX(), (float) p01.getY());
          front.lineTo((float) p00.getX(), (float) p00.getY());
          front.closePath();
  
          GeneralPath left = new GeneralPath();
          left.moveTo((float) p0.getX(), (float) p0.getY());
          left.lineTo((float) p1.getX(), (float) p1.getY());
          left.lineTo((float) p2.getX(), (float) p2.getY());
          left.lineTo((float) p3.getX(), (float) p3.getY());
          left.closePath();
  
          GeneralPath right = new GeneralPath();
          right.moveTo((float) p00.getX(), (float) p00.getY());
          right.lineTo((float) p01.getX(), (float) p01.getY());
          right.lineTo((float) p02.getX(), (float) p02.getY());
          right.lineTo((float) p03.getX(), (float) p03.getY());
          right.closePath();
          result[0] = bottom;
          result[1] = back;
          if (inverted) {
              result[2] = right;
              result[3] = left;
          }
          else {
              result[2] = left;
              result[3] = right;
          }
          result[4] = top;
          result[5] = front;
          return result;
      }
  
      /**
       * Draws a stack of bars for one category, with a vertical orientation.
       *
       * @param values  the value list.
       * @param category  the category.
       * @param g2  the graphics device.
       * @param state  the state.
       * @param dataArea  the data area (adjusted for the 3D effect).
       * @param plot  the plot.
       * @param domainAxis  the domain axis.
       * @param rangeAxis  the range axis.
       * @param dataset  the dataset.
       *
       * @since 1.0.4
       */
      protected void drawStackVertical(List values, Comparable category,
              Graphics2D g2, CategoryItemRendererState state,
              Rectangle2D dataArea, CategoryPlot plot,
              CategoryAxis domainAxis, ValueAxis rangeAxis,
              CategoryDataset dataset) {
  
          int column = dataset.getColumnIndex(category);
          double barX0 = domainAxis.getCategoryMiddle(column,
                  dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
                  - state.getBarWidth() / 2.0;
          double barW = state.getBarWidth();
  
          // a list to store the series index and bar region, so we can draw
          // all the labels at the end...
          List<Object[]> itemLabelList = new ArrayList<Object[]>();
  
          // draw the blocks
          boolean inverted = rangeAxis.isInverted();
          int blockCount = values.size() - 1;
          for (int k = 0; k < blockCount; k++) {
              int index = (inverted ? blockCount - k - 1 : k);
              Object[] prev = (Object[]) values.get(index);
              Object[] curr = (Object[]) values.get(index + 1);
              int series = 0;
              if (curr[0] == null) {
                  series = -(Integer) prev[0] - 1;
              }
              else {
                  series = (Integer) curr[0];
                  if (series < 0) {
                      series = -(Integer) prev[0] - 1;
                  }
              }
              double v0 = (Double) prev[1];
              double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
                      plot.getRangeAxisEdge());
  
              double v1 = (Double) curr[1];
              double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
                      plot.getRangeAxisEdge());
  
              Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1,
                      inverted);
              Paint fillPaint = getItemPaint(series, column);
              Paint fillPaintDark = fillPaint;
              if (fillPaintDark instanceof Color) {
                  fillPaintDark = ((Color) fillPaint).darker();
              }
              boolean drawOutlines = isDrawBarOutline();
              Paint outlinePaint = fillPaint;
              if (drawOutlines) {
                  outlinePaint = getItemOutlinePaint(series, column);
                  g2.setStroke(getItemOutlineStroke(series, column));
              }
  
              for (int f = 0; f < 6; f++) {
                  if (f == 5) {
                      g2.setPaint(fillPaint);
                  }
                  else {
                      g2.setPaint(fillPaintDark);
                  }
                  g2.fill(faces[f]);
                  if (drawOutlines) {
                      g2.setPaint(outlinePaint);
                      g2.draw(faces[f]);
                  }
              }
  
              itemLabelList.add(new Object[] {series,
                      faces[5].getBounds2D(), v0 < getBase()});
  
              // add an item entity, if this information is being collected
              EntityCollection entities = state.getEntityCollection();
              if (entities != null) {
                  addItemEntity(entities, dataset, series, column, faces[5]);
              }
  
          }
  
          for (Object[] record : itemLabelList) {
              int series = (Integer) record[0];
              Rectangle2D bar = (Rectangle2D) record[1];
              boolean neg = (Boolean) record[2];
              CategoryItemLabelGenerator generator
                      = getItemLabelGenerator(series, column);
              if (generator != null && isItemLabelVisible(series, column)) {
                  drawItemLabel(g2, dataset, series, column, plot, generator,
                          bar, neg);
              }
  
          }
      }
  
      /**
       * Creates an array of shapes representing the six sides of a block in a
       * vertical stack.
       *
       * @param x0  left edge of bar (in Java2D space).
       * @param width  the width of the bar (in Java2D units).
       * @param y0  the base of the block (in Java2D space).
       * @param y1  the top of the block (in Java2D space).
       * @param inverted  a flag indicating whether or not the block is inverted
       *     (this changes the order of the faces of the block).
       *
       * @return The sides of the block.
       */
      private Shape[] createVerticalBlock(double x0, double width, double y0,
              double y1, boolean inverted) {
          Shape[] result = new Shape[6];
          Point2D p00 = new Point2D.Double(x0, y0);
          Point2D p01 = new Point2D.Double(x0 + width, y0);
          Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
                  p01.getY() - getYOffset());
          Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
                  p00.getY() - getYOffset());
  
  
          Point2D p0 = new Point2D.Double(x0, y1);
          Point2D p1 = new Point2D.Double(x0 + width, y1);
          Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
                  p1.getY() - getYOffset());
          Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
                  p0.getY() - getYOffset());
  
          GeneralPath right = new GeneralPath();
          right.moveTo((float) p1.getX(), (float) p1.getY());
          right.lineTo((float) p01.getX(), (float) p01.getY());
          right.lineTo((float) p02.getX(), (float) p02.getY());
          right.lineTo((float) p2.getX(), (float) p2.getY());
          right.closePath();
  
          GeneralPath left = new GeneralPath();
          left.moveTo((float) p0.getX(), (float) p0.getY());
          left.lineTo((float) p00.getX(), (float) p00.getY());
          left.lineTo((float) p03.getX(), (float) p03.getY());
          left.lineTo((float) p3.getX(), (float) p3.getY());
          left.closePath();
  
          GeneralPath back = new GeneralPath();
          back.moveTo((float) p2.getX(), (float) p2.getY());
          back.lineTo((float) p02.getX(), (float) p02.getY());
          back.lineTo((float) p03.getX(), (float) p03.getY());
          back.lineTo((float) p3.getX(), (float) p3.getY());
          back.closePath();
  
          GeneralPath front = new GeneralPath();
          front.moveTo((float) p0.getX(), (float) p0.getY());
          front.lineTo((float) p1.getX(), (float) p1.getY());
          front.lineTo((float) p01.getX(), (float) p01.getY());
          front.lineTo((float) p00.getX(), (float) p00.getY());
          front.closePath();
  
          GeneralPath top = new GeneralPath();
          top.moveTo((float) p0.getX(), (float) p0.getY());
          top.lineTo((float) p1.getX(), (float) p1.getY());
          top.lineTo((float) p2.getX(), (float) p2.getY());
          top.lineTo((float) p3.getX(), (float) p3.getY());
          top.closePath();
  
          GeneralPath bottom = new GeneralPath();
          bottom.moveTo((float) p00.getX(), (float) p00.getY());
          bottom.lineTo((float) p01.getX(), (float) p01.getY());
          bottom.lineTo((float) p02.getX(), (float) p02.getY());
          bottom.lineTo((float) p03.getX(), (float) p03.getY());
          bottom.closePath();
  
          result[0] = bottom;
          result[1] = back;
          result[2] = left;
          result[3] = right;
          result[4] = top;
          result[5] = front;
          if (inverted) {
              result[0] = top;
              result[4] = bottom;
          }
          return result;
      }
  
      /**
       * Tests this renderer for equality with an arbitrary object.
       *
       * @param obj  the object (<code>null</code> permitted).
       *
       * @return A boolean.
       */
      @Override
      public boolean equals(Object obj) {
          if (obj == this) {
              return true;
          }
          if (!(obj instanceof StackedBarRenderer3D)) {
              return false;
          }
          StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
          if (this.renderAsPercentages != that.getRenderAsPercentages()) {
              return false;
          }
          if (this.ignoreZeroValues != that.ignoreZeroValues) {
              return false;
          }
          return super.equals(obj);
      }
  
      /**
       * Returns a hash code for this instance.
       *
       * @return A hash code.
       */
      @Override
      public int hashCode() {
          int hash = super.hashCode();
          hash = HashUtilities.hashCode(hash, this.renderAsPercentages);
          hash = HashUtilities.hashCode(hash, this.ignoreZeroValues);
          return hash;
      }
  
  }
  