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 * XYBoxAnnotation.java
029 * --------------------
030 * (C) Copyright 2005-present, by David Gilbert and Contributors.
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.Rectangle2D;
046import java.io.IOException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.io.Serializable;
050import java.util.Objects;
051import org.jfree.chart.HashUtils;
052
053import org.jfree.chart.axis.ValueAxis;
054import org.jfree.chart.plot.Plot;
055import org.jfree.chart.plot.PlotOrientation;
056import org.jfree.chart.plot.PlotRenderingInfo;
057import org.jfree.chart.plot.XYPlot;
058import org.jfree.chart.ui.RectangleEdge;
059import org.jfree.chart.util.Args;
060import org.jfree.chart.util.PaintUtils;
061import org.jfree.chart.util.PublicCloneable;
062import org.jfree.chart.util.SerialUtils;
063
064/**
065 * A box annotation that can be placed on an {@link XYPlot}.  The
066 * box coordinates are specified in data space.
067 */
068public class XYBoxAnnotation extends AbstractXYAnnotation
069        implements Cloneable, PublicCloneable, Serializable {
070
071    /** For serialization. */
072    private static final long serialVersionUID = 6764703772526757457L;
073
074    /** The lower x-coordinate. */
075    private double x0;
076
077    /** The lower y-coordinate. */
078    private double y0;
079
080    /** The upper x-coordinate. */
081    private double x1;
082
083    /** The upper y-coordinate. */
084    private double y1;
085
086    /** The stroke used to draw the box outline. */
087    private transient Stroke stroke;
088
089    /** The paint used to draw the box outline. */
090    private transient Paint outlinePaint;
091
092    /** The paint used to fill the box. */
093    private transient Paint fillPaint;
094
095    /**
096     * Creates a new annotation (where, by default, the box is drawn
097     * with a black outline).
098     *
099     * @param x0  the lower x-coordinate of the box (in data space).
100     * @param y0  the lower y-coordinate of the box (in data space).
101     * @param x1  the upper x-coordinate of the box (in data space).
102     * @param y1  the upper y-coordinate of the box (in data space).
103     */
104    public XYBoxAnnotation(double x0, double y0, double x1, double y1) {
105        this(x0, y0, x1, y1, new BasicStroke(1.0f), Color.BLACK);
106    }
107
108    /**
109     * Creates a new annotation where the box is drawn as an outline using
110     * the specified {@code stroke} and {@code outlinePaint}.
111     *
112     * @param x0  the lower x-coordinate of the box (in data space).
113     * @param y0  the lower y-coordinate of the box (in data space).
114     * @param x1  the upper x-coordinate of the box (in data space).
115     * @param y1  the upper y-coordinate of the box (in data space).
116     * @param stroke  the shape stroke ({@code null} permitted).
117     * @param outlinePaint  the shape color ({@code null} permitted).
118     */
119    public XYBoxAnnotation(double x0, double y0, double x1, double y1,
120                           Stroke stroke, Paint outlinePaint) {
121        this(x0, y0, x1, y1, stroke, outlinePaint, null);
122    }
123
124    /**
125     * Creates a new annotation.
126     *
127     * @param x0  the lower x-coordinate of the box (in data space, must be finite).
128     * @param y0  the lower y-coordinate of the box (in data space, must be finite).
129     * @param x1  the upper x-coordinate of the box (in data space, must be finite).
130     * @param y1  the upper y-coordinate of the box (in data space, must be finite).
131     * @param stroke  the shape stroke ({@code null} permitted).
132     * @param outlinePaint  the shape color ({@code null} permitted).
133     * @param fillPaint  the paint used to fill the shape ({@code null}
134     *                   permitted).
135     */
136    public XYBoxAnnotation(double x0, double y0, double x1, double y1,
137                           Stroke stroke, Paint outlinePaint, Paint fillPaint) {
138        super();
139        Args.requireFinite(x0, "x0");
140        Args.requireFinite(y0, "y0");
141        Args.requireFinite(x1, "x1");
142        Args.requireFinite(y1, "y1");
143        this.x0 = x0;
144        this.y0 = y0;
145        this.x1 = x1;
146        this.y1 = y1;
147        this.stroke = stroke;
148        this.outlinePaint = outlinePaint;
149        this.fillPaint = fillPaint;
150    }
151
152    /**
153     * Returns the x-coordinate for the bottom left corner of the box (set in the
154     * constructor).
155     * 
156     * @return The x-coordinate for the bottom left corner of the box.
157     */
158    public double getX0() {
159        return x0;
160    }
161
162    /**
163     * Returns the y-coordinate for the bottom left corner of the box (set in the
164     * constructor).
165     * 
166     * @return The y-coordinate for the bottom left corner of the box.
167     */
168    public double getY0() {
169        return y0;
170    }
171
172    /**
173     * Returns the x-coordinate for the top right corner of the box (set in the
174     * constructor).
175     * 
176     * @return The x-coordinate for the top right corner of the box.
177     */
178    public double getX1() {
179        return x1;
180    }
181
182    /**
183     * Returns the y-coordinate for the top right corner of the box (set in the
184     * constructor).
185     * 
186     * @return The y-coordinate for the top right corner of the box.
187     */
188    public double getY1() {
189        return y1;
190    }
191
192    /**
193     * Returns the stroke used for the box outline.
194     * 
195     * @return The stroke (possibly {@code null}). 
196     */
197    public Stroke getStroke() {
198        return stroke;
199    }
200
201    /**
202     * Returns the paint used for the box outline.
203     * 
204     * @return The paint (possibly {@code null}). 
205     */
206    public Paint getOutlinePaint() {
207        return outlinePaint;
208    }
209
210    /**
211     * Returns the paint used for the box fill.
212     * 
213     * @return The paint (possibly {@code null}). 
214     */
215    public Paint getFillPaint() {
216        return fillPaint;
217    }
218
219    /**
220     * Draws the annotation.  This method is usually called by the
221     * {@link XYPlot} class, you shouldn't need to call it directly.
222     *
223     * @param g2  the graphics device.
224     * @param plot  the plot.
225     * @param dataArea  the data area.
226     * @param domainAxis  the domain axis.
227     * @param rangeAxis  the range axis.
228     * @param rendererIndex  the renderer index.
229     * @param info  the plot rendering info.
230     */
231    @Override
232    public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
233                     ValueAxis domainAxis, ValueAxis rangeAxis,
234                     int rendererIndex, PlotRenderingInfo info) {
235
236        PlotOrientation orientation = plot.getOrientation();
237        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
238                plot.getDomainAxisLocation(), orientation);
239        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
240                plot.getRangeAxisLocation(), orientation);
241
242        double transX0 = domainAxis.valueToJava2D(this.x0, dataArea,
243                domainEdge);
244        double transY0 = rangeAxis.valueToJava2D(this.y0, dataArea, rangeEdge);
245        double transX1 = domainAxis.valueToJava2D(this.x1, dataArea,
246                domainEdge);
247        double transY1 = rangeAxis.valueToJava2D(this.y1, dataArea, rangeEdge);
248
249        Rectangle2D box = null;
250        if (orientation == PlotOrientation.HORIZONTAL) {
251            box = new Rectangle2D.Double(transY0, transX1, transY1 - transY0,
252                    transX0 - transX1);
253        } else if (orientation == PlotOrientation.VERTICAL) {
254            box = new Rectangle2D.Double(transX0, transY1, transX1 - transX0,
255                    transY0 - transY1);
256        }
257
258        if (this.fillPaint != null) {
259            g2.setPaint(this.fillPaint);
260            g2.fill(box);
261        }
262
263        if (this.stroke != null && this.outlinePaint != null) {
264            g2.setPaint(this.outlinePaint);
265            g2.setStroke(this.stroke);
266            g2.draw(box);
267        }
268        addEntity(info, box, rendererIndex, getToolTipText(), getURL());
269    }
270
271    /**
272     * Tests this annotation for equality with an arbitrary object.
273     *
274     * @param obj  the object ({@code null} permitted).
275     *
276     * @return A boolean.
277     */
278    @Override
279    public boolean equals(Object obj) {
280        if (obj == this) {
281            return true;
282        }
283        if (!(obj instanceof XYBoxAnnotation)) {
284            return false;
285        }
286        XYBoxAnnotation that = (XYBoxAnnotation) obj;
287        if (Double.doubleToLongBits(this.x0) !=
288            Double.doubleToLongBits(that.x0)) {
289            return false;
290        }
291        if (Double.doubleToLongBits(this.y0) != 
292            Double.doubleToLongBits(that.y0)) {
293            return false;
294        }
295        if (Double.doubleToLongBits(this.x1) !=
296            Double.doubleToLongBits(that.x1)) {
297            return false;
298        }
299        if (Double.doubleToLongBits(this.y1) !=
300            Double.doubleToLongBits(that.y1)) {
301            return false;
302        }
303        if (!Objects.equals(this.stroke, that.stroke)) {
304            return false;
305        }
306        if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) {
307            return false;
308        }
309        if (!PaintUtils.equal(this.fillPaint, that.fillPaint)) {
310            return false;
311        }
312        // fix the "equals not symmetric" problem
313        if (!that.canEqual(this)) {
314            return false;
315        }
316        return super.equals(obj);
317    }
318
319    /**
320     * Ensures symmetry between super/subclass implementations of equals. For
321     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
322     *
323     * @param other Object
324     * 
325     * @return true ONLY if the parameter is THIS class type
326     */
327    @Override
328    public boolean canEqual(Object other) {
329        // fix the "equals not symmetric" problem
330        return (other instanceof XYBoxAnnotation);
331    }
332    
333    /**
334     * Returns a hash code.
335     *
336     * @return A hash code.
337     */
338    @Override
339    public int hashCode() {
340        int hash = super.hashCode(); // equals calls superclass, hashCode must also
341        hash = 67 * hash + (int) (Double.doubleToLongBits(this.x0) ^
342                                 (Double.doubleToLongBits(this.x0) >>> 32));
343        hash = 67 * hash + (int) (Double.doubleToLongBits(this.y0) ^
344                                 (Double.doubleToLongBits(this.y0) >>> 32));
345        hash = 67 * hash + (int) (Double.doubleToLongBits(this.x1) ^
346                                 (Double.doubleToLongBits(this.x1) >>> 32));
347        hash = 67 * hash + (int) (Double.doubleToLongBits(this.y1) ^
348                                (Double.doubleToLongBits(this.y1) >>> 32));
349        hash = 67 * hash + Objects.hashCode(this.stroke);
350        hash = 67 * hash + HashUtils.hashCodeForPaint(this.outlinePaint);
351        hash = 67 * hash + HashUtils.hashCodeForPaint(this.fillPaint);
352        return hash;
353    }
354
355    /**
356     * Returns a clone.
357     *
358     * @return A clone.
359     *
360     * @throws CloneNotSupportedException not thrown by this class, but may be
361     *                                    by subclasses.
362     */
363    @Override
364    public Object clone() throws CloneNotSupportedException {
365        return super.clone();
366    }
367
368    /**
369     * Provides serialization support.
370     *
371     * @param stream  the output stream ({@code null} not permitted).
372     *
373     * @throws IOException if there is an I/O error.
374     */
375    private void writeObject(ObjectOutputStream stream) throws IOException {
376        stream.defaultWriteObject();
377        SerialUtils.writeStroke(this.stroke, stream);
378        SerialUtils.writePaint(this.outlinePaint, stream);
379        SerialUtils.writePaint(this.fillPaint, stream);
380    }
381
382    /**
383     * Provides serialization support.
384     *
385     * @param stream  the input stream ({@code null} not permitted).
386     *
387     * @throws IOException  if there is an I/O error.
388     * @throws ClassNotFoundException  if there is a classpath problem.
389     */
390    private void readObject(ObjectInputStream stream)
391        throws IOException, ClassNotFoundException {
392
393        stream.defaultReadObject();
394        this.stroke = SerialUtils.readStroke(stream);
395        this.outlinePaint = SerialUtils.readPaint(stream);
396        this.fillPaint = SerialUtils.readPaint(stream);
397    }
398
399}