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