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 fireChangeEvent();
484 }
485
486 /**
487 * Returns the lower bound for the thermometer. The data value can be set
488 * lower than this, but it will not be shown in the thermometer.
489 *
490 * @return The lower bound.
491 *
492 * @see #setLowerBound(double)
493 */
494 public double getLowerBound() {
495 return this.lowerBound;
496 }
497
498 /**
499 * Sets the lower bound for the thermometer.
500 *
501 * @param lower the lower bound.
502 *
503 * @see #getLowerBound()
504 */
505 public void setLowerBound(double lower) {
506 this.lowerBound = lower;
507 setAxisRange();
508 }
509
510 /**
511 * Returns the upper bound for the thermometer. The data value can be set
512 * higher than this, but it will not be shown in the thermometer.
513 *
514 * @return The upper bound.
515 *
516 * @see #setUpperBound(double)
517 */
518 public double getUpperBound() {
519 return this.upperBound;
520 }
521
522 /**
523 * Sets the upper bound for the thermometer.
524 *
525 * @param upper the upper bound.
526 *
527 * @see #getUpperBound()
528 */
529 public void setUpperBound(double upper) {
530 this.upperBound = upper;
531 setAxisRange();
532 }
533
534 /**
535 * Sets the lower and upper bounds for the thermometer.
536 *
537 * @param lower the lower bound.
538 * @param upper the upper bound.
539 */
540 public void setRange(double lower, double upper) {
541 this.lowerBound = lower;
542 this.upperBound = upper;
543 setAxisRange();
544 }
545
546 /**
547 * Returns the padding for the thermometer. This is the space inside the
548 * plot area.
549 *
550 * @return The padding (never <code>null</code>).
551 *
552 * @see #setPadding(RectangleInsets)
553 */
554 public RectangleInsets getPadding() {
555 return this.padding;
556 }
557
558 /**
559 * Sets the padding for the thermometer and sends a {@link PlotChangeEvent}
560 * to all registered listeners.
561 *
562 * @param padding the padding (<code>null</code> not permitted).
563 *
564 * @see #getPadding()
565 */
566 public void setPadding(RectangleInsets padding) {
567 if (padding == null) {
568 throw new IllegalArgumentException("Null 'padding' argument.");
569 }
570 this.padding = padding;
571 fireChangeEvent();
572 }
573
574 /**
575 * Returns the stroke used to draw the thermometer outline.
576 *
577 * @return The stroke (never <code>null</code>).
578 *
579 * @see #setThermometerStroke(Stroke)
580 * @see #getThermometerPaint()
581 */
582 public Stroke getThermometerStroke() {
583 return this.thermometerStroke;
584 }
585
586 /**
587 * Sets the stroke used to draw the thermometer outline and sends a
588 * {@link PlotChangeEvent} to all registered listeners.
589 *
590 * @param s the new stroke (<code>null</code> ignored).
591 *
592 * @see #getThermometerStroke()
593 */
594 public void setThermometerStroke(Stroke s) {
595 if (s != null) {
596 this.thermometerStroke = s;
597 fireChangeEvent();
598 }
599 }
600
601 /**
602 * Returns the paint used to draw the thermometer outline.
603 *
604 * @return The paint (never <code>null</code>).
605 *
606 * @see #setThermometerPaint(Paint)
607 * @see #getThermometerStroke()
608 */
609 public Paint getThermometerPaint() {
610 return this.thermometerPaint;
611 }
612
613 /**
614 * Sets the paint used to draw the thermometer outline and sends a
615 * {@link PlotChangeEvent} to all registered listeners.
616 *
617 * @param paint the new paint (<code>null</code> ignored).
618 *
619 * @see #getThermometerPaint()
620 */
621 public void setThermometerPaint(Paint paint) {
622 if (paint != null) {
623 this.thermometerPaint = paint;
624 fireChangeEvent();
625 }
626 }
627
628 /**
629 * Returns a code indicating the unit display type. This is one of
630 * {@link #UNITS_NONE}, {@link #UNITS_FAHRENHEIT}, {@link #UNITS_CELCIUS}
631 * and {@link #UNITS_KELVIN}.
632 *
633 * @return The units type.
634 *
635 * @see #setUnits(int)
636 */
637 public int getUnits() {
638 return this.units;
639 }
640
641 /**
642 * Sets the units to be displayed in the thermometer. Use one of the
643 * following constants:
644 *
645 * <ul>
646 * <li>UNITS_NONE : no units displayed.</li>
647 * <li>UNITS_FAHRENHEIT : units displayed in Fahrenheit.</li>
648 * <li>UNITS_CELCIUS : units displayed in Celcius.</li>
649 * <li>UNITS_KELVIN : units displayed in Kelvin.</li>
650 * </ul>
651 *
652 * @param u the new unit type.
653 *
654 * @see #getUnits()
655 */
656 public void setUnits(int u) {
657 if ((u >= 0) && (u < UNITS.length)) {
658 if (this.units != u) {
659 this.units = u;
660 fireChangeEvent();
661 }
662 }
663 }
664
665 /**
666 * Sets the unit type.
667 *
668 * @param u the unit type (<code>null</code> ignored).
669 *
670 * @deprecated Use setUnits(int) instead. Deprecated as of version 1.0.6,
671 * because this method is a little obscure and redundant anyway.
672 */
673 public void setUnits(String u) {
674 if (u == null) {
675 return;
676 }
677
678 u = u.toUpperCase().trim();
679 for (int i = 0; i < UNITS.length; ++i) {
680 if (u.equals(UNITS[i].toUpperCase().trim())) {
681 setUnits(i);
682 i = UNITS.length;
683 }
684 }
685 }
686
687 /**
688 * Returns a code indicating the location at which the value label is
689 * displayed.
690 *
691 * @return The location (one of {@link #NONE}, {@link #RIGHT},
692 * {@link #LEFT} and {@link #BULB}.).
693 */
694 public int getValueLocation() {
695 return this.valueLocation;
696 }
697
698 /**
699 * Sets the location at which the current value is displayed and sends a
700 * {@link PlotChangeEvent} to all registered listeners.
701 * <P>
702 * The location can be one of the constants:
703 * <code>NONE</code>,
704 * <code>RIGHT</code>
705 * <code>LEFT</code> and
706 * <code>BULB</code>.
707 *
708 * @param location the location.
709 */
710 public void setValueLocation(int location) {
711 if ((location >= 0) && (location < 4)) {
712 this.valueLocation = location;
713 fireChangeEvent();
714 }
715 else {
716 throw new IllegalArgumentException("Location not recognised.");
717 }
718 }
719
720 /**
721 * Returns the axis location.
722 *
723 * @return The location (one of {@link #NONE}, {@link #LEFT} and
724 * {@link #RIGHT}).
725 *
726 * @see #setAxisLocation(int)
727 */
728 public int getAxisLocation() {
729 return this.axisLocation;
730 }
731
732 /**
733 * Sets the location at which the axis is displayed relative to the
734 * thermometer, and sends a {@link PlotChangeEvent} to all registered
735 * listeners.
736 *
737 * @param location the location (one of {@link #NONE}, {@link #LEFT} and
738 * {@link #RIGHT}).
739 *
740 * @see #getAxisLocation()
741 */
742 public void setAxisLocation(int location) {
743 if ((location >= 0) && (location < 3)) {
744 this.axisLocation = location;
745 fireChangeEvent();
746 }
747 else {
748 throw new IllegalArgumentException("Location not recognised.");
749 }
750 }
751
752 /**
753 * Gets the font used to display the current value.
754 *
755 * @return The font.
756 *
757 * @see #setValueFont(Font)
758 */
759 public Font getValueFont() {
760 return this.valueFont;
761 }
762
763 /**
764 * Sets the font used to display the current value.
765 *
766 * @param f the new font (<code>null</code> not permitted).
767 *
768 * @see #getValueFont()
769 */
770 public void setValueFont(Font f) {
771 if (f == null) {
772 throw new IllegalArgumentException("Null 'font' argument.");
773 }
774 if (!this.valueFont.equals(f)) {
775 this.valueFont = f;
776 fireChangeEvent();
777 }
778 }
779
780 /**
781 * Gets the paint used to display the current value.
782 *
783 * @return The paint.
784 *
785 * @see #setValuePaint(Paint)
786 */
787 public Paint getValuePaint() {
788 return this.valuePaint;
789 }
790
791 /**
792 * Sets the paint used to display the current value and sends a
793 * {@link PlotChangeEvent} to all registered listeners.
794 *
795 * @param paint the new paint (<code>null</code> not permitted).
796 *
797 * @see #getValuePaint()
798 */
799 public void setValuePaint(Paint paint) {
800 if (paint == null) {
801 throw new IllegalArgumentException("Null 'paint' argument.");
802 }
803 if (!this.valuePaint.equals(paint)) {
804 this.valuePaint = paint;
805 fireChangeEvent();
806 }
807 }
808
809 // FIXME: No getValueFormat() method?
810
811 /**
812 * Sets the formatter for the value label and sends a
813 * {@link PlotChangeEvent} to all registered listeners.
814 *
815 * @param formatter the new formatter (<code>null</code> not permitted).
816 */
817 public void setValueFormat(NumberFormat formatter) {
818 if (formatter == null) {
819 throw new IllegalArgumentException("Null 'formatter' argument.");
820 }
821 this.valueFormat = formatter;
822 fireChangeEvent();
823 }
824
825 /**
826 * Returns the default mercury paint.
827 *
828 * @return The paint (never <code>null</code>).
829 *
830 * @see #setMercuryPaint(Paint)
831 */
832 public Paint getMercuryPaint() {
833 return this.mercuryPaint;
834 }
835
836 /**
837 * Sets the default mercury paint and sends a {@link PlotChangeEvent} to
838 * all registered listeners.
839 *
840 * @param paint the new paint (<code>null</code> not permitted).
841 *
842 * @see #getMercuryPaint()
843 */
844 public void setMercuryPaint(Paint paint) {
845 if (paint == null) {
846 throw new IllegalArgumentException("Null 'paint' argument.");
847 }
848 this.mercuryPaint = paint;
849 fireChangeEvent();
850 }
851
852 /**
853 * Returns the flag that controls whether not value lines are displayed.
854 *
855 * @return The flag.
856 *
857 * @see #setShowValueLines(boolean)
858 *
859 * @deprecated This flag doesn't do anything useful/visible. Deprecated
860 * as of version 1.0.6.
861 */
862 public boolean getShowValueLines() {
863 return this.showValueLines;
864 }
865
866 /**
867 * Sets the display as to whether to show value lines in the output.
868 *
869 * @param b Whether to show value lines in the thermometer
870 *
871 * @see #getShowValueLines()
872 *
873 * @deprecated This flag doesn't do anything useful/visible. Deprecated
874 * as of version 1.0.6.
875 */
876 public void setShowValueLines(boolean b) {
877 this.showValueLines = b;
878 fireChangeEvent();
879 }
880
881 /**
882 * Sets information for a particular range.
883 *
884 * @param range the range to specify information about.
885 * @param low the low value for the range
886 * @param hi the high value for the range
887 */
888 public void setSubrangeInfo(int range, double low, double hi) {
889 setSubrangeInfo(range, low, hi, low, hi);
890 }
891
892 /**
893 * Sets the subrangeInfo attribute of the ThermometerPlot object
894 *
895 * @param range the new rangeInfo value.
896 * @param rangeLow the new rangeInfo value
897 * @param rangeHigh the new rangeInfo value
898 * @param displayLow the new rangeInfo value
899 * @param displayHigh the new rangeInfo value
900 */
901 public void setSubrangeInfo(int range,
902 double rangeLow, double rangeHigh,
903 double displayLow, double displayHigh) {
904
905 if ((range >= 0) && (range < 3)) {
906 setSubrange(range, rangeLow, rangeHigh);
907 setDisplayRange(range, displayLow, displayHigh);
908 setAxisRange();
909 fireChangeEvent();
910 }
911
912 }
913
914 /**
915 * Sets the bounds for a subrange.
916 *
917 * @param range the range type.
918 * @param low the low value.
919 * @param high the high value.
920 */
921 public void setSubrange(int range, double low, double high) {
922 if ((range >= 0) && (range < 3)) {
923 this.subrangeInfo[range][RANGE_HIGH] = high;
924 this.subrangeInfo[range][RANGE_LOW] = low;
925 }
926 }
927
928 /**
929 * Sets the displayed bounds for a sub range.
930 *
931 * @param range the range type.
932 * @param low the low value.
933 * @param high the high value.
934 */
935 public void setDisplayRange(int range, double low, double high) {
936
937 if ((range >= 0) && (range < this.subrangeInfo.length)
938 && isValidNumber(high) && isValidNumber(low)) {
939
940 if (high > low) {
941 this.subrangeInfo[range][DISPLAY_HIGH] = high;
942 this.subrangeInfo[range][DISPLAY_LOW] = low;
943 }
944 else {
945 this.subrangeInfo[range][DISPLAY_HIGH] = low;
946 this.subrangeInfo[range][DISPLAY_LOW] = high;
947 }
948
949 }
950
951 }
952
953 /**
954 * Gets the paint used for a particular subrange.
955 *
956 * @param range the range (.
957 *
958 * @return The paint.
959 *
960 * @see #setSubrangePaint(int, Paint)
961 */
962 public Paint getSubrangePaint(int range) {
963 if ((range >= 0) && (range < this.subrangePaint.length)) {
964 return this.subrangePaint[range];
965 }
966 else {
967 return this.mercuryPaint;
968 }
969 }
970
971 /**
972 * Sets the paint to be used for a subrange and sends a
973 * {@link PlotChangeEvent} to all registered listeners.
974 *
975 * @param range the range (0, 1 or 2).
976 * @param paint the paint to be applied (<code>null</code> not permitted).
977 *
978 * @see #getSubrangePaint(int)
979 */
980 public void setSubrangePaint(int range, Paint paint) {
981 if ((range >= 0)
982 && (range < this.subrangePaint.length) && (paint != null)) {
983 this.subrangePaint[range] = paint;
984 fireChangeEvent();
985 }
986 }
987
988 /**
989 * Returns a flag that controls whether or not the thermometer axis zooms
990 * to display the subrange within which the data value falls.
991 *
992 * @return The flag.
993 */
994 public boolean getFollowDataInSubranges() {
995 return this.followDataInSubranges;
996 }
997
998 /**
999 * Sets the flag that controls whether or not the thermometer axis zooms
1000 * to display the subrange within which the data value falls.
1001 *
1002 * @param flag the flag.
1003 */
1004 public void setFollowDataInSubranges(boolean flag) {
1005 this.followDataInSubranges = flag;
1006 fireChangeEvent();
1007 }
1008
1009 /**
1010 * Returns a flag that controls whether or not the mercury color changes
1011 * for each subrange.
1012 *
1013 * @return The flag.
1014 *
1015 * @see #setUseSubrangePaint(boolean)
1016 */
1017 public boolean getUseSubrangePaint() {
1018 return this.useSubrangePaint;
1019 }
1020
1021 /**
1022 * Sets the range colour change option.
1023 *
1024 * @param flag the new range colour change option
1025 *
1026 * @see #getUseSubrangePaint()
1027 */
1028 public void setUseSubrangePaint(boolean flag) {
1029 this.useSubrangePaint = flag;
1030 fireChangeEvent();
1031 }
1032
1033 /**
1034 * Returns the bulb radius, in Java2D units.
1035
1036 * @return The bulb radius.
1037 *
1038 * @since 1.0.7
1039 */
1040 public int getBulbRadius() {
1041 return this.bulbRadius;
1042 }
1043
1044 /**
1045 * Sets the bulb radius (in Java2D units) and sends a
1046 * {@link PlotChangeEvent} to all registered listeners.
1047 *
1048 * @param r the new radius (in Java2D units).
1049 *
1050 * @see #getBulbRadius()
1051 *
1052 * @since 1.0.7
1053 */
1054 public void setBulbRadius(int r) {
1055 this.bulbRadius = r;
1056 fireChangeEvent();
1057 }
1058
1059 /**
1060 * Returns the bulb diameter, which is always twice the value returned
1061 * by {@link #getBulbRadius()}.
1062 *
1063 * @return The bulb diameter.
1064 *
1065 * @since 1.0.7
1066 */
1067 public int getBulbDiameter() {
1068 return getBulbRadius() * 2;
1069 }
1070
1071 /**
1072 * Returns the column radius, in Java2D units.
1073 *
1074 * @return The column radius.
1075 *
1076 * @see #setColumnRadius(int)
1077 *
1078 * @since 1.0.7
1079 */
1080 public int getColumnRadius() {
1081 return this.columnRadius;
1082 }
1083
1084 /**
1085 * Sets the column radius (in Java2D units) and sends a
1086 * {@link PlotChangeEvent} to all registered listeners.
1087 *
1088 * @param r the new radius.
1089 *
1090 * @see #getColumnRadius()
1091 *
1092 * @since 1.0.7
1093 */
1094 public void setColumnRadius(int r) {
1095 this.columnRadius = r;
1096 fireChangeEvent();
1097 }
1098
1099 /**
1100 * Returns the column diameter, which is always twice the value returned
1101 * by {@link #getColumnRadius()}.
1102 *
1103 * @return The column diameter.
1104 *
1105 * @since 1.0.7
1106 */
1107 public int getColumnDiameter() {
1108 return getColumnRadius() * 2;
1109 }
1110
1111 /**
1112 * Returns the gap, in Java2D units, between the two outlines that
1113 * represent the thermometer.
1114 *
1115 * @return The gap.
1116 *
1117 * @see #setGap(int)
1118 *
1119 * @since 1.0.7
1120 */
1121 public int getGap() {
1122 return this.gap;
1123 }
1124
1125 /**
1126 * Sets the gap (in Java2D units) between the two outlines that represent
1127 * the thermometer, and sends a {@link PlotChangeEvent} to all registered
1128 * listeners.
1129 *
1130 * @param gap the new gap.
1131 *
1132 * @see #getGap()
1133 *
1134 * @since 1.0.7
1135 */
1136 public void setGap(int gap) {
1137 this.gap = gap;
1138 fireChangeEvent();
1139 }
1140
1141 /**
1142 * Draws the plot on a Java 2D graphics device (such as the screen or a
1143 * printer).
1144 *
1145 * @param g2 the graphics device.
1146 * @param area the area within which the plot should be drawn.
1147 * @param anchor the anchor point (<code>null</code> permitted).
1148 * @param parentState the state from the parent plot, if there is one.
1149 * @param info collects info about the drawing.
1150 */
1151 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
1152 PlotState parentState,
1153 PlotRenderingInfo info) {
1154
1155 RoundRectangle2D outerStem = new RoundRectangle2D.Double();
1156 RoundRectangle2D innerStem = new RoundRectangle2D.Double();
1157 RoundRectangle2D mercuryStem = new RoundRectangle2D.Double();
1158 Ellipse2D outerBulb = new Ellipse2D.Double();
1159 Ellipse2D innerBulb = new Ellipse2D.Double();
1160 String temp = null;
1161 FontMetrics metrics = null;
1162 if (info != null) {
1163 info.setPlotArea(area);
1164 }
1165
1166 // adjust for insets...
1167 RectangleInsets insets = getInsets();
1168 insets.trim(area);
1169 drawBackground(g2, area);
1170
1171 // adjust for padding...
1172 Rectangle2D interior = (Rectangle2D) area.clone();
1173 this.padding.trim(interior);
1174 int midX = (int) (interior.getX() + (interior.getWidth() / 2));
1175 int midY = (int) (interior.getY() + (interior.getHeight() / 2));
1176 int stemTop = (int) (interior.getMinY() + getBulbRadius());
1177 int stemBottom = (int) (interior.getMaxY() - getBulbDiameter());
1178 Rectangle2D dataArea = new Rectangle2D.Double(midX - getColumnRadius(),
1179 stemTop, getColumnRadius(), stemBottom - stemTop);
1180
1181 outerBulb.setFrame(midX - getBulbRadius(), stemBottom,
1182 getBulbDiameter(), getBulbDiameter());
1183
1184 outerStem.setRoundRect(midX - getColumnRadius(), interior.getMinY(),
1185 getColumnDiameter(), stemBottom + getBulbDiameter() - stemTop,
1186 getColumnDiameter(), getColumnDiameter());
1187
1188 Area outerThermometer = new Area(outerBulb);
1189 Area tempArea = new Area(outerStem);
1190 outerThermometer.add(tempArea);
1191
1192 innerBulb.setFrame(midX - getBulbRadius() + getGap(), stemBottom
1193 + getGap(), getBulbDiameter() - getGap() * 2, getBulbDiameter()
1194 - getGap() * 2);
1195
1196 innerStem.setRoundRect(midX - getColumnRadius() + getGap(),
1197 interior.getMinY() + getGap(), getColumnDiameter()
1198 - getGap() * 2, stemBottom + getBulbDiameter() - getGap() * 2
1199 - stemTop, getColumnDiameter() - getGap() * 2,
1200 getColumnDiameter() - getGap() * 2);
1201
1202 Area innerThermometer = new Area(innerBulb);
1203 tempArea = new Area(innerStem);
1204 innerThermometer.add(tempArea);
1205
1206 if ((this.dataset != null) && (this.dataset.getValue() != null)) {
1207 double current = this.dataset.getValue().doubleValue();
1208 double ds = this.rangeAxis.valueToJava2D(current, dataArea,
1209 RectangleEdge.LEFT);
1210
1211 int i = getColumnDiameter() - getGap() * 2; // already calculated
1212 int j = getColumnRadius() - getGap(); // already calculated
1213 int l = (i / 2);
1214 int k = (int) Math.round(ds);
1215 if (k < (getGap() + interior.getMinY())) {
1216 k = (int) (getGap() + interior.getMinY());
1217 l = getBulbRadius();
1218 }
1219
1220 Area mercury = new Area(innerBulb);
1221
1222 if (k < (stemBottom + getBulbRadius())) {
1223 mercuryStem.setRoundRect(midX - j, k, i,
1224 (stemBottom + getBulbRadius()) - k, l, l);
1225 tempArea = new Area(mercuryStem);
1226 mercury.add(tempArea);
1227 }
1228
1229 g2.setPaint(getCurrentPaint());
1230 g2.fill(mercury);
1231
1232 // draw range indicators...
1233 if (this.subrangeIndicatorsVisible) {
1234 g2.setStroke(this.subrangeIndicatorStroke);
1235 Range range = this.rangeAxis.getRange();
1236
1237 // draw start of normal range
1238 double value = this.subrangeInfo[NORMAL][RANGE_LOW];
1239 if (range.contains(value)) {
1240 double x = midX + getColumnRadius() + 2;
1241 double y = this.rangeAxis.valueToJava2D(value, dataArea,
1242 RectangleEdge.LEFT);
1243 Line2D line = new Line2D.Double(x, y, x + 10, y);
1244 g2.setPaint(this.subrangePaint[NORMAL]);
1245 g2.draw(line);
1246 }
1247
1248 // draw start of warning range
1249 value = this.subrangeInfo[WARNING][RANGE_LOW];
1250 if (range.contains(value)) {
1251 double x = midX + getColumnRadius() + 2;
1252 double y = this.rangeAxis.valueToJava2D(value, dataArea,
1253 RectangleEdge.LEFT);
1254 Line2D line = new Line2D.Double(x, y, x + 10, y);
1255 g2.setPaint(this.subrangePaint[WARNING]);
1256 g2.draw(line);
1257 }
1258
1259 // draw start of critical range
1260 value = this.subrangeInfo[CRITICAL][RANGE_LOW];
1261 if (range.contains(value)) {
1262 double x = midX + getColumnRadius() + 2;
1263 double y = this.rangeAxis.valueToJava2D(value, dataArea,
1264 RectangleEdge.LEFT);
1265 Line2D line = new Line2D.Double(x, y, x + 10, y);
1266 g2.setPaint(this.subrangePaint[CRITICAL]);
1267 g2.draw(line);
1268 }
1269 }
1270
1271 // draw the axis...
1272 if ((this.rangeAxis != null) && (this.axisLocation != NONE)) {
1273 int drawWidth = AXIS_GAP;
1274 if (this.showValueLines) {
1275 drawWidth += getColumnDiameter();
1276 }
1277 Rectangle2D drawArea;
1278 double cursor = 0;
1279
1280 switch (this.axisLocation) {
1281 case RIGHT:
1282 cursor = midX + getColumnRadius();
1283 drawArea = new Rectangle2D.Double(cursor,
1284 stemTop, drawWidth, (stemBottom - stemTop + 1));
1285 this.rangeAxis.draw(g2, cursor, area, drawArea,
1286 RectangleEdge.RIGHT, null);
1287 break;
1288
1289 case LEFT:
1290 default:
1291 //cursor = midX - COLUMN_RADIUS - AXIS_GAP;
1292 cursor = midX - getColumnRadius();
1293 drawArea = new Rectangle2D.Double(cursor, stemTop,
1294 drawWidth, (stemBottom - stemTop + 1));
1295 this.rangeAxis.draw(g2, cursor, area, drawArea,
1296 RectangleEdge.LEFT, null);
1297 break;
1298 }
1299
1300 }
1301
1302 // draw text value on screen
1303 g2.setFont(this.valueFont);
1304 g2.setPaint(this.valuePaint);
1305 metrics = g2.getFontMetrics();
1306 switch (this.valueLocation) {
1307 case RIGHT:
1308 g2.drawString(this.valueFormat.format(current),
1309 midX + getColumnRadius() + getGap(), midY);
1310 break;
1311 case LEFT:
1312 String valueString = this.valueFormat.format(current);
1313 int stringWidth = metrics.stringWidth(valueString);
1314 g2.drawString(valueString, midX - getColumnRadius()
1315 - getGap() - stringWidth, midY);
1316 break;
1317 case BULB:
1318 temp = this.valueFormat.format(current);
1319 i = metrics.stringWidth(temp) / 2;
1320 g2.drawString(temp, midX - i,
1321 stemBottom + getBulbRadius() + getGap());
1322 break;
1323 default:
1324 }
1325 /***/
1326 }
1327
1328 g2.setPaint(this.thermometerPaint);
1329 g2.setFont(this.valueFont);
1330
1331 // draw units indicator
1332 metrics = g2.getFontMetrics();
1333 int tickX1 = midX - getColumnRadius() - getGap() * 2
1334 - metrics.stringWidth(UNITS[this.units]);
1335 if (tickX1 > area.getMinX()) {
1336 g2.drawString(UNITS[this.units], tickX1,
1337 (int) (area.getMinY() + 20));
1338 }
1339
1340 // draw thermometer outline
1341 g2.setStroke(this.thermometerStroke);
1342 g2.draw(outerThermometer);
1343 g2.draw(innerThermometer);
1344
1345 drawOutline(g2, area);
1346 }
1347
1348 /**
1349 * A zoom method that does nothing. Plots are required to support the
1350 * zoom operation. In the case of a thermometer chart, it doesn't make
1351 * sense to zoom in or out, so the method is empty.
1352 *
1353 * @param percent the zoom percentage.
1354 */
1355 public void zoom(double percent) {
1356 // intentionally blank
1357 }
1358
1359 /**
1360 * Returns a short string describing the type of plot.
1361 *
1362 * @return A short string describing the type of plot.
1363 */
1364 public String getPlotType() {
1365 return localizationResources.getString("Thermometer_Plot");
1366 }
1367
1368 /**
1369 * Checks to see if a new value means the axis range needs adjusting.
1370 *
1371 * @param event the dataset change event.
1372 */
1373 public void datasetChanged(DatasetChangeEvent event) {
1374 if (this.dataset != null) {
1375 Number vn = this.dataset.getValue();
1376 if (vn != null) {
1377 double value = vn.doubleValue();
1378 if (inSubrange(NORMAL, value)) {
1379 this.subrange = NORMAL;
1380 }
1381 else if (inSubrange(WARNING, value)) {
1382 this.subrange = WARNING;
1383 }
1384 else if (inSubrange(CRITICAL, value)) {
1385 this.subrange = CRITICAL;
1386 }
1387 else {
1388 this.subrange = -1;
1389 }
1390 setAxisRange();
1391 }
1392 }
1393 super.datasetChanged(event);
1394 }
1395
1396 /**
1397 * Returns the minimum value in either the domain or the range, whichever
1398 * is displayed against the vertical axis for the particular type of plot
1399 * implementing this interface.
1400 *
1401 * @return The minimum value in either the domain or the range.
1402 *
1403 * @deprecated This method is not used. Officially deprecated in version
1404 * 1.0.6.
1405 */
1406 public Number getMinimumVerticalDataValue() {
1407 return new Double(this.lowerBound);
1408 }
1409
1410 /**
1411 * Returns the maximum value in either the domain or the range, whichever
1412 * is displayed against the vertical axis for the particular type of plot
1413 * implementing this interface.
1414 *
1415 * @return The maximum value in either the domain or the range
1416 *
1417 * @deprecated This method is not used. Officially deprecated in version
1418 * 1.0.6.
1419 */
1420 public Number getMaximumVerticalDataValue() {
1421 return new Double(this.upperBound);
1422 }
1423
1424 /**
1425 * Returns the data range.
1426 *
1427 * @param axis the axis.
1428 *
1429 * @return The range of data displayed.
1430 */
1431 public Range getDataRange(ValueAxis axis) {
1432 return new Range(this.lowerBound, this.upperBound);
1433 }
1434
1435 /**
1436 * Sets the axis range to the current values in the rangeInfo array.
1437 */
1438 protected void setAxisRange() {
1439 if ((this.subrange >= 0) && (this.followDataInSubranges)) {
1440 this.rangeAxis.setRange(
1441 new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW],
1442 this.subrangeInfo[this.subrange][DISPLAY_HIGH]));
1443 }
1444 else {
1445 this.rangeAxis.setRange(this.lowerBound, this.upperBound);
1446 }
1447 }
1448
1449 /**
1450 * Returns the legend items for the plot.
1451 *
1452 * @return <code>null</code>.
1453 */
1454 public LegendItemCollection getLegendItems() {
1455 return null;
1456 }
1457
1458 /**
1459 * Returns the orientation of the plot.
1460 *
1461 * @return The orientation (always {@link PlotOrientation#VERTICAL}).
1462 */
1463 public PlotOrientation getOrientation() {
1464 return PlotOrientation.VERTICAL;
1465 }
1466
1467 /**
1468 * Determine whether a number is valid and finite.
1469 *
1470 * @param d the number to be tested.
1471 *
1472 * @return <code>true</code> if the number is valid and finite, and
1473 * <code>false</code> otherwise.
1474 */
1475 protected static boolean isValidNumber(double d) {
1476 return (!(Double.isNaN(d) || Double.isInfinite(d)));
1477 }
1478
1479 /**
1480 * Returns true if the value is in the specified range, and false otherwise.
1481 *
1482 * @param subrange the subrange.
1483 * @param value the value to check.
1484 *
1485 * @return A boolean.
1486 */
1487 private boolean inSubrange(int subrange, double value) {
1488 return (value > this.subrangeInfo[subrange][RANGE_LOW]
1489 && value <= this.subrangeInfo[subrange][RANGE_HIGH]);
1490 }
1491
1492 /**
1493 * Returns the mercury paint corresponding to the current data value.
1494 * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D,
1495 * PlotState, PlotRenderingInfo)} method.
1496 *
1497 * @return The paint (never <code>null</code>).
1498 */
1499 private Paint getCurrentPaint() {
1500 Paint result = this.mercuryPaint;
1501 if (this.useSubrangePaint) {
1502 double value = this.dataset.getValue().doubleValue();
1503 if (inSubrange(NORMAL, value)) {
1504 result = this.subrangePaint[NORMAL];
1505 }
1506 else if (inSubrange(WARNING, value)) {
1507 result = this.subrangePaint[WARNING];
1508 }
1509 else if (inSubrange(CRITICAL, value)) {
1510 result = this.subrangePaint[CRITICAL];
1511 }
1512 }
1513 return result;
1514 }
1515
1516 /**
1517 * Tests this plot for equality with another object. The plot's dataset
1518 * is not considered in the test.
1519 *
1520 * @param obj the object (<code>null</code> permitted).
1521 *
1522 * @return <code>true</code> or <code>false</code>.
1523 */
1524 public boolean equals(Object obj) {
1525 if (obj == this) {
1526 return true;
1527 }
1528 if (!(obj instanceof ThermometerPlot)) {
1529 return false;
1530 }
1531 ThermometerPlot that = (ThermometerPlot) obj;
1532 if (!super.equals(obj)) {
1533 return false;
1534 }
1535 if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
1536 return false;
1537 }
1538 if (this.axisLocation != that.axisLocation) {
1539 return false;
1540 }
1541 if (this.lowerBound != that.lowerBound) {
1542 return false;
1543 }
1544 if (this.upperBound != that.upperBound) {
1545 return false;
1546 }
1547 if (!ObjectUtilities.equal(this.padding, that.padding)) {
1548 return false;
1549 }
1550 if (!ObjectUtilities.equal(this.thermometerStroke,
1551 that.thermometerStroke)) {
1552 return false;
1553 }
1554 if (!PaintUtilities.equal(this.thermometerPaint,
1555 that.thermometerPaint)) {
1556 return false;
1557 }
1558 if (this.units != that.units) {
1559 return false;
1560 }
1561 if (this.valueLocation != that.valueLocation) {
1562 return false;
1563 }
1564 if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) {
1565 return false;
1566 }
1567 if (!PaintUtilities.equal(this.valuePaint, that.valuePaint)) {
1568 return false;
1569 }
1570 if (!ObjectUtilities.equal(this.valueFormat, that.valueFormat)) {
1571 return false;
1572 }
1573 if (!PaintUtilities.equal(this.mercuryPaint, that.mercuryPaint)) {
1574 return false;
1575 }
1576 if (this.showValueLines != that.showValueLines) {
1577 return false;
1578 }
1579 if (this.subrange != that.subrange) {
1580 return false;
1581 }
1582 if (this.followDataInSubranges != that.followDataInSubranges) {
1583 return false;
1584 }
1585 if (!equal(this.subrangeInfo, that.subrangeInfo)) {
1586 return false;
1587 }
1588 if (this.useSubrangePaint != that.useSubrangePaint) {
1589 return false;
1590 }
1591 if (this.bulbRadius != that.bulbRadius) {
1592 return false;
1593 }
1594 if (this.columnRadius != that.columnRadius) {
1595 return false;
1596 }
1597 if (this.gap != that.gap) {
1598 return false;
1599 }
1600 for (int i = 0; i < this.subrangePaint.length; i++) {
1601 if (!PaintUtilities.equal(this.subrangePaint[i],
1602 that.subrangePaint[i])) {
1603 return false;
1604 }
1605 }
1606 return true;
1607 }
1608
1609 /**
1610 * Tests two double[][] arrays for equality.
1611 *
1612 * @param array1 the first array (<code>null</code> permitted).
1613 * @param array2 the second arrray (<code>null</code> permitted).
1614 *
1615 * @return A boolean.
1616 */
1617 private static boolean equal(double[][] array1, double[][] array2) {
1618 if (array1 == null) {
1619 return (array2 == null);
1620 }
1621 if (array2 == null) {
1622 return false;
1623 }
1624 if (array1.length != array2.length) {
1625 return false;
1626 }
1627 for (int i = 0; i < array1.length; i++) {
1628 if (!Arrays.equals(array1[i], array2[i])) {
1629 return false;
1630 }
1631 }
1632 return true;
1633 }
1634
1635 /**
1636 * Returns a clone of the plot.
1637 *
1638 * @return A clone.
1639 *
1640 * @throws CloneNotSupportedException if the plot cannot be cloned.
1641 */
1642 public Object clone() throws CloneNotSupportedException {
1643
1644 ThermometerPlot clone = (ThermometerPlot) super.clone();
1645
1646 if (clone.dataset != null) {
1647 clone.dataset.addChangeListener(clone);
1648 }
1649 clone.rangeAxis = (ValueAxis) ObjectUtilities.clone(this.rangeAxis);
1650 if (clone.rangeAxis != null) {
1651 clone.rangeAxis.setPlot(clone);
1652 clone.rangeAxis.addChangeListener(clone);
1653 }
1654 clone.valueFormat = (NumberFormat) this.valueFormat.clone();
1655 clone.subrangePaint = (Paint[]) this.subrangePaint.clone();
1656
1657 return clone;
1658
1659 }
1660
1661 /**
1662 * Provides serialization support.
1663 *
1664 * @param stream the output stream.
1665 *
1666 * @throws IOException if there is an I/O error.
1667 */
1668 private void writeObject(ObjectOutputStream stream) throws IOException {
1669 stream.defaultWriteObject();
1670 SerialUtilities.writeStroke(this.thermometerStroke, stream);
1671 SerialUtilities.writePaint(this.thermometerPaint, stream);
1672 SerialUtilities.writePaint(this.valuePaint, stream);
1673 SerialUtilities.writePaint(this.mercuryPaint, stream);
1674 SerialUtilities.writeStroke(this.subrangeIndicatorStroke, stream);
1675 SerialUtilities.writeStroke(this.rangeIndicatorStroke, stream);
1676 for (int i = 0; i < 3; i++) {
1677 SerialUtilities.writePaint(this.subrangePaint[i], stream);
1678 }
1679 }
1680
1681 /**
1682 * Provides serialization support.
1683 *
1684 * @param stream the input stream.
1685 *
1686 * @throws IOException if there is an I/O error.
1687 * @throws ClassNotFoundException if there is a classpath problem.
1688 */
1689 private void readObject(ObjectInputStream stream) throws IOException,
1690 ClassNotFoundException {
1691 stream.defaultReadObject();
1692 this.thermometerStroke = SerialUtilities.readStroke(stream);
1693 this.thermometerPaint = SerialUtilities.readPaint(stream);
1694 this.valuePaint = SerialUtilities.readPaint(stream);
1695 this.mercuryPaint = SerialUtilities.readPaint(stream);
1696 this.subrangeIndicatorStroke = SerialUtilities.readStroke(stream);
1697 this.rangeIndicatorStroke = SerialUtilities.readStroke(stream);
1698 this.subrangePaint = new Paint[3];
1699 for (int i = 0; i < 3; i++) {
1700 this.subrangePaint[i] = SerialUtilities.readPaint(stream);
1701 }
1702 if (this.rangeAxis != null) {
1703 this.rangeAxis.addChangeListener(this);
1704 }
1705 }
1706
1707 /**
1708 * Multiplies the range on the domain axis/axes by the specified factor.
1709 *
1710 * @param factor the zoom factor.
1711 * @param state the plot state.
1712 * @param source the source point.
1713 */
1714 public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1715 Point2D source) {
1716 // no domain axis to zoom
1717 }
1718
1719 /**
1720 * Multiplies the range on the domain axis/axes by the specified factor.
1721 *
1722 * @param factor the zoom factor.
1723 * @param state the plot state.
1724 * @param source the source point.
1725 * @param useAnchor a flag that controls whether or not the source point
1726 * is used for the zoom anchor.
1727 *
1728 * @since 1.0.7
1729 */
1730 public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1731 Point2D source, boolean useAnchor) {
1732 // no domain axis to zoom
1733 }
1734
1735 /**
1736 * Multiplies the range on the range axis/axes by the specified factor.
1737 *
1738 * @param factor the zoom factor.
1739 * @param state the plot state.
1740 * @param source the source point.
1741 */
1742 public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1743 Point2D source) {
1744 this.rangeAxis.resizeRange(factor);
1745 }
1746
1747 /**
1748 * Multiplies the range on the range axis/axes by the specified factor.
1749 *
1750 * @param factor the zoom factor.
1751 * @param state the plot state.
1752 * @param source the source point.
1753 * @param useAnchor a flag that controls whether or not the source point
1754 * is used for the zoom anchor.
1755 *
1756 * @since 1.0.7
1757 */
1758 public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1759 Point2D source, boolean useAnchor) {
1760 double anchorY = this.getRangeAxis().java2DToValue(source.getY(),
1761 state.getDataArea(), RectangleEdge.LEFT);
1762 this.rangeAxis.resizeRange(factor, anchorY);
1763 }
1764
1765 /**
1766 * This method does nothing.
1767 *
1768 * @param lowerPercent the lower percent.
1769 * @param upperPercent the upper percent.
1770 * @param state the plot state.
1771 * @param source the source point.
1772 */
1773 public void zoomDomainAxes(double lowerPercent, double upperPercent,
1774 PlotRenderingInfo state, Point2D source) {
1775 // no domain axis to zoom
1776 }
1777
1778 /**
1779 * Zooms the range axes.
1780 *
1781 * @param lowerPercent the lower percent.
1782 * @param upperPercent the upper percent.
1783 * @param state the plot state.
1784 * @param source the source point.
1785 */
1786 public void zoomRangeAxes(double lowerPercent, double upperPercent,
1787 PlotRenderingInfo state, Point2D source) {
1788 this.rangeAxis.zoomRange(lowerPercent, upperPercent);
1789 }
1790
1791 /**
1792 * Returns <code>false</code>.
1793 *
1794 * @return A boolean.
1795 */
1796 public boolean isDomainZoomable() {
1797 return false;
1798 }
1799
1800 /**
1801 * Returns <code>true</code>.
1802 *
1803 * @return A boolean.
1804 */
1805 public boolean isRangeZoomable() {
1806 return true;
1807 }
1808
1809 }