001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * PolarPlot.java
029 * --------------
030 * (C) Copyright 2004-2013, by Solution Engineering, Inc. and Contributors.
031 *
032 * Original Author:  Daniel Bridenbecker, Solution Engineering, Inc.;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *                   Martin Hoeller (patches 1871902 and 2850344);
035 *
036 * Changes
037 * -------
038 * 19-Jan-2004 : Version 1, contributed by DB with minor changes by DG (DG);
039 * 07-Apr-2004 : Changed text bounds calculation (DG);
040 * 05-May-2005 : Updated draw() method parameters (DG);
041 * 09-Jun-2005 : Fixed getDataRange() and equals() methods (DG);
042 * 25-Oct-2005 : Implemented Zoomable (DG);
043 * ------------- JFREECHART 1.0.x ---------------------------------------------
044 * 07-Feb-2007 : Fixed bug 1599761, data value less than axis minimum (DG);
045 * 21-Mar-2007 : Fixed serialization bug (DG);
046 * 24-Sep-2007 : Implemented new zooming methods (DG);
047 * 17-Feb-2007 : Added angle tick unit attribute (see patch 1871902 by
048 *               Martin Hoeller) (DG);
049 * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
050 *               Jess Thrysoee (DG);
051 * 03-Sep-2009 : Applied patch 2850344 by Martin Hoeller (DG);
052 * 27-Nov-2009 : Added support for multiple datasets, renderers and axes (DG);
053 * 09-Dec-2009 : Extended getLegendItems() to handle multiple datasets (DG);
054 * 25-Jun-2010 : Better support for multiple axes (MH);
055 * 03-Oct-2011 : Added support for angleOffset and direction (MH);
056 * 12-Nov-2011 : Fixed bug 3432721, log-axis doesn't work (MH);
057 * 12-Dec-2011 : Added support for radiusMinorGridlinesVisible (MH);
058 * 02-Jul-2013 : Use ParamChecks (DG);
059 * 05-Jul-2013 : Fire change event from setRadiusMinorGridlinesVisible (DG);
060 * 
061 */
062
063package org.jfree.chart.plot;
064
065import java.awt.AlphaComposite;
066import java.awt.BasicStroke;
067import java.awt.Color;
068import java.awt.Composite;
069import java.awt.Font;
070import java.awt.FontMetrics;
071import java.awt.Graphics2D;
072import java.awt.Paint;
073import java.awt.Point;
074import java.awt.Shape;
075import java.awt.Stroke;
076import java.awt.geom.Point2D;
077import java.awt.geom.Rectangle2D;
078import java.io.IOException;
079import java.io.ObjectInputStream;
080import java.io.ObjectOutputStream;
081import java.io.Serializable;
082import java.util.ArrayList;
083import java.util.HashSet;
084import java.util.Iterator;
085import java.util.List;
086import java.util.Map;
087import java.util.ResourceBundle;
088import java.util.TreeMap;
089
090import org.jfree.chart.LegendItem;
091import org.jfree.chart.LegendItemCollection;
092import org.jfree.chart.axis.Axis;
093import org.jfree.chart.axis.AxisState;
094import org.jfree.chart.axis.NumberTick;
095import org.jfree.chart.axis.NumberTickUnit;
096import org.jfree.chart.axis.TickType;
097import org.jfree.chart.axis.TickUnit;
098import org.jfree.chart.axis.ValueAxis;
099import org.jfree.chart.axis.ValueTick;
100import org.jfree.chart.event.PlotChangeEvent;
101import org.jfree.chart.event.RendererChangeEvent;
102import org.jfree.chart.event.RendererChangeListener;
103import org.jfree.chart.renderer.PolarItemRenderer;
104import org.jfree.chart.util.ParamChecks;
105import org.jfree.chart.util.ResourceBundleWrapper;
106import org.jfree.data.Range;
107import org.jfree.data.general.Dataset;
108import org.jfree.data.general.DatasetChangeEvent;
109import org.jfree.data.general.DatasetUtilities;
110import org.jfree.data.xy.XYDataset;
111import org.jfree.io.SerialUtilities;
112import org.jfree.text.TextUtilities;
113import org.jfree.ui.RectangleEdge;
114import org.jfree.ui.RectangleInsets;
115import org.jfree.ui.TextAnchor;
116import org.jfree.util.ObjectList;
117import org.jfree.util.ObjectUtilities;
118import org.jfree.util.PaintUtilities;
119import org.jfree.util.PublicCloneable;
120
121/**
122 * Plots data that is in (theta, radius) pairs where
123 * theta equal to zero is due north and increases clockwise.
124 */
125public class PolarPlot extends Plot implements ValueAxisPlot, Zoomable,
126        RendererChangeListener, Cloneable, Serializable {
127
128    /** For serialization. */
129    private static final long serialVersionUID = 3794383185924179525L;
130
131    /** The default margin. */
132    private static final int DEFAULT_MARGIN = 20;
133
134    /** The annotation margin. */
135    private static final double ANNOTATION_MARGIN = 7.0;
136
137    /**
138     * The default angle tick unit size.
139     *
140     * @since 1.0.10
141     */
142    public static final double DEFAULT_ANGLE_TICK_UNIT_SIZE = 45.0;
143
144    /**
145     * The default angle offset.
146     *
147     * @since 1.0.14
148     */
149    public static final double DEFAULT_ANGLE_OFFSET = -90.0;
150
151    /** The default grid line stroke. */
152    public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(
153            0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
154            0.0f, new float[]{2.0f, 2.0f}, 0.0f);
155
156    /** The default grid line paint. */
157    public static final Paint DEFAULT_GRIDLINE_PAINT = Color.gray;
158
159    /** The resourceBundle for the localization. */
160    protected static ResourceBundle localizationResources
161            = ResourceBundleWrapper.getBundle(
162                    "org.jfree.chart.plot.LocalizationBundle");
163
164    /** The angles that are marked with gridlines. */
165    private List angleTicks;
166
167    /** The range axis (used for the y-values). */
168    private ObjectList axes;
169
170    /** The axis locations. */
171    private ObjectList axisLocations;
172
173    /** Storage for the datasets. */
174    private ObjectList datasets;
175
176    /** Storage for the renderers. */
177    private ObjectList renderers;
178
179    /**
180     * The tick unit that controls the spacing between the angular grid lines.
181     *
182     * @since 1.0.10
183     */
184    private TickUnit angleTickUnit;
185
186    /**
187     * An offset for the angles, to start with 0 degrees at north, east, south
188     * or west.
189     *
190     * @since 1.0.14
191     */
192    private double angleOffset;
193
194    /**
195     * A flag indicating if the angles increase counterclockwise or clockwise.
196     *
197     * @since 1.0.14
198     */
199    private boolean counterClockwise;
200
201    /** A flag that controls whether or not the angle labels are visible. */
202    private boolean angleLabelsVisible = true;
203
204    /** The font used to display the angle labels - never null. */
205    private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12);
206
207    /** The paint used to display the angle labels. */
208    private transient Paint angleLabelPaint = Color.black;
209
210    /** A flag that controls whether the angular grid-lines are visible. */
211    private boolean angleGridlinesVisible;
212
213    /** The stroke used to draw the angular grid-lines. */
214    private transient Stroke angleGridlineStroke;
215
216    /** The paint used to draw the angular grid-lines. */
217    private transient Paint angleGridlinePaint;
218
219    /** A flag that controls whether the radius grid-lines are visible. */
220    private boolean radiusGridlinesVisible;
221
222    /** The stroke used to draw the radius grid-lines. */
223    private transient Stroke radiusGridlineStroke;
224
225    /** The paint used to draw the radius grid-lines. */
226    private transient Paint radiusGridlinePaint;
227
228    /**
229     * A flag that controls whether the radial minor grid-lines are visible.
230     * @since 1.0.15
231     */
232    private boolean radiusMinorGridlinesVisible;
233
234    /** The annotations for the plot. */
235    private List cornerTextItems = new ArrayList();
236
237    /**
238     * The actual margin in pixels.
239     *
240     * @since 1.0.14
241     */
242    private int margin;
243
244    /**
245     * An optional collection of legend items that can be returned by the
246     * getLegendItems() method.
247     */
248    private LegendItemCollection fixedLegendItems;
249
250    /**
251     * Storage for the mapping between datasets/renderers and range axes.  The
252     * keys in the map are Integer objects, corresponding to the dataset
253     * index.  The values in the map are List objects containing Integer
254     * objects (corresponding to the axis indices).  If the map contains no
255     * entry for a dataset, it is assumed to map to the primary domain axis
256     * (index = 0).
257     */
258    private Map datasetToAxesMap;
259
260    /**
261     * Default constructor.
262     */
263    public PolarPlot() {
264        this(null, null, null);
265    }
266
267   /**
268     * Creates a new plot.
269     *
270     * @param dataset  the dataset (<code>null</code> permitted).
271     * @param radiusAxis  the radius axis (<code>null</code> permitted).
272     * @param renderer  the renderer (<code>null</code> permitted).
273     */
274    public PolarPlot(XYDataset dataset, ValueAxis radiusAxis,
275                PolarItemRenderer renderer) {
276
277        super();
278
279        this.datasets = new ObjectList();
280        this.datasets.set(0, dataset);
281        if (dataset != null) {
282            dataset.addChangeListener(this);
283        }
284        this.angleTickUnit = new NumberTickUnit(DEFAULT_ANGLE_TICK_UNIT_SIZE);
285
286        this.axes = new ObjectList();
287        this.datasetToAxesMap = new TreeMap();
288        this.axes.set(0, radiusAxis);
289        if (radiusAxis != null) {
290            radiusAxis.setPlot(this);
291            radiusAxis.addChangeListener(this);
292        }
293
294        // define the default locations for up to 8 axes...
295        this.axisLocations = new ObjectList();
296        this.axisLocations.set(0, PolarAxisLocation.EAST_ABOVE);
297        this.axisLocations.set(1, PolarAxisLocation.NORTH_LEFT);
298        this.axisLocations.set(2, PolarAxisLocation.WEST_BELOW);
299        this.axisLocations.set(3, PolarAxisLocation.SOUTH_RIGHT);
300        this.axisLocations.set(4, PolarAxisLocation.EAST_BELOW);
301        this.axisLocations.set(5, PolarAxisLocation.NORTH_RIGHT);
302        this.axisLocations.set(6, PolarAxisLocation.WEST_ABOVE);
303        this.axisLocations.set(7, PolarAxisLocation.SOUTH_LEFT);
304
305        this.renderers = new ObjectList();
306        this.renderers.set(0, renderer);
307        if (renderer != null) {
308            renderer.setPlot(this);
309            renderer.addChangeListener(this);
310        }
311
312        this.angleOffset = DEFAULT_ANGLE_OFFSET;
313        this.counterClockwise = false;
314        this.angleGridlinesVisible = true;
315        this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE;
316        this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT;
317
318        this.radiusGridlinesVisible = true;
319        this.radiusMinorGridlinesVisible = true;
320        this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE;
321        this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT;
322        this.margin = DEFAULT_MARGIN;
323    }
324
325    /**
326     * Returns the plot type as a string.
327     *
328     * @return A short string describing the type of plot.
329     */
330    @Override
331    public String getPlotType() {
332       return PolarPlot.localizationResources.getString("Polar_Plot");
333    }
334
335    /**
336     * Returns the primary axis for the plot.
337     *
338     * @return The primary axis (possibly <code>null</code>).
339     *
340     * @see #setAxis(ValueAxis)
341     */
342    public ValueAxis getAxis() {
343        return getAxis(0);
344    }
345
346    /**
347     * Returns an axis for the plot.
348     *
349     * @param index  the axis index.
350     *
351     * @return The axis (<code>null</code> possible).
352     *
353     * @see #setAxis(int, ValueAxis)
354     *
355     * @since 1.0.14
356     */
357    public ValueAxis getAxis(int index) {
358        ValueAxis result = null;
359        if (index < this.axes.size()) {
360            result = (ValueAxis) this.axes.get(index);
361        }
362        return result;
363    }
364
365    /**
366     * Sets the primary axis for the plot and sends a {@link PlotChangeEvent}
367     * to all registered listeners.
368     *
369     * @param axis  the new primary axis (<code>null</code> permitted).
370     */
371    public void setAxis(ValueAxis axis) {
372        setAxis(0, axis);
373    }
374
375    /**
376     * Sets an axis for the plot and sends a {@link PlotChangeEvent} to all
377     * registered listeners.
378     *
379     * @param index  the axis index.
380     * @param axis  the axis (<code>null</code> permitted).
381     *
382     * @see #getAxis(int)
383     *
384     * @since 1.0.14
385     */
386    public void setAxis(int index, ValueAxis axis) {
387        setAxis(index, axis, true);
388    }
389
390    /**
391     * Sets an axis for the plot and, if requested, sends a
392     * {@link PlotChangeEvent} to all registered listeners.
393     *
394     * @param index  the axis index.
395     * @param axis  the axis (<code>null</code> permitted).
396     * @param notify  notify listeners?
397     *
398     * @see #getAxis(int)
399     *
400     * @since 1.0.14
401     */
402    public void setAxis(int index, ValueAxis axis, boolean notify) {
403        ValueAxis existing = getAxis(index);
404        if (existing != null) {
405            existing.removeChangeListener(this);
406        }
407        if (axis != null) {
408            axis.setPlot(this);
409        }
410        this.axes.set(index, axis);
411        if (axis != null) {
412            axis.configure();
413            axis.addChangeListener(this);
414        }
415        if (notify) {
416            fireChangeEvent();
417        }
418    }
419
420    /**
421     * Returns the location of the primary axis.
422     *
423     * @return The location (never <code>null</code>).
424     *
425     * @see #setAxisLocation(PolarAxisLocation)
426     *
427     * @since 1.0.14
428     */
429    public PolarAxisLocation getAxisLocation() {
430        return getAxisLocation(0);
431    }
432
433    /**
434     * Returns the location for an axis.
435     *
436     * @param index  the axis index.
437     *
438     * @return The location (never <code>null</code>).
439     *
440     * @see #setAxisLocation(int, PolarAxisLocation)
441     *
442     * @since 1.0.14
443     */
444    public PolarAxisLocation getAxisLocation(int index) {
445        PolarAxisLocation result = null;
446        if (index < this.axisLocations.size()) {
447            result = (PolarAxisLocation) this.axisLocations.get(index);
448        }
449        return result;
450    }
451
452    /**
453     * Sets the location of the primary axis and sends a
454     * {@link PlotChangeEvent} to all registered listeners.
455     *
456     * @param location  the location (<code>null</code> not permitted).
457     *
458     * @see #getAxisLocation()
459     *
460     * @since 1.0.14
461     */
462    public void setAxisLocation(PolarAxisLocation location) {
463        // delegate...
464        setAxisLocation(0, location, true);
465    }
466
467    /**
468     * Sets the location of the primary axis and, if requested, sends a
469     * {@link PlotChangeEvent} to all registered listeners.
470     *
471     * @param location  the location (<code>null</code> not permitted).
472     * @param notify  notify listeners?
473     *
474     * @see #getAxisLocation()
475     *
476     * @since 1.0.14
477     */
478    public void setAxisLocation(PolarAxisLocation location, boolean notify) {
479        // delegate...
480        setAxisLocation(0, location, notify);
481    }
482
483    /**
484     * Sets the location for an axis and sends a {@link PlotChangeEvent}
485     * to all registered listeners.
486     *
487     * @param index  the axis index.
488     * @param location  the location (<code>null</code> not permitted).
489     *
490     * @see #getAxisLocation(int)
491     *
492     * @since 1.0.14
493     */
494    public void setAxisLocation(int index, PolarAxisLocation location) {
495        // delegate...
496        setAxisLocation(index, location, true);
497    }
498
499    /**
500     * Sets the axis location for an axis and, if requested, sends a
501     * {@link PlotChangeEvent} to all registered listeners.
502     *
503     * @param index  the axis index.
504     * @param location  the location (<code>null</code> not permitted).
505     * @param notify  notify listeners?
506     *
507     * @since 1.0.14
508     */
509    public void setAxisLocation(int index, PolarAxisLocation location,
510            boolean notify) {
511        ParamChecks.nullNotPermitted(location, "location");
512        this.axisLocations.set(index, location);
513        if (notify) {
514            fireChangeEvent();
515        }
516    }
517
518    /**
519     * Returns the number of domain axes.
520     *
521     * @return The axis count.
522     *
523     * @since 1.0.14
524     **/
525    public int getAxisCount() {
526        return this.axes.size();
527    }
528
529    /**
530     * Returns the primary dataset for the plot.
531     *
532     * @return The primary dataset (possibly <code>null</code>).
533     *
534     * @see #setDataset(XYDataset)
535     */
536    public XYDataset getDataset() {
537        return getDataset(0);
538    }
539
540    /**
541     * Returns the dataset with the specified index, if any.
542     *
543     * @param index  the dataset index.
544     *
545     * @return The dataset (possibly <code>null</code>).
546     *
547     * @see #setDataset(int, XYDataset)
548     *
549     * @since 1.0.14
550     */
551    public XYDataset getDataset(int index) {
552        XYDataset result = null;
553        if (index < this.datasets.size()) {
554            result = (XYDataset) this.datasets.get(index);
555        }
556        return result;
557    }
558
559    /**
560     * Sets the primary dataset for the plot, replacing the existing dataset
561     * if there is one, and sends a {@code link PlotChangeEvent} to all
562     * registered listeners.
563     *
564     * @param dataset  the dataset (<code>null</code> permitted).
565     *
566     * @see #getDataset()
567     */
568    public void setDataset(XYDataset dataset) {
569        setDataset(0, dataset);
570    }
571
572    /**
573     * Sets a dataset for the plot, replacing the existing dataset at the same
574     * index if there is one, and sends a {@code link PlotChangeEvent} to all
575     * registered listeners.
576     *
577     * @param index  the dataset index.
578     * @param dataset  the dataset (<code>null</code> permitted).
579     *
580     * @see #getDataset(int)
581     *
582     * @since 1.0.14
583     */
584    public void setDataset(int index, XYDataset dataset) {
585        XYDataset existing = getDataset(index);
586        if (existing != null) {
587            existing.removeChangeListener(this);
588        }
589        this.datasets.set(index, dataset);
590        if (dataset != null) {
591            dataset.addChangeListener(this);
592        }
593
594        // send a dataset change event to self...
595        DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
596        datasetChanged(event);
597    }
598
599    /**
600     * Returns the number of datasets.
601     *
602     * @return The number of datasets.
603     *
604     * @since 1.0.14
605     */
606    public int getDatasetCount() {
607        return this.datasets.size();
608    }
609
610    /**
611     * Returns the index of the specified dataset, or <code>-1</code> if the
612     * dataset does not belong to the plot.
613     *
614     * @param dataset  the dataset (<code>null</code> not permitted).
615     *
616     * @return The index.
617     *
618     * @since 1.0.14
619     */
620    public int indexOf(XYDataset dataset) {
621        int result = -1;
622        for (int i = 0; i < this.datasets.size(); i++) {
623            if (dataset == this.datasets.get(i)) {
624                result = i;
625                break;
626            }
627        }
628        return result;
629    }
630
631    /**
632     * Returns the primary renderer.
633     *
634     * @return The renderer (possibly <code>null</code>).
635     *
636     * @see #setRenderer(PolarItemRenderer)
637     */
638    public PolarItemRenderer getRenderer() {
639        return getRenderer(0);
640    }
641
642    /**
643     * Returns the renderer at the specified index, if there is one.
644     *
645     * @param index  the renderer index.
646     *
647     * @return The renderer (possibly <code>null</code>).
648     *
649     * @see #setRenderer(int, PolarItemRenderer)
650     *
651     * @since 1.0.14
652     */
653    public PolarItemRenderer getRenderer(int index) {
654        PolarItemRenderer result = null;
655        if (index < this.renderers.size()) {
656            result = (PolarItemRenderer) this.renderers.get(index);
657        }
658        return result;
659    }
660
661    /**
662     * Sets the primary renderer, and notifies all listeners of a change to the
663     * plot.  If the renderer is set to <code>null</code>, no data items will
664     * be drawn for the corresponding dataset.
665     *
666     * @param renderer  the new renderer (<code>null</code> permitted).
667     *
668     * @see #getRenderer()
669     */
670    public void setRenderer(PolarItemRenderer renderer) {
671        setRenderer(0, renderer);
672    }
673
674    /**
675     * Sets a renderer and sends a {@link PlotChangeEvent} to all
676     * registered listeners.
677     *
678     * @param index  the index.
679     * @param renderer  the renderer.
680     *
681     * @see #getRenderer(int)
682     *
683     * @since 1.0.14
684     */
685    public void setRenderer(int index, PolarItemRenderer renderer) {
686        setRenderer(index, renderer, true);
687    }
688
689    /**
690     * Sets a renderer and, if requested, sends a {@link PlotChangeEvent} to
691     * all registered listeners.
692     *
693     * @param index  the index.
694     * @param renderer  the renderer.
695     * @param notify  notify listeners?
696     *
697     * @see #getRenderer(int)
698     *
699     * @since 1.0.14
700     */
701    public void setRenderer(int index, PolarItemRenderer renderer,
702                            boolean notify) {
703        PolarItemRenderer existing = getRenderer(index);
704        if (existing != null) {
705            existing.removeChangeListener(this);
706        }
707        this.renderers.set(index, renderer);
708        if (renderer != null) {
709            renderer.setPlot(this);
710            renderer.addChangeListener(this);
711        }
712        if (notify) {
713            fireChangeEvent();
714        }
715    }
716
717    /**
718     * Returns the tick unit that controls the spacing of the angular grid
719     * lines.
720     *
721     * @return The tick unit (never <code>null</code>).
722     *
723     * @since 1.0.10
724     */
725    public TickUnit getAngleTickUnit() {
726        return this.angleTickUnit;
727    }
728
729    /**
730     * Sets the tick unit that controls the spacing of the angular grid
731     * lines, and sends a {@link PlotChangeEvent} to all registered listeners.
732     *
733     * @param unit  the tick unit (<code>null</code> not permitted).
734     *
735     * @since 1.0.10
736     */
737    public void setAngleTickUnit(TickUnit unit) {
738        ParamChecks.nullNotPermitted(unit, "unit");
739        this.angleTickUnit = unit;
740        fireChangeEvent();
741    }
742
743    /**
744     * Returns the offset that is used for all angles.
745     *
746     * @return The offset for the angles.
747     * @since 1.0.14
748     */
749    public double getAngleOffset() {
750        return this.angleOffset;
751    }
752
753    /**
754     * Sets the offset that is used for all angles and sends a
755     * {@link PlotChangeEvent} to all registered listeners.
756     *
757     * This is useful to let 0 degrees be at the north, east, south or west
758     * side of the chart.
759     *
760     * @param offset The offset
761     * @since 1.0.14
762     */
763    public void setAngleOffset(double offset) {
764        this.angleOffset = offset;
765        fireChangeEvent();
766    }
767
768    /**
769     * Get the direction for growing angle degrees.
770     *
771     * @return <code>true</code> if angle increases counterclockwise,
772     *         <code>false</code> otherwise.
773     * @since 1.0.14
774     */
775    public boolean isCounterClockwise() {
776        return this.counterClockwise;
777    }
778
779    /**
780     * Sets the flag for increasing angle degrees direction.
781     *
782     * <code>true</code> for counterclockwise, <code>false</code> for
783     * clockwise.
784     *
785     * @param counterClockwise The flag.
786     * @since 1.0.14
787     */
788    public void setCounterClockwise(boolean counterClockwise)
789    {
790        this.counterClockwise = counterClockwise;
791    }
792
793    /**
794     * Returns a flag that controls whether or not the angle labels are visible.
795     *
796     * @return A boolean.
797     *
798     * @see #setAngleLabelsVisible(boolean)
799     */
800    public boolean isAngleLabelsVisible() {
801        return this.angleLabelsVisible;
802    }
803
804    /**
805     * Sets the flag that controls whether or not the angle labels are visible,
806     * and sends a {@link PlotChangeEvent} to all registered listeners.
807     *
808     * @param visible  the flag.
809     *
810     * @see #isAngleLabelsVisible()
811     */
812    public void setAngleLabelsVisible(boolean visible) {
813        if (this.angleLabelsVisible != visible) {
814            this.angleLabelsVisible = visible;
815            fireChangeEvent();
816        }
817    }
818
819    /**
820     * Returns the font used to display the angle labels.
821     *
822     * @return A font (never <code>null</code>).
823     *
824     * @see #setAngleLabelFont(Font)
825     */
826    public Font getAngleLabelFont() {
827        return this.angleLabelFont;
828    }
829
830    /**
831     * Sets the font used to display the angle labels and sends a
832     * {@link PlotChangeEvent} to all registered listeners.
833     *
834     * @param font  the font (<code>null</code> not permitted).
835     *
836     * @see #getAngleLabelFont()
837     */
838    public void setAngleLabelFont(Font font) {
839        ParamChecks.nullNotPermitted(font, "font");
840        this.angleLabelFont = font;
841        fireChangeEvent();
842    }
843
844    /**
845     * Returns the paint used to display the angle labels.
846     *
847     * @return A paint (never <code>null</code>).
848     *
849     * @see #setAngleLabelPaint(Paint)
850     */
851    public Paint getAngleLabelPaint() {
852        return this.angleLabelPaint;
853    }
854
855    /**
856     * Sets the paint used to display the angle labels and sends a
857     * {@link PlotChangeEvent} to all registered listeners.
858     *
859     * @param paint  the paint (<code>null</code> not permitted).
860     */
861    public void setAngleLabelPaint(Paint paint) {
862        ParamChecks.nullNotPermitted(paint, "paint");
863        this.angleLabelPaint = paint;
864        fireChangeEvent();
865    }
866
867    /**
868     * Returns <code>true</code> if the angular gridlines are visible, and
869     * <code>false</code> otherwise.
870     *
871     * @return <code>true</code> or <code>false</code>.
872     *
873     * @see #setAngleGridlinesVisible(boolean)
874     */
875    public boolean isAngleGridlinesVisible() {
876        return this.angleGridlinesVisible;
877    }
878
879    /**
880     * Sets the flag that controls whether or not the angular grid-lines are
881     * visible.
882     * <p>
883     * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
884     * registered listeners.
885     *
886     * @param visible  the new value of the flag.
887     *
888     * @see #isAngleGridlinesVisible()
889     */
890    public void setAngleGridlinesVisible(boolean visible) {
891        if (this.angleGridlinesVisible != visible) {
892            this.angleGridlinesVisible = visible;
893            fireChangeEvent();
894        }
895    }
896
897    /**
898     * Returns the stroke for the grid-lines (if any) plotted against the
899     * angular axis.
900     *
901     * @return The stroke (possibly <code>null</code>).
902     *
903     * @see #setAngleGridlineStroke(Stroke)
904     */
905    public Stroke getAngleGridlineStroke() {
906        return this.angleGridlineStroke;
907    }
908
909    /**
910     * Sets the stroke for the grid lines plotted against the angular axis and
911     * sends a {@link PlotChangeEvent} to all registered listeners.
912     * <p>
913     * If you set this to <code>null</code>, no grid lines will be drawn.
914     *
915     * @param stroke  the stroke (<code>null</code> permitted).
916     *
917     * @see #getAngleGridlineStroke()
918     */
919    public void setAngleGridlineStroke(Stroke stroke) {
920        this.angleGridlineStroke = stroke;
921        fireChangeEvent();
922    }
923
924    /**
925     * Returns the paint for the grid lines (if any) plotted against the
926     * angular axis.
927     *
928     * @return The paint (possibly <code>null</code>).
929     *
930     * @see #setAngleGridlinePaint(Paint)
931     */
932    public Paint getAngleGridlinePaint() {
933        return this.angleGridlinePaint;
934    }
935
936    /**
937     * Sets the paint for the grid lines plotted against the angular axis.
938     * <p>
939     * If you set this to <code>null</code>, no grid lines will be drawn.
940     *
941     * @param paint  the paint (<code>null</code> permitted).
942     *
943     * @see #getAngleGridlinePaint()
944     */
945    public void setAngleGridlinePaint(Paint paint) {
946        this.angleGridlinePaint = paint;
947        fireChangeEvent();
948    }
949
950    /**
951     * Returns <code>true</code> if the radius axis grid is visible, and
952     * <code>false</code> otherwise.
953     *
954     * @return <code>true</code> or <code>false</code>.
955     *
956     * @see #setRadiusGridlinesVisible(boolean)
957     */
958    public boolean isRadiusGridlinesVisible() {
959        return this.radiusGridlinesVisible;
960    }
961
962    /**
963     * Sets the flag that controls whether or not the radius axis grid lines
964     * are visible.
965     * <p>
966     * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
967     * registered listeners.
968     *
969     * @param visible  the new value of the flag.
970     *
971     * @see #isRadiusGridlinesVisible()
972     */
973    public void setRadiusGridlinesVisible(boolean visible) {
974        if (this.radiusGridlinesVisible != visible) {
975            this.radiusGridlinesVisible = visible;
976            fireChangeEvent();
977        }
978    }
979
980    /**
981     * Returns the stroke for the grid lines (if any) plotted against the
982     * radius axis.
983     *
984     * @return The stroke (possibly <code>null</code>).
985     *
986     * @see #setRadiusGridlineStroke(Stroke)
987     */
988    public Stroke getRadiusGridlineStroke() {
989        return this.radiusGridlineStroke;
990    }
991
992    /**
993     * Sets the stroke for the grid lines plotted against the radius axis and
994     * sends a {@link PlotChangeEvent} to all registered listeners.
995     * <p>
996     * If you set this to <code>null</code>, no grid lines will be drawn.
997     *
998     * @param stroke  the stroke (<code>null</code> permitted).
999     *
1000     * @see #getRadiusGridlineStroke()
1001     */
1002    public void setRadiusGridlineStroke(Stroke stroke) {
1003        this.radiusGridlineStroke = stroke;
1004        fireChangeEvent();
1005    }
1006
1007    /**
1008     * Returns the paint for the grid lines (if any) plotted against the radius
1009     * axis.
1010     *
1011     * @return The paint (possibly <code>null</code>).
1012     *
1013     * @see #setRadiusGridlinePaint(Paint)
1014     */
1015    public Paint getRadiusGridlinePaint() {
1016        return this.radiusGridlinePaint;
1017    }
1018
1019    /**
1020     * Sets the paint for the grid lines plotted against the radius axis and
1021     * sends a {@link PlotChangeEvent} to all registered listeners.
1022     * <p>
1023     * If you set this to <code>null</code>, no grid lines will be drawn.
1024     *
1025     * @param paint  the paint (<code>null</code> permitted).
1026     *
1027     * @see #getRadiusGridlinePaint()
1028     */
1029    public void setRadiusGridlinePaint(Paint paint) {
1030        this.radiusGridlinePaint = paint;
1031        fireChangeEvent();
1032    }
1033
1034    /**
1035     * Return the current value of the flag indicating if radial minor
1036     * grid-lines will be drawn or not.
1037     *
1038     * @return Returns <code>true</code> if radial minor grid-lines are drawn.
1039     * @since 1.0.15
1040     */
1041    public boolean isRadiusMinorGridlinesVisible() {
1042        return this.radiusMinorGridlinesVisible;
1043    }
1044
1045    /**
1046     * Set the flag that determines if radial minor grid-lines will be drawn,
1047     * and sends a {@link PlotChangeEvent} to all registered listeners.
1048     *
1049     * @param flag <code>true</code> to draw the radial minor grid-lines,
1050     *             <code>false</code> to hide them.
1051     * @since 1.0.15
1052     */
1053    public void setRadiusMinorGridlinesVisible(boolean flag) {
1054        this.radiusMinorGridlinesVisible = flag;
1055        fireChangeEvent();
1056    }
1057
1058    /**
1059     * Returns the margin around the plot area.
1060     *
1061     * @return The actual margin in pixels.
1062     *
1063     * @since 1.0.14
1064     */
1065    public int getMargin() {
1066        return this.margin;
1067    }
1068
1069    /**
1070     * Set the margin around the plot area and sends a
1071     * {@link PlotChangeEvent} to all registered listeners.
1072     *
1073     * @param margin The new margin in pixels.
1074     *
1075     * @since 1.0.14
1076     */
1077    public void setMargin(int margin) {
1078        this.margin = margin;
1079        fireChangeEvent();
1080    }
1081
1082    /**
1083     * Returns the fixed legend items, if any.
1084     *
1085     * @return The legend items (possibly <code>null</code>).
1086     *
1087     * @see #setFixedLegendItems(LegendItemCollection)
1088     *
1089     * @since 1.0.14
1090     */
1091    public LegendItemCollection getFixedLegendItems() {
1092        return this.fixedLegendItems;
1093    }
1094
1095    /**
1096     * Sets the fixed legend items for the plot.  Leave this set to
1097     * <code>null</code> if you prefer the legend items to be created
1098     * automatically.
1099     *
1100     * @param items  the legend items (<code>null</code> permitted).
1101     *
1102     * @see #getFixedLegendItems()
1103     *
1104     * @since 1.0.14
1105     */
1106    public void setFixedLegendItems(LegendItemCollection items) {
1107        this.fixedLegendItems = items;
1108        fireChangeEvent();
1109    }
1110
1111    /**
1112     * Add text to be displayed in the lower right hand corner and sends a
1113     * {@link PlotChangeEvent} to all registered listeners.
1114     *
1115     * @param text  the text to display (<code>null</code> not permitted).
1116     *
1117     * @see #removeCornerTextItem(String)
1118     */
1119    public void addCornerTextItem(String text) {
1120        ParamChecks.nullNotPermitted(text, "text");
1121        this.cornerTextItems.add(text);
1122        fireChangeEvent();
1123    }
1124
1125    /**
1126     * Remove the given text from the list of corner text items and
1127     * sends a {@link PlotChangeEvent} to all registered listeners.
1128     *
1129     * @param text  the text to remove (<code>null</code> ignored).
1130     *
1131     * @see #addCornerTextItem(String)
1132     */
1133    public void removeCornerTextItem(String text) {
1134        boolean removed = this.cornerTextItems.remove(text);
1135        if (removed) {
1136            fireChangeEvent();
1137        }
1138    }
1139
1140    /**
1141     * Clear the list of corner text items and sends a {@link PlotChangeEvent}
1142     * to all registered listeners.
1143     *
1144     * @see #addCornerTextItem(String)
1145     * @see #removeCornerTextItem(String)
1146     */
1147    public void clearCornerTextItems() {
1148        if (this.cornerTextItems.size() > 0) {
1149            this.cornerTextItems.clear();
1150            fireChangeEvent();
1151        }
1152    }
1153
1154    /**
1155     * Generates a list of tick values for the angular tick marks.
1156     *
1157     * @return A list of {@link NumberTick} instances.
1158     *
1159     * @since 1.0.10
1160     */
1161    protected List refreshAngleTicks() {
1162        List ticks = new ArrayList();
1163        for (double currentTickVal = 0.0; currentTickVal < 360.0;
1164                currentTickVal += this.angleTickUnit.getSize()) {
1165
1166            TextAnchor ta = calculateTextAnchor(currentTickVal);
1167            NumberTick tick = new NumberTick(new Double(currentTickVal),
1168                this.angleTickUnit.valueToString(currentTickVal),
1169                ta, TextAnchor.CENTER, 0.0);
1170            ticks.add(tick);
1171        }
1172        return ticks;
1173    }
1174
1175    /**
1176     * Calculate the text position for the given degrees.
1177     *
1178     * @param angleDegrees  the angle in degrees.
1179     * 
1180     * @return The optimal text anchor.
1181     * @since 1.0.14
1182     */
1183    protected TextAnchor calculateTextAnchor(double angleDegrees) {
1184        TextAnchor ta = TextAnchor.CENTER;
1185
1186        // normalize angle
1187        double offset = this.angleOffset;
1188        while (offset < 0.0) {
1189            offset += 360.0;
1190        }
1191        double normalizedAngle = (((this.counterClockwise ? -1 : 1)
1192                * angleDegrees) + offset) % 360;
1193        while (this.counterClockwise && (normalizedAngle < 0.0)) {
1194            normalizedAngle += 360.0;
1195        }
1196
1197        if (normalizedAngle == 0.0) {
1198            ta = TextAnchor.CENTER_LEFT;
1199        }
1200        else if (normalizedAngle > 0.0 && normalizedAngle < 90.0) {
1201            ta = TextAnchor.TOP_LEFT;
1202        }
1203        else if (normalizedAngle == 90.0) {
1204            ta = TextAnchor.TOP_CENTER;
1205        }
1206        else if (normalizedAngle > 90.0 && normalizedAngle < 180.0) {
1207            ta = TextAnchor.TOP_RIGHT;
1208        }
1209        else if (normalizedAngle == 180) {
1210            ta = TextAnchor.CENTER_RIGHT;
1211        }
1212        else if (normalizedAngle > 180.0 && normalizedAngle < 270.0) {
1213            ta = TextAnchor.BOTTOM_RIGHT;
1214        }
1215        else if (normalizedAngle == 270) {
1216            ta = TextAnchor.BOTTOM_CENTER;
1217        }
1218        else if (normalizedAngle > 270.0 && normalizedAngle < 360.0) {
1219            ta = TextAnchor.BOTTOM_LEFT;
1220        }
1221        return ta;
1222    }
1223
1224    /**
1225     * Maps a dataset to a particular axis.  All data will be plotted
1226     * against axis zero by default, no mapping is required for this case.
1227     *
1228     * @param index  the dataset index (zero-based).
1229     * @param axisIndex  the axis index.
1230     *
1231     * @since 1.0.14
1232     */
1233    public void mapDatasetToAxis(int index, int axisIndex) {
1234        List axisIndices = new java.util.ArrayList(1);
1235        axisIndices.add(new Integer(axisIndex));
1236        mapDatasetToAxes(index, axisIndices);
1237    }
1238
1239    /**
1240     * Maps the specified dataset to the axes in the list.  Note that the
1241     * conversion of data values into Java2D space is always performed using
1242     * the first axis in the list.
1243     *
1244     * @param index  the dataset index (zero-based).
1245     * @param axisIndices  the axis indices (<code>null</code> permitted).
1246     *
1247     * @since 1.0.14
1248     */
1249    public void mapDatasetToAxes(int index, List axisIndices) {
1250        if (index < 0) {
1251            throw new IllegalArgumentException("Requires 'index' >= 0.");
1252        }
1253        checkAxisIndices(axisIndices);
1254        Integer key = new Integer(index);
1255        this.datasetToAxesMap.put(key, new ArrayList(axisIndices));
1256        // fake a dataset change event to update axes...
1257        datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
1258    }
1259
1260    /**
1261     * This method is used to perform argument checking on the list of
1262     * axis indices passed to mapDatasetToAxes().
1263     *
1264     * @param indices  the list of indices (<code>null</code> permitted).
1265     */
1266    private void checkAxisIndices(List indices) {
1267        // axisIndices can be:
1268        // 1.  null;
1269        // 2.  non-empty, containing only Integer objects that are unique.
1270        if (indices == null) {
1271            return;  // OK
1272        }
1273        int count = indices.size();
1274        if (count == 0) {
1275            throw new IllegalArgumentException("Empty list not permitted.");
1276        }
1277        HashSet set = new HashSet();
1278        for (int i = 0; i < count; i++) {
1279            Object item = indices.get(i);
1280            if (!(item instanceof Integer)) {
1281                throw new IllegalArgumentException(
1282                        "Indices must be Integer instances.");
1283            }
1284            if (set.contains(item)) {
1285                throw new IllegalArgumentException("Indices must be unique.");
1286            }
1287            set.add(item);
1288        }
1289    }
1290
1291    /**
1292     * Returns the axis for a dataset.
1293     *
1294     * @param index  the dataset index.
1295     *
1296     * @return The axis.
1297     *
1298     * @since 1.0.14
1299     */
1300    public ValueAxis getAxisForDataset(int index) {
1301        ValueAxis valueAxis;
1302        List axisIndices = (List) this.datasetToAxesMap.get(
1303                new Integer(index));
1304        if (axisIndices != null) {
1305            // the first axis in the list is used for data <--> Java2D
1306            Integer axisIndex = (Integer) axisIndices.get(0);
1307            valueAxis = getAxis(axisIndex.intValue());
1308        }
1309        else {
1310            valueAxis = getAxis(0);
1311        }
1312        return valueAxis;
1313    }
1314
1315    /**
1316     * Returns the index of the given axis.
1317     *
1318     * @param axis  the axis.
1319     *
1320     * @return The axis index or -1 if axis is not used in this plot.
1321     *
1322     * @since 1.0.14
1323     */
1324    public int getAxisIndex(ValueAxis axis) {
1325        int result = this.axes.indexOf(axis);
1326        if (result < 0) {
1327            // try the parent plot
1328            Plot parent = getParent();
1329            if (parent instanceof PolarPlot) {
1330                PolarPlot p = (PolarPlot) parent;
1331                result = p.getAxisIndex(axis);
1332            }
1333        }
1334        return result;
1335    }
1336
1337    /**
1338     * Returns the index of the specified renderer, or <code>-1</code> if the
1339     * renderer is not assigned to this plot.
1340     *
1341     * @param renderer  the renderer (<code>null</code> permitted).
1342     *
1343     * @return The renderer index.
1344     *
1345     * @since 1.0.14
1346     */
1347    public int getIndexOf(PolarItemRenderer renderer) {
1348        return this.renderers.indexOf(renderer);
1349    }
1350
1351    /**
1352     * Draws the plot on a Java 2D graphics device (such as the screen or a
1353     * printer).
1354     * <P>
1355     * This plot relies on a {@link PolarItemRenderer} to draw each
1356     * item in the plot.  This allows the visual representation of the data to
1357     * be changed easily.
1358     * <P>
1359     * The optional info argument collects information about the rendering of
1360     * the plot (dimensions, tooltip information etc).  Just pass in
1361     * <code>null</code> if you do not need this information.
1362     *
1363     * @param g2  the graphics device.
1364     * @param area  the area within which the plot (including axes and
1365     *              labels) should be drawn.
1366     * @param anchor  the anchor point (<code>null</code> permitted).
1367     * @param parentState  ignored.
1368     * @param info  collects chart drawing information (<code>null</code>
1369     *              permitted).
1370     */
1371    @Override
1372    public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
1373            PlotState parentState, PlotRenderingInfo info) {
1374
1375        // if the plot area is too small, just return...
1376        boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
1377        boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
1378        if (b1 || b2) {
1379            return;
1380        }
1381
1382        // record the plot area...
1383        if (info != null) {
1384            info.setPlotArea(area);
1385        }
1386
1387        // adjust the drawing area for the plot insets (if any)...
1388        RectangleInsets insets = getInsets();
1389        insets.trim(area);
1390
1391        Rectangle2D dataArea = area;
1392        if (info != null) {
1393            info.setDataArea(dataArea);
1394        }
1395
1396        // draw the plot background and axes...
1397        drawBackground(g2, dataArea);
1398        int axisCount = this.axes.size();
1399        AxisState state = null;
1400        for (int i = 0; i < axisCount; i++) {
1401            ValueAxis axis = getAxis(i);
1402            if (axis != null) {
1403                PolarAxisLocation location
1404                        = (PolarAxisLocation) this.axisLocations.get(i);
1405                AxisState s = this.drawAxis(axis, location, g2, dataArea);
1406                if (i == 0) {
1407                    state = s;
1408                }
1409            }
1410        }
1411
1412        // now for each dataset, get the renderer and the appropriate axis
1413        // and render the dataset...
1414        Shape originalClip = g2.getClip();
1415        Composite originalComposite = g2.getComposite();
1416
1417        g2.clip(dataArea);
1418        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1419                getForegroundAlpha()));
1420        this.angleTicks = refreshAngleTicks();
1421        drawGridlines(g2, dataArea, this.angleTicks, state.getTicks());
1422        render(g2, dataArea, info);
1423        g2.setClip(originalClip);
1424        g2.setComposite(originalComposite);
1425        drawOutline(g2, dataArea);
1426        drawCornerTextItems(g2, dataArea);
1427    }
1428
1429    /**
1430     * Draws the corner text items.
1431     *
1432     * @param g2  the drawing surface.
1433     * @param area  the area.
1434     */
1435    protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) {
1436        if (this.cornerTextItems.isEmpty()) {
1437            return;
1438        }
1439
1440        g2.setColor(Color.black);
1441        double width = 0.0;
1442        double height = 0.0;
1443        for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
1444            String msg = (String) it.next();
1445            FontMetrics fm = g2.getFontMetrics();
1446            Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm);
1447            width = Math.max(width, bounds.getWidth());
1448            height += bounds.getHeight();
1449        }
1450
1451        double xadj = ANNOTATION_MARGIN * 2.0;
1452        double yadj = ANNOTATION_MARGIN;
1453        width += xadj;
1454        height += yadj;
1455
1456        double x = area.getMaxX() - width;
1457        double y = area.getMaxY() - height;
1458        g2.drawRect((int) x, (int) y, (int) width, (int) height);
1459        x += ANNOTATION_MARGIN;
1460        for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
1461            String msg = (String) it.next();
1462            Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2,
1463                    g2.getFontMetrics());
1464            y += bounds.getHeight();
1465            g2.drawString(msg, (int) x, (int) y);
1466        }
1467    }
1468
1469    /**
1470     * Draws the axis with the specified index.
1471     *
1472     * @param axis  the axis.
1473     * @param location  the axis location.
1474     * @param g2  the graphics target.
1475     * @param plotArea  the plot area.
1476     *
1477     * @return The axis state.
1478     *
1479     * @since 1.0.14
1480     */
1481    protected AxisState drawAxis(ValueAxis axis, PolarAxisLocation location,
1482            Graphics2D g2, Rectangle2D plotArea) {
1483
1484        double centerX = plotArea.getCenterX();
1485        double centerY = plotArea.getCenterY();
1486        double r = Math.min(plotArea.getWidth() / 2.0,
1487                plotArea.getHeight() / 2.0) - this.margin;
1488        double x = centerX - r;
1489        double y = centerY - r;
1490
1491        Rectangle2D dataArea = null;
1492        AxisState result = null;
1493        if (location == PolarAxisLocation.NORTH_RIGHT) {
1494            dataArea = new Rectangle2D.Double(x, y, r, r);
1495            result = axis.draw(g2, centerX, plotArea, dataArea,
1496                    RectangleEdge.RIGHT, null);
1497        }
1498        else if (location == PolarAxisLocation.NORTH_LEFT) {
1499            dataArea = new Rectangle2D.Double(centerX, y, r, r);
1500            result = axis.draw(g2, centerX, plotArea, dataArea,
1501                    RectangleEdge.LEFT, null);
1502        }
1503        else if (location == PolarAxisLocation.SOUTH_LEFT) {
1504            dataArea = new Rectangle2D.Double(centerX, centerY, r, r);
1505            result = axis.draw(g2, centerX, plotArea, dataArea,
1506                    RectangleEdge.LEFT, null);
1507        }
1508        else if (location == PolarAxisLocation.SOUTH_RIGHT) {
1509            dataArea = new Rectangle2D.Double(x, centerY, r, r);
1510            result = axis.draw(g2, centerX, plotArea, dataArea,
1511                    RectangleEdge.RIGHT, null);
1512        }
1513        else if (location == PolarAxisLocation.EAST_ABOVE) {
1514            dataArea = new Rectangle2D.Double(centerX, centerY, r, r);
1515            result = axis.draw(g2, centerY, plotArea, dataArea,
1516                    RectangleEdge.TOP, null);
1517        }
1518        else if (location == PolarAxisLocation.EAST_BELOW) {
1519            dataArea = new Rectangle2D.Double(centerX, y, r, r);
1520            result = axis.draw(g2, centerY, plotArea, dataArea,
1521                    RectangleEdge.BOTTOM, null);
1522        }
1523        else if (location == PolarAxisLocation.WEST_ABOVE) {
1524            dataArea = new Rectangle2D.Double(x, centerY, r, r);
1525            result = axis.draw(g2, centerY, plotArea, dataArea,
1526                    RectangleEdge.TOP, null);
1527        }
1528        else if (location == PolarAxisLocation.WEST_BELOW) {
1529            dataArea = new Rectangle2D.Double(x, y, r, r);
1530            result = axis.draw(g2, centerY, plotArea, dataArea,
1531                    RectangleEdge.BOTTOM, null);
1532        }
1533
1534        return result;
1535    }
1536
1537    /**
1538     * Draws a representation of the data within the dataArea region, using the
1539     * current m_Renderer.
1540     *
1541     * @param g2  the graphics device.
1542     * @param dataArea  the region in which the data is to be drawn.
1543     * @param info  an optional object for collection dimension
1544     *              information (<code>null</code> permitted).
1545     */
1546    protected void render(Graphics2D g2, Rectangle2D dataArea,
1547            PlotRenderingInfo info) {
1548
1549        // now get the data and plot it (the visual representation will depend
1550        // on the m_Renderer that has been set)...
1551        boolean hasData = false;
1552        int datasetCount = this.datasets.size();
1553        for (int i = datasetCount - 1; i >= 0; i--) {
1554            XYDataset dataset = getDataset(i);
1555            if (dataset == null) {
1556                continue;
1557            }
1558            PolarItemRenderer renderer = getRenderer(i);
1559            if (renderer == null) {
1560                continue;
1561            }
1562            if (!DatasetUtilities.isEmptyOrNull(dataset)) {
1563                hasData = true;
1564                int seriesCount = dataset.getSeriesCount();
1565                for (int series = 0; series < seriesCount; series++) {
1566                    renderer.drawSeries(g2, dataArea, info, this, dataset,
1567                            series);
1568                }
1569            }
1570        }
1571        if (!hasData) {
1572            drawNoDataMessage(g2, dataArea);
1573        }
1574    }
1575
1576    /**
1577     * Draws the gridlines for the plot, if they are visible.
1578     *
1579     * @param g2  the graphics device.
1580     * @param dataArea  the data area.
1581     * @param angularTicks  the ticks for the angular axis.
1582     * @param radialTicks  the ticks for the radial axis.
1583     */
1584    protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea,
1585                                 List angularTicks, List radialTicks) {
1586
1587        PolarItemRenderer renderer = getRenderer();
1588        // no renderer, no gridlines...
1589        if (renderer == null) {
1590            return;
1591        }
1592
1593        // draw the domain grid lines, if any...
1594        if (isAngleGridlinesVisible()) {
1595            Stroke gridStroke = getAngleGridlineStroke();
1596            Paint gridPaint = getAngleGridlinePaint();
1597            if ((gridStroke != null) && (gridPaint != null)) {
1598                renderer.drawAngularGridLines(g2, this, angularTicks,
1599                        dataArea);
1600            }
1601        }
1602
1603        // draw the radius grid lines, if any...
1604        if (isRadiusGridlinesVisible()) {
1605            Stroke gridStroke = getRadiusGridlineStroke();
1606            Paint gridPaint = getRadiusGridlinePaint();
1607            if ((gridStroke != null) && (gridPaint != null)) {
1608                List ticks = buildRadialTicks(radialTicks);
1609                renderer.drawRadialGridLines(g2, this, getAxis(),
1610                        ticks, dataArea);
1611            }
1612        }
1613    }
1614
1615    /**
1616     * Create a list of ticks based on the given list and plot properties.
1617     * Only ticks of a specific type may be in the result list.
1618     *
1619     * @param allTicks A list of all available ticks for the primary axis.
1620     *        <code>null</code> not permitted.
1621     * @return Ticks to use for radial gridlines.
1622     * @since 1.0.15
1623     */
1624    protected List buildRadialTicks(List allTicks)
1625    {
1626        List ticks = new ArrayList();
1627        Iterator it = allTicks.iterator();
1628        while (it.hasNext()) {
1629            ValueTick tick = (ValueTick) it.next();
1630            if (isRadiusMinorGridlinesVisible() ||
1631                    TickType.MAJOR.equals(tick.getTickType())) {
1632                ticks.add(tick);
1633            }
1634        }
1635        return ticks;
1636    }
1637
1638    /**
1639     * Zooms the axis ranges by the specified percentage about the anchor point.
1640     *
1641     * @param percent  the amount of the zoom.
1642     */
1643    @Override
1644    public void zoom(double percent) {
1645        for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) {
1646            final ValueAxis axis = getAxis(axisIdx);
1647            if (axis != null) {
1648                if (percent > 0.0) {
1649                    double radius = axis.getUpperBound();
1650                    double scaledRadius = radius * percent;
1651                    axis.setUpperBound(scaledRadius);
1652                    axis.setAutoRange(false);
1653                }
1654                else {
1655                    axis.setAutoRange(true);
1656                }
1657            }
1658        }
1659    }
1660
1661    /**
1662     * A utility method that returns a list of datasets that are mapped to a
1663     * particular axis.
1664     *
1665     * @param axisIndex  the axis index (<code>null</code> not permitted).
1666     *
1667     * @return A list of datasets.
1668     *
1669     * @since 1.0.14
1670     */
1671    private List getDatasetsMappedToAxis(Integer axisIndex) {
1672        ParamChecks.nullNotPermitted(axisIndex, "axisIndex");
1673        List result = new ArrayList();
1674        for (int i = 0; i < this.datasets.size(); i++) {
1675            List mappedAxes = (List) this.datasetToAxesMap.get(new Integer(i));
1676            if (mappedAxes == null) {
1677                if (axisIndex.equals(ZERO)) {
1678                    result.add(this.datasets.get(i));
1679                }
1680            }
1681            else {
1682                if (mappedAxes.contains(axisIndex)) {
1683                    result.add(this.datasets.get(i));
1684                }
1685            }
1686        }
1687        return result;
1688    }
1689
1690    /**
1691     * Returns the range for the specified axis.
1692     *
1693     * @param axis  the axis.
1694     *
1695     * @return The range.
1696     */
1697    @Override
1698    public Range getDataRange(ValueAxis axis) {
1699        Range result = null;
1700        int axisIdx = getAxisIndex(axis);
1701        List mappedDatasets = new ArrayList();
1702
1703        if (axisIdx >= 0) {
1704            mappedDatasets = getDatasetsMappedToAxis(new Integer(axisIdx));
1705        }
1706
1707        // iterate through the datasets that map to the axis and get the union
1708        // of the ranges.
1709        Iterator iterator = mappedDatasets.iterator();
1710        int datasetIdx = -1;
1711        while (iterator.hasNext()) {
1712            datasetIdx++;
1713            XYDataset d = (XYDataset) iterator.next();
1714            if (d != null) {
1715                // FIXME better ask the renderer instead of DatasetUtilities
1716                result = Range.combine(result,
1717                        DatasetUtilities.findRangeBounds(d));
1718            }
1719        }
1720
1721        return result;
1722    }
1723
1724    /**
1725     * Receives notification of a change to the plot's m_Dataset.
1726     * <P>
1727     * The axis ranges are updated if necessary.
1728     *
1729     * @param event  information about the event (not used here).
1730     */
1731    @Override
1732    public void datasetChanged(DatasetChangeEvent event) {
1733        for (int i = 0; i < this.axes.size(); i++) {
1734            final ValueAxis axis = (ValueAxis) this.axes.get(i);
1735            if (axis != null) {
1736                axis.configure();
1737            }
1738        }
1739        if (getParent() != null) {
1740            getParent().datasetChanged(event);
1741        }
1742        else {
1743            super.datasetChanged(event);
1744        }
1745    }
1746
1747    /**
1748     * Notifies all registered listeners of a property change.
1749     * <P>
1750     * One source of property change events is the plot's m_Renderer.
1751     *
1752     * @param event  information about the property change.
1753     */
1754    @Override
1755    public void rendererChanged(RendererChangeEvent event) {
1756        fireChangeEvent();
1757    }
1758
1759    /**
1760     * Returns the legend items for the plot.  Each legend item is generated by
1761     * the plot's m_Renderer, since the m_Renderer is responsible for the visual
1762     * representation of the data.
1763     *
1764     * @return The legend items.
1765     */
1766    @Override
1767    public LegendItemCollection getLegendItems() {
1768        if (this.fixedLegendItems != null) {
1769            return this.fixedLegendItems;
1770        }
1771        LegendItemCollection result = new LegendItemCollection();
1772        int count = this.datasets.size();
1773        for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) {
1774            XYDataset dataset = getDataset(datasetIndex);
1775            PolarItemRenderer renderer = getRenderer(datasetIndex);
1776            if (dataset != null && renderer != null) {
1777                int seriesCount = dataset.getSeriesCount();
1778                for (int i = 0; i < seriesCount; i++) {
1779                    LegendItem item = renderer.getLegendItem(i);
1780                    result.add(item);
1781                }
1782            }
1783        }
1784        return result;
1785    }
1786
1787    /**
1788     * Tests this plot for equality with another object.
1789     *
1790     * @param obj  the object (<code>null</code> permitted).
1791     *
1792     * @return <code>true</code> or <code>false</code>.
1793     */
1794    @Override
1795    public boolean equals(Object obj) {
1796        if (obj == this) {
1797            return true;
1798        }
1799        if (!(obj instanceof PolarPlot)) {
1800            return false;
1801        }
1802        PolarPlot that = (PolarPlot) obj;
1803        if (!this.axes.equals(that.axes)) {
1804            return false;
1805        }
1806        if (!this.axisLocations.equals(that.axisLocations)) {
1807            return false;
1808        }
1809        if (!this.renderers.equals(that.renderers)) {
1810            return false;
1811        }
1812        if (!this.angleTickUnit.equals(that.angleTickUnit)) {
1813            return false;
1814        }
1815        if (this.angleGridlinesVisible != that.angleGridlinesVisible) {
1816            return false;
1817        }
1818        if (this.angleOffset != that.angleOffset)
1819        {
1820            return false;
1821        }
1822        if (this.counterClockwise != that.counterClockwise)
1823        {
1824            return false;
1825        }
1826        if (this.angleLabelsVisible != that.angleLabelsVisible) {
1827            return false;
1828        }
1829        if (!this.angleLabelFont.equals(that.angleLabelFont)) {
1830            return false;
1831        }
1832        if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) {
1833            return false;
1834        }
1835        if (!ObjectUtilities.equal(this.angleGridlineStroke,
1836                that.angleGridlineStroke)) {
1837            return false;
1838        }
1839        if (!PaintUtilities.equal(
1840            this.angleGridlinePaint, that.angleGridlinePaint
1841        )) {
1842            return false;
1843        }
1844        if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) {
1845            return false;
1846        }
1847        if (!ObjectUtilities.equal(this.radiusGridlineStroke,
1848                that.radiusGridlineStroke)) {
1849            return false;
1850        }
1851        if (!PaintUtilities.equal(this.radiusGridlinePaint,
1852                that.radiusGridlinePaint)) {
1853            return false;
1854        }
1855        if (this.radiusMinorGridlinesVisible !=
1856            that.radiusMinorGridlinesVisible) {
1857            return false;
1858        }
1859        if (!this.cornerTextItems.equals(that.cornerTextItems)) {
1860            return false;
1861        }
1862        if (this.margin != that.margin) {
1863            return false;
1864        }
1865        if (!ObjectUtilities.equal(this.fixedLegendItems,
1866                that.fixedLegendItems)) {
1867            return false;
1868        }
1869        return super.equals(obj);
1870    }
1871
1872    /**
1873     * Returns a clone of the plot.
1874     *
1875     * @return A clone.
1876     *
1877     * @throws CloneNotSupportedException  this can occur if some component of
1878     *         the plot cannot be cloned.
1879     */
1880    @Override
1881    public Object clone() throws CloneNotSupportedException {
1882        PolarPlot clone = (PolarPlot) super.clone();
1883        clone.axes = (ObjectList) ObjectUtilities.clone(this.axes);
1884        for (int i = 0; i < this.axes.size(); i++) {
1885            ValueAxis axis = (ValueAxis) this.axes.get(i);
1886            if (axis != null) {
1887                ValueAxis clonedAxis = (ValueAxis) axis.clone();
1888                clone.axes.set(i, clonedAxis);
1889                clonedAxis.setPlot(clone);
1890                clonedAxis.addChangeListener(clone);
1891            }
1892        }
1893
1894        // the datasets are not cloned, but listeners need to be added...
1895        clone.datasets = (ObjectList) ObjectUtilities.clone(this.datasets);
1896        for (int i = 0; i < clone.datasets.size(); ++i) {
1897            XYDataset d = getDataset(i);
1898            if (d != null) {
1899                d.addChangeListener(clone);
1900            }
1901        }
1902
1903        clone.renderers = (ObjectList) ObjectUtilities.clone(this.renderers);
1904        for (int i = 0; i < this.renderers.size(); i++) {
1905            PolarItemRenderer renderer2 = (PolarItemRenderer) this.renderers.get(i);
1906            if (renderer2 instanceof PublicCloneable) {
1907                PublicCloneable pc = (PublicCloneable) renderer2;
1908                PolarItemRenderer rc = (PolarItemRenderer) pc.clone();
1909                clone.renderers.set(i, rc);
1910                rc.setPlot(clone);
1911                rc.addChangeListener(clone);
1912            }
1913        }
1914
1915        clone.cornerTextItems = new ArrayList(this.cornerTextItems);
1916
1917        return clone;
1918    }
1919
1920    /**
1921     * Provides serialization support.
1922     *
1923     * @param stream  the output stream.
1924     *
1925     * @throws IOException  if there is an I/O error.
1926     */
1927    private void writeObject(ObjectOutputStream stream) throws IOException {
1928        stream.defaultWriteObject();
1929        SerialUtilities.writeStroke(this.angleGridlineStroke, stream);
1930        SerialUtilities.writePaint(this.angleGridlinePaint, stream);
1931        SerialUtilities.writeStroke(this.radiusGridlineStroke, stream);
1932        SerialUtilities.writePaint(this.radiusGridlinePaint, stream);
1933        SerialUtilities.writePaint(this.angleLabelPaint, stream);
1934    }
1935
1936    /**
1937     * Provides serialization support.
1938     *
1939     * @param stream  the input stream.
1940     *
1941     * @throws IOException  if there is an I/O error.
1942     * @throws ClassNotFoundException  if there is a classpath problem.
1943     */
1944    private void readObject(ObjectInputStream stream)
1945        throws IOException, ClassNotFoundException {
1946
1947        stream.defaultReadObject();
1948        this.angleGridlineStroke = SerialUtilities.readStroke(stream);
1949        this.angleGridlinePaint = SerialUtilities.readPaint(stream);
1950        this.radiusGridlineStroke = SerialUtilities.readStroke(stream);
1951        this.radiusGridlinePaint = SerialUtilities.readPaint(stream);
1952        this.angleLabelPaint = SerialUtilities.readPaint(stream);
1953
1954        int rangeAxisCount = this.axes.size();
1955        for (int i = 0; i < rangeAxisCount; i++) {
1956            Axis axis = (Axis) this.axes.get(i);
1957            if (axis != null) {
1958                axis.setPlot(this);
1959                axis.addChangeListener(this);
1960            }
1961        }
1962        int datasetCount = this.datasets.size();
1963        for (int i = 0; i < datasetCount; i++) {
1964            Dataset dataset = (Dataset) this.datasets.get(i);
1965            if (dataset != null) {
1966                dataset.addChangeListener(this);
1967            }
1968        }
1969        int rendererCount = this.renderers.size();
1970        for (int i = 0; i < rendererCount; i++) {
1971            PolarItemRenderer renderer = (PolarItemRenderer) this.renderers.get(i);
1972            if (renderer != null) {
1973                renderer.addChangeListener(this);
1974            }
1975        }
1976    }
1977
1978    /**
1979     * This method is required by the {@link Zoomable} interface, but since
1980     * the plot does not have any domain axes, it does nothing.
1981     *
1982     * @param factor  the zoom factor.
1983     * @param state  the plot state.
1984     * @param source  the source point (in Java2D coordinates).
1985     */
1986    @Override
1987    public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1988                               Point2D source) {
1989        // do nothing
1990    }
1991
1992    /**
1993     * This method is required by the {@link Zoomable} interface, but since
1994     * the plot does not have any domain axes, it does nothing.
1995     *
1996     * @param factor  the zoom factor.
1997     * @param state  the plot state.
1998     * @param source  the source point (in Java2D coordinates).
1999     * @param useAnchor  use source point as zoom anchor?
2000     *
2001     * @since 1.0.7
2002     */
2003    @Override
2004    public void zoomDomainAxes(double factor, PlotRenderingInfo state,
2005                               Point2D source, boolean useAnchor) {
2006        // do nothing
2007    }
2008
2009    /**
2010     * This method is required by the {@link Zoomable} interface, but since
2011     * the plot does not have any domain axes, it does nothing.
2012     *
2013     * @param lowerPercent  the new lower bound.
2014     * @param upperPercent  the new upper bound.
2015     * @param state  the plot state.
2016     * @param source  the source point (in Java2D coordinates).
2017     */
2018    @Override
2019    public void zoomDomainAxes(double lowerPercent, double upperPercent,
2020                               PlotRenderingInfo state, Point2D source) {
2021        // do nothing
2022    }
2023
2024    /**
2025     * Multiplies the range on the range axis/axes by the specified factor.
2026     *
2027     * @param factor  the zoom factor.
2028     * @param state  the plot state.
2029     * @param source  the source point (in Java2D coordinates).
2030     */
2031    @Override
2032    public void zoomRangeAxes(double factor, PlotRenderingInfo state,
2033                              Point2D source) {
2034        zoom(factor);
2035    }
2036
2037    /**
2038     * Multiplies the range on the range axis by the specified factor.
2039     *
2040     * @param factor  the zoom factor.
2041     * @param info  the plot rendering info.
2042     * @param source  the source point (in Java2D space).
2043     * @param useAnchor  use source point as zoom anchor?
2044     *
2045     * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
2046     *
2047     * @since 1.0.7
2048     */
2049    @Override
2050    public void zoomRangeAxes(double factor, PlotRenderingInfo info,
2051                              Point2D source, boolean useAnchor) {
2052        // get the source coordinate - this plot has always a VERTICAL
2053        // orientation
2054        final double sourceX = source.getX();
2055
2056        for (int axisIdx = 0; axisIdx < getAxisCount(); axisIdx++) {
2057            final ValueAxis axis = getAxis(axisIdx);
2058            if (axis != null) {
2059                if (useAnchor) {
2060                    double anchorX = axis.java2DToValue(sourceX,
2061                            info.getDataArea(), RectangleEdge.BOTTOM);
2062                    axis.resizeRange(factor, anchorX);
2063                }
2064                else {
2065                    axis.resizeRange(factor);
2066                }
2067            }
2068        }
2069    }
2070
2071    /**
2072     * Zooms in on the range axes.
2073     *
2074     * @param lowerPercent  the new lower bound.
2075     * @param upperPercent  the new upper bound.
2076     * @param state  the plot state.
2077     * @param source  the source point (in Java2D coordinates).
2078     */
2079    @Override
2080    public void zoomRangeAxes(double lowerPercent, double upperPercent,
2081                              PlotRenderingInfo state, Point2D source) {
2082        zoom((upperPercent + lowerPercent) / 2.0);
2083    }
2084
2085    /**
2086     * Returns <code>false</code> always.
2087     *
2088     * @return <code>false</code> always.
2089     */
2090    @Override
2091    public boolean isDomainZoomable() {
2092        return false;
2093    }
2094
2095    /**
2096     * Returns <code>true</code> to indicate that the range axis is zoomable.
2097     *
2098     * @return <code>true</code>.
2099     */
2100    @Override
2101    public boolean isRangeZoomable() {
2102        return true;
2103    }
2104
2105    /**
2106     * Returns the orientation of the plot.
2107     *
2108     * @return The orientation.
2109     */
2110    @Override
2111    public PlotOrientation getOrientation() {
2112        return PlotOrientation.HORIZONTAL;
2113    }
2114
2115    /**
2116     * Translates a (theta, radius) pair into Java2D coordinates.  If
2117     * <code>radius</code> is less than the lower bound of the axis, then
2118     * this method returns the centre point.
2119     *
2120     * @param angleDegrees  the angle in degrees.
2121     * @param radius  the radius.
2122     * @param axis  the axis.
2123     * @param dataArea  the data area.
2124     *
2125     * @return A point in Java2D space.
2126     *
2127     * @since 1.0.14
2128     */
2129    public Point translateToJava2D(double angleDegrees, double radius,
2130            ValueAxis axis, Rectangle2D dataArea) {
2131
2132        if (counterClockwise) {
2133            angleDegrees = -angleDegrees;
2134        }
2135        double radians = Math.toRadians(angleDegrees + this.angleOffset);
2136
2137        double minx = dataArea.getMinX() + this.margin;
2138        double maxx = dataArea.getMaxX() - this.margin;
2139        double miny = dataArea.getMinY() + this.margin;
2140        double maxy = dataArea.getMaxY() - this.margin;
2141
2142        double halfWidth = (maxx - minx) / 2.0;
2143        double halfHeight = (maxy - miny) / 2.0;
2144
2145        double midX = minx + halfWidth;
2146        double midY = miny + halfHeight;
2147
2148        double l = Math.min(halfWidth, halfHeight);
2149        Rectangle2D quadrant = new Rectangle2D.Double(midX, midY, l, l);
2150
2151        double axisMin = axis.getLowerBound();
2152        double adjustedRadius = Math.max(radius, axisMin);
2153
2154        double length = axis.valueToJava2D(adjustedRadius, quadrant, RectangleEdge.BOTTOM) - midX;
2155        float x = (float) (midX + Math.cos(radians) * length);
2156        float y = (float) (midY + Math.sin(radians) * length);
2157
2158        int ix = Math.round(x);
2159        int iy = Math.round(y);
2160
2161        Point p = new Point(ix, iy);
2162        return p;
2163
2164    }
2165
2166    /**
2167     * Translates a (theta, radius) pair into Java2D coordinates.  If
2168     * <code>radius</code> is less than the lower bound of the axis, then
2169     * this method returns the centre point.
2170     *
2171     * @param angleDegrees  the angle in degrees.
2172     * @param radius  the radius.
2173     * @param dataArea  the data area.
2174     *
2175     * @return A point in Java2D space.
2176     *
2177     * @deprecated Since 1.0.14, use {@link #translateToJava2D(double, double,
2178     * org.jfree.chart.axis.ValueAxis, java.awt.geom.Rectangle2D)} instead.
2179     */
2180    public Point translateValueThetaRadiusToJava2D(double angleDegrees,
2181            double radius, Rectangle2D dataArea) {
2182
2183        return translateToJava2D(angleDegrees, radius, getAxis(), dataArea);
2184    }
2185
2186    /**
2187     * Returns the upper bound of the radius axis.
2188     *
2189     * @return The upper bound.
2190     *
2191     * @deprecated Since 1.0.14, use {@link #getAxis()} and call the
2192     *         getUpperBound() method.
2193     */
2194    public double getMaxRadius() {
2195        return getAxis().getUpperBound();
2196    }
2197
2198    /**
2199     * Returns the number of series in the dataset for this plot.  If the
2200     * dataset is <code>null</code>, the method returns 0.
2201     *
2202     * @return The series count.
2203     *
2204     * @deprecated Since 1.0.14, grab a reference to the dataset and check
2205     *     the series count directly.
2206     */
2207    public int getSeriesCount() {
2208        int result = 0;
2209        XYDataset dataset = getDataset(0);
2210        if (dataset != null) {
2211            result = dataset.getSeriesCount();
2212        }
2213        return result;
2214    }
2215
2216    /**
2217     * A utility method for drawing the axes.
2218     *
2219     * @param g2  the graphics device.
2220     * @param plotArea  the plot area.
2221     * @param dataArea  the data area.
2222     *
2223     * @return A map containing the axis states.
2224     *
2225     * @deprecated As of version 1.0.14, this method is no longer used.
2226     */
2227    protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea,
2228                                 Rectangle2D dataArea) {
2229        return getAxis().draw(g2, dataArea.getMinY(), plotArea, dataArea,
2230                RectangleEdge.TOP, null);
2231    }
2232
2233}