001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, 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 * SpiderWebPlot.java 029 * ------------------ 030 * (C) Copyright 2005-2007, by Heaps of Flavour Pty Ltd and Contributors. 031 * 032 * Company Info: http://www.i4-talent.com 033 * 034 * Original Author: Don Elliott; 035 * Contributor(s): David Gilbert (for Object Refinery Limited); 036 * Nina Jeliazkova; 037 * 038 * Changes 039 * ------- 040 * 28-Jan-2005 : First cut - missing a few features - still to do: 041 * - needs tooltips/URL/label generator functions 042 * - ticks on axes / background grid? 043 * 31-Jan-2005 : Renamed SpiderWebPlot, added label generator support, and 044 * reformatted for consistency with other source files in 045 * JFreeChart (DG); 046 * 20-Apr-2005 : Renamed CategoryLabelGenerator 047 * --> CategoryItemLabelGenerator (DG); 048 * 05-May-2005 : Updated draw() method parameters (DG); 049 * 10-Jun-2005 : Added equals() method and fixed serialization (DG); 050 * 16-Jun-2005 : Added default constructor and get/setDataset() 051 * methods (DG); 052 * ------------- JFREECHART 1.0.x --------------------------------------------- 053 * 05-Apr-2006 : Fixed bug preventing the display of zero values - see patch 054 * 1462727 (DG); 055 * 05-Apr-2006 : Added support for mouse clicks, tool tips and URLs - see patch 056 * 1463455 (DG); 057 * 01-Jun-2006 : Fix bug 1493199, NullPointerException when drawing with null 058 * info (DG); 059 * 05-Feb-2007 : Added attributes for axis stroke and paint, while fixing 060 * bug 1651277, and implemented clone() properly (DG); 061 * 06-Feb-2007 : Changed getPlotValue() to protected, as suggested in bug 062 * 1605202 (DG); 063 * 05-Mar-2007 : Restore clip region correctly (see bug 1667750) (DG); 064 * 18-May-2007 : Set dataset for LegendItem (DG); 065 * 066 */ 067 068 package org.jfree.chart.plot; 069 070 import java.awt.AlphaComposite; 071 import java.awt.BasicStroke; 072 import java.awt.Color; 073 import java.awt.Composite; 074 import java.awt.Font; 075 import java.awt.Graphics2D; 076 import java.awt.Paint; 077 import java.awt.Polygon; 078 import java.awt.Rectangle; 079 import java.awt.Shape; 080 import java.awt.Stroke; 081 import java.awt.font.FontRenderContext; 082 import java.awt.font.LineMetrics; 083 import java.awt.geom.Arc2D; 084 import java.awt.geom.Ellipse2D; 085 import java.awt.geom.Line2D; 086 import java.awt.geom.Point2D; 087 import java.awt.geom.Rectangle2D; 088 import java.io.IOException; 089 import java.io.ObjectInputStream; 090 import java.io.ObjectOutputStream; 091 import java.io.Serializable; 092 import java.util.Iterator; 093 import java.util.List; 094 095 import org.jfree.chart.LegendItem; 096 import org.jfree.chart.LegendItemCollection; 097 import org.jfree.chart.entity.CategoryItemEntity; 098 import org.jfree.chart.entity.EntityCollection; 099 import org.jfree.chart.event.PlotChangeEvent; 100 import org.jfree.chart.labels.CategoryItemLabelGenerator; 101 import org.jfree.chart.labels.CategoryToolTipGenerator; 102 import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; 103 import org.jfree.chart.urls.CategoryURLGenerator; 104 import org.jfree.data.category.CategoryDataset; 105 import org.jfree.data.general.DatasetChangeEvent; 106 import org.jfree.data.general.DatasetUtilities; 107 import org.jfree.io.SerialUtilities; 108 import org.jfree.ui.RectangleInsets; 109 import org.jfree.util.ObjectUtilities; 110 import org.jfree.util.PaintList; 111 import org.jfree.util.PaintUtilities; 112 import org.jfree.util.Rotation; 113 import org.jfree.util.ShapeUtilities; 114 import org.jfree.util.StrokeList; 115 import org.jfree.util.TableOrder; 116 117 /** 118 * A plot that displays data from a {@link CategoryDataset} in the form of a 119 * "spider web". Multiple series can be plotted on the same axis to allow 120 * easy comparison. This plot doesn't support negative values at present. 121 */ 122 public class SpiderWebPlot extends Plot implements Cloneable, Serializable { 123 124 /** For serialization. */ 125 private static final long serialVersionUID = -5376340422031599463L; 126 127 /** The default head radius percent (currently 1%). */ 128 public static final double DEFAULT_HEAD = 0.01; 129 130 /** The default axis label gap (currently 10%). */ 131 public static final double DEFAULT_AXIS_LABEL_GAP = 0.10; 132 133 /** The default interior gap. */ 134 public static final double DEFAULT_INTERIOR_GAP = 0.25; 135 136 /** The maximum interior gap (currently 40%). */ 137 public static final double MAX_INTERIOR_GAP = 0.40; 138 139 /** The default starting angle for the radar chart axes. */ 140 public static final double DEFAULT_START_ANGLE = 90.0; 141 142 /** The default series label font. */ 143 public static final Font DEFAULT_LABEL_FONT = new Font("SansSerif", 144 Font.PLAIN, 10); 145 146 /** The default series label paint. */ 147 public static final Paint DEFAULT_LABEL_PAINT = Color.black; 148 149 /** The default series label background paint. */ 150 public static final Paint DEFAULT_LABEL_BACKGROUND_PAINT 151 = new Color(255, 255, 192); 152 153 /** The default series label outline paint. */ 154 public static final Paint DEFAULT_LABEL_OUTLINE_PAINT = Color.black; 155 156 /** The default series label outline stroke. */ 157 public static final Stroke DEFAULT_LABEL_OUTLINE_STROKE 158 = new BasicStroke(0.5f); 159 160 /** The default series label shadow paint. */ 161 public static final Paint DEFAULT_LABEL_SHADOW_PAINT = Color.lightGray; 162 163 /** 164 * The default maximum value plotted - forces the plot to evaluate 165 * the maximum from the data passed in 166 */ 167 public static final double DEFAULT_MAX_VALUE = -1.0; 168 169 /** The head radius as a percentage of the available drawing area. */ 170 protected double headPercent; 171 172 /** The space left around the outside of the plot as a percentage. */ 173 private double interiorGap; 174 175 /** The gap between the labels and the axes as a %age of the radius. */ 176 private double axisLabelGap; 177 178 /** 179 * The paint used to draw the axis lines. 180 * 181 * @since 1.0.4 182 */ 183 private transient Paint axisLinePaint; 184 185 /** 186 * The stroke used to draw the axis lines. 187 * 188 * @since 1.0.4 189 */ 190 private transient Stroke axisLineStroke; 191 192 /** The dataset. */ 193 private CategoryDataset dataset; 194 195 /** The maximum value we are plotting against on each category axis */ 196 private double maxValue; 197 198 /** 199 * The data extract order (BY_ROW or BY_COLUMN). This denotes whether 200 * the data series are stored in rows (in which case the category names are 201 * derived from the column keys) or in columns (in which case the category 202 * names are derived from the row keys). 203 */ 204 private TableOrder dataExtractOrder; 205 206 /** The starting angle. */ 207 private double startAngle; 208 209 /** The direction for drawing the radar axis & plots. */ 210 private Rotation direction; 211 212 /** The legend item shape. */ 213 private transient Shape legendItemShape; 214 215 /** The paint for ALL series (overrides list). */ 216 private transient Paint seriesPaint; 217 218 /** The series paint list. */ 219 private PaintList seriesPaintList; 220 221 /** The base series paint (fallback). */ 222 private transient Paint baseSeriesPaint; 223 224 /** The outline paint for ALL series (overrides list). */ 225 private transient Paint seriesOutlinePaint; 226 227 /** The series outline paint list. */ 228 private PaintList seriesOutlinePaintList; 229 230 /** The base series outline paint (fallback). */ 231 private transient Paint baseSeriesOutlinePaint; 232 233 /** The outline stroke for ALL series (overrides list). */ 234 private transient Stroke seriesOutlineStroke; 235 236 /** The series outline stroke list. */ 237 private StrokeList seriesOutlineStrokeList; 238 239 /** The base series outline stroke (fallback). */ 240 private transient Stroke baseSeriesOutlineStroke; 241 242 /** The font used to display the category labels. */ 243 private Font labelFont; 244 245 /** The color used to draw the category labels. */ 246 private transient Paint labelPaint; 247 248 /** The label generator. */ 249 private CategoryItemLabelGenerator labelGenerator; 250 251 /** controls if the web polygons are filled or not */ 252 private boolean webFilled = true; 253 254 /** A tooltip generator for the plot (<code>null</code> permitted). */ 255 private CategoryToolTipGenerator toolTipGenerator; 256 257 /** A URL generator for the plot (<code>null</code> permitted). */ 258 private CategoryURLGenerator urlGenerator; 259 260 /** 261 * Creates a default plot with no dataset. 262 */ 263 public SpiderWebPlot() { 264 this(null); 265 } 266 267 /** 268 * Creates a new spider web plot with the given dataset, with each row 269 * representing a series. 270 * 271 * @param dataset the dataset (<code>null</code> permitted). 272 */ 273 public SpiderWebPlot(CategoryDataset dataset) { 274 this(dataset, TableOrder.BY_ROW); 275 } 276 277 /** 278 * Creates a new spider web plot with the given dataset. 279 * 280 * @param dataset the dataset. 281 * @param extract controls how data is extracted ({@link TableOrder#BY_ROW} 282 * or {@link TableOrder#BY_COLUMN}). 283 */ 284 public SpiderWebPlot(CategoryDataset dataset, TableOrder extract) { 285 super(); 286 if (extract == null) { 287 throw new IllegalArgumentException("Null 'extract' argument."); 288 } 289 this.dataset = dataset; 290 if (dataset != null) { 291 dataset.addChangeListener(this); 292 } 293 294 this.dataExtractOrder = extract; 295 this.headPercent = DEFAULT_HEAD; 296 this.axisLabelGap = DEFAULT_AXIS_LABEL_GAP; 297 this.axisLinePaint = Color.black; 298 this.axisLineStroke = new BasicStroke(1.0f); 299 300 this.interiorGap = DEFAULT_INTERIOR_GAP; 301 this.startAngle = DEFAULT_START_ANGLE; 302 this.direction = Rotation.CLOCKWISE; 303 this.maxValue = DEFAULT_MAX_VALUE; 304 305 this.seriesPaint = null; 306 this.seriesPaintList = new PaintList(); 307 this.baseSeriesPaint = null; 308 309 this.seriesOutlinePaint = null; 310 this.seriesOutlinePaintList = new PaintList(); 311 this.baseSeriesOutlinePaint = DEFAULT_OUTLINE_PAINT; 312 313 this.seriesOutlineStroke = null; 314 this.seriesOutlineStrokeList = new StrokeList(); 315 this.baseSeriesOutlineStroke = DEFAULT_OUTLINE_STROKE; 316 317 this.labelFont = DEFAULT_LABEL_FONT; 318 this.labelPaint = DEFAULT_LABEL_PAINT; 319 this.labelGenerator = new StandardCategoryItemLabelGenerator(); 320 321 this.legendItemShape = DEFAULT_LEGEND_ITEM_CIRCLE; 322 } 323 324 /** 325 * Returns a short string describing the type of plot. 326 * 327 * @return The plot type. 328 */ 329 public String getPlotType() { 330 // return localizationResources.getString("Radar_Plot"); 331 return ("Spider Web Plot"); 332 } 333 334 /** 335 * Returns the dataset. 336 * 337 * @return The dataset (possibly <code>null</code>). 338 * 339 * @see #setDataset(CategoryDataset) 340 */ 341 public CategoryDataset getDataset() { 342 return this.dataset; 343 } 344 345 /** 346 * Sets the dataset used by the plot and sends a {@link PlotChangeEvent} 347 * to all registered listeners. 348 * 349 * @param dataset the dataset (<code>null</code> permitted). 350 * 351 * @see #getDataset() 352 */ 353 public void setDataset(CategoryDataset dataset) { 354 // if there is an existing dataset, remove the plot from the list of 355 // change listeners... 356 if (this.dataset != null) { 357 this.dataset.removeChangeListener(this); 358 } 359 360 // set the new dataset, and register the chart as a change listener... 361 this.dataset = dataset; 362 if (dataset != null) { 363 setDatasetGroup(dataset.getGroup()); 364 dataset.addChangeListener(this); 365 } 366 367 // send a dataset change event to self to trigger plot change event 368 datasetChanged(new DatasetChangeEvent(this, dataset)); 369 } 370 371 /** 372 * Method to determine if the web chart is to be filled. 373 * 374 * @return A boolean. 375 * 376 * @see #setWebFilled(boolean) 377 */ 378 public boolean isWebFilled() { 379 return this.webFilled; 380 } 381 382 /** 383 * Sets the webFilled flag and sends a {@link PlotChangeEvent} to all 384 * registered listeners. 385 * 386 * @param flag the flag. 387 * 388 * @see #isWebFilled() 389 */ 390 public void setWebFilled(boolean flag) { 391 this.webFilled = flag; 392 notifyListeners(new PlotChangeEvent(this)); 393 } 394 395 /** 396 * Returns the data extract order (by row or by column). 397 * 398 * @return The data extract order (never <code>null</code>). 399 * 400 * @see #setDataExtractOrder(TableOrder) 401 */ 402 public TableOrder getDataExtractOrder() { 403 return this.dataExtractOrder; 404 } 405 406 /** 407 * Sets the data extract order (by row or by column) and sends a 408 * {@link PlotChangeEvent}to all registered listeners. 409 * 410 * @param order the order (<code>null</code> not permitted). 411 * 412 * @throws IllegalArgumentException if <code>order</code> is 413 * <code>null</code>. 414 * 415 * @see #getDataExtractOrder() 416 */ 417 public void setDataExtractOrder(TableOrder order) { 418 if (order == null) { 419 throw new IllegalArgumentException("Null 'order' argument"); 420 } 421 this.dataExtractOrder = order; 422 notifyListeners(new PlotChangeEvent(this)); 423 } 424 425 /** 426 * Returns the head percent. 427 * 428 * @return The head percent. 429 * 430 * @see #setHeadPercent(double) 431 */ 432 public double getHeadPercent() { 433 return this.headPercent; 434 } 435 436 /** 437 * Sets the head percent and sends a {@link PlotChangeEvent} to all 438 * registered listeners. 439 * 440 * @param percent the percent. 441 * 442 * @see #getHeadPercent() 443 */ 444 public void setHeadPercent(double percent) { 445 this.headPercent = percent; 446 notifyListeners(new PlotChangeEvent(this)); 447 } 448 449 /** 450 * Returns the start angle for the first radar axis. 451 * <BR> 452 * This is measured in degrees starting from 3 o'clock (Java Arc2D default) 453 * and measuring anti-clockwise. 454 * 455 * @return The start angle. 456 * 457 * @see #setStartAngle(double) 458 */ 459 public double getStartAngle() { 460 return this.startAngle; 461 } 462 463 /** 464 * Sets the starting angle and sends a {@link PlotChangeEvent} to all 465 * registered listeners. 466 * <P> 467 * The initial default value is 90 degrees, which corresponds to 12 o'clock. 468 * A value of zero corresponds to 3 o'clock... this is the encoding used by 469 * Java's Arc2D class. 470 * 471 * @param angle the angle (in degrees). 472 * 473 * @see #getStartAngle() 474 */ 475 public void setStartAngle(double angle) { 476 this.startAngle = angle; 477 notifyListeners(new PlotChangeEvent(this)); 478 } 479 480 /** 481 * Returns the maximum value any category axis can take. 482 * 483 * @return The maximum value. 484 * 485 * @see #setMaxValue(double) 486 */ 487 public double getMaxValue() { 488 return this.maxValue; 489 } 490 491 /** 492 * Sets the maximum value any category axis can take and sends 493 * a {@link PlotChangeEvent} to all registered listeners. 494 * 495 * @param value the maximum value. 496 * 497 * @see #getMaxValue() 498 */ 499 public void setMaxValue(double value) { 500 this.maxValue = value; 501 notifyListeners(new PlotChangeEvent(this)); 502 } 503 504 /** 505 * Returns the direction in which the radar axes are drawn 506 * (clockwise or anti-clockwise). 507 * 508 * @return The direction (never <code>null</code>). 509 * 510 * @see #setDirection(Rotation) 511 */ 512 public Rotation getDirection() { 513 return this.direction; 514 } 515 516 /** 517 * Sets the direction in which the radar axes are drawn and sends a 518 * {@link PlotChangeEvent} to all registered listeners. 519 * 520 * @param direction the direction (<code>null</code> not permitted). 521 * 522 * @see #getDirection() 523 */ 524 public void setDirection(Rotation direction) { 525 if (direction == null) { 526 throw new IllegalArgumentException("Null 'direction' argument."); 527 } 528 this.direction = direction; 529 notifyListeners(new PlotChangeEvent(this)); 530 } 531 532 /** 533 * Returns the interior gap, measured as a percentage of the available 534 * drawing space. 535 * 536 * @return The gap (as a percentage of the available drawing space). 537 * 538 * @see #setInteriorGap(double) 539 */ 540 public double getInteriorGap() { 541 return this.interiorGap; 542 } 543 544 /** 545 * Sets the interior gap and sends a {@link PlotChangeEvent} to all 546 * registered listeners. This controls the space between the edges of the 547 * plot and the plot area itself (the region where the axis labels appear). 548 * 549 * @param percent the gap (as a percentage of the available drawing space). 550 * 551 * @see #getInteriorGap() 552 */ 553 public void setInteriorGap(double percent) { 554 if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) { 555 throw new IllegalArgumentException( 556 "Percentage outside valid range."); 557 } 558 if (this.interiorGap != percent) { 559 this.interiorGap = percent; 560 notifyListeners(new PlotChangeEvent(this)); 561 } 562 } 563 564 /** 565 * Returns the axis label gap. 566 * 567 * @return The axis label gap. 568 * 569 * @see #setAxisLabelGap(double) 570 */ 571 public double getAxisLabelGap() { 572 return this.axisLabelGap; 573 } 574 575 /** 576 * Sets the axis label gap and sends a {@link PlotChangeEvent} to all 577 * registered listeners. 578 * 579 * @param gap the gap. 580 * 581 * @see #getAxisLabelGap() 582 */ 583 public void setAxisLabelGap(double gap) { 584 this.axisLabelGap = gap; 585 notifyListeners(new PlotChangeEvent(this)); 586 } 587 588 /** 589 * Returns the paint used to draw the axis lines. 590 * 591 * @return The paint used to draw the axis lines (never <code>null</code>). 592 * 593 * @see #setAxisLinePaint(Paint) 594 * @see #getAxisLineStroke() 595 * @since 1.0.4 596 */ 597 public Paint getAxisLinePaint() { 598 return this.axisLinePaint; 599 } 600 601 /** 602 * Sets the paint used to draw the axis lines and sends a 603 * {@link PlotChangeEvent} to all registered listeners. 604 * 605 * @param paint the paint (<code>null</code> not permitted). 606 * 607 * @see #getAxisLinePaint() 608 * @since 1.0.4 609 */ 610 public void setAxisLinePaint(Paint paint) { 611 if (paint == null) { 612 throw new IllegalArgumentException("Null 'paint' argument."); 613 } 614 this.axisLinePaint = paint; 615 notifyListeners(new PlotChangeEvent(this)); 616 } 617 618 /** 619 * Returns the stroke used to draw the axis lines. 620 * 621 * @return The stroke used to draw the axis lines (never <code>null</code>). 622 * 623 * @see #setAxisLineStroke(Stroke) 624 * @see #getAxisLinePaint() 625 * @since 1.0.4 626 */ 627 public Stroke getAxisLineStroke() { 628 return this.axisLineStroke; 629 } 630 631 /** 632 * Sets the stroke used to draw the axis lines and sends a 633 * {@link PlotChangeEvent} to all registered listeners. 634 * 635 * @param stroke the stroke (<code>null</code> not permitted). 636 * 637 * @see #getAxisLineStroke() 638 * @since 1.0.4 639 */ 640 public void setAxisLineStroke(Stroke stroke) { 641 if (stroke == null) { 642 throw new IllegalArgumentException("Null 'stroke' argument."); 643 } 644 this.axisLineStroke = stroke; 645 notifyListeners(new PlotChangeEvent(this)); 646 } 647 648 //// SERIES PAINT ///////////////////////// 649 650 /** 651 * Returns the paint for ALL series in the plot. 652 * 653 * @return The paint (possibly <code>null</code>). 654 * 655 * @see #setSeriesPaint(Paint) 656 */ 657 public Paint getSeriesPaint() { 658 return this.seriesPaint; 659 } 660 661 /** 662 * Sets the paint for ALL series in the plot. If this is set to</code> null 663 * </code>, then a list of paints is used instead (to allow different colors 664 * to be used for each series of the radar group). 665 * 666 * @param paint the paint (<code>null</code> permitted). 667 * 668 * @see #getSeriesPaint() 669 */ 670 public void setSeriesPaint(Paint paint) { 671 this.seriesPaint = paint; 672 notifyListeners(new PlotChangeEvent(this)); 673 } 674 675 /** 676 * Returns the paint for the specified series. 677 * 678 * @param series the series index (zero-based). 679 * 680 * @return The paint (never <code>null</code>). 681 * 682 * @see #setSeriesPaint(int, Paint) 683 */ 684 public Paint getSeriesPaint(int series) { 685 686 // return the override, if there is one... 687 if (this.seriesPaint != null) { 688 return this.seriesPaint; 689 } 690 691 // otherwise look up the paint list 692 Paint result = this.seriesPaintList.getPaint(series); 693 if (result == null) { 694 DrawingSupplier supplier = getDrawingSupplier(); 695 if (supplier != null) { 696 Paint p = supplier.getNextPaint(); 697 this.seriesPaintList.setPaint(series, p); 698 result = p; 699 } 700 else { 701 result = this.baseSeriesPaint; 702 } 703 } 704 return result; 705 706 } 707 708 /** 709 * Sets the paint used to fill a series of the radar and sends a 710 * {@link PlotChangeEvent} to all registered listeners. 711 * 712 * @param series the series index (zero-based). 713 * @param paint the paint (<code>null</code> permitted). 714 * 715 * @see #getSeriesPaint(int) 716 */ 717 public void setSeriesPaint(int series, Paint paint) { 718 this.seriesPaintList.setPaint(series, paint); 719 notifyListeners(new PlotChangeEvent(this)); 720 } 721 722 /** 723 * Returns the base series paint. This is used when no other paint is 724 * available. 725 * 726 * @return The paint (never <code>null</code>). 727 * 728 * @see #setBaseSeriesPaint(Paint) 729 */ 730 public Paint getBaseSeriesPaint() { 731 return this.baseSeriesPaint; 732 } 733 734 /** 735 * Sets the base series paint. 736 * 737 * @param paint the paint (<code>null</code> not permitted). 738 * 739 * @see #getBaseSeriesPaint() 740 */ 741 public void setBaseSeriesPaint(Paint paint) { 742 if (paint == null) { 743 throw new IllegalArgumentException("Null 'paint' argument."); 744 } 745 this.baseSeriesPaint = paint; 746 notifyListeners(new PlotChangeEvent(this)); 747 } 748 749 //// SERIES OUTLINE PAINT //////////////////////////// 750 751 /** 752 * Returns the outline paint for ALL series in the plot. 753 * 754 * @return The paint (possibly <code>null</code>). 755 */ 756 public Paint getSeriesOutlinePaint() { 757 return this.seriesOutlinePaint; 758 } 759 760 /** 761 * Sets the outline paint for ALL series in the plot. If this is set to 762 * </code> null</code>, then a list of paints is used instead (to allow 763 * different colors to be used for each series). 764 * 765 * @param paint the paint (<code>null</code> permitted). 766 */ 767 public void setSeriesOutlinePaint(Paint paint) { 768 this.seriesOutlinePaint = paint; 769 notifyListeners(new PlotChangeEvent(this)); 770 } 771 772 /** 773 * Returns the paint for the specified series. 774 * 775 * @param series the series index (zero-based). 776 * 777 * @return The paint (never <code>null</code>). 778 */ 779 public Paint getSeriesOutlinePaint(int series) { 780 // return the override, if there is one... 781 if (this.seriesOutlinePaint != null) { 782 return this.seriesOutlinePaint; 783 } 784 // otherwise look up the paint list 785 Paint result = this.seriesOutlinePaintList.getPaint(series); 786 if (result == null) { 787 result = this.baseSeriesOutlinePaint; 788 } 789 return result; 790 } 791 792 /** 793 * Sets the paint used to fill a series of the radar and sends a 794 * {@link PlotChangeEvent} to all registered listeners. 795 * 796 * @param series the series index (zero-based). 797 * @param paint the paint (<code>null</code> permitted). 798 */ 799 public void setSeriesOutlinePaint(int series, Paint paint) { 800 this.seriesOutlinePaintList.setPaint(series, paint); 801 notifyListeners(new PlotChangeEvent(this)); 802 } 803 804 /** 805 * Returns the base series paint. This is used when no other paint is 806 * available. 807 * 808 * @return The paint (never <code>null</code>). 809 */ 810 public Paint getBaseSeriesOutlinePaint() { 811 return this.baseSeriesOutlinePaint; 812 } 813 814 /** 815 * Sets the base series paint. 816 * 817 * @param paint the paint (<code>null</code> not permitted). 818 */ 819 public void setBaseSeriesOutlinePaint(Paint paint) { 820 if (paint == null) { 821 throw new IllegalArgumentException("Null 'paint' argument."); 822 } 823 this.baseSeriesOutlinePaint = paint; 824 notifyListeners(new PlotChangeEvent(this)); 825 } 826 827 //// SERIES OUTLINE STROKE ///////////////////// 828 829 /** 830 * Returns the outline stroke for ALL series in the plot. 831 * 832 * @return The stroke (possibly <code>null</code>). 833 */ 834 public Stroke getSeriesOutlineStroke() { 835 return this.seriesOutlineStroke; 836 } 837 838 /** 839 * Sets the outline stroke for ALL series in the plot. If this is set to 840 * </code> null</code>, then a list of paints is used instead (to allow 841 * different colors to be used for each series). 842 * 843 * @param stroke the stroke (<code>null</code> permitted). 844 */ 845 public void setSeriesOutlineStroke(Stroke stroke) { 846 this.seriesOutlineStroke = stroke; 847 notifyListeners(new PlotChangeEvent(this)); 848 } 849 850 /** 851 * Returns the stroke for the specified series. 852 * 853 * @param series the series index (zero-based). 854 * 855 * @return The stroke (never <code>null</code>). 856 */ 857 public Stroke getSeriesOutlineStroke(int series) { 858 859 // return the override, if there is one... 860 if (this.seriesOutlineStroke != null) { 861 return this.seriesOutlineStroke; 862 } 863 864 // otherwise look up the paint list 865 Stroke result = this.seriesOutlineStrokeList.getStroke(series); 866 if (result == null) { 867 result = this.baseSeriesOutlineStroke; 868 } 869 return result; 870 871 } 872 873 /** 874 * Sets the stroke used to fill a series of the radar and sends a 875 * {@link PlotChangeEvent} to all registered listeners. 876 * 877 * @param series the series index (zero-based). 878 * @param stroke the stroke (<code>null</code> permitted). 879 */ 880 public void setSeriesOutlineStroke(int series, Stroke stroke) { 881 this.seriesOutlineStrokeList.setStroke(series, stroke); 882 notifyListeners(new PlotChangeEvent(this)); 883 } 884 885 /** 886 * Returns the base series stroke. This is used when no other stroke is 887 * available. 888 * 889 * @return The stroke (never <code>null</code>). 890 */ 891 public Stroke getBaseSeriesOutlineStroke() { 892 return this.baseSeriesOutlineStroke; 893 } 894 895 /** 896 * Sets the base series stroke. 897 * 898 * @param stroke the stroke (<code>null</code> not permitted). 899 */ 900 public void setBaseSeriesOutlineStroke(Stroke stroke) { 901 if (stroke == null) { 902 throw new IllegalArgumentException("Null 'stroke' argument."); 903 } 904 this.baseSeriesOutlineStroke = stroke; 905 notifyListeners(new PlotChangeEvent(this)); 906 } 907 908 /** 909 * Returns the shape used for legend items. 910 * 911 * @return The shape (never <code>null</code>). 912 * 913 * @see #setLegendItemShape(Shape) 914 */ 915 public Shape getLegendItemShape() { 916 return this.legendItemShape; 917 } 918 919 /** 920 * Sets the shape used for legend items and sends a {@link PlotChangeEvent} 921 * to all registered listeners. 922 * 923 * @param shape the shape (<code>null</code> not permitted). 924 * 925 * @see #getLegendItemShape() 926 */ 927 public void setLegendItemShape(Shape shape) { 928 if (shape == null) { 929 throw new IllegalArgumentException("Null 'shape' argument."); 930 } 931 this.legendItemShape = shape; 932 notifyListeners(new PlotChangeEvent(this)); 933 } 934 935 /** 936 * Returns the series label font. 937 * 938 * @return The font (never <code>null</code>). 939 * 940 * @see #setLabelFont(Font) 941 */ 942 public Font getLabelFont() { 943 return this.labelFont; 944 } 945 946 /** 947 * Sets the series label font and sends a {@link PlotChangeEvent} to all 948 * registered listeners. 949 * 950 * @param font the font (<code>null</code> not permitted). 951 * 952 * @see #getLabelFont() 953 */ 954 public void setLabelFont(Font font) { 955 if (font == null) { 956 throw new IllegalArgumentException("Null 'font' argument."); 957 } 958 this.labelFont = font; 959 notifyListeners(new PlotChangeEvent(this)); 960 } 961 962 /** 963 * Returns the series label paint. 964 * 965 * @return The paint (never <code>null</code>). 966 * 967 * @see #setLabelPaint(Paint) 968 */ 969 public Paint getLabelPaint() { 970 return this.labelPaint; 971 } 972 973 /** 974 * Sets the series label paint and sends a {@link PlotChangeEvent} to all 975 * registered listeners. 976 * 977 * @param paint the paint (<code>null</code> not permitted). 978 * 979 * @see #getLabelPaint() 980 */ 981 public void setLabelPaint(Paint paint) { 982 if (paint == null) { 983 throw new IllegalArgumentException("Null 'paint' argument."); 984 } 985 this.labelPaint = paint; 986 notifyListeners(new PlotChangeEvent(this)); 987 } 988 989 /** 990 * Returns the label generator. 991 * 992 * @return The label generator (never <code>null</code>). 993 * 994 * @see #setLabelGenerator(CategoryItemLabelGenerator) 995 */ 996 public CategoryItemLabelGenerator getLabelGenerator() { 997 return this.labelGenerator; 998 } 999 1000 /** 1001 * Sets the label generator and sends a {@link PlotChangeEvent} to all 1002 * registered listeners. 1003 * 1004 * @param generator the generator (<code>null</code> not permitted). 1005 * 1006 * @see #getLabelGenerator() 1007 */ 1008 public void setLabelGenerator(CategoryItemLabelGenerator generator) { 1009 if (generator == null) { 1010 throw new IllegalArgumentException("Null 'generator' argument."); 1011 } 1012 this.labelGenerator = generator; 1013 } 1014 1015 /** 1016 * Returns the tool tip generator for the plot. 1017 * 1018 * @return The tool tip generator (possibly <code>null</code>). 1019 * 1020 * @see #setToolTipGenerator(CategoryToolTipGenerator) 1021 * 1022 * @since 1.0.2 1023 */ 1024 public CategoryToolTipGenerator getToolTipGenerator() { 1025 return this.toolTipGenerator; 1026 } 1027 1028 /** 1029 * Sets the tool tip generator for the plot and sends a 1030 * {@link PlotChangeEvent} to all registered listeners. 1031 * 1032 * @param generator the generator (<code>null</code> permitted). 1033 * 1034 * @see #getToolTipGenerator() 1035 * 1036 * @since 1.0.2 1037 */ 1038 public void setToolTipGenerator(CategoryToolTipGenerator generator) { 1039 this.toolTipGenerator = generator; 1040 this.notifyListeners(new PlotChangeEvent(this)); 1041 } 1042 1043 /** 1044 * Returns the URL generator for the plot. 1045 * 1046 * @return The URL generator (possibly <code>null</code>). 1047 * 1048 * @see #setURLGenerator(CategoryURLGenerator) 1049 * 1050 * @since 1.0.2 1051 */ 1052 public CategoryURLGenerator getURLGenerator() { 1053 return this.urlGenerator; 1054 } 1055 1056 /** 1057 * Sets the URL generator for the plot and sends a 1058 * {@link PlotChangeEvent} to all registered listeners. 1059 * 1060 * @param generator the generator (<code>null</code> permitted). 1061 * 1062 * @see #getURLGenerator() 1063 * 1064 * @since 1.0.2 1065 */ 1066 public void setURLGenerator(CategoryURLGenerator generator) { 1067 this.urlGenerator = generator; 1068 this.notifyListeners(new PlotChangeEvent(this)); 1069 } 1070 1071 /** 1072 * Returns a collection of legend items for the radar chart. 1073 * 1074 * @return The legend items. 1075 */ 1076 public LegendItemCollection getLegendItems() { 1077 LegendItemCollection result = new LegendItemCollection(); 1078 1079 List keys = null; 1080 1081 if (this.dataExtractOrder == TableOrder.BY_ROW) { 1082 keys = this.dataset.getRowKeys(); 1083 } 1084 else if (this.dataExtractOrder == TableOrder.BY_COLUMN) { 1085 keys = this.dataset.getColumnKeys(); 1086 } 1087 1088 if (keys != null) { 1089 int series = 0; 1090 Iterator iterator = keys.iterator(); 1091 Shape shape = getLegendItemShape(); 1092 1093 while (iterator.hasNext()) { 1094 String label = iterator.next().toString(); 1095 String description = label; 1096 1097 Paint paint = getSeriesPaint(series); 1098 Paint outlinePaint = getSeriesOutlinePaint(series); 1099 Stroke stroke = getSeriesOutlineStroke(series); 1100 LegendItem item = new LegendItem(label, description, 1101 null, null, shape, paint, stroke, outlinePaint); 1102 item.setDataset(getDataset()); 1103 result.add(item); 1104 series++; 1105 } 1106 } 1107 1108 return result; 1109 } 1110 1111 /** 1112 * Returns a cartesian point from a polar angle, length and bounding box 1113 * 1114 * @param bounds the area inside which the point needs to be. 1115 * @param angle the polar angle, in degrees. 1116 * @param length the relative length. Given in percent of maximum extend. 1117 * 1118 * @return The cartesian point. 1119 */ 1120 protected Point2D getWebPoint(Rectangle2D bounds, 1121 double angle, double length) { 1122 1123 double angrad = Math.toRadians(angle); 1124 double x = Math.cos(angrad) * length * bounds.getWidth() / 2; 1125 double y = -Math.sin(angrad) * length * bounds.getHeight() / 2; 1126 1127 return new Point2D.Double(bounds.getX() + x + bounds.getWidth() / 2, 1128 bounds.getY() + y + bounds.getHeight() / 2); 1129 } 1130 1131 /** 1132 * Draws the plot on a Java 2D graphics device (such as the screen or a 1133 * printer). 1134 * 1135 * @param g2 the graphics device. 1136 * @param area the area within which the plot should be drawn. 1137 * @param anchor the anchor point (<code>null</code> permitted). 1138 * @param parentState the state from the parent plot, if there is one. 1139 * @param info collects info about the drawing. 1140 */ 1141 public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 1142 PlotState parentState, 1143 PlotRenderingInfo info) 1144 { 1145 // adjust for insets... 1146 RectangleInsets insets = getInsets(); 1147 insets.trim(area); 1148 1149 if (info != null) { 1150 info.setPlotArea(area); 1151 info.setDataArea(area); 1152 } 1153 1154 drawBackground(g2, area); 1155 drawOutline(g2, area); 1156 1157 Shape savedClip = g2.getClip(); 1158 1159 g2.clip(area); 1160 Composite originalComposite = g2.getComposite(); 1161 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1162 getForegroundAlpha())); 1163 1164 if (!DatasetUtilities.isEmptyOrNull(this.dataset)) { 1165 int seriesCount = 0, catCount = 0; 1166 1167 if (this.dataExtractOrder == TableOrder.BY_ROW) { 1168 seriesCount = this.dataset.getRowCount(); 1169 catCount = this.dataset.getColumnCount(); 1170 } 1171 else { 1172 seriesCount = this.dataset.getColumnCount(); 1173 catCount = this.dataset.getRowCount(); 1174 } 1175 1176 // ensure we have a maximum value to use on the axes 1177 if (this.maxValue == DEFAULT_MAX_VALUE) 1178 calculateMaxValue(seriesCount, catCount); 1179 1180 // Next, setup the plot area 1181 1182 // adjust the plot area by the interior spacing value 1183 1184 double gapHorizontal = area.getWidth() * getInteriorGap(); 1185 double gapVertical = area.getHeight() * getInteriorGap(); 1186 1187 double X = area.getX() + gapHorizontal / 2; 1188 double Y = area.getY() + gapVertical / 2; 1189 double W = area.getWidth() - gapHorizontal; 1190 double H = area.getHeight() - gapVertical; 1191 1192 double headW = area.getWidth() * this.headPercent; 1193 double headH = area.getHeight() * this.headPercent; 1194 1195 // make the chart area a square 1196 double min = Math.min(W, H) / 2; 1197 X = (X + X + W) / 2 - min; 1198 Y = (Y + Y + H) / 2 - min; 1199 W = 2 * min; 1200 H = 2 * min; 1201 1202 Point2D centre = new Point2D.Double(X + W / 2, Y + H / 2); 1203 Rectangle2D radarArea = new Rectangle2D.Double(X, Y, W, H); 1204 1205 // draw the axis and category label 1206 for (int cat = 0; cat < catCount; cat++) { 1207 double angle = getStartAngle() 1208 + (getDirection().getFactor() * cat * 360 / catCount); 1209 1210 Point2D endPoint = getWebPoint(radarArea, angle, 1); 1211 // 1 = end of axis 1212 Line2D line = new Line2D.Double(centre, endPoint); 1213 g2.setPaint(this.axisLinePaint); 1214 g2.setStroke(this.axisLineStroke); 1215 g2.draw(line); 1216 drawLabel(g2, radarArea, 0.0, cat, angle, 360.0 / catCount); 1217 } 1218 1219 // Now actually plot each of the series polygons.. 1220 for (int series = 0; series < seriesCount; series++) { 1221 drawRadarPoly(g2, radarArea, centre, info, series, catCount, 1222 headH, headW); 1223 } 1224 } 1225 else { 1226 drawNoDataMessage(g2, area); 1227 } 1228 g2.setClip(savedClip); 1229 g2.setComposite(originalComposite); 1230 drawOutline(g2, area); 1231 } 1232 1233 /** 1234 * loop through each of the series to get the maximum value 1235 * on each category axis 1236 * 1237 * @param seriesCount the number of series 1238 * @param catCount the number of categories 1239 */ 1240 private void calculateMaxValue(int seriesCount, int catCount) { 1241 double v = 0; 1242 Number nV = null; 1243 1244 for (int seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) { 1245 for (int catIndex = 0; catIndex < catCount; catIndex++) { 1246 nV = getPlotValue(seriesIndex, catIndex); 1247 if (nV != null) { 1248 v = nV.doubleValue(); 1249 if (v > this.maxValue) { 1250 this.maxValue = v; 1251 } 1252 } 1253 } 1254 } 1255 } 1256 1257 /** 1258 * Draws a radar plot polygon. 1259 * 1260 * @param g2 the graphics device. 1261 * @param plotArea the area we are plotting in (already adjusted). 1262 * @param centre the centre point of the radar axes 1263 * @param info chart rendering info. 1264 * @param series the series within the dataset we are plotting 1265 * @param catCount the number of categories per radar plot 1266 * @param headH the data point height 1267 * @param headW the data point width 1268 */ 1269 protected void drawRadarPoly(Graphics2D g2, 1270 Rectangle2D plotArea, 1271 Point2D centre, 1272 PlotRenderingInfo info, 1273 int series, int catCount, 1274 double headH, double headW) { 1275 1276 Polygon polygon = new Polygon(); 1277 1278 EntityCollection entities = null; 1279 if (info != null) { 1280 entities = info.getOwner().getEntityCollection(); 1281 } 1282 1283 // plot the data... 1284 for (int cat = 0; cat < catCount; cat++) { 1285 1286 Number dataValue = getPlotValue(series, cat); 1287 1288 if (dataValue != null) { 1289 double value = dataValue.doubleValue(); 1290 1291 if (value >= 0) { // draw the polygon series... 1292 1293 // Finds our starting angle from the centre for this axis 1294 1295 double angle = getStartAngle() 1296 + (getDirection().getFactor() * cat * 360 / catCount); 1297 1298 // The following angle calc will ensure there isn't a top 1299 // vertical axis - this may be useful if you don't want any 1300 // given criteria to 'appear' move important than the 1301 // others.. 1302 // + (getDirection().getFactor() 1303 // * (cat + 0.5) * 360 / catCount); 1304 1305 // find the point at the appropriate distance end point 1306 // along the axis/angle identified above and add it to the 1307 // polygon 1308 1309 Point2D point = getWebPoint(plotArea, angle, 1310 value / this.maxValue); 1311 polygon.addPoint((int) point.getX(), (int) point.getY()); 1312 1313 // put an elipse at the point being plotted.. 1314 1315 Paint paint = getSeriesPaint(series); 1316 Paint outlinePaint = getSeriesOutlinePaint(series); 1317 Stroke outlineStroke = getSeriesOutlineStroke(series); 1318 1319 Ellipse2D head = new Ellipse2D.Double(point.getX() 1320 - headW / 2, point.getY() - headH / 2, headW, 1321 headH); 1322 g2.setPaint(paint); 1323 g2.fill(head); 1324 g2.setStroke(outlineStroke); 1325 g2.setPaint(outlinePaint); 1326 g2.draw(head); 1327 1328 if (entities != null) { 1329 String tip = null; 1330 if (this.toolTipGenerator != null) { 1331 tip = this.toolTipGenerator.generateToolTip( 1332 this.dataset, series, cat); 1333 } 1334 1335 String url = null; 1336 if (this.urlGenerator != null) { 1337 url = this.urlGenerator.generateURL(this.dataset, 1338 series, cat); 1339 } 1340 1341 Shape area = new Rectangle( 1342 (int) (point.getX() - headW), 1343 (int) (point.getY() - headH), 1344 (int) (headW * 2), (int) (headH * 2)); 1345 CategoryItemEntity entity = new CategoryItemEntity( 1346 area, tip, url, this.dataset, 1347 this.dataset.getRowKey(series), 1348 this.dataset.getColumnKey(cat)); 1349 entities.add(entity); 1350 } 1351 1352 } 1353 } 1354 } 1355 // Plot the polygon 1356 1357 Paint paint = getSeriesPaint(series); 1358 g2.setPaint(paint); 1359 g2.setStroke(getSeriesOutlineStroke(series)); 1360 g2.draw(polygon); 1361 1362 // Lastly, fill the web polygon if this is required 1363 1364 if (this.webFilled) { 1365 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1366 0.1f)); 1367 g2.fill(polygon); 1368 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1369 getForegroundAlpha())); 1370 } 1371 } 1372 1373 /** 1374 * Returns the value to be plotted at the interseries of the 1375 * series and the category. This allows us to plot 1376 * <code>BY_ROW</code> or <code>BY_COLUMN</code> which basically is just 1377 * reversing the definition of the categories and data series being 1378 * plotted. 1379 * 1380 * @param series the series to be plotted. 1381 * @param cat the category within the series to be plotted. 1382 * 1383 * @return The value to be plotted (possibly <code>null</code>). 1384 * 1385 * @see #getDataExtractOrder() 1386 */ 1387 protected Number getPlotValue(int series, int cat) { 1388 Number value = null; 1389 if (this.dataExtractOrder == TableOrder.BY_ROW) { 1390 value = this.dataset.getValue(series, cat); 1391 } 1392 else if (this.dataExtractOrder == TableOrder.BY_COLUMN) { 1393 value = this.dataset.getValue(cat, series); 1394 } 1395 return value; 1396 } 1397 1398 /** 1399 * Draws the label for one axis. 1400 * 1401 * @param g2 the graphics device. 1402 * @param plotArea the plot area 1403 * @param value the value of the label (ignored). 1404 * @param cat the category (zero-based index). 1405 * @param startAngle the starting angle. 1406 * @param extent the extent of the arc. 1407 */ 1408 protected void drawLabel(Graphics2D g2, Rectangle2D plotArea, double value, 1409 int cat, double startAngle, double extent) { 1410 FontRenderContext frc = g2.getFontRenderContext(); 1411 1412 String label = null; 1413 if (this.dataExtractOrder == TableOrder.BY_ROW) { 1414 // if series are in rows, then the categories are the column keys 1415 label = this.labelGenerator.generateColumnLabel(this.dataset, cat); 1416 } 1417 else { 1418 // if series are in columns, then the categories are the row keys 1419 label = this.labelGenerator.generateRowLabel(this.dataset, cat); 1420 } 1421 1422 Rectangle2D labelBounds = getLabelFont().getStringBounds(label, frc); 1423 LineMetrics lm = getLabelFont().getLineMetrics(label, frc); 1424 double ascent = lm.getAscent(); 1425 1426 Point2D labelLocation = calculateLabelLocation(labelBounds, ascent, 1427 plotArea, startAngle); 1428 1429 Composite saveComposite = g2.getComposite(); 1430 1431 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1432 1.0f)); 1433 g2.setPaint(getLabelPaint()); 1434 g2.setFont(getLabelFont()); 1435 g2.drawString(label, (float) labelLocation.getX(), 1436 (float) labelLocation.getY()); 1437 g2.setComposite(saveComposite); 1438 } 1439 1440 /** 1441 * Returns the location for a label 1442 * 1443 * @param labelBounds the label bounds. 1444 * @param ascent the ascent (height of font). 1445 * @param plotArea the plot area 1446 * @param startAngle the start angle for the pie series. 1447 * 1448 * @return The location for a label. 1449 */ 1450 protected Point2D calculateLabelLocation(Rectangle2D labelBounds, 1451 double ascent, 1452 Rectangle2D plotArea, 1453 double startAngle) 1454 { 1455 Arc2D arc1 = new Arc2D.Double(plotArea, startAngle, 0, Arc2D.OPEN); 1456 Point2D point1 = arc1.getEndPoint(); 1457 1458 double deltaX = -(point1.getX() - plotArea.getCenterX()) 1459 * this.axisLabelGap; 1460 double deltaY = -(point1.getY() - plotArea.getCenterY()) 1461 * this.axisLabelGap; 1462 1463 double labelX = point1.getX() - deltaX; 1464 double labelY = point1.getY() - deltaY; 1465 1466 if (labelX < plotArea.getCenterX()) { 1467 labelX -= labelBounds.getWidth(); 1468 } 1469 1470 if (labelX == plotArea.getCenterX()) { 1471 labelX -= labelBounds.getWidth() / 2; 1472 } 1473 1474 if (labelY > plotArea.getCenterY()) { 1475 labelY += ascent; 1476 } 1477 1478 return new Point2D.Double(labelX, labelY); 1479 } 1480 1481 /** 1482 * Tests this plot for equality with an arbitrary object. 1483 * 1484 * @param obj the object (<code>null</code> permitted). 1485 * 1486 * @return A boolean. 1487 */ 1488 public boolean equals(Object obj) { 1489 if (obj == this) { 1490 return true; 1491 } 1492 if (!(obj instanceof SpiderWebPlot)) { 1493 return false; 1494 } 1495 if (!super.equals(obj)) { 1496 return false; 1497 } 1498 SpiderWebPlot that = (SpiderWebPlot) obj; 1499 if (!this.dataExtractOrder.equals(that.dataExtractOrder)) { 1500 return false; 1501 } 1502 if (this.headPercent != that.headPercent) { 1503 return false; 1504 } 1505 if (this.interiorGap != that.interiorGap) { 1506 return false; 1507 } 1508 if (this.startAngle != that.startAngle) { 1509 return false; 1510 } 1511 if (!this.direction.equals(that.direction)) { 1512 return false; 1513 } 1514 if (this.maxValue != that.maxValue) { 1515 return false; 1516 } 1517 if (this.webFilled != that.webFilled) { 1518 return false; 1519 } 1520 if (this.axisLabelGap != that.axisLabelGap) { 1521 return false; 1522 } 1523 if (!PaintUtilities.equal(this.axisLinePaint, that.axisLinePaint)) { 1524 return false; 1525 } 1526 if (!this.axisLineStroke.equals(that.axisLineStroke)) { 1527 return false; 1528 } 1529 if (!ShapeUtilities.equal(this.legendItemShape, that.legendItemShape)) { 1530 return false; 1531 } 1532 if (!PaintUtilities.equal(this.seriesPaint, that.seriesPaint)) { 1533 return false; 1534 } 1535 if (!this.seriesPaintList.equals(that.seriesPaintList)) { 1536 return false; 1537 } 1538 if (!PaintUtilities.equal(this.baseSeriesPaint, that.baseSeriesPaint)) { 1539 return false; 1540 } 1541 if (!PaintUtilities.equal(this.seriesOutlinePaint, 1542 that.seriesOutlinePaint)) { 1543 return false; 1544 } 1545 if (!this.seriesOutlinePaintList.equals(that.seriesOutlinePaintList)) { 1546 return false; 1547 } 1548 if (!PaintUtilities.equal(this.baseSeriesOutlinePaint, 1549 that.baseSeriesOutlinePaint)) { 1550 return false; 1551 } 1552 if (!ObjectUtilities.equal(this.seriesOutlineStroke, 1553 that.seriesOutlineStroke)) { 1554 return false; 1555 } 1556 if (!this.seriesOutlineStrokeList.equals( 1557 that.seriesOutlineStrokeList)) { 1558 return false; 1559 } 1560 if (!this.baseSeriesOutlineStroke.equals( 1561 that.baseSeriesOutlineStroke)) { 1562 return false; 1563 } 1564 if (!this.labelFont.equals(that.labelFont)) { 1565 return false; 1566 } 1567 if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) { 1568 return false; 1569 } 1570 if (!this.labelGenerator.equals(that.labelGenerator)) { 1571 return false; 1572 } 1573 if (!ObjectUtilities.equal(this.toolTipGenerator, 1574 that.toolTipGenerator)) { 1575 return false; 1576 } 1577 if (!ObjectUtilities.equal(this.urlGenerator, 1578 that.urlGenerator)) { 1579 return false; 1580 } 1581 return true; 1582 } 1583 1584 /** 1585 * Returns a clone of this plot. 1586 * 1587 * @return A clone of this plot. 1588 * 1589 * @throws CloneNotSupportedException if the plot cannot be cloned for 1590 * any reason. 1591 */ 1592 public Object clone() throws CloneNotSupportedException { 1593 SpiderWebPlot clone = (SpiderWebPlot) super.clone(); 1594 clone.legendItemShape = ShapeUtilities.clone(this.legendItemShape); 1595 clone.seriesPaintList = (PaintList) this.seriesPaintList.clone(); 1596 clone.seriesOutlinePaintList 1597 = (PaintList) this.seriesOutlinePaintList.clone(); 1598 clone.seriesOutlineStrokeList 1599 = (StrokeList) this.seriesOutlineStrokeList.clone(); 1600 return clone; 1601 } 1602 1603 /** 1604 * Provides serialization support. 1605 * 1606 * @param stream the output stream. 1607 * 1608 * @throws IOException if there is an I/O error. 1609 */ 1610 private void writeObject(ObjectOutputStream stream) throws IOException { 1611 stream.defaultWriteObject(); 1612 1613 SerialUtilities.writeShape(this.legendItemShape, stream); 1614 SerialUtilities.writePaint(this.seriesPaint, stream); 1615 SerialUtilities.writePaint(this.baseSeriesPaint, stream); 1616 SerialUtilities.writePaint(this.seriesOutlinePaint, stream); 1617 SerialUtilities.writePaint(this.baseSeriesOutlinePaint, stream); 1618 SerialUtilities.writeStroke(this.seriesOutlineStroke, stream); 1619 SerialUtilities.writeStroke(this.baseSeriesOutlineStroke, stream); 1620 SerialUtilities.writePaint(this.labelPaint, stream); 1621 SerialUtilities.writePaint(this.axisLinePaint, stream); 1622 SerialUtilities.writeStroke(this.axisLineStroke, stream); 1623 } 1624 1625 /** 1626 * Provides serialization support. 1627 * 1628 * @param stream the input stream. 1629 * 1630 * @throws IOException if there is an I/O error. 1631 * @throws ClassNotFoundException if there is a classpath problem. 1632 */ 1633 private void readObject(ObjectInputStream stream) throws IOException, 1634 ClassNotFoundException { 1635 stream.defaultReadObject(); 1636 1637 this.legendItemShape = SerialUtilities.readShape(stream); 1638 this.seriesPaint = SerialUtilities.readPaint(stream); 1639 this.baseSeriesPaint = SerialUtilities.readPaint(stream); 1640 this.seriesOutlinePaint = SerialUtilities.readPaint(stream); 1641 this.baseSeriesOutlinePaint = SerialUtilities.readPaint(stream); 1642 this.seriesOutlineStroke = SerialUtilities.readStroke(stream); 1643 this.baseSeriesOutlineStroke = SerialUtilities.readStroke(stream); 1644 this.labelPaint = SerialUtilities.readPaint(stream); 1645 this.axisLinePaint = SerialUtilities.readPaint(stream); 1646 this.axisLineStroke = SerialUtilities.readStroke(stream); 1647 if (this.dataset != null) { 1648 this.dataset.addChangeListener(this); 1649 } 1650 } 1651 1652 }