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