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:  * Changes
  36:  * -------
  37:  * 25-Oct-2006 : Version 1 (DG);
  38:  * 23-Mar-2007 : Check item visibility before drawing error bars - see bug
  39:  *               1686178 (DG);
  40:  * 
  41:  */
  42: 
  43: package org.jfree.chart.renderer.xy;
  44: 
  45: import java.awt.BasicStroke;
  46: import java.awt.Graphics2D;
  47: import java.awt.Paint;
  48: import java.awt.geom.Line2D;
  49: import java.awt.geom.Rectangle2D;
  50: import java.io.IOException;
  51: import java.io.ObjectInputStream;
  52: import java.io.ObjectOutputStream;
  53: 
  54: import org.jfree.chart.axis.ValueAxis;
  55: import org.jfree.chart.event.RendererChangeEvent;
  56: import org.jfree.chart.plot.CrosshairState;
  57: import org.jfree.chart.plot.PlotOrientation;
  58: import org.jfree.chart.plot.PlotRenderingInfo;
  59: import org.jfree.chart.plot.XYPlot;
  60: import org.jfree.data.Range;
  61: import org.jfree.data.general.DatasetUtilities;
  62: import org.jfree.data.xy.IntervalXYDataset;
  63: import org.jfree.data.xy.XYDataset;
  64: import org.jfree.io.SerialUtilities;
  65: import org.jfree.ui.RectangleEdge;
  66: import org.jfree.util.PaintUtilities;
  67: 
  68: /**
  69:  * A line and shape renderer that can also display x and/or y-error values.  
  70:  * This renderer expects an {@link IntervalXYDataset}, otherwise it reverts
  71:  * to the behaviour of the super class.
  72:  * 
  73:  * @since 1.0.3
  74:  */
  75: public class XYErrorRenderer extends XYLineAndShapeRenderer {
  76: 
  77:     /** For serialization. */
  78:     static final long serialVersionUID = 5162283570955172424L;
  79:     
  80:     /** A flag that controls whether or not the x-error bars are drawn. */
  81:     private boolean drawXError;
  82:     
  83:     /** A flag that controls whether or not the y-error bars are drawn. */
  84:     private boolean drawYError;
  85:     
  86:     /** The length of the cap at the end of the error bars. */
  87:     private double capLength;
  88:     
  89:     /** 
  90:      * The paint used to draw the error bars (if <code>null</code> we use the
  91:      * series paint).
  92:      */
  93:     private transient Paint errorPaint;
  94:     
  95:     /**
  96:      * Creates a new <code>XYErrorRenderer</code> instance.
  97:      */
  98:     public XYErrorRenderer() {
  99:         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: }