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