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