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