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