Source for org.jfree.chart.renderer.category.StackedBarRenderer3D

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it
  10:  * under the terms of the GNU Lesser General Public License as published by
  11:  * the Free Software Foundation; either version 2.1 of the License, or
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
  22:  * USA.
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  25:  * in the United States and other countries.]
  26:  *
  27:  * -------------------------
  28:  * StackedBarRenderer3D.java
  29:  * -------------------------
  30:  * (C) Copyright 2000-2008, by Serge V. Grachov and Contributors.
  31:  *
  32:  * Original Author:  Serge V. Grachov;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Richard Atkinson;
  35:  *                   Christian W. Zuckschwerdt;
  36:  *                   Max Herfort (patch 1459313);
  37:  *
  38:  * Changes
  39:  * -------
  40:  * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG);
  41:  * 15-Nov-2001 : Modified to allow for null data values (DG);
  42:  * 13-Dec-2001 : Added tooltips (DG);
  43:  * 15-Feb-2002 : Added isStacked() method (DG);
  44:  * 24-May-2002 : Incorporated tooltips into chart entities (DG);
  45:  * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG);
  46:  * 25-Jun-2002 : Removed redundant imports (DG);
  47:  * 26-Jun-2002 : Small change to entity (DG);
  48:  * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
  49:  *               for HTML image maps (RA);
  50:  * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  51:  * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
  52:  *               CategoryToolTipGenerator interface (DG);
  53:  * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
  54:  * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
  55:  * 17-Jan-2003 : Moved plot classes to a separate package (DG);
  56:  * 25-Mar-2003 : Implemented Serializable (DG);
  57:  * 01-May-2003 : Added default constructor (bug 726235) and fixed bug
  58:  *               726260) (DG);
  59:  * 13-May-2003 : Renamed StackedVerticalBarRenderer3D
  60:  *               --> StackedBarRenderer3D (DG);
  61:  * 30-Jul-2003 : Modified entity constructor (CZ);
  62:  * 07-Oct-2003 : Added renderer state (DG);
  63:  * 21-Nov-2003 : Added a new constructor (DG);
  64:  * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG);
  65:  * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG);
  66:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  67:  * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
  68:  * 18-Mar-2005 : Override for getPassCount() method (DG);
  69:  * 20-Apr-2005 : Renamed CategoryLabelGenerator
  70:  *               --> CategoryItemLabelGenerator (DG);
  71:  * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
  72:  * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
  73:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  74:  * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted
  75:  *               by Max Herfort (DG);
  76:  * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG);
  77:  * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList()
  78:  *               method (DG);
  79:  * 18-Jan-2007 : Updated block drawing code to take account of inverted axes,
  80:  *               see bug report 1599652 (DG);
  81:  * 08-May-2007 : Fixed bugs 1713401 (drawBarOutlines flag) and  1713474
  82:  *               (shading) (DG);
  83:  *
  84:  */
  85: 
  86: package org.jfree.chart.renderer.category;
  87: 
  88: import java.awt.Color;
  89: import java.awt.Graphics2D;
  90: import java.awt.Paint;
  91: import java.awt.Shape;
  92: import java.awt.geom.GeneralPath;
  93: import java.awt.geom.Point2D;
  94: import java.awt.geom.Rectangle2D;
  95: import java.io.Serializable;
  96: import java.util.ArrayList;
  97: import java.util.List;
  98: 
  99: import org.jfree.chart.axis.CategoryAxis;
 100: import org.jfree.chart.axis.ValueAxis;
 101: import org.jfree.chart.entity.EntityCollection;
 102: import org.jfree.chart.event.RendererChangeEvent;
 103: import org.jfree.chart.labels.CategoryItemLabelGenerator;
 104: import org.jfree.chart.plot.CategoryPlot;
 105: import org.jfree.chart.plot.PlotOrientation;
 106: import org.jfree.data.DataUtilities;
 107: import org.jfree.data.Range;
 108: import org.jfree.data.category.CategoryDataset;
 109: import org.jfree.data.general.DatasetUtilities;
 110: import org.jfree.util.BooleanUtilities;
 111: import org.jfree.util.PublicCloneable;
 112: 
 113: /**
 114:  * Renders stacked bars with 3D-effect, for use with the
 115:  * {@link org.jfree.chart.plot.CategoryPlot} class.
 116:  */
 117: public class StackedBarRenderer3D extends BarRenderer3D
 118:         implements Cloneable, PublicCloneable, Serializable {
 119: 
 120:     /** For serialization. */
 121:     private static final long serialVersionUID = -5832945916493247123L;
 122: 
 123:     /** A flag that controls whether the bars display values or percentages. */
 124:     private boolean renderAsPercentages;
 125: 
 126:     /**
 127:      * Creates a new renderer with no tool tip generator and no URL generator.
 128:      * <P>
 129:      * The defaults (no tool tip or URL generators) have been chosen to
 130:      * minimise the processing required to generate a default chart.  If you
 131:      * require tool tips or URLs, then you can easily add the required
 132:      * generators.
 133:      */
 134:     public StackedBarRenderer3D() {
 135:         this(false);
 136:     }
 137: 
 138:     /**
 139:      * Constructs a new renderer with the specified '3D effect'.
 140:      *
 141:      * @param xOffset  the x-offset for the 3D effect.
 142:      * @param yOffset  the y-offset for the 3D effect.
 143:      */
 144:     public StackedBarRenderer3D(double xOffset, double yOffset) {
 145:         super(xOffset, yOffset);
 146:     }
 147: 
 148:     /**
 149:      * Creates a new renderer.
 150:      *
 151:      * @param renderAsPercentages  a flag that controls whether the data values
 152:      *                             are rendered as percentages.
 153:      *
 154:      * @since 1.0.2
 155:      */
 156:     public StackedBarRenderer3D(boolean renderAsPercentages) {
 157:         super();
 158:         this.renderAsPercentages = renderAsPercentages;
 159:     }
 160: 
 161:     /**
 162:      * Constructs a new renderer with the specified '3D effect'.
 163:      *
 164:      * @param xOffset  the x-offset for the 3D effect.
 165:      * @param yOffset  the y-offset for the 3D effect.
 166:      * @param renderAsPercentages  a flag that controls whether the data values
 167:      *                             are rendered as percentages.
 168:      *
 169:      * @since 1.0.2
 170:      */
 171:     public StackedBarRenderer3D(double xOffset, double yOffset,
 172:             boolean renderAsPercentages) {
 173:         super(xOffset, yOffset);
 174:         this.renderAsPercentages = renderAsPercentages;
 175:     }
 176: 
 177:     /**
 178:      * Returns <code>true</code> if the renderer displays each item value as
 179:      * a percentage (so that the stacked bars add to 100%), and
 180:      * <code>false</code> otherwise.
 181:      *
 182:      * @return A boolean.
 183:      *
 184:      * @since 1.0.2
 185:      */
 186:     public boolean getRenderAsPercentages() {
 187:         return this.renderAsPercentages;
 188:     }
 189: 
 190:     /**
 191:      * Sets the flag that controls whether the renderer displays each item
 192:      * value as a percentage (so that the stacked bars add to 100%), and sends
 193:      * a {@link RendererChangeEvent} to all registered listeners.
 194:      *
 195:      * @param asPercentages  the flag.
 196:      *
 197:      * @since 1.0.2
 198:      */
 199:     public void setRenderAsPercentages(boolean asPercentages) {
 200:         this.renderAsPercentages = asPercentages;
 201:         fireChangeEvent();
 202:     }
 203: 
 204:     /**
 205:      * Returns the range of values the renderer requires to display all the
 206:      * items from the specified dataset.
 207:      *
 208:      * @param dataset  the dataset (<code>null</code> not permitted).
 209:      *
 210:      * @return The range (or <code>null</code> if the dataset is empty).
 211:      */
 212:     public Range findRangeBounds(CategoryDataset dataset) {
 213:         if (this.renderAsPercentages) {
 214:             return new Range(0.0, 1.0);
 215:         }
 216:         else {
 217:             return DatasetUtilities.findStackedRangeBounds(dataset);
 218:         }
 219:     }
 220: 
 221:     /**
 222:      * Calculates the bar width and stores it in the renderer state.
 223:      *
 224:      * @param plot  the plot.
 225:      * @param dataArea  the data area.
 226:      * @param rendererIndex  the renderer index.
 227:      * @param state  the renderer state.
 228:      */
 229:     protected void calculateBarWidth(CategoryPlot plot,
 230:                                      Rectangle2D dataArea,
 231:                                      int rendererIndex,
 232:                                      CategoryItemRendererState state) {
 233: 
 234:         // calculate the bar width
 235:         CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
 236:         CategoryDataset data = plot.getDataset(rendererIndex);
 237:         if (data != null) {
 238:             PlotOrientation orientation = plot.getOrientation();
 239:             double space = 0.0;
 240:             if (orientation == PlotOrientation.HORIZONTAL) {
 241:                 space = dataArea.getHeight();
 242:             }
 243:             else if (orientation == PlotOrientation.VERTICAL) {
 244:                 space = dataArea.getWidth();
 245:             }
 246:             double maxWidth = space * getMaximumBarWidth();
 247:             int columns = data.getColumnCount();
 248:             double categoryMargin = 0.0;
 249:             if (columns > 1) {
 250:                 categoryMargin = domainAxis.getCategoryMargin();
 251:             }
 252: 
 253:             double used = space * (1 - domainAxis.getLowerMargin()
 254:                                      - domainAxis.getUpperMargin()
 255:                                      - categoryMargin);
 256:             if (columns > 0) {
 257:                 state.setBarWidth(Math.min(used / columns, maxWidth));
 258:             }
 259:             else {
 260:                 state.setBarWidth(Math.min(used, maxWidth));
 261:             }
 262:         }
 263: 
 264:     }
 265: 
 266:     /**
 267:      * Returns a list containing the stacked values for the specified series
 268:      * in the given dataset, plus the supplied base value.
 269:      *
 270:      * @param dataset  the dataset (<code>null</code> not permitted).
 271:      * @param category  the category key (<code>null</code> not permitted).
 272:      * @param base  the base value.
 273:      * @param asPercentages  a flag that controls whether the values in the
 274:      *     list are converted to percentages of the total.
 275:      *
 276:      * @return The value list.
 277:      *
 278:      * @since 1.0.4
 279:      */
 280:     protected static List createStackedValueList(CategoryDataset dataset,
 281:             Comparable category, double base, boolean asPercentages) {
 282: 
 283:         List result = new ArrayList();
 284:         double posBase = base;
 285:         double negBase = base;
 286:         double total = 0.0;
 287:         if (asPercentages) {
 288:             total = DataUtilities.calculateColumnTotal(dataset,
 289:                     dataset.getColumnIndex(category));
 290:         }
 291: 
 292:         int baseIndex = -1;
 293:         int seriesCount = dataset.getRowCount();
 294:         for (int s = 0; s < seriesCount; s++) {
 295:             Number n = dataset.getValue(dataset.getRowKey(s), category);
 296:             if (n == null) {
 297:                 continue;
 298:             }
 299:             double v = n.doubleValue();
 300:             if (asPercentages) {
 301:                 v = v / total;
 302:             }
 303:             if (v >= 0.0) {
 304:                 if (baseIndex < 0) {
 305:                     result.add(new Object[] {null, new Double(base)});
 306:                     baseIndex = 0;
 307:                 }
 308:                 posBase = posBase + v;
 309:                 result.add(new Object[] {new Integer(s), new Double(posBase)});
 310:             }
 311:             else if (v < 0.0) {
 312:                 if (baseIndex < 0) {
 313:                     result.add(new Object[] {null, new Double(base)});
 314:                     baseIndex = 0;
 315:                 }
 316:                 negBase = negBase + v; // '+' because v is negative
 317:                 result.add(0, new Object[] {new Integer(-s),
 318:                         new Double(negBase)});
 319:                 baseIndex++;
 320:             }
 321:         }
 322:         return result;
 323: 
 324:     }
 325: 
 326:     /**
 327:      * Draws the visual representation of one data item from the chart (in
 328:      * fact, this method does nothing until it reaches the last item for each
 329:      * category, at which point it draws all the items for that category).
 330:      *
 331:      * @param g2  the graphics device.
 332:      * @param state  the renderer state.
 333:      * @param dataArea  the plot area.
 334:      * @param plot  the plot.
 335:      * @param domainAxis  the domain (category) axis.
 336:      * @param rangeAxis  the range (value) axis.
 337:      * @param dataset  the data.
 338:      * @param row  the row index (zero-based).
 339:      * @param column  the column index (zero-based).
 340:      * @param pass  the pass index.
 341:      */
 342:     public void drawItem(Graphics2D g2,
 343:                          CategoryItemRendererState state,
 344:                          Rectangle2D dataArea,
 345:                          CategoryPlot plot,
 346:                          CategoryAxis domainAxis,
 347:                          ValueAxis rangeAxis,
 348:                          CategoryDataset dataset,
 349:                          int row,
 350:                          int column,
 351:                          int pass) {
 352: 
 353:         // wait till we are at the last item for the row then draw the
 354:         // whole stack at once
 355:         if (row < dataset.getRowCount() - 1) {
 356:             return;
 357:         }
 358:         Comparable category = dataset.getColumnKey(column);
 359: 
 360:         List values = createStackedValueList(dataset,
 361:                 dataset.getColumnKey(column), getBase(),
 362:                 this.renderAsPercentages);
 363: 
 364:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
 365:                 dataArea.getY() + getYOffset(),
 366:                 dataArea.getWidth() - getXOffset(),
 367:                 dataArea.getHeight() - getYOffset());
 368: 
 369: 
 370:         PlotOrientation orientation = plot.getOrientation();
 371: 
 372:         // handle rendering separately for the two plot orientations...
 373:         if (orientation == PlotOrientation.HORIZONTAL) {
 374:             drawStackHorizontal(values, category, g2, state, adjusted, plot,
 375:                     domainAxis, rangeAxis, dataset);
 376:         }
 377:         else {
 378:             drawStackVertical(values, category, g2, state, adjusted, plot,
 379:                     domainAxis, rangeAxis, dataset);
 380:         }
 381: 
 382:     }
 383: 
 384:     /**
 385:      * Draws a stack of bars for one category, with a horizontal orientation.
 386:      *
 387:      * @param values  the value list.
 388:      * @param category  the category.
 389:      * @param g2  the graphics device.
 390:      * @param state  the state.
 391:      * @param dataArea  the data area (adjusted for the 3D effect).
 392:      * @param plot  the plot.
 393:      * @param domainAxis  the domain axis.
 394:      * @param rangeAxis  the range axis.
 395:      * @param dataset  the dataset.
 396:      *
 397:      * @since 1.0.4
 398:      */
 399:     protected void drawStackHorizontal(List values, Comparable category,
 400:             Graphics2D g2, CategoryItemRendererState state,
 401:             Rectangle2D dataArea, CategoryPlot plot,
 402:             CategoryAxis domainAxis, ValueAxis rangeAxis,
 403:             CategoryDataset dataset) {
 404: 
 405:         int column = dataset.getColumnIndex(category);
 406:         double barX0 = domainAxis.getCategoryMiddle(column,
 407:                 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
 408:                 - state.getBarWidth() / 2.0;
 409:         double barW = state.getBarWidth();
 410: 
 411:         // a list to store the series index and bar region, so we can draw
 412:         // all the labels at the end...
 413:         List itemLabelList = new ArrayList();
 414: 
 415:         // draw the blocks
 416:         boolean inverted = rangeAxis.isInverted();
 417:         int blockCount = values.size() - 1;
 418:         for (int k = 0; k < blockCount; k++) {
 419:             int index = (inverted ? blockCount - k - 1 : k);
 420:             Object[] prev = (Object[]) values.get(index);
 421:             Object[] curr = (Object[]) values.get(index + 1);
 422:             int series = 0;
 423:             if (curr[0] == null) {
 424:                 series = -((Integer) prev[0]).intValue();
 425:             }
 426:             else {
 427:                 series = ((Integer) curr[0]).intValue();
 428:                 if (series < 0) {
 429:                     series = -((Integer) prev[0]).intValue();
 430:                 }
 431:             }
 432:             double v0 = ((Double) prev[1]).doubleValue();
 433:             double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
 434:                     plot.getRangeAxisEdge());
 435: 
 436:             double v1 = ((Double) curr[1]).doubleValue();
 437:             double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
 438:                     plot.getRangeAxisEdge());
 439: 
 440:             Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1,
 441:                     inverted);
 442:             Paint fillPaint = getItemPaint(series, column);
 443:             Paint fillPaintDark = fillPaint;
 444:             if (fillPaintDark instanceof Color) {
 445:                 fillPaintDark = ((Color) fillPaint).darker();
 446:             }
 447:             boolean drawOutlines = isDrawBarOutline();
 448:             Paint outlinePaint = fillPaint;
 449:             if (drawOutlines) {
 450:                 outlinePaint = getItemOutlinePaint(series, column);
 451:                 g2.setStroke(getItemOutlineStroke(series, column));
 452:             }
 453:             for (int f = 0; f < 6; f++) {
 454:                 if (f == 5) {
 455:                     g2.setPaint(fillPaint);
 456:                 }
 457:                 else {
 458:                     g2.setPaint(fillPaintDark);
 459:                 }
 460:                 g2.fill(faces[f]);
 461:                 if (drawOutlines) {
 462:                     g2.setPaint(outlinePaint);
 463:                     g2.draw(faces[f]);
 464:                 }
 465:             }
 466: 
 467:             itemLabelList.add(new Object[] {new Integer(series),
 468:                     faces[5].getBounds2D(),
 469:                     BooleanUtilities.valueOf(v0 < getBase())});
 470: 
 471:             // add an item entity, if this information is being collected
 472:             EntityCollection entities = state.getEntityCollection();
 473:             if (entities != null) {
 474:                 addItemEntity(entities, dataset, series, column, faces[5]);
 475:             }
 476: 
 477:         }
 478: 
 479:         for (int i = 0; i < itemLabelList.size(); i++) {
 480:             Object[] record = (Object[]) itemLabelList.get(i);
 481:             int series = ((Integer) record[0]).intValue();
 482:             Rectangle2D bar = (Rectangle2D) record[1];
 483:             boolean neg = ((Boolean) record[2]).booleanValue();
 484:             CategoryItemLabelGenerator generator
 485:                     = getItemLabelGenerator(series, column);
 486:             if (generator != null && isItemLabelVisible(series, column)) {
 487:                 drawItemLabel(g2, dataset, series, column, plot, generator,
 488:                         bar, neg);
 489:             }
 490: 
 491:         }
 492:     }
 493: 
 494:     /**
 495:      * Creates an array of shapes representing the six sides of a block in a
 496:      * horizontal stack.
 497:      *
 498:      * @param x0  left edge of bar (in Java2D space).
 499:      * @param width  the width of the bar (in Java2D units).
 500:      * @param y0  the base of the block (in Java2D space).
 501:      * @param y1  the top of the block (in Java2D space).
 502:      * @param inverted  a flag indicating whether or not the block is inverted
 503:      *     (this changes the order of the faces of the block).
 504:      *
 505:      * @return The sides of the block.
 506:      */
 507:     private Shape[] createHorizontalBlock(double x0, double width, double y0,
 508:             double y1, boolean inverted) {
 509:         Shape[] result = new Shape[6];
 510:         Point2D p00 = new Point2D.Double(y0, x0);
 511:         Point2D p01 = new Point2D.Double(y0, x0 + width);
 512:         Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
 513:                 p01.getY() - getYOffset());
 514:         Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
 515:                 p00.getY() - getYOffset());
 516: 
 517:         Point2D p0 = new Point2D.Double(y1, x0);
 518:         Point2D p1 = new Point2D.Double(y1, x0 + width);
 519:         Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
 520:                 p1.getY() - getYOffset());
 521:         Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
 522:                 p0.getY() - getYOffset());
 523: 
 524:         GeneralPath bottom = new GeneralPath();
 525:         bottom.moveTo((float) p1.getX(), (float) p1.getY());
 526:         bottom.lineTo((float) p01.getX(), (float) p01.getY());
 527:         bottom.lineTo((float) p02.getX(), (float) p02.getY());
 528:         bottom.lineTo((float) p2.getX(), (float) p2.getY());
 529:         bottom.closePath();
 530: 
 531:         GeneralPath top = new GeneralPath();
 532:         top.moveTo((float) p0.getX(), (float) p0.getY());
 533:         top.lineTo((float) p00.getX(), (float) p00.getY());
 534:         top.lineTo((float) p03.getX(), (float) p03.getY());
 535:         top.lineTo((float) p3.getX(), (float) p3.getY());
 536:         top.closePath();
 537: 
 538:         GeneralPath back = new GeneralPath();
 539:         back.moveTo((float) p2.getX(), (float) p2.getY());
 540:         back.lineTo((float) p02.getX(), (float) p02.getY());
 541:         back.lineTo((float) p03.getX(), (float) p03.getY());
 542:         back.lineTo((float) p3.getX(), (float) p3.getY());
 543:         back.closePath();
 544: 
 545:         GeneralPath front = new GeneralPath();
 546:         front.moveTo((float) p0.getX(), (float) p0.getY());
 547:         front.lineTo((float) p1.getX(), (float) p1.getY());
 548:         front.lineTo((float) p01.getX(), (float) p01.getY());
 549:         front.lineTo((float) p00.getX(), (float) p00.getY());
 550:         front.closePath();
 551: 
 552:         GeneralPath left = new GeneralPath();
 553:         left.moveTo((float) p0.getX(), (float) p0.getY());
 554:         left.lineTo((float) p1.getX(), (float) p1.getY());
 555:         left.lineTo((float) p2.getX(), (float) p2.getY());
 556:         left.lineTo((float) p3.getX(), (float) p3.getY());
 557:         left.closePath();
 558: 
 559:         GeneralPath right = new GeneralPath();
 560:         right.moveTo((float) p00.getX(), (float) p00.getY());
 561:         right.lineTo((float) p01.getX(), (float) p01.getY());
 562:         right.lineTo((float) p02.getX(), (float) p02.getY());
 563:         right.lineTo((float) p03.getX(), (float) p03.getY());
 564:         right.closePath();
 565:         result[0] = bottom;
 566:         result[1] = back;
 567:         if (inverted) {
 568:             result[2] = right;
 569:             result[3] = left;
 570:         }
 571:         else {
 572:             result[2] = left;
 573:             result[3] = right;
 574:         }
 575:         result[4] = top;
 576:         result[5] = front;
 577:         return result;
 578:     }
 579: 
 580:     /**
 581:      * Draws a stack of bars for one category, with a vertical orientation.
 582:      *
 583:      * @param values  the value list.
 584:      * @param category  the category.
 585:      * @param g2  the graphics device.
 586:      * @param state  the state.
 587:      * @param dataArea  the data area (adjusted for the 3D effect).
 588:      * @param plot  the plot.
 589:      * @param domainAxis  the domain axis.
 590:      * @param rangeAxis  the range axis.
 591:      * @param dataset  the dataset.
 592:      *
 593:      * @since 1.0.4
 594:      */
 595:     protected void drawStackVertical(List values, Comparable category,
 596:             Graphics2D g2, CategoryItemRendererState state,
 597:             Rectangle2D dataArea, CategoryPlot plot,
 598:             CategoryAxis domainAxis, ValueAxis rangeAxis,
 599:             CategoryDataset dataset) {
 600: 
 601:         int column = dataset.getColumnIndex(category);
 602:         double barX0 = domainAxis.getCategoryMiddle(column,
 603:                 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
 604:                 - state.getBarWidth() / 2.0;
 605:         double barW = state.getBarWidth();
 606: 
 607:         // a list to store the series index and bar region, so we can draw
 608:         // all the labels at the end...
 609:         List itemLabelList = new ArrayList();
 610: 
 611:         // draw the blocks
 612:         boolean inverted = rangeAxis.isInverted();
 613:         int blockCount = values.size() - 1;
 614:         for (int k = 0; k < blockCount; k++) {
 615:             int index = (inverted ? blockCount - k - 1 : k);
 616:             Object[] prev = (Object[]) values.get(index);
 617:             Object[] curr = (Object[]) values.get(index + 1);
 618:             int series = 0;
 619:             if (curr[0] == null) {
 620:                 series = -((Integer) prev[0]).intValue();
 621:             }
 622:             else {
 623:                 series = ((Integer) curr[0]).intValue();
 624:                 if (series < 0) {
 625:                     series = -((Integer) prev[0]).intValue();
 626:                 }
 627:             }
 628:             double v0 = ((Double) prev[1]).doubleValue();
 629:             double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
 630:                     plot.getRangeAxisEdge());
 631: 
 632:             double v1 = ((Double) curr[1]).doubleValue();
 633:             double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
 634:                     plot.getRangeAxisEdge());
 635: 
 636:             Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1,
 637:                     inverted);
 638:             Paint fillPaint = getItemPaint(series, column);
 639:             Paint fillPaintDark = fillPaint;
 640:             if (fillPaintDark instanceof Color) {
 641:                 fillPaintDark = ((Color) fillPaint).darker();
 642:             }
 643:             boolean drawOutlines = isDrawBarOutline();
 644:             Paint outlinePaint = fillPaint;
 645:             if (drawOutlines) {
 646:                 outlinePaint = getItemOutlinePaint(series, column);
 647:                 g2.setStroke(getItemOutlineStroke(series, column));
 648:             }
 649: 
 650:             for (int f = 0; f < 6; f++) {
 651:                 if (f == 5) {
 652:                     g2.setPaint(fillPaint);
 653:                 }
 654:                 else {
 655:                     g2.setPaint(fillPaintDark);
 656:                 }
 657:                 g2.fill(faces[f]);
 658:                 if (drawOutlines) {
 659:                     g2.setPaint(outlinePaint);
 660:                     g2.draw(faces[f]);
 661:                 }
 662:             }
 663: 
 664:             itemLabelList.add(new Object[] {new Integer(series),
 665:                     faces[5].getBounds2D(),
 666:                     BooleanUtilities.valueOf(v0 < getBase())});
 667: 
 668:             // add an item entity, if this information is being collected
 669:             EntityCollection entities = state.getEntityCollection();
 670:             if (entities != null) {
 671:                 addItemEntity(entities, dataset, series, column, faces[5]);
 672:             }
 673: 
 674:         }
 675: 
 676:         for (int i = 0; i < itemLabelList.size(); i++) {
 677:             Object[] record = (Object[]) itemLabelList.get(i);
 678:             int series = ((Integer) record[0]).intValue();
 679:             Rectangle2D bar = (Rectangle2D) record[1];
 680:             boolean neg = ((Boolean) record[2]).booleanValue();
 681:             CategoryItemLabelGenerator generator
 682:                     = getItemLabelGenerator(series, column);
 683:             if (generator != null && isItemLabelVisible(series, column)) {
 684:                 drawItemLabel(g2, dataset, series, column, plot, generator,
 685:                         bar, neg);
 686:             }
 687: 
 688:         }
 689:     }
 690: 
 691:     /**
 692:      * Creates an array of shapes representing the six sides of a block in a
 693:      * vertical stack.
 694:      *
 695:      * @param x0  left edge of bar (in Java2D space).
 696:      * @param width  the width of the bar (in Java2D units).
 697:      * @param y0  the base of the block (in Java2D space).
 698:      * @param y1  the top of the block (in Java2D space).
 699:      * @param inverted  a flag indicating whether or not the block is inverted
 700:      *     (this changes the order of the faces of the block).
 701:      *
 702:      * @return The sides of the block.
 703:      */
 704:     private Shape[] createVerticalBlock(double x0, double width, double y0,
 705:             double y1, boolean inverted) {
 706:         Shape[] result = new Shape[6];
 707:         Point2D p00 = new Point2D.Double(x0, y0);
 708:         Point2D p01 = new Point2D.Double(x0 + width, y0);
 709:         Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
 710:                 p01.getY() - getYOffset());
 711:         Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
 712:                 p00.getY() - getYOffset());
 713: 
 714: 
 715:         Point2D p0 = new Point2D.Double(x0, y1);
 716:         Point2D p1 = new Point2D.Double(x0 + width, y1);
 717:         Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
 718:                 p1.getY() - getYOffset());
 719:         Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
 720:                 p0.getY() - getYOffset());
 721: 
 722:         GeneralPath right = new GeneralPath();
 723:         right.moveTo((float) p1.getX(), (float) p1.getY());
 724:         right.lineTo((float) p01.getX(), (float) p01.getY());
 725:         right.lineTo((float) p02.getX(), (float) p02.getY());
 726:         right.lineTo((float) p2.getX(), (float) p2.getY());
 727:         right.closePath();
 728: 
 729:         GeneralPath left = new GeneralPath();
 730:         left.moveTo((float) p0.getX(), (float) p0.getY());
 731:         left.lineTo((float) p00.getX(), (float) p00.getY());
 732:         left.lineTo((float) p03.getX(), (float) p03.getY());
 733:         left.lineTo((float) p3.getX(), (float) p3.getY());
 734:         left.closePath();
 735: 
 736:         GeneralPath back = new GeneralPath();
 737:         back.moveTo((float) p2.getX(), (float) p2.getY());
 738:         back.lineTo((float) p02.getX(), (float) p02.getY());
 739:         back.lineTo((float) p03.getX(), (float) p03.getY());
 740:         back.lineTo((float) p3.getX(), (float) p3.getY());
 741:         back.closePath();
 742: 
 743:         GeneralPath front = new GeneralPath();
 744:         front.moveTo((float) p0.getX(), (float) p0.getY());
 745:         front.lineTo((float) p1.getX(), (float) p1.getY());
 746:         front.lineTo((float) p01.getX(), (float) p01.getY());
 747:         front.lineTo((float) p00.getX(), (float) p00.getY());
 748:         front.closePath();
 749: 
 750:         GeneralPath top = new GeneralPath();
 751:         top.moveTo((float) p0.getX(), (float) p0.getY());
 752:         top.lineTo((float) p1.getX(), (float) p1.getY());
 753:         top.lineTo((float) p2.getX(), (float) p2.getY());
 754:         top.lineTo((float) p3.getX(), (float) p3.getY());
 755:         top.closePath();
 756: 
 757:         GeneralPath bottom = new GeneralPath();
 758:         bottom.moveTo((float) p00.getX(), (float) p00.getY());
 759:         bottom.lineTo((float) p01.getX(), (float) p01.getY());
 760:         bottom.lineTo((float) p02.getX(), (float) p02.getY());
 761:         bottom.lineTo((float) p03.getX(), (float) p03.getY());
 762:         bottom.closePath();
 763: 
 764:         result[0] = bottom;
 765:         result[1] = back;
 766:         result[2] = left;
 767:         result[3] = right;
 768:         result[4] = top;
 769:         result[5] = front;
 770:         if (inverted) {
 771:             result[0] = top;
 772:             result[4] = bottom;
 773:         }
 774:         return result;
 775:     }
 776: 
 777:     /**
 778:      * Tests this renderer for equality with an arbitrary object.
 779:      *
 780:      * @param obj  the object (<code>null</code> permitted).
 781:      *
 782:      * @return A boolean.
 783:      */
 784:     public boolean equals(Object obj) {
 785:         if (obj == this) {
 786:             return true;
 787:         }
 788:         if (!(obj instanceof StackedBarRenderer3D)) {
 789:             return false;
 790:         }
 791:         if (!super.equals(obj)) {
 792:             return false;
 793:         }
 794:         StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
 795:         if (this.renderAsPercentages != that.getRenderAsPercentages()) {
 796:             return false;
 797:         }
 798:         return true;
 799:     }
 800: 
 801: }