Source for org.jfree.chart.renderer.category.WaterfallBarRenderer

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * -------------------------
  28:  * WaterfallBarRenderer.java
  29:  * -------------------------
  30:  * (C) Copyright 2003-2007, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  Darshan Shah;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 20-Oct-2003 : Version 1, contributed by Darshan Shah (DG);
  38:  * 06-Nov-2003 : Changed order of parameters in constructor, and added support 
  39:  *               for GradientPaint (DG);
  40:  * 10-Feb-2004 : Updated drawItem() method to make cut-and-paste overriding 
  41:  *               easier.  Also fixed a bug that meant the minimum bar length 
  42:  *               was being ignored (DG);
  43:  * 04-Oct-2004 : Reworked equals() method and renamed PaintUtils 
  44:  *               --> PaintUtilities (DG);
  45:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  46:  * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
  47:  * 23-Feb-2005 : Added argument checking (DG);
  48:  * 20-Apr-2005 : Renamed CategoryLabelGenerator 
  49:  *               --> CategoryItemLabelGenerator (DG);
  50:  * 09-Jun-2005 : Use addItemEntity() from superclass (DG);
  51:  * 
  52:  */
  53: 
  54: package org.jfree.chart.renderer.category;
  55: 
  56: import java.awt.Color;
  57: import java.awt.GradientPaint;
  58: import java.awt.Graphics2D;
  59: import java.awt.Paint;
  60: import java.awt.Stroke;
  61: import java.awt.geom.Rectangle2D;
  62: import java.io.IOException;
  63: import java.io.ObjectInputStream;
  64: import java.io.ObjectOutputStream;
  65: import java.io.Serializable;
  66: 
  67: import org.jfree.chart.axis.CategoryAxis;
  68: import org.jfree.chart.axis.ValueAxis;
  69: import org.jfree.chart.entity.EntityCollection;
  70: import org.jfree.chart.event.RendererChangeEvent;
  71: import org.jfree.chart.labels.CategoryItemLabelGenerator;
  72: import org.jfree.chart.plot.CategoryPlot;
  73: import org.jfree.chart.plot.PlotOrientation;
  74: import org.jfree.chart.renderer.AbstractRenderer;
  75: import org.jfree.data.Range;
  76: import org.jfree.data.category.CategoryDataset;
  77: import org.jfree.data.general.DatasetUtilities;
  78: import org.jfree.io.SerialUtilities;
  79: import org.jfree.ui.GradientPaintTransformType;
  80: import org.jfree.ui.RectangleEdge;
  81: import org.jfree.ui.StandardGradientPaintTransformer;
  82: import org.jfree.util.PaintUtilities;
  83: import org.jfree.util.PublicCloneable;
  84: 
  85: /**
  86:  * A renderer that handles the drawing of waterfall bar charts, for use with 
  87:  * the {@link CategoryPlot} class.  Note that the bar colors are defined 
  88:  * using special methods in this class - the inherited methods (for example,
  89:  * {@link AbstractRenderer#setSeriesPaint(int, Paint)}) are ignored.
  90:  */
  91: public class WaterfallBarRenderer extends BarRenderer 
  92:                                   implements Cloneable, PublicCloneable, 
  93:                                              Serializable {
  94: 
  95:     /** For serialization. */
  96:     private static final long serialVersionUID = -2482910643727230911L;
  97:     
  98:     /** The paint used to draw the first bar. */
  99:     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: }