001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2011, 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 * XYErrorRenderer.java 029 * -------------------- 030 * (C) Copyright 2006-2011, 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 044package org.jfree.chart.renderer.xy; 045 046import java.awt.Graphics2D; 047import java.awt.Paint; 048import java.awt.Stroke; 049import java.awt.geom.Line2D; 050import java.awt.geom.Rectangle2D; 051import java.io.IOException; 052import java.io.ObjectInputStream; 053import java.io.ObjectOutputStream; 054 055import org.jfree.chart.axis.ValueAxis; 056import org.jfree.chart.event.RendererChangeEvent; 057import org.jfree.chart.plot.CrosshairState; 058import org.jfree.chart.plot.PlotOrientation; 059import org.jfree.chart.plot.PlotRenderingInfo; 060import org.jfree.chart.plot.XYPlot; 061import org.jfree.data.Range; 062import org.jfree.data.general.DatasetUtilities; 063import org.jfree.data.xy.IntervalXYDataset; 064import org.jfree.data.xy.XYDataset; 065import org.jfree.io.SerialUtilities; 066import org.jfree.ui.RectangleEdge; 067import org.jfree.util.ObjectUtilities; 068import 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 */ 082public 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 // include the interval if there is one 269 return findDomainBounds(dataset, true); 270 } 271 272 /** 273 * Returns the range required by this renderer to display all the range 274 * values in the specified dataset. 275 * 276 * @param dataset the dataset (<code>null</code> permitted). 277 * 278 * @return The range, or <code>null</code> if the dataset is 279 * <code>null</code>. 280 */ 281 public Range findRangeBounds(XYDataset dataset) { 282 // include the interval if there is one 283 return findRangeBounds(dataset, true); 284 } 285 286 /** 287 * Draws the visual representation for one data item. 288 * 289 * @param g2 the graphics output target. 290 * @param state the renderer state. 291 * @param dataArea the data area. 292 * @param info the plot rendering info. 293 * @param plot the plot. 294 * @param domainAxis the domain axis. 295 * @param rangeAxis the range axis. 296 * @param dataset the dataset. 297 * @param series the series index. 298 * @param item the item index. 299 * @param crosshairState the crosshair state. 300 * @param pass the pass index. 301 */ 302 public void drawItem(Graphics2D g2, XYItemRendererState state, 303 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 304 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 305 int series, int item, CrosshairState crosshairState, int pass) { 306 307 if (pass == 0 && dataset instanceof IntervalXYDataset 308 && getItemVisible(series, item)) { 309 IntervalXYDataset ixyd = (IntervalXYDataset) dataset; 310 PlotOrientation orientation = plot.getOrientation(); 311 if (this.drawXError) { 312 // draw the error bar for the x-interval 313 double x0 = ixyd.getStartXValue(series, item); 314 double x1 = ixyd.getEndXValue(series, item); 315 double y = ixyd.getYValue(series, item); 316 RectangleEdge edge = plot.getDomainAxisEdge(); 317 double xx0 = domainAxis.valueToJava2D(x0, dataArea, edge); 318 double xx1 = domainAxis.valueToJava2D(x1, dataArea, edge); 319 double yy = rangeAxis.valueToJava2D(y, dataArea, 320 plot.getRangeAxisEdge()); 321 Line2D line; 322 Line2D cap1 = null; 323 Line2D cap2 = null; 324 double adj = this.capLength / 2.0; 325 if (orientation == PlotOrientation.VERTICAL) { 326 line = new Line2D.Double(xx0, yy, xx1, yy); 327 cap1 = new Line2D.Double(xx0, yy - adj, xx0, yy + adj); 328 cap2 = new Line2D.Double(xx1, yy - adj, xx1, yy + adj); 329 } 330 else { // PlotOrientation.HORIZONTAL 331 line = new Line2D.Double(yy, xx0, yy, xx1); 332 cap1 = new Line2D.Double(yy - adj, xx0, yy + adj, xx0); 333 cap2 = new Line2D.Double(yy - adj, xx1, yy + adj, xx1); 334 } 335 if (this.errorPaint != null) { 336 g2.setPaint(this.errorPaint); 337 } 338 else { 339 g2.setPaint(getItemPaint(series, item)); 340 } 341 if (this.errorStroke != null) { 342 g2.setStroke(this.errorStroke); 343 } 344 else { 345 g2.setStroke(getItemStroke(series, item)); 346 } 347 g2.draw(line); 348 g2.draw(cap1); 349 g2.draw(cap2); 350 } 351 if (this.drawYError) { 352 // draw the error bar for the y-interval 353 double y0 = ixyd.getStartYValue(series, item); 354 double y1 = ixyd.getEndYValue(series, item); 355 double x = ixyd.getXValue(series, item); 356 RectangleEdge edge = plot.getRangeAxisEdge(); 357 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, edge); 358 double yy1 = rangeAxis.valueToJava2D(y1, dataArea, edge); 359 double xx = domainAxis.valueToJava2D(x, dataArea, 360 plot.getDomainAxisEdge()); 361 Line2D line; 362 Line2D cap1 = null; 363 Line2D cap2 = null; 364 double adj = this.capLength / 2.0; 365 if (orientation == PlotOrientation.VERTICAL) { 366 line = new Line2D.Double(xx, yy0, xx, yy1); 367 cap1 = new Line2D.Double(xx - adj, yy0, xx + adj, yy0); 368 cap2 = new Line2D.Double(xx - adj, yy1, xx + adj, yy1); 369 } 370 else { // PlotOrientation.HORIZONTAL 371 line = new Line2D.Double(yy0, xx, yy1, xx); 372 cap1 = new Line2D.Double(yy0, xx - adj, yy0, xx + adj); 373 cap2 = new Line2D.Double(yy1, xx - adj, yy1, xx + adj); 374 } 375 if (this.errorPaint != null) { 376 g2.setPaint(this.errorPaint); 377 } 378 else { 379 g2.setPaint(getItemPaint(series, item)); 380 } 381 if (this.errorStroke != null) { 382 g2.setStroke(this.errorStroke); 383 } 384 else { 385 g2.setStroke(getItemStroke(series, item)); 386 } 387 g2.draw(line); 388 g2.draw(cap1); 389 g2.draw(cap2); 390 } 391 } 392 super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis, 393 dataset, series, item, crosshairState, pass); 394 } 395 396 /** 397 * Tests this instance for equality with an arbitrary object. 398 * 399 * @param obj the object (<code>null</code> permitted). 400 * 401 * @return A boolean. 402 */ 403 public boolean equals(Object obj) { 404 if (obj == this) { 405 return true; 406 } 407 if (!(obj instanceof XYErrorRenderer)) { 408 return false; 409 } 410 XYErrorRenderer that = (XYErrorRenderer) obj; 411 if (this.drawXError != that.drawXError) { 412 return false; 413 } 414 if (this.drawYError != that.drawYError) { 415 return false; 416 } 417 if (this.capLength != that.capLength) { 418 return false; 419 } 420 if (!PaintUtilities.equal(this.errorPaint, that.errorPaint)) { 421 return false; 422 } 423 if (!ObjectUtilities.equal(this.errorStroke, that.errorStroke)) { 424 return false; 425 } 426 return super.equals(obj); 427 } 428 429 /** 430 * Provides serialization support. 431 * 432 * @param stream the input stream. 433 * 434 * @throws IOException if there is an I/O error. 435 * @throws ClassNotFoundException if there is a classpath problem. 436 */ 437 private void readObject(ObjectInputStream stream) 438 throws IOException, ClassNotFoundException { 439 stream.defaultReadObject(); 440 this.errorPaint = SerialUtilities.readPaint(stream); 441 this.errorStroke = SerialUtilities.readStroke(stream); 442 } 443 444 /** 445 * Provides serialization support. 446 * 447 * @param stream the output stream. 448 * 449 * @throws IOException if there is an I/O error. 450 */ 451 private void writeObject(ObjectOutputStream stream) throws IOException { 452 stream.defaultWriteObject(); 453 SerialUtilities.writePaint(this.errorPaint, stream); 454 SerialUtilities.writeStroke(this.errorStroke, stream); 455 } 456 457}