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