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