001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * DialPointer.java
029 * ----------------
030 * (C) Copyright 2006-2013, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes
036 * -------
037 * 03-Nov-2006 : Version 1 (DG);
038 * 17-Oct-2007 : Added equals() overrides (DG);
039 * 24-Oct-2007 : Implemented PublicCloneable, changed default radius,
040 *               and added argument checks (DG);
041 * 23-Nov-2007 : Added fillPaint and outlinePaint attributes to
042 *               DialPointer.Pointer (DG);
043 * 03-Jul-2013 : Use ParamChecks (DG);
044 *
045 */
046
047package org.jfree.chart.plot.dial;
048
049import java.awt.BasicStroke;
050import java.awt.Color;
051import java.awt.Graphics2D;
052import java.awt.Paint;
053import java.awt.Stroke;
054import java.awt.geom.Arc2D;
055import java.awt.geom.GeneralPath;
056import java.awt.geom.Line2D;
057import java.awt.geom.Point2D;
058import java.awt.geom.Rectangle2D;
059import java.io.IOException;
060import java.io.ObjectInputStream;
061import java.io.ObjectOutputStream;
062import java.io.Serializable;
063
064import org.jfree.chart.HashUtilities;
065import org.jfree.chart.util.ParamChecks;
066import org.jfree.io.SerialUtilities;
067import org.jfree.util.PaintUtilities;
068import org.jfree.util.PublicCloneable;
069
070/**
071 * A base class for the pointer in a {@link DialPlot}.
072 *
073 * @since 1.0.7
074 */
075public abstract class DialPointer extends AbstractDialLayer
076        implements DialLayer, Cloneable, PublicCloneable, Serializable {
077
078    /** The needle radius. */
079    double radius;
080
081    /**
082     * The dataset index for the needle.
083     */
084    int datasetIndex;
085
086    /**
087     * Creates a new <code>DialPointer</code> instance.
088     */
089    protected DialPointer() {
090        this(0);
091    }
092
093    /**
094     * Creates a new pointer for the specified dataset.
095     *
096     * @param datasetIndex  the dataset index.
097     */
098    protected DialPointer(int datasetIndex) {
099        this.radius = 0.9;
100        this.datasetIndex = datasetIndex;
101    }
102
103    /**
104     * Returns the dataset index that the pointer maps to.
105     *
106     * @return The dataset index.
107     *
108     * @see #getDatasetIndex()
109     */
110    public int getDatasetIndex() {
111        return this.datasetIndex;
112    }
113
114    /**
115     * Sets the dataset index for the pointer and sends a
116     * {@link DialLayerChangeEvent} to all registered listeners.
117     *
118     * @param index  the index.
119     *
120     * @see #getDatasetIndex()
121     */
122    public void setDatasetIndex(int index) {
123        this.datasetIndex = index;
124        notifyListeners(new DialLayerChangeEvent(this));
125    }
126
127    /**
128     * Returns the radius of the pointer, as a percentage of the dial's
129     * framing rectangle.
130     *
131     * @return The radius.
132     *
133     * @see #setRadius(double)
134     */
135    public double getRadius() {
136        return this.radius;
137    }
138
139    /**
140     * Sets the radius of the pointer and sends a
141     * {@link DialLayerChangeEvent} to all registered listeners.
142     *
143     * @param radius  the radius.
144     *
145     * @see #getRadius()
146     */
147    public void setRadius(double radius) {
148        this.radius = radius;
149        notifyListeners(new DialLayerChangeEvent(this));
150    }
151
152    /**
153     * Returns <code>true</code> to indicate that this layer should be
154     * clipped within the dial window.
155     *
156     * @return <code>true</code>.
157     */
158    @Override
159    public boolean isClippedToWindow() {
160        return true;
161    }
162
163    /**
164     * Checks this instance for equality with an arbitrary object.
165     *
166     * @param obj  the object (<code>null</code> not permitted).
167     *
168     * @return A boolean.
169     */
170    @Override
171    public boolean equals(Object obj) {
172        if (obj == this) {
173            return true;
174        }
175        if (!(obj instanceof DialPointer)) {
176            return false;
177        }
178        DialPointer that = (DialPointer) obj;
179        if (this.datasetIndex != that.datasetIndex) {
180            return false;
181        }
182        if (this.radius != that.radius) {
183            return false;
184        }
185        return super.equals(obj);
186    }
187
188    /**
189     * Returns a hash code.
190     *
191     * @return A hash code.
192     */
193    @Override
194    public int hashCode() {
195        int result = 23;
196        result = HashUtilities.hashCode(result, this.radius);
197        return result;
198    }
199
200    /**
201     * Returns a clone of the pointer.
202     *
203     * @return a clone.
204     *
205     * @throws CloneNotSupportedException if one of the attributes cannot
206     *     be cloned.
207     */
208    @Override
209    public Object clone() throws CloneNotSupportedException {
210        return super.clone();
211    }
212
213    /**
214     * A dial pointer that draws a thin line (like a pin).
215     */
216    public static class Pin extends DialPointer {
217
218        /** For serialization. */
219        static final long serialVersionUID = -8445860485367689750L;
220
221        /** The paint. */
222        private transient Paint paint;
223
224        /** The stroke. */
225        private transient Stroke stroke;
226
227        /**
228         * Creates a new instance.
229         */
230        public Pin() {
231            this(0);
232        }
233
234        /**
235         * Creates a new instance.
236         *
237         * @param datasetIndex  the dataset index.
238         */
239        public Pin(int datasetIndex) {
240            super(datasetIndex);
241            this.paint = Color.red;
242            this.stroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND,
243                    BasicStroke.JOIN_BEVEL);
244        }
245
246        /**
247         * Returns the paint.
248         *
249         * @return The paint (never <code>null</code>).
250         *
251         * @see #setPaint(Paint)
252         */
253        public Paint getPaint() {
254            return this.paint;
255        }
256
257        /**
258         * Sets the paint and sends a {@link DialLayerChangeEvent} to all
259         * registered listeners.
260         *
261         * @param paint  the paint (<code>null</code> not permitted).
262         *
263         * @see #getPaint()
264         */
265        public void setPaint(Paint paint) {
266            ParamChecks.nullNotPermitted(paint, "paint");
267            this.paint = paint;
268            notifyListeners(new DialLayerChangeEvent(this));
269        }
270
271        /**
272         * Returns the stroke.
273         *
274         * @return The stroke (never <code>null</code>).
275         *
276         * @see #setStroke(Stroke)
277         */
278        public Stroke getStroke() {
279            return this.stroke;
280        }
281
282        /**
283         * Sets the stroke and sends a {@link DialLayerChangeEvent} to all
284         * registered listeners.
285         *
286         * @param stroke  the stroke (<code>null</code> not permitted).
287         *
288         * @see #getStroke()
289         */
290        public void setStroke(Stroke stroke) {
291            ParamChecks.nullNotPermitted(stroke, "stroke");
292            this.stroke = stroke;
293            notifyListeners(new DialLayerChangeEvent(this));
294        }
295
296        /**
297         * Draws the pointer.
298         *
299         * @param g2  the graphics target.
300         * @param plot  the plot.
301         * @param frame  the dial's reference frame.
302         * @param view  the dial's view.
303         */
304        @Override
305        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
306            Rectangle2D view) {
307
308            g2.setPaint(this.paint);
309            g2.setStroke(this.stroke);
310            Rectangle2D arcRect = DialPlot.rectangleByRadius(frame,
311                    this.radius, this.radius);
312
313            double value = plot.getValue(this.datasetIndex);
314            DialScale scale = plot.getScaleForDataset(this.datasetIndex);
315            double angle = scale.valueToAngle(value);
316
317            Arc2D arc = new Arc2D.Double(arcRect, angle, 0, Arc2D.OPEN);
318            Point2D pt = arc.getEndPoint();
319
320            Line2D line = new Line2D.Double(frame.getCenterX(),
321                    frame.getCenterY(), pt.getX(), pt.getY());
322            g2.draw(line);
323        }
324
325        /**
326         * Tests this pointer for equality with an arbitrary object.
327         *
328         * @param obj  the object (<code>null</code> permitted).
329         *
330         * @return A boolean.
331         */
332        @Override
333        public boolean equals(Object obj) {
334            if (obj == this) {
335                return true;
336            }
337            if (!(obj instanceof DialPointer.Pin)) {
338                return false;
339            }
340            DialPointer.Pin that = (DialPointer.Pin) obj;
341            if (!PaintUtilities.equal(this.paint, that.paint)) {
342                return false;
343            }
344            if (!this.stroke.equals(that.stroke)) {
345                return false;
346            }
347            return super.equals(obj);
348        }
349
350        /**
351         * Returns a hash code for this instance.
352         *
353         * @return A hash code.
354         */
355        @Override
356        public int hashCode() {
357            int result = super.hashCode();
358            result = HashUtilities.hashCode(result, this.paint);
359            result = HashUtilities.hashCode(result, this.stroke);
360            return result;
361        }
362
363        /**
364         * Provides serialization support.
365         *
366         * @param stream  the output stream.
367         *
368         * @throws IOException  if there is an I/O error.
369         */
370        private void writeObject(ObjectOutputStream stream) throws IOException {
371            stream.defaultWriteObject();
372            SerialUtilities.writePaint(this.paint, stream);
373            SerialUtilities.writeStroke(this.stroke, stream);
374        }
375
376        /**
377         * Provides serialization support.
378         *
379         * @param stream  the input stream.
380         *
381         * @throws IOException  if there is an I/O error.
382         * @throws ClassNotFoundException  if there is a classpath problem.
383         */
384        private void readObject(ObjectInputStream stream)
385                throws IOException, ClassNotFoundException {
386            stream.defaultReadObject();
387            this.paint = SerialUtilities.readPaint(stream);
388            this.stroke = SerialUtilities.readStroke(stream);
389        }
390
391    }
392
393    /**
394     * A dial pointer.
395     */
396    public static class Pointer extends DialPointer {
397
398        /** For serialization. */
399        static final long serialVersionUID = -4180500011963176960L;
400
401        /**
402         * The radius that defines the width of the pointer at the base.
403         */
404        private double widthRadius;
405
406        /**
407         * The fill paint.
408         *
409         * @since 1.0.8
410         */
411        private transient Paint fillPaint;
412
413        /**
414         * The outline paint.
415         *
416         * @since 1.0.8
417         */
418        private transient Paint outlinePaint;
419
420        /**
421         * Creates a new instance.
422         */
423        public Pointer() {
424            this(0);
425        }
426
427        /**
428         * Creates a new instance.
429         *
430         * @param datasetIndex  the dataset index.
431         */
432        public Pointer(int datasetIndex) {
433            super(datasetIndex);
434            this.widthRadius = 0.05;
435            this.fillPaint = Color.gray;
436            this.outlinePaint = Color.black;
437        }
438
439        /**
440         * Returns the width radius.
441         *
442         * @return The width radius.
443         *
444         * @see #setWidthRadius(double)
445         */
446        public double getWidthRadius() {
447            return this.widthRadius;
448        }
449
450        /**
451         * Sets the width radius and sends a {@link DialLayerChangeEvent} to
452         * all registered listeners.
453         *
454         * @param radius  the radius
455         *
456         * @see #getWidthRadius()
457         */
458        public void setWidthRadius(double radius) {
459            this.widthRadius = radius;
460            notifyListeners(new DialLayerChangeEvent(this));
461        }
462
463        /**
464         * Returns the fill paint.
465         *
466         * @return The paint (never <code>null</code>).
467         *
468         * @see #setFillPaint(Paint)
469         *
470         * @since 1.0.8
471         */
472        public Paint getFillPaint() {
473            return this.fillPaint;
474        }
475
476        /**
477         * Sets the fill paint and sends a {@link DialLayerChangeEvent} to all
478         * registered listeners.
479         *
480         * @param paint  the paint (<code>null</code> not permitted).
481         *
482         * @see #getFillPaint()
483         *
484         * @since 1.0.8
485         */
486        public void setFillPaint(Paint paint) {
487            ParamChecks.nullNotPermitted(paint, "paint");
488            this.fillPaint = paint;
489            notifyListeners(new DialLayerChangeEvent(this));
490        }
491
492        /**
493         * Returns the outline paint.
494         *
495         * @return The paint (never <code>null</code>).
496         *
497         * @see #setOutlinePaint(Paint)
498         *
499         * @since 1.0.8
500         */
501        public Paint getOutlinePaint() {
502            return this.outlinePaint;
503        }
504
505        /**
506         * Sets the outline paint and sends a {@link DialLayerChangeEvent} to
507         * all registered listeners.
508         *
509         * @param paint  the paint (<code>null</code> not permitted).
510         *
511         * @see #getOutlinePaint()
512         *
513         * @since 1.0.8
514         */
515        public void setOutlinePaint(Paint paint) {
516            ParamChecks.nullNotPermitted(paint, "paint");
517            this.outlinePaint = paint;
518            notifyListeners(new DialLayerChangeEvent(this));
519        }
520
521        /**
522         * Draws the pointer.
523         *
524         * @param g2  the graphics target.
525         * @param plot  the plot.
526         * @param frame  the dial's reference frame.
527         * @param view  the dial's view.
528         */
529        @Override
530        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
531                Rectangle2D view) {
532
533            g2.setPaint(Color.blue);
534            g2.setStroke(new BasicStroke(1.0f));
535            Rectangle2D lengthRect = DialPlot.rectangleByRadius(frame,
536                    this.radius, this.radius);
537            Rectangle2D widthRect = DialPlot.rectangleByRadius(frame,
538                    this.widthRadius, this.widthRadius);
539            double value = plot.getValue(this.datasetIndex);
540            DialScale scale = plot.getScaleForDataset(this.datasetIndex);
541            double angle = scale.valueToAngle(value);
542
543            Arc2D arc1 = new Arc2D.Double(lengthRect, angle, 0, Arc2D.OPEN);
544            Point2D pt1 = arc1.getEndPoint();
545            Arc2D arc2 = new Arc2D.Double(widthRect, angle - 90.0, 180.0,
546                    Arc2D.OPEN);
547            Point2D pt2 = arc2.getStartPoint();
548            Point2D pt3 = arc2.getEndPoint();
549            Arc2D arc3 = new Arc2D.Double(widthRect, angle - 180.0, 0.0,
550                    Arc2D.OPEN);
551            Point2D pt4 = arc3.getStartPoint();
552
553            GeneralPath gp = new GeneralPath();
554            gp.moveTo((float) pt1.getX(), (float) pt1.getY());
555            gp.lineTo((float) pt2.getX(), (float) pt2.getY());
556            gp.lineTo((float) pt4.getX(), (float) pt4.getY());
557            gp.lineTo((float) pt3.getX(), (float) pt3.getY());
558            gp.closePath();
559            g2.setPaint(this.fillPaint);
560            g2.fill(gp);
561
562            g2.setPaint(this.outlinePaint);
563            Line2D line = new Line2D.Double(frame.getCenterX(),
564                    frame.getCenterY(), pt1.getX(), pt1.getY());
565            g2.draw(line);
566
567            line.setLine(pt2, pt3);
568            g2.draw(line);
569
570            line.setLine(pt3, pt1);
571            g2.draw(line);
572
573            line.setLine(pt2, pt1);
574            g2.draw(line);
575
576            line.setLine(pt2, pt4);
577            g2.draw(line);
578
579            line.setLine(pt3, pt4);
580            g2.draw(line);
581        }
582
583        /**
584         * Tests this pointer for equality with an arbitrary object.
585         *
586         * @param obj  the object (<code>null</code> permitted).
587         *
588         * @return A boolean.
589         */
590        @Override
591        public boolean equals(Object obj) {
592            if (obj == this) {
593                return true;
594            }
595            if (!(obj instanceof DialPointer.Pointer)) {
596                return false;
597            }
598            DialPointer.Pointer that = (DialPointer.Pointer) obj;
599
600            if (this.widthRadius != that.widthRadius) {
601                return false;
602            }
603            if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
604                return false;
605            }
606            if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
607                return false;
608            }
609            return super.equals(obj);
610        }
611
612        /**
613         * Returns a hash code for this instance.
614         *
615         * @return A hash code.
616         */
617        @Override
618        public int hashCode() {
619            int result = super.hashCode();
620            result = HashUtilities.hashCode(result, this.widthRadius);
621            result = HashUtilities.hashCode(result, this.fillPaint);
622            result = HashUtilities.hashCode(result, this.outlinePaint);
623            return result;
624        }
625
626        /**
627         * Provides serialization support.
628         *
629         * @param stream  the output stream.
630         *
631         * @throws IOException  if there is an I/O error.
632         */
633        private void writeObject(ObjectOutputStream stream) throws IOException {
634            stream.defaultWriteObject();
635            SerialUtilities.writePaint(this.fillPaint, stream);
636            SerialUtilities.writePaint(this.outlinePaint, stream);
637        }
638
639        /**
640         * Provides serialization support.
641         *
642         * @param stream  the input stream.
643         *
644         * @throws IOException  if there is an I/O error.
645         * @throws ClassNotFoundException  if there is a classpath problem.
646         */
647        private void readObject(ObjectInputStream stream)
648                throws IOException, ClassNotFoundException {
649            stream.defaultReadObject();
650            this.fillPaint = SerialUtilities.readPaint(stream);
651            this.outlinePaint = SerialUtilities.readPaint(stream);
652        }
653
654    }
655
656}