001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, 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-2007, 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 * $Id: StackedBarRenderer3D.java,v 1.8.2.8 2007/01/18 14:49:52 mungady Exp $ 039 * 040 * Changes 041 * ------- 042 * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG); 043 * 15-Nov-2001 : Modified to allow for null data values (DG); 044 * 13-Dec-2001 : Added tooltips (DG); 045 * 15-Feb-2002 : Added isStacked() method (DG); 046 * 24-May-2002 : Incorporated tooltips into chart entities (DG); 047 * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG); 048 * 25-Jun-2002 : Removed redundant imports (DG); 049 * 26-Jun-2002 : Small change to entity (DG); 050 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 051 * for HTML image maps (RA); 052 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); 053 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 054 * CategoryToolTipGenerator interface (DG); 055 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG); 056 * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG); 057 * 17-Jan-2003 : Moved plot classes to a separate package (DG); 058 * 25-Mar-2003 : Implemented Serializable (DG); 059 * 01-May-2003 : Added default constructor (bug 726235) and fixed bug 060 * 726260) (DG); 061 * 13-May-2003 : Renamed StackedVerticalBarRenderer3D 062 * --> StackedBarRenderer3D (DG); 063 * 30-Jul-2003 : Modified entity constructor (CZ); 064 * 07-Oct-2003 : Added renderer state (DG); 065 * 21-Nov-2003 : Added a new constructor (DG); 066 * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG); 067 * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG); 068 * 05-Nov-2004 : Modified drawItem() signature (DG); 069 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG); 070 * 18-Mar-2005 : Override for getPassCount() method (DG); 071 * 20-Apr-2005 : Renamed CategoryLabelGenerator 072 * --> CategoryItemLabelGenerator (DG); 073 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG); 074 * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG); 075 * ------------- JFREECHART 1.0.x --------------------------------------------- 076 * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted 077 * by Max Herfort (DG); 078 * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG); 079 * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList() 080 * method (DG); 081 * 18-Jan-2007 : Updated block drawing code to take account of inverted axes, 082 * see bug report 1599652 (DG); 083 * 084 */ 085 086 package org.jfree.chart.renderer.category; 087 088 import java.awt.Graphics2D; 089 import java.awt.Paint; 090 import java.awt.Shape; 091 import java.awt.geom.GeneralPath; 092 import java.awt.geom.Point2D; 093 import java.awt.geom.Rectangle2D; 094 import java.io.Serializable; 095 import java.util.ArrayList; 096 import java.util.List; 097 098 import org.jfree.chart.axis.CategoryAxis; 099 import org.jfree.chart.axis.ValueAxis; 100 import org.jfree.chart.entity.EntityCollection; 101 import org.jfree.chart.event.RendererChangeEvent; 102 import org.jfree.chart.labels.CategoryItemLabelGenerator; 103 import org.jfree.chart.plot.CategoryPlot; 104 import org.jfree.chart.plot.PlotOrientation; 105 import org.jfree.data.DataUtilities; 106 import org.jfree.data.Range; 107 import org.jfree.data.category.CategoryDataset; 108 import org.jfree.data.general.DatasetUtilities; 109 import org.jfree.util.BooleanUtilities; 110 import org.jfree.util.PublicCloneable; 111 112 /** 113 * Renders stacked bars with 3D-effect, for use with the 114 * {@link org.jfree.chart.plot.CategoryPlot} class. 115 */ 116 public class StackedBarRenderer3D extends BarRenderer3D 117 implements Cloneable, PublicCloneable, 118 Serializable { 119 120 /** For serialization. */ 121 private static final long serialVersionUID = -5832945916493247123L; 122 123 /** A flag that controls whether the bars display values or percentages. */ 124 private boolean renderAsPercentages; 125 126 /** 127 * Creates a new renderer with no tool tip generator and no URL generator. 128 * <P> 129 * The defaults (no tool tip or URL generators) have been chosen to 130 * minimise the processing required to generate a default chart. If you 131 * require tool tips or URLs, then you can easily add the required 132 * generators. 133 */ 134 public StackedBarRenderer3D() { 135 this(false); 136 } 137 138 /** 139 * Constructs a new renderer with the specified '3D effect'. 140 * 141 * @param xOffset the x-offset for the 3D effect. 142 * @param yOffset the y-offset for the 3D effect. 143 */ 144 public StackedBarRenderer3D(double xOffset, double yOffset) { 145 super(xOffset, yOffset); 146 } 147 148 /** 149 * Creates a new renderer. 150 * 151 * @param renderAsPercentages a flag that controls whether the data values 152 * are rendered as percentages. 153 * 154 * @since 1.0.2 155 */ 156 public StackedBarRenderer3D(boolean renderAsPercentages) { 157 super(); 158 this.renderAsPercentages = renderAsPercentages; 159 } 160 161 /** 162 * Constructs a new renderer with the specified '3D effect'. 163 * 164 * @param xOffset the x-offset for the 3D effect. 165 * @param yOffset the y-offset for the 3D effect. 166 * @param renderAsPercentages a flag that controls whether the data values 167 * are rendered as percentages. 168 * 169 * @since 1.0.2 170 */ 171 public StackedBarRenderer3D(double xOffset, double yOffset, 172 boolean renderAsPercentages) { 173 super(xOffset, yOffset); 174 this.renderAsPercentages = renderAsPercentages; 175 } 176 177 /** 178 * Returns <code>true</code> if the renderer displays each item value as 179 * a percentage (so that the stacked bars add to 100%), and 180 * <code>false</code> otherwise. 181 * 182 * @return A boolean. 183 * 184 * @since 1.0.2 185 */ 186 public boolean getRenderAsPercentages() { 187 return this.renderAsPercentages; 188 } 189 190 /** 191 * Sets the flag that controls whether the renderer displays each item 192 * value as a percentage (so that the stacked bars add to 100%), and sends 193 * a {@link RendererChangeEvent} to all registered listeners. 194 * 195 * @param asPercentages the flag. 196 * 197 * @since 1.0.2 198 */ 199 public void setRenderAsPercentages(boolean asPercentages) { 200 this.renderAsPercentages = asPercentages; 201 notifyListeners(new RendererChangeEvent(this)); 202 } 203 204 /** 205 * Returns the range of values the renderer requires to display all the 206 * items from the specified dataset. 207 * 208 * @param dataset the dataset (<code>null</code> not permitted). 209 * 210 * @return The range (or <code>null</code> if the dataset is empty). 211 */ 212 public Range findRangeBounds(CategoryDataset dataset) { 213 if (this.renderAsPercentages) { 214 return new Range(0.0, 1.0); 215 } 216 else { 217 return DatasetUtilities.findStackedRangeBounds(dataset); 218 } 219 } 220 221 /** 222 * Calculates the bar width and stores it in the renderer state. 223 * 224 * @param plot the plot. 225 * @param dataArea the data area. 226 * @param rendererIndex the renderer index. 227 * @param state the renderer state. 228 */ 229 protected void calculateBarWidth(CategoryPlot plot, 230 Rectangle2D dataArea, 231 int rendererIndex, 232 CategoryItemRendererState state) { 233 234 // calculate the bar width 235 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); 236 CategoryDataset data = plot.getDataset(rendererIndex); 237 if (data != null) { 238 PlotOrientation orientation = plot.getOrientation(); 239 double space = 0.0; 240 if (orientation == PlotOrientation.HORIZONTAL) { 241 space = dataArea.getHeight(); 242 } 243 else if (orientation == PlotOrientation.VERTICAL) { 244 space = dataArea.getWidth(); 245 } 246 double maxWidth = space * getMaximumBarWidth(); 247 int columns = data.getColumnCount(); 248 double categoryMargin = 0.0; 249 if (columns > 1) { 250 categoryMargin = domainAxis.getCategoryMargin(); 251 } 252 253 double used = space * (1 - domainAxis.getLowerMargin() 254 - domainAxis.getUpperMargin() 255 - categoryMargin); 256 if (columns > 0) { 257 state.setBarWidth(Math.min(used / columns, maxWidth)); 258 } 259 else { 260 state.setBarWidth(Math.min(used, maxWidth)); 261 } 262 } 263 264 } 265 266 /** 267 * Returns a list containing the stacked values for the specified series 268 * in the given dataset, plus the supplied base value. 269 * 270 * @param dataset the dataset (<code>null</code> not permitted). 271 * @param category the category key (<code>null</code> not permitted). 272 * @param base the base value. 273 * @param asPercentages a flag that controls whether the values in the 274 * list are converted to percentages of the total. 275 * 276 * @return The value list. 277 * 278 * @since 1.0.4 279 */ 280 protected static List createStackedValueList(CategoryDataset dataset, 281 Comparable category, double base, boolean asPercentages) { 282 283 List result = new ArrayList(); 284 double posBase = base; 285 double negBase = base; 286 double total = 0.0; 287 if (asPercentages) { 288 total = DataUtilities.calculateColumnTotal(dataset, 289 dataset.getColumnIndex(category)); 290 } 291 292 int baseIndex = -1; 293 int seriesCount = dataset.getRowCount(); 294 for (int s = 0; s < seriesCount; s++) { 295 Number n = dataset.getValue(dataset.getRowKey(s), category); 296 if (n == null) { 297 continue; 298 } 299 double v = n.doubleValue(); 300 if (asPercentages) { 301 v = v / total; 302 } 303 if (v >= 0.0) { 304 if (baseIndex < 0) { 305 result.add(new Object[] {null, new Double(base)}); 306 baseIndex = 0; 307 } 308 posBase = posBase + v; 309 result.add(new Object[] {new Integer(s), new Double(posBase)}); 310 } 311 else if (v < 0.0) { 312 if (baseIndex < 0) { 313 result.add(new Object[] {null, new Double(base)}); 314 baseIndex = 0; 315 } 316 negBase = negBase + v; // '+' because v is negative 317 result.add(0, new Object[] {new Integer(-s), 318 new Double(negBase)}); 319 baseIndex++; 320 } 321 } 322 return result; 323 324 } 325 326 /** 327 * Draws the visual representation of one data item from the chart (in 328 * fact, this method does nothing until it reaches the last item for each 329 * category, at which point it draws all the items for that category). 330 * 331 * @param g2 the graphics device. 332 * @param state the renderer state. 333 * @param dataArea the plot area. 334 * @param plot the plot. 335 * @param domainAxis the domain (category) axis. 336 * @param rangeAxis the range (value) axis. 337 * @param dataset the data. 338 * @param row the row index (zero-based). 339 * @param column the column index (zero-based). 340 * @param pass the pass index. 341 */ 342 public void drawItem(Graphics2D g2, 343 CategoryItemRendererState state, 344 Rectangle2D dataArea, 345 CategoryPlot plot, 346 CategoryAxis domainAxis, 347 ValueAxis rangeAxis, 348 CategoryDataset dataset, 349 int row, 350 int column, 351 int pass) { 352 353 // wait till we are at the last item for the row then draw the 354 // whole stack at once 355 if (row < dataset.getRowCount() - 1) { 356 return; 357 } 358 Comparable category = dataset.getColumnKey(column); 359 360 List values = createStackedValueList(dataset, 361 dataset.getColumnKey(column), getBase(), 362 this.renderAsPercentages); 363 364 Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 365 dataArea.getY() + getYOffset(), 366 dataArea.getWidth() - getXOffset(), 367 dataArea.getHeight() - getYOffset()); 368 369 370 PlotOrientation orientation = plot.getOrientation(); 371 372 // handle rendering separately for the two plot orientations... 373 if (orientation == PlotOrientation.HORIZONTAL) { 374 drawStackHorizontal(values, category, g2, state, adjusted, plot, 375 domainAxis, rangeAxis, dataset); 376 } 377 else { 378 drawStackVertical(values, category, g2, state, adjusted, plot, 379 domainAxis, rangeAxis, dataset); 380 } 381 382 } 383 384 /** 385 * Draws a stack of bars for one category, with a horizontal orientation. 386 * 387 * @param values the value list. 388 * @param category the category. 389 * @param g2 the graphics device. 390 * @param state the state. 391 * @param dataArea the data area (adjusted for the 3D effect). 392 * @param plot the plot. 393 * @param domainAxis the domain axis. 394 * @param rangeAxis the range axis. 395 * @param dataset the dataset. 396 * 397 * @since 1.0.4 398 */ 399 protected void drawStackHorizontal(List values, Comparable category, 400 Graphics2D g2, CategoryItemRendererState state, 401 Rectangle2D dataArea, CategoryPlot plot, 402 CategoryAxis domainAxis, ValueAxis rangeAxis, 403 CategoryDataset dataset) { 404 405 int column = dataset.getColumnIndex(category); 406 double barX0 = domainAxis.getCategoryMiddle(column, 407 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 408 - state.getBarWidth() / 2.0; 409 double barW = state.getBarWidth(); 410 411 // a list to store the series index and bar region, so we can draw 412 // all the labels at the end... 413 List itemLabelList = new ArrayList(); 414 415 // draw the blocks 416 boolean inverted = rangeAxis.isInverted(); 417 int blockCount = values.size() - 1; 418 for (int k = 0; k < blockCount; k++) { 419 int index = (inverted ? blockCount - k - 1 : k); 420 Object[] prev = (Object[]) values.get(index); 421 Object[] curr = (Object[]) values.get(index + 1); 422 int series = 0; 423 if (curr[0] == null) { 424 series = -((Integer) prev[0]).intValue(); 425 } 426 else { 427 series = ((Integer) curr[0]).intValue(); 428 if (series < 0) { 429 series = -((Integer) prev[0]).intValue(); 430 } 431 } 432 double v0 = ((Double) prev[1]).doubleValue(); 433 double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 434 plot.getRangeAxisEdge()); 435 436 double v1 = ((Double) curr[1]).doubleValue(); 437 double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 438 plot.getRangeAxisEdge()); 439 440 Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1, 441 inverted); 442 Paint fillPaint = getItemPaint(series, column); 443 Paint outlinePaint = getItemOutlinePaint(series, column); 444 g2.setStroke(getItemOutlineStroke(series, column)); 445 446 for (int f = 0; f < 6; f++) { 447 g2.setPaint(fillPaint); 448 g2.fill(faces[f]); 449 g2.setPaint(outlinePaint); 450 g2.draw(faces[f]); 451 } 452 453 itemLabelList.add(new Object[] {new Integer(series), 454 faces[5].getBounds2D(), 455 BooleanUtilities.valueOf(v0 < getBase())}); 456 457 // add an item entity, if this information is being collected 458 EntityCollection entities = state.getEntityCollection(); 459 if (entities != null) { 460 addItemEntity(entities, dataset, series, column, faces[5]); 461 } 462 463 } 464 465 for (int i = 0; i < itemLabelList.size(); i++) { 466 Object[] record = (Object[]) itemLabelList.get(i); 467 int series = ((Integer) record[0]).intValue(); 468 Rectangle2D bar = (Rectangle2D) record[1]; 469 boolean neg = ((Boolean) record[2]).booleanValue(); 470 CategoryItemLabelGenerator generator 471 = getItemLabelGenerator(series, column); 472 if (generator != null && isItemLabelVisible(series, column)) { 473 drawItemLabel(g2, dataset, series, column, plot, generator, 474 bar, neg); 475 } 476 477 } 478 } 479 480 /** 481 * Creates an array of shapes representing the six sides of a block in a 482 * horizontal stack. 483 * 484 * @param x0 left edge of bar (in Java2D space). 485 * @param width the width of the bar (in Java2D units). 486 * @param y0 the base of the block (in Java2D space). 487 * @param y1 the top of the block (in Java2D space). 488 * @param inverted a flag indicating whether or not the block is inverted 489 * (this changes the order of the faces of the block). 490 * 491 * @return The sides of the block. 492 */ 493 private Shape[] createHorizontalBlock(double x0, double width, double y0, 494 double y1, boolean inverted) { 495 Shape[] result = new Shape[6]; 496 Point2D p00 = new Point2D.Double(y0, x0); 497 Point2D p01 = new Point2D.Double(y0, x0 + width); 498 Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 499 p01.getY() - getYOffset()); 500 Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 501 p00.getY() - getYOffset()); 502 503 Point2D p0 = new Point2D.Double(y1, x0); 504 Point2D p1 = new Point2D.Double(y1, x0 + width); 505 Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 506 p1.getY() - getYOffset()); 507 Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 508 p0.getY() - getYOffset()); 509 510 GeneralPath bottom = new GeneralPath(); 511 bottom.moveTo((float) p1.getX(), (float) p1.getY()); 512 bottom.lineTo((float) p01.getX(), (float) p01.getY()); 513 bottom.lineTo((float) p02.getX(), (float) p02.getY()); 514 bottom.lineTo((float) p2.getX(), (float) p2.getY()); 515 bottom.closePath(); 516 517 GeneralPath top = new GeneralPath(); 518 top.moveTo((float) p0.getX(), (float) p0.getY()); 519 top.lineTo((float) p00.getX(), (float) p00.getY()); 520 top.lineTo((float) p03.getX(), (float) p03.getY()); 521 top.lineTo((float) p3.getX(), (float) p3.getY()); 522 top.closePath(); 523 524 GeneralPath back = new GeneralPath(); 525 back.moveTo((float) p2.getX(), (float) p2.getY()); 526 back.lineTo((float) p02.getX(), (float) p02.getY()); 527 back.lineTo((float) p03.getX(), (float) p03.getY()); 528 back.lineTo((float) p3.getX(), (float) p3.getY()); 529 back.closePath(); 530 531 GeneralPath front = new GeneralPath(); 532 front.moveTo((float) p0.getX(), (float) p0.getY()); 533 front.lineTo((float) p1.getX(), (float) p1.getY()); 534 front.lineTo((float) p01.getX(), (float) p01.getY()); 535 front.lineTo((float) p00.getX(), (float) p00.getY()); 536 front.closePath(); 537 538 GeneralPath left = new GeneralPath(); 539 left.moveTo((float) p0.getX(), (float) p0.getY()); 540 left.lineTo((float) p1.getX(), (float) p1.getY()); 541 left.lineTo((float) p2.getX(), (float) p2.getY()); 542 left.lineTo((float) p3.getX(), (float) p3.getY()); 543 left.closePath(); 544 545 GeneralPath right = new GeneralPath(); 546 right.moveTo((float) p00.getX(), (float) p00.getY()); 547 right.lineTo((float) p01.getX(), (float) p01.getY()); 548 right.lineTo((float) p02.getX(), (float) p02.getY()); 549 right.lineTo((float) p03.getX(), (float) p03.getY()); 550 right.closePath(); 551 result[0] = bottom; 552 result[1] = back; 553 if (inverted) { 554 result[2] = right; 555 result[3] = left; 556 } 557 else { 558 result[2] = left; 559 result[3] = right; 560 } 561 result[4] = top; 562 result[5] = front; 563 return result; 564 } 565 566 /** 567 * Draws a stack of bars for one category, with a vertical orientation. 568 * 569 * @param values the value list. 570 * @param category the category. 571 * @param g2 the graphics device. 572 * @param state the state. 573 * @param dataArea the data area (adjusted for the 3D effect). 574 * @param plot the plot. 575 * @param domainAxis the domain axis. 576 * @param rangeAxis the range axis. 577 * @param dataset the dataset. 578 * 579 * @since 1.0.4 580 */ 581 protected void drawStackVertical(List values, Comparable category, 582 Graphics2D g2, CategoryItemRendererState state, 583 Rectangle2D dataArea, CategoryPlot plot, 584 CategoryAxis domainAxis, ValueAxis rangeAxis, 585 CategoryDataset dataset) { 586 587 int column = dataset.getColumnIndex(category); 588 double barX0 = domainAxis.getCategoryMiddle(column, 589 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 590 - state.getBarWidth() / 2.0; 591 double barW = state.getBarWidth(); 592 593 // a list to store the series index and bar region, so we can draw 594 // all the labels at the end... 595 List itemLabelList = new ArrayList(); 596 597 // draw the blocks 598 boolean inverted = rangeAxis.isInverted(); 599 int blockCount = values.size() - 1; 600 for (int k = 0; k < blockCount; k++) { 601 int index = (inverted ? blockCount - k - 1 : k); 602 Object[] prev = (Object[]) values.get(index); 603 Object[] curr = (Object[]) values.get(index + 1); 604 int series = 0; 605 if (curr[0] == null) { 606 series = -((Integer) prev[0]).intValue(); 607 } 608 else { 609 series = ((Integer) curr[0]).intValue(); 610 if (series < 0) { 611 series = -((Integer) prev[0]).intValue(); 612 } 613 } 614 double v0 = ((Double) prev[1]).doubleValue(); 615 double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 616 plot.getRangeAxisEdge()); 617 618 double v1 = ((Double) curr[1]).doubleValue(); 619 double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 620 plot.getRangeAxisEdge()); 621 622 Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1, 623 inverted); 624 Paint fillPaint = getItemPaint(series, column); 625 Paint outlinePaint = getItemOutlinePaint(series, column); 626 g2.setStroke(getItemOutlineStroke(series, column)); 627 628 for (int f = 0; f < 6; f++) { 629 g2.setPaint(fillPaint); 630 g2.fill(faces[f]); 631 g2.setPaint(outlinePaint); 632 g2.draw(faces[f]); 633 } 634 635 itemLabelList.add(new Object[] {new Integer(series), 636 faces[5].getBounds2D(), 637 BooleanUtilities.valueOf(v0 < getBase())}); 638 639 // add an item entity, if this information is being collected 640 EntityCollection entities = state.getEntityCollection(); 641 if (entities != null) { 642 addItemEntity(entities, dataset, series, column, faces[5]); 643 } 644 645 } 646 647 for (int i = 0; i < itemLabelList.size(); i++) { 648 Object[] record = (Object[]) itemLabelList.get(i); 649 int series = ((Integer) record[0]).intValue(); 650 Rectangle2D bar = (Rectangle2D) record[1]; 651 boolean neg = ((Boolean) record[2]).booleanValue(); 652 CategoryItemLabelGenerator generator 653 = getItemLabelGenerator(series, column); 654 if (generator != null && isItemLabelVisible(series, column)) { 655 drawItemLabel(g2, dataset, series, column, plot, generator, 656 bar, neg); 657 } 658 659 } 660 } 661 662 /** 663 * Creates an array of shapes representing the six sides of a block in a 664 * vertical stack. 665 * 666 * @param x0 left edge of bar (in Java2D space). 667 * @param width the width of the bar (in Java2D units). 668 * @param y0 the base of the block (in Java2D space). 669 * @param y1 the top of the block (in Java2D space). 670 * @param inverted a flag indicating whether or not the block is inverted 671 * (this changes the order of the faces of the block). 672 * 673 * @return The sides of the block. 674 */ 675 private Shape[] createVerticalBlock(double x0, double width, double y0, 676 double y1, boolean inverted) { 677 Shape[] result = new Shape[6]; 678 Point2D p00 = new Point2D.Double(x0, y0); 679 Point2D p01 = new Point2D.Double(x0 + width, y0); 680 Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 681 p01.getY() - getYOffset()); 682 Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 683 p00.getY() - getYOffset()); 684 685 686 Point2D p0 = new Point2D.Double(x0, y1); 687 Point2D p1 = new Point2D.Double(x0 + width, y1); 688 Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 689 p1.getY() - getYOffset()); 690 Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 691 p0.getY() - getYOffset()); 692 693 GeneralPath right = new GeneralPath(); 694 right.moveTo((float) p1.getX(), (float) p1.getY()); 695 right.lineTo((float) p01.getX(), (float) p01.getY()); 696 right.lineTo((float) p02.getX(), (float) p02.getY()); 697 right.lineTo((float) p2.getX(), (float) p2.getY()); 698 right.closePath(); 699 700 GeneralPath left = new GeneralPath(); 701 left.moveTo((float) p0.getX(), (float) p0.getY()); 702 left.lineTo((float) p00.getX(), (float) p00.getY()); 703 left.lineTo((float) p03.getX(), (float) p03.getY()); 704 left.lineTo((float) p3.getX(), (float) p3.getY()); 705 left.closePath(); 706 707 GeneralPath back = new GeneralPath(); 708 back.moveTo((float) p2.getX(), (float) p2.getY()); 709 back.lineTo((float) p02.getX(), (float) p02.getY()); 710 back.lineTo((float) p03.getX(), (float) p03.getY()); 711 back.lineTo((float) p3.getX(), (float) p3.getY()); 712 back.closePath(); 713 714 GeneralPath front = new GeneralPath(); 715 front.moveTo((float) p0.getX(), (float) p0.getY()); 716 front.lineTo((float) p1.getX(), (float) p1.getY()); 717 front.lineTo((float) p01.getX(), (float) p01.getY()); 718 front.lineTo((float) p00.getX(), (float) p00.getY()); 719 front.closePath(); 720 721 GeneralPath top = new GeneralPath(); 722 top.moveTo((float) p0.getX(), (float) p0.getY()); 723 top.lineTo((float) p1.getX(), (float) p1.getY()); 724 top.lineTo((float) p2.getX(), (float) p2.getY()); 725 top.lineTo((float) p3.getX(), (float) p3.getY()); 726 top.closePath(); 727 728 GeneralPath bottom = new GeneralPath(); 729 bottom.moveTo((float) p00.getX(), (float) p00.getY()); 730 bottom.lineTo((float) p01.getX(), (float) p01.getY()); 731 bottom.lineTo((float) p02.getX(), (float) p02.getY()); 732 bottom.lineTo((float) p03.getX(), (float) p03.getY()); 733 bottom.closePath(); 734 735 result[0] = bottom; 736 result[1] = back; 737 result[2] = left; 738 result[3] = right; 739 result[4] = top; 740 result[5] = front; 741 if (inverted) { 742 result[0] = top; 743 result[4] = bottom; 744 } 745 return result; 746 } 747 748 /** 749 * Tests this renderer for equality with an arbitrary object. 750 * 751 * @param obj the object (<code>null</code> permitted). 752 * 753 * @return A boolean. 754 */ 755 public boolean equals(Object obj) { 756 if (obj == this) { 757 return true; 758 } 759 if (!(obj instanceof StackedBarRenderer3D)) { 760 return false; 761 } 762 if (!super.equals(obj)) { 763 return false; 764 } 765 StackedBarRenderer3D that = (StackedBarRenderer3D) obj; 766 if (this.renderAsPercentages != that.getRenderAsPercentages()) { 767 return false; 768 } 769 return true; 770 } 771 772 }