001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, 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 * Crosshair.java
029 * --------------
030 * (C) Copyright 2009-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.chart.plot;
038
039import java.awt.BasicStroke;
040import java.awt.Color;
041import java.awt.Font;
042import java.awt.Paint;
043import java.awt.Stroke;
044import java.beans.PropertyChangeListener;
045import java.beans.PropertyChangeSupport;
046import java.io.IOException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.io.Serializable;
050import org.jfree.chart.internal.HashUtils;
051import org.jfree.chart.labels.CrosshairLabelGenerator;
052import org.jfree.chart.labels.StandardCrosshairLabelGenerator;
053import org.jfree.chart.swing.CrosshairOverlay;
054import org.jfree.chart.api.RectangleAnchor;
055import org.jfree.chart.internal.PaintUtils;
056import org.jfree.chart.internal.Args;
057import org.jfree.chart.api.PublicCloneable;
058import org.jfree.chart.internal.SerialUtils;
059
060/**
061 * A {@code Crosshair} represents a value on a chart and is usually displayed
062 * as a line perpendicular to the x or y-axis (and sometimes including a label
063 * that shows the crosshair value as text).  Instances of this class are used
064 * to store the cross hair value plus the visual characteristics of the line
065 * that will be rendered once the instance is added to a 
066 * {@link CrosshairOverlay} (or {@code CrosshairOverlaydFX} if you are using 
067 * the JavaFX extensions for JFreeChart).
068 * <br><br>
069 * Crosshairs support a property change mechanism which is used by JFreeChart
070 * to automatically repaint the overlay whenever a crosshair attribute is 
071 * updated.
072 */
073public class Crosshair implements Cloneable, PublicCloneable, Serializable {
074
075    /** Flag controlling visibility. */
076    private boolean visible;
077
078    /** The crosshair value. */
079    private double value;
080
081    /** The paint for the crosshair line. */
082    private transient Paint paint;
083
084    /** The stroke for the crosshair line. */
085    private transient Stroke stroke;
086
087    /**
088     * A flag that controls whether or not the crosshair has a label
089     * visible.
090     */
091    private boolean labelVisible;
092
093    /**
094     * The label anchor.
095     */
096    private RectangleAnchor labelAnchor;
097
098    /** A label generator. */
099    private CrosshairLabelGenerator labelGenerator;
100
101    /**
102     * The x-offset in Java2D units.
103     */
104    private double labelXOffset;
105
106    /**
107     * The y-offset in Java2D units.
108     */
109    private double labelYOffset;
110
111    /**
112     * The label font.
113     */
114    private Font labelFont;
115
116    /**
117     * The label paint.
118     */
119    private transient Paint labelPaint;
120
121    /**
122     * The label background paint.
123     */
124    private transient Paint labelBackgroundPaint;
125
126    /** A flag that controls the visibility of the label outline. */
127    private boolean labelOutlineVisible;
128
129    /** The label outline stroke. */
130    private transient Stroke labelOutlineStroke;
131
132    /** The label outline paint. */
133    private transient Paint labelOutlinePaint;
134
135    /** Property change support. */
136    private transient PropertyChangeSupport pcs;
137
138    /**
139     * Creates a new crosshair with value 0.0.
140     */
141    public Crosshair() {
142        this(0.0);
143    }
144
145    /**
146     * Creates a new crosshair with the specified value.
147     *
148     * @param value  the value.
149     */
150    public Crosshair(double value) {
151       this(value, Color.BLACK, new BasicStroke(1.0f));
152    }
153
154    /**
155     * Creates a new crosshair value with the specified value and line style.
156     *
157     * @param value  the value.
158     * @param paint  the line paint ({@code null} not permitted).
159     * @param stroke  the line stroke ({@code null} not permitted).
160     */
161    public Crosshair(double value, Paint paint, Stroke stroke) {
162        Args.nullNotPermitted(paint, "paint");
163        Args.nullNotPermitted(stroke, "stroke");
164        this.visible = true;
165        this.value = value;
166        this.paint = paint;
167        this.stroke = stroke;
168        this.labelVisible = false;
169        this.labelGenerator = new StandardCrosshairLabelGenerator();
170        this.labelAnchor = RectangleAnchor.BOTTOM_LEFT;
171        this.labelXOffset = 5.0;
172        this.labelYOffset = 5.0;
173        this.labelFont = new Font("Tahoma", Font.PLAIN, 12);
174        this.labelPaint = Color.BLACK;
175        this.labelBackgroundPaint = new Color(0, 0, 255, 63);
176        this.labelOutlineVisible = true;
177        this.labelOutlinePaint = Color.BLACK;
178        this.labelOutlineStroke = new BasicStroke(0.5f);
179        this.pcs = new PropertyChangeSupport(this);
180    }
181
182    /**
183     * Returns the flag that indicates whether or not the crosshair is
184     * currently visible.
185     *
186     * @return A boolean.
187     *
188     * @see #setVisible(boolean)
189     */
190    public boolean isVisible() {
191        return this.visible;
192    }
193
194    /**
195     * Sets the flag that controls the visibility of the crosshair and sends
196     * a proerty change event (with the name 'visible') to all registered
197     * listeners.
198     *
199     * @param visible  the new flag value.
200     *
201     * @see #isVisible()
202     */
203    public void setVisible(boolean visible) {
204        boolean old = this.visible;
205        this.visible = visible;
206        this.pcs.firePropertyChange("visible", old, visible);
207    }
208
209    /**
210     * Returns the crosshair value.
211     *
212     * @return The crosshair value.
213     *
214     * @see #setValue(double)
215     */
216    public double getValue() {
217        return this.value;
218    }
219
220    /**
221     * Sets the crosshair value and sends a property change event with the name
222     * 'value' to all registered listeners.
223     *
224     * @param value  the value.
225     *
226     * @see #getValue()
227     */
228    public void setValue(double value) {
229        Double oldValue = this.value;
230        this.value = value;
231        this.pcs.firePropertyChange("value", oldValue, value);
232    }
233
234    /**
235     * Returns the paint for the crosshair line.
236     *
237     * @return The paint (never {@code null}).
238     *
239     * @see #setPaint(java.awt.Paint)
240     */
241    public Paint getPaint() {
242        return this.paint;
243    }
244
245    /**
246     * Sets the paint for the crosshair line and sends a property change event
247     * with the name "paint" to all registered listeners.
248     *
249     * @param paint  the paint ({@code null} not permitted).
250     *
251     * @see #getPaint()
252     */
253    public void setPaint(Paint paint) {
254        Paint old = this.paint;
255        this.paint = paint;
256        this.pcs.firePropertyChange("paint", old, paint);
257    }
258
259    /**
260     * Returns the stroke for the crosshair line.
261     *
262     * @return The stroke (never {@code null}).
263     *
264     * @see #setStroke(java.awt.Stroke)
265     */
266    public Stroke getStroke() {
267        return this.stroke;
268    }
269
270    /**
271     * Sets the stroke for the crosshair line and sends a property change event
272     * with the name "stroke" to all registered listeners.
273     *
274     * @param stroke  the stroke ({@code null} not permitted).
275     *
276     * @see #getStroke()
277     */
278    public void setStroke(Stroke stroke) {
279        Stroke old = this.stroke;
280        this.stroke = stroke;
281        this.pcs.firePropertyChange("stroke", old, stroke);
282    }
283
284    /**
285     * Returns the flag that controls whether or not a label is drawn for
286     * this crosshair.
287     *
288     * @return A boolean.
289     *
290     * @see #setLabelVisible(boolean)
291     */
292    public boolean isLabelVisible() {
293        return this.labelVisible;
294    }
295
296    /**
297     * Sets the flag that controls whether or not a label is drawn for the
298     * crosshair and sends a property change event (with the name
299     * 'labelVisible') to all registered listeners.
300     *
301     * @param visible  the new flag value.
302     *
303     * @see #isLabelVisible()
304     */
305    public void setLabelVisible(boolean visible) {
306        boolean old = this.labelVisible;
307        this.labelVisible = visible;
308        this.pcs.firePropertyChange("labelVisible", old, visible);
309    }
310
311    /**
312     * Returns the crosshair label generator.
313     *
314     * @return The label crosshair generator (never {@code null}).
315     *
316     * @see #setLabelGenerator(org.jfree.chart.labels.CrosshairLabelGenerator)
317     */
318    public CrosshairLabelGenerator getLabelGenerator() {
319        return this.labelGenerator;
320    }
321
322    /**
323     * Sets the crosshair label generator and sends a property change event
324     * (with the name 'labelGenerator') to all registered listeners.
325     *
326     * @param generator  the new generator ({@code null} not permitted).
327     *
328     * @see #getLabelGenerator()
329     */
330    public void setLabelGenerator(CrosshairLabelGenerator generator) {
331        Args.nullNotPermitted(generator, "generator");
332        CrosshairLabelGenerator old = this.labelGenerator;
333        this.labelGenerator = generator;
334        this.pcs.firePropertyChange("labelGenerator", old, generator);
335    }
336
337    /**
338     * Returns the label anchor point.
339     *
340     * @return the label anchor point (never {@code null}).
341     *
342     * @see #setLabelAnchor(org.jfree.chart.ui.RectangleAnchor)
343     */
344    public RectangleAnchor getLabelAnchor() {
345        return this.labelAnchor;
346    }
347
348    /**
349     * Sets the label anchor point and sends a property change event (with the
350     * name 'labelAnchor') to all registered listeners.
351     *
352     * @param anchor  the anchor ({@code null} not permitted).
353     *
354     * @see #getLabelAnchor()
355     */
356    public void setLabelAnchor(RectangleAnchor anchor) {
357        RectangleAnchor old = this.labelAnchor;
358        this.labelAnchor = anchor;
359        this.pcs.firePropertyChange("labelAnchor", old, anchor);
360    }
361
362    /**
363     * Returns the x-offset for the label (in Java2D units).
364     *
365     * @return The x-offset.
366     *
367     * @see #setLabelXOffset(double)
368     */
369    public double getLabelXOffset() {
370        return this.labelXOffset;
371    }
372
373    /**
374     * Sets the x-offset and sends a property change event (with the name
375     * 'labelXOffset') to all registered listeners.
376     *
377     * @param offset  the new offset.
378     *
379     * @see #getLabelXOffset()
380     */
381    public void setLabelXOffset(double offset) {
382        Double old = this.labelXOffset;
383        this.labelXOffset = offset;
384        this.pcs.firePropertyChange("labelXOffset", old, offset);
385    }
386
387    /**
388     * Returns the y-offset for the label (in Java2D units).
389     *
390     * @return The y-offset.
391     *
392     * @see #setLabelYOffset(double)
393     */
394    public double getLabelYOffset() {
395        return this.labelYOffset;
396    }
397
398    /**
399     * Sets the y-offset and sends a property change event (with the name
400     * 'labelYOffset') to all registered listeners.
401     *
402     * @param offset  the new offset.
403     *
404     * @see #getLabelYOffset()
405     */
406    public void setLabelYOffset(double offset) {
407        Double old = this.labelYOffset;
408        this.labelYOffset = offset;
409        this.pcs.firePropertyChange("labelYOffset", old, offset);
410    }
411
412    /**
413     * Returns the label font.
414     *
415     * @return The label font (never {@code null}).
416     *
417     * @see #setLabelFont(java.awt.Font)
418     */
419    public Font getLabelFont() {
420        return this.labelFont;
421    }
422
423    /**
424     * Sets the label font and sends a property change event (with the name
425     * 'labelFont') to all registered listeners.
426     *
427     * @param font  the font ({@code null} not permitted).
428     *
429     * @see #getLabelFont()
430     */
431    public void setLabelFont(Font font) {
432        Args.nullNotPermitted(font, "font");
433        Font old = this.labelFont;
434        this.labelFont = font;
435        this.pcs.firePropertyChange("labelFont", old, font);
436    }
437
438    /**
439     * Returns the label paint.  The default value is {@code Color.BLACK}.
440     *
441     * @return The label paint (never {@code null}).
442     *
443     * @see #setLabelPaint
444     */
445    public Paint getLabelPaint() {
446        return this.labelPaint;
447    }
448
449    /**
450     * Sets the label paint and sends a property change event (with the name
451     * 'labelPaint') to all registered listeners.
452     *
453     * @param paint  the paint ({@code null} not permitted).
454     *
455     * @see #getLabelPaint()
456     */
457    public void setLabelPaint(Paint paint) {
458        Args.nullNotPermitted(paint, "paint");
459        Paint old = this.labelPaint;
460        this.labelPaint = paint;
461        this.pcs.firePropertyChange("labelPaint", old, paint);
462    }
463
464    /**
465     * Returns the label background paint.
466     *
467     * @return The label background paint (possibly {@code null}).
468     *
469     * @see #setLabelBackgroundPaint(java.awt.Paint)
470     */
471    public Paint getLabelBackgroundPaint() {
472        return this.labelBackgroundPaint;
473    }
474
475    /**
476     * Sets the label background paint and sends a property change event with
477     * the name 'labelBackgroundPaint') to all registered listeners.
478     *
479     * @param paint  the paint ({@code null} permitted).
480     *
481     * @see #getLabelBackgroundPaint()
482     */
483    public void setLabelBackgroundPaint(Paint paint) {
484        Paint old = this.labelBackgroundPaint;
485        this.labelBackgroundPaint = paint;
486        this.pcs.firePropertyChange("labelBackgroundPaint", old, paint);
487    }
488
489    /**
490     * Returns the flag that controls the visibility of the label outline.
491     * The default value is {@code true}.
492     *
493     * @return A boolean.
494     *
495     * @see #setLabelOutlineVisible(boolean)
496     */
497    public boolean isLabelOutlineVisible() {
498        return this.labelOutlineVisible;
499    }
500
501    /**
502     * Sets the flag that controls the visibility of the label outlines and
503     * sends a property change event (with the name "labelOutlineVisible") to
504     * all registered listeners.
505     *
506     * @param visible  the new flag value.
507     *
508     * @see #isLabelOutlineVisible()
509     */
510    public void setLabelOutlineVisible(boolean visible) {
511        boolean old = this.labelOutlineVisible;
512        this.labelOutlineVisible = visible;
513        this.pcs.firePropertyChange("labelOutlineVisible", old, visible);
514    }
515
516    /**
517     * Returns the label outline paint.
518     *
519     * @return The label outline paint (never {@code null}).
520     *
521     * @see #setLabelOutlinePaint(java.awt.Paint)
522     */
523    public Paint getLabelOutlinePaint() {
524        return this.labelOutlinePaint;
525    }
526
527    /**
528     * Sets the label outline paint and sends a property change event (with the
529     * name "labelOutlinePaint") to all registered listeners.
530     *
531     * @param paint  the paint ({@code null} not permitted).
532     *
533     * @see #getLabelOutlinePaint()
534     */
535    public void setLabelOutlinePaint(Paint paint) {
536        Args.nullNotPermitted(paint, "paint");
537        Paint old = this.labelOutlinePaint;
538        this.labelOutlinePaint = paint;
539        this.pcs.firePropertyChange("labelOutlinePaint", old, paint);
540    }
541
542    /**
543     * Returns the label outline stroke. The default value is 
544     * {@code BasicStroke(0.5)}.
545     *
546     * @return The label outline stroke (never {@code null}).
547     *
548     * @see #setLabelOutlineStroke(java.awt.Stroke)
549     */
550    public Stroke getLabelOutlineStroke() {
551        return this.labelOutlineStroke;
552    }
553
554    /**
555     * Sets the label outline stroke and sends a property change event (with
556     * the name 'labelOutlineStroke') to all registered listeners.
557     *
558     * @param stroke  the stroke ({@code null} not permitted).
559     *
560     * @see #getLabelOutlineStroke()
561     */
562    public void setLabelOutlineStroke(Stroke stroke) {
563        Args.nullNotPermitted(stroke, "stroke");
564        Stroke old = this.labelOutlineStroke;
565        this.labelOutlineStroke = stroke;
566        this.pcs.firePropertyChange("labelOutlineStroke", old, stroke);
567    }
568
569    /**
570     * Tests this crosshair for equality with an arbitrary object.
571     *
572     * @param obj  the object ({@code null} permitted).
573     *
574     * @return A boolean.
575     */
576    @Override
577    public boolean equals(Object obj) {
578        if (obj == this) {
579            return true;
580        }
581        if (!(obj instanceof Crosshair)) {
582            return false;
583        }
584        Crosshair that = (Crosshair) obj;
585        if (this.visible != that.visible) {
586            return false;
587        }
588        if (this.value != that.value) {
589            return false;
590        }
591        if (!PaintUtils.equal(this.paint, that.paint)) {
592            return false;
593        }
594        if (!this.stroke.equals(that.stroke)) {
595            return false;
596        }
597        if (this.labelVisible != that.labelVisible) {
598            return false;
599        }
600        if (!this.labelGenerator.equals(that.labelGenerator)) {
601            return false;
602        }
603        if (!this.labelAnchor.equals(that.labelAnchor)) {
604            return false;
605        }
606        if (this.labelXOffset != that.labelXOffset) {
607            return false;
608        }
609        if (this.labelYOffset != that.labelYOffset) {
610            return false;
611        }
612        if (!this.labelFont.equals(that.labelFont)) {
613            return false;
614        }
615        if (!PaintUtils.equal(this.labelPaint, that.labelPaint)) {
616            return false;
617        }
618        if (!PaintUtils.equal(this.labelBackgroundPaint,
619                that.labelBackgroundPaint)) {
620            return false;
621        }
622        if (this.labelOutlineVisible != that.labelOutlineVisible) {
623            return false;
624        }
625        if (!PaintUtils.equal(this.labelOutlinePaint,
626                that.labelOutlinePaint)) {
627            return false;
628        }
629        if (!this.labelOutlineStroke.equals(that.labelOutlineStroke)) {
630            return false;
631        }
632        return true;  // can't find any difference
633    }
634
635    /**
636     * Returns a hash code for this instance.
637     *
638     * @return A hash code.
639     */
640    @Override
641    public int hashCode() {
642        int hash = 7;
643        hash = HashUtils.hashCode(hash, this.visible);
644        hash = HashUtils.hashCode(hash, this.value);
645        hash = HashUtils.hashCode(hash, this.paint);
646        hash = HashUtils.hashCode(hash, this.stroke);
647        hash = HashUtils.hashCode(hash, this.labelVisible);
648        hash = HashUtils.hashCode(hash, this.labelAnchor);
649        hash = HashUtils.hashCode(hash, this.labelGenerator);
650        hash = HashUtils.hashCode(hash, this.labelXOffset);
651        hash = HashUtils.hashCode(hash, this.labelYOffset);
652        hash = HashUtils.hashCode(hash, this.labelFont);
653        hash = HashUtils.hashCode(hash, this.labelPaint);
654        hash = HashUtils.hashCode(hash, this.labelBackgroundPaint);
655        hash = HashUtils.hashCode(hash, this.labelOutlineVisible);
656        hash = HashUtils.hashCode(hash, this.labelOutlineStroke);
657        hash = HashUtils.hashCode(hash, this.labelOutlinePaint);
658        return hash;
659    }
660
661    /**
662     * Returns an independent copy of this instance.
663     *
664     * @return An independent copy of this instance.
665     *
666     * @throws java.lang.CloneNotSupportedException if there is a problem with
667     *         cloning.
668     */
669    @Override
670    public Object clone() throws CloneNotSupportedException {
671        // FIXME: clone generator
672        return super.clone();
673    }
674
675    /**
676     * Adds a property change listener.
677     *
678     * @param l  the listener.
679     *
680     * @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
681     */
682    public void addPropertyChangeListener(PropertyChangeListener l) {
683        this.pcs.addPropertyChangeListener(l);
684    }
685
686    /**
687     * Removes a property change listener.
688     *
689     * @param l  the listener.
690     *
691     * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) 
692     */
693    public void removePropertyChangeListener(PropertyChangeListener l) {
694        this.pcs.removePropertyChangeListener(l);
695    }
696
697    /**
698     * Provides serialization support.
699     *
700     * @param stream  the output stream.
701     *
702     * @throws IOException  if there is an I/O error.
703     */
704    private void writeObject(ObjectOutputStream stream) throws IOException {
705        stream.defaultWriteObject();
706        SerialUtils.writePaint(this.paint, stream);
707        SerialUtils.writeStroke(this.stroke, stream);
708        SerialUtils.writePaint(this.labelPaint, stream);
709        SerialUtils.writePaint(this.labelBackgroundPaint, stream);
710        SerialUtils.writeStroke(this.labelOutlineStroke, stream);
711        SerialUtils.writePaint(this.labelOutlinePaint, stream);
712    }
713
714    /**
715     * Provides serialization support.
716     *
717     * @param stream  the input stream.
718     *
719     * @throws IOException  if there is an I/O error.
720     * @throws ClassNotFoundException  if there is a classpath problem.
721     */
722    private void readObject(ObjectInputStream stream)
723            throws IOException, ClassNotFoundException {
724        stream.defaultReadObject();
725        this.paint = SerialUtils.readPaint(stream);
726        this.stroke = SerialUtils.readStroke(stream);
727        this.labelPaint = SerialUtils.readPaint(stream);
728        this.labelBackgroundPaint = SerialUtils.readPaint(stream);
729        this.labelOutlineStroke = SerialUtils.readStroke(stream);
730        this.labelOutlinePaint = SerialUtils.readPaint(stream);
731        this.pcs = new PropertyChangeSupport(this);
732    }
733
734}