001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * --------------------
028 * ThermometerPlot.java
029 * --------------------
030 *
031 * (C) Copyright 2000-2007, by Bryan Scott and Contributors.
032 *
033 * Original Author: Bryan Scott (based on MeterPlot by Hari).
034 * Contributor(s): David Gilbert (for Object Refinery Limited).
035 * Arnaud Lelievre;
036 * Julien Henry (see patch 1769088) (DG);
037 *
038 * Changes
039 * -------
040 * 11-Apr-2002 : Version 1, contributed by Bryan Scott;
041 * 15-Apr-2002 : Changed to implement VerticalValuePlot;
042 * 29-Apr-2002 : Added getVerticalValueAxis() method (DG);
043 * 25-Jun-2002 : Removed redundant imports (DG);
044 * 17-Sep-2002 : Reviewed with Checkstyle utility (DG);
045 * 18-Sep-2002 : Extensive changes made to API, to iron out bugs and
046 * inconsistencies (DG);
047 * 13-Oct-2002 : Corrected error datasetChanged which would generate exceptions
048 * when value set to null (BRS).
049 * 23-Jan-2003 : Removed one constructor (DG);
050 * 26-Mar-2003 : Implemented Serializable (DG);
051 * 02-Jun-2003 : Removed test for compatible range axis (DG);
052 * 01-Jul-2003 : Added additional check in draw method to ensure value not
053 * null (BRS);
054 * 08-Sep-2003 : Added internationalization via use of properties
055 * resourceBundle (RFE 690236) (AL);
056 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
057 * 29-Sep-2003 : Updated draw to set value of cursor to non-zero and allow
058 * painting of axis. An incomplete fix and needs to be set for
059 * left or right drawing (BRS);
060 * 19-Nov-2003 : Added support for value labels to be displayed left of the
061 * thermometer
062 * 19-Nov-2003 : Improved axis drawing (now default axis does not draw axis line
063 * and is closer to the bulb). Added support for the positioning
064 * of the axis to the left or right of the bulb. (BRS);
065 * 03-Dec-2003 : Directly mapped deprecated setData()/getData() method to
066 * get/setDataset() (TM);
067 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
068 * 07-Apr-2004 : Changed string width calculation (DG);
069 * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
070 * 06-Jan-2004 : Added getOrientation() method (DG);
071 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
072 * 29-Mar-2005 : Fixed equals() method (DG);
073 * 05-May-2005 : Updated draw() method parameters (DG);
074 * 09-Jun-2005 : Fixed more bugs in equals() method (DG);
075 * 10-Jun-2005 : Fixed minor bug in setDisplayRange() method (DG);
076 * ------------- JFREECHART 1.0.x ---------------------------------------------
077 * 14-Nov-2006 : Fixed margin when drawing (DG);
078 * 03-May-2007 : Fixed datasetChanged() to handle null dataset, added null
079 * argument check and event notification to setRangeAxis(),
080 * added null argument check to setPadding(), setValueFont(),
081 * setValuePaint(), setValueFormat() and setMercuryPaint(),
082 * deprecated get/setShowValueLines(), deprecated
083 * getMinimum/MaximumVerticalDataValue(), and fixed serialization
084 * bug (DG);
085 * 24-Sep-2007 : Implemented new methods in Zoomable interface (DG);
086 * 08-Oct-2007 : Added attributes for thermometer dimensions - see patch 1769088
087 * by Julien Henry (DG);
088 *
089 */
090
091 package org.jfree.chart.plot;
092
093 import java.awt.BasicStroke;
094 import java.awt.Color;
095 import java.awt.Font;
096 import java.awt.FontMetrics;
097 import java.awt.Graphics2D;
098 import java.awt.Paint;
099 import java.awt.Stroke;
100 import java.awt.geom.Area;
101 import java.awt.geom.Ellipse2D;
102 import java.awt.geom.Line2D;
103 import java.awt.geom.Point2D;
104 import java.awt.geom.Rectangle2D;
105 import java.awt.geom.RoundRectangle2D;
106 import java.io.IOException;
107 import java.io.ObjectInputStream;
108 import java.io.ObjectOutputStream;
109 import java.io.Serializable;
110 import java.text.DecimalFormat;
111 import java.text.NumberFormat;
112 import java.util.Arrays;
113 import java.util.ResourceBundle;
114
115 import org.jfree.chart.LegendItemCollection;
116 import org.jfree.chart.axis.NumberAxis;
117 import org.jfree.chart.axis.ValueAxis;
118 import org.jfree.chart.event.PlotChangeEvent;
119 import org.jfree.data.Range;
120 import org.jfree.data.general.DatasetChangeEvent;
121 import org.jfree.data.general.DefaultValueDataset;
122 import org.jfree.data.general.ValueDataset;
123 import org.jfree.io.SerialUtilities;
124 import org.jfree.ui.RectangleEdge;
125 import org.jfree.ui.RectangleInsets;
126 import org.jfree.util.ObjectUtilities;
127 import org.jfree.util.PaintUtilities;
128 import org.jfree.util.UnitType;
129
130 /**
131 * A plot that displays a single value (from a {@link ValueDataset}) in a
132 * thermometer type display.
133 * <p>
134 * This plot supports a number of options:
135 * <ol>
136 * <li>three sub-ranges which could be viewed as 'Normal', 'Warning'
137 * and 'Critical' ranges.</li>
138 * <li>the thermometer can be run in two modes:
139 * <ul>
140 * <li>fixed range, or</li>
141 * <li>range adjusts to current sub-range.</li>
142 * </ul>
143 * </li>
144 * <li>settable units to be displayed.</li>
145 * <li>settable display location for the value text.</li>
146 * </ol>
147 */
148 public class ThermometerPlot extends Plot implements ValueAxisPlot,
149 Zoomable, Cloneable, Serializable {
150
151 /** For serialization. */
152 private static final long serialVersionUID = 4087093313147984390L;
153
154 /** A constant for unit type 'None'. */
155 public static final int UNITS_NONE = 0;
156
157 /** A constant for unit type 'Fahrenheit'. */
158 public static final int UNITS_FAHRENHEIT = 1;
159
160 /** A constant for unit type 'Celcius'. */
161 public static final int UNITS_CELCIUS = 2;
162
163 /** A constant for unit type 'Kelvin'. */
164 public static final int UNITS_KELVIN = 3;
165
166 /** A constant for the value label position (no label). */
167 public static final int NONE = 0;
168
169 /** A constant for the value label position (right of the thermometer). */
170 public static final int RIGHT = 1;
171
172 /** A constant for the value label position (left of the thermometer). */
173 public static final int LEFT = 2;
174
175 /** A constant for the value label position (in the thermometer bulb). */
176 public static final int BULB = 3;
177
178 /** A constant for the 'normal' range. */
179 public static final int NORMAL = 0;
180
181 /** A constant for the 'warning' range. */
182 public static final int WARNING = 1;
183
184 /** A constant for the 'critical' range. */
185 public static final int CRITICAL = 2;
186
187 /**
188 * The bulb radius.
189 *
190 * @deprecated As of 1.0.7, use {@link #getBulbRadius()}.
191 */
192 protected static final int BULB_RADIUS = 40;
193
194 /**
195 * The bulb diameter.
196 *
197 * @deprecated As of 1.0.7, use {@link #getBulbDiameter()}.
198 */
199 protected static final int BULB_DIAMETER = BULB_RADIUS * 2;
200
201 /**
202 * The column radius.
203 *
204 * @deprecated As of 1.0.7, use {@link #getColumnRadius()}.
205 */
206 protected static final int COLUMN_RADIUS = 20;
207
208 /**
209 * The column diameter.
210 *
211 * @deprecated As of 1.0.7, use {@link #getColumnDiameter()}.
212 */
213 protected static final int COLUMN_DIAMETER = COLUMN_RADIUS * 2;
214
215 /**
216 * The gap radius.
217 *
218 * @deprecated As of 1.0.7, use {@link #getGap()}.
219 */
220 protected static final int GAP_RADIUS = 5;
221
222 /**
223 * The gap diameter.
224 *
225 * @deprecated As of 1.0.7, use {@link #getGap()} times two.
226 */
227 protected static final int GAP_DIAMETER = GAP_RADIUS * 2;
228
229 /** The axis gap. */
230 protected static final int AXIS_GAP = 10;
231
232 /** The unit strings. */
233 protected static final String[] UNITS = {"", "\u00B0F", "\u00B0C",
234 "\u00B0K"};
235
236 /** Index for low value in subrangeInfo matrix. */
237 protected static final int RANGE_LOW = 0;
238
239 /** Index for high value in subrangeInfo matrix. */
240 protected static final int RANGE_HIGH = 1;
241
242 /** Index for display low value in subrangeInfo matrix. */
243 protected static final int DISPLAY_LOW = 2;
244
245 /** Index for display high value in subrangeInfo matrix. */
246 protected static final int DISPLAY_HIGH = 3;
247
248 /** The default lower bound. */
249 protected static final double DEFAULT_LOWER_BOUND = 0.0;
250
251 /** The default upper bound. */
252 protected static final double DEFAULT_UPPER_BOUND = 100.0;
253
254 /**
255 * The default bulb radius.
256 *
257 * @since 1.0.7
258 */
259 protected static final int DEFAULT_BULB_RADIUS = 40;
260
261 /**
262 * The default column radius.
263 *
264 * @since 1.0.7
265 */
266 protected static final int DEFAULT_COLUMN_RADIUS = 20;
267
268 /**
269 * The default gap between the outlines representing the thermometer.
270 *
271 * @since 1.0.7
272 */
273 protected static final int DEFAULT_GAP = 5;
274
275 /** The dataset for the plot. */
276 private ValueDataset dataset;
277
278 /** The range axis. */
279 private ValueAxis rangeAxis;
280
281 /** The lower bound for the thermometer. */
282 private double lowerBound = DEFAULT_LOWER_BOUND;
283
284 /** The upper bound for the thermometer. */
285 private double upperBound = DEFAULT_UPPER_BOUND;
286
287 /**
288 * The value label position.
289 *
290 * @since 1.0.7
291 */
292 private int bulbRadius = DEFAULT_BULB_RADIUS;
293
294 /**
295 * The column radius.
296 *
297 * @since 1.0.7
298 */
299 private int columnRadius = DEFAULT_COLUMN_RADIUS;
300
301 /**
302 * The gap between the two outlines the represent the thermometer.
303 *
304 * @since 1.0.7
305 */
306 private int gap = DEFAULT_GAP;
307
308 /**
309 * Blank space inside the plot area around the outside of the thermometer.
310 */
311 private RectangleInsets padding;
312
313 /** Stroke for drawing the thermometer */
314 private transient Stroke thermometerStroke = new BasicStroke(1.0f);
315
316 /** Paint for drawing the thermometer */
317 private transient Paint thermometerPaint = Color.black;
318
319 /** The display units */
320 private int units = UNITS_CELCIUS;
321
322 /** The value label position. */
323 private int valueLocation = BULB;
324
325 /** The position of the axis **/
326 private int axisLocation = LEFT;
327
328 /** The font to write the value in */
329 private Font valueFont = new Font("SansSerif", Font.BOLD, 16);
330
331 /** Colour that the value is written in */
332 private transient Paint valuePaint = Color.white;
333
334 /** Number format for the value */
335 private NumberFormat valueFormat = new DecimalFormat();
336
337 /** The default paint for the mercury in the thermometer. */
338 private transient Paint mercuryPaint = Color.lightGray;
339
340 /** A flag that controls whether value lines are drawn. */
341 private boolean showValueLines = false;
342
343 /** The display sub-range. */
344 private int subrange = -1;
345
346 /** The start and end values for the subranges. */
347 private double[][] subrangeInfo = {
348 {0.0, 50.0, 0.0, 50.0},
349 {50.0, 75.0, 50.0, 75.0},
350 {75.0, 100.0, 75.0, 100.0}
351 };
352
353 /**
354 * A flag that controls whether or not the axis range adjusts to the
355 * sub-ranges.
356 */
357 private boolean followDataInSubranges = false;
358
359 /**
360 * A flag that controls whether or not the mercury paint changes with
361 * the subranges.
362 */
363 private boolean useSubrangePaint = true;
364
365 /** Paint for each range */
366 private transient Paint[] subrangePaint = {Color.green, Color.orange,
367 Color.red};
368
369 /** A flag that controls whether the sub-range indicators are visible. */
370 private boolean subrangeIndicatorsVisible = true;
371
372 /** The stroke for the sub-range indicators. */
373 private transient Stroke subrangeIndicatorStroke = new BasicStroke(2.0f);
374
375 /** The range indicator stroke. */
376 private transient Stroke rangeIndicatorStroke = new BasicStroke(3.0f);
377
378 /** The resourceBundle for the localization. */
379 protected static ResourceBundle localizationResources =
380 ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
381
382 /**
383 * Creates a new thermometer plot.
384 */
385 public ThermometerPlot() {
386 this(new DefaultValueDataset());
387 }
388
389 /**
390 * Creates a new thermometer plot, using default attributes where necessary.
391 *
392 * @param dataset the data set.
393 */
394 public ThermometerPlot(ValueDataset dataset) {
395
396 super();
397
398 this.padding = new RectangleInsets(UnitType.RELATIVE, 0.05, 0.05, 0.05,
399 0.05);
400 this.dataset = dataset;
401 if (dataset != null) {
402 dataset.addChangeListener(this);
403 }
404 NumberAxis axis = new NumberAxis(null);
405 axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
406 axis.setAxisLineVisible(false);
407 axis.setPlot(this);
408 axis.addChangeListener(this);
409 this.rangeAxis = axis;
410 setAxisRange();
411 }
412
413 /**
414 * Returns the dataset for the plot.
415 *
416 * @return The dataset (possibly <code>null</code>).
417 *
418 * @see #setDataset(ValueDataset)
419 */
420 public ValueDataset getDataset() {
421 return this.dataset;
422 }
423
424 /**
425 * Sets the dataset for the plot, replacing the existing dataset if there
426 * is one, and sends a {@link PlotChangeEvent} to all registered listeners.
427 *
428 * @param dataset the dataset (<code>null</code> permitted).
429 *
430 * @see #getDataset()
431 */
432 public void setDataset(ValueDataset dataset) {
433
434 // if there is an existing dataset, remove the plot from the list
435 // of change listeners...
436 ValueDataset existing = this.dataset;
437 if (existing != null) {
438 existing.removeChangeListener(this);
439 }
440
441 // set the new dataset, and register the chart as a change listener...
442 this.dataset = dataset;
443 if (dataset != null) {
444 setDatasetGroup(dataset.getGroup());
445 dataset.addChangeListener(this);
446 }
447
448 // send a dataset change event to self...
449 DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
450 datasetChanged(event);
451
452 }
453
454 /**
455 * Returns the range axis.
456 *
457 * @return The range axis (never <code>null</code>).
458 *
459 * @see #setRangeAxis(ValueAxis)
460 */
461 public ValueAxis getRangeAxis() {
462 return this.rangeAxis;
463 }
464
465 /**
466 * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
467 * all registered listeners.
468 *
469 * @param axis the new axis (<code>null</code> not permitted).
470 *
471 * @see #getRangeAxis()
472 */
473 public void setRangeAxis(ValueAxis axis) {
474 if (axis == null) {
475 throw new IllegalArgumentException("Null 'axis' argument.");
476 }
477 // plot is registered as a listener with the existing axis...
478 this.rangeAxis.removeChangeListener(this);
479
480 axis.setPlot(this);
481 axis.addChangeListener(this);
482 this.rangeAxis = axis;
483 notifyListeners(new PlotChangeEvent(this));
484
485 }
486
487 /**
488 * Returns the lower bound for the thermometer. The data value can be set
489 * lower than this, but it will not be shown in the thermometer.
490 *
491 * @return The lower bound.
492 *
493 * @see #setLowerBound(double)
494 */
495 public double getLowerBound() {
496 return this.lowerBound;
497 }
498
499 /**
500 * Sets the lower bound for the thermometer.
501 *
502 * @param lower the lower bound.
503 *
504 * @see #getLowerBound()
505 */
506 public void setLowerBound(double lower) {
507 this.lowerBound = lower;
508 setAxisRange();
509 }
510
511 /**
512 * Returns the upper bound for the thermometer. The data value can be set
513 * higher than this, but it will not be shown in the thermometer.
514 *
515 * @return The upper bound.
516 *
517 * @see #setUpperBound(double)
518 */
519 public double getUpperBound() {
520 return this.upperBound;
521 }
522
523 /**
524 * Sets the upper bound for the thermometer.
525 *
526 * @param upper the upper bound.
527 *
528 * @see #getUpperBound()
529 */
530 public void setUpperBound(double upper) {
531 this.upperBound = upper;
532 setAxisRange();
533 }
534
535 /**
536 * Sets the lower and upper bounds for the thermometer.
537 *
538 * @param lower the lower bound.
539 * @param upper the upper bound.
540 */
541 public void setRange(double lower, double upper) {
542 this.lowerBound = lower;
543 this.upperBound = upper;
544 setAxisRange();
545 }
546
547 /**
548 * Returns the padding for the thermometer. This is the space inside the
549 * plot area.
550 *
551 * @return The padding (never <code>null</code>).
552 *
553 * @see #setPadding(RectangleInsets)
554 */
555 public RectangleInsets getPadding() {
556 return this.padding;
557 }
558
559 /**
560 * Sets the padding for the thermometer and sends a {@link PlotChangeEvent}
561 * to all registered listeners.
562 *
563 * @param padding the padding (<code>null</code> not permitted).
564 *
565 * @see #getPadding()
566 */
567 public void setPadding(RectangleInsets padding) {
568 if (padding == null) {
569 throw new IllegalArgumentException("Null 'padding' argument.");
570 }
571 this.padding = padding;
572 notifyListeners(new PlotChangeEvent(this));
573 }
574
575 /**
576 * Returns the stroke used to draw the thermometer outline.
577 *
578 * @return The stroke (never <code>null</code>).
579 *
580 * @see #setThermometerStroke(Stroke)
581 * @see #getThermometerPaint()
582 */
583 public Stroke getThermometerStroke() {
584 return this.thermometerStroke;
585 }
586
587 /**
588 * Sets the stroke used to draw the thermometer outline and sends a
589 * {@link PlotChangeEvent} to all registered listeners.
590 *
591 * @param s the new stroke (<code>null</code> ignored).
592 *
593 * @see #getThermometerStroke()
594 */
595 public void setThermometerStroke(Stroke s) {
596 if (s != null) {
597 this.thermometerStroke = s;
598 notifyListeners(new PlotChangeEvent(this));
599 }
600 }
601
602 /**
603 * Returns the paint used to draw the thermometer outline.
604 *
605 * @return The paint (never <code>null</code>).
606 *
607 * @see #setThermometerPaint(Paint)
608 * @see #getThermometerStroke()
609 */
610 public Paint getThermometerPaint() {
611 return this.thermometerPaint;
612 }
613
614 /**
615 * Sets the paint used to draw the thermometer outline and sends a
616 * {@link PlotChangeEvent} to all registered listeners.
617 *
618 * @param paint the new paint (<code>null</code> ignored).
619 *
620 * @see #getThermometerPaint()
621 */
622 public void setThermometerPaint(Paint paint) {
623 if (paint != null) {
624 this.thermometerPaint = paint;
625 notifyListeners(new PlotChangeEvent(this));
626 }
627 }
628
629 /**
630 * Returns a code indicating the unit display type. This is one of
631 * {@link #UNITS_NONE}, {@link #UNITS_FAHRENHEIT}, {@link #UNITS_CELCIUS}
632 * and {@link #UNITS_KELVIN}.
633 *
634 * @return The units type.
635 *
636 * @see #setUnits(int)
637 */
638 public int getUnits() {
639 return this.units;
640 }
641
642 /**
643 * Sets the units to be displayed in the thermometer. Use one of the
644 * following constants:
645 *
646 * <ul>
647 * <li>UNITS_NONE : no units displayed.</li>
648 * <li>UNITS_FAHRENHEIT : units displayed in Fahrenheit.</li>
649 * <li>UNITS_CELCIUS : units displayed in Celcius.</li>
650 * <li>UNITS_KELVIN : units displayed in Kelvin.</li>
651 * </ul>
652 *
653 * @param u the new unit type.
654 *
655 * @see #getUnits()
656 */
657 public void setUnits(int u) {
658 if ((u >= 0) && (u < UNITS.length)) {
659 if (this.units != u) {
660 this.units = u;
661 notifyListeners(new PlotChangeEvent(this));
662 }
663 }
664 }
665
666 /**
667 * Sets the unit type.
668 *
669 * @param u the unit type (<code>null</code> ignored).
670 *
671 * @deprecated Use setUnits(int) instead. Deprecated as of version 1.0.6,
672 * because this method is a little obscure and redundant anyway.
673 */
674 public void setUnits(String u) {
675 if (u == null) {
676 return;
677 }
678
679 u = u.toUpperCase().trim();
680 for (int i = 0; i < UNITS.length; ++i) {
681 if (u.equals(UNITS[i].toUpperCase().trim())) {
682 setUnits(i);
683 i = UNITS.length;
684 }
685 }
686 }
687
688 /**
689 * Returns a code indicating the location at which the value label is
690 * displayed.
691 *
692 * @return The location (one of {@link #NONE}, {@link #RIGHT},
693 * {@link #LEFT} and {@link #BULB}.).
694 */
695 public int getValueLocation() {
696 return this.valueLocation;
697 }
698
699 /**
700 * Sets the location at which the current value is displayed and sends a
701 * {@link PlotChangeEvent} to all registered listeners.
702 * <P>
703 * The location can be one of the constants:
704 * <code>NONE</code>,
705 * <code>RIGHT</code>
706 * <code>LEFT</code> and
707 * <code>BULB</code>.
708 *
709 * @param location the location.
710 */
711 public void setValueLocation(int location) {
712 if ((location >= 0) && (location < 4)) {
713 this.valueLocation = location;
714 notifyListeners(new PlotChangeEvent(this));
715 }
716 else {
717 throw new IllegalArgumentException("Location not recognised.");
718 }
719 }
720
721 /**
722 * Returns the axis location.
723 *
724 * @return The location (one of {@link #NONE}, {@link #LEFT} and
725 * {@link #RIGHT}).
726 *
727 * @see #setAxisLocation(int)
728 */
729 public int getAxisLocation() {
730 return this.axisLocation;
731 }
732
733 /**
734 * Sets the location at which the axis is displayed relative to the
735 * thermometer, and sends a {@link PlotChangeEvent} to all registered
736 * listeners.
737 *
738 * @param location the location (one of {@link #NONE}, {@link #LEFT} and
739 * {@link #RIGHT}).
740 *
741 * @see #getAxisLocation()
742 */
743 public void setAxisLocation(int location) {
744 if ((location >= 0) && (location < 3)) {
745 this.axisLocation = location;
746 notifyListeners(new PlotChangeEvent(this));
747 }
748 else {
749 throw new IllegalArgumentException("Location not recognised.");
750 }
751 }
752
753 /**
754 * Gets the font used to display the current value.
755 *
756 * @return The font.
757 *
758 * @see #setValueFont(Font)
759 */
760 public Font getValueFont() {
761 return this.valueFont;
762 }
763
764 /**
765 * Sets the font used to display the current value.
766 *
767 * @param f the new font (<code>null</code> not permitted).
768 *
769 * @see #getValueFont()
770 */
771 public void setValueFont(Font f) {
772 if (f == null) {
773 throw new IllegalArgumentException("Null 'font' argument.");
774 }
775 if (!this.valueFont.equals(f)) {
776 this.valueFont = f;
777 notifyListeners(new PlotChangeEvent(this));
778 }
779 }
780
781 /**
782 * Gets the paint used to display the current value.
783 *
784 * @return The paint.
785 *
786 * @see #setValuePaint(Paint)
787 */
788 public Paint getValuePaint() {
789 return this.valuePaint;
790 }
791
792 /**
793 * Sets the paint used to display the current value and sends a
794 * {@link PlotChangeEvent} to all registered listeners.
795 *
796 * @param paint the new paint (<code>null</code> not permitted).
797 *
798 * @see #getValuePaint()
799 */
800 public void setValuePaint(Paint paint) {
801 if (paint == null) {
802 throw new IllegalArgumentException("Null 'paint' argument.");
803 }
804 if (!this.valuePaint.equals(paint)) {
805 this.valuePaint = paint;
806 notifyListeners(new PlotChangeEvent(this));
807 }
808 }
809
810 // FIXME: No getValueFormat() method?
811
812 /**
813 * Sets the formatter for the value label and sends a
814 * {@link PlotChangeEvent} to all registered listeners.
815 *
816 * @param formatter the new formatter (<code>null</code> not permitted).
817 */
818 public void setValueFormat(NumberFormat formatter) {
819 if (formatter == null) {
820 throw new IllegalArgumentException("Null 'formatter' argument.");
821 }
822 this.valueFormat = formatter;
823 notifyListeners(new PlotChangeEvent(this));
824 }
825
826 /**
827 * Returns the default mercury paint.
828 *
829 * @return The paint (never <code>null</code>).
830 *
831 * @see #setMercuryPaint(Paint)
832 */
833 public Paint getMercuryPaint() {
834 return this.mercuryPaint;
835 }
836
837 /**
838 * Sets the default mercury paint and sends a {@link PlotChangeEvent} to
839 * all registered listeners.
840 *
841 * @param paint the new paint (<code>null</code> not permitted).
842 *
843 * @see #getMercuryPaint()
844 */
845 public void setMercuryPaint(Paint paint) {
846 if (paint == null) {
847 throw new IllegalArgumentException("Null 'paint' argument.");
848 }
849 this.mercuryPaint = paint;
850 notifyListeners(new PlotChangeEvent(this));
851 }
852
853 /**
854 * Returns the flag that controls whether not value lines are displayed.
855 *
856 * @return The flag.
857 *
858 * @see #setShowValueLines(boolean)
859 *
860 * @deprecated This flag doesn't do anything useful/visible. Deprecated
861 * as of version 1.0.6.
862 */
863 public boolean getShowValueLines() {
864 return this.showValueLines;
865 }
866
867 /**
868 * Sets the display as to whether to show value lines in the output.
869 *
870 * @param b Whether to show value lines in the thermometer
871 *
872 * @see #getShowValueLines()
873 *
874 * @deprecated This flag doesn't do anything useful/visible. Deprecated
875 * as of version 1.0.6.
876 */
877 public void setShowValueLines(boolean b) {
878 this.showValueLines = b;
879 notifyListeners(new PlotChangeEvent(this));
880 }
881
882 /**
883 * Sets information for a particular range.
884 *
885 * @param range the range to specify information about.
886 * @param low the low value for the range
887 * @param hi the high value for the range
888 */
889 public void setSubrangeInfo(int range, double low, double hi) {
890 setSubrangeInfo(range, low, hi, low, hi);
891 }
892
893 /**
894 * Sets the subrangeInfo attribute of the ThermometerPlot object
895 *
896 * @param range the new rangeInfo value.
897 * @param rangeLow the new rangeInfo value
898 * @param rangeHigh the new rangeInfo value
899 * @param displayLow the new rangeInfo value
900 * @param displayHigh the new rangeInfo value
901 */
902 public void setSubrangeInfo(int range,
903 double rangeLow, double rangeHigh,
904 double displayLow, double displayHigh) {
905
906 if ((range >= 0) && (range < 3)) {
907 setSubrange(range, rangeLow, rangeHigh);
908 setDisplayRange(range, displayLow, displayHigh);
909 setAxisRange();
910 notifyListeners(new PlotChangeEvent(this));
911 }
912
913 }
914
915 /**
916 * Sets the bounds for a subrange.
917 *
918 * @param range the range type.
919 * @param low the low value.
920 * @param high the high value.
921 */
922 public void setSubrange(int range, double low, double high) {
923 if ((range >= 0) && (range < 3)) {
924 this.subrangeInfo[range][RANGE_HIGH] = high;
925 this.subrangeInfo[range][RANGE_LOW] = low;
926 }
927 }
928
929 /**
930 * Sets the displayed bounds for a sub range.
931 *
932 * @param range the range type.
933 * @param low the low value.
934 * @param high the high value.
935 */
936 public void setDisplayRange(int range, double low, double high) {
937
938 if ((range >= 0) && (range < this.subrangeInfo.length)
939 && isValidNumber(high) && isValidNumber(low)) {
940
941 if (high > low) {
942 this.subrangeInfo[range][DISPLAY_HIGH] = high;
943 this.subrangeInfo[range][DISPLAY_LOW] = low;
944 }
945 else {
946 this.subrangeInfo[range][DISPLAY_HIGH] = low;
947 this.subrangeInfo[range][DISPLAY_LOW] = high;
948 }
949
950 }
951
952 }
953
954 /**
955 * Gets the paint used for a particular subrange.
956 *
957 * @param range the range (.
958 *
959 * @return The paint.
960 *
961 * @see #setSubrangePaint(int, Paint)
962 */
963 public Paint getSubrangePaint(int range) {
964 if ((range >= 0) && (range < this.subrangePaint.length)) {
965 return this.subrangePaint[range];
966 }
967 else {
968 return this.mercuryPaint;
969 }
970 }
971
972 /**
973 * Sets the paint to be used for a subrange and sends a
974 * {@link PlotChangeEvent} to all registered listeners.
975 *
976 * @param range the range (0, 1 or 2).
977 * @param paint the paint to be applied (<code>null</code> not permitted).
978 *
979 * @see #getSubrangePaint(int)
980 */
981 public void setSubrangePaint(int range, Paint paint) {
982 if ((range >= 0)
983 && (range < this.subrangePaint.length) && (paint != null)) {
984 this.subrangePaint[range] = paint;
985 notifyListeners(new PlotChangeEvent(this));
986 }
987 }
988
989 /**
990 * Returns a flag that controls whether or not the thermometer axis zooms
991 * to display the subrange within which the data value falls.
992 *
993 * @return The flag.
994 */
995 public boolean getFollowDataInSubranges() {
996 return this.followDataInSubranges;
997 }
998
999 /**
1000 * Sets the flag that controls whether or not the thermometer axis zooms
1001 * to display the subrange within which the data value falls.
1002 *
1003 * @param flag the flag.
1004 */
1005 public void setFollowDataInSubranges(boolean flag) {
1006 this.followDataInSubranges = flag;
1007 notifyListeners(new PlotChangeEvent(this));
1008 }
1009
1010 /**
1011 * Returns a flag that controls whether or not the mercury color changes
1012 * for each subrange.
1013 *
1014 * @return The flag.
1015 *
1016 * @see #setUseSubrangePaint(boolean)
1017 */
1018 public boolean getUseSubrangePaint() {
1019 return this.useSubrangePaint;
1020 }
1021
1022 /**
1023 * Sets the range colour change option.
1024 *
1025 * @param flag the new range colour change option
1026 *
1027 * @see #getUseSubrangePaint()
1028 */
1029 public void setUseSubrangePaint(boolean flag) {
1030 this.useSubrangePaint = flag;
1031 notifyListeners(new PlotChangeEvent(this));
1032 }
1033
1034 /**
1035 * Returns the bulb radius, in Java2D units.
1036
1037 * @return The bulb radius.
1038 *
1039 * @since 1.0.7
1040 */
1041 public int getBulbRadius() {
1042 return this.bulbRadius;
1043 }
1044
1045 /**
1046 * Sets the bulb radius (in Java2D units) and sends a
1047 * {@link PlotChangeEvent} to all registered listeners.
1048 *
1049 * @param r the new radius (in Java2D units).
1050 *
1051 * @see #getBulbRadius()
1052 *
1053 * @since 1.0.7
1054 */
1055 public void setBulbRadius(int r) {
1056 this.bulbRadius = r;
1057 notifyListeners(new PlotChangeEvent(this));
1058 }
1059
1060 /**
1061 * Returns the bulb diameter, which is always twice the value returned
1062 * by {@link #getBulbRadius()}.
1063 *
1064 * @return The bulb diameter.
1065 *
1066 * @since 1.0.7
1067 */
1068 public int getBulbDiameter() {
1069 return getBulbRadius() * 2;
1070 }
1071
1072 /**
1073 * Returns the column radius, in Java2D units.
1074 *
1075 * @return The column radius.
1076 *
1077 * @see #setColumnRadius(int)
1078 *
1079 * @since 1.0.7
1080 */
1081 public int getColumnRadius() {
1082 return this.columnRadius;
1083 }
1084
1085 /**
1086 * Sets the column radius (in Java2D units) and sends a
1087 * {@link PlotChangeEvent} to all registered listeners.
1088 *
1089 * @param r the new radius.
1090 *
1091 * @see #getColumnRadius()
1092 *
1093 * @since 1.0.7
1094 */
1095 public void setColumnRadius(int r) {
1096 this.columnRadius = r;
1097 notifyListeners(new PlotChangeEvent(this));
1098 }
1099
1100 /**
1101 * Returns the column diameter, which is always twice the value returned
1102 * by {@link #getColumnRadius()}.
1103 *
1104 * @return The column diameter.
1105 *
1106 * @since 1.0.7
1107 */
1108 public int getColumnDiameter() {
1109 return getColumnRadius() * 2;
1110 }
1111
1112 /**
1113 * Returns the gap, in Java2D units, between the two outlines that
1114 * represent the thermometer.
1115 *
1116 * @return The gap.
1117 *
1118 * @see #setGap(int)
1119 *
1120 * @since 1.0.7
1121 */
1122 public int getGap() {
1123 return this.gap;
1124 }
1125
1126 /**
1127 * Sets the gap (in Java2D units) between the two outlines that represent
1128 * the thermometer, and sends a {@link PlotChangeEvent} to all registered
1129 * listeners.
1130 *
1131 * @param gap the new gap.
1132 *
1133 * @see #getGap()
1134 *
1135 * @since 1.0.7
1136 */
1137 public void setGap(int gap) {
1138 this.gap = gap;
1139 notifyListeners(new PlotChangeEvent(this));
1140 }
1141
1142 /**
1143 * Draws the plot on a Java 2D graphics device (such as the screen or a
1144 * printer).
1145 *
1146 * @param g2 the graphics device.
1147 * @param area the area within which the plot should be drawn.
1148 * @param anchor the anchor point (<code>null</code> permitted).
1149 * @param parentState the state from the parent plot, if there is one.
1150 * @param info collects info about the drawing.
1151 */
1152 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
1153 PlotState parentState,
1154 PlotRenderingInfo info) {
1155
1156 RoundRectangle2D outerStem = new RoundRectangle2D.Double();
1157 RoundRectangle2D innerStem = new RoundRectangle2D.Double();
1158 RoundRectangle2D mercuryStem = new RoundRectangle2D.Double();
1159 Ellipse2D outerBulb = new Ellipse2D.Double();
1160 Ellipse2D innerBulb = new Ellipse2D.Double();
1161 String temp = null;
1162 FontMetrics metrics = null;
1163 if (info != null) {
1164 info.setPlotArea(area);
1165 }
1166
1167 // adjust for insets...
1168 RectangleInsets insets = getInsets();
1169 insets.trim(area);
1170 drawBackground(g2, area);
1171
1172 // adjust for padding...
1173 Rectangle2D interior = (Rectangle2D) area.clone();
1174 this.padding.trim(interior);
1175 int midX = (int) (interior.getX() + (interior.getWidth() / 2));
1176 int midY = (int) (interior.getY() + (interior.getHeight() / 2));
1177 int stemTop = (int) (interior.getMinY() + getBulbRadius());
1178 int stemBottom = (int) (interior.getMaxY() - getBulbDiameter());
1179 Rectangle2D dataArea = new Rectangle2D.Double(midX - getColumnRadius(),
1180 stemTop, getColumnRadius(), stemBottom - stemTop);
1181
1182 outerBulb.setFrame(midX - getBulbRadius(), stemBottom,
1183 getBulbDiameter(), getBulbDiameter());
1184
1185 outerStem.setRoundRect(midX - getColumnRadius(), interior.getMinY(),
1186 getColumnDiameter(), stemBottom + getBulbDiameter() - stemTop,
1187 getColumnDiameter(), getColumnDiameter());
1188
1189 Area outerThermometer = new Area(outerBulb);
1190 Area tempArea = new Area(outerStem);
1191 outerThermometer.add(tempArea);
1192
1193 innerBulb.setFrame(midX - getBulbRadius() + getGap(), stemBottom
1194 + getGap(), getBulbDiameter() - getGap() * 2, getBulbDiameter()
1195 - getGap() * 2);
1196
1197 innerStem.setRoundRect(midX - getColumnRadius() + getGap(),
1198 interior.getMinY() + getGap(), getColumnDiameter()
1199 - getGap() * 2, stemBottom + getBulbDiameter() - getGap() * 2
1200 - stemTop, getColumnDiameter() - getGap() * 2,
1201 getColumnDiameter() - getGap() * 2);
1202
1203 Area innerThermometer = new Area(innerBulb);
1204 tempArea = new Area(innerStem);
1205 innerThermometer.add(tempArea);
1206
1207 if ((this.dataset != null) && (this.dataset.getValue() != null)) {
1208 double current = this.dataset.getValue().doubleValue();
1209 double ds = this.rangeAxis.valueToJava2D(current, dataArea,
1210 RectangleEdge.LEFT);
1211
1212 int i = getColumnDiameter() - getGap() * 2; // already calculated
1213 int j = getColumnRadius() - getGap(); // already calculated
1214 int l = (i / 2);
1215 int k = (int) Math.round(ds);
1216 if (k < (getGap() + interior.getMinY())) {
1217 k = (int) (getGap() + interior.getMinY());
1218 l = getBulbRadius();
1219 }
1220
1221 Area mercury = new Area(innerBulb);
1222
1223 if (k < (stemBottom + getBulbRadius())) {
1224 mercuryStem.setRoundRect(midX - j, k, i,
1225 (stemBottom + getBulbRadius()) - k, l, l);
1226 tempArea = new Area(mercuryStem);
1227 mercury.add(tempArea);
1228 }
1229
1230 g2.setPaint(getCurrentPaint());
1231 g2.fill(mercury);
1232
1233 // draw range indicators...
1234 if (this.subrangeIndicatorsVisible) {
1235 g2.setStroke(this.subrangeIndicatorStroke);
1236 Range range = this.rangeAxis.getRange();
1237
1238 // draw start of normal range
1239 double value = this.subrangeInfo[NORMAL][RANGE_LOW];
1240 if (range.contains(value)) {
1241 double x = midX + getColumnRadius() + 2;
1242 double y = this.rangeAxis.valueToJava2D(value, dataArea,
1243 RectangleEdge.LEFT);
1244 Line2D line = new Line2D.Double(x, y, x + 10, y);
1245 g2.setPaint(this.subrangePaint[NORMAL]);
1246 g2.draw(line);
1247 }
1248
1249 // draw start of warning range
1250 value = this.subrangeInfo[WARNING][RANGE_LOW];
1251 if (range.contains(value)) {
1252 double x = midX + getColumnRadius() + 2;
1253 double y = this.rangeAxis.valueToJava2D(value, dataArea,
1254 RectangleEdge.LEFT);
1255 Line2D line = new Line2D.Double(x, y, x + 10, y);
1256 g2.setPaint(this.subrangePaint[WARNING]);
1257 g2.draw(line);
1258 }
1259
1260 // draw start of critical range
1261 value = this.subrangeInfo[CRITICAL][RANGE_LOW];
1262 if (range.contains(value)) {
1263 double x = midX + getColumnRadius() + 2;
1264 double y = this.rangeAxis.valueToJava2D(value, dataArea,
1265 RectangleEdge.LEFT);
1266 Line2D line = new Line2D.Double(x, y, x + 10, y);
1267 g2.setPaint(this.subrangePaint[CRITICAL]);
1268 g2.draw(line);
1269 }
1270 }
1271
1272 // draw the axis...
1273 if ((this.rangeAxis != null) && (this.axisLocation != NONE)) {
1274 int drawWidth = AXIS_GAP;
1275 if (this.showValueLines) {
1276 drawWidth += getColumnDiameter();
1277 }
1278 Rectangle2D drawArea;
1279 double cursor = 0;
1280
1281 switch (this.axisLocation) {
1282 case RIGHT:
1283 cursor = midX + getColumnRadius();
1284 drawArea = new Rectangle2D.Double(cursor,
1285 stemTop, drawWidth, (stemBottom - stemTop + 1));
1286 this.rangeAxis.draw(g2, cursor, area, drawArea,
1287 RectangleEdge.RIGHT, null);
1288 break;
1289
1290 case LEFT:
1291 default:
1292 //cursor = midX - COLUMN_RADIUS - AXIS_GAP;
1293 cursor = midX - getColumnRadius();
1294 drawArea = new Rectangle2D.Double(cursor, stemTop,
1295 drawWidth, (stemBottom - stemTop + 1));
1296 this.rangeAxis.draw(g2, cursor, area, drawArea,
1297 RectangleEdge.LEFT, null);
1298 break;
1299 }
1300
1301 }
1302
1303 // draw text value on screen
1304 g2.setFont(this.valueFont);
1305 g2.setPaint(this.valuePaint);
1306 metrics = g2.getFontMetrics();
1307 switch (this.valueLocation) {
1308 case RIGHT:
1309 g2.drawString(this.valueFormat.format(current),
1310 midX + getColumnRadius() + getGap(), midY);
1311 break;
1312 case LEFT:
1313 String valueString = this.valueFormat.format(current);
1314 int stringWidth = metrics.stringWidth(valueString);
1315 g2.drawString(valueString, midX - getColumnRadius()
1316 - getGap() - stringWidth, midY);
1317 break;
1318 case BULB:
1319 temp = this.valueFormat.format(current);
1320 i = metrics.stringWidth(temp) / 2;
1321 g2.drawString(temp, midX - i,
1322 stemBottom + getBulbRadius() + getGap());
1323 break;
1324 default:
1325 }
1326 /***/
1327 }
1328
1329 g2.setPaint(this.thermometerPaint);
1330 g2.setFont(this.valueFont);
1331
1332 // draw units indicator
1333 metrics = g2.getFontMetrics();
1334 int tickX1 = midX - getColumnRadius() - getGap() * 2
1335 - metrics.stringWidth(UNITS[this.units]);
1336 if (tickX1 > area.getMinX()) {
1337 g2.drawString(UNITS[this.units], tickX1,
1338 (int) (area.getMinY() + 20));
1339 }
1340
1341 // draw thermometer outline
1342 g2.setStroke(this.thermometerStroke);
1343 g2.draw(outerThermometer);
1344 g2.draw(innerThermometer);
1345
1346 drawOutline(g2, area);
1347 }
1348
1349 /**
1350 * A zoom method that does nothing. Plots are required to support the
1351 * zoom operation. In the case of a thermometer chart, it doesn't make
1352 * sense to zoom in or out, so the method is empty.
1353 *
1354 * @param percent the zoom percentage.
1355 */
1356 public void zoom(double percent) {
1357 // intentionally blank
1358 }
1359
1360 /**
1361 * Returns a short string describing the type of plot.
1362 *
1363 * @return A short string describing the type of plot.
1364 */
1365 public String getPlotType() {
1366 return localizationResources.getString("Thermometer_Plot");
1367 }
1368
1369 /**
1370 * Checks to see if a new value means the axis range needs adjusting.
1371 *
1372 * @param event the dataset change event.
1373 */
1374 public void datasetChanged(DatasetChangeEvent event) {
1375 if (this.dataset != null) {
1376 Number vn = this.dataset.getValue();
1377 if (vn != null) {
1378 double value = vn.doubleValue();
1379 if (inSubrange(NORMAL, value)) {
1380 this.subrange = NORMAL;
1381 }
1382 else if (inSubrange(WARNING, value)) {
1383 this.subrange = WARNING;
1384 }
1385 else if (inSubrange(CRITICAL, value)) {
1386 this.subrange = CRITICAL;
1387 }
1388 else {
1389 this.subrange = -1;
1390 }
1391 setAxisRange();
1392 }
1393 }
1394 super.datasetChanged(event);
1395 }
1396
1397 /**
1398 * Returns the minimum value in either the domain or the range, whichever
1399 * is displayed against the vertical axis for the particular type of plot
1400 * implementing this interface.
1401 *
1402 * @return The minimum value in either the domain or the range.
1403 *
1404 * @deprecated This method is not used. Officially deprecated in version
1405 * 1.0.6.
1406 */
1407 public Number getMinimumVerticalDataValue() {
1408 return new Double(this.lowerBound);
1409 }
1410
1411 /**
1412 * Returns the maximum value in either the domain or the range, whichever
1413 * is displayed against the vertical axis for the particular type of plot
1414 * implementing this interface.
1415 *
1416 * @return The maximum value in either the domain or the range
1417 *
1418 * @deprecated This method is not used. Officially deprecated in version
1419 * 1.0.6.
1420 */
1421 public Number getMaximumVerticalDataValue() {
1422 return new Double(this.upperBound);
1423 }
1424
1425 /**
1426 * Returns the data range.
1427 *
1428 * @param axis the axis.
1429 *
1430 * @return The range of data displayed.
1431 */
1432 public Range getDataRange(ValueAxis axis) {
1433 return new Range(this.lowerBound, this.upperBound);
1434 }
1435
1436 /**
1437 * Sets the axis range to the current values in the rangeInfo array.
1438 */
1439 protected void setAxisRange() {
1440 if ((this.subrange >= 0) && (this.followDataInSubranges)) {
1441 this.rangeAxis.setRange(
1442 new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW],
1443 this.subrangeInfo[this.subrange][DISPLAY_HIGH]));
1444 }
1445 else {
1446 this.rangeAxis.setRange(this.lowerBound, this.upperBound);
1447 }
1448 }
1449
1450 /**
1451 * Returns the legend items for the plot.
1452 *
1453 * @return <code>null</code>.
1454 */
1455 public LegendItemCollection getLegendItems() {
1456 return null;
1457 }
1458
1459 /**
1460 * Returns the orientation of the plot.
1461 *
1462 * @return The orientation (always {@link PlotOrientation#VERTICAL}).
1463 */
1464 public PlotOrientation getOrientation() {
1465 return PlotOrientation.VERTICAL;
1466 }
1467
1468 /**
1469 * Determine whether a number is valid and finite.
1470 *
1471 * @param d the number to be tested.
1472 *
1473 * @return <code>true</code> if the number is valid and finite, and
1474 * <code>false</code> otherwise.
1475 */
1476 protected static boolean isValidNumber(double d) {
1477 return (!(Double.isNaN(d) || Double.isInfinite(d)));
1478 }
1479
1480 /**
1481 * Returns true if the value is in the specified range, and false otherwise.
1482 *
1483 * @param subrange the subrange.
1484 * @param value the value to check.
1485 *
1486 * @return A boolean.
1487 */
1488 private boolean inSubrange(int subrange, double value) {
1489 return (value > this.subrangeInfo[subrange][RANGE_LOW]
1490 && value <= this.subrangeInfo[subrange][RANGE_HIGH]);
1491 }
1492
1493 /**
1494 * Returns the mercury paint corresponding to the current data value.
1495 * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D,
1496 * PlotState, PlotRenderingInfo)} method.
1497 *
1498 * @return The paint (never <code>null</code>).
1499 */
1500 private Paint getCurrentPaint() {
1501 Paint result = this.mercuryPaint;
1502 if (this.useSubrangePaint) {
1503 double value = this.dataset.getValue().doubleValue();
1504 if (inSubrange(NORMAL, value)) {
1505 result = this.subrangePaint[NORMAL];
1506 }
1507 else if (inSubrange(WARNING, value)) {
1508 result = this.subrangePaint[WARNING];
1509 }
1510 else if (inSubrange(CRITICAL, value)) {
1511 result = this.subrangePaint[CRITICAL];
1512 }
1513 }
1514 return result;
1515 }
1516
1517 /**
1518 * Tests this plot for equality with another object. The plot's dataset
1519 * is not considered in the test.
1520 *
1521 * @param obj the object (<code>null</code> permitted).
1522 *
1523 * @return <code>true</code> or <code>false</code>.
1524 */
1525 public boolean equals(Object obj) {
1526 if (obj == this) {
1527 return true;
1528 }
1529 if (!(obj instanceof ThermometerPlot)) {
1530 return false;
1531 }
1532 ThermometerPlot that = (ThermometerPlot) obj;
1533 if (!super.equals(obj)) {
1534 return false;
1535 }
1536 if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
1537 return false;
1538 }
1539 if (this.axisLocation != that.axisLocation) {
1540 return false;
1541 }
1542 if (this.lowerBound != that.lowerBound) {
1543 return false;
1544 }
1545 if (this.upperBound != that.upperBound) {
1546 return false;
1547 }
1548 if (!ObjectUtilities.equal(this.padding, that.padding)) {
1549 return false;
1550 }
1551 if (!ObjectUtilities.equal(this.thermometerStroke,
1552 that.thermometerStroke)) {
1553 return false;
1554 }
1555 if (!PaintUtilities.equal(this.thermometerPaint,
1556 that.thermometerPaint)) {
1557 return false;
1558 }
1559 if (this.units != that.units) {
1560 return false;
1561 }
1562 if (this.valueLocation != that.valueLocation) {
1563 return false;
1564 }
1565 if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) {
1566 return false;
1567 }
1568 if (!PaintUtilities.equal(this.valuePaint, that.valuePaint)) {
1569 return false;
1570 }
1571 if (!ObjectUtilities.equal(this.valueFormat, that.valueFormat)) {
1572 return false;
1573 }
1574 if (!PaintUtilities.equal(this.mercuryPaint, that.mercuryPaint)) {
1575 return false;
1576 }
1577 if (this.showValueLines != that.showValueLines) {
1578 return false;
1579 }
1580 if (this.subrange != that.subrange) {
1581 return false;
1582 }
1583 if (this.followDataInSubranges != that.followDataInSubranges) {
1584 return false;
1585 }
1586 if (!equal(this.subrangeInfo, that.subrangeInfo)) {
1587 return false;
1588 }
1589 if (this.useSubrangePaint != that.useSubrangePaint) {
1590 return false;
1591 }
1592 if (this.bulbRadius != that.bulbRadius) {
1593 return false;
1594 }
1595 if (this.columnRadius != that.columnRadius) {
1596 return false;
1597 }
1598 if (this.gap != that.gap) {
1599 return false;
1600 }
1601 for (int i = 0; i < this.subrangePaint.length; i++) {
1602 if (!PaintUtilities.equal(this.subrangePaint[i],
1603 that.subrangePaint[i])) {
1604 return false;
1605 }
1606 }
1607 return true;
1608 }
1609
1610 /**
1611 * Tests two double[][] arrays for equality.
1612 *
1613 * @param array1 the first array (<code>null</code> permitted).
1614 * @param array2 the second arrray (<code>null</code> permitted).
1615 *
1616 * @return A boolean.
1617 */
1618 private static boolean equal(double[][] array1, double[][] array2) {
1619 if (array1 == null) {
1620 return (array2 == null);
1621 }
1622 if (array2 == null) {
1623 return false;
1624 }
1625 if (array1.length != array2.length) {
1626 return false;
1627 }
1628 for (int i = 0; i < array1.length; i++) {
1629 if (!Arrays.equals(array1[i], array2[i])) {
1630 return false;
1631 }
1632 }
1633 return true;
1634 }
1635
1636 /**
1637 * Returns a clone of the plot.
1638 *
1639 * @return A clone.
1640 *
1641 * @throws CloneNotSupportedException if the plot cannot be cloned.
1642 */
1643 public Object clone() throws CloneNotSupportedException {
1644
1645 ThermometerPlot clone = (ThermometerPlot) super.clone();
1646
1647 if (clone.dataset != null) {
1648 clone.dataset.addChangeListener(clone);
1649 }
1650 clone.rangeAxis = (ValueAxis) ObjectUtilities.clone(this.rangeAxis);
1651 if (clone.rangeAxis != null) {
1652 clone.rangeAxis.setPlot(clone);
1653 clone.rangeAxis.addChangeListener(clone);
1654 }
1655 clone.valueFormat = (NumberFormat) this.valueFormat.clone();
1656 clone.subrangePaint = (Paint[]) this.subrangePaint.clone();
1657
1658 return clone;
1659
1660 }
1661
1662 /**
1663 * Provides serialization support.
1664 *
1665 * @param stream the output stream.
1666 *
1667 * @throws IOException if there is an I/O error.
1668 */
1669 private void writeObject(ObjectOutputStream stream) throws IOException {
1670 stream.defaultWriteObject();
1671 SerialUtilities.writeStroke(this.thermometerStroke, stream);
1672 SerialUtilities.writePaint(this.thermometerPaint, stream);
1673 SerialUtilities.writePaint(this.valuePaint, stream);
1674 SerialUtilities.writePaint(this.mercuryPaint, stream);
1675 SerialUtilities.writeStroke(this.subrangeIndicatorStroke, stream);
1676 SerialUtilities.writeStroke(this.rangeIndicatorStroke, stream);
1677 for (int i = 0; i < 3; i++) {
1678 SerialUtilities.writePaint(this.subrangePaint[i], stream);
1679 }
1680 }
1681
1682 /**
1683 * Provides serialization support.
1684 *
1685 * @param stream the input stream.
1686 *
1687 * @throws IOException if there is an I/O error.
1688 * @throws ClassNotFoundException if there is a classpath problem.
1689 */
1690 private void readObject(ObjectInputStream stream) throws IOException,
1691 ClassNotFoundException {
1692 stream.defaultReadObject();
1693 this.thermometerStroke = SerialUtilities.readStroke(stream);
1694 this.thermometerPaint = SerialUtilities.readPaint(stream);
1695 this.valuePaint = SerialUtilities.readPaint(stream);
1696 this.mercuryPaint = SerialUtilities.readPaint(stream);
1697 this.subrangeIndicatorStroke = SerialUtilities.readStroke(stream);
1698 this.rangeIndicatorStroke = SerialUtilities.readStroke(stream);
1699 this.subrangePaint = new Paint[3];
1700 for (int i = 0; i < 3; i++) {
1701 this.subrangePaint[i] = SerialUtilities.readPaint(stream);
1702 }
1703 if (this.rangeAxis != null) {
1704 this.rangeAxis.addChangeListener(this);
1705 }
1706 }
1707
1708 /**
1709 * Multiplies the range on the domain axis/axes by the specified factor.
1710 *
1711 * @param factor the zoom factor.
1712 * @param state the plot state.
1713 * @param source the source point.
1714 */
1715 public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1716 Point2D source) {
1717 // no domain axis to zoom
1718 }
1719
1720 /**
1721 * Multiplies the range on the domain axis/axes by the specified factor.
1722 *
1723 * @param factor the zoom factor.
1724 * @param state the plot state.
1725 * @param source the source point.
1726 * @param useAnchor a flag that controls whether or not the source point
1727 * is used for the zoom anchor.
1728 *
1729 * @since 1.0.7
1730 */
1731 public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1732 Point2D source, boolean useAnchor) {
1733 // no domain axis to zoom
1734 }
1735
1736 /**
1737 * Multiplies the range on the range axis/axes by the specified factor.
1738 *
1739 * @param factor the zoom factor.
1740 * @param state the plot state.
1741 * @param source the source point.
1742 */
1743 public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1744 Point2D source) {
1745 this.rangeAxis.resizeRange(factor);
1746 }
1747
1748 /**
1749 * Multiplies the range on the range axis/axes by the specified factor.
1750 *
1751 * @param factor the zoom factor.
1752 * @param state the plot state.
1753 * @param source the source point.
1754 * @param useAnchor a flag that controls whether or not the source point
1755 * is used for the zoom anchor.
1756 *
1757 * @since 1.0.7
1758 */
1759 public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1760 Point2D source, boolean useAnchor) {
1761 double anchorY = this.getRangeAxis().java2DToValue(source.getY(),
1762 state.getDataArea(), RectangleEdge.LEFT);
1763 this.rangeAxis.resizeRange(factor, anchorY);
1764 }
1765
1766 /**
1767 * This method does nothing.
1768 *
1769 * @param lowerPercent the lower percent.
1770 * @param upperPercent the upper percent.
1771 * @param state the plot state.
1772 * @param source the source point.
1773 */
1774 public void zoomDomainAxes(double lowerPercent, double upperPercent,
1775 PlotRenderingInfo state, Point2D source) {
1776 // no domain axis to zoom
1777 }
1778
1779 /**
1780 * Zooms the range axes.
1781 *
1782 * @param lowerPercent the lower percent.
1783 * @param upperPercent the upper percent.
1784 * @param state the plot state.
1785 * @param source the source point.
1786 */
1787 public void zoomRangeAxes(double lowerPercent, double upperPercent,
1788 PlotRenderingInfo state, Point2D source) {
1789 this.rangeAxis.zoomRange(lowerPercent, upperPercent);
1790 }
1791
1792 /**
1793 * Returns <code>false</code>.
1794 *
1795 * @return A boolean.
1796 */
1797 public boolean isDomainZoomable() {
1798 return false;
1799 }
1800
1801 /**
1802 * Returns <code>true</code>.
1803 *
1804 * @return A boolean.
1805 */
1806 public boolean isRangeZoomable() {
1807 return true;
1808 }
1809
1810 }