Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ------------------------- 28: * StackedBarRenderer3D.java 29: * ------------------------- 30: * (C) Copyright 2000-2006, by Serge V. Grachov and Contributors. 31: * 32: * Original Author: Serge V. Grachov; 33: * Contributor(s): David Gilbert (for Object Refinery Limited); 34: * Richard Atkinson; 35: * Christian W. Zuckschwerdt; 36: * Max Herfort (patch 1459313); 37: * 38: * $Id: StackedBarRenderer3D.java,v 1.8.2.4 2006/04/05 10:38:21 mungady Exp $ 39: * 40: * Changes 41: * ------- 42: * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG); 43: * 15-Nov-2001 : Modified to allow for null data values (DG); 44: * 13-Dec-2001 : Added tooltips (DG); 45: * 15-Feb-2002 : Added isStacked() method (DG); 46: * 24-May-2002 : Incorporated tooltips into chart entities (DG); 47: * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG); 48: * 25-Jun-2002 : Removed redundant imports (DG); 49: * 26-Jun-2002 : Small change to entity (DG); 50: * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 51: * for HTML image maps (RA); 52: * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); 53: * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 54: * CategoryToolTipGenerator interface (DG); 55: * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG); 56: * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG); 57: * 17-Jan-2003 : Moved plot classes to a separate package (DG); 58: * 25-Mar-2003 : Implemented Serializable (DG); 59: * 01-May-2003 : Added default constructor (bug 726235) and fixed bug 60: * 726260) (DG); 61: * 13-May-2003 : Renamed StackedVerticalBarRenderer3D 62: * --> StackedBarRenderer3D (DG); 63: * 30-Jul-2003 : Modified entity constructor (CZ); 64: * 07-Oct-2003 : Added renderer state (DG); 65: * 21-Nov-2003 : Added a new constructor (DG); 66: * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG); 67: * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG); 68: * 05-Nov-2004 : Modified drawItem() signature (DG); 69: * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG); 70: * 18-Mar-2005 : Override for getPassCount() method (DG); 71: * 20-Apr-2005 : Renamed CategoryLabelGenerator 72: * --> CategoryItemLabelGenerator (DG); 73: * 09-Jun-2005 : Use addItemEntity() method from superclass (DG); 74: * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG); 75: * ------------- JFREECHART 1.0.0 --------------------------------------------- 76: * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted 77: * by Max Herfort (DG); 78: * 79: */ 80: 81: package org.jfree.chart.renderer.category; 82: 83: import java.awt.Color; 84: import java.awt.Graphics2D; 85: import java.awt.Paint; 86: import java.awt.geom.GeneralPath; 87: import java.awt.geom.Rectangle2D; 88: import java.io.Serializable; 89: 90: import org.jfree.chart.axis.CategoryAxis; 91: import org.jfree.chart.axis.ValueAxis; 92: import org.jfree.chart.entity.EntityCollection; 93: import org.jfree.chart.event.RendererChangeEvent; 94: import org.jfree.chart.labels.CategoryItemLabelGenerator; 95: import org.jfree.chart.plot.CategoryPlot; 96: import org.jfree.chart.plot.PlotOrientation; 97: import org.jfree.data.DataUtilities; 98: import org.jfree.data.Range; 99: import org.jfree.data.category.CategoryDataset; 100: import org.jfree.data.general.DatasetUtilities; 101: import org.jfree.ui.RectangleEdge; 102: import org.jfree.util.PublicCloneable; 103: 104: /** 105: * Renders stacked bars with 3D-effect, for use with the 106: * {@link org.jfree.chart.plot.CategoryPlot} class. 107: */ 108: public class StackedBarRenderer3D extends BarRenderer3D 109: implements Cloneable, PublicCloneable, 110: Serializable { 111: 112: /** For serialization. */ 113: private static final long serialVersionUID = -5832945916493247123L; 114: 115: /** A flag that controls whether the bars display values or percentages. */ 116: private boolean renderAsPercentages; 117: 118: /** 119: * Creates a new renderer with no tool tip generator and no URL generator. 120: * <P> 121: * The defaults (no tool tip or URL generators) have been chosen to 122: * minimise the processing required to generate a default chart. If you 123: * require tool tips or URLs, then you can easily add the required 124: * generators. 125: */ 126: public StackedBarRenderer3D() { 127: this(false); 128: } 129: 130: /** 131: * Constructs a new renderer with the specified '3D effect'. 132: * 133: * @param xOffset the x-offset for the 3D effect. 134: * @param yOffset the y-offset for the 3D effect. 135: */ 136: public StackedBarRenderer3D(double xOffset, double yOffset) { 137: super(xOffset, yOffset); 138: } 139: 140: /** 141: * Creates a new renderer. 142: * 143: * @param renderAsPercentages a flag that controls whether the data values 144: * are rendered as percentages. 145: * 146: * @since 1.0.2 147: */ 148: public StackedBarRenderer3D(boolean renderAsPercentages) { 149: super(); 150: this.renderAsPercentages = renderAsPercentages; 151: } 152: 153: /** 154: * Constructs a new renderer with the specified '3D effect'. 155: * 156: * @param xOffset the x-offset for the 3D effect. 157: * @param yOffset the y-offset for the 3D effect. 158: * @param renderAsPercentages a flag that controls whether the data values 159: * are rendered as percentages. 160: * 161: * @since 1.0.2 162: */ 163: public StackedBarRenderer3D(double xOffset, double yOffset, 164: boolean renderAsPercentages) { 165: super(xOffset, yOffset); 166: this.renderAsPercentages = renderAsPercentages; 167: } 168: 169: /** 170: * Returns <code>true</code> if the renderer displays each item value as 171: * a percentage (so that the stacked bars add to 100%), and 172: * <code>false</code> otherwise. 173: * 174: * @return A boolean. 175: * 176: * @since 1.0.2 177: */ 178: public boolean getRenderAsPercentages() { 179: return this.renderAsPercentages; 180: } 181: 182: /** 183: * Sets the flag that controls whether the renderer displays each item 184: * value as a percentage (so that the stacked bars add to 100%), and sends 185: * a {@link RendererChangeEvent} to all registered listeners. 186: * 187: * @param asPercentages the flag. 188: * 189: * @since 1.0.2 190: */ 191: public void setRenderAsPercentages(boolean asPercentages) { 192: this.renderAsPercentages = asPercentages; 193: notifyListeners(new RendererChangeEvent(this)); 194: } 195: 196: /** 197: * Returns the range of values the renderer requires to display all the 198: * items from the specified dataset. 199: * 200: * @param dataset the dataset (<code>null</code> not permitted). 201: * 202: * @return The range (or <code>null</code> if the dataset is empty). 203: */ 204: public Range findRangeBounds(CategoryDataset dataset) { 205: if (this.renderAsPercentages) { 206: return new Range(0.0, 1.0); 207: } 208: else { 209: return DatasetUtilities.findStackedRangeBounds(dataset); 210: } 211: } 212: 213: /** 214: * Calculates the bar width and stores it in the renderer state. 215: * 216: * @param plot the plot. 217: * @param dataArea the data area. 218: * @param rendererIndex the renderer index. 219: * @param state the renderer state. 220: */ 221: protected void calculateBarWidth(CategoryPlot plot, 222: Rectangle2D dataArea, 223: int rendererIndex, 224: CategoryItemRendererState state) { 225: 226: // calculate the bar width 227: CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); 228: CategoryDataset data = plot.getDataset(rendererIndex); 229: if (data != null) { 230: PlotOrientation orientation = plot.getOrientation(); 231: double space = 0.0; 232: if (orientation == PlotOrientation.HORIZONTAL) { 233: space = dataArea.getHeight(); 234: } 235: else if (orientation == PlotOrientation.VERTICAL) { 236: space = dataArea.getWidth(); 237: } 238: double maxWidth = space * getMaximumBarWidth(); 239: int columns = data.getColumnCount(); 240: double categoryMargin = 0.0; 241: if (columns > 1) { 242: categoryMargin = domainAxis.getCategoryMargin(); 243: } 244: 245: double used = space * (1 - domainAxis.getLowerMargin() 246: - domainAxis.getUpperMargin() 247: - categoryMargin); 248: if (columns > 0) { 249: state.setBarWidth(Math.min(used / columns, maxWidth)); 250: } 251: else { 252: state.setBarWidth(Math.min(used, maxWidth)); 253: } 254: } 255: 256: } 257: 258: /** 259: * Draws a stacked bar (with 3D-effect) for a specific item. 260: * 261: * @param g2 the graphics device. 262: * @param state the renderer state. 263: * @param dataArea the plot area. 264: * @param plot the plot. 265: * @param domainAxis the domain (category) axis. 266: * @param rangeAxis the range (value) axis. 267: * @param dataset the data. 268: * @param row the row index (zero-based). 269: * @param column the column index (zero-based). 270: * @param pass the pass index. 271: */ 272: public void drawItem(Graphics2D g2, 273: CategoryItemRendererState state, 274: Rectangle2D dataArea, 275: CategoryPlot plot, 276: CategoryAxis domainAxis, 277: ValueAxis rangeAxis, 278: CategoryDataset dataset, 279: int row, 280: int column, 281: int pass) { 282: 283: // check the value we are plotting... 284: Number dataValue = dataset.getValue(row, column); 285: if (dataValue == null) { 286: return; 287: } 288: 289: double value = dataValue.doubleValue(); 290: double total = 0.0; // only needed if calculating percentages 291: if (this.renderAsPercentages) { 292: total = DataUtilities.calculateColumnTotal(dataset, column); 293: value = value / total; 294: } 295: 296: Rectangle2D adjusted = new Rectangle2D.Double( 297: dataArea.getX(), dataArea.getY() + getYOffset(), 298: dataArea.getWidth() - getXOffset(), 299: dataArea.getHeight() - getYOffset() 300: ); 301: 302: PlotOrientation orientation = plot.getOrientation(); 303: 304: double barW0 = domainAxis.getCategoryMiddle( 305: column, getColumnCount(), adjusted, plot.getDomainAxisEdge() 306: ) - state.getBarWidth() / 2.0; 307: 308: double positiveBase = getBase(); 309: double negativeBase = positiveBase; 310: for (int i = 0; i < row; i++) { 311: Number v = dataset.getValue(i, column); 312: if (v != null) { 313: double d = v.doubleValue(); 314: if (this.renderAsPercentages) { 315: d = d / total; 316: } 317: if (d > 0) { 318: positiveBase = positiveBase + d; 319: } 320: else { 321: negativeBase = negativeBase + d; 322: } 323: } 324: } 325: 326: double translatedBase; 327: double translatedValue; 328: RectangleEdge location = plot.getRangeAxisEdge(); 329: if (value > 0.0) { 330: translatedBase = rangeAxis.valueToJava2D(positiveBase, adjusted, 331: location); 332: translatedValue = rangeAxis.valueToJava2D(positiveBase + value, 333: adjusted, location); 334: } 335: else { 336: translatedBase = rangeAxis.valueToJava2D(negativeBase, adjusted, 337: location); 338: translatedValue = rangeAxis.valueToJava2D(negativeBase + value, 339: adjusted, location); 340: } 341: double barL0 = Math.min(translatedBase, translatedValue); 342: double barLength = Math.max( 343: Math.abs(translatedValue - translatedBase), getMinimumBarLength() 344: ); 345: 346: Rectangle2D bar = null; 347: if (orientation == PlotOrientation.HORIZONTAL) { 348: bar = new Rectangle2D.Double(barL0, barW0, barLength, 349: state.getBarWidth()); 350: } 351: else { 352: bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(), 353: barLength); 354: } 355: Paint itemPaint = getItemPaint(row, column); 356: g2.setPaint(itemPaint); 357: g2.fill(bar); 358: 359: if (pass == 0) { 360: double x0 = bar.getMinX(); 361: double x1 = x0 + getXOffset(); 362: double x2 = bar.getMaxX(); 363: double x3 = x2 + getXOffset(); 364: 365: double y0 = bar.getMinY() - getYOffset(); 366: double y1 = bar.getMinY(); 367: double y2 = bar.getMaxY() - getYOffset(); 368: double y3 = bar.getMaxY(); 369: 370: GeneralPath bar3dRight = null; 371: GeneralPath bar3dTop = null; 372: if (value > 0.0 || orientation == PlotOrientation.VERTICAL) { 373: bar3dRight = new GeneralPath(); 374: bar3dRight.moveTo((float) x2, (float) y3); 375: bar3dRight.lineTo((float) x2, (float) y1); 376: bar3dRight.lineTo((float) x3, (float) y0); 377: bar3dRight.lineTo((float) x3, (float) y2); 378: bar3dRight.closePath(); 379: 380: if (itemPaint instanceof Color) { 381: g2.setPaint(((Color) itemPaint).darker()); 382: } 383: g2.fill(bar3dRight); 384: } 385: 386: if (value > 0.0 || orientation == PlotOrientation.HORIZONTAL) { 387: bar3dTop = new GeneralPath(); 388: bar3dTop.moveTo((float) x0, (float) y1); 389: bar3dTop.lineTo((float) x1, (float) y0); 390: bar3dTop.lineTo((float) x3, (float) y0); 391: bar3dTop.lineTo((float) x2, (float) y1); 392: bar3dTop.closePath(); 393: g2.fill(bar3dTop); 394: } 395: 396: if (isDrawBarOutline() && state.getBarWidth() > 3) { 397: g2.setStroke(getItemOutlineStroke(row, column)); 398: g2.setPaint(getItemOutlinePaint(row, column)); 399: g2.draw(bar); 400: if (bar3dRight != null) { 401: g2.draw(bar3dRight); 402: } 403: if (bar3dTop != null) { 404: g2.draw(bar3dTop); 405: } 406: } 407: 408: // add an item entity, if this information is being collected 409: EntityCollection entities = state.getEntityCollection(); 410: if (entities != null) { 411: addItemEntity(entities, dataset, row, column, bar); 412: } 413: } 414: else if (pass == 1) { 415: CategoryItemLabelGenerator generator 416: = getItemLabelGenerator(row, column); 417: if (generator != null && isItemLabelVisible(row, column)) { 418: drawItemLabel( 419: g2, dataset, row, column, plot, generator, bar, 420: (value < 0.0) 421: ); 422: } 423: } 424: 425: } 426: 427: /** 428: * Returns the number of passes through the dataset required by the 429: * renderer. This method returns <code>2</code>, the second pass is used 430: * to draw the item labels. 431: * 432: * @return The pass count. 433: */ 434: public int getPassCount() { 435: return 2; 436: } 437: 438: /** 439: * Tests this renderer for equality with an arbitrary object. 440: * 441: * @param obj the object (<code>null</code> permitted). 442: * 443: * @return A boolean. 444: */ 445: public boolean equals(Object obj) { 446: if (obj == this) { 447: return true; 448: } 449: if (!(obj instanceof StackedBarRenderer3D)) { 450: return false; 451: } 452: if (!super.equals(obj)) { 453: return false; 454: } 455: StackedBarRenderer3D that = (StackedBarRenderer3D) obj; 456: if (this.renderAsPercentages != that.renderAsPercentages) { 457: return false; 458: } 459: return true; 460: } 461: 462: }