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