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