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