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