KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > chart > plot > SpiderWebPlot


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
6  *
7  * Project Info: http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22  * USA.
23  *
24  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
25  * in the United States and other countries.]
26  *
27  * ------------------
28  * SpiderWebPlot.java
29  * ------------------
30  * (C) Copyright 2005, 2006, by Heaps of Flavour Pty Ltd and Contributors.
31  *
32  * Company Info: http://www.i4-talent.com
33  *
34  * Original Author: Don Elliott;
35  * Contributor(s): David Gilbert (for Object Refinery Limited);
36  * Nina Jeliazkova;
37  *
38  * $Id: SpiderWebPlot.java,v 1.11.2.10 2006/08/01 16:29:32 mungady Exp $
39  *
40  * Changes (from 28-Jan-2005)
41  * --------------------------
42  * 28-Jan-2005 : First cut - missing a few features - still to do:
43  * - needs tooltips/URL/label generator functions
44  * - ticks on axes / background grid?
45  * 31-Jan-2005 : Renamed SpiderWebPlot, added label generator support, and
46  * reformatted for consistency with other source files in
47  * JFreeChart (DG);
48  * 20-Apr-2005 : Renamed CategoryLabelGenerator
49  * --> CategoryItemLabelGenerator (DG);
50  * 05-May-2005 : Updated draw() method parameters (DG);
51  * 10-Jun-2005 : Added equals() method and fixed serialization (DG);
52  * 16-Jun-2005 : Added default constructor and get/setDataset()
53  * methods (DG);
54  * ------------- JFREECHART 1.0.0 ---------------------------------------------
55  * 05-Apr-2006 : Fixed bug preventing the display of zero values - see patch
56  * 1462727 (DG);
57  * 05-Apr-2006 : Added support for mouse clicks, tool tips and URLs - see patch
58  * 1463455 (DG);
59  * 01-Jun-2006 : Fix bug 1493199, NullPointerException when drawing with null
60  * info (DG);
61  *
62  */

63
64 package org.jfree.chart.plot;
65
66 import java.awt.AlphaComposite JavaDoc;
67 import java.awt.BasicStroke JavaDoc;
68 import java.awt.Color JavaDoc;
69 import java.awt.Composite JavaDoc;
70 import java.awt.Font JavaDoc;
71 import java.awt.Graphics2D JavaDoc;
72 import java.awt.Paint JavaDoc;
73 import java.awt.Polygon JavaDoc;
74 import java.awt.Rectangle JavaDoc;
75 import java.awt.Shape JavaDoc;
76 import java.awt.Stroke JavaDoc;
77 import java.awt.font.FontRenderContext JavaDoc;
78 import java.awt.font.LineMetrics JavaDoc;
79 import java.awt.geom.Arc2D JavaDoc;
80 import java.awt.geom.Ellipse2D JavaDoc;
81 import java.awt.geom.Line2D JavaDoc;
82 import java.awt.geom.Point2D JavaDoc;
83 import java.awt.geom.Rectangle2D JavaDoc;
84 import java.io.IOException JavaDoc;
85 import java.io.ObjectInputStream JavaDoc;
86 import java.io.ObjectOutputStream JavaDoc;
87 import java.io.Serializable JavaDoc;
88 import java.util.Iterator JavaDoc;
89 import java.util.List JavaDoc;
90
91 import org.jfree.chart.LegendItem;
92 import org.jfree.chart.LegendItemCollection;
93 import org.jfree.chart.entity.CategoryItemEntity;
94 import org.jfree.chart.entity.EntityCollection;
95 import org.jfree.chart.event.PlotChangeEvent;
96 import org.jfree.chart.labels.CategoryItemLabelGenerator;
97 import org.jfree.chart.labels.CategoryToolTipGenerator;
98 import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
99 import org.jfree.chart.urls.CategoryURLGenerator;
100 import org.jfree.data.category.CategoryDataset;
101 import org.jfree.data.general.DatasetChangeEvent;
102 import org.jfree.data.general.DatasetUtilities;
103 import org.jfree.io.SerialUtilities;
104 import org.jfree.ui.RectangleInsets;
105 import org.jfree.util.ObjectUtilities;
106 import org.jfree.util.PaintList;
107 import org.jfree.util.PaintUtilities;
108 import org.jfree.util.Rotation;
109 import org.jfree.util.ShapeUtilities;
110 import org.jfree.util.StrokeList;
111 import org.jfree.util.TableOrder;
112
113 /**
114  * A plot that displays data from a {@link CategoryDataset} in the form of a
115  * "spider web". Multiple series can be plotted on the same axis to allow
116  * easy comparison. This plot doesn't support negative values at present.
117  */

118 public class SpiderWebPlot extends Plot implements Cloneable JavaDoc, Serializable JavaDoc {
119     
120     /** For serialization. */
121     private static final long serialVersionUID = -5376340422031599463L;
122     
123     /** The default head radius percent (currently 1%). */
124     public static final double DEFAULT_HEAD = 0.01;
125
126     /** The default axis label gap (currently 10%). */
127     public static final double DEFAULT_AXIS_LABEL_GAP = 0.10;
128  
129     /** The default interior gap. */
130     public static final double DEFAULT_INTERIOR_GAP = 0.25;
131
132     /** The maximum interior gap (currently 40%). */
133     public static final double MAX_INTERIOR_GAP = 0.40;
134
135     /** The default starting angle for the radar chart axes. */
136     public static final double DEFAULT_START_ANGLE = 90.0;
137
138     /** The default series label font. */
139     public static final Font JavaDoc DEFAULT_LABEL_FONT = new Font JavaDoc("SansSerif",
140             Font.PLAIN, 10);
141     
142     /** The default series label paint. */
143     public static final Paint JavaDoc DEFAULT_LABEL_PAINT = Color.black;
144
145     /** The default series label background paint. */
146     public static final Paint JavaDoc DEFAULT_LABEL_BACKGROUND_PAINT
147         = new Color JavaDoc(255, 255, 192);
148
149     /** The default series label outline paint. */
150     public static final Paint JavaDoc DEFAULT_LABEL_OUTLINE_PAINT = Color.black;
151
152     /** The default series label outline stroke. */
153     public static final Stroke JavaDoc DEFAULT_LABEL_OUTLINE_STROKE
154         = new BasicStroke JavaDoc(0.5f);
155
156     /** The default series label shadow paint. */
157     public static final Paint JavaDoc DEFAULT_LABEL_SHADOW_PAINT = Color.lightGray;
158
159     /**
160      * The default maximum value plotted - forces the plot to evaluate
161      * the maximum from the data passed in
162      */

163     public static final double DEFAULT_MAX_VALUE = -1.0;
164
165     /** The head radius as a percentage of the available drawing area. */
166     protected double headPercent;
167
168     /** The space left around the outside of the plot as a percentage. */
169     private double interiorGap;
170
171     /** The gap between the labels and the axes as a %age of the radius. */
172     private double axisLabelGap;
173
174     /** The dataset. */
175     private CategoryDataset dataset;
176
177     /** The maximum value we are plotting against on each category axis */
178     private double maxValue;
179   
180     /**
181      * The data extract order (BY_ROW or BY_COLUMN). This denotes whether
182      * the data series are stored in rows (in which case the category names are
183      * derived from the column keys) or in columns (in which case the category
184      * names are derived from the row keys).
185      */

186     private TableOrder dataExtractOrder;
187
188     /** The starting angle. */
189     private double startAngle;
190
191     /** The direction for drawing the radar axis & plots. */
192     private Rotation direction;
193
194     /** The legend item shape. */
195     private transient Shape JavaDoc legendItemShape;
196
197     /** The paint for ALL series (overrides list). */
198     private transient Paint JavaDoc seriesPaint;
199
200     /** The series paint list. */
201     private PaintList seriesPaintList;
202
203     /** The base series paint (fallback). */
204     private transient Paint JavaDoc baseSeriesPaint;
205
206     /** The outline paint for ALL series (overrides list). */
207     private transient Paint JavaDoc seriesOutlinePaint;
208
209     /** The series outline paint list. */
210     private PaintList seriesOutlinePaintList;
211
212     /** The base series outline paint (fallback). */
213     private transient Paint JavaDoc baseSeriesOutlinePaint;
214
215     /** The outline stroke for ALL series (overrides list). */
216     private transient Stroke JavaDoc seriesOutlineStroke;
217
218     /** The series outline stroke list. */
219     private StrokeList seriesOutlineStrokeList;
220
221     /** The base series outline stroke (fallback). */
222     private transient Stroke JavaDoc baseSeriesOutlineStroke;
223
224     /** The font used to display the category labels. */
225     private Font JavaDoc labelFont;
226
227     /** The color used to draw the category labels. */
228     private transient Paint JavaDoc labelPaint;
229     
230     /** The label generator. */
231     private CategoryItemLabelGenerator labelGenerator;
232
233     /** controls if the web polygons are filled or not */
234     private boolean webFilled = true;
235     
236     /** A tooltip generator for the plot (<code>null</code> permitted). */
237     private CategoryToolTipGenerator toolTipGenerator;
238     
239     /** A URL generator for the plot (<code>null</code> permitted). */
240     private CategoryURLGenerator urlGenerator;
241   
242     /**
243      * Creates a default plot with no dataset.
244      */

245     public SpiderWebPlot() {
246         this(null);
247     }
248     
249     /**
250      * Creates a new spider web plot with the given dataset, with each row
251      * representing a series.
252      *
253      * @param dataset the dataset (<code>null</code> permitted).
254      */

255     public SpiderWebPlot(CategoryDataset dataset) {
256         this(dataset, TableOrder.BY_ROW);
257     }
258
259     /**
260      * Creates a new spider web plot with the given dataset.
261      *
262      * @param dataset the dataset.
263      * @param extract controls how data is extracted ({@link TableOrder#BY_ROW}
264      * or {@link TableOrder#BY_COLUMN}).
265      */

266     public SpiderWebPlot(CategoryDataset dataset, TableOrder extract) {
267         super();
268         if (extract == null) {
269             throw new IllegalArgumentException JavaDoc("Null 'extract' argument.");
270         }
271         this.dataset = dataset;
272         if (dataset != null) {
273             dataset.addChangeListener(this);
274         }
275
276         this.dataExtractOrder = extract;
277         this.headPercent = DEFAULT_HEAD;
278         this.axisLabelGap = DEFAULT_AXIS_LABEL_GAP;
279
280         this.interiorGap = DEFAULT_INTERIOR_GAP;
281         this.startAngle = DEFAULT_START_ANGLE;
282         this.direction = Rotation.CLOCKWISE;
283         this.maxValue = DEFAULT_MAX_VALUE;
284
285         this.seriesPaint = null;
286         this.seriesPaintList = new PaintList();
287         this.baseSeriesPaint = null;
288
289         this.seriesOutlinePaint = null;
290         this.seriesOutlinePaintList = new PaintList();
291         this.baseSeriesOutlinePaint = DEFAULT_OUTLINE_PAINT;
292
293         this.seriesOutlineStroke = null;
294         this.seriesOutlineStrokeList = new StrokeList();
295         this.baseSeriesOutlineStroke = DEFAULT_OUTLINE_STROKE;
296
297         this.labelFont = DEFAULT_LABEL_FONT;
298         this.labelPaint = DEFAULT_LABEL_PAINT;
299         this.labelGenerator = new StandardCategoryItemLabelGenerator();
300         
301         this.legendItemShape = DEFAULT_LEGEND_ITEM_CIRCLE;
302     }
303
304     /**
305      * Returns a short string describing the type of plot.
306      *
307      * @return The plot type.
308      */

309     public String JavaDoc getPlotType() {
310         // return localizationResources.getString("Radar_Plot");
311
return ("Spider Web Plot");
312     }
313     
314     /**
315      * Returns the dataset.
316      *
317      * @return The dataset (possibly <code>null</code>).
318      */

319     public CategoryDataset getDataset() {
320         return this.dataset;
321     }
322     
323     /**
324      * Sets the dataset used by the plot and sends a {@link PlotChangeEvent}
325      * to all registered listeners.
326      *
327      * @param dataset the dataset (<code>null</code> permitted).
328      */

329     public void setDataset(CategoryDataset dataset) {
330         // if there is an existing dataset, remove the plot from the list of
331
// change listeners...
332
if (this.dataset != null) {
333             this.dataset.removeChangeListener(this);
334         }
335
336         // set the new dataset, and register the chart as a change listener...
337
this.dataset = dataset;
338         if (dataset != null) {
339             setDatasetGroup(dataset.getGroup());
340             dataset.addChangeListener(this);
341         }
342
343         // send a dataset change event to self to trigger plot change event
344
datasetChanged(new DatasetChangeEvent(this, dataset));
345     }
346     
347     /**
348      * Method to determine if the web chart is to be filled.
349      *
350      * @return A boolean.
351      */

352     public boolean isWebFilled() {
353         return this.webFilled;
354     }
355
356     /**
357      * Sets the webFilled flag and sends a {@link PlotChangeEvent} to all
358      * registered listeners.
359      *
360      * @param flag the flag.
361      */

362     public void setWebFilled(boolean flag) {
363         this.webFilled = flag;
364         notifyListeners(new PlotChangeEvent(this));
365     }
366   
367     /**
368      * Returns the data extract order (by row or by column).
369      *
370      * @return The data extract order (never <code>null</code>).
371      *
372      * @see #setDataExtractOrder(TableOrder)
373      */

374     public TableOrder getDataExtractOrder() {
375         return this.dataExtractOrder;
376     }
377
378     /**
379      * Sets the data extract order (by row or by column) and sends a
380      * {@link PlotChangeEvent}to all registered listeners.
381      *
382      * @param order the order (<code>null</code> not permitted).
383      *
384      * @throws IllegalArgumentException if <code>order</code> is
385      * <code>null</code>.
386      *
387      * @see #getDataExtractOrder()
388      */

389     public void setDataExtractOrder(TableOrder order) {
390         if (order == null) {
391             throw new IllegalArgumentException JavaDoc("Null 'order' argument");
392         }
393         this.dataExtractOrder = order;
394         notifyListeners(new PlotChangeEvent(this));
395     }
396
397     /**
398      * Returns the head percent.
399      *
400      * @return The head percent.
401      */

402     public double getHeadPercent() {
403         return this.headPercent;
404     }
405     
406     /**
407      * Sets the head percent and sends a {@link PlotChangeEvent} to all
408      * registered listeners.
409      *
410      * @param percent the percent.
411      */

412     public void setHeadPercent(double percent) {
413         this.headPercent = percent;
414         notifyListeners(new PlotChangeEvent(this));
415     }
416     
417     /**
418      * Returns the start angle for the first radar axis.
419      * <BR>
420      * This is measured in degrees starting from 3 o'clock (Java Arc2D default)
421      * and measuring anti-clockwise.
422      *
423      * @return The start angle.
424      */

425     public double getStartAngle() {
426         return this.startAngle;
427     }
428
429     /**
430      * Sets the starting angle and sends a {@link PlotChangeEvent} to all
431      * registered listeners.
432      * <P>
433      * The initial default value is 90 degrees, which corresponds to 12 o'clock.
434      * A value of zero corresponds to 3 o'clock... this is the encoding used by
435      * Java's Arc2D class.
436      *
437      * @param angle the angle (in degrees).
438      */

439     public void setStartAngle(double angle) {
440         this.startAngle = angle;
441         notifyListeners(new PlotChangeEvent(this));
442     }
443
444     /**
445      * Returns the maximum value any category axis can take.
446      *
447      * @return The maximum value.
448      */

449     public double getMaxValue() {
450         return this.maxValue;
451     }
452
453     /**
454      * Sets the maximum value any category axis can take and sends
455      * a {@link PlotChangeEvent} to all registered listeners.
456      *
457      * @param value the maximum value.
458      */

459     public void setMaxValue(double value) {
460         this.maxValue = value;
461         notifyListeners(new PlotChangeEvent(this));
462     }
463
464     /**
465      * Returns the direction in which the radar axes are drawn
466      * (clockwise or anti-clockwise).
467      *
468      * @return The direction (never <code>null</code>).
469      */

470     public Rotation getDirection() {
471         return this.direction;
472     }
473
474     /**
475      * Sets the direction in which the radar axes are drawn and sends a
476      * {@link PlotChangeEvent} to all registered listeners.
477      *
478      * @param direction the direction (<code>null</code> not permitted).
479      */

480     public void setDirection(Rotation direction) {
481         if (direction == null) {
482             throw new IllegalArgumentException JavaDoc("Null 'direction' argument.");
483         }
484         this.direction = direction;
485         notifyListeners(new PlotChangeEvent(this));
486     }
487
488     /**
489      * Returns the interior gap, measured as a percentage of the available
490      * drawing space.
491      *
492      * @return The gap (as a percentage of the available drawing space).
493      */

494     public double getInteriorGap() {
495         return this.interiorGap;
496     }
497
498     /**
499      * Sets the interior gap and sends a {@link PlotChangeEvent} to all
500      * registered listeners. This controls the space between the edges of the
501      * plot and the plot area itself (the region where the axis labels appear).
502      *
503      * @param percent the gap (as a percentage of the available drawing space).
504      */

505     public void setInteriorGap(double percent) {
506         if ((percent < 0.0) || (percent > MAX_INTERIOR_GAP)) {
507             throw new IllegalArgumentException JavaDoc(
508                     "Percentage outside valid range.");
509         }
510         if (this.interiorGap != percent) {
511             this.interiorGap = percent;
512             notifyListeners(new PlotChangeEvent(this));
513         }
514     }
515
516     /**
517      * Returns the axis label gap.
518      *
519      * @return The axis label gap.
520      */

521     public double getAxisLabelGap() {
522         return this.axisLabelGap;
523     }
524     
525     /**
526      * Sets the axis label gap and sends a {@link PlotChangeEvent} to all
527      * registered listeners.
528      *
529      * @param gap the gap.
530      */

531     public void setAxisLabelGap(double gap) {
532         this.axisLabelGap = gap;
533         notifyListeners(new PlotChangeEvent(this));
534     }
535     
536     //// SERIES PAINT /////////////////////////
537

538     /**
539      * Returns the paint for ALL series in the plot.
540      *
541      * @return The paint (possibly <code>null</code>).
542      */

543     public Paint JavaDoc getSeriesPaint() {
544         return this.seriesPaint;
545     }
546
547     /**
548      * Sets the paint for ALL series in the plot. If this is set to</code> null
549      * </code>, then a list of paints is used instead (to allow different colors
550      * to be used for each series of the radar group).
551      *
552      * @param paint the paint (<code>null</code> permitted).
553      */

554     public void setSeriesPaint(Paint JavaDoc paint) {
555         this.seriesPaint = paint;
556         notifyListeners(new PlotChangeEvent(this));
557     }
558
559     /**
560      * Returns the paint for the specified series.
561      *
562      * @param series the series index (zero-based).
563      *
564      * @return The paint (never <code>null</code>).
565      */

566     public Paint JavaDoc getSeriesPaint(int series) {
567
568         // return the override, if there is one...
569
if (this.seriesPaint != null) {
570             return this.seriesPaint;
571         }
572
573         // otherwise look up the paint list
574
Paint JavaDoc result = this.seriesPaintList.getPaint(series);
575         if (result == null) {
576             DrawingSupplier supplier = getDrawingSupplier();
577             if (supplier != null) {
578                 Paint JavaDoc p = supplier.getNextPaint();
579                 this.seriesPaintList.setPaint(series, p);
580                 result = p;
581             }
582             else {
583                 result = this.baseSeriesPaint;
584             }
585         }
586         return result;
587
588     }
589
590     /**
591      * Sets the paint used to fill a series of the radar and sends a
592      * {@link PlotChangeEvent} to all registered listeners.
593      *
594      * @param series the series index (zero-based).
595      * @param paint the paint (<code>null</code> permitted).
596      */

597     public void setSeriesPaint(int series, Paint JavaDoc paint) {
598         this.seriesPaintList.setPaint(series, paint);
599         notifyListeners(new PlotChangeEvent(this));
600     }
601
602     /**
603      * Returns the base series paint. This is used when no other paint is
604      * available.
605      *
606      * @return The paint (never <code>null</code>).
607      */

608     public Paint JavaDoc getBaseSeriesPaint() {
609       return this.baseSeriesPaint;
610     }
611
612     /**
613      * Sets the base series paint.
614      *
615      * @param paint the paint (<code>null</code> not permitted).
616      */

617     public void setBaseSeriesPaint(Paint JavaDoc paint) {
618         if (paint == null) {
619             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
620         }
621         this.baseSeriesPaint = paint;
622         notifyListeners(new PlotChangeEvent(this));
623     }
624
625     //// SERIES OUTLINE PAINT ////////////////////////////
626

627     /**
628      * Returns the outline paint for ALL series in the plot.
629      *
630      * @return The paint (possibly <code>null</code>).
631      */

632     public Paint JavaDoc getSeriesOutlinePaint() {
633         return this.seriesOutlinePaint;
634     }
635
636     /**
637      * Sets the outline paint for ALL series in the plot. If this is set to
638      * </code> null</code>, then a list of paints is used instead (to allow
639      * different colors to be used for each series).
640      *
641      * @param paint the paint (<code>null</code> permitted).
642      */

643     public void setSeriesOutlinePaint(Paint JavaDoc paint) {
644         this.seriesOutlinePaint = paint;
645         notifyListeners(new PlotChangeEvent(this));
646     }
647
648     /**
649      * Returns the paint for the specified series.
650      *
651      * @param series the series index (zero-based).
652      *
653      * @return The paint (never <code>null</code>).
654      */

655     public Paint JavaDoc getSeriesOutlinePaint(int series) {
656         // return the override, if there is one...
657
if (this.seriesOutlinePaint != null) {
658             return this.seriesOutlinePaint;
659         }
660         // otherwise look up the paint list
661
Paint JavaDoc result = this.seriesOutlinePaintList.getPaint(series);
662         if (result == null) {
663             result = this.baseSeriesOutlinePaint;
664         }
665         return result;
666     }
667
668     /**
669      * Sets the paint used to fill a series of the radar and sends a
670      * {@link PlotChangeEvent} to all registered listeners.
671      *
672      * @param series the series index (zero-based).
673      * @param paint the paint (<code>null</code> permitted).
674      */

675     public void setSeriesOutlinePaint(int series, Paint JavaDoc paint) {
676         this.seriesOutlinePaintList.setPaint(series, paint);
677         notifyListeners(new PlotChangeEvent(this));
678     }
679
680     /**
681      * Returns the base series paint. This is used when no other paint is
682      * available.
683      *
684      * @return The paint (never <code>null</code>).
685      */

686     public Paint JavaDoc getBaseSeriesOutlinePaint() {
687         return this.baseSeriesOutlinePaint;
688     }
689
690     /**
691      * Sets the base series paint.
692      *
693      * @param paint the paint (<code>null</code> not permitted).
694      */

695     public void setBaseSeriesOutlinePaint(Paint JavaDoc paint) {
696         if (paint == null) {
697             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
698         }
699         this.baseSeriesOutlinePaint = paint;
700         notifyListeners(new PlotChangeEvent(this));
701     }
702
703     //// SERIES OUTLINE STROKE /////////////////////
704

705     /**
706      * Returns the outline stroke for ALL series in the plot.
707      *
708      * @return The stroke (possibly <code>null</code>).
709      */

710     public Stroke JavaDoc getSeriesOutlineStroke() {
711         return this.seriesOutlineStroke;
712     }
713
714     /**
715      * Sets the outline stroke for ALL series in the plot. If this is set to
716      * </code> null</code>, then a list of paints is used instead (to allow
717      * different colors to be used for each series).
718      *
719      * @param stroke the stroke (<code>null</code> permitted).
720      */

721     public void setSeriesOutlineStroke(Stroke JavaDoc stroke) {
722         this.seriesOutlineStroke = stroke;
723         notifyListeners(new PlotChangeEvent(this));
724     }
725
726     /**
727      * Returns the stroke for the specified series.
728      *
729      * @param series the series index (zero-based).
730      *
731      * @return The stroke (never <code>null</code>).
732      */

733     public Stroke JavaDoc getSeriesOutlineStroke(int series) {
734
735         // return the override, if there is one...
736
if (this.seriesOutlineStroke != null) {
737             return this.seriesOutlineStroke;
738         }
739
740         // otherwise look up the paint list
741
Stroke JavaDoc result = this.seriesOutlineStrokeList.getStroke(series);
742         if (result == null) {
743             result = this.baseSeriesOutlineStroke;
744         }
745         return result;
746
747     }
748
749     /**
750      * Sets the stroke used to fill a series of the radar and sends a
751      * {@link PlotChangeEvent} to all registered listeners.
752      *
753      * @param series the series index (zero-based).
754      * @param stroke the stroke (<code>null</code> permitted).
755      */

756     public void setSeriesOutlineStroke(int series, Stroke JavaDoc stroke) {
757         this.seriesOutlineStrokeList.setStroke(series, stroke);
758         notifyListeners(new PlotChangeEvent(this));
759     }
760
761     /**
762      * Returns the base series stroke. This is used when no other stroke is
763      * available.
764      *
765      * @return The stroke (never <code>null</code>).
766      */

767     public Stroke JavaDoc getBaseSeriesOutlineStroke() {
768         return this.baseSeriesOutlineStroke;
769     }
770
771     /**
772      * Sets the base series stroke.
773      *
774      * @param stroke the stroke (<code>null</code> not permitted).
775      */

776     public void setBaseSeriesOutlineStroke(Stroke JavaDoc stroke) {
777         if (stroke == null) {
778             throw new IllegalArgumentException JavaDoc("Null 'stroke' argument.");
779         }
780         this.baseSeriesOutlineStroke = stroke;
781         notifyListeners(new PlotChangeEvent(this));
782     }
783
784     /**
785      * Returns the shape used for legend items.
786      *
787      * @return The shape.
788      */

789     public Shape JavaDoc getLegendItemShape() {
790         return this.legendItemShape;
791     }
792
793     /**
794      * Sets the shape used for legend items.
795      *
796      * @param shape the shape (<code>null</code> not permitted).
797      */

798     public void setLegendItemShape(Shape JavaDoc shape) {
799         if (shape == null) {
800             throw new IllegalArgumentException JavaDoc("Null 'shape' argument.");
801         }
802         this.legendItemShape = shape;
803         notifyListeners(new PlotChangeEvent(this));
804     }
805
806     /**
807      * Returns the series label font.
808      *
809      * @return The font (never <code>null</code>).
810      */

811     public Font JavaDoc getLabelFont() {
812         return this.labelFont;
813     }
814
815     /**
816      * Sets the series label font and sends a {@link PlotChangeEvent} to all
817      * registered listeners.
818      *
819      * @param font the font (<code>null</code> not permitted).
820      */

821     public void setLabelFont(Font JavaDoc font) {
822         if (font == null) {
823             throw new IllegalArgumentException JavaDoc("Null 'font' argument.");
824         }
825         this.labelFont = font;
826         notifyListeners(new PlotChangeEvent(this));
827     }
828
829     /**
830      * Returns the series label paint.
831      *
832      * @return The paint (never <code>null</code>).
833      */

834     public Paint JavaDoc getLabelPaint() {
835         return this.labelPaint;
836     }
837
838     /**
839      * Sets the series label paint and sends a {@link PlotChangeEvent} to all
840      * registered listeners.
841      *
842      * @param paint the paint (<code>null</code> not permitted).
843      */

844     public void setLabelPaint(Paint JavaDoc paint) {
845         if (paint == null) {
846             throw new IllegalArgumentException JavaDoc("Null 'paint' argument.");
847         }
848         this.labelPaint = paint;
849         notifyListeners(new PlotChangeEvent(this));
850     }
851
852     /**
853      * Returns the label generator.
854      *
855      * @return The label generator (never <code>null</code>).
856      */

857     public CategoryItemLabelGenerator getLabelGenerator() {
858         return this.labelGenerator;
859     }
860     
861     /**
862      * Sets the label generator and sends a {@link PlotChangeEvent} to all
863      * registered listeners.
864      *
865      * @param generator the generator (<code>null</code> not permitted).
866      */

867     public void setLabelGenerator(CategoryItemLabelGenerator generator) {
868         if (generator == null) {
869             throw new IllegalArgumentException JavaDoc("Null 'generator' argument.");
870         }
871         this.labelGenerator = generator;
872     }
873     
874     /**
875      * Returns the tool tip generator for the plot.
876      *
877      * @return The tool tip generator (possibly <code>null</code>).
878      *
879      * @see #setToolTipGenerator(CategoryToolTipGenerator)
880      *
881      * @since 1.0.2
882      */

883     public CategoryToolTipGenerator getToolTipGenerator() {
884         return this.toolTipGenerator;
885     }
886     
887     /**
888      * Sets the tool tip generator for the plot and sends a
889      * {@link PlotChangeEvent} to all registered listeners.
890      *
891      * @param generator the generator (<code>null</code> permitted).
892      *
893      * @see #getToolTipGenerator()
894      *
895      * @since 1.0.2
896      */

897     public void setToolTipGenerator(CategoryToolTipGenerator generator) {
898         this.toolTipGenerator = generator;
899         this.notifyListeners(new PlotChangeEvent(this));
900     }
901     
902     /**
903      * Returns the URL generator for the plot.
904      *
905      * @return The URL generator (possibly <code>null</code>).
906      *
907      * @see #setURLGenerator(CategoryURLGenerator)
908      *
909      * @since 1.0.2
910      */

911     public CategoryURLGenerator getURLGenerator() {
912         return this.urlGenerator;
913     }
914     
915     /**
916      * Sets the URL generator for the plot and sends a
917      * {@link PlotChangeEvent} to all registered listeners.
918      *
919      * @param generator the generator (<code>null</code> permitted).
920      *
921      * @see #getURLGenerator()
922      *
923      * @since 1.0.2
924      */

925     public void setURLGenerator(CategoryURLGenerator generator) {
926         this.urlGenerator = generator;
927         this.notifyListeners(new PlotChangeEvent(this));
928     }
929     
930