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