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 * SamplingXYLineRenderer.java 029 * --------------------------- 030 * (C) Copyright 2008-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 */ 035 036package org.jfree.chart.renderer.xy; 037 038import java.awt.Graphics2D; 039import java.awt.Shape; 040import java.awt.geom.GeneralPath; 041import java.awt.geom.Line2D; 042import java.awt.geom.PathIterator; 043import java.awt.geom.Rectangle2D; 044import java.io.IOException; 045import java.io.ObjectInputStream; 046import java.io.ObjectOutputStream; 047import java.io.Serializable; 048 049import org.jfree.chart.axis.ValueAxis; 050import org.jfree.chart.plot.CrosshairState; 051import org.jfree.chart.plot.PlotOrientation; 052import org.jfree.chart.plot.PlotRenderingInfo; 053import org.jfree.chart.plot.XYPlot; 054import org.jfree.chart.ui.RectangleEdge; 055import org.jfree.chart.util.PublicCloneable; 056import org.jfree.chart.util.SerialUtils; 057import org.jfree.chart.util.ShapeUtils; 058import org.jfree.data.xy.XYDataset; 059 060/** 061 * A renderer that draws line charts. The renderer doesn't necessarily plot 062 * every data item - instead, it tries to plot only those data items that 063 * make a difference to the visual output (the other data items are skipped). 064 * This renderer is designed for use with the {@link XYPlot} class. 065 */ 066public class SamplingXYLineRenderer extends AbstractXYItemRenderer 067 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 068 069 /** The shape that is used to represent a line in the legend. */ 070 private transient Shape legendLine; 071 072 /** 073 * Creates a new renderer. 074 */ 075 public SamplingXYLineRenderer() { 076 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 077 setDefaultLegendShape(this.legendLine); 078 setTreatLegendShapeAsLine(true); 079 } 080 081 /** 082 * Returns the number of passes through the data that the renderer requires 083 * in order to draw the chart. Most charts will require a single pass, but 084 * some require two passes. 085 * 086 * @return The pass count. 087 */ 088 @Override 089 public int getPassCount() { 090 return 1; 091 } 092 093 /** 094 * Records the state for the renderer. This is used to preserve state 095 * information between calls to the drawItem() method for a single chart 096 * drawing. 097 */ 098 public static class State extends XYItemRendererState { 099 100 /** The path for the current series. */ 101 GeneralPath seriesPath; 102 103 /** 104 * A second path that draws vertical intervals to cover any extreme 105 * values. 106 */ 107 GeneralPath intervalPath; 108 109 /** 110 * The minimum change in the x-value needed to trigger an update to 111 * the seriesPath. 112 */ 113 double dX = 1.0; 114 115 /** The last x-coordinate visited by the seriesPath. */ 116 double lastX; 117 118 /** The initial y-coordinate for the current x-coordinate. */ 119 double openY = 0.0; 120 121 /** The highest y-coordinate for the current x-coordinate. */ 122 double highY = 0.0; 123 124 /** The lowest y-coordinate for the current x-coordinate. */ 125 double lowY = 0.0; 126 127 /** The final y-coordinate for the current x-coordinate. */ 128 double closeY = 0.0; 129 130 /** 131 * A flag that indicates if the last (x, y) point was 'good' 132 * (non-null). 133 */ 134 boolean lastPointGood; 135 136 /** 137 * Creates a new state instance. 138 * 139 * @param info the plot rendering info. 140 */ 141 public State(PlotRenderingInfo info) { 142 super(info); 143 } 144 145 /** 146 * This method is called by the {@link XYPlot} at the start of each 147 * series pass. We reset the state for the current series. 148 * 149 * @param dataset the dataset. 150 * @param series the series index. 151 * @param firstItem the first item index for this pass. 152 * @param lastItem the last item index for this pass. 153 * @param pass the current pass index. 154 * @param passCount the number of passes. 155 */ 156 @Override 157 public void startSeriesPass(XYDataset dataset, int series, 158 int firstItem, int lastItem, int pass, int passCount) { 159 this.seriesPath.reset(); 160 this.intervalPath.reset(); 161 this.lastPointGood = false; 162 super.startSeriesPass(dataset, series, firstItem, lastItem, pass, 163 passCount); 164 } 165 166 } 167 168 /** 169 * Initialises the renderer. 170 * <P> 171 * This method will be called before the first item is rendered, giving the 172 * renderer an opportunity to initialise any state information it wants to 173 * maintain. The renderer can do nothing if it chooses. 174 * 175 * @param g2 the graphics device. 176 * @param dataArea the area inside the axes. 177 * @param plot the plot. 178 * @param data the data. 179 * @param info an optional info collection object to return data back to 180 * the caller. 181 * 182 * @return The renderer state. 183 */ 184 @Override 185 public XYItemRendererState initialise(Graphics2D g2, 186 Rectangle2D dataArea, XYPlot plot, XYDataset data, 187 PlotRenderingInfo info) { 188 189 double dpi = 72; 190 // Integer dpiVal = (Integer) g2.getRenderingHint(HintKey.DPI); 191 // if (dpiVal != null) { 192 // dpi = dpiVal.intValue(); 193 // } 194 State state = new State(info); 195 state.seriesPath = new GeneralPath(); 196 state.intervalPath = new GeneralPath(); 197 state.dX = 72.0 / dpi; 198 return state; 199 } 200 201 /** 202 * Draws the visual representation of a single data item. 203 * 204 * @param g2 the graphics device. 205 * @param state the renderer state. 206 * @param dataArea the area within which the data is being drawn. 207 * @param info collects information about the drawing. 208 * @param plot the plot (can be used to obtain standard color 209 * information etc). 210 * @param domainAxis the domain axis. 211 * @param rangeAxis the range axis. 212 * @param dataset the dataset. 213 * @param series the series index (zero-based). 214 * @param item the item index (zero-based). 215 * @param crosshairState crosshair information for the plot 216 * ({@code null} permitted). 217 * @param pass the pass index. 218 */ 219 @Override 220 public void drawItem(Graphics2D g2, XYItemRendererState state, 221 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 222 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 223 int series, int item, CrosshairState crosshairState, int pass) { 224 225 // do nothing if item is not visible 226 if (!getItemVisible(series, item)) { 227 return; 228 } 229 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 230 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 231 232 // get the data point... 233 double x1 = dataset.getXValue(series, item); 234 double y1 = dataset.getYValue(series, item); 235 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 236 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 237 238 State s = (State) state; 239 // update path to reflect latest point 240 if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { 241 float x = (float) transX1; 242 float y = (float) transY1; 243 PlotOrientation orientation = plot.getOrientation(); 244 if (orientation == PlotOrientation.HORIZONTAL) { 245 x = (float) transY1; 246 y = (float) transX1; 247 } 248 if (s.lastPointGood) { 249 if ((Math.abs(x - s.lastX) > s.dX)) { 250 s.seriesPath.lineTo(x, y); 251 if (s.lowY < s.highY) { 252 s.intervalPath.moveTo((float) s.lastX, (float) s.lowY); 253 s.intervalPath.lineTo((float) s.lastX, (float) s.highY); 254 } 255 s.lastX = x; 256 s.openY = y; 257 s.highY = y; 258 s.lowY = y; 259 s.closeY = y; 260 } 261 else { 262 s.highY = Math.max(s.highY, y); 263 s.lowY = Math.min(s.lowY, y); 264 s.closeY = y; 265 } 266 } 267 else { 268 s.seriesPath.moveTo(x, y); 269 s.lastX = x; 270 s.openY = y; 271 s.highY = y; 272 s.lowY = y; 273 s.closeY = y; 274 } 275 s.lastPointGood = true; 276 } 277 else { 278 s.lastPointGood = false; 279 } 280 // if this is the last item, draw the path ... 281 if (item == s.getLastItemIndex()) { 282 // draw path 283 PathIterator pi = s.seriesPath.getPathIterator(null); 284 int count = 0; 285 while (!pi.isDone()) { 286 count++; 287 pi.next(); 288 } 289 g2.setStroke(getItemStroke(series, item)); 290 g2.setPaint(getItemPaint(series, item)); 291 g2.draw(s.seriesPath); 292 g2.draw(s.intervalPath); 293 } 294 } 295 296 /** 297 * Returns a clone of the renderer. 298 * 299 * @return A clone. 300 * 301 * @throws CloneNotSupportedException if the clone cannot be created. 302 */ 303 @Override 304 public Object clone() throws CloneNotSupportedException { 305 SamplingXYLineRenderer clone = (SamplingXYLineRenderer) super.clone(); 306 if (this.legendLine != null) { 307 clone.legendLine = ShapeUtils.clone(this.legendLine); 308 } 309 return clone; 310 } 311 312 /** 313 * Tests this renderer for equality with an arbitrary object. 314 * 315 * @param obj the object ({@code null} permitted). 316 * 317 * @return {@code true} or {@code false}. 318 */ 319 @Override 320 public boolean equals(Object obj) { 321 if (obj == this) { 322 return true; 323 } 324 if (!(obj instanceof SamplingXYLineRenderer)) { 325 return false; 326 } 327 if (!super.equals(obj)) { 328 return false; 329 } 330 SamplingXYLineRenderer that = (SamplingXYLineRenderer) obj; 331 if (!ShapeUtils.equal(this.legendLine, that.legendLine)) { 332 return false; 333 } 334 return true; 335 } 336 337 /** 338 * Provides serialization support. 339 * 340 * @param stream the input stream. 341 * 342 * @throws IOException if there is an I/O error. 343 * @throws ClassNotFoundException if there is a classpath problem. 344 */ 345 private void readObject(ObjectInputStream stream) 346 throws IOException, ClassNotFoundException { 347 stream.defaultReadObject(); 348 this.legendLine = SerialUtils.readShape(stream); 349 } 350 351 /** 352 * Provides serialization support. 353 * 354 * @param stream the output stream. 355 * 356 * @throws IOException if there is an I/O error. 357 */ 358 private void writeObject(ObjectOutputStream stream) throws IOException { 359 stream.defaultWriteObject(); 360 SerialUtils.writeShape(this.legendLine, stream); 361 } 362 363}