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