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 * StatisticalBarRenderer.java 029 * --------------------------- 030 * (C) Copyright 2002-2014, by Pascal Collet and Contributors. 031 * 032 * Original Author: Pascal Collet; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * Christian W. Zuckschwerdt; 035 * Peter Kolb (patches 2497611, 2791407); 036 * Martin Hoeller; 037 * 038 * Changes 039 * ------- 040 * 21-Aug-2002 : Version 1, contributed by Pascal Collet (DG); 041 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG); 042 * 24-Oct-2002 : Changes to dataset interface (DG); 043 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG); 044 * 05-Feb-2003 : Updates for new DefaultStatisticalCategoryDataset (DG); 045 * 25-Mar-2003 : Implemented Serializable (DG); 046 * 30-Jul-2003 : Modified entity constructor (CZ); 047 * 06-Oct-2003 : Corrected typo in exception message (DG); 048 * 05-Nov-2004 : Modified drawItem() signature (DG); 049 * 15-Jun-2005 : Added errorIndicatorPaint attribute (DG); 050 * ------------- JFREECHART 1.0.x --------------------------------------------- 051 * 19-May-2006 : Added support for tooltips and URLs (DG); 052 * 12-Jul-2006 : Added support for item labels (DG); 053 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG); 054 * 28-Aug-2007 : Fixed NullPointerException - see bug 1779941 (DG); 055 * 14-Nov-2007 : Added errorIndicatorStroke, and fixed bugs with drawBarOutline 056 * and gradientPaintTransformer attributes being ignored (DG); 057 * 14-Jan-2009 : Added support for seriesVisible flags (PK); 058 * 16-May-2009 : Added findRangeBounds() override to take into account the 059 * dataset interval (PK); 060 * 28-Oct-2011 : Fixed problem with maximalBarWidth, bug #2810220 (MH); 061 * 30-Oct-2011 : Additional change for bug #2810220 (DG); 062 * 063 */ 064 065package org.jfree.chart.renderer.category; 066 067import java.awt.BasicStroke; 068import java.awt.Color; 069import java.awt.GradientPaint; 070import java.awt.Graphics2D; 071import java.awt.Paint; 072import java.awt.Stroke; 073import java.awt.geom.Line2D; 074import java.awt.geom.Rectangle2D; 075import java.io.IOException; 076import java.io.ObjectInputStream; 077import java.io.ObjectOutputStream; 078import java.io.Serializable; 079 080import org.jfree.chart.axis.CategoryAxis; 081import org.jfree.chart.axis.ValueAxis; 082import org.jfree.chart.entity.EntityCollection; 083import org.jfree.chart.event.RendererChangeEvent; 084import org.jfree.chart.labels.CategoryItemLabelGenerator; 085import org.jfree.chart.plot.CategoryPlot; 086import org.jfree.chart.plot.PlotOrientation; 087import org.jfree.data.Range; 088import org.jfree.data.category.CategoryDataset; 089import org.jfree.data.statistics.StatisticalCategoryDataset; 090import org.jfree.io.SerialUtilities; 091import org.jfree.ui.GradientPaintTransformer; 092import org.jfree.ui.RectangleEdge; 093import org.jfree.util.ObjectUtilities; 094import org.jfree.util.PaintUtilities; 095import org.jfree.util.PublicCloneable; 096 097/** 098 * A renderer that handles the drawing a bar plot where 099 * each bar has a mean value and a standard deviation line. The example shown 100 * here is generated by the <code>StatisticalBarChartDemo1.java</code> program 101 * included in the JFreeChart Demo Collection: 102 * <br><br> 103 * <img src="../../../../../images/StatisticalBarRendererSample.png" 104 * alt="StatisticalBarRendererSample.png"> 105 */ 106public class StatisticalBarRenderer extends BarRenderer 107 implements CategoryItemRenderer, Cloneable, PublicCloneable, 108 Serializable { 109 110 /** For serialization. */ 111 private static final long serialVersionUID = -4986038395414039117L; 112 113 /** The paint used to show the error indicator. */ 114 private transient Paint errorIndicatorPaint; 115 116 /** 117 * The stroke used to draw the error indicators. 118 * 119 * @since 1.0.8 120 */ 121 private transient Stroke errorIndicatorStroke; 122 123 /** 124 * Default constructor. 125 */ 126 public StatisticalBarRenderer() { 127 super(); 128 this.errorIndicatorPaint = Color.gray; 129 this.errorIndicatorStroke = new BasicStroke(1.0f); 130 } 131 132 /** 133 * Returns the paint used for the error indicators. 134 * 135 * @return The paint used for the error indicators (possibly 136 * <code>null</code>). 137 * 138 * @see #setErrorIndicatorPaint(Paint) 139 */ 140 public Paint getErrorIndicatorPaint() { 141 return this.errorIndicatorPaint; 142 } 143 144 /** 145 * Sets the paint used for the error indicators (if <code>null</code>, 146 * the item outline paint is used instead) and sends a 147 * {@link RendererChangeEvent} to all registered listeners. 148 * 149 * @param paint the paint (<code>null</code> permitted). 150 * 151 * @see #getErrorIndicatorPaint() 152 */ 153 public void setErrorIndicatorPaint(Paint paint) { 154 this.errorIndicatorPaint = paint; 155 fireChangeEvent(); 156 } 157 158 /** 159 * Returns the stroke used to draw the error indicators. If this is 160 * <code>null</code>, the renderer will use the item outline stroke). 161 * 162 * @return The stroke (possibly <code>null</code>). 163 * 164 * @see #setErrorIndicatorStroke(Stroke) 165 * 166 * @since 1.0.8 167 */ 168 public Stroke getErrorIndicatorStroke() { 169 return this.errorIndicatorStroke; 170 } 171 172 /** 173 * Sets the stroke used to draw the error indicators, and sends a 174 * {@link RendererChangeEvent} to all registered listeners. If you set 175 * this to <code>null</code>, the renderer will use the item outline 176 * stroke. 177 * 178 * @param stroke the stroke (<code>null</code> permitted). 179 * 180 * @see #getErrorIndicatorStroke() 181 * 182 * @since 1.0.8 183 */ 184 public void setErrorIndicatorStroke(Stroke stroke) { 185 this.errorIndicatorStroke = stroke; 186 fireChangeEvent(); 187 } 188 189 /** 190 * Returns the range of values the renderer requires to display all the 191 * items from the specified dataset. This takes into account the range 192 * between the min/max values, possibly ignoring invisible series. 193 * 194 * @param dataset the dataset (<code>null</code> permitted). 195 * 196 * @return The range (or <code>null</code> if the dataset is 197 * <code>null</code> or empty). 198 */ 199 @Override 200 public Range findRangeBounds(CategoryDataset dataset) { 201 return findRangeBounds(dataset, true); 202 } 203 204 /** 205 * Draws the bar with its standard deviation line range for a single 206 * (series, category) data item. 207 * 208 * @param g2 the graphics device. 209 * @param state the renderer state. 210 * @param dataArea the data area. 211 * @param plot the plot. 212 * @param domainAxis the domain axis. 213 * @param rangeAxis the range axis. 214 * @param data the data. 215 * @param row the row index (zero-based). 216 * @param column the column index (zero-based). 217 * @param pass the pass index. 218 */ 219 @Override 220 public void drawItem(Graphics2D g2, CategoryItemRendererState state, 221 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 222 ValueAxis rangeAxis, CategoryDataset data, int row, int column, 223 int pass) { 224 225 int visibleRow = state.getVisibleSeriesIndex(row); 226 if (visibleRow < 0) { 227 return; 228 } 229 // defensive check 230 if (!(data instanceof StatisticalCategoryDataset)) { 231 throw new IllegalArgumentException( 232 "Requires StatisticalCategoryDataset."); 233 } 234 StatisticalCategoryDataset statData = (StatisticalCategoryDataset) data; 235 236 PlotOrientation orientation = plot.getOrientation(); 237 if (orientation == PlotOrientation.HORIZONTAL) { 238 drawHorizontalItem(g2, state, dataArea, plot, domainAxis, 239 rangeAxis, statData, visibleRow, row, column); 240 } 241 else if (orientation == PlotOrientation.VERTICAL) { 242 drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 243 statData, visibleRow, row, column); 244 } 245 } 246 247 /** 248 * Draws an item for a plot with a horizontal orientation. 249 * 250 * @param g2 the graphics device. 251 * @param state the renderer state. 252 * @param dataArea the data area. 253 * @param plot the plot. 254 * @param domainAxis the domain axis. 255 * @param rangeAxis the range axis. 256 * @param dataset the data. 257 * @param visibleRow the visible row index. 258 * @param row the row index (zero-based). 259 * @param column the column index (zero-based). 260 */ 261 protected void drawHorizontalItem(Graphics2D g2, 262 CategoryItemRendererState state, 263 Rectangle2D dataArea, 264 CategoryPlot plot, 265 CategoryAxis domainAxis, 266 ValueAxis rangeAxis, 267 StatisticalCategoryDataset dataset, 268 int visibleRow, 269 int row, 270 int column) { 271 272 // BAR Y 273 double rectY = calculateBarW0(plot, PlotOrientation.HORIZONTAL, 274 dataArea, domainAxis, state, visibleRow, column); 275 276 // BAR X 277 Number meanValue = dataset.getMeanValue(row, column); 278 if (meanValue == null) { 279 return; 280 } 281 double value = meanValue.doubleValue(); 282 double base = 0.0; 283 double lclip = getLowerClip(); 284 double uclip = getUpperClip(); 285 286 if (uclip <= 0.0) { // cases 1, 2, 3 and 4 287 if (value >= uclip) { 288 return; // bar is not visible 289 } 290 base = uclip; 291 if (value <= lclip) { 292 value = lclip; 293 } 294 } 295 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8 296 if (value >= uclip) { 297 value = uclip; 298 } 299 else { 300 if (value <= lclip) { 301 value = lclip; 302 } 303 } 304 } 305 else { // cases 9, 10, 11 and 12 306 if (value <= lclip) { 307 return; // bar is not visible 308 } 309 base = getLowerClip(); 310 if (value >= uclip) { 311 value = uclip; 312 } 313 } 314 315 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 316 double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation); 317 double transY2 = rangeAxis.valueToJava2D(value, dataArea, 318 yAxisLocation); 319 double rectX = Math.min(transY2, transY1); 320 321 double rectHeight = state.getBarWidth(); 322 double rectWidth = Math.abs(transY2 - transY1); 323 324 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 325 rectHeight); 326 Paint itemPaint = getItemPaint(row, column); 327 GradientPaintTransformer t = getGradientPaintTransformer(); 328 if (t != null && itemPaint instanceof GradientPaint) { 329 itemPaint = t.transform((GradientPaint) itemPaint, bar); 330 } 331 g2.setPaint(itemPaint); 332 g2.fill(bar); 333 334 // draw the outline... 335 if (isDrawBarOutline() 336 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 337 Stroke stroke = getItemOutlineStroke(row, column); 338 Paint paint = getItemOutlinePaint(row, column); 339 if (stroke != null && paint != null) { 340 g2.setStroke(stroke); 341 g2.setPaint(paint); 342 g2.draw(bar); 343 } 344 } 345 346 // standard deviation lines 347 Number n = dataset.getStdDevValue(row, column); 348 if (n != null) { 349 double valueDelta = n.doubleValue(); 350 double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 351 + valueDelta, dataArea, yAxisLocation); 352 double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 353 - valueDelta, dataArea, yAxisLocation); 354 355 if (this.errorIndicatorPaint != null) { 356 g2.setPaint(this.errorIndicatorPaint); 357 } 358 else { 359 g2.setPaint(getItemOutlinePaint(row, column)); 360 } 361 if (this.errorIndicatorStroke != null) { 362 g2.setStroke(this.errorIndicatorStroke); 363 } 364 else { 365 g2.setStroke(getItemOutlineStroke(row, column)); 366 } 367 Line2D line; 368 line = new Line2D.Double(lowVal, rectY + rectHeight / 2.0d, 369 highVal, rectY + rectHeight / 2.0d); 370 g2.draw(line); 371 line = new Line2D.Double(highVal, rectY + rectHeight * 0.25, 372 highVal, rectY + rectHeight * 0.75); 373 g2.draw(line); 374 line = new Line2D.Double(lowVal, rectY + rectHeight * 0.25, 375 lowVal, rectY + rectHeight * 0.75); 376 g2.draw(line); 377 } 378 379 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 380 column); 381 if (generator != null && isItemLabelVisible(row, column)) { 382 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 383 (value < 0.0)); 384 } 385 386 // add an item entity, if this information is being collected 387 EntityCollection entities = state.getEntityCollection(); 388 if (entities != null) { 389 addItemEntity(entities, dataset, row, column, bar); 390 } 391 392 } 393 394 /** 395 * Draws an item for a plot with a vertical orientation. 396 * 397 * @param g2 the graphics device. 398 * @param state the renderer state. 399 * @param dataArea the data area. 400 * @param plot the plot. 401 * @param domainAxis the domain axis. 402 * @param rangeAxis the range axis. 403 * @param dataset the data. 404 * @param visibleRow the visible row index. 405 * @param row the row index (zero-based). 406 * @param column the column index (zero-based). 407 */ 408 protected void drawVerticalItem(Graphics2D g2, 409 CategoryItemRendererState state, 410 Rectangle2D dataArea, 411 CategoryPlot plot, 412 CategoryAxis domainAxis, 413 ValueAxis rangeAxis, 414 StatisticalCategoryDataset dataset, 415 int visibleRow, 416 int row, 417 int column) { 418 419 // BAR X 420 double rectX = calculateBarW0(plot, PlotOrientation.VERTICAL, dataArea, 421 domainAxis, state, visibleRow, column); 422 423 // BAR Y 424 Number meanValue = dataset.getMeanValue(row, column); 425 if (meanValue == null) { 426 return; 427 } 428 429 double value = meanValue.doubleValue(); 430 double base = 0.0; 431 double lclip = getLowerClip(); 432 double uclip = getUpperClip(); 433 434 if (uclip <= 0.0) { // cases 1, 2, 3 and 4 435 if (value >= uclip) { 436 return; // bar is not visible 437 } 438 base = uclip; 439 if (value <= lclip) { 440 value = lclip; 441 } 442 } 443 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8 444 if (value >= uclip) { 445 value = uclip; 446 } 447 else { 448 if (value <= lclip) { 449 value = lclip; 450 } 451 } 452 } 453 else { // cases 9, 10, 11 and 12 454 if (value <= lclip) { 455 return; // bar is not visible 456 } 457 base = getLowerClip(); 458 if (value >= uclip) { 459 value = uclip; 460 } 461 } 462 463 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 464 double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation); 465 double transY2 = rangeAxis.valueToJava2D(value, dataArea, 466 yAxisLocation); 467 double rectY = Math.min(transY2, transY1); 468 469 double rectWidth = state.getBarWidth(); 470 double rectHeight = Math.abs(transY2 - transY1); 471 472 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 473 rectHeight); 474 Paint itemPaint = getItemPaint(row, column); 475 GradientPaintTransformer t = getGradientPaintTransformer(); 476 if (t != null && itemPaint instanceof GradientPaint) { 477 itemPaint = t.transform((GradientPaint) itemPaint, bar); 478 } 479 g2.setPaint(itemPaint); 480 g2.fill(bar); 481 // draw the outline... 482 if (isDrawBarOutline() 483 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 484 Stroke stroke = getItemOutlineStroke(row, column); 485 Paint paint = getItemOutlinePaint(row, column); 486 if (stroke != null && paint != null) { 487 g2.setStroke(stroke); 488 g2.setPaint(paint); 489 g2.draw(bar); 490 } 491 } 492 493 // standard deviation lines 494 Number n = dataset.getStdDevValue(row, column); 495 if (n != null) { 496 double valueDelta = n.doubleValue(); 497 double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 498 + valueDelta, dataArea, yAxisLocation); 499 double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 500 - valueDelta, dataArea, yAxisLocation); 501 502 if (this.errorIndicatorPaint != null) { 503 g2.setPaint(this.errorIndicatorPaint); 504 } 505 else { 506 g2.setPaint(getItemOutlinePaint(row, column)); 507 } 508 if (this.errorIndicatorStroke != null) { 509 g2.setStroke(this.errorIndicatorStroke); 510 } 511 else { 512 g2.setStroke(getItemOutlineStroke(row, column)); 513 } 514 515 Line2D line; 516 line = new Line2D.Double(rectX + rectWidth / 2.0d, lowVal, 517 rectX + rectWidth / 2.0d, highVal); 518 g2.draw(line); 519 line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, highVal, 520 rectX + rectWidth / 2.0d + 5.0d, highVal); 521 g2.draw(line); 522 line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, lowVal, 523 rectX + rectWidth / 2.0d + 5.0d, lowVal); 524 g2.draw(line); 525 } 526 527 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 528 column); 529 if (generator != null && isItemLabelVisible(row, column)) { 530 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 531 (value < 0.0)); 532 } 533 534 // add an item entity, if this information is being collected 535 EntityCollection entities = state.getEntityCollection(); 536 if (entities != null) { 537 addItemEntity(entities, dataset, row, column, bar); 538 } 539 } 540 541 /** 542 * Tests this renderer for equality with an arbitrary object. 543 * 544 * @param obj the object (<code>null</code> permitted). 545 * 546 * @return A boolean. 547 */ 548 @Override 549 public boolean equals(Object obj) { 550 if (obj == this) { 551 return true; 552 } 553 if (!(obj instanceof StatisticalBarRenderer)) { 554 return false; 555 } 556 StatisticalBarRenderer that = (StatisticalBarRenderer) obj; 557 if (!PaintUtilities.equal(this.errorIndicatorPaint, 558 that.errorIndicatorPaint)) { 559 return false; 560 } 561 if (!ObjectUtilities.equal(this.errorIndicatorStroke, 562 that.errorIndicatorStroke)) { 563 return false; 564 } 565 return super.equals(obj); 566 } 567 568 /** 569 * Provides serialization support. 570 * 571 * @param stream the output stream. 572 * 573 * @throws IOException if there is an I/O error. 574 */ 575 private void writeObject(ObjectOutputStream stream) throws IOException { 576 stream.defaultWriteObject(); 577 SerialUtilities.writePaint(this.errorIndicatorPaint, stream); 578 SerialUtilities.writeStroke(this.errorIndicatorStroke, stream); 579 } 580 581 /** 582 * Provides serialization support. 583 * 584 * @param stream the input stream. 585 * 586 * @throws IOException if there is an I/O error. 587 * @throws ClassNotFoundException if there is a classpath problem. 588 */ 589 private void readObject(ObjectInputStream stream) 590 throws IOException, ClassNotFoundException { 591 stream.defaultReadObject(); 592 this.errorIndicatorPaint = SerialUtilities.readPaint(stream); 593 this.errorIndicatorStroke = SerialUtilities.readStroke(stream); 594 } 595 596}