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