Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2005, 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: * StackedAreaRenderer.java 29: * ------------------------ 30: * (C) Copyright 2002-2006, by Dan Rivett (d.rivett@ukonline.co.uk) and 31: * Contributors. 32: * 33: * Original Author: Dan Rivett (adapted from AreaCategoryItemRenderer); 34: * Contributor(s): Jon Iles; 35: * David Gilbert (for Object Refinery Limited); 36: * Christian W. Zuckschwerdt; 37: * 38: * $Id: StackedAreaRenderer.java,v 1.6.2.3 2006/10/11 16:25:50 mungady Exp $ 39: * 40: * Changes: 41: * -------- 42: * 20-Sep-2002 : Version 1, contributed by Dan Rivett; 43: * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 44: * CategoryToolTipGenerator interface (DG); 45: * 01-Nov-2002 : Added tooltips (DG); 46: * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis 47: * for category spacing. Renamed StackedAreaCategoryItemRenderer 48: * --> StackedAreaRenderer (DG); 49: * 26-Nov-2002 : Switched CategoryDataset --> TableDataset (DG); 50: * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG); 51: * 17-Jan-2003 : Moved plot classes to a separate package (DG); 52: * 25-Mar-2003 : Implemented Serializable (DG); 53: * 13-May-2003 : Modified to take into account the plot orientation (DG); 54: * 30-Jul-2003 : Modified entity constructor (CZ); 55: * 07-Oct-2003 : Added renderer state (DG); 56: * 29-Apr-2004 : Added getRangeExtent() override (DG); 57: * 05-Nov-2004 : Modified drawItem() signature (DG); 58: * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() (DG); 59: * ------------- JFREECHART 1.0.x --------------------------------------------- 60: * 11-Oct-2006 : Added support for rendering data values as percentages, 61: * and added a second pass for drawing item labels (DG); 62: * 63: */ 64: 65: package org.jfree.chart.renderer.category; 66: 67: import java.awt.Graphics2D; 68: import java.awt.Polygon; 69: import java.awt.Shape; 70: import java.awt.geom.Rectangle2D; 71: import java.io.Serializable; 72: 73: import org.jfree.chart.axis.CategoryAxis; 74: import org.jfree.chart.axis.ValueAxis; 75: import org.jfree.chart.entity.EntityCollection; 76: import org.jfree.chart.event.RendererChangeEvent; 77: import org.jfree.chart.plot.CategoryPlot; 78: import org.jfree.chart.plot.PlotOrientation; 79: import org.jfree.data.DataUtilities; 80: import org.jfree.data.Range; 81: import org.jfree.data.category.CategoryDataset; 82: import org.jfree.data.general.DatasetUtilities; 83: import org.jfree.ui.RectangleEdge; 84: import org.jfree.util.PublicCloneable; 85: 86: /** 87: * A renderer that draws stacked area charts for a 88: * {@link org.jfree.chart.plot.CategoryPlot}. 89: */ 90: public class StackedAreaRenderer extends AreaRenderer 91: implements Cloneable, PublicCloneable, 92: Serializable { 93: 94: /** For serialization. */ 95: private static final long serialVersionUID = -3595635038460823663L; 96: 97: /** A flag that controls whether the areas display values or percentages. */ 98: private boolean renderAsPercentages; 99: 100: /** 101: * Creates a new renderer. 102: */ 103: public StackedAreaRenderer() { 104: this(false); 105: } 106: 107: /** 108: * Creates a new renderer. 109: * 110: * @param renderAsPercentages a flag that controls whether the data values 111: * are rendered as percentages. 112: */ 113: public StackedAreaRenderer(boolean renderAsPercentages) { 114: super(); 115: this.renderAsPercentages = renderAsPercentages; 116: } 117: 118: /** 119: * Returns <code>true</code> if the renderer displays each item value as 120: * a percentage (so that the stacked areas add to 100%), and 121: * <code>false</code> otherwise. 122: * 123: * @return A boolean. 124: * 125: * @since 1.0.3 126: */ 127: public boolean getRenderAsPercentages() { 128: return this.renderAsPercentages; 129: } 130: 131: /** 132: * Sets the flag that controls whether the renderer displays each item 133: * value as a percentage (so that the stacked areas add to 100%), and sends 134: * a {@link RendererChangeEvent} to all registered listeners. 135: * 136: * @param asPercentages the flag. 137: * 138: * @since 1.0.3 139: */ 140: public void setRenderAsPercentages(boolean asPercentages) { 141: this.renderAsPercentages = asPercentages; 142: notifyListeners(new RendererChangeEvent(this)); 143: } 144: 145: /** 146: * Returns the number of passes (<code>2</code>) required by this renderer. 147: * The first pass is used to draw the bars, the second pass is used to 148: * draw the item labels (if visible). 149: * 150: * @return The number of passes required by the renderer. 151: */ 152: public int getPassCount() { 153: return 2; 154: } 155: 156: /** 157: * Returns the range of values the renderer requires to display all the 158: * items from the specified dataset. 159: * 160: * @param dataset the dataset (<code>null</code> not permitted). 161: * 162: * @return The range (or <code>null</code> if the dataset is empty). 163: */ 164: public Range findRangeBounds(CategoryDataset dataset) { 165: if (this.renderAsPercentages) { 166: return new Range(0.0, 1.0); 167: } 168: else { 169: return DatasetUtilities.findStackedRangeBounds(dataset); 170: } 171: } 172: 173: /** 174: * Draw a single data item. 175: * 176: * @param g2 the graphics device. 177: * @param state the renderer state. 178: * @param dataArea the data plot area. 179: * @param plot the plot. 180: * @param domainAxis the domain axis. 181: * @param rangeAxis the range axis. 182: * @param dataset the data. 183: * @param row the row index (zero-based). 184: * @param column the column index (zero-based). 185: * @param pass the pass index. 186: */ 187: public void drawItem(Graphics2D g2, 188: CategoryItemRendererState state, 189: Rectangle2D dataArea, 190: CategoryPlot plot, 191: CategoryAxis domainAxis, 192: ValueAxis rangeAxis, 193: CategoryDataset dataset, 194: int row, 195: int column, 196: int pass) { 197: 198: // plot non-null values... 199: Number dataValue = dataset.getValue(row, column); 200: if (dataValue == null) { 201: return; 202: } 203: 204: double value = dataValue.doubleValue(); 205: double total = 0.0; // only needed if calculating percentages 206: if (this.renderAsPercentages) { 207: total = DataUtilities.calculateColumnTotal(dataset, column); 208: value = value / total; 209: } 210: 211: // leave the y values (y1, y0) untranslated as it is going to be be 212: // stacked up later by previous series values, after this it will be 213: // translated. 214: double xx1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 215: dataArea, plot.getDomainAxisEdge()); 216: 217: double previousHeightx1 = getPreviousHeight(dataset, row, column); 218: double y1 = value + previousHeightx1; 219: RectangleEdge location = plot.getRangeAxisEdge(); 220: double yy1 = rangeAxis.valueToJava2D(y1, dataArea, location); 221: 222: g2.setPaint(getItemPaint(row, column)); 223: g2.setStroke(getItemStroke(row, column)); 224: 225: // in column zero, the only job to do is draw any visible item labels 226: // and this is done in the second pass... 227: if (column == 0) { 228: if (pass == 1) { 229: // draw item labels, if visible 230: if (isItemLabelVisible(row, column)) { 231: drawItemLabel(g2, plot.getOrientation(), dataset, row, column, 232: xx1, yy1, (y1 < 0.0)); 233: } 234: } 235: } 236: else { 237: Number previousValue = dataset.getValue(row, column - 1); 238: if (previousValue != null) { 239: 240: double xx0 = domainAxis.getCategoryMiddle(column - 1, 241: getColumnCount(), dataArea, plot.getDomainAxisEdge()); 242: double y0 = previousValue.doubleValue(); 243: if (this.renderAsPercentages) { 244: total = DataUtilities.calculateColumnTotal(dataset, 245: column - 1); 246: y0 = y0 / total; 247: } 248: 249: 250: // Get the previous height, but this will be different for both 251: // y0 and y1 as the previous series values could differ. 252: double previousHeightx0 = getPreviousHeight(dataset, row, 253: column - 1); 254: 255: // Now stack the current y values on top of the previous values. 256: y0 += previousHeightx0; 257: 258: // Now translate the previous heights 259: double previousHeightxx0 = rangeAxis.valueToJava2D( 260: previousHeightx0, dataArea, location); 261: double previousHeightxx1 = rangeAxis.valueToJava2D( 262: previousHeightx1, dataArea, location); 263: 264: // Now translate the current y values. 265: double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location); 266: 267: if (pass == 0) { 268: Polygon p = null; 269: PlotOrientation orientation = plot.getOrientation(); 270: if (orientation == PlotOrientation.HORIZONTAL) { 271: p = new Polygon(); 272: p.addPoint((int) yy0, (int) xx0); 273: p.addPoint((int) yy1, (int) xx1); 274: p.addPoint((int) previousHeightxx1, (int) xx1); 275: p.addPoint((int) previousHeightxx0, (int) xx0); 276: } 277: else if (orientation == PlotOrientation.VERTICAL) { 278: p = new Polygon(); 279: p.addPoint((int) xx0, (int) yy0); 280: p.addPoint((int) xx1, (int) yy1); 281: p.addPoint((int) xx1, (int) previousHeightxx1); 282: p.addPoint((int) xx0, (int) previousHeightxx0); 283: } 284: g2.setPaint(getItemPaint(row, column)); 285: g2.setStroke(getItemStroke(row, column)); 286: g2.fill(p); 287: } 288: else { 289: if (isItemLabelVisible(row, column)) { 290: drawItemLabel(g2, plot.getOrientation(), dataset, row, 291: column, xx1, yy1, (y1 < 0.0)); 292: } 293: } 294: } 295: 296: 297: } 298: 299: 300: // add an item entity, if this information is being collected 301: EntityCollection entities = state.getEntityCollection(); 302: if (entities != null) { 303: Shape shape = new Rectangle2D.Double(xx1 - 3.0, yy1 - 3.0, 6.0, 6.0); 304: addItemEntity(entities, dataset, row, column, shape); 305: } 306: 307: } 308: 309: /** 310: * Calculates the stacked value of the all series up to, but not including 311: * <code>series</code> for the specified category, <code>category</code>. 312: * It returns 0.0 if <code>series</code> is the first series, i.e. 0. 313: * 314: * @param dataset the dataset (<code>null</code> not permitted). 315: * @param series the series. 316: * @param category the category. 317: * 318: * @return double returns a cumulative value for all series' values up to 319: * but excluding <code>series</code> for Object 320: * <code>category</code>. 321: */ 322: protected double getPreviousHeight(CategoryDataset dataset, 323: int series, int category) { 324: 325: double result = 0.0; 326: Number n; 327: double total = 0.0; 328: if (this.renderAsPercentages) { 329: total = DataUtilities.calculateColumnTotal(dataset, category); 330: } 331: for (int i = 0; i < series; i++) { 332: n = dataset.getValue(i, category); 333: if (n != null) { 334: double v = n.doubleValue(); 335: if (this.renderAsPercentages) { 336: v = v / total; 337: } 338: result += v; 339: } 340: } 341: return result; 342: 343: } 344: 345: /** 346: * Checks this instance for equality with an arbitrary object. 347: * 348: * @param obj the object (<code>null</code> not permitted). 349: * 350: * @return A boolean. 351: */ 352: public boolean equals(Object obj) { 353: if (obj == this) { 354: return true; 355: } 356: if (! (obj instanceof StackedAreaRenderer)) { 357: return false; 358: } 359: StackedAreaRenderer that = (StackedAreaRenderer) obj; 360: if (this.renderAsPercentages != that.renderAsPercentages) { 361: return false; 362: } 363: return super.equals(obj); 364: } 365: }