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