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
043package org.jfree.chart.renderer.xy;
044
045import java.awt.Graphics2D;
046import java.awt.geom.GeneralPath;
047import java.awt.geom.Line2D;
048import java.awt.geom.Rectangle2D;
049import java.io.Serializable;
050
051import org.jfree.chart.axis.ValueAxis;
052import org.jfree.chart.plot.CrosshairState;
053import org.jfree.chart.plot.PlotOrientation;
054import org.jfree.chart.plot.PlotRenderingInfo;
055import org.jfree.chart.plot.XYPlot;
056import org.jfree.data.Range;
057import org.jfree.data.xy.VectorXYDataset;
058import 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 */
066public 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}