001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * -------------- 028 * PolarPlot.java 029 * -------------- 030 * (C) Copyright 2004, 2005, by Solution Engineering, Inc. and Contributors. 031 * 032 * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * 035 * $Id: PolarPlot.java,v 1.13.2.3 2005/10/25 20:52:08 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 19-Jan-2004 : Version 1, contributed by DB with minor changes by DG (DG); 040 * 07-Apr-2004 : Changed text bounds calculation (DG); 041 * 05-May-2005 : Updated draw() method parameters (DG); 042 * 09-Jun-2005 : Fixed getDataRange() and equals() methods (DG); 043 * 25-Oct-2005 : Implemented Zoomable (DG); 044 * 045 */ 046 047 package org.jfree.chart.plot; 048 049 import java.awt.AlphaComposite; 050 import java.awt.BasicStroke; 051 import java.awt.Color; 052 import java.awt.Composite; 053 import java.awt.Font; 054 import java.awt.FontMetrics; 055 import java.awt.Graphics2D; 056 import java.awt.Paint; 057 import java.awt.Point; 058 import java.awt.Shape; 059 import java.awt.Stroke; 060 import java.awt.geom.Point2D; 061 import java.awt.geom.Rectangle2D; 062 import java.io.IOException; 063 import java.io.ObjectInputStream; 064 import java.io.ObjectOutputStream; 065 import java.io.Serializable; 066 import java.util.ArrayList; 067 import java.util.Iterator; 068 import java.util.List; 069 import java.util.ResourceBundle; 070 071 import org.jfree.chart.LegendItem; 072 import org.jfree.chart.LegendItemCollection; 073 import org.jfree.chart.axis.AxisState; 074 import org.jfree.chart.axis.NumberTick; 075 import org.jfree.chart.axis.ValueAxis; 076 import org.jfree.chart.event.PlotChangeEvent; 077 import org.jfree.chart.event.RendererChangeEvent; 078 import org.jfree.chart.event.RendererChangeListener; 079 import org.jfree.chart.renderer.PolarItemRenderer; 080 import org.jfree.data.Range; 081 import org.jfree.data.general.DatasetChangeEvent; 082 import org.jfree.data.general.DatasetUtilities; 083 import org.jfree.data.xy.XYDataset; 084 import org.jfree.io.SerialUtilities; 085 import org.jfree.text.TextUtilities; 086 import org.jfree.ui.RectangleEdge; 087 import org.jfree.ui.RectangleInsets; 088 import org.jfree.ui.TextAnchor; 089 import org.jfree.util.ObjectUtilities; 090 import org.jfree.util.PaintUtilities; 091 092 093 /** 094 * Plots data that is in (theta, radius) pairs where 095 * theta equal to zero is due north and and increases clockwise. 096 * 097 * @author Daniel Bridenbecker, Solution Engineering, Inc. 098 */ 099 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 }