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-2007, 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-2007, 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:  * $Id: StackedBarRenderer3D.java,v 1.8.2.8 2007/01/18 14:49:52 mungady Exp $
  39:  *
  40:  * Changes
  41:  * -------
  42:  * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG);
  43:  * 15-Nov-2001 : Modified to allow for null data values (DG);
  44:  * 13-Dec-2001 : Added tooltips (DG);
  45:  * 15-Feb-2002 : Added isStacked() method (DG);
  46:  * 24-May-2002 : Incorporated tooltips into chart entities (DG);
  47:  * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG);
  48:  * 25-Jun-2002 : Removed redundant imports (DG);
  49:  * 26-Jun-2002 : Small change to entity (DG);
  50:  * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 
  51:  *               for HTML image maps (RA);
  52:  * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  53:  * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
  54:  *               CategoryToolTipGenerator interface (DG);
  55:  * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
  56:  * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
  57:  * 17-Jan-2003 : Moved plot classes to a separate package (DG);
  58:  * 25-Mar-2003 : Implemented Serializable (DG);
  59:  * 01-May-2003 : Added default constructor (bug 726235) and fixed bug 
  60:  *               726260) (DG);
  61:  * 13-May-2003 : Renamed StackedVerticalBarRenderer3D 
  62:  *               --> StackedBarRenderer3D (DG);
  63:  * 30-Jul-2003 : Modified entity constructor (CZ);
  64:  * 07-Oct-2003 : Added renderer state (DG);
  65:  * 21-Nov-2003 : Added a new constructor (DG);
  66:  * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG);
  67:  * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG);
  68:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  69:  * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
  70:  * 18-Mar-2005 : Override for getPassCount() method (DG);
  71:  * 20-Apr-2005 : Renamed CategoryLabelGenerator 
  72:  *               --> CategoryItemLabelGenerator (DG);
  73:  * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
  74:  * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
  75:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  76:  * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted
  77:  *               by Max Herfort (DG);
  78:  * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG);
  79:  * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList() 
  80:  *               method (DG);
  81:  * 18-Jan-2007 : Updated block drawing code to take account of inverted axes,
  82:  *               see bug report 1599652 (DG);
  83:  *               
  84:  */
  85: 
  86: package org.jfree.chart.renderer.category;
  87: 
  88: import java.awt.Graphics2D;
  89: import java.awt.Paint;
  90: import java.awt.Shape;
  91: import java.awt.geom.GeneralPath;
  92: import java.awt.geom.Point2D;
  93: import java.awt.geom.Rectangle2D;
  94: import java.io.Serializable;
  95: import java.util.ArrayList;
  96: import java.util.List;
  97: 
  98: import org.jfree.chart.axis.CategoryAxis;
  99: import org.jfree.chart.axis.ValueAxis;
 100: import org.jfree.chart.entity.EntityCollection;
 101: import org.jfree.chart.event.RendererChangeEvent;
 102: import org.jfree.chart.labels.CategoryItemLabelGenerator;
 103: import org.jfree.chart.plot.CategoryPlot;
 104: import org.jfree.chart.plot.PlotOrientation;
 105: import org.jfree.data.DataUtilities;
 106: import org.jfree.data.Range;
 107: import org.jfree.data.category.CategoryDataset;
 108: import org.jfree.data.general.DatasetUtilities;
 109: import org.jfree.util.BooleanUtilities;
 110: import org.jfree.util.PublicCloneable;
 111: 
 112: /**
 113:  * Renders stacked bars with 3D-effect, for use with the 
 114:  * {@link org.jfree.chart.plot.CategoryPlot} class.
 115:  */
 116: public class StackedBarRenderer3D extends BarRenderer3D 
 117:                                   implements Cloneable, PublicCloneable, 
 118:                                              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:         notifyListeners(new RendererChangeEvent(this));
 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 outlinePaint = getItemOutlinePaint(series, column);
 444:             g2.setStroke(getItemOutlineStroke(series, column));
 445:             
 446:             for (int f = 0; f < 6; f++) {
 447:                 g2.setPaint(fillPaint);
 448:                 g2.fill(faces[f]);
 449:                 g2.setPaint(outlinePaint);
 450:                 g2.draw(faces[f]); 
 451:             }
 452:                         
 453:             itemLabelList.add(new Object[] {new Integer(series), 
 454:                     faces[5].getBounds2D(), 
 455:                     BooleanUtilities.valueOf(v0 < getBase())});
 456: 
 457:             // add an item entity, if this information is being collected
 458:             EntityCollection entities = state.getEntityCollection();
 459:             if (entities != null) {
 460:                 addItemEntity(entities, dataset, series, column, faces[5]);
 461:             }
 462: 
 463:         }        
 464: 
 465:         for (int i = 0; i < itemLabelList.size(); i++) {
 466:             Object[] record = (Object[]) itemLabelList.get(i);
 467:             int series = ((Integer) record[0]).intValue();
 468:             Rectangle2D bar = (Rectangle2D) record[1];
 469:             boolean neg = ((Boolean) record[2]).booleanValue();
 470:             CategoryItemLabelGenerator generator 
 471:                     = getItemLabelGenerator(series, column);
 472:             if (generator != null && isItemLabelVisible(series, column)) {
 473:                 drawItemLabel(g2, dataset, series, column, plot, generator, 
 474:                         bar, neg);
 475:             }
 476: 
 477:         }
 478:     }
 479:     
 480:     /**
 481:      * Creates an array of shapes representing the six sides of a block in a
 482:      * horizontal stack.
 483:      * 
 484:      * @param x0  left edge of bar (in Java2D space).
 485:      * @param width  the width of the bar (in Java2D units).
 486:      * @param y0  the base of the block (in Java2D space).
 487:      * @param y1  the top of the block (in Java2D space).
 488:      * @param inverted  a flag indicating whether or not the block is inverted
 489:      *     (this changes the order of the faces of the block).
 490:      * 
 491:      * @return The sides of the block.
 492:      */
 493:     private Shape[] createHorizontalBlock(double x0, double width, double y0, 
 494:             double y1, boolean inverted) {
 495:         Shape[] result = new Shape[6];
 496:         Point2D p00 = new Point2D.Double(y0, x0);
 497:         Point2D p01 = new Point2D.Double(y0, x0 + width);
 498:         Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 
 499:                 p01.getY() - getYOffset());
 500:         Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 
 501:                 p00.getY() - getYOffset());
 502: 
 503:         Point2D p0 = new Point2D.Double(y1, x0);
 504:         Point2D p1 = new Point2D.Double(y1, x0 + width);
 505:         Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 
 506:                 p1.getY() - getYOffset());
 507:         Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 
 508:                 p0.getY() - getYOffset());
 509:         
 510:         GeneralPath bottom = new GeneralPath();
 511:         bottom.moveTo((float) p1.getX(), (float) p1.getY());
 512:         bottom.lineTo((float) p01.getX(), (float) p01.getY());
 513:         bottom.lineTo((float) p02.getX(), (float) p02.getY());
 514:         bottom.lineTo((float) p2.getX(), (float) p2.getY());
 515:         bottom.closePath();
 516:         
 517:         GeneralPath top = new GeneralPath();
 518:         top.moveTo((float) p0.getX(), (float) p0.getY());
 519:         top.lineTo((float) p00.getX(), (float) p00.getY());
 520:         top.lineTo((float) p03.getX(), (float) p03.getY());
 521:         top.lineTo((float) p3.getX(), (float) p3.getY());
 522:         top.closePath();
 523: 
 524:         GeneralPath back = new GeneralPath();
 525:         back.moveTo((float) p2.getX(), (float) p2.getY());
 526:         back.lineTo((float) p02.getX(), (float) p02.getY());
 527:         back.lineTo((float) p03.getX(), (float) p03.getY());
 528:         back.lineTo((float) p3.getX(), (float) p3.getY());
 529:         back.closePath();
 530:         
 531:         GeneralPath front = new GeneralPath();
 532:         front.moveTo((float) p0.getX(), (float) p0.getY());
 533:         front.lineTo((float) p1.getX(), (float) p1.getY());
 534:         front.lineTo((float) p01.getX(), (float) p01.getY());
 535:         front.lineTo((float) p00.getX(), (float) p00.getY());
 536:         front.closePath();
 537: 
 538:         GeneralPath left = new GeneralPath();
 539:         left.moveTo((float) p0.getX(), (float) p0.getY());
 540:         left.lineTo((float) p1.getX(), (float) p1.getY());
 541:         left.lineTo((float) p2.getX(), (float) p2.getY());
 542:         left.lineTo((float) p3.getX(), (float) p3.getY());
 543:         left.closePath();
 544:         
 545:         GeneralPath right = new GeneralPath();
 546:         right.moveTo((float) p00.getX(), (float) p00.getY());
 547:         right.lineTo((float) p01.getX(), (float) p01.getY());
 548:         right.lineTo((float) p02.getX(), (float) p02.getY());
 549:         right.lineTo((float) p03.getX(), (float) p03.getY());
 550:         right.closePath();
 551:         result[0] = bottom;
 552:         result[1] = back;
 553:         if (inverted) {
 554:             result[2] = right;
 555:             result[3] = left;
 556:         }
 557:         else {
 558:             result[2] = left;
 559:             result[3] = right;
 560:         }
 561:         result[4] = top;
 562:         result[5] = front;
 563:         return result;
 564:     }
 565:     
 566:     /**
 567:      * Draws a stack of bars for one category, with a vertical orientation.
 568:      * 
 569:      * @param values  the value list.
 570:      * @param category  the category.
 571:      * @param g2  the graphics device.
 572:      * @param state  the state.
 573:      * @param dataArea  the data area (adjusted for the 3D effect).
 574:      * @param plot  the plot.
 575:      * @param domainAxis  the domain axis.
 576:      * @param rangeAxis  the range axis.
 577:      * @param dataset  the dataset.
 578:      *
 579:      * @since 1.0.4
 580:      */
 581:     protected void drawStackVertical(List values, Comparable category, 
 582:             Graphics2D g2, CategoryItemRendererState state, 
 583:             Rectangle2D dataArea, CategoryPlot plot, 
 584:             CategoryAxis domainAxis, ValueAxis rangeAxis, 
 585:             CategoryDataset dataset) {
 586:         
 587:         int column = dataset.getColumnIndex(category);
 588:         double barX0 = domainAxis.getCategoryMiddle(column, 
 589:                 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 
 590:                 - state.getBarWidth() / 2.0;
 591:         double barW = state.getBarWidth();
 592: 
 593:         // a list to store the series index and bar region, so we can draw
 594:         // all the labels at the end...
 595:         List itemLabelList = new ArrayList();
 596:         
 597:         // draw the blocks
 598:         boolean inverted = rangeAxis.isInverted();
 599:         int blockCount = values.size() - 1;
 600:         for (int k = 0; k < blockCount; k++) {
 601:             int index = (inverted ? blockCount - k - 1 : k);
 602:             Object[] prev = (Object[]) values.get(index);
 603:             Object[] curr = (Object[]) values.get(index + 1);
 604:             int series = 0;
 605:             if (curr[0] == null) {
 606:                 series = -((Integer) prev[0]).intValue();
 607:             }
 608:             else {
 609:                 series = ((Integer) curr[0]).intValue();
 610:                 if (series < 0) {
 611:                     series = -((Integer) prev[0]).intValue();
 612:                 }
 613:             }
 614:             double v0 = ((Double) prev[1]).doubleValue();
 615:             double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 
 616:                     plot.getRangeAxisEdge());
 617: 
 618:             double v1 = ((Double) curr[1]).doubleValue();
 619:             double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 
 620:                     plot.getRangeAxisEdge());
 621:             
 622:             Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1, 
 623:                     inverted);
 624:             Paint fillPaint = getItemPaint(series, column);
 625:             Paint outlinePaint = getItemOutlinePaint(series, column);
 626:             g2.setStroke(getItemOutlineStroke(series, column));
 627:             
 628:             for (int f = 0; f < 6; f++) {
 629:                 g2.setPaint(fillPaint);
 630:                 g2.fill(faces[f]);
 631:                 g2.setPaint(outlinePaint);
 632:                 g2.draw(faces[f]); 
 633:             }
 634: 
 635:             itemLabelList.add(new Object[] {new Integer(series), 
 636:                     faces[5].getBounds2D(), 
 637:                     BooleanUtilities.valueOf(v0 < getBase())});
 638:             
 639:             // add an item entity, if this information is being collected
 640:             EntityCollection entities = state.getEntityCollection();
 641:             if (entities != null) {
 642:                 addItemEntity(entities, dataset, series, column, faces[5]);
 643:             }
 644: 
 645:         }
 646:         
 647:         for (int i = 0; i < itemLabelList.size(); i++) {
 648:             Object[] record = (Object[]) itemLabelList.get(i);
 649:             int series = ((Integer) record[0]).intValue();
 650:             Rectangle2D bar = (Rectangle2D) record[1];
 651:             boolean neg = ((Boolean) record[2]).booleanValue();
 652:             CategoryItemLabelGenerator generator 
 653:                     = getItemLabelGenerator(series, column);
 654:             if (generator != null && isItemLabelVisible(series, column)) {
 655:                 drawItemLabel(g2, dataset, series, column, plot, generator, 
 656:                         bar, neg);
 657:             }
 658: 
 659:         }
 660:     }
 661:     
 662:     /**
 663:      * Creates an array of shapes representing the six sides of a block in a
 664:      * vertical stack.
 665:      * 
 666:      * @param x0  left edge of bar (in Java2D space).
 667:      * @param width  the width of the bar (in Java2D units).
 668:      * @param y0  the base of the block (in Java2D space).
 669:      * @param y1  the top of the block (in Java2D space).
 670:      * @param inverted  a flag indicating whether or not the block is inverted
 671:      *     (this changes the order of the faces of the block).
 672:      * 
 673:      * @return The sides of the block.
 674:      */
 675:     private Shape[] createVerticalBlock(double x0, double width, double y0, 
 676:             double y1, boolean inverted) {
 677:         Shape[] result = new Shape[6];
 678:         Point2D p00 = new Point2D.Double(x0, y0);
 679:         Point2D p01 = new Point2D.Double(x0 + width, y0);
 680:         Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 
 681:                 p01.getY() - getYOffset());
 682:         Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 
 683:                 p00.getY() - getYOffset());
 684: 
 685: 
 686:         Point2D p0 = new Point2D.Double(x0, y1);
 687:         Point2D p1 = new Point2D.Double(x0 + width, y1);
 688:         Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 
 689:                 p1.getY() - getYOffset());
 690:         Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 
 691:                 p0.getY() - getYOffset());
 692:         
 693:         GeneralPath right = new GeneralPath();
 694:         right.moveTo((float) p1.getX(), (float) p1.getY());
 695:         right.lineTo((float) p01.getX(), (float) p01.getY());
 696:         right.lineTo((float) p02.getX(), (float) p02.getY());
 697:         right.lineTo((float) p2.getX(), (float) p2.getY());
 698:         right.closePath();
 699:         
 700:         GeneralPath left = new GeneralPath();
 701:         left.moveTo((float) p0.getX(), (float) p0.getY());
 702:         left.lineTo((float) p00.getX(), (float) p00.getY());
 703:         left.lineTo((float) p03.getX(), (float) p03.getY());
 704:         left.lineTo((float) p3.getX(), (float) p3.getY());
 705:         left.closePath();
 706: 
 707:         GeneralPath back = new GeneralPath();
 708:         back.moveTo((float) p2.getX(), (float) p2.getY());
 709:         back.lineTo((float) p02.getX(), (float) p02.getY());
 710:         back.lineTo((float) p03.getX(), (float) p03.getY());
 711:         back.lineTo((float) p3.getX(), (float) p3.getY());
 712:         back.closePath();
 713:         
 714:         GeneralPath front = new GeneralPath();
 715:         front.moveTo((float) p0.getX(), (float) p0.getY());
 716:         front.lineTo((float) p1.getX(), (float) p1.getY());
 717:         front.lineTo((float) p01.getX(), (float) p01.getY());
 718:         front.lineTo((float) p00.getX(), (float) p00.getY());
 719:         front.closePath();
 720: 
 721:         GeneralPath top = new GeneralPath();
 722:         top.moveTo((float) p0.getX(), (float) p0.getY());
 723:         top.lineTo((float) p1.getX(), (float) p1.getY());
 724:         top.lineTo((float) p2.getX(), (float) p2.getY());
 725:         top.lineTo((float) p3.getX(), (float) p3.getY());
 726:         top.closePath();
 727:         
 728:         GeneralPath bottom = new GeneralPath();
 729:         bottom.moveTo((float) p00.getX(), (float) p00.getY());
 730:         bottom.lineTo((float) p01.getX(), (float) p01.getY());
 731:         bottom.lineTo((float) p02.getX(), (float) p02.getY());
 732:         bottom.lineTo((float) p03.getX(), (float) p03.getY());
 733:         bottom.closePath();
 734:         
 735:         result[0] = bottom;
 736:         result[1] = back;
 737:         result[2] = left;
 738:         result[3] = right;
 739:         result[4] = top;
 740:         result[5] = front;
 741:         if (inverted) {
 742:             result[0] = top;
 743:             result[4] = bottom;
 744:         }
 745:         return result;
 746:     }
 747:     
 748:     /**
 749:      * Tests this renderer for equality with an arbitrary object.
 750:      * 
 751:      * @param obj  the object (<code>null</code> permitted).
 752:      * 
 753:      * @return A boolean.
 754:      */
 755:     public boolean equals(Object obj) {
 756:         if (obj == this) {
 757:             return true;   
 758:         }
 759:         if (!(obj instanceof StackedBarRenderer3D)) {
 760:             return false;   
 761:         }
 762:         if (!super.equals(obj)) {
 763:             return false;   
 764:         }
 765:         StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
 766:         if (this.renderAsPercentages != that.getRenderAsPercentages()) {
 767:             return false;   
 768:         }
 769:         return true;
 770:     }
 771: 
 772: }