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 * XYDrawableAnnotation.java
029 * -------------------------
030 * (C) Copyright 2003-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
034 *
035 */
036
037package org.jfree.chart.annotations;
038
039import java.awt.Graphics2D;
040import java.awt.geom.AffineTransform;
041import java.awt.geom.Rectangle2D;
042import java.io.Serializable;
043import java.util.Objects;
044
045import org.jfree.chart.axis.ValueAxis;
046import org.jfree.chart.plot.Plot;
047import org.jfree.chart.plot.PlotOrientation;
048import org.jfree.chart.plot.PlotRenderingInfo;
049import org.jfree.chart.plot.XYPlot;
050import org.jfree.chart.ui.Drawable;
051import org.jfree.chart.ui.RectangleEdge;
052import org.jfree.chart.util.Args;
053import org.jfree.chart.util.PublicCloneable;
054
055/**
056 * A general annotation that can be placed on an {@link XYPlot}.
057 */
058public class XYDrawableAnnotation extends AbstractXYAnnotation
059        implements Cloneable, PublicCloneable, Serializable {
060
061    /** For serialization. */
062    private static final long serialVersionUID = -6540812859722691020L;
063
064    /** The scaling factor. */
065    private double drawScaleFactor;
066
067    /** The x-coordinate. */
068    private double x;
069
070    /** The y-coordinate. */
071    private double y;
072
073    /** The width. */
074    private double displayWidth;
075
076    /** The height. */
077    private double displayHeight;
078
079    /** The drawable object. */
080    private Drawable drawable;
081
082    /**
083     * Creates a new annotation to be displayed within the given area.
084     *
085     * @param x  the x-coordinate for the area (must be finite).
086     * @param y  the y-coordinate for the area (must be finite).
087     * @param width  the width of the area (must be finite).
088     * @param height  the height of the area (must be finite).
089     * @param drawable  the drawable object ({@code null} not permitted).
090     */
091    public XYDrawableAnnotation(double x, double y, double width, double height,
092                                Drawable drawable) {
093        this(x, y, width, height, 1.0, drawable);
094    }
095
096    /**
097     * Creates a new annotation to be displayed within the given area.  If you
098     * specify a {@code drawScaleFactor} of 2.0, the {@code drawable}
099     * will be drawn at twice the requested display size then scaled down to
100     * fit the space.
101     *
102     * @param x  the x-coordinate for the area (must be finite).
103     * @param y  the y-coordinate for the area (must be finite).
104     * @param displayWidth  the width of the area (must be finite).
105     * @param displayHeight  the height of the area (must be finite).
106     * @param drawScaleFactor  the scaling factor for drawing (must be finite).
107     * @param drawable  the drawable object ({@code null} not permitted).
108     */
109    public XYDrawableAnnotation(double x, double y, double displayWidth,
110            double displayHeight, double drawScaleFactor, Drawable drawable) {
111        super();
112        Args.nullNotPermitted(drawable, "drawable");
113        Args.requireFinite(x, "x");
114        Args.requireFinite(y, "y");
115        Args.requireFinite(displayWidth, "displayWidth");
116        Args.requireFinite(displayHeight, "displayHeight");
117        Args.requireFinite(drawScaleFactor, "drawScaleFactor");
118        this.x = x;
119        this.y = y;
120        this.displayWidth = displayWidth;
121        this.displayHeight = displayHeight;
122        this.drawScaleFactor = drawScaleFactor;
123        this.drawable = drawable;
124    }
125
126    /**
127     * Returns the x-coordinate (set in the constructor).
128     * 
129     * @return The x-coordinate.
130     */
131    public double getX() {
132        return x;
133    }
134
135    /**
136     * Returns the y-coordinate (set in the constructor).
137     * 
138     * @return The y-coordinate.
139     */
140    public double getY() {
141        return y;
142    }
143
144    /**
145     * Returns the display width (set in the constructor).
146     * 
147     * @return The display width.
148     */
149    public double getDisplayWidth() {
150        return displayWidth;
151    }
152
153    /**
154     * Returns the display height (set in the constructor).
155     * 
156     * @return The display height.
157     */
158    public double getDisplayHeight() {
159        return displayHeight;
160    }
161
162    /**
163     * Returns the scale factor (set in the constructor).
164     * 
165     * @return The scale factor.
166     */
167    public double getDrawScaleFactor() {
168        return drawScaleFactor;
169    }
170
171    /**
172     * Draws the annotation.
173     *
174     * @param g2  the graphics device.
175     * @param plot  the plot.
176     * @param dataArea  the data area.
177     * @param domainAxis  the domain axis.
178     * @param rangeAxis  the range axis.
179     * @param rendererIndex  the renderer index.
180     * @param info  if supplied, this info object will be populated with
181     *              entity information.
182     */
183    @Override
184    public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
185                     ValueAxis domainAxis, ValueAxis rangeAxis,
186                     int rendererIndex,
187                     PlotRenderingInfo info) {
188
189        PlotOrientation orientation = plot.getOrientation();
190        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
191                plot.getDomainAxisLocation(), orientation);
192        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
193                plot.getRangeAxisLocation(), orientation);
194        float j2DX = (float) domainAxis.valueToJava2D(this.x, dataArea,
195                domainEdge);
196        float j2DY = (float) rangeAxis.valueToJava2D(this.y, dataArea,
197                rangeEdge);
198        Rectangle2D displayArea = new Rectangle2D.Double(
199                j2DX - this.displayWidth / 2.0,
200                j2DY - this.displayHeight / 2.0, this.displayWidth,
201                this.displayHeight);
202
203        // here we change the AffineTransform so we can draw the annotation
204        // to a larger area and scale it down into the display area
205        // afterwards, the original transform is restored
206        AffineTransform savedTransform = g2.getTransform();
207        Rectangle2D drawArea = new Rectangle2D.Double(0.0, 0.0,
208                this.displayWidth * this.drawScaleFactor,
209                this.displayHeight * this.drawScaleFactor);
210
211        g2.scale(1 / this.drawScaleFactor, 1 / this.drawScaleFactor);
212        g2.translate((j2DX - this.displayWidth / 2.0) * this.drawScaleFactor,
213                (j2DY - this.displayHeight / 2.0) * this.drawScaleFactor);
214        this.drawable.draw(g2, drawArea);
215        g2.setTransform(savedTransform);
216        String toolTip = getToolTipText();
217        String url = getURL();
218        if (toolTip != null || url != null) {
219            addEntity(info, displayArea, rendererIndex, toolTip, url);
220        }
221
222    }
223
224    /**
225     * Tests this annotation for equality with an arbitrary object.
226     *
227     * @param obj  the object to test against.
228     *
229     * @return {@code true} or {@code false}.
230     */
231    @Override
232    public boolean equals(Object obj) {
233        if (obj == this) { // simple case
234            return true;
235        }
236        if (!(obj instanceof XYDrawableAnnotation)) {
237            return false;
238        }
239        XYDrawableAnnotation that = (XYDrawableAnnotation) obj;
240        if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(that.x)) {
241            return false;
242        }
243        if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(that.y)) {
244            return false;
245        }
246        if (Double.doubleToLongBits(this.displayWidth) != 
247            Double.doubleToLongBits(that.displayWidth)) {
248            return false;
249        }
250        if (Double.doubleToLongBits(this.displayHeight) != 
251            Double.doubleToLongBits(that.displayHeight)) {
252            return false;
253        }
254        if (Double.doubleToLongBits(this.drawScaleFactor) != 
255            Double.doubleToLongBits(that.drawScaleFactor)) {
256            return false;
257        }
258        if (!Objects.equals(this.drawable, that.drawable)) {
259            return false;
260        }
261
262        // fix the "equals not symmetric" problem
263        if (!that.canEqual(this)) {
264            return false;
265        }
266
267        return super.equals(obj);
268    }
269
270    /**
271     * Ensures symmetry between super/subclass implementations of equals. For
272     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
273     *
274     * @param other Object
275     * 
276     * @return true ONLY if the parameter is THIS class type
277     */
278    @Override
279    public boolean canEqual(Object other) {
280        // fix the "equals not symmetric" problem
281        return (other instanceof XYDrawableAnnotation);
282    }
283    
284    /**
285     * Returns a hash code.
286     *
287     * @return A hash code.
288     */
289    @Override
290    public int hashCode() {
291        int hash = super.hashCode(); // equals calls superclass, hashCode must also
292        hash = 41 * hash + (int) (Double.doubleToLongBits(this.x) ^ 
293                                 (Double.doubleToLongBits(this.x) >>> 32));
294        hash = 41 * hash + (int) (Double.doubleToLongBits(this.y) ^ 
295                                 (Double.doubleToLongBits(this.y) >>> 32));
296        hash = 41 * hash + (int) (Double.doubleToLongBits(this.drawScaleFactor) ^ 
297                                 (Double.doubleToLongBits(this.drawScaleFactor) >>> 32));
298        hash = 41 * hash + (int) (Double.doubleToLongBits(this.displayWidth) ^ 
299                                 (Double.doubleToLongBits(this.displayWidth) >>> 32));
300        hash = 41 * hash + (int) (Double.doubleToLongBits(this.displayHeight) ^ 
301                                 (Double.doubleToLongBits(this.displayHeight) >>> 32));
302        hash = 41 * hash + Objects.hashCode(this.drawable);
303        return hash;
304    }
305
306    /**
307     * Returns a clone of the annotation.
308     *
309     * @return A clone.
310     *
311     * @throws CloneNotSupportedException  if the annotation can't be cloned.
312     */
313    @Override
314    public Object clone() throws CloneNotSupportedException {
315        return super.clone();
316    }
317
318}