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 * DeviationRenderer.java 029 * ---------------------- 030 * (C) Copyright 2007-2009, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 21-Feb-2007 : Version 1 (DG); 038 * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG); 039 * 11-Apr-2008 : New override for findRangeBounds() (DG); 040 * 27-Mar-2009 : Updated findRangeBounds() to call new inherited method (DG); 041 * 042 */ 043 044 package org.jfree.chart.renderer.xy; 045 046 import java.awt.AlphaComposite; 047 import java.awt.Composite; 048 import java.awt.Graphics2D; 049 import java.awt.geom.GeneralPath; 050 import java.awt.geom.Rectangle2D; 051 import java.util.List; 052 053 import org.jfree.chart.axis.ValueAxis; 054 import org.jfree.chart.entity.EntityCollection; 055 import org.jfree.chart.event.RendererChangeEvent; 056 import org.jfree.chart.plot.CrosshairState; 057 import org.jfree.chart.plot.PlotOrientation; 058 import org.jfree.chart.plot.PlotRenderingInfo; 059 import org.jfree.chart.plot.XYPlot; 060 import org.jfree.data.Range; 061 import org.jfree.data.general.DatasetUtilities; 062 import org.jfree.data.xy.IntervalXYDataset; 063 import org.jfree.data.xy.XYDataset; 064 import org.jfree.ui.RectangleEdge; 065 066 /** 067 * A specialised subclass of the {@link XYLineAndShapeRenderer} that requires 068 * an {@link IntervalXYDataset} and represents the y-interval by shading an 069 * area behind the y-values on the chart. 070 * The example shown here is generated by the 071 * <code>DeviationRendererDemo1.java</code> program included in the 072 * JFreeChart demo collection: 073 * <br><br> 074 * <img src="../../../../../images/DeviationRendererSample.png" 075 * alt="DeviationRendererSample.png" /> 076 * 077 * @since 1.0.5 078 */ 079 public class DeviationRenderer extends XYLineAndShapeRenderer { 080 081 /** 082 * A state object that is passed to each call to <code>drawItem</code>. 083 */ 084 public static class State extends XYLineAndShapeRenderer.State { 085 086 /** 087 * A list of coordinates for the upper y-values in the current series 088 * (after translation into Java2D space). 089 */ 090 public List upperCoordinates; 091 092 /** 093 * A list of coordinates for the lower y-values in the current series 094 * (after translation into Java2D space). 095 */ 096 public List lowerCoordinates; 097 098 /** 099 * Creates a new state instance. 100 * 101 * @param info the plot rendering info. 102 */ 103 public State(PlotRenderingInfo info) { 104 super(info); 105 this.lowerCoordinates = new java.util.ArrayList(); 106 this.upperCoordinates = new java.util.ArrayList(); 107 } 108 109 } 110 111 /** The alpha transparency for the interval shading. */ 112 private float alpha; 113 114 /** 115 * Creates a new renderer that displays lines and shapes for the data 116 * items, as well as the shaded area for the y-interval. 117 */ 118 public DeviationRenderer() { 119 this(true, true); 120 } 121 122 /** 123 * Creates a new renderer. 124 * 125 * @param lines show lines between data items? 126 * @param shapes show a shape for each data item? 127 */ 128 public DeviationRenderer(boolean lines, boolean shapes) { 129 super(lines, shapes); 130 super.setDrawSeriesLineAsPath(true); 131 this.alpha = 0.5f; 132 } 133 134 /** 135 * Returns the alpha transparency for the background shading. 136 * 137 * @return The alpha transparency. 138 * 139 * @see #setAlpha(float) 140 */ 141 public float getAlpha() { 142 return this.alpha; 143 } 144 145 /** 146 * Sets the alpha transparency for the background shading, and sends a 147 * {@link RendererChangeEvent} to all registered listeners. 148 * 149 * @param alpha the alpha (in the range 0.0f to 1.0f). 150 * 151 * @see #getAlpha() 152 */ 153 public void setAlpha(float alpha) { 154 if (alpha < 0.0f || alpha > 1.0f) { 155 throw new IllegalArgumentException( 156 "Requires 'alpha' in the range 0.0 to 1.0."); 157 } 158 this.alpha = alpha; 159 fireChangeEvent(); 160 } 161 162 /** 163 * This method is overridden so that this flag cannot be changed---it is 164 * set to <code>true</code> for this renderer. 165 * 166 * @param flag ignored. 167 */ 168 public void setDrawSeriesLineAsPath(boolean flag) { 169 // ignore 170 } 171 172 /** 173 * Returns the range of values the renderer requires to display all the 174 * items from the specified dataset. 175 * 176 * @param dataset the dataset (<code>null</code> permitted). 177 * 178 * @return The range (<code>null</code> if the dataset is <code>null</code> 179 * or empty). 180 */ 181 public Range findRangeBounds(XYDataset dataset) { 182 return findRangeBounds(dataset, true); 183 } 184 185 /** 186 * Initialises and returns a state object that can be passed to each 187 * invocation of the {@link #drawItem} method. 188 * 189 * @param g2 the graphics target. 190 * @param dataArea the data area. 191 * @param plot the plot. 192 * @param dataset the dataset. 193 * @param info the plot rendering info. 194 * 195 * @return A newly initialised state object. 196 */ 197 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 198 XYPlot plot, XYDataset dataset, PlotRenderingInfo info) { 199 State state = new State(info); 200 state.seriesPath = new GeneralPath(); 201 state.setProcessVisibleItemsOnly(false); 202 return state; 203 } 204 205 /** 206 * Returns the number of passes (through the dataset) used by this 207 * renderer. 208 * 209 * @return <code>3</code>. 210 */ 211 public int getPassCount() { 212 return 3; 213 } 214 215 /** 216 * Returns <code>true</code> if this is the pass where the shapes are 217 * drawn. 218 * 219 * @param pass the pass index. 220 * 221 * @return A boolean. 222 * 223 * @see #isLinePass(int) 224 */ 225 protected boolean isItemPass(int pass) { 226 return (pass == 2); 227 } 228 229 /** 230 * Returns <code>true</code> if this is the pass where the lines are 231 * drawn. 232 * 233 * @param pass the pass index. 234 * 235 * @return A boolean. 236 * 237 * @see #isItemPass(int) 238 */ 239 protected boolean isLinePass(int pass) { 240 return (pass == 1); 241 } 242 243 /** 244 * Draws the visual representation of a single data item. 245 * 246 * @param g2 the graphics device. 247 * @param state the renderer state. 248 * @param dataArea the area within which the data is being drawn. 249 * @param info collects information about the drawing. 250 * @param plot the plot (can be used to obtain standard color 251 * information etc). 252 * @param domainAxis the domain axis. 253 * @param rangeAxis the range axis. 254 * @param dataset the dataset. 255 * @param series the series index (zero-based). 256 * @param item the item index (zero-based). 257 * @param crosshairState crosshair information for the plot 258 * (<code>null</code> permitted). 259 * @param pass the pass index. 260 */ 261 public void drawItem(Graphics2D g2, 262 XYItemRendererState state, 263 Rectangle2D dataArea, 264 PlotRenderingInfo info, 265 XYPlot plot, 266 ValueAxis domainAxis, 267 ValueAxis rangeAxis, 268 XYDataset dataset, 269 int series, 270 int item, 271 CrosshairState crosshairState, 272 int pass) { 273 274 // do nothing if item is not visible 275 if (!getItemVisible(series, item)) { 276 return; 277 } 278 279 // first pass draws the shading 280 if (pass == 0) { 281 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 282 State drState = (State) state; 283 284 double x = intervalDataset.getXValue(series, item); 285 double yLow = intervalDataset.getStartYValue(series, item); 286 double yHigh = intervalDataset.getEndYValue(series, item); 287 288 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 289 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 290 291 double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation); 292 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 293 yAxisLocation); 294 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 295 yAxisLocation); 296 297 PlotOrientation orientation = plot.getOrientation(); 298 if (orientation == PlotOrientation.HORIZONTAL) { 299 drState.lowerCoordinates.add(new double[] {yyLow, xx}); 300 drState.upperCoordinates.add(new double[] {yyHigh, xx}); 301 } 302 else if (orientation == PlotOrientation.VERTICAL) { 303 drState.lowerCoordinates.add(new double[] {xx, yyLow}); 304 drState.upperCoordinates.add(new double[] {xx, yyHigh}); 305 } 306 307 if (item == (dataset.getItemCount(series) - 1)) { 308 // last item in series, draw the lot... 309 // set up the alpha-transparency... 310 Composite originalComposite = g2.getComposite(); 311 g2.setComposite(AlphaComposite.getInstance( 312 AlphaComposite.SRC_OVER, this.alpha)); 313 g2.setPaint(getItemFillPaint(series, item)); 314 GeneralPath area = new GeneralPath(); 315 double[] coords = (double[]) drState.lowerCoordinates.get(0); 316 area.moveTo((float) coords[0], (float) coords[1]); 317 for (int i = 1; i < drState.lowerCoordinates.size(); i++) { 318 coords = (double[]) drState.lowerCoordinates.get(i); 319 area.lineTo((float) coords[0], (float) coords[1]); 320 } 321 int count = drState.upperCoordinates.size(); 322 coords = (double[]) drState.upperCoordinates.get(count - 1); 323 area.lineTo((float) coords[0], (float) coords[1]); 324 for (int i = count - 2; i >= 0; i--) { 325 coords = (double[]) drState.upperCoordinates.get(i); 326 area.lineTo((float) coords[0], (float) coords[1]); 327 } 328 area.closePath(); 329 g2.fill(area); 330 g2.setComposite(originalComposite); 331 332 drState.lowerCoordinates.clear(); 333 drState.upperCoordinates.clear(); 334 } 335 } 336 if (isLinePass(pass)) { 337 338 // the following code handles the line for the y-values...it's 339 // all done by code in the super class 340 if (item == 0) { 341 State s = (State) state; 342 s.seriesPath.reset(); 343 s.setLastPointGood(false); 344 } 345 346 if (getItemLineVisible(series, item)) { 347 drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 348 series, item, domainAxis, rangeAxis, dataArea); 349 } 350 } 351 352 // second pass adds shapes where the items are .. 353 else if (isItemPass(pass)) { 354 355 // setup for collecting optional entity info... 356 EntityCollection entities = null; 357 if (info != null) { 358 entities = info.getOwner().getEntityCollection(); 359 } 360 361 drawSecondaryPass(g2, plot, dataset, pass, series, item, 362 domainAxis, dataArea, rangeAxis, crosshairState, entities); 363 } 364 } 365 366 /** 367 * Tests this renderer for equality with an arbitrary object. 368 * 369 * @param obj the object (<code>null</code> permitted). 370 * 371 * @return A boolean. 372 */ 373 public boolean equals(Object obj) { 374 if (obj == this) { 375 return true; 376 } 377 if (!(obj instanceof DeviationRenderer)) { 378 return false; 379 } 380 DeviationRenderer that = (DeviationRenderer) obj; 381 if (this.alpha != that.alpha) { 382 return false; 383 } 384 return super.equals(obj); 385 } 386 387 }