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 }