All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jfree.chart.plot.ThermometerPlot Maven / Gradle / Ivy

Go to download

JFreeChart is a class library, written in Java, for generating charts. Utilising the Java2D APIs, it currently supports bar charts, pie charts, line charts, XY-plots and time series plots.

There is a newer version: 1.5.5
Show newest version
/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
 * Other names may be trademarks of their respective owners.]
 *
 * --------------------
 * ThermometerPlot.java
 * --------------------
 *
 * (C) Copyright 2000-2013, by Bryan Scott and Contributors.
 *
 * Original Author:  Bryan Scott (based on MeterPlot by Hari).
 * Contributor(s):   David Gilbert (for Object Refinery Limited).
 *                   Arnaud Lelievre;
 *                   Julien Henry (see patch 1769088) (DG);
 *
 * Changes
 * -------
 * 11-Apr-2002 : Version 1, contributed by Bryan Scott;
 * 15-Apr-2002 : Changed to implement VerticalValuePlot;
 * 29-Apr-2002 : Added getVerticalValueAxis() method (DG);
 * 25-Jun-2002 : Removed redundant imports (DG);
 * 17-Sep-2002 : Reviewed with Checkstyle utility (DG);
 * 18-Sep-2002 : Extensive changes made to API, to iron out bugs and
 *               inconsistencies (DG);
 * 13-Oct-2002 : Corrected error datasetChanged which would generate exceptions
 *               when value set to null (BRS).
 * 23-Jan-2003 : Removed one constructor (DG);
 * 26-Mar-2003 : Implemented Serializable (DG);
 * 02-Jun-2003 : Removed test for compatible range axis (DG);
 * 01-Jul-2003 : Added additional check in draw method to ensure value not
 *               null (BRS);
 * 08-Sep-2003 : Added internationalization via use of properties
 *               resourceBundle (RFE 690236) (AL);
 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
 * 29-Sep-2003 : Updated draw to set value of cursor to non-zero and allow
 *               painting of axis.  An incomplete fix and needs to be set for
 *               left or right drawing (BRS);
 * 19-Nov-2003 : Added support for value labels to be displayed left of the
 *               thermometer
 * 19-Nov-2003 : Improved axis drawing (now default axis does not draw axis line
 *               and is closer to the bulb).  Added support for the positioning
 *               of the axis to the left or right of the bulb. (BRS);
 * 03-Dec-2003 : Directly mapped deprecated setData()/getData() method to
 *               get/setDataset() (TM);
 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
 * 07-Apr-2004 : Changed string width calculation (DG);
 * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
 * 06-Jan-2004 : Added getOrientation() method (DG);
 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
 * 29-Mar-2005 : Fixed equals() method (DG);
 * 05-May-2005 : Updated draw() method parameters (DG);
 * 09-Jun-2005 : Fixed more bugs in equals() method (DG);
 * 10-Jun-2005 : Fixed minor bug in setDisplayRange() method (DG);
 * ------------- JFREECHART 1.0.x ---------------------------------------------
 * 14-Nov-2006 : Fixed margin when drawing (DG);
 * 03-May-2007 : Fixed datasetChanged() to handle null dataset, added null
 *               argument check and event notification to setRangeAxis(),
 *               added null argument check to setPadding(), setValueFont(),
 *               setValuePaint(), setValueFormat() and setMercuryPaint(),
 *               deprecated get/setShowValueLines(), deprecated
 *               getMinimum/MaximumVerticalDataValue(), and fixed serialization
 *               bug (DG);
 * 24-Sep-2007 : Implemented new methods in Zoomable interface (DG);
 * 08-Oct-2007 : Added attributes for thermometer dimensions - see patch 1769088
 *               by Julien Henry (DG);
 * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
 *               Jess Thrysoee (DG);
 * 02-Jul-2013 : Use ParamChecks (DG);
 *
 */

package org.jfree.chart.plot;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.ResourceBundle;

import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.PlotChangeEvent;
import org.jfree.chart.util.ParamChecks;
import org.jfree.chart.util.ResourceBundleWrapper;
import org.jfree.data.Range;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.DefaultValueDataset;
import org.jfree.data.general.ValueDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;
import org.jfree.util.UnitType;

/**
 * A plot that displays a single value (from a {@link ValueDataset}) in a
 * thermometer type display.
 * 

* This plot supports a number of options: *

    *
  1. three sub-ranges which could be viewed as 'Normal', 'Warning' * and 'Critical' ranges.
  2. *
  3. the thermometer can be run in two modes: *
      *
    • fixed range, or
    • *
    • range adjusts to current sub-range.
    • *
    *
  4. *
  5. settable units to be displayed.
  6. *
  7. settable display location for the value text.
  8. *
*/ public class ThermometerPlot extends Plot implements ValueAxisPlot, Zoomable, Cloneable, Serializable { /** For serialization. */ private static final long serialVersionUID = 4087093313147984390L; /** A constant for unit type 'None'. */ public static final int UNITS_NONE = 0; /** A constant for unit type 'Fahrenheit'. */ public static final int UNITS_FAHRENHEIT = 1; /** A constant for unit type 'Celcius'. */ public static final int UNITS_CELCIUS = 2; /** A constant for unit type 'Kelvin'. */ public static final int UNITS_KELVIN = 3; /** A constant for the value label position (no label). */ public static final int NONE = 0; /** A constant for the value label position (right of the thermometer). */ public static final int RIGHT = 1; /** A constant for the value label position (left of the thermometer). */ public static final int LEFT = 2; /** A constant for the value label position (in the thermometer bulb). */ public static final int BULB = 3; /** A constant for the 'normal' range. */ public static final int NORMAL = 0; /** A constant for the 'warning' range. */ public static final int WARNING = 1; /** A constant for the 'critical' range. */ public static final int CRITICAL = 2; /** * The bulb radius. * * @deprecated As of 1.0.7, use {@link #getBulbRadius()}. */ protected static final int BULB_RADIUS = 40; /** * The bulb diameter. * * @deprecated As of 1.0.7, use {@link #getBulbDiameter()}. */ protected static final int BULB_DIAMETER = BULB_RADIUS * 2; /** * The column radius. * * @deprecated As of 1.0.7, use {@link #getColumnRadius()}. */ protected static final int COLUMN_RADIUS = 20; /** * The column diameter. * * @deprecated As of 1.0.7, use {@link #getColumnDiameter()}. */ protected static final int COLUMN_DIAMETER = COLUMN_RADIUS * 2; /** * The gap radius. * * @deprecated As of 1.0.7, use {@link #getGap()}. */ protected static final int GAP_RADIUS = 5; /** * The gap diameter. * * @deprecated As of 1.0.7, use {@link #getGap()} times two. */ protected static final int GAP_DIAMETER = GAP_RADIUS * 2; /** The axis gap. */ protected static final int AXIS_GAP = 10; /** The unit strings. */ protected static final String[] UNITS = {"", "\u00B0F", "\u00B0C", "\u00B0K"}; /** Index for low value in subrangeInfo matrix. */ protected static final int RANGE_LOW = 0; /** Index for high value in subrangeInfo matrix. */ protected static final int RANGE_HIGH = 1; /** Index for display low value in subrangeInfo matrix. */ protected static final int DISPLAY_LOW = 2; /** Index for display high value in subrangeInfo matrix. */ protected static final int DISPLAY_HIGH = 3; /** The default lower bound. */ protected static final double DEFAULT_LOWER_BOUND = 0.0; /** The default upper bound. */ protected static final double DEFAULT_UPPER_BOUND = 100.0; /** * The default bulb radius. * * @since 1.0.7 */ protected static final int DEFAULT_BULB_RADIUS = 40; /** * The default column radius. * * @since 1.0.7 */ protected static final int DEFAULT_COLUMN_RADIUS = 20; /** * The default gap between the outlines representing the thermometer. * * @since 1.0.7 */ protected static final int DEFAULT_GAP = 5; /** The dataset for the plot. */ private ValueDataset dataset; /** The range axis. */ private ValueAxis rangeAxis; /** The lower bound for the thermometer. */ private double lowerBound = DEFAULT_LOWER_BOUND; /** The upper bound for the thermometer. */ private double upperBound = DEFAULT_UPPER_BOUND; /** * The value label position. * * @since 1.0.7 */ private int bulbRadius = DEFAULT_BULB_RADIUS; /** * The column radius. * * @since 1.0.7 */ private int columnRadius = DEFAULT_COLUMN_RADIUS; /** * The gap between the two outlines the represent the thermometer. * * @since 1.0.7 */ private int gap = DEFAULT_GAP; /** * Blank space inside the plot area around the outside of the thermometer. */ private RectangleInsets padding; /** Stroke for drawing the thermometer */ private transient Stroke thermometerStroke = new BasicStroke(1.0f); /** Paint for drawing the thermometer */ private transient Paint thermometerPaint = Color.black; /** The display units */ private int units = UNITS_CELCIUS; /** The value label position. */ private int valueLocation = BULB; /** The position of the axis **/ private int axisLocation = LEFT; /** The font to write the value in */ private Font valueFont = new Font("SansSerif", Font.BOLD, 16); /** Colour that the value is written in */ private transient Paint valuePaint = Color.white; /** Number format for the value */ private NumberFormat valueFormat = new DecimalFormat(); /** The default paint for the mercury in the thermometer. */ private transient Paint mercuryPaint = Color.lightGray; /** A flag that controls whether value lines are drawn. */ private boolean showValueLines = false; /** The display sub-range. */ private int subrange = -1; /** The start and end values for the subranges. */ private double[][] subrangeInfo = { {0.0, 50.0, 0.0, 50.0}, {50.0, 75.0, 50.0, 75.0}, {75.0, 100.0, 75.0, 100.0} }; /** * A flag that controls whether or not the axis range adjusts to the * sub-ranges. */ private boolean followDataInSubranges = false; /** * A flag that controls whether or not the mercury paint changes with * the subranges. */ private boolean useSubrangePaint = true; /** Paint for each range */ private transient Paint[] subrangePaint = {Color.green, Color.orange, Color.red}; /** A flag that controls whether the sub-range indicators are visible. */ private boolean subrangeIndicatorsVisible = true; /** The stroke for the sub-range indicators. */ private transient Stroke subrangeIndicatorStroke = new BasicStroke(2.0f); /** The range indicator stroke. */ private transient Stroke rangeIndicatorStroke = new BasicStroke(3.0f); /** The resourceBundle for the localization. */ protected static ResourceBundle localizationResources = ResourceBundleWrapper.getBundle( "org.jfree.chart.plot.LocalizationBundle"); /** * Creates a new thermometer plot. */ public ThermometerPlot() { this(new DefaultValueDataset()); } /** * Creates a new thermometer plot, using default attributes where necessary. * * @param dataset the data set. */ public ThermometerPlot(ValueDataset dataset) { super(); this.padding = new RectangleInsets(UnitType.RELATIVE, 0.05, 0.05, 0.05, 0.05); this.dataset = dataset; if (dataset != null) { dataset.addChangeListener(this); } NumberAxis axis = new NumberAxis(null); axis.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); axis.setAxisLineVisible(false); axis.setPlot(this); axis.addChangeListener(this); this.rangeAxis = axis; setAxisRange(); } /** * Returns the dataset for the plot. * * @return The dataset (possibly null). * * @see #setDataset(ValueDataset) */ public ValueDataset getDataset() { return this.dataset; } /** * Sets the dataset for the plot, replacing the existing dataset if there * is one, and sends a {@link PlotChangeEvent} to all registered listeners. * * @param dataset the dataset (null permitted). * * @see #getDataset() */ public void setDataset(ValueDataset dataset) { // if there is an existing dataset, remove the plot from the list // of change listeners... ValueDataset existing = this.dataset; if (existing != null) { existing.removeChangeListener(this); } // set the new dataset, and register the chart as a change listener... this.dataset = dataset; if (dataset != null) { setDatasetGroup(dataset.getGroup()); dataset.addChangeListener(this); } // send a dataset change event to self... DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); datasetChanged(event); } /** * Returns the range axis. * * @return The range axis (never null). * * @see #setRangeAxis(ValueAxis) */ public ValueAxis getRangeAxis() { return this.rangeAxis; } /** * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param axis the new axis (null not permitted). * * @see #getRangeAxis() */ public void setRangeAxis(ValueAxis axis) { ParamChecks.nullNotPermitted(axis, "axis"); // plot is registered as a listener with the existing axis... this.rangeAxis.removeChangeListener(this); axis.setPlot(this); axis.addChangeListener(this); this.rangeAxis = axis; fireChangeEvent(); } /** * Returns the lower bound for the thermometer. The data value can be set * lower than this, but it will not be shown in the thermometer. * * @return The lower bound. * * @see #setLowerBound(double) */ public double getLowerBound() { return this.lowerBound; } /** * Sets the lower bound for the thermometer. * * @param lower the lower bound. * * @see #getLowerBound() */ public void setLowerBound(double lower) { this.lowerBound = lower; setAxisRange(); } /** * Returns the upper bound for the thermometer. The data value can be set * higher than this, but it will not be shown in the thermometer. * * @return The upper bound. * * @see #setUpperBound(double) */ public double getUpperBound() { return this.upperBound; } /** * Sets the upper bound for the thermometer. * * @param upper the upper bound. * * @see #getUpperBound() */ public void setUpperBound(double upper) { this.upperBound = upper; setAxisRange(); } /** * Sets the lower and upper bounds for the thermometer. * * @param lower the lower bound. * @param upper the upper bound. */ public void setRange(double lower, double upper) { this.lowerBound = lower; this.upperBound = upper; setAxisRange(); } /** * Returns the padding for the thermometer. This is the space inside the * plot area. * * @return The padding (never null). * * @see #setPadding(RectangleInsets) */ public RectangleInsets getPadding() { return this.padding; } /** * Sets the padding for the thermometer and sends a {@link PlotChangeEvent} * to all registered listeners. * * @param padding the padding (null not permitted). * * @see #getPadding() */ public void setPadding(RectangleInsets padding) { ParamChecks.nullNotPermitted(padding, "padding"); this.padding = padding; fireChangeEvent(); } /** * Returns the stroke used to draw the thermometer outline. * * @return The stroke (never null). * * @see #setThermometerStroke(Stroke) * @see #getThermometerPaint() */ public Stroke getThermometerStroke() { return this.thermometerStroke; } /** * Sets the stroke used to draw the thermometer outline and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param s the new stroke (null ignored). * * @see #getThermometerStroke() */ public void setThermometerStroke(Stroke s) { if (s != null) { this.thermometerStroke = s; fireChangeEvent(); } } /** * Returns the paint used to draw the thermometer outline. * * @return The paint (never null). * * @see #setThermometerPaint(Paint) * @see #getThermometerStroke() */ public Paint getThermometerPaint() { return this.thermometerPaint; } /** * Sets the paint used to draw the thermometer outline and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param paint the new paint (null ignored). * * @see #getThermometerPaint() */ public void setThermometerPaint(Paint paint) { if (paint != null) { this.thermometerPaint = paint; fireChangeEvent(); } } /** * Returns a code indicating the unit display type. This is one of * {@link #UNITS_NONE}, {@link #UNITS_FAHRENHEIT}, {@link #UNITS_CELCIUS} * and {@link #UNITS_KELVIN}. * * @return The units type. * * @see #setUnits(int) */ public int getUnits() { return this.units; } /** * Sets the units to be displayed in the thermometer. Use one of the * following constants: * *
    *
  • UNITS_NONE : no units displayed.
  • *
  • UNITS_FAHRENHEIT : units displayed in Fahrenheit.
  • *
  • UNITS_CELCIUS : units displayed in Celcius.
  • *
  • UNITS_KELVIN : units displayed in Kelvin.
  • *
* * @param u the new unit type. * * @see #getUnits() */ public void setUnits(int u) { if ((u >= 0) && (u < UNITS.length)) { if (this.units != u) { this.units = u; fireChangeEvent(); } } } /** * Sets the unit type. * * @param u the unit type (null ignored). * * @deprecated Use setUnits(int) instead. Deprecated as of version 1.0.6, * because this method is a little obscure and redundant anyway. */ public void setUnits(String u) { if (u == null) { return; } u = u.toUpperCase().trim(); for (int i = 0; i < UNITS.length; ++i) { if (u.equals(UNITS[i].toUpperCase().trim())) { setUnits(i); i = UNITS.length; } } } /** * Returns a code indicating the location at which the value label is * displayed. * * @return The location (one of {@link #NONE}, {@link #RIGHT}, * {@link #LEFT} and {@link #BULB}.). */ public int getValueLocation() { return this.valueLocation; } /** * Sets the location at which the current value is displayed and sends a * {@link PlotChangeEvent} to all registered listeners. *

* The location can be one of the constants: * NONE, * RIGHT * LEFT and * BULB. * * @param location the location. */ public void setValueLocation(int location) { if ((location >= 0) && (location < 4)) { this.valueLocation = location; fireChangeEvent(); } else { throw new IllegalArgumentException("Location not recognised."); } } /** * Returns the axis location. * * @return The location (one of {@link #NONE}, {@link #LEFT} and * {@link #RIGHT}). * * @see #setAxisLocation(int) */ public int getAxisLocation() { return this.axisLocation; } /** * Sets the location at which the axis is displayed relative to the * thermometer, and sends a {@link PlotChangeEvent} to all registered * listeners. * * @param location the location (one of {@link #NONE}, {@link #LEFT} and * {@link #RIGHT}). * * @see #getAxisLocation() */ public void setAxisLocation(int location) { if ((location >= 0) && (location < 3)) { this.axisLocation = location; fireChangeEvent(); } else { throw new IllegalArgumentException("Location not recognised."); } } /** * Gets the font used to display the current value. * * @return The font. * * @see #setValueFont(Font) */ public Font getValueFont() { return this.valueFont; } /** * Sets the font used to display the current value. * * @param f the new font (null not permitted). * * @see #getValueFont() */ public void setValueFont(Font f) { ParamChecks.nullNotPermitted(f, "f"); if (!this.valueFont.equals(f)) { this.valueFont = f; fireChangeEvent(); } } /** * Gets the paint used to display the current value. * * @return The paint. * * @see #setValuePaint(Paint) */ public Paint getValuePaint() { return this.valuePaint; } /** * Sets the paint used to display the current value and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param paint the new paint (null not permitted). * * @see #getValuePaint() */ public void setValuePaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); if (!this.valuePaint.equals(paint)) { this.valuePaint = paint; fireChangeEvent(); } } // FIXME: No getValueFormat() method? /** * Sets the formatter for the value label and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param formatter the new formatter (null not permitted). */ public void setValueFormat(NumberFormat formatter) { ParamChecks.nullNotPermitted(formatter, "formatter"); this.valueFormat = formatter; fireChangeEvent(); } /** * Returns the default mercury paint. * * @return The paint (never null). * * @see #setMercuryPaint(Paint) */ public Paint getMercuryPaint() { return this.mercuryPaint; } /** * Sets the default mercury paint and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param paint the new paint (null not permitted). * * @see #getMercuryPaint() */ public void setMercuryPaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); this.mercuryPaint = paint; fireChangeEvent(); } /** * Returns the flag that controls whether not value lines are displayed. * * @return The flag. * * @see #setShowValueLines(boolean) * * @deprecated This flag doesn't do anything useful/visible. Deprecated * as of version 1.0.6. */ public boolean getShowValueLines() { return this.showValueLines; } /** * Sets the display as to whether to show value lines in the output. * * @param b Whether to show value lines in the thermometer * * @see #getShowValueLines() * * @deprecated This flag doesn't do anything useful/visible. Deprecated * as of version 1.0.6. */ public void setShowValueLines(boolean b) { this.showValueLines = b; fireChangeEvent(); } /** * Sets information for a particular range. * * @param range the range to specify information about. * @param low the low value for the range * @param hi the high value for the range */ public void setSubrangeInfo(int range, double low, double hi) { setSubrangeInfo(range, low, hi, low, hi); } /** * Sets the subrangeInfo attribute of the ThermometerPlot object * * @param range the new rangeInfo value. * @param rangeLow the new rangeInfo value * @param rangeHigh the new rangeInfo value * @param displayLow the new rangeInfo value * @param displayHigh the new rangeInfo value */ public void setSubrangeInfo(int range, double rangeLow, double rangeHigh, double displayLow, double displayHigh) { if ((range >= 0) && (range < 3)) { setSubrange(range, rangeLow, rangeHigh); setDisplayRange(range, displayLow, displayHigh); setAxisRange(); fireChangeEvent(); } } /** * Sets the bounds for a subrange. * * @param range the range type. * @param low the low value. * @param high the high value. */ public void setSubrange(int range, double low, double high) { if ((range >= 0) && (range < 3)) { this.subrangeInfo[range][RANGE_HIGH] = high; this.subrangeInfo[range][RANGE_LOW] = low; } } /** * Sets the displayed bounds for a sub range. * * @param range the range type. * @param low the low value. * @param high the high value. */ public void setDisplayRange(int range, double low, double high) { if ((range >= 0) && (range < this.subrangeInfo.length) && isValidNumber(high) && isValidNumber(low)) { if (high > low) { this.subrangeInfo[range][DISPLAY_HIGH] = high; this.subrangeInfo[range][DISPLAY_LOW] = low; } else { this.subrangeInfo[range][DISPLAY_HIGH] = low; this.subrangeInfo[range][DISPLAY_LOW] = high; } } } /** * Gets the paint used for a particular subrange. * * @param range the range (. * * @return The paint. * * @see #setSubrangePaint(int, Paint) */ public Paint getSubrangePaint(int range) { if ((range >= 0) && (range < this.subrangePaint.length)) { return this.subrangePaint[range]; } else { return this.mercuryPaint; } } /** * Sets the paint to be used for a subrange and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param range the range (0, 1 or 2). * @param paint the paint to be applied (null not permitted). * * @see #getSubrangePaint(int) */ public void setSubrangePaint(int range, Paint paint) { if ((range >= 0) && (range < this.subrangePaint.length) && (paint != null)) { this.subrangePaint[range] = paint; fireChangeEvent(); } } /** * Returns a flag that controls whether or not the thermometer axis zooms * to display the subrange within which the data value falls. * * @return The flag. */ public boolean getFollowDataInSubranges() { return this.followDataInSubranges; } /** * Sets the flag that controls whether or not the thermometer axis zooms * to display the subrange within which the data value falls. * * @param flag the flag. */ public void setFollowDataInSubranges(boolean flag) { this.followDataInSubranges = flag; fireChangeEvent(); } /** * Returns a flag that controls whether or not the mercury color changes * for each subrange. * * @return The flag. * * @see #setUseSubrangePaint(boolean) */ public boolean getUseSubrangePaint() { return this.useSubrangePaint; } /** * Sets the range colour change option. * * @param flag the new range colour change option * * @see #getUseSubrangePaint() */ public void setUseSubrangePaint(boolean flag) { this.useSubrangePaint = flag; fireChangeEvent(); } /** * Returns the bulb radius, in Java2D units. * @return The bulb radius. * * @since 1.0.7 */ public int getBulbRadius() { return this.bulbRadius; } /** * Sets the bulb radius (in Java2D units) and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param r the new radius (in Java2D units). * * @see #getBulbRadius() * * @since 1.0.7 */ public void setBulbRadius(int r) { this.bulbRadius = r; fireChangeEvent(); } /** * Returns the bulb diameter, which is always twice the value returned * by {@link #getBulbRadius()}. * * @return The bulb diameter. * * @since 1.0.7 */ public int getBulbDiameter() { return getBulbRadius() * 2; } /** * Returns the column radius, in Java2D units. * * @return The column radius. * * @see #setColumnRadius(int) * * @since 1.0.7 */ public int getColumnRadius() { return this.columnRadius; } /** * Sets the column radius (in Java2D units) and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param r the new radius. * * @see #getColumnRadius() * * @since 1.0.7 */ public void setColumnRadius(int r) { this.columnRadius = r; fireChangeEvent(); } /** * Returns the column diameter, which is always twice the value returned * by {@link #getColumnRadius()}. * * @return The column diameter. * * @since 1.0.7 */ public int getColumnDiameter() { return getColumnRadius() * 2; } /** * Returns the gap, in Java2D units, between the two outlines that * represent the thermometer. * * @return The gap. * * @see #setGap(int) * * @since 1.0.7 */ public int getGap() { return this.gap; } /** * Sets the gap (in Java2D units) between the two outlines that represent * the thermometer, and sends a {@link PlotChangeEvent} to all registered * listeners. * * @param gap the new gap. * * @see #getGap() * * @since 1.0.7 */ public void setGap(int gap) { this.gap = gap; fireChangeEvent(); } /** * Draws the plot on a Java 2D graphics device (such as the screen or a * printer). * * @param g2 the graphics device. * @param area the area within which the plot should be drawn. * @param anchor the anchor point (null permitted). * @param parentState the state from the parent plot, if there is one. * @param info collects info about the drawing. */ @Override public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState, PlotRenderingInfo info) { RoundRectangle2D outerStem = new RoundRectangle2D.Double(); RoundRectangle2D innerStem = new RoundRectangle2D.Double(); RoundRectangle2D mercuryStem = new RoundRectangle2D.Double(); Ellipse2D outerBulb = new Ellipse2D.Double(); Ellipse2D innerBulb = new Ellipse2D.Double(); String temp; FontMetrics metrics; if (info != null) { info.setPlotArea(area); } // adjust for insets... RectangleInsets insets = getInsets(); insets.trim(area); drawBackground(g2, area); // adjust for padding... Rectangle2D interior = (Rectangle2D) area.clone(); this.padding.trim(interior); int midX = (int) (interior.getX() + (interior.getWidth() / 2)); int midY = (int) (interior.getY() + (interior.getHeight() / 2)); int stemTop = (int) (interior.getMinY() + getBulbRadius()); int stemBottom = (int) (interior.getMaxY() - getBulbDiameter()); Rectangle2D dataArea = new Rectangle2D.Double(midX - getColumnRadius(), stemTop, getColumnRadius(), stemBottom - stemTop); outerBulb.setFrame(midX - getBulbRadius(), stemBottom, getBulbDiameter(), getBulbDiameter()); outerStem.setRoundRect(midX - getColumnRadius(), interior.getMinY(), getColumnDiameter(), stemBottom + getBulbDiameter() - stemTop, getColumnDiameter(), getColumnDiameter()); Area outerThermometer = new Area(outerBulb); Area tempArea = new Area(outerStem); outerThermometer.add(tempArea); innerBulb.setFrame(midX - getBulbRadius() + getGap(), stemBottom + getGap(), getBulbDiameter() - getGap() * 2, getBulbDiameter() - getGap() * 2); innerStem.setRoundRect(midX - getColumnRadius() + getGap(), interior.getMinY() + getGap(), getColumnDiameter() - getGap() * 2, stemBottom + getBulbDiameter() - getGap() * 2 - stemTop, getColumnDiameter() - getGap() * 2, getColumnDiameter() - getGap() * 2); Area innerThermometer = new Area(innerBulb); tempArea = new Area(innerStem); innerThermometer.add(tempArea); if ((this.dataset != null) && (this.dataset.getValue() != null)) { double current = this.dataset.getValue().doubleValue(); double ds = this.rangeAxis.valueToJava2D(current, dataArea, RectangleEdge.LEFT); int i = getColumnDiameter() - getGap() * 2; // already calculated int j = getColumnRadius() - getGap(); // already calculated int l = (i / 2); int k = (int) Math.round(ds); if (k < (getGap() + interior.getMinY())) { k = (int) (getGap() + interior.getMinY()); l = getBulbRadius(); } Area mercury = new Area(innerBulb); if (k < (stemBottom + getBulbRadius())) { mercuryStem.setRoundRect(midX - j, k, i, (stemBottom + getBulbRadius()) - k, l, l); tempArea = new Area(mercuryStem); mercury.add(tempArea); } g2.setPaint(getCurrentPaint()); g2.fill(mercury); // draw range indicators... if (this.subrangeIndicatorsVisible) { g2.setStroke(this.subrangeIndicatorStroke); Range range = this.rangeAxis.getRange(); // draw start of normal range double value = this.subrangeInfo[NORMAL][RANGE_LOW]; if (range.contains(value)) { double x = midX + getColumnRadius() + 2; double y = this.rangeAxis.valueToJava2D(value, dataArea, RectangleEdge.LEFT); Line2D line = new Line2D.Double(x, y, x + 10, y); g2.setPaint(this.subrangePaint[NORMAL]); g2.draw(line); } // draw start of warning range value = this.subrangeInfo[WARNING][RANGE_LOW]; if (range.contains(value)) { double x = midX + getColumnRadius() + 2; double y = this.rangeAxis.valueToJava2D(value, dataArea, RectangleEdge.LEFT); Line2D line = new Line2D.Double(x, y, x + 10, y); g2.setPaint(this.subrangePaint[WARNING]); g2.draw(line); } // draw start of critical range value = this.subrangeInfo[CRITICAL][RANGE_LOW]; if (range.contains(value)) { double x = midX + getColumnRadius() + 2; double y = this.rangeAxis.valueToJava2D(value, dataArea, RectangleEdge.LEFT); Line2D line = new Line2D.Double(x, y, x + 10, y); g2.setPaint(this.subrangePaint[CRITICAL]); g2.draw(line); } } // draw the axis... if ((this.rangeAxis != null) && (this.axisLocation != NONE)) { int drawWidth = AXIS_GAP; if (this.showValueLines) { drawWidth += getColumnDiameter(); } Rectangle2D drawArea; double cursor; switch (this.axisLocation) { case RIGHT: cursor = midX + getColumnRadius(); drawArea = new Rectangle2D.Double(cursor, stemTop, drawWidth, (stemBottom - stemTop + 1)); this.rangeAxis.draw(g2, cursor, area, drawArea, RectangleEdge.RIGHT, null); break; case LEFT: default: //cursor = midX - COLUMN_RADIUS - AXIS_GAP; cursor = midX - getColumnRadius(); drawArea = new Rectangle2D.Double(cursor, stemTop, drawWidth, (stemBottom - stemTop + 1)); this.rangeAxis.draw(g2, cursor, area, drawArea, RectangleEdge.LEFT, null); break; } } // draw text value on screen g2.setFont(this.valueFont); g2.setPaint(this.valuePaint); metrics = g2.getFontMetrics(); switch (this.valueLocation) { case RIGHT: g2.drawString(this.valueFormat.format(current), midX + getColumnRadius() + getGap(), midY); break; case LEFT: String valueString = this.valueFormat.format(current); int stringWidth = metrics.stringWidth(valueString); g2.drawString(valueString, midX - getColumnRadius() - getGap() - stringWidth, midY); break; case BULB: temp = this.valueFormat.format(current); i = metrics.stringWidth(temp) / 2; g2.drawString(temp, midX - i, stemBottom + getBulbRadius() + getGap()); break; default: } /***/ } g2.setPaint(this.thermometerPaint); g2.setFont(this.valueFont); // draw units indicator metrics = g2.getFontMetrics(); int tickX1 = midX - getColumnRadius() - getGap() * 2 - metrics.stringWidth(UNITS[this.units]); if (tickX1 > area.getMinX()) { g2.drawString(UNITS[this.units], tickX1, (int) (area.getMinY() + 20)); } // draw thermometer outline g2.setStroke(this.thermometerStroke); g2.draw(outerThermometer); g2.draw(innerThermometer); drawOutline(g2, area); } /** * A zoom method that does nothing. Plots are required to support the * zoom operation. In the case of a thermometer chart, it doesn't make * sense to zoom in or out, so the method is empty. * * @param percent the zoom percentage. */ @Override public void zoom(double percent) { // intentionally blank } /** * Returns a short string describing the type of plot. * * @return A short string describing the type of plot. */ @Override public String getPlotType() { return localizationResources.getString("Thermometer_Plot"); } /** * Checks to see if a new value means the axis range needs adjusting. * * @param event the dataset change event. */ @Override public void datasetChanged(DatasetChangeEvent event) { if (this.dataset != null) { Number vn = this.dataset.getValue(); if (vn != null) { double value = vn.doubleValue(); if (inSubrange(NORMAL, value)) { this.subrange = NORMAL; } else if (inSubrange(WARNING, value)) { this.subrange = WARNING; } else if (inSubrange(CRITICAL, value)) { this.subrange = CRITICAL; } else { this.subrange = -1; } setAxisRange(); } } super.datasetChanged(event); } /** * Returns the minimum value in either the domain or the range, whichever * is displayed against the vertical axis for the particular type of plot * implementing this interface. * * @return The minimum value in either the domain or the range. * * @deprecated This method is not used. Officially deprecated in version * 1.0.6. */ public Number getMinimumVerticalDataValue() { return new Double(this.lowerBound); } /** * Returns the maximum value in either the domain or the range, whichever * is displayed against the vertical axis for the particular type of plot * implementing this interface. * * @return The maximum value in either the domain or the range * * @deprecated This method is not used. Officially deprecated in version * 1.0.6. */ public Number getMaximumVerticalDataValue() { return new Double(this.upperBound); } /** * Returns the data range. * * @param axis the axis. * * @return The range of data displayed. */ @Override public Range getDataRange(ValueAxis axis) { return new Range(this.lowerBound, this.upperBound); } /** * Sets the axis range to the current values in the rangeInfo array. */ protected void setAxisRange() { if ((this.subrange >= 0) && (this.followDataInSubranges)) { this.rangeAxis.setRange( new Range(this.subrangeInfo[this.subrange][DISPLAY_LOW], this.subrangeInfo[this.subrange][DISPLAY_HIGH])); } else { this.rangeAxis.setRange(this.lowerBound, this.upperBound); } } /** * Returns the legend items for the plot. * * @return null. */ @Override public LegendItemCollection getLegendItems() { return null; } /** * Returns the orientation of the plot. * * @return The orientation (always {@link PlotOrientation#VERTICAL}). */ @Override public PlotOrientation getOrientation() { return PlotOrientation.VERTICAL; } /** * Determine whether a number is valid and finite. * * @param d the number to be tested. * * @return true if the number is valid and finite, and * false otherwise. */ protected static boolean isValidNumber(double d) { return (!(Double.isNaN(d) || Double.isInfinite(d))); } /** * Returns true if the value is in the specified range, and false otherwise. * * @param subrange the subrange. * @param value the value to check. * * @return A boolean. */ private boolean inSubrange(int subrange, double value) { return (value > this.subrangeInfo[subrange][RANGE_LOW] && value <= this.subrangeInfo[subrange][RANGE_HIGH]); } /** * Returns the mercury paint corresponding to the current data value. * Called from the {@link #draw(Graphics2D, Rectangle2D, Point2D, * PlotState, PlotRenderingInfo)} method. * * @return The paint (never null). */ private Paint getCurrentPaint() { Paint result = this.mercuryPaint; if (this.useSubrangePaint) { double value = this.dataset.getValue().doubleValue(); if (inSubrange(NORMAL, value)) { result = this.subrangePaint[NORMAL]; } else if (inSubrange(WARNING, value)) { result = this.subrangePaint[WARNING]; } else if (inSubrange(CRITICAL, value)) { result = this.subrangePaint[CRITICAL]; } } return result; } /** * Tests this plot for equality with another object. The plot's dataset * is not considered in the test. * * @param obj the object (null permitted). * * @return true or false. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof ThermometerPlot)) { return false; } ThermometerPlot that = (ThermometerPlot) obj; if (!super.equals(obj)) { return false; } if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) { return false; } if (this.axisLocation != that.axisLocation) { return false; } if (this.lowerBound != that.lowerBound) { return false; } if (this.upperBound != that.upperBound) { return false; } if (!ObjectUtilities.equal(this.padding, that.padding)) { return false; } if (!ObjectUtilities.equal(this.thermometerStroke, that.thermometerStroke)) { return false; } if (!PaintUtilities.equal(this.thermometerPaint, that.thermometerPaint)) { return false; } if (this.units != that.units) { return false; } if (this.valueLocation != that.valueLocation) { return false; } if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) { return false; } if (!PaintUtilities.equal(this.valuePaint, that.valuePaint)) { return false; } if (!ObjectUtilities.equal(this.valueFormat, that.valueFormat)) { return false; } if (!PaintUtilities.equal(this.mercuryPaint, that.mercuryPaint)) { return false; } if (this.showValueLines != that.showValueLines) { return false; } if (this.subrange != that.subrange) { return false; } if (this.followDataInSubranges != that.followDataInSubranges) { return false; } if (!equal(this.subrangeInfo, that.subrangeInfo)) { return false; } if (this.useSubrangePaint != that.useSubrangePaint) { return false; } if (this.bulbRadius != that.bulbRadius) { return false; } if (this.columnRadius != that.columnRadius) { return false; } if (this.gap != that.gap) { return false; } for (int i = 0; i < this.subrangePaint.length; i++) { if (!PaintUtilities.equal(this.subrangePaint[i], that.subrangePaint[i])) { return false; } } return true; } /** * Tests two double[][] arrays for equality. * * @param array1 the first array (null permitted). * @param array2 the second arrray (null permitted). * * @return A boolean. */ private static boolean equal(double[][] array1, double[][] array2) { if (array1 == null) { return (array2 == null); } if (array2 == null) { return false; } if (array1.length != array2.length) { return false; } for (int i = 0; i < array1.length; i++) { if (!Arrays.equals(array1[i], array2[i])) { return false; } } return true; } /** * Returns a clone of the plot. * * @return A clone. * * @throws CloneNotSupportedException if the plot cannot be cloned. */ @Override public Object clone() throws CloneNotSupportedException { ThermometerPlot clone = (ThermometerPlot) super.clone(); if (clone.dataset != null) { clone.dataset.addChangeListener(clone); } clone.rangeAxis = (ValueAxis) ObjectUtilities.clone(this.rangeAxis); if (clone.rangeAxis != null) { clone.rangeAxis.setPlot(clone); clone.rangeAxis.addChangeListener(clone); } clone.valueFormat = (NumberFormat) this.valueFormat.clone(); clone.subrangePaint = (Paint[]) this.subrangePaint.clone(); return clone; } /** * Provides serialization support. * * @param stream the output stream. * * @throws IOException if there is an I/O error. */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); SerialUtilities.writeStroke(this.thermometerStroke, stream); SerialUtilities.writePaint(this.thermometerPaint, stream); SerialUtilities.writePaint(this.valuePaint, stream); SerialUtilities.writePaint(this.mercuryPaint, stream); SerialUtilities.writeStroke(this.subrangeIndicatorStroke, stream); SerialUtilities.writeStroke(this.rangeIndicatorStroke, stream); for (int i = 0; i < 3; i++) { SerialUtilities.writePaint(this.subrangePaint[i], stream); } } /** * Provides serialization support. * * @param stream the input stream. * * @throws IOException if there is an I/O error. * @throws ClassNotFoundException if there is a classpath problem. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); this.thermometerStroke = SerialUtilities.readStroke(stream); this.thermometerPaint = SerialUtilities.readPaint(stream); this.valuePaint = SerialUtilities.readPaint(stream); this.mercuryPaint = SerialUtilities.readPaint(stream); this.subrangeIndicatorStroke = SerialUtilities.readStroke(stream); this.rangeIndicatorStroke = SerialUtilities.readStroke(stream); this.subrangePaint = new Paint[3]; for (int i = 0; i < 3; i++) { this.subrangePaint[i] = SerialUtilities.readPaint(stream); } if (this.rangeAxis != null) { this.rangeAxis.addChangeListener(this); } } /** * Multiplies the range on the domain axis/axes by the specified factor. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point. */ @Override public void zoomDomainAxes(double factor, PlotRenderingInfo state, Point2D source) { // no domain axis to zoom } /** * Multiplies the range on the domain axis/axes by the specified factor. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point. * @param useAnchor a flag that controls whether or not the source point * is used for the zoom anchor. * * @since 1.0.7 */ @Override public void zoomDomainAxes(double factor, PlotRenderingInfo state, Point2D source, boolean useAnchor) { // no domain axis to zoom } /** * Multiplies the range on the range axis/axes by the specified factor. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point. */ @Override public void zoomRangeAxes(double factor, PlotRenderingInfo state, Point2D source) { this.rangeAxis.resizeRange(factor); } /** * Multiplies the range on the range axis/axes by the specified factor. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point. * @param useAnchor a flag that controls whether or not the source point * is used for the zoom anchor. * * @since 1.0.7 */ @Override public void zoomRangeAxes(double factor, PlotRenderingInfo state, Point2D source, boolean useAnchor) { double anchorY = this.getRangeAxis().java2DToValue(source.getY(), state.getDataArea(), RectangleEdge.LEFT); this.rangeAxis.resizeRange(factor, anchorY); } /** * This method does nothing. * * @param lowerPercent the lower percent. * @param upperPercent the upper percent. * @param state the plot state. * @param source the source point. */ @Override public void zoomDomainAxes(double lowerPercent, double upperPercent, PlotRenderingInfo state, Point2D source) { // no domain axis to zoom } /** * Zooms the range axes. * * @param lowerPercent the lower percent. * @param upperPercent the upper percent. * @param state the plot state. * @param source the source point. */ @Override public void zoomRangeAxes(double lowerPercent, double upperPercent, PlotRenderingInfo state, Point2D source) { this.rangeAxis.zoomRange(lowerPercent, upperPercent); } /** * Returns false. * * @return A boolean. */ @Override public boolean isDomainZoomable() { return false; } /** * Returns true. * * @return A boolean. */ @Override public boolean isRangeZoomable() { return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy