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