KickJava   Java API By Example, From Geeks To Geeks.

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


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  * ThermometerPlot.java
28  * --------------------
29  *
30  * (C) Copyright 2000-2005, by Bryan Scott and Contributors.
31  *
32  * Original Author: Bryan Scott (based on MeterPlot by Hari).
33  * Contributor(s): David Gilbert (for Object Refinery Limited).
34  * Arnaud Lelievre;
35  *
36  * Changes
37  * -------
38  * 11-Apr-2002 : Version 1, contributed by Bryan Scott;
39  * 15-Apr-2002 : Changed to implement VerticalValuePlot;
40  * 29-Apr-2002 : Added getVerticalValueAxis() method (DG);
41  * 25-Jun-2002 : Removed redundant imports (DG);
42  * 17-Sep-2002 : Reviewed with Checkstyle utility (DG);
43  * 18-Sep-2002 : Extensive changes made to API, to iron out bugs and
44  * inconsistencies (DG);
45  * 13-Oct-2002 : Corrected error datasetChanged which would generate exceptions
46  * when value set to null (BRS).
47  * 23-Jan-2003 : Removed one constructor (DG);
48  * 26-Mar-2003 : Implemented Serializable (DG);
49  * 02-Jun-2003 : Removed test for compatible range axis (DG);
50  * 01-Jul-2003 : Added additional check in draw method to ensure value not
51  * null (BRS);
52  * 08-Sep-2003 : Added internationalization via use of properties
53  * resourceBundle (RFE 690236) (AL);
54  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
55  * 29-Sep-2003 : Updated draw to set value of cursor to non-zero and allow
56  * painting of axis. An incomplete fix and needs to be set for
57  * left or right drawing (BRS);
58  * 19-Nov-2003 : Added support for value labels to be displayed left of the
59  * thermometer
60  * 19-Nov-2003 : Improved axis drawing (now default axis does not draw axis line
61  * and is closer to the bulb). Added support for the positioning
62  * of the axis to the left or right of the bulb. (BRS);
63  * 03-Dec-2003 : Directly mapped deprecated setData()/getData() method to
64  * get/setDataset() (TM);
65  * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
66  * 07-Apr-2004 : Changed string width calculation (DG);
67  * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
68  * 06-Jan-2004 : Added getOrientation() method (DG);
69  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
70  * 29-Mar-2005 : Fixed equals() method (DG);
71  * 05-May-2005 : Updated draw() method parameters (DG);
72  *
73  */

74
75 package org.jfree.chart.plot;
76
77 import java.awt.BasicStroke JavaDoc;
78 import java.awt.Color JavaDoc;
79 import java.awt.Font JavaDoc;
80 import java.awt.FontMetrics JavaDoc;
81 import java.awt.Graphics2D JavaDoc;
82 import java.awt.Paint JavaDoc;
83 import java.awt.Stroke JavaDoc;
84 import java.awt.geom.Area JavaDoc;
85 import java.awt.geom.Ellipse2D JavaDoc;
86 import java.awt.geom.Line2D JavaDoc;
87 import java.awt.geom.Point2D JavaDoc;
88 import java.awt.geom.Rectangle2D JavaDoc;
89 import java.awt.geom.RoundRectangle2D JavaDoc;
90 import java.io.IOException JavaDoc;
91 import java.io.ObjectInputStream JavaDoc;
92 import java.io.ObjectOutputStream JavaDoc;
93 import java.io.Serializable JavaDoc;
94 import java.text.DecimalFormat JavaDoc;
95 import java.text.NumberFormat JavaDoc;
96 import java.util.ResourceBundle JavaDoc;
97
98 import org.jfree.chart.LegendItemCollection;
99 import org.jfree.chart.axis.NumberAxis;
100 import org.jfree.chart.axis.ValueAxis;
101 import org.jfree.chart.event.PlotChangeEvent;
102 import org.jfree.data.Range;
103 import org.jfree.data.general.DatasetChangeEvent;
104 import org.jfree.data.general.DefaultValueDataset;
105 import org.jfree.data.general.ValueDataset;
106 import org.jfree.io.SerialUtilities;
107 import org.jfree.ui.RectangleEdge;
108 import org.jfree.ui.RectangleInsets;
109 import org.jfree.util.ObjectUtilities;
110 import org.jfree.util.UnitType;
111
112 /**
113  * A plot that displays a single value (from a {@link ValueDataset}) in a
114  * thermometer type display.
115  * <p>
116  * This plot supports a number of options:
117  * <ol>
118  * <li>three sub-ranges which could be viewed as 'Normal', 'Warning'
119  * and 'Critical' ranges.</li>
120  * <li>the thermometer can be run in two modes:
121  * <ul>
122  * <li>fixed range, or</li>
123  * <li>range adjusts to current sub-range.</li>
124  * </ul>
125  * </li>
126  * <li>settable units to be displayed.</li>
127  * <li>settable display location for the value text.</li>
128  * </ol>
129  *
130  * @author Bryan Scott
131  */

