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