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