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 * XYErrorRenderer.java 029 * -------------------- 030 * (C) Copyright 2006-2009, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 25-Oct-2006 : Version 1 (DG); 038 * 23-Mar-2007 : Check item visibility before drawing error bars - see bug 039 * 1686178 (DG); 040 * 28-Jan-2009 : Added stroke options for error indicators (DG); 041 * 042 */ 043 044 package org.jfree.chart.renderer.xy; 045 046 import java.awt.Graphics2D; 047 import java.awt.Paint; 048 import java.awt.Stroke; 049 import java.awt.geom.Line2D; 050 import java.awt.geom.Rectangle2D; 051 import java.io.IOException; 052 import java.io.ObjectInputStream; 053 import java.io.ObjectOutputStream; 054 055 import org.jfree.chart.axis.ValueAxis; 056 import org.jfree.chart.event.RendererChangeEvent; 057 import org.jfree.chart.plot.CrosshairState; 058 import org.jfree.chart.plot.PlotOrientation; 059 import org.jfree.chart.plot.PlotRenderingInfo; 060 import org.jfree.chart.plot.XYPlot; 061 import org.jfree.data.Range; 062 import org.jfree.data.general.DatasetUtilities; 063 import org.jfree.data.xy.IntervalXYDataset; 064 import org.jfree.data.xy.XYDataset; 065 import org.jfree.io.SerialUtilities; 066 import org.jfree.ui.RectangleEdge; 067 import org.jfree.util.ObjectUtilities; 068 import org.jfree.util.PaintUtilities; 069 070 /** 071 * A line and shape renderer that can also display x and/or y-error values. 072 * This renderer expects an {@link IntervalXYDataset}, otherwise it reverts 073 * to the behaviour of the super class. The example shown here is generated by 074 * the <code>XYErrorRendererDemo1.java</code> program included in the 075 * JFreeChart demo collection: 076 * <br><br> 077 * <img src="../../../../../images/XYErrorRendererSample.png" 078 * alt="XYErrorRendererSample.png" /> 079 * 080 * @since 1.0.3 081 */ 082 public class XYErrorRenderer extends XYLineAndShapeRenderer { 083 084 /** For serialization. */ 085 static final long serialVersionUID = 5162283570955172424L; 086 087 /** A flag that controls whether or not the x-error bars are drawn. */ 088 private boolean drawXError; 089 090 /** A flag that controls whether or not the y-error bars are drawn. */ 091 private boolean drawYError; 092 093 /** The length of the cap at the end of the error bars. */ 094 private double capLength; 095 096 /** 097 * The paint used to draw the error bars (if <code>null</code> we use the 098 * series paint). 099 */ 100 private transient Paint errorPaint; 101 102 /** 103 * The stroke used to draw the error bars (if <code>null</code> we use the 104 * series outline stroke). 105 * 106 * @since 1.0.13 107 */ 108 private transient Stroke errorStroke; 109 110 /** 111 * Creates a new <code>XYErrorRenderer</code> instance. 112 */ 113 public XYErrorRenderer() { 114 super(false, true); 115 this.drawXError = true; 116 this.drawYError = true; 117 this.errorPaint = null; 118 this.errorStroke = null; 119 this.capLength = 4.0; 120 } 121 122 /** 123 * Returns the flag that controls whether or not the renderer draws error 124 * bars for the x-values. 125 * 126 * @return A boolean. 127 * 128 * @see #setDrawXError(boolean) 129 */ 130 public boolean getDrawXError() { 131 return this.drawXError; 132 } 133 134 /** 135 * Sets the flag that controls whether or not the renderer draws error 136 * bars for the x-values and, if the flag changes, sends a 137 * {@link RendererChangeEvent} to all registered listeners. 138 * 139 * @param draw the flag value. 140 * 141 * @see #getDrawXError() 142 */ 143 public void setDrawXError(boolean draw) { 144 if (this.drawXError != draw) { 145 this.drawXError = draw; 146 fireChangeEvent(); 147 } 148 } 149 150 /** 151 * Returns the flag that controls whether or not the renderer draws error 152 * bars for the y-values. 153 * 154 * @return A boolean. 155 * 156 * @see #setDrawYError(boolean) 157 */ 158 public boolean getDrawYError() { 159 return this.drawYError; 160 } 161 162 /** 163 * Sets the flag that controls whether or not the renderer draws error 164 * bars for the y-values and, if the flag changes, sends a 165 * {@link RendererChangeEvent} to all registered listeners. 166 * 167 * @param draw the flag value. 168 * 169 * @see #getDrawYError() 170 */ 171 public void setDrawYError(boolean draw) { 172 if (this.drawYError != draw) { 173 this.drawYError = draw; 174 fireChangeEvent(); 175 } 176 } 177 178 /** 179 * Returns the length (in Java2D units) of the cap at the end of the error 180 * bars. 181 * 182 * @return The cap length. 183 * 184 * @see #setCapLength(double) 185 */ 186 public double getCapLength() { 187 return this.capLength; 188 } 189 190 /** 191 * Sets the length of the cap at the end of the error bars, and sends a 192 * {@link RendererChangeEvent} to all registered listeners. 193 * 194 * @param length the length (in Java2D units). 195 * 196 * @see #getCapLength() 197 */ 198 public void setCapLength(double length) { 199 this.capLength = length; 200 fireChangeEvent(); 201 } 202 203 /** 204 * Returns the paint used to draw the error bars. If this is 205 * <code>null</code> (the default), the item paint is used instead. 206 * 207 * @return The paint (possibly <code>null</code>). 208 * 209 * @see #setErrorPaint(Paint) 210 */ 211 public Paint getErrorPaint() { 212 return this.errorPaint; 213 } 214 215 /** 216 * Sets the paint used to draw the error bars and sends a 217 * {@link RendererChangeEvent} to all registered listeners. 218 * 219 * @param paint the paint (<code>null</code> permitted). 220 * 221 * @see #getErrorPaint() 222 */ 223 public void setErrorPaint(Paint paint) { 224 this.errorPaint = paint; 225 fireChangeEvent(); 226 } 227 228 /** 229 * Returns the stroke used to draw the error bars. If this is 230 * <code>null</code> (the default), the item outline stroke is used 231 * instead. 232 * 233 * @return The stroke (possibly <code>null</code>). 234 * 235 * @see #setErrorStroke(Stroke) 236 * 237 * @since 1.0.13 238 */ 239 public Stroke getErrorStroke() { 240 return this.errorStroke; 241 } 242 243 /** 244 * Sets the stroke used to draw the error bars and sends a 245 * {@link RendererChangeEvent} to all registered listeners. 246 * 247 * @param stroke the stroke (<code>null</code> permitted). 248 * 249 * @see #getErrorStroke() 250 * 251 * @since 1.0.13 252 */ 253 public void setErrorStroke(Stroke stroke) { 254 this.errorStroke = stroke; 255 fireChangeEvent(); 256 } 257 258 /** 259 * Returns the range required by this renderer to display all the domain 260 * values in the specified dataset. 261 * 262 * @param dataset the dataset (<code>null</code> permitted). 263 * 264 * @return The range, or <code>null</code> if the dataset is 265 * <code>null</code>. 266 */ 267 public Range findDomainBounds(XYDataset dataset) { 268 if (dataset != null) { 269 return DatasetUtilities.findDomainBounds(dataset, true); 270 } 271 else { 272 return null; 273 } 274 } 275 276 /** 277 * Returns the range required by this renderer to display all the range 278 * values in the specified dataset. 279 * 280 * @param dataset the dataset (<code>null</code> permitted). 281 * 282 * @return The range, or <code>null</code> if the dataset is 283 * <code>null</code>. 284 */ 285 public Range findRangeBounds(XYDataset dataset) { 286 if (dataset != null) { 287 return DatasetUtilities.findRangeBounds(dataset, true); 288 } 289 else { 290 return null; 291 } 292 } 293 294 /** 295 * Draws the visual representation for one data item. 296 * 297 * @param g2 the graphics output target. 298 * @param state the renderer state. 299 * @param dataArea the data area. 300 * @param info the plot rendering info. 301 * @param plot the plot. 302 * @param domainAxis the domain axis. 303 * @param rangeAxis the range axis. 304 * @param dataset the dataset. 305 * @param series the series index. 306 * @param item the item index. 307 * @param crosshairState the crosshair state. 308 * @param pass the pass index. 309 */ 310 public void drawItem(Graphics2D g2, XYItemRendererState state, 311 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 312 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 313 int series, int item, CrosshairState crosshairState, int pass) { 314 315 if (pass == 0 && dataset instanceof IntervalXYDataset 316 && getItemVisible(series, item)) { 317 IntervalXYDataset ixyd = (IntervalXYDataset) dataset; 318 PlotOrientation orientation = plot.getOrientation(); 319 if (this.drawXError) { 320 // draw the error bar for the x-interval 321 double x0 = ixyd.getStartXValue(series, item); 322 double x1 = ixyd.getEndXValue(series, item); 323 double y = ixyd.getYValue(series, item); 324 RectangleEdge edge = plot.getDomainAxisEdge(); 325 double xx0 = domainAxis.valueToJava2D(x0, dataArea, edge); 326 double xx1 = domainAxis.valueToJava2D(x1, dataArea, edge); 327 double yy = rangeAxis.valueToJava2D(y, dataArea, 328 plot.getRangeAxisEdge()); 329 Line2D line; 330 Line2D cap1 = null; 331 Line2D cap2 = null; 332 double adj = this.capLength / 2.0; 333 if (orientation == PlotOrientation.VERTICAL) { 334 line = new Line2D.Double(xx0, yy, xx1, yy); 335 cap1 = new Line2D.Double(xx0, yy - adj, xx0, yy + adj); 336 cap2 = new Line2D.Double(xx1, yy - adj, xx1, yy + adj); 337 } 338 else { // PlotOrientation.HORIZONTAL 339 line = new Line2D.Double(yy, xx0, yy, xx1); 340 cap1 = new Line2D.Double(yy - adj, xx0, yy + adj, xx0); 341 cap2 = new Line2D.Double(yy - adj, xx1, yy + adj, xx1); 342 } 343 if (this.errorPaint != null) { 344 g2.setPaint(this.errorPaint); 345 } 346 else { 347 g2.setPaint(getItemPaint(series, item)); 348 } 349 if (this.errorStroke != null) { 350 g2.setStroke(this.errorStroke); 351 } 352 else { 353 g2.setStroke(getItemStroke(series, item)); 354 } 355 g2.draw(line); 356 g2.draw(cap1); 357 g2.draw(cap2); 358 } 359 if (this.drawYError) { 360 // draw the error bar for the y-interval 361 double y0 = ixyd.getStartYValue(series, item); 362 double y1 = ixyd.getEndYValue(series, item); 363 double x = ixyd.getXValue(series, item); 364 RectangleEdge edge = plot.getRangeAxisEdge(); 365 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, edge); 366 double yy1 = rangeAxis.valueToJava2D(y1, dataArea, edge); 367 double xx = domainAxis.valueToJava2D(x, dataArea, 368 plot.getDomainAxisEdge()); 369 Line2D line; 370 Line2D cap1 = null; 371 Line2D cap2 = null; 372 double adj = this.capLength / 2.0; 373 if (orientation == PlotOrientation.VERTICAL) { 374 line = new Line2D.Double(xx, yy0, xx, yy1); 375 cap1 = new Line2D.Double(xx - adj, yy0, xx + adj, yy0); 376 cap2 = new Line2D.Double(xx - adj, yy1, xx + adj, yy1); 377 } 378 else { // PlotOrientation.HORIZONTAL 379 line = new Line2D.Double(yy0, xx, yy1, xx); 380 cap1 = new Line2D.Double(yy0, xx - adj, yy0, xx + adj); 381 cap2 = new Line2D.Double(yy1, xx - adj, yy1, xx + adj); 382 } 383 if (this.errorPaint != null) { 384 g2.setPaint(this.errorPaint); 385 } 386 else { 387 g2.setPaint(getItemPaint(series, item)); 388 } 389 if (this.errorStroke != null) { 390 g2.setStroke(this.errorStroke); 391 } 392 else { 393 g2.setStroke(getItemStroke(series, item)); 394 } 395 g2.draw(line); 396 g2.draw(cap1); 397 g2.draw(cap2); 398 } 399 } 400 super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis, 401 dataset, series, item, crosshairState, pass); 402 } 403 404 /** 405 * Tests this instance for equality with an arbitrary object. 406 * 407 * @param obj the object (<code>null</code> permitted). 408 * 409 * @return A boolean. 410 */ 411 public boolean equals(Object obj) { 412 if (obj == this) { 413 return true; 414 } 415 if (!(obj instanceof XYErrorRenderer)) { 416 return false; 417 } 418 XYErrorRenderer that = (XYErrorRenderer) obj; 419 if (this.drawXError != that.drawXError) { 420 return false; 421 } 422 if (this.drawYError != that.drawYError) { 423 return false; 424 } 425 if (this.capLength != that.capLength) { 426 return false; 427 } 428 if (!PaintUtilities.equal(this.errorPaint, that.errorPaint)) { 429 return false; 430 } 431 if (!ObjectUtilities.equal(this.errorStroke, that.errorStroke)) { 432 return false; 433 } 434 return super.equals(obj); 435 } 436 437 /** 438 * Provides serialization support. 439 * 440 * @param stream the input stream. 441 * 442 * @throws IOException if there is an I/O error. 443 * @throws ClassNotFoundException if there is a classpath problem. 444 */ 445 private void readObject(ObjectInputStream stream) 446 throws IOException, ClassNotFoundException { 447 stream.defaultReadObject(); 448 this.errorPaint = SerialUtilities.readPaint(stream); 449 this.errorStroke = SerialUtilities.readStroke(stream); 450 } 451 452 /** 453 * Provides serialization support. 454 * 455 * @param stream the output stream. 456 * 457 * @throws IOException if there is an I/O error. 458 */ 459 private void writeObject(ObjectOutputStream stream) throws IOException { 460 stream.defaultWriteObject(); 461 SerialUtilities.writePaint(this.errorPaint, stream); 462 SerialUtilities.writeStroke(this.errorStroke, stream); 463 } 464 465 }