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 054 package org.jfree.chart.renderer.category; 055 056 import java.awt.Color; 057 import java.awt.GradientPaint; 058 import java.awt.Graphics2D; 059 import java.awt.Paint; 060 import java.awt.Stroke; 061 import java.awt.geom.Rectangle2D; 062 import java.io.IOException; 063 import java.io.ObjectInputStream; 064 import java.io.ObjectOutputStream; 065 import java.io.Serializable; 066 067 import org.jfree.chart.axis.CategoryAxis; 068 import org.jfree.chart.axis.ValueAxis; 069 import org.jfree.chart.entity.EntityCollection; 070 import org.jfree.chart.event.RendererChangeEvent; 071 import org.jfree.chart.labels.CategoryItemLabelGenerator; 072 import org.jfree.chart.plot.CategoryPlot; 073 import org.jfree.chart.plot.PlotOrientation; 074 import org.jfree.chart.renderer.AbstractRenderer; 075 import org.jfree.data.Range; 076 import org.jfree.data.category.CategoryDataset; 077 import org.jfree.data.general.DatasetUtilities; 078 import org.jfree.io.SerialUtilities; 079 import org.jfree.ui.GradientPaintTransformType; 080 import org.jfree.ui.RectangleEdge; 081 import org.jfree.ui.StandardGradientPaintTransformer; 082 import org.jfree.util.PaintUtilities; 083 import 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 */ 091 public 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 fireChangeEvent(); 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 and sends a 210 * {@link RendererChangeEvent} to all registered listeners. 211 * 212 * @param paint the paint (<code>null</code> not permitted). 213 */ 214 public void setLastBarPaint(Paint paint) { 215 if (paint == null) { 216 throw new IllegalArgumentException("Null 'paint' argument"); 217 } 218 this.lastBarPaint = paint; 219 fireChangeEvent(); 220 } 221 222 /** 223 * Returns the paint used to draw bars with positive values. 224 * 225 * @return The paint (never <code>null</code>). 226 */ 227 public Paint getPositiveBarPaint() { 228 return this.positiveBarPaint; 229 } 230 231 /** 232 * Sets the paint that will be used to draw bars having positive values. 233 * 234 * @param paint the paint (<code>null</code> not permitted). 235 */ 236 public void setPositiveBarPaint(Paint paint) { 237 if (paint == null) { 238 throw new IllegalArgumentException("Null 'paint' argument"); 239 } 240 this.positiveBarPaint = paint; 241 fireChangeEvent(); 242 } 243 244 /** 245 * Returns the paint used to draw bars with negative values. 246 * 247 * @return The paint (never <code>null</code>). 248 */ 249 public Paint getNegativeBarPaint() { 250 return this.negativeBarPaint; 251 } 252 253 /** 254 * Sets the paint that will be used to draw bars having negative values, 255 * and sends a {@link RendererChangeEvent} to all registered listeners. 256 * 257 * @param paint the paint (<code>null</code> not permitted). 258 */ 259 public void setNegativeBarPaint(Paint paint) { 260 if (paint == null) { 261 throw new IllegalArgumentException("Null 'paint' argument"); 262 } 263 this.negativeBarPaint = paint; 264 fireChangeEvent(); 265 } 266 267 /** 268 * Draws the bar for a single (series, category) data item. 269 * 270 * @param g2 the graphics device. 271 * @param state the renderer state. 272 * @param dataArea the data area. 273 * @param plot the plot. 274 * @param domainAxis the domain axis. 275 * @param rangeAxis the range axis. 276 * @param dataset the dataset. 277 * @param row the row index (zero-based). 278 * @param column the column index (zero-based). 279 * @param pass the pass index. 280 */ 281 public void drawItem(Graphics2D g2, 282 CategoryItemRendererState state, 283 Rectangle2D dataArea, 284 CategoryPlot plot, 285 CategoryAxis domainAxis, 286 ValueAxis rangeAxis, 287 CategoryDataset dataset, 288 int row, 289 int column, 290 int pass) { 291 292 double previous = state.getSeriesRunningTotal(); 293 if (column == dataset.getColumnCount() - 1) { 294 previous = 0.0; 295 } 296 double current = 0.0; 297 Number n = dataset.getValue(row, column); 298 if (n != null) { 299 current = previous + n.doubleValue(); 300 } 301 state.setSeriesRunningTotal(current); 302 303 int seriesCount = getRowCount(); 304 int categoryCount = getColumnCount(); 305 PlotOrientation orientation = plot.getOrientation(); 306 307 double rectX = 0.0; 308 double rectY = 0.0; 309 310 RectangleEdge domainAxisLocation = plot.getDomainAxisEdge(); 311 RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge(); 312 313 // Y0 314 double j2dy0 = rangeAxis.valueToJava2D(previous, dataArea, 315 rangeAxisLocation); 316 317 // Y1 318 double j2dy1 = rangeAxis.valueToJava2D(current, dataArea, 319 rangeAxisLocation); 320 321 double valDiff = current - previous; 322 if (j2dy1 < j2dy0) { 323 double temp = j2dy1; 324 j2dy1 = j2dy0; 325 j2dy0 = temp; 326 } 327 328 // BAR WIDTH 329 double rectWidth = state.getBarWidth(); 330 331 // BAR HEIGHT 332 double rectHeight = Math.max(getMinimumBarLength(), 333 Math.abs(j2dy1 - j2dy0)); 334 335 if (orientation == PlotOrientation.HORIZONTAL) { 336 // BAR Y 337 rectY = domainAxis.getCategoryStart(column, getColumnCount(), 338 dataArea, domainAxisLocation); 339 if (seriesCount > 1) { 340 double seriesGap = dataArea.getHeight() * getItemMargin() 341 / (categoryCount * (seriesCount - 1)); 342 rectY = rectY + row * (state.getBarWidth() + seriesGap); 343 } 344 else { 345 rectY = rectY + row * state.getBarWidth(); 346 } 347 348 rectX = j2dy0; 349 rectHeight = state.getBarWidth(); 350 rectWidth = Math.max(getMinimumBarLength(), 351 Math.abs(j2dy1 - j2dy0)); 352 353 } 354 else if (orientation == PlotOrientation.VERTICAL) { 355 // BAR X 356 rectX = domainAxis.getCategoryStart(column, getColumnCount(), 357 dataArea, domainAxisLocation); 358 359 if (seriesCount > 1) { 360 double seriesGap = dataArea.getWidth() * getItemMargin() 361 / (categoryCount * (seriesCount - 1)); 362 rectX = rectX + row * (state.getBarWidth() + seriesGap); 363 } 364 else { 365 rectX = rectX + row * state.getBarWidth(); 366 } 367 368 rectY = j2dy0; 369 } 370 Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 371 rectHeight); 372 Paint seriesPaint = getFirstBarPaint(); 373 if (column == 0) { 374 seriesPaint = getFirstBarPaint(); 375 } 376 else if (column == categoryCount - 1) { 377 seriesPaint = getLastBarPaint(); 378 } 379 else { 380 if (valDiff < 0.0) { 381 seriesPaint = getNegativeBarPaint(); 382 } 383 else if (valDiff > 0.0) { 384 seriesPaint = getPositiveBarPaint(); 385 } 386 else { 387 seriesPaint = getLastBarPaint(); 388 } 389 } 390 if (getGradientPaintTransformer() != null 391 && seriesPaint instanceof GradientPaint) { 392 GradientPaint gp = (GradientPaint) seriesPaint; 393 seriesPaint = getGradientPaintTransformer().transform(gp, bar); 394 } 395 g2.setPaint(seriesPaint); 396 g2.fill(bar); 397 398 // draw the outline... 399 if (isDrawBarOutline() 400 && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) { 401 Stroke stroke = getItemOutlineStroke(row, column); 402 Paint paint = getItemOutlinePaint(row, column); 403 if (stroke != null && paint != null) { 404 g2.setStroke(stroke); 405 g2.setPaint(paint); 406 g2.draw(bar); 407 } 408 } 409 410 CategoryItemLabelGenerator generator 411 = getItemLabelGenerator(row, column); 412 if (generator != null && isItemLabelVisible(row, column)) { 413 drawItemLabel(g2, dataset, row, column, plot, generator, bar, 414 (valDiff < 0.0)); 415 } 416 417 // add an item entity, if this information is being collected 418 EntityCollection entities = state.getEntityCollection(); 419 if (entities != null) { 420 addItemEntity(entities, dataset, row, column, bar); 421 } 422 423 } 424 425 /** 426 * Tests an object for equality with this instance. 427 * 428 * @param obj the object (<code>null</code> permitted). 429 * 430 * @return A boolean. 431 */ 432 public boolean equals(Object obj) { 433 434 if (obj == this) { 435 return true; 436 } 437 if (!super.equals(obj)) { 438 return false; 439 } 440 if (!(obj instanceof WaterfallBarRenderer)) { 441 return false; 442 } 443 WaterfallBarRenderer that = (WaterfallBarRenderer) obj; 444 if (!PaintUtilities.equal(this.firstBarPaint, that.firstBarPaint)) { 445 return false; 446 } 447 if (!PaintUtilities.equal(this.lastBarPaint, that.lastBarPaint)) { 448 return false; 449 } 450 if (!PaintUtilities.equal(this.positiveBarPaint, 451 that.positiveBarPaint)) { 452 return false; 453 } 454 if (!PaintUtilities.equal(this.negativeBarPaint, 455 that.negativeBarPaint)) { 456 return false; 457 } 458 return true; 459 460 } 461 462 /** 463 * Provides serialization support. 464 * 465 * @param stream the output stream. 466 * 467 * @throws IOException if there is an I/O error. 468 */ 469 private void writeObject(ObjectOutputStream stream) throws IOException { 470 stream.defaultWriteObject(); 471 SerialUtilities.writePaint(this.firstBarPaint, stream); 472 SerialUtilities.writePaint(this.lastBarPaint, stream); 473 SerialUtilities.writePaint(this.positiveBarPaint, stream); 474 SerialUtilities.writePaint(this.negativeBarPaint, stream); 475 } 476 477 /** 478 * Provides serialization support. 479 * 480 * @param stream the input stream. 481 * 482 * @throws IOException if there is an I/O error. 483 * @throws ClassNotFoundException if there is a classpath problem. 484 */ 485 private void readObject(ObjectInputStream stream) 486 throws IOException, ClassNotFoundException { 487 stream.defaultReadObject(); 488 this.firstBarPaint = SerialUtilities.readPaint(stream); 489 this.lastBarPaint = SerialUtilities.readPaint(stream); 490 this.positiveBarPaint = SerialUtilities.readPaint(stream); 491 this.negativeBarPaint = SerialUtilities.readPaint(stream); 492 } 493 494 }