001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, 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-2007, 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    }