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