Source for org.jfree.chart.plot.MeterPlot

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * --------------
  28:  * MeterPlot.java
  29:  * --------------
  30:  * (C) Copyright 2000-2007, by Hari and Contributors.
  31:  *
  32:  * Original Author:  Hari (ourhari@hotmail.com);
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Bob Orchard;
  35:  *                   Arnaud Lelievre;
  36:  *                   Nicolas Brodu;
  37:  *                   David Bastend;
  38:  *
  39:  * $Id: MeterPlot.java,v 1.13.2.9 2007/03/21 10:25:00 mungady Exp $
  40:  *
  41:  * Changes
  42:  * -------
  43:  * 01-Apr-2002 : Version 1, contributed by Hari (DG);
  44:  * 23-Apr-2002 : Moved dataset from JFreeChart to Plot (DG);
  45:  * 22-Aug-2002 : Added changes suggest by Bob Orchard, changed Color to Paint 
  46:  *               for consistency, plus added Javadoc comments (DG);
  47:  * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  48:  * 23-Jan-2003 : Removed one constructor (DG);
  49:  * 26-Mar-2003 : Implemented Serializable (DG);
  50:  * 20-Aug-2003 : Changed dataset from MeterDataset --> ValueDataset, added 
  51:  *               equals() method,
  52:  * 08-Sep-2003 : Added internationalization via use of properties 
  53:  *               resourceBundle (RFE 690236) (AL); 
  54:  *               implemented Cloneable, and various other changes (DG);
  55:  * 08-Sep-2003 : Added serialization methods (NB);
  56:  * 11-Sep-2003 : Added cloning support (NB);
  57:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  58:  * 25-Sep-2003 : Fix useless cloning. Correct dataset listener registration in 
  59:  *               constructor. (NB)
  60:  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
  61:  * 17-Jan-2004 : Changed to allow dialBackgroundPaint to be set to null - see 
  62:  *               bug 823628 (DG);
  63:  * 07-Apr-2004 : Changed string bounds calculation (DG);
  64:  * 12-May-2004 : Added tickLabelFormat attribute - see RFE 949566.  Also 
  65:  *               updated the equals() method (DG);
  66:  * 02-Nov-2004 : Added sanity checks for range, and only draw the needle if the 
  67:  *               value is contained within the overall range - see bug report 
  68:  *               1056047 (DG);
  69:  * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 
  70:  *               release (DG);
  71:  * 02-Feb-2005 : Added optional background paint for each region (DG);
  72:  * 22-Mar-2005 : Removed 'normal', 'warning' and 'critical' regions and put in
  73:  *               facility to define an arbitrary number of MeterIntervals,
  74:  *               based on a contribution by David Bastend (DG);
  75:  * 20-Apr-2005 : Small update for change to LegendItem constructors (DG);
  76:  * 05-May-2005 : Updated draw() method parameters (DG);
  77:  * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
  78:  * 10-Nov-2005 : Added tickPaint, tickSize and valuePaint attributes, and
  79:  *               put value label drawing code into a separate method (DG);
  80:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  81:  * 05-Mar-2007 : Restore clip region correctly (see bug 1667750) (DG);
  82:  * 
  83:  */
  84: 
  85: package org.jfree.chart.plot;
  86: 
  87: import java.awt.AlphaComposite;
  88: import java.awt.BasicStroke;
  89: import java.awt.Color;
  90: import java.awt.Composite;
  91: import java.awt.Font;
  92: import java.awt.FontMetrics;
  93: import java.awt.Graphics2D;
  94: import java.awt.Paint;
  95: import java.awt.Polygon;
  96: import java.awt.Shape;
  97: import java.awt.Stroke;
  98: import java.awt.geom.Arc2D;
  99: import java.awt.geom.Ellipse2D;
 100: import java.awt.geom.Line2D;
 101: import java.awt.geom.Point2D;
 102: import java.awt.geom.Rectangle2D;
 103: import java.io.IOException;
 104: import java.io.ObjectInputStream;
 105: import java.io.ObjectOutputStream;
 106: import java.io.Serializable;
 107: import java.text.NumberFormat;
 108: import java.util.Collections;
 109: import java.util.Iterator;
 110: import java.util.List;
 111: import java.util.ResourceBundle;
 112: 
 113: import org.jfree.chart.LegendItem;
 114: import org.jfree.chart.LegendItemCollection;
 115: import org.jfree.chart.event.PlotChangeEvent;
 116: import org.jfree.data.Range;
 117: import org.jfree.data.general.DatasetChangeEvent;
 118: import org.jfree.data.general.ValueDataset;
 119: import org.jfree.io.SerialUtilities;
 120: import org.jfree.text.TextUtilities;
 121: import org.jfree.ui.RectangleInsets;
 122: import org.jfree.ui.TextAnchor;
 123: import org.jfree.util.ObjectUtilities;
 124: import org.jfree.util.PaintUtilities;
 125: 
 126: /**
 127:  * A plot that displays a single value in the form of a needle on a dial.  
 128:  * Defined ranges (for example, 'normal', 'warning' and 'critical') can be
 129:  * highlighted on the dial.
 130:  */
 131: public class MeterPlot extends Plot implements Serializable, Cloneable {
 132: 
 133:     /** For serialization. */
 134:     private static final long serialVersionUID = 2987472457734470962L;
 135:     
 136:     /** The default background paint. */
 137:     static final Paint DEFAULT_DIAL_BACKGROUND_PAINT = Color.black;
 138: 
 139:     /** The default needle paint. */
 140:     static final Paint DEFAULT_NEEDLE_PAINT = Color.green;
 141: 
 142:     /** The default value font. */
 143:     static final Font DEFAULT_VALUE_FONT = new Font("SansSerif", Font.BOLD, 12);
 144: 
 145:     /** The default value paint. */
 146:     static final Paint DEFAULT_VALUE_PAINT = Color.yellow;
 147: 
 148:     /** The default meter angle. */
 149:     public static final int DEFAULT_METER_ANGLE = 270;
 150: 
 151:     /** The default border size. */
 152:     public static final float DEFAULT_BORDER_SIZE = 3f;
 153: 
 154:     /** The default circle size. */
 155:     public static final float DEFAULT_CIRCLE_SIZE = 10f;
 156: 
 157:     /** The default label font. */
 158:     public static final Font DEFAULT_LABEL_FONT = new Font("SansSerif", 
 159:             Font.BOLD, 10);
 160: 
 161:     /** The dataset (contains a single value). */
 162:     private ValueDataset dataset;
 163: 
 164:     /** The dial shape (background shape). */
 165:     private DialShape shape;
 166: 
 167:     /** The dial extent (measured in degrees). */
 168:     private int meterAngle;
 169:     
 170:     /** The overall range of data values on the dial. */
 171:     private Range range;
 172:     
 173:     /** The tick size. */
 174:     private double tickSize;
 175:     
 176:     /** The paint used to draw the ticks. */
 177:     private transient Paint tickPaint;
 178:     
 179:     /** The units displayed on the dial. */    
 180:     private String units;
 181:     
 182:     /** The font for the value displayed in the center of the dial. */
 183:     private Font valueFont;
 184: 
 185:     /** The paint for the value displayed in the center of the dial. */
 186:     private transient Paint valuePaint;
 187: 
 188:     /** A flag that controls whether or not the border is drawn. */
 189:     private boolean drawBorder;
 190: 
 191:     /** The outline paint. */
 192:     private transient Paint dialOutlinePaint;
 193: 
 194:     /** The paint for the dial background. */
 195:     private transient Paint dialBackgroundPaint;
 196: 
 197:     /** The paint for the needle. */
 198:     private transient Paint needlePaint;
 199: 
 200:     /** A flag that controls whether or not the tick labels are visible. */
 201:     private boolean tickLabelsVisible;
 202: 
 203:     /** The tick label font. */
 204:     private Font tickLabelFont;
 205: 
 206:     /** The tick label paint. */
 207:     private transient Paint tickLabelPaint;
 208:     
 209:     /** The tick label format. */
 210:     private NumberFormat tickLabelFormat;
 211: 
 212:     /** The resourceBundle for the localization. */
 213:     protected static ResourceBundle localizationResources = 
 214:         ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
 215: 
 216:     /** 
 217:      * A (possibly empty) list of the {@link MeterInterval}s to be highlighted 
 218:      * on the dial. 
 219:      */
 220:     private List intervals;
 221: 
 222:     /**
 223:      * Creates a new plot with a default range of <code>0</code> to 
 224:      * <code>100</code> and no value to display.
 225:      */
 226:     public MeterPlot() {
 227:         this(null);   
 228:     }
 229:     
 230:     /**
 231:      * Creates a new plot that displays the value from the supplied dataset.
 232:      *
 233:      * @param dataset  the dataset (<code>null</code> permitted).
 234:      */
 235:     public MeterPlot(ValueDataset dataset) {
 236:         super();
 237:         this.shape = DialShape.CIRCLE;
 238:         this.meterAngle = DEFAULT_METER_ANGLE;
 239:         this.range = new Range(0.0, 100.0);
 240:         this.tickSize = 10.0;
 241:         this.tickPaint = Color.white;
 242:         this.units = "Units";
 243:         this.needlePaint = MeterPlot.DEFAULT_NEEDLE_PAINT;
 244:         this.tickLabelsVisible = true;
 245:         this.tickLabelFont = MeterPlot.DEFAULT_LABEL_FONT;
 246:         this.tickLabelPaint = Color.black;
 247:         this.tickLabelFormat = NumberFormat.getInstance();
 248:         this.valueFont = MeterPlot.DEFAULT_VALUE_FONT;
 249:         this.valuePaint = MeterPlot.DEFAULT_VALUE_PAINT;
 250:         this.dialBackgroundPaint = MeterPlot.DEFAULT_DIAL_BACKGROUND_PAINT;
 251:         this.intervals = new java.util.ArrayList();
 252:         setDataset(dataset);
 253:     }
 254: 
 255:     /**
 256:      * Returns the dial shape.  The default is {@link DialShape#CIRCLE}).
 257:      * 
 258:      * @return The dial shape (never <code>null</code>).
 259:      * 
 260:      * @see #setDialShape(DialShape)
 261:      */
 262:     public DialShape getDialShape() {
 263:         return this.shape;
 264:     }
 265:     
 266:     /**
 267:      * Sets the dial shape and sends a {@link PlotChangeEvent} to all 
 268:      * registered listeners.
 269:      * 
 270:      * @param shape  the shape (<code>null</code> not permitted).
 271:      * 
 272:      * @see #getDialShape()
 273:      */
 274:     public void setDialShape(DialShape shape) {
 275:         if (shape == null) {
 276:             throw new IllegalArgumentException("Null 'shape' argument.");
 277:         }
 278:         this.shape = shape;
 279:         notifyListeners(new PlotChangeEvent(this));
 280:     }
 281:     
 282:     /**
 283:      * Returns the meter angle in degrees.  This defines, in part, the shape
 284:      * of the dial.  The default is 270 degrees.
 285:      *
 286:      * @return The meter angle (in degrees).
 287:      * 
 288:      * @see #setMeterAngle(int)
 289:      */
 290:     public int getMeterAngle() {
 291:         return this.meterAngle;
 292:     }
 293: 
 294:     /**
 295:      * Sets the angle (in degrees) for the whole range of the dial and sends 
 296:      * a {@link PlotChangeEvent} to all registered listeners.
 297:      * 
 298:      * @param angle  the angle (in degrees, in the range 1-360).
 299:      * 
 300:      * @see #getMeterAngle()
 301:      */
 302:     public void setMeterAngle(int angle) {
 303:         if (angle < 1 || angle > 360) {
 304:             throw new IllegalArgumentException("Invalid 'angle' (" + angle 
 305:                     + ")");
 306:         }
 307:         this.meterAngle = angle;
 308:         notifyListeners(new PlotChangeEvent(this));
 309:     }
 310: 
 311:     /**
 312:      * Returns the overall range for the dial.
 313:      * 
 314:      * @return The overall range (never <code>null</code>).
 315:      * 
 316:      * @see #setRange(Range)
 317:      */
 318:     public Range getRange() {
 319:         return this.range;    
 320:     }
 321:     
 322:     /**
 323:      * Sets the range for the dial and sends a {@link PlotChangeEvent} to all
 324:      * registered listeners.
 325:      * 
 326:      * @param range  the range (<code>null</code> not permitted and zero-length
 327:      *               ranges not permitted).
 328:      *             
 329:      * @see #getRange()
 330:      */
 331:     public void setRange(Range range) {
 332:         if (range == null) {
 333:             throw new IllegalArgumentException("Null 'range' argument.");
 334:         }
 335:         if (!(range.getLength() > 0.0)) {
 336:             throw new IllegalArgumentException(
 337:                     "Range length must be positive.");
 338:         }
 339:         this.range = range;
 340:         notifyListeners(new PlotChangeEvent(this));
 341:     }
 342:     
 343:     /**
 344:      * Returns the tick size (the interval between ticks on the dial).
 345:      * 
 346:      * @return The tick size.
 347:      * 
 348:      * @see #setTickSize(double)
 349:      */
 350:     public double getTickSize() {
 351:         return this.tickSize;
 352:     }
 353:     
 354:     /**
 355:      * Sets the tick size and sends a {@link PlotChangeEvent} to all 
 356:      * registered listeners.
 357:      * 
 358:      * @param size  the tick size (must be > 0).
 359:      * 
 360:      * @see #getTickSize()
 361:      */
 362:     public void setTickSize(double size) {
 363:         if (size <= 0) {
 364:             throw new IllegalArgumentException("Requires 'size' > 0.");
 365:         }
 366:         this.tickSize = size;
 367:         notifyListeners(new PlotChangeEvent(this));
 368:     }
 369:     
 370:     /**
 371:      * Returns the paint used to draw the ticks around the dial. 
 372:      * 
 373:      * @return The paint used to draw the ticks around the dial (never 
 374:      *         <code>null</code>).
 375:      *         
 376:      * @see #setTickPaint(Paint)
 377:      */
 378:     public Paint getTickPaint() {
 379:         return this.tickPaint;
 380:     }
 381:     
 382:     /**
 383:      * Sets the paint used to draw the tick labels around the dial and sends
 384:      * a {@link PlotChangeEvent} to all registered listeners.
 385:      * 
 386:      * @param paint  the paint (<code>null</code> not permitted).
 387:      * 
 388:      * @see #getTickPaint()
 389:      */
 390:     public void setTickPaint(Paint paint) {
 391:         if (paint == null) {
 392:             throw new IllegalArgumentException("Null 'paint' argument.");
 393:         }
 394:         this.tickPaint = paint;
 395:         notifyListeners(new PlotChangeEvent(this));
 396:     }
 397: 
 398:     /**
 399:      * Returns a string describing the units for the dial.
 400:      * 
 401:      * @return The units (possibly <code>null</code>).
 402:      * 
 403:      * @see #setUnits(String)
 404:      */
 405:     public String getUnits() {
 406:         return this.units;
 407:     }
 408:     
 409:     /**
 410:      * Sets the units for the dial and sends a {@link PlotChangeEvent} to all
 411:      * registered listeners.
 412:      * 
 413:      * @param units  the units (<code>null</code> permitted).
 414:      * 
 415:      * @see #getUnits()
 416:      */
 417:     public void setUnits(String units) {
 418:         this.units = units;    
 419:         notifyListeners(new PlotChangeEvent(this));
 420:     }
 421:         
 422:     /**
 423:      * Returns the paint for the needle.
 424:      *
 425:      * @return The paint (never <code>null</code>).
 426:      * 
 427:      * @see #setNeedlePaint(Paint)
 428:      */
 429:     public Paint getNeedlePaint() {
 430:         return this.needlePaint;
 431:     }
 432: 
 433:     /**
 434:      * Sets the paint used to display the needle and sends a 
 435:      * {@link PlotChangeEvent} to all registered listeners.
 436:      *
 437:      * @param paint  the paint (<code>null</code> not permitted).
 438:      * 
 439:      * @see #getNeedlePaint()
 440:      */
 441:     public void setNeedlePaint(Paint paint) {
 442:         if (paint == null) {
 443:             throw new IllegalArgumentException("Null 'paint' argument.");
 444:         }
 445:         this.needlePaint = paint;
 446:         notifyListeners(new PlotChangeEvent(this));
 447:     }
 448: 
 449:     /**
 450:      * Returns the flag that determines whether or not tick labels are visible.
 451:      *
 452:      * @return The flag.
 453:      * 
 454:      * @see #setTickLabelsVisible(boolean)
 455:      */
 456:     public boolean getTickLabelsVisible() {
 457:         return this.tickLabelsVisible;
 458:     }
 459: 
 460:     /**
 461:      * Sets the flag that controls whether or not the tick labels are visible
 462:      * and sends a {@link PlotChangeEvent} to all registered listeners.
 463:      *
 464:      * @param visible  the flag.
 465:      * 
 466:      * @see #getTickLabelsVisible()
 467:      */
 468:     public void setTickLabelsVisible(boolean visible) {
 469:         if (this.tickLabelsVisible != visible) {
 470:             this.tickLabelsVisible = visible;
 471:             notifyListeners(new PlotChangeEvent(this));
 472:         }
 473:     }
 474: 
 475:     /**
 476:      * Returns the tick label font.
 477:      *
 478:      * @return The font (never <code>null</code>).
 479:      * 
 480:      * @see #setTickLabelFont(Font)
 481:      */
 482:     public Font getTickLabelFont() {
 483:         return this.tickLabelFont;
 484:     }
 485: 
 486:     /**
 487:      * Sets the tick label font and sends a {@link PlotChangeEvent} to all 
 488:      * registered listeners.
 489:      *
 490:      * @param font  the font (<code>null</code> not permitted).
 491:      * 
 492:      * @see #getTickLabelFont()
 493:      */
 494:     public void setTickLabelFont(Font font) {
 495:         if (font == null) {
 496:             throw new IllegalArgumentException("Null 'font' argument.");
 497:         }
 498:         if (!this.tickLabelFont.equals(font)) {
 499:             this.tickLabelFont = font;
 500:             notifyListeners(new PlotChangeEvent(this));
 501:         }
 502:     }
 503: 
 504:     /**
 505:      * Returns the tick label paint.
 506:      *
 507:      * @return The paint (never <code>null</code>).
 508:      * 
 509:      * @see #setTickLabelPaint(Paint)
 510:      */
 511:     public Paint getTickLabelPaint() {
 512:         return this.tickLabelPaint;
 513:     }
 514: 
 515:     /**
 516:      * Sets the tick label paint and sends a {@link PlotChangeEvent} to all 
 517:      * registered listeners.
 518:      *
 519:      * @param paint  the paint (<code>null</code> not permitted).
 520:      * 
 521:      * @see #getTickLabelPaint()
 522:      */
 523:     public void setTickLabelPaint(Paint paint) {
 524:         if (paint == null) {
 525:             throw new IllegalArgumentException("Null 'paint' argument.");
 526:         }
 527:         if (!this.tickLabelPaint.equals(paint)) {
 528:             this.tickLabelPaint = paint;
 529:             notifyListeners(new PlotChangeEvent(this));
 530:         }
 531:     }
 532: 
 533:     /**
 534:      * Returns the tick label format.
 535:      * 
 536:      * @return The tick label format (never <code>null</code>).
 537:      * 
 538:      * @see #setTickLabelFormat(NumberFormat)
 539:      */
 540:     public NumberFormat getTickLabelFormat() {
 541:         return this.tickLabelFormat;    
 542:     }
 543:     
 544:     /**
 545:      * Sets the format for the tick labels and sends a {@link PlotChangeEvent} 
 546:      * to all registered listeners.
 547:      * 
 548:      * @param format  the format (<code>null</code> not permitted).
 549:      * 
 550:      * @see #getTickLabelFormat()
 551:      */
 552:     public void setTickLabelFormat(NumberFormat format) {
 553:         if (format == null) {
 554:             throw new IllegalArgumentException("Null 'format' argument.");   
 555:         }
 556:         this.tickLabelFormat = format;
 557:         notifyListeners(new PlotChangeEvent(this));
 558:     }
 559:     
 560:     /**
 561:      * Returns the font for the value label.
 562:      *
 563:      * @return The font (never <code>null</code>).
 564:      * 
 565:      * @see #setValueFont(Font)
 566:      */
 567:     public Font getValueFont() {
 568:         return this.valueFont;
 569:     }
 570: 
 571:     /**
 572:      * Sets the font used to display the value label and sends a 
 573:      * {@link PlotChangeEvent} to all registered listeners.
 574:      *
 575:      * @param font  the font (<code>null</code> not permitted).
 576:      * 
 577:      * @see #getValueFont()
 578:      */
 579:     public void setValueFont(Font font) {
 580:         if (font == null) {
 581:             throw new IllegalArgumentException("Null 'font' argument.");
 582:         }
 583:         this.valueFont = font;
 584:         notifyListeners(new PlotChangeEvent(this));
 585:     }
 586: 
 587:     /**
 588:      * Returns the paint for the value label.
 589:      *
 590:      * @return The paint (never <code>null</code>).
 591:      * 
 592:      * @see #setValuePaint(Paint)
 593:      */
 594:     public Paint getValuePaint() {
 595:         return this.valuePaint;
 596:     }
 597: 
 598:     /**
 599:      * Sets the paint used to display the value label and sends a 
 600:      * {@link PlotChangeEvent} to all registered listeners.
 601:      *
 602:      * @param paint  the paint (<code>null</code> not permitted).
 603:      * 
 604:      * @see #getValuePaint()
 605:      */
 606:     public void setValuePaint(Paint paint) {
 607:         if (paint == null) {
 608:             throw new IllegalArgumentException("Null 'paint' argument.");
 609:         }
 610:         this.valuePaint = paint;
 611:         notifyListeners(new PlotChangeEvent(this));
 612:     }
 613: 
 614:     /**
 615:      * Returns the paint for the dial background.
 616:      *
 617:      * @return The paint (possibly <code>null</code>).
 618:      * 
 619:      * @see #setDialBackgroundPaint(Paint)
 620:      */
 621:     public Paint getDialBackgroundPaint() {
 622:         return this.dialBackgroundPaint;
 623:     }
 624: 
 625:     /**
 626:      * Sets the paint used to fill the dial background.  Set this to 
 627:      * <code>null</code> for no background.
 628:      *
 629:      * @param paint  the paint (<code>null</code> permitted).
 630:      * 
 631:      * @see #getDialBackgroundPaint()
 632:      */
 633:     public void setDialBackgroundPaint(Paint paint) {
 634:         this.dialBackgroundPaint = paint;
 635:         notifyListeners(new PlotChangeEvent(this));
 636:     }
 637: 
 638:     /**
 639:      * Returns a flag that controls whether or not a rectangular border is 
 640:      * drawn around the plot area.
 641:      *
 642:      * @return A flag.
 643:      * 
 644:      * @see #setDrawBorder(boolean)
 645:      */
 646:     public boolean getDrawBorder() {
 647:         return this.drawBorder;
 648:     }
 649: 
 650:     /**
 651:      * Sets the flag that controls whether or not a rectangular border is drawn
 652:      * around the plot area and sends a {@link PlotChangeEvent} to all 
 653:      * registered listeners.
 654:      *
 655:      * @param draw  the flag.
 656:      * 
 657:      * @see #getDrawBorder()
 658:      */
 659:     public void setDrawBorder(boolean draw) {
 660:         // TODO: fix output when this flag is set to true
 661:         this.drawBorder = draw;
 662:         notifyListeners(new PlotChangeEvent(this));
 663:     }
 664: 
 665:     /**
 666:      * Returns the dial outline paint.
 667:      *
 668:      * @return The paint.
 669:      * 
 670:      * @see #setDialOutlinePaint(Paint)
 671:      */
 672:     public Paint getDialOutlinePaint() {
 673:         return this.dialOutlinePaint;
 674:     }
 675: 
 676:     /**
 677:      * Sets the dial outline paint and sends a {@link PlotChangeEvent} to all
 678:      * registered listeners.
 679:      *
 680:      * @param paint  the paint.
 681:      * 
 682:      * @see #getDialOutlinePaint()
 683:      */
 684:     public void setDialOutlinePaint(Paint paint) {
 685:         this.dialOutlinePaint = paint;
 686:         notifyListeners(new PlotChangeEvent(this));        
 687:     }
 688: 
 689:     /**
 690:      * Returns the dataset for the plot.
 691:      * 
 692:      * @return The dataset (possibly <code>null</code>).
 693:      * 
 694:      * @see #setDataset(ValueDataset)
 695:      */
 696:     public ValueDataset getDataset() {
 697:         return this.dataset;
 698:     }
 699:     
 700:     /**
 701:      * Sets the dataset for the plot, replacing the existing dataset if there 
 702:      * is one, and triggers a {@link PlotChangeEvent}.
 703:      * 
 704:      * @param dataset  the dataset (<code>null</code> permitted).
 705:      * 
 706:      * @see #getDataset()
 707:      */
 708:     public void setDataset(ValueDataset dataset) {
 709:         
 710:         // if there is an existing dataset, remove the plot from the list of 
 711:         // change listeners...
 712:         ValueDataset existing = this.dataset;
 713:         if (existing != null) {
 714:             existing.removeChangeListener(this);
 715:         }
 716: 
 717:         // set the new dataset, and register the chart as a change listener...
 718:         this.dataset = dataset;
 719:         if (dataset != null) {
 720:             setDatasetGroup(dataset.getGroup());
 721:             dataset.addChangeListener(this);
 722:         }
 723: 
 724:         // send a dataset change event to self...
 725:         DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
 726:         datasetChanged(event);
 727:         
 728:     }
 729: 
 730:     /**
 731:      * Returns an unmodifiable list of the intervals for the plot.
 732:      * 
 733:      * @return A list.
 734:      * 
 735:      * @see #addInterval(MeterInterval)
 736:      */
 737:     public List getIntervals() {
 738:         return Collections.unmodifiableList(this.intervals);
 739:     }
 740:     
 741:     /**
 742:      * Adds an interval and sends a {@link PlotChangeEvent} to all registered
 743:      * listeners.
 744:      * 
 745:      * @param interval  the interval (<code>null</code> not permitted).
 746:      * 
 747:      * @see #getIntervals()
 748:      * @see #clearIntervals()
 749:      */
 750:     public void addInterval(MeterInterval interval) {
 751:         if (interval == null) {
 752:             throw new IllegalArgumentException("Null 'interval' argument.");
 753:         }
 754:         this.intervals.add(interval);
 755:         notifyListeners(new PlotChangeEvent(this));
 756:     }
 757:     
 758:     /**
 759:      * Clears the intervals for the plot and sends a {@link PlotChangeEvent} to
 760:      * all registered listeners.
 761:      * 
 762:      * @see #addInterval(MeterInterval)
 763:      */
 764:     public void clearIntervals() {
 765:         this.intervals.clear();
 766:         notifyListeners(new PlotChangeEvent(this));
 767:     }
 768:     
 769:     /**
 770:      * Returns an item for each interval.
 771:      *
 772:      * @return A collection of legend items.
 773:      */
 774:     public LegendItemCollection getLegendItems() {
 775:         LegendItemCollection result = new LegendItemCollection();
 776:         Iterator iterator = this.intervals.iterator();
 777:         while (iterator.hasNext()) {
 778:             MeterInterval mi = (MeterInterval) iterator.next();
 779:             Paint color = mi.getBackgroundPaint();
 780:             if (color == null) {
 781:                 color = mi.getOutlinePaint();
 782:             }
 783:             LegendItem item = new LegendItem(mi.getLabel(), mi.getLabel(),
 784:                     null, null, new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0), 
 785:                     color);
 786:             result.add(item);
 787:         }
 788:         return result;
 789:     }
 790: 
 791:     /**
 792:      * Draws the plot on a Java 2D graphics device (such as the screen or a 
 793:      * printer).
 794:      *
 795:      * @param g2  the graphics device.
 796:      * @param area  the area within which the plot should be drawn.
 797:      * @param anchor  the anchor point (<code>null</code> permitted).
 798:      * @param parentState  the state from the parent plot, if there is one.
 799:      * @param info  collects info about the drawing.
 800:      */
 801:     public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
 802:                      PlotState parentState,
 803:                      PlotRenderingInfo info) {
 804: 
 805:         if (info != null) {
 806:             info.setPlotArea(area);
 807:         }
 808: 
 809:         // adjust for insets...
 810:         RectangleInsets insets = getInsets();
 811:         insets.trim(area);
 812: 
 813:         area.setRect(area.getX() + 4, area.getY() + 4, area.getWidth() - 8, 
 814:                 area.getHeight() - 8);
 815: 
 816:         // draw the background
 817:         if (this.drawBorder) {
 818:             drawBackground(g2, area);
 819:         }
 820: 
 821:         // adjust the plot area by the interior spacing value
 822:         double gapHorizontal = (2 * DEFAULT_BORDER_SIZE);
 823:         double gapVertical = (2 * DEFAULT_BORDER_SIZE);
 824:         double meterX = area.getX() + gapHorizontal / 2;
 825:         double meterY = area.getY() + gapVertical / 2;
 826:         double meterW = area.getWidth() - gapHorizontal;
 827:         double meterH = area.getHeight() - gapVertical
 828:                 + ((this.meterAngle <= 180) && (this.shape != DialShape.CIRCLE)
 829:                 ? area.getHeight() / 1.25 : 0);
 830: 
 831:         double min = Math.min(meterW, meterH) / 2;
 832:         meterX = (meterX + meterX + meterW) / 2 - min;
 833:         meterY = (meterY + meterY + meterH) / 2 - min;
 834:         meterW = 2 * min;
 835:         meterH = 2 * min;
 836: 
 837:         Rectangle2D meterArea = new Rectangle2D.Double(meterX, meterY, meterW, 
 838:                 meterH);
 839: 
 840:         Rectangle2D.Double originalArea = new Rectangle2D.Double(
 841:                 meterArea.getX() - 4, meterArea.getY() - 4, 
 842:                 meterArea.getWidth() + 8, meterArea.getHeight() + 8);
 843: 
 844:         double meterMiddleX = meterArea.getCenterX();
 845:         double meterMiddleY = meterArea.getCenterY();
 846: 
 847:         // plot the data (unless the dataset is null)...
 848:         ValueDataset data = getDataset();
 849:         if (data != null) {
 850:             double dataMin = this.range.getLowerBound();
 851:             double dataMax = this.range.getUpperBound();
 852: 
 853:             Shape savedClip = g2.getClip();
 854:             g2.clip(originalArea);
 855:             Composite originalComposite = g2.getComposite();
 856:             g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
 857:                     getForegroundAlpha()));
 858: 
 859:             if (this.dialBackgroundPaint != null) {
 860:                 fillArc(g2, originalArea, dataMin, dataMax, 
 861:                         this.dialBackgroundPaint, true);
 862:             }
 863:             drawTicks(g2, meterArea, dataMin, dataMax);
 864:             drawArcForInterval(g2, meterArea, new MeterInterval("", this.range,
 865:                     this.dialOutlinePaint, new BasicStroke(1.0f), null));
 866:             
 867:             Iterator iterator = this.intervals.iterator();
 868:             while (iterator.hasNext()) {
 869:                 MeterInterval interval = (MeterInterval) iterator.next();
 870:                 drawArcForInterval(g2, meterArea, interval);
 871:             }
 872: 
 873:             Number n = data.getValue();
 874:             if (n != null) {
 875:                 double value = n.doubleValue();
 876:                 drawValueLabel(g2, meterArea);
 877:   
 878:                 if (this.range.contains(value)) {
 879:                     g2.setPaint(this.needlePaint);
 880:                     g2.setStroke(new BasicStroke(2.0f));
 881: 
 882:                     double radius = (meterArea.getWidth() / 2) 
 883:                                     + DEFAULT_BORDER_SIZE + 15;
 884:                     double valueAngle = valueToAngle(value);
 885:                     double valueP1 = meterMiddleX 
 886:                             + (radius * Math.cos(Math.PI * (valueAngle / 180)));
 887:                     double valueP2 = meterMiddleY 
 888:                             - (radius * Math.sin(Math.PI * (valueAngle / 180)));
 889: 
 890:                     Polygon arrow = new Polygon();
 891:                     if ((valueAngle > 135 && valueAngle < 225)
 892:                         || (valueAngle < 45 && valueAngle > -45)) {
 893: 
 894:                         double valueP3 = (meterMiddleY 
 895:                                 - DEFAULT_CIRCLE_SIZE / 4);
 896:                         double valueP4 = (meterMiddleY 
 897:                                 + DEFAULT_CIRCLE_SIZE / 4);
 898:                         arrow.addPoint((int) meterMiddleX, (int) valueP3);
 899:                         arrow.addPoint((int) meterMiddleX, (int) valueP4);
 900:  
 901:                     }
 902:                     else {
 903:                         arrow.addPoint((int) (meterMiddleX 
 904:                                 - DEFAULT_CIRCLE_SIZE / 4), (int) meterMiddleY);
 905:                         arrow.addPoint((int) (meterMiddleX 
 906:                                 + DEFAULT_CIRCLE_SIZE / 4), (int) meterMiddleY);
 907:                     }
 908:                     arrow.addPoint((int) valueP1, (int) valueP2);
 909:                     g2.fill(arrow);
 910: 
 911:                     Ellipse2D circle = new Ellipse2D.Double(meterMiddleX 
 912:                             - DEFAULT_CIRCLE_SIZE / 2, meterMiddleY 
 913:                             - DEFAULT_CIRCLE_SIZE / 2, DEFAULT_CIRCLE_SIZE, 
 914:                             DEFAULT_CIRCLE_SIZE);
 915:                     g2.fill(circle);
 916:                 }
 917:             }
 918:                 
 919:             g2.setClip(savedClip);
 920:             g2.setComposite(originalComposite);
 921: 
 922:         }
 923:         if (this.drawBorder) {
 924:             drawOutline(g2, area);
 925:         }
 926: 
 927:     }
 928: 
 929:     /**
 930:      * Draws the arc to represent an interval.
 931:      *
 932:      * @param g2  the graphics device.
 933:      * @param meterArea  the drawing area.
 934:      * @param interval  the interval.
 935:      */
 936:     protected void drawArcForInterval(Graphics2D g2, Rectangle2D meterArea, 
 937:                                       MeterInterval interval) {
 938: 
 939:         double minValue = interval.getRange().getLowerBound();
 940:         double maxValue = interval.getRange().getUpperBound();
 941:         Paint outlinePaint = interval.getOutlinePaint();
 942:         Stroke outlineStroke = interval.getOutlineStroke();
 943:         Paint backgroundPaint = interval.getBackgroundPaint();
 944:  
 945:         if (backgroundPaint != null) {
 946:             fillArc(g2, meterArea, minValue, maxValue, backgroundPaint, false);
 947:         }
 948:         if (outlinePaint != null) {
 949:             if (outlineStroke != null) {
 950:                 drawArc(g2, meterArea, minValue, maxValue, outlinePaint, 
 951:                         outlineStroke);
 952:             }
 953:             drawTick(g2, meterArea, minValue, true);
 954:             drawTick(g2, meterArea, maxValue, true);
 955:         }
 956:     }
 957: 
 958:     /**
 959:      * Draws an arc.
 960:      *
 961:      * @param g2  the graphics device.
 962:      * @param area  the plot area.
 963:      * @param minValue  the minimum value.
 964:      * @param maxValue  the maximum value.
 965:      * @param paint  the paint.
 966:      * @param stroke  the stroke.
 967:      */
 968:     protected void drawArc(Graphics2D g2, Rectangle2D area, double minValue, 
 969:                            double maxValue, Paint paint, Stroke stroke) {
 970: 
 971:         double startAngle = valueToAngle(maxValue);
 972:         double endAngle = valueToAngle(minValue);
 973:         double extent = endAngle - startAngle;
 974: 
 975:         double x = area.getX();
 976:         double y = area.getY();
 977:         double w = area.getWidth();
 978:         double h = area.getHeight();
 979:         g2.setPaint(paint);
 980:         g2.setStroke(stroke);
 981: 
 982:         if (paint != null && stroke != null) {
 983:             Arc2D.Double arc = new Arc2D.Double(x, y, w, h, startAngle, 
 984:                     extent, Arc2D.OPEN);
 985:             g2.setPaint(paint); 
 986:             g2.setStroke(stroke);
 987:             g2.draw(arc);
 988:         }
 989: 
 990:     }
 991: 
 992:     /**
 993:      * Fills an arc on the dial between the given values.
 994:      *
 995:      * @param g2  the graphics device.
 996:      * @param area  the plot area.
 997:      * @param minValue  the minimum data value.
 998:      * @param maxValue  the maximum data value.
 999:      * @param paint  the background paint (<code>null</code> not permitted).
1000:      * @param dial  a flag that indicates whether the arc represents the whole 
1001:      *              dial.
1002:      */
1003:     protected void fillArc(Graphics2D g2, Rectangle2D area, 
1004:                            double minValue, double maxValue, Paint paint,
1005:                            boolean dial) {
1006:         if (paint == null) {
1007:             throw new IllegalArgumentException("Null 'paint' argument");
1008:         }
1009:         double startAngle = valueToAngle(maxValue);
1010:         double endAngle = valueToAngle(minValue);
1011:         double extent = endAngle - startAngle;
1012: 
1013:         double x = area.getX();
1014:         double y = area.getY();
1015:         double w = area.getWidth();
1016:         double h = area.getHeight();
1017:         int joinType = Arc2D.OPEN;
1018:         if (this.shape == DialShape.PIE) {
1019:             joinType = Arc2D.PIE;
1020:         }
1021:         else if (this.shape == DialShape.CHORD) {
1022:             if (dial && this.meterAngle > 180) {
1023:                 joinType = Arc2D.CHORD;
1024:             }
1025:             else {
1026:                 joinType = Arc2D.PIE;
1027:             }
1028:         }
1029:         else if (this.shape == DialShape.CIRCLE) {
1030:             joinType = Arc2D.PIE;
1031:             if (dial) {
1032:                 extent = 360;
1033:             }
1034:         }
1035:         else {
1036:             throw new IllegalStateException("DialShape not recognised.");
1037:         }
1038: 
1039:         g2.setPaint(paint);
1040:         Arc2D.Double arc = new Arc2D.Double(x, y, w, h, startAngle, extent, 
1041:                 joinType);
1042:         g2.fill(arc);
1043:     }
1044:     
1045:     /**
1046:      * Translates a data value to an angle on the dial.
1047:      *
1048:      * @param value  the value.
1049:      *
1050:      * @return The angle on the dial.
1051:      */
1052:     public double valueToAngle(double value) {
1053:         value = value - this.range.getLowerBound();
1054:         double baseAngle = 180 + ((this.meterAngle - 180) / 2);
1055:         return baseAngle - ((value / this.range.getLength()) * this.meterAngle);
1056:     }
1057: 
1058:     /**
1059:      * Draws the ticks that subdivide the overall range.
1060:      *
1061:      * @param g2  the graphics device.
1062:      * @param meterArea  the meter area.
1063:      * @param minValue  the minimum value.
1064:      * @param maxValue  the maximum value.
1065:      */
1066:     protected void drawTicks(Graphics2D g2, Rectangle2D meterArea, 
1067:                              double minValue, double maxValue) {
1068:         for (double v = minValue; v <= maxValue; v += this.tickSize) {
1069:             drawTick(g2, meterArea, v);
1070:         }
1071:     }
1072: 
1073:     /**
1074:      * Draws a tick.
1075:      *
1076:      * @param g2  the graphics device.
1077:      * @param meterArea  the meter area.
1078:      * @param value  the value.
1079:      */
1080:     protected void drawTick(Graphics2D g2, Rectangle2D meterArea, 
1081:             double value) {
1082:         drawTick(g2, meterArea, value, false);
1083:     }
1084: 
1085:     /**
1086:      * Draws a tick on the dial.
1087:      *
1088:      * @param g2  the graphics device.
1089:      * @param meterArea  the meter area.
1090:      * @param value  the tick value.
1091:      * @param label  a flag that controls whether or not a value label is drawn.
1092:      */
1093:     protected void drawTick(Graphics2D g2, Rectangle2D meterArea,
1094:                             double value, boolean label) {
1095: 
1096:         double valueAngle = valueToAngle(value);
1097: 
1098:         double meterMiddleX = meterArea.getCenterX();
1099:         double meterMiddleY = meterArea.getCenterY();
1100: 
1101:         g2.setPaint(this.tickPaint);
1102:         g2.setStroke(new BasicStroke(2.0f));
1103: 
1104:         double valueP2X = 0;
1105:         double valueP2Y = 0;
1106: 
1107:         double radius = (meterArea.getWidth() / 2) + DEFAULT_BORDER_SIZE;
1108:         double radius1 = radius - 15;
1109: 
1110:         double valueP1X = meterMiddleX 
1111:                 + (radius * Math.cos(Math.PI * (valueAngle / 180)));
1112:         double valueP1Y = meterMiddleY 
1113:                 - (radius * Math.sin(Math.PI * (valueAngle / 180)));
1114: 
1115:         valueP2X = meterMiddleX 
1116:                 + (radius1 * Math.cos(Math.PI * (valueAngle / 180)));
1117:         valueP2Y = meterMiddleY 
1118:                 - (radius1 * Math.sin(Math.PI * (valueAngle / 180)));
1119: 
1120:         Line2D.Double line = new Line2D.Double(valueP1X, valueP1Y, valueP2X, 
1121:                 valueP2Y);
1122:         g2.draw(line);
1123: 
1124:         if (this.tickLabelsVisible && label) {
1125: 
1126:             String tickLabel =  this.tickLabelFormat.format(value);
1127:             g2.setFont(this.tickLabelFont);
1128:             g2.setPaint(this.tickLabelPaint);
1129: 
1130:             FontMetrics fm = g2.getFontMetrics();
1131:             Rectangle2D tickLabelBounds 
1132:                 = TextUtilities.getTextBounds(tickLabel, g2, fm);
1133: 
1134:             double x = valueP2X;
1135:             double y = valueP2Y;
1136:             if (valueAngle == 90 || valueAngle == 270) {
1137:                 x = x - tickLabelBounds.getWidth() / 2;
1138:             }
1139:             else if (valueAngle < 90 || valueAngle > 270) {
1140:                 x = x - tickLabelBounds.getWidth();
1141:             }
1142:             if ((valueAngle > 135 && valueAngle < 225) 
1143:                     || valueAngle > 315 || valueAngle < 45) {
1144:                 y = y - tickLabelBounds.getHeight() / 2;
1145:             }
1146:             else {
1147:                 y = y + tickLabelBounds.getHeight() / 2;
1148:             }
1149:             g2.drawString(tickLabel, (float) x, (float) y);
1150:         }
1151:     }
1152:     
1153:     /**
1154:      * Draws the value label just below the center of the dial.
1155:      * 
1156:      * @param g2  the graphics device.
1157:      * @param area  the plot area.
1158:      */
1159:     protected void drawValueLabel(Graphics2D g2, Rectangle2D area) {
1160:         g2.setFont(this.valueFont);
1161:         g2.setPaint(this.valuePaint);
1162:         String valueStr = "No value";
1163:         if (this.dataset != null) {
1164:             Number n = this.dataset.getValue();
1165:             if (n != null) {
1166:                 valueStr = this.tickLabelFormat.format(n.doubleValue()) + " " 
1167:                          + this.units;
1168:             }
1169:         }
1170:         float x = (float) area.getCenterX();
1171:         float y = (float) area.getCenterY() + DEFAULT_CIRCLE_SIZE;
1172:         TextUtilities.drawAlignedString(valueStr, g2, x, y, 
1173:                 TextAnchor.TOP_CENTER);
1174:     }
1175: 
1176:     /**
1177:      * Returns a short string describing the type of plot.
1178:      *
1179:      * @return A string describing the type of plot.
1180:      */
1181:     public String getPlotType() {
1182:         return localizationResources.getString("Meter_Plot");
1183:     }
1184: 
1185:     /**
1186:      * A zoom method that does nothing.  Plots are required to support the 
1187:      * zoom operation.  In the case of a meter plot, it doesn't make sense to 
1188:      * zoom in or out, so the method is empty.
1189:      *
1190:      * @param percent   The zoom percentage.
1191:      */
1192:     public void zoom(double percent) {
1193:         // intentionally blank
1194:     }
1195:     
1196:     /**
1197:      * Tests the plot for equality with an arbitrary object.  Note that the 
1198:      * dataset is ignored for the purposes of testing equality.
1199:      * 
1200:      * @param obj  the object (<code>null</code> permitted).
1201:      * 
1202:      * @return A boolean.
1203:      */
1204:     public boolean equals(Object obj) {
1205:         if (obj == this) {
1206:             return true;
1207:         }   
1208:         if (!(obj instanceof MeterPlot)) {
1209:             return false;   
1210:         }
1211:         if (!super.equals(obj)) {
1212:             return false;
1213:         }
1214:         MeterPlot that = (MeterPlot) obj;
1215:         if (!ObjectUtilities.equal(this.units, that.units)) {
1216:             return false;   
1217:         }
1218:         if (!ObjectUtilities.equal(this.range, that.range)) {
1219:             return false;
1220:         }
1221:         if (!ObjectUtilities.equal(this.intervals, that.intervals)) {
1222:             return false;   
1223:         }
1224:         if (!PaintUtilities.equal(this.dialOutlinePaint, 
1225:                 that.dialOutlinePaint)) {
1226:             return false;   
1227:         }
1228:         if (this.shape != that.shape) {
1229:             return false;   
1230:         }
1231:         if (!PaintUtilities.equal(this.dialBackgroundPaint, 
1232:                 that.dialBackgroundPaint)) {
1233:             return false;   
1234:         }
1235:         if (!PaintUtilities.equal(this.needlePaint, that.needlePaint)) {
1236:             return false;   
1237:         }
1238:         if (!ObjectUtilities.equal(this.valueFont, that.valueFont)) {
1239:             return false;   
1240:         }
1241:         if (!PaintUtilities.equal(this.valuePaint, that.valuePaint)) {
1242:             return false;   
1243:         }
1244:         if (!PaintUtilities.equal(this.tickPaint, that.tickPaint)) {
1245:             return false;
1246:         }
1247:         if (this.tickSize != that.tickSize) {
1248:             return false;
1249:         }
1250:         if (this.tickLabelsVisible != that.tickLabelsVisible) {
1251:             return false;   
1252:         }
1253:         if (!ObjectUtilities.equal(this.tickLabelFont, that.tickLabelFont)) {
1254:             return false;   
1255:         }
1256:         if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) {
1257:             return false;
1258:         }
1259:         if (!ObjectUtilities.equal(this.tickLabelFormat, 
1260:                 that.tickLabelFormat)) {
1261:             return false;   
1262:         }
1263:         if (this.drawBorder != that.drawBorder) {
1264:             return false;   
1265:         }
1266:         if (this.meterAngle != that.meterAngle) {
1267:             return false;   
1268:         }
1269:         return true;      
1270:     }
1271:     
1272:     /**
1273:      * Provides serialization support.
1274:      *
1275:      * @param stream  the output stream.
1276:      *
1277:      * @throws IOException  if there is an I/O error.
1278:      */
1279:     private void writeObject(ObjectOutputStream stream) throws IOException {
1280:         stream.defaultWriteObject();
1281:         SerialUtilities.writePaint(this.dialBackgroundPaint, stream);
1282:         SerialUtilities.writePaint(this.needlePaint, stream);
1283:         SerialUtilities.writePaint(this.valuePaint, stream);
1284:         SerialUtilities.writePaint(this.tickPaint, stream);
1285:         SerialUtilities.writePaint(this.tickLabelPaint, stream);
1286:     }
1287:     
1288:     /**
1289:      * Provides serialization support.
1290:      *
1291:      * @param stream  the input stream.
1292:      *
1293:      * @throws IOException  if there is an I/O error.
1294:      * @throws ClassNotFoundException  if there is a classpath problem.
1295:      */
1296:     private void readObject(ObjectInputStream stream) 
1297:         throws IOException, ClassNotFoundException {
1298:         stream.defaultReadObject();
1299:         this.dialBackgroundPaint = SerialUtilities.readPaint(stream);
1300:         this.needlePaint = SerialUtilities.readPaint(stream);
1301:         this.valuePaint = SerialUtilities.readPaint(stream);
1302:         this.tickPaint = SerialUtilities.readPaint(stream);
1303:         this.tickLabelPaint = SerialUtilities.readPaint(stream);
1304:         if (this.dataset != null) {
1305:             this.dataset.addChangeListener(this);
1306:         }
1307:     }
1308: 
1309:     /** 
1310:      * Returns an independent copy (clone) of the plot.  The dataset is NOT 
1311:      * cloned - both the original and the clone will have a reference to the
1312:      * same dataset.
1313:      * 
1314:      * @return A clone.
1315:      * 
1316:      * @throws CloneNotSupportedException if some component of the plot cannot
1317:      *         be cloned.
1318:      */
1319:     public Object clone() throws CloneNotSupportedException {
1320:         MeterPlot clone = (MeterPlot) super.clone();
1321:         clone.tickLabelFormat = (NumberFormat) this.tickLabelFormat.clone();
1322:         // the following relies on the fact that the intervals are immutable
1323:         clone.intervals = new java.util.ArrayList(this.intervals);
1324:         if (clone.dataset != null) {
1325:             clone.dataset.addChangeListener(clone); 
1326:         }
1327:         return clone;
1328:     }
1329: 
1330: }