132 public class ThermometerPlot extends Plot implements ValueAxisPlot,
133                                                      Zoomable,
134                                                      Cloneable JavaDoc,
135                                                      Serializable JavaDoc {
136
137     /** For serialization. */
138     private static final long serialVersionUID = 4087093313147984390L;
139     
140     /** A constant for unit type 'None'. */
141     public static final int UNITS_NONE = 0;
142
143     /** A constant for unit type 'Fahrenheit'. */
144     public static final int UNITS_FAHRENHEIT = 1;
145
146     /** A constant for unit type 'Celcius'. */
147     public static final int UNITS_CELCIUS = 2;
148
149     /** A constant for unit type 'Kelvin'. */
150     public static final int UNITS_KELVIN = 3;
151
152     /** A constant for the value label position (no label). */
153     public static final int NONE = 0;
154
155     /** A constant for the value label position (right of the thermometer). */
156     public static final int RIGHT = 1;
157
158     /** A constant for the value label position (left of the thermometer). */
159     public static final int LEFT = 2;
160
161     /** A constant for the value label position (in the thermometer bulb). */
162     public static final int BULB = 3;
163
164     /** A constant for the 'normal' range. */
165     public static final int NORMAL = 0;
166
167     /** A constant for the 'warning' range. */
168     public static final int WARNING = 1;
169
170     /** A constant for the 'critical' range. */
171     public static final int CRITICAL = 2;
172
173     /** The bulb radius. */
174     protected static final int BULB_RADIUS = 40;
175
176     /** The bulb diameter. */
177     protected static final int BULB_DIAMETER = BULB_RADIUS * 2;
178
179     /** The column radius. */
180     protected static final int COLUMN_RADIUS = 20;
181
182     /** The column diameter.*/
183     protected static final int COLUMN_DIAMETER = COLUMN_RADIUS * 2;
184
185     /** The gap radius. */
186     protected static final int GAP_RADIUS = 5;
187
188     /** The gap diameter. */
189     protected static final int GAP_DIAMETER = GAP_RADIUS * 2;
190
191     /** The axis gap. */
192     protected static final int AXIS_GAP = 10;
193
194     /** The unit strings. */
195     protected static final String JavaDoc[] UNITS
196         = {"", "\u00B0F", "\u00B0C", "\u00B0K"};
197
198     /** Index for low value in subrangeInfo matrix. */
199     protected static final int RANGE_LOW = 0;
200
201     /** Index for high value in subrangeInfo matrix. */
202     protected static final int RANGE_HIGH = 1;
203
204     /** Index for display low value in subrangeInfo matrix. */
205     protected static final int DISPLAY_LOW = 2;
206
207     /** Index for display high value in subrangeInfo matrix. */
208     protected static final int DISPLAY_HIGH = 3;
209
210     /** The default lower bound. */
211     protected static final double DEFAULT_LOWER_BOUND = 0.0;
212
213     /** The default upper bound. */
214     protected static final double DEFAULT_UPPER_BOUND = 100.0;
215
216     /** The dataset for the plot. */
217     private ValueDataset dataset;
218
219     /** The range axis. */
220     private ValueAxis rangeAxis;
221
222     /** The lower bound for the thermometer. */
223     private double lowerBound = DEFAULT_LOWER_BOUND;
224
225     /** The upper bound for the thermometer. */
226     private double upperBound = DEFAULT_UPPER_BOUND;
227
228     /**
229      * Blank space inside the plot area around the outside of the thermometer.
230      */

231     private RectangleInsets padding;
232
233     /** Stroke for drawing the thermometer */
234     private transient Stroke JavaDoc thermometerStroke = new BasicStroke JavaDoc(1.0f);
235
236     /** Paint for drawing the thermometer */
237     private transient Paint JavaDoc thermometerPaint = Color.black;
238
239     /** The display units */
240     private int units = UNITS_CELCIUS;
241
242     /** The value label position. */
243     private int valueLocation = BULB;
244
245     /** The position of the axis **/
246     private int axisLocation = LEFT;
247
248     /** The font to write the value in */
249     private Font JavaDoc valueFont = new Font JavaDoc("SansSerif", Font.BOLD, 16);
250
251     /** Colour that the value is written in */
252     private transient Paint JavaDoc valuePaint = Color.white;
253
254     /** Number format for the value */
255     private NumberFormat JavaDoc valueFormat = new DecimalFormat JavaDoc();
256
257     /** The default paint for the mercury in the thermometer. */
258     private transient Paint JavaDoc mercuryPaint = Color.lightGray;
259
260     /** A flag that controls whether value lines are drawn. */
261     private boolean showValueLines = false;
262
263     /** The display sub-range. */
264     private int subrange = -1;
265
266     /** The start and end values for the subranges. */
267     private double[][] subrangeInfo = {
268         {0.0, 50.0, 0.0, 50.0},
269         {50.0, 75.0, 50.0, 75.0},
270         {75.0, 100.0, 75.0, 100.0}
271     };
272
273     /**
274      * A flag that controls whether or not the axis range adjusts to the
275      * sub-ranges.
276      */

277     private boolean followDataInSubranges = false;
278
279     /**
280      * A flag that controls whether or not the mercury paint changes with
281      * the subranges.
282      */

283     private boolean useSubrangePaint = true;
284
285     /** Paint for each range */
286     private Paint JavaDoc[] subrangePaint = {
287         Color.green,
288         Color.orange,
289         Color.red
290     };
291
292     /** A flag that controls whether the sub-range indicators are visible. */
293     private boolean subrangeIndicatorsVisible = true;
294
295     /** The stroke for the sub-range indicators. */
296     private transient Stroke JavaDoc subrangeIndicatorStroke = new BasicStroke JavaDoc(2.0f);
297
298     /** The range indicator stroke. */
299     private transient Stroke JavaDoc rangeIndicatorStroke = new BasicStroke JavaDoc(3.0f);
300
301     /** The resourceBundle for the localization. */
302     protected static ResourceBundle JavaDoc localizationResources =
303         ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
304
305     /**
306      * Creates a new thermometer plot.
307      */

308     public ThermometerPlot() {
309         this(new DefaultValueDataset());
310     }
311
312     /**
313      * Creates a new thermometer plot, using default attributes where necessary.
314      *
315      * @param dataset the data set.
316      */

317     public ThermometerPlot(ValueDataset dataset) {
318
319         super();
320
321         this.padding = new RectangleInsets(
322             UnitType.RELATIVE, 0.05, 0.05, 0.05, 0.05
323         );
324         this.dataset = dataset;
325         if (dataset != null) {
326             dataset.addChangeListener(this);
327         }
328         NumberAxis axis = new NumberAxis(null);
329         axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
330         axis.setAxisLineVisible(false);
331
332         setRangeAxis(axis);
333         setAxisRange();
334     }
335
336     /**
337      * Returns the primary dataset for the plot.
338      *
339      * @return The primary dataset (possibly <code>null</code>).
340      */

341     public ValueDataset getDataset() {
342         return this.dataset;
343     }
344
345     /**
346      * Sets the dataset for the plot, replacing the existing dataset if there
347      * is one.
348      *
349      * @param dataset the dataset (<code>null</code> permitted).
350      */

351     public void setDataset(ValueDataset dataset) {
352
353         // if there is an existing dataset, remove the plot from the list
354
// of change listeners...
355
ValueDataset existing = this.dataset;
356         if (existing != null) {
357             existing.removeChangeListener(this);
358         }
359
360         // set the new dataset, and register the chart as a change listener...
361
this.dataset = dataset;
362         if (dataset != null) {
363             setDatasetGroup(dataset.getGroup());
364             dataset.addChangeListener(this);
365         }
366
367         // send a dataset change event to self...
368
DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
369         datasetChanged(event);
370
371     }
372
373     /**
374      * Returns the range axis.
375      *
376      * @return The range axis.
377      */

378     public ValueAxis getRangeAxis() {
379         return this.rangeAxis;
380     }
381
382     /**
383      * Sets the range axis for the plot.
384      *
385      * @param axis the new axis.
386      */

387     public void setRangeAxis(ValueAxis axis) {
388
389         if (axis != null) {
390             axis.setPlot(this);
391             axis.addChangeListener(this);
392         }
393
394         // plot is likely registered as a listener with the existing axis...
395
if (this.rangeAxis != null) {
396             this.rangeAxis.removeChangeListener(this);
397         }
398
399         this.rangeAxis = axis;
400
401     }
402
403     /**
404      * Returns the lower bound for the thermometer. The data value can be set
405      * lower than this, but it will not be shown in the thermometer.
406      *
407      * @return The lower bound.
408      *
409      */

410     public double getLowerBound() {
411         return this.lowerBound;
412     }
413
414     /**
415      * Sets the lower bound for the thermometer.
416      *
417      * @param lower the lower bound.
418      */

419     public void setLowerBound(double lower) {
420         this.lowerBound = lower;
421         setAxisRange();
422     }
423
424     /**
425      * Returns the upper bound for the thermometer. The data value can be set
426      * higher than this, but it will not be shown in the thermometer.
427      *
428      * @return The upper bound.
429      */

430     public double getUpperBound() {
431         return this.upperBound;
432     }
433
434     /**
435      * Sets the upper bound for the thermometer.
436      *
437      * @param upper the upper bound.
438      */

439     public void setUpperBound(double upper) {
440         this.upperBound = upper;
441         setAxisRange();
442     }
443
444     /**
445      * Sets the lower and upper bounds for the thermometer.
446      *
447      * @param lower the lower bound.
448      * @param upper the upper bound.
449      */

450     public void setRange(double lower, double upper) {
451         this.lowerBound = lower;
452         this.upperBound = upper;
453         setAxisRange();
454     }
455
456     /**
457      * Returns the padding for the thermometer. This is the space inside the
458      * plot area.
459      *
460      * @return The padding.
461      */

462     public RectangleInsets getPadding() {
463         return this.padding;
464     }
465
466     /**
467      * Sets the padding for the thermometer.
468      *
469      * @param padding the padding.
470      */

471     public void setPadding(RectangleInsets padding) {
472         this.padding = padding;
473         notifyListeners(new PlotChangeEvent(this));
474     }
475
476     /**
477      * Returns the stroke used to draw the thermometer outline.
478      *
479      * @return The stroke.
480      */

481     public Stroke JavaDoc getThermometerStroke() {
482         return this.thermometerStroke;
483     }
484
485     /**
486      * Sets the stroke used to draw the thermometer outline.
487      *
488      * @param s the new stroke (null ignored).
489      */

490     public void setThermometerStroke(Stroke JavaDoc s) {
491         if (s != null) {
492             this.thermometerStroke = s;
493             notifyListeners(new PlotChangeEvent(this));
494         }
495     }
496
497     /**
498      * Returns the paint used to draw the thermometer outline.
499      *
500      * @return The paint.
501      */

502     public Paint JavaDoc getThermometerPaint() {
503         return this.thermometerPaint;
504     }
505
506     /**
507      * Sets the paint used to draw the thermometer outline.
508      *
509      * @param paint the new paint (null ignored).
510      */

511     public void setThermometerPaint(Paint JavaDoc paint) {
512         if (paint != null) {
513             this.thermometerPaint = paint;
514             notifyListeners(new PlotChangeEvent(this));
515         }
516     }
517
518     /**
519      * Returns the unit display type (none/Fahrenheit/Celcius/Kelvin).
520      *
521      * @return The units type.
522      */

523     public int getUnits() {
524         return this.units;
525     }
526
527     /**
528      * Sets the units to be displayed in the thermometer.
529      * <p>
530      * Use one of the following constants:
531      *
532      * <ul>
533      * <li>UNITS_NONE : no units displayed.</li>
534      * <li>UNITS_FAHRENHEIT : units displayed in Fahrenheit.</li>
535      * <li>UNITS_CELCIUS : units displayed in Celcius.</li>
536      * <li>UNITS_KELVIN : units displayed in Kelvin.</li>
537      * </ul>
538      *
539      * @param u the new unit type.
540      */

541     public void setUnits(int u) {
542         if ((u >= 0) && (u < UNITS.length)) {
543             if (this.units != u) {
544                 this.units = u;
545                 notifyListeners(new PlotChangeEvent(this));
546             }
547         }
548     }
549
550     /**
551      * Sets the unit type.
552      *
553      * @param u the unit type (null ignored).
554      */

555     public void setUnits(String JavaDoc u) {
556         if (u == null) {
557             return;
558         }
559
560         u = u.toUpperCase().trim();
561         for (int i = 0; i < UNITS.length; ++i) {
562             if (u.equals(UNITS[i].toUpperCase().trim())) {
563                 setUnits(i);
564                 i = UNITS.length;
565             }
566         }
567     }
568
569     /**
570      * Returns the value location.
571      *
572      * @return The location.
573      */

574     public int getValueLocation() {
575         return this.valueLocation;
576     }
577
578     /**
579      * Sets the location at which the current value is displayed.
580      * <P>
581      * The location can be one of the constants:
582      * <code>NONE</code>,
583      * <code>RIGHT</code>
584      * <code>LEFT</code> and
585      * <code>BULB</code>.
586      *
587      * @param location the location.
588      */

589     public void setValueLocation(int location) {
590         if ((location >= 0) && (location < 4)) {
591             this.valueLocation = location;
592             notifyListeners(new PlotChangeEvent(this));
593         }
594         else {
595             throw new IllegalArgumentException JavaDoc("Location not recognised.");
596         }
597     }
598
599     /**
600      * Sets the location at which the axis is displayed with reference to the
601      * bulb.
602      * <P>
603      * The location can be one of the constants:
604      * <code>NONE</code>,
605      * <code>RIGHT</code> and
606      * <code>LEFT</code>.
607      *
608      * @param location the location.
609      */

610     public void setAxisLocation(int location) {
611         if ((location >= 0) && (location < 3)) {
612             this.axisLocation = location;
613             notifyListeners(new PlotChangeEvent(this));
614         }
615         else {
616             throw new IllegalArgumentException JavaDoc("Location not recognised.");
617         }
618     }
619
620     /**
621      * Returns the axis location.
622      *
623      * @return The location.
624      */

625     public int getAxisLocation() {
626         return this.axisLocation;
627     }
628
629     /**
630      * Gets the font used to display the current value.
631      *
632      * @return The font.
633      */

634     public Font JavaDoc getValueFont() {
635         return this.valueFont;
636     }
637
638     /**
639      * Sets the font used to display the current value.
640      *
641      * @param f the new font.
642      */

643     public void setValueFont(Font JavaDoc f) {
644         if ((f != null) && (!this.valueFont.equals(f))) {
645             this.valueFont = f;
646             notifyListeners(new PlotChangeEvent(this));
647         }
648     }
649
650     /**
651      * Gets the paint used to display the current value.
652     *
653      * @return The paint.
654      */

655     public Paint JavaDoc getValuePaint() {
656         return this.valuePaint;
657     }
658
659     /**
660      * Sets the paint used to display the current value.
661      *
662      * @param p the new paint.
663      */

664     public void setValuePaint(Paint JavaDoc p) {
665         if ((p != null) && (!this.valuePaint.equals(p))) {
666             this.valuePaint = p;
667             notifyListeners(new PlotChangeEvent(this));
668         }
669     }
670
671     /**
672      * Sets the formatter for the value label.
673      *
674      * @param formatter the new formatter.
675      */

676     public void setValueFormat(NumberFormat JavaDoc formatter) {
677         if (formatter != null) {
678             this.valueFormat = formatter;
679             notifyListeners(new PlotChangeEvent(this));
680         }
681     }
682
683     /**
684      * Returns the default mercury paint.
685      *
686      * @return The paint.
687      */

688     public Paint JavaDoc getMercuryPaint() {
689         return this.mercuryPaint;
690     }
691
692     /**
693      * Sets the default mercury paint.
694      *
695      * @param paint the new paint.
696      */

697     public void setMercuryPaint(Paint JavaDoc paint) {
698         this.mercuryPaint = paint;
699         notifyListeners(new PlotChangeEvent(this));
700     }
701
702     /**
703      * Returns the flag that controls whether not value lines are displayed.
704      *
705      * @return The flag.
706      */

707     public boolean getShowValueLines() {
708         return this.showValueLines;
709     }
710
711     /**
712      * Sets the display as to whether to show value lines in the output.
713      *
714      * @param b Whether to show value lines in the thermometer
715      */

716     public void setShowValueLines(boolean b) {
717         this.showValueLines = b;
718         notifyListeners(new PlotChangeEvent(this));
719     }
720
721     /**
722      * Sets information for a particular range.
723      *
724      * @param range the range to specify information about.
725      * @param low the low value for the range
726      * @param hi the high value for the range
727      */

728     public void setSubrangeInfo(int range, double low, double hi) {
729         setSubrangeInfo(range, low, hi, low, hi);
730     }
731
732     /**
733      * Sets the subrangeInfo attribute of the ThermometerPlot object
734      *
735      * @param range the new rangeInfo value.
736      * @param rangeLow the new rangeInfo value
737      * @param rangeHigh the new rangeInfo value
738      * @param displayLow the new rangeInfo value
739      * @param displayHigh the new rangeInfo value
740      */

741     public void setSubrangeInfo(int range,
742                                 double rangeLow, double rangeHigh,
743                                 double displayLow, double displayHigh) {
744
745         if ((range >= 0) && (range < 3)) {
746             setSubrange(range, rangeLow, rangeHigh);
747             setDisplayRange(range, displayLow, displayHigh);
748             setAxisRange();
749             notifyListeners(new PlotChangeEvent(this));
750         }
751
752     }
753
754     /**
755      * Sets the range.
756      *
757      * @param range the range type.
758      * @param low the low value.
759      * @param high the high value.
760      */

761     public void setSubrange(int range, double low, double high) {
762         if ((range >= 0) && (range < 3)) {
763             this.subrangeInfo[range][RANGE_HIGH] = high;
764             this.subrangeInfo[range][RANGE_LOW] = low;
765         }
766     }
767
768     /**
769      * Sets the display range.
770      *
771      * @param range the range type.
772      * @param low the low value.
773      * @param high the high value.
774      */

775     public void setDisplayRange(int range, double low, double high) {
776
777         if ((range >= 0) && (range < this.subrangeInfo.length)
778             && isValidNumber(high) && isValidNumber(low)) {
779  
780             if (high > low) {
781                 this.subrangeInfo[range][DISPLAY_HIGH] = high;
782                 this.subrangeInfo[range][DISPLAY_LOW] = low;
783             }
784             else {
785                 this.subrangeInfo[range][DISPLAY_HIGH] = high;
786                 this.subrangeInfo[range][DISPLAY_LOW] = low;
787             }
788
789         }
790
791     }
792
793     /**
794      * Gets the paint used for a particular subrange.
795      *
796      * @param range the range.
797      *
798      * @return The paint.
799      */

800     public Paint JavaDoc getSubrangePaint(int range) {
801         if ((range >= 0) && (range < this.subrangePaint.length)) {
802             return this.subrangePaint[range];
803         }
804         else {
805             return this.mercuryPaint;
806         }
807     }
808
809     /**
810      * Sets the paint to be used for a range.
811      *
812      * @param range the range.
813      * @param paint the paint to be applied.
814      */

815     public void setSubrangePaint(int range, Paint JavaDoc paint) {
816         if ((range >= 0)
817                 && (range < this.subrangePaint.length) && (paint != null)) {
818             this.subrangePaint[range] = paint;
819             notifyListeners(new PlotChangeEvent(this));
820         }
821     }
822
823     /**
824      * Returns a flag that controls whether or not the thermometer axis zooms
825      * to display the subrange within which the data value falls.
826      *
827      * @return The flag.
828      */

829     public boolean getFollowDataInSubranges() {
830         return this.followDataInSubranges;
831     }
832
833     /**
834      * Sets the flag that controls whether or not the thermometer axis zooms
835      * to display the subrange within which the data value falls.
836      *
837      * @param flag the flag.
838      */

839     public void setFollowDataInSubranges(boolean flag) {
840         this.followDataInSubranges = flag;
841         notifyListeners(new PlotChangeEvent(this));
842     }
843
844     /**
845      * Returns a flag that controls whether or not the mercury color changes
846      * for each subrange.
847      *
848      * @return The flag.
849      */

850     public boolean getUseSubrangePaint() {
851         return this.useSubrangePaint;
852     }
853
854     /**
855      * Sets the range colour change option.
856      *
857      * @param flag The new range colour change option
858      */

859     public void setUseSubrangePaint(boolean flag) {
860         this.useSubrangePaint = flag;
861         notifyListeners(new PlotChangeEvent(this));
862     }
863
864     /**
865      * Draws the plot on a Java 2D graphics device (such as the screen or a
866      * printer).
867      *
868      * @param g2 the graphics device.
869      * @param area the area within which the plot should be drawn.
870      * @param anchor the anchor point (<code>null</code> permitted).
871      * @param parentState the state from the parent plot, if there is one.
872      * @param info collects info about the drawing.
873      */

874     public void draw(Graphics2D JavaDoc g2, Rectangle2D JavaDoc area, Point2D JavaDoc anchor,
875                      PlotState parentState,
876                      PlotRenderingInfo info) {
877
878         RoundRectangle2D JavaDoc outerStem = new RoundRectangle2D.Double JavaDoc();
879         RoundRectangle2D JavaDoc innerStem = new RoundRectangle2D.Double JavaDoc();
880         RoundRectangle2D JavaDoc mercuryStem = new RoundRectangle2D.Double JavaDoc();
881         Ellipse2D JavaDoc outerBulb = new Ellipse2D.Double JavaDoc();
882         Ellipse2D JavaDoc innerBulb = new Ellipse2D.Double JavaDoc();
883         String JavaDoc temp = null;
884         FontMetrics JavaDoc metrics = null;
885         if (info != null) {
886             info.setPlotArea(area);
887         }
888
889         // adjust for insets...
890
RectangleInsets insets = getInsets();
891         insets.trim(area);
892         drawBackground(g2, area);
893
894         // adjust for padding...
895
//this.padding.trim(plotArea);
896
int midX = (int) (area.getX() + (area.getWidth() / 2));
897         int midY = (int) (area.getY() + (area.getHeight() / 2));
898         int stemTop = (int) (area.getMinY() + BULB_RADIUS);
899         int stemBottom = (int) (area.getMaxY() - BULB_DIAMETER);
900         Rectangle2D JavaDoc dataArea = new Rectangle2D.Double JavaDoc(
901             midX - COLUMN_RADIUS, stemTop, COLUMN_RADIUS, stemBottom - stemTop
902         );
903
904         outerBulb.setFrame(
905             midX - BULB_RADIUS, stemBottom, BULB_DIAMETER, BULB_DIAMETER
906         );
907
908         outerStem.setRoundRect(
909             midX - COLUMN_RADIUS, area.getMinY(), COLUMN_DIAMETER,
910             stemBottom + BULB_DIAMETER - stemTop,
911             COLUMN_DIAMETER, COLUMN_DIAMETER
912         );
913
914         Area JavaDoc outerThermometer = new Area JavaDoc(outerBulb);
915         Area JavaDoc tempArea = new Area JavaDoc(outerStem);
916         outerThermometer.add(tempArea);
917
918         innerBulb.setFrame(
919             midX - BULB_RADIUS + GAP_RADIUS, stemBottom + GAP_RADIUS,
920             BULB_DIAMETER - GAP_DIAMETER, BULB_DIAMETER - GAP_DIAMETER
921         );
922
923         innerStem.setRoundRect(
924             midX - COLUMN_RADIUS + GAP_RADIUS, area.getMinY() + GAP_RADIUS,
925             COLUMN_DIAMETER - GAP_DIAMETER,
926             stemBottom + BULB_DIAMETER - GAP_DIAMETER - stemTop,
927             COLUMN_DIAMETER - GAP_DIAMETER, COLUMN_DIAMETER - GAP_DIAMETER
928         );
929
930         Area JavaDoc innerThermometer = new Area JavaDoc(innerBulb);
931         tempArea = new Area JavaDoc(innerStem);
932         innerThermometer.add(tempArea);
933    
934         if ((this.dataset != null) && (this.dataset.getValue() != null)) {
935             double current = this.dataset.getValue().doubleValue();
936             double ds = this.rangeAxis.valueToJava2D(
937                 current, dataArea, RectangleEdge.LEFT
938             );
939
940             int i = COLUMN_DIAMETER - GAP_DIAMETER; // already calculated
941
int j = COLUMN_RADIUS - GAP_RADIUS; // already calculated
942
int l = (i / 2);
943             int k = (int) Math.round(ds);
944             if (k < (GAP_RADIUS + area.getMinY())) {
945                 k = (int) (GAP_RADIUS + area.getMinY());
946                 l = BULB_RADIUS;
947             }
948
949             Area JavaDoc mercury = new Area JavaDoc(innerBulb);
950
951             if (k < (stemBottom + BULB_RADIUS)) {
952                 mercuryStem.setRoundRect(
953                     midX - j, k, i, (stemBottom + BULB_RADIUS) - k, l, l
954                 );
955                 tempArea = new Area JavaDoc(mercuryStem);
956                 mercury.add(tempArea);
957             }
958
959             g2.setPaint(getCurrentPaint());
960             g2.fill(mercury);
961
962             // draw range indicators...
963
if (this.subrangeIndicatorsVisible) {
964                 g2.setStroke(this.subrangeIndicatorStroke);
965                 Range range = this.rangeAxis.getRange();
966
967                 // draw start of normal range
968
double value = this.subrangeInfo[NORMAL][RANGE_LOW];
969                 if (range.contains(value)) {
970                     double x = midX + COLUMN_RADIUS + 2;
971                     double y = this.rangeAxis.valueToJava2D(
972                         value, dataArea, RectangleEdge.LEFT
973                     );
974                     Line2D JavaDoc line = new Line2D.Double JavaDoc(x, y, x + 10, y);
975                     g2.setPaint(this.subrangePaint[NORMAL]);
976                     g2.draw(line);
977                 }
978
979                 // draw start of warning range
980
value = this.subrangeInfo[WARNING][RANGE_LOW];
981                 if (range.contains(value)) {
982                     double x = midX + COLUMN_RADIUS + 2;
983                     double y = this.rangeAxis.valueToJava2D(
984                         value, dataArea, RectangleEdge.LEFT
985                     );
986                     Line2D JavaDoc line = new Line2D.Double JavaDoc(x, y, x + 10, y);
987                     g2.setPaint(this.subrangePaint[WARNING]);
988                     g2.draw(line);
989                 }
990
991                 // draw start of critical range
992
value = this.subrangeInfo[CRITICAL][RANGE_LOW];
993                 if (range.contains(value)) {
994                     double x = midX + COLUMN_RADIUS + 2;
995                     double y = this.rangeAxis.valueToJava2D(
996                         value, dataArea, RectangleEdge.LEFT
997                     );
998                     Line2D JavaDoc line = new Line2D.Double JavaDoc(x, y, x + 10, y);
999                     g2.setPaint(this.subrangePaint[CRITICAL]);
1000                    g2.draw(line);
1001                }
1002            }
1003
1004            // draw the axis...
1005
if ((this.rangeAxis != null) && (this.axisLocation != NONE)) {
1006                int drawWidth = AXIS_GAP;
1007                if (this.showValueLines) {
1008                    drawWidth += COLUMN_DIAMETER;
1009                }
1010                Rectangle2D JavaDoc drawArea;
1011                double cursor = 0;
1012
1013                switch (this.axisLocation) {
1014                    case RIGHT:
1015                        cursor = midX + COLUMN_RADIUS;
1016                        drawArea = new Rectangle2D.Double JavaDoc(
1017                            cursor,
1018                            stemTop,
1019                            drawWidth,
1020                            (stemBottom - stemTop + 1)
1021                        );
1022                        this.rangeAxis.draw(
1023                            g2, cursor, area, drawArea,
1024                            RectangleEdge.RIGHT, null
1025                        );
1026                        break;
1027
1028                    case LEFT:
1029                    default:
1030                        //cursor = midX - COLUMN_RADIUS - AXIS_GAP;
1031
cursor = midX - COLUMN_RADIUS;
1032                        drawArea = new Rectangle2D.Double JavaDoc(
1033                            cursor,
1034                            stemTop,
1035                            drawWidth,
1036                            (stemBottom - stemTop + 1)
1037                        );
1038                        this.rangeAxis.draw(
1039                            g2, cursor, area, drawArea,
1040                            RectangleEdge.LEFT, null
1041                        );
1042                        break;
1043                }
1044                   
1045            }
1046
1047            // draw text value on screen
1048
g2.setFont(this.valueFont);
1049            g2.setPaint(this.valuePaint);
1050            metrics = g2.getFontMetrics();
1051            switch (this.valueLocation) {
1052                case RIGHT:
1053                    g2.drawString(
1054                        this.valueFormat.format(current),
1055                        midX + COLUMN_RADIUS + GAP_RADIUS, midY
1056                    );
1057                    break;
1058                case LEFT:
1059                    String JavaDoc valueString = this.valueFormat.format(current);
1060                    int stringWidth = metrics.stringWidth(valueString);
1061                    g2.drawString(
1062                        valueString,
1063                        midX - COLUMN_RADIUS - GAP_RADIUS - stringWidth, midY
1064                    );
1065                    break;
1066                case BULB:
1067                    temp = this.valueFormat.format(current);
1068                    i = metrics.stringWidth(temp) / 2;
1069                    g2.drawString(
1070                        temp, midX - i,
1071                        stemBottom + BULB_RADIUS + GAP_RADIUS
1072                    );
1073                    break;
1074                default:
1075            }
1076            /***/
1077        }
1078
1079        g2.setPaint(this.thermometerPaint);
1080        g2.setFont(this.valueFont);
1081
1082        // draw units indicator
1083
metrics = g2.getFontMetrics();
1084        int tickX1 = midX - COLUMN_RADIUS - GAP_DIAMETER
1085                     - metrics.stringWidth(UNITS[this.units]);
1086        if (tickX1 > area.getMinX()) {
1087            g2.drawString(
1088                UNITS[this.units], tickX1, (int) (area.getMinY() + 20)
1089            );
1090        }
1091
1092        // draw thermometer outline
1093
g2.setStroke(this.thermometerStroke);
1094        g2.draw(outerThermometer);
1095        g2.draw(innerThermometer);
1096
1097        drawOutline(g2, area);
1098    }
1099
1100    /**
1101     * A zoom method that does nothing. Plots are required to support the
1102     * zoom operation. In the case of a thermometer chart, it doesn't make
1103     * sense to zoom in or out, so the method is empty.
1104     *
1105     * @param percent the zoom percentage.
1106     */

1107    public void zoom(double percent) {
1108        // intentionally blank
1109
}
1110
1111    /**
1112     * Returns a short string describing the type of plot.
1113     *
1114     * @return A short string describing the type of plot.
1115     */

1116    public String JavaDoc getPlotType() {
1117        return localizationResources.getString("Thermometer_Plot");
1118    }
1119
1120    /**
1121     * Checks to see if a new value means the axis range needs adjusting.
1122     *
1123     * @param event the dataset change event.
1124     */

1125    public void datasetChanged(DatasetChangeEvent event) {
1126        Number JavaDoc vn = this.dataset.getValue();
1127        if (vn != null) {
1128            double value = vn.doubleValue();
1129            if (inSubrange(NORMAL, value)) {
1130                this.subrange = NORMAL;
1131            }
1132            else if (inSubrange(WARNING, value)) {
1133               this.subrange = WARNING;
1134            }
1135            else if (inSubrange(CRITICAL, value)) {
1136                this.subrange = CRITICAL;
1137            }
1138            else {
1139                this.subrange = -1;
1140            }
1141            setAxisRange();
1142        }
1143        super.datasetChanged(event);
1144    }
1145
1146    /**
1147     * Returns the minimum value in either the domain or the range, whichever
1148     * is displayed against the vertical axis for the particular type of plot
1149     * implementing this interface.
1150     *
1151     * @return The minimum value in either the domain or the range.
1152     */

1153    public Number JavaDoc getMinimumVerticalDataValue() {
1154        return new Double JavaDoc(this.lowerBound);
1155    }
1156
1157    /**
1158     * Returns the maximum value in either the domain or the range, whichever
1159     * is displayed against the vertical axis for the particular type of plot
1160     * implementing this interface.
1161     *
1162     * @return The maximum value in either the domain or the range
1163     */

1164    public Number JavaDoc getMaximumVerticalDataValue() {
1165        return new Double JavaDoc(this.upperBound);
1166    }
1167
1168    /**
1169     * Returns the data range.
1170     *
1171     * @param axis the axis.
1172     *
1173     * @return The range of data displayed.
1174     */

1175    public Range getDataRange(ValueAxis axis) {
1176       return new Range(this.lowerBound, this.upperBound);
1177    }
1178
1179    /**
1180     * Sets the axis range to the current values in the rangeInfo array.
1181     */

1182    protected void setAxisRange() {
1183        if ((this.subrange >= 0) && (this.followDataInSubranges)) {
1184            this.rangeAxis.setRange(
1185                new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW],
1186                this.subrangeInfo[this.subrange][DISPLAY_HIGH])
1187            );
1188        }
1189        else {
1190            this.rangeAxis.setRange(this.lowerBound, this.upperBound);
1191        }
1192    }
1193
1194    /**
1195     * Returns the legend items for the plot.
1196     *
1197     * @return <code>null</code>.
1198     */

