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