Source for org.jfree.chart.renderer.xy.XYErrorRenderer

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * --------------------
  28:  * XYErrorRenderer.java
  29:  * --------------------
  30:  * (C) Copyright 2006, 2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: XYErrorRenderer.java,v 1.1.2.4 2007/03/23 14:01:12 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 25-Oct-2006 : Version 1 (DG);
  40:  * 23-Mar-2007 : Check item visibility before drawing error bars - see bug
  41:  *               1686178 (DG);
  42:  * 
  43:  */
  44: 
  45: package org.jfree.chart.renderer.xy;
  46: 
  47: import java.awt.BasicStroke;
  48: import java.awt.Graphics2D;
  49: import java.awt.Paint;
  50: import java.awt.geom.Line2D;
  51: import java.awt.geom.Rectangle2D;
  52: import java.io.IOException;
  53: import java.io.ObjectInputStream;
  54: import java.io.ObjectOutputStream;
  55: 
  56: import org.jfree.chart.axis.ValueAxis;
  57: import org.jfree.chart.event.RendererChangeEvent;
  58: import org.jfree.chart.plot.CrosshairState;
  59: import org.jfree.chart.plot.PlotOrientation;
  60: import org.jfree.chart.plot.PlotRenderingInfo;
  61: import org.jfree.chart.plot.XYPlot;
  62: import org.jfree.data.Range;
  63: import org.jfree.data.general.DatasetUtilities;
  64: import org.jfree.data.xy.IntervalXYDataset;
  65: import org.jfree.data.xy.XYDataset;
  66: import org.jfree.io.SerialUtilities;
  67: import org.jfree.ui.RectangleEdge;
  68: import org.jfree.util.PaintUtilities;
  69: 
  70: /**
  71:  * A line and shape renderer that can also display x and/or y-error values.  
  72:  * This renderer expects an {@link IntervalXYDataset}, otherwise it reverts
  73:  * to the behaviour of the super class.
  74:  * 
  75:  * @since 1.0.3
  76:  */
  77: public class XYErrorRenderer extends XYLineAndShapeRenderer {
  78: 
  79:     /** A flag that controls whether or not the x-error bars are drawn. */
  80:     private boolean drawXError;
  81:     
  82:     /** A flag that controls whether or not the y-error bars are drawn. */
  83:     private boolean drawYError;
  84:     
  85:     /** The length of the cap at the end of the error bars. */
  86:     private double capLength;
  87:     
  88:     /** 
  89:      * The paint used to draw the error bars (if <code>null</code> we use the
  90:      * series paint).
  91:      */
  92:     private transient Paint errorPaint;
  93:     
  94:     /**
  95:      * Creates a new <code>XYErrorRenderer</code> instance.
  96:      */
  97:     public XYErrorRenderer() {
  98:         super(false, true);
  99:         this.drawXError = true;
 100:         this.drawYError = true;
 101:         this.errorPaint = null;
 102:         this.capLength = 4.0;
 103:     }
 104:     
 105:     /**
 106:      * Returns the flag that controls whether or not the renderer draws error
 107:      * bars for the x-values.
 108:      * 
 109:      * @return A boolean.
 110:      * 
 111:      * @see #setDrawXError(boolean)
 112:      */
 113:     public boolean getDrawXError() {
 114:         return this.drawXError;
 115:     }
 116:     
 117:     /**
 118:      * Sets the flag that controls whether or not the renderer draws error
 119:      * bars for the x-values and, if the flag changes, sends a 
 120:      * {@link RendererChangeEvent} to all registered listeners.
 121:      *
 122:      * @param draw  the flag value.
 123:      * 
 124:      * @see #getDrawXError()
 125:      */
 126:     public void setDrawXError(boolean draw) {
 127:         if (this.drawXError != draw) {
 128:             this.drawXError = draw;
 129:             this.notifyListeners(new RendererChangeEvent(this));
 130:         }
 131:     }
 132:     
 133:     /**
 134:      * Returns the flag that controls whether or not the renderer draws error
 135:      * bars for the y-values.
 136:      * 
 137:      * @return A boolean.
 138:      * 
 139:      * @see #setDrawYError(boolean)
 140:      */
 141:     public boolean getDrawYError() {
 142:         return this.drawYError;
 143:     }
 144:     
 145:     /**
 146:      * Sets the flag that controls whether or not the renderer draws error
 147:      * bars for the y-values and, if the flag changes, sends a 
 148:      * {@link RendererChangeEvent} to all registered listeners.
 149:      *
 150:      * @param draw  the flag value.
 151:      * 
 152:      * @see #getDrawYError()
 153:      */
 154:     public void setDrawYError(boolean draw) {
 155:         if (this.drawYError != draw) {
 156:             this.drawYError = draw;
 157:             notifyListeners(new RendererChangeEvent(this));
 158:         }
 159:     }
 160:     
 161:     /**
 162:      * Returns the length (in Java2D units) of the cap at the end of the error 
 163:      * bars.
 164:      * 
 165:      * @return The cap length.
 166:      * 
 167:      * @see #setCapLength(double)
 168:      */
 169:     public double getCapLength() {
 170:         return this.capLength;
 171:     }
 172:     
 173:     /**
 174:      * Sets the length of the cap at the end of the error bars, and sends a
 175:      * {@link RendererChangeEvent} to all registered listeners.
 176:      * 
 177:      * @param length  the length (in Java2D units).
 178:      * 
 179:      * @see #getCapLength()
 180:      */
 181:     public void setCapLength(double length) {
 182:         this.capLength = length;
 183:         notifyListeners(new RendererChangeEvent(this));
 184:     }
 185:     
 186:     /**
 187:      * Returns the paint used to draw the error bars.  If this is 
 188:      * <code>null</code> (the default), the item paint is used instead.
 189:      * 
 190:      * @return The paint (possibly <code>null</code>).
 191:      * 
 192:      * @see #setErrorPaint(Paint)
 193:      */
 194:     public Paint getErrorPaint() {
 195:         return this.errorPaint;
 196:     }
 197:     
 198:     /**
 199:      * Sets the paint used to draw the error bars.
 200:      * 
 201:      * @param paint  the paint (<code>null</code> permitted).
 202:      * 
 203:      * @see #getErrorPaint()
 204:      */
 205:     public void setErrorPaint(Paint paint) {
 206:         this.errorPaint = paint;
 207:         notifyListeners(new RendererChangeEvent(this));
 208:     }
 209:     
 210:     /**
 211:      * Returns the range required by this renderer to display all the domain
 212:      * values in the specified dataset.
 213:      * 
 214:      * @param dataset  the dataset (<code>null</code> permitted).
 215:      * 
 216:      * @return The range, or <code>null</code> if the dataset is 
 217:      *     <code>null</code>.
 218:      */
 219:     public Range findDomainBounds(XYDataset dataset) {
 220:         if (dataset != null) {
 221:             return DatasetUtilities.findDomainBounds(dataset, true);
 222:         }
 223:         else {
 224:             return null;
 225:         }
 226:     }
 227: 
 228:     /**
 229:      * Returns the range required by this renderer to display all the range
 230:      * values in the specified dataset.
 231:      * 
 232:      * @param dataset  the dataset (<code>null</code> permitted).
 233:      * 
 234:      * @return The range, or <code>null</code> if the dataset is 
 235:      *     <code>null</code>.
 236:      */
 237:     public Range findRangeBounds(XYDataset dataset) {
 238:         if (dataset != null) {
 239:             return DatasetUtilities.findRangeBounds(dataset, true);
 240:         }
 241:         else {
 242:             return null;
 243:         }
 244:     }
 245: 
 246:     /**
 247:      * Draws the visual representation for one data item.
 248:      * 
 249:      * @param g2  the graphics output target.
 250:      * @param state  the renderer state.
 251:      * @param dataArea  the data area.
 252:      * @param info  the plot rendering info.
 253:      * @param plot  the plot.
 254:      * @param domainAxis  the domain axis.
 255:      * @param rangeAxis  the range axis.
 256:      * @param dataset  the dataset.
 257:      * @param series  the series index.
 258:      * @param item  the item index.
 259:      * @param crosshairState  the crosshair state.
 260:      * @param pass  the pass index.
 261:      */
 262:     public void drawItem(Graphics2D g2, XYItemRendererState state, 
 263:             Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 
 264:             ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 
 265:             int series, int item, CrosshairState crosshairState, int pass) {
 266: 
 267:         if (pass == 0 && dataset instanceof IntervalXYDataset 
 268:                 && getItemVisible(series, item)) {
 269:             IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
 270:             PlotOrientation orientation = plot.getOrientation();
 271:             if (this.drawXError) {
 272:                 // draw the error bar for the x-interval
 273:                 double x0 = ixyd.getStartXValue(series, item);
 274:                 double x1 = ixyd.getEndXValue(series, item);
 275:                 double y = ixyd.getYValue(series, item);
 276:                 RectangleEdge edge = plot.getDomainAxisEdge();
 277:                 double xx0 = domainAxis.valueToJava2D(x0, dataArea, edge);
 278:                 double xx1 = domainAxis.valueToJava2D(x1, dataArea, edge);
 279:                 double yy = rangeAxis.valueToJava2D(y, dataArea, 
 280:                         plot.getRangeAxisEdge());
 281:                 Line2D line;
 282:                 Line2D cap1 = null;
 283:                 Line2D cap2 = null;
 284:                 double adj = this.capLength / 2.0;
 285:                 if (orientation == PlotOrientation.VERTICAL) {
 286:                     line = new Line2D.Double(xx0, yy, xx1, yy);
 287:                     cap1 = new Line2D.Double(xx0, yy - adj, xx0, yy + adj);
 288:                     cap2 = new Line2D.Double(xx1, yy - adj, xx1, yy + adj);
 289:                 }
 290:                 else {  // PlotOrientation.HORIZONTAL
 291:                     line = new Line2D.Double(yy, xx0, yy, xx1);
 292:                     cap1 = new Line2D.Double(yy - adj, xx0, yy + adj, xx0);
 293:                     cap2 = new Line2D.Double(yy - adj, xx1, yy + adj, xx1);
 294:                 }
 295:                 g2.setStroke(new BasicStroke(1.0f));
 296:                 if (this.errorPaint != null) {
 297:                     g2.setPaint(this.errorPaint);    
 298:                 }
 299:                 else {
 300:                     g2.setPaint(getItemPaint(series, item));
 301:                 }
 302:                 g2.draw(line);
 303:                 g2.draw(cap1);
 304:                 g2.draw(cap2);
 305:             }
 306:             if (this.drawYError) {
 307:                 // draw the error bar for the y-interval
 308:                 double y0 = ixyd.getStartYValue(series, item);
 309:                 double y1 = ixyd.getEndYValue(series, item);
 310:                 double x = ixyd.getXValue(series, item);
 311:                 RectangleEdge edge = plot.getRangeAxisEdge();
 312:                 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, edge);
 313:                 double yy1 = rangeAxis.valueToJava2D(y1, dataArea, edge);
 314:                 double xx = domainAxis.valueToJava2D(x, dataArea, 
 315:                         plot.getDomainAxisEdge());
 316:                 Line2D line;
 317:                 Line2D cap1 = null;
 318:                 Line2D cap2 = null;
 319:                 double adj = this.capLength / 2.0;
 320:                 if (orientation == PlotOrientation.VERTICAL) {
 321:                     line = new Line2D.Double(xx, yy0, xx, yy1);
 322:                     cap1 = new Line2D.Double(xx - adj, yy0, xx + adj, yy0);
 323:                     cap2 = new Line2D.Double(xx - adj, yy1, xx + adj, yy1);
 324:                 }
 325:                 else {  // PlotOrientation.HORIZONTAL
 326:                     line = new Line2D.Double(yy0, xx, yy1, xx);
 327:                     cap1 = new Line2D.Double(yy0, xx - adj, yy0, xx + adj);
 328:                     cap2 = new Line2D.Double(yy1, xx - adj, yy1, xx + adj);
 329:                 }
 330:                 g2.setStroke(new BasicStroke(1.0f));
 331:                 if (this.errorPaint != null) {
 332:                     g2.setPaint(this.errorPaint);    
 333:                 }
 334:                 else {
 335:                     g2.setPaint(getItemPaint(series, item));
 336:                 }
 337:                 g2.draw(line);                    
 338:                 g2.draw(cap1);                    
 339:                 g2.draw(cap2);                    
 340:             }
 341:         }
 342:         super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis, 
 343:                 dataset, series, item, crosshairState, pass);
 344:     }
 345:     
 346:     /**
 347:      * Tests this instance for equality with an arbitrary object.
 348:      * 
 349:      * @param obj  the object (<code>null</code> permitted).
 350:      * 
 351:      * @return A boolean.
 352:      */
 353:     public boolean equals(Object obj) {
 354:         if (obj == this) {
 355:             return true;
 356:         }
 357:         if (!(obj instanceof XYErrorRenderer)) {
 358:             return false;
 359:         }
 360:         XYErrorRenderer that = (XYErrorRenderer) obj;
 361:         if (this.drawXError != that.drawXError) {
 362:             return false;
 363:         }
 364:         if (this.drawYError != that.drawYError) {
 365:             return false;
 366:         }
 367:         if (this.capLength != that.capLength) {
 368:             return false;
 369:         }
 370:         if (!PaintUtilities.equal(this.errorPaint, that.errorPaint)) {
 371:             return false;
 372:         }
 373:         return super.equals(obj);
 374:     }
 375:     
 376:     /**
 377:      * Provides serialization support.
 378:      *
 379:      * @param stream  the input stream.
 380:      *
 381:      * @throws IOException  if there is an I/O error.
 382:      * @throws ClassNotFoundException  if there is a classpath problem.
 383:      */
 384:     private void readObject(ObjectInputStream stream) 
 385:             throws IOException, ClassNotFoundException {
 386:         stream.defaultReadObject();
 387:         this.errorPaint = SerialUtilities.readPaint(stream);
 388:     }
 389:     
 390:     /**
 391:      * Provides serialization support.
 392:      *
 393:      * @param stream  the output stream.
 394:      *
 395:      * @throws IOException  if there is an I/O error.
 396:      */
 397:     private void writeObject(ObjectOutputStream stream) throws IOException {
 398:         stream.defaultWriteObject();
 399:         SerialUtilities.writePaint(this.errorPaint, stream);
 400:     }
 401:     
 402: }