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 * XYLineAnnotation.java
029 * ---------------------
030 * (C) Copyright 2003-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Peter Kolb (see patch 2809117);
034                     Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
035 *
036 */
037
038package org.jfree.chart.annotations;
039
040import java.awt.BasicStroke;
041import java.awt.Color;
042import java.awt.Graphics2D;
043import java.awt.Paint;
044import java.awt.Stroke;
045import java.awt.geom.Line2D;
046import java.awt.geom.Rectangle2D;
047import java.io.IOException;
048import java.io.ObjectInputStream;
049import java.io.ObjectOutputStream;
050import java.io.Serializable;
051import java.util.Objects;
052import org.jfree.chart.HashUtils;
053
054import org.jfree.chart.axis.ValueAxis;
055import org.jfree.chart.plot.Plot;
056import org.jfree.chart.plot.PlotOrientation;
057import org.jfree.chart.plot.PlotRenderingInfo;
058import org.jfree.chart.plot.XYPlot;
059import org.jfree.chart.ui.RectangleEdge;
060import org.jfree.chart.util.LineUtils;
061import org.jfree.chart.util.PaintUtils;
062import org.jfree.chart.util.Args;
063import org.jfree.chart.util.PublicCloneable;
064import org.jfree.chart.util.SerialUtils;
065import org.jfree.chart.util.ShapeUtils;
066
067/**
068 * A simple line annotation that can be placed on an {@link XYPlot}.
069 * Instances of this class are immutable.
070 */
071public class XYLineAnnotation extends AbstractXYAnnotation
072        implements Cloneable, PublicCloneable, Serializable {
073
074    /** For serialization. */
075    private static final long serialVersionUID = -80535465244091334L;
076
077    /** The x-coordinate. */
078    private double x1;
079
080    /** The y-coordinate. */
081    private double y1;
082
083    /** The x-coordinate. */
084    private double x2;
085
086    /** The y-coordinate. */
087    private double y2;
088
089    /** The line stroke. */
090    private transient Stroke stroke;
091
092    /** The line color. */
093    private transient Paint paint;
094
095    /**
096     * Creates a new annotation that draws a line from (x1, y1) to (x2, y2)
097     * where the coordinates are measured in data space (that is, against the
098     * plot's axes).  All the line coordinates are required to be finite values.
099     *
100     * @param x1  the x-coordinate for the start of the line.
101     * @param y1  the y-coordinate for the start of the line.
102     * @param x2  the x-coordinate for the end of the line.
103     * @param y2  the y-coordinate for the end of the line.
104     */
105    public XYLineAnnotation(double x1, double y1, double x2, double y2) {
106        this(x1, y1, x2, y2, new BasicStroke(1.0f), Color.BLACK);
107    }
108
109    /**
110     * Creates a new annotation that draws a line from (x1, y1) to (x2, y2)
111     * where the coordinates are measured in data space (that is, against the
112     * plot's axes).
113     *
114     * @param x1  the x-coordinate for the start of the line (must be finite).
115     * @param y1  the y-coordinate for the start of the line (must be finite).
116     * @param x2  the x-coordinate for the end of the line (must be finite).
117     * @param y2  the y-coordinate for the end of the line (must be finite).
118     * @param stroke  the line stroke ({@code null} not permitted).
119     * @param paint  the line color ({@code null} not permitted).
120     */
121    public XYLineAnnotation(double x1, double y1, double x2, double y2,
122                            Stroke stroke, Paint paint) {
123        super();
124        Args.nullNotPermitted(stroke, "stroke");
125        Args.nullNotPermitted(paint, "paint");
126        Args.requireFinite(x1, "x1");
127        Args.requireFinite(y1, "y1");
128        Args.requireFinite(x2, "x2");
129        Args.requireFinite(y2, "y2");
130        this.x1 = x1;
131        this.y1 = y1;
132        this.x2 = x2;
133        this.y2 = y2;
134        this.stroke = stroke;
135        this.paint = paint;
136    }
137
138    /**
139     * Returns the x-coordinate for the starting point of the line (set in the
140     * constructor).
141     * 
142     * @return The x-coordinate for the starting point of the line.
143     */
144    public double getX1() {
145        return x1;
146    }
147
148    /**
149     * Returns the y-coordinate for the starting point of the line (set in the
150     * constructor).
151     * 
152     * @return The y-coordinate for the starting point of the line.
153     */
154    public double getY1() {
155        return y1;
156    }
157
158    /**
159     * Returns the x-coordinate for the ending point of the line (set in the
160     * constructor).
161     * 
162     * @return The x-coordinate for the ending point of the line.
163     */
164    public double getX2() {
165        return x2;
166    }
167
168    /**
169     * Returns the y-coordinate for the ending point of the line (set in the
170     * constructor).
171     * 
172     * @return The y-coordinate for the ending point of the line.
173     */
174    public double getY2() {
175        return y2;
176    }
177
178    /**
179     * Returns the stroke used for drawing the line (set in the constructor).
180     * 
181     * @return The stroke (never {@code null}).
182     */
183    public Stroke getStroke() {
184        return stroke;
185    }
186
187    /**
188     * Returns the paint used for drawing the line (set in the constructor).
189     * 
190     * @return The paint (never {@code null}).
191     */
192    public Paint getPaint() {
193        return paint;
194    }
195
196    /**
197     * Draws the annotation.  This method is called by the {@link XYPlot}
198     * class, you won't normally need to call it yourself.
199     *
200     * @param g2  the graphics device.
201     * @param plot  the plot.
202     * @param dataArea  the data area.
203     * @param domainAxis  the domain axis.
204     * @param rangeAxis  the range axis.
205     * @param rendererIndex  the renderer index.
206     * @param info  if supplied, this info object will be populated with
207     *              entity information.
208     */
209    @Override
210    public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
211                     ValueAxis domainAxis, ValueAxis rangeAxis,
212                     int rendererIndex,
213                     PlotRenderingInfo info) {
214
215        PlotOrientation orientation = plot.getOrientation();
216        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
217                plot.getDomainAxisLocation(), orientation);
218        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
219                plot.getRangeAxisLocation(), orientation);
220        float j2DX1 = 0.0f;
221        float j2DX2 = 0.0f;
222        float j2DY1 = 0.0f;
223        float j2DY2 = 0.0f;
224        if (orientation == PlotOrientation.VERTICAL) {
225            j2DX1 = (float) domainAxis.valueToJava2D(this.x1, dataArea,
226                    domainEdge);
227            j2DY1 = (float) rangeAxis.valueToJava2D(this.y1, dataArea,
228                    rangeEdge);
229            j2DX2 = (float) domainAxis.valueToJava2D(this.x2, dataArea,
230                    domainEdge);
231            j2DY2 = (float) rangeAxis.valueToJava2D(this.y2, dataArea,
232                    rangeEdge);
233        } else if (orientation == PlotOrientation.HORIZONTAL) {
234            j2DY1 = (float) domainAxis.valueToJava2D(this.x1, dataArea,
235                    domainEdge);
236            j2DX1 = (float) rangeAxis.valueToJava2D(this.y1, dataArea,
237                    rangeEdge);
238            j2DY2 = (float) domainAxis.valueToJava2D(this.x2, dataArea,
239                    domainEdge);
240            j2DX2 = (float) rangeAxis.valueToJava2D(this.y2, dataArea,
241                    rangeEdge);
242        }
243        g2.setPaint(this.paint);
244        g2.setStroke(this.stroke);
245        Line2D line = new Line2D.Float(j2DX1, j2DY1, j2DX2, j2DY2);
246        // line is clipped to avoid JRE bug 6574155, for more info
247        // see JFreeChart bug 2221495
248        boolean visible = LineUtils.clipLine(line, dataArea);
249        if (visible) {
250            g2.draw(line);
251        }
252
253        String toolTip = getToolTipText();
254        String url = getURL();
255        if (toolTip != null || url != null) {
256            addEntity(info, ShapeUtils.createLineRegion(line, 1.0f),
257                    rendererIndex, toolTip, url);
258        }
259    }
260
261    /**
262     * Tests this object for equality with an arbitrary object.
263     *
264     * @param obj  the object to test against ({@code null} permitted).
265     *
266     * @return {@code true} or {@code false}.
267     */
268    @Override
269    public boolean equals(Object obj) {
270        if (obj == this) {
271            return true;
272        }
273        if (!(obj instanceof XYLineAnnotation)) {
274            return false;
275        }
276        XYLineAnnotation that = (XYLineAnnotation) obj;
277        if (Double.doubleToLongBits(this.x1) != Double.doubleToLongBits(that.x1)) {
278            return false;
279        }
280        if (Double.doubleToLongBits(this.y1) != Double.doubleToLongBits(that.y1)) {
281            return false;
282        }
283        if (Double.doubleToLongBits(this.x2) != Double.doubleToLongBits(that.x2)) {
284            return false;
285        }
286        if (Double.doubleToLongBits(this.y2) != Double.doubleToLongBits(that.y2)) {
287            return false;
288        }
289        if (!PaintUtils.equal(this.paint, that.paint)) {
290            return false;
291        }
292        if (!Objects.equals(this.stroke, that.stroke)) {
293            return false;
294        }
295        // fix the "equals not symmetric" problem
296        if (!that.canEqual(this)) {
297            return false;
298        }
299
300        return super.equals(obj);
301    }
302
303    /**
304     * Ensures symmetry between super/subclass implementations of equals. For
305     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
306     *
307     * @param other Object
308     * 
309     * @return true ONLY if the parameter is THIS class type
310     */
311    @Override
312    public boolean canEqual(Object other) {
313        // fix the "equals not symmetric" problem
314        return (other instanceof XYLineAnnotation);
315    }
316
317    /**
318     * Returns a hash code.
319     *
320     * @return A hash code.
321     */
322    @Override
323    public int hashCode() {
324        int hash = super.hashCode(); // equals calls superclass, hashCode must also
325        hash = 89 * hash + (int) (Double.doubleToLongBits(this.x1) ^
326                                 (Double.doubleToLongBits(this.x1) >>> 32));
327        hash = 89 * hash + (int) (Double.doubleToLongBits(this.y1) ^
328                                 (Double.doubleToLongBits(this.y1) >>> 32));
329        hash = 89 * hash + (int) (Double.doubleToLongBits(this.x2) ^
330                                 (Double.doubleToLongBits(this.x2) >>> 32));
331        hash = 89 * hash + (int) (Double.doubleToLongBits(this.y2) ^
332                                 (Double.doubleToLongBits(this.y2) >>> 32));
333        hash = 89 * hash + Objects.hashCode(this.stroke);
334        hash = 89 * hash + HashUtils.hashCodeForPaint(this.paint);
335        return hash;
336    }
337
338    /**
339     * Returns a clone of the annotation.
340     *
341     * @return A clone.
342     *
343     * @throws CloneNotSupportedException  if the annotation can't be cloned.
344     */
345    @Override
346    public Object clone() throws CloneNotSupportedException {
347        return super.clone();
348    }
349
350    /**
351     * Provides serialization support.
352     *
353     * @param stream  the output stream.
354     *
355     * @throws IOException  if there is an I/O error.
356     */
357    private void writeObject(ObjectOutputStream stream) throws IOException {
358        stream.defaultWriteObject();
359        SerialUtils.writePaint(this.paint, stream);
360        SerialUtils.writeStroke(this.stroke, stream);
361    }
362
363    /**
364     * Provides serialization support.
365     *
366     * @param stream  the input stream.
367     *
368     * @throws IOException  if there is an I/O error.
369     * @throws ClassNotFoundException  if there is a classpath problem.
370     */
371    private void readObject(ObjectInputStream stream)
372        throws IOException, ClassNotFoundException {
373        stream.defaultReadObject();
374        this.paint = SerialUtils.readPaint(stream);
375        this.stroke = SerialUtils.readStroke(stream);
376    }
377
378}