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 * XYErrorRenderer.java
029 * --------------------
030 * (C) Copyright 2006, 2007, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * Changes
036 * -------
037 * 25-Oct-2006 : Version 1 (DG);
038 * 23-Mar-2007 : Check item visibility before drawing error bars - see bug
039 * 1686178 (DG);
040 *
041 */
042
043 package org.jfree.chart.renderer.xy;
044
045 import java.awt.BasicStroke;
046 import java.awt.Graphics2D;
047 import java.awt.Paint;
048 import java.awt.geom.Line2D;
049 import java.awt.geom.Rectangle2D;
050 import java.io.IOException;
051 import java.io.ObjectInputStream;
052 import java.io.ObjectOutputStream;
053
054 import org.jfree.chart.axis.ValueAxis;
055 import org.jfree.chart.event.RendererChangeEvent;
056 import org.jfree.chart.plot.CrosshairState;
057 import org.jfree.chart.plot.PlotOrientation;
058 import org.jfree.chart.plot.PlotRenderingInfo;
059 import org.jfree.chart.plot.XYPlot;
060 import org.jfree.data.Range;
061 import org.jfree.data.general.DatasetUtilities;
062 import org.jfree.data.xy.IntervalXYDataset;
063 import org.jfree.data.xy.XYDataset;
064 import org.jfree.io.SerialUtilities;
065 import org.jfree.ui.RectangleEdge;
066 import org.jfree.util.PaintUtilities;
067
068 /**
069 * A line and shape renderer that can also display x and/or y-error values.
070 * This renderer expects an {@link IntervalXYDataset}, otherwise it reverts
071 * to the behaviour of the super class.
072 *
073 * @since 1.0.3
074 */
075 public class XYErrorRenderer extends XYLineAndShapeRenderer {
076
077 /** For serialization. */
078 static final long serialVersionUID = 5162283570955172424L;
079
080 /** A flag that controls whether or not the x-error bars are drawn. */
081 private boolean drawXError;
082
083 /** A flag that controls whether or not the y-error bars are drawn. */
084 private boolean drawYError;
085
086 /** The length of the cap at the end of the error bars. */
087 private double capLength;
088
089 /**
090 * The paint used to draw the error bars (if <code>null</code> we use the
091 * series paint).
092 */
093 private transient Paint errorPaint;
094
095 /**
096 * Creates a new <code>XYErrorRenderer</code> instance.
097 */
098 public XYErrorRenderer() {
099 super(false, true);
100 this.drawXError = true;
101 this.drawYError = true;
102 this.errorPaint = null;
103 this.capLength = 4.0;
104 }
105
106 /**
107 * Returns the flag that controls whether or not the renderer draws error
108 * bars for the x-values.
109 *
110 * @return A boolean.
111 *
112 * @see #setDrawXError(boolean)
113 */
114 public boolean getDrawXError() {
115 return this.drawXError;
116 }
117
118 /**
119 * Sets the flag that controls whether or not the renderer draws error
120 * bars for the x-values and, if the flag changes, sends a
121 * {@link RendererChangeEvent} to all registered listeners.
122 *
123 * @param draw the flag value.
124 *
125 * @see #getDrawXError()
126 */
127 public void setDrawXError(boolean draw) {
128 if (this.drawXError != draw) {
129 this.drawXError = draw;
130 fireChangeEvent();
131 }
132 }
133
134 /**
135 * Returns the flag that controls whether or not the renderer draws error
136 * bars for the y-values.
137 *
138 * @return A boolean.
139 *
140 * @see #setDrawYError(boolean)
141 */
142 public boolean getDrawYError() {
143 return this.drawYError;
144 }
145
146 /**
147 * Sets the flag that controls whether or not the renderer draws error
148 * bars for the y-values and, if the flag changes, sends a
149 * {@link RendererChangeEvent} to all registered listeners.
150 *
151 * @param draw the flag value.
152 *
153 * @see #getDrawYError()
154 */
155 public void setDrawYError(boolean draw) {
156 if (this.drawYError != draw) {
157 this.drawYError = draw;
158 fireChangeEvent();
159 }
160 }
161
162 /**
163 * Returns the length (in Java2D units) of the cap at the end of the error
164 * bars.
165 *
166 * @return The cap length.
167 *
168 * @see #setCapLength(double)
169 */
170 public double getCapLength() {
171 return this.capLength;
172 }
173
174 /**
175 * Sets the length of the cap at the end of the error bars, and sends a
176 * {@link RendererChangeEvent} to all registered listeners.
177 *
178 * @param length the length (in Java2D units).
179 *
180 * @see #getCapLength()
181 */
182 public void setCapLength(double length) {
183 this.capLength = length;
184 fireChangeEvent();
185 }
186
187 /**
188 * Returns the paint used to draw the error bars. If this is
189 * <code>null</code> (the default), the item paint is used instead.
190 *
191 * @return The paint (possibly <code>null</code>).
192 *
193 * @see #setErrorPaint(Paint)
194 */
195 public Paint getErrorPaint() {
196 return this.errorPaint;
197 }
198
199 /**
200 * Sets the paint used to draw the error bars and sends a
201 * {@link RendererChangeEvent} to all registered listeners.
202 *
203 * @param paint the paint (<code>null</code> permitted).
204 *
205 * @see #getErrorPaint()
206 */
207 public void setErrorPaint(Paint paint) {
208 this.errorPaint = paint;
209 fireChangeEvent();
210 }
211
212 /**
213 * Returns the range required by this renderer to display all the domain
214 * values in the specified dataset.
215 *
216 * @param dataset the dataset (<code>null</code> permitted).
217 *
218 * @return The range, or <code>null</code> if the dataset is
219 * <code>null</code>.
220 */
221 public Range findDomainBounds(XYDataset dataset) {
222 if (dataset != null) {
223 return DatasetUtilities.findDomainBounds(dataset, true);
224 }
225 else {
226 return null;
227 }
228 }
229
230 /**
231 * Returns the range required by this renderer to display all the range
232 * values in the specified dataset.
233 *
234 * @param dataset the dataset (<code>null</code> permitted).
235 *
236 * @return The range, or <code>null</code> if the dataset is
237 * <code>null</code>.
238 */
239 public Range findRangeBounds(XYDataset dataset) {
240 if (dataset != null) {
241 return DatasetUtilities.findRangeBounds(dataset, true);
242 }
243 else {
244 return null;
245 }
246 }
247
248 /**
249 * Draws the visual representation for one data item.
250 *
251 * @param g2 the graphics output target.
252 * @param state the renderer state.
253 * @param dataArea the data area.
254 * @param info the plot rendering info.
255 * @param plot the plot.
256 * @param domainAxis the domain axis.
257 * @param rangeAxis the range axis.
258 * @param dataset the dataset.
259 * @param series the series index.
260 * @param item the item index.
261 * @param crosshairState the crosshair state.
262 * @param pass the pass index.
263 */
264 public void drawItem(Graphics2D g2, XYItemRendererState state,
265 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
266 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
267 int series, int item, CrosshairState crosshairState, int pass) {
268
269 if (pass == 0 && dataset instanceof IntervalXYDataset
270 && getItemVisible(series, item)) {
271 IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
272 PlotOrientation orientation = plot.getOrientation();
273 if (this.drawXError) {
274 // draw the error bar for the x-interval
275 double x0 = ixyd.getStartXValue(series, item);
276 double x1 = ixyd.getEndXValue(series, item);
277 double y = ixyd.getYValue(series, item);
278 RectangleEdge edge = plot.getDomainAxisEdge();
279 double xx0 = domainAxis.valueToJava2D(x0, dataArea, edge);
280 double xx1 = domainAxis.valueToJava2D(x1, dataArea, edge);
281 double yy = rangeAxis.valueToJava2D(y, dataArea,
282 plot.getRangeAxisEdge());
283 Line2D line;
284 Line2D cap1 = null;
285 Line2D cap2 = null;
286 double adj = this.capLength / 2.0;
287 if (orientation == PlotOrientation.VERTICAL) {
288 line = new Line2D.Double(xx0, yy, xx1, yy);
289 cap1 = new Line2D.Double(xx0, yy - adj, xx0, yy + adj);
290 cap2 = new Line2D.Double(xx1, yy - adj, xx1, yy + adj);
291 }
292 else { // PlotOrientation.HORIZONTAL
293 line = new Line2D.Double(yy, xx0, yy, xx1);
294 cap1 = new Line2D.Double(yy - adj, xx0, yy + adj, xx0);
295 cap2 = new Line2D.Double(yy - adj, xx1, yy + adj, xx1);
296 }
297 g2.setStroke(new BasicStroke(1.0f));
298 if (this.errorPaint != null) {
299 g2.setPaint(this.errorPaint);
300 }
301 else {
302 g2.setPaint(getItemPaint(series, item));
303 }
304 g2.draw(line);
305 g2.draw(cap1);
306 g2.draw(cap2);
307 }
308 if (this.drawYError) {
309 // draw the error bar for the y-interval
310 double y0 = ixyd.getStartYValue(series, item);
311 double y1 = ixyd.getEndYValue(series, item);
312 double x = ixyd.getXValue(series, item);
313 RectangleEdge edge = plot.getRangeAxisEdge();
314 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, edge);
315 double yy1 = rangeAxis.valueToJava2D(y1, dataArea, edge);
316 double xx = domainAxis.valueToJava2D(x, dataArea,
317 plot.getDomainAxisEdge());
318 Line2D line;
319 Line2D cap1 = null;
320 Line2D cap2 = null;
321 double adj = this.capLength / 2.0;
322 if (orientation == PlotOrientation.VERTICAL) {
323 line = new Line2D.Double(xx, yy0, xx, yy1);
324 cap1 = new Line2D.Double(xx - adj, yy0, xx + adj, yy0);
325 cap2 = new Line2D.Double(xx - adj, yy1, xx + adj, yy1);
326 }
327 else { // PlotOrientation.HORIZONTAL
328 line = new Line2D.Double(yy0, xx, yy1, xx);
329 cap1 = new Line2D.Double(yy0, xx - adj, yy0, xx + adj);
330 cap2 = new Line2D.Double(yy1, xx - adj, yy1, xx + adj);
331 }
332 g2.setStroke(new BasicStroke(1.0f));
333 if (this.errorPaint != null) {
334 g2.setPaint(this.errorPaint);
335 }
336 else {
337 g2.setPaint(getItemPaint(series, item));
338 }
339 g2.draw(line);
340 g2.draw(cap1);
341 g2.draw(cap2);
342 }
343 }
344 super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis,
345 dataset, series, item, crosshairState, pass);
346 }
347
348 /**
349 * Tests this instance for equality with an arbitrary object.
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 XYErrorRenderer)) {
360 return false;
361 }
362 XYErrorRenderer that = (XYErrorRenderer) obj;
363 if (this.drawXError != that.drawXError) {
364 return false;
365 }
366 if (this.drawYError != that.drawYError) {
367 return false;
368 }
369 if (this.capLength != that.capLength) {
370 return false;
371 }
372 if (!PaintUtilities.equal(this.errorPaint, that.errorPaint)) {
373 return false;
374 }
375 return super.equals(obj);
376 }
377
378 /**
379 * Provides serialization support.
380 *
381 * @param stream the input stream.
382 *
383 * @throws IOException if there is an I/O error.
384 * @throws ClassNotFoundException if there is a classpath problem.
385 */
386 private void readObject(ObjectInputStream stream)
387 throws IOException, ClassNotFoundException {
388 stream.defaultReadObject();
389 this.errorPaint = SerialUtilities.readPaint(stream);
390 }
391
392 /**
393 * Provides serialization support.
394 *
395 * @param stream the output stream.
396 *
397 * @throws IOException if there is an I/O error.
398 */
399 private void writeObject(ObjectOutputStream stream) throws IOException {
400 stream.defaultWriteObject();
401 SerialUtilities.writePaint(this.errorPaint, stream);
402 }
403
404 }