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