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