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