KickJava   Java API By Example, From Geeks To Geeks.

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


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  * MeterPlot.java
28  * --------------
29  * (C) Copyright 2000-2005, by Hari and Contributors.
30  *
31  * Original Author: Hari (ourhari@hotmail.com);
32  * Contributor(s): David Gilbert (for Object Refinery Limited);
33  * Bob Orchard;
34  * Arnaud Lelievre;
35  * Nicolas Brodu;
36  * David Bastend;
37  *
38  * $Id: MeterPlot.java,v 1.13 2005/05/19 14:03:41 mungady Exp $
39  *
40  * Changes
41  * -------
42  * 01-Apr-2002 : Version 1, contributed by Hari (DG);
43  * 23-Apr-2002 : Moved dataset from JFreeChart to Plot (DG);
44  * 22-Aug-2002 : Added changes suggest by Bob Orchard, changed Color to Paint
45  * for consistency, plus added Javadoc comments (DG);
46  * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
47  * 23-Jan-2003 : Removed one constructor (DG);
48  * 26-Mar-2003 : Implemented Serializable (DG);
49  * 20-Aug-2003 : Changed dataset from MeterDataset --> ValueDataset, added
50  * equals() method,
51  * 08-Sep-2003 : Added internationalization via use of properties
52  * resourceBundle (RFE 690236) (AL);
53  * implemented Cloneable, and various other changes (DG);
54  * 08-Sep-2003 : Added serialization methods (NB);
55  * 11-Sep-2003 : Added cloning support (NB);
56  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
57  * 25-Sep-2003 : Fix useless cloning. Correct dataset listener registration in
58  * constructor. (NB)
59  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
60  * 17-Jan-2004 : Changed to allow dialBackgroundPaint to be set to null - see
61  * bug 823628 (DG);
62  * 07-Apr-2004 : Changed string bounds calculation (DG);
63  * 12-May-2004 : Added tickLabelFormat attribute - see RFE 949566. Also
64  * updated the equals() method (DG);
65  * 02-Nov-2004 : Added sanity checks for range, and only draw the needle if the
66  * value is contained within the overall range - see bug report
67  * 1056047 (DG);
68  * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
69  * release (DG);
70  * 02-Feb-2005 : Added optional background paint for each region (DG);
71  * 22-Mar-2005 : Removed 'normal', 'warning' and 'critical' regions and put in
72  * facility to define an arbitrary number of MeterIntervals,
73  * based on a contribution by David Bastend (DG);
74  * 20-Apr-2005 : Small update for change to LegendItem constructors (DG);
75  * 05-May-2005 : Updated draw() method parameters (DG);
76  *
77  */

78
79 package org.jfree.chart.plot;
80
81 import java.awt.AlphaComposite JavaDoc;
82 import java.awt.BasicStroke JavaDoc;
83 import java.awt.Color JavaDoc;
84 import java.awt.Composite JavaDoc;
85 import java.awt.Font JavaDoc;
86 import java.awt.FontMetrics JavaDoc;
87 import java.awt.Graphics2D JavaDoc;
88 import java.awt.Paint JavaDoc;
89 import java.awt.Polygon JavaDoc;
90 import java.awt.Shape JavaDoc;
91 import java.awt.Stroke JavaDoc;
92 import java.awt.geom.Arc2D JavaDoc;
93 import java.awt.geom.Ellipse2D JavaDoc;
94 import java.awt.geom.Line2D JavaDoc;
95 import java.awt.geom.Point2D JavaDoc;
96 import java.awt.geom.Rectangle2D JavaDoc;
97 import java.io.IOException JavaDoc;
98 import java.io.ObjectInputStream JavaDoc;
99 import java.io.ObjectOutputStream JavaDoc;
100 import java.io.Serializable JavaDoc;
101 import java.text.NumberFormat JavaDoc;
102 import java.util.Collections JavaDoc;
103 import java.util.Iterator JavaDoc;
104 import java.util.List JavaDoc;
105 import java.util.ResourceBundle JavaDoc;
106
107 import org.jfree.chart.LegendItem;
108 import org.jfree.chart.LegendItemCollection;
109 import org.jfree.chart.event.PlotChangeEvent;
110 import org.jfree.data.Range;
111 import org.jfree.data.general.DatasetChangeEvent;
112 import org.jfree.data.general.ValueDataset;
113 import org.jfree.io.SerialUtilities;
114 import org.jfree.text.TextUtilities;
115 import org.jfree.ui.RectangleInsets;
116 import org.jfree.util.ObjectUtilities;
117
118 /**
119  * A plot that displays a single value in the form of a needle on a dial.
120  * Defined ranges (for example, 'normal', 'warning' and 'critical') can be
121  * highlighted on the dial.
122  */

