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