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 * 084 */ 085 086 package org.jfree.chart.renderer.category; 087 088 import java.awt.Color; 089 import java.awt.Graphics2D; 090 import java.awt.Paint; 091 import java.awt.Shape; 092 import java.awt.geom.GeneralPath; 093 import java.awt.geom.Point2D; 094 import java.awt.geom.Rectangle2D; 095 import java.io.Serializable; 096 import java.util.ArrayList; 097 import java.util.List; 098 099 import org.jfree.chart.axis.CategoryAxis; 100 import org.jfree.chart.axis.ValueAxis; 101 import org.jfree.chart.entity.EntityCollection; 102 import org.jfree.chart.event.RendererChangeEvent; 103 import org.jfree.chart.labels.CategoryItemLabelGenerator; 104 import org.jfree.chart.plot.CategoryPlot; 105 import org.jfree.chart.plot.PlotOrientation; 106 import org.jfree.data.DataUtilities; 107 import org.jfree.data.Range; 108 import org.jfree.data.category.CategoryDataset; 109 import org.jfree.data.general.DatasetUtilities; 110 import org.jfree.util.BooleanUtilities; 111 import org.jfree.util.PublicCloneable; 112 113 /** 114 * Renders stacked bars with 3D-effect, for use with the 115 * {@link org.jfree.chart.plot.CategoryPlot} class. 116 */ 117 public class StackedBarRenderer3D extends BarRenderer3D 118 implements Cloneable, PublicCloneable, 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 fireChangeEvent(); 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 fillPaintDark = fillPaint; 444 if (fillPaintDark instanceof Color) { 445 fillPaintDark = ((Color) fillPaint).darker(); 446 } 447 boolean drawOutlines = isDrawBarOutline(); 448 Paint outlinePaint = fillPaint; 449 if (drawOutlines) { 450 outlinePaint = getItemOutlinePaint(series, column); 451 g2.setStroke(getItemOutlineStroke(series, column)); 452 } 453 for (int f = 0; f < 6; f++) { 454 if (f == 5) { 455 g2.setPaint(fillPaint); 456 } 457 else { 458 g2.setPaint(fillPaintDark); 459 } 460 g2.fill(faces[f]); 461 if (drawOutlines) { 462 g2.setPaint(outlinePaint); 463 g2.draw(faces[f]); 464 } 465 } 466 467 itemLabelList.add(new Object[] {new Integer(series), 468 faces[5].getBounds2D(), 469 BooleanUtilities.valueOf(v0 < getBase())}); 470 471 // add an item entity, if this information is being collected 472 EntityCollection entities = state.getEntityCollection(); 473 if (entities != null) { 474 addItemEntity(entities, dataset, series, column, faces[5]); 475 } 476 477 } 478 479 for (int i = 0; i < itemLabelList.size(); i++) { 480 Object[] record = (Object[]) itemLabelList.get(i); 481 int series = ((Integer) record[0]).intValue(); 482 Rectangle2D bar = (Rectangle2D) record[1]; 483 boolean neg = ((Boolean) record[2]).booleanValue(); 484 CategoryItemLabelGenerator generator 485 = getItemLabelGenerator(series, column); 486 if (generator != null && isItemLabelVisible(series, column)) { 487 drawItemLabel(g2, dataset, series, column, plot, generator, 488 bar, neg); 489 } 490 491 } 492 } 493 494 /** 495 * Creates an array of shapes representing the six sides of a block in a 496 * horizontal stack. 497 * 498 * @param x0 left edge of bar (in Java2D space). 499 * @param width the width of the bar (in Java2D units). 500 * @param y0 the base of the block (in Java2D space). 501 * @param y1 the top of the block (in Java2D space). 502 * @param inverted a flag indicating whether or not the block is inverted 503 * (this changes the order of the faces of the block). 504 * 505 * @return The sides of the block. 506 */ 507 private Shape[] createHorizontalBlock(double x0, double width, double y0, 508 double y1, boolean inverted) { 509 Shape[] result = new Shape[6]; 510 Point2D p00 = new Point2D.Double(y0, x0); 511 Point2D p01 = new Point2D.Double(y0, x0 + width); 512 Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 513 p01.getY() - getYOffset()); 514 Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 515 p00.getY() - getYOffset()); 516 517 Point2D p0 = new Point2D.Double(y1, x0); 518 Point2D p1 = new Point2D.Double(y1, x0 + width); 519 Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 520 p1.getY() - getYOffset()); 521 Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 522 p0.getY() - getYOffset()); 523 524 GeneralPath bottom = new GeneralPath(); 525 bottom.moveTo((float) p1.getX(), (float) p1.getY()); 526 bottom.lineTo((float) p01.getX(), (float) p01.getY()); 527 bottom.lineTo((float) p02.getX(), (float) p02.getY()); 528 bottom.lineTo((float) p2.getX(), (float) p2.getY()); 529 bottom.closePath(); 530 531 GeneralPath top = new GeneralPath(); 532 top.moveTo((float) p0.getX(), (float) p0.getY()); 533 top.lineTo((float) p00.getX(), (float) p00.getY()); 534 top.lineTo((float) p03.getX(), (float) p03.getY()); 535 top.lineTo((float) p3.getX(), (float) p3.getY()); 536 top.closePath(); 537 538 GeneralPath back = new GeneralPath(); 539 back.moveTo((float) p2.getX(), (float) p2.getY()); 540 back.lineTo((float) p02.getX(), (float) p02.getY()); 541 back.lineTo((float) p03.getX(), (float) p03.getY()); 542 back.lineTo((float) p3.getX(), (float) p3.getY()); 543 back.closePath(); 544 545 GeneralPath front = new GeneralPath(); 546 front.moveTo((float) p0.getX(), (float) p0.getY()); 547 front.lineTo((float) p1.getX(), (float) p1.getY()); 548 front.lineTo((float) p01.getX(), (float) p01.getY()); 549 front.lineTo((float) p00.getX(), (float) p00.getY()); 550 front.closePath(); 551 552 GeneralPath left = new GeneralPath(); 553 left.moveTo((float) p0.getX(), (float) p0.getY()); 554 left.lineTo((float) p1.getX(), (float) p1.getY()); 555 left.lineTo((float) p2.getX(), (float) p2.getY()); 556 left.lineTo((float) p3.getX(), (float) p3.getY()); 557 left.closePath(); 558 559 GeneralPath right = new GeneralPath(); 560 right.moveTo((float) p00.getX(), (float) p00.getY()); 561 right.lineTo((float) p01.getX(), (float) p01.getY()); 562 right.lineTo((float) p02.getX(), (float) p02.getY()); 563 right.lineTo((float) p03.getX(), (float) p03.getY()); 564 right.closePath(); 565 result[0] = bottom; 566 result[1] = back; 567 if (inverted) { 568 result[2] = right; 569 result[3] = left; 570 } 571 else { 572 result[2] = left; 573 result[3] = right; 574 } 575 result[4] = top; 576 result[5] = front; 577 return result; 578 } 579 580 /** 581 * Draws a stack of bars for one category, with a vertical orientation. 582 * 583 * @param values the value list. 584 * @param category the category. 585 * @param g2 the graphics device. 586 * @param state the state. 587 * @param dataArea the data area (adjusted for the 3D effect). 588 * @param plot the plot. 589 * @param domainAxis the domain axis. 590 * @param rangeAxis the range axis. 591 * @param dataset the dataset. 592 * 593 * @since 1.0.4 594 */ 595 protected void drawStackVertical(List values, Comparable category, 596 Graphics2D g2, CategoryItemRendererState state, 597 Rectangle2D dataArea, CategoryPlot plot, 598 CategoryAxis domainAxis, ValueAxis rangeAxis, 599 CategoryDataset dataset) { 600 601 int column = dataset.getColumnIndex(category); 602 double barX0 = domainAxis.getCategoryMiddle(column, 603 dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 604 - state.getBarWidth() / 2.0; 605 double barW = state.getBarWidth(); 606 607 // a list to store the series index and bar region, so we can draw 608 // all the labels at the end... 609 List itemLabelList = new ArrayList(); 610 611 // draw the blocks 612 boolean inverted = rangeAxis.isInverted(); 613 int blockCount = values.size() - 1; 614 for (int k = 0; k < blockCount; k++) { 615 int index = (inverted ? blockCount - k - 1 : k); 616 Object[] prev = (Object[]) values.get(index); 617 Object[] curr = (Object[]) values.get(index + 1); 618 int series = 0; 619 if (curr[0] == null) { 620 series = -((Integer) prev[0]).intValue(); 621 } 622 else { 623 series = ((Integer) curr[0]).intValue(); 624 if (series < 0) { 625 series = -((Integer) prev[0]).intValue(); 626 } 627 } 628 double v0 = ((Double) prev[1]).doubleValue(); 629 double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 630 plot.getRangeAxisEdge()); 631 632 double v1 = ((Double) curr[1]).doubleValue(); 633 double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 634 plot.getRangeAxisEdge()); 635 636 Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1, 637 inverted); 638 Paint fillPaint = getItemPaint(series, column); 639 Paint fillPaintDark = fillPaint; 640 if (fillPaintDark instanceof Color) { 641 fillPaintDark = ((Color) fillPaint).darker(); 642 } 643 boolean drawOutlines = isDrawBarOutline(); 644 Paint outlinePaint = fillPaint; 645 if (drawOutlines) { 646 outlinePaint = getItemOutlinePaint(series, column); 647 g2.setStroke(getItemOutlineStroke(series, column)); 648 } 649 650 for (int f = 0; f < 6; f++) { 651 if (f == 5) { 652 g2.setPaint(fillPaint); 653 } 654 else { 655 g2.setPaint(fillPaintDark); 656 } 657 g2.fill(faces[f]); 658 if (drawOutlines) { 659 g2.setPaint(outlinePaint); 660 g2.draw(faces[f]); 661 } 662 } 663 664 itemLabelList.add(new Object[] {new Integer(series), 665 faces[5].getBounds2D(), 666 BooleanUtilities.valueOf(v0 < getBase())}); 667 668 // add an item entity, if this information is being collected 669 EntityCollection entities = state.getEntityCollection(); 670 if (entities != null) { 671 addItemEntity(entities, dataset, series, column, faces[5]); 672 } 673 674 } 675 676 for (int i = 0; i < itemLabelList.size(); i++) { 677 Object[] record = (Object[]) itemLabelList.get(i); 678 int series = ((Integer) record[0]).intValue(); 679 Rectangle2D bar = (Rectangle2D) record[1]; 680 boolean neg = ((Boolean) record[2]).booleanValue(); 681 CategoryItemLabelGenerator generator 682 = getItemLabelGenerator(series, column); 683 if (generator != null && isItemLabelVisible(series, column)) { 684 drawItemLabel(g2, dataset, series, column, plot, generator, 685 bar, neg); 686 } 687 688 } 689 } 690 691 /** 692 * Creates an array of shapes representing the six sides of a block in a 693 * vertical stack. 694 * 695 * @param x0 left edge of bar (in Java2D space). 696 * @param width the width of the bar (in Java2D units). 697 * @param y0 the base of the block (in Java2D space). 698 * @param y1 the top of the block (in Java2D space). 699 * @param inverted a flag indicating whether or not the block is inverted 700 * (this changes the order of the faces of the block). 701 * 702 * @return The sides of the block. 703 */ 704 private Shape[] createVerticalBlock(double x0, double width, double y0, 705 double y1, boolean inverted) { 706 Shape[] result = new Shape[6]; 707 Point2D p00 = new Point2D.Double(x0, y0); 708 Point2D p01 = new Point2D.Double(x0 + width, y0); 709 Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 710 p01.getY() - getYOffset()); 711 Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 712 p00.getY() - getYOffset()); 713 714 715 Point2D p0 = new Point2D.Double(x0, y1); 716 Point2D p1 = new Point2D.Double(x0 + width, y1); 717 Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 718 p1.getY() - getYOffset()); 719 Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 720 p0.getY() - getYOffset()); 721 722 GeneralPath right = new GeneralPath(); 723 right.moveTo((float) p1.getX(), (float) p1.getY()); 724 right.lineTo((float) p01.getX(), (float) p01.getY()); 725 right.lineTo((float) p02.getX(), (float) p02.getY()); 726 right.lineTo((float) p2.getX(), (float) p2.getY()); 727 right.closePath(); 728 729 GeneralPath left = new GeneralPath(); 730 left.moveTo((float) p0.getX(), (float) p0.getY()); 731 left.lineTo((float) p00.getX(), (float) p00.getY()); 732 left.lineTo((float) p03.getX(), (float) p03.getY()); 733 left.lineTo((float) p3.getX(), (float) p3.getY()); 734 left.closePath(); 735 736 GeneralPath back = new GeneralPath(); 737 back.moveTo((float) p2.getX(), (float) p2.getY()); 738 back.lineTo((float) p02.getX(), (float) p02.getY()); 739 back.lineTo((float) p03.getX(), (float) p03.getY()); 740 back.lineTo((float) p3.getX(), (float) p3.getY()); 741 back.closePath(); 742 743 GeneralPath front = new GeneralPath(); 744 front.moveTo((float) p0.getX(), (float) p0.getY()); 745 front.lineTo((float) p1.getX(), (float) p1.getY()); 746 front.lineTo((float) p01.getX(), (float) p01.getY()); 747 front.lineTo((float) p00.getX(), (float) p00.getY()); 748 front.closePath(); 749 750 GeneralPath top = new GeneralPath(); 751 top.moveTo((float) p0.getX(), (float) p0.getY()); 752 top.lineTo((float) p1.getX(), (float) p1.getY()); 753 top.lineTo((float) p2.getX(), (float) p2.getY()); 754 top.lineTo((float) p3.getX(), (float) p3.getY()); 755 top.closePath(); 756 757 GeneralPath bottom = new GeneralPath(); 758 bottom.moveTo((float) p00.getX(), (float) p00.getY()); 759 bottom.lineTo((float) p01.getX(), (float) p01.getY()); 760 bottom.lineTo((float) p02.getX(), (float) p02.getY()); 761 bottom.lineTo((float) p03.getX(), (float) p03.getY()); 762 bottom.closePath(); 763 764 result[0] = bottom; 765 result[1] = back; 766 result[2] = left; 767 result[3] = right; 768 result[4] = top; 769 result[5] = front; 770 if (inverted) { 771 result[0] = top; 772 result[4] = bottom; 773 } 774 return result; 775 } 776 777 /** 778 * Tests this renderer for equality with an arbitrary object. 779 * 780 * @param obj the object (<code>null</code> permitted). 781 * 782 * @return A boolean. 783 */ 784 public boolean equals(Object obj) { 785 if (obj == this) { 786 return true; 787 } 788 if (!(obj instanceof StackedBarRenderer3D)) { 789 return false; 790 } 791 if (!super.equals(obj)) { 792 return false; 793 } 794 StackedBarRenderer3D that = (StackedBarRenderer3D) obj; 795 if (this.renderAsPercentages != that.getRenderAsPercentages()) { 796 return false; 797 } 798 return true; 799 } 800 801 }