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