1199    public LegendItemCollection getLegendItems() {
1200        return null;
1201    }
1202
1203    /**
1204     * Returns the orientation of the plot.
1205     *
1206     * @return The orientation (always {@link PlotOrientation#VERTICAL}).
1207     */

1208    public PlotOrientation getOrientation() {
1209        return PlotOrientation.VERTICAL;
1210    }
1211
1212    /**
1213     * Determine whether a number is valid and finite.
1214     *
1215     * @param d the number to be tested.
1216     *
1217     * @return <code>true</code> if the number is valid and finite, and
1218     * <code>false</code> otherwise.
1219     */

1220    protected static boolean isValidNumber(double d) {
1221        return (!(Double.isNaN(d) || Double.isInfinite(d)));
1222    }
1223
1224    /**
1225     * Returns true if the value is in the specified range, and false otherwise.
1226     *
1227     * @param subrange the subrange.
1228     * @param value the value to check.
1229     *
1230     * @return A boolean.
1231     */

1232    private boolean inSubrange(int subrange, double value) {
1233        return (value > this.subrangeInfo[subrange][RANGE_LOW]
1234            && value <= this.subrangeInfo[subrange][RANGE_HIGH]);
1235    }
1236
1237    /**
1238     * Returns the mercury paint corresponding to the current data value.
1239     *
1240     * @return The paint.
1241     */

1242    private Paint JavaDoc getCurrentPaint() {
1243
1244        Paint JavaDoc result = this.mercuryPaint;
1245        if (this.useSubrangePaint) {
1246            double value = this.dataset.getValue().doubleValue();
1247            if (inSubrange(NORMAL, value)) {
1248                result = this.subrangePaint[NORMAL];
1249            }
1250            else if (inSubrange(WARNING, value)) {
1251                result = this.subrangePaint[WARNING];
1252            }
1253            else if (inSubrange(CRITICAL, value)) {
1254                result = this.subrangePaint[CRITICAL];
1255            }
1256        }
1257        return result;
1258    }
1259
1260    /**
1261     * Tests this plot for equality with another object.
1262     *
1263     * @param obj the object.
1264     *
1265     * @return <code>true</code> or <code>false</code>.
1266     */

1267    public boolean equals(Object JavaDoc obj) {
1268
1269        if (obj == this) {
1270            return true;
1271        }
1272
1273        if (!(obj instanceof ThermometerPlot)) {
1274            return false;
1275        }
1276        ThermometerPlot that = (ThermometerPlot) obj;
1277        if (!super.equals(obj)) {
1278            return false;
1279        }
1280        if (!ObjectUtilities.equal(this.dataset, that.dataset)) {
1281            return false;
1282        }
1283        if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
1284            return false;
1285        }
1286        if (this.lowerBound != that.lowerBound) {
1287            return false;
1288        }
1289        if (this.upperBound != that.upperBound) {
1290            return false;
1291        }
1292        if (!ObjectUtilities.equal(this.padding, that.padding)) {
1293            return false;
1294        }
1295        if (!ObjectUtilities.equal(
1296            this.thermometerStroke, that.thermometerStroke
1297        )) {
1298            return false;
1299        }
1300        if (!ObjectUtilities.equal(
1301            this.thermometerPaint, that.thermometerPaint
1302        )) {
1303            return false;
1304        }
1305        if (this.units != that.units) {
1306            return false;
1307        }
1308        if (this.valueLocation != that.valueLocation) {
1309            return false;
1310        }
1311        if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) {
1312            return false;
1313        }
1314        if (!ObjectUtilities.equal(this.valuePaint, that.valuePaint)) {
1315            return false;
1316        }
1317        if (!ObjectUtilities.equal(this.valueFormat, that.valueFormat)) {
1318            return false;
1319        }
1320        if (!ObjectUtilities.equal(this.mercuryPaint, that.mercuryPaint)) {
1321            return false;
1322        }
1323        if (this.showValueLines != that.showValueLines) {
1324            return false;
1325        }
1326        if (this.subrange != that.subrange) {
1327            return false;
1328        }
1329        
1330        if (this.followDataInSubranges != that.followDataInSubranges) {
1331            return false;
1332        }
1333        if (this.useSubrangePaint != that.useSubrangePaint) {
1334            return false;
1335        }
1336        return true;
1337    
1338    }
1339
1340    /**
1341     * Returns a clone of the plot.
1342     *
1343     * @return A clone.
1344     *
1345     * @throws CloneNotSupportedException if the plot cannot be cloned.
1346     */

1347    public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
1348
1349        ThermometerPlot clone = (ThermometerPlot) super.clone();
1350
1351        if (clone.dataset != null) {
1352            clone.dataset.addChangeListener(clone);
1353        }
1354        clone.rangeAxis = (ValueAxis) ObjectUtilities.clone(this.rangeAxis);
1355        if (clone.rangeAxis != null) {
1356            clone.rangeAxis.setPlot(clone);
1357            clone.rangeAxis.addChangeListener(clone);
1358        }
1359        clone.valueFormat = (NumberFormat JavaDoc) this.valueFormat.clone();
1360        clone.subrangePaint = (Paint JavaDoc[]) this.subrangePaint.clone();
1361
1362        return clone;
1363
1364    }
1365
1366    /**
1367     * Provides serialization support.
1368     *
1369     * @param stream the output stream.
1370     *
1371     * @throws IOException if there is an I/O error.
1372     */

1373    private void writeObject(ObjectOutputStream JavaDoc stream) throws IOException JavaDoc {
1374        stream.defaultWriteObject();
1375        SerialUtilities.writeStroke(this.thermometerStroke, stream);
1376        SerialUtilities.writePaint(this.thermometerPaint, stream);
1377        SerialUtilities.writePaint(this.valuePaint, stream);
1378        SerialUtilities.writePaint(this.mercuryPaint, stream);
1379        SerialUtilities.writeStroke(this.subrangeIndicatorStroke, stream);
1380        SerialUtilities.writeStroke(this.rangeIndicatorStroke, stream);
1381    }
1382
1383    /**
1384     * Provides serialization support.
1385     *
1386     * @param stream the input stream.
1387     *
1388     * @throws IOException if there is an I/O error.
1389     * @throws ClassNotFoundException if there is a classpath problem.
1390     */

1391    private void readObject(ObjectInputStream JavaDoc stream) throws IOException JavaDoc,
1392            ClassNotFoundException JavaDoc {
1393        stream.defaultReadObject();
1394        this.thermometerStroke = SerialUtilities.readStroke(stream);
1395        this.thermometerPaint = SerialUtilities.readPaint(stream);
1396        this.valuePaint = SerialUtilities.readPaint(stream);
1397        this.mercuryPaint = SerialUtilities.readPaint(stream);
1398        this.subrangeIndicatorStroke = SerialUtilities.readStroke(stream);
1399        this.rangeIndicatorStroke = SerialUtilities.readStroke(stream);
1400
1401        if (this.rangeAxis != null) {
1402            this.rangeAxis.addChangeListener(this);
1403        }
1404    }
1405
1406    /**
1407     * Multiplies the range on the domain axis/axes by the specified factor.
1408     *
1409     * @param factor the zoom factor.
1410     * @param state the plot state.
1411     * @param source the source point.
1412     */

1413    public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1414                               Point2D JavaDoc source) {
1415        // TODO: to be implemented.
1416
}
1417
1418    /**
1419     * Multiplies the range on the range axis/axes by the specified factor.
1420     *
1421     * @param factor the zoom factor.
1422     * @param state the plot state.
1423     * @param source the source point.
1424     */

1425    public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1426                              Point2D JavaDoc source) {
1427        this.rangeAxis.resizeRange(factor);
1428    }
1429
1430    /**
1431     * This method does nothing.
1432     *
1433     * @param lowerPercent the lower percent.
1434     * @param upperPercent the upper percent.
1435     * @param state the plot state.
1436     * @param source the source point.
1437     */

1438    public void zoomDomainAxes(double lowerPercent, double upperPercent,
1439                               PlotRenderingInfo state, Point2D JavaDoc source) {
1440        // no domain axis to zoom
1441
}
1442
1443    /**
1444     * Zooms the range axes.
1445     *
1446     * @param lowerPercent the lower percent.
1447     * @param upperPercent the upper percent.
1448     * @param state the plot state.
1449     * @param source the source point.
1450     */

1451    public void zoomRangeAxes(double lowerPercent, double upperPercent,
1452                              PlotRenderingInfo state, Point2D JavaDoc source) {
1453        this.rangeAxis.zoomRange(lowerPercent, upperPercent);
1454    }
1455  
1456    /**
1457     * Returns <code>false</code>.
1458     *
1459     * @return A boolean.
1460     */

1461    public boolean isDomainZoomable() {
1462        return false;
1463    }
1464    
1465    /**
1466     * Returns <code>true</code>.
1467     *
1468     * @return A boolean.
1469     */

1470    public boolean isRangeZoomable() {
1471        return true;
1472    }
1473
1474}
1475
Popular Tags