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