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 * WaterfallBarRenderer.java 029 * ------------------------- 030 * (C) Copyright 2003-2005, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: Darshan Shah; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * $Id: WaterfallBarRenderer.java,v 1.9.2.2 2005/10/25 20:54:16 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 20-Oct-2003 : Version 1, contributed by Darshan Shah (DG); 040 * 06-Nov-2003 : Changed order of parameters in constructor, and added support 041 * for GradientPaint (DG); 042 * 10-Feb-2004 : Updated drawItem() method to make cut-and-paste overriding 043 * easier. Also fixed a bug that meant the minimum bar length 044 * was being ignored (DG); 045 * 04-Oct-2004 : Reworked equals() method and renamed PaintUtils 046 * --> PaintUtilities (DG); 047 * 05-Nov-2004 : Modified drawItem() signature (DG); 048 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG); 049 * 23-Feb-2005 : Added argument checking (DG); 050 * 20-Apr-2005 : Renamed CategoryLabelGenerator 051 * --> CategoryItemLabelGenerator (DG); 052 * 09-Jun-2005 : Use addItemEntity() from superclass (DG); 053 */ 054 055 package org.jfree.chart.renderer.category; 056 057 import java.awt.Color; 058 import java.awt.GradientPaint; 059 import java.awt.Graphics2D; 060 import java.awt.Paint; 061 import java.awt.Stroke; 062 import java.awt.geom.Rectangle2D; 063 import java.io.IOException; 064 import java.io.ObjectInputStream; 065 import java.io.ObjectOutputStream; 066 import java.io.Serializable; 067 068 import org.jfree.chart.axis.CategoryAxis; 069 import org.jfree.chart.axis.ValueAxis; 070 import org.jfree.chart.entity.EntityCollection; 071 import org.jfree.chart.event.RendererChangeEvent; 072 import org.jfree.chart.labels.CategoryItemLabelGenerator; 073 import org.jfree.chart.plot.CategoryPlot; 074 import org.jfree.chart.plot.PlotOrientation; 075 import org.jfree.chart.renderer.AbstractRenderer; 076 import org.jfree.data.Range; 077 import org.jfree.data.category.CategoryDataset; 078 import org.jfree.data.general.DatasetUtilities; 079 import org.jfree.io.SerialUtilities; 080 import org.jfree.ui.GradientPaintTransformType; 081 import org.jfree.ui.RectangleEdge; 082 import org.jfree.ui.StandardGradientPaintTransformer; 083 import org.jfree.util.PaintUtilities; 084 import org.jfree.util.PublicCloneable; 085 086 /** 087 * A renderer that handles the drawing of waterfall bar charts, for use with 088 * the {@link CategoryPlot} class. Note that the bar colors are defined 089 * using special methods in this class - the inherited methods (for example, 090 * {@link AbstractRenderer#setSeriesPaint(int, Paint)}) are ignored. 091 */ 092 public class WaterfallBarRenderer extends BarRenderer 093 implements Cloneable, PublicCloneable, 094 Serializable { 095 096 /** For serialization. */ 097 private static final long serialVersionUID = -2482910643727230911L; 098 099 /** The paint used to draw the first bar. */ 100 private transient Paint firstBarPaint; 101 102 /** The paint used to draw the last bar. */ 103 private transient Paint lastBarPaint; 104 105 /** The paint used to draw bars having positive values. */ 106 private transient Paint positiveBarPaint; 107 108 /** The paint used to draw bars having negative values. */ 109 private transient Paint negativeBarPaint; 110 111 /** 112 * Constructs a new renderer with default values for the bar colors. 113 */ 114 public WaterfallBarRenderer() { 115 this( 116 new GradientPaint( 117 0.0f, 0.0f, new Color(0x22, 0x22, 0xFF), 118 0.0f, 0.0f, new Color(0x66, 0x66, 0xFF) 119 ), 120 new GradientPaint( 121 0.0f, 0.0f, new Color(0x22, 0xFF, 0x22), 122 0.0f, 0.0f, new Color(0x66, 0xFF, 0x66) 123 ), 124 new GradientPaint( 125 0.0f, 0.0f, new Color(0xFF, 0x22, 0x22), 126 0.0f, 0.0f, new Color(0xFF, 0x66, 0x66) 127 ), 128 new GradientPaint( 129 0.0f, 0.0f, new Color(0xFF, 0xFF, 0x22), 130 0.0f, 0.0f, new Color(0xFF, 0xFF, 0x66) 131 ) 132 ); 133 } 134 135 /** 136 * Constructs a new waterfall renderer. 137 * 138 * @param firstBarPaint the color of the first bar (<code>null</code> not 139 * permitted). 140 * @param positiveBarPaint the color for bars with positive values 141 * (<code>null</code> not permitted). 142 * @param negativeBarPaint the color for bars with negative values 143 * (<code>null</code> not permitted). 144 * @param lastBarPaint the color of the last bar (<code>null</code> not 145 * permitted). 146 */ 147 public WaterfallBarRenderer(Paint firstBarPaint, 148 Paint positiveBarPaint, 149 Paint negativeBarPaint, 150 Paint lastBarPaint) { 151 super(); 152 if (firstBarPaint == null) { 153 throw new IllegalArgumentException("Null 'firstBarPaint' argument"); 154 } 155 if (positiveBarPaint == null) { 156 throw new IllegalArgumentException( 157 "Null 'positiveBarPaint' argument" 158 ); 159 } 160 if (negativeBarPaint == null) { 161 throw new IllegalArgumentException( 162 "Null 'negativeBarPaint' argument" 163 ); 164 } 165 if (lastBarPaint == null) { 166 throw new IllegalArgumentException("Null 'lastBarPaint' argument"); 167 } 168 this.firstBarPaint = firstBarPaint; 169 this.lastBarPaint = lastBarPaint; 170 this.positiveBarPaint = positiveBarPaint; 171 this.negativeBarPaint = negativeBarPaint; 172 setGradientPaintTransformer( 173 new StandardGradientPaintTransformer( 174 GradientPaintTransformType.CENTER_VERTICAL 175 ) 176 ); 177 setMinimumBarLength(1.0); 178 } 179 180 /** 181 * Returns the range of values the renderer requires to display all the 182 * items from the specified dataset. 183 * 184 * @param dataset the dataset (<code>null</code> not permitted). 185 * 186 * @return The range (or <code>null</code> if the dataset is empty). 187 */ 188 public Range findRangeBounds(CategoryDataset dataset) { 189 return DatasetUtilities.findCumulativeRangeBounds(dataset); 190 } 191 192 /** 193 * Returns the paint used to draw the first bar. 194 * 195 * @return The paint (never <code>null</code>). 196 */ 197 public Paint getFirstBarPaint() { 198 return this.firstBarPaint; 199 } 200 201 /** 202 * Sets the paint that will be used to draw the first bar and sends a 203 * {@link RendererChangeEvent} to all registered listeners. 204 * 205 * @param paint the paint (<code>null</code> not permitted). 206 */ 207 public void setFirstBarPaint(Paint paint) { 208 if (paint == null) { 209 throw new IllegalArgumentException("Null 'paint' argument"); 210 } 211 this.firstBarPaint = paint; 212 notifyListeners(new RendererChangeEvent(this)); 213 } 214 215 /** 216 * Returns the paint used to draw the last bar. 217 * 218 * @return The paint (never <code>null</code>). 219 */ 220 public Paint getLastBarPaint() { 221 return this.lastBarPaint; 222 } 223 224 /** 225 * Sets the paint that will be used to draw the last bar. 226 * 227 * @param paint the paint (<code>null</code> not permitted). 228 */ 229 public void setLastBarPaint(Paint paint) { 230 if (paint == null) { 231 throw new IllegalArgumentException("Null 'paint' argument"); 232 } 233 this.lastBarPaint = paint; 234 notifyListeners(new RendererChangeEvent(this)); 235 } 236 237 /** 238 * Returns the paint used to draw bars with positive values. 239 * 240 * @return The paint (never <code>null</code>). 241 */ 242 public Paint getPositiveBarPaint() { 243 return this.positiveBarPaint; 244 } 245 246 /** 247 * Sets the paint that will be used to draw bars having positive values. 248 * 249 * @param paint the paint (<code>null</code> not permitted). 250 */ 251 public void setPositiveBarPaint(Paint paint) { 252 if (paint == null) { 253 throw new IllegalArgumentException("Null 'paint' argument"); 254 } 255 this.positiveBarPaint = paint; 256 notifyListeners(new RendererChangeEvent(this)); 257 } 258 259 /** 260 * Returns the paint used to draw bars with negative values. 261 * 262 * @return The paint (never <code>null</code>). 263 */ 264 public Paint getNegativeBarPaint() { 265 return this.negativeBarPaint; 266 } 267 268 /** 269 * Sets the paint that will be used to draw bars having negative values. 270 * 271 * @param paint the paint (<code>null</code> not permitted). 272 */ 273 public void setNegativeBarPaint(Paint paint) { 274 if (paint == null) { 275 throw new IllegalArgumentException("Null 'paint' argument"); 276 } 277 this.negativeBarPaint = paint; 278 notifyListeners(new RendererChangeEvent(this)); 279 } 280 281 /** 282 * Draws the bar for a single (series, category) data item. 283 * 284 * @param g2 the graphics device. 285 * @param state the renderer state. 286 * @param dataArea the data area. 287 * @param plot the plot. 288 * @param domainAxis the domain axis. 289 * @param rangeAxis the range axis. 290 * @param dataset the dataset. 291 * @param row the row index (zero-based). 292 * @param column the column index (zero-based). 293 * @param pass the pass index. 294 */ 295 public void drawItem(Graphics2D g2, 296 CategoryItemRendererState state, 297 Rectangle2D dataArea, 298 CategoryPlot plot, 299 CategoryAxis domainAxis, 300 ValueAxis rangeAxis, 301 CategoryDataset dataset, 302 int row, 303 int column, 304 int pass) { 305 306 double previous = state.getSeriesRunningTotal(); 307 if (column == dataset.getColumnCount() - 1) { 308 previous = 0.0; 309 } 310 double current = 0.0; 311 Number n = dataset.getValue(row, column); 312 if (n != null) { 313 current = previous + n.doubleValue(); 314 } 315 state.setSeriesRunningTotal(current); 316 317 int seriesCount = getRowCount(); 318 int categoryCount = getColumnCount(); 319 PlotOrientation orientation = plot.getOrientation(); 320 321 double rectX = 0.0; 322 double rectY = 0.0; 323 324 RectangleEdge domainAxisLocation = plot.getDomainAxisEdge(); 325 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 326 327 // Y0 328 double j2dy0 = rangeAxis.valueToJava2D( 329 previous, dataArea, rangeAxisLocation 330 ); 331 332 // Y1 333 double j2dy1 = rangeAxis.valueToJava2D( 334 current, dataArea, rangeAxisLocation 335 ); 336 337 double valDiff = current - previous; 338 if (j2dy1 < j2dy0) { 339 double temp = j2dy1; 340 j2dy1 = j2dy0; 341 j2dy0 = temp; 342 } 343 344 // BAR WIDTH 345 double rectWidth = state.getBarWidth(); 346 347 // BAR HEIGHT 348 double rectHeight = Math.max( 349 getMinimumBarLength(), Math.abs(j2dy1 - j2dy0) 350 ); 351 352 if (orientation == PlotOrientation.HORIZONTAL) { 353 // BAR Y 354 rectY = domainAxis.getCategoryStart( 355 column, getColumnCount(), dataArea, domainAxisLocation 356 ); 357 if (seriesCount > 1) { 358 double seriesGap = dataArea.getHeight() * getItemMargin() 359 / (categoryCount * (seriesCount - 1)); 360 rectY = rectY + row * (state.getBarWidth() + seriesGap); 361 } 362 else { 363 rectY = rectY + row * state.getBarWidth(); 364 } 365 366 rectX = j2dy0; 367 rectHeight = state.getBarWidth(); 368 rectWidth = Math.max( 369 getMinimumBarLength(), Math.abs(j2dy1 - j2dy0) 370 ); 371 372 } 373 else if (orientation == PlotOrientation.VERTICAL) { 374 // BAR X 375 rectX = domainAxis.getCategoryStart( 376 column, getColumnCount(), dataArea, domainAxisLocation 377 ); 378 379 if (seriesCount > 1) { 380 double seriesGap = dataArea.getWidth() * getItemMargin() 381 / (categoryCount * (seriesCount - 1)); 382 rectX = rectX + row * (state.getBarWidth() + seriesGap); 383 } 384 else { 385 rectX = rectX + row * state.getBarWidth(); 386 } 387 388 rectY = j2dy0; 389 } 390 Rectangle2D bar = new Rectangle2D.Double( 391 rectX, rectY, rectWidth, rectHeight 392 ); 393 Paint seriesPaint = getFirstBarPaint(); 394 if (column == 0) { 395 seriesPaint = getFirstBarPaint(); 396 } 397 else if (column == categoryCount - 1) { 398 seriesPaint = getLastBarPaint(); 399 } 400 else { 401 if (valDiff < 0.0) { 402 seriesPaint = getNegativeBarPaint(); 403 } 404 else if (valDiff > 0.0) { 405 seriesPaint = getPositiveBarPaint(); 406 } 407 else { 408 seriesPaint = getLastBarPaint(); 409 } 410 } 411 if (getGradientPaintTransformer() != null 412 && seriesPaint instanceof GradientPaint) { 413 GradientPaint gp = (GradientPaint) seriesPaint; 414 seriesPaint = getGradientPaintTransformer().transform(gp, bar); 415 } 416 g2.setPaint(seriesPaint); 417 g2.fill(bar); 418 419 // draw the outline... 420 if (isDrawBarOutline() 421 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 422 Stroke stroke = getItemOutlineStroke(row, column); 423 Paint paint = getItemOutlinePaint(row, column); 424 if (stroke != null && paint != null) { 425 g2.setStroke(stroke); 426 g2.setPaint(paint); 427 g2.draw(bar); 428 } 429 } 430 431 CategoryItemLabelGenerator generator 432 = getItemLabelGenerator(row, column); 433 if (generator != null && isItemLabelVisible(row, column)) { 434 drawItemLabel( 435 g2, dataset, row, column, plot, generator, bar, (valDiff < 0.0) 436 ); 437 } 438 439 // add an item entity, if this information is being collected 440 EntityCollection entities = state.getEntityCollection(); 441 if (entities != null) { 442 addItemEntity(entities, dataset, row, column, bar); 443 } 444 445 } 446 447 /** 448 * Tests an object for equality with this instance. 449 * 450 * @param obj the object (<code>null</code> permitted). 451 * 452 * @return A boolean. 453 */ 454 public boolean equals(Object obj) { 455 456 if (obj == this) { 457 return true; 458 } 459 if (!super.equals(obj)) { 460 return false; 461 } 462 if (!(obj instanceof WaterfallBarRenderer)) { 463 return false; 464 } 465 WaterfallBarRenderer that = (WaterfallBarRenderer) obj; 466 if (!PaintUtilities.equal(this.firstBarPaint, that.firstBarPaint)) { 467 return false; 468 } 469 if (!PaintUtilities.equal(this.lastBarPaint, that.lastBarPaint)) { 470 return false; 471 } 472 if (!PaintUtilities.equal(this.positiveBarPaint, 473 that.positiveBarPaint)) { 474 return false; 475 } 476 if (!PaintUtilities.equal(this.negativeBarPaint, 477 that.negativeBarPaint)) { 478 return false; 479 } 480 return true; 481 482 } 483 484 /** 485 * Provides serialization support. 486 * 487 * @param stream the output stream. 488 * 489 * @throws IOException if there is an I/O error. 490 */ 491 private void writeObject(ObjectOutputStream stream) throws IOException { 492 stream.defaultWriteObject(); 493 SerialUtilities.writePaint(this.firstBarPaint, stream); 494 SerialUtilities.writePaint(this.lastBarPaint, stream); 495 SerialUtilities.writePaint(this.positiveBarPaint, stream); 496 SerialUtilities.writePaint(this.negativeBarPaint, stream); 497 } 498 499 /** 500 * Provides serialization support. 501 * 502 * @param stream the input stream. 503 * 504 * @throws IOException if there is an I/O error. 505 * @throws ClassNotFoundException if there is a classpath problem. 506 */ 507 private void readObject(ObjectInputStream stream) 508 throws IOException, ClassNotFoundException { 509 stream.defaultReadObject(); 510 this.firstBarPaint = SerialUtilities.readPaint(stream); 511 this.lastBarPaint = SerialUtilities.readPaint(stream); 512 this.positiveBarPaint = SerialUtilities.readPaint(stream); 513 this.negativeBarPaint = SerialUtilities.readPaint(stream); 514 } 515 516 }