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