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