001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------------------------ 028 * StatisticalLineAndShapeRenderer.java 029 * ------------------------------------ 030 * (C) Copyright 2005-2008, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: Mofeed Shahin; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * Changes 036 * ------- 037 * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG); 038 * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with 039 * StatisticalBarRenderer (DG); 040 * ------------- JFREECHART 1.0.x --------------------------------------------- 041 * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering 042 * plots with horizontal orientation (DG); 043 * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG); 044 * 01-Jun-2007 : Return early from drawItem() method if item is not 045 * visible (DG); 046 * 14-Jun-2007 : If the dataset is not a StatisticalCategoryDataset, revert 047 * to the drawing behaviour of LineAndShapeRenderer (DG); 048 * 27-Sep-2007 : Added offset option to match new option in 049 * LineAndShapeRenderer (DG); 050 * 051 */ 052 053 package org.jfree.chart.renderer.category; 054 055 import java.awt.Graphics2D; 056 import java.awt.Paint; 057 import java.awt.Shape; 058 import java.awt.geom.Line2D; 059 import java.awt.geom.Rectangle2D; 060 import java.io.IOException; 061 import java.io.ObjectInputStream; 062 import java.io.ObjectOutputStream; 063 import java.io.Serializable; 064 065 import org.jfree.chart.axis.CategoryAxis; 066 import org.jfree.chart.axis.ValueAxis; 067 import org.jfree.chart.entity.EntityCollection; 068 import org.jfree.chart.event.RendererChangeEvent; 069 import org.jfree.chart.plot.CategoryPlot; 070 import org.jfree.chart.plot.PlotOrientation; 071 import org.jfree.data.category.CategoryDataset; 072 import org.jfree.data.statistics.StatisticalCategoryDataset; 073 import org.jfree.io.SerialUtilities; 074 import org.jfree.ui.RectangleEdge; 075 import org.jfree.util.PaintUtilities; 076 import org.jfree.util.PublicCloneable; 077 import org.jfree.util.ShapeUtilities; 078 079 /** 080 * A renderer that draws shapes for each data item, and lines between data 081 * items. Each point has a mean value and a standard deviation line. For use 082 * with the {@link CategoryPlot} class. 083 */ 084 public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer 085 implements Cloneable, PublicCloneable, Serializable { 086 087 /** For serialization. */ 088 private static final long serialVersionUID = -3557517173697777579L; 089 090 /** The paint used to show the error indicator. */ 091 private transient Paint errorIndicatorPaint; 092 093 /** 094 * Constructs a default renderer (draws shapes and lines). 095 */ 096 public StatisticalLineAndShapeRenderer() { 097 this(true, true); 098 } 099 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 }