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: * ClusteredXYBarRenderer.java
029: * ---------------------------
030: * (C) Copyright 2003-2007, by Paolo Cova and Contributors.
031: *
032: * Original Author: Paolo Cova;
033: * Contributor(s): David Gilbert (for Object Refinery Limited);
034: * Christian W. Zuckschwerdt;
035: * Matthias Rose;
036: *
037: * $Id: ClusteredXYBarRenderer.java,v 1.8.2.5 2007/06/12 11:36:24 mungady Exp $
038: *
039: * Changes
040: * -------
041: * 24-Jan-2003 : Version 1, contributed by Paolo Cova (DG);
042: * 25-Mar-2003 : Implemented Serializable (DG);
043: * 01-May-2003 : Modified drawItem() method signature (DG);
044: * 30-Jul-2003 : Modified entity constructor (CZ);
045: * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
046: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
047: * 07-Oct-2003 : Added renderer state (DG);
048: * 03-Nov-2003 : In draw method added state parameter and y==null value
049: * handling (MR);
050: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
051: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
052: * getYValue() (DG);
053: * 01-Oct-2004 : Fixed bug where 'drawBarOutline' flag is ignored (DG);
054: * 16-May-2005 : Fixed to used outline stroke for bar outlines. Removed some
055: * redundant code with the result that the renderer now respects
056: * the 'base' setting from the super-class. Added an equals()
057: * method (DG);
058: * 19-May-2005 : Added minimal item label implementation - needs improving (DG);
059: * ------------- JFREECHART 1.0.x ---------------------------------------------
060: * 11-Dec-2006 : Added support for GradientPaint (DG);
061: * 12-Jun-2007 : Added override to findDomainBounds() to handle cluster offset,
062: * fixed rendering to handle inverted axes, and simplified
063: * entity generation code (DG);
064: *
065: */
066:
067: package org.jfree.chart.renderer.xy;
068:
069: import java.awt.GradientPaint;
070: import java.awt.Graphics2D;
071: import java.awt.Paint;
072: import java.awt.geom.Rectangle2D;
073: import java.io.Serializable;
074:
075: import org.jfree.chart.axis.ValueAxis;
076: import org.jfree.chart.entity.EntityCollection;
077: import org.jfree.chart.labels.XYItemLabelGenerator;
078: import org.jfree.chart.plot.CrosshairState;
079: import org.jfree.chart.plot.PlotOrientation;
080: import org.jfree.chart.plot.PlotRenderingInfo;
081: import org.jfree.chart.plot.XYPlot;
082: import org.jfree.data.Range;
083: import org.jfree.data.xy.IntervalXYDataset;
084: import org.jfree.data.xy.XYDataset;
085: import org.jfree.ui.RectangleEdge;
086: import org.jfree.util.PublicCloneable;
087:
088: /**
089: * An extension of {@link XYBarRenderer} that displays bars for different
090: * series values at the same x next to each other. The assumption here is
091: * that for each x (time or else) there is a y value for each series. If
092: * this is not the case, there will be spaces between bars for a given x.
093: * <P>
094: * This renderer does not include code to calculate the crosshair point for the
095: * plot.
096: */
097: public class ClusteredXYBarRenderer extends XYBarRenderer implements
098: Cloneable, PublicCloneable, Serializable {
099:
100: /** For serialization. */
101: private static final long serialVersionUID = 5864462149177133147L;
102:
103: /** Determines whether bar center should be interval start. */
104: private boolean centerBarAtStartValue;
105:
106: /**
107: * Default constructor. Bar margin is set to 0.0.
108: */
109: public ClusteredXYBarRenderer() {
110: this (0.0, false);
111: }
112:
113: /**
114: * Constructs a new XY clustered bar renderer.
115: *
116: * @param margin the percentage amount to trim from the width of each bar.
117: * @param centerBarAtStartValue if true, bars will be centered on the start
118: * of the time period.
119: */
120: public ClusteredXYBarRenderer(double margin,
121: boolean centerBarAtStartValue) {
122: super (margin);
123: this .centerBarAtStartValue = centerBarAtStartValue;
124: }
125:
126: /**
127: * Returns the x-value bounds for the specified dataset.
128: *
129: * @param dataset the dataset (<code>null</code> permitted).
130: *
131: * @return The bounds (possibly <code>null</code>).
132: */
133: public Range findDomainBounds(XYDataset dataset) {
134: if (dataset == null) {
135: return null;
136: }
137: // need to handle cluster centering as a special case
138: if (this .centerBarAtStartValue) {
139: return findDomainBoundsWithOffset((IntervalXYDataset) dataset);
140: } else {
141: return super .findDomainBounds(dataset);
142: }
143: }
144:
145: /**
146: * Iterates over the items in an {@link IntervalXYDataset} to find
147: * the range of x-values including the interval OFFSET so that it centers
148: * the interval around the start value.
149: *
150: * @param dataset the dataset (<code>null</code> not permitted).
151: *
152: * @return The range (possibly <code>null</code>).
153: */
154: protected Range findDomainBoundsWithOffset(IntervalXYDataset dataset) {
155: if (dataset == null) {
156: throw new IllegalArgumentException(
157: "Null 'dataset' argument.");
158: }
159: double minimum = Double.POSITIVE_INFINITY;
160: double maximum = Double.NEGATIVE_INFINITY;
161: int seriesCount = dataset.getSeriesCount();
162: double lvalue;
163: double uvalue;
164: for (int series = 0; series < seriesCount; series++) {
165: int itemCount = dataset.getItemCount(series);
166: for (int item = 0; item < itemCount; item++) {
167: lvalue = dataset.getStartXValue(series, item);
168: uvalue = dataset.getEndXValue(series, item);
169: double offset = (uvalue - lvalue) / 2.0;
170: lvalue = lvalue - offset;
171: uvalue = uvalue - offset;
172: minimum = Math.min(minimum, lvalue);
173: maximum = Math.max(maximum, uvalue);
174: }
175: }
176:
177: if (minimum > maximum) {
178: return null;
179: } else {
180: return new Range(minimum, maximum);
181: }
182: }
183:
184: /**
185: * Draws the visual representation of a single data item. This method
186: * is mostly copied from the superclass, the change is that in the
187: * calculated space for a singe bar we draw bars for each series next to
188: * each other. The width of each bar is the available width divided by
189: * the number of series. Bars for each series are drawn in order left to
190: * right.
191: *
192: * @param g2 the graphics device.
193: * @param state the renderer state.
194: * @param dataArea the area within which the plot is being drawn.
195: * @param info collects information about the drawing.
196: * @param plot the plot (can be used to obtain standard color
197: * information etc).
198: * @param domainAxis the domain axis.
199: * @param rangeAxis the range axis.
200: * @param dataset the dataset.
201: * @param series the series index.
202: * @param item the item index.
203: * @param crosshairState crosshair information for the plot
204: * (<code>null</code> permitted).
205: * @param pass the pass index.
206: */
207: public void drawItem(Graphics2D g2, XYItemRendererState state,
208: Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
209: ValueAxis domainAxis, ValueAxis rangeAxis,
210: XYDataset dataset, int series, int item,
211: CrosshairState crosshairState, int pass) {
212:
213: IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
214:
215: double y0;
216: double y1;
217: if (getUseYInterval()) {
218: y0 = intervalDataset.getStartYValue(series, item);
219: y1 = intervalDataset.getEndYValue(series, item);
220: } else {
221: y0 = getBase();
222: y1 = intervalDataset.getYValue(series, item);
223: }
224: if (Double.isNaN(y0) || Double.isNaN(y1)) {
225: return;
226: }
227:
228: double yy0 = rangeAxis.valueToJava2D(y0, dataArea, plot
229: .getRangeAxisEdge());
230: double yy1 = rangeAxis.valueToJava2D(y1, dataArea, plot
231: .getRangeAxisEdge());
232:
233: RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
234: double x0 = intervalDataset.getStartXValue(series, item);
235: double xx0 = domainAxis.valueToJava2D(x0, dataArea,
236: xAxisLocation);
237:
238: double x1 = intervalDataset.getEndXValue(series, item);
239: double xx1 = domainAxis.valueToJava2D(x1, dataArea,
240: xAxisLocation);
241:
242: double intervalW = xx1 - xx0; // this may be negative
243: double baseX = xx0;
244: if (this .centerBarAtStartValue) {
245: baseX = baseX - intervalW / 2.0;
246: }
247: double m = getMargin();
248: if (m > 0.0) {
249: double cut = intervalW * getMargin();
250: intervalW = intervalW - cut;
251: baseX = baseX + (cut / 2);
252: }
253:
254: double intervalH = Math.abs(yy0 - yy1); // we don't need the sign
255:
256: PlotOrientation orientation = plot.getOrientation();
257:
258: int numSeries = dataset.getSeriesCount();
259: double seriesBarWidth = intervalW / numSeries; // may be negative
260:
261: Rectangle2D bar = null;
262: if (orientation == PlotOrientation.HORIZONTAL) {
263: double barY0 = baseX + (seriesBarWidth * series);
264: double barY1 = barY0 + seriesBarWidth;
265: double rx = Math.min(yy0, yy1);
266: double rw = intervalH;
267: double ry = Math.min(barY0, barY1);
268: double rh = Math.abs(barY1 - barY0);
269: bar = new Rectangle2D.Double(rx, ry, rw, rh);
270: } else if (orientation == PlotOrientation.VERTICAL) {
271: double barX0 = baseX + (seriesBarWidth * series);
272: double barX1 = barX0 + seriesBarWidth;
273: double rx = Math.min(barX0, barX1);
274: double rw = Math.abs(barX1 - barX0);
275: double ry = Math.min(yy0, yy1);
276: ;
277: double rh = intervalH;
278: bar = new Rectangle2D.Double(rx, ry, rw, rh);
279: }
280: Paint itemPaint = getItemPaint(series, item);
281: if (getGradientPaintTransformer() != null
282: && itemPaint instanceof GradientPaint) {
283: GradientPaint gp = (GradientPaint) itemPaint;
284: itemPaint = getGradientPaintTransformer()
285: .transform(gp, bar);
286: }
287: g2.setPaint(itemPaint);
288:
289: g2.fill(bar);
290: if (isDrawBarOutline() && Math.abs(seriesBarWidth) > 3) {
291: g2.setStroke(getItemOutlineStroke(series, item));
292: g2.setPaint(getItemOutlinePaint(series, item));
293: g2.draw(bar);
294: }
295:
296: if (isItemLabelVisible(series, item)) {
297: XYItemLabelGenerator generator = getItemLabelGenerator(
298: series, item);
299: drawItemLabel(g2, dataset, series, item, plot, generator,
300: bar, y1 < 0.0);
301: }
302:
303: // add an entity for the item...
304: if (info != null) {
305: EntityCollection entities = info.getOwner()
306: .getEntityCollection();
307: if (entities != null) {
308: addEntity(entities, bar, dataset, series, item, bar
309: .getCenterX(), bar.getCenterY());
310: }
311: }
312:
313: }
314:
315: /**
316: * Tests this renderer for equality with an arbitrary object, returning
317: * <code>true</code> if <code>obj</code> is a
318: * <code>ClusteredXYBarRenderer</code> with the same settings as this
319: * renderer, and <code>false</code> otherwise.
320: *
321: * @param obj the object (<code>null</code> permitted).
322: *
323: * @return A boolean.
324: */
325: public boolean equals(Object obj) {
326: if (obj == this ) {
327: return true;
328: }
329: if (!(obj instanceof ClusteredXYBarRenderer)) {
330: return false;
331: }
332: ClusteredXYBarRenderer that = (ClusteredXYBarRenderer) obj;
333: if (this .centerBarAtStartValue != that.centerBarAtStartValue) {
334: return false;
335: }
336: return super .equals(obj);
337: }
338:
339: /**
340: * Returns a clone of the renderer.
341: *
342: * @return A clone.
343: *
344: * @throws CloneNotSupportedException if the renderer cannot be cloned.
345: */
346: public Object clone() throws CloneNotSupportedException {
347: return super.clone();
348: }
349:
350: }
|