123 public class MeterPlot extends Plot implements Serializable JavaDoc, Cloneable JavaDoc {
124
125     /** For serialization. */
126     private static final long serialVersionUID = 2987472457734470962L;
127     
128     /** The default background paint. */
129     static final Paint JavaDoc DEFAULT_DIAL_BACKGROUND_PAINT = Color.black;
130
131     /** The default needle paint. */
132     static final Paint JavaDoc DEFAULT_NEEDLE_PAINT = Color.green;
133
134     /** The default value font. */
135     static final Font JavaDoc DEFAULT_VALUE_FONT = new Font JavaDoc("SansSerif", Font.BOLD, 12);
136
137     /** The default value paint. */
138     static final Paint JavaDoc DEFAULT_VALUE_PAINT = Color.yellow;
139
140     /** The default meter angle. */
141     public static final int DEFAULT_METER_ANGLE = 270;
142
143     /** The default border size. */
144     public static final float DEFAULT_BORDER_SIZE = 3f;
145
146     /** The default circle size. */
147     public static final float DEFAULT_CIRCLE_SIZE = 10f;
148
149     /** The default label font. */
150     public static final Font JavaDoc DEFAULT_LABEL_FONT
151         = new Font JavaDoc("SansSerif", Font.BOLD, 10);
152
153     /** The dataset (contains a single value). */
154     private ValueDataset dataset;
155
156     /** The dial shape (background shape). */
157     private DialShape shape;
158
159     /** The dial extent (measured in degrees). */
160     private int meterAngle;
161     
162     /** The overall range of data values on the dial. */
163     private Range range;
164     
165     /** The units displayed on the dial. */
166     private String JavaDoc units;
167     
168     /** The font for the value displayed in the center of the dial. */
169     private Font JavaDoc valueFont;
170
171     /** The paint for the value displayed in the center of the dial. */
172     private transient Paint JavaDoc valuePaint;
173
174     /** A flag that controls whether or not the border is drawn. */
175     private boolean drawBorder;
176
177     /** The outline paint. */
178     private transient Paint JavaDoc dialOutlinePaint;
179
180     /** The paint for the dial background. */
181     private transient Paint JavaDoc dialBackgroundPaint;
182
183     /** The paint for the needle. */
184     private transient Paint JavaDoc needlePaint;
185
186     /** A flag that controls whether or not the tick labels are visible. */
187     private boolean tickLabelsVisible;
188
189     /** The tick label font. */
190     private Font JavaDoc tickLabelFont;
191
192     /** The tick label format. */
193     private NumberFormat JavaDoc tickLabelFormat;
194
195     /** The resourceBundle for the localization. */
196     protected static ResourceBundle JavaDoc localizationResources =
197         ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
198
199     /**
200      * A (possibly empty) list of the {@link MeterInterval}s to be highlighted
201      * on the dial.
202      */

203     private List JavaDoc intervals;
204
205     /**
206      * Creates a new plot with a default range of <code>0</code> to
207      * <code>100</code> and no value to display.
208      */

209     public MeterPlot() {
210         this(null);
211     }
212     
213     /**
214      * Creates a new plot that displays the value from the supplied dataset.
215      *
216      * @param dataset the dataset (<code>null</code> permitted).
217      */

218     public MeterPlot(ValueDataset dataset) {
219         super();
220         this.shape = DialShape.CIRCLE;
221         this.meterAngle = DEFAULT_METER_ANGLE;
222         this.range = new Range(0.0, 100.0);
223         this.units = "Units";
224         this.needlePaint = MeterPlot.DEFAULT_NEEDLE_PAINT;
225         this.tickLabelsVisible = true;
226         this.tickLabelFont = MeterPlot.DEFAULT_LABEL_FONT;
227         this.tickLabelFormat = NumberFormat.getInstance();
228         this.valueFont = MeterPlot.DEFAULT_VALUE_FONT;
229         this.valuePaint = MeterPlot.DEFAULT_VALUE_PAINT;
230         this.dialBackgroundPaint = MeterPlot.DEFAULT_DIAL_BACKGROUND_PAINT;
231         this.intervals = new java.util.ArrayList JavaDoc();
232         setDataset(dataset);
233     }
234
235     /**
236      * Returns the dial shape. The default is {@link DialShape#CIRCLE}).
237      *
238      * @return The dial shape (never <code>null</code>).
239      */

240     public DialShape getDialShape() {
241         return this.shape;
242     }
243     
244     /**
245      * Sets the dial shape and sends a {@link PlotChangeEvent} to all
246      * registered listeners.
247      *
248      * @param shape the shape (<code>null</code> not permitted).
249      */

250     public void setDialShape(DialShape shape) {
251         if (shape == null) {
252             throw new IllegalArgumentException JavaDoc("Null 'shape' argument.");
253         }
254         this.shape = shape;
255         notifyListeners(new PlotChangeEvent(this));
256     }
257     
258     /**
259      * Returns the meter angle in degrees. This defines, in part, the shape
260      * of the dial. The default is 270 degrees.
261      *
262      * @return The meter angle (in degrees).
263      */

264     public int getMeterAngle() {
265         return this.meterAngle;
266     }
267
268     /**
269      * Sets the angle (in degrees) for the whole range of the dial and sends
270      * a {@link PlotChangeEvent} to all registered listeners.
271      *
272      * @param angle the angle (in degrees, in the range 1-360).
273      */

274     public void setMeterAngle(int angle) {
275         if (angle < 1 || angle > 360) {
276             throw new IllegalArgumentException JavaDoc(
277                 "Invalid 'angle' (" + angle + ")"
278             );
279         }
280         this.meterAngle = angle;
281         notifyListeners(new PlotChangeEvent(this));
282     }
283
284     /**
285      * Returns the overall range for the dial.
286      *
287      * @return The overall range (never <code>null</code>).
288      */

289     public Range getRange() {
290         return this.range;
291     }
292     
293     /**
294      * Sets the range for the dial and sends a {@link PlotChangeEvent} to all
295      * registered listeners.
296      *
297      * @param range the range (<code>null</code> not permitted and zero-length
298      * ranges not permitted).
299      */

300     public void setRange(Range range) {
301         if (range == null) {
302             throw new IllegalArgumentException JavaDoc("Null 'range' argument.");
303         }
304         if (!(range.getLength() > 0.0)) {
305             throw new IllegalArgumentException JavaDoc(
306                 "Range length must be positive."
307             );
308         }
309         this.range = range;
310         notifyListeners(new PlotChangeEvent(this));
311     }
312
313     /**
314      * Returns a string describing the units for the dial.
315      *
316      * @return The units (possibly <code>null</code>).
317      */

318     public String JavaDoc getUnits() {
319         return this.units;
320     }
321     
322     /**
323      * Sets the units for the dial and sends a {@link PlotChangeEvent} to all
324      * registered listeners.
325      *
326      * @param units the units (<code>null</code> permitted).
327      */

328     public void setUnits(String JavaDoc units) {
329         this.units = units;
330         notifyListeners(new PlotChangeEvent(this));
331     }
332         
333     /**
334      * Returns the paint for the needle.
335      *
336      * @return The paint (never <code>null</code>).
337      */

338     public Paint JavaDoc getNeedlePaint() {
339         return this.needlePaint;
340     }
341
342     /**
343      * Sets the paint used to display the needle and sends a
344      * {@link PlotChangeEvent} to all registered listeners.
345      *
346      * @param paint the paint (<code>null</code> not permitted).
347      */

348     public void setNeedlePaint(Paint JavaDoc paint) {
349         if (paint == null) {
350             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
351         }
352         this.needlePaint = paint;
353         notifyListeners(new PlotChangeEvent(this));
354     }
355
356     /**
357      * Returns the flag that determines whether or not tick labels are visible.
358      *
359      * @return The flag.
360      */

361     public boolean getTickLabelsVisible() {
362         return this.tickLabelsVisible;
363     }
364
365     /**
366      * Sets the flag that controls whether or not the tick labels are visible
367      * and sends a {@link PlotChangeEvent} to all registered listeners.
368      *
369      * @param visible the flag.
370      */

371     public void setTickLabelsVisible(boolean visible) {
372         if (this.tickLabelsVisible != visible) {
373             this.tickLabelsVisible = visible;
374             notifyListeners(new PlotChangeEvent(this));
375         }
376     }
377
378     /**
379      * Returns the tick label font.
380      *
381      * @return The font (never <code>null</code>).
382      */

383     public Font JavaDoc getTickLabelFont() {
384         return this.tickLabelFont;
385     }
386
387     /**
388      * Sets the tick label font and sends a {@link PlotChangeEvent} to all
389      * registered listeners.
390      *
391      * @param font the font (<code>null</code> not permitted).
392      */

393     public void setTickLabelFont(Font JavaDoc font) {
394         if (font == null) {
395             throw new IllegalArgumentException JavaDoc("Null 'font' argument.");
396         }
397         if (!this.tickLabelFont.equals(font)) {
398             this.tickLabelFont = font;
399             notifyListeners(new PlotChangeEvent(this));
400         }
401     }
402
403     /**
404      * Returns the tick label format.
405      *
406      * @return The tick label format (never <code>null</code>).
407      */

408     public NumberFormat JavaDoc getTickLabelFormat() {
409         return this.tickLabelFormat;
410     }
411     
412     /**
413      * Sets the format for the tick labels and sends a {@link PlotChangeEvent}
414      * to all registered listeners.
415      *
416      * @param format the format (<code>null</code> not permitted).
417      */

418     public void setTickLabelFormat(NumberFormat JavaDoc format) {
419         if (format == null) {
420             throw new IllegalArgumentException JavaDoc("Null 'format' argument.");
421         }
422         this.tickLabelFormat = format;
423         notifyListeners(new PlotChangeEvent(this));
424     }
425     
426     /**
427      * Returns the font for the value label.
428      *
429      * @return The font (never <code>null</code>).
430      */

431     public Font JavaDoc getValueFont() {
432         return this.valueFont;
433     }
434
435     /**
436      * Sets the font used to display the value label and sends a
437      * {@link PlotChangeEvent} to all registered listeners.
438      *
439      * @param font the font (<code>null</code> not permitted).
440      */

441     public void setValueFont(Font JavaDoc font) {
442         if (font == null) {
443             throw new IllegalArgumentException JavaDoc("Null 'font' argument.");
444         }
445         this.valueFont = font;
446         notifyListeners(new PlotChangeEvent(this));
447     }
448
449     /**
450      * Returns the paint for the value label.
451      *
452      * @return The paint (never <code>null</code>).
453      */

454     public Paint JavaDoc getValuePaint() {
455         return this.valuePaint;
456     }
457
458     /**
459      * Sets the paint used to display the value label and sends a
460      * {@link PlotChangeEvent} to all registered listeners.
461      *
462      * @param paint the paint (<code>null</code> not permitted).
463      */

464     public void setValuePaint(Paint JavaDoc paint) {
465         if (paint == null) {
466             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
467         }
468         this.valuePaint = paint;
469         notifyListeners(new PlotChangeEvent(this));
470     }
471
472     /**
473      * Returns the paint for the dial background.
474      *
475      * @return The paint (possibly <code>null</code>).
476      */

477     public Paint JavaDoc getDialBackgroundPaint() {
478         return this.dialBackgroundPaint;
479     }
480
481     /**
482      * Sets the paint used to fill the dial background. Set this to
483      * <code>null</code> for no background.
484      *
485      * @param paint the paint (<code>null</code> permitted).
486      */

487     public void setDialBackgroundPaint(Paint JavaDoc paint) {
488         this.dialBackgroundPaint = paint;
489         notifyListeners(new PlotChangeEvent(this));
490     }
491
492     /**
493      * Returns a flag that controls whether or not a rectangular border is
494      * drawn around the plot area.
495      *
496      * @return A flag.
497      */

498     public boolean getDrawBorder() {
499         return this.drawBorder;
500     }
501
502     /**
503      * Sets the flag that controls whether or not a rectangular border is drawn
504      * around the plot area and sends a {@link PlotChangeEvent} to all
505      * registered listeners.
506      *
507      * @param draw the flag.
508      */

509     public void setDrawBorder(boolean draw) {
510         // TODO: fix output when this flag is set to true
511
this.drawBorder = draw;
512         notifyListeners(new PlotChangeEvent(this));
513     }
514
515     /**
516      * Returns the dial outline paint.
517      *
518      * @return The paint.
519      */

520     public Paint JavaDoc getDialOutlinePaint() {
521         return this.dialOutlinePaint;
522     }
523
524     /**
525      * Sets the dial outline paint and sends a {@link PlotChangeEvent} to all
526      * registered listeners.
527      *
528      * @param paint the paint.
529      */

530     public void setDialOutlinePaint(Paint JavaDoc paint) {
531         this.dialOutlinePaint = paint;
532         notifyListeners(new PlotChangeEvent(this));
533     }
534
535     /**
536      * Returns the dataset for the plot.
537      *
538      * @return The dataset (possibly <code>null</code>).
539      */

540     public ValueDataset getDataset() {
541         return this.dataset;
542     }
543     
544     /**
545      * Sets the dataset for the plot, replacing the existing dataset if there
546      * is one, and triggers a {@link PlotChangeEvent}.
547      *
548      * @param dataset the dataset (<code>null</code> permitted).
549      */

550     public void setDataset(ValueDataset dataset) {
551         
552         // if there is an existing dataset, remove the plot from the list of
553
// change listeners...
554
ValueDataset existing = this.dataset;
555         if (existing != null) {
556             existing.removeChangeListener(this);
557         }
558
559         // set the new dataset, and register the chart as a change listener...
560
this.dataset = dataset;
561         if (dataset != null) {
562             setDatasetGroup(dataset.getGroup());
563             dataset.addChangeListener(this);
564         }
565
566         // send a dataset change event to self...
567
DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
568         datasetChanged(event);
569         
570     }
571
572     /**
573      * Returns an unmodifiable list of the intervals for the plot.
574      *
575      * @return A list.
576      */

577     public List JavaDoc getIntervals() {
578         return Collections.unmodifiableList(this.intervals);
579     }
580     
581     /**
582      * Adds an interval and sends a {@link PlotChangeEvent} to all registered
583      * listeners.
584      *
585      * @param interval the interval (<code>null</code> not permitted).
586      */

587     public void addInterval(MeterInterval interval) {
588         if (interval == null) {
589             throw new IllegalArgumentException JavaDoc("Null 'interval' argument.");
590         }
591         this.intervals.add(interval);
592         notifyListeners(new PlotChangeEvent(this));
593     }
594     
595     /**
596      * Clears the intervals for the plot and sends a {@link PlotChangeEvent} to
597      * all registered listeners.
598      */

599     public void clearIntervals() {
600         this.intervals.clear();
601         notifyListeners(new PlotChangeEvent(this));
602     }
603     
604     /**
605      * Returns an item for each interval.
606      *
607      * @return A collection of legend items.
608      */

609     public LegendItemCollection getLegendItems() {
610         LegendItemCollection result = new LegendItemCollection();
611         Iterator JavaDoc iterator = this.intervals.iterator();
612         while (iterator.hasNext()) {
613             MeterInterval mi = (MeterInterval) iterator.next();
614             LegendItem item = new LegendItem(
615                 mi.getLabel(), mi.getLabel(), null, null,
616                 new Rectangle2D.Double JavaDoc(-4.0, -4.0, 8.0, 8.0),
617                 mi.getOutlinePaint()
618             );
619             result.add(item);
620         }
621         return result;
622     }
623
624     /**
625      * Draws the plot on a Java 2D graphics device (such as the screen or a
626      * printer).
627      *
628      * @param g2 the graphics device.
629      * @param area the area within which the plot should be drawn.
630      * @param anchor the anchor point (<code>null</code> permitted).
631      * @param parentState the state from the parent plot, if there is one.
632      * @param info collects info about the drawing.
633      */

634     public void draw(Graphics2D JavaDoc g2, Rectangle2D JavaDoc area, Point2D JavaDoc anchor,
635                      PlotState parentState,
636                      PlotRenderingInfo info) {
637
638         if (info != null) {
639             info.setPlotArea(area);
640         }
641
642         // adjust for insets...
643
RectangleInsets insets = getInsets();
644         insets.trim(area);
645
646         area.setRect(
647             area.getX() + 4, area.getY() + 4,
648             area.getWidth() - 8, area.getHeight() - 8
649         );
650
651         // draw the background
652
if (this.drawBorder) {
653             drawBackground(g2, area);
654         }
655
656         // adjust the plot area by the interior spacing value
657
double gapHorizontal = (2 * DEFAULT_BORDER_SIZE);
658         double gapVertical = (2 * DEFAULT_BORDER_SIZE);
659         double meterX = area.getX() + gapHorizontal / 2;
660         double meterY = area.getY() + gapVertical / 2;
661         double meterW = area.getWidth() - gapHorizontal;
662         double meterH = area.getHeight() - gapVertical
663             + ((this.meterAngle <= 180) && (this.shape != DialShape.CIRCLE)
664                 ? area.getHeight() / 1.25 : 0);
665
666         double min = Math.min(meterW, meterH) / 2;
667         meterX = (meterX + meterX + meterW) / 2 - min;
668         meterY = (meterY + meterY + meterH) / 2 - min;
669         meterW = 2 * min;
670         meterH = 2 * min;
671
672         Rectangle2D JavaDoc meterArea = new Rectangle2D.Double JavaDoc(
673             meterX, meterY, meterW, meterH
674         );
675
676         Rectangle2D.Double JavaDoc originalArea = new Rectangle2D.Double JavaDoc(
677             meterArea.getX() - 4, meterArea.getY() - 4,
678             meterArea.getWidth() + 8, meterArea.getHeight() + 8
679         );
680
681         double meterMiddleX = meterArea.getCenterX();
682         double meterMiddleY = meterArea.getCenterY();
683
684         // plot the data (unless the dataset is null)...
685
ValueDataset data = getDataset();
686         if (data != null) {
687             double dataMin = this.range.getLowerBound();
688             double dataMax = this.range.getUpperBound();
689
690             Shape JavaDoc savedClip = g2.getClip();
691             g2.clip(originalArea);
692             Composite JavaDoc originalComposite = g2.getComposite();
693             g2.setComposite(AlphaComposite.getInstance(
694                 AlphaComposite.SRC_OVER, getForegroundAlpha())
695             );
696
697             if (this.dialBackgroundPaint != null) {
698                 fillArc(
699                     g2, originalArea, dataMin, dataMax,
700                     this.dialBackgroundPaint, true
701                 );
702             }
703             drawTicks(g2, meterArea, dataMin, dataMax);
704             drawArcForInterval(
705                 g2, meterArea,
706                 new MeterInterval(
707                     "", this.range, this.dialOutlinePaint,
708                     new BasicStroke JavaDoc(1.0f), null
709                 )
710             );
711             
712             Iterator JavaDoc iterator = this.intervals.iterator();
713             while (iterator.hasNext()) {
714                 MeterInterval interval = (MeterInterval) iterator.next();
715                 drawArcForInterval(g2, meterArea, interval);
716             }
717
718             Number JavaDoc n = data.getValue();
719             if (n != null) {
720                 double value = n.doubleValue();
721                 drawTick(
722                     g2, meterArea, value, true, this.valuePaint, true,
723                     getUnits()
724                 );
725   
726                 if (this.range.contains(value)) {
727                     g2.setPaint(this.needlePaint);
728                     g2.setStroke(new BasicStroke JavaDoc(2.0f));
729
730                     double radius = (meterArea.getWidth() / 2)
731                                     + DEFAULT_BORDER_SIZE + 15;
732                     double valueAngle = valueToAngle(value);
733                     double valueP1 = meterMiddleX
734                         + (radius * Math.cos(Math.PI * (valueAngle / 180)));
735                     double valueP2 = meterMiddleY
736                         - (radius * Math.sin(Math.PI * (valueAngle / 180)));
737
738                     Polygon JavaDoc arrow = new Polygon JavaDoc();
739                     if ((valueAngle > 135 && valueAngle < 225)
740                         || (valueAngle < 45 && valueAngle > -45)) {
741
742                         double valueP3 = (meterMiddleY
743                                 - DEFAULT_CIRCLE_SIZE / 4);
744                         double valueP4 = (meterMiddleY
745                                 + DEFAULT_CIRCLE_SIZE / 4);
746                         arrow.addPoint((int) meterMiddleX, (int) valueP3);
747                         arrow.addPoint((int) meterMiddleX, (int) valueP4);
748  
749                     }
750                     else {
751                         arrow.addPoint(
752                             (int) (meterMiddleX - DEFAULT_CIRCLE_SIZE / 4),
753                             (int) meterMiddleY
754                         );
755                         arrow.addPoint(
756                             (int) (meterMiddleX + DEFAULT_CIRCLE_SIZE / 4),
757                             (int) meterMiddleY
758                         );
759                     }
760                     arrow.addPoint((int) valueP1, (int) valueP2);
761                     g2.fill(arrow);
762
763                     Ellipse2D JavaDoc circle = new Ellipse2D.Double JavaDoc(
764                         meterMiddleX - DEFAULT_CIRCLE_SIZE / 2,
765                         meterMiddleY - DEFAULT_CIRCLE_SIZE / 2,
766                         DEFAULT_CIRCLE_SIZE, DEFAULT_CIRCLE_SIZE
767                     );
768                     g2.fill(circle);
769                 }
770             }
771                 
772
773             g2.clip(savedClip);
774             g2.setComposite(originalComposite);
775
776         }
777         if (this.drawBorder) {
778             drawOutline(g2, area);
779         }
780
781     }
782
783     /**
784      * Draws the arc to represent an interval.
785      *
786      * @param g2 the graphics device.
787      * @param meterArea the drawing area.
788      * @param interval the interval.
789      */

790     protected void drawArcForInterval(Graphics2D JavaDoc g2, Rectangle2D JavaDoc meterArea,
791                                       MeterInterval interval) {
792
793         double minValue = interval.getRange().getLowerBound();
794         double maxValue = interval.getRange().getUpperBound();
795         Paint JavaDoc outlinePaint = interval.getOutlinePaint();
796         Stroke JavaDoc outlineStroke = interval.getOutlineStroke();
797         Paint JavaDoc backgroundPaint = interval.getBackgroundPaint();
798  
799         if (backgroundPaint != null) {
800             fillArc(g2, meterArea, minValue, maxValue, backgroundPaint, false);
801         }
802         if (outlinePaint != null) {
803             if (outlineStroke != null) {
804                 drawArc(
805                     g2, meterArea, minValue, maxValue,
806                     outlinePaint, outlineStroke
807                 );
808             }
809             drawTick(g2, meterArea, minValue, true, outlinePaint);
810             drawTick(g2, meterArea, maxValue, true, outlinePaint);
811         }
812     }
813
814     /**
815      * Draws an arc.
816      *
817      * @param g2 the graphics device.
818      * @param area the plot area.
819      * @param minValue the minimum value.
820      * @param maxValue the maximum value.
821      * @param paint the paint.
822      * @param stroke the stroke.
823      */

824     protected void drawArc(Graphics2D JavaDoc g2, Rectangle2D JavaDoc area, double minValue,
825                            double maxValue, Paint JavaDoc paint, Stroke JavaDoc stroke) {
826
827         double startAngle = valueToAngle(maxValue);
828         double endAngle = valueToAngle(minValue);
829         double extent = endAngle - startAngle;
830
831         double x = area.getX();
832         double y = area.getY();
833         double w = area.getWidth();
834         double h = area.getHeight();
835         g2.setPaint(paint);
836         g2.setStroke(stroke);
837
838         if (paint != null && stroke != null) {
839             Arc2D.Double JavaDoc arc = new Arc2D.Double JavaDoc(
840                 x, y, w, h, startAngle, extent, Arc2D.OPEN
841             );
842             g2.setPaint(paint);
843             g2.setStroke(stroke);
844             g2.draw(arc);
845         }
846
847     }
848
849     /**
850      * Fills an arc on the dial between the given values.
851      *
852      * @param g2 the graphics device.
853      * @param area the plot area.
854      * @param minValue the minimum data value.
855      * @param maxValue the maximum data value.
856      * @param paint the background paint (<code>null</code> not permitted).
857      */

858     private void fillArc(Graphics2D JavaDoc g2, Rectangle2D JavaDoc area,
859                          double minValue, double maxValue, Paint JavaDoc paint,
860                          boolean dial) {
861         if (paint == null) {
862             throw new IllegalArgumentException JavaDoc("Null 'paint' argument");
863         }
864         double startAngle = valueToAngle(maxValue);
865         double endAngle = valueToAngle(minValue);
866         double extent = endAngle - startAngle;
867
868         double x = area.getX();
869         double y = area.getY();
870         double w = area.getWidth();
871         double h = area.getHeight();
872         int joinType = Arc2D.OPEN;
873         if (this.shape == DialShape.PIE) {
874             joinType = Arc2D.PIE;
875         }
876         else if (this.shape == DialShape.CHORD) {
877             if (dial && this.meterAngle > 180) {
878                 joinType = Arc2D.CHORD;
879             }
880             else {
881                 joinType = Arc2D.PIE;
882             }
883         }
884         else if (this.shape == DialShape.CIRCLE) {
885             joinType = Arc2D.PIE;
886             if (dial) {
887                 extent = 360;
888             }
889         }
890         else {
891             throw new IllegalStateException JavaDoc("DialShape not recognised.");
892         }
893
894         g2.setPaint(paint);
895         Arc2D.Double JavaDoc arc = new Arc2D.Double JavaDoc(
896             x, y, w, h, startAngle, extent, joinType
897         );
898         g2.fill(arc);
899     }
900     
901     /**
902      * Translates a data value to an angle on the dial.
903      *
904      * @param value the value.
905      *
906      * @return The angle on the dial.
907      */

908     public double valueToAngle(double value) {
909         value = value - this.range.getLowerBound();
910         double baseAngle = 180 + ((this.meterAngle - 180) / 2);
911         return baseAngle - ((value / this.range.getLength()) * this.meterAngle);
912     }
913
914     /**
915      * Draws the 20 ticks that subdivide the overall range.
916      *
917      * @param g2 the graphics device.
918      * @param meterArea the meter area.
919      * @param minValue the minimum value.
920      * @param maxValue the maximum value.
921      */

922     protected void drawTicks(Graphics2D JavaDoc g2, Rectangle2D JavaDoc meterArea,
923                              double minValue, double maxValue) {
924         int numberOfTicks = 20;
925         double diff = (maxValue - minValue) / numberOfTicks;
926         for (double v = minValue; v <= maxValue; v += diff) {
927             drawTick(g2, meterArea, v);
928         }
929     }
930
931     /**
932      * Draws a tick.
933      *
934      * @param g2 the graphics device.
935      * @param meterArea the meter area.
936      * @param value the value.
937      */

938     protected void drawTick(Graphics2D JavaDoc g2, Rectangle2D JavaDoc meterArea,
939                             double value) {
940         drawTick(g2, meterArea, value, false, null, false, null);
941     }
942
943     /**
944      * Draws a tick.
945      *
946      * @param g2 the graphics device.
947      * @param meterArea the meter area.
948      * @param value the value.
949      * @param label display a label?
950      * @param paint the paint.
951      */

952     protected void drawTick(Graphics2D JavaDoc g2, Rectangle2D JavaDoc meterArea, double value,
953                             boolean label, Paint JavaDoc paint) {
954         drawTick(g2, meterArea, value, label, paint, false, null);
955     }
956
957     /**
958      * Draws a tick on the chart (also handles a special case [curValue=true]
959      * that draws the value in the middle of the dial).
960      *
961      * @param g2 the graphics device.
962      * @param meterArea the meter area.
963      * @param value the tick value.
964      * @param label a flag that controls whether or not a value label is drawn.
965      * @param labelPaint the label color.
966      * @param curValue a flag for the special case of the current value.
967      * @param units the unit-of-measure for the dial.
968      */

969     protected void drawTick(Graphics2D JavaDoc g2, Rectangle2D JavaDoc meterArea,
970                             double value, boolean label, Paint JavaDoc labelPaint,
971                             boolean curValue, String JavaDoc units) {
972
973         double valueAngle = valueToAngle(value);
974
975         double meterMiddleX = meterArea.getCenterX();
976         double meterMiddleY = meterArea.getCenterY();
977
978         if (labelPaint == null) {
979             labelPaint = Color.white;
980         }
981         g2.setPaint(labelPaint);
982         g2.setStroke(new BasicStroke JavaDoc(2.0f));
983
984         double valueP2X = 0;
985         double valueP2Y = 0;
986
987         if (!curValue) {
988             double radius = (meterArea.getWidth() / 2) + DEFAULT_BORDER_SIZE;
989             double radius1 = radius - 15;
990
991             double valueP1X = meterMiddleX
992                 + (radius * Math.cos(Math.PI * (valueAngle / 180)));
993             double valueP1Y = meterMiddleY
994                 - (radius * Math.sin(Math.PI * (valueAngle / 180)));
995
996             valueP2X = meterMiddleX
997                        + (radius1 * Math.cos(Math.PI * (valueAngle / 180)));
998             valueP2Y = meterMiddleY
999                        - (radius1 * Math.sin(Math.PI * (valueAngle / 180)));
1000
1001            Line2D.Double JavaDoc line = new Line2D.Double JavaDoc(
1002                valueP1X, valueP1Y, valueP2X, valueP2Y
1003            );
1004            g2.draw(line);
1005        }
1006        else {
1007            valueP2X = meterMiddleX;
1008            valueP2Y = meterMiddleY;
1009            valueAngle = 90;
1010        }
1011
1012        if (this.tickLabelsVisible && label) {
1013
1014            String JavaDoc tickLabel = this.tickLabelFormat.format(value);
1015            if (curValue && units != null) {
1016                tickLabel += " " + units;
1017            }
1018            if (curValue) {
1019                g2.setFont(getValueFont());
1020            }
1021            else {
1022                if (this.tickLabelFont != null) {
1023                    g2.setFont(this.tickLabelFont);
1024                }
1025            }
1026
1027            FontMetrics JavaDoc fm = g2.getFontMetrics();
1028            Rectangle2D JavaDoc tickLabelBounds
1029                = TextUtilities.getTextBounds(tickLabel, g2, fm);
1030
1031            double x = valueP2X;
1032            double y = valueP2Y;
1033            if (curValue) {
1034                y += DEFAULT_CIRCLE_SIZE;
1035            }
1036            if (valueAngle == 90 || valueAngle == 270) {
1037                x = x - tickLabelBounds.getWidth() / 2;
1038            }
1039            else if (valueAngle < 90 || valueAngle > 270) {
1040                x = x - tickLabelBounds.getWidth();
1041            }
1042            if ((valueAngle > 135 && valueAngle < 225)
1043                    || valueAngle > 315 || valueAngle < 45) {
1044                y = y - tickLabelBounds.getHeight() / 2;
1045            }
1046            else {
1047                y = y + tickLabelBounds.getHeight() / 2;
1048            }
1049            g2.drawString(tickLabel, (float) x, (float) y);
1050        }
1051    }
1052
1053    /**
1054     * Returns a short string describing the type of plot.
1055     *
1056     * @return A string describing the type of plot.
1057     */

1058    public String JavaDoc getPlotType() {
1059        return localizationResources.getString("Meter_Plot");
1060    }
1061
1062    /**
1063     * A zoom method that does nothing. Plots are required to support the
1064     * zoom operation. In the case of a meter plot, it doesn't make sense to
1065     * zoom in or out, so the method is empty.
1066     *
1067     * @param percent The zoom percentage.
1068     */

1069    public void zoom(double percent) {
1070        // intentionally blank
1071
}
1072    
1073    /**
1074     * Tests the plot for equality with an arbitrary object. Note that the
1075     * dataset is ignored for the purposes of testing equality.
1076     *
1077     * @param object the object (<code>null</code> permitted).
1078     *
1079     * @return A boolean.
1080     */

1081    public boolean equals(Object JavaDoc object) {
1082        if (object == this) {
1083            return true;
1084        }
1085        if (object instanceof MeterPlot && super.equals(object)) {
1086            MeterPlot that = (MeterPlot) object;
1087            if (!ObjectUtilities.equal(this.units, that.units)) {
1088                return false;
1089            }
1090            if (!ObjectUtilities.equal(this.range, that.range)) {
1091                return false;
1092            }
1093            if (!ObjectUtilities.equal(this.intervals, that.intervals)) {
1094                return false;
1095            }
1096            if (!ObjectUtilities.equal(this.dialOutlinePaint,
1097                    that.dialOutlinePaint)) {
1098                return false;
1099            }
1100            if (this.shape != that.shape) {
1101                return false;
1102            }
1103            if (!ObjectUtilities.equal(this.dialBackgroundPaint,
1104                    that.dialBackgroundPaint)) {
1105                return false;
1106            }
1107            if (!ObjectUtilities.equal(this.needlePaint, that.needlePaint)) {
1108                return false;
1109            }
1110            if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) {
1111                return false;
1112            }
1113            if (!ObjectUtilities.equal(this.valuePaint, that.valuePaint)) {
1114                return false;
1115            }
1116            if (this.tickLabelsVisible != that.tickLabelsVisible) {
1117                return false;
1118            }
1119            if (!ObjectUtilities.equal(this.tickLabelFont,
1120                    that.tickLabelFont)) {
1121                return false;
1122            }
1123            if (!ObjectUtilities.equal(this.tickLabelFormat,
1124                    that.tickLabelFormat)) {
1125                return false;
1126            }
1127            if (this.drawBorder != that.drawBorder) {
1128                return false;
1129            }
1130            if (this.meterAngle != that.meterAngle) {
1131                return false;
1132            }
1133            
1134            return true;
1135        }
1136        return false;
1137    }
1138    
1139    /**
1140     * Provides serialization support.
1141     *
1142     * @param stream the output stream.
1143     *
1144     * @throws IOException if there is an I/O error.
1145     */

