001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2016, 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 * DeviationStepRenderer.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 * Changes 036 * ------- 037 * 21-Sep-2020 : Version 1; 038 * 039 */ 040 041package org.jfree.chart.renderer.xy; 042 043import org.jfree.chart.axis.ValueAxis; 044import org.jfree.chart.entity.EntityCollection; 045import org.jfree.chart.plot.CrosshairState; 046import org.jfree.chart.plot.PlotOrientation; 047import org.jfree.chart.plot.PlotRenderingInfo; 048import org.jfree.chart.plot.XYPlot; 049import org.jfree.chart.ui.RectangleEdge; 050import org.jfree.data.xy.IntervalXYDataset; 051import org.jfree.data.xy.XYDataset; 052 053import java.awt.*; 054import java.awt.geom.GeneralPath; 055import java.awt.geom.Rectangle2D; 056 057/** 058 * A specialised subclass of the {@link DeviationRenderer} that requires 059 * an {@link IntervalXYDataset} and represents the y-interval by shading an 060 * area behind the y-values on the chart, drawing only horizontal or 061 * vertical lines (steps); 062 * 063 * @since 1.5.1 064 */ 065public class DeviationStepRenderer extends DeviationRenderer { 066 067 /** 068 * Creates a new renderer that displays lines and shapes for the data 069 * items, as well as the shaded area for the y-interval. 070 */ 071 public DeviationStepRenderer() { 072 super(); 073 } 074 075 /** 076 * Creates a new renderer. 077 * 078 * @param lines show lines between data items? 079 * @param shapes show a shape for each data item? 080 */ 081 public DeviationStepRenderer(boolean lines, boolean shapes) { 082 super(lines, shapes); 083 } 084 085 /** 086 * Draws the visual representation of a single data item. 087 * 088 * @param g2 the graphics device. 089 * @param state the renderer state. 090 * @param dataArea the area within which the data is being drawn. 091 * @param info collects information about the drawing. 092 * @param plot the plot (can be used to obtain standard color 093 * information etc). 094 * @param domainAxis the domain axis. 095 * @param rangeAxis the range axis. 096 * @param dataset the dataset. 097 * @param series the series index (zero-based). 098 * @param item the item index (zero-based). 099 * @param crosshairState crosshair information for the plot 100 * ({@code null} permitted). 101 * @param pass the pass index. 102 */ 103 @Override 104 public void drawItem(Graphics2D g2, XYItemRendererState state, 105 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 106 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 107 int series, int item, CrosshairState crosshairState, int pass) { 108 109 // do nothing if item is not visible 110 if (!getItemVisible(series, item)) { 111 return; 112 } 113 114 // first pass draws the shading 115 if (pass == 0) { 116 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset; 117 State drState = (State) state; 118 119 double x = intervalDataset.getXValue(series, item); 120 double yLow = intervalDataset.getStartYValue(series, item); 121 double yHigh = intervalDataset.getEndYValue(series, item); 122 123 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 124 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 125 126 double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation); 127 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 128 yAxisLocation); 129 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 130 yAxisLocation); 131 132 133 PlotOrientation orientation = plot.getOrientation(); 134 if (item > 0 && !Double.isNaN(xx)) { 135 double yLowPrev = intervalDataset.getStartYValue(series, item-1); 136 double yHighPrev = intervalDataset.getEndYValue(series, item-1); 137 double yyLowPrev = rangeAxis.valueToJava2D(yLowPrev, dataArea, 138 yAxisLocation); 139 double yyHighPrev = rangeAxis.valueToJava2D(yHighPrev, dataArea, 140 yAxisLocation); 141 142 if(!Double.isNaN(yyLow) && !Double.isNaN(yyHigh)) { 143 if (orientation == PlotOrientation.HORIZONTAL) { 144 drState.lowerCoordinates.add(new double[]{yyLowPrev, xx}); 145 drState.upperCoordinates.add(new double[]{yyHighPrev, xx}); 146 } else if (orientation == PlotOrientation.VERTICAL) { 147 drState.lowerCoordinates.add(new double[]{xx, yyLowPrev}); 148 drState.upperCoordinates.add(new double[]{xx, yyHighPrev}); 149 } 150 } 151 } 152 153 boolean intervalGood = !Double.isNaN(xx) && !Double.isNaN(yLow) && !Double.isNaN(yHigh); 154 if (intervalGood) { 155 if (orientation == PlotOrientation.HORIZONTAL) { 156 drState.lowerCoordinates.add(new double[]{yyLow, xx}); 157 drState.upperCoordinates.add(new double[]{yyHigh, xx}); 158 } else if (orientation == PlotOrientation.VERTICAL) { 159 drState.lowerCoordinates.add(new double[]{xx, yyLow}); 160 drState.upperCoordinates.add(new double[]{xx, yyHigh}); 161 } 162 } 163 164 if (item == (dataset.getItemCount(series) - 1) || 165 (!intervalGood && drState.lowerCoordinates.size() > 1)) { 166 // draw items so far, either we reached the end of the series or the next interval is invalid 167 // last item in series, draw the lot... 168 // set up the alpha-transparency... 169 Composite originalComposite = g2.getComposite(); 170 g2.setComposite(AlphaComposite.getInstance( 171 AlphaComposite.SRC_OVER, this.alpha)); 172 g2.setPaint(getItemFillPaint(series, item)); 173 GeneralPath area = new GeneralPath(GeneralPath.WIND_NON_ZERO, 174 drState.lowerCoordinates.size() 175 + drState.upperCoordinates.size()); 176 double[] coords = (double[]) drState.lowerCoordinates.get(0); 177 area.moveTo((float) coords[0], (float) coords[1]); 178 for (int i = 1; i < drState.lowerCoordinates.size(); i++) { 179 coords = (double[]) drState.lowerCoordinates.get(i); 180 area.lineTo((float) coords[0], (float) coords[1]); 181 } 182 int count = drState.upperCoordinates.size(); 183 coords = (double[]) drState.upperCoordinates.get(count - 1); 184 area.lineTo((float) coords[0], (float) coords[1]); 185 for (int i = count - 2; i >= 0; i--) { 186 coords = (double[]) drState.upperCoordinates.get(i); 187 area.lineTo((float) coords[0], (float) coords[1]); 188 } 189 area.closePath(); 190 g2.fill(area); 191 g2.setComposite(originalComposite); 192 193 drState.lowerCoordinates.clear(); 194 drState.upperCoordinates.clear(); 195 } 196 } 197 if (isLinePass(pass)) { 198 199 // the following code handles the line for the y-values...it's 200 // all done by code in the super class 201 if (item == 0) { 202 State s = (State) state; 203 s.seriesPath.reset(); 204 s.setLastPointGood(false); 205 } 206 207 if (getItemLineVisible(series, item)) { 208 drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 209 series, item, domainAxis, rangeAxis, dataArea); 210 } 211 } 212 213 // second pass adds shapes where the items are .. 214 else if (isItemPass(pass)) { 215 216 // setup for collecting optional entity info... 217 EntityCollection entities = null; 218 if (info != null) { 219 entities = info.getOwner().getEntityCollection(); 220 } 221 222 drawSecondaryPass(g2, plot, dataset, pass, series, item, 223 domainAxis, dataArea, rangeAxis, crosshairState, entities); 224 } 225 } 226 227 /** 228 * Draws the item (first pass). This method draws the lines 229 * connecting the items. Instead of drawing separate lines, 230 * a {@code GeneralPath} is constructed and drawn at the end of 231 * the series painting. 232 * 233 * @param g2 the graphics device. 234 * @param state the renderer state. 235 * @param plot the plot (can be used to obtain standard color information 236 * etc). 237 * @param dataset the dataset. 238 * @param pass the pass. 239 * @param series the series index (zero-based). 240 * @param item the item index (zero-based). 241 * @param domainAxis the domain axis. 242 * @param rangeAxis the range axis. 243 * @param dataArea the area within which the data is being drawn. 244 */ 245 @Override 246 protected void drawPrimaryLineAsPath(XYItemRendererState state, 247 Graphics2D g2, XYPlot plot, XYDataset dataset, int pass, 248 int series, int item, ValueAxis domainAxis, ValueAxis rangeAxis, 249 Rectangle2D dataArea) { 250 251 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 252 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 253 254 // get the data point... 255 double x1 = dataset.getXValue(series, item); 256 double y1 = dataset.getYValue(series, item); 257 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 258 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 259 260 XYLineAndShapeRenderer.State s = (XYLineAndShapeRenderer.State) state; 261 // update path to reflect latest point 262 if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { 263 float x = (float) transX1; 264 float y = (float) transY1; 265 PlotOrientation orientation = plot.getOrientation(); 266 if (orientation == PlotOrientation.HORIZONTAL) { 267 x = (float) transY1; 268 y = (float) transX1; 269 } 270 if (s.isLastPointGood()) { 271 if (item > 0) { 272 if (orientation == PlotOrientation.HORIZONTAL) { 273 s.seriesPath.lineTo(s.seriesPath.getCurrentPoint().getX(), y); 274 } else { 275 s.seriesPath.lineTo(x, s.seriesPath.getCurrentPoint().getY()); 276 } 277 } 278 s.seriesPath.lineTo(x, y); 279 } 280 else { 281 s.seriesPath.moveTo(x, y); 282 } 283 s.setLastPointGood(true); 284 } else { 285 s.setLastPointGood(false); 286 } 287 // if this is the last item, draw the path ... 288 if (item == s.getLastItemIndex()) { 289 // draw path 290 drawFirstPassShape(g2, pass, series, item, s.seriesPath); 291 } 292 } 293 294 295 /** 296 * Tests this renderer for equality with an arbitrary object. 297 * 298 * @param obj the object ({@code null} permitted). 299 * 300 * @return A boolean. 301 */ 302 @Override 303 public boolean equals(Object obj) { 304 if (obj == this) { 305 return true; 306 } 307 if (!(obj instanceof DeviationStepRenderer)) { 308 return false; 309 } 310 DeviationStepRenderer that = (DeviationStepRenderer) obj; 311 if (this.alpha != that.alpha) { 312 return false; 313 } 314 return super.equals(obj); 315 } 316 317}