001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * -------------- 028 * PolarPlot.java 029 * -------------- 030 * (C) Copyright 2004-2013, 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 * Martin Hoeller (patches 1871902 and 2850344); 035 * 036 * Changes 037 * ------- 038 * 19-Jan-2004 : Version 1, contributed by DB with minor changes by DG (DG); 039 * 07-Apr-2004 : Changed text bounds calculation (DG); 040 * 05-May-2005 : Updated draw() method parameters (DG); 041 * 09-Jun-2005 : Fixed getDataRange() and equals() methods (DG); 042 * 25-Oct-2005 : Implemented Zoomable (DG); 043 * ------------- JFREECHART 1.0.x --------------------------------------------- 044 * 07-Feb-2007 : Fixed bug 1599761, data value less than axis minimum (DG); 045 * 21-Mar-2007 : Fixed serialization bug (DG); 046 * 24-Sep-2007 : Implemented new zooming methods (DG); 047 * 17-Feb-2007 : Added angle tick unit attribute (see patch 1871902 by 048 * Martin Hoeller) (DG); 049 * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by 050 * Jess Thrysoee (DG); 051 * 03-Sep-2009 : Applied patch 2850344 by Martin Hoeller (DG); 052 * 27-Nov-2009 : Added support for multiple datasets, renderers and axes (DG); 053 * 09-Dec-2009 : Extended getLegendItems() to handle multiple datasets (DG); 054 * 25-Jun-2010 : Better support for multiple axes (MH); 055 * 03-Oct-2011 : Added support for angleOffset and direction (MH); 056 * 12-Nov-2011 : Fixed bug 3432721, log-axis doesn't work (MH); 057 * 12-Dec-2011 : Added support for radiusMinorGridlinesVisible (MH); 058 * 02-Jul-2013 : Use ParamChecks (DG); 059 * 05-Jul-2013 : Fire change event from setRadiusMinorGridlinesVisible (DG); 060 * 061 */ 062 063package org.jfree.chart.plot; 064 065import java.awt.AlphaComposite; 066import java.awt.BasicStroke; 067import java.awt.Color; 068import java.awt.Composite; 069import java.awt.Font; 070import java.awt.FontMetrics; 071import java.awt.Graphics2D; 072import java.awt.Paint; 073import java.awt.Point; 074import java.awt.Shape; 075import java.awt.Stroke; 076import java.awt.geom.Point2D; 077import java.awt.geom.Rectangle2D; 078import java.io.IOException; 079import java.io.ObjectInputStream; 080import java.io.ObjectOutputStream; 081import java.io.Serializable; 082import java.util.ArrayList; 083import java.util.HashSet; 084import java.util.Iterator; 085import java.util.List; 086import java.util.Map; 087import java.util.ResourceBundle; 088import java.util.TreeMap; 089 090import org.jfree.chart.LegendItem; 091import org.jfree.chart.LegendItemCollection; 092import org.jfree.chart.axis.Axis; 093import org.jfree.chart.axis.AxisState; 094import org.jfree.chart.axis.NumberTick; 095import org.jfree.chart.axis.NumberTickUnit; 096import org.jfree.chart.axis.TickType; 097import org.jfree.chart.axis.TickUnit; 098import org.jfree.chart.axis.ValueAxis; 099import org.jfree.chart.axis.ValueTick; 100import org.jfree.chart.event.PlotChangeEvent; 101import org.jfree.chart.event.RendererChangeEvent; 102import org.jfree.chart.event.RendererChangeListener; 103import org.jfree.chart.renderer.PolarItemRenderer; 104import org.jfree.chart.util.ParamChecks; 105import org.jfree.chart.util.ResourceBundleWrapper; 106import org.jfree.data.Range; 107import org.jfree.data.general.Dataset; 108import org.jfree.data.general.DatasetChangeEvent; 109import org.jfree.data.general.DatasetUtilities; 110import org.jfree.data.xy.XYDataset; 111import org.jfree.io.SerialUtilities; 112import org.jfree.text.TextUtilities; 113import org.jfree.ui.RectangleEdge; 114import org.jfree.ui.RectangleInsets; 115import org.jfree.ui.TextAnchor; 116import org.jfree.util.ObjectList; 117import org.jfree.util.ObjectUtilities; 118import org.jfree.util.PaintUtilities; 119import org.jfree.util.PublicCloneable; 120 121/** 122 * Plots data that is in (theta, radius) pairs where 123 * theta equal to zero is due north and increases clockwise. 124 */ 125public class PolarPlot extends Plot implements ValueAxisPlot, Zoomable, 126 RendererChangeListener, Cloneable, Serializable { 127 128 /** For serialization. */ 129 private static final long serialVersionUID = 3794383185924179525L; 130 131 /** The default margin. */ 132 private static final int DEFAULT_MARGIN = 20; 133 134 /** The annotation margin. */ 135 private static final double ANNOTATION_MARGIN = 7.0; 136 137 /** 138 * The default angle tick unit size. 139 * 140 * @since 1.0.10 141 */ 142 public static final double DEFAULT_ANGLE_TICK_UNIT_SIZE = 45.0; 143 144 /** 145 * The default angle offset. 146 * 147 * @since 1.0.14 148 */ 149 public static final double DEFAULT_ANGLE_OFFSET = -90.0; 150 151 /** The default grid line stroke. */ 152 public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke( 153 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 154 0.0f, new float[]{2.0f, 2.0f}, 0.0f); 155 156 /** The default grid line paint. */ 157 public static final Paint DEFAULT_GRIDLINE_PAINT = Color.gray; 158 159 /** The resourceBundle for the localization. */ 160 protected static ResourceBundle localizationResources 161 = ResourceBundleWrapper.getBundle( 162 "org.jfree.chart.plot.LocalizationBundle"); 163 164 /** The angles that are marked with gridlines. */ 165 private List angleTicks; 166 167 /** The range axis (used for the y-values). */ 168 private ObjectList axes; 169 170 /** The axis locations. */ 171 private ObjectList axisLocations; 172 173 /** Storage for the datasets. */ 174 private ObjectList datasets; 175 176 /** Storage for the renderers. */ 177 private ObjectList renderers; 178 179 /** 180 * The tick unit that controls the spacing between the angular grid lines. 181 * 182 * @since 1.0.10 183 */ 184 private TickUnit angleTickUnit; 185 186 /** 187 * An offset for the angles, to start with 0 degrees at north, east, south 188 * or west. 189 * 190 * @since 1.0.14 191 */ 192 private double angleOffset; 193 194 /** 195 * A flag indicating if the angles increase counterclockwise or clockwise. 196 * 197 * @since 1.0.14 198 */ 199 private boolean counterClockwise; 200 201 /** A flag that controls whether or not the angle labels are visible. */ 202 private boolean angleLabelsVisible = true; 203 204 /** The font used to display the angle labels - never null. */ 205 private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12); 206 207 /** The paint used to display the angle labels. */ 208 private transient Paint angleLabelPaint = Color.black; 209 210 /** A flag that controls whether the angular grid-lines are visible. */ 211 private boolean angleGridlinesVisible; 212 213 /** The stroke used to draw the angular grid-lines. */ 214 private transient Stroke angleGridlineStroke; 215 216 /** The paint used to draw the angular grid-lines. */ 217 private transient Paint angleGridlinePaint; 218 219 /** A flag that controls whether the radius grid-lines are visible. */ 220 private boolean radiusGridlinesVisible; 221 222 /** The stroke used to draw the radius grid-lines. */ 223 private transient Stroke radiusGridlineStroke; 224 225 /** The paint used to draw the radius grid-lines. */ 226 private transient Paint radiusGridlinePaint; 227 228 /** 229 * A flag that controls whether the radial minor grid-lines are visible. 230 * @since 1.0.15 231 */ 232 private boolean radiusMinorGridlinesVisible; 233 234 /** The annotations for the plot. */ 235 private List cornerTextItems = new ArrayList(); 236 237 /** 238 * The actual margin in pixels. 239 * 240 * @since 1.0.14 241 */ 242 private int margin; 243 244 /** 245 * An optional collection of legend items that can be returned by the 246 * getLegendItems() method. 247 */ 248 private LegendItemCollection fixedLegendItems; 249 250 /** 251 * Storage for the mapping between datasets/renderers and range axes. The 252 * keys in the map are Integer objects, corresponding to the dataset 253 * index. The values in the map are List objects containing Integer 254 * objects (corresponding to the axis indices). If the map contains no 255 * entry for a dataset, it is assumed to map to the primary domain axis 256 * (index = 0). 257 */ 258 private Map datasetToAxesMap; 259 260 /** 261 * Default constructor. 262 */ 263 public PolarPlot() { 264 this(null, null, null); 265 } 266 267 /** 268 * Creates a new plot. 269 * 270 * @param dataset the dataset (<code>null</code> permitted). 271 * @param radiusAxis the radius axis (<code>null</code> permitted). 272 * @param renderer the renderer (<code>null</code> permitted). 273 */ 274 public PolarPlot(XYDataset dataset, ValueAxis radiusAxis, 275 PolarItemRenderer renderer) { 276 277 super(); 278 279 this.datasets = new ObjectList(); 280 this.datasets.set(0, dataset); 281 if (dataset != null) { 282 dataset.addChangeListener(this); 283 } 284 this.angleTickUnit = new NumberTickUnit(DEFAULT_ANGLE_TICK_UNIT_SIZE); 285 286 this.axes = new ObjectList(); 287 this.datasetToAxesMap = new TreeMap(); 288 this.axes.set(0, radiusAxis); 289 if (radiusAxis != null) { 290 radiusAxis.setPlot(this); 291 radiusAxis.addChangeListener(this); 292 } 293 294 // define the default locations for up to 8 axes... 295 this.axisLocations = new ObjectList(); 296 this.axisLocations.set(0, PolarAxisLocation.EAST_ABOVE); 297 this.axisLocations.set(1, PolarAxisLocation.NORTH_LEFT); 298 this.axisLocations.set(2, PolarAxisLocation.WEST_BELOW); 299 this.axisLocations.set(3, PolarAxisLocation.SOUTH_RIGHT); 300 this.axisLocations.set(4, PolarAxisLocation.EAST_BELOW); 301 this.axisLocations.set(5, PolarAxisLocation.NORTH_RIGHT); 302 this.axisLocations.set(6, PolarAxisLocation.WEST_ABOVE); 303 this.axisLocations.set(7, PolarAxisLocation.SOUTH_LEFT); 304 305 this.renderers = new ObjectList(); 306 this.renderers.set(0, renderer); 307 if (renderer != null) { 308 renderer.setPlot(this); 309 renderer.addChangeListener(this); 310 } 311 312 this.angleOffset = DEFAULT_ANGLE_OFFSET; 313 this.counterClockwise = false; 314 this.angleGridlinesVisible = true; 315 this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE; 316 this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT; 317 318 this.radiusGridlinesVisible = true; 319 this.radiusMinorGridlinesVisible = true; 320 this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE; 321 this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT; 322 this.margin = DEFAULT_MARGIN; 323 } 324 325 /** 326 * Returns the plot type as a string. 327 * 328 * @return A short string describing the type of plot. 329 */ 330 @Override 331 public String getPlotType() { 332 return PolarPlot.localizationResources.getString("Polar_Plot"); 333 } 334 335 /** 336 * Returns the primary axis for the plot. 337 * 338 * @return The primary axis (possibly <code>null</code>). 339 * 340 * @see #setAxis(ValueAxis) 341 */ 342 public ValueAxis getAxis() { 343 return getAxis(0); 344 } 345 346 /** 347 * Returns an axis for the plot. 348 * 349 * @param index the axis index. 350 * 351 * @return The axis (<code>null</code> possible). 352 * 353 * @see #setAxis(int, ValueAxis) 354 * 355 * @since 1.0.14 356 */ 357 public ValueAxis getAxis(int index) { 358 ValueAxis result = null; 359 if (index < this.axes.size()) { 360 result = (ValueAxis) this.axes.get(index); 361 } 362 return result; 363 } 364 365 /** 366 * Sets the primary axis for the plot and sends a {@link PlotChangeEvent} 367 * to all registered listeners. 368 * 369 * @param axis the new primary axis (<code>null</code> permitted). 370 */ 371 public void setAxis(ValueAxis axis) { 372 setAxis(0, axis); 373 } 374 375 /** 376 * Sets an axis for the plot and sends a {@link PlotChangeEvent} to all 377 * registered listeners. 378 * 379 * @param index the axis index. 380 * @param axis the axis (<code>null</code> permitted). 381 * 382 * @see #getAxis(int) 383 * 384 * @since 1.0.14 385 */ 386 public void setAxis(int index, ValueAxis axis) { 387 setAxis(index, axis, true); 388 } 389 390 /** 391 * Sets an axis for the plot and, if requested, sends a 392 * {@link PlotChangeEvent} to all registered listeners. 393 * 394 * @param index the axis index. 395 * @param axis the axis (<code>null</code> permitted). 396 * @param notify notify listeners? 397 * 398 * @see #getAxis(int) 399 * 400 * @since 1.0.14 401 */ 402 public void setAxis(int index, ValueAxis axis, boolean notify) { 403 ValueAxis existing = getAxis(index); 404 if (existing != null) { 405 existing.removeChangeListener(this); 406 } 407 if (axis != null) { 408 axis.setPlot(this); 409 } 410 this.axes.set(index, axis); 411 if (axis != null) { 412 axis.configure(); 413 axis.addChangeListener(this); 414 } 415 if (notify) { 416 fireChangeEvent(); 417 } 418 } 419 420 /** 421 * Returns the location of the primary axis. 422 * 423 * @return The location (never <code>null</code>). 424 * 425 * @see #setAxisLocation(PolarAxisLocation) 426 * 427 * @since 1.0.14 428 */ 429 public PolarAxisLocation getAxisLocation() { 430 return getAxisLocation(0); 431 } 432 433 /** 434 * Returns the location for an axis. 435 * 436 * @param index the axis index. 437 * 438 * @return The location (never <code>null</code>). 439 * 440 * @see #setAxisLocation(int, PolarAxisLocation) 441 * 442 * @since 1.0.14 443 */ 444 public PolarAxisLocation getAxisLocation(int index) { 445 PolarAxisLocation result = null; 446 if (index < this.axisLocations.size()) { 447 result = (PolarAxisLocation) this.axisLocations.get(index); 448 } 449 return result; 450 } 451 452 /** 453 * Sets the location of the primary axis and sends a 454 * {@link PlotChangeEvent} to all registered listeners. 455 * 456 * @param location the location (<code>null</code> not permitted). 457 * 458 * @see #getAxisLocation() 459 * 460 * @since 1.0.14 461 */ 462 public void setAxisLocation(PolarAxisLocation location) { 463 // delegate... 464 setAxisLocation(0, location, true); 465 } 466 467 /** 468 * Sets the location of the primary axis and, if requested, sends a 469 * {@link PlotChangeEvent} to all registered listeners. 470 * 471 * @param location the location (<code>null</code> not permitted). 472 * @param notify notify listeners? 473 * 474 * @see #getAxisLocation() 475 * 476 * @since 1.0.14 477 */ 478 public void setAxisLocation(PolarAxisLocation location, boolean notify) { 479 // delegate... 480 setAxisLocation(0, location, notify); 481 } 482 483 /** 484 * Sets the location for an axis and sends a {@link PlotChangeEvent} 485 * to all registered listeners. 486 * 487 * @param index the axis index. 488 * @param location the location (<code>null</code> not permitted). 489 * 490 * @see #getAxisLocation(int) 491 * 492 * @since 1.0.14 493 */ 494 public void setAxisLocation(int index, PolarAxisLocation location) { 495 // delegate... 496 setAxisLocation(index, location, true); 497 } 498 499 /** 500 * Sets the axis location for an axis and, if requested, sends a 501 * {@link PlotChangeEvent} to all registered listeners. 502 * 503 * @param index the axis index. 504 * @param location the location (<code>null</code> not permitted). 505 * @param notify notify listeners? 506 * 507 * @since 1.0.14 508 */ 509 public void setAxisLocation(int index, PolarAxisLocation location, 510 boolean notify) { 511 ParamChecks.nullNotPermitted(location, "location"); 512 this.axisLocations.set(index, location); 513 if (notify) { 514 fireChangeEvent(); 515 } 516 } 517 518 /** 519 * Returns the number of domain axes. 520 * 521 * @return The axis count. 522 * 523 * @since 1.0.14 524 **/ 525 public int getAxisCount() { 526 return this.axes.size(); 527 } 528 529 /** 530 * Returns the primary dataset for the plot. 531 * 532 * @return The primary dataset (possibly <code>null</code>). 533 * 534 * @see #setDataset(XYDataset) 535 */ 536 public XYDataset getDataset() { 537 return getDataset(0); 538 } 539 540 /** 541 * Returns the dataset with the specified index, if any. 542 * 543 * @param index the dataset index. 544 * 545 * @return The dataset (possibly <code>null</code>). 546 * 547 * @see #setDataset(int, XYDataset) 548 * 549 * @since 1.0.14 550 */ 551 public XYDataset getDataset(int index) { 552 XYDataset result = null; 553 if (index < this.datasets.size()) { 554 result = (XYDataset) this.datasets.get(index); 555 } 556 return result; 557 } 558 559 /** 560 * Sets the primary dataset for the plot, replacing the existing dataset 561 * if there is one, and sends a {@code link PlotChangeEvent} to all 562 * registered listeners. 563 * 564 * @param dataset the dataset (<code>null</code> permitted). 565 * 566 * @see #getDataset() 567 */ 568 public void setDataset(XYDataset dataset) { 569 setDataset(0, dataset); 570 } 571 572 /** 573 * Sets a dataset for the plot, replacing the existing dataset at the same 574 * index if there is one, and sends a {@code link PlotChangeEvent} to all 575 * registered listeners. 576 * 577 * @param index the dataset index. 578 * @param dataset the dataset (<code>null</code> permitted). 579 * 580 * @see #getDataset(int) 581 * 582 * @since 1.0.14 583 */ 584 public void setDataset(int index, XYDataset dataset) { 585 XYDataset existing = getDataset(index); 586 if (existing != null) { 587 existing.removeChangeListener(this); 588 } 589 this.datasets.set(index, dataset); 590 if (dataset != null) { 591 dataset.addChangeListener(this); 592 } 593 594 // send a dataset change event to self... 595 DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); 596 datasetChanged(event); 597 } 598 599 /** 600 * Returns the number of datasets. 601 * 602 * @return The number of datasets. 603 * 604 * @since 1.0.14 605 */ 606 public int getDatasetCount() { 607 return this.datasets.size(); 608 } 609 610 /** 611 * Returns the index of the specified dataset, or <code>-1</code> if the 612 * dataset does not belong to the plot. 613 * 614 * @param dataset the dataset (<code>null</code> not permitted). 615 * 616 * @return The index. 617 * 618 * @since 1.0.14 619 */ 620 public int indexOf(XYDataset dataset) { 621 int result = -1; 622 for (int i = 0; i < this.datasets.size(); i++) { 623 if (dataset == this.datasets.get(i)) { 624 result = i; 625 break; 626 } 627 } 628 return result; 629 } 630 631 /** 632 * Returns the primary renderer. 633 * 634 * @return The renderer (possibly <code>null</code>). 635 * 636 * @see #setRenderer(PolarItemRenderer) 637 */ 638 public PolarItemRenderer getRenderer() { 639 return getRenderer(0); 640 } 641 642 /** 643 * Returns the renderer at the specified index, if there is one. 644 * 645 * @param index the renderer index. 646 * 647 * @return The renderer (possibly <code>null</code>). 648 * 649 * @see #setRenderer(int, PolarItemRenderer) 650 * 651 * @since 1.0.14 652 */ 653 public PolarItemRenderer getRenderer(int index) { 654 PolarItemRenderer result = null; 655 if (index < this.renderers.size()) { 656 result = (PolarItemRenderer) this.renderers.get(index); 657 } 658 return result; 659 } 660 661 /** 662 * Sets the primary renderer, and notifies all listeners of a change to the 663 * plot. If the renderer is set to <code>null</code>, no data items will 664 * be drawn for the corresponding dataset. 665 * 666 * @param renderer the new renderer (<code>null</code> permitted). 667 * 668 * @see #getRenderer() 669 */ 670 public void setRenderer(PolarItemRenderer renderer) { 671 setRenderer(0, renderer); 672 } 673 674 /** 675 * Sets a renderer and sends a {@link PlotChangeEvent} to all 676 * registered listeners. 677 * 678 * @param index the index. 679 * @param renderer the renderer. 680 * 681 * @see #getRenderer(int) 682 * 683 * @since 1.0.14 684 */ 685 public void setRenderer(int index, PolarItemRenderer renderer) { 686 setRenderer(index, renderer, true); 687 } 688 689 /** 690 * Sets a renderer and, if requested, sends a {@link PlotChangeEvent} to 691 * all registered listeners. 692 * 693 * @param index the index. 694 * @param renderer the renderer. 695 * @param notify notify listeners? 696 * 697 * @see #getRenderer(int) 698 * 699 * @since 1.0.14 700 */ 701 public void setRenderer(int index, PolarItemRenderer renderer, 702 boolean notify) { 703 PolarItemRenderer existing = getRenderer(index); 704 if (existing != null) { 705 existing.removeChangeListener(this); 706 } 707 this.renderers.set(index, renderer); 708 if (renderer != null) { 709 renderer.setPlot(this); 710 renderer.addChangeListener(this); 711 } 712 if (notify) { 713 fireChangeEvent(); 714 } 715 } 716 717 /** 718 * Returns the tick unit that controls the spacing of the angular grid 719 * lines. 720 * 721 * @return The tick unit (never <code>null</code>). 722 * 723 * @since 1.0.10 724 */ 725 public TickUnit getAngleTickUnit() { 726 return this.angleTickUnit; 727 } 728 729 /** 730 * Sets the tick unit that controls the spacing of the angular grid 731 * lines, and sends a {@link PlotChangeEvent} to all registered listeners. 732 * 733 * @param unit the tick unit (<code>null</code> not permitted). 734 * 735 * @since 1.0.10 736 */ 737 public void setAngleTickUnit(TickUnit unit) { 738 ParamChecks.nullNotPermitted(unit, "unit"); 739 this.angleTickUnit = unit; 740 fireChangeEvent(); 741 } 742 743 /** 744 * Returns the offset that is used for all angles. 745 * 746 * @return The offset for the angles. 747 * @since 1.0.14 748 */ 749 public double getAngleOffset() { 750 return this.angleOffset; 751 } 752 753 /** 754 * Sets the offset that is used for all angles and sends a 755 * {@link PlotChangeEvent} to all registered listeners. 756 * 757 * This is useful to let 0 degrees be at the north, east, south or west 758 * side of the chart. 759 * 760 * @param offset The offset 761 * @since 1.0.14 762 */ 763 public void setAngleOffset(double offset) { 764 this.angleOffset = offset; 765 fireChangeEvent(); 766 } 767 768 /** 769 * Get the direction for growing angle degrees. 770 * 771 * @return <code>true</code> if angle increases counterclockwise, 772 * <code>false</code> otherwise. 773 * @since 1.0.14 774 */ 775 public boolean isCounterClockwise() { 776 return this.counterClockwise; 777 } 778 779 /** 780 * Sets the flag for increasing angle degrees direction. 781 * 782 * <code>true</code> for counterclockwise, <code>false</code> for 783 * clockwise. 784 * 785 * @param counterClockwise The flag. 786 * @since 1.0.14 787 */ 788 public void setCounterClockwise(boolean counterClockwise) 789 { 790 this.counterClockwise = counterClockwise; 791 } 792 793 /** 794 * Returns a flag that controls whether or not the angle labels are visible. 795 * 796 * @return A boolean. 797 * 798 * @see #setAngleLabelsVisible(boolean) 799 */ 800 public boolean isAngleLabelsVisible() { 801 return this.angleLabelsVisible; 802 } 803 804 /** 805 * Sets the flag that controls whether or not the angle labels are visible, 806 * and sends a {@link PlotChangeEvent} to all registered listeners. 807 * 808 * @param visible the flag. 809 * 810 * @see #isAngleLabelsVisible() 811 */ 812 public void setAngleLabelsVisible(boolean visible) { 813 if (this.angleLabelsVisible != visible) { 814 this.angleLabelsVisible = visible; 815 fireChangeEvent(); 816 } 817 } 818 819 /** 820 * Returns the font used to display the angle labels. 821 * 822 * @return A font (never <code>null</code>). 823 * 824 * @see #setAngleLabelFont(Font) 825 */ 826 public Font getAngleLabelFont() { 827 return this.angleLabelFont; 828 } 829 830 /** 831 * Sets the font used to display the angle labels and sends a 832 * {@link PlotChangeEvent} to all registered listeners. 833 * 834 * @param font the font (<code>null</code> not permitted). 835 * 836 * @see #getAngleLabelFont() 837 */ 838 public void setAngleLabelFont(Font font) { 839 ParamChecks.nullNotPermitted(font, "font"); 840 this.angleLabelFont = font; 841 fireChangeEvent(); 842 } 843 844 /** 845 * Returns the paint used to display the angle labels. 846 * 847 * @return A paint (never <code>null</code>). 848 * 849 * @see #setAngleLabelPaint(Paint) 850 */ 851 public Paint getAngleLabelPaint() { 852 return this.angleLabelPaint; 853 } 854 855 /** 856 * Sets the paint used to display the angle labels and sends a 857 * {@link PlotChangeEvent} to all registered listeners. 858 * 859 * @param paint the paint (<code>null</code> not permitted). 860 */ 861 public void setAngleLabelPaint(Paint paint) { 862 ParamChecks.nullNotPermitted(paint, "paint"); 863 this.angleLabelPaint = paint; 864 fireChangeEvent(); 865 } 866 867 /** 868 * Returns <code>true</code> if the angular gridlines are visible, and 869 * <code>false</code> otherwise. 870 * 871 * @return <code>true</code> or <code>false</code>. 872 * 873 * @see #setAngleGridlinesVisible(boolean) 874 */ 875 public boolean isAngleGridlinesVisible() { 876 return this.angleGridlinesVisible; 877 } 878 879 /** 880 * Sets the flag that controls whether or not the angular grid-lines are 881 * visible. 882 * <p> 883 * If the flag value is changed, a {@link PlotChangeEvent} is sent to all 884 * registered listeners. 885 * 886 * @param visible the new value of the flag. 887 * 888 * @see #isAngleGridlinesVisible() 889 */ 890 public void setAngleGridlinesVisible(boolean visible) { 891 if (this.angleGridlinesVisible != visible) { 892 this.angleGridlinesVisible = visible; 893 fireChangeEvent(); 894 } 895 } 896 897 /** 898 * Returns the stroke for the grid-lines (if any) plotted against the 899 * angular axis. 900 * 901 * @return The stroke (possibly <code>null</code>). 902 * 903 * @see #setAngleGridlineStroke(Stroke) 904 */ 905 public Stroke getAngleGridlineStroke() { 906 return this.angleGridlineStroke; 907 } 908 909 /** 910 * Sets the stroke for the grid lines plotted against the angular axis and 911 * sends a {@link PlotChangeEvent} to all registered listeners. 912 * <p> 913 * If you set this to <code>null</code>, no grid lines will be drawn. 914 * 915 * @param stroke the stroke (<code>null</code> permitted). 916 * 917 * @see #getAngleGridlineStroke() 918 */ 919 public void setAngleGridlineStroke(Stroke stroke) { 920 this.angleGridlineStroke = stroke; 921 fireChangeEvent(); 922 } 923 924 /** 925 * Returns the paint for the grid lines (if any) plotted against the 926 * angular axis. 927 * 928 * @return The paint (possibly <code>null</code>). 929 * 930 * @see #setAngleGridlinePaint(Paint) 931 */ 932 public Paint getAngleGridlinePaint() { 933 return this.angleGridlinePaint; 934 } 935 936 /** 937 * Sets the paint for the grid lines plotted against the angular axis. 938 * <p> 939 * If you set this to <code>null</code>, no grid lines will be drawn. 940 * 941 * @param paint the paint (<code>null</code> permitted). 942 * 943 * @see #getAngleGridlinePaint() 944 */ 945 public void setAngleGridlinePaint(Paint paint) { 946 this.angleGridlinePaint = paint; 947 fireChangeEvent(); 948 } 949 950 /** 951 * Returns <code>true</code> if the radius axis grid is visible, and 952 * <code>false</code> otherwise. 953 * 954 * @return <code>true</code> or <code>false</code>. 955 * 956 * @see #setRadiusGridlinesVisible(boolean) 957 */ 958 public boolean isRadiusGridlinesVisible() { 959 return this.radiusGridlinesVisible; 960 } 961 962 /** 963 * Sets the flag that controls whether or not the radius axis grid lines 964 * are visible. 965 * <p> 966 * If the flag value is changed, a {@link PlotChangeEvent} is sent to all 967 * registered listeners. 968 * 969 * @param visible the new value of the flag. 970 * 971 * @see #isRadiusGridlinesVisible() 972 */ 973 public void setRadiusGridlinesVisible(boolean visible) { 974 if (this.radiusGridlinesVisible != visible) { 975 this.radiusGridlinesVisible = visible; 976 fireChangeEvent(); 977 } 978 } 979 980 /** 981 * Returns the stroke for the grid lines (if any) plotted against the 982 * radius axis. 983 * 984 * @return The stroke (possibly <code>null</code>). 985 * 986 * @see #setRadiusGridlineStroke(Stroke) 987 */ 988 public Stroke getRadiusGridlineStroke() { 989 return this.radiusGridlineStroke; 990 } 991 992 /** 993 * Sets the stroke for the grid lines plotted against the radius axis and 994 * sends a {@link PlotChangeEvent} to all registered listeners. 995 * <p> 996 * If you set this to <code>null</code>, no grid lines will be drawn. 997 * 998 * @param stroke the stroke (<code>null</code> permitted). 999 * 1000 * @see #getRadiusGridlineStroke() 1001 */ 1002 public void setRadiusGridlineStroke(Stroke stroke) { 1003 this.radiusGridlineStroke = stroke; 1004 fireChangeEvent(); 1005 } 1006 1007 /** 1008 * Returns the paint for the grid lines (if any) plotted against the radius 1009 * axis. 1010 * 1011 * @return The paint (possibly <code>null</code>). 1012 * 1013 * @see #setRadiusGridlinePaint(Paint) 1014 */ 1015 public Paint getRadiusGridlinePaint() { 1016 return this.radiusGridlinePaint; 1017 } 1018 1019 /** 1020 * Sets the paint for the grid lines plotted against the radius axis and 1021 * sends a {@link PlotChangeEvent} to all registered listeners. 1022 * <p> 1023 * If you set this to <code>null</code>, no grid lines will be drawn. 1024 * 1025 * @param paint the paint (<code>null</code> permitted). 1026 * 1027 * @see #getRadiusGridlinePaint() 1028 */ 1029 public void setRadiusGridlinePaint(Paint paint) { 1030 this.radiusGridlinePaint = paint; 1031 fireChangeEvent(); 1032 } 1033 1034 /** 1035 * Return the current value of the flag indicating if radial minor 1036 * grid-lines will be drawn or not. 1037 * 1038 * @return Returns <code>true</code> if radial minor grid-lines are drawn. 1039 * @since 1.0.15 1040 */ 1041 public boolean isRadiusMinorGridlinesVisible() { 1042 return this.radiusMinorGridlinesVisible; 1043 } 1044 1045 /** 1046 * Set the flag that determines if radial minor grid-lines will be drawn, 1047 * and sends a {@link PlotChangeEvent} to all registered listeners. 1048 * 1049 * @param flag <code>true</code> to draw the radial minor grid-lines, 1050 * <code>false</code> to hide them. 1051 * @since 1.0.15 1052 */ 1053 public void setRadiusMinorGridlinesVisible(boolean flag) { 1054 this.radiusMinorGridlinesVisible = flag; 1055 fireChangeEvent(); 1056 } 1057 1058 /** 1059 * Returns the margin around the plot area. 1060 * 1061 * @return The actual margin in pixels. 1062 * 1063 * @since 1.0.14 1064 */ 1065 public int getMargin() { 1066 return this.margin; 1067 } 1068 1069 /** 1070 * Set the margin around the plot area and sends a 1071 * {@link PlotChangeEvent} to all registered listeners. 1072 * 1073 * @param margin The new margin in pixels. 1074 * 1075 * @since 1.0.14 1076 */ 1077 public void setMargin(int margin) { 1078 this.margin = margin; 1079 fireChangeEvent(); 1080 } 1081 1082 /** 1083 * Returns the fixed legend items, if any. 1084 * 1085 * @return The legend items (possibly <code>null</code>). 1086 * 1087 * @see #setFixedLegendItems(LegendItemCollection) 1088 * 1089 * @since 1.0.14 1090 */ 1091 public LegendItemCollection getFixedLegendItems() { 1092 return this.fixedLegendItems; 1093 } 1094 1095 /** 1096 * Sets the fixed legend items for the plot. Leave this set to 1097 * <code>null</code> if you prefer the legend items to be created 1098 * automatically. 1099 * 1100 * @param items the legend items (<code>null</code> permitted). 1101 * 1102 * @see #getFixedLegendItems() 1103 * 1104 * @since 1.0.14 1105 */ 1106 public void setFixedLegendItems(LegendItemCollection items) { 1107 this.fixedLegendItems = items; 1108 fireChangeEvent(); 1109 } 1110 1111 /** 1112 * Add text to be displayed in the lower right hand corner and sends a 1113 * {@link PlotChangeEvent} to all registered listeners. 1114 * 1115 * @param text the text to display (<code>null</code> not permitted). 1116 * 1117 * @see #removeCornerTextItem(String) 1118 */ 1119 public void addCornerTextItem(String text) { 1120 ParamChecks.nullNotPermitted(text, "text"); 1121 this.cornerTextItems.add(text); 1122 fireChangeEvent(); 1123 } 1124 1125 /** 1126 * Remove the given text from the list of corner text items and 1127 * sends a {@link PlotChangeEvent} to all registered listeners. 1128 * 1129 * @param text the text to remove (<code>null</code> ignored). 1130 * 1131 * @see #addCornerTextItem(String) 1132 */ 1133 public void removeCornerTextItem(String text) { 1134 boolean removed = this.cornerTextItems.remove(text); 1135 if (removed) { 1136 fireChangeEvent(); 1137 } 1138 } 1139 1140 /** 1141 * Clear the list of corner text items and sends a {@link PlotChangeEvent} 1142 * to all registered listeners. 1143 * 1144 * @see #addCornerTextItem(String) 1145 * @see #removeCornerTextItem(String) 1146 */ 1147 public void clearCornerTextItems() { 1148 if (this.cornerTextItems.size() > 0) { 1149 this.cornerTextItems.clear(); 1150 fireChangeEvent(); 1151 } 1152 } 1153 1154 /** 1155 * Generates a list of tick values for the angular tick marks. 1156 * 1157 * @return A list of {@link NumberTick} instances. 1158 * 1159 * @since 1.0.10 1160 */ 1161 protected List refreshAngleTicks() { 1162 List ticks = new ArrayList(); 1163 for (double currentTickVal = 0.0; currentTickVal < 360.0; 1164 currentTickVal += this.angleTickUnit.getSize()) { 1165 1166 TextAnchor ta = calculateTextAnchor(currentTickVal); 1167 NumberTick tick = new NumberTick(new Double(currentTickVal), 1168 this.angleTickUnit.valueToString(currentTickVal), 1169 ta, TextAnchor.CENTER, 0.0); 1170 ticks.add(tick); 1171 } 1172 return ticks; 1173 } 1174 1175 /** 1176 * Calculate the text position for the given degrees. 1177 * 1178 * @param angleDegrees the angle in degrees. 1179 * 1180 * @return The optimal text anchor. 1181 * @since 1.0.14 1182 */ 1183 protected TextAnchor calculateTextAnchor(double angleDegrees) { 1184 TextAnchor ta = TextAnchor.CENTER; 1185 1186 // normalize angle 1187 double offset = this.angleOffset; 1188 while (offset < 0.0) { 1189 offset += 360.0; 1190 } 1191 double normalizedAngle = (((this.counterClockwise ? -1 : 1) 1192 * angleDegrees) + offset) % 360; 1193 while (this.counterClockwise && (normalizedAngle < 0.0)) { 1194 normalizedAngle += 360.0; 1195 } 1196 1197 if (normalizedAngle == 0.0) { 1198 ta = TextAnchor.CENTER_LEFT; 1199 } 1200 else if (normalizedAngle > 0.0 && normalizedAngle < 90.0) { 1201 ta = TextAnchor.TOP_LEFT; 1202 } 1203 else if (normalizedAngle == 90.0) { 1204 ta = TextAnchor.TOP_CENTER; 1205 } 1206 else if (normalizedAngle > 90.0 && normalizedAngle < 180.0) { 1207 ta = TextAnchor.TOP_RIGHT; 1208 } 1209 else if (normalizedAngle == 180) { 1210 ta = TextAnchor.CENTER_RIGHT; 1211 } 1212 else if (normalizedAngle > 180.0 && normalizedAngle < 270.0) { 1213 ta = TextAnchor.BOTTOM_RIGHT; 1214 } 1215 else if (normalizedAngle == 270) { 1216 ta = TextAnchor.BOTTOM_CENTER; 1217 } 1218 else if (normalizedAngle > 270.0 && normalizedAngle < 360.0) { 1219 ta = TextAnchor.BOTTOM_LEFT; 1220 } 1221 return ta; 1222 } 1223 1224 /** 1225 * Maps a dataset to a particular axis. All data will be plotted 1226 * against axis zero by default, no mapping is required for this case. 1227 * 1228 * @param index the dataset index (zero-based). 1229 * @param axisIndex the axis index. 1230 * 1231 * @since 1.0.14 1232 */ 1233 public void mapDatasetToAxis(int index, int axisIndex) { 1234 List axisIndices = new java.util.ArrayList(1); 1235 axisIndices.add(new Integer(axisIndex)); 1236 mapDatasetToAxes(index, axisIndices); 1237 } 1238 1239 /** 1240 * Maps the specified dataset to the axes in the list. Note that the 1241 * conversion of data values into Java2D space is always performed using 1242 * the first axis in the list. 1243 * 1244 * @param index the dataset index (zero-based). 1245 * @param axisIndices the axis indices (<code>null</code> permitted). 1246 * 1247 * @since 1.0.14 1248 */ 1249 public void mapDatasetToAxes(int index, List axisIndices) { 1250 if (index < 0) { 1251 throw new IllegalArgumentException("Requires 'index' >= 0."); 1252 } 1253 checkAxisIndices(axisIndices); 1254 Integer key = new Integer(index); 1255 this.datasetToAxesMap.put(key, new ArrayList(axisIndices)); 1256 // fake a dataset change event to update axes... 1257 datasetChanged(new DatasetChangeEvent(this, getDataset(index))); 1258 } 1259 1260 /** 1261 * This method is used to perform argument checking on the list of 1262 * axis indices passed to mapDatasetToAxes(). 1263 * 1264 * @param indices the list of indices (<code>null</code> permitted). 1265 */ 1266 private void checkAxisIndices(List indices) { 1267 // axisIndices can be: 1268 // 1. null; 1269 // 2. non-empty, containing only Integer objects that are unique. 1270 if (indices == null) { 1271 return; // OK 1272 } 1273 int count = indices.size(); 1274 if (count == 0) { 1275 throw new IllegalArgumentException("Empty list not permitted."); 1276 } 1277 HashSet set = new HashSet(); 1278 for (int i = 0; i < count; i++) { 1279 Object item = indices.get(i); 1280 if (!(item instanceof Integer)) { 1281 throw new IllegalArgumentException( 1282 "Indices must be Integer instances."); 1283 } 1284 if (set.contains(item)) { 1285 throw new IllegalArgumentException("Indices must be unique."); 1286 } 1287 set.add(item); 1288 } 1289 } 1290 1291 /** 1292 * Returns the axis for a dataset. 1293 * 1294 * @param index the dataset index. 1295 * 1296 * @return The axis. 1297 * 1298 * @since 1.0.14 1299 */ 1300 public ValueAxis getAxisForDataset(int index) { 1301 ValueAxis valueAxis; 1302 List axisIndices = (List) this.datasetToAxesMap.get( 1303 new Integer(index)); 1304 if (axisIndices != null) { 1305 // the first axis in the list is used for data <--> Java2D 1306 Integer axisIndex = (Integer) axisIndices.get(0); 1307 valueAxis = getAxis(axisIndex.intValue()); 1308 } 1309 else { 1310 valueAxis = getAxis(0); 1311 } 1312 return valueAxis; 1313 } 1314 1315 /** 1316 * Returns the index of the given axis. 1317 * 1318 * @param axis the axis. 1319 * 1320 * @return The axis index or -1 if axis is not used in this plot. 1321 * 1322 * @since 1.0.14 1323 */ 1324 public int getAxisIndex(ValueAxis axis) { 1325 int result = this.axes.indexOf(axis); 1326 if (result < 0) { 1327 // try the parent plot 1328 Plot parent = getParent(); 1329 if (parent instanceof PolarPlot) { 1330 PolarPlot p = (PolarPlot) parent; 1331 result = p.getAxisIndex(axis); 1332 } 1333 } 1334 return result; 1335 } 1336 1337 /** 1338 * Returns the index of the specified renderer, or <code>-1</code> if the 1339 * renderer is not assigned to this plot. 1340 * 1341 * @param renderer the renderer (<code>null</code> permitted). 1342 * 1343 * @return The renderer index. 1344 * 1345 * @since 1.0.14 1346 */ 1347 public int getIndexOf(PolarItemRenderer renderer) { 1348 return this.renderers.indexOf(renderer); 1349 } 1350 1351 /** 1352 * Draws the plot on a Java 2D graphics device (such as the screen or a 1353 * printer). 1354 * <P> 1355 * This plot relies on a {@link PolarItemRenderer} to draw each 1356 * item in the plot. This allows the visual representation of the data to 1357 * be changed easily. 1358 * <P> 1359 * The optional info argument collects information about the rendering of 1360 * the plot (dimensions, tooltip information etc). Just pass in 1361 * <code>null</code> if you do not need this information. 1362 * 1363 * @param g2 the graphics device. 1364 * @param area the area within which the plot (including axes and 1365 * labels) should be drawn. 1366 * @param anchor the anchor point (<code>null</code> permitted). 1367 * @param parentState ignored. 1368 * @param info collects chart drawing information (<code>null</code> 1369 * permitted). 1370 */ 1371 @Override 1372 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 1373 PlotState parentState, PlotRenderingInfo info) { 1374 1375 // if the plot area is too small, just return... 1376 boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); 1377 boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); 1378 if (b1 || b2) { 1379 return; 1380 } 1381 1382 // record the plot area... 1383 if (info != null) { 1384 info.setPlotArea(area); 1385 } 1386 1387 // adjust the drawing area for the plot insets (if any)... 1388 RectangleInsets insets = getInsets(); 1389 insets.trim(area); 1390 1391 Rectangle2D dataArea = area; 1392 if (info != null) { 1393 info.setDataArea(dataArea); 1394 } 1395 1396 // draw the plot background and axes... 1397 drawBackground(g2, dataArea); 1398 int axisCount = this.axes.size(); 1399 AxisState state = null; 1400 for (int i = 0; i < axisCount; i++) { 1401 ValueAxis axis = getAxis(i); 1402 if (axis != null) { 1403 PolarAxisLocation location 1404 = (PolarAxisLocation) this.axisLocations.get(i); 1405 AxisState s = this.drawAxis(axis, location, g2, dataArea); 1406 if (i == 0) { 1407 state = s; 1408 } 1409 } 1410 } 1411 1412 // now for each dataset, get the renderer and the appropriate axis 1413 // and render the dataset... 1414 Shape originalClip = g2.getClip(); 1415 Composite originalComposite = g2.getComposite(); 1416 1417 g2.clip(dataArea); 1418 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1419 getForegroundAlpha())); 1420 this.angleTicks = refreshAngleTicks(); 1421 drawGridlines(g2, dataArea, this.angleTicks, state.getTicks()); 1422 render(g2, dataArea, info); 1423 g2.setClip(originalClip); 1424 g2.setComposite(originalComposite); 1425 drawOutline(g2, dataArea); 1426 drawCornerTextItems(g2, dataArea); 1427 } 1428 1429 /** 1430 * Draws the corner text items. 1431 * 1432 * @param g2 the drawing surface. 1433 * @param area the area. 1434 */ 1435 protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) { 1436 if (this.cornerTextItems.isEmpty()) { 1437 return; 1438 } 1439 1440 g2.setColor(Color.black); 1441 double width = 0.0; 1442 double height = 0.0; 1443 for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { 1444 String msg = (String) it.next(); 1445 FontMetrics fm = g2.getFontMetrics(); 1446 Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm); 1447 width = Math.max(width, bounds.getWidth()); 1448 height += bounds.getHeight(); 1449 } 1450 1451 double xadj = ANNOTATION_MARGIN * 2.0; 1452 double yadj = ANNOTATION_MARGIN; 1453 width += xadj; 1454 height += yadj; 1455 1456 double x = area.getMaxX() - width; 1457 double y = area.getMaxY() - height; 1458 g2.drawRect((int) x, (int) y, (int) width, (int) height); 1459 x += ANNOTATION_MARGIN; 1460 for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { 1461 String msg = (String) it.next(); 1462 Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, 1463 g2.getFontMetrics()); 1464 y += bounds.getHeight(); 1465 g2.drawString(msg, (int) x, (int) y); 1466 } 1467 } 1468 1469 /** 1470 * Draws the axis with the specified index. 1471 * 1472 * @param axis the axis. 1473 * @param location the axis location. 1474 * @param g2 the graphics target. 1475 * @param plotArea the plot area. 1476 * 1477 * @return The axis state. 1478 * 1479 * @since 1.0.14 1480 */ 1481 protected AxisState drawAxis(ValueAxis axis, PolarAxisLocation location, 1482 Graphics2D g2, Rectangle2D plotArea) { 1483 1484 double centerX = plotArea.getCenterX(); 1485 double centerY = plotArea.getCenterY(); 1486 double r = Math.min(plotArea.getWidth() / 2.0, 1487 plotArea.getHeight() / 2.0) - this.margin; 1488 double x = centerX - r; 1489 double y = centerY - r; 1490 1491 Rectangle2D dataArea = null; 1492 AxisState result = null; 1493 if (location == PolarAxisLocation.NORTH_RIGHT) { 1494 dataArea = new Rectangle2D.Double(x, y, r, r); 1495 result = axis.draw(g2, centerX, plotArea, dataArea, 1496 RectangleEdge.RIGHT, null); 1497 } 1498 else if (location == PolarAxisLocation.NORTH_LEFT) { 1499 dataArea = new Rectangle2D.Double(centerX, y, r, r); 1500 result = axis.draw(g2, centerX, plotArea, dataArea, 1501 RectangleEdge.LEFT, null); 1502 } 1503 else if (location == PolarAxisLocation.SOUTH_LEFT) { 1504 dataArea = new Rectangle2D.Double(centerX, centerY, r, r); 1505 result = axis.draw(g2, centerX, plotArea, dataArea, 1506 RectangleEdge.LEFT, null); 1507 } 1508 else if (location == PolarAxisLocation.SOUTH_RIGHT) { 1509 dataArea = new Rectangle2D.Double(x, centerY, r, r); 1510 result = axis.draw(g2, centerX, plotArea, dataArea, 1511 RectangleEdge.RIGHT, null); 1512 } 1513 else if (location == PolarAxisLocation.EAST_ABOVE) { 1514 dataArea = new Rectangle2D.Double(centerX, centerY, r, r); 1515 result = axis.draw(g2, centerY, plotArea, dataArea, 1516 RectangleEdge.TOP, null); 1517 } 1518 else if (location == PolarAxisLocation.EAST_BELOW) { 1519 dataArea = new Rectangle2D.Double(centerX, y, r, r); 1520 result = axis.draw(g2, centerY, plotArea, dataArea, 1521 RectangleEdge.BOTTOM, null); 1522 } 1523 else if (location == PolarAxisLocation.WEST_ABOVE) { 1524 dataArea = new Rectangle2D.Double(x, centerY, r, r); 1525 result = axis.draw(g2, centerY, plotArea, dataArea, 1526 RectangleEdge.TOP, null); 1527 } 1528 else if (location == PolarAxisLocation.WEST_BELOW) { 1529 dataArea = new Rectangle2D.Double(x, y, r, r); 1530 result = axis.draw(g2, centerY, plotArea, dataArea, 1531 RectangleEdge.BOTTOM, null); 1532 } 1533 1534 return result; 1535 } 1536 1537 /** 1538 * Draws a representation of the data within the dataArea region, using the 1539 * current m_Renderer. 1540 * 1541 * @param g2 the graphics device. 1542 * @param dataArea the region in which the data is to be drawn. 1543 * @param info an optional object for collection dimension 1544 * information (<code>null</code> permitted). 1545 */ 1546 protected void render(Graphics2D g2, Rectangle2D dataArea, 1547 PlotRenderingInfo info) { 1548 1549 // now get the data and plot it (the visual representation will depend 1550 // on the m_Renderer that has been set)... 1551 boolean hasData = false; 1552 int datasetCount = this.datasets.size(); 1553 for (int i = datasetCount - 1; i >= 0; i--) { 1554 XYDataset dataset = getDataset(i); 1555 if (dataset == null) { 1556 continue; 1557 } 1558 PolarItemRenderer renderer = getRenderer(i); 1559 if (renderer == null) { 1560 continue; 1561 } 1562 if (!DatasetUtilities.isEmptyOrNull(dataset)) { 1563 hasData = true; 1564 int seriesCount = dataset.getSeriesCount(); 1565 for (int series = 0; series < seriesCount; series++) { 1566 renderer.drawSeries(g2, dataArea, info, this, dataset, 1567 series); 1568 } 1569 } 1570 } 1571 if (!hasData) { 1572 drawNoDataMessage(g2, dataArea); 1573 } 1574 } 1575 1576 /** 1577 * Draws the gridlines for the plot, if they are visible. 1578 * 1579 * @param g2 the graphics device. 1580 * @param dataArea the data area. 1581 * @param angularTicks the ticks for the angular axis. 1582 * @param radialTicks the ticks for the radial axis. 1583 */ 1584 protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea, 1585 List angularTicks, List radialTicks) { 1586 1587 PolarItemRenderer renderer = getRenderer(); 1588 // no renderer, no gridlines... 1589 if (renderer == null) { 1590 return; 1591 } 1592 1593 // draw the domain grid lines, if any... 1594 if (isAngleGridlinesVisible()) { 1595 Stroke gridStroke = getAngleGridlineStroke(); 1596 Paint gridPaint = getAngleGridlinePaint(); 1597 if ((gridStroke != null) && (gridPaint != null)) { 1598 renderer.drawAngularGridLines(g2, this, angularTicks, 1599 dataArea); 1600 } 1601 } 1602 1603 // draw the radius grid lines, if any... 1604 if (isRadiusGridlinesVisible()) { 1605 Stroke gridStroke = getRadiusGridlineStroke(); 1606 Paint gridPaint = getRadiusGridlinePaint(); 1607 if ((gridStroke != null) && (gridPaint != null)) { 1608 List ticks = buildRadialTicks(radialTicks); 1609 renderer.drawRadialGridLines(g2, this, getAxis(), 1610 ticks, dataArea); 1611 } 1612 } 1613 } 1614 1615 /** 1616 * Create a list of ticks based on the given list and plot properties. 1617 * Only ticks of a specific type may be in the result list. 1618 * 1619 * @param allTicks A list of all available ticks for the primary axis. 1620 * <code>null</code> not permitted. 1621 * @return Ticks to use for radial gridlines. 1622 * @since 1.0.15 1623 */ 1624 protected List buildRadialTicks(List allTicks) 1625 { 1626 List ticks = new ArrayList(); 1627 Iterator it = allTicks.iterator(); 1628 while (it.hasNext()) { 1629 ValueTick tick = (ValueTick) it.next(); 1630 if (isRadiusMinorGridlinesVisible() || 1631 TickType.MAJOR.equals(tick.getTickType())) { 1632 ticks.add(tick); 1633 } 1634 } 1635 return ticks; 1636 } 1637 1638 /** 1639 * Zooms the axis ranges by the specified percentage about the anchor point. 1640 * 1641 * @param percent the amount of the zoom. 1642 */ 1643 @Override 1644 public void zoom(double percent) { 1645 for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) { 1646 final ValueAxis axis = getAxis(axisIdx); 1647 if (axis != null) { 1648 if (percent > 0.0) { 1649 double radius = axis.getUpperBound(); 1650 double scaledRadius = radius * percent; 1651 axis.setUpperBound(scaledRadius); 1652 axis.setAutoRange(false); 1653 } 1654 else { 1655 axis.setAutoRange(true); 1656 } 1657 } 1658 } 1659 } 1660 1661 /** 1662 * A utility method that returns a list of datasets that are mapped to a 1663 * particular axis. 1664 * 1665 * @param axisIndex the axis index (<code>null</code> not permitted). 1666 * 1667 * @return A list of datasets. 1668 * 1669 * @since 1.0.14 1670 */ 1671 private List getDatasetsMappedToAxis(Integer axisIndex) { 1672 ParamChecks.nullNotPermitted(axisIndex, "axisIndex"); 1673 List result = new ArrayList(); 1674 for (int i = 0; i < this.datasets.size(); i++) { 1675 List mappedAxes = (List) this.datasetToAxesMap.get(new Integer(i)); 1676 if (mappedAxes == null) { 1677 if (axisIndex.equals(ZERO)) { 1678 result.add(this.datasets.get(i)); 1679 } 1680 } 1681 else { 1682 if (mappedAxes.contains(axisIndex)) { 1683 result.add(this.datasets.get(i)); 1684 } 1685 } 1686 } 1687 return result; 1688 } 1689 1690 /** 1691 * Returns the range for the specified axis. 1692 * 1693 * @param axis the axis. 1694 * 1695 * @return The range. 1696 */ 1697 @Override 1698 public Range getDataRange(ValueAxis axis) { 1699 Range result = null; 1700 int axisIdx = getAxisIndex(axis); 1701 List mappedDatasets = new ArrayList(); 1702 1703 if (axisIdx >= 0) { 1704 mappedDatasets = getDatasetsMappedToAxis(new Integer(axisIdx)); 1705 } 1706 1707 // iterate through the datasets that map to the axis and get the union 1708 // of the ranges. 1709 Iterator iterator = mappedDatasets.iterator(); 1710 int datasetIdx = -1; 1711 while (iterator.hasNext()) { 1712 datasetIdx++; 1713 XYDataset d = (XYDataset) iterator.next(); 1714 if (d != null) { 1715 // FIXME better ask the renderer instead of DatasetUtilities 1716 result = Range.combine(result, 1717 DatasetUtilities.findRangeBounds(d)); 1718 } 1719 } 1720 1721 return result; 1722 } 1723 1724 /** 1725 * Receives notification of a change to the plot's m_Dataset. 1726 * <P> 1727 * The axis ranges are updated if necessary. 1728 * 1729 * @param event information about the event (not used here). 1730 */ 1731 @Override 1732 public void datasetChanged(DatasetChangeEvent event) { 1733 for (int i = 0; i < this.axes.size(); i++) { 1734 final ValueAxis axis = (ValueAxis) this.axes.get(i); 1735 if (axis != null) { 1736 axis.configure(); 1737 } 1738 } 1739 if (getParent() != null) { 1740 getParent().datasetChanged(event); 1741 } 1742 else { 1743 super.datasetChanged(event); 1744 } 1745 } 1746 1747 /** 1748 * Notifies all registered listeners of a property change. 1749 * <P> 1750 * One source of property change events is the plot's m_Renderer. 1751 * 1752 * @param event information about the property change. 1753 */ 1754 @Override 1755 public void rendererChanged(RendererChangeEvent event) { 1756 fireChangeEvent(); 1757 } 1758 1759 /** 1760 * Returns the legend items for the plot. Each legend item is generated by 1761 * the plot's m_Renderer, since the m_Renderer is responsible for the visual 1762 * representation of the data. 1763 * 1764 * @return The legend items. 1765 */ 1766 @Override 1767 public LegendItemCollection getLegendItems() { 1768 if (this.fixedLegendItems != null) { 1769 return this.fixedLegendItems; 1770 } 1771 LegendItemCollection result = new LegendItemCollection(); 1772 int count = this.datasets.size(); 1773 for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) { 1774 XYDataset dataset = getDataset(datasetIndex); 1775 PolarItemRenderer renderer = getRenderer(datasetIndex); 1776 if (dataset != null && renderer != null) { 1777 int seriesCount = dataset.getSeriesCount(); 1778 for (int i = 0; i < seriesCount; i++) { 1779 LegendItem item = renderer.getLegendItem(i); 1780 result.add(item); 1781 } 1782 } 1783 } 1784 return result; 1785 } 1786 1787 /** 1788 * Tests this plot for equality with another object. 1789 * 1790 * @param obj the object (<code>null</code> permitted). 1791 * 1792 * @return <code>true</code> or <code>false</code>. 1793 */ 1794 @Override 1795 public boolean equals(Object obj) { 1796 if (obj == this) { 1797 return true; 1798 } 1799 if (!(obj instanceof PolarPlot)) { 1800 return false; 1801 } 1802 PolarPlot that = (PolarPlot) obj; 1803 if (!this.axes.equals(that.axes)) { 1804 return false; 1805 } 1806 if (!this.axisLocations.equals(that.axisLocations)) { 1807 return false; 1808 } 1809 if (!this.renderers.equals(that.renderers)) { 1810 return false; 1811 } 1812 if (!this.angleTickUnit.equals(that.angleTickUnit)) { 1813 return false; 1814 } 1815 if (this.angleGridlinesVisible != that.angleGridlinesVisible) { 1816 return false; 1817 } 1818 if (this.angleOffset != that.angleOffset) 1819 { 1820 return false; 1821 } 1822 if (this.counterClockwise != that.counterClockwise) 1823 { 1824 return false; 1825 } 1826 if (this.angleLabelsVisible != that.angleLabelsVisible) { 1827 return false; 1828 } 1829 if (!this.angleLabelFont.equals(that.angleLabelFont)) { 1830 return false; 1831 } 1832 if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) { 1833 return false; 1834 } 1835 if (!ObjectUtilities.equal(this.angleGridlineStroke, 1836 that.angleGridlineStroke)) { 1837 return false; 1838 } 1839 if (!PaintUtilities.equal( 1840 this.angleGridlinePaint, that.angleGridlinePaint 1841 )) { 1842 return false; 1843 } 1844 if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) { 1845 return false; 1846 } 1847 if (!ObjectUtilities.equal(this.radiusGridlineStroke, 1848 that.radiusGridlineStroke)) { 1849 return false; 1850 } 1851 if (!PaintUtilities.equal(this.radiusGridlinePaint, 1852 that.radiusGridlinePaint)) { 1853 return false; 1854 } 1855 if (this.radiusMinorGridlinesVisible != 1856 that.radiusMinorGridlinesVisible) { 1857 return false; 1858 } 1859 if (!this.cornerTextItems.equals(that.cornerTextItems)) { 1860 return false; 1861 } 1862 if (this.margin != that.margin) { 1863 return false; 1864 } 1865 if (!ObjectUtilities.equal(this.fixedLegendItems, 1866 that.fixedLegendItems)) { 1867 return false; 1868 } 1869 return super.equals(obj); 1870 } 1871 1872 /** 1873 * Returns a clone of the plot. 1874 * 1875 * @return A clone. 1876 * 1877 * @throws CloneNotSupportedException this can occur if some component of 1878 * the plot cannot be cloned. 1879 */ 1880 @Override 1881 public Object clone() throws CloneNotSupportedException { 1882 PolarPlot clone = (PolarPlot) super.clone(); 1883 clone.axes = (ObjectList) ObjectUtilities.clone(this.axes); 1884 for (int i = 0; i < this.axes.size(); i++) { 1885 ValueAxis axis = (ValueAxis) this.axes.get(i); 1886 if (axis != null) { 1887 ValueAxis clonedAxis = (ValueAxis) axis.clone(); 1888 clone.axes.set(i, clonedAxis); 1889 clonedAxis.setPlot(clone); 1890 clonedAxis.addChangeListener(clone); 1891 } 1892 } 1893 1894 // the datasets are not cloned, but listeners need to be added... 1895 clone.datasets = (ObjectList) ObjectUtilities.clone(this.datasets); 1896 for (int i = 0; i < clone.datasets.size(); ++i) { 1897 XYDataset d = getDataset(i); 1898 if (d != null) { 1899 d.addChangeListener(clone); 1900 } 1901 } 1902 1903 clone.renderers = (ObjectList) ObjectUtilities.clone(this.renderers); 1904 for (int i = 0; i < this.renderers.size(); i++) { 1905 PolarItemRenderer renderer2 = (PolarItemRenderer) this.renderers.get(i); 1906 if (renderer2 instanceof PublicCloneable) { 1907 PublicCloneable pc = (PublicCloneable) renderer2; 1908 PolarItemRenderer rc = (PolarItemRenderer) pc.clone(); 1909 clone.renderers.set(i, rc); 1910 rc.setPlot(clone); 1911 rc.addChangeListener(clone); 1912 } 1913 } 1914 1915 clone.cornerTextItems = new ArrayList(this.cornerTextItems); 1916 1917 return clone; 1918 } 1919 1920 /** 1921 * Provides serialization support. 1922 * 1923 * @param stream the output stream. 1924 * 1925 * @throws IOException if there is an I/O error. 1926 */ 1927 private void writeObject(ObjectOutputStream stream) throws IOException { 1928 stream.defaultWriteObject(); 1929 SerialUtilities.writeStroke(this.angleGridlineStroke, stream); 1930 SerialUtilities.writePaint(this.angleGridlinePaint, stream); 1931 SerialUtilities.writeStroke(this.radiusGridlineStroke, stream); 1932 SerialUtilities.writePaint(this.radiusGridlinePaint, stream); 1933 SerialUtilities.writePaint(this.angleLabelPaint, stream); 1934 } 1935 1936 /** 1937 * Provides serialization support. 1938 * 1939 * @param stream the input stream. 1940 * 1941 * @throws IOException if there is an I/O error. 1942 * @throws ClassNotFoundException if there is a classpath problem. 1943 */ 1944 private void readObject(ObjectInputStream stream) 1945 throws IOException, ClassNotFoundException { 1946 1947 stream.defaultReadObject(); 1948 this.angleGridlineStroke = SerialUtilities.readStroke(stream); 1949 this.angleGridlinePaint = SerialUtilities.readPaint(stream); 1950 this.radiusGridlineStroke = SerialUtilities.readStroke(stream); 1951 this.radiusGridlinePaint = SerialUtilities.readPaint(stream); 1952 this.angleLabelPaint = SerialUtilities.readPaint(stream); 1953 1954 int rangeAxisCount = this.axes.size(); 1955 for (int i = 0; i < rangeAxisCount; i++) { 1956 Axis axis = (Axis) this.axes.get(i); 1957 if (axis != null) { 1958 axis.setPlot(this); 1959 axis.addChangeListener(this); 1960 } 1961 } 1962 int datasetCount = this.datasets.size(); 1963 for (int i = 0; i < datasetCount; i++) { 1964 Dataset dataset = (Dataset) this.datasets.get(i); 1965 if (dataset != null) { 1966 dataset.addChangeListener(this); 1967 } 1968 } 1969 int rendererCount = this.renderers.size(); 1970 for (int i = 0; i < rendererCount; i++) { 1971 PolarItemRenderer renderer = (PolarItemRenderer) this.renderers.get(i); 1972 if (renderer != null) { 1973 renderer.addChangeListener(this); 1974 } 1975 } 1976 } 1977 1978 /** 1979 * This method is required by the {@link Zoomable} interface, but since 1980 * the plot does not have any domain axes, it does nothing. 1981 * 1982 * @param factor the zoom factor. 1983 * @param state the plot state. 1984 * @param source the source point (in Java2D coordinates). 1985 */ 1986 @Override 1987 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 1988 Point2D source) { 1989 // do nothing 1990 } 1991 1992 /** 1993 * This method is required by the {@link Zoomable} interface, but since 1994 * the plot does not have any domain axes, it does nothing. 1995 * 1996 * @param factor the zoom factor. 1997 * @param state the plot state. 1998 * @param source the source point (in Java2D coordinates). 1999 * @param useAnchor use source point as zoom anchor? 2000 * 2001 * @since 1.0.7 2002 */ 2003 @Override 2004 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 2005 Point2D source, boolean useAnchor) { 2006 // do nothing 2007 } 2008 2009 /** 2010 * This method is required by the {@link Zoomable} interface, but since 2011 * the plot does not have any domain axes, it does nothing. 2012 * 2013 * @param lowerPercent the new lower bound. 2014 * @param upperPercent the new upper bound. 2015 * @param state the plot state. 2016 * @param source the source point (in Java2D coordinates). 2017 */ 2018 @Override 2019 public void zoomDomainAxes(double lowerPercent, double upperPercent, 2020 PlotRenderingInfo state, Point2D source) { 2021 // do nothing 2022 } 2023 2024 /** 2025 * Multiplies the range on the range axis/axes by the specified factor. 2026 * 2027 * @param factor the zoom factor. 2028 * @param state the plot state. 2029 * @param source the source point (in Java2D coordinates). 2030 */ 2031 @Override 2032 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 2033 Point2D source) { 2034 zoom(factor); 2035 } 2036 2037 /** 2038 * Multiplies the range on the range axis by the specified factor. 2039 * 2040 * @param factor the zoom factor. 2041 * @param info the plot rendering info. 2042 * @param source the source point (in Java2D space). 2043 * @param useAnchor use source point as zoom anchor? 2044 * 2045 * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) 2046 * 2047 * @since 1.0.7 2048 */ 2049 @Override 2050 public void zoomRangeAxes(double factor, PlotRenderingInfo info, 2051 Point2D source, boolean useAnchor) { 2052 // get the source coordinate - this plot has always a VERTICAL 2053 // orientation 2054 final double sourceX = source.getX(); 2055 2056 for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) { 2057 final ValueAxis axis = getAxis(axisIdx); 2058 if (axis != null) { 2059 if (useAnchor) { 2060 double anchorX = axis.java2DToValue(sourceX, 2061 info.getDataArea(), RectangleEdge.BOTTOM); 2062 axis.resizeRange(factor, anchorX); 2063 } 2064 else { 2065 axis.resizeRange(factor); 2066 } 2067 } 2068 } 2069 } 2070 2071 /** 2072 * Zooms in on the range axes. 2073 * 2074 * @param lowerPercent the new lower bound. 2075 * @param upperPercent the new upper bound. 2076 * @param state the plot state. 2077 * @param source the source point (in Java2D coordinates). 2078 */ 2079 @Override 2080 public void zoomRangeAxes(double lowerPercent, double upperPercent, 2081 PlotRenderingInfo state, Point2D source) { 2082 zoom((upperPercent + lowerPercent) / 2.0); 2083 } 2084 2085 /** 2086 * Returns <code>false</code> always. 2087 * 2088 * @return <code>false</code> always. 2089 */ 2090 @Override 2091 public boolean isDomainZoomable() { 2092 return false; 2093 } 2094 2095 /** 2096 * Returns <code>true</code> to indicate that the range axis is zoomable. 2097 * 2098 * @return <code>true</code>. 2099 */ 2100 @Override 2101 public boolean isRangeZoomable() { 2102 return true; 2103 } 2104 2105 /** 2106 * Returns the orientation of the plot. 2107 * 2108 * @return The orientation. 2109 */ 2110 @Override 2111 public PlotOrientation getOrientation() { 2112 return PlotOrientation.HORIZONTAL; 2113 } 2114 2115 /** 2116 * Translates a (theta, radius) pair into Java2D coordinates. If 2117 * <code>radius</code> is less than the lower bound of the axis, then 2118 * this method returns the centre point. 2119 * 2120 * @param angleDegrees the angle in degrees. 2121 * @param radius the radius. 2122 * @param axis the axis. 2123 * @param dataArea the data area. 2124 * 2125 * @return A point in Java2D space. 2126 * 2127 * @since 1.0.14 2128 */ 2129 public Point translateToJava2D(double angleDegrees, double radius, 2130 ValueAxis axis, Rectangle2D dataArea) { 2131 2132 if (counterClockwise) { 2133 angleDegrees = -angleDegrees; 2134 } 2135 double radians = Math.toRadians(angleDegrees + this.angleOffset); 2136 2137 double minx = dataArea.getMinX() + this.margin; 2138 double maxx = dataArea.getMaxX() - this.margin; 2139 double miny = dataArea.getMinY() + this.margin; 2140 double maxy = dataArea.getMaxY() - this.margin; 2141 2142 double halfWidth = (maxx - minx) / 2.0; 2143 double halfHeight = (maxy - miny) / 2.0; 2144 2145 double midX = minx + halfWidth; 2146 double midY = miny + halfHeight; 2147 2148 double l = Math.min(halfWidth, halfHeight); 2149 Rectangle2D quadrant = new Rectangle2D.Double(midX, midY, l, l); 2150 2151 double axisMin = axis.getLowerBound(); 2152 double adjustedRadius = Math.max(radius, axisMin); 2153 2154 double length = axis.valueToJava2D(adjustedRadius, quadrant, RectangleEdge.BOTTOM) - midX; 2155 float x = (float) (midX + Math.cos(radians) * length); 2156 float y = (float) (midY + Math.sin(radians) * length); 2157 2158 int ix = Math.round(x); 2159 int iy = Math.round(y); 2160 2161 Point p = new Point(ix, iy); 2162 return p; 2163 2164 } 2165 2166 /** 2167 * Translates a (theta, radius) pair into Java2D coordinates. If 2168 * <code>radius</code> is less than the lower bound of the axis, then 2169 * this method returns the centre point. 2170 * 2171 * @param angleDegrees the angle in degrees. 2172 * @param radius the radius. 2173 * @param dataArea the data area. 2174 * 2175 * @return A point in Java2D space. 2176 * 2177 * @deprecated Since 1.0.14, use {@link #translateToJava2D(double, double, 2178 * org.jfree.chart.axis.ValueAxis, java.awt.geom.Rectangle2D)} instead. 2179 */ 2180 public Point translateValueThetaRadiusToJava2D(double angleDegrees, 2181 double radius, Rectangle2D dataArea) { 2182 2183 return translateToJava2D(angleDegrees, radius, getAxis(), dataArea); 2184 } 2185 2186 /** 2187 * Returns the upper bound of the radius axis. 2188 * 2189 * @return The upper bound. 2190 * 2191 * @deprecated Since 1.0.14, use {@link #getAxis()} and call the 2192 * getUpperBound() method. 2193 */ 2194 public double getMaxRadius() { 2195 return getAxis().getUpperBound(); 2196 } 2197 2198 /** 2199 * Returns the number of series in the dataset for this plot. If the 2200 * dataset is <code>null</code>, the method returns 0. 2201 * 2202 * @return The series count. 2203 * 2204 * @deprecated Since 1.0.14, grab a reference to the dataset and check 2205 * the series count directly. 2206 */ 2207 public int getSeriesCount() { 2208 int result = 0; 2209 XYDataset dataset = getDataset(0); 2210 if (dataset != null) { 2211 result = dataset.getSeriesCount(); 2212 } 2213 return result; 2214 } 2215 2216 /** 2217 * A utility method for drawing the axes. 2218 * 2219 * @param g2 the graphics device. 2220 * @param plotArea the plot area. 2221 * @param dataArea the data area. 2222 * 2223 * @return A map containing the axis states. 2224 * 2225 * @deprecated As of version 1.0.14, this method is no longer used. 2226 */ 2227 protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea, 2228 Rectangle2D dataArea) { 2229 return getAxis().draw(g2, dataArea.getMinY(), plotArea, dataArea, 2230 RectangleEdge.TOP, null); 2231 } 2232 2233}