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 * The example shown here is generated by the
091 * <code>ClusteredXYBarRendererDemo1.java</code> program included in the
092 * JFreeChart demo collection:
093 * <br><br>
094 * <img src="../../../../../images/ClusteredXYBarRendererSample.png"
095 * alt="ClusteredXYBarRendererSample.png" />
096 * <P>
097 * This renderer does not include code to calculate the crosshair point for the
098 * plot.
099 */
100 public class ClusteredXYBarRenderer extends XYBarRenderer
101 implements Cloneable, PublicCloneable, Serializable {
102
103 /** For serialization. */
104 private static final long serialVersionUID = 5864462149177133147L;
105
106 /** Determines whether bar center should be interval start. */
107 private boolean centerBarAtStartValue;
108
109 /**
110 * Default constructor. Bar margin is set to 0.0.
111 */
112 public ClusteredXYBarRenderer() {
113 this(0.0, false);
114 }
115
116 /**
117 * Constructs a new XY clustered bar renderer.
118 *
119 * @param margin the percentage amount to trim from the width of each bar.
120 * @param centerBarAtStartValue if true, bars will be centered on the
121 * start of the time period.
122 */
123 public ClusteredXYBarRenderer(double margin,
124 boolean centerBarAtStartValue) {
125 super(margin);
126 this.centerBarAtStartValue = centerBarAtStartValue;
127 }
128
129 /**
130 * Returns the number of passes through the dataset that this renderer
131 * requires. In this case, two passes are required, the first for drawing
132 * the shadows (if visible), and the second for drawing the bars.
133 *
134 * @return <code>2</code>.
135 */
136 public int getPassCount() {
137 return 2;
138 }
139
140 /**
141 * Returns the x-value bounds for the specified dataset.
142 *
143 * @param dataset the dataset (<code>null</code> permitted).
144 *
145 * @return The bounds (possibly <code>null</code>).
146 */
147 public Range findDomainBounds(XYDataset dataset) {
148 if (dataset == null) {
149 return null;
150 }
151 // need to handle cluster centering as a special case
152 if (this.centerBarAtStartValue) {
153 return findDomainBoundsWithOffset((IntervalXYDataset) dataset);
154 }
155 else {
156 return super.findDomainBounds(dataset);
157 }
158 }
159
160 /**
161 * Iterates over the items in an {@link IntervalXYDataset} to find
162 * the range of x-values including the interval OFFSET so that it centers
163 * the interval around the start value.
164 *
165 * @param dataset the dataset (<code>null</code> not permitted).
166 *
167 * @return The range (possibly <code>null</code>).
168 */
169 protected Range findDomainBoundsWithOffset(IntervalXYDataset dataset) {
170 if (dataset == null) {
171 throw new IllegalArgumentException("Null 'dataset' argument.");
172 }
173 double minimum = Double.POSITIVE_INFINITY;
174 double maximum = Double.NEGATIVE_INFINITY;
175 int seriesCount = dataset.getSeriesCount();
176 double lvalue;
177 double uvalue;
178 for (int series = 0; series < seriesCount; series++) {
179 int itemCount = dataset.getItemCount(series);
180 for (int item = 0; item < itemCount; item++) {
181 lvalue = dataset.getStartXValue(series, item);
182 uvalue = dataset.getEndXValue(series, item);
183 double offset = (uvalue - lvalue) / 2.0;
184 lvalue = lvalue - offset;
185 uvalue = uvalue - offset;
186 minimum = Math.min(minimum, lvalue);
187 maximum = Math.max(maximum, uvalue);
188 }
189 }
190
191 if (minimum > maximum) {
192 return null;
193 }
194 else {
195 return new Range(minimum, maximum);
196 }
197 }
198
199 /**
200 * Draws the visual representation of a single data item. This method
201 * is mostly copied from the superclass, the change is that in the
202 * calculated space for a singe bar we draw bars for each series next to
203 * each other. The width of each bar is the available width divided by
204 * the number of series. Bars for each series are drawn in order left to
205 * right.
206 *
207 * @param g2 the graphics device.
208 * @param state the renderer state.
209 * @param dataArea the area within which the plot is being drawn.
210 * @param info collects information about the drawing.
211 * @param plot the plot (can be used to obtain standard color
212 * information etc).
213 * @param domainAxis the domain axis.
214 * @param rangeAxis the range axis.
215 * @param dataset the dataset.
216 * @param series the series index.
217 * @param item the item index.
218 * @param crosshairState crosshair information for the plot
219 * (<code>null</code> permitted).
220 * @param pass the pass index.
221 */
222 public void drawItem(Graphics2D g2,
223 XYItemRendererState state,
224 Rectangle2D dataArea,
225 PlotRenderingInfo info,
226 XYPlot plot,
227 ValueAxis domainAxis,
228 ValueAxis rangeAxis,
229 XYDataset dataset, int series, int item,
230 CrosshairState crosshairState,
231 int pass) {
232
233 IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
234
235 double y0;
236 double y1;
237 if (getUseYInterval()) {
238 y0 = intervalDataset.getStartYValue(series, item);
239 y1 = intervalDataset.getEndYValue(series, item);
240 }
241 else {
242 y0 = getBase();
243 y1 = intervalDataset.getYValue(series, item);
244 }
245 if (Double.isNaN(y0) || Double.isNaN(y1)) {
246 return;
247 }
248
249 double yy0 = rangeAxis.valueToJava2D(y0, dataArea,
250 plot.getRangeAxisEdge());
251 double yy1 = rangeAxis.valueToJava2D(y1, dataArea,
252 plot.getRangeAxisEdge());
253
254 RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
255 double x0 = intervalDataset.getStartXValue(series, item);
256 double xx0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
257
258 double x1 = intervalDataset.getEndXValue(series, item);
259 double xx1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
260
261 double intervalW = xx1 - xx0; // this may be negative
262 double baseX = xx0;
263 if (this.centerBarAtStartValue) {
264 baseX = baseX - intervalW / 2.0;
265 }
266 double m = getMargin();
267 if (m > 0.0) {
268 double cut = intervalW * getMargin();
269 intervalW = intervalW - cut;
270 baseX = baseX + (cut / 2);
271 }
272
273 double intervalH = Math.abs(yy0 - yy1); // we don't need the sign
274
275 PlotOrientation orientation = plot.getOrientation();
276
277 int numSeries = dataset.getSeriesCount();
278 double seriesBarWidth = intervalW / numSeries; // may be negative
279
280 Rectangle2D bar = null;
281 if (orientation == PlotOrientation.HORIZONTAL) {
282 double barY0 = baseX + (seriesBarWidth * series);
283 double barY1 = barY0 + seriesBarWidth;
284 double rx = Math.min(yy0, yy1);
285 double rw = intervalH;
286 double ry = Math.min(barY0, barY1);
287 double rh = Math.abs(barY1 - barY0);
288 bar = new Rectangle2D.Double(rx, ry, rw, rh);
289 }
290 else if (orientation == PlotOrientation.VERTICAL) {
291 double barX0 = baseX + (seriesBarWidth * series);
292 double barX1 = barX0 + seriesBarWidth;
293 double rx = Math.min(barX0, barX1);
294 double rw = Math.abs(barX1 - barX0);
295 double ry = Math.min(yy0, yy1);
296 double rh = intervalH;
297 bar = new Rectangle2D.Double(rx, ry, rw, rh);
298 }
299 boolean positive = (y1 > 0.0);
300 boolean inverted = rangeAxis.isInverted();
301 RectangleEdge barBase;
302 if (orientation == PlotOrientation.HORIZONTAL) {
303 if (positive && inverted || !positive && !inverted) {
304 barBase = RectangleEdge.RIGHT;
305 }
306 else {
307 barBase = RectangleEdge.LEFT;
308 }
309 }
310 else {
311 if (positive && !inverted || !positive && inverted) {
312 barBase = RectangleEdge.BOTTOM;
313 }
314 else {
315 barBase = RectangleEdge.TOP;
316 }
317 }
318 if (pass == 0 && getShadowsVisible()) {
319 getBarPainter().paintBarShadow(g2, this, series, item, bar, barBase,
320 !getUseYInterval());
321 }
322 if (pass == 1) {
323 getBarPainter().paintBar(g2, this, series, item, bar, barBase);
324
325 if (isItemLabelVisible(series, item)) {
326 XYItemLabelGenerator generator = getItemLabelGenerator(series,
327 item);
328 drawItemLabel(g2, dataset, series, item, plot, generator, bar,
329 y1 < 0.0);
330 }
331
332 // add an entity for the item...
333 if (info != null) {
334 EntityCollection entities
335 = info.getOwner().getEntityCollection();
336 if (entities != null) {
337 addEntity(entities, bar, dataset, series, item,
338 bar.getCenterX(), bar.getCenterY());
339 }
340 }
341 }
342
343 }
344
345 /**
346 * Tests this renderer for equality with an arbitrary object, returning
347 * <code>true</code> if <code>obj</code> is a
348 * <code>ClusteredXYBarRenderer</code> with the same settings as this
349 * renderer, and <code>false</code> otherwise.
350 *
351 * @param obj the object (<code>null</code> permitted).
352 *
353 * @return A boolean.
354 */
355 public boolean equals(Object obj) {
356 if (obj == this) {
357 return true;
358 }
359 if (!(obj instanceof ClusteredXYBarRenderer)) {
360 return false;
361 }
362 ClusteredXYBarRenderer that = (ClusteredXYBarRenderer) obj;
363 if (this.centerBarAtStartValue != that.centerBarAtStartValue) {
364 return false;
365 }
366 return super.equals(obj);
367 }
368
369 /**
370 * Returns a clone of the renderer.
371 *
372 * @return A clone.
373 *
374 * @throws CloneNotSupportedException if the renderer cannot be cloned.
375 */
376 public Object clone() throws CloneNotSupportedException {
377 return super.clone();
378 }
379
380 }