001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2014, 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-2014, 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 * DaveLaw (dave ATT davelaw DOTT de) (patch 3204823); 038 * 039 * Changes 040 * ------- 041 * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG); 042 * 15-Nov-2001 : Modified to allow for null data values (DG); 043 * 13-Dec-2001 : Added tooltips (DG); 044 * 15-Feb-2002 : Added isStacked() method (DG); 045 * 24-May-2002 : Incorporated tooltips into chart entities (DG); 046 * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG); 047 * 25-Jun-2002 : Removed redundant imports (DG); 048 * 26-Jun-2002 : Small change to entity (DG); 049 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 050 * for HTML image maps (RA); 051 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); 052 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 053 * CategoryToolTipGenerator interface (DG); 054 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG); 055 * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG); 056 * 17-Jan-2003 : Moved plot classes to a separate package (DG); 057 * 25-Mar-2003 : Implemented Serializable (DG); 058 * 01-May-2003 : Added default constructor (bug 726235) and fixed bug 059 * 726260) (DG); 060 * 13-May-2003 : Renamed StackedVerticalBarRenderer3D 061 * --> StackedBarRenderer3D (DG); 062 * 30-Jul-2003 : Modified entity constructor (CZ); 063 * 07-Oct-2003 : Added renderer state (DG); 064 * 21-Nov-2003 : Added a new constructor (DG); 065 * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG); 066 * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG); 067 * 05-Nov-2004 : Modified drawItem() signature (DG); 068 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG); 069 * 18-Mar-2005 : Override for getPassCount() method (DG); 070 * 20-Apr-2005 : Renamed CategoryLabelGenerator 071 * --> CategoryItemLabelGenerator (DG); 072 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG); 073 * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG); 074 * ------------- JFREECHART 1.0.x --------------------------------------------- 075 * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted 076 * by Max Herfort (DG); 077 * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG); 078 * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList() 079 * method (DG); 080 * 18-Jan-2007 : Updated block drawing code to take account of inverted axes, 081 * see bug report 1599652 (DG); 082 * 08-May-2007 : Fixed bugs 1713401 (drawBarOutlines flag) and 1713474 083 * (shading) (DG); 084 * 15-Aug-2008 : Fixed bug 2031407 - no negative zero for stack encoding (DG); 085 * 03-Feb-2009 : Fixed regression in findRangeBounds() method for null 086 * dataset (DG); 087 * 04-Feb-2009 : Handle seriesVisible flag (DG); 088 * 07-Jul-2009 : Added flag for handling zero values (DG); 089 * 11-Jun-2012 : Use new PaintAlpha class (patch 3204823 from DaveLaw) (DG); 090 * 091 */ 092 093package org.jfree.chart.renderer.category; 094 095import java.awt.Graphics2D; 096import java.awt.Paint; 097import java.awt.Shape; 098import java.awt.geom.GeneralPath; 099import java.awt.geom.Point2D; 100import java.awt.geom.Rectangle2D; 101import java.io.Serializable; 102import java.util.ArrayList; 103import java.util.List; 104 105import org.jfree.chart.HashUtilities; 106import org.jfree.chart.axis.CategoryAxis; 107import org.jfree.chart.axis.ValueAxis; 108import org.jfree.chart.entity.EntityCollection; 109import org.jfree.chart.event.RendererChangeEvent; 110import org.jfree.chart.labels.CategoryItemLabelGenerator; 111import org.jfree.chart.plot.CategoryPlot; 112import org.jfree.chart.plot.PlotOrientation; 113import org.jfree.chart.util.PaintAlpha; 114import org.jfree.data.DataUtilities; 115import org.jfree.data.Range; 116import org.jfree.data.category.CategoryDataset; 117import org.jfree.data.general.DatasetUtilities; 118import org.jfree.util.BooleanUtilities; 119import org.jfree.util.PublicCloneable; 120 121/** 122 * Renders stacked bars with 3D-effect, for use with the {@link CategoryPlot} 123 * class. The example shown here is generated by the 124 * <code>StackedBarChart3DDemo1.java</code> program included in the 125 * JFreeChart Demo Collection: 126 * <br><br> 127 * <img src="../../../../../images/StackedBarRenderer3DSample.png" 128 * alt="StackedBarRenderer3DSample.png"> 129 */ 130public class StackedBarRenderer3D extends BarRenderer3D 131 implements Cloneable, PublicCloneable, Serializable { 132 133 /** For serialization. */ 134 private static final long serialVersionUID = -5832945916493247123L; 135 136 /** A flag that controls whether the bars display values or percentages. */ 137 private boolean renderAsPercentages; 138 139 /** 140 * A flag that controls whether or not zero values are drawn by the 141 * renderer. 142 * 143 * @since 1.0.14 144 */ 145 private boolean ignoreZeroValues; 146 147 /** 148 * Creates a new renderer with no tool tip generator and no URL generator. 149 * <P> 150 * The defaults (no tool tip or URL generators) have been chosen to 151 * minimise the processing required to generate a default chart. If you 152 * require tool tips or URLs, then you can easily add the required 153 * generators. 154 */ 155 public StackedBarRenderer3D() { 156 this(false); 157 } 158 159 /** 160 * Constructs a new renderer with the specified '3D effect'. 161 * 162 * @param xOffset the x-offset for the 3D effect. 163 * @param yOffset the y-offset for the 3D effect. 164 */ 165 public StackedBarRenderer3D(double xOffset, double yOffset) { 166 super(xOffset, yOffset); 167 } 168 169 /** 170 * Creates a new renderer. 171 * 172 * @param renderAsPercentages a flag that controls whether the data values 173 * are rendered as percentages. 174 * 175 * @since 1.0.2 176 */ 177 public StackedBarRenderer3D(boolean renderAsPercentages) { 178 super(); 179 this.renderAsPercentages = renderAsPercentages; 180 } 181 182 /** 183 * Constructs a new renderer with the specified '3D effect'. 184 * 185 * @param xOffset the x-offset for the 3D effect. 186 * @param yOffset the y-offset for the 3D effect. 187 * @param renderAsPercentages a flag that controls whether the data values 188 * are rendered as percentages. 189 * 190 * @since 1.0.2 191 */ 192 public StackedBarRenderer3D(double xOffset, double yOffset, 193 boolean renderAsPercentages) { 194 super(xOffset, yOffset); 195 this.renderAsPercentages = renderAsPercentages; 196 } 197 198 /** 199 * Returns <code>true</code> if the renderer displays each item value as 200 * a percentage (so that the stacked bars add to 100%), and 201 * <code>false</code> otherwise. 202 * 203 * @return A boolean. 204 * 205 * @since 1.0.2 206 */ 207 public boolean getRenderAsPercentages() { 208 return this.renderAsPercentages; 209 } 210 211 /** 212 * Sets the flag that controls whether the renderer displays each item 213 * value as a percentage (so that the stacked bars add to 100%), and sends 214 * a {@link RendererChangeEvent} to all registered listeners. 215 * 216 * @param asPercentages the flag. 217 * 218 * @since 1.0.2 219 */ 220 public void setRenderAsPercentages(boolean asPercentages) { 221 this.renderAsPercentages = asPercentages; 222 fireChangeEvent(); 223 } 224 225 /** 226 * Returns the flag that controls whether or not zero values are drawn 227 * by the renderer. 228 * 229 * @return A boolean. 230 * 231 * @since 1.0.14 232 */ 233 public boolean getIgnoreZeroValues() { 234 return this.ignoreZeroValues; 235 } 236 237 /** 238 * Sets a flag that controls whether or not zero values are drawn by the 239 * renderer, and sends a {@link RendererChangeEvent} to all registered 240 * listeners. 241 * 242 * @param ignore the new flag value. 243 * 244 * @since 1.0.14 245 */ 246 public void setIgnoreZeroValues(boolean ignore) { 247 this.ignoreZeroValues = ignore; 248 notifyListeners(new RendererChangeEvent(this)); 249 } 250 251 /** 252 * Returns the range of values the renderer requires to display all the 253 * items from the specified dataset. 254 * 255 * @param dataset the dataset (<code>null</code> not permitted). 256 * 257 * @return The range (or <code>null</code> if the dataset is empty). 258 */ 259 @Override 260 public Range findRangeBounds(CategoryDataset dataset) { 261 if (dataset == null) { 262 return null; 263 } 264 if (this.renderAsPercentages) { 265 return new Range(0.0, 1.0); 266 } 267 return DatasetUtilities.findStackedRangeBounds(dataset); 268 } 269 270 /** 271 * Calculates the bar width and stores it in the renderer state. 272 * 273 * @param plot the plot. 274 * @param dataArea the data area. 275 * @param rendererIndex the renderer index. 276 * @param state the renderer state. 277 */ 278 @Override 279 protected void calculateBarWidth(CategoryPlot plot, Rectangle2D dataArea, 280 int rendererIndex, 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 @Override 421 public void drawItem(Graphics2D g2, CategoryItemRendererState state, 422 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 423 ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, 424 int pass) { 425 426 // wait till we are at the last item for the row then draw the 427 // whole stack at once 428 if (row < dataset.getRowCount() - 1) { 429 return; 430 } 431 Comparable category = dataset.getColumnKey(column); 432 433 List values = createStackedValueList(dataset, 434 dataset.getColumnKey(column), state.getVisibleSeriesArray(), 435 getBase(), this.renderAsPercentages); 436 437 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 438 dataArea.getY() + getYOffset(), 439 dataArea.getWidth() - getXOffset(), 440 dataArea.getHeight() - getYOffset()); 441 442 443 PlotOrientation orientation = plot.getOrientation(); 444 445 // handle rendering separately for the two plot orientations... 446 if (orientation == PlotOrientation.HORIZONTAL) { 447 drawStackHorizontal(values, category, g2, state, adjusted, plot, 448 domainAxis, rangeAxis, dataset); 449 } 450 else { 451 drawStackVertical(values, category, g2, state, adjusted, plot, 452 domainAxis, rangeAxis, dataset); 453 } 454 455 } 456 457 /** 458 * Draws a stack of bars for one category, with a horizontal orientation. 459 * 460 * @param values the value list. 461 * @param category the category. 462 * @param g2 the graphics device. 463 * @param state the state. 464 * @param dataArea the data area (adjusted for the 3D effect). 465 * @param plot the plot. 466 * @param domainAxis the domain axis. 467 * @param rangeAxis the range axis. 468 * @param dataset the dataset. 469 * 470 * @since 1.0.4 471 */ 472 protected void drawStackHorizontal(List values, Comparable category, 473 Graphics2D g2, CategoryItemRendererState state, 474 Rectangle2D dataArea, CategoryPlot plot, 475 CategoryAxis domainAxis, ValueAxis rangeAxis, 476 CategoryDataset dataset) { 477 478 int column = dataset.getColumnIndex(category); 479 double barX0 = domainAxis.getCategoryMiddle(column, 480 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 481 - state.getBarWidth() / 2.0; 482 double barW = state.getBarWidth(); 483 484 // a list to store the series index and bar region, so we can draw 485 // all the labels at the end... 486 List itemLabelList = new ArrayList(); 487 488 // draw the blocks 489 boolean inverted = rangeAxis.isInverted(); 490 int blockCount = values.size() - 1; 491 for (int k = 0; k < blockCount; k++) { 492 int index = (inverted ? blockCount - k - 1 : k); 493 Object[] prev = (Object[]) values.get(index); 494 Object[] curr = (Object[]) values.get(index + 1); 495 int series; 496 if (curr[0] == null) { 497 series = -((Integer) prev[0]).intValue() - 1; 498 } 499 else { 500 series = ((Integer) curr[0]).intValue(); 501 if (series < 0) { 502 series = -((Integer) prev[0]).intValue() - 1; 503 } 504 } 505 double v0 = ((Double) prev[1]).doubleValue(); 506 double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 507 plot.getRangeAxisEdge()); 508 509 double v1 = ((Double) curr[1]).doubleValue(); 510 double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 511 plot.getRangeAxisEdge()); 512 513 Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1, 514 inverted); 515 Paint fillPaint = getItemPaint(series, column); 516 Paint fillPaintDark = PaintAlpha.darker(fillPaint); 517 boolean drawOutlines = isDrawBarOutline(); 518 Paint outlinePaint = fillPaint; 519 if (drawOutlines) { 520 outlinePaint = getItemOutlinePaint(series, column); 521 g2.setStroke(getItemOutlineStroke(series, column)); 522 } 523 for (int f = 0; f < 6; f++) { 524 if (f == 5) { 525 g2.setPaint(fillPaint); 526 } 527 else { 528 g2.setPaint(fillPaintDark); 529 } 530 g2.fill(faces[f]); 531 if (drawOutlines) { 532 g2.setPaint(outlinePaint); 533 g2.draw(faces[f]); 534 } 535 } 536 537 itemLabelList.add(new Object[] {new Integer(series), 538 faces[5].getBounds2D(), 539 BooleanUtilities.valueOf(v0 < getBase())}); 540 541 // add an item entity, if this information is being collected 542 EntityCollection entities = state.getEntityCollection(); 543 if (entities != null) { 544 addItemEntity(entities, dataset, series, column, faces[5]); 545 } 546 547 } 548 549 for (int i = 0; i < itemLabelList.size(); i++) { 550 Object[] record = (Object[]) itemLabelList.get(i); 551 int series = ((Integer) record[0]).intValue(); 552 Rectangle2D bar = (Rectangle2D) record[1]; 553 boolean neg = ((Boolean) record[2]).booleanValue(); 554 CategoryItemLabelGenerator generator 555 = getItemLabelGenerator(series, column); 556 if (generator != null && isItemLabelVisible(series, column)) { 557 drawItemLabel(g2, dataset, series, column, plot, generator, 558 bar, neg); 559 } 560 561 } 562 } 563 564 /** 565 * Creates an array of shapes representing the six sides of a block in a 566 * horizontal stack. 567 * 568 * @param x0 left edge of bar (in Java2D space). 569 * @param width the width of the bar (in Java2D units). 570 * @param y0 the base of the block (in Java2D space). 571 * @param y1 the top of the block (in Java2D space). 572 * @param inverted a flag indicating whether or not the block is inverted 573 * (this changes the order of the faces of the block). 574 * 575 * @return The sides of the block. 576 */ 577 private Shape[] createHorizontalBlock(double x0, double width, double y0, 578 double y1, boolean inverted) { 579 Shape[] result = new Shape[6]; 580 Point2D p00 = new Point2D.Double(y0, x0); 581 Point2D p01 = new Point2D.Double(y0, x0 + width); 582 Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 583 p01.getY() - getYOffset()); 584 Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 585 p00.getY() - getYOffset()); 586 587 Point2D p0 = new Point2D.Double(y1, x0); 588 Point2D p1 = new Point2D.Double(y1, x0 + width); 589 Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 590 p1.getY() - getYOffset()); 591 Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 592 p0.getY() - getYOffset()); 593 594 GeneralPath bottom = new GeneralPath(); 595 bottom.moveTo((float) p1.getX(), (float) p1.getY()); 596 bottom.lineTo((float) p01.getX(), (float) p01.getY()); 597 bottom.lineTo((float) p02.getX(), (float) p02.getY()); 598 bottom.lineTo((float) p2.getX(), (float) p2.getY()); 599 bottom.closePath(); 600 601 GeneralPath top = new GeneralPath(); 602 top.moveTo((float) p0.getX(), (float) p0.getY()); 603 top.lineTo((float) p00.getX(), (float) p00.getY()); 604 top.lineTo((float) p03.getX(), (float) p03.getY()); 605 top.lineTo((float) p3.getX(), (float) p3.getY()); 606 top.closePath(); 607 608 GeneralPath back = new GeneralPath(); 609 back.moveTo((float) p2.getX(), (float) p2.getY()); 610 back.lineTo((float) p02.getX(), (float) p02.getY()); 611 back.lineTo((float) p03.getX(), (float) p03.getY()); 612 back.lineTo((float) p3.getX(), (float) p3.getY()); 613 back.closePath(); 614 615 GeneralPath front = new GeneralPath(); 616 front.moveTo((float) p0.getX(), (float) p0.getY()); 617 front.lineTo((float) p1.getX(), (float) p1.getY()); 618 front.lineTo((float) p01.getX(), (float) p01.getY()); 619 front.lineTo((float) p00.getX(), (float) p00.getY()); 620 front.closePath(); 621 622 GeneralPath left = new GeneralPath(); 623 left.moveTo((float) p0.getX(), (float) p0.getY()); 624 left.lineTo((float) p1.getX(), (float) p1.getY()); 625 left.lineTo((float) p2.getX(), (float) p2.getY()); 626 left.lineTo((float) p3.getX(), (float) p3.getY()); 627 left.closePath(); 628 629 GeneralPath right = new GeneralPath(); 630 right.moveTo((float) p00.getX(), (float) p00.getY()); 631 right.lineTo((float) p01.getX(), (float) p01.getY()); 632 right.lineTo((float) p02.getX(), (float) p02.getY()); 633 right.lineTo((float) p03.getX(), (float) p03.getY()); 634 right.closePath(); 635 result[0] = bottom; 636 result[1] = back; 637 if (inverted) { 638 result[2] = right; 639 result[3] = left; 640 } 641 else { 642 result[2] = left; 643 result[3] = right; 644 } 645 result[4] = top; 646 result[5] = front; 647 return result; 648 } 649 650 /** 651 * Draws a stack of bars for one category, with a vertical orientation. 652 * 653 * @param values the value list. 654 * @param category the category. 655 * @param g2 the graphics device. 656 * @param state the state. 657 * @param dataArea the data area (adjusted for the 3D effect). 658 * @param plot the plot. 659 * @param domainAxis the domain axis. 660 * @param rangeAxis the range axis. 661 * @param dataset the dataset. 662 * 663 * @since 1.0.4 664 */ 665 protected void drawStackVertical(List values, Comparable category, 666 Graphics2D g2, CategoryItemRendererState state, 667 Rectangle2D dataArea, CategoryPlot plot, 668 CategoryAxis domainAxis, ValueAxis rangeAxis, 669 CategoryDataset dataset) { 670 671 int column = dataset.getColumnIndex(category); 672 double barX0 = domainAxis.getCategoryMiddle(column, 673 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 674 - state.getBarWidth() / 2.0; 675 double barW = state.getBarWidth(); 676 677 // a list to store the series index and bar region, so we can draw 678 // all the labels at the end... 679 List itemLabelList = new ArrayList(); 680 681 // draw the blocks 682 boolean inverted = rangeAxis.isInverted(); 683 int blockCount = values.size() - 1; 684 for (int k = 0; k < blockCount; k++) { 685 int index = (inverted ? blockCount - k - 1 : k); 686 Object[] prev = (Object[]) values.get(index); 687 Object[] curr = (Object[]) values.get(index + 1); 688 int series; 689 if (curr[0] == null) { 690 series = -((Integer) prev[0]).intValue() - 1; 691 } 692 else { 693 series = ((Integer) curr[0]).intValue(); 694 if (series < 0) { 695 series = -((Integer) prev[0]).intValue() - 1; 696 } 697 } 698 double v0 = ((Double) prev[1]).doubleValue(); 699 double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 700 plot.getRangeAxisEdge()); 701 702 double v1 = ((Double) curr[1]).doubleValue(); 703 double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 704 plot.getRangeAxisEdge()); 705 706 Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1, 707 inverted); 708 Paint fillPaint = getItemPaint(series, column); 709 Paint fillPaintDark = PaintAlpha.darker(fillPaint); 710 boolean drawOutlines = isDrawBarOutline(); 711 Paint outlinePaint = fillPaint; 712 if (drawOutlines) { 713 outlinePaint = getItemOutlinePaint(series, column); 714 g2.setStroke(getItemOutlineStroke(series, column)); 715 } 716 717 for (int f = 0; f < 6; f++) { 718 if (f == 5) { 719 g2.setPaint(fillPaint); 720 } 721 else { 722 g2.setPaint(fillPaintDark); 723 } 724 g2.fill(faces[f]); 725 if (drawOutlines) { 726 g2.setPaint(outlinePaint); 727 g2.draw(faces[f]); 728 } 729 } 730 731 itemLabelList.add(new Object[] {new Integer(series), 732 faces[5].getBounds2D(), 733 BooleanUtilities.valueOf(v0 < getBase())}); 734 735 // add an item entity, if this information is being collected 736 EntityCollection entities = state.getEntityCollection(); 737 if (entities != null) { 738 addItemEntity(entities, dataset, series, column, faces[5]); 739 } 740 741 } 742 743 for (int i = 0; i < itemLabelList.size(); i++) { 744 Object[] record = (Object[]) itemLabelList.get(i); 745 int series = ((Integer) record[0]).intValue(); 746 Rectangle2D bar = (Rectangle2D) record[1]; 747 boolean neg = ((Boolean) record[2]).booleanValue(); 748 CategoryItemLabelGenerator generator 749 = getItemLabelGenerator(series, column); 750 if (generator != null && isItemLabelVisible(series, column)) { 751 drawItemLabel(g2, dataset, series, column, plot, generator, 752 bar, neg); 753 } 754 755 } 756 } 757 758 /** 759 * Creates an array of shapes representing the six sides of a block in a 760 * vertical stack. 761 * 762 * @param x0 left edge of bar (in Java2D space). 763 * @param width the width of the bar (in Java2D units). 764 * @param y0 the base of the block (in Java2D space). 765 * @param y1 the top of the block (in Java2D space). 766 * @param inverted a flag indicating whether or not the block is inverted 767 * (this changes the order of the faces of the block). 768 * 769 * @return The sides of the block. 770 */ 771 private Shape[] createVerticalBlock(double x0, double width, double y0, 772 double y1, boolean inverted) { 773 Shape[] result = new Shape[6]; 774 Point2D p00 = new Point2D.Double(x0, y0); 775 Point2D p01 = new Point2D.Double(x0 + width, y0); 776 Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 777 p01.getY() - getYOffset()); 778 Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 779 p00.getY() - getYOffset()); 780 781 782 Point2D p0 = new Point2D.Double(x0, y1); 783 Point2D p1 = new Point2D.Double(x0 + width, y1); 784 Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 785 p1.getY() - getYOffset()); 786 Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 787 p0.getY() - getYOffset()); 788 789 GeneralPath right = new GeneralPath(); 790 right.moveTo((float) p1.getX(), (float) p1.getY()); 791 right.lineTo((float) p01.getX(), (float) p01.getY()); 792 right.lineTo((float) p02.getX(), (float) p02.getY()); 793 right.lineTo((float) p2.getX(), (float) p2.getY()); 794 right.closePath(); 795 796 GeneralPath left = new GeneralPath(); 797 left.moveTo((float) p0.getX(), (float) p0.getY()); 798 left.lineTo((float) p00.getX(), (float) p00.getY()); 799 left.lineTo((float) p03.getX(), (float) p03.getY()); 800 left.lineTo((float) p3.getX(), (float) p3.getY()); 801 left.closePath(); 802 803 GeneralPath back = new GeneralPath(); 804 back.moveTo((float) p2.getX(), (float) p2.getY()); 805 back.lineTo((float) p02.getX(), (float) p02.getY()); 806 back.lineTo((float) p03.getX(), (float) p03.getY()); 807 back.lineTo((float) p3.getX(), (float) p3.getY()); 808 back.closePath(); 809 810 GeneralPath front = new GeneralPath(); 811 front.moveTo((float) p0.getX(), (float) p0.getY()); 812 front.lineTo((float) p1.getX(), (float) p1.getY()); 813 front.lineTo((float) p01.getX(), (float) p01.getY()); 814 front.lineTo((float) p00.getX(), (float) p00.getY()); 815 front.closePath(); 816 817 GeneralPath top = new GeneralPath(); 818 top.moveTo((float) p0.getX(), (float) p0.getY()); 819 top.lineTo((float) p1.getX(), (float) p1.getY()); 820 top.lineTo((float) p2.getX(), (float) p2.getY()); 821 top.lineTo((float) p3.getX(), (float) p3.getY()); 822 top.closePath(); 823 824 GeneralPath bottom = new GeneralPath(); 825 bottom.moveTo((float) p00.getX(), (float) p00.getY()); 826 bottom.lineTo((float) p01.getX(), (float) p01.getY()); 827 bottom.lineTo((float) p02.getX(), (float) p02.getY()); 828 bottom.lineTo((float) p03.getX(), (float) p03.getY()); 829 bottom.closePath(); 830 831 result[0] = bottom; 832 result[1] = back; 833 result[2] = left; 834 result[3] = right; 835 result[4] = top; 836 result[5] = front; 837 if (inverted) { 838 result[0] = top; 839 result[4] = bottom; 840 } 841 return result; 842 } 843 844 /** 845 * Tests this renderer for equality with an arbitrary object. 846 * 847 * @param obj the object (<code>null</code> permitted). 848 * 849 * @return A boolean. 850 */ 851 @Override 852 public boolean equals(Object obj) { 853 if (obj == this) { 854 return true; 855 } 856 if (!(obj instanceof StackedBarRenderer3D)) { 857 return false; 858 } 859 StackedBarRenderer3D that = (StackedBarRenderer3D) obj; 860 if (this.renderAsPercentages != that.getRenderAsPercentages()) { 861 return false; 862 } 863 if (this.ignoreZeroValues != that.ignoreZeroValues) { 864 return false; 865 } 866 return super.equals(obj); 867 } 868 869 /** 870 * Returns a hash code for this instance. 871 * 872 * @return A hash code. 873 */ 874 @Override 875 public int hashCode() { 876 int hash = super.hashCode(); 877 hash = HashUtilities.hashCode(hash, this.renderAsPercentages); 878 hash = HashUtilities.hashCode(hash, this.ignoreZeroValues); 879 return hash; 880 } 881 882}