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:         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: }