001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2021, 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 * XYDrawableAnnotation.java
029 * -------------------------
030 * (C) Copyright 2003-2021, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
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.
086     * @param y  the y-coordinate for the area.
087     * @param width  the width of the area.
088     * @param height  the height of the area.
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.
103     * @param y  the y-coordinate for the area.
104     * @param displayWidth  the width of the area.
105     * @param displayHeight  the height of the area.
106     * @param drawScaleFactor  the scaling factor for drawing.
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
112        super();
113        Args.nullNotPermitted(drawable, "drawable");
114        this.x = x;
115        this.y = y;
116        this.displayWidth = displayWidth;
117        this.displayHeight = displayHeight;
118        this.drawScaleFactor = drawScaleFactor;
119        this.drawable = drawable;
120
121    }
122
123    /**
124     * Draws the annotation.
125     *
126     * @param g2  the graphics device.
127     * @param plot  the plot.
128     * @param dataArea  the data area.
129     * @param domainAxis  the domain axis.
130     * @param rangeAxis  the range axis.
131     * @param rendererIndex  the renderer index.
132     * @param info  if supplied, this info object will be populated with
133     *              entity information.
134     */
135    @Override
136    public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
137                     ValueAxis domainAxis, ValueAxis rangeAxis,
138                     int rendererIndex,
139                     PlotRenderingInfo info) {
140
141        PlotOrientation orientation = plot.getOrientation();
142        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
143                plot.getDomainAxisLocation(), orientation);
144        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
145                plot.getRangeAxisLocation(), orientation);
146        float j2DX = (float) domainAxis.valueToJava2D(this.x, dataArea,
147                domainEdge);
148        float j2DY = (float) rangeAxis.valueToJava2D(this.y, dataArea,
149                rangeEdge);
150        Rectangle2D displayArea = new Rectangle2D.Double(
151                j2DX - this.displayWidth / 2.0,
152                j2DY - this.displayHeight / 2.0, this.displayWidth,
153                this.displayHeight);
154
155        // here we change the AffineTransform so we can draw the annotation
156        // to a larger area and scale it down into the display area
157        // afterwards, the original transform is restored
158        AffineTransform savedTransform = g2.getTransform();
159        Rectangle2D drawArea = new Rectangle2D.Double(0.0, 0.0,
160                this.displayWidth * this.drawScaleFactor,
161                this.displayHeight * this.drawScaleFactor);
162
163        g2.scale(1 / this.drawScaleFactor, 1 / this.drawScaleFactor);
164        g2.translate((j2DX - this.displayWidth / 2.0) * this.drawScaleFactor,
165                (j2DY - this.displayHeight / 2.0) * this.drawScaleFactor);
166        this.drawable.draw(g2, drawArea);
167        g2.setTransform(savedTransform);
168        String toolTip = getToolTipText();
169        String url = getURL();
170        if (toolTip != null || url != null) {
171            addEntity(info, displayArea, rendererIndex, toolTip, url);
172        }
173
174    }
175
176    /**
177     * Tests this annotation for equality with an arbitrary object.
178     *
179     * @param obj  the object to test against.
180     *
181     * @return {@code true} or {@code false}.
182     */
183    @Override
184    public boolean equals(Object obj) {
185
186        if (obj == this) { // simple case
187            return true;
188        }
189        // now try to reject equality...
190        if (!super.equals(obj)) {
191            return false;
192        }
193        if (!(obj instanceof XYDrawableAnnotation)) {
194            return false;
195        }
196        XYDrawableAnnotation that = (XYDrawableAnnotation) obj;
197        if (this.x != that.x) {
198            return false;
199        }
200        if (this.y != that.y) {
201            return false;
202        }
203        if (this.displayWidth != that.displayWidth) {
204            return false;
205        }
206        if (this.displayHeight != that.displayHeight) {
207            return false;
208        }
209        if (this.drawScaleFactor != that.drawScaleFactor) {
210            return false;
211        }
212        if (!Objects.equals(this.drawable, that.drawable)) {
213            return false;
214        }
215        // seem to be the same...
216        return true;
217
218    }
219
220    /**
221     * Returns a hash code.
222     *
223     * @return A hash code.
224     */
225    @Override
226    public int hashCode() {
227        int result;
228        long temp;
229        temp = Double.doubleToLongBits(this.x);
230        result = (int) (temp ^ (temp >>> 32));
231        temp = Double.doubleToLongBits(this.y);
232        result = 29 * result + (int) (temp ^ (temp >>> 32));
233        temp = Double.doubleToLongBits(this.displayWidth);
234        result = 29 * result + (int) (temp ^ (temp >>> 32));
235        temp = Double.doubleToLongBits(this.displayHeight);
236        result = 29 * result + (int) (temp ^ (temp >>> 32));
237        return result;
238    }
239
240    /**
241     * Returns a clone of the annotation.
242     *
243     * @return A clone.
244     *
245     * @throws CloneNotSupportedException  if the annotation can't be cloned.
246     */
247    @Override
248    public Object clone() throws CloneNotSupportedException {
249        return super.clone();
250    }
251
252}