001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, 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-2009, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: Mofeed Shahin; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * Peter Kolb (patch 2497611); 035 * 036 * Changes 037 * ------- 038 * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG); 039 * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with 040 * StatisticalBarRenderer (DG); 041 * ------------- JFREECHART 1.0.x --------------------------------------------- 042 * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering 043 * plots with horizontal orientation (DG); 044 * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG); 045 * 01-Jun-2007 : Return early from drawItem() method if item is not 046 * visible (DG); 047 * 14-Jun-2007 : If the dataset is not a StatisticalCategoryDataset, revert 048 * to the drawing behaviour of LineAndShapeRenderer (DG); 049 * 27-Sep-2007 : Added offset option to match new option in 050 * LineAndShapeRenderer (DG); 051 * 14-Jan-2009 : Added support for seriesVisible flags (PK); 052 * 23-Jan-2009 : Observe useFillPaint and drawOutlines flags (PK); 053 * 23-Jan-2009 : In drawItem, divide code into passes (DG); 054 * 05-Feb-2009 : Added errorIndicatorStroke field (DG); 055 * 01-Apr-2009 : Added override for findRangeBounds(), and fixed NPE in 056 * creating item entities (DG); 057 */ 058 059 package org.jfree.chart.renderer.category; 060 061 import java.awt.Graphics2D; 062 import java.awt.Paint; 063 import java.awt.Shape; 064 import java.awt.Stroke; 065 import java.awt.geom.Line2D; 066 import java.awt.geom.Rectangle2D; 067 import java.io.IOException; 068 import java.io.ObjectInputStream; 069 import java.io.ObjectOutputStream; 070 import java.io.Serializable; 071 072 import org.jfree.chart.HashUtilities; 073 import org.jfree.chart.axis.CategoryAxis; 074 import org.jfree.chart.axis.ValueAxis; 075 import org.jfree.chart.entity.EntityCollection; 076 import org.jfree.chart.event.RendererChangeEvent; 077 import org.jfree.chart.plot.CategoryPlot; 078 import org.jfree.chart.plot.PlotOrientation; 079 import org.jfree.data.Range; 080 import org.jfree.data.category.CategoryDataset; 081 import org.jfree.data.statistics.StatisticalCategoryDataset; 082 import org.jfree.io.SerialUtilities; 083 import org.jfree.ui.RectangleEdge; 084 import org.jfree.util.ObjectUtilities; 085 import org.jfree.util.PaintUtilities; 086 import org.jfree.util.PublicCloneable; 087 import org.jfree.util.ShapeUtilities; 088 089 /** 090 * A renderer that draws shapes for each data item, and lines between data 091 * items. Each point has a mean value and a standard deviation line. For use 092 * with the {@link CategoryPlot} class. The example shown 093 * here is generated by the <code>StatisticalLineChartDemo1.java</code> program 094 * included in the JFreeChart Demo Collection: 095 * <br><br> 096 * <img src="../../../../../images/StatisticalLineRendererSample.png" 097 * alt="StatisticalLineRendererSample.png" /> 098 */ 099 public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer 100 implements Cloneable, PublicCloneable, Serializable { 101 102 /** For serialization. */ 103 private static final long serialVersionUID = -3557517173697777579L; 104 105 /** The paint used to show the error indicator. */ 106 private transient Paint errorIndicatorPaint; 107 108 /** 109 * The stroke used to draw the error indicators. If null, the renderer 110 * will use the itemOutlineStroke. 111 * 112 * @since 1.0.13 113 */ 114 private transient Stroke errorIndicatorStroke; 115 116 /** 117 * Constructs a default renderer (draws shapes and lines). 118 */ 119 public StatisticalLineAndShapeRenderer() { 120 this(true, true); 121 } 122 123 /** 124 * Constructs a new renderer. 125 * 126 * @param linesVisible draw lines? 127 * @param shapesVisible draw shapes? 128 */ 129 public StatisticalLineAndShapeRenderer(boolean linesVisible, 130 boolean shapesVisible) { 131 super(linesVisible, shapesVisible); 132 this.errorIndicatorPaint = null; 133 this.errorIndicatorStroke = null; 134 } 135 136 /** 137 * Returns the paint used for the error indicators. 138 * 139 * @return The paint used for the error indicators (possibly 140 * <code>null</code>). 141 * 142 * @see #setErrorIndicatorPaint(Paint) 143 */ 144 public Paint getErrorIndicatorPaint() { 145 return this.errorIndicatorPaint; 146 } 147 148 /** 149 * Sets the paint used for the error indicators (if <code>null</code>, 150 * the item paint is used instead) and sends a 151 * {@link RendererChangeEvent} to all registered listeners. 152 * 153 * @param paint the paint (<code>null</code> permitted). 154 * 155 * @see #getErrorIndicatorPaint() 156 */ 157 public void setErrorIndicatorPaint(Paint paint) { 158 this.errorIndicatorPaint = paint; 159 fireChangeEvent(); 160 } 161 162 /** 163 * Returns the stroke used for the error indicators. 164 * 165 * @return The stroke used for the error indicators (possibly 166 * <code>null</code>). 167 * 168 * @see #setErrorIndicatorStroke(Stroke) 169 * 170 * @since 1.0.13 171 */ 172 public Stroke getErrorIndicatorStroke() { 173 return this.errorIndicatorStroke; 174 } 175 176 /** 177 * Sets the stroke used for the error indicators (if <code>null</code>, 178 * the item outline stroke is used instead) and sends a 179 * {@link RendererChangeEvent} to all registered listeners. 180 * 181 * @param stroke the stroke (<code>null</code> permitted). 182 * 183 * @see #getErrorIndicatorStroke() 184 * 185 * @since 1.0.13 186 */ 187 public void setErrorIndicatorStroke(Stroke stroke) { 188 this.errorIndicatorStroke = stroke; 189 fireChangeEvent(); 190 } 191 192 /** 193 * Returns the range of values the renderer requires to display all the 194 * items from the specified dataset. 195 * 196 * @param dataset the dataset (<code>null</code> permitted). 197 * 198 * @return The range (or <code>null</code> if the dataset is 199 * <code>null</code> or empty). 200 */ 201 public Range findRangeBounds(CategoryDataset dataset) { 202 return findRangeBounds(dataset, true); 203 } 204 205 /** 206 * Draw a single data item. 207 * 208 * @param g2 the graphics device. 209 * @param state the renderer state. 210 * @param dataArea the area in which the data is drawn. 211 * @param plot the plot. 212 * @param domainAxis the domain axis. 213 * @param rangeAxis the range axis. 214 * @param dataset the dataset (a {@link StatisticalCategoryDataset} is 215 * required). 216 * @param row the row index (zero-based). 217 * @param column the column index (zero-based). 218 * @param pass the pass. 219 */ 220 public void drawItem(Graphics2D g2, 221 CategoryItemRendererState state, 222 Rectangle2D dataArea, 223 CategoryPlot plot, 224 CategoryAxis domainAxis, 225 ValueAxis rangeAxis, 226 CategoryDataset dataset, 227 int row, 228 int column, 229 int pass) { 230 231 // do nothing if item is not visible 232 if (!getItemVisible(row, column)) { 233 return; 234 } 235 236 // if the dataset is not a StatisticalCategoryDataset then just revert 237 // to the superclass (LineAndShapeRenderer) behaviour... 238 if (!(dataset instanceof StatisticalCategoryDataset)) { 239 super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 240 dataset, row, column, pass); 241 return; 242 } 243 244 int visibleRow = state.getVisibleSeriesIndex(row); 245 if (visibleRow < 0) { 246 return; 247 } 248 int visibleRowCount = state.getVisibleSeriesCount(); 249 250 StatisticalCategoryDataset statDataset 251 = (StatisticalCategoryDataset) dataset; 252 Number meanValue = statDataset.getMeanValue(row, column); 253 if (meanValue == null) { 254 return; 255 } 256 PlotOrientation orientation = plot.getOrientation(); 257 258 // current data point... 259 double x1; 260 if (getUseSeriesOffset()) { 261 x1 = domainAxis.getCategorySeriesMiddle(column, 262 dataset.getColumnCount(), 263 visibleRow, visibleRowCount, 264 getItemMargin(), dataArea, plot.getDomainAxisEdge()); 265 } 266 else { 267 x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 268 dataArea, plot.getDomainAxisEdge()); 269 } 270 double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea, 271 plot.getRangeAxisEdge()); 272 273 // draw the standard deviation lines *before* the shapes (if they're 274 // visible) - it looks better if the shape fill colour is different to 275 // the line colour 276 Number sdv = statDataset.getStdDevValue(row, column); 277 if (pass == 1 && sdv != null) { 278 //standard deviation lines 279 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 280 double valueDelta = sdv.doubleValue(); 281 double highVal, lowVal; 282 if ((meanValue.doubleValue() + valueDelta) 283 > rangeAxis.getRange().getUpperBound()) { 284 highVal = rangeAxis.valueToJava2D( 285 rangeAxis.getRange().getUpperBound(), dataArea, 286 yAxisLocation); 287 } 288 else { 289 highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 290 + valueDelta, dataArea, yAxisLocation); 291 } 292 293 if ((meanValue.doubleValue() + valueDelta) 294 < rangeAxis.getRange().getLowerBound()) { 295 lowVal = rangeAxis.valueToJava2D( 296 rangeAxis.getRange().getLowerBound(), dataArea, 297 yAxisLocation); 298 } 299 else { 300 lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 301 - valueDelta, dataArea, yAxisLocation); 302 } 303 304 if (this.errorIndicatorPaint != null) { 305 g2.setPaint(this.errorIndicatorPaint); 306 } 307 else { 308 g2.setPaint(getItemPaint(row, column)); 309 } 310 if (this.errorIndicatorStroke != null) { 311 g2.setStroke(this.errorIndicatorStroke); 312 } 313 else { 314 g2.setStroke(getItemOutlineStroke(row, column)); 315 } 316 Line2D line = new Line2D.Double(); 317 if (orientation == PlotOrientation.HORIZONTAL) { 318 line.setLine(lowVal, x1, highVal, x1); 319 g2.draw(line); 320 line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d); 321 g2.draw(line); 322 line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d); 323 g2.draw(line); 324 } 325 else { // PlotOrientation.VERTICAL 326 line.setLine(x1, lowVal, x1, highVal); 327 g2.draw(line); 328 line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal); 329 g2.draw(line); 330 line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal); 331 g2.draw(line); 332 } 333 334 } 335 336 Shape hotspot = null; 337 if (pass == 1 && getItemShapeVisible(row, column)) { 338 Shape shape = getItemShape(row, column); 339 if (orientation == PlotOrientation.HORIZONTAL) { 340 shape = ShapeUtilities.createTranslatedShape(shape, y1, x1); 341 } 342 else if (orientation == PlotOrientation.VERTICAL) { 343 shape = ShapeUtilities.createTranslatedShape(shape, x1, y1); 344 } 345 hotspot = shape; 346 347 if (getItemShapeFilled(row, column)) { 348 if (getUseFillPaint()) { 349 g2.setPaint(getItemFillPaint(row, column)); 350 } 351 else { 352 g2.setPaint(getItemPaint(row, column)); 353 } 354 g2.fill(shape); 355 } 356 if (getDrawOutlines()) { 357 if (getUseOutlinePaint()) { 358 g2.setPaint(getItemOutlinePaint(row, column)); 359 } 360 else { 361 g2.setPaint(getItemPaint(row, column)); 362 } 363 g2.setStroke(getItemOutlineStroke(row, column)); 364 g2.draw(shape); 365 } 366 // draw the item label if there is one... 367 if (isItemLabelVisible(row, column)) { 368 if (orientation == PlotOrientation.HORIZONTAL) { 369 drawItemLabel(g2, orientation, dataset, row, column, 370 y1, x1, (meanValue.doubleValue() < 0.0)); 371 } 372 else if (orientation == PlotOrientation.VERTICAL) { 373 drawItemLabel(g2, orientation, dataset, row, column, 374 x1, y1, (meanValue.doubleValue() < 0.0)); 375 } 376 } 377 } 378 379 if (pass == 0 && getItemLineVisible(row, column)) { 380 if (column != 0) { 381 382 Number previousValue = statDataset.getValue(row, column - 1); 383 if (previousValue != null) { 384 385 // previous data point... 386 double previous = previousValue.doubleValue(); 387 double x0; 388 if (getUseSeriesOffset()) { 389 x0 = domainAxis.getCategorySeriesMiddle( 390 column - 1, dataset.getColumnCount(), 391 visibleRow, visibleRowCount, 392 getItemMargin(), dataArea, 393 plot.getDomainAxisEdge()); 394 } 395 else { 396 x0 = domainAxis.getCategoryMiddle(column - 1, 397 getColumnCount(), dataArea, 398 plot.getDomainAxisEdge()); 399 } 400 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 401 plot.getRangeAxisEdge()); 402 403 Line2D line = null; 404 if (orientation == PlotOrientation.HORIZONTAL) { 405 line = new Line2D.Double(y0, x0, y1, x1); 406 } 407 else if (orientation == PlotOrientation.VERTICAL) { 408 line = new Line2D.Double(x0, y0, x1, y1); 409 } 410 g2.setPaint(getItemPaint(row, column)); 411 g2.setStroke(getItemStroke(row, column)); 412 g2.draw(line); 413 } 414 } 415 } 416 417 if (pass == 1) { 418 // add an item entity, if this information is being collected 419 EntityCollection entities = state.getEntityCollection(); 420 if (entities != null) { 421 addEntity(entities, hotspot, dataset, row, column, x1, y1); 422 } 423 } 424 425 } 426 427 /** 428 * Tests this renderer for equality with an arbitrary object. 429 * 430 * @param obj the object (<code>null</code> permitted). 431 * 432 * @return A boolean. 433 */ 434 public boolean equals(Object obj) { 435 if (obj == this) { 436 return true; 437 } 438 if (!(obj instanceof StatisticalLineAndShapeRenderer)) { 439 return false; 440 } 441 StatisticalLineAndShapeRenderer that 442 = (StatisticalLineAndShapeRenderer) obj; 443 if (!PaintUtilities.equal(this.errorIndicatorPaint, 444 that.errorIndicatorPaint)) { 445 return false; 446 } 447 if (!ObjectUtilities.equal(this.errorIndicatorStroke, 448 that.errorIndicatorStroke)) { 449 return false; 450 } 451 return super.equals(obj); 452 } 453 454 /** 455 * Returns a hash code for this instance. 456 * 457 * @return A hash code. 458 */ 459 public int hashCode() { 460 int hash = super.hashCode(); 461 hash = HashUtilities.hashCode(hash, this.errorIndicatorPaint); 462 return hash; 463 } 464 465 /** 466 * Provides serialization support. 467 * 468 * @param stream the output stream. 469 * 470 * @throws IOException if there is an I/O error. 471 */ 472 private void writeObject(ObjectOutputStream stream) throws IOException { 473 stream.defaultWriteObject(); 474 SerialUtilities.writePaint(this.errorIndicatorPaint, stream); 475 SerialUtilities.writeStroke(this.errorIndicatorStroke, stream); 476 } 477 478 /** 479 * Provides serialization support. 480 * 481 * @param stream the input stream. 482 * 483 * @throws IOException if there is an I/O error. 484 * @throws ClassNotFoundException if there is a classpath problem. 485 */ 486 private void readObject(ObjectInputStream stream) 487 throws IOException, ClassNotFoundException { 488 stream.defaultReadObject(); 489 this.errorIndicatorPaint = SerialUtilities.readPaint(stream); 490 this.errorIndicatorStroke = SerialUtilities.readStroke(stream); 491 } 492 493 }