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 * CategoryLineAnnotation.java
029 * ---------------------------
030 * (C) Copyright 2005-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Peter Kolb (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;
051
052import org.jfree.chart.HashUtils;
053import org.jfree.chart.axis.CategoryAnchor;
054import org.jfree.chart.axis.CategoryAxis;
055import org.jfree.chart.axis.ValueAxis;
056import org.jfree.chart.event.AnnotationChangeEvent;
057import org.jfree.chart.plot.CategoryPlot;
058import org.jfree.chart.plot.Plot;
059import org.jfree.chart.plot.PlotOrientation;
060import org.jfree.chart.ui.RectangleEdge;
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.data.category.CategoryDataset;
066
067/**
068 * A line annotation that can be placed on a {@link CategoryPlot}.
069 */
070public class CategoryLineAnnotation extends AbstractAnnotation 
071        implements CategoryAnnotation, Cloneable, PublicCloneable,
072        Serializable {
073
074    /** For serialization. */
075    static final long serialVersionUID = 3477740483341587984L;
076
077    /** The category for the start of the line. */
078    private Comparable category1;
079
080    /** The value for the start of the line. */
081    private double value1;
082
083    /** The category for the end of the line. */
084    private Comparable category2;
085
086    /** The value for the end of the line. */
087    private double value2;
088
089    /** The line color. */
090    private transient Paint paint = Color.BLACK;
091
092    /** The line stroke. */
093    private transient Stroke stroke = new BasicStroke(1.0f);
094
095    /**
096     * Creates a new annotation that draws a line between (category1, value1)
097     * and (category2, value2).
098     *
099     * @param category1  the category ({@code null} not permitted).
100     * @param value1  the value (must be finite).
101     * @param category2  the category ({@code null} not permitted).
102     * @param value2  the value (must be finite).
103     * @param paint  the line color ({@code null} not permitted).
104     * @param stroke  the line stroke ({@code null} not permitted).
105     */
106    public CategoryLineAnnotation(Comparable category1, double value1,
107                                  Comparable category2, double value2,
108                                  Paint paint, Stroke stroke) {
109        super();
110        Args.nullNotPermitted(category1, "category1");
111        Args.requireFinite(value1, "value1");
112        Args.nullNotPermitted(category2, "category2");
113        Args.requireFinite(value2, "value2");
114        Args.nullNotPermitted(paint, "paint");
115        Args.nullNotPermitted(stroke, "stroke");
116        this.category1 = category1;
117        this.value1 = value1;
118        this.category2 = category2;
119        this.value2 = value2;
120        this.paint = paint;
121        this.stroke = stroke;
122    }
123
124    /**
125     * Returns the category for the start of the line.
126     *
127     * @return The category for the start of the line (never {@code null}).
128     *
129     * @see #setCategory1(Comparable)
130     */
131    public Comparable getCategory1() {
132        return this.category1;
133    }
134
135    /**
136     * Sets the category for the start of the line and sends an
137     * {@link AnnotationChangeEvent} to all registered listeners.
138     *
139     * @param category  the category ({@code null} not permitted).
140     *
141     * @see #getCategory1()
142     */
143    public void setCategory1(Comparable category) {
144        Args.nullNotPermitted(category, "category");
145        this.category1 = category;
146        fireAnnotationChanged();
147    }
148
149    /**
150     * Returns the y-value for the start of the line.
151     *
152     * @return The y-value for the start of the line.
153     *
154     * @see #setValue1(double)
155     */
156    public double getValue1() {
157        return this.value1;
158    }
159
160    /**
161     * Sets the y-value for the start of the line and sends an
162     * {@link AnnotationChangeEvent} to all registered listeners.
163     *
164     * @param value  the value (must be finite).
165     *
166     * @see #getValue1()
167     */
168    public void setValue1(double value) {
169        Args.requireFinite(value, "value");
170        this.value1 = value;
171        fireAnnotationChanged();
172    }
173
174    /**
175     * Returns the category for the end of the line.
176     *
177     * @return The category for the end of the line (never {@code null}).
178     *
179     * @see #setCategory2(Comparable)
180     */
181    public Comparable getCategory2() {
182        return this.category2;
183    }
184
185    /**
186     * Sets the category for the end of the line and sends an
187     * {@link AnnotationChangeEvent} to all registered listeners.
188     *
189     * @param category  the category ({@code null} not permitted).
190     *
191     * @see #getCategory2()
192     */
193    public void setCategory2(Comparable category) {
194        Args.nullNotPermitted(category, "category");
195        this.category2 = category;
196        fireAnnotationChanged();
197    }
198
199    /**
200     * Returns the y-value for the end of the line.
201     *
202     * @return The y-value for the end of the line.
203     *
204     * @see #setValue2(double)
205     */
206    public double getValue2() {
207        return this.value2;
208    }
209
210    /**
211     * Sets the y-value for the end of the line and sends an
212     * {@link AnnotationChangeEvent} to all registered listeners.
213     *
214     * @param value  the value (must be finite).
215     *
216     * @see #getValue2()
217     */
218    public void setValue2(double value) {
219        Args.requireFinite(value, "value");
220        this.value2 = value;
221        fireAnnotationChanged();
222    }
223
224    /**
225     * Returns the paint used to draw the connecting line.
226     *
227     * @return The paint (never {@code null}).
228     *
229     * @see #setPaint(Paint)
230     */
231    public Paint getPaint() {
232        return this.paint;
233    }
234
235    /**
236     * Sets the paint used to draw the connecting line and sends an
237     * {@link AnnotationChangeEvent} to all registered listeners.
238     *
239     * @param paint  the paint ({@code null} not permitted).
240     *
241     * @see #getPaint()
242     */
243    public void setPaint(Paint paint) {
244        Args.nullNotPermitted(paint, "paint");
245        this.paint = paint;
246        fireAnnotationChanged();
247    }
248
249    /**
250     * Returns the stroke used to draw the connecting line.
251     *
252     * @return The stroke (never {@code null}).
253     *
254     * @see #setStroke(Stroke)
255     */
256    public Stroke getStroke() {
257        return this.stroke;
258    }
259
260    /**
261     * Sets the stroke used to draw the connecting line and sends an
262     * {@link AnnotationChangeEvent} to all registered listeners.
263     *
264     * @param stroke  the stroke ({@code null} not permitted).
265     *
266     * @see #getStroke()
267     */
268    public void setStroke(Stroke stroke) {
269        Args.nullNotPermitted(stroke, "stroke");
270        this.stroke = stroke;
271        fireAnnotationChanged();
272    }
273
274    /**
275     * Draws the annotation.
276     *
277     * @param g2  the graphics device.
278     * @param plot  the plot.
279     * @param dataArea  the data area.
280     * @param domainAxis  the domain axis.
281     * @param rangeAxis  the range axis.
282     */
283    @Override
284    public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea,
285                     CategoryAxis domainAxis, ValueAxis rangeAxis) {
286
287        CategoryDataset dataset = plot.getDataset();
288        int catIndex1 = dataset.getColumnIndex(this.category1);
289        int catIndex2 = dataset.getColumnIndex(this.category2);
290        int catCount = dataset.getColumnCount();
291
292        double lineX1 = 0.0f;
293        double lineY1 = 0.0f;
294        double lineX2 = 0.0f;
295        double lineY2 = 0.0f;
296        PlotOrientation orientation = plot.getOrientation();
297        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
298            plot.getDomainAxisLocation(), orientation);
299        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
300            plot.getRangeAxisLocation(), orientation);
301
302        if (orientation == PlotOrientation.HORIZONTAL) {
303            lineY1 = domainAxis.getCategoryJava2DCoordinate(
304                CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
305                domainEdge);
306            lineX1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
307            lineY2 = domainAxis.getCategoryJava2DCoordinate(
308                CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
309                domainEdge);
310            lineX2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
311        } else if (orientation == PlotOrientation.VERTICAL) {
312            lineX1 = domainAxis.getCategoryJava2DCoordinate(
313                CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
314                domainEdge);
315            lineY1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
316            lineX2 = domainAxis.getCategoryJava2DCoordinate(
317                CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
318                domainEdge);
319            lineY2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
320        }
321        g2.setPaint(this.paint);
322        g2.setStroke(this.stroke);
323        g2.drawLine((int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2);
324    }
325
326    /**
327     * Tests this object for equality with another.
328     *
329     * @param obj  the object ({@code null} permitted).
330     *
331     * @return {@code true} or {@code false}.
332     */
333    @Override
334    public boolean equals(Object obj) {
335        if (obj == this) {
336            return true;
337        }
338        if (!(obj instanceof CategoryLineAnnotation)) {
339            return false;
340        }
341        CategoryLineAnnotation that = (CategoryLineAnnotation) obj;
342        if (!Objects.equals(this.category1, that.category1)) {
343            return false;
344        }
345        if (Double.doubleToLongBits(this.value1) !=
346            Double.doubleToLongBits(that.value1)) {
347            return false;
348        }
349        if (!Objects.equals(this.category2, that.category2)) {
350            return false;
351        }
352        if (Double.doubleToLongBits(this.value2) !=
353            Double.doubleToLongBits(that.value2)) {
354            return false;
355        }
356        if (!PaintUtils.equal(this.paint, that.paint)) {
357            return false;
358        }
359        if (!Objects.equals(this.stroke, that.stroke)) {
360            return false;
361        }
362        // fix the "equals not symmetric" problem
363        if (!that.canEqual(this)) {
364            return false;
365        }
366
367        return super.equals(obj);
368    }
369
370    /**
371     * Ensures symmetry between super/subclass implementations of equals. For
372     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
373     *
374     * @param other Object
375     * 
376     * @return true ONLY if the parameter is THIS class type
377     */
378    @Override
379    public boolean canEqual(Object other) {
380        // fix the "equals not symmetric" problem
381        return (other instanceof CategoryLineAnnotation);
382    }
383    
384    /**
385     * Returns a hash code for this instance.
386     *
387     * @return A hash code.
388     */
389    @Override
390    public int hashCode() {
391        int result = super.hashCode(); // equals calls superclass, hashCode must also
392        result = 37 * result + Objects.hashCode(this.category1);
393        long temp = Double.doubleToLongBits(this.value1);
394        result = 37 * result + (int) (temp ^ (temp >>> 32));
395        result = 37 * result + Objects.hashCode(this.category2);
396        temp = Double.doubleToLongBits(this.value2);
397        result = 37 * result + (int) (temp ^ (temp >>> 32));
398        result = 37 * result + HashUtils.hashCodeForPaint(this.paint);
399        result = 37 * result + Objects.hashCode(this.stroke);
400        return result;
401    }
402
403    /**
404     * Returns a clone of the annotation.
405     *
406     * @return A clone.
407     *
408     * @throws CloneNotSupportedException  this class will not throw this
409     *         exception, but subclasses (if any) might.
410     */
411    @Override
412    public Object clone() throws CloneNotSupportedException {
413        return super.clone();
414    }
415
416    /**
417     * Provides serialization support.
418     *
419     * @param stream  the output stream.
420     *
421     * @throws IOException if there is an I/O error.
422     */
423    private void writeObject(ObjectOutputStream stream) throws IOException {
424        stream.defaultWriteObject();
425        SerialUtils.writePaint(this.paint, stream);
426        SerialUtils.writeStroke(this.stroke, stream);
427    }
428
429    /**
430     * Provides serialization support.
431     *
432     * @param stream  the input stream.
433     *
434     * @throws IOException  if there is an I/O error.
435     * @throws ClassNotFoundException  if there is a classpath problem.
436     */
437    private void readObject(ObjectInputStream stream)
438        throws IOException, ClassNotFoundException {
439        stream.defaultReadObject();
440        this.paint = SerialUtils.readPaint(stream);
441        this.stroke = SerialUtils.readStroke(stream);
442    }
443
444}