Source for org.jfree.chart.plot.CompassPlot

   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:  * CompassPlot.java
  29:  * ----------------
  30:  * (C) Copyright 2002-2007, by the Australian Antarctic Division and 
  31:  * Contributors.
  32:  *
  33:  * Original Author:  Bryan Scott (for the Australian Antarctic Division);
  34:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  35:  *                   Arnaud Lelievre;
  36:  *
  37:  * Changes:
  38:  * --------
  39:  * 25-Sep-2002 : Version 1, contributed by Bryan Scott (DG);
  40:  * 23-Jan-2003 : Removed one constructor (DG);
  41:  * 26-Mar-2003 : Implemented Serializable (DG);
  42:  * 27-Mar-2003 : Changed MeterDataset to ValueDataset (DG);
  43:  * 21-Aug-2003 : Implemented Cloneable (DG);
  44:  * 08-Sep-2003 : Added internationalization via use of properties 
  45:  *               resourceBundle (RFE 690236) (AL);
  46:  * 09-Sep-2003 : Changed Color --> Paint (DG);
  47:  * 15-Sep-2003 : Added null data value check (bug report 805009) (DG);
  48:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  49:  * 16-Mar-2004 : Added support for revolutionDistance to enable support for
  50:  *               other units than degrees.
  51:  * 16-Mar-2004 : Enabled LongNeedle to rotate about center.
  52:  * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
  53:  * 17-Apr-2005 : Fixed bug in clone() method (DG);
  54:  * 05-May-2005 : Updated draw() method parameters (DG);
  55:  * 08-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
  56:  * 16-Jun-2005 : Renamed getData() --> getDatasets() and 
  57:  *               addData() --> addDataset() (DG);
  58:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  59:  * 20-Mar-2007 : Fixed serialization (DG);
  60:  *
  61:  */
  62: 
  63: package org.jfree.chart.plot;
  64: 
  65: import java.awt.BasicStroke;
  66: import java.awt.Color;
  67: import java.awt.Font;
  68: import java.awt.Graphics2D;
  69: import java.awt.Paint;
  70: import java.awt.Polygon;
  71: import java.awt.Stroke;
  72: import java.awt.geom.Area;
  73: import java.awt.geom.Ellipse2D;
  74: import java.awt.geom.Point2D;
  75: import java.awt.geom.Rectangle2D;
  76: import java.io.IOException;
  77: import java.io.ObjectInputStream;
  78: import java.io.ObjectOutputStream;
  79: import java.io.Serializable;
  80: import java.util.Arrays;
  81: import java.util.ResourceBundle;
  82: 
  83: import org.jfree.chart.LegendItemCollection;
  84: import org.jfree.chart.event.PlotChangeEvent;
  85: import org.jfree.chart.needle.ArrowNeedle;
  86: import org.jfree.chart.needle.LineNeedle;
  87: import org.jfree.chart.needle.LongNeedle;
  88: import org.jfree.chart.needle.MeterNeedle;
  89: import org.jfree.chart.needle.MiddlePinNeedle;
  90: import org.jfree.chart.needle.PinNeedle;
  91: import org.jfree.chart.needle.PlumNeedle;
  92: import org.jfree.chart.needle.PointerNeedle;
  93: import org.jfree.chart.needle.ShipNeedle;
  94: import org.jfree.chart.needle.WindNeedle;
  95: import org.jfree.data.general.DefaultValueDataset;
  96: import org.jfree.data.general.ValueDataset;
  97: import org.jfree.io.SerialUtilities;
  98: import org.jfree.ui.RectangleInsets;
  99: import org.jfree.util.ObjectUtilities;
 100: import org.jfree.util.PaintUtilities;
 101: 
 102: /**
 103:  * A specialised plot that draws a compass to indicate a direction based on the
 104:  * value from a {@link ValueDataset}.
 105:  */
 106: public class CompassPlot extends Plot implements Cloneable, Serializable {
 107: 
 108:     /** For serialization. */
 109:     private static final long serialVersionUID = 6924382802125527395L;
 110:     
 111:     /** The default label font. */
 112:     public static final Font DEFAULT_LABEL_FONT = new Font("SansSerif", 
 113:             Font.BOLD, 10);
 114: 
 115:     /** A constant for the label type. */
 116:     public static final int NO_LABELS = 0;
 117: 
 118:     /** A constant for the label type. */
 119:     public static final int VALUE_LABELS = 1;
 120: 
 121:     /** The label type (NO_LABELS, VALUE_LABELS). */
 122:     private int labelType;
 123: 
 124:     /** The label font. */
 125:     private Font labelFont;
 126: 
 127:     /** A flag that controls whether or not a border is drawn. */
 128:     private boolean drawBorder = false;
 129: 
 130:     /** The rose highlight paint. */
 131:     private transient Paint roseHighlightPaint = Color.black;
 132: 
 133:     /** The rose paint. */
 134:     private transient Paint rosePaint = Color.yellow;
 135: 
 136:     /** The rose center paint. */
 137:     private transient Paint roseCenterPaint = Color.white;
 138: 
 139:     /** The compass font. */
 140:     private Font compassFont = new Font("Arial", Font.PLAIN, 10);
 141: 
 142:     /** A working shape. */
 143:     private transient Ellipse2D circle1;
 144: 
 145:     /** A working shape. */
 146:     private transient Ellipse2D circle2;
 147: 
 148:     /** A working area. */
 149:     private transient Area a1;
 150: 
 151:     /** A working area. */
 152:     private transient Area a2;
 153: 
 154:     /** A working shape. */
 155:     private transient Rectangle2D rect1;
 156: 
 157:     /** An array of value datasets. */
 158:     private ValueDataset[] datasets = new ValueDataset[1];
 159: 
 160:     /** An array of needles. */
 161:     private MeterNeedle[] seriesNeedle = new MeterNeedle[1];
 162: 
 163:     /** The resourceBundle for the localization. */
 164:     protected static ResourceBundle localizationResources 
 165:             = ResourceBundle.getBundle(
 166:                     "org.jfree.chart.plot.LocalizationBundle");
 167: 
 168:     /** 
 169:      * The count to complete one revolution.  Can be arbitrarily set
 170:      * For degrees (the default) it is 360, for radians this is 2*Pi, etc
 171:      */
 172:     protected double revolutionDistance = 360;
 173: 
 174:     /**
 175:      * Default constructor.
 176:      */
 177:     public CompassPlot() {
 178:         this(new DefaultValueDataset());
 179:     }
 180: 
 181:     /**
 182:      * Constructs a new compass plot.
 183:      *
 184:      * @param dataset  the dataset for the plot (<code>null</code> permitted).
 185:      */
 186:     public CompassPlot(ValueDataset dataset) {
 187:         super();
 188:         if (dataset != null) {
 189:             this.datasets[0] = dataset;
 190:             dataset.addChangeListener(this);
 191:         }
 192:         this.circle1 = new Ellipse2D.Double();
 193:         this.circle2 = new Ellipse2D.Double();
 194:         this.rect1   = new Rectangle2D.Double();
 195:         setSeriesNeedle(0);
 196:     }
 197: 
 198:     /**
 199:      * Returns the label type.  Defined by the constants: {@link #NO_LABELS}
 200:      * and {@link #VALUE_LABELS}.
 201:      *
 202:      * @return The label type.
 203:      * 
 204:      * @see #setLabelType(int)
 205:      */
 206:     public int getLabelType() {
 207:         // FIXME: this attribute is never used - deprecate?
 208:         return this.labelType;
 209:     }
 210: 
 211:     /**
 212:      * Sets the label type (either {@link #NO_LABELS} or {@link #VALUE_LABELS}.
 213:      *
 214:      * @param type  the type.
 215:      * 
 216:      * @see #getLabelType()
 217:      */
 218:     public void setLabelType(int type) {
 219:         // FIXME: this attribute is never used - deprecate?
 220:         if ((type != NO_LABELS) && (type != VALUE_LABELS)) {
 221:             throw new IllegalArgumentException(
 222:                     "MeterPlot.setLabelType(int): unrecognised type.");
 223:         }
 224:         if (this.labelType != type) {
 225:             this.labelType = type;
 226:             notifyListeners(new PlotChangeEvent(this));
 227:         }
 228:     }
 229: 
 230:     /**
 231:      * Returns the label font.
 232:      *
 233:      * @return The label font.
 234:      * 
 235:      * @see #setLabelFont(Font)
 236:      */
 237:     public Font getLabelFont() {
 238:         // FIXME: this attribute is not used - deprecate?
 239:         return this.labelFont;
 240:     }
 241: 
 242:     /**
 243:      * Sets the label font and sends a {@link PlotChangeEvent} to all 
 244:      * registered listeners.
 245:      *
 246:      * @param font  the new label font.
 247:      * 
 248:      * @see #getLabelFont()
 249:      */
 250:     public void setLabelFont(Font font) {
 251:         // FIXME: this attribute is not used - deprecate?
 252:         if (font == null) {
 253:             throw new IllegalArgumentException("Null 'font' not allowed.");
 254:         }
 255:         this.labelFont = font;
 256:         notifyListeners(new PlotChangeEvent(this));
 257:     }
 258: 
 259:     /**
 260:      * Returns the paint used to fill the outer circle of the compass.
 261:      * 
 262:      * @return The paint (never <code>null</code>).
 263:      * 
 264:      * @see #setRosePaint(Paint)
 265:      */
 266:     public Paint getRosePaint() {
 267:         return this.rosePaint;   
 268:     }
 269:     
 270:     /**
 271:      * Sets the paint used to fill the outer circle of the compass, 
 272:      * and sends a {@link PlotChangeEvent} to all registered listeners.
 273:      * 
 274:      * @param paint  the paint (<code>null</code> not permitted).
 275:      * 
 276:      * @see #getRosePaint()
 277:      */
 278:     public void setRosePaint(Paint paint) {
 279:         if (paint == null) {   
 280:             throw new IllegalArgumentException("Null 'paint' argument.");
 281:         }
 282:         this.rosePaint = paint;
 283:         notifyListeners(new PlotChangeEvent(this));        
 284:     }
 285: 
 286:     /**
 287:      * Returns the paint used to fill the inner background area of the 
 288:      * compass.
 289:      * 
 290:      * @return The paint (never <code>null</code>).
 291:      * 
 292:      * @see #setRoseCenterPaint(Paint)
 293:      */
 294:     public Paint getRoseCenterPaint() {
 295:         return this.roseCenterPaint;   
 296:     }
 297:     
 298:     /**
 299:      * Sets the paint used to fill the inner background area of the compass, 
 300:      * and sends a {@link PlotChangeEvent} to all registered listeners.
 301:      * 
 302:      * @param paint  the paint (<code>null</code> not permitted).
 303:      * 
 304:      * @see #getRoseCenterPaint()
 305:      */
 306:     public void setRoseCenterPaint(Paint paint) {
 307:         if (paint == null) {   
 308:             throw new IllegalArgumentException("Null 'paint' argument.");
 309:         }
 310:         this.roseCenterPaint = paint;
 311:         notifyListeners(new PlotChangeEvent(this));        
 312:     }
 313:     
 314:     /**
 315:      * Returns the paint used to draw the circles, symbols and labels on the
 316:      * compass.
 317:      * 
 318:      * @return The paint (never <code>null</code>).
 319:      * 
 320:      * @see #setRoseHighlightPaint(Paint)
 321:      */
 322:     public Paint getRoseHighlightPaint() {
 323:         return this.roseHighlightPaint;   
 324:     }
 325:     
 326:     /**
 327:      * Sets the paint used to draw the circles, symbols and labels of the 
 328:      * compass, and sends a {@link PlotChangeEvent} to all registered listeners.
 329:      * 
 330:      * @param paint  the paint (<code>null</code> not permitted).
 331:      * 
 332:      * @see #getRoseHighlightPaint()
 333:      */
 334:     public void setRoseHighlightPaint(Paint paint) {
 335:         if (paint == null) {   
 336:             throw new IllegalArgumentException("Null 'paint' argument.");
 337:         }
 338:         this.roseHighlightPaint = paint;
 339:         notifyListeners(new PlotChangeEvent(this));        
 340:     }
 341:     
 342:     /**
 343:      * Returns a flag that controls whether or not a border is drawn.
 344:      *
 345:      * @return The flag.
 346:      * 
 347:      * @see #setDrawBorder(boolean)
 348:      */
 349:     public boolean getDrawBorder() {
 350:         return this.drawBorder;
 351:     }
 352: 
 353:     /**
 354:      * Sets a flag that controls whether or not a border is drawn.
 355:      *
 356:      * @param status  the flag status.
 357:      * 
 358:      * @see #getDrawBorder()
 359:      */
 360:     public void setDrawBorder(boolean status) {
 361:         this.drawBorder = status;
 362:         notifyListeners(new PlotChangeEvent(this));
 363:     }
 364: 
 365:     /**
 366:      * Sets the series paint.
 367:      *
 368:      * @param series  the series index.
 369:      * @param paint  the paint.
 370:      * 
 371:      * @see #setSeriesOutlinePaint(int, Paint)
 372:      */
 373:     public void setSeriesPaint(int series, Paint paint) {
 374:        // super.setSeriesPaint(series, paint);
 375:         if ((series >= 0) && (series < this.seriesNeedle.length)) {
 376:             this.seriesNeedle[series].setFillPaint(paint);
 377:         }
 378:     }
 379: 
 380:     /**
 381:      * Sets the series outline paint.
 382:      *
 383:      * @param series  the series index.
 384:      * @param p  the paint.
 385:      * 
 386:      * @see #setSeriesPaint(int, Paint)
 387:      */
 388:     public void setSeriesOutlinePaint(int series, Paint p) {
 389: 
 390:         if ((series >= 0) && (series < this.seriesNeedle.length)) {
 391:             this.seriesNeedle[series].setOutlinePaint(p);
 392:         }
 393: 
 394:     }
 395: 
 396:     /**
 397:      * Sets the series outline stroke.
 398:      *
 399:      * @param series  the series index.
 400:      * @param stroke  the stroke.
 401:      * 
 402:      * @see #setSeriesOutlinePaint(int, Paint)
 403:      */
 404:     public void setSeriesOutlineStroke(int series, Stroke stroke) {
 405: 
 406:         if ((series >= 0) && (series < this.seriesNeedle.length)) {
 407:             this.seriesNeedle[series].setOutlineStroke(stroke);
 408:         }
 409: 
 410:     }
 411: 
 412:     /**
 413:      * Sets the needle type.
 414:      *
 415:      * @param type  the type.
 416:      * 
 417:      * @see #setSeriesNeedle(int, int)
 418:      */
 419:     public void setSeriesNeedle(int type) {
 420:         setSeriesNeedle(0, type);
 421:     }
 422: 
 423:     /**
 424:      * Sets the needle for a series.  The needle type is one of the following:
 425:      * <ul>
 426:      * <li>0 = {@link ArrowNeedle};</li>
 427:      * <li>1 = {@link LineNeedle};</li>
 428:      * <li>2 = {@link LongNeedle};</li>
 429:      * <li>3 = {@link PinNeedle};</li>
 430:      * <li>4 = {@link PlumNeedle};</li>
 431:      * <li>5 = {@link PointerNeedle};</li>
 432:      * <li>6 = {@link ShipNeedle};</li>
 433:      * <li>7 = {@link WindNeedle};</li>
 434:      * <li>8 = {@link ArrowNeedle};</li>
 435:      * <li>9 = {@link MiddlePinNeedle};</li>
 436:      * </ul>
 437:      * @param index  the series index.
 438:      * @param type  the needle type.
 439:      * 
 440:      * @see #setSeriesNeedle(int)
 441:      */
 442:     public void setSeriesNeedle(int index, int type) {
 443:         switch (type) {
 444:             case 0:
 445:                 setSeriesNeedle(index, new ArrowNeedle(true));
 446:                 setSeriesPaint(index, Color.red);
 447:                 this.seriesNeedle[index].setHighlightPaint(Color.white);
 448:                 break;
 449:             case 1:
 450:                 setSeriesNeedle(index, new LineNeedle());
 451:                 break;
 452:             case 2:
 453:                 MeterNeedle longNeedle = new LongNeedle();
 454:                 longNeedle.setRotateY(0.5);
 455:                 setSeriesNeedle(index, longNeedle);
 456:                 break;
 457:             case 3:
 458:                 setSeriesNeedle(index, new PinNeedle());
 459:                 break;
 460:             case 4:
 461:                 setSeriesNeedle(index, new PlumNeedle());
 462:                 break;
 463:             case 5:
 464:                 setSeriesNeedle(index, new PointerNeedle());
 465:                 break;
 466:             case 6:
 467:                 setSeriesPaint(index, null);
 468:                 setSeriesOutlineStroke(index, new BasicStroke(3));
 469:                 setSeriesNeedle(index, new ShipNeedle());
 470:                 break;
 471:             case 7:
 472:                 setSeriesPaint(index, Color.blue);
 473:                 setSeriesNeedle(index, new WindNeedle());
 474:                 break;
 475:             case 8:
 476:                 setSeriesNeedle(index, new ArrowNeedle(true));
 477:                 break;
 478:             case 9:
 479:                 setSeriesNeedle(index, new MiddlePinNeedle());
 480:                 break;
 481: 
 482:             default:
 483:                 throw new IllegalArgumentException("Unrecognised type.");
 484:         }
 485: 
 486:     }
 487: 
 488:     /**
 489:      * Sets the needle for a series and sends a {@link PlotChangeEvent} to all
 490:      * registered listeners.
 491:      *
 492:      * @param index  the series index.
 493:      * @param needle  the needle.
 494:      */
 495:     public void setSeriesNeedle(int index, MeterNeedle needle) {
 496: 
 497:         if ((needle != null) && (index < this.seriesNeedle.length)) {
 498:             this.seriesNeedle[index] = needle;
 499:         }
 500:         notifyListeners(new PlotChangeEvent(this));
 501: 
 502:     }
 503: 
 504:     /**
 505:      * Returns an array of dataset references for the plot.
 506:      *
 507:      * @return The dataset for the plot, cast as a ValueDataset.
 508:      * 
 509:      * @see #addDataset(ValueDataset)
 510:      */
 511:     public ValueDataset[] getDatasets() {
 512:         return this.datasets;
 513:     }
 514: 
 515:     /**
 516:      * Adds a dataset to the compass.
 517:      *
 518:      * @param dataset  the new dataset (<code>null</code> ignored).
 519:      * 
 520:      * @see #addDataset(ValueDataset, MeterNeedle)
 521:      */
 522:     public void addDataset(ValueDataset dataset) {
 523:         addDataset(dataset, null);
 524:     }
 525: 
 526:     /**
 527:      * Adds a dataset to the compass.
 528:      *
 529:      * @param dataset  the new dataset (<code>null</code> ignored).
 530:      * @param needle  the needle (<code>null</code> permitted).
 531:      */
 532:     public void addDataset(ValueDataset dataset, MeterNeedle needle) {
 533: 
 534:         if (dataset != null) {
 535:             int i = this.datasets.length + 1;
 536:             ValueDataset[] t = new ValueDataset[i];
 537:             MeterNeedle[] p = new MeterNeedle[i];
 538:             i = i - 2;
 539:             for (; i >= 0; --i) {
 540:                 t[i] = this.datasets[i];
 541:                 p[i] = this.seriesNeedle[i];
 542:             }
 543:             i = this.datasets.length;
 544:             t[i] = dataset;
 545:             p[i] = ((needle != null) ? needle : p[i - 1]);
 546: 
 547:             ValueDataset[] a = this.datasets;
 548:             MeterNeedle[] b = this.seriesNeedle;
 549:             this.datasets = t;
 550:             this.seriesNeedle = p;
 551: 
 552:             for (--i; i >= 0; --i) {
 553:                 a[i] = null;
 554:                 b[i] = null;
 555:             }
 556:             dataset.addChangeListener(this);
 557:         }
 558:     }
 559: 
 560:     /**
 561:      * Draws the plot on a Java 2D graphics device (such as the screen or a 
 562:      * printer).
 563:      *
 564:      * @param g2  the graphics device.
 565:      * @param area  the area within which the plot should be drawn.
 566:      * @param anchor  the anchor point (<code>null</code> permitted).
 567:      * @param parentState  the state from the parent plot, if there is one.
 568:      * @param info  collects info about the drawing.
 569:      */
 570:     public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
 571:                      PlotState parentState,
 572:                      PlotRenderingInfo info) {
 573: 
 574:         int outerRadius = 0;
 575:         int innerRadius = 0;
 576:         int x1, y1, x2, y2;
 577:         double a;
 578: 
 579:         if (info != null) {
 580:             info.setPlotArea(area);
 581:         }
 582: 
 583:         // adjust for insets...
 584:         RectangleInsets insets = getInsets();
 585:         insets.trim(area);
 586: 
 587:         // draw the background
 588:         if (this.drawBorder) {
 589:             drawBackground(g2, area);
 590:         }
 591: 
 592:         int midX = (int) (area.getWidth() / 2);
 593:         int midY = (int) (area.getHeight() / 2);
 594:         int radius = midX;
 595:         if (midY < midX) {
 596:             radius = midY;
 597:         }
 598:         --radius;
 599:         int diameter = 2 * radius;
 600: 
 601:         midX += (int) area.getMinX();
 602:         midY += (int) area.getMinY();
 603: 
 604:         this.circle1.setFrame(midX - radius, midY - radius, diameter, diameter);
 605:         this.circle2.setFrame(
 606:             midX - radius + 15, midY - radius + 15, 
 607:             diameter - 30, diameter - 30
 608:         );
 609:         g2.setPaint(this.rosePaint);
 610:         this.a1 = new Area(this.circle1);
 611:         this.a2 = new Area(this.circle2);
 612:         this.a1.subtract(this.a2);
 613:         g2.fill(this.a1);
 614: 
 615:         g2.setPaint(this.roseCenterPaint);
 616:         x1 = diameter - 30;
 617:         g2.fillOval(midX - radius + 15, midY - radius + 15, x1, x1);
 618:         g2.setPaint(this.roseHighlightPaint);
 619:         g2.drawOval(midX - radius, midY - radius, diameter, diameter);
 620:         x1 = diameter - 20;
 621:         g2.drawOval(midX - radius + 10, midY - radius + 10, x1, x1);
 622:         x1 = diameter - 30;
 623:         g2.drawOval(midX - radius + 15, midY - radius + 15, x1, x1);
 624:         x1 = diameter - 80;
 625:         g2.drawOval(midX - radius + 40, midY - radius + 40, x1, x1);
 626: 
 627:         outerRadius = radius - 20;
 628:         innerRadius = radius - 32;
 629:         for (int w = 0; w < 360; w += 15) {
 630:             a = Math.toRadians(w);
 631:             x1 = midX - ((int) (Math.sin(a) * innerRadius));
 632:             x2 = midX - ((int) (Math.sin(a) * outerRadius));
 633:             y1 = midY - ((int) (Math.cos(a) * innerRadius));
 634:             y2 = midY - ((int) (Math.cos(a) * outerRadius));
 635:             g2.drawLine(x1, y1, x2, y2);
 636:         }
 637: 
 638:         g2.setPaint(this.roseHighlightPaint);
 639:         innerRadius = radius - 26;
 640:         outerRadius = 7;
 641:         for (int w = 45; w < 360; w += 90) {
 642:             a = Math.toRadians(w);
 643:             x1 = midX - ((int) (Math.sin(a) * innerRadius));
 644:             y1 = midY - ((int) (Math.cos(a) * innerRadius));
 645:             g2.fillOval(x1 - outerRadius, y1 - outerRadius, 2 * outerRadius, 
 646:                     2 * outerRadius);
 647:         }
 648: 
 649:         /// Squares
 650:         for (int w = 0; w < 360; w += 90) {
 651:             a = Math.toRadians(w);
 652:             x1 = midX - ((int) (Math.sin(a) * innerRadius));
 653:             y1 = midY - ((int) (Math.cos(a) * innerRadius));
 654: 
 655:             Polygon p = new Polygon();
 656:             p.addPoint(x1 - outerRadius, y1);
 657:             p.addPoint(x1, y1 + outerRadius);
 658:             p.addPoint(x1 + outerRadius, y1);
 659:             p.addPoint(x1, y1 - outerRadius);
 660:             g2.fillPolygon(p);
 661:         }
 662: 
 663:         /// Draw N, S, E, W
 664:         innerRadius = radius - 42;
 665:         Font f = getCompassFont(radius);
 666:         g2.setFont(f);
 667:         g2.drawString("N", midX - 5, midY - innerRadius + f.getSize());
 668:         g2.drawString("S", midX - 5, midY + innerRadius - 5);
 669:         g2.drawString("W", midX - innerRadius + 5, midY + 5);
 670:         g2.drawString("E", midX + innerRadius - f.getSize(), midY + 5);
 671: 
 672:         // plot the data (unless the dataset is null)...
 673:         y1 = radius / 2;
 674:         x1 = radius / 6;
 675:         Rectangle2D needleArea = new Rectangle2D.Double(
 676:             (midX - x1), (midY - y1), (2 * x1), (2 * y1)
 677:         );
 678:         int x = this.seriesNeedle.length;
 679:         int current = 0;
 680:         double value = 0;
 681:         int i = (this.datasets.length - 1);
 682:         for (; i >= 0; --i) {
 683:             ValueDataset data = this.datasets[i];
 684: 
 685:             if (data != null && data.getValue() != null) {
 686:                 value = (data.getValue().doubleValue()) 
 687:                     % this.revolutionDistance;
 688:                 value = value / this.revolutionDistance * 360;
 689:                 current = i % x;
 690:                 this.seriesNeedle[current].draw(g2, needleArea, value);
 691:             }
 692:         }
 693: 
 694:         if (this.drawBorder) {
 695:             drawOutline(g2, area);
 696:         }
 697: 
 698:     }
 699: 
 700:     /**
 701:      * Returns a short string describing the type of plot.
 702:      *
 703:      * @return A string describing the plot.
 704:      */
 705:     public String getPlotType() {
 706:         return localizationResources.getString("Compass_Plot");
 707:     }
 708: 
 709:     /**
 710:      * Returns the legend items for the plot.  For now, no legend is available 
 711:      * - this method returns null.
 712:      *
 713:      * @return The legend items.
 714:      */
 715:     public LegendItemCollection getLegendItems() {
 716:         return null;
 717:     }
 718: 
 719:     /**
 720:      * No zooming is implemented for compass plot, so this method is empty.
 721:      *
 722:      * @param percent  the zoom amount.
 723:      */
 724:     public void zoom(double percent) {
 725:         // no zooming possible
 726:     }
 727: 
 728:     /**
 729:      * Returns the font for the compass, adjusted for the size of the plot.
 730:      *
 731:      * @param radius the radius.
 732:      *
 733:      * @return The font.
 734:      */
 735:     protected Font getCompassFont(int radius) {
 736:         float fontSize = radius / 10.0f;
 737:         if (fontSize < 8) {
 738:             fontSize = 8;
 739:         }
 740:         Font newFont = this.compassFont.deriveFont(fontSize);
 741:         return newFont;
 742:     }
 743: 
 744:     /**
 745:      * Tests an object for equality with this plot.
 746:      *
 747:      * @param obj  the object (<code>null</code> permitted).
 748:      *
 749:      * @return A boolean.
 750:      */
 751:     public boolean equals(Object obj) {
 752:         if (obj == this) {
 753:             return true;
 754:         }
 755:         if (!(obj instanceof CompassPlot)) {
 756:             return false;
 757:         }
 758:         if (!super.equals(obj)) {
 759:             return false;
 760:         }
 761:         CompassPlot that = (CompassPlot) obj;
 762:         if (this.labelType != that.labelType) {
 763:             return false;
 764:         }
 765:         if (!ObjectUtilities.equal(this.labelFont, that.labelFont)) {
 766:             return false;
 767:         }
 768:         if (this.drawBorder != that.drawBorder) {
 769:             return false;
 770:         }
 771:         if (!PaintUtilities.equal(this.roseHighlightPaint, 
 772:                 that.roseHighlightPaint)) {
 773:             return false;
 774:         }
 775:         if (!PaintUtilities.equal(this.rosePaint, that.rosePaint)) {
 776:             return false;
 777:         }
 778:         if (!PaintUtilities.equal(this.roseCenterPaint, 
 779:                 that.roseCenterPaint)) {
 780:             return false;
 781:         }
 782:         if (!ObjectUtilities.equal(this.compassFont, that.compassFont)) {
 783:             return false;
 784:         }
 785:         if (!Arrays.equals(this.seriesNeedle, that.seriesNeedle)) {
 786:             return false;
 787:         }
 788:         if (getRevolutionDistance() != that.getRevolutionDistance()) {
 789:             return false;
 790:         }
 791:         return true;
 792: 
 793:     }
 794: 
 795:     /**
 796:      * Returns a clone of the plot.
 797:      *
 798:      * @return A clone.
 799:      *
 800:      * @throws CloneNotSupportedException  this class will not throw this 
 801:      *         exception, but subclasses (if any) might.
 802:      */
 803:     public Object clone() throws CloneNotSupportedException {
 804: 
 805:         CompassPlot clone = (CompassPlot) super.clone();
 806:         if (this.circle1 != null) {
 807:             clone.circle1 = (Ellipse2D) this.circle1.clone();
 808:         }
 809:         if (this.circle2 != null) {
 810:             clone.circle2 = (Ellipse2D) this.circle2.clone();
 811:         }
 812:         if (this.a1 != null) {
 813:             clone.a1 = (Area) this.a1.clone();
 814:         }
 815:         if (this.a2 != null) {
 816:             clone.a2 = (Area) this.a2.clone();
 817:         }
 818:         if (this.rect1 != null) {
 819:             clone.rect1 = (Rectangle2D) this.rect1.clone();            
 820:         }
 821:         clone.datasets = (ValueDataset[]) this.datasets.clone();
 822:         clone.seriesNeedle = (MeterNeedle[]) this.seriesNeedle.clone();
 823: 
 824:         // clone share data sets => add the clone as listener to the dataset
 825:         for (int i = 0; i < this.datasets.length; ++i) {
 826:             if (clone.datasets[i] != null) {
 827:                 clone.datasets[i].addChangeListener(clone);
 828:             }
 829:         }
 830:         return clone;
 831: 
 832:     }
 833: 
 834:     /**
 835:      * Sets the count to complete one revolution.  Can be arbitrarily set
 836:      * For degrees (the default) it is 360, for radians this is 2*Pi, etc
 837:      *
 838:      * @param size the count to complete one revolution.
 839:      * 
 840:      * @see #getRevolutionDistance()
 841:      */
 842:     public void setRevolutionDistance(double size) {
 843:         if (size > 0) {
 844:             this.revolutionDistance = size;
 845:         }
 846:     }
 847: 
 848:     /**
 849:      * Gets the count to complete one revolution.
 850:      *
 851:      * @return The count to complete one revolution.
 852:      * 
 853:      * @see #setRevolutionDistance(double)
 854:      */
 855:     public double getRevolutionDistance() {
 856:         return this.revolutionDistance;
 857:     }
 858:     
 859:     /**
 860:      * Provides serialization support.
 861:      *
 862:      * @param stream  the output stream.
 863:      *
 864:      * @throws IOException  if there is an I/O error.
 865:      */
 866:     private void writeObject(ObjectOutputStream stream) throws IOException {
 867:         stream.defaultWriteObject();
 868:         SerialUtilities.writePaint(this.rosePaint, stream);
 869:         SerialUtilities.writePaint(this.roseCenterPaint, stream);
 870:         SerialUtilities.writePaint(this.roseHighlightPaint, stream);
 871:     }
 872: 
 873:     /**
 874:      * Provides serialization support.
 875:      *
 876:      * @param stream  the input stream.
 877:      *
 878:      * @throws IOException  if there is an I/O error.
 879:      * @throws ClassNotFoundException  if there is a classpath problem.
 880:      */
 881:     private void readObject(ObjectInputStream stream) 
 882:         throws IOException, ClassNotFoundException {
 883:         stream.defaultReadObject();
 884:         this.rosePaint = SerialUtilities.readPaint(stream);
 885:         this.roseCenterPaint = SerialUtilities.readPaint(stream);
 886:         this.roseHighlightPaint = SerialUtilities.readPaint(stream);
 887:     }
 888: 
 889: }