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: * MeterPlot.java
0029: * --------------
0030: * (C) Copyright 2000-2007, by Hari and Contributors.
0031: *
0032: * Original Author: Hari (ourhari@hotmail.com);
0033: * Contributor(s): David Gilbert (for Object Refinery Limited);
0034: * Bob Orchard;
0035: * Arnaud Lelievre;
0036: * Nicolas Brodu;
0037: * David Bastend;
0038: *
0039: * $Id: MeterPlot.java,v 1.13.2.10 2007/05/18 10:28:21 mungady Exp $
0040: *
0041: * Changes
0042: * -------
0043: * 01-Apr-2002 : Version 1, contributed by Hari (DG);
0044: * 23-Apr-2002 : Moved dataset from JFreeChart to Plot (DG);
0045: * 22-Aug-2002 : Added changes suggest by Bob Orchard, changed Color to Paint
0046: * for consistency, plus added Javadoc comments (DG);
0047: * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
0048: * 23-Jan-2003 : Removed one constructor (DG);
0049: * 26-Mar-2003 : Implemented Serializable (DG);
0050: * 20-Aug-2003 : Changed dataset from MeterDataset --> ValueDataset, added
0051: * equals() method,
0052: * 08-Sep-2003 : Added internationalization via use of properties
0053: * resourceBundle (RFE 690236) (AL);
0054: * implemented Cloneable, and various other changes (DG);
0055: * 08-Sep-2003 : Added serialization methods (NB);
0056: * 11-Sep-2003 : Added cloning support (NB);
0057: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
0058: * 25-Sep-2003 : Fix useless cloning. Correct dataset listener registration in
0059: * constructor. (NB)
0060: * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
0061: * 17-Jan-2004 : Changed to allow dialBackgroundPaint to be set to null - see
0062: * bug 823628 (DG);
0063: * 07-Apr-2004 : Changed string bounds calculation (DG);
0064: * 12-May-2004 : Added tickLabelFormat attribute - see RFE 949566. Also
0065: * updated the equals() method (DG);
0066: * 02-Nov-2004 : Added sanity checks for range, and only draw the needle if the
0067: * value is contained within the overall range - see bug report
0068: * 1056047 (DG);
0069: * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
0070: * release (DG);
0071: * 02-Feb-2005 : Added optional background paint for each region (DG);
0072: * 22-Mar-2005 : Removed 'normal', 'warning' and 'critical' regions and put in
0073: * facility to define an arbitrary number of MeterIntervals,
0074: * based on a contribution by David Bastend (DG);
0075: * 20-Apr-2005 : Small update for change to LegendItem constructors (DG);
0076: * 05-May-2005 : Updated draw() method parameters (DG);
0077: * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
0078: * 10-Nov-2005 : Added tickPaint, tickSize and valuePaint attributes, and
0079: * put value label drawing code into a separate method (DG);
0080: * ------------- JFREECHART 1.0.x ---------------------------------------------
0081: * 05-Mar-2007 : Restore clip region correctly (see bug 1667750) (DG);
0082: * 18-May-2007 : Set dataset for LegendItem (DG);
0083: *
0084: */
0085:
0086: package org.jfree.chart.plot;
0087:
0088: import java.awt.AlphaComposite;
0089: import java.awt.BasicStroke;
0090: import java.awt.Color;
0091: import java.awt.Composite;
0092: import java.awt.Font;
0093: import java.awt.FontMetrics;
0094: import java.awt.Graphics2D;
0095: import java.awt.Paint;
0096: import java.awt.Polygon;
0097: import java.awt.Shape;
0098: import java.awt.Stroke;
0099: import java.awt.geom.Arc2D;
0100: import java.awt.geom.Ellipse2D;
0101: import java.awt.geom.Line2D;
0102: import java.awt.geom.Point2D;
0103: import java.awt.geom.Rectangle2D;
0104: import java.io.IOException;
0105: import java.io.ObjectInputStream;
0106: import java.io.ObjectOutputStream;
0107: import java.io.Serializable;
0108: import java.text.NumberFormat;
0109: import java.util.Collections;
0110: import java.util.Iterator;
0111: import java.util.List;
0112: import java.util.ResourceBundle;
0113:
0114: import org.jfree.chart.LegendItem;
0115: import org.jfree.chart.LegendItemCollection;
0116: import org.jfree.chart.event.PlotChangeEvent;
0117: import org.jfree.data.Range;
0118: import org.jfree.data.general.DatasetChangeEvent;
0119: import org.jfree.data.general.ValueDataset;
0120: import org.jfree.io.SerialUtilities;
0121: import org.jfree.text.TextUtilities;
0122: import org.jfree.ui.RectangleInsets;
0123: import org.jfree.ui.TextAnchor;
0124: import org.jfree.util.ObjectUtilities;
0125: import org.jfree.util.PaintUtilities;
0126:
0127: /**
0128: * A plot that displays a single value in the form of a needle on a dial.
0129: * Defined ranges (for example, 'normal', 'warning' and 'critical') can be
0130: * highlighted on the dial.
0131: */
0132: public class MeterPlot extends Plot implements Serializable, Cloneable {
0133:
0134: /** For serialization. */
0135: private static final long serialVersionUID = 2987472457734470962L;
0136:
0137: /** The default background paint. */
0138: static final Paint DEFAULT_DIAL_BACKGROUND_PAINT = Color.black;
0139:
0140: /** The default needle paint. */
0141: static final Paint DEFAULT_NEEDLE_PAINT = Color.green;
0142:
0143: /** The default value font. */
0144: static final Font DEFAULT_VALUE_FONT = new Font("SansSerif",
0145: Font.BOLD, 12);
0146:
0147: /** The default value paint. */
0148: static final Paint DEFAULT_VALUE_PAINT = Color.yellow;
0149:
0150: /** The default meter angle. */
0151: public static final int DEFAULT_METER_ANGLE = 270;
0152:
0153: /** The default border size. */
0154: public static final float DEFAULT_BORDER_SIZE = 3f;
0155:
0156: /** The default circle size. */
0157: public static final float DEFAULT_CIRCLE_SIZE = 10f;
0158:
0159: /** The default label font. */
0160: public static final Font DEFAULT_LABEL_FONT = new Font("SansSerif",
0161: Font.BOLD, 10);
0162:
0163: /** The dataset (contains a single value). */
0164: private ValueDataset dataset;
0165:
0166: /** The dial shape (background shape). */
0167: private DialShape shape;
0168:
0169: /** The dial extent (measured in degrees). */
0170: private int meterAngle;
0171:
0172: /** The overall range of data values on the dial. */
0173: private Range range;
0174:
0175: /** The tick size. */
0176: private double tickSize;
0177:
0178: /** The paint used to draw the ticks. */
0179: private transient Paint tickPaint;
0180:
0181: /** The units displayed on the dial. */
0182: private String units;
0183:
0184: /** The font for the value displayed in the center of the dial. */
0185: private Font valueFont;
0186:
0187: /** The paint for the value displayed in the center of the dial. */
0188: private transient Paint valuePaint;
0189:
0190: /** A flag that controls whether or not the border is drawn. */
0191: private boolean drawBorder;
0192:
0193: /** The outline paint. */
0194: private transient Paint dialOutlinePaint;
0195:
0196: /** The paint for the dial background. */
0197: private transient Paint dialBackgroundPaint;
0198:
0199: /** The paint for the needle. */
0200: private transient Paint needlePaint;
0201:
0202: /** A flag that controls whether or not the tick labels are visible. */
0203: private boolean tickLabelsVisible;
0204:
0205: /** The tick label font. */
0206: private Font tickLabelFont;
0207:
0208: /** The tick label paint. */
0209: private transient Paint tickLabelPaint;
0210:
0211: /** The tick label format. */
0212: private NumberFormat tickLabelFormat;
0213:
0214: /** The resourceBundle for the localization. */
0215: protected static ResourceBundle localizationResources = ResourceBundle
0216: .getBundle("org.jfree.chart.plot.LocalizationBundle");
0217:
0218: /**
0219: * A (possibly empty) list of the {@link MeterInterval}s to be highlighted
0220: * on the dial.
0221: */
0222: private List intervals;
0223:
0224: /**
0225: * Creates a new plot with a default range of <code>0</code> to
0226: * <code>100</code> and no value to display.
0227: */
0228: public MeterPlot() {
0229: this (null);
0230: }
0231:
0232: /**
0233: * Creates a new plot that displays the value from the supplied dataset.
0234: *
0235: * @param dataset the dataset (<code>null</code> permitted).
0236: */
0237: public MeterPlot(ValueDataset dataset) {
0238: super ();
0239: this .shape = DialShape.CIRCLE;
0240: this .meterAngle = DEFAULT_METER_ANGLE;
0241: this .range = new Range(0.0, 100.0);
0242: this .tickSize = 10.0;
0243: this .tickPaint = Color.white;
0244: this .units = "Units";
0245: this .needlePaint = MeterPlot.DEFAULT_NEEDLE_PAINT;
0246: this .tickLabelsVisible = true;
0247: this .tickLabelFont = MeterPlot.DEFAULT_LABEL_FONT;
0248: this .tickLabelPaint = Color.black;
0249: this .tickLabelFormat = NumberFormat.getInstance();
0250: this .valueFont = MeterPlot.DEFAULT_VALUE_FONT;
0251: this .valuePaint = MeterPlot.DEFAULT_VALUE_PAINT;
0252: this .dialBackgroundPaint = MeterPlot.DEFAULT_DIAL_BACKGROUND_PAINT;
0253: this .intervals = new java.util.ArrayList();
0254: setDataset(dataset);
0255: }
0256:
0257: /**
0258: * Returns the dial shape. The default is {@link DialShape#CIRCLE}).
0259: *
0260: * @return The dial shape (never <code>null</code>).
0261: *
0262: * @see #setDialShape(DialShape)
0263: */
0264: public DialShape getDialShape() {
0265: return this .shape;
0266: }
0267:
0268: /**
0269: * Sets the dial shape and sends a {@link PlotChangeEvent} to all
0270: * registered listeners.
0271: *
0272: * @param shape the shape (<code>null</code> not permitted).
0273: *
0274: * @see #getDialShape()
0275: */
0276: public void setDialShape(DialShape shape) {
0277: if (shape == null) {
0278: throw new IllegalArgumentException("Null 'shape' argument.");
0279: }
0280: this .shape = shape;
0281: notifyListeners(new PlotChangeEvent(this ));
0282: }
0283:
0284: /**
0285: * Returns the meter angle in degrees. This defines, in part, the shape
0286: * of the dial. The default is 270 degrees.
0287: *
0288: * @return The meter angle (in degrees).
0289: *
0290: * @see #setMeterAngle(int)
0291: */
0292: public int getMeterAngle() {
0293: return this .meterAngle;
0294: }
0295:
0296: /**
0297: * Sets the angle (in degrees) for the whole range of the dial and sends
0298: * a {@link PlotChangeEvent} to all registered listeners.
0299: *
0300: * @param angle the angle (in degrees, in the range 1-360).
0301: *
0302: * @see #getMeterAngle()
0303: */
0304: public void setMeterAngle(int angle) {
0305: if (angle < 1 || angle > 360) {
0306: throw new IllegalArgumentException("Invalid 'angle' ("
0307: + angle + ")");
0308: }
0309: this .meterAngle = angle;
0310: notifyListeners(new PlotChangeEvent(this ));
0311: }
0312:
0313: /**
0314: * Returns the overall range for the dial.
0315: *
0316: * @return The overall range (never <code>null</code>).
0317: *
0318: * @see #setRange(Range)
0319: */
0320: public Range getRange() {
0321: return this .range;
0322: }
0323:
0324: /**
0325: * Sets the range for the dial and sends a {@link PlotChangeEvent} to all
0326: * registered listeners.
0327: *
0328: * @param range the range (<code>null</code> not permitted and zero-length
0329: * ranges not permitted).
0330: *
0331: * @see #getRange()
0332: */
0333: public void setRange(Range range) {
0334: if (range == null) {
0335: throw new IllegalArgumentException("Null 'range' argument.");
0336: }
0337: if (!(range.getLength() > 0.0)) {
0338: throw new IllegalArgumentException(
0339: "Range length must be positive.");
0340: }
0341: this .range = range;
0342: notifyListeners(new PlotChangeEvent(this ));
0343: }
0344:
0345: /**
0346: * Returns the tick size (the interval between ticks on the dial).
0347: *
0348: * @return The tick size.
0349: *
0350: * @see #setTickSize(double)
0351: */
0352: public double getTickSize() {
0353: return this .tickSize;
0354: }
0355:
0356: /**
0357: * Sets the tick size and sends a {@link PlotChangeEvent} to all
0358: * registered listeners.
0359: *
0360: * @param size the tick size (must be > 0).
0361: *
0362: * @see #getTickSize()
0363: */
0364: public void setTickSize(double size) {
0365: if (size <= 0) {
0366: throw new IllegalArgumentException("Requires 'size' > 0.");
0367: }
0368: this .tickSize = size;
0369: notifyListeners(new PlotChangeEvent(this ));
0370: }
0371:
0372: /**
0373: * Returns the paint used to draw the ticks around the dial.
0374: *
0375: * @return The paint used to draw the ticks around the dial (never
0376: * <code>null</code>).
0377: *
0378: * @see #setTickPaint(Paint)
0379: */
0380: public Paint getTickPaint() {
0381: return this .tickPaint;
0382: }
0383:
0384: /**
0385: * Sets the paint used to draw the tick labels around the dial and sends
0386: * a {@link PlotChangeEvent} to all registered listeners.
0387: *
0388: * @param paint the paint (<code>null</code> not permitted).
0389: *
0390: * @see #getTickPaint()
0391: */
0392: public void setTickPaint(Paint paint) {
0393: if (paint == null) {
0394: throw new IllegalArgumentException("Null 'paint' argument.");
0395: }
0396: this .tickPaint = paint;
0397: notifyListeners(new PlotChangeEvent(this ));
0398: }
0399:
0400: /**
0401: * Returns a string describing the units for the dial.
0402: *
0403: * @return The units (possibly <code>null</code>).
0404: *
0405: * @see #setUnits(String)
0406: */
0407: public String getUnits() {
0408: return this .units;
0409: }
0410:
0411: /**
0412: * Sets the units for the dial and sends a {@link PlotChangeEvent} to all
0413: * registered listeners.
0414: *
0415: * @param units the units (<code>null</code> permitted).
0416: *
0417: * @see #getUnits()
0418: */
0419: public void setUnits(String units) {
0420: this .units = units;
0421: notifyListeners(new PlotChangeEvent(this ));
0422: }
0423:
0424: /**
0425: * Returns the paint for the needle.
0426: *
0427: * @return The paint (never <code>null</code>).
0428: *
0429: * @see #setNeedlePaint(Paint)
0430: */
0431: public Paint getNeedlePaint() {
0432: return this .needlePaint;
0433: }
0434:
0435: /**
0436: * Sets the paint used to display the needle and sends a
0437: * {@link PlotChangeEvent} to all registered listeners.
0438: *
0439: * @param paint the paint (<code>null</code> not permitted).
0440: *
0441: * @see #getNeedlePaint()
0442: */
0443: public void setNeedlePaint(Paint paint) {
0444: if (paint == null) {
0445: throw new IllegalArgumentException("Null 'paint' argument.");
0446: }
0447: this .needlePaint = paint;
0448: notifyListeners(new PlotChangeEvent(this ));
0449: }
0450:
0451: /**
0452: * Returns the flag that determines whether or not tick labels are visible.
0453: *
0454: * @return The flag.
0455: *
0456: * @see #setTickLabelsVisible(boolean)
0457: */
0458: public boolean getTickLabelsVisible() {
0459: return this .tickLabelsVisible;
0460: }
0461:
0462: /**
0463: * Sets the flag that controls whether or not the tick labels are visible
0464: * and sends a {@link PlotChangeEvent} to all registered listeners.
0465: *
0466: * @param visible the flag.
0467: *
0468: * @see #getTickLabelsVisible()
0469: */
0470: public void setTickLabelsVisible(boolean visible) {
0471: if (this .tickLabelsVisible != visible) {
0472: this .tickLabelsVisible = visible;
0473: notifyListeners(new PlotChangeEvent(this ));
0474: }
0475: }
0476:
0477: /**
0478: * Returns the tick label font.
0479: *
0480: * @return The font (never <code>null</code>).
0481: *
0482: * @see #setTickLabelFont(Font)
0483: */
0484: public Font getTickLabelFont() {
0485: return this .tickLabelFont;
0486: }
0487:
0488: /**
0489: * Sets the tick label font and sends a {@link PlotChangeEvent} to all
0490: * registered listeners.
0491: *
0492: * @param font the font (<code>null</code> not permitted).
0493: *
0494: * @see #getTickLabelFont()
0495: */
0496: public void setTickLabelFont(Font font) {
0497: if (font == null) {
0498: throw new IllegalArgumentException("Null 'font' argument.");
0499: }
0500: if (!this .tickLabelFont.equals(font)) {
0501: this .tickLabelFont = font;
0502: notifyListeners(new PlotChangeEvent(this ));
0503: }
0504: }
0505:
0506: /**
0507: * Returns the tick label paint.
0508: *
0509: * @return The paint (never <code>null</code>).
0510: *
0511: * @see #setTickLabelPaint(Paint)
0512: */
0513: public Paint getTickLabelPaint() {
0514: return this .tickLabelPaint;
0515: }
0516:
0517: /**
0518: * Sets the tick label paint and sends a {@link PlotChangeEvent} to all
0519: * registered listeners.
0520: *
0521: * @param paint the paint (<code>null</code> not permitted).
0522: *
0523: * @see #getTickLabelPaint()
0524: */
0525: public void setTickLabelPaint(Paint paint) {
0526: if (paint == null) {
0527: throw new IllegalArgumentException("Null 'paint' argument.");
0528: }
0529: if (!this .tickLabelPaint.equals(paint)) {
0530: this .tickLabelPaint = paint;
0531: notifyListeners(new PlotChangeEvent(this ));
0532: }
0533: }
0534:
0535: /**
0536: * Returns the tick label format.
0537: *
0538: * @return The tick label format (never <code>null</code>).
0539: *
0540: * @see #setTickLabelFormat(NumberFormat)
0541: */
0542: public NumberFormat getTickLabelFormat() {
0543: return this .tickLabelFormat;
0544: }
0545:
0546: /**
0547: * Sets the format for the tick labels and sends a {@link PlotChangeEvent}
0548: * to all registered listeners.
0549: *
0550: * @param format the format (<code>null</code> not permitted).
0551: *
0552: * @see #getTickLabelFormat()
0553: */
0554: public void setTickLabelFormat(NumberFormat format) {
0555: if (format == null) {
0556: throw new IllegalArgumentException(
0557: "Null 'format' argument.");
0558: }
0559: this .tickLabelFormat = format;
0560: notifyListeners(new PlotChangeEvent(this ));
0561: }
0562:
0563: /**
0564: * Returns the font for the value label.
0565: *
0566: * @return The font (never <code>null</code>).
0567: *
0568: * @see #setValueFont(Font)
0569: */
0570: public Font getValueFont() {
0571: return this .valueFont;
0572: }
0573:
0574: /**
0575: * Sets the font used to display the value label and sends a
0576: * {@link PlotChangeEvent} to all registered listeners.
0577: *
0578: * @param font the font (<code>null</code> not permitted).
0579: *
0580: * @see #getValueFont()
0581: */
0582: public void setValueFont(Font font) {
0583: if (font == null) {
0584: throw new IllegalArgumentException("Null 'font' argument.");
0585: }
0586: this .valueFont = font;
0587: notifyListeners(new PlotChangeEvent(this ));
0588: }
0589:
0590: /**
0591: * Returns the paint for the value label.
0592: *
0593: * @return The paint (never <code>null</code>).
0594: *
0595: * @see #setValuePaint(Paint)
0596: */
0597: public Paint getValuePaint() {
0598: return this .valuePaint;
0599: }
0600:
0601: /**
0602: * Sets the paint used to display the value label and sends a
0603: * {@link PlotChangeEvent} to all registered listeners.
0604: *
0605: * @param paint the paint (<code>null</code> not permitted).
0606: *
0607: * @see #getValuePaint()
0608: */
0609: public void setValuePaint(Paint paint) {
0610: if (paint == null) {
0611: throw new IllegalArgumentException("Null 'paint' argument.");
0612: }
0613: this .valuePaint = paint;
0614: notifyListeners(new PlotChangeEvent(this ));
0615: }
0616:
0617: /**
0618: * Returns the paint for the dial background.
0619: *
0620: * @return The paint (possibly <code>null</code>).
0621: *
0622: * @see #setDialBackgroundPaint(Paint)
0623: */
0624: public Paint getDialBackgroundPaint() {
0625: return this .dialBackgroundPaint;
0626: }
0627:
0628: /**
0629: * Sets the paint used to fill the dial background. Set this to
0630: * <code>null</code> for no background.
0631: *
0632: * @param paint the paint (<code>null</code> permitted).
0633: *
0634: * @see #getDialBackgroundPaint()
0635: */
0636: public void setDialBackgroundPaint(Paint paint) {
0637: this .dialBackgroundPaint = paint;
0638: notifyListeners(new PlotChangeEvent(this ));
0639: }
0640:
0641: /**
0642: * Returns a flag that controls whether or not a rectangular border is
0643: * drawn around the plot area.
0644: *
0645: * @return A flag.
0646: *
0647: * @see #setDrawBorder(boolean)
0648: */
0649: public boolean getDrawBorder() {
0650: return this .drawBorder;
0651: }
0652:
0653: /**
0654: * Sets the flag that controls whether or not a rectangular border is drawn
0655: * around the plot area and sends a {@link PlotChangeEvent} to all
0656: * registered listeners.
0657: *
0658: * @param draw the flag.
0659: *
0660: * @see #getDrawBorder()
0661: */
0662: public void setDrawBorder(boolean draw) {
0663: // TODO: fix output when this flag is set to true
0664: this .drawBorder = draw;
0665: notifyListeners(new PlotChangeEvent(this ));
0666: }
0667:
0668: /**
0669: * Returns the dial outline paint.
0670: *
0671: * @return The paint.
0672: *
0673: * @see #setDialOutlinePaint(Paint)
0674: */
0675: public Paint getDialOutlinePaint() {
0676: return this .dialOutlinePaint;
0677: }
0678:
0679: /**
0680: * Sets the dial outline paint and sends a {@link PlotChangeEvent} to all
0681: * registered listeners.
0682: *
0683: * @param paint the paint.
0684: *
0685: * @see #getDialOutlinePaint()
0686: */
0687: public void setDialOutlinePaint(Paint paint) {
0688: this .dialOutlinePaint = paint;
0689: notifyListeners(new PlotChangeEvent(this ));
0690: }
0691:
0692: /**
0693: * Returns the dataset for the plot.
0694: *
0695: * @return The dataset (possibly <code>null</code>).
0696: *
0697: * @see #setDataset(ValueDataset)
0698: */
0699: public ValueDataset getDataset() {
0700: return this .dataset;
0701: }
0702:
0703: /**
0704: * Sets the dataset for the plot, replacing the existing dataset if there
0705: * is one, and triggers a {@link PlotChangeEvent}.
0706: *
0707: * @param dataset the dataset (<code>null</code> permitted).
0708: *
0709: * @see #getDataset()
0710: */
0711: public void setDataset(ValueDataset dataset) {
0712:
0713: // if there is an existing dataset, remove the plot from the list of
0714: // change listeners...
0715: ValueDataset existing = this .dataset;
0716: if (existing != null) {
0717: existing.removeChangeListener(this );
0718: }
0719:
0720: // set the new dataset, and register the chart as a change listener...
0721: this .dataset = dataset;
0722: if (dataset != null) {
0723: setDatasetGroup(dataset.getGroup());
0724: dataset.addChangeListener(this );
0725: }
0726:
0727: // send a dataset change event to self...
0728: DatasetChangeEvent event = new DatasetChangeEvent(this , dataset);
0729: datasetChanged(event);
0730:
0731: }
0732:
0733: /**
0734: * Returns an unmodifiable list of the intervals for the plot.
0735: *
0736: * @return A list.
0737: *
0738: * @see #addInterval(MeterInterval)
0739: */
0740: public List getIntervals() {
0741: return Collections.unmodifiableList(this .intervals);
0742: }
0743:
0744: /**
0745: * Adds an interval and sends a {@link PlotChangeEvent} to all registered
0746: * listeners.
0747: *
0748: * @param interval the interval (<code>null</code> not permitted).
0749: *
0750: * @see #getIntervals()
0751: * @see #clearIntervals()
0752: */
0753: public void addInterval(MeterInterval interval) {
0754: if (interval == null) {
0755: throw new IllegalArgumentException(
0756: "Null 'interval' argument.");
0757: }
0758: this .intervals.add(interval);
0759: notifyListeners(new PlotChangeEvent(this ));
0760: }
0761:
0762: /**
0763: * Clears the intervals for the plot and sends a {@link PlotChangeEvent} to
0764: * all registered listeners.
0765: *
0766: * @see #addInterval(MeterInterval)
0767: */
0768: public void clearIntervals() {
0769: this .intervals.clear();
0770: notifyListeners(new PlotChangeEvent(this ));
0771: }
0772:
0773: /**
0774: * Returns an item for each interval.
0775: *
0776: * @return A collection of legend items.
0777: */
0778: public LegendItemCollection getLegendItems() {
0779: LegendItemCollection result = new LegendItemCollection();
0780: Iterator iterator = this .intervals.iterator();
0781: while (iterator.hasNext()) {
0782: MeterInterval mi = (MeterInterval) iterator.next();
0783: Paint color = mi.getBackgroundPaint();
0784: if (color == null) {
0785: color = mi.getOutlinePaint();
0786: }
0787: LegendItem item = new LegendItem(mi.getLabel(), mi
0788: .getLabel(), null, null, new Rectangle2D.Double(
0789: -4.0, -4.0, 8.0, 8.0), color);
0790: item.setDataset(getDataset());
0791: result.add(item);
0792: }
0793: return result;
0794: }
0795:
0796: /**
0797: * Draws the plot on a Java 2D graphics device (such as the screen or a
0798: * printer).
0799: *
0800: * @param g2 the graphics device.
0801: * @param area the area within which the plot should be drawn.
0802: * @param anchor the anchor point (<code>null</code> permitted).
0803: * @param parentState the state from the parent plot, if there is one.
0804: * @param info collects info about the drawing.
0805: */
0806: public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
0807: PlotState parentState, PlotRenderingInfo info) {
0808:
0809: if (info != null) {
0810: info.setPlotArea(area);
0811: }
0812:
0813: // adjust for insets...
0814: RectangleInsets insets = getInsets();
0815: insets.trim(area);
0816:
0817: area.setRect(area.getX() + 4, area.getY() + 4,
0818: area.getWidth() - 8, area.getHeight() - 8);
0819:
0820: // draw the background
0821: if (this .drawBorder) {
0822: drawBackground(g2, area);
0823: }
0824:
0825: // adjust the plot area by the interior spacing value
0826: double gapHorizontal = (2 * DEFAULT_BORDER_SIZE);
0827: double gapVertical = (2 * DEFAULT_BORDER_SIZE);
0828: double meterX = area.getX() + gapHorizontal / 2;
0829: double meterY = area.getY() + gapVertical / 2;
0830: double meterW = area.getWidth() - gapHorizontal;
0831: double meterH = area.getHeight()
0832: - gapVertical
0833: + ((this .meterAngle <= 180)
0834: && (this .shape != DialShape.CIRCLE) ? area
0835: .getHeight() / 1.25 : 0);
0836:
0837: double min = Math.min(meterW, meterH) / 2;
0838: meterX = (meterX + meterX + meterW) / 2 - min;
0839: meterY = (meterY + meterY + meterH) / 2 - min;
0840: meterW = 2 * min;
0841: meterH = 2 * min;
0842:
0843: Rectangle2D meterArea = new Rectangle2D.Double(meterX, meterY,
0844: meterW, meterH);
0845:
0846: Rectangle2D.Double originalArea = new Rectangle2D.Double(
0847: meterArea.getX() - 4, meterArea.getY() - 4, meterArea
0848: .getWidth() + 8, meterArea.getHeight() + 8);
0849:
0850: double meterMiddleX = meterArea.getCenterX();
0851: double meterMiddleY = meterArea.getCenterY();
0852:
0853: // plot the data (unless the dataset is null)...
0854: ValueDataset data = getDataset();
0855: if (data != null) {
0856: double dataMin = this .range.getLowerBound();
0857: double dataMax = this .range.getUpperBound();
0858:
0859: Shape savedClip = g2.getClip();
0860: g2.clip(originalArea);
0861: Composite originalComposite = g2.getComposite();
0862: g2.setComposite(AlphaComposite.getInstance(
0863: AlphaComposite.SRC_OVER, getForegroundAlpha()));
0864:
0865: if (this .dialBackgroundPaint != null) {
0866: fillArc(g2, originalArea, dataMin, dataMax,
0867: this .dialBackgroundPaint, true);
0868: }
0869: drawTicks(g2, meterArea, dataMin, dataMax);
0870: drawArcForInterval(g2, meterArea, new MeterInterval("",
0871: this .range, this .dialOutlinePaint, new BasicStroke(
0872: 1.0f), null));
0873:
0874: Iterator iterator = this .intervals.iterator();
0875: while (iterator.hasNext()) {
0876: MeterInterval interval = (MeterInterval) iterator
0877: .next();
0878: drawArcForInterval(g2, meterArea, interval);
0879: }
0880:
0881: Number n = data.getValue();
0882: if (n != null) {
0883: double value = n.doubleValue();
0884: drawValueLabel(g2, meterArea);
0885:
0886: if (this .range.contains(value)) {
0887: g2.setPaint(this .needlePaint);
0888: g2.setStroke(new BasicStroke(2.0f));
0889:
0890: double radius = (meterArea.getWidth() / 2)
0891: + DEFAULT_BORDER_SIZE + 15;
0892: double valueAngle = valueToAngle(value);
0893: double valueP1 = meterMiddleX
0894: + (radius * Math.cos(Math.PI
0895: * (valueAngle / 180)));
0896: double valueP2 = meterMiddleY
0897: - (radius * Math.sin(Math.PI
0898: * (valueAngle / 180)));
0899:
0900: Polygon arrow = new Polygon();
0901: if ((valueAngle > 135 && valueAngle < 225)
0902: || (valueAngle < 45 && valueAngle > -45)) {
0903:
0904: double valueP3 = (meterMiddleY - DEFAULT_CIRCLE_SIZE / 4);
0905: double valueP4 = (meterMiddleY + DEFAULT_CIRCLE_SIZE / 4);
0906: arrow.addPoint((int) meterMiddleX,
0907: (int) valueP3);
0908: arrow.addPoint((int) meterMiddleX,
0909: (int) valueP4);
0910:
0911: } else {
0912: arrow
0913: .addPoint(
0914: (int) (meterMiddleX - DEFAULT_CIRCLE_SIZE / 4),
0915: (int) meterMiddleY);
0916: arrow
0917: .addPoint(
0918: (int) (meterMiddleX + DEFAULT_CIRCLE_SIZE / 4),
0919: (int) meterMiddleY);
0920: }
0921: arrow.addPoint((int) valueP1, (int) valueP2);
0922: g2.fill(arrow);
0923:
0924: Ellipse2D circle = new Ellipse2D.Double(
0925: meterMiddleX - DEFAULT_CIRCLE_SIZE / 2,
0926: meterMiddleY - DEFAULT_CIRCLE_SIZE / 2,
0927: DEFAULT_CIRCLE_SIZE, DEFAULT_CIRCLE_SIZE);
0928: g2.fill(circle);
0929: }
0930: }
0931:
0932: g2.setClip(savedClip);
0933: g2.setComposite(originalComposite);
0934:
0935: }
0936: if (this .drawBorder) {
0937: drawOutline(g2, area);
0938: }
0939:
0940: }
0941:
0942: /**
0943: * Draws the arc to represent an interval.
0944: *
0945: * @param g2 the graphics device.
0946: * @param meterArea the drawing area.
0947: * @param interval the interval.
0948: */
0949: protected void drawArcForInterval(Graphics2D g2,
0950: Rectangle2D meterArea, MeterInterval interval) {
0951:
0952: double minValue = interval.getRange().getLowerBound();
0953: double maxValue = interval.getRange().getUpperBound();
0954: Paint outlinePaint = interval.getOutlinePaint();
0955: Stroke outlineStroke = interval.getOutlineStroke();
0956: Paint backgroundPaint = interval.getBackgroundPaint();
0957:
0958: if (backgroundPaint != null) {
0959: fillArc(g2, meterArea, minValue, maxValue, backgroundPaint,
0960: false);
0961: }
0962: if (outlinePaint != null) {
0963: if (outlineStroke != null) {
0964: drawArc(g2, meterArea, minValue, maxValue,
0965: outlinePaint, outlineStroke);
0966: }
0967: drawTick(g2, meterArea, minValue, true);
0968: drawTick(g2, meterArea, maxValue, true);
0969: }
0970: }
0971:
0972: /**
0973: * Draws an arc.
0974: *
0975: * @param g2 the graphics device.
0976: * @param area the plot area.
0977: * @param minValue the minimum value.
0978: * @param maxValue the maximum value.
0979: * @param paint the paint.
0980: * @param stroke the stroke.
0981: */
0982: protected void drawArc(Graphics2D g2, Rectangle2D area,
0983: double minValue, double maxValue, Paint paint, Stroke stroke) {
0984:
0985: double startAngle = valueToAngle(maxValue);
0986: double endAngle = valueToAngle(minValue);
0987: double extent = endAngle - startAngle;
0988:
0989: double x = area.getX();
0990: double y = area.getY();
0991: double w = area.getWidth();
0992: double h = area.getHeight();
0993: g2.setPaint(paint);
0994: g2.setStroke(stroke);
0995:
0996: if (paint != null && stroke != null) {
0997: Arc2D.Double arc = new Arc2D.Double(x, y, w, h, startAngle,
0998: extent, Arc2D.OPEN);
0999: g2.setPaint(paint);
1000: g2.setStroke(stroke);
1001: g2.draw(arc);
1002: }
1003:
1004: }
1005:
1006: /**
1007: * Fills an arc on the dial between the given values.
1008: *
1009: * @param g2 the graphics device.
1010: * @param area the plot area.
1011: * @param minValue the minimum data value.
1012: * @param maxValue the maximum data value.
1013: * @param paint the background paint (<code>null</code> not permitted).
1014: * @param dial a flag that indicates whether the arc represents the whole
1015: * dial.
1016: */
1017: protected void fillArc(Graphics2D g2, Rectangle2D area,
1018: double minValue, double maxValue, Paint paint, boolean dial) {
1019: if (paint == null) {
1020: throw new IllegalArgumentException("Null 'paint' argument");
1021: }
1022: double startAngle = valueToAngle(maxValue);
1023: double endAngle = valueToAngle(minValue);
1024: double extent = endAngle - startAngle;
1025:
1026: double x = area.getX();
1027: double y = area.getY();
1028: double w = area.getWidth();
1029: double h = area.getHeight();
1030: int joinType = Arc2D.OPEN;
1031: if (this .shape == DialShape.PIE) {
1032: joinType = Arc2D.PIE;
1033: } else if (this .shape == DialShape.CHORD) {
1034: if (dial && this .meterAngle > 180) {
1035: joinType = Arc2D.CHORD;
1036: } else {
1037: joinType = Arc2D.PIE;
1038: }
1039: } else if (this .shape == DialShape.CIRCLE) {
1040: joinType = Arc2D.PIE;
1041: if (dial) {
1042: extent = 360;
1043: }
1044: } else {
1045: throw new IllegalStateException("DialShape not recognised.");
1046: }
1047:
1048: g2.setPaint(paint);
1049: Arc2D.Double arc = new Arc2D.Double(x, y, w, h, startAngle,
1050: extent, joinType);
1051: g2.fill(arc);
1052: }
1053:
1054: /**
1055: * Translates a data value to an angle on the dial.
1056: *
1057: * @param value the value.
1058: *
1059: * @return The angle on the dial.
1060: */
1061: public double valueToAngle(double value) {
1062: value = value - this .range.getLowerBound();
1063: double baseAngle = 180 + ((this .meterAngle - 180) / 2);
1064: return baseAngle
1065: - ((value / this .range.getLength()) * this .meterAngle);
1066: }
1067:
1068: /**
1069: * Draws the ticks that subdivide the overall range.
1070: *
1071: * @param g2 the graphics device.
1072: * @param meterArea the meter area.
1073: * @param minValue the minimum value.
1074: * @param maxValue the maximum value.
1075: */
1076: protected void drawTicks(Graphics2D g2, Rectangle2D meterArea,
1077: double minValue, double maxValue) {
1078: for (double v = minValue; v <= maxValue; v += this .tickSize) {
1079: drawTick(g2, meterArea, v);
1080: }
1081: }
1082:
1083: /**
1084: * Draws a tick.
1085: *
1086: * @param g2 the graphics device.
1087: * @param meterArea the meter area.
1088: * @param value the value.
1089: */
1090: protected void drawTick(Graphics2D g2, Rectangle2D meterArea,
1091: double value) {
1092: drawTick(g2, meterArea, value, false);
1093: }
1094:
1095: /**
1096: * Draws a tick on the dial.
1097: *
1098: * @param g2 the graphics device.
1099: * @param meterArea the meter area.
1100: * @param value the tick value.
1101: * @param label a flag that controls whether or not a value label is drawn.
1102: */
1103: protected void drawTick(Graphics2D g2, Rectangle2D meterArea,
1104: double value, boolean label) {
1105:
1106: double valueAngle = valueToAngle(value);
1107:
1108: double meterMiddleX = meterArea.getCenterX();
1109: double meterMiddleY = meterArea.getCenterY();
1110:
1111: g2.setPaint(this .tickPaint);
1112: g2.setStroke(new BasicStroke(2.0f));
1113:
1114: double valueP2X = 0;
1115: double valueP2Y = 0;
1116:
1117: double radius = (meterArea.getWidth() / 2)
1118: + DEFAULT_BORDER_SIZE;
1119: double radius1 = radius - 15;
1120:
1121: double valueP1X = meterMiddleX
1122: + (radius * Math.cos(Math.PI * (valueAngle / 180)));
1123: double valueP1Y = meterMiddleY
1124: - (radius * Math.sin(Math.PI * (valueAngle / 180)));
1125:
1126: valueP2X = meterMiddleX
1127: + (radius1 * Math.cos(Math.PI * (valueAngle / 180)));
1128: valueP2Y = meterMiddleY
1129: - (radius1 * Math.sin(Math.PI * (valueAngle / 180)));
1130:
1131: Line2D.Double line = new Line2D.Double(valueP1X, valueP1Y,
1132: valueP2X, valueP2Y);
1133: g2.draw(line);
1134:
1135: if (this .tickLabelsVisible && label) {
1136:
1137: String tickLabel = this .tickLabelFormat.format(value);
1138: g2.setFont(this .tickLabelFont);
1139: g2.setPaint(this .tickLabelPaint);
1140:
1141: FontMetrics fm = g2.getFontMetrics();
1142: Rectangle2D tickLabelBounds = TextUtilities.getTextBounds(
1143: tickLabel, g2, fm);
1144:
1145: double x = valueP2X;
1146: double y = valueP2Y;
1147: if (valueAngle == 90 || valueAngle == 270) {
1148: x = x - tickLabelBounds.getWidth() / 2;
1149: } else if (valueAngle < 90 || valueAngle > 270) {
1150: x = x - tickLabelBounds.getWidth();
1151: }
1152: if ((valueAngle > 135 && valueAngle < 225)
1153: || valueAngle > 315 || valueAngle < 45) {
1154: y = y - tickLabelBounds.getHeight() / 2;
1155: } else {
1156: y = y + tickLabelBounds.getHeight() / 2;
1157: }
1158: g2.drawString(tickLabel, (float) x, (float) y);
1159: }
1160: }
1161:
1162: /**
1163: * Draws the value label just below the center of the dial.
1164: *
1165: * @param g2 the graphics device.
1166: * @param area the plot area.
1167: */
1168: protected void drawValueLabel(Graphics2D g2, Rectangle2D area) {
1169: g2.setFont(this .valueFont);
1170: g2.setPaint(this .valuePaint);
1171: String valueStr = "No value";
1172: if (this .dataset != null) {
1173: Number n = this .dataset.getValue();
1174: if (n != null) {
1175: valueStr = this .tickLabelFormat.format(n.doubleValue())
1176: + " " + this .units;
1177: }
1178: }
1179: float x = (float) area.getCenterX();
1180: float y = (float) area.getCenterY() + DEFAULT_CIRCLE_SIZE;
1181: TextUtilities.drawAlignedString(valueStr, g2, x, y,
1182: TextAnchor.TOP_CENTER);
1183: }
1184:
1185: /**
1186: * Returns a short string describing the type of plot.
1187: *
1188: * @return A string describing the type of plot.
1189: */
1190: public String getPlotType() {
1191: return localizationResources.getString("Meter_Plot");
1192: }
1193:
1194: /**
1195: * A zoom method that does nothing. Plots are required to support the
1196: * zoom operation. In the case of a meter plot, it doesn't make sense to
1197: * zoom in or out, so the method is empty.
1198: *
1199: * @param percent The zoom percentage.
1200: */
1201: public void zoom(double percent) {
1202: // intentionally blank
1203: }
1204:
1205: /**
1206: * Tests the plot for equality with an arbitrary object. Note that the
1207: * dataset is ignored for the purposes of testing equality.
1208: *
1209: * @param obj the object (<code>null</code> permitted).
1210: *
1211: * @return A boolean.
1212: */
1213: public boolean equals(Object obj) {
1214: if (obj == this ) {
1215: return true;
1216: }
1217: if (!(obj instanceof MeterPlot)) {
1218: return false;
1219: }
1220: if (!super .equals(obj)) {
1221: return false;
1222: }
1223: MeterPlot that = (MeterPlot) obj;
1224: if (!ObjectUtilities.equal(this .units, that.units)) {
1225: return false;
1226: }
1227: if (!ObjectUtilities.equal(this .range, that.range)) {
1228: return false;
1229: }
1230: if (!ObjectUtilities.equal(this .intervals, that.intervals)) {
1231: return false;
1232: }
1233: if (!PaintUtilities.equal(this .dialOutlinePaint,
1234: that.dialOutlinePaint)) {
1235: return false;
1236: }
1237: if (this .shape != that.shape) {
1238: return false;
1239: }
1240: if (!PaintUtilities.equal(this .dialBackgroundPaint,
1241: that.dialBackgroundPaint)) {
1242: return false;
1243: }
1244: if (!PaintUtilities.equal(this .needlePaint, that.needlePaint)) {
1245: return false;
1246: }
1247: if (!ObjectUtilities.equal(this .valueFont, that.valueFont)) {
1248: return false;
1249: }
1250: if (!PaintUtilities.equal(this .valuePaint, that.valuePaint)) {
1251: return false;
1252: }
1253: if (!PaintUtilities.equal(this .tickPaint, that.tickPaint)) {
1254: return false;
1255: }
1256: if (this .tickSize != that.tickSize) {
1257: return false;
1258: }
1259: if (this .tickLabelsVisible != that.tickLabelsVisible) {
1260: return false;
1261: }
1262: if (!ObjectUtilities.equal(this .tickLabelFont,
1263: that.tickLabelFont)) {
1264: return false;
1265: }
1266: if (!PaintUtilities.equal(this .tickLabelPaint,
1267: that.tickLabelPaint)) {
1268: return false;
1269: }
1270: if (!ObjectUtilities.equal(this .tickLabelFormat,
1271: that.tickLabelFormat)) {
1272: return false;
1273: }
1274: if (this .drawBorder != that.drawBorder) {
1275: return false;
1276: }
1277: if (this .meterAngle != that.meterAngle) {
1278: return false;
1279: }
1280: return true;
1281: }
1282:
1283: /**
1284: * Provides serialization support.
1285: *
1286: * @param stream the output stream.
1287: *
1288: * @throws IOException if there is an I/O error.
1289: */
1290: private void writeObject(ObjectOutputStream stream)
1291: throws IOException {
1292: stream.defaultWriteObject();
1293: SerialUtilities.writePaint(this .dialBackgroundPaint, stream);
1294: SerialUtilities.writePaint(this .needlePaint, stream);
1295: SerialUtilities.writePaint(this .valuePaint, stream);
1296: SerialUtilities.writePaint(this .tickPaint, stream);
1297: SerialUtilities.writePaint(this .tickLabelPaint, stream);
1298: }
1299:
1300: /**
1301: * Provides serialization support.
1302: *
1303: * @param stream the input stream.
1304: *
1305: * @throws IOException if there is an I/O error.
1306: * @throws ClassNotFoundException if there is a classpath problem.
1307: */
1308: private void readObject(ObjectInputStream stream)
1309: throws IOException, ClassNotFoundException {
1310: stream.defaultReadObject();
1311: this .dialBackgroundPaint = SerialUtilities.readPaint(stream);
1312: this .needlePaint = SerialUtilities.readPaint(stream);
1313: this .valuePaint = SerialUtilities.readPaint(stream);
1314: this .tickPaint = SerialUtilities.readPaint(stream);
1315: this .tickLabelPaint = SerialUtilities.readPaint(stream);
1316: if (this .dataset != null) {
1317: this .dataset.addChangeListener(this );
1318: }
1319: }
1320:
1321: /**
1322: * Returns an independent copy (clone) of the plot. The dataset is NOT
1323: * cloned - both the original and the clone will have a reference to the
1324: * same dataset.
1325: *
1326: * @return A clone.
1327: *
1328: * @throws CloneNotSupportedException if some component of the plot cannot
1329: * be cloned.
1330: */
1331: public Object clone() throws CloneNotSupportedException {
1332: MeterPlot clone = (MeterPlot) super .clone();
1333: clone.tickLabelFormat = (NumberFormat) this .tickLabelFormat
1334: .clone();
1335: // the following relies on the fact that the intervals are immutable
1336: clone.intervals = new java.util.ArrayList(this.intervals);
1337: if (clone.dataset != null) {
1338: clone.dataset.addChangeListener(clone);
1339: }
1340: return clone;
1341: }
1342:
1343: }
|