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