1146    private void writeObject(ObjectOutputStream JavaDoc stream) throws IOException JavaDoc {
1147        stream.defaultWriteObject();
1148        SerialUtilities.writePaint(this.dialBackgroundPaint, stream);
1149        SerialUtilities.writePaint(this.needlePaint, stream);
1150        SerialUtilities.writePaint(this.valuePaint, stream);
1151    }
1152    
1153    /**
1154     * Provides serialization support.
1155     *
1156     * @param stream the input stream.
1157     *
1158     * @throws IOException if there is an I/O error.
1159     * @throws ClassNotFoundException if there is a classpath problem.
1160     */

1161    private void readObject(ObjectInputStream JavaDoc stream)
1162        throws IOException JavaDoc, ClassNotFoundException JavaDoc {
1163        stream.defaultReadObject();
1164        this.dialBackgroundPaint = SerialUtilities.readPaint(stream);
1165        this.needlePaint = SerialUtilities.readPaint(stream);
1166        this.valuePaint = SerialUtilities.readPaint(stream);
1167        if (this.dataset != null) {
1168            this.dataset.addChangeListener(this);
1169        }
1170    }
1171
1172    /**
1173     * Returns an independent copy (clone) of the plot. The dataset is NOT
1174     * cloned - both the original and the clone will have a reference to the
1175     * same dataset.
1176     *
1177     * @return A clone.
1178     *
1179     * @throws CloneNotSupportedException if some component of the plot cannot
1180     * be cloned.
1181     */

1182    public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
1183        MeterPlot clone = (MeterPlot) super.clone();
1184        clone.tickLabelFormat = (NumberFormat JavaDoc) this.tickLabelFormat.clone();
1185        // the following relies on the fact that the intervals are immutable
1186
clone.intervals = new java.util.ArrayList JavaDoc(this.intervals);
1187        if (clone.dataset != null) {
1188            clone.dataset.addChangeListener(clone);
1189        }
1190        return clone;
1191    }
1192
1193}
1194
Popular Tags