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