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