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 * 054 */ 055 056package org.jfree.chart.renderer.category; 057 058import java.awt.Color; 059import java.awt.Graphics2D; 060import java.awt.Paint; 061import java.awt.geom.Line2D; 062import java.awt.geom.Rectangle2D; 063import java.io.IOException; 064import java.io.ObjectInputStream; 065import java.io.ObjectOutputStream; 066import java.io.Serializable; 067 068import org.jfree.chart.axis.CategoryAxis; 069import org.jfree.chart.axis.ValueAxis; 070import org.jfree.chart.entity.EntityCollection; 071import org.jfree.chart.event.RendererChangeEvent; 072import org.jfree.chart.labels.CategoryItemLabelGenerator; 073import org.jfree.chart.plot.CategoryPlot; 074import org.jfree.chart.plot.PlotOrientation; 075import org.jfree.data.category.CategoryDataset; 076import org.jfree.data.statistics.StatisticalCategoryDataset; 077import org.jfree.io.SerialUtilities; 078import org.jfree.ui.RectangleEdge; 079import org.jfree.util.PaintUtilities; 080import org.jfree.util.PublicCloneable; 081 082/** 083 * A renderer that handles the drawing a bar plot where 084 * each bar has a mean value and a standard deviation line. 085 */ 086public class StatisticalBarRenderer extends BarRenderer 087 implements CategoryItemRenderer, 088 Cloneable, PublicCloneable, 089 Serializable { 090 091 /** For serialization. */ 092 private static final long serialVersionUID = -4986038395414039117L; 093 094 /** The paint used to show the error indicator. */ 095 private transient Paint errorIndicatorPaint; 096 097 /** 098 * Default constructor. 099 */ 100 public StatisticalBarRenderer() { 101 super(); 102 this.errorIndicatorPaint = Color.gray; 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</code>). 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</code>, 119 * the item outline paint is used instead) 120 * 121 * @param paint the paint (<code>null</code> permitted). 122 * 123 * @see #getErrorIndicatorPaint() 124 */ 125 public void setErrorIndicatorPaint(Paint paint) { 126 this.errorIndicatorPaint = paint; 127 notifyListeners(new RendererChangeEvent(this)); 128 } 129 130 /** 131 * Draws the bar with its standard deviation line range for a single 132 * (series, category) data item. 133 * 134 * @param g2 the graphics device. 135 * @param state the renderer state. 136 * @param dataArea the data area. 137 * @param plot the plot. 138 * @param domainAxis the domain axis. 139 * @param rangeAxis the range axis. 140 * @param data the data. 141 * @param row the row index (zero-based). 142 * @param column the column index (zero-based). 143 * @param pass the pass index. 144 */ 145 public void drawItem(Graphics2D g2, 146 CategoryItemRendererState state, 147 Rectangle2D dataArea, 148 CategoryPlot plot, 149 CategoryAxis domainAxis, 150 ValueAxis rangeAxis, 151 CategoryDataset data, 152 int row, 153 int column, 154 int pass) { 155 156 // defensive check 157 if (!(data instanceof StatisticalCategoryDataset)) { 158 throw new IllegalArgumentException( 159 "Requires StatisticalCategoryDataset."); 160 } 161 StatisticalCategoryDataset statData = (StatisticalCategoryDataset) data; 162 163 PlotOrientation orientation = plot.getOrientation(); 164 if (orientation == PlotOrientation.HORIZONTAL) { 165 drawHorizontalItem(g2, state, dataArea, plot, domainAxis, 166 rangeAxis, statData, row, column); 167 } 168 else if (orientation == PlotOrientation.VERTICAL) { 169 drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 170 statData, row, column); 171 } 172 } 173 174 /** 175 * Draws an item for a plot with a horizontal orientation. 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 dataset the data. 184 * @param row the row index (zero-based). 185 * @param column the column index (zero-based). 186 */ 187 protected void drawHorizontalItem(Graphics2D g2, 188 CategoryItemRendererState state, 189 Rectangle2D dataArea, 190 CategoryPlot plot, 191 CategoryAxis domainAxis, 192 ValueAxis rangeAxis, 193 StatisticalCategoryDataset dataset, 194 int row, 195 int column) { 196 197 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 198 199 // BAR Y 200 double rectY = domainAxis.getCategoryStart(column, getColumnCount(), 201 dataArea, xAxisLocation); 202 203 int seriesCount = getRowCount(); 204 int categoryCount = getColumnCount(); 205 if (seriesCount > 1) { 206 double seriesGap = dataArea.getHeight() * getItemMargin() 207 / (categoryCount * (seriesCount - 1)); 208 rectY = rectY + row * (state.getBarWidth() + seriesGap); 209 } 210 else { 211 rectY = rectY + row * state.getBarWidth(); 212 } 213 214 // BAR X 215 Number meanValue = dataset.getMeanValue(row, column); 216 if (meanValue == null) { 217 return; 218 } 219 double value = meanValue.doubleValue(); 220 double base = 0.0; 221 double lclip = getLowerClip(); 222 double uclip = getUpperClip(); 223 224 if (uclip <= 0.0) { // cases 1, 2, 3 and 4 225 if (value >= uclip) { 226 return; // bar is not visible 227 } 228 base = uclip; 229 if (value <= lclip) { 230 value = lclip; 231 } 232 } 233 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8 234 if (value >= uclip) { 235 value = uclip; 236 } 237 else { 238 if (value <= lclip) { 239 value = lclip; 240 } 241 } 242 } 243 else { // cases 9, 10, 11 and 12 244 if (value <= lclip) { 245 return; // bar is not visible 246 } 247 base = getLowerClip(); 248 if (value >= uclip) { 249 value = uclip; 250 } 251 } 252 253 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 254 double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation); 255 double transY2 = rangeAxis.valueToJava2D(value, dataArea, 256 yAxisLocation); 257 double rectX = Math.min(transY2, transY1); 258 259 double rectHeight = state.getBarWidth(); 260 double rectWidth = Math.abs(transY2 - transY1); 261 262 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 263 rectHeight); 264 Paint seriesPaint = getItemPaint(row, column); 265 g2.setPaint(seriesPaint); 266 g2.fill(bar); 267 if (state.getBarWidth() > 3) { 268 g2.setStroke(getItemStroke(row, column)); 269 g2.setPaint(getItemOutlinePaint(row, column)); 270 g2.draw(bar); 271 } 272 273 // standard deviation lines 274 Number n = dataset.getStdDevValue(row, column); 275 if (n != null) { 276 double valueDelta = n.doubleValue(); 277 double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 278 + valueDelta, dataArea, yAxisLocation); 279 double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 280 - valueDelta, dataArea, yAxisLocation); 281 282 if (this.errorIndicatorPaint != null) { 283 g2.setPaint(this.errorIndicatorPaint); 284 } 285 else { 286 g2.setPaint(getItemOutlinePaint(row, column)); 287 } 288 Line2D line = null; 289 line = new Line2D.Double(lowVal, rectY + rectHeight / 2.0d, 290 highVal, rectY + rectHeight / 2.0d); 291 g2.draw(line); 292 line = new Line2D.Double(highVal, rectY + rectHeight * 0.25, 293 highVal, rectY + rectHeight * 0.75); 294 g2.draw(line); 295 line = new Line2D.Double(lowVal, rectY + rectHeight * 0.25, 296 lowVal, rectY + rectHeight * 0.75); 297 g2.draw(line); 298 } 299 300 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 301 column); 302 if (generator != null && isItemLabelVisible(row, column)) { 303 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 304 (value < 0.0)); 305 } 306 307 // add an item entity, if this information is being collected 308 EntityCollection entities = state.getEntityCollection(); 309 if (entities != null) { 310 addItemEntity(entities, dataset, row, column, bar); 311 } 312 313 } 314 315 /** 316 * Draws an item for a plot with a vertical orientation. 317 * 318 * @param g2 the graphics device. 319 * @param state the renderer state. 320 * @param dataArea the data area. 321 * @param plot the plot. 322 * @param domainAxis the domain axis. 323 * @param rangeAxis the range axis. 324 * @param dataset the data. 325 * @param row the row index (zero-based). 326 * @param column the column index (zero-based). 327 */ 328 protected void drawVerticalItem(Graphics2D g2, 329 CategoryItemRendererState state, 330 Rectangle2D dataArea, 331 CategoryPlot plot, 332 CategoryAxis domainAxis, 333 ValueAxis rangeAxis, 334 StatisticalCategoryDataset dataset, 335 int row, 336 int column) { 337 338 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 339 340 // BAR X 341 double rectX = domainAxis.getCategoryStart( 342 column, getColumnCount(), dataArea, xAxisLocation 343 ); 344 345 int seriesCount = getRowCount(); 346 int categoryCount = getColumnCount(); 347 if (seriesCount > 1) { 348 double seriesGap = dataArea.getWidth() * getItemMargin() 349 / (categoryCount * (seriesCount - 1)); 350 rectX = rectX + row * (state.getBarWidth() + seriesGap); 351 } 352 else { 353 rectX = rectX + row * state.getBarWidth(); 354 } 355 356 // BAR Y 357 Number meanValue = dataset.getMeanValue(row, column); 358 if (meanValue == null) { 359 return; 360 } 361 362 double value = meanValue.doubleValue(); 363 double base = 0.0; 364 double lclip = getLowerClip(); 365 double uclip = getUpperClip(); 366 367 if (uclip <= 0.0) { // cases 1, 2, 3 and 4 368 if (value >= uclip) { 369 return; // bar is not visible 370 } 371 base = uclip; 372 if (value <= lclip) { 373 value = lclip; 374 } 375 } 376 else if (lclip <= 0.0) { // cases 5, 6, 7 and 8 377 if (value >= uclip) { 378 value = uclip; 379 } 380 else { 381 if (value <= lclip) { 382 value = lclip; 383 } 384 } 385 } 386 else { // cases 9, 10, 11 and 12 387 if (value <= lclip) { 388 return; // bar is not visible 389 } 390 base = getLowerClip(); 391 if (value >= uclip) { 392 value = uclip; 393 } 394 } 395 396 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 397 double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation); 398 double transY2 = rangeAxis.valueToJava2D(value, dataArea, 399 yAxisLocation); 400 double rectY = Math.min(transY2, transY1); 401 402 double rectWidth = state.getBarWidth(); 403 double rectHeight = Math.abs(transY2 - transY1); 404 405 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 406 rectHeight); 407 Paint seriesPaint = getItemPaint(row, column); 408 g2.setPaint(seriesPaint); 409 g2.fill(bar); 410 if (state.getBarWidth() > 3) { 411 g2.setStroke(getItemStroke(row, column)); 412 g2.setPaint(getItemOutlinePaint(row, column)); 413 g2.draw(bar); 414 } 415 416 // standard deviation lines 417 Number n = dataset.getStdDevValue(row, column); 418 if (n != null) { 419 double valueDelta = n.doubleValue(); 420 double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 421 + valueDelta, dataArea, yAxisLocation); 422 double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 423 - valueDelta, dataArea, yAxisLocation); 424 425 if (this.errorIndicatorPaint != null) { 426 g2.setPaint(this.errorIndicatorPaint); 427 } 428 else { 429 g2.setPaint(getItemOutlinePaint(row, column)); 430 } 431 Line2D line = null; 432 line = new Line2D.Double(rectX + rectWidth / 2.0d, lowVal, 433 rectX + rectWidth / 2.0d, highVal); 434 g2.draw(line); 435 line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, highVal, 436 rectX + rectWidth / 2.0d + 5.0d, highVal); 437 g2.draw(line); 438 line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, lowVal, 439 rectX + rectWidth / 2.0d + 5.0d, lowVal); 440 g2.draw(line); 441 } 442 443 CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 444 column); 445 if (generator != null && isItemLabelVisible(row, column)) { 446 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 447 (value < 0.0)); 448 } 449 450 // add an item entity, if this information is being collected 451 EntityCollection entities = state.getEntityCollection(); 452 if (entities != null) { 453 addItemEntity(entities, dataset, row, column, bar); 454 } 455 } 456 457 /** 458 * Tests this renderer for equality with an arbitrary object. 459 * 460 * @param obj the object (<code>null</code> permitted). 461 * 462 * @return A boolean. 463 */ 464 public boolean equals(Object obj) { 465 if (obj == this) { 466 return true; 467 } 468 if (!(obj instanceof StatisticalBarRenderer)) { 469 return false; 470 } 471 if (!super.equals(obj)) { 472 return false; 473 } 474 StatisticalBarRenderer that = (StatisticalBarRenderer) obj; 475 if (!PaintUtilities.equal(this.errorIndicatorPaint, 476 that.errorIndicatorPaint)) { 477 return false; 478 } 479 return true; 480 } 481 482 /** 483 * Provides serialization support. 484 * 485 * @param stream the output stream. 486 * 487 * @throws IOException if there is an I/O error. 488 */ 489 private void writeObject(ObjectOutputStream stream) throws IOException { 490 stream.defaultWriteObject(); 491 SerialUtilities.writePaint(this.errorIndicatorPaint, stream); 492 } 493 494 /** 495 * Provides serialization support. 496 * 497 * @param stream the input stream. 498 * 499 * @throws IOException if there is an I/O error. 500 * @throws ClassNotFoundException if there is a classpath problem. 501 */ 502 private void readObject(ObjectInputStream stream) 503 throws IOException, ClassNotFoundException { 504 stream.defaultReadObject(); 505 this.errorIndicatorPaint = SerialUtilities.readPaint(stream); 506 } 507 508}