Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2008, 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: * DeviationRenderer.java 29: * ---------------------- 30: * (C) Copyright 2007, 2008, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * Changes 36: * ------- 37: * 21-Feb-2007 : Version 1 (DG); 38: * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG); 39: * 11-Apr-2008 : New override for findRangeBounds() (DG); 40: * 41: */ 42: 43: package org.jfree.chart.renderer.xy; 44: 45: import java.awt.AlphaComposite; 46: import java.awt.Composite; 47: import java.awt.Graphics2D; 48: import java.awt.geom.GeneralPath; 49: import java.awt.geom.Rectangle2D; 50: import java.util.List; 51: 52: import org.jfree.chart.axis.ValueAxis; 53: import org.jfree.chart.entity.EntityCollection; 54: import org.jfree.chart.event.RendererChangeEvent; 55: import org.jfree.chart.plot.CrosshairState; 56: import org.jfree.chart.plot.PlotOrientation; 57: import org.jfree.chart.plot.PlotRenderingInfo; 58: import org.jfree.chart.plot.XYPlot; 59: import org.jfree.data.Range; 60: import org.jfree.data.general.DatasetUtilities; 61: import org.jfree.data.xy.IntervalXYDataset; 62: import org.jfree.data.xy.XYDataset; 63: import org.jfree.ui.RectangleEdge; 64: 65: /** 66: * A specialised subclass of the {@link XYLineAndShapeRenderer} that requires 67: * an {@link IntervalXYDataset} and represents the y-interval by shading an 68: * area behind the y-values on the chart. 69: * 70: * @since 1.0.5 71: */ 72: public class DeviationRenderer extends XYLineAndShapeRenderer { 73: 74: /** 75: * A state object that is passed to each call to <code>drawItem</code>. 76: */ 77: public static class State extends XYLineAndShapeRenderer.State { 78: 79: /** 80: * A list of coordinates for the upper y-values in the current series 81: * (after translation into Java2D space). 82: */ 83: public List upperCoordinates; 84: 85: /** 86: * A list of coordinates for the lower y-values in the current series 87: * (after translation into Java2D space). 88: */ 89: public List lowerCoordinates; 90: 91: /** 92: * Creates a new state instance. 93: * 94: * @param info the plot rendering info. 95: */ 96: public State(PlotRenderingInfo info) { 97: super(info); 98: this.lowerCoordinates = new java.util.ArrayList(); 99: this.upperCoordinates = new java.util.ArrayList(); 100: } 101: 102: } 103: 104: /** The alpha transparency for the interval shading. */ 105: private float alpha; 106: 107: /** 108: * Creates a new renderer that displays lines and shapes for the data 109: * items, as well as the shaded area for the y-interval. 110: */ 111: public DeviationRenderer() { 112: this(true, true); 113: } 114: 115: /** 116: * Creates a new renderer. 117: * 118: * @param lines show lines between data items? 119: * @param shapes show a shape for each data item? 120: */ 121: public DeviationRenderer(boolean lines, boolean shapes) { 122: super(lines, shapes); 123: super.setDrawSeriesLineAsPath(true); 124: this.alpha = 0.5f; 125: } 126: 127: /** 128: * Returns the alpha transparency for the background shading. 129: * 130: * @return The alpha transparency. 131: * 132: * @see #setAlpha(float) 133: */ 134: public float getAlpha() { 135: return this.alpha; 136: } 137: 138: /** 139: * Sets the alpha transparency for the background shading, and sends a 140: * {@link RendererChangeEvent} to all registered listeners. 141: * 142: * @param alpha the alpha (in the range 0.0f to 1.0f). 143: * 144: * @see #getAlpha() 145: */ 146: public void setAlpha(float alpha) { 147: if (alpha < 0.0f || alpha > 1.0f) { 148: throw new IllegalArgumentException( 149: "Requires 'alpha' in the range 0.0 to 1.0."); 150: } 151: this.alpha = alpha; 152: fireChangeEvent(); 153: } 154: 155: /** 156: * This method is overridden so that this flag cannot be changed---it is 157: * set to <code>true</code> for this renderer. 158: * 159: * @param flag ignored. 160: */ 161: public void setDrawSeriesLineAsPath(boolean flag) { 162: // ignore 163: } 164: 165: /** 166: * Returns the range of values the renderer requires to display all the 167: * items from the specified dataset. 168: * 169: * @param dataset the dataset (<code>null</code> permitted). 170: * 171: * @return The range (<code>null</code> if the dataset is <code>null</code> 172: * or empty). 173: */ 174: public Range findRangeBounds(XYDataset dataset) { 175: if (dataset != null) { 176: return DatasetUtilities.findRangeBounds(dataset, true); 177: } 178: else { 179: return null; 180: } 181: } 182: 183: /** 184: * Initialises and returns a state object that can be passed to each 185: * invocation of the {@link #drawItem} method. 186: * 187: * @param g2 the graphics target. 188: * @param dataArea the data area. 189: * @param plot the plot. 190: * @param dataset the dataset. 191: * @param info the plot rendering info. 192: * 193: * @return A newly initialised state object. 194: */ 195: public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 196: XYPlot plot, XYDataset dataset, PlotRenderingInfo info) { 197: State state = new State(info); 198: state.seriesPath = new GeneralPath(); 199: state.setProcessVisibleItemsOnly(false); 200: return state; 201: } 202: 203: /** 204: * Returns the number of passes (through the dataset) used by this 205: * renderer. 206: * 207: * @return <code>3</code>. 208: */ 209: public int getPassCount() { 210: return 3; 211: } 212: 213: /** 214: * Returns <code>true</code> if this is the pass where the shapes are 215: * drawn. 216: * 217: * @param pass the pass index. 218: * 219: * @return A boolean. 220: * 221: * @see #isLinePass(int) 222: */ 223: protected boolean isItemPass(int pass) { 224: return (pass == 2); 225: } 226: 227: /** 228: * Returns <code>true</code> if this is the pass where the lines are 229: * drawn. 230: * 231: * @param pass the pass index. 232: * 233: * @return A boolean. 234: * 235: * @see #isItemPass(int) 236: */ 237: protected boolean isLinePass(int pass) { 238: return (pass == 1); 239: } 240: 241: /** 242: * Draws the visual representation of a single data item. 243: * 244: * @param g2 the graphics device. 245: * @param state the renderer state. 246: * @param dataArea the area within which the data is being drawn. 247: * @param info collects information about the drawing. 248: * @param plot the plot (can be used to obtain standard color 249: * information etc). 250: * @param domainAxis the domain axis. 251: * @param rangeAxis the range axis. 252: * @param dataset the dataset. 253: * @param series the series index (zero-based). 254: * @param item the item index (zero-based). 255: * @param crosshairState crosshair information for the plot 256: * (<code>null</code> permitted). 257: * @param pass the pass index. 258: */ 259: public void drawItem(Graphics2D g2, 260: XYItemRendererState state, 261: Rectangle2D dataArea, 262: PlotRenderingInfo info, 263: XYPlot plot, 264: ValueAxis domainAxis, 265: ValueAxis rangeAxis, 266: XYDataset dataset, 267: int series, 268: int item, 269: CrosshairState crosshairState, 270: int pass) { 271: 272: // do nothing if item is not visible 273: if (!getItemVisible(series, item)) { 274: return; 275: } 276: 277: // first pass draws the shading 278: if (pass == 0) { 279: IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 280: State drState = (State) state; 281: 282: double x = intervalDataset.getXValue(series, item); 283: double yLow = intervalDataset.getStartYValue(series, item); 284: double yHigh = intervalDataset.getEndYValue(series, item); 285: 286: RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 287: RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 288: 289: double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation); 290: double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 291: yAxisLocation); 292: double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 293: yAxisLocation); 294: 295: PlotOrientation orientation = plot.getOrientation(); 296: if (orientation == PlotOrientation.HORIZONTAL) { 297: drState.lowerCoordinates.add(new double[] {yyLow, xx}); 298: drState.upperCoordinates.add(new double[] {yyHigh, xx}); 299: } 300: else if (orientation == PlotOrientation.VERTICAL) { 301: drState.lowerCoordinates.add(new double[] {xx, yyLow}); 302: drState.upperCoordinates.add(new double[] {xx, yyHigh}); 303: } 304: 305: if (item == (dataset.getItemCount(series) - 1)) { 306: // last item in series, draw the lot... 307: // set up the alpha-transparency... 308: Composite originalComposite = g2.getComposite(); 309: g2.setComposite(AlphaComposite.getInstance( 310: AlphaComposite.SRC_OVER, this.alpha)); 311: g2.setPaint(getItemFillPaint(series, item)); 312: GeneralPath area = new GeneralPath(); 313: double[] coords = (double[]) drState.lowerCoordinates.get(0); 314: area.moveTo((float) coords[0], (float) coords[1]); 315: for (int i = 1; i < drState.lowerCoordinates.size(); i++) { 316: coords = (double[]) drState.lowerCoordinates.get(i); 317: area.lineTo((float) coords[0], (float) coords[1]); 318: } 319: int count = drState.upperCoordinates.size(); 320: coords = (double[]) drState.upperCoordinates.get(count - 1); 321: area.lineTo((float) coords[0], (float) coords[1]); 322: for (int i = count - 2; i >= 0; i--) { 323: coords = (double[]) drState.upperCoordinates.get(i); 324: area.lineTo((float) coords[0], (float) coords[1]); 325: } 326: area.closePath(); 327: g2.fill(area); 328: g2.setComposite(originalComposite); 329: 330: drState.lowerCoordinates.clear(); 331: drState.upperCoordinates.clear(); 332: } 333: } 334: if (isLinePass(pass)) { 335: 336: // the following code handles the line for the y-values...it's 337: // all done by code in the super class 338: if (item == 0) { 339: State s = (State) state; 340: s.seriesPath.reset(); 341: s.setLastPointGood(false); 342: } 343: 344: if (getItemLineVisible(series, item)) { 345: drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 346: series, item, domainAxis, rangeAxis, dataArea); 347: } 348: } 349: 350: // second pass adds shapes where the items are .. 351: else if (isItemPass(pass)) { 352: 353: // setup for collecting optional entity info... 354: EntityCollection entities = null; 355: if (info != null) { 356: entities = info.getOwner().getEntityCollection(); 357: } 358: 359: drawSecondaryPass(g2, plot, dataset, pass, series, item, 360: domainAxis, dataArea, rangeAxis, crosshairState, entities); 361: } 362: } 363: 364: /** 365: * Tests this renderer for equality with an arbitrary object. 366: * 367: * @param obj the object (<code>null</code> permitted). 368: * 369: * @return A boolean. 370: */ 371: public boolean equals(Object obj) { 372: if (obj == this) { 373: return true; 374: } 375: if (!(obj instanceof DeviationRenderer)) { 376: return false; 377: } 378: DeviationRenderer that = (DeviationRenderer) obj; 379: if (this.alpha != that.alpha) { 380: return false; 381: } 382: return super.equals(obj); 383: } 384: 385: }