001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2011, 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-2011, 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 065 package org.jfree.chart.renderer.category; 066 067 import java.awt.BasicStroke; 068 import java.awt.Color; 069 import java.awt.GradientPaint; 070 import java.awt.Graphics2D; 071 import java.awt.Paint; 072 import java.awt.Stroke; 073 import java.awt.geom.Line2D; 074 import java.awt.geom.Rectangle2D; 075 import java.io.IOException; 076 import java.io.ObjectInputStream; 077 import java.io.ObjectOutputStream; 078 import java.io.Serializable; 079 080 import org.jfree.chart.axis.CategoryAxis; 081 import org.jfree.chart.axis.ValueAxis; 082 import org.jfree.chart.entity.EntityCollection; 083 import org.jfree.chart.event.RendererChangeEvent; 084 import org.jfree.chart.labels.CategoryItemLabelGenerator; 085 import org.jfree.chart.plot.CategoryPlot; 086 import org.jfree.chart.plot.PlotOrientation; 087 import org.jfree.data.Range; 088 import org.jfree.data.category.CategoryDataset; 089 import org.jfree.data.statistics.StatisticalCategoryDataset; 090 import org.jfree.io.SerialUtilities; 091 import org.jfree.ui.GradientPaintTransformer; 092 import org.jfree.ui.RectangleEdge; 093 import org.jfree.util.ObjectUtilities; 094 import org.jfree.util.PaintUtilities; 095 import 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 */ 106 public 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 public Range findRangeBounds(CategoryDataset dataset) { 200 return findRangeBounds(dataset, true); 201 } 202 203 /** 204 * Draws the bar with its standard deviation line range for a single 205 * (series, category) data item. 206 * 207 * @param g2 the graphics device. 208 * @param state the renderer state. 209 * @param dataArea the data area. 210 * @param plot the plot. 211 * @param domainAxis the domain axis. 212 * @param rangeAxis the range axis. 213 * @param data the data. 214 * @param row the row index (zero-based). 215 * @param column the column index (zero-based). 216 * @param pass the pass index. 217 */ 218 public void drawItem(Graphics2D g2, 219 CategoryItemRendererState state, 220 Rectangle2D dataArea, 221 CategoryPlot plot, 222 CategoryAxis domainAxis, 223 ValueAxis rangeAxis, 224 CategoryDataset data, 225 int row, 226 int column, 227 int pass) { 228 229 int visibleRow = state.getVisibleSeriesIndex(row); 230 if (visibleRow < 0) { 231 return; 232 } 233 // defensive check 234 if (!(data instanceof StatisticalCategoryDataset)) { 235 throw new IllegalArgumentException( 236 "Requires StatisticalCategoryDataset."); 237 } 238 StatisticalCategoryDataset statData = (StatisticalCategoryDataset) data; 239 240 PlotOrientation orientation = plot.getOrientation(); 241 if (orientation == PlotOrientation.HORIZONTAL) { 242 drawHorizontalItem(g2, state, dataArea, plot, domainAxis, 243 rangeAxis, statData, visibleRow, row, column); 244 } 245 else if (orientation == PlotOrientation.VERTICAL) { 246 drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 247 statData, visibleRow, row, column); 248 } 249 } 250 251 /** 252 * Draws an item for a plot with a horizontal orientation. 253 * 254 * @param g2 the graphics device. 255 * @param state the renderer state. 256 * @param dataArea the data area. 257 * @param plot the plot. 258 * @param domainAxis the domain axis. 259 * @param rangeAxis the range axis. 260 * @param dataset the data. 261 * @param visibleRow the visible row index. 262 * @param row the row index (zero-based). 263 * @param column the column index (zero-based). 264 */ 265 protected void drawHorizontalItem(Graphics2D g2, 266 CategoryItemRendererState state, 267 Rectangle2D dataArea, 268 CategoryPlot plot, 269 CategoryAxis domainAxis, 270 ValueAxis rangeAxis, 271 StatisticalCategoryDataset dataset, 272 int visibleRow, 273 int row, 274 int column) { 275 276 // BAR Y 277 double rectY = calculateBarW0(plot, PlotOrientation.HORIZONTAL, 278 dataArea, domainAxis, state, visibleRow, column); 279 280 // BAR X 281 Number meanValue = dataset.getMeanValue(row, column); 282 if (meanValue == null) { 283 return; 284 } 285 double value = meanValue.doubleValue(); 286 double base = 0.0; 287 double lclip = getLowerClip(); 288 double uclip = getUpperClip(); 289 290 if (uclip <= 0.0) { // cases 1, 2, 3 and 4 291 if (value >= uclip) { 292 return; // bar is not visible 293 } 294 base = uclip; 295 if (value <= lclip) { 296 value = lclip; 297 } 298 } 299 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8 300 if (value >= uclip) { 301 value = uclip; 302 } 303 else { 304 if (value <= lclip) { 305 value = lclip; 306 } 307 } 308 } 309 else { // cases 9, 10, 11 and 12 310 if (value <= lclip) { 311 return; // bar is not visible 312 } 313 base = getLowerClip(); 314 if (value >= uclip) { 315 value = uclip; 316 } 317 } 318 319 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 320 double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation); 321 double transY2 = rangeAxis.valueToJava2D(value, dataArea, 322 yAxisLocation); 323 double rectX = Math.min(transY2, transY1); 324 325 double rectHeight = state.getBarWidth(); 326 double rectWidth = Math.abs(transY2 - transY1); 327 328 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 329 rectHeight); 330 Paint itemPaint = getItemPaint(row, column); 331 GradientPaintTransformer t = getGradientPaintTransformer(); 332 if (t != null && itemPaint instanceof GradientPaint) { 333 itemPaint = t.transform((GradientPaint) itemPaint, bar); 334 } 335 g2.setPaint(itemPaint); 336 g2.fill(bar); 337 338 // draw the outline... 339 if (isDrawBarOutline() 340 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 341 Stroke stroke = getItemOutlineStroke(row, column); 342 Paint paint = getItemOutlinePaint(row, column); 343 if (stroke != null && paint != null) { 344 g2.setStroke(stroke); 345 g2.setPaint(paint); 346 g2.draw(bar); 347 } 348 } 349 350 // standard deviation lines 351 Number n = dataset.getStdDevValue(row, column); 352 if (n != null) { 353 double valueDelta = n.doubleValue(); 354 double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 355 + valueDelta, dataArea, yAxisLocation); 356 double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 357 - valueDelta, dataArea, yAxisLocation); 358 359 if (this.errorIndicatorPaint != null) { 360 g2.setPaint(this.errorIndicatorPaint); 361 } 362 else { 363 g2.setPaint(getItemOutlinePaint(row, column)); 364 } 365 if (this.errorIndicatorStroke != null) { 366 g2.setStroke(this.errorIndicatorStroke); 367 } 368 else { 369 g2.setStroke(getItemOutlineStroke(row, column)); 370 } 371 Line2D line = null; 372 line = new Line2D.Double(lowVal, rectY + rectHeight / 2.0d, 373 highVal, rectY + rectHeight / 2.0d); 374 g2.draw(line); 375 line = new Line2D.Double(highVal, rectY + rectHeight * 0.25, 376 highVal, rectY + rectHeight * 0.75); 377 g2.draw(line); 378 line = new Line2D.Double(lowVal, rectY + rectHeight * 0.25, 379 lowVal, rectY + rectHeight * 0.75); 380 g2.draw(line); 381 } 382 383 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 384 column); 385 if (generator != null && isItemLabelVisible(row, column)) { 386 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 387 (value < 0.0)); 388 } 389 390 // add an item entity, if this information is being collected 391 EntityCollection entities = state.getEntityCollection(); 392 if (entities != null) { 393 addItemEntity(entities, dataset, row, column, bar); 394 } 395 396 } 397 398 /** 399 * Draws an item for a plot with a vertical orientation. 400 * 401 * @param g2 the graphics device. 402 * @param state the renderer state. 403 * @param dataArea the data area. 404 * @param plot the plot. 405 * @param domainAxis the domain axis. 406 * @param rangeAxis the range axis. 407 * @param dataset the data. 408 * @param visibleRow the visible row index. 409 * @param row the row index (zero-based). 410 * @param column the column index (zero-based). 411 */ 412 protected void drawVerticalItem(Graphics2D g2, 413 CategoryItemRendererState state, 414 Rectangle2D dataArea, 415 CategoryPlot plot, 416 CategoryAxis domainAxis, 417 ValueAxis rangeAxis, 418 StatisticalCategoryDataset dataset, 419 int visibleRow, 420 int row, 421 int column) { 422 423 // BAR X 424 double rectX = calculateBarW0(plot, PlotOrientation.VERTICAL, dataArea, 425 domainAxis, state, visibleRow, column); 426 427 // BAR Y 428 Number meanValue = dataset.getMeanValue(row, column); 429 if (meanValue == null) { 430 return; 431 } 432 433 double value = meanValue.doubleValue(); 434 double base = 0.0; 435 double lclip = getLowerClip(); 436 double uclip = getUpperClip(); 437 438 if (uclip <= 0.0) { // cases 1, 2, 3 and 4 439 if (value >= uclip) { 440 return; // bar is not visible 441 } 442 base = uclip; 443 if (value <= lclip) { 444 value = lclip; 445 } 446 } 447 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8 448 if (value >= uclip) { 449 value = uclip; 450 } 451 else { 452 if (value <= lclip) { 453 value = lclip; 454 } 455 } 456 } 457 else { // cases 9, 10, 11 and 12 458 if (value <= lclip) { 459 return; // bar is not visible 460 } 461 base = getLowerClip(); 462 if (value >= uclip) { 463 value = uclip; 464 } 465 } 466 467 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 468 double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation); 469 double transY2 = rangeAxis.valueToJava2D(value, dataArea, 470 yAxisLocation); 471 double rectY = Math.min(transY2, transY1); 472 473 double rectWidth = state.getBarWidth(); 474 double rectHeight = Math.abs(transY2 - transY1); 475 476 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 477 rectHeight); 478 Paint itemPaint = getItemPaint(row, column); 479 GradientPaintTransformer t = getGradientPaintTransformer(); 480 if (t != null && itemPaint instanceof GradientPaint) { 481 itemPaint = t.transform((GradientPaint) itemPaint, bar); 482 } 483 g2.setPaint(itemPaint); 484 g2.fill(bar); 485 // draw the outline... 486 if (isDrawBarOutline() 487 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 488 Stroke stroke = getItemOutlineStroke(row, column); 489 Paint paint = getItemOutlinePaint(row, column); 490 if (stroke != null && paint != null) { 491 g2.setStroke(stroke); 492 g2.setPaint(paint); 493 g2.draw(bar); 494 } 495 } 496 497 // standard deviation lines 498 Number n = dataset.getStdDevValue(row, column); 499 if (n != null) { 500 double valueDelta = n.doubleValue(); 501 double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 502 + valueDelta, dataArea, yAxisLocation); 503 double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 504 - valueDelta, dataArea, yAxisLocation); 505 506 if (this.errorIndicatorPaint != null) { 507 g2.setPaint(this.errorIndicatorPaint); 508 } 509 else { 510 g2.setPaint(getItemOutlinePaint(row, column)); 511 } 512 if (this.errorIndicatorStroke != null) { 513 g2.setStroke(this.errorIndicatorStroke); 514 } 515 else { 516 g2.setStroke(getItemOutlineStroke(row, column)); 517 } 518 519 Line2D line = null; 520 line = new Line2D.Double(rectX + rectWidth / 2.0d, lowVal, 521 rectX + rectWidth / 2.0d, highVal); 522 g2.draw(line); 523 line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, highVal, 524 rectX + rectWidth / 2.0d + 5.0d, highVal); 525 g2.draw(line); 526 line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, lowVal, 527 rectX + rectWidth / 2.0d + 5.0d, lowVal); 528 g2.draw(line); 529 } 530 531 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 532 column); 533 if (generator != null && isItemLabelVisible(row, column)) { 534 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 535 (value < 0.0)); 536 } 537 538 // add an item entity, if this information is being collected 539 EntityCollection entities = state.getEntityCollection(); 540 if (entities != null) { 541 addItemEntity(entities, dataset, row, column, bar); 542 } 543 } 544 545 /** 546 * Tests this renderer for equality with an arbitrary object. 547 * 548 * @param obj the object (<code>null</code> permitted). 549 * 550 * @return A boolean. 551 */ 552 public boolean equals(Object obj) { 553 if (obj == this) { 554 return true; 555 } 556 if (!(obj instanceof StatisticalBarRenderer)) { 557 return false; 558 } 559 StatisticalBarRenderer that = (StatisticalBarRenderer) obj; 560 if (!PaintUtilities.equal(this.errorIndicatorPaint, 561 that.errorIndicatorPaint)) { 562 return false; 563 } 564 if (!ObjectUtilities.equal(this.errorIndicatorStroke, 565 that.errorIndicatorStroke)) { 566 return false; 567 } 568 return super.equals(obj); 569 } 570 571 /** 572 * Provides serialization support. 573 * 574 * @param stream the output stream. 575 * 576 * @throws IOException if there is an I/O error. 577 */ 578 private void writeObject(ObjectOutputStream stream) throws IOException { 579 stream.defaultWriteObject(); 580 SerialUtilities.writePaint(this.errorIndicatorPaint, stream); 581 SerialUtilities.writeStroke(this.errorIndicatorStroke, stream); 582 } 583 584 /** 585 * Provides serialization support. 586 * 587 * @param stream the input stream. 588 * 589 * @throws IOException if there is an I/O error. 590 * @throws ClassNotFoundException if there is a classpath problem. 591 */ 592 private void readObject(ObjectInputStream stream) 593 throws IOException, ClassNotFoundException { 594 stream.defaultReadObject(); 595 this.errorIndicatorPaint = SerialUtilities.readPaint(stream); 596 this.errorIndicatorStroke = SerialUtilities.readStroke(stream); 597 } 598 599 }