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     * VectorRenderer.java
029     * -------------------
030     * (C) Copyright 2007, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 30-Jan-2007 : Version 1 (DG);
038     * 24-May-2007 : Updated for method name changes (DG);
039     * 25-May-2007 : Moved from experimental to the main source tree (DG);
040     * 
041     */
042    
043    package org.jfree.chart.renderer.xy;
044    
045    import java.awt.Graphics2D;
046    import java.awt.geom.GeneralPath;
047    import java.awt.geom.Line2D;
048    import java.awt.geom.Rectangle2D;
049    import java.io.Serializable;
050    
051    import org.jfree.chart.axis.ValueAxis;
052    import org.jfree.chart.plot.CrosshairState;
053    import org.jfree.chart.plot.PlotOrientation;
054    import org.jfree.chart.plot.PlotRenderingInfo;
055    import org.jfree.chart.plot.XYPlot;
056    import org.jfree.data.Range;
057    import org.jfree.data.xy.VectorXYDataset;
058    import org.jfree.data.xy.XYDataset;
059    
060    /**
061     * A renderer that represents data from an {@link VectorXYDataset} by drawing a
062     * line with an arrow at each (x, y) point.
063     * 
064     * @since 1.0.6
065     */
066    public class VectorRenderer extends AbstractXYItemRenderer 
067            implements XYItemRenderer, Cloneable, Serializable {
068        
069        /** The length of the base. */
070        private double baseLength = 0.10;
071        
072        /** The length of the head. */
073        private double headLength = 0.14;
074        
075        
076        /**
077         * Creates a new <code>XYBlockRenderer</code> instance with default 
078         * attributes.
079         */
080        public VectorRenderer() {
081        }
082        
083        /**
084         * Returns the lower and upper bounds (range) of the x-values in the 
085         * specified dataset.
086         * 
087         * @param dataset  the dataset (<code>null</code> permitted).
088         * 
089         * @return The range (<code>null</code> if the dataset is <code>null</code>
090         *         or empty).
091         */
092        public Range findDomainBounds(XYDataset dataset) {
093            if (dataset == null) {
094                throw new IllegalArgumentException("Null 'dataset' argument.");   
095            }
096            double minimum = Double.POSITIVE_INFINITY;
097            double maximum = Double.NEGATIVE_INFINITY;
098            int seriesCount = dataset.getSeriesCount();
099            double lvalue;
100            double uvalue;
101            if (dataset instanceof VectorXYDataset) {
102                VectorXYDataset vdataset = (VectorXYDataset) dataset;
103                for (int series = 0; series < seriesCount; series++) {
104                    int itemCount = dataset.getItemCount(series);
105                    for (int item = 0; item < itemCount; item++) {
106                        double delta = vdataset.getVectorXValue(series, item);
107                        if (delta < 0.0) {
108                            uvalue = vdataset.getXValue(series, item);
109                            lvalue = uvalue + delta;
110                        }
111                        else {
112                            lvalue = vdataset.getXValue(series, item);
113                            uvalue = lvalue + delta;
114                        }
115                        minimum = Math.min(minimum, lvalue);
116                        maximum = Math.max(maximum, uvalue);
117                    }
118                }
119            }
120            else {
121                for (int series = 0; series < seriesCount; series++) {
122                    int itemCount = dataset.getItemCount(series);
123                    for (int item = 0; item < itemCount; item++) {
124                        lvalue = dataset.getXValue(series, item);
125                        uvalue = lvalue;
126                        minimum = Math.min(minimum, lvalue);
127                        maximum = Math.max(maximum, uvalue);
128                    }
129                }
130            }
131            if (minimum > maximum) {
132                return null;
133            }
134            else {
135                return new Range(minimum, maximum);
136            }
137        }
138        
139        /**
140         * Returns the range of values the renderer requires to display all the 
141         * items from the specified dataset.
142         * 
143         * @param dataset  the dataset (<code>null</code> permitted).
144         * 
145         * @return The range (<code>null</code> if the dataset is <code>null</code> 
146         *         or empty).
147         */
148        public Range findRangeBounds(XYDataset dataset) {
149            if (dataset == null) {
150                throw new IllegalArgumentException("Null 'dataset' argument.");   
151            }
152            double minimum = Double.POSITIVE_INFINITY;
153            double maximum = Double.NEGATIVE_INFINITY;
154            int seriesCount = dataset.getSeriesCount();
155            double lvalue;
156            double uvalue;
157            if (dataset instanceof VectorXYDataset) {
158                VectorXYDataset vdataset = (VectorXYDataset) dataset;
159                for (int series = 0; series < seriesCount; series++) {
160                    int itemCount = dataset.getItemCount(series);
161                    for (int item = 0; item < itemCount; item++) {
162                        double delta = vdataset.getVectorYValue(series, item);
163                        if (delta < 0.0) {
164                            uvalue = vdataset.getYValue(series, item);
165                            lvalue = uvalue + delta;
166                        }
167                        else {
168                            lvalue = vdataset.getYValue(series, item);
169                            uvalue = lvalue + delta;
170                        }
171                        minimum = Math.min(minimum, lvalue);
172                        maximum = Math.max(maximum, uvalue);
173                    }
174                }
175            }
176            else {
177                for (int series = 0; series < seriesCount; series++) {
178                    int itemCount = dataset.getItemCount(series);
179                    for (int item = 0; item < itemCount; item++) {
180                        lvalue = dataset.getYValue(series, item);
181                        uvalue = lvalue;
182                        minimum = Math.min(minimum, lvalue);
183                        maximum = Math.max(maximum, uvalue);
184                    }
185                }
186            }
187            if (minimum > maximum) {
188                return null;
189            }
190            else {
191                return new Range(minimum, maximum);
192            }
193        }
194        
195        /**
196         * Draws the block representing the specified item.
197         * 
198         * @param g2  the graphics device.
199         * @param state  the state.
200         * @param dataArea  the data area.
201         * @param info  the plot rendering info.
202         * @param plot  the plot.
203         * @param domainAxis  the x-axis.
204         * @param rangeAxis  the y-axis.
205         * @param dataset  the dataset.
206         * @param series  the series index.
207         * @param item  the item index.
208         * @param crosshairState  the crosshair state.
209         * @param pass  the pass index.
210         */
211        public void drawItem(Graphics2D g2, XYItemRendererState state, 
212                Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 
213                ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 
214                int series, int item, CrosshairState crosshairState, int pass) {
215            
216            double x = dataset.getXValue(series, item);
217            double y = dataset.getYValue(series, item);
218            double dx = 0.0;
219            double dy = 0.0;
220            if (dataset instanceof VectorXYDataset) {
221                dx = ((VectorXYDataset) dataset).getVectorXValue(series, item);
222                dy = ((VectorXYDataset) dataset).getVectorYValue(series, item);
223            }
224            double xx0 = domainAxis.valueToJava2D(x, dataArea, 
225                    plot.getDomainAxisEdge());
226            double yy0 = rangeAxis.valueToJava2D(y, dataArea, 
227                    plot.getRangeAxisEdge());
228            double xx1 = domainAxis.valueToJava2D(x + dx, dataArea, 
229                    plot.getDomainAxisEdge());
230            double yy1 = rangeAxis.valueToJava2D(y + dy, dataArea, 
231                    plot.getRangeAxisEdge());
232            Line2D line;
233            PlotOrientation orientation = plot.getOrientation();
234            if (orientation.equals(PlotOrientation.HORIZONTAL)) {
235                line = new Line2D.Double(yy0, xx0, yy1, xx1);
236            }
237            else {
238                line = new Line2D.Double(xx0, yy0, xx1, yy1);
239            }
240            g2.setPaint(getItemPaint(series, item));
241            g2.setStroke(getItemStroke(series, item));
242            g2.draw(line);
243            
244            // calculate the arrow head and draw it...
245            double dxx = (xx1 - xx0);
246            double dyy = (yy1 - yy0);
247            double bx = xx0 + (1.0 - this.baseLength) * dxx;
248            double by = yy0 + (1.0 - this.baseLength) * dyy;
249            
250            double cx = xx0 + (1.0 - this.headLength) * dxx;
251            double cy = yy0 + (1.0 - this.headLength) * dyy;
252     
253            double angle = 0.0;
254            if (dxx != 0.0) {
255                angle = Math.PI / 2.0 - Math.atan(dyy / dxx);
256            }
257            double deltaX = 2.0 * Math.cos(angle);
258            double deltaY = 2.0 * Math.sin(angle);
259            
260            double leftx = cx + deltaX;
261            double lefty = cy - deltaY;
262            double rightx = cx - deltaX;
263            double righty = cy + deltaY;
264            
265            GeneralPath p = new GeneralPath();
266            p.moveTo((float) xx1, (float) yy1);
267            p.lineTo((float) rightx, (float) righty);
268            p.lineTo((float) bx, (float) by);
269            p.lineTo((float) leftx, (float) lefty);
270            p.closePath();
271            g2.draw(p);
272            
273            
274        }
275        
276        /**
277         * Tests this <code>VectorRenderer</code> for equality with an arbitrary
278         * object.  This method returns <code>true</code> if and only if:
279         * <ul>
280         * <li><code>obj</code> is an instance of <code>VectorRenderer</code> (not
281         *     <code>null</code>);</li>
282         * <li><code>obj</code> has the same field values as this 
283         *     <code>VectorRenderer</code>;</li>
284         * </ul>
285         * 
286         * @param obj  the object (<code>null</code> permitted).
287         * 
288         * @return A boolean.
289         */
290        public boolean equals(Object obj) {
291            if (obj == this) {
292                return true;
293            }
294            if (!(obj instanceof VectorRenderer)) {
295                return false;
296            }
297            VectorRenderer that = (VectorRenderer) obj;
298            if (this.baseLength != that.baseLength) {
299                return false;
300            }
301            if (this.headLength != that.headLength) {
302                return false;
303            }
304            return super.equals(obj);
305        }
306        
307        /**
308         * Returns a clone of this renderer.
309         * 
310         * @return A clone of this renderer.
311         * 
312         * @throws CloneNotSupportedException if there is a problem creating the 
313         *     clone.
314         */
315        public Object clone() throws CloneNotSupportedException {
316            return super.clone();
317        }
318    
319    }