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