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