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