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 * 070 * @since 1.0.5 071 */ 072 public class DeviationRenderer extends XYLineAndShapeRenderer { 073 074 /** 075 * A state object that is passed to each call to <code>drawItem</code>. 076 */ 077 public static class State extends XYLineAndShapeRenderer.State { 078 079 /** 080 * A list of coordinates for the upper y-values in the current series 081 * (after translation into Java2D space). 082 */ 083 public List upperCoordinates; 084 085 /** 086 * A list of coordinates for the lower y-values in the current series 087 * (after translation into Java2D space). 088 */ 089 public List lowerCoordinates; 090 091 /** 092 * Creates a new state instance. 093 * 094 * @param info the plot rendering info. 095 */ 096 public State(PlotRenderingInfo info) { 097 super(info); 098 this.lowerCoordinates = new java.util.ArrayList(); 099 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 }