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