Source for org.jfree.chart.renderer.category.StatisticalLineAndShapeRenderer

   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:  * StatisticalLineAndShapeRenderer.java
  29:  * ------------------------------------
  30:  * (C) Copyright 2005-2007, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  Mofeed Shahin;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG);
  38:  * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with
  39:  *               StatisticalBarRenderer (DG);
  40:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  41:  * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering
  42:  *               plots with horizontal orientation (DG);
  43:  * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG);
  44:  * 01-Jun-2007 : Return early from drawItem() method if item is not
  45:  *               visible (DG);
  46:  * 14-Jun-2007 : If the dataset is not a StatisticalCategoryDataset, revert
  47:  *               to the drawing behaviour of LineAndShapeRenderer (DG);
  48:  * 27-Sep-2007 : Added offset option to match new option in 
  49:  *               LineAndShapeRenderer (DG);
  50:  *
  51:  */
  52: 
  53: package org.jfree.chart.renderer.category;
  54: 
  55: import java.awt.Graphics2D;
  56: import java.awt.Paint;
  57: import java.awt.Shape;
  58: import java.awt.geom.Line2D;
  59: import java.awt.geom.Rectangle2D;
  60: import java.io.IOException;
  61: import java.io.ObjectInputStream;
  62: import java.io.ObjectOutputStream;
  63: import java.io.Serializable;
  64: 
  65: import org.jfree.chart.axis.CategoryAxis;
  66: import org.jfree.chart.axis.ValueAxis;
  67: import org.jfree.chart.entity.EntityCollection;
  68: import org.jfree.chart.event.RendererChangeEvent;
  69: import org.jfree.chart.plot.CategoryPlot;
  70: import org.jfree.chart.plot.PlotOrientation;
  71: import org.jfree.data.category.CategoryDataset;
  72: import org.jfree.data.statistics.StatisticalCategoryDataset;
  73: import org.jfree.io.SerialUtilities;
  74: import org.jfree.ui.RectangleEdge;
  75: import org.jfree.util.PaintUtilities;
  76: import org.jfree.util.PublicCloneable;
  77: import org.jfree.util.ShapeUtilities;
  78: 
  79: /**
  80:  * A renderer that draws shapes for each data item, and lines between data
  81:  * items.  Each point has a mean value and a standard deviation line. For use
  82:  * with the {@link CategoryPlot} class.
  83:  */
  84: public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer
  85:         implements Cloneable, PublicCloneable, Serializable {
  86: 
  87:     /** For serialization. */
  88:     private static final long serialVersionUID = -3557517173697777579L;
  89: 
  90:     /** The paint used to show the error indicator. */
  91:     private transient Paint errorIndicatorPaint;
  92: 
  93:     /**
  94:      * Constructs a default renderer (draws shapes and lines).
  95:      */
  96:     public StatisticalLineAndShapeRenderer() {
  97:         this(true, true);
  98:     }
  99: 
 100:     /**
 101:      * Constructs a new renderer.
 102:      *
 103:      * @param linesVisible  draw lines?
 104:      * @param shapesVisible  draw shapes?
 105:      */
 106:     public StatisticalLineAndShapeRenderer(boolean linesVisible,
 107:                                            boolean shapesVisible) {
 108:         super(linesVisible, shapesVisible);
 109:         this.errorIndicatorPaint = null;
 110:     }
 111: 
 112:     /**
 113:      * Returns the paint used for the error indicators.
 114:      *
 115:      * @return The paint used for the error indicators (possibly
 116:      *         <code>null</code>).
 117:      *         
 118:      * @see #setErrorIndicatorPaint(Paint)
 119:      */
 120:     public Paint getErrorIndicatorPaint() {
 121:         return this.errorIndicatorPaint;
 122:     }
 123: 
 124:     /**
 125:      * Sets the paint used for the error indicators (if <code>null</code>,
 126:      * the item outline paint is used instead) and sends a 
 127:      * {@link RendererChangeEvent} to all registered listeners.
 128:      *
 129:      * @param paint  the paint (<code>null</code> permitted).
 130:      * 
 131:      * @see #getErrorIndicatorPaint()
 132:      */
 133:     public void setErrorIndicatorPaint(Paint paint) {
 134:         this.errorIndicatorPaint = paint;
 135:         fireChangeEvent();
 136:     }
 137: 
 138:     /**
 139:      * Draw a single data item.
 140:      *
 141:      * @param g2  the graphics device.
 142:      * @param state  the renderer state.
 143:      * @param dataArea  the area in which the data is drawn.
 144:      * @param plot  the plot.
 145:      * @param domainAxis  the domain axis.
 146:      * @param rangeAxis  the range axis.
 147:      * @param dataset  the dataset (a {@link StatisticalCategoryDataset} is
 148:      *                 required).
 149:      * @param row  the row index (zero-based).
 150:      * @param column  the column index (zero-based).
 151:      * @param pass  the pass.
 152:      */
 153:     public void drawItem(Graphics2D g2,
 154:                          CategoryItemRendererState state,
 155:                          Rectangle2D dataArea,
 156:                          CategoryPlot plot,
 157:                          CategoryAxis domainAxis,
 158:                          ValueAxis rangeAxis,
 159:                          CategoryDataset dataset,
 160:                          int row,
 161:                          int column,
 162:                          int pass) {
 163: 
 164:         // do nothing if item is not visible
 165:         if (!getItemVisible(row, column)) {
 166:             return;
 167:         }
 168: 
 169:         // nothing is drawn for null...
 170:         Number v = dataset.getValue(row, column);
 171:         if (v == null) {
 172:             return;
 173:         }
 174: 
 175:         // if the dataset is not a StatisticalCategoryDataset then just revert
 176:         // to the superclass (LineAndShapeRenderer) behaviour...
 177:         if (!(dataset instanceof StatisticalCategoryDataset)) {
 178:             super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
 179:                     dataset, row, column, pass);
 180:             return;
 181:         }
 182: 
 183:         StatisticalCategoryDataset statData
 184:                 = (StatisticalCategoryDataset) dataset;
 185: 
 186:         Number meanValue = statData.getMeanValue(row, column);
 187: 
 188:         PlotOrientation orientation = plot.getOrientation();
 189: 
 190:         // current data point...
 191:         double x1;
 192:         if (getUseSeriesOffset()) {
 193:             x1 = domainAxis.getCategorySeriesMiddle(dataset.getColumnKey(
 194:                     column), dataset.getRowKey(row), dataset, getItemMargin(), 
 195:                     dataArea, plot.getDomainAxisEdge());            
 196:         }
 197:         else {
 198:             x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
 199:                     dataArea, plot.getDomainAxisEdge());
 200:         }
 201: 
 202:         double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea,
 203:                 plot.getRangeAxisEdge());
 204: 
 205:         Shape shape = getItemShape(row, column);
 206:         if (orientation == PlotOrientation.HORIZONTAL) {
 207:             shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
 208:         }
 209:         else if (orientation == PlotOrientation.VERTICAL) {
 210:             shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
 211:         }
 212:         if (getItemShapeVisible(row, column)) {
 213: 
 214:             if (getItemShapeFilled(row, column)) {
 215:                 g2.setPaint(getItemPaint(row, column));
 216:                 g2.fill(shape);
 217:             }
 218:             else {
 219:                 if (getUseOutlinePaint()) {
 220:                     g2.setPaint(getItemOutlinePaint(row, column));
 221:                 }
 222:                 else {
 223:                     g2.setPaint(getItemPaint(row, column));
 224:                 }
 225:                 g2.setStroke(getItemOutlineStroke(row, column));
 226:                 g2.draw(shape);
 227:             }
 228:         }
 229: 
 230:         if (getItemLineVisible(row, column)) {
 231:             if (column != 0) {
 232: 
 233:                 Number previousValue = statData.getValue(row, column - 1);
 234:                 if (previousValue != null) {
 235: 
 236:                     // previous data point...
 237:                     double previous = previousValue.doubleValue();
 238:                     double x0;
 239:                     if (getUseSeriesOffset()) {
 240:                         x0 = domainAxis.getCategorySeriesMiddle(
 241:                                 dataset.getColumnKey(column - 1), 
 242:                                 dataset.getRowKey(row), dataset, 
 243:                                 getItemMargin(), dataArea, 
 244:                                 plot.getDomainAxisEdge());
 245:                     }
 246:                     else {
 247:                         x0 = domainAxis.getCategoryMiddle(column - 1, 
 248:                                 getColumnCount(), dataArea, 
 249:                                 plot.getDomainAxisEdge());
 250:                     }
 251:                     double y0 = rangeAxis.valueToJava2D(previous, dataArea,
 252:                             plot.getRangeAxisEdge());
 253: 
 254:                     Line2D line = null;
 255:                     if (orientation == PlotOrientation.HORIZONTAL) {
 256:                         line = new Line2D.Double(y0, x0, y1, x1);
 257:                     }
 258:                     else if (orientation == PlotOrientation.VERTICAL) {
 259:                         line = new Line2D.Double(x0, y0, x1, y1);
 260:                     }
 261:                     g2.setPaint(getItemPaint(row, column));
 262:                     g2.setStroke(getItemStroke(row, column));
 263:                     g2.draw(line);
 264:                 }
 265:             }
 266:         }
 267: 
 268:         RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
 269:         g2.setPaint(getItemPaint(row, column));
 270: 
 271:         //standard deviation lines
 272:         double valueDelta = statData.getStdDevValue(row, column).doubleValue();
 273: 
 274:         double highVal, lowVal;
 275:         if ((meanValue.doubleValue() + valueDelta)
 276:                 > rangeAxis.getRange().getUpperBound()) {
 277:             highVal = rangeAxis.valueToJava2D(
 278:                     rangeAxis.getRange().getUpperBound(), dataArea,
 279:                     yAxisLocation);
 280:         }
 281:         else {
 282:             highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
 283:                     + valueDelta, dataArea, yAxisLocation);
 284:         }
 285: 
 286:         if ((meanValue.doubleValue() + valueDelta)
 287:                 < rangeAxis.getRange().getLowerBound()) {
 288:             lowVal = rangeAxis.valueToJava2D(
 289:                     rangeAxis.getRange().getLowerBound(), dataArea,
 290:                     yAxisLocation);
 291:         }
 292:         else {
 293:             lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
 294:                     - valueDelta, dataArea, yAxisLocation);
 295:         }
 296: 
 297:         if (this.errorIndicatorPaint != null) {
 298:             g2.setPaint(this.errorIndicatorPaint);
 299:         }
 300:         else {
 301:             g2.setPaint(getItemPaint(row, column));
 302:         }
 303:         Line2D line = new Line2D.Double();
 304:         if (orientation == PlotOrientation.HORIZONTAL) {
 305:             line.setLine(lowVal, x1, highVal, x1);
 306:             g2.draw(line);
 307:             line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d);
 308:             g2.draw(line);
 309:             line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d);
 310:             g2.draw(line);
 311:         }
 312:         else {  // PlotOrientation.VERTICAL
 313:             line.setLine(x1, lowVal, x1, highVal);
 314:             g2.draw(line);
 315:             line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal);
 316:             g2.draw(line);
 317:             line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal);
 318:             g2.draw(line);
 319:         }
 320: 
 321:         // draw the item label if there is one...
 322:         if (isItemLabelVisible(row, column)) {
 323:             if (orientation == PlotOrientation.HORIZONTAL) {
 324:                 drawItemLabel(g2, orientation, dataset, row, column,
 325:                         y1, x1, (meanValue.doubleValue() < 0.0));
 326:             }
 327:             else if (orientation == PlotOrientation.VERTICAL) {
 328:                 drawItemLabel(g2, orientation, dataset, row, column,
 329:                         x1, y1, (meanValue.doubleValue() < 0.0));
 330:             }
 331:         }
 332: 
 333:         // add an item entity, if this information is being collected
 334:         EntityCollection entities = state.getEntityCollection();
 335:         if (entities != null && shape != null) {
 336:             addItemEntity(entities, dataset, row, column, shape);
 337:         }
 338: 
 339:     }
 340: 
 341:     /**
 342:      * Tests this renderer for equality with an arbitrary object.
 343:      *
 344:      * @param obj  the object (<code>null</code> permitted).
 345:      *
 346:      * @return A boolean.
 347:      */
 348:     public boolean equals(Object obj) {
 349:         if (obj == this) {
 350:             return true;
 351:         }
 352:         if (!(obj instanceof StatisticalLineAndShapeRenderer)) {
 353:             return false;
 354:         }
 355:         StatisticalLineAndShapeRenderer that
 356:                 = (StatisticalLineAndShapeRenderer) obj;
 357:         if (!PaintUtilities.equal(this.errorIndicatorPaint,
 358:                 that.errorIndicatorPaint)) {
 359:             return false;
 360:         }
 361:         return super.equals(obj);
 362:     }
 363: 
 364:     /**
 365:      * Provides serialization support.
 366:      *
 367:      * @param stream  the output stream.
 368:      *
 369:      * @throws IOException  if there is an I/O error.
 370:      */
 371:     private void writeObject(ObjectOutputStream stream) throws IOException {
 372:         stream.defaultWriteObject();
 373:         SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
 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.errorIndicatorPaint = SerialUtilities.readPaint(stream);
 388:     }
 389: 
 390: }