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