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