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