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