KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > chart > plot > CompassPlot


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this library; if not, write to the Free Software Foundation,
21  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22  *
23  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
24  * in the United States and other countries.]
25  *
26  * ----------------
27  * CompassPlot.java
28  * ----------------
29  * (C) Copyright 2002-2005, by the Australian Antarctic Division and
30  * Contributors.
31  *
32  * Original Author: Bryan Scott (for the Australian Antarctic Division);
33  * Contributor(s): David Gilbert (for Object Refinery Limited);
34  * Arnaud Lelievre;
35  *
36  * $Id: CompassPlot.java,v 1.11 2005/05/19 14:03:41 mungady Exp $
37  *
38  * Changes:
39  * --------
40  * 25-Sep-2002 : Version 1, contributed by Bryan Scott (DG);
41  * 23-Jan-2003 : Removed one constructor (DG);
42  * 26-Mar-2003 : Implemented Serializable (DG);
43  * 27-Mar-2003 : Changed MeterDataset to ValueDataset (DG);
44  * 21-Aug-2003 : Implemented Cloneable (DG);
45  * 08-Sep-2003 : Added internationalization via use of properties
46  * resourceBundle (RFE 690236) (AL);
47  * 09-Sep-2003 : Changed Color --> Paint (DG);
48  * 15-Sep-2003 : Added null data value check (bug report 805009) (DG);
49  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
50  * 16-Mar-2004 : Added support for revolutionDistance to enable support for
51  * other units than degrees.
52  * 16-Mar-2004 : Enabled LongNeedle to rotate about center.
53  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
54  * 17-Apr-2005 : Fixed bug in clone() method (DG);
55  * 05-May-2005 : Updated draw() method parameters (DG);
56  *
57  */

58
59 package org.jfree.chart.plot;
60
61 import java.awt.BasicStroke JavaDoc;
62 import java.awt.Color JavaDoc;
63 import java.awt.Font JavaDoc;
64 import java.awt.Graphics2D JavaDoc;
65 import java.awt.Paint JavaDoc;
66 import java.awt.Polygon JavaDoc;
67 import java.awt.Stroke JavaDoc;
68 import java.awt.geom.Area JavaDoc;
69 import java.awt.geom.Ellipse2D JavaDoc;
70 import java.awt.geom.Point2D JavaDoc;
71 import java.awt.geom.Rectangle2D JavaDoc;
72 import java.io.Serializable JavaDoc;
73 import java.util.Arrays JavaDoc;
74 import java.util.ResourceBundle JavaDoc;
75
76 import org.jfree.chart.LegendItemCollection;
77 import org.jfree.chart.event.PlotChangeEvent;
78 import org.jfree.chart.needle.ArrowNeedle;
79 import org.jfree.chart.needle.LineNeedle;
80 import org.jfree.chart.needle.LongNeedle;
81 import org.jfree.chart.needle.MeterNeedle;
82 import org.jfree.chart.needle.MiddlePinNeedle;
83 import org.jfree.chart.needle.PinNeedle;
84 import org.jfree.chart.needle.PlumNeedle;
85 import org.jfree.chart.needle.PointerNeedle;
86 import org.jfree.chart.needle.ShipNeedle;
87 import org.jfree.chart.needle.WindNeedle;
88 import org.jfree.data.general.DefaultValueDataset;
89 import org.jfree.data.general.ValueDataset;
90 import org.jfree.ui.RectangleInsets;
91 import org.jfree.util.ObjectUtilities;
92
93 /**
94  * A specialised plot that draws a compass to indicate a direction based on the
95  * value from a {@link ValueDataset}.
96  *
97  * @author Bryan Scott
98  */

99 public class CompassPlot extends Plot implements Cloneable JavaDoc, Serializable JavaDoc {
100
101     /** For serialization. */
102     private static final long serialVersionUID = 6924382802125527395L;
103     
104     /** The default label font. */
105     public static final Font JavaDoc DEFAULT_LABEL_FONT
106         = new Font JavaDoc("SansSerif", Font.BOLD, 10);
107
108     /** A constant for the label type. */
109     public static final int NO_LABELS = 0;
110
111     /** A constant for the label type. */
112     public static final int VALUE_LABELS = 1;
113
114     /** The label type (NO_LABELS, VALUE_LABELS). */
115     private int labelType;
116
117     /** The label font. */
118     private Font JavaDoc labelFont;
119
120     /** A flag that controls whether or not a border is drawn. */
121     private boolean drawBorder = false;
122
123     /** The rose highlight paint. */
124     private Paint JavaDoc roseHighlightPaint = Color.black;
125
126     /** The rose paint. */
127     private Paint JavaDoc rosePaint = Color.yellow;
128
129     /** The rose center paint. */
130     private Paint JavaDoc roseCenterPaint = Color.white;
131
132     /** The compass font. */
133     private Font JavaDoc compassFont = new Font JavaDoc("Arial", Font.PLAIN, 10);
134
135     /** A working shape. */
136     private transient Ellipse2D JavaDoc circle1;
137
138     /** A working shape. */
139     private transient Ellipse2D JavaDoc circle2;
140
141     /** A working area. */
142     private transient Area JavaDoc a1;
143
144     /** A working area. */
145     private transient Area JavaDoc a2;
146
147     /** A working shape. */
148     private transient Rectangle2D JavaDoc rect1;
149
150     /** An array of value datasets. */
151     private ValueDataset[] datasets = new ValueDataset[1];
152
153     /** An array of needles. */
154     private MeterNeedle[] seriesNeedle = new MeterNeedle[1];
155
156     /** The resourceBundle for the localization. */
157     protected static ResourceBundle JavaDoc localizationResources =
158         ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
159
160     /** The count to complete one revolution. Can be arbitaly set
161      * For degrees (the default) it is 360, for radians this is 2*Pi, etc
162      */

163     protected double revolutionDistance = 360;
164
165     /**
166      * Default constructor.
167      */

168     public CompassPlot() {
169         this(new DefaultValueDataset());
170     }
171
172     /**
173      * Constructs a new compass plot.
174      *
175      * @param dataset the dataset for the plot (<code>null</code> permitted).
176      */

177     public CompassPlot(ValueDataset dataset) {
178
179         super();
180
181         if (dataset != null) {
182             this.datasets[0] = dataset;
183             dataset.addChangeListener(this);
184         }
185
186
187         this.circle1 = new Ellipse2D.Double JavaDoc();
188         this.circle2 = new Ellipse2D.Double JavaDoc();
189         this.rect1 = new Rectangle2D.Double JavaDoc();
190         setSeriesNeedle(0);
191
192     }
193
194     /**
195      * Returns the label type. Defined by the constants: NO_LABELS,
196      * VALUE_LABELS.
197      *
198      * @return The label type.
199      */

200     public int getLabelType() {
201         return this.labelType;
202     }
203
204     /**
205      * Sets the label type.
206      * <P>
207      * Valid types are defined by the following constants: NO_LABELS,
208      * VALUE_LABELS.
209      *
210      * @param type the type.
211      */

212     public void setLabelType(int type) {
213
214         if ((type != NO_LABELS) && (type != VALUE_LABELS)) {
215             throw new IllegalArgumentException JavaDoc(
216                 "MeterPlot.setLabelType(int): unrecognised type."
217             );
218         }
219
220         if (this.labelType != type) {
221             this.labelType = type;
222             notifyListeners(new PlotChangeEvent(this));
223         }
224
225     }
226
227     /**
228      * Returns the label font.
229      *
230      * @return The label font.
231      */

232     public Font JavaDoc getLabelFont() {
233         return this.labelFont;
234     }
235
236     /**
237      * Sets the label font.
238      * <P>
239      * Notifies registered listeners that the plot has been changed.
240      *
241      * @param font the new label font.
242      */

243     public void setLabelFont(Font JavaDoc font) {
244
245         // check arguments...
246
if (font == null) {
247             throw new IllegalArgumentException JavaDoc("Null 'font' not allowed.");
248         }
249
250         // make the change...
251
if (!this.labelFont.equals(font)) {
252             this.labelFont = font;
253             notifyListeners(new PlotChangeEvent(this));
254         }
255
256     }
257
258     /**
259      * Returns a flag that controls whether or not a border is drawn.
260      *
261      * @return The flag.
262      */

263     public boolean getDrawBorder() {
264         return this.drawBorder;
265     }
266
267     /**
268      * Sets a flag that controls whether or not a border is drawn.
269      *
270      * @param status the flag status.
271      */

272     public void setDrawBorder(boolean status) {
273         this.drawBorder = status;
274     }
275
276     /**
277      * Sets the series paint.
278      *
279      * @param series the series index.
280      * @param paint the paint.
281      */

282     public void setSeriesPaint(int series, Paint JavaDoc paint) {
283        // super.setSeriesPaint(series, paint);
284
if ((series >= 0) && (series < this.seriesNeedle.length)) {
285             this.seriesNeedle[series].setFillPaint(paint);
286         }
287     }
288
289     /**
290      * Sets the series outline paint.
291      *
292      * @param series the series index.
293      * @param p the paint.
294      */

295     public void setSeriesOutlinePaint(int series, Paint JavaDoc p) {
296
297         if ((series >= 0) && (series < this.seriesNeedle.length)) {
298             this.seriesNeedle[series].setOutlinePaint(p);
299         }
300
301     }
302
303     /**
304      * Sets the series outline stroke.
305      *
306      * @param series the series index.
307      * @param stroke the stroke.
308      */

309     public void setSeriesOutlineStroke(int series, Stroke JavaDoc stroke) {
310
311       if ((series >= 0) && (series < this.seriesNeedle.length)) {
312         this.seriesNeedle[series].setOutlineStroke(stroke);
313       }
314
315     }
316
317     /**
318      * Sets the needle type.
319      *
320      * @param type the type.
321      */

322     public void setSeriesNeedle(int type) {
323         setSeriesNeedle(0, type);
324     }
325
326     /**
327      * Sets the needle for a series.
328      *
329      * @param index the series index.
330      * @param type the needle type.
331      */

332     public void setSeriesNeedle(int index, int type) {
333         switch (type) {
334             case 0:
335                 setSeriesNeedle(index, new ArrowNeedle(true));
336                 setSeriesPaint(index, Color.red);
337                 this.seriesNeedle[index].setHighlightPaint(Color.white);
338                 break;
339             case 1:
340                 setSeriesNeedle(index, new LineNeedle());
341                 break;
342             case 2:
343                 MeterNeedle longNeedle = new LongNeedle();
344                 longNeedle.setRotateY(0.5);
345                 setSeriesNeedle(index, longNeedle);
346                 break;
347             case 3:
348                 setSeriesNeedle(index, new PinNeedle());
349                 break;
350             case 4:
351                 setSeriesNeedle(index, new PlumNeedle());
352                 break;
353             case 5:
354                 setSeriesNeedle(index, new PointerNeedle());
355                 break;
356             case 6:
357                 setSeriesPaint(index, null);
358                 setSeriesOutlineStroke(index, new BasicStroke JavaDoc(3));
359                 setSeriesNeedle(index, new ShipNeedle());
360                 break;
361             case 7:
362                 setSeriesPaint(index, Color.blue);
363                 setSeriesNeedle(index, new WindNeedle());
364                 break;
365             case 8:
366                 setSeriesNeedle(index, new ArrowNeedle(true));
367                 break;
368             case 9:
369                 setSeriesNeedle(index, new MiddlePinNeedle());
370                 break;
371
372             default:
373                 throw new IllegalArgumentException JavaDoc("Unrecognised type.");
374         }
375
376     }
377
378     /**
379      * Sets the needle for a series.
380      *
381      * @param index the series index.
382      * @param needle the needle.
383      */

384     public void setSeriesNeedle(int index, MeterNeedle needle) {
385
386         if ((needle != null) && (index < this.seriesNeedle.length)) {
387             this.seriesNeedle[index] = needle;
388         }
389         notifyListeners(new PlotChangeEvent(this));
390
391     }
392
393     /**
394      * Returns the dataset.
395      * <P>
396      * Provided for convenience.
397      *
398      * @return The dataset for the plot, cast as a ValueDataset.
399      */

400     public ValueDataset[] getData() {
401         return this.datasets;
402     }
403
404     /**
405      * Adds a dataset to the compass.
406      *
407      * @param data the new dataset.
408      */

409     public void addData(ValueDataset data) {
410         addData(data, null);
411     }
412
413     /**
414      * Adds a dataset to the compass.
415      *
416      * @param data the new dataset.
417      * @param needle the needle.
418      */

419     public void addData(ValueDataset data, MeterNeedle needle) {
420
421         if (data != null) {
422             int i = this.datasets.length + 1;
423             ValueDataset[] t = new ValueDataset[i];
424             MeterNeedle[] p = new MeterNeedle[i];
425             i = i - 2;
426             for (; i >= 0; --i) {
427                 t[i] = this.datasets[i];
428                 p[i] = this.seriesNeedle[i];
429             }
430             i = this.datasets.length;
431             t[i] = data;
432             p[i] = ((needle != null) ? needle : p[i - 1]);
433
434             ValueDataset[] a = this.datasets;
435             MeterNeedle[] b = this.seriesNeedle;
436             this.datasets = t;
437             this.seriesNeedle = p;
438
439             for (--i; i >= 0; --i) {
440                 a[i] = null;
441                 b[i] = null;
442             }
443             data.addChangeListener(this);
444         }
445     }
446
447     /**
448      * Draws the plot on a Java 2D graphics device (such as the screen or a
449      * printer).
450      *
451      * @param g2 the graphics device.
452      * @param area the area within which the plot should be drawn.
453      * @param anchor the anchor point (<code>null</code> permitted).
454      * @param parentState the state from the parent plot, if there is one.
455      * @param info collects info about the drawing.
456      */

457     public void draw(Graphics2D JavaDoc g2, Rectangle2D JavaDoc area, Point2D JavaDoc anchor,
458                      PlotState parentState,
459                      PlotRenderingInfo info) {
460
461         int outerRadius = 0;
462         int innerRadius = 0;
463         int x1, y1, x2, y2;
464         double a;
465
466         if (info != null) {
467             info.setPlotArea(area);
468         }
469
470         // adjust for insets...
471
RectangleInsets insets = getInsets();
472         insets.trim(area);
473
474         // draw the background
475
if (this.drawBorder) {
476             drawBackground(g2, area);
477         }
478
479         int midX = (int) (area.getWidth() / 2);
480         int midY = (int) (area.getHeight() / 2);
481         int radius = midX;
482         if (midY < midX) {
483             radius = midY;
484         }
485         --radius;
486         int diameter = 2 * radius;
487
488         midX += (int) area.getMinX();
489         midY += (int) area.getMinY();
490
491         this.circle1.setFrame(midX - radius, midY - radius, diameter, diameter);
492         this.circle2.setFrame(
493             midX - radius + 15, midY - radius + 15,
494             diameter - 30, diameter - 30
495         );
496         g2.setPaint(this.rosePaint);
497         this.a1 = new Area JavaDoc(this.circle1);
498         this.a2 = new Area JavaDoc(this.circle2);
499         this.a1.subtract(this.a2);
500         g2.fill(this.a1);
501
502         g2.setPaint(this.roseCenterPaint);
503         x1 = diameter - 30;
504         g2.fillOval(midX - radius + 15, midY - radius + 15, x1, x1);
505         g2.setPaint(this.roseHighlightPaint);
506         g2.drawOval(midX - radius, midY - radius, diameter, diameter);
507         x1 = diameter - 20;
508         g2.drawOval(midX - radius + 10, midY - radius + 10, x1, x1);
509         x1 = diameter - 30;
510         g2.drawOval(midX - radius + 15, midY - radius + 15, x1, x1);
511         x1 = diameter - 80;
512         g2.drawOval(midX - radius + 40, midY - radius + 40, x1, x1);
513
514         outerRadius = radius - 20;
515         innerRadius = radius - 32;
516         for (int w = 0; w < 360; w += 15) {
517             a = Math.toRadians(w);
518             x1 = midX - ((int) (Math.sin(a) * innerRadius));
519             x2 = midX - ((int) (Math.sin(a) * outerRadius));
520             y1 = midY - ((int) (Math.cos(a) * innerRadius));
521             y2 = midY - ((int) (Math.cos(a) * outerRadius));
522             g2.drawLine(x1, y1, x2, y2);
523         }
524
525         g2.setPaint(this.roseHighlightPaint);
526         innerRadius = radius - 26;
527         outerRadius = 7;
528         for (int w = 45; w < 360; w += 90) {
529             a = Math.toRadians(w);
530             x1 = midX - ((int) (Math.sin(a) * innerRadius));
531             y1 = midY - ((int) (Math.cos(a) * innerRadius));
532             g2.fillOval(
533                 x1 - outerRadius, y1 - outerRadius,
534                 2 * outerRadius, 2 * outerRadius
535             );
536         }
537
538         /// Squares
539
for (int w = 0; w < 360; w += 90) {
540             a = Math.toRadians(w);
541             x1 = midX - ((int) (Math.sin(a) * innerRadius));
542             y1 = midY - ((int) (Math.cos(a) * innerRadius));
543
544             Polygon JavaDoc p = new Polygon JavaDoc();
545             p.addPoint(x1 - outerRadius, y1);
546             p.addPoint(x1, y1 + outerRadius);
547             p.addPoint(x1 + outerRadius, y1);
548             p.addPoint(x1, y1 - outerRadius);
549             g2.fillPolygon(p);
550         }
551
552         /// Draw N, S, E, W
553
innerRadius = radius - 42;
554         Font JavaDoc f = getCompassFont(radius);
555         g2.setFont(f);
556         g2.drawString("N", midX - 5, midY - innerRadius + f.getSize());
557         g2.drawString("S", midX - 5, midY + innerRadius - 5);
558         g2.drawString("W", midX - innerRadius + 5, midY + 5);
559         g2.drawString("E", midX + innerRadius - f.getSize(), midY + 5);
560
561         // plot the data (unless the dataset is null)...
562
y1 = radius / 2;
563         x1 = radius / 6;
564         Rectangle2D JavaDoc needleArea = new Rectangle2D.Double JavaDoc(
565             (midX - x1), (midY - y1), (2 * x1), (2 * y1)
566         );
567         int x = this.seriesNeedle.length;
568         int current = 0;
569         double value = 0;
570         int i = (this.datasets.length - 1);
571         for (; i >= 0; --i) {
572             ValueDataset data = this.datasets[i];
573
574             if (data != null && data.getValue() != null) {
575                 value = (data.getValue().doubleValue())
576                     % this.revolutionDistance;
577                 value = value / this.revolutionDistance * 360;
578                 current = i % x;
579                 this.seriesNeedle[current].draw(g2, needleArea, value);
580             }
581         }
582
583         if (this.drawBorder) {
584             drawOutline(g2, area);
585         }
586
587     }
588
589     /**
590      * Returns a short string describing the type of plot.
591      *
592      * @return A string describing the plot.
593      */

594     public String JavaDoc getPlotType() {
595         return localizationResources.getString("Compass_Plot");
596     }
597
598     /**
599      * Returns the legend items for the plot. For now, no legend is available
600      * - this method returns null.
601      *
602      * @return The legend items.
603      */

604     public LegendItemCollection getLegendItems() {
605         return null;
606     }
607
608     /**
609      * No zooming is implemented for compass plot, so this method is empty.
610      *
611      * @param percent the zoom amount.
612      */

613     public void zoom(double percent) {
614         // no zooming possible
615
}
616
617     /**
618      * Returns the font for the compass.
619      *
620      * @param radius the radius.
621      *
622      * @return The font.
623      */

624     protected Font JavaDoc getCompassFont(int radius) {
625
626         float fontSize = radius / 10.0f;
627         if (fontSize < 8) {
628             fontSize = 8;
629         }
630
631         Font JavaDoc newFont = this.compassFont.deriveFont(fontSize);
632         return newFont;
633
634     }
635
636     /**
637      * Tests an object for equality with this plot.
638      *
639      * @param obj the object (<code>null</code> permitted).
640      *
641      * @return A boolean.
642      */

643     public boolean equals(Object JavaDoc obj) {
644
645         if (obj == this) {
646             return true;
647         }
648
649         if (!(obj instanceof CompassPlot)) {
650             return false;
651         }
652         if (!super.equals(obj)) {
653             return false;
654         }
655         CompassPlot that = (CompassPlot) obj;
656         if (this.labelType != that.labelType) {
657             return false;
658         }
659         if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
660             return false;
661         }
662         if (this.drawBorder != that.drawBorder) {
663             return false;
664         }
665         if (!ObjectUtilities.equal(this.roseHighlightPaint,
666                 that.roseHighlightPaint)) {
667             return false;
668         }
669         if (!ObjectUtilities.equal(this.rosePaint, that.rosePaint)) {
670             return false;
671         }
672         if (!ObjectUtilities.equal(this.roseCenterPaint,
673                 that.roseCenterPaint)) {
674             return false;
675         }
676         if (!ObjectUtilities.equal(this.compassFont, that.compassFont)) {
677             return false;
678         }
679         if (!Arrays.equals(this.seriesNeedle, that.seriesNeedle)) {
680             return false;
681         }
682         if (getRevolutionDistance() != that.getRevolutionDistance()) {
683             return false;
684         }
685         return true;
686
687     }
688
689     /**
690      * Returns a clone of the annotation.
691      *
692      * @return A clone.
693      *
694      * @throws CloneNotSupportedException this class will not throw this
695      * exception, but subclasses (if any) might.
696      */

697     public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
698
699         CompassPlot clone = (CompassPlot) super.clone();
700         //private int labelType <-- primitive
701
//private Font labelFont <-- immutable
702
//private boolean drawBorder = false <-- primitive
703
//private Color roseHighlightColour <-- immutable
704
//private Color roseColour <-- immutable
705
//private Color roseCenterColour <-- immutable
706
//private Font compassFont <-- immutable
707
if (this.circle1 != null) {
708             clone.circle1 = (Ellipse2D JavaDoc) this.circle1.clone();
709         }
710         if (this.circle2 != null) {
711             clone.circle2 = (Ellipse2D JavaDoc) this.circle2.clone();
712         }
713         if (this.a1 != null) {
714             clone.a1 = (Area JavaDoc) this.a1.clone();
715         }
716         if (this.a2 != null) {
717             clone.a2 = (Area JavaDoc) this.a2.clone();
718         }
719         if (this.rect1 != null) {
720             clone.rect1 = (Rectangle2D JavaDoc) this.rect1.clone();
721         }
722         clone.datasets = (ValueDataset[]) this.datasets.clone();
723         clone.seriesNeedle = (MeterNeedle[]) this.seriesNeedle.clone();
724
725         // clone share data sets => add the clone as listener to the dataset
726
for (int i = 0; i < this.datasets.length; ++i) {
727             if (clone.datasets[i] != null) {
728                 clone.datasets[i].addChangeListener(clone);
729             }
730         }
731         return clone;
732
733     }
734
735     /**
736      * Sets the count to complete one revolution. Can be arbitaly set
737      * For degrees (the default) it is 360, for radians this is 2*Pi, etc
738      *
739      * @param size the count to complete one revolution.
740      */

741     public void setRevolutionDistance(double size) {
742         if (size > 0) {
743             this.revolutionDistance = size;
744         }
745     }
746
747     /**
748      * Gets the count to complete one revolution.
749      *
750      * @return The count to complete one revolution
751      */

752     public double getRevolutionDistance() {
753         return this.revolutionDistance;
754     }
755 }
756
Popular Tags