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