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