0001: /* ===========================================================
0002: * JFreeChart : a free chart library for the Java(tm) platform
0003: * ===========================================================
0004: *
0005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0006: *
0007: * Project Info: http://www.jfree.org/jfreechart/index.html
0008: *
0009: * This library is free software; you can redistribute it and/or modify it
0010: * under the terms of the GNU Lesser General Public License as published by
0011: * the Free Software Foundation; either version 2.1 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but
0015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
0016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
0017: * License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0022: * USA.
0023: *
0024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0025: * in the United States and other countries.]
0026: *
0027: * --------------------
0028: * ThermometerPlot.java
0029: * --------------------
0030: *
0031: * (C) Copyright 2000-2007, by Bryan Scott and Contributors.
0032: *
0033: * Original Author: Bryan Scott (based on MeterPlot by Hari).
0034: * Contributor(s): David Gilbert (for Object Refinery Limited).
0035: * Arnaud Lelievre;
0036: *
0037: * Changes
0038: * -------
0039: * 11-Apr-2002 : Version 1, contributed by Bryan Scott;
0040: * 15-Apr-2002 : Changed to implement VerticalValuePlot;
0041: * 29-Apr-2002 : Added getVerticalValueAxis() method (DG);
0042: * 25-Jun-2002 : Removed redundant imports (DG);
0043: * 17-Sep-2002 : Reviewed with Checkstyle utility (DG);
0044: * 18-Sep-2002 : Extensive changes made to API, to iron out bugs and
0045: * inconsistencies (DG);
0046: * 13-Oct-2002 : Corrected error datasetChanged which would generate exceptions
0047: * when value set to null (BRS).
0048: * 23-Jan-2003 : Removed one constructor (DG);
0049: * 26-Mar-2003 : Implemented Serializable (DG);
0050: * 02-Jun-2003 : Removed test for compatible range axis (DG);
0051: * 01-Jul-2003 : Added additional check in draw method to ensure value not
0052: * null (BRS);
0053: * 08-Sep-2003 : Added internationalization via use of properties
0054: * resourceBundle (RFE 690236) (AL);
0055: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
0056: * 29-Sep-2003 : Updated draw to set value of cursor to non-zero and allow
0057: * painting of axis. An incomplete fix and needs to be set for
0058: * left or right drawing (BRS);
0059: * 19-Nov-2003 : Added support for value labels to be displayed left of the
0060: * thermometer
0061: * 19-Nov-2003 : Improved axis drawing (now default axis does not draw axis line
0062: * and is closer to the bulb). Added support for the positioning
0063: * of the axis to the left or right of the bulb. (BRS);
0064: * 03-Dec-2003 : Directly mapped deprecated setData()/getData() method to
0065: * get/setDataset() (TM);
0066: * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
0067: * 07-Apr-2004 : Changed string width calculation (DG);
0068: * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
0069: * 06-Jan-2004 : Added getOrientation() method (DG);
0070: * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
0071: * 29-Mar-2005 : Fixed equals() method (DG);
0072: * 05-May-2005 : Updated draw() method parameters (DG);
0073: * 09-Jun-2005 : Fixed more bugs in equals() method (DG);
0074: * 10-Jun-2005 : Fixed minor bug in setDisplayRange() method (DG);
0075: * ------------- JFREECHART 1.0.x ---------------------------------------------
0076: * 14-Nov-2006 : Fixed margin when drawing (DG);
0077: * 03-May-2007 : Fixed datasetChanged() to handle null dataset, added null
0078: * argument check and event notification to setRangeAxis(),
0079: * added null argument check to setPadding(), setValueFont(),
0080: * setValuePaint(), setValueFormat() and setMercuryPaint(),
0081: * deprecated get/setShowValueLines(), deprecated
0082: * getMinimum/MaximumVerticalDataValue(), and fixed serialization
0083: * bug (DG);
0084: *
0085: */
0086:
0087: package org.jfree.chart.plot;
0088:
0089: import java.awt.BasicStroke;
0090: import java.awt.Color;
0091: import java.awt.Font;
0092: import java.awt.FontMetrics;
0093: import java.awt.Graphics2D;
0094: import java.awt.Paint;
0095: import java.awt.Stroke;
0096: import java.awt.geom.Area;
0097: import java.awt.geom.Ellipse2D;
0098: import java.awt.geom.Line2D;
0099: import java.awt.geom.Point2D;
0100: import java.awt.geom.Rectangle2D;
0101: import java.awt.geom.RoundRectangle2D;
0102: import java.io.IOException;
0103: import java.io.ObjectInputStream;
0104: import java.io.ObjectOutputStream;
0105: import java.io.Serializable;
0106: import java.text.DecimalFormat;
0107: import java.text.NumberFormat;
0108: import java.util.Arrays;
0109: import java.util.ResourceBundle;
0110:
0111: import org.jfree.chart.LegendItemCollection;
0112: import org.jfree.chart.axis.NumberAxis;
0113: import org.jfree.chart.axis.ValueAxis;
0114: import org.jfree.chart.event.PlotChangeEvent;
0115: import org.jfree.data.Range;
0116: import org.jfree.data.general.DatasetChangeEvent;
0117: import org.jfree.data.general.DefaultValueDataset;
0118: import org.jfree.data.general.ValueDataset;
0119: import org.jfree.io.SerialUtilities;
0120: import org.jfree.ui.RectangleEdge;
0121: import org.jfree.ui.RectangleInsets;
0122: import org.jfree.util.ObjectUtilities;
0123: import org.jfree.util.PaintUtilities;
0124: import org.jfree.util.UnitType;
0125:
0126: /**
0127: * A plot that displays a single value (from a {@link ValueDataset}) in a
0128: * thermometer type display.
0129: * <p>
0130: * This plot supports a number of options:
0131: * <ol>
0132: * <li>three sub-ranges which could be viewed as 'Normal', 'Warning'
0133: * and 'Critical' ranges.</li>
0134: * <li>the thermometer can be run in two modes:
0135: * <ul>
0136: * <li>fixed range, or</li>
0137: * <li>range adjusts to current sub-range.</li>
0138: * </ul>
0139: * </li>
0140: * <li>settable units to be displayed.</li>
0141: * <li>settable display location for the value text.</li>
0142: * </ol>
0143: */
0144: public class ThermometerPlot extends Plot implements ValueAxisPlot,
0145: Zoomable, Cloneable, Serializable {
0146:
0147: /** For serialization. */
0148: private static final long serialVersionUID = 4087093313147984390L;
0149:
0150: /** A constant for unit type 'None'. */
0151: public static final int UNITS_NONE = 0;
0152:
0153: /** A constant for unit type 'Fahrenheit'. */
0154: public static final int UNITS_FAHRENHEIT = 1;
0155:
0156: /** A constant for unit type 'Celcius'. */
0157: public static final int UNITS_CELCIUS = 2;
0158:
0159: /** A constant for unit type 'Kelvin'. */
0160: public static final int UNITS_KELVIN = 3;
0161:
0162: /** A constant for the value label position (no label). */
0163: public static final int NONE = 0;
0164:
0165: /** A constant for the value label position (right of the thermometer). */
0166: public static final int RIGHT = 1;
0167:
0168: /** A constant for the value label position (left of the thermometer). */
0169: public static final int LEFT = 2;
0170:
0171: /** A constant for the value label position (in the thermometer bulb). */
0172: public static final int BULB = 3;
0173:
0174: /** A constant for the 'normal' range. */
0175: public static final int NORMAL = 0;
0176:
0177: /** A constant for the 'warning' range. */
0178: public static final int WARNING = 1;
0179:
0180: /** A constant for the 'critical' range. */
0181: public static final int CRITICAL = 2;
0182:
0183: /** The bulb radius. */
0184: protected static final int BULB_RADIUS = 40;
0185:
0186: /** The bulb diameter. */
0187: protected static final int BULB_DIAMETER = BULB_RADIUS * 2;
0188:
0189: /** The column radius. */
0190: protected static final int COLUMN_RADIUS = 20;
0191:
0192: /** The column diameter.*/
0193: protected static final int COLUMN_DIAMETER = COLUMN_RADIUS * 2;
0194:
0195: /** The gap radius. */
0196: protected static final int GAP_RADIUS = 5;
0197:
0198: /** The gap diameter. */
0199: protected static final int GAP_DIAMETER = GAP_RADIUS * 2;
0200:
0201: /** The axis gap. */
0202: protected static final int AXIS_GAP = 10;
0203:
0204: /** The unit strings. */
0205: protected static final String[] UNITS = { "", "\u00B0F", "\u00B0C",
0206: "\u00B0K" };
0207:
0208: /** Index for low value in subrangeInfo matrix. */
0209: protected static final int RANGE_LOW = 0;
0210:
0211: /** Index for high value in subrangeInfo matrix. */
0212: protected static final int RANGE_HIGH = 1;
0213:
0214: /** Index for display low value in subrangeInfo matrix. */
0215: protected static final int DISPLAY_LOW = 2;
0216:
0217: /** Index for display high value in subrangeInfo matrix. */
0218: protected static final int DISPLAY_HIGH = 3;
0219:
0220: /** The default lower bound. */
0221: protected static final double DEFAULT_LOWER_BOUND = 0.0;
0222:
0223: /** The default upper bound. */
0224: protected static final double DEFAULT_UPPER_BOUND = 100.0;
0225:
0226: /** The dataset for the plot. */
0227: private ValueDataset dataset;
0228:
0229: /** The range axis. */
0230: private ValueAxis rangeAxis;
0231:
0232: /** The lower bound for the thermometer. */
0233: private double lowerBound = DEFAULT_LOWER_BOUND;
0234:
0235: /** The upper bound for the thermometer. */
0236: private double upperBound = DEFAULT_UPPER_BOUND;
0237:
0238: /**
0239: * Blank space inside the plot area around the outside of the thermometer.
0240: */
0241: private RectangleInsets padding;
0242:
0243: /** Stroke for drawing the thermometer */
0244: private transient Stroke thermometerStroke = new BasicStroke(1.0f);
0245:
0246: /** Paint for drawing the thermometer */
0247: private transient Paint thermometerPaint = Color.black;
0248:
0249: /** The display units */
0250: private int units = UNITS_CELCIUS;
0251:
0252: /** The value label position. */
0253: private int valueLocation = BULB;
0254:
0255: /** The position of the axis **/
0256: private int axisLocation = LEFT;
0257:
0258: /** The font to write the value in */
0259: private Font valueFont = new Font("SansSerif", Font.BOLD, 16);
0260:
0261: /** Colour that the value is written in */
0262: private transient Paint valuePaint = Color.white;
0263:
0264: /** Number format for the value */
0265: private NumberFormat valueFormat = new DecimalFormat();
0266:
0267: /** The default paint for the mercury in the thermometer. */
0268: private transient Paint mercuryPaint = Color.lightGray;
0269:
0270: /** A flag that controls whether value lines are drawn. */
0271: private boolean showValueLines = false;
0272:
0273: /** The display sub-range. */
0274: private int subrange = -1;
0275:
0276: /** The start and end values for the subranges. */
0277: private double[][] subrangeInfo = { { 0.0, 50.0, 0.0, 50.0 },
0278: { 50.0, 75.0, 50.0, 75.0 }, { 75.0, 100.0, 75.0, 100.0 } };
0279:
0280: /**
0281: * A flag that controls whether or not the axis range adjusts to the
0282: * sub-ranges.
0283: */
0284: private boolean followDataInSubranges = false;
0285:
0286: /**
0287: * A flag that controls whether or not the mercury paint changes with
0288: * the subranges.
0289: */
0290: private boolean useSubrangePaint = true;
0291:
0292: /** Paint for each range */
0293: private transient Paint[] subrangePaint = { Color.green,
0294: Color.orange, Color.red };
0295:
0296: /** A flag that controls whether the sub-range indicators are visible. */
0297: private boolean subrangeIndicatorsVisible = true;
0298:
0299: /** The stroke for the sub-range indicators. */
0300: private transient Stroke subrangeIndicatorStroke = new BasicStroke(
0301: 2.0f);
0302:
0303: /** The range indicator stroke. */
0304: private transient Stroke rangeIndicatorStroke = new BasicStroke(
0305: 3.0f);
0306:
0307: /** The resourceBundle for the localization. */
0308: protected static ResourceBundle localizationResources = ResourceBundle
0309: .getBundle("org.jfree.chart.plot.LocalizationBundle");
0310:
0311: /**
0312: * Creates a new thermometer plot.
0313: */
0314: public ThermometerPlot() {
0315: this (new DefaultValueDataset());
0316: }
0317:
0318: /**
0319: * Creates a new thermometer plot, using default attributes where necessary.
0320: *
0321: * @param dataset the data set.
0322: */
0323: public ThermometerPlot(ValueDataset dataset) {
0324:
0325: super ();
0326:
0327: this .padding = new RectangleInsets(UnitType.RELATIVE, 0.05,
0328: 0.05, 0.05, 0.05);
0329: this .dataset = dataset;
0330: if (dataset != null) {
0331: dataset.addChangeListener(this );
0332: }
0333: NumberAxis axis = new NumberAxis(null);
0334: axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
0335: axis.setAxisLineVisible(false);
0336: axis.setPlot(this );
0337: axis.addChangeListener(this );
0338: this .rangeAxis = axis;
0339: setAxisRange();
0340: }
0341:
0342: /**
0343: * Returns the dataset for the plot.
0344: *
0345: * @return The dataset (possibly <code>null</code>).
0346: *
0347: * @see #setDataset(ValueDataset)
0348: */
0349: public ValueDataset getDataset() {
0350: return this .dataset;
0351: }
0352:
0353: /**
0354: * Sets the dataset for the plot, replacing the existing dataset if there
0355: * is one, and sends a {@link PlotChangeEvent} to all registered listeners.
0356: *
0357: * @param dataset the dataset (<code>null</code> permitted).
0358: *
0359: * @see #getDataset()
0360: */
0361: public void setDataset(ValueDataset dataset) {
0362:
0363: // if there is an existing dataset, remove the plot from the list
0364: // of change listeners...
0365: ValueDataset existing = this .dataset;
0366: if (existing != null) {
0367: existing.removeChangeListener(this );
0368: }
0369:
0370: // set the new dataset, and register the chart as a change listener...
0371: this .dataset = dataset;
0372: if (dataset != null) {
0373: setDatasetGroup(dataset.getGroup());
0374: dataset.addChangeListener(this );
0375: }
0376:
0377: // send a dataset change event to self...
0378: DatasetChangeEvent event = new DatasetChangeEvent(this , dataset);
0379: datasetChanged(event);
0380:
0381: }
0382:
0383: /**
0384: * Returns the range axis.
0385: *
0386: * @return The range axis (never <code>null</code>).
0387: *
0388: * @see #setRangeAxis(ValueAxis)
0389: */
0390: public ValueAxis getRangeAxis() {
0391: return this .rangeAxis;
0392: }
0393:
0394: /**
0395: * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
0396: * all registered listeners.
0397: *
0398: * @param axis the new axis (<code>null</code> not permitted).
0399: *
0400: * @see #getRangeAxis()
0401: */
0402: public void setRangeAxis(ValueAxis axis) {
0403: if (axis == null) {
0404: throw new IllegalArgumentException("Null 'axis' argument.");
0405: }
0406: // plot is registered as a listener with the existing axis...
0407: this .rangeAxis.removeChangeListener(this );
0408:
0409: axis.setPlot(this );
0410: axis.addChangeListener(this );
0411: this .rangeAxis = axis;
0412: notifyListeners(new PlotChangeEvent(this ));
0413:
0414: }
0415:
0416: /**
0417: * Returns the lower bound for the thermometer. The data value can be set
0418: * lower than this, but it will not be shown in the thermometer.
0419: *
0420: * @return The lower bound.
0421: *
0422: * @see #setLowerBound(double)
0423: */
0424: public double getLowerBound() {
0425: return this .lowerBound;
0426: }
0427:
0428: /**
0429: * Sets the lower bound for the thermometer.
0430: *
0431: * @param lower the lower bound.
0432: *
0433: * @see #getLowerBound()
0434: */
0435: public void setLowerBound(double lower) {
0436: this .lowerBound = lower;
0437: setAxisRange();
0438: }
0439:
0440: /**
0441: * Returns the upper bound for the thermometer. The data value can be set
0442: * higher than this, but it will not be shown in the thermometer.
0443: *
0444: * @return The upper bound.
0445: *
0446: * @see #setUpperBound(double)
0447: */
0448: public double getUpperBound() {
0449: return this .upperBound;
0450: }
0451:
0452: /**
0453: * Sets the upper bound for the thermometer.
0454: *
0455: * @param upper the upper bound.
0456: *
0457: * @see #getUpperBound()
0458: */
0459: public void setUpperBound(double upper) {
0460: this .upperBound = upper;
0461: setAxisRange();
0462: }
0463:
0464: /**
0465: * Sets the lower and upper bounds for the thermometer.
0466: *
0467: * @param lower the lower bound.
0468: * @param upper the upper bound.
0469: */
0470: public void setRange(double lower, double upper) {
0471: this .lowerBound = lower;
0472: this .upperBound = upper;
0473: setAxisRange();
0474: }
0475:
0476: /**
0477: * Returns the padding for the thermometer. This is the space inside the
0478: * plot area.
0479: *
0480: * @return The padding (never <code>null</code>).
0481: *
0482: * @see #setPadding(RectangleInsets)
0483: */
0484: public RectangleInsets getPadding() {
0485: return this .padding;
0486: }
0487:
0488: /**
0489: * Sets the padding for the thermometer and sends a {@link PlotChangeEvent}
0490: * to all registered listeners.
0491: *
0492: * @param padding the padding (<code>null</code> not permitted).
0493: *
0494: * @see #getPadding()
0495: */
0496: public void setPadding(RectangleInsets padding) {
0497: if (padding == null) {
0498: throw new IllegalArgumentException(
0499: "Null 'padding' argument.");
0500: }
0501: this .padding = padding;
0502: notifyListeners(new PlotChangeEvent(this ));
0503: }
0504:
0505: /**
0506: * Returns the stroke used to draw the thermometer outline.
0507: *
0508: * @return The stroke (never <code>null</code>).
0509: *
0510: * @see #setThermometerStroke(Stroke)
0511: * @see #getThermometerPaint()
0512: */
0513: public Stroke getThermometerStroke() {
0514: return this .thermometerStroke;
0515: }
0516:
0517: /**
0518: * Sets the stroke used to draw the thermometer outline and sends a
0519: * {@link PlotChangeEvent} to all registered listeners.
0520: *
0521: * @param s the new stroke (<code>null</code> ignored).
0522: *
0523: * @see #getThermometerStroke()
0524: */
0525: public void setThermometerStroke(Stroke s) {
0526: if (s != null) {
0527: this .thermometerStroke = s;
0528: notifyListeners(new PlotChangeEvent(this ));
0529: }
0530: }
0531:
0532: /**
0533: * Returns the paint used to draw the thermometer outline.
0534: *
0535: * @return The paint (never <code>null</code>).
0536: *
0537: * @see #setThermometerPaint(Paint)
0538: * @see #getThermometerStroke()
0539: */
0540: public Paint getThermometerPaint() {
0541: return this .thermometerPaint;
0542: }
0543:
0544: /**
0545: * Sets the paint used to draw the thermometer outline and sends a
0546: * {@link PlotChangeEvent} to all registered listeners.
0547: *
0548: * @param paint the new paint (<code>null</code> ignored).
0549: *
0550: * @see #getThermometerPaint()
0551: */
0552: public void setThermometerPaint(Paint paint) {
0553: if (paint != null) {
0554: this .thermometerPaint = paint;
0555: notifyListeners(new PlotChangeEvent(this ));
0556: }
0557: }
0558:
0559: /**
0560: * Returns a code indicating the unit display type. This is one of
0561: * {@link #UNITS_NONE}, {@link #UNITS_FAHRENHEIT}, {@link #UNITS_CELCIUS}
0562: * and {@link #UNITS_KELVIN}.
0563: *
0564: * @return The units type.
0565: *
0566: * @see #setUnits(int)
0567: */
0568: public int getUnits() {
0569: return this .units;
0570: }
0571:
0572: /**
0573: * Sets the units to be displayed in the thermometer. Use one of the
0574: * following constants:
0575: *
0576: * <ul>
0577: * <li>UNITS_NONE : no units displayed.</li>
0578: * <li>UNITS_FAHRENHEIT : units displayed in Fahrenheit.</li>
0579: * <li>UNITS_CELCIUS : units displayed in Celcius.</li>
0580: * <li>UNITS_KELVIN : units displayed in Kelvin.</li>
0581: * </ul>
0582: *
0583: * @param u the new unit type.
0584: *
0585: * @see #getUnits()
0586: */
0587: public void setUnits(int u) {
0588: if ((u >= 0) && (u < UNITS.length)) {
0589: if (this .units != u) {
0590: this .units = u;
0591: notifyListeners(new PlotChangeEvent(this ));
0592: }
0593: }
0594: }
0595:
0596: /**
0597: * Sets the unit type.
0598: *
0599: * @param u the unit type (<code>null</code> ignored).
0600: *
0601: * @deprecated Use setUnits(int) instead. Deprecated as of version 1.0.6,
0602: * because this method is a little obscure and redundant anyway.
0603: */
0604: public void setUnits(String u) {
0605: if (u == null) {
0606: return;
0607: }
0608:
0609: u = u.toUpperCase().trim();
0610: for (int i = 0; i < UNITS.length; ++i) {
0611: if (u.equals(UNITS[i].toUpperCase().trim())) {
0612: setUnits(i);
0613: i = UNITS.length;
0614: }
0615: }
0616: }
0617:
0618: /**
0619: * Returns a code indicating the location at which the value label is
0620: * displayed.
0621: *
0622: * @return The location (one of {@link #NONE}, {@link #RIGHT},
0623: * {@link #LEFT} and {@link #BULB}.).
0624: */
0625: public int getValueLocation() {
0626: return this .valueLocation;
0627: }
0628:
0629: /**
0630: * Sets the location at which the current value is displayed and sends a
0631: * {@link PlotChangeEvent} to all registered listeners.
0632: * <P>
0633: * The location can be one of the constants:
0634: * <code>NONE</code>,
0635: * <code>RIGHT</code>
0636: * <code>LEFT</code> and
0637: * <code>BULB</code>.
0638: *
0639: * @param location the location.
0640: */
0641: public void setValueLocation(int location) {
0642: if ((location >= 0) && (location < 4)) {
0643: this .valueLocation = location;
0644: notifyListeners(new PlotChangeEvent(this ));
0645: } else {
0646: throw new IllegalArgumentException(
0647: "Location not recognised.");
0648: }
0649: }
0650:
0651: /**
0652: * Returns the axis location.
0653: *
0654: * @return The location (one of {@link #NONE}, {@link #LEFT} and
0655: * {@link #RIGHT}).
0656: *
0657: * @see #setAxisLocation(int)
0658: */
0659: public int getAxisLocation() {
0660: return this .axisLocation;
0661: }
0662:
0663: /**
0664: * Sets the location at which the axis is displayed relative to the
0665: * thermometer, and sends a {@link PlotChangeEvent} to all registered
0666: * listeners.
0667: *
0668: * @param location the location (one of {@link #NONE}, {@link #LEFT} and
0669: * {@link #RIGHT}).
0670: *
0671: * @see #getAxisLocation()
0672: */
0673: public void setAxisLocation(int location) {
0674: if ((location >= 0) && (location < 3)) {
0675: this .axisLocation = location;
0676: notifyListeners(new PlotChangeEvent(this ));
0677: } else {
0678: throw new IllegalArgumentException(
0679: "Location not recognised.");
0680: }
0681: }
0682:
0683: /**
0684: * Gets the font used to display the current value.
0685: *
0686: * @return The font.
0687: *
0688: * @see #setValueFont(Font)
0689: */
0690: public Font getValueFont() {
0691: return this .valueFont;
0692: }
0693:
0694: /**
0695: * Sets the font used to display the current value.
0696: *
0697: * @param f the new font (<code>null</code> not permitted).
0698: *
0699: * @see #getValueFont()
0700: */
0701: public void setValueFont(Font f) {
0702: if (f == null) {
0703: throw new IllegalArgumentException("Null 'font' argument.");
0704: }
0705: if (!this .valueFont.equals(f)) {
0706: this .valueFont = f;
0707: notifyListeners(new PlotChangeEvent(this ));
0708: }
0709: }
0710:
0711: /**
0712: * Gets the paint used to display the current value.
0713: *
0714: * @return The paint.
0715: *
0716: * @see #setValuePaint(Paint)
0717: */
0718: public Paint getValuePaint() {
0719: return this .valuePaint;
0720: }
0721:
0722: /**
0723: * Sets the paint used to display the current value and sends a
0724: * {@link PlotChangeEvent} to all registered listeners.
0725: *
0726: * @param paint the new paint (<code>null</code> not permitted).
0727: *
0728: * @see #getValuePaint()
0729: */
0730: public void setValuePaint(Paint paint) {
0731: if (paint == null) {
0732: throw new IllegalArgumentException("Null 'paint' argument.");
0733: }
0734: if (!this .valuePaint.equals(paint)) {
0735: this .valuePaint = paint;
0736: notifyListeners(new PlotChangeEvent(this ));
0737: }
0738: }
0739:
0740: // FIXME: No getValueFormat() method?
0741:
0742: /**
0743: * Sets the formatter for the value label and sends a
0744: * {@link PlotChangeEvent} to all registered listeners.
0745: *
0746: * @param formatter the new formatter (<code>null</code> not permitted).
0747: */
0748: public void setValueFormat(NumberFormat formatter) {
0749: if (formatter == null) {
0750: throw new IllegalArgumentException(
0751: "Null 'formatter' argument.");
0752: }
0753: this .valueFormat = formatter;
0754: notifyListeners(new PlotChangeEvent(this ));
0755: }
0756:
0757: /**
0758: * Returns the default mercury paint.
0759: *
0760: * @return The paint (never <code>null</code>).
0761: *
0762: * @see #setMercuryPaint(Paint)
0763: */
0764: public Paint getMercuryPaint() {
0765: return this .mercuryPaint;
0766: }
0767:
0768: /**
0769: * Sets the default mercury paint and sends a {@link PlotChangeEvent} to
0770: * all registered listeners.
0771: *
0772: * @param paint the new paint (<code>null</code> not permitted).
0773: *
0774: * @see #getMercuryPaint()
0775: */
0776: public void setMercuryPaint(Paint paint) {
0777: if (paint == null) {
0778: throw new IllegalArgumentException("Null 'paint' argument.");
0779: }
0780: this .mercuryPaint = paint;
0781: notifyListeners(new PlotChangeEvent(this ));
0782: }
0783:
0784: /**
0785: * Returns the flag that controls whether not value lines are displayed.
0786: *
0787: * @return The flag.
0788: *
0789: * @see #setShowValueLines(boolean)
0790: *
0791: * @deprecated This flag doesn't do anything useful/visible. Deprecated
0792: * as of version 1.0.6.
0793: */
0794: public boolean getShowValueLines() {
0795: return this .showValueLines;
0796: }
0797:
0798: /**
0799: * Sets the display as to whether to show value lines in the output.
0800: *
0801: * @param b Whether to show value lines in the thermometer
0802: *
0803: * @see #getShowValueLines()
0804: *
0805: * @deprecated This flag doesn't do anything useful/visible. Deprecated
0806: * as of version 1.0.6.
0807: */
0808: public void setShowValueLines(boolean b) {
0809: this .showValueLines = b;
0810: notifyListeners(new PlotChangeEvent(this ));
0811: }
0812:
0813: /**
0814: * Sets information for a particular range.
0815: *
0816: * @param range the range to specify information about.
0817: * @param low the low value for the range
0818: * @param hi the high value for the range
0819: */
0820: public void setSubrangeInfo(int range, double low, double hi) {
0821: setSubrangeInfo(range, low, hi, low, hi);
0822: }
0823:
0824: /**
0825: * Sets the subrangeInfo attribute of the ThermometerPlot object
0826: *
0827: * @param range the new rangeInfo value.
0828: * @param rangeLow the new rangeInfo value
0829: * @param rangeHigh the new rangeInfo value
0830: * @param displayLow the new rangeInfo value
0831: * @param displayHigh the new rangeInfo value
0832: */
0833: public void setSubrangeInfo(int range, double rangeLow,
0834: double rangeHigh, double displayLow, double displayHigh) {
0835:
0836: if ((range >= 0) && (range < 3)) {
0837: setSubrange(range, rangeLow, rangeHigh);
0838: setDisplayRange(range, displayLow, displayHigh);
0839: setAxisRange();
0840: notifyListeners(new PlotChangeEvent(this ));
0841: }
0842:
0843: }
0844:
0845: /**
0846: * Sets the bounds for a subrange.
0847: *
0848: * @param range the range type.
0849: * @param low the low value.
0850: * @param high the high value.
0851: */
0852: public void setSubrange(int range, double low, double high) {
0853: if ((range >= 0) && (range < 3)) {
0854: this .subrangeInfo[range][RANGE_HIGH] = high;
0855: this .subrangeInfo[range][RANGE_LOW] = low;
0856: }
0857: }
0858:
0859: /**
0860: * Sets the displayed bounds for a sub range.
0861: *
0862: * @param range the range type.
0863: * @param low the low value.
0864: * @param high the high value.
0865: */
0866: public void setDisplayRange(int range, double low, double high) {
0867:
0868: if ((range >= 0) && (range < this .subrangeInfo.length)
0869: && isValidNumber(high) && isValidNumber(low)) {
0870:
0871: if (high > low) {
0872: this .subrangeInfo[range][DISPLAY_HIGH] = high;
0873: this .subrangeInfo[range][DISPLAY_LOW] = low;
0874: } else {
0875: this .subrangeInfo[range][DISPLAY_HIGH] = low;
0876: this .subrangeInfo[range][DISPLAY_LOW] = high;
0877: }
0878:
0879: }
0880:
0881: }
0882:
0883: /**
0884: * Gets the paint used for a particular subrange.
0885: *
0886: * @param range the range (.
0887: *
0888: * @return The paint.
0889: *
0890: * @see #setSubrangePaint(int, Paint)
0891: */
0892: public Paint getSubrangePaint(int range) {
0893: if ((range >= 0) && (range < this .subrangePaint.length)) {
0894: return this .subrangePaint[range];
0895: } else {
0896: return this .mercuryPaint;
0897: }
0898: }
0899:
0900: /**
0901: * Sets the paint to be used for a subrange and sends a
0902: * {@link PlotChangeEvent} to all registered listeners.
0903: *
0904: * @param range the range (0, 1 or 2).
0905: * @param paint the paint to be applied (<code>null</code> not permitted).
0906: *
0907: * @see #getSubrangePaint(int)
0908: */
0909: public void setSubrangePaint(int range, Paint paint) {
0910: if ((range >= 0) && (range < this .subrangePaint.length)
0911: && (paint != null)) {
0912: this .subrangePaint[range] = paint;
0913: notifyListeners(new PlotChangeEvent(this ));
0914: }
0915: }
0916:
0917: /**
0918: * Returns a flag that controls whether or not the thermometer axis zooms
0919: * to display the subrange within which the data value falls.
0920: *
0921: * @return The flag.
0922: */
0923: public boolean getFollowDataInSubranges() {
0924: return this .followDataInSubranges;
0925: }
0926:
0927: /**
0928: * Sets the flag that controls whether or not the thermometer axis zooms
0929: * to display the subrange within which the data value falls.
0930: *
0931: * @param flag the flag.
0932: */
0933: public void setFollowDataInSubranges(boolean flag) {
0934: this .followDataInSubranges = flag;
0935: notifyListeners(new PlotChangeEvent(this ));
0936: }
0937:
0938: /**
0939: * Returns a flag that controls whether or not the mercury color changes
0940: * for each subrange.
0941: *
0942: * @return The flag.
0943: *
0944: * @see #setUseSubrangePaint(boolean)
0945: */
0946: public boolean getUseSubrangePaint() {
0947: return this .useSubrangePaint;
0948: }
0949:
0950: /**
0951: * Sets the range colour change option.
0952: *
0953: * @param flag the new range colour change option
0954: *
0955: * @see #getUseSubrangePaint()
0956: */
0957: public void setUseSubrangePaint(boolean flag) {
0958: this .useSubrangePaint = flag;
0959: notifyListeners(new PlotChangeEvent(this ));
0960: }
0961:
0962: /**
0963: * Draws the plot on a Java 2D graphics device (such as the screen or a
0964: * printer).
0965: *
0966: * @param g2 the graphics device.
0967: * @param area the area within which the plot should be drawn.
0968: * @param anchor the anchor point (<code>null</code> permitted).
0969: * @param parentState the state from the parent plot, if there is one.
0970: * @param info collects info about the drawing.
0971: */
0972: public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
0973: PlotState parentState, PlotRenderingInfo info) {
0974:
0975: RoundRectangle2D outerStem = new RoundRectangle2D.Double();
0976: RoundRectangle2D innerStem = new RoundRectangle2D.Double();
0977: RoundRectangle2D mercuryStem = new RoundRectangle2D.Double();
0978: Ellipse2D outerBulb = new Ellipse2D.Double();
0979: Ellipse2D innerBulb = new Ellipse2D.Double();
0980: String temp = null;
0981: FontMetrics metrics = null;
0982: if (info != null) {
0983: info.setPlotArea(area);
0984: }
0985:
0986: // adjust for insets...
0987: RectangleInsets insets = getInsets();
0988: insets.trim(area);
0989: drawBackground(g2, area);
0990:
0991: // adjust for padding...
0992: Rectangle2D interior = (Rectangle2D) area.clone();
0993: this .padding.trim(interior);
0994: int midX = (int) (interior.getX() + (interior.getWidth() / 2));
0995: int midY = (int) (interior.getY() + (interior.getHeight() / 2));
0996: int stemTop = (int) (interior.getMinY() + BULB_RADIUS);
0997: int stemBottom = (int) (interior.getMaxY() - BULB_DIAMETER);
0998: Rectangle2D dataArea = new Rectangle2D.Double(midX
0999: - COLUMN_RADIUS, stemTop, COLUMN_RADIUS, stemBottom
1000: - stemTop);
1001:
1002: outerBulb.setFrame(midX - BULB_RADIUS, stemBottom,
1003: BULB_DIAMETER, BULB_DIAMETER);
1004:
1005: outerStem.setRoundRect(midX - COLUMN_RADIUS,
1006: interior.getMinY(), COLUMN_DIAMETER, stemBottom
1007: + BULB_DIAMETER - stemTop, COLUMN_DIAMETER,
1008: COLUMN_DIAMETER);
1009:
1010: Area outerThermometer = new Area(outerBulb);
1011: Area tempArea = new Area(outerStem);
1012: outerThermometer.add(tempArea);
1013:
1014: innerBulb.setFrame(midX - BULB_RADIUS + GAP_RADIUS, stemBottom
1015: + GAP_RADIUS, BULB_DIAMETER - GAP_DIAMETER,
1016: BULB_DIAMETER - GAP_DIAMETER);
1017:
1018: innerStem.setRoundRect(midX - COLUMN_RADIUS + GAP_RADIUS,
1019: interior.getMinY() + GAP_RADIUS, COLUMN_DIAMETER
1020: - GAP_DIAMETER, stemBottom + BULB_DIAMETER
1021: - GAP_DIAMETER - stemTop, COLUMN_DIAMETER
1022: - GAP_DIAMETER, COLUMN_DIAMETER - GAP_DIAMETER);
1023:
1024: Area innerThermometer = new Area(innerBulb);
1025: tempArea = new Area(innerStem);
1026: innerThermometer.add(tempArea);
1027:
1028: if ((this .dataset != null) && (this .dataset.getValue() != null)) {
1029: double current = this .dataset.getValue().doubleValue();
1030: double ds = this .rangeAxis.valueToJava2D(current, dataArea,
1031: RectangleEdge.LEFT);
1032:
1033: int i = COLUMN_DIAMETER - GAP_DIAMETER; // already calculated
1034: int j = COLUMN_RADIUS - GAP_RADIUS; // already calculated
1035: int l = (i / 2);
1036: int k = (int) Math.round(ds);
1037: if (k < (GAP_RADIUS + interior.getMinY())) {
1038: k = (int) (GAP_RADIUS + interior.getMinY());
1039: l = BULB_RADIUS;
1040: }
1041:
1042: Area mercury = new Area(innerBulb);
1043:
1044: if (k < (stemBottom + BULB_RADIUS)) {
1045: mercuryStem.setRoundRect(midX - j, k, i,
1046: (stemBottom + BULB_RADIUS) - k, l, l);
1047: tempArea = new Area(mercuryStem);
1048: mercury.add(tempArea);
1049: }
1050:
1051: g2.setPaint(getCurrentPaint());
1052: g2.fill(mercury);
1053:
1054: // draw range indicators...
1055: if (this .subrangeIndicatorsVisible) {
1056: g2.setStroke(this .subrangeIndicatorStroke);
1057: Range range = this .rangeAxis.getRange();
1058:
1059: // draw start of normal range
1060: double value = this .subrangeInfo[NORMAL][RANGE_LOW];
1061: if (range.contains(value)) {
1062: double x = midX + COLUMN_RADIUS + 2;
1063: double y = this .rangeAxis.valueToJava2D(value,
1064: dataArea, RectangleEdge.LEFT);
1065: Line2D line = new Line2D.Double(x, y, x + 10, y);
1066: g2.setPaint(this .subrangePaint[NORMAL]);
1067: g2.draw(line);
1068: }
1069:
1070: // draw start of warning range
1071: value = this .subrangeInfo[WARNING][RANGE_LOW];
1072: if (range.contains(value)) {
1073: double x = midX + COLUMN_RADIUS + 2;
1074: double y = this .rangeAxis.valueToJava2D(value,
1075: dataArea, RectangleEdge.LEFT);
1076: Line2D line = new Line2D.Double(x, y, x + 10, y);
1077: g2.setPaint(this .subrangePaint[WARNING]);
1078: g2.draw(line);
1079: }
1080:
1081: // draw start of critical range
1082: value = this .subrangeInfo[CRITICAL][RANGE_LOW];
1083: if (range.contains(value)) {
1084: double x = midX + COLUMN_RADIUS + 2;
1085: double y = this .rangeAxis.valueToJava2D(value,
1086: dataArea, RectangleEdge.LEFT);
1087: Line2D line = new Line2D.Double(x, y, x + 10, y);
1088: g2.setPaint(this .subrangePaint[CRITICAL]);
1089: g2.draw(line);
1090: }
1091: }
1092:
1093: // draw the axis...
1094: if ((this .rangeAxis != null) && (this .axisLocation != NONE)) {
1095: int drawWidth = AXIS_GAP;
1096: if (this .showValueLines) {
1097: drawWidth += COLUMN_DIAMETER;
1098: }
1099: Rectangle2D drawArea;
1100: double cursor = 0;
1101:
1102: switch (this .axisLocation) {
1103: case RIGHT:
1104: cursor = midX + COLUMN_RADIUS;
1105: drawArea = new Rectangle2D.Double(cursor, stemTop,
1106: drawWidth, (stemBottom - stemTop + 1));
1107: this .rangeAxis.draw(g2, cursor, area, drawArea,
1108: RectangleEdge.RIGHT, null);
1109: break;
1110:
1111: case LEFT:
1112: default:
1113: //cursor = midX - COLUMN_RADIUS - AXIS_GAP;
1114: cursor = midX - COLUMN_RADIUS;
1115: drawArea = new Rectangle2D.Double(cursor, stemTop,
1116: drawWidth, (stemBottom - stemTop + 1));
1117: this .rangeAxis.draw(g2, cursor, area, drawArea,
1118: RectangleEdge.LEFT, null);
1119: break;
1120: }
1121:
1122: }
1123:
1124: // draw text value on screen
1125: g2.setFont(this .valueFont);
1126: g2.setPaint(this .valuePaint);
1127: metrics = g2.getFontMetrics();
1128: switch (this .valueLocation) {
1129: case RIGHT:
1130: g2.drawString(this .valueFormat.format(current), midX
1131: + COLUMN_RADIUS + GAP_RADIUS, midY);
1132: break;
1133: case LEFT:
1134: String valueString = this .valueFormat.format(current);
1135: int stringWidth = metrics.stringWidth(valueString);
1136: g2.drawString(valueString, midX - COLUMN_RADIUS
1137: - GAP_RADIUS - stringWidth, midY);
1138: break;
1139: case BULB:
1140: temp = this .valueFormat.format(current);
1141: i = metrics.stringWidth(temp) / 2;
1142: g2.drawString(temp, midX - i, stemBottom + BULB_RADIUS
1143: + GAP_RADIUS);
1144: break;
1145: default:
1146: }
1147: /***/
1148: }
1149:
1150: g2.setPaint(this .thermometerPaint);
1151: g2.setFont(this .valueFont);
1152:
1153: // draw units indicator
1154: metrics = g2.getFontMetrics();
1155: int tickX1 = midX - COLUMN_RADIUS - GAP_DIAMETER
1156: - metrics.stringWidth(UNITS[this .units]);
1157: if (tickX1 > area.getMinX()) {
1158: g2.drawString(UNITS[this .units], tickX1, (int) (area
1159: .getMinY() + 20));
1160: }
1161:
1162: // draw thermometer outline
1163: g2.setStroke(this .thermometerStroke);
1164: g2.draw(outerThermometer);
1165: g2.draw(innerThermometer);
1166:
1167: drawOutline(g2, area);
1168: }
1169:
1170: /**
1171: * A zoom method that does nothing. Plots are required to support the
1172: * zoom operation. In the case of a thermometer chart, it doesn't make
1173: * sense to zoom in or out, so the method is empty.
1174: *
1175: * @param percent the zoom percentage.
1176: */
1177: public void zoom(double percent) {
1178: // intentionally blank
1179: }
1180:
1181: /**
1182: * Returns a short string describing the type of plot.
1183: *
1184: * @return A short string describing the type of plot.
1185: */
1186: public String getPlotType() {
1187: return localizationResources.getString("Thermometer_Plot");
1188: }
1189:
1190: /**
1191: * Checks to see if a new value means the axis range needs adjusting.
1192: *
1193: * @param event the dataset change event.
1194: */
1195: public void datasetChanged(DatasetChangeEvent event) {
1196: if (this .dataset != null) {
1197: Number vn = this .dataset.getValue();
1198: if (vn != null) {
1199: double value = vn.doubleValue();
1200: if (inSubrange(NORMAL, value)) {
1201: this .subrange = NORMAL;
1202: } else if (inSubrange(WARNING, value)) {
1203: this .subrange = WARNING;
1204: } else if (inSubrange(CRITICAL, value)) {
1205: this .subrange = CRITICAL;
1206: } else {
1207: this .subrange = -1;
1208: }
1209: setAxisRange();
1210: }
1211: }
1212: super .datasetChanged(event);
1213: }
1214:
1215: /**
1216: * Returns the minimum value in either the domain or the range, whichever
1217: * is displayed against the vertical axis for the particular type of plot
1218: * implementing this interface.
1219: *
1220: * @return The minimum value in either the domain or the range.
1221: *
1222: * @deprecated This method is not used. Officially deprecated in version
1223: * 1.0.6.
1224: */
1225: public Number getMinimumVerticalDataValue() {
1226: return new Double(this .lowerBound);
1227: }
1228:
1229: /**
1230: * Returns the maximum value in either the domain or the range, whichever
1231: * is displayed against the vertical axis for the particular type of plot
1232: * implementing this interface.
1233: *
1234: * @return The maximum value in either the domain or the range
1235: *
1236: * @deprecated This method is not used. Officially deprecated in version
1237: * 1.0.6.
1238: */
1239: public Number getMaximumVerticalDataValue() {
1240: return new Double(this .upperBound);
1241: }
1242:
1243: /**
1244: * Returns the data range.
1245: *
1246: * @param axis the axis.
1247: *
1248: * @return The range of data displayed.
1249: */
1250: public Range getDataRange(ValueAxis axis) {
1251: return new Range(this .lowerBound, this .upperBound);
1252: }
1253:
1254: /**
1255: * Sets the axis range to the current values in the rangeInfo array.
1256: */
1257: protected void setAxisRange() {
1258: if ((this .subrange >= 0) && (this .followDataInSubranges)) {
1259: this .rangeAxis.setRange(new Range(
1260: this .subrangeInfo[this .subrange][DISPLAY_LOW],
1261: this .subrangeInfo[this .subrange][DISPLAY_HIGH]));
1262: } else {
1263: this .rangeAxis.setRange(this .lowerBound, this .upperBound);
1264: }
1265: }
1266:
1267: /**
1268: * Returns the legend items for the plot.
1269: *
1270: * @return <code>null</code>.
1271: */
1272: public LegendItemCollection getLegendItems() {
1273: return null;
1274: }
1275:
1276: /**
1277: * Returns the orientation of the plot.
1278: *
1279: * @return The orientation (always {@link PlotOrientation#VERTICAL}).
1280: */
1281: public PlotOrientation getOrientation() {
1282: return PlotOrientation.VERTICAL;
1283: }
1284:
1285: /**
1286: * Determine whether a number is valid and finite.
1287: *
1288: * @param d the number to be tested.
1289: *
1290: * @return <code>true</code> if the number is valid and finite, and
1291: * <code>false</code> otherwise.
1292: */
1293: protected static boolean isValidNumber(double d) {
1294: return (!(Double.isNaN(d) || Double.isInfinite(d)));
1295: }
1296:
1297: /**
1298: * Returns true if the value is in the specified range, and false otherwise.
1299: *
1300: * @param subrange the subrange.
1301: * @param value the value to check.
1302: *
1303: * @return A boolean.
1304: */
1305: private boolean inSubrange(int subrange, double value) {
1306: return (value > this .subrangeInfo[subrange][RANGE_LOW] && value <= this .subrangeInfo[subrange][RANGE_HIGH]);
1307: }
1308:
1309: /**
1310: * Returns the mercury paint corresponding to the current data value.
1311: * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D,
1312: * PlotState, PlotRenderingInfo)} method.
1313: *
1314: * @return The paint (never <code>null</code>).
1315: */
1316: private Paint getCurrentPaint() {
1317: Paint result = this .mercuryPaint;
1318: if (this .useSubrangePaint) {
1319: double value = this .dataset.getValue().doubleValue();
1320: if (inSubrange(NORMAL, value)) {
1321: result = this .subrangePaint[NORMAL];
1322: } else if (inSubrange(WARNING, value)) {
1323: result = this .subrangePaint[WARNING];
1324: } else if (inSubrange(CRITICAL, value)) {
1325: result = this .subrangePaint[CRITICAL];
1326: }
1327: }
1328: return result;
1329: }
1330:
1331: /**
1332: * Tests this plot for equality with another object. The plot's dataset
1333: * is not considered in the test.
1334: *
1335: * @param obj the object (<code>null</code> permitted).
1336: *
1337: * @return <code>true</code> or <code>false</code>.
1338: */
1339: public boolean equals(Object obj) {
1340: if (obj == this ) {
1341: return true;
1342: }
1343: if (!(obj instanceof ThermometerPlot)) {
1344: return false;
1345: }
1346: ThermometerPlot that = (ThermometerPlot) obj;
1347: if (!super .equals(obj)) {
1348: return false;
1349: }
1350: if (!ObjectUtilities.equal(this .rangeAxis, that.rangeAxis)) {
1351: return false;
1352: }
1353: if (this .axisLocation != that.axisLocation) {
1354: return false;
1355: }
1356: if (this .lowerBound != that.lowerBound) {
1357: return false;
1358: }
1359: if (this .upperBound != that.upperBound) {
1360: return false;
1361: }
1362: if (!ObjectUtilities.equal(this .padding, that.padding)) {
1363: return false;
1364: }
1365: if (!ObjectUtilities.equal(this .thermometerStroke,
1366: that.thermometerStroke)) {
1367: return false;
1368: }
1369: if (!PaintUtilities.equal(this .thermometerPaint,
1370: that.thermometerPaint)) {
1371: return false;
1372: }
1373: if (this .units != that.units) {
1374: return false;
1375: }
1376: if (this .valueLocation != that.valueLocation) {
1377: return false;
1378: }
1379: if (!ObjectUtilities.equal(this .valueFont, that.valueFont)) {
1380: return false;
1381: }
1382: if (!PaintUtilities.equal(this .valuePaint, that.valuePaint)) {
1383: return false;
1384: }
1385: if (!ObjectUtilities.equal(this .valueFormat, that.valueFormat)) {
1386: return false;
1387: }
1388: if (!PaintUtilities.equal(this .mercuryPaint, that.mercuryPaint)) {
1389: return false;
1390: }
1391: if (this .showValueLines != that.showValueLines) {
1392: return false;
1393: }
1394: if (this .subrange != that.subrange) {
1395: return false;
1396: }
1397: if (this .followDataInSubranges != that.followDataInSubranges) {
1398: return false;
1399: }
1400: if (!equal(this .subrangeInfo, that.subrangeInfo)) {
1401: return false;
1402: }
1403: if (this .useSubrangePaint != that.useSubrangePaint) {
1404: return false;
1405: }
1406: for (int i = 0; i < this .subrangePaint.length; i++) {
1407: if (!PaintUtilities.equal(this .subrangePaint[i],
1408: that.subrangePaint[i])) {
1409: return false;
1410: }
1411: }
1412: return true;
1413: }
1414:
1415: /**
1416: * Tests two double[][] arrays for equality.
1417: *
1418: * @param array1 the first array (<code>null</code> permitted).
1419: * @param array2 the second arrray (<code>null</code> permitted).
1420: *
1421: * @return A boolean.
1422: */
1423: private static boolean equal(double[][] array1, double[][] array2) {
1424: if (array1 == null) {
1425: return (array2 == null);
1426: }
1427: if (array2 == null) {
1428: return false;
1429: }
1430: if (array1.length != array2.length) {
1431: return false;
1432: }
1433: for (int i = 0; i < array1.length; i++) {
1434: if (!Arrays.equals(array1[i], array2[i])) {
1435: return false;
1436: }
1437: }
1438: return true;
1439: }
1440:
1441: /**
1442: * Returns a clone of the plot.
1443: *
1444: * @return A clone.
1445: *
1446: * @throws CloneNotSupportedException if the plot cannot be cloned.
1447: */
1448: public Object clone() throws CloneNotSupportedException {
1449:
1450: ThermometerPlot clone = (ThermometerPlot) super .clone();
1451:
1452: if (clone.dataset != null) {
1453: clone.dataset.addChangeListener(clone);
1454: }
1455: clone.rangeAxis = (ValueAxis) ObjectUtilities
1456: .clone(this .rangeAxis);
1457: if (clone.rangeAxis != null) {
1458: clone.rangeAxis.setPlot(clone);
1459: clone.rangeAxis.addChangeListener(clone);
1460: }
1461: clone.valueFormat = (NumberFormat) this .valueFormat.clone();
1462: clone.subrangePaint = (Paint[]) this .subrangePaint.clone();
1463:
1464: return clone;
1465:
1466: }
1467:
1468: /**
1469: * Provides serialization support.
1470: *
1471: * @param stream the output stream.
1472: *
1473: * @throws IOException if there is an I/O error.
1474: */
1475: private void writeObject(ObjectOutputStream stream)
1476: throws IOException {
1477: stream.defaultWriteObject();
1478: SerialUtilities.writeStroke(this .thermometerStroke, stream);
1479: SerialUtilities.writePaint(this .thermometerPaint, stream);
1480: SerialUtilities.writePaint(this .valuePaint, stream);
1481: SerialUtilities.writePaint(this .mercuryPaint, stream);
1482: SerialUtilities.writeStroke(this .subrangeIndicatorStroke,
1483: stream);
1484: SerialUtilities.writeStroke(this .rangeIndicatorStroke, stream);
1485: for (int i = 0; i < 3; i++) {
1486: SerialUtilities.writePaint(this .subrangePaint[i], stream);
1487: }
1488: }
1489:
1490: /**
1491: * Provides serialization support.
1492: *
1493: * @param stream the input stream.
1494: *
1495: * @throws IOException if there is an I/O error.
1496: * @throws ClassNotFoundException if there is a classpath problem.
1497: */
1498: private void readObject(ObjectInputStream stream)
1499: throws IOException, ClassNotFoundException {
1500: stream.defaultReadObject();
1501: this .thermometerStroke = SerialUtilities.readStroke(stream);
1502: this .thermometerPaint = SerialUtilities.readPaint(stream);
1503: this .valuePaint = SerialUtilities.readPaint(stream);
1504: this .mercuryPaint = SerialUtilities.readPaint(stream);
1505: this .subrangeIndicatorStroke = SerialUtilities
1506: .readStroke(stream);
1507: this .rangeIndicatorStroke = SerialUtilities.readStroke(stream);
1508: this .subrangePaint = new Paint[3];
1509: for (int i = 0; i < 3; i++) {
1510: this .subrangePaint[i] = SerialUtilities.readPaint(stream);
1511: }
1512: if (this .rangeAxis != null) {
1513: this .rangeAxis.addChangeListener(this );
1514: }
1515: }
1516:
1517: /**
1518: * Multiplies the range on the domain axis/axes by the specified factor.
1519: *
1520: * @param factor the zoom factor.
1521: * @param state the plot state.
1522: * @param source the source point.
1523: */
1524: public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1525: Point2D source) {
1526: // no domain axis to zoom
1527: }
1528:
1529: /**
1530: * Multiplies the range on the range axis/axes by the specified factor.
1531: *
1532: * @param factor the zoom factor.
1533: * @param state the plot state.
1534: * @param source the source point.
1535: */
1536: public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1537: Point2D source) {
1538: this .rangeAxis.resizeRange(factor);
1539: }
1540:
1541: /**
1542: * This method does nothing.
1543: *
1544: * @param lowerPercent the lower percent.
1545: * @param upperPercent the upper percent.
1546: * @param state the plot state.
1547: * @param source the source point.
1548: */
1549: public void zoomDomainAxes(double lowerPercent,
1550: double upperPercent, PlotRenderingInfo state, Point2D source) {
1551: // no domain axis to zoom
1552: }
1553:
1554: /**
1555: * Zooms the range axes.
1556: *
1557: * @param lowerPercent the lower percent.
1558: * @param upperPercent the upper percent.
1559: * @param state the plot state.
1560: * @param source the source point.
1561: */
1562: public void zoomRangeAxes(double lowerPercent, double upperPercent,
1563: PlotRenderingInfo state, Point2D source) {
1564: this .rangeAxis.zoomRange(lowerPercent, upperPercent);
1565: }
1566:
1567: /**
1568: * Returns <code>false</code>.
1569: *
1570: * @return A boolean.
1571: */
1572: public boolean isDomainZoomable() {
1573: return false;
1574: }
1575:
1576: /**
1577: * Returns <code>true</code>.
1578: *
1579: * @return A boolean.
1580: */
1581: public boolean isRangeZoomable() {
1582: return true;
1583: }
1584:
1585: }
|