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