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