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 043 package org.jfree.chart.renderer.xy; 044 045 import java.awt.BasicStroke; 046 import java.awt.Graphics2D; 047 import java.awt.Paint; 048 import java.awt.geom.Line2D; 049 import java.awt.geom.Rectangle2D; 050 import java.io.IOException; 051 import java.io.ObjectInputStream; 052 import java.io.ObjectOutputStream; 053 054 import org.jfree.chart.axis.ValueAxis; 055 import org.jfree.chart.event.RendererChangeEvent; 056 import org.jfree.chart.plot.CrosshairState; 057 import org.jfree.chart.plot.PlotOrientation; 058 import org.jfree.chart.plot.PlotRenderingInfo; 059 import org.jfree.chart.plot.XYPlot; 060 import org.jfree.data.Range; 061 import org.jfree.data.general.DatasetUtilities; 062 import org.jfree.data.xy.IntervalXYDataset; 063 import org.jfree.data.xy.XYDataset; 064 import org.jfree.io.SerialUtilities; 065 import org.jfree.ui.RectangleEdge; 066 import 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 */ 075 public 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 fireChangeEvent(); 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 fireChangeEvent(); 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 fireChangeEvent(); 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 and sends a 201 * {@link RendererChangeEvent} to all registered listeners. 202 * 203 * @param paint the paint (<code>null</code> permitted). 204 * 205 * @see #getErrorPaint() 206 */ 207 public void setErrorPaint(Paint paint) { 208 this.errorPaint = paint; 209 fireChangeEvent(); 210 } 211 212 /** 213 * Returns the range required by this renderer to display all the domain 214 * values in the specified dataset. 215 * 216 * @param dataset the dataset (<code>null</code> permitted). 217 * 218 * @return The range, or <code>null</code> if the dataset is 219 * <code>null</code>. 220 */ 221 public Range findDomainBounds(XYDataset dataset) { 222 if (dataset != null) { 223 return DatasetUtilities.findDomainBounds(dataset, true); 224 } 225 else { 226 return null; 227 } 228 } 229 230 /** 231 * Returns the range required by this renderer to display all the range 232 * values in the specified dataset. 233 * 234 * @param dataset the dataset (<code>null</code> permitted). 235 * 236 * @return The range, or <code>null</code> if the dataset is 237 * <code>null</code>. 238 */ 239 public Range findRangeBounds(XYDataset dataset) { 240 if (dataset != null) { 241 return DatasetUtilities.findRangeBounds(dataset, true); 242 } 243 else { 244 return null; 245 } 246 } 247 248 /** 249 * Draws the visual representation for one data item. 250 * 251 * @param g2 the graphics output target. 252 * @param state the renderer state. 253 * @param dataArea the data area. 254 * @param info the plot rendering info. 255 * @param plot the plot. 256 * @param domainAxis the domain axis. 257 * @param rangeAxis the range axis. 258 * @param dataset the dataset. 259 * @param series the series index. 260 * @param item the item index. 261 * @param crosshairState the crosshair state. 262 * @param pass the pass index. 263 */ 264 public void drawItem(Graphics2D g2, XYItemRendererState state, 265 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 266 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 267 int series, int item, CrosshairState crosshairState, int pass) { 268 269 if (pass == 0 && dataset instanceof IntervalXYDataset 270 && getItemVisible(series, item)) { 271 IntervalXYDataset ixyd = (IntervalXYDataset) dataset; 272 PlotOrientation orientation = plot.getOrientation(); 273 if (this.drawXError) { 274 // draw the error bar for the x-interval 275 double x0 = ixyd.getStartXValue(series, item); 276 double x1 = ixyd.getEndXValue(series, item); 277 double y = ixyd.getYValue(series, item); 278 RectangleEdge edge = plot.getDomainAxisEdge(); 279 double xx0 = domainAxis.valueToJava2D(x0, dataArea, edge); 280 double xx1 = domainAxis.valueToJava2D(x1, dataArea, edge); 281 double yy = rangeAxis.valueToJava2D(y, dataArea, 282 plot.getRangeAxisEdge()); 283 Line2D line; 284 Line2D cap1 = null; 285 Line2D cap2 = null; 286 double adj = this.capLength / 2.0; 287 if (orientation == PlotOrientation.VERTICAL) { 288 line = new Line2D.Double(xx0, yy, xx1, yy); 289 cap1 = new Line2D.Double(xx0, yy - adj, xx0, yy + adj); 290 cap2 = new Line2D.Double(xx1, yy - adj, xx1, yy + adj); 291 } 292 else { // PlotOrientation.HORIZONTAL 293 line = new Line2D.Double(yy, xx0, yy, xx1); 294 cap1 = new Line2D.Double(yy - adj, xx0, yy + adj, xx0); 295 cap2 = new Line2D.Double(yy - adj, xx1, yy + adj, xx1); 296 } 297 g2.setStroke(new BasicStroke(1.0f)); 298 if (this.errorPaint != null) { 299 g2.setPaint(this.errorPaint); 300 } 301 else { 302 g2.setPaint(getItemPaint(series, item)); 303 } 304 g2.draw(line); 305 g2.draw(cap1); 306 g2.draw(cap2); 307 } 308 if (this.drawYError) { 309 // draw the error bar for the y-interval 310 double y0 = ixyd.getStartYValue(series, item); 311 double y1 = ixyd.getEndYValue(series, item); 312 double x = ixyd.getXValue(series, item); 313 RectangleEdge edge = plot.getRangeAxisEdge(); 314 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, edge); 315 double yy1 = rangeAxis.valueToJava2D(y1, dataArea, edge); 316 double xx = domainAxis.valueToJava2D(x, dataArea, 317 plot.getDomainAxisEdge()); 318 Line2D line; 319 Line2D cap1 = null; 320 Line2D cap2 = null; 321 double adj = this.capLength / 2.0; 322 if (orientation == PlotOrientation.VERTICAL) { 323 line = new Line2D.Double(xx, yy0, xx, yy1); 324 cap1 = new Line2D.Double(xx - adj, yy0, xx + adj, yy0); 325 cap2 = new Line2D.Double(xx - adj, yy1, xx + adj, yy1); 326 } 327 else { // PlotOrientation.HORIZONTAL 328 line = new Line2D.Double(yy0, xx, yy1, xx); 329 cap1 = new Line2D.Double(yy0, xx - adj, yy0, xx + adj); 330 cap2 = new Line2D.Double(yy1, xx - adj, yy1, xx + adj); 331 } 332 g2.setStroke(new BasicStroke(1.0f)); 333 if (this.errorPaint != null) { 334 g2.setPaint(this.errorPaint); 335 } 336 else { 337 g2.setPaint(getItemPaint(series, item)); 338 } 339 g2.draw(line); 340 g2.draw(cap1); 341 g2.draw(cap2); 342 } 343 } 344 super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis, 345 dataset, series, item, crosshairState, pass); 346 } 347 348 /** 349 * Tests this instance for equality with an arbitrary object. 350 * 351 * @param obj the object (<code>null</code> permitted). 352 * 353 * @return A boolean. 354 */ 355 public boolean equals(Object obj) { 356 if (obj == this) { 357 return true; 358 } 359 if (!(obj instanceof XYErrorRenderer)) { 360 return false; 361 } 362 XYErrorRenderer that = (XYErrorRenderer) obj; 363 if (this.drawXError != that.drawXError) { 364 return false; 365 } 366 if (this.drawYError != that.drawYError) { 367 return false; 368 } 369 if (this.capLength != that.capLength) { 370 return false; 371 } 372 if (!PaintUtilities.equal(this.errorPaint, that.errorPaint)) { 373 return false; 374 } 375 return super.equals(obj); 376 } 377 378 /** 379 * Provides serialization support. 380 * 381 * @param stream the input stream. 382 * 383 * @throws IOException if there is an I/O error. 384 * @throws ClassNotFoundException if there is a classpath problem. 385 */ 386 private void readObject(ObjectInputStream stream) 387 throws IOException, ClassNotFoundException { 388 stream.defaultReadObject(); 389 this.errorPaint = SerialUtilities.readPaint(stream); 390 } 391 392 /** 393 * Provides serialization support. 394 * 395 * @param stream the output stream. 396 * 397 * @throws IOException if there is an I/O error. 398 */ 399 private void writeObject(ObjectOutputStream stream) throws IOException { 400 stream.defaultWriteObject(); 401 SerialUtilities.writePaint(this.errorPaint, stream); 402 } 403 404 }