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:  * 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, 
 119:                                              Serializable {
 120: 
 121:     /** For serialization. */
 122:     private static final long serialVersionUID = -5832945916493247123L;
 123:     
 124:     /** A flag that controls whether the bars display values or percentages. */
 125:     private boolean renderAsPercentages;
 126:     
 127:     /**
 128:      * Creates a new renderer with no tool tip generator and no URL generator.
 129:      * <P>
 130:      * The defaults (no tool tip or URL generators) have been chosen to 
 131:      * minimise the processing required to generate a default chart.  If you 
 132:      * require tool tips or URLs, then you can easily add the required 
 133:      * generators.
 134:      */
 135:     public StackedBarRenderer3D() {
 136:         this(false);
 137:     }
 138: 
 139:     /**
 140:      * Constructs a new renderer with the specified '3D effect'.
 141:      *
 142:      * @param xOffset  the x-offset for the 3D effect.
 143:      * @param yOffset  the y-offset for the 3D effect.
 144:      */
 145:     public StackedBarRenderer3D(double xOffset, double yOffset) {
 146:         super(xOffset, yOffset);
 147:     }
 148:     
 149:     /**
 150:      * Creates a new renderer.
 151:      * 
 152:      * @param renderAsPercentages  a flag that controls whether the data values
 153:      *                             are rendered as percentages.
 154:      * 
 155:      * @since 1.0.2
 156:      */
 157:     public StackedBarRenderer3D(boolean renderAsPercentages) {
 158:         super();
 159:         this.renderAsPercentages = renderAsPercentages;
 160:     }
 161:     
 162:     /**
 163:      * Constructs a new renderer with the specified '3D effect'.
 164:      *
 165:      * @param xOffset  the x-offset for the 3D effect.
 166:      * @param yOffset  the y-offset for the 3D effect.
 167:      * @param renderAsPercentages  a flag that controls whether the data values
 168:      *                             are rendered as percentages.
 169:      * 
 170:      * @since 1.0.2
 171:      */
 172:     public StackedBarRenderer3D(double xOffset, double yOffset, 
 173:             boolean renderAsPercentages) {
 174:         super(xOffset, yOffset);
 175:         this.renderAsPercentages = renderAsPercentages;
 176:     }
 177:     
 178:     /**
 179:      * Returns <code>true</code> if the renderer displays each item value as
 180:      * a percentage (so that the stacked bars add to 100%), and 
 181:      * <code>false</code> otherwise.
 182:      * 
 183:      * @return A boolean.
 184:      *
 185:      * @since 1.0.2
 186:      */
 187:     public boolean getRenderAsPercentages() {
 188:         return this.renderAsPercentages;   
 189:     }
 190:     
 191:     /**
 192:      * Sets the flag that controls whether the renderer displays each item
 193:      * value as a percentage (so that the stacked bars add to 100%), and sends
 194:      * a {@link RendererChangeEvent} to all registered listeners.
 195:      * 
 196:      * @param asPercentages  the flag.
 197:      *
 198:      * @since 1.0.2
 199:      */
 200:     public void setRenderAsPercentages(boolean asPercentages) {
 201:         this.renderAsPercentages = asPercentages; 
 202:         notifyListeners(new RendererChangeEvent(this));
 203:     }
 204: 
 205:     /**
 206:      * Returns the range of values the renderer requires to display all the 
 207:      * items from the specified dataset.
 208:      * 
 209:      * @param dataset  the dataset (<code>null</code> not permitted).
 210:      * 
 211:      * @return The range (or <code>null</code> if the dataset is empty).
 212:      */
 213:     public Range findRangeBounds(CategoryDataset dataset) {
 214:         if (this.renderAsPercentages) {
 215:             return new Range(0.0, 1.0);   
 216:         }
 217:         else {
 218:             return DatasetUtilities.findStackedRangeBounds(dataset);
 219:         }
 220:     }
 221: 
 222:     /**
 223:      * Calculates the bar width and stores it in the renderer state.
 224:      * 
 225:      * @param plot  the plot.
 226:      * @param dataArea  the data area.
 227:      * @param rendererIndex  the renderer index.
 228:      * @param state  the renderer state.
 229:      */
 230:     protected void calculateBarWidth(CategoryPlot plot, 
 231:                                      Rectangle2D dataArea, 
 232:                                      int rendererIndex,
 233:                                      CategoryItemRendererState state) {
 234: 
 235:         // calculate the bar width
 236:         CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
 237:         CategoryDataset data = plot.getDataset(rendererIndex);
 238:         if (data != null) {
 239:             PlotOrientation orientation = plot.getOrientation();
 240:             double space = 0.0;
 241:             if (orientation == PlotOrientation.HORIZONTAL) {
 242:                 space = dataArea.getHeight();
 243:             }
 244:             else if (orientation == PlotOrientation.VERTICAL) {
 245:                 space = dataArea.getWidth();
 246:             }
 247:             double maxWidth = space * getMaximumBarWidth();
 248:             int columns = data.getColumnCount();
 249:             double categoryMargin = 0.0;
 250:             if (columns > 1) {
 251:                 categoryMargin = domainAxis.getCategoryMargin();
 252:             }
 253: 
 254:             double used = space * (1 - domainAxis.getLowerMargin() 
 255:                                      - domainAxis.getUpperMargin()
 256:                                      - categoryMargin);
 257:             if (columns > 0) {
 258:                 state.setBarWidth(Math.min(used / columns, maxWidth));
 259:             }
 260:             else {
 261:                 state.setBarWidth(Math.min(used, maxWidth));
 262:             }
 263:         }
 264: 
 265:     }
 266:     
 267:     /**
 268:      * Returns a list containing the stacked values for the specified series
 269:      * in the given dataset, plus the supplied base value.
 270:      *  
 271:      * @param dataset  the dataset (<code>null</code> not permitted).
 272:      * @param category  the category key (<code>null</code> not permitted).
 273:      * @param base  the base value.
 274:      * @param asPercentages  a flag that controls whether the values in the
 275:      *     list are converted to percentages of the total.
 276:      *     
 277:      * @return The value list.
 278:      *
 279:      * @since 1.0.4
 280:      */
 281:     protected static List createStackedValueList(CategoryDataset dataset, 
 282:             Comparable category, double base, boolean asPercentages) {
 283:         
 284:         List result = new ArrayList();
 285:         double posBase = base;
 286:         double negBase = base;
 287:         double total = 0.0;
 288:         if (asPercentages) {
 289:             total = DataUtilities.calculateColumnTotal(dataset, 
 290:                     dataset.getColumnIndex(category));
 291:         }
 292: 
 293:         int baseIndex = -1;
 294:         int seriesCount = dataset.getRowCount();
 295:         for (int s = 0; s < seriesCount; s++) {
 296:             Number n = dataset.getValue(dataset.getRowKey(s), category);
 297:             if (n == null) {
 298:                 continue;
 299:             }
 300:             double v = n.doubleValue();
 301:             if (asPercentages) {
 302:                 v = v / total;
 303:             }
 304:             if (v >= 0.0) {
 305:                 if (baseIndex < 0) {
 306:                     result.add(new Object[] {null, new Double(base)});
 307:                     baseIndex = 0;
 308:                 }
 309:                 posBase = posBase + v;
 310:                 result.add(new Object[] {new Integer(s), new Double(posBase)});
 311:             }
 312:             else if (v < 0.0) {
 313:                 if (baseIndex < 0) {
 314:                     result.add(new Object[] {null, new Double(base)});
 315:                     baseIndex = 0;
 316:                 }
 317:                 negBase = negBase + v; // '+' because v is negative
 318:                 result.add(0, new Object[] {new Integer(-s), 
 319:                         new Double(negBase)});
 320:                 baseIndex++;
 321:             }
 322:         }
 323:         return result;
 324:         
 325:     }
 326:     
 327:     /**
 328:      * Draws the visual representation of one data item from the chart (in 
 329:      * fact, this method does nothing until it reaches the last item for each
 330:      * category, at which point it draws all the items for that category).
 331:      *
 332:      * @param g2  the graphics device.
 333:      * @param state  the renderer state.
 334:      * @param dataArea  the plot area.
 335:      * @param plot  the plot.
 336:      * @param domainAxis  the domain (category) axis.
 337:      * @param rangeAxis  the range (value) axis.
 338:      * @param dataset  the data.
 339:      * @param row  the row index (zero-based).
 340:      * @param column  the column index (zero-based).
 341:      * @param pass  the pass index.
 342:      */
 343:     public void drawItem(Graphics2D g2,
 344:                          CategoryItemRendererState state,
 345:                          Rectangle2D dataArea,
 346:                          CategoryPlot plot,
 347:                          CategoryAxis domainAxis,
 348:                          ValueAxis rangeAxis,
 349:                          CategoryDataset dataset,
 350:                          int row,
 351:                          int column,
 352:                          int pass) {
 353: 
 354:         // wait till we are at the last item for the row then draw the
 355:         // whole stack at once
 356:         if (row < dataset.getRowCount() - 1) {
 357:             return;
 358:         }
 359:         Comparable category = dataset.getColumnKey(column);
 360:         
 361:         List values = createStackedValueList(dataset, 
 362:                 dataset.getColumnKey(column), getBase(), 
 363:                 this.renderAsPercentages);
 364:         
 365:         Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 
 366:                 dataArea.getY() + getYOffset(), 
 367:                 dataArea.getWidth() - getXOffset(), 
 368:                 dataArea.getHeight() - getYOffset());
 369: 
 370: 
 371:         PlotOrientation orientation = plot.getOrientation();
 372: 
 373:         // handle rendering separately for the two plot orientations...
 374:         if (orientation == PlotOrientation.HORIZONTAL) {
 375:             drawStackHorizontal(values, category, g2, state, adjusted, plot, 
 376:                     domainAxis, rangeAxis, dataset);
 377:         }
 378:         else {
 379:             drawStackVertical(values, category, g2, state, adjusted, plot, 
 380:                     domainAxis, rangeAxis, dataset);
 381:         }
 382: 
 383:     }
 384:     
 385:     /**
 386:      * Draws a stack of bars for one category, with a horizontal orientation.
 387:      * 
 388:      * @param values  the value list.
 389:      * @param category  the category.
 390:      * @param g2  the graphics device.
 391:      * @param state  the state.
 392:      * @param dataArea  the data area (adjusted for the 3D effect).
 393:      * @param plot  the plot.
 394:      * @param domainAxis  the domain axis.
 395:      * @param rangeAxis  the range axis.
 396:      * @param dataset  the dataset.
 397:      *
 398:      * @since 1.0.4
 399:      */
 400:     protected void drawStackHorizontal(List values, Comparable category, 
 401:             Graphics2D g2, CategoryItemRendererState state, 
 402:             Rectangle2D dataArea, CategoryPlot plot, 
 403:             CategoryAxis domainAxis, ValueAxis rangeAxis, 
 404:             CategoryDataset dataset) {
 405:         
 406:         int column = dataset.getColumnIndex(category);
 407:         double barX0 = domainAxis.getCategoryMiddle(column, 
 408:                 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 
 409:                 - state.getBarWidth() / 2.0;
 410:         double barW = state.getBarWidth();
 411:         
 412:         // a list to store the series index and bar region, so we can draw
 413:         // all the labels at the end...
 414:         List itemLabelList = new ArrayList();
 415:         
 416:         // draw the blocks
 417:         boolean inverted = rangeAxis.isInverted();
 418:         int blockCount = values.size() - 1;
 419:         for (int k = 0; k < blockCount; k++) {
 420:             int index = (inverted ? blockCount - k - 1 : k);
 421:             Object[] prev = (Object[]) values.get(index);
 422:             Object[] curr = (Object[]) values.get(index + 1);
 423:             int series = 0;
 424:             if (curr[0] == null) {
 425:                 series = -((Integer) prev[0]).intValue();
 426:             }
 427:             else {
 428:                 series = ((Integer) curr[0]).intValue();
 429:                 if (series < 0) {
 430:                     series = -((Integer) prev[0]).intValue();
 431:                 }
 432:             }
 433:             double v0 = ((Double) prev[1]).doubleValue();
 434:             double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 
 435:                     plot.getRangeAxisEdge());
 436: 
 437:             double v1 = ((Double) curr[1]).doubleValue();
 438:             double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 
 439:                     plot.getRangeAxisEdge());
 440: 
 441:             Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1, 
 442:                     inverted);
 443:             Paint fillPaint = getItemPaint(series, column);
 444:             Paint fillPaintDark = fillPaint;
 445:             if (fillPaintDark instanceof Color) {
 446:                 fillPaintDark = ((Color) fillPaint).darker();
 447:             }
 448:             boolean drawOutlines = isDrawBarOutline();
 449:             Paint outlinePaint = fillPaint;
 450:             if (drawOutlines) {
 451:                 outlinePaint = getItemOutlinePaint(series, column);
 452:                 g2.setStroke(getItemOutlineStroke(series, column));
 453:             }
 454:             for (int f = 0; f < 6; f++) {
 455:                 if (f == 5) {
 456:                     g2.setPaint(fillPaint);
 457:                 }
 458:                 else {
 459:                     g2.setPaint(fillPaintDark);
 460:                 }
 461:                 g2.fill(faces[f]);
 462:                 if (drawOutlines) {
 463:                     g2.setPaint(outlinePaint);
 464:                     g2.draw(faces[f]);
 465:                 }
 466:             }
 467:                         
 468:             itemLabelList.add(new Object[] {new Integer(series), 
 469:                     faces[5].getBounds2D(), 
 470:                     BooleanUtilities.valueOf(v0 < getBase())});
 471: 
 472:             // add an item entity, if this information is being collected
 473:             EntityCollection entities = state.getEntityCollection();
 474:             if (entities != null) {
 475:                 addItemEntity(entities, dataset, series, column, faces[5]);
 476:             }
 477: 
 478:         }        
 479: 
 480:         for (int i = 0; i < itemLabelList.size(); i++) {
 481:             Object[] record = (Object[]) itemLabelList.get(i);
 482:             int series = ((Integer) record[0]).intValue();
 483:             Rectangle2D bar = (Rectangle2D) record[1];
 484:             boolean neg = ((Boolean) record[2]).booleanValue();
 485:             CategoryItemLabelGenerator generator 
 486:                     = getItemLabelGenerator(series, column);
 487:             if (generator != null && isItemLabelVisible(series, column)) {
 488:                 drawItemLabel(g2, dataset, series, column, plot, generator, 
 489:                         bar, neg);
 490:             }
 491: 
 492:         }
 493:     }
 494:     
 495:     /**
 496:      * Creates an array of shapes representing the six sides of a block in a
 497:      * horizontal stack.
 498:      * 
 499:      * @param x0  left edge of bar (in Java2D space).
 500:      * @param width  the width of the bar (in Java2D units).
 501:      * @param y0  the base of the block (in Java2D space).
 502:      * @param y1  the top of the block (in Java2D space).
 503:      * @param inverted  a flag indicating whether or not the block is inverted
 504:      *     (this changes the order of the faces of the block).
 505:      * 
 506:      * @return The sides of the block.
 507:      */
 508:     private Shape[] createHorizontalBlock(double x0, double width, double y0, 
 509:             double y1, boolean inverted) {
 510:         Shape[] result = new Shape[6];
 511:         Point2D p00 = new Point2D.Double(y0, x0);
 512:         Point2D p01 = new Point2D.Double(y0, x0 + width);
 513:         Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 
 514:                 p01.getY() - getYOffset());
 515:         Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 
 516:                 p00.getY() - getYOffset());
 517: 
 518:         Point2D p0 = new Point2D.Double(y1, x0);
 519:         Point2D p1 = new Point2D.Double(y1, x0 + width);
 520:         Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 
 521:                 p1.getY() - getYOffset());
 522:         Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 
 523:                 p0.getY() - getYOffset());
 524:         
 525:         GeneralPath bottom = new GeneralPath();
 526:         bottom.moveTo((float) p1.getX(), (float) p1.getY());
 527:         bottom.lineTo((float) p01.getX(), (float) p01.getY());
 528:         bottom.lineTo((float) p02.getX(), (float) p02.getY());
 529:         bottom.lineTo((float) p2.getX(), (float) p2.getY());
 530:         bottom.closePath();
 531:         
 532:         GeneralPath top = new GeneralPath();
 533:         top.moveTo((float) p0.getX(), (float) p0.getY());
 534:         top.lineTo((float) p00.getX(), (float) p00.getY());
 535:         top.lineTo((float) p03.getX(), (float) p03.getY());
 536:         top.lineTo((float) p3.getX(), (float) p3.getY());
 537:         top.closePath();
 538: 
 539:         GeneralPath back = new GeneralPath();
 540:         back.moveTo((float) p2.getX(), (float) p2.getY());
 541:         back.lineTo((float) p02.getX(), (float) p02.getY());
 542:         back.lineTo((float) p03.getX(), (float) p03.getY());
 543:         back.lineTo((float) p3.getX(), (float) p3.getY());
 544:         back.closePath();
 545:         
 546:         GeneralPath front = new GeneralPath();
 547:         front.moveTo((float) p0.getX(), (float) p0.getY());
 548:         front.lineTo((float) p1.getX(), (float) p1.getY());
 549:         front.lineTo((float) p01.getX(), (float) p01.getY());
 550:         front.lineTo((float) p00.getX(), (float) p00.getY());
 551:         front.closePath();
 552: 
 553:         GeneralPath left = new GeneralPath();
 554:         left.moveTo((float) p0.getX(), (float) p0.getY());
 555:         left.lineTo((float) p1.getX(), (float) p1.getY());
 556:         left.lineTo((float) p2.getX(), (float) p2.getY());
 557:         left.lineTo((float) p3.getX(), (float) p3.getY());
 558:         left.closePath();
 559:         
 560:         GeneralPath right = new GeneralPath();
 561:         right.moveTo((float) p00.getX(), (float) p00.getY());
 562:         right.lineTo((float) p01.getX(), (float) p01.getY());
 563:         right.lineTo((float) p02.getX(), (float) p02.getY());
 564:         right.lineTo((float) p03.getX(), (float) p03.getY());
 565:         right.closePath();
 566:         result[0] = bottom;
 567:         result[1] = back;
 568:         if (inverted) {
 569:             result[2] = right;
 570:             result[3] = left;
 571:         }
 572:         else {
 573:             result[2] = left;
 574:             result[3] = right;
 575:         }
 576:         result[4] = top;
 577:         result[5] = front;
 578:         return result;
 579:     }
 580:     
 581:     /**
 582:      * Draws a stack of bars for one category, with a vertical orientation.
 583:      * 
 584:      * @param values  the value list.
 585:      * @param category  the category.
 586:      * @param g2  the graphics device.
 587:      * @param state  the state.
 588:      * @param dataArea  the data area (adjusted for the 3D effect).
 589:      * @param plot  the plot.
 590:      * @param domainAxis  the domain axis.
 591:      * @param rangeAxis  the range axis.
 592:      * @param dataset  the dataset.
 593:      *
 594:      * @since 1.0.4
 595:      */
 596:     protected void drawStackVertical(List values, Comparable category, 
 597:             Graphics2D g2, CategoryItemRendererState state, 
 598:             Rectangle2D dataArea, CategoryPlot plot, 
 599:             CategoryAxis domainAxis, ValueAxis rangeAxis, 
 600:             CategoryDataset dataset) {
 601:         
 602:         int column = dataset.getColumnIndex(category);
 603:         double barX0 = domainAxis.getCategoryMiddle(column, 
 604:                 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 
 605:                 - state.getBarWidth() / 2.0;
 606:         double barW = state.getBarWidth();
 607: 
 608:         // a list to store the series index and bar region, so we can draw
 609:         // all the labels at the end...
 610:         List itemLabelList = new ArrayList();
 611:         
 612:         // draw the blocks
 613:         boolean inverted = rangeAxis.isInverted();
 614:         int blockCount = values.size() - 1;
 615:         for (int k = 0; k < blockCount; k++) {
 616:             int index = (inverted ? blockCount - k - 1 : k);
 617:             Object[] prev = (Object[]) values.get(index);
 618:             Object[] curr = (Object[]) values.get(index + 1);
 619:             int series = 0;
 620:             if (curr[0] == null) {
 621:                 series = -((Integer) prev[0]).intValue();
 622:             }
 623:             else {
 624:                 series = ((Integer) curr[0]).intValue();
 625:                 if (series < 0) {
 626:                     series = -((Integer) prev[0]).intValue();
 627:                 }
 628:             }
 629:             double v0 = ((Double) prev[1]).doubleValue();
 630:             double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 
 631:                     plot.getRangeAxisEdge());
 632: 
 633:             double v1 = ((Double) curr[1]).doubleValue();
 634:             double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 
 635:                     plot.getRangeAxisEdge());
 636:             
 637:             Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1, 
 638:                     inverted);
 639:             Paint fillPaint = getItemPaint(series, column);
 640:             Paint fillPaintDark = fillPaint;
 641:             if (fillPaintDark instanceof Color) {
 642:                 fillPaintDark = ((Color) fillPaint).darker();
 643:             }
 644:             boolean drawOutlines = isDrawBarOutline();
 645:             Paint outlinePaint = fillPaint;
 646:             if (drawOutlines) {
 647:                 outlinePaint = getItemOutlinePaint(series, column);
 648:                 g2.setStroke(getItemOutlineStroke(series, column));
 649:             }
 650:             
 651:             for (int f = 0; f < 6; f++) {
 652:                 if (f == 5) {
 653:                     g2.setPaint(fillPaint);
 654:                 }
 655:                 else {
 656:                     g2.setPaint(fillPaintDark);
 657:                 }
 658:                 g2.fill(faces[f]);
 659:                 if (drawOutlines) {
 660:                     g2.setPaint(outlinePaint);
 661:                     g2.draw(faces[f]);
 662:                 }
 663:             }
 664: 
 665:             itemLabelList.add(new Object[] {new Integer(series), 
 666:                     faces[5].getBounds2D(), 
 667:                     BooleanUtilities.valueOf(v0 < getBase())});
 668:             
 669:             // add an item entity, if this information is being collected
 670:             EntityCollection entities = state.getEntityCollection();
 671:             if (entities != null) {
 672:                 addItemEntity(entities, dataset, series, column, faces[5]);
 673:             }
 674: 
 675:         }
 676:         
 677:         for (int i = 0; i < itemLabelList.size(); i++) {
 678:             Object[] record = (Object[]) itemLabelList.get(i);
 679:             int series = ((Integer) record[0]).intValue();
 680:             Rectangle2D bar = (Rectangle2D) record[1];
 681:             boolean neg = ((Boolean) record[2]).booleanValue();
 682:             CategoryItemLabelGenerator generator 
 683:                     = getItemLabelGenerator(series, column);
 684:             if (generator != null && isItemLabelVisible(series, column)) {
 685:                 drawItemLabel(g2, dataset, series, column, plot, generator, 
 686:                         bar, neg);
 687:             }
 688: 
 689:         }
 690:     }
 691:     
 692:     /**
 693:      * Creates an array of shapes representing the six sides of a block in a
 694:      * vertical stack.
 695:      * 
 696:      * @param x0  left edge of bar (in Java2D space).
 697:      * @param width  the width of the bar (in Java2D units).
 698:      * @param y0  the base of the block (in Java2D space).
 699:      * @param y1  the top of the block (in Java2D space).
 700:      * @param inverted  a flag indicating whether or not the block is inverted
 701:      *     (this changes the order of the faces of the block).
 702:      * 
 703:      * @return The sides of the block.
 704:      */
 705:     private Shape[] createVerticalBlock(double x0, double width, double y0, 
 706:             double y1, boolean inverted) {
 707:         Shape[] result = new Shape[6];
 708:         Point2D p00 = new Point2D.Double(x0, y0);
 709:         Point2D p01 = new Point2D.Double(x0 + width, y0);
 710:         Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 
 711:                 p01.getY() - getYOffset());
 712:         Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 
 713:                 p00.getY() - getYOffset());
 714: 
 715: 
 716:         Point2D p0 = new Point2D.Double(x0, y1);
 717:         Point2D p1 = new Point2D.Double(x0 + width, y1);
 718:         Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 
 719:                 p1.getY() - getYOffset());
 720:         Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 
 721:                 p0.getY() - getYOffset());
 722:         
 723:         GeneralPath right = new GeneralPath();
 724:         right.moveTo((float) p1.getX(), (float) p1.getY());
 725:         right.lineTo((float) p01.getX(), (float) p01.getY());
 726:         right.lineTo((float) p02.getX(), (float) p02.getY());
 727:         right.lineTo((float) p2.getX(), (float) p2.getY());
 728:         right.closePath();
 729:         
 730:         GeneralPath left = new GeneralPath();
 731:         left.moveTo((float) p0.getX(), (float) p0.getY());
 732:         left.lineTo((float) p00.getX(), (float) p00.getY());
 733:         left.lineTo((float) p03.getX(), (float) p03.getY());
 734:         left.lineTo((float) p3.getX(), (float) p3.getY());
 735:         left.closePath();
 736: 
 737:         GeneralPath back = new GeneralPath();
 738:         back.moveTo((float) p2.getX(), (float) p2.getY());
 739:         back.lineTo((float) p02.getX(), (float) p02.getY());
 740:         back.lineTo((float) p03.getX(), (float) p03.getY());
 741:         back.lineTo((float) p3.getX(), (float) p3.getY());
 742:         back.closePath();
 743:         
 744:         GeneralPath front = new GeneralPath();
 745:         front.moveTo((float) p0.getX(), (float) p0.getY());
 746:         front.lineTo((float) p1.getX(), (float) p1.getY());
 747:         front.lineTo((float) p01.getX(), (float) p01.getY());
 748:         front.lineTo((float) p00.getX(), (float) p00.getY());
 749:         front.closePath();
 750: 
 751:         GeneralPath top = new GeneralPath();
 752:         top.moveTo((float) p0.getX(), (float) p0.getY());
 753:         top.lineTo((float) p1.getX(), (float) p1.getY());
 754:         top.lineTo((float) p2.getX(), (float) p2.getY());
 755:         top.lineTo((float) p3.getX(), (float) p3.getY());
 756:         top.closePath();
 757:         
 758:         GeneralPath bottom = new GeneralPath();
 759:         bottom.moveTo((float) p00.getX(), (float) p00.getY());
 760:         bottom.lineTo((float) p01.getX(), (float) p01.getY());
 761:         bottom.lineTo((float) p02.getX(), (float) p02.getY());
 762:         bottom.lineTo((float) p03.getX(), (float) p03.getY());
 763:         bottom.closePath();
 764:         
 765:         result[0] = bottom;
 766:         result[1] = back;
 767:         result[2] = left;
 768:         result[3] = right;
 769:         result[4] = top;
 770:         result[5] = front;
 771:         if (inverted) {
 772:             result[0] = top;
 773:             result[4] = bottom;
 774:         }
 775:         return result;
 776:     }
 777:     
 778:     /**
 779:      * Tests this renderer for equality with an arbitrary object.
 780:      * 
 781:      * @param obj  the object (<code>null</code> permitted).
 782:      * 
 783:      * @return A boolean.
 784:      */
 785:     public boolean equals(Object obj) {
 786:         if (obj == this) {
 787:             return true;   
 788:         }
 789:         if (!(obj instanceof StackedBarRenderer3D)) {
 790:             return false;   
 791:         }
 792:         if (!super.equals(obj)) {
 793:             return false;   
 794:         }
 795:         StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
 796:         if (this.renderAsPercentages != that.getRenderAsPercentages()) {
 797:             return false;   
 798:         }
 799:         return true;
 800:     }
 801: 
 802: }