    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
    060package org.jfree.chart.plot;
    061
    062import java.awt.AlphaComposite;
    063import java.awt.BasicStroke;
    064import java.awt.Color;
    065import java.awt.Composite;
    066import java.awt.Font;
    067import java.awt.FontMetrics;
    068import java.awt.Graphics2D;
    069import java.awt.Paint;
    070import java.awt.Point;
    071import java.awt.Shape;
    072import java.awt.Stroke;
    073import java.awt.geom.Point2D;
    074import java.awt.geom.Rectangle2D;
    075import java.io.IOException;
    076import java.io.ObjectInputStream;
    077import java.io.ObjectOutputStream;
    078import java.io.Serializable;
    079import java.util.ArrayList;
    080import java.util.HashSet;
    081import java.util.Iterator;
    082import java.util.List;
    083import java.util.Map;
    084import java.util.ResourceBundle;
    085import java.util.TreeMap;
    086
    087import org.jfree.chart.LegendItem;
    088import org.jfree.chart.LegendItemCollection;
    089import org.jfree.chart.axis.Axis;
    090import org.jfree.chart.axis.AxisState;
    091import org.jfree.chart.axis.NumberTick;
    092import org.jfree.chart.axis.NumberTickUnit;
    093import org.jfree.chart.axis.TickUnit;
    094import org.jfree.chart.axis.ValueAxis;
    095import org.jfree.chart.event.PlotChangeEvent;
    096import org.jfree.chart.event.RendererChangeEvent;
    097import org.jfree.chart.event.RendererChangeListener;
    098import org.jfree.chart.renderer.PolarItemRenderer;
    099import org.jfree.chart.util.ResourceBundleWrapper;
    100import org.jfree.data.Range;
    101import org.jfree.data.general.Dataset;
    102import org.jfree.data.general.DatasetChangeEvent;
    103import org.jfree.data.general.DatasetUtilities;
    104import org.jfree.data.xy.XYDataset;
    105import org.jfree.io.SerialUtilities;
    106import org.jfree.text.TextUtilities;
    107import org.jfree.ui.RectangleEdge;
    108import org.jfree.ui.RectangleInsets;
    109import org.jfree.ui.TextAnchor;
    110import org.jfree.util.ObjectList;
    111import org.jfree.util.ObjectUtilities;
    112import org.jfree.util.PaintUtilities;
    113import 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 */
    119public 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}