001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------------------------ 028 * StatisticalLineAndShapeRenderer.java 029 * ------------------------------------ 030 * (C) Copyright 2005-2014, 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 059package org.jfree.chart.renderer.category; 060 061import java.awt.Graphics2D; 062import java.awt.Paint; 063import java.awt.Shape; 064import java.awt.Stroke; 065import java.awt.geom.Line2D; 066import java.awt.geom.Rectangle2D; 067import java.io.IOException; 068import java.io.ObjectInputStream; 069import java.io.ObjectOutputStream; 070import java.io.Serializable; 071 072import org.jfree.chart.HashUtilities; 073import org.jfree.chart.axis.CategoryAxis; 074import org.jfree.chart.axis.ValueAxis; 075import org.jfree.chart.entity.EntityCollection; 076import org.jfree.chart.event.RendererChangeEvent; 077import org.jfree.chart.plot.CategoryPlot; 078import org.jfree.chart.plot.PlotOrientation; 079import org.jfree.data.Range; 080import org.jfree.data.category.CategoryDataset; 081import org.jfree.data.statistics.StatisticalCategoryDataset; 082import org.jfree.io.SerialUtilities; 083import org.jfree.ui.RectangleEdge; 084import org.jfree.util.ObjectUtilities; 085import org.jfree.util.PaintUtilities; 086import org.jfree.util.PublicCloneable; 087import 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 */ 099public 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 @Override 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 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 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 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 */ 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 */ 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}