1 /* =========================================================== 2 * JFreeChart : a free chart library for the Java(tm) platform 3 * =========================================================== 4 * 5 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 25 * Other names may be trademarks of their respective owners.] 26 * 27 * ------------------------------------ 28 * StatisticalLineAndShapeRenderer.java 29 * ------------------------------------ 30 * (C) Copyright 2005-2009, by Object Refinery Limited and Contributors. 31 * 32 * Original Author: Mofeed Shahin; 33 * Contributor(s): David Gilbert (for Object Refinery Limited); 34 * Peter Kolb (patch 2497611); 35 * 36 * Changes 37 * ------- 38 * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG); 39 * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with 40 * StatisticalBarRenderer (DG); 41 * ------------- JFREECHART 1.0.x --------------------------------------------- 42 * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering 43 * plots with horizontal orientation (DG); 44 * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG); 45 * 01-Jun-2007 : Return early from drawItem() method if item is not 46 * visible (DG); 47 * 14-Jun-2007 : If the dataset is not a StatisticalCategoryDataset, revert 48 * to the drawing behaviour of LineAndShapeRenderer (DG); 49 * 27-Sep-2007 : Added offset option to match new option in 50 * LineAndShapeRenderer (DG); 51 * 14-Jan-2009 : Added support for seriesVisible flags (PK); 52 * 23-Jan-2009 : Observe useFillPaint and drawOutlines flags (PK); 53 * 23-Jan-2009 : In drawItem, divide code into passes (DG); 54 * 05-Feb-2009 : Added errorIndicatorStroke field (DG); 55 * 01-Apr-2009 : Added override for findRangeBounds(), and fixed NPE in 56 * creating item entities (DG); 57 */ 58 59 package org.jfree.chart.renderer.category; 60 61 import java.awt.Graphics2D; 62 import java.awt.Paint; 63 import java.awt.Shape; 64 import java.awt.Stroke; 65 import java.awt.geom.Line2D; 66 import java.awt.geom.Rectangle2D; 67 import java.io.IOException; 68 import java.io.ObjectInputStream; 69 import java.io.ObjectOutputStream; 70 import java.io.Serializable; 71 72 import org.jfree.chart.HashUtilities; 73 import org.jfree.chart.axis.CategoryAxis; 74 import org.jfree.chart.axis.ValueAxis; 75 import org.jfree.chart.entity.EntityCollection; 76 import org.jfree.chart.event.RendererChangeEvent; 77 import org.jfree.chart.plot.CategoryPlot; 78 import org.jfree.chart.plot.PlotOrientation; 79 import org.jfree.data.Range; 80 import org.jfree.data.category.CategoryDataset; 81 import org.jfree.data.statistics.StatisticalCategoryDataset; 82 import org.jfree.io.SerialUtilities; 83 import org.jfree.ui.RectangleEdge; 84 import org.jfree.util.ObjectUtilities; 85 import org.jfree.util.PaintUtilities; 86 import org.jfree.util.PublicCloneable; 87 import org.jfree.util.ShapeUtilities; 88 89 /** 90 * A renderer that draws shapes for each data item, and lines between data 91 * items. Each point has a mean value and a standard deviation line. For use 92 * with the {@link CategoryPlot} class. The example shown 93 * here is generated by the <code>StatisticalLineChartDemo1.java</code> program 94 * included in the JFreeChart Demo Collection: 95 * <br><br> 96 * <img src="../../../../../images/StatisticalLineRendererSample.png" 97 * alt="StatisticalLineRendererSample.png" /> 98 */ 99 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 */ StatisticalLineAndShapeRenderer()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 */ StatisticalLineAndShapeRenderer(boolean linesVisible, boolean shapesVisible)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 */ getErrorIndicatorPaint()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 */ setErrorIndicatorPaint(Paint paint)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 */ getErrorIndicatorStroke()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 */ setErrorIndicatorStroke(Stroke stroke)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 @Override findRangeBounds(CategoryDataset dataset)202 public Range findRangeBounds(CategoryDataset dataset) { 203 return findRangeBounds(dataset, true); 204 } 205 206 /** 207 * Draw a single data item. 208 * 209 * @param g2 the graphics device. 210 * @param state the renderer state. 211 * @param dataArea the area in which the data is drawn. 212 * @param plot the plot. 213 * @param domainAxis the domain axis. 214 * @param rangeAxis the range axis. 215 * @param dataset the dataset (a {@link StatisticalCategoryDataset} is 216 * required). 217 * @param row the row index (zero-based). 218 * @param column the column index (zero-based). 219 * @param pass the pass. 220 */ 221 @Override drawItem(Graphics2D g2, CategoryItemRendererState state, Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, int pass)222 public void drawItem(Graphics2D g2, CategoryItemRendererState state, 223 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 224 ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, 225 int pass) { 226 227 // do nothing if item is not visible 228 if (!getItemVisible(row, column)) { 229 return; 230 } 231 232 // if the dataset is not a StatisticalCategoryDataset then just revert 233 // to the superclass (LineAndShapeRenderer) behaviour... 234 if (!(dataset instanceof StatisticalCategoryDataset)) { 235 super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 236 dataset, row, column, pass); 237 return; 238 } 239 240 int visibleRow = state.getVisibleSeriesIndex(row); 241 if (visibleRow < 0) { 242 return; 243 } 244 int visibleRowCount = state.getVisibleSeriesCount(); 245 246 StatisticalCategoryDataset statDataset 247 = (StatisticalCategoryDataset) dataset; 248 Number meanValue = statDataset.getMeanValue(row, column); 249 if (meanValue == null) { 250 return; 251 } 252 PlotOrientation orientation = plot.getOrientation(); 253 254 // current data point... 255 double x1; 256 if (getUseSeriesOffset()) { 257 x1 = domainAxis.getCategorySeriesMiddle(column, 258 dataset.getColumnCount(), 259 visibleRow, visibleRowCount, 260 getItemMargin(), dataArea, plot.getDomainAxisEdge()); 261 } 262 else { 263 x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 264 dataArea, plot.getDomainAxisEdge()); 265 } 266 double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea, 267 plot.getRangeAxisEdge()); 268 269 // draw the standard deviation lines *before* the shapes (if they're 270 // visible) - it looks better if the shape fill colour is different to 271 // the line colour 272 Number sdv = statDataset.getStdDevValue(row, column); 273 if (pass == 1 && sdv != null) { 274 //standard deviation lines 275 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 276 double valueDelta = sdv.doubleValue(); 277 double highVal, lowVal; 278 if ((meanValue.doubleValue() + valueDelta) 279 > rangeAxis.getRange().getUpperBound()) { 280 highVal = rangeAxis.valueToJava2D( 281 rangeAxis.getRange().getUpperBound(), dataArea, 282 yAxisLocation); 283 } 284 else { 285 highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 286 + valueDelta, dataArea, yAxisLocation); 287 } 288 289 if ((meanValue.doubleValue() + valueDelta) 290 < rangeAxis.getRange().getLowerBound()) { 291 lowVal = rangeAxis.valueToJava2D( 292 rangeAxis.getRange().getLowerBound(), dataArea, 293 yAxisLocation); 294 } 295 else { 296 lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 297 - valueDelta, dataArea, yAxisLocation); 298 } 299 300 if (this.errorIndicatorPaint != null) { 301 g2.setPaint(this.errorIndicatorPaint); 302 } 303 else { 304 g2.setPaint(getItemPaint(row, column)); 305 } 306 if (this.errorIndicatorStroke != null) { 307 g2.setStroke(this.errorIndicatorStroke); 308 } 309 else { 310 g2.setStroke(getItemOutlineStroke(row, column)); 311 } 312 Line2D line = new Line2D.Double(); 313 if (orientation == PlotOrientation.HORIZONTAL) { 314 line.setLine(lowVal, x1, highVal, x1); 315 g2.draw(line); 316 line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d); 317 g2.draw(line); 318 line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d); 319 g2.draw(line); 320 } 321 else { // PlotOrientation.VERTICAL 322 line.setLine(x1, lowVal, x1, highVal); 323 g2.draw(line); 324 line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal); 325 g2.draw(line); 326 line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal); 327 g2.draw(line); 328 } 329 330 } 331 332 Shape hotspot = null; 333 if (pass == 1 && getItemShapeVisible(row, column)) { 334 Shape shape = getItemShape(row, column); 335 if (orientation == PlotOrientation.HORIZONTAL) { 336 shape = ShapeUtilities.createTranslatedShape(shape, y1, x1); 337 } 338 else if (orientation == PlotOrientation.VERTICAL) { 339 shape = ShapeUtilities.createTranslatedShape(shape, x1, y1); 340 } 341 hotspot = shape; 342 343 if (getItemShapeFilled(row, column)) { 344 if (getUseFillPaint()) { 345 g2.setPaint(getItemFillPaint(row, column)); 346 } 347 else { 348 g2.setPaint(getItemPaint(row, column)); 349 } 350 g2.fill(shape); 351 } 352 if (getDrawOutlines()) { 353 if (getUseOutlinePaint()) { 354 g2.setPaint(getItemOutlinePaint(row, column)); 355 } 356 else { 357 g2.setPaint(getItemPaint(row, column)); 358 } 359 g2.setStroke(getItemOutlineStroke(row, column)); 360 g2.draw(shape); 361 } 362 // draw the item label if there is one... 363 if (isItemLabelVisible(row, column)) { 364 if (orientation == PlotOrientation.HORIZONTAL) { 365 drawItemLabel(g2, orientation, dataset, row, column, 366 y1, x1, (meanValue.doubleValue() < 0.0)); 367 } 368 else if (orientation == PlotOrientation.VERTICAL) { 369 drawItemLabel(g2, orientation, dataset, row, column, 370 x1, y1, (meanValue.doubleValue() < 0.0)); 371 } 372 } 373 } 374 375 if (pass == 0 && getItemLineVisible(row, column)) { 376 if (column != 0) { 377 378 Number previousValue = statDataset.getValue(row, column - 1); 379 if (previousValue != null) { 380 381 // previous data point... 382 double previous = previousValue.doubleValue(); 383 double x0; 384 if (getUseSeriesOffset()) { 385 x0 = domainAxis.getCategorySeriesMiddle( 386 column - 1, dataset.getColumnCount(), 387 visibleRow, visibleRowCount, 388 getItemMargin(), dataArea, 389 plot.getDomainAxisEdge()); 390 } 391 else { 392 x0 = domainAxis.getCategoryMiddle(column - 1, 393 getColumnCount(), dataArea, 394 plot.getDomainAxisEdge()); 395 } 396 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 397 plot.getRangeAxisEdge()); 398 399 Line2D line = null; 400 if (orientation == PlotOrientation.HORIZONTAL) { 401 line = new Line2D.Double(y0, x0, y1, x1); 402 } 403 else if (orientation == PlotOrientation.VERTICAL) { 404 line = new Line2D.Double(x0, y0, x1, y1); 405 } 406 g2.setPaint(getItemPaint(row, column)); 407 g2.setStroke(getItemStroke(row, column)); 408 g2.draw(line); 409 } 410 } 411 } 412 413 if (pass == 1) { 414 // add an item entity, if this information is being collected 415 EntityCollection entities = state.getEntityCollection(); 416 if (entities != null) { 417 addEntity(entities, hotspot, dataset, row, column, x1, y1); 418 } 419 } 420 421 } 422 423 /** 424 * Tests this renderer for equality with an arbitrary object. 425 * 426 * @param obj the object (<code>null</code> permitted). 427 * 428 * @return A boolean. 429 */ 430 @Override equals(Object obj)431 public boolean equals(Object obj) { 432 if (obj == this) { 433 return true; 434 } 435 if (!(obj instanceof StatisticalLineAndShapeRenderer)) { 436 return false; 437 } 438 StatisticalLineAndShapeRenderer that 439 = (StatisticalLineAndShapeRenderer) obj; 440 if (!PaintUtilities.equal(this.errorIndicatorPaint, 441 that.errorIndicatorPaint)) { 442 return false; 443 } 444 if (!ObjectUtilities.equal(this.errorIndicatorStroke, 445 that.errorIndicatorStroke)) { 446 return false; 447 } 448 return super.equals(obj); 449 } 450 451 /** 452 * Returns a hash code for this instance. 453 * 454 * @return A hash code. 455 */ 456 @Override hashCode()457 public int hashCode() { 458 int hash = super.hashCode(); 459 hash = HashUtilities.hashCode(hash, this.errorIndicatorPaint); 460 return hash; 461 } 462 463 /** 464 * Provides serialization support. 465 * 466 * @param stream the output stream. 467 * 468 * @throws IOException if there is an I/O error. 469 */ writeObject(ObjectOutputStream stream)470 private void writeObject(ObjectOutputStream stream) throws IOException { 471 stream.defaultWriteObject(); 472 SerialUtilities.writePaint(this.errorIndicatorPaint, stream); 473 SerialUtilities.writeStroke(this.errorIndicatorStroke, stream); 474 } 475 476 /** 477 * Provides serialization support. 478 * 479 * @param stream the input stream. 480 * 481 * @throws IOException if there is an I/O error. 482 * @throws ClassNotFoundException if there is a classpath problem. 483 */ readObject(ObjectInputStream stream)484 private void readObject(ObjectInputStream stream) 485 throws IOException, ClassNotFoundException { 486 stream.defaultReadObject(); 487 this.errorIndicatorPaint = SerialUtilities.readPaint(stream); 488 this.errorIndicatorStroke = SerialUtilities.readStroke(stream); 489 } 490 491 } 492