001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------------- 028 * StackedBarRenderer3D.java 029 * ------------------------- 030 * (C) Copyright 2000-2008, by Serge V. Grachov and Contributors. 031 * 032 * Original Author: Serge V. Grachov; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * Richard Atkinson; 035 * Christian W. Zuckschwerdt; 036 * Max Herfort (patch 1459313); 037 * 038 * Changes 039 * ------- 040 * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG); 041 * 15-Nov-2001 : Modified to allow for null data values (DG); 042 * 13-Dec-2001 : Added tooltips (DG); 043 * 15-Feb-2002 : Added isStacked() method (DG); 044 * 24-May-2002 : Incorporated tooltips into chart entities (DG); 045 * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG); 046 * 25-Jun-2002 : Removed redundant imports (DG); 047 * 26-Jun-2002 : Small change to entity (DG); 048 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 049 * for HTML image maps (RA); 050 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); 051 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 052 * CategoryToolTipGenerator interface (DG); 053 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG); 054 * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG); 055 * 17-Jan-2003 : Moved plot classes to a separate package (DG); 056 * 25-Mar-2003 : Implemented Serializable (DG); 057 * 01-May-2003 : Added default constructor (bug 726235) and fixed bug 058 * 726260) (DG); 059 * 13-May-2003 : Renamed StackedVerticalBarRenderer3D 060 * --> StackedBarRenderer3D (DG); 061 * 30-Jul-2003 : Modified entity constructor (CZ); 062 * 07-Oct-2003 : Added renderer state (DG); 063 * 21-Nov-2003 : Added a new constructor (DG); 064 * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG); 065 * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG); 066 * 05-Nov-2004 : Modified drawItem() signature (DG); 067 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG); 068 * 18-Mar-2005 : Override for getPassCount() method (DG); 069 * 20-Apr-2005 : Renamed CategoryLabelGenerator 070 * --> CategoryItemLabelGenerator (DG); 071 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG); 072 * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG); 073 * ------------- JFREECHART 1.0.x --------------------------------------------- 074 * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted 075 * by Max Herfort (DG); 076 * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG); 077 * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList() 078 * method (DG); 079 * 18-Jan-2007 : Updated block drawing code to take account of inverted axes, 080 * see bug report 1599652 (DG); 081 * 08-May-2007 : Fixed bugs 1713401 (drawBarOutlines flag) and 1713474 082 * (shading) (DG); 083 * 15-Aug-2008 : Fixed bug 2031407 - no negative zero for stack encoding (DG); 084 * 085 */ 086 087 package org.jfree.chart.renderer.category; 088 089 import java.awt.Color; 090 import java.awt.Graphics2D; 091 import java.awt.Paint; 092 import java.awt.Shape; 093 import java.awt.geom.GeneralPath; 094 import java.awt.geom.Point2D; 095 import java.awt.geom.Rectangle2D; 096 import java.io.Serializable; 097 import java.util.ArrayList; 098 import java.util.List; 099 100 import org.jfree.chart.axis.CategoryAxis; 101 import org.jfree.chart.axis.ValueAxis; 102 import org.jfree.chart.entity.EntityCollection; 103 import org.jfree.chart.event.RendererChangeEvent; 104 import org.jfree.chart.labels.CategoryItemLabelGenerator; 105 import org.jfree.chart.plot.CategoryPlot; 106 import org.jfree.chart.plot.PlotOrientation; 107 import org.jfree.data.DataUtilities; 108 import org.jfree.data.Range; 109 import org.jfree.data.category.CategoryDataset; 110 import org.jfree.data.general.DatasetUtilities; 111 import org.jfree.util.BooleanUtilities; 112 import org.jfree.util.PublicCloneable; 113 114 /** 115 * Renders stacked bars with 3D-effect, for use with the 116 * {@link org.jfree.chart.plot.CategoryPlot} class. 117 */ 118 public class StackedBarRenderer3D extends BarRenderer3D 119 implements Cloneable, PublicCloneable, 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 fireChangeEvent(); 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 - 1), 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() - 1; 426 } 427 else { 428 series = ((Integer) curr[0]).intValue(); 429 if (series < 0) { 430 series = -((Integer) prev[0]).intValue() - 1; 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() - 1; 622 } 623 else { 624 series = ((Integer) curr[0]).intValue(); 625 if (series < 0) { 626 series = -((Integer) prev[0]).intValue() - 1; 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 }