Source for org.jfree.chart.plot.PolarPlot

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, 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:  * PolarPlot.java
  29:  * --------------
  30:  * (C) Copyright 2004-2008, by Solution Engineering, Inc. and Contributors.
  31:  *
  32:  * Original Author:  Daniel Bridenbecker, Solution Engineering, Inc.;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Martin Hoeller (patch 1871902);
  35:  *
  36:  * Changes
  37:  * -------
  38:  * 19-Jan-2004 : Version 1, contributed by DB with minor changes by DG (DG);
  39:  * 07-Apr-2004 : Changed text bounds calculation (DG);
  40:  * 05-May-2005 : Updated draw() method parameters (DG);
  41:  * 09-Jun-2005 : Fixed getDataRange() and equals() methods (DG);
  42:  * 25-Oct-2005 : Implemented Zoomable (DG);
  43:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  44:  * 07-Feb-2007 : Fixed bug 1599761, data value less than axis minimum (DG);
  45:  * 21-Mar-2007 : Fixed serialization bug (DG);
  46:  * 24-Sep-2007 : Implemented new zooming methods (DG);
  47:  * 17-Feb-2007 : Added angle tick unit attribute (see patch 1871902 by
  48:  *               Martin Hoeller) (DG);
  49:  *
  50:  */
  51: 
  52: package org.jfree.chart.plot;
  53: 
  54: import java.awt.AlphaComposite;
  55: import java.awt.BasicStroke;
  56: import java.awt.Color;
  57: import java.awt.Composite;
  58: import java.awt.Font;
  59: import java.awt.FontMetrics;
  60: import java.awt.Graphics2D;
  61: import java.awt.Paint;
  62: import java.awt.Point;
  63: import java.awt.Shape;
  64: import java.awt.Stroke;
  65: import java.awt.geom.Point2D;
  66: import java.awt.geom.Rectangle2D;
  67: import java.io.IOException;
  68: import java.io.ObjectInputStream;
  69: import java.io.ObjectOutputStream;
  70: import java.io.Serializable;
  71: import java.util.ArrayList;
  72: import java.util.Iterator;
  73: import java.util.List;
  74: import java.util.ResourceBundle;
  75: 
  76: import org.jfree.chart.LegendItem;
  77: import org.jfree.chart.LegendItemCollection;
  78: import org.jfree.chart.axis.AxisState;
  79: import org.jfree.chart.axis.NumberTick;
  80: import org.jfree.chart.axis.NumberTickUnit;
  81: import org.jfree.chart.axis.TickUnit;
  82: import org.jfree.chart.axis.ValueAxis;
  83: import org.jfree.chart.event.PlotChangeEvent;
  84: import org.jfree.chart.event.RendererChangeEvent;
  85: import org.jfree.chart.event.RendererChangeListener;
  86: import org.jfree.chart.renderer.PolarItemRenderer;
  87: import org.jfree.data.Range;
  88: import org.jfree.data.general.DatasetChangeEvent;
  89: import org.jfree.data.general.DatasetUtilities;
  90: import org.jfree.data.xy.XYDataset;
  91: import org.jfree.io.SerialUtilities;
  92: import org.jfree.text.TextUtilities;
  93: import org.jfree.ui.RectangleEdge;
  94: import org.jfree.ui.RectangleInsets;
  95: import org.jfree.ui.TextAnchor;
  96: import org.jfree.util.ObjectUtilities;
  97: import org.jfree.util.PaintUtilities;
  98: 
  99: /**
 100:  * Plots data that is in (theta, radius) pairs where
 101:  * theta equal to zero is due north and increases clockwise.
 102:  */
 103: public class PolarPlot extends Plot implements ValueAxisPlot, Zoomable,
 104:         RendererChangeListener, Cloneable, Serializable {
 105: 
 106:     /** For serialization. */
 107:     private static final long serialVersionUID = 3794383185924179525L;
 108: 
 109:     /** The default margin. */
 110:     private static final int MARGIN = 20;
 111: 
 112:     /** The annotation margin. */
 113:     private static final double ANNOTATION_MARGIN = 7.0;
 114: 
 115:     /**
 116:      * The default angle tick unit size.
 117:      *
 118:      * @since 1.0.10
 119:      */
 120:     public static final double DEFAULT_ANGLE_TICK_UNIT_SIZE = 45.0;
 121: 
 122:     /** The default grid line stroke. */
 123:     public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(
 124:             0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
 125:             0.0f, new float[]{2.0f, 2.0f}, 0.0f);
 126: 
 127:     /** The default grid line paint. */
 128:     public static final Paint DEFAULT_GRIDLINE_PAINT = Color.gray;
 129: 
 130:     /** The resourceBundle for the localization. */
 131:     protected static ResourceBundle localizationResources
 132:         = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
 133: 
 134:     /** The angles that are marked with gridlines. */
 135:     private List angleTicks;
 136: 
 137:     /** The axis (used for the y-values). */
 138:     private ValueAxis axis;
 139: 
 140:     /** The dataset. */
 141:     private XYDataset dataset;
 142: 
 143:     /**
 144:      * Object responsible for drawing the visual representation of each point
 145:      * on the plot.
 146:      */
 147:     private PolarItemRenderer renderer;
 148: 
 149:     /**
 150:      * The tick unit that controls the spacing between the angular grid lines.
 151:      *
 152:      * @since 1.0.10
 153:      */
 154:     private TickUnit angleTickUnit;
 155: 
 156:     /** A flag that controls whether or not the angle labels are visible. */
 157:     private boolean angleLabelsVisible = true;
 158: 
 159:     /** The font used to display the angle labels - never null. */
 160:     private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12);
 161: 
 162:     /** The paint used to display the angle labels. */
 163:     private transient Paint angleLabelPaint = Color.black;
 164: 
 165:     /** A flag that controls whether the angular grid-lines are visible. */
 166:     private boolean angleGridlinesVisible;
 167: 
 168:     /** The stroke used to draw the angular grid-lines. */
 169:     private transient Stroke angleGridlineStroke;
 170: 
 171:     /** The paint used to draw the angular grid-lines. */
 172:     private transient Paint angleGridlinePaint;
 173: 
 174:     /** A flag that controls whether the radius grid-lines are visible. */
 175:     private boolean radiusGridlinesVisible;
 176: 
 177:     /** The stroke used to draw the radius grid-lines. */
 178:     private transient Stroke radiusGridlineStroke;
 179: 
 180:     /** The paint used to draw the radius grid-lines. */
 181:     private transient Paint radiusGridlinePaint;
 182: 
 183:     /** The annotations for the plot. */
 184:     private List cornerTextItems = new ArrayList();
 185: 
 186:     /**
 187:      * Default constructor.
 188:      */
 189:     public PolarPlot() {
 190:         this(null, null, null);
 191:     }
 192: 
 193:    /**
 194:      * Creates a new plot.
 195:      *
 196:      * @param dataset  the dataset (<code>null</code> permitted).
 197:      * @param radiusAxis  the radius axis (<code>null</code> permitted).
 198:      * @param renderer  the renderer (<code>null</code> permitted).
 199:      */
 200:     public PolarPlot(XYDataset dataset,
 201:                      ValueAxis radiusAxis,
 202:                      PolarItemRenderer renderer) {
 203: 
 204:         super();
 205: 
 206:         this.dataset = dataset;
 207:         if (this.dataset != null) {
 208:             this.dataset.addChangeListener(this);
 209:         }
 210:         this.angleTickUnit = new NumberTickUnit(DEFAULT_ANGLE_TICK_UNIT_SIZE);
 211: 
 212:         this.axis = radiusAxis;
 213:         if (this.axis != null) {
 214:             this.axis.setPlot(this);
 215:             this.axis.addChangeListener(this);
 216:         }
 217: 
 218:         this.renderer = renderer;
 219:         if (this.renderer != null) {
 220:             this.renderer.setPlot(this);
 221:             this.renderer.addChangeListener(this);
 222:         }
 223: 
 224:         this.angleGridlinesVisible = true;
 225:         this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE;
 226:         this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT;
 227: 
 228:         this.radiusGridlinesVisible = true;
 229:         this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE;
 230:         this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT;
 231:     }
 232: 
 233:     /**
 234:      * Add text to be displayed in the lower right hand corner and sends a
 235:      * {@link PlotChangeEvent} to all registered listeners.
 236:      *
 237:      * @param text  the text to display (<code>null</code> not permitted).
 238:      *
 239:      * @see #removeCornerTextItem(String)
 240:      */
 241:     public void addCornerTextItem(String text) {
 242:         if (text == null) {
 243:             throw new IllegalArgumentException("Null 'text' argument.");
 244:         }
 245:         this.cornerTextItems.add(text);
 246:         fireChangeEvent();
 247:     }
 248: 
 249:     /**
 250:      * Remove the given text from the list of corner text items and
 251:      * sends a {@link PlotChangeEvent} to all registered listeners.
 252:      *
 253:      * @param text  the text to remove (<code>null</code> ignored).
 254:      *
 255:      * @see #addCornerTextItem(String)
 256:      */
 257:     public void removeCornerTextItem(String text) {
 258:         boolean removed = this.cornerTextItems.remove(text);
 259:         if (removed) {
 260:             fireChangeEvent();
 261:         }
 262:     }
 263: 
 264:     /**
 265:      * Clear the list of corner text items and sends a {@link PlotChangeEvent}
 266:      * to all registered listeners.
 267:      *
 268:      * @see #addCornerTextItem(String)
 269:      * @see #removeCornerTextItem(String)
 270:      */
 271:     public void clearCornerTextItems() {
 272:         if (this.cornerTextItems.size() > 0) {
 273:             this.cornerTextItems.clear();
 274:             fireChangeEvent();
 275:         }
 276:     }
 277: 
 278:     /**
 279:      * Returns the plot type as a string.
 280:      *
 281:      * @return A short string describing the type of plot.
 282:      */
 283:     public String getPlotType() {
 284:        return PolarPlot.localizationResources.getString("Polar_Plot");
 285:     }
 286: 
 287:     /**
 288:      * Returns the axis for the plot.
 289:      *
 290:      * @return The radius axis (possibly <code>null</code>).
 291:      *
 292:      * @see #setAxis(ValueAxis)
 293:      */
 294:     public ValueAxis getAxis() {
 295:         return this.axis;
 296:     }
 297: 
 298:     /**
 299:      * Sets the axis for the plot and sends a {@link PlotChangeEvent} to all
 300:      * registered listeners.
 301:      *
 302:      * @param axis  the new axis (<code>null</code> permitted).
 303:      */
 304:     public void setAxis(ValueAxis axis) {
 305:         if (axis != null) {
 306:             axis.setPlot(this);
 307:         }
 308: 
 309:         // plot is likely registered as a listener with the existing axis...
 310:         if (this.axis != null) {
 311:             this.axis.removeChangeListener(this);
 312:         }
 313: 
 314:         this.axis = axis;
 315:         if (this.axis != null) {
 316:             this.axis.configure();
 317:             this.axis.addChangeListener(this);
 318:         }
 319:         fireChangeEvent();
 320:     }
 321: 
 322:     /**
 323:      * Returns the primary dataset for the plot.
 324:      *
 325:      * @return The primary dataset (possibly <code>null</code>).
 326:      *
 327:      * @see #setDataset(XYDataset)
 328:      */
 329:     public XYDataset getDataset() {
 330:         return this.dataset;
 331:     }
 332: 
 333:     /**
 334:      * Sets the dataset for the plot, replacing the existing dataset if there
 335:      * is one.
 336:      *
 337:      * @param dataset  the dataset (<code>null</code> permitted).
 338:      *
 339:      * @see #getDataset()
 340:      */
 341:     public void setDataset(XYDataset dataset) {
 342:         // if there is an existing dataset, remove the plot from the list of
 343:         // change listeners...
 344:         XYDataset existing = this.dataset;
 345:         if (existing != null) {
 346:             existing.removeChangeListener(this);
 347:         }
 348: 
 349:         // set the new m_Dataset, and register the chart as a change listener...
 350:         this.dataset = dataset;
 351:         if (this.dataset != null) {
 352:             setDatasetGroup(this.dataset.getGroup());
 353:             this.dataset.addChangeListener(this);
 354:         }
 355: 
 356:         // send a m_Dataset change event to self...
 357:         DatasetChangeEvent event = new DatasetChangeEvent(this, this.dataset);
 358:         datasetChanged(event);
 359:     }
 360: 
 361:     /**
 362:      * Returns the item renderer.
 363:      *
 364:      * @return The renderer (possibly <code>null</code>).
 365:      *
 366:      * @see #setRenderer(PolarItemRenderer)
 367:      */
 368:     public PolarItemRenderer getRenderer() {
 369:         return this.renderer;
 370:     }
 371: 
 372:     /**
 373:      * Sets the item renderer, and notifies all listeners of a change to the
 374:      * plot.
 375:      * <P>
 376:      * If the renderer is set to <code>null</code>, no chart will be drawn.
 377:      *
 378:      * @param renderer  the new renderer (<code>null</code> permitted).
 379:      *
 380:      * @see #getRenderer()
 381:      */
 382:     public void setRenderer(PolarItemRenderer renderer) {
 383:         if (this.renderer != null) {
 384:             this.renderer.removeChangeListener(this);
 385:         }
 386: 
 387:         this.renderer = renderer;
 388:         if (this.renderer != null) {
 389:             this.renderer.setPlot(this);
 390:         }
 391:         fireChangeEvent();
 392:     }
 393: 
 394:     /**
 395:      * Returns the tick unit that controls the spacing of the angular grid
 396:      * lines.
 397:      *
 398:      * @return The tick unit (never <code>null</code>).
 399:      *
 400:      * @since 1.0.10
 401:      */
 402:     public TickUnit getAngleTickUnit() {
 403:         return this.angleTickUnit;
 404:     }
 405: 
 406:     /**
 407:      * Sets the tick unit that controls the spacing of the angular grid
 408:      * lines, and sends a {@link PlotChangeEvent} to all registered listeners.
 409:      *
 410:      * @param unit  the tick unit (<code>null</code> not permitted).
 411:      *
 412:      * @since 1.0.10
 413:      */
 414:     public void setAngleTickUnit(TickUnit unit) {
 415:         if (unit == null) {
 416:             throw new IllegalArgumentException("Null 'unit' argument.");
 417:         }
 418:         this.angleTickUnit = unit;
 419:         fireChangeEvent();
 420:     }
 421: 
 422:     /**
 423:      * Returns a flag that controls whether or not the angle labels are visible.
 424:      *
 425:      * @return A boolean.
 426:      *
 427:      * @see #setAngleLabelsVisible(boolean)
 428:      */
 429:     public boolean isAngleLabelsVisible() {
 430:         return this.angleLabelsVisible;
 431:     }
 432: 
 433:     /**
 434:      * Sets the flag that controls whether or not the angle labels are visible,
 435:      * and sends a {@link PlotChangeEvent} to all registered listeners.
 436:      *
 437:      * @param visible  the flag.
 438:      *
 439:      * @see #isAngleLabelsVisible()
 440:      */
 441:     public void setAngleLabelsVisible(boolean visible) {
 442:         if (this.angleLabelsVisible != visible) {
 443:             this.angleLabelsVisible = visible;
 444:             fireChangeEvent();
 445:         }
 446:     }
 447: 
 448:     /**
 449:      * Returns the font used to display the angle labels.
 450:      *
 451:      * @return A font (never <code>null</code>).
 452:      *
 453:      * @see #setAngleLabelFont(Font)
 454:      */
 455:     public Font getAngleLabelFont() {
 456:         return this.angleLabelFont;
 457:     }
 458: 
 459:     /**
 460:      * Sets the font used to display the angle labels and sends a
 461:      * {@link PlotChangeEvent} to all registered listeners.
 462:      *
 463:      * @param font  the font (<code>null</code> not permitted).
 464:      *
 465:      * @see #getAngleLabelFont()
 466:      */
 467:     public void setAngleLabelFont(Font font) {
 468:         if (font == null) {
 469:             throw new IllegalArgumentException("Null 'font' argument.");
 470:         }
 471:         this.angleLabelFont = font;
 472:         fireChangeEvent();
 473:     }
 474: 
 475:     /**
 476:      * Returns the paint used to display the angle labels.
 477:      *
 478:      * @return A paint (never <code>null</code>).
 479:      *
 480:      * @see #setAngleLabelPaint(Paint)
 481:      */
 482:     public Paint getAngleLabelPaint() {
 483:         return this.angleLabelPaint;
 484:     }
 485: 
 486:     /**
 487:      * Sets the paint used to display the angle labels and sends a
 488:      * {@link PlotChangeEvent} to all registered listeners.
 489:      *
 490:      * @param paint  the paint (<code>null</code> not permitted).
 491:      */
 492:     public void setAngleLabelPaint(Paint paint) {
 493:         if (paint == null) {
 494:             throw new IllegalArgumentException("Null 'paint' argument.");
 495:         }
 496:         this.angleLabelPaint = paint;
 497:         fireChangeEvent();
 498:     }
 499: 
 500:     /**
 501:      * Returns <code>true</code> if the angular gridlines are visible, and
 502:      * <code>false<code> otherwise.
 503:      *
 504:      * @return <code>true</code> or <code>false</code>.
 505:      *
 506:      * @see #setAngleGridlinesVisible(boolean)
 507:      */
 508:     public boolean isAngleGridlinesVisible() {
 509:         return this.angleGridlinesVisible;
 510:     }
 511: 
 512:     /**
 513:      * Sets the flag that controls whether or not the angular grid-lines are
 514:      * visible.
 515:      * <p>
 516:      * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
 517:      * registered listeners.
 518:      *
 519:      * @param visible  the new value of the flag.
 520:      *
 521:      * @see #isAngleGridlinesVisible()
 522:      */
 523:     public void setAngleGridlinesVisible(boolean visible) {
 524:         if (this.angleGridlinesVisible != visible) {
 525:             this.angleGridlinesVisible = visible;
 526:             fireChangeEvent();
 527:         }
 528:     }
 529: 
 530:     /**
 531:      * Returns the stroke for the grid-lines (if any) plotted against the
 532:      * angular axis.
 533:      *
 534:      * @return The stroke (possibly <code>null</code>).
 535:      *
 536:      * @see #setAngleGridlineStroke(Stroke)
 537:      */
 538:     public Stroke getAngleGridlineStroke() {
 539:         return this.angleGridlineStroke;
 540:     }
 541: 
 542:     /**
 543:      * Sets the stroke for the grid lines plotted against the angular axis and
 544:      * sends a {@link PlotChangeEvent} to all registered listeners.
 545:      * <p>
 546:      * If you set this to <code>null</code>, no grid lines will be drawn.
 547:      *
 548:      * @param stroke  the stroke (<code>null</code> permitted).
 549:      *
 550:      * @see #getAngleGridlineStroke()
 551:      */
 552:     public void setAngleGridlineStroke(Stroke stroke) {
 553:         this.angleGridlineStroke = stroke;
 554:         fireChangeEvent();
 555:     }
 556: 
 557:     /**
 558:      * Returns the paint for the grid lines (if any) plotted against the
 559:      * angular axis.
 560:      *
 561:      * @return The paint (possibly <code>null</code>).
 562:      *
 563:      * @see #setAngleGridlinePaint(Paint)
 564:      */
 565:     public Paint getAngleGridlinePaint() {
 566:         return this.angleGridlinePaint;
 567:     }
 568: 
 569:     /**
 570:      * Sets the paint for the grid lines plotted against the angular axis.
 571:      * <p>
 572:      * If you set this to <code>null</code>, no grid lines will be drawn.
 573:      *
 574:      * @param paint  the paint (<code>null</code> permitted).
 575:      *
 576:      * @see #getAngleGridlinePaint()
 577:      */
 578:     public void setAngleGridlinePaint(Paint paint) {
 579:         this.angleGridlinePaint = paint;
 580:         fireChangeEvent();
 581:     }
 582: 
 583:     /**
 584:      * Returns <code>true</code> if the radius axis grid is visible, and
 585:      * <code>false<code> otherwise.
 586:      *
 587:      * @return <code>true</code> or <code>false</code>.
 588:      *
 589:      * @see #setRadiusGridlinesVisible(boolean)
 590:      */
 591:     public boolean isRadiusGridlinesVisible() {
 592:         return this.radiusGridlinesVisible;
 593:     }
 594: 
 595:     /**
 596:      * Sets the flag that controls whether or not the radius axis grid lines
 597:      * are visible.
 598:      * <p>
 599:      * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
 600:      * registered listeners.
 601:      *
 602:      * @param visible  the new value of the flag.
 603:      *
 604:      * @see #isRadiusGridlinesVisible()
 605:      */
 606:     public void setRadiusGridlinesVisible(boolean visible) {
 607:         if (this.radiusGridlinesVisible != visible) {
 608:             this.radiusGridlinesVisible = visible;
 609:             fireChangeEvent();
 610:         }
 611:     }
 612: 
 613:     /**
 614:      * Returns the stroke for the grid lines (if any) plotted against the
 615:      * radius axis.
 616:      *
 617:      * @return The stroke (possibly <code>null</code>).
 618:      *
 619:      * @see #setRadiusGridlineStroke(Stroke)
 620:      */
 621:     public Stroke getRadiusGridlineStroke() {
 622:         return this.radiusGridlineStroke;
 623:     }
 624: 
 625:     /**
 626:      * Sets the stroke for the grid lines plotted against the radius axis and
 627:      * sends a {@link PlotChangeEvent} to all registered listeners.
 628:      * <p>
 629:      * If you set this to <code>null</code>, no grid lines will be drawn.
 630:      *
 631:      * @param stroke  the stroke (<code>null</code> permitted).
 632:      *
 633:      * @see #getRadiusGridlineStroke()
 634:      */
 635:     public void setRadiusGridlineStroke(Stroke stroke) {
 636:         this.radiusGridlineStroke = stroke;
 637:         fireChangeEvent();
 638:     }
 639: 
 640:     /**
 641:      * Returns the paint for the grid lines (if any) plotted against the radius
 642:      * axis.
 643:      *
 644:      * @return The paint (possibly <code>null</code>).
 645:      *
 646:      * @see #setRadiusGridlinePaint(Paint)
 647:      */
 648:     public Paint getRadiusGridlinePaint() {
 649:         return this.radiusGridlinePaint;
 650:     }
 651: 
 652:     /**
 653:      * Sets the paint for the grid lines plotted against the radius axis and
 654:      * sends a {@link PlotChangeEvent} to all registered listeners.
 655:      * <p>
 656:      * If you set this to <code>null</code>, no grid lines will be drawn.
 657:      *
 658:      * @param paint  the paint (<code>null</code> permitted).
 659:      *
 660:      * @see #getRadiusGridlinePaint()
 661:      */
 662:     public void setRadiusGridlinePaint(Paint paint) {
 663:         this.radiusGridlinePaint = paint;
 664:         fireChangeEvent();
 665:     }
 666: 
 667:     /**
 668:      * Generates a list of tick values for the angular tick marks.
 669:      *
 670:      * @return A list of {@link NumberTick} instances.
 671:      *
 672:      * @since 1.0.10
 673:      */
 674:     protected List refreshAngleTicks() {
 675:         List ticks = new ArrayList();
 676:         for (double currentTickVal = 0.0; currentTickVal < 360.0;
 677:                 currentTickVal += this.angleTickUnit.getSize()) {
 678:             NumberTick tick = new NumberTick(new Double(currentTickVal),
 679:                 this.angleTickUnit.valueToString(currentTickVal),
 680:                 TextAnchor.CENTER, TextAnchor.CENTER, 0.0);
 681:             ticks.add(tick);
 682:         }
 683:         return ticks;
 684:     }
 685: 
 686:     /**
 687:      * Draws the plot on a Java 2D graphics device (such as the screen or a
 688:      * printer).
 689:      * <P>
 690:      * This plot relies on a {@link PolarItemRenderer} to draw each
 691:      * item in the plot.  This allows the visual representation of the data to
 692:      * be changed easily.
 693:      * <P>
 694:      * The optional info argument collects information about the rendering of
 695:      * the plot (dimensions, tooltip information etc).  Just pass in
 696:      * <code>null</code> if you do not need this information.
 697:      *
 698:      * @param g2  the graphics device.
 699:      * @param area  the area within which the plot (including axes and
 700:      *              labels) should be drawn.
 701:      * @param anchor  the anchor point (<code>null</code> permitted).
 702:      * @param parentState  ignored.
 703:      * @param info  collects chart drawing information (<code>null</code>
 704:      *              permitted).
 705:      */
 706:     public void draw(Graphics2D g2,
 707:                      Rectangle2D area,
 708:                      Point2D anchor,
 709:                      PlotState parentState,
 710:                      PlotRenderingInfo info) {
 711: 
 712:         // if the plot area is too small, just return...
 713:         boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
 714:         boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
 715:         if (b1 || b2) {
 716:             return;
 717:         }
 718: 
 719:         // record the plot area...
 720:         if (info != null) {
 721:             info.setPlotArea(area);
 722:         }
 723: 
 724:         // adjust the drawing area for the plot insets (if any)...
 725:         RectangleInsets insets = getInsets();
 726:         insets.trim(area);
 727: 
 728:         Rectangle2D dataArea = area;
 729:         if (info != null) {
 730:             info.setDataArea(dataArea);
 731:         }
 732: 
 733:         // draw the plot background and axes...
 734:         drawBackground(g2, dataArea);
 735:         double h = Math.min(dataArea.getWidth() / 2.0,
 736:                 dataArea.getHeight() / 2.0) - MARGIN;
 737:         Rectangle2D quadrant = new Rectangle2D.Double(dataArea.getCenterX(),
 738:                 dataArea.getCenterY(), h, h);
 739:         AxisState state = drawAxis(g2, area, quadrant);
 740:         if (this.renderer != null) {
 741:             Shape originalClip = g2.getClip();
 742:             Composite originalComposite = g2.getComposite();
 743: 
 744:             g2.clip(dataArea);
 745:             g2.setComposite(AlphaComposite.getInstance(
 746:                     AlphaComposite.SRC_OVER, getForegroundAlpha()));
 747: 
 748:             this.angleTicks = refreshAngleTicks();
 749:             drawGridlines(g2, dataArea, this.angleTicks, state.getTicks());
 750: 
 751:             // draw...
 752:             render(g2, dataArea, info);
 753: 
 754:             g2.setClip(originalClip);
 755:             g2.setComposite(originalComposite);
 756:         }
 757:         drawOutline(g2, dataArea);
 758:         drawCornerTextItems(g2, dataArea);
 759:     }
 760: 
 761:     /**
 762:      * Draws the corner text items.
 763:      *
 764:      * @param g2  the drawing surface.
 765:      * @param area  the area.
 766:      */
 767:     protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) {
 768:         if (this.cornerTextItems.isEmpty()) {
 769:             return;
 770:         }
 771: 
 772:         g2.setColor(Color.black);
 773:         double width = 0.0;
 774:         double height = 0.0;
 775:         for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
 776:             String msg = (String) it.next();
 777:             FontMetrics fm = g2.getFontMetrics();
 778:             Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm);
 779:             width = Math.max(width, bounds.getWidth());
 780:             height += bounds.getHeight();
 781:         }
 782: 
 783:         double xadj = ANNOTATION_MARGIN * 2.0;
 784:         double yadj = ANNOTATION_MARGIN;
 785:         width += xadj;
 786:         height += yadj;
 787: 
 788:         double x = area.getMaxX() - width;
 789:         double y = area.getMaxY() - height;
 790:         g2.drawRect((int) x, (int) y, (int) width, (int) height);
 791:         x += ANNOTATION_MARGIN;
 792:         for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
 793:             String msg = (String) it.next();
 794:             Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2,
 795:                     g2.getFontMetrics());
 796:             y += bounds.getHeight();
 797:             g2.drawString(msg, (int) x, (int) y);
 798:         }
 799:     }
 800: 
 801:     /**
 802:      * A utility method for drawing the axes.
 803:      *
 804:      * @param g2  the graphics device.
 805:      * @param plotArea  the plot area.
 806:      * @param dataArea  the data area.
 807:      *
 808:      * @return A map containing the axis states.
 809:      */
 810:     protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea,
 811:                                  Rectangle2D dataArea) {
 812:         return this.axis.draw(g2, dataArea.getMinY(), plotArea, dataArea,
 813:                 RectangleEdge.TOP, null);
 814:     }
 815: 
 816:     /**
 817:      * Draws a representation of the data within the dataArea region, using the
 818:      * current m_Renderer.
 819:      *
 820:      * @param g2  the graphics device.
 821:      * @param dataArea  the region in which the data is to be drawn.
 822:      * @param info  an optional object for collection dimension
 823:      *              information (<code>null</code> permitted).
 824:      */
 825:     protected void render(Graphics2D g2,
 826:                        Rectangle2D dataArea,
 827:                        PlotRenderingInfo info) {
 828: 
 829:         // now get the data and plot it (the visual representation will depend
 830:         // on the m_Renderer that has been set)...
 831:         if (!DatasetUtilities.isEmptyOrNull(this.dataset)) {
 832:             int seriesCount = this.dataset.getSeriesCount();
 833:             for (int series = 0; series < seriesCount; series++) {
 834:                 this.renderer.drawSeries(g2, dataArea, info, this,
 835:                         this.dataset, series);
 836:             }
 837:         }
 838:         else {
 839:             drawNoDataMessage(g2, dataArea);
 840:         }
 841:     }
 842: 
 843:     /**
 844:      * Draws the gridlines for the plot, if they are visible.
 845:      *
 846:      * @param g2  the graphics device.
 847:      * @param dataArea  the data area.
 848:      * @param angularTicks  the ticks for the angular axis.
 849:      * @param radialTicks  the ticks for the radial axis.
 850:      */
 851:     protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea,
 852:                                  List angularTicks, List radialTicks) {
 853: 
 854:         // no renderer, no gridlines...
 855:         if (this.renderer == null) {
 856:             return;
 857:         }
 858: 
 859:         // draw the domain grid lines, if any...
 860:         if (isAngleGridlinesVisible()) {
 861:             Stroke gridStroke = getAngleGridlineStroke();
 862:             Paint gridPaint = getAngleGridlinePaint();
 863:             if ((gridStroke != null) && (gridPaint != null)) {
 864:                 this.renderer.drawAngularGridLines(g2, this, angularTicks,
 865:                         dataArea);
 866:             }
 867:         }
 868: 
 869:         // draw the radius grid lines, if any...
 870:         if (isRadiusGridlinesVisible()) {
 871:             Stroke gridStroke = getRadiusGridlineStroke();
 872:             Paint gridPaint = getRadiusGridlinePaint();
 873:             if ((gridStroke != null) && (gridPaint != null)) {
 874:                 this.renderer.drawRadialGridLines(g2, this, this.axis,
 875:                         radialTicks, dataArea);
 876:             }
 877:         }
 878:     }
 879: 
 880:     /**
 881:      * Zooms the axis ranges by the specified percentage about the anchor point.
 882:      *
 883:      * @param percent  the amount of the zoom.
 884:      */
 885:     public void zoom(double percent) {
 886:         if (percent > 0.0) {
 887:             double radius = getMaxRadius();
 888:             double scaledRadius = radius * percent;
 889:             this.axis.setUpperBound(scaledRadius);
 890:             getAxis().setAutoRange(false);
 891:         }
 892:         else {
 893:             getAxis().setAutoRange(true);
 894:         }
 895:     }
 896: 
 897:     /**
 898:      * Returns the range for the specified axis.
 899:      *
 900:      * @param axis  the axis.
 901:      *
 902:      * @return The range.
 903:      */
 904:     public Range getDataRange(ValueAxis axis) {
 905:         Range result = null;
 906:         if (this.dataset != null) {
 907:             result = Range.combine(result,
 908:                     DatasetUtilities.findRangeBounds(this.dataset));
 909:         }
 910:         return result;
 911:     }
 912: 
 913:     /**
 914:      * Receives notification of a change to the plot's m_Dataset.
 915:      * <P>
 916:      * The axis ranges are updated if necessary.
 917:      *
 918:      * @param event  information about the event (not used here).
 919:      */
 920:     public void datasetChanged(DatasetChangeEvent event) {
 921: 
 922:         if (this.axis != null) {
 923:             this.axis.configure();
 924:         }
 925: 
 926:         if (getParent() != null) {
 927:             getParent().datasetChanged(event);
 928:         }
 929:         else {
 930:             super.datasetChanged(event);
 931:         }
 932:     }
 933: 
 934:     /**
 935:      * Notifies all registered listeners of a property change.
 936:      * <P>
 937:      * One source of property change events is the plot's m_Renderer.
 938:      *
 939:      * @param event  information about the property change.
 940:      */
 941:     public void rendererChanged(RendererChangeEvent event) {
 942:         fireChangeEvent();
 943:     }
 944: 
 945:     /**
 946:      * Returns the number of series in the dataset for this plot.  If the
 947:      * dataset is <code>null</code>, the method returns 0.
 948:      *
 949:      * @return The series count.
 950:      */
 951:     public int getSeriesCount() {
 952:         int result = 0;
 953: 
 954:         if (this.dataset != null) {
 955:             result = this.dataset.getSeriesCount();
 956:         }
 957:         return result;
 958:     }
 959: 
 960:     /**
 961:      * Returns the legend items for the plot.  Each legend item is generated by
 962:      * the plot's m_Renderer, since the m_Renderer is responsible for the visual
 963:      * representation of the data.
 964:      *
 965:      * @return The legend items.
 966:      */
 967:     public LegendItemCollection getLegendItems() {
 968:         LegendItemCollection result = new LegendItemCollection();
 969: 
 970:         // get the legend items for the main m_Dataset...
 971:         if (this.dataset != null) {
 972:             if (this.renderer != null) {
 973:                 int seriesCount = this.dataset.getSeriesCount();
 974:                 for (int i = 0; i < seriesCount; i++) {
 975:                     LegendItem item = this.renderer.getLegendItem(i);
 976:                     result.add(item);
 977:                 }
 978:             }
 979:         }
 980:         return result;
 981:     }
 982: 
 983:     /**
 984:      * Tests this plot for equality with another object.
 985:      *
 986:      * @param obj  the object (<code>null</code> permitted).
 987:      *
 988:      * @return <code>true</code> or <code>false</code>.
 989:      */
 990:     public boolean equals(Object obj) {
 991:         if (obj == this) {
 992:             return true;
 993:         }
 994:         if (!(obj instanceof PolarPlot)) {
 995:             return false;
 996:         }
 997:         PolarPlot that = (PolarPlot) obj;
 998:         if (!ObjectUtilities.equal(this.axis, that.axis)) {
 999:             return false;
1000:         }
1001:         if (!ObjectUtilities.equal(this.renderer, that.renderer)) {
1002:             return false;
1003:         }
1004:         if (!this.angleTickUnit.equals(that.angleTickUnit)) {
1005:             return false;
1006:         }
1007:         if (this.angleGridlinesVisible != that.angleGridlinesVisible) {
1008:             return false;
1009:         }
1010:         if (this.angleLabelsVisible != that.angleLabelsVisible) {
1011:             return false;
1012:         }
1013:         if (!this.angleLabelFont.equals(that.angleLabelFont)) {
1014:             return false;
1015:         }
1016:         if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) {
1017:             return false;
1018:         }
1019:         if (!ObjectUtilities.equal(this.angleGridlineStroke,
1020:                 that.angleGridlineStroke)) {
1021:             return false;
1022:         }
1023:         if (!PaintUtilities.equal(
1024:             this.angleGridlinePaint, that.angleGridlinePaint
1025:         )) {
1026:             return false;
1027:         }
1028:         if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) {
1029:             return false;
1030:         }
1031:         if (!ObjectUtilities.equal(this.radiusGridlineStroke,
1032:                 that.radiusGridlineStroke)) {
1033:             return false;
1034:         }
1035:         if (!PaintUtilities.equal(this.radiusGridlinePaint,
1036:                 that.radiusGridlinePaint)) {
1037:             return false;
1038:         }
1039:         if (!this.cornerTextItems.equals(that.cornerTextItems)) {
1040:             return false;
1041:         }
1042:         return super.equals(obj);
1043:     }
1044: 
1045:     /**
1046:      * Returns a clone of the plot.
1047:      *
1048:      * @return A clone.
1049:      *
1050:      * @throws CloneNotSupportedException  this can occur if some component of
1051:      *         the plot cannot be cloned.
1052:      */
1053:     public Object clone() throws CloneNotSupportedException {
1054: 
1055:         PolarPlot clone = (PolarPlot) super.clone();
1056:         if (this.axis != null) {
1057:             clone.axis = (ValueAxis) ObjectUtilities.clone(this.axis);
1058:             clone.axis.setPlot(clone);
1059:             clone.axis.addChangeListener(clone);
1060:         }
1061: 
1062:         if (clone.dataset != null) {
1063:             clone.dataset.addChangeListener(clone);
1064:         }
1065: 
1066:         if (this.renderer != null) {
1067:             clone.renderer
1068:                 = (PolarItemRenderer) ObjectUtilities.clone(this.renderer);
1069:         }
1070: 
1071:         clone.cornerTextItems = new ArrayList(this.cornerTextItems);
1072: 
1073:         return clone;
1074:     }
1075: 
1076:     /**
1077:      * Provides serialization support.
1078:      *
1079:      * @param stream  the output stream.
1080:      *
1081:      * @throws IOException  if there is an I/O error.
1082:      */
1083:     private void writeObject(ObjectOutputStream stream) throws IOException {
1084:         stream.defaultWriteObject();
1085:         SerialUtilities.writeStroke(this.angleGridlineStroke, stream);
1086:         SerialUtilities.writePaint(this.angleGridlinePaint, stream);
1087:         SerialUtilities.writeStroke(this.radiusGridlineStroke, stream);
1088:         SerialUtilities.writePaint(this.radiusGridlinePaint, stream);
1089:         SerialUtilities.writePaint(this.angleLabelPaint, stream);
1090:     }
1091: 
1092:     /**
1093:      * Provides serialization support.
1094:      *
1095:      * @param stream  the input stream.
1096:      *
1097:      * @throws IOException  if there is an I/O error.
1098:      * @throws ClassNotFoundException  if there is a classpath problem.
1099:      */
1100:     private void readObject(ObjectInputStream stream)
1101:         throws IOException, ClassNotFoundException {
1102: 
1103:         stream.defaultReadObject();
1104:         this.angleGridlineStroke = SerialUtilities.readStroke(stream);
1105:         this.angleGridlinePaint = SerialUtilities.readPaint(stream);
1106:         this.radiusGridlineStroke = SerialUtilities.readStroke(stream);
1107:         this.radiusGridlinePaint = SerialUtilities.readPaint(stream);
1108:         this.angleLabelPaint = SerialUtilities.readPaint(stream);
1109: 
1110:         if (this.axis != null) {
1111:             this.axis.setPlot(this);
1112:             this.axis.addChangeListener(this);
1113:         }
1114: 
1115:         if (this.dataset != null) {
1116:             this.dataset.addChangeListener(this);
1117:         }
1118:     }
1119: 
1120:     /**
1121:      * This method is required by the {@link Zoomable} interface, but since
1122:      * the plot does not have any domain axes, it does nothing.
1123:      *
1124:      * @param factor  the zoom factor.
1125:      * @param state  the plot state.
1126:      * @param source  the source point (in Java2D coordinates).
1127:      */
1128:     public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1129:                                Point2D source) {
1130:         // do nothing
1131:     }
1132: 
1133:     /**
1134:      * This method is required by the {@link Zoomable} interface, but since
1135:      * the plot does not have any domain axes, it does nothing.
1136:      *
1137:      * @param factor  the zoom factor.
1138:      * @param state  the plot state.
1139:      * @param source  the source point (in Java2D coordinates).
1140:      * @param useAnchor  use source point as zoom anchor?
1141:      *
1142:      * @since 1.0.7
1143:      */
1144:     public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1145:                                Point2D source, boolean useAnchor) {
1146:         // do nothing
1147:     }
1148: 
1149:     /**
1150:      * This method is required by the {@link Zoomable} interface, but since
1151:      * the plot does not have any domain axes, it does nothing.
1152:      *
1153:      * @param lowerPercent  the new lower bound.
1154:      * @param upperPercent  the new upper bound.
1155:      * @param state  the plot state.
1156:      * @param source  the source point (in Java2D coordinates).
1157:      */
1158:     public void zoomDomainAxes(double lowerPercent, double upperPercent,
1159:                                PlotRenderingInfo state, Point2D source) {
1160:         // do nothing
1161:     }
1162: 
1163:     /**
1164:      * Multiplies the range on the range axis/axes by the specified factor.
1165:      *
1166:      * @param factor  the zoom factor.
1167:      * @param state  the plot state.
1168:      * @param source  the source point (in Java2D coordinates).
1169:      */
1170:     public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1171:                               Point2D source) {
1172:         zoom(factor);
1173:     }
1174: 
1175:     /**
1176:      * Multiplies the range on the range axis by the specified factor.
1177:      *
1178:      * @param factor  the zoom factor.
1179:      * @param info  the plot rendering info.
1180:      * @param source  the source point (in Java2D space).
1181:      * @param useAnchor  use source point as zoom anchor?
1182:      *
1183:      * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
1184:      *
1185:      * @since 1.0.7
1186:      */
1187:     public void zoomRangeAxes(double factor, PlotRenderingInfo info,
1188:                               Point2D source, boolean useAnchor) {
1189: 
1190:         if (useAnchor) {
1191:             // get the source coordinate - this plot has always a VERTICAL
1192:             // orientation
1193:             double sourceX = source.getX();
1194:             double anchorX = this.axis.java2DToValue(sourceX,
1195:                     info.getDataArea(), RectangleEdge.BOTTOM);
1196:             this.axis.resizeRange(factor, anchorX);
1197:         }
1198:         else {
1199:             this.axis.resizeRange(factor);
1200:         }
1201: 
1202:     }
1203: 
1204:     /**
1205:      * Zooms in on the range axes.
1206:      *
1207:      * @param lowerPercent  the new lower bound.
1208:      * @param upperPercent  the new upper bound.
1209:      * @param state  the plot state.
1210:      * @param source  the source point (in Java2D coordinates).
1211:      */
1212:     public void zoomRangeAxes(double lowerPercent, double upperPercent,
1213:                               PlotRenderingInfo state, Point2D source) {
1214:         zoom((upperPercent + lowerPercent) / 2.0);
1215:     }
1216: 
1217:     /**
1218:      * Returns <code>false</code> always.
1219:      *
1220:      * @return <code>false</code> always.
1221:      */
1222:     public boolean isDomainZoomable() {
1223:         return false;
1224:     }
1225: 
1226:     /**
1227:      * Returns <code>true</code> to indicate that the range axis is zoomable.
1228:      *
1229:      * @return <code>true</code>.
1230:      */
1231:     public boolean isRangeZoomable() {
1232:         return true;
1233:     }
1234: 
1235:     /**
1236:      * Returns the orientation of the plot.
1237:      *
1238:      * @return The orientation.
1239:      */
1240:     public PlotOrientation getOrientation() {
1241:         return PlotOrientation.HORIZONTAL;
1242:     }
1243: 
1244:     /**
1245:      * Returns the upper bound of the radius axis.
1246:      *
1247:      * @return The upper bound.
1248:      */
1249:     public double getMaxRadius() {
1250:         return this.axis.getUpperBound();
1251:     }
1252: 
1253:     /**
1254:      * Translates a (theta, radius) pair into Java2D coordinates.  If
1255:      * <code>radius</code> is less than the lower bound of the axis, then
1256:      * this method returns the centre point.
1257:      *
1258:      * @param angleDegrees  the angle in degrees.
1259:      * @param radius  the radius.
1260:      * @param dataArea  the data area.
1261:      *
1262:      * @return A point in Java2D space.
1263:      */
1264:     public Point translateValueThetaRadiusToJava2D(double angleDegrees,
1265:                                                    double radius,
1266:                                                    Rectangle2D dataArea) {
1267: 
1268:         double radians = Math.toRadians(angleDegrees - 90.0);
1269: 
1270:         double minx = dataArea.getMinX() + MARGIN;
1271:         double maxx = dataArea.getMaxX() - MARGIN;
1272:         double miny = dataArea.getMinY() + MARGIN;
1273:         double maxy = dataArea.getMaxY() - MARGIN;
1274: 
1275:         double lengthX = maxx - minx;
1276:         double lengthY = maxy - miny;
1277:         double length = Math.min(lengthX, lengthY);
1278: 
1279:         double midX = minx + lengthX / 2.0;
1280:         double midY = miny + lengthY / 2.0;
1281: 
1282:         double axisMin = this.axis.getLowerBound();
1283:         double axisMax =  getMaxRadius();
1284:         double adjustedRadius = Math.max(radius, axisMin);
1285: 
1286:         double xv = length / 2.0 * Math.cos(radians);
1287:         double yv = length / 2.0 * Math.sin(radians);
1288: 
1289:         float x = (float) (midX + (xv * (adjustedRadius - axisMin)
1290:                 / (axisMax - axisMin)));
1291:         float y = (float) (midY + (yv * (adjustedRadius - axisMin)
1292:                 / (axisMax - axisMin)));
1293: 
1294:         int ix = Math.round(x);
1295:         int iy = Math.round(y);
1296: 
1297:         Point p = new Point(ix, iy);
1298:         return p;
1299: 
1300:     }
1301: 
1302: }