org.jfree.chart.plot.MeterPlot Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jfreechart Show documentation
Show all versions of jfreechart Show documentation
JFreeChart is a class library, written in Java, for generating charts.
Utilising the Java2D API, it supports a wide range of chart types including
bar charts, pie charts, line charts, XY-plots, time series plots, Sankey charts
and more.
/* ===========================================================
* JFreeChart : a free chart library for the Java(tm) platform
* ===========================================================
*
* (C) Copyright 2000-2011, 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.]
*
* --------------
* MeterPlot.java
* --------------
* (C) Copyright 2000-2008, by Hari and Contributors.
*
* Original Author: Hari ([email protected]);
* Contributor(s): David Gilbert (for Object Refinery Limited);
* Bob Orchard;
* Arnaud Lelievre;
* Nicolas Brodu;
* David Bastend;
*
* Changes
* -------
* 01-Apr-2002 : Version 1, contributed by Hari (DG);
* 23-Apr-2002 : Moved dataset from JFreeChart to Plot (DG);
* 22-Aug-2002 : Added changes suggest by Bob Orchard, changed Color to Paint
* for consistency, plus added Javadoc comments (DG);
* 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
* 23-Jan-2003 : Removed one constructor (DG);
* 26-Mar-2003 : Implemented Serializable (DG);
* 20-Aug-2003 : Changed dataset from MeterDataset --> ValueDataset, added
* equals() method,
* 08-Sep-2003 : Added internationalization via use of properties
* resourceBundle (RFE 690236) (AL);
* implemented Cloneable, and various other changes (DG);
* 08-Sep-2003 : Added serialization methods (NB);
* 11-Sep-2003 : Added cloning support (NB);
* 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
* 25-Sep-2003 : Fix useless cloning. Correct dataset listener registration in
* constructor. (NB)
* 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
* 17-Jan-2004 : Changed to allow dialBackgroundPaint to be set to null - see
* bug 823628 (DG);
* 07-Apr-2004 : Changed string bounds calculation (DG);
* 12-May-2004 : Added tickLabelFormat attribute - see RFE 949566. Also
* updated the equals() method (DG);
* 02-Nov-2004 : Added sanity checks for range, and only draw the needle if the
* value is contained within the overall range - see bug report
* 1056047 (DG);
* 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
* release (DG);
* 02-Feb-2005 : Added optional background paint for each region (DG);
* 22-Mar-2005 : Removed 'normal', 'warning' and 'critical' regions and put in
* facility to define an arbitrary number of MeterIntervals,
* based on a contribution by David Bastend (DG);
* 20-Apr-2005 : Small update for change to LegendItem constructors (DG);
* 05-May-2005 : Updated draw() method parameters (DG);
* 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
* 10-Nov-2005 : Added tickPaint, tickSize and valuePaint attributes, and
* put value label drawing code into a separate method (DG);
* ------------- JFREECHART 1.0.x ---------------------------------------------
* 05-Mar-2007 : Restore clip region correctly (see bug 1667750) (DG);
* 18-May-2007 : Set dataset for LegendItem (DG);
* 29-Nov-2007 : Fixed serialization bug with dialOutlinePaint (DG);
* 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
* Jess Thrysoee (DG);
*
*/
package org.jfree.chart.plot;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.NumberFormat;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.event.PlotChangeEvent;
import org.jfree.chart.util.ResourceBundleWrapper;
import org.jfree.data.Range;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.ValueDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.text.TextUtilities;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.TextAnchor;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;
/**
* A plot that displays a single value in the form of a needle on a dial.
* Defined ranges (for example, 'normal', 'warning' and 'critical') can be
* highlighted on the dial.
*/
public class MeterPlot extends Plot implements Serializable, Cloneable {
/** For serialization. */
private static final long serialVersionUID = 2987472457734470962L;
/** The default background paint. */
static final Paint DEFAULT_DIAL_BACKGROUND_PAINT = Color.black;
/** The default needle paint. */
static final Paint DEFAULT_NEEDLE_PAINT = Color.green;
/** The default value font. */
static final Font DEFAULT_VALUE_FONT = new Font("SansSerif", Font.BOLD, 12);
/** The default value paint. */
static final Paint DEFAULT_VALUE_PAINT = Color.yellow;
/** The default meter angle. */
public static final int DEFAULT_METER_ANGLE = 270;
/** The default border size. */
public static final float DEFAULT_BORDER_SIZE = 3f;
/** The default circle size. */
public static final float DEFAULT_CIRCLE_SIZE = 10f;
/** The default label font. */
public static final Font DEFAULT_LABEL_FONT = new Font("SansSerif",
Font.BOLD, 10);
/** The dataset (contains a single value). */
private ValueDataset dataset;
/** The dial shape (background shape). */
private DialShape shape;
/** The dial extent (measured in degrees). */
private int meterAngle;
/** The overall range of data values on the dial. */
private Range range;
/** The tick size. */
private double tickSize;
/** The paint used to draw the ticks. */
private transient Paint tickPaint;
/** The units displayed on the dial. */
private String units;
/** The font for the value displayed in the center of the dial. */
private Font valueFont;
/** The paint for the value displayed in the center of the dial. */
private transient Paint valuePaint;
/** A flag that controls whether or not the border is drawn. */
private boolean drawBorder;
/** The outline paint. */
private transient Paint dialOutlinePaint;
/** The paint for the dial background. */
private transient Paint dialBackgroundPaint;
/** The paint for the needle. */
private transient Paint needlePaint;
/** A flag that controls whether or not the tick labels are visible. */
private boolean tickLabelsVisible;
/** The tick label font. */
private Font tickLabelFont;
/** The tick label paint. */
private transient Paint tickLabelPaint;
/** The tick label format. */
private NumberFormat tickLabelFormat;
/** The resourceBundle for the localization. */
protected static ResourceBundle localizationResources
= ResourceBundleWrapper.getBundle(
"org.jfree.chart.plot.LocalizationBundle");
/**
* A (possibly empty) list of the {@link MeterInterval}s to be highlighted
* on the dial.
*/
private List intervals;
/**
* Creates a new plot with a default range of 0
to
* 100
and no value to display.
*/
public MeterPlot() {
this(null);
}
/**
* Creates a new plot that displays the value from the supplied dataset.
*
* @param dataset the dataset (null
permitted).
*/
public MeterPlot(ValueDataset dataset) {
super();
this.shape = DialShape.CIRCLE;
this.meterAngle = DEFAULT_METER_ANGLE;
this.range = new Range(0.0, 100.0);
this.tickSize = 10.0;
this.tickPaint = Color.white;
this.units = "Units";
this.needlePaint = MeterPlot.DEFAULT_NEEDLE_PAINT;
this.tickLabelsVisible = true;
this.tickLabelFont = MeterPlot.DEFAULT_LABEL_FONT;
this.tickLabelPaint = Color.black;
this.tickLabelFormat = NumberFormat.getInstance();
this.valueFont = MeterPlot.DEFAULT_VALUE_FONT;
this.valuePaint = MeterPlot.DEFAULT_VALUE_PAINT;
this.dialBackgroundPaint = MeterPlot.DEFAULT_DIAL_BACKGROUND_PAINT;
this.intervals = new java.util.ArrayList();
setDataset(dataset);
}
/**
* Returns the dial shape. The default is {@link DialShape#CIRCLE}).
*
* @return The dial shape (never null
).
*
* @see #setDialShape(DialShape)
*/
public DialShape getDialShape() {
return this.shape;
}
/**
* Sets the dial shape and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param shape the shape (null
not permitted).
*
* @see #getDialShape()
*/
public void setDialShape(DialShape shape) {
if (shape == null) {
throw new IllegalArgumentException("Null 'shape' argument.");
}
this.shape = shape;
fireChangeEvent();
}
/**
* Returns the meter angle in degrees. This defines, in part, the shape
* of the dial. The default is 270 degrees.
*
* @return The meter angle (in degrees).
*
* @see #setMeterAngle(int)
*/
public int getMeterAngle() {
return this.meterAngle;
}
/**
* Sets the angle (in degrees) for the whole range of the dial and sends
* a {@link PlotChangeEvent} to all registered listeners.
*
* @param angle the angle (in degrees, in the range 1-360).
*
* @see #getMeterAngle()
*/
public void setMeterAngle(int angle) {
if (angle < 1 || angle > 360) {
throw new IllegalArgumentException("Invalid 'angle' (" + angle
+ ")");
}
this.meterAngle = angle;
fireChangeEvent();
}
/**
* Returns the overall range for the dial.
*
* @return The overall range (never null
).
*
* @see #setRange(Range)
*/
public Range getRange() {
return this.range;
}
/**
* Sets the range for the dial and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param range the range (null
not permitted and zero-length
* ranges not permitted).
*
* @see #getRange()
*/
public void setRange(Range range) {
if (range == null) {
throw new IllegalArgumentException("Null 'range' argument.");
}
if (!(range.getLength() > 0.0)) {
throw new IllegalArgumentException(
"Range length must be positive.");
}
this.range = range;
fireChangeEvent();
}
/**
* Returns the tick size (the interval between ticks on the dial).
*
* @return The tick size.
*
* @see #setTickSize(double)
*/
public double getTickSize() {
return this.tickSize;
}
/**
* Sets the tick size and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param size the tick size (must be > 0).
*
* @see #getTickSize()
*/
public void setTickSize(double size) {
if (size <= 0) {
throw new IllegalArgumentException("Requires 'size' > 0.");
}
this.tickSize = size;
fireChangeEvent();
}
/**
* Returns the paint used to draw the ticks around the dial.
*
* @return The paint used to draw the ticks around the dial (never
* null
).
*
* @see #setTickPaint(Paint)
*/
public Paint getTickPaint() {
return this.tickPaint;
}
/**
* Sets the paint used to draw the tick labels around the dial and sends
* a {@link PlotChangeEvent} to all registered listeners.
*
* @param paint the paint (null
not permitted).
*
* @see #getTickPaint()
*/
public void setTickPaint(Paint paint) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
this.tickPaint = paint;
fireChangeEvent();
}
/**
* Returns a string describing the units for the dial.
*
* @return The units (possibly null
).
*
* @see #setUnits(String)
*/
public String getUnits() {
return this.units;
}
/**
* Sets the units for the dial and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param units the units (null
permitted).
*
* @see #getUnits()
*/
public void setUnits(String units) {
this.units = units;
fireChangeEvent();
}
/**
* Returns the paint for the needle.
*
* @return The paint (never null
).
*
* @see #setNeedlePaint(Paint)
*/
public Paint getNeedlePaint() {
return this.needlePaint;
}
/**
* Sets the paint used to display the needle and sends a
* {@link PlotChangeEvent} to all registered listeners.
*
* @param paint the paint (null
not permitted).
*
* @see #getNeedlePaint()
*/
public void setNeedlePaint(Paint paint) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
this.needlePaint = paint;
fireChangeEvent();
}
/**
* Returns the flag that determines whether or not tick labels are visible.
*
* @return The flag.
*
* @see #setTickLabelsVisible(boolean)
*/
public boolean getTickLabelsVisible() {
return this.tickLabelsVisible;
}
/**
* Sets the flag that controls whether or not the tick labels are visible
* and sends a {@link PlotChangeEvent} to all registered listeners.
*
* @param visible the flag.
*
* @see #getTickLabelsVisible()
*/
public void setTickLabelsVisible(boolean visible) {
if (this.tickLabelsVisible != visible) {
this.tickLabelsVisible = visible;
fireChangeEvent();
}
}
/**
* Returns the tick label font.
*
* @return The font (never null
).
*
* @see #setTickLabelFont(Font)
*/
public Font getTickLabelFont() {
return this.tickLabelFont;
}
/**
* Sets the tick label font and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param font the font (null
not permitted).
*
* @see #getTickLabelFont()
*/
public void setTickLabelFont(Font font) {
if (font == null) {
throw new IllegalArgumentException("Null 'font' argument.");
}
if (!this.tickLabelFont.equals(font)) {
this.tickLabelFont = font;
fireChangeEvent();
}
}
/**
* Returns the tick label paint.
*
* @return The paint (never null
).
*
* @see #setTickLabelPaint(Paint)
*/
public Paint getTickLabelPaint() {
return this.tickLabelPaint;
}
/**
* Sets the tick label paint and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param paint the paint (null
not permitted).
*
* @see #getTickLabelPaint()
*/
public void setTickLabelPaint(Paint paint) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
if (!this.tickLabelPaint.equals(paint)) {
this.tickLabelPaint = paint;
fireChangeEvent();
}
}
/**
* Returns the tick label format.
*
* @return The tick label format (never null
).
*
* @see #setTickLabelFormat(NumberFormat)
*/
public NumberFormat getTickLabelFormat() {
return this.tickLabelFormat;
}
/**
* Sets the format for the tick labels and sends a {@link PlotChangeEvent}
* to all registered listeners.
*
* @param format the format (null
not permitted).
*
* @see #getTickLabelFormat()
*/
public void setTickLabelFormat(NumberFormat format) {
if (format == null) {
throw new IllegalArgumentException("Null 'format' argument.");
}
this.tickLabelFormat = format;
fireChangeEvent();
}
/**
* Returns the font for the value label.
*
* @return The font (never null
).
*
* @see #setValueFont(Font)
*/
public Font getValueFont() {
return this.valueFont;
}
/**
* Sets the font used to display the value label and sends a
* {@link PlotChangeEvent} to all registered listeners.
*
* @param font the font (null
not permitted).
*
* @see #getValueFont()
*/
public void setValueFont(Font font) {
if (font == null) {
throw new IllegalArgumentException("Null 'font' argument.");
}
this.valueFont = font;
fireChangeEvent();
}
/**
* Returns the paint for the value label.
*
* @return The paint (never null
).
*
* @see #setValuePaint(Paint)
*/
public Paint getValuePaint() {
return this.valuePaint;
}
/**
* Sets the paint used to display the value label and sends a
* {@link PlotChangeEvent} to all registered listeners.
*
* @param paint the paint (null
not permitted).
*
* @see #getValuePaint()
*/
public void setValuePaint(Paint paint) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument.");
}
this.valuePaint = paint;
fireChangeEvent();
}
/**
* Returns the paint for the dial background.
*
* @return The paint (possibly null
).
*
* @see #setDialBackgroundPaint(Paint)
*/
public Paint getDialBackgroundPaint() {
return this.dialBackgroundPaint;
}
/**
* Sets the paint used to fill the dial background. Set this to
* null
for no background.
*
* @param paint the paint (null
permitted).
*
* @see #getDialBackgroundPaint()
*/
public void setDialBackgroundPaint(Paint paint) {
this.dialBackgroundPaint = paint;
fireChangeEvent();
}
/**
* Returns a flag that controls whether or not a rectangular border is
* drawn around the plot area.
*
* @return A flag.
*
* @see #setDrawBorder(boolean)
*/
public boolean getDrawBorder() {
return this.drawBorder;
}
/**
* Sets the flag that controls whether or not a rectangular border is drawn
* around the plot area and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param draw the flag.
*
* @see #getDrawBorder()
*/
public void setDrawBorder(boolean draw) {
// TODO: fix output when this flag is set to true
this.drawBorder = draw;
fireChangeEvent();
}
/**
* Returns the dial outline paint.
*
* @return The paint.
*
* @see #setDialOutlinePaint(Paint)
*/
public Paint getDialOutlinePaint() {
return this.dialOutlinePaint;
}
/**
* Sets the dial outline paint and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param paint the paint.
*
* @see #getDialOutlinePaint()
*/
public void setDialOutlinePaint(Paint paint) {
this.dialOutlinePaint = paint;
fireChangeEvent();
}
/**
* 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 triggers a {@link PlotChangeEvent}.
*
* @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 an unmodifiable list of the intervals for the plot.
*
* @return A list.
*
* @see #addInterval(MeterInterval)
*/
public List getIntervals() {
return Collections.unmodifiableList(this.intervals);
}
/**
* Adds an interval and sends a {@link PlotChangeEvent} to all registered
* listeners.
*
* @param interval the interval (null
not permitted).
*
* @see #getIntervals()
* @see #clearIntervals()
*/
public void addInterval(MeterInterval interval) {
if (interval == null) {
throw new IllegalArgumentException("Null 'interval' argument.");
}
this.intervals.add(interval);
fireChangeEvent();
}
/**
* Clears the intervals for the plot and sends a {@link PlotChangeEvent} to
* all registered listeners.
*
* @see #addInterval(MeterInterval)
*/
public void clearIntervals() {
this.intervals.clear();
fireChangeEvent();
}
/**
* Returns an item for each interval.
*
* @return A collection of legend items.
*/
public LegendItemCollection getLegendItems() {
LegendItemCollection result = new LegendItemCollection();
Iterator iterator = this.intervals.iterator();
while (iterator.hasNext()) {
MeterInterval mi = (MeterInterval) iterator.next();
Paint color = mi.getBackgroundPaint();
if (color == null) {
color = mi.getOutlinePaint();
}
LegendItem item = new LegendItem(mi.getLabel(), mi.getLabel(),
null, null, new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0),
color);
item.setDataset(getDataset());
result.add(item);
}
return result;
}
/**
* 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.
*/
public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
PlotState parentState,
PlotRenderingInfo info) {
if (info != null) {
info.setPlotArea(area);
}
// adjust for insets...
RectangleInsets insets = getInsets();
insets.trim(area);
area.setRect(area.getX() + 4, area.getY() + 4, area.getWidth() - 8,
area.getHeight() - 8);
// draw the background
if (this.drawBorder) {
drawBackground(g2, area);
}
// adjust the plot area by the interior spacing value
double gapHorizontal = (2 * DEFAULT_BORDER_SIZE);
double gapVertical = (2 * DEFAULT_BORDER_SIZE);
double meterX = area.getX() + gapHorizontal / 2;
double meterY = area.getY() + gapVertical / 2;
double meterW = area.getWidth() - gapHorizontal;
double meterH = area.getHeight() - gapVertical
+ ((this.meterAngle <= 180) && (this.shape != DialShape.CIRCLE)
? area.getHeight() / 1.25 : 0);
double min = Math.min(meterW, meterH) / 2;
meterX = (meterX + meterX + meterW) / 2 - min;
meterY = (meterY + meterY + meterH) / 2 - min;
meterW = 2 * min;
meterH = 2 * min;
Rectangle2D meterArea = new Rectangle2D.Double(meterX, meterY, meterW,
meterH);
Rectangle2D.Double originalArea = new Rectangle2D.Double(
meterArea.getX() - 4, meterArea.getY() - 4,
meterArea.getWidth() + 8, meterArea.getHeight() + 8);
double meterMiddleX = meterArea.getCenterX();
double meterMiddleY = meterArea.getCenterY();
// plot the data (unless the dataset is null)...
ValueDataset data = getDataset();
if (data != null) {
double dataMin = this.range.getLowerBound();
double dataMax = this.range.getUpperBound();
Shape savedClip = g2.getClip();
g2.clip(originalArea);
Composite originalComposite = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
getForegroundAlpha()));
if (this.dialBackgroundPaint != null) {
fillArc(g2, originalArea, dataMin, dataMax,
this.dialBackgroundPaint, true);
}
drawTicks(g2, meterArea, dataMin, dataMax);
drawArcForInterval(g2, meterArea, new MeterInterval("", this.range,
this.dialOutlinePaint, new BasicStroke(1.0f), null));
Iterator iterator = this.intervals.iterator();
while (iterator.hasNext()) {
MeterInterval interval = (MeterInterval) iterator.next();
drawArcForInterval(g2, meterArea, interval);
}
Number n = data.getValue();
if (n != null) {
double value = n.doubleValue();
drawValueLabel(g2, meterArea);
if (this.range.contains(value)) {
g2.setPaint(this.needlePaint);
g2.setStroke(new BasicStroke(2.0f));
double radius = (meterArea.getWidth() / 2)
+ DEFAULT_BORDER_SIZE + 15;
double valueAngle = valueToAngle(value);
double valueP1 = meterMiddleX
+ (radius * Math.cos(Math.PI * (valueAngle / 180)));
double valueP2 = meterMiddleY
- (radius * Math.sin(Math.PI * (valueAngle / 180)));
Polygon arrow = new Polygon();
if ((valueAngle > 135 && valueAngle < 225)
|| (valueAngle < 45 && valueAngle > -45)) {
double valueP3 = (meterMiddleY
- DEFAULT_CIRCLE_SIZE / 4);
double valueP4 = (meterMiddleY
+ DEFAULT_CIRCLE_SIZE / 4);
arrow.addPoint((int) meterMiddleX, (int) valueP3);
arrow.addPoint((int) meterMiddleX, (int) valueP4);
}
else {
arrow.addPoint((int) (meterMiddleX
- DEFAULT_CIRCLE_SIZE / 4), (int) meterMiddleY);
arrow.addPoint((int) (meterMiddleX
+ DEFAULT_CIRCLE_SIZE / 4), (int) meterMiddleY);
}
arrow.addPoint((int) valueP1, (int) valueP2);
g2.fill(arrow);
Ellipse2D circle = new Ellipse2D.Double(meterMiddleX
- DEFAULT_CIRCLE_SIZE / 2, meterMiddleY
- DEFAULT_CIRCLE_SIZE / 2, DEFAULT_CIRCLE_SIZE,
DEFAULT_CIRCLE_SIZE);
g2.fill(circle);
}
}
g2.setClip(savedClip);
g2.setComposite(originalComposite);
}
if (this.drawBorder) {
drawOutline(g2, area);
}
}
/**
* Draws the arc to represent an interval.
*
* @param g2 the graphics device.
* @param meterArea the drawing area.
* @param interval the interval.
*/
protected void drawArcForInterval(Graphics2D g2, Rectangle2D meterArea,
MeterInterval interval) {
double minValue = interval.getRange().getLowerBound();
double maxValue = interval.getRange().getUpperBound();
Paint outlinePaint = interval.getOutlinePaint();
Stroke outlineStroke = interval.getOutlineStroke();
Paint backgroundPaint = interval.getBackgroundPaint();
if (backgroundPaint != null) {
fillArc(g2, meterArea, minValue, maxValue, backgroundPaint, false);
}
if (outlinePaint != null) {
if (outlineStroke != null) {
drawArc(g2, meterArea, minValue, maxValue, outlinePaint,
outlineStroke);
}
drawTick(g2, meterArea, minValue, true);
drawTick(g2, meterArea, maxValue, true);
}
}
/**
* Draws an arc.
*
* @param g2 the graphics device.
* @param area the plot area.
* @param minValue the minimum value.
* @param maxValue the maximum value.
* @param paint the paint.
* @param stroke the stroke.
*/
protected void drawArc(Graphics2D g2, Rectangle2D area, double minValue,
double maxValue, Paint paint, Stroke stroke) {
double startAngle = valueToAngle(maxValue);
double endAngle = valueToAngle(minValue);
double extent = endAngle - startAngle;
double x = area.getX();
double y = area.getY();
double w = area.getWidth();
double h = area.getHeight();
g2.setPaint(paint);
g2.setStroke(stroke);
if (paint != null && stroke != null) {
Arc2D.Double arc = new Arc2D.Double(x, y, w, h, startAngle,
extent, Arc2D.OPEN);
g2.setPaint(paint);
g2.setStroke(stroke);
g2.draw(arc);
}
}
/**
* Fills an arc on the dial between the given values.
*
* @param g2 the graphics device.
* @param area the plot area.
* @param minValue the minimum data value.
* @param maxValue the maximum data value.
* @param paint the background paint (null
not permitted).
* @param dial a flag that indicates whether the arc represents the whole
* dial.
*/
protected void fillArc(Graphics2D g2, Rectangle2D area,
double minValue, double maxValue, Paint paint,
boolean dial) {
if (paint == null) {
throw new IllegalArgumentException("Null 'paint' argument");
}
double startAngle = valueToAngle(maxValue);
double endAngle = valueToAngle(minValue);
double extent = endAngle - startAngle;
double x = area.getX();
double y = area.getY();
double w = area.getWidth();
double h = area.getHeight();
int joinType = Arc2D.OPEN;
if (this.shape == DialShape.PIE) {
joinType = Arc2D.PIE;
}
else if (this.shape == DialShape.CHORD) {
if (dial && this.meterAngle > 180) {
joinType = Arc2D.CHORD;
}
else {
joinType = Arc2D.PIE;
}
}
else if (this.shape == DialShape.CIRCLE) {
joinType = Arc2D.PIE;
if (dial) {
extent = 360;
}
}
else {
throw new IllegalStateException("DialShape not recognised.");
}
g2.setPaint(paint);
Arc2D.Double arc = new Arc2D.Double(x, y, w, h, startAngle, extent,
joinType);
g2.fill(arc);
}
/**
* Translates a data value to an angle on the dial.
*
* @param value the value.
*
* @return The angle on the dial.
*/
public double valueToAngle(double value) {
value = value - this.range.getLowerBound();
double baseAngle = 180 + ((this.meterAngle - 180) / 2);
return baseAngle - ((value / this.range.getLength()) * this.meterAngle);
}
/**
* Draws the ticks that subdivide the overall range.
*
* @param g2 the graphics device.
* @param meterArea the meter area.
* @param minValue the minimum value.
* @param maxValue the maximum value.
*/
protected void drawTicks(Graphics2D g2, Rectangle2D meterArea,
double minValue, double maxValue) {
for (double v = minValue; v <= maxValue; v += this.tickSize) {
drawTick(g2, meterArea, v);
}
}
/**
* Draws a tick.
*
* @param g2 the graphics device.
* @param meterArea the meter area.
* @param value the value.
*/
protected void drawTick(Graphics2D g2, Rectangle2D meterArea,
double value) {
drawTick(g2, meterArea, value, false);
}
/**
* Draws a tick on the dial.
*
* @param g2 the graphics device.
* @param meterArea the meter area.
* @param value the tick value.
* @param label a flag that controls whether or not a value label is drawn.
*/
protected void drawTick(Graphics2D g2, Rectangle2D meterArea,
double value, boolean label) {
double valueAngle = valueToAngle(value);
double meterMiddleX = meterArea.getCenterX();
double meterMiddleY = meterArea.getCenterY();
g2.setPaint(this.tickPaint);
g2.setStroke(new BasicStroke(2.0f));
double valueP2X = 0;
double valueP2Y = 0;
double radius = (meterArea.getWidth() / 2) + DEFAULT_BORDER_SIZE;
double radius1 = radius - 15;
double valueP1X = meterMiddleX
+ (radius * Math.cos(Math.PI * (valueAngle / 180)));
double valueP1Y = meterMiddleY
- (radius * Math.sin(Math.PI * (valueAngle / 180)));
valueP2X = meterMiddleX
+ (radius1 * Math.cos(Math.PI * (valueAngle / 180)));
valueP2Y = meterMiddleY
- (radius1 * Math.sin(Math.PI * (valueAngle / 180)));
Line2D.Double line = new Line2D.Double(valueP1X, valueP1Y, valueP2X,
valueP2Y);
g2.draw(line);
if (this.tickLabelsVisible && label) {
String tickLabel = this.tickLabelFormat.format(value);
g2.setFont(this.tickLabelFont);
g2.setPaint(this.tickLabelPaint);
FontMetrics fm = g2.getFontMetrics();
Rectangle2D tickLabelBounds
= TextUtilities.getTextBounds(tickLabel, g2, fm);
double x = valueP2X;
double y = valueP2Y;
if (valueAngle == 90 || valueAngle == 270) {
x = x - tickLabelBounds.getWidth() / 2;
}
else if (valueAngle < 90 || valueAngle > 270) {
x = x - tickLabelBounds.getWidth();
}
if ((valueAngle > 135 && valueAngle < 225)
|| valueAngle > 315 || valueAngle < 45) {
y = y - tickLabelBounds.getHeight() / 2;
}
else {
y = y + tickLabelBounds.getHeight() / 2;
}
g2.drawString(tickLabel, (float) x, (float) y);
}
}
/**
* Draws the value label just below the center of the dial.
*
* @param g2 the graphics device.
* @param area the plot area.
*/
protected void drawValueLabel(Graphics2D g2, Rectangle2D area) {
g2.setFont(this.valueFont);
g2.setPaint(this.valuePaint);
String valueStr = "No value";
if (this.dataset != null) {
Number n = this.dataset.getValue();
if (n != null) {
valueStr = this.tickLabelFormat.format(n.doubleValue()) + " "
+ this.units;
}
}
float x = (float) area.getCenterX();
float y = (float) area.getCenterY() + DEFAULT_CIRCLE_SIZE;
TextUtilities.drawAlignedString(valueStr, g2, x, y,
TextAnchor.TOP_CENTER);
}
/**
* Returns a short string describing the type of plot.
*
* @return A string describing the type of plot.
*/
public String getPlotType() {
return localizationResources.getString("Meter_Plot");
}
/**
* A zoom method that does nothing. Plots are required to support the
* zoom operation. In the case of a meter plot, it doesn't make sense to
* zoom in or out, so the method is empty.
*
* @param percent The zoom percentage.
*/
public void zoom(double percent) {
// intentionally blank
}
/**
* Tests the plot for equality with an arbitrary object. Note that the
* dataset is ignored for the purposes of testing equality.
*
* @param obj the object (null
permitted).
*
* @return A boolean.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MeterPlot)) {
return false;
}
if (!super.equals(obj)) {
return false;
}
MeterPlot that = (MeterPlot) obj;
if (!ObjectUtilities.equal(this.units, that.units)) {
return false;
}
if (!ObjectUtilities.equal(this.range, that.range)) {
return false;
}
if (!ObjectUtilities.equal(this.intervals, that.intervals)) {
return false;
}
if (!PaintUtilities.equal(this.dialOutlinePaint,
that.dialOutlinePaint)) {
return false;
}
if (this.shape != that.shape) {
return false;
}
if (!PaintUtilities.equal(this.dialBackgroundPaint,
that.dialBackgroundPaint)) {
return false;
}
if (!PaintUtilities.equal(this.needlePaint, that.needlePaint)) {
return false;
}
if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) {
return false;
}
if (!PaintUtilities.equal(this.valuePaint, that.valuePaint)) {
return false;
}
if (!PaintUtilities.equal(this.tickPaint, that.tickPaint)) {
return false;
}
if (this.tickSize != that.tickSize) {
return false;
}
if (this.tickLabelsVisible != that.tickLabelsVisible) {
return false;
}
if (!ObjectUtilities.equal(this.tickLabelFont, that.tickLabelFont)) {
return false;
}
if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) {
return false;
}
if (!ObjectUtilities.equal(this.tickLabelFormat,
that.tickLabelFormat)) {
return false;
}
if (this.drawBorder != that.drawBorder) {
return false;
}
if (this.meterAngle != that.meterAngle) {
return false;
}
return true;
}
/**
* 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.writePaint(this.dialBackgroundPaint, stream);
SerialUtilities.writePaint(this.dialOutlinePaint, stream);
SerialUtilities.writePaint(this.needlePaint, stream);
SerialUtilities.writePaint(this.valuePaint, stream);
SerialUtilities.writePaint(this.tickPaint, stream);
SerialUtilities.writePaint(this.tickLabelPaint, 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.dialBackgroundPaint = SerialUtilities.readPaint(stream);
this.dialOutlinePaint = SerialUtilities.readPaint(stream);
this.needlePaint = SerialUtilities.readPaint(stream);
this.valuePaint = SerialUtilities.readPaint(stream);
this.tickPaint = SerialUtilities.readPaint(stream);
this.tickLabelPaint = SerialUtilities.readPaint(stream);
if (this.dataset != null) {
this.dataset.addChangeListener(this);
}
}
/**
* Returns an independent copy (clone) of the plot. The dataset is NOT
* cloned - both the original and the clone will have a reference to the
* same dataset.
*
* @return A clone.
*
* @throws CloneNotSupportedException if some component of the plot cannot
* be cloned.
*/
public Object clone() throws CloneNotSupportedException {
MeterPlot clone = (MeterPlot) super.clone();
clone.tickLabelFormat = (NumberFormat) this.tickLabelFormat.clone();
// the following relies on the fact that the intervals are immutable
clone.intervals = new java.util.ArrayList(this.intervals);
if (clone.dataset != null) {
clone.dataset.addChangeListener(clone);
}
return clone;
}
}