    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * --------------
    028     * PolarPlot.java
    029     * --------------
    030     * (C) Copyright 2004-2008, 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 (patch 1871902);
    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     *
    050     */
    051
    052    package org.jfree.chart.plot;
    053
    054    import java.awt.AlphaComposite;
    055    import java.awt.BasicStroke;
    056    import java.awt.Color;
    057    import java.awt.Composite;
    058    import java.awt.Font;
    059    import java.awt.FontMetrics;
    060    import java.awt.Graphics2D;
    061    import java.awt.Paint;
    062    import java.awt.Point;
    063    import java.awt.Shape;
    064    import java.awt.Stroke;
    065    import java.awt.geom.Point2D;
    066    import java.awt.geom.Rectangle2D;
    067    import java.io.IOException;
    068    import java.io.ObjectInputStream;
    069    import java.io.ObjectOutputStream;
    070    import java.io.Serializable;
    071    import java.util.ArrayList;
    072    import java.util.Iterator;
    073    import java.util.List;
    074    import java.util.ResourceBundle;
    075
    076    import org.jfree.chart.LegendItem;
    077    import org.jfree.chart.LegendItemCollection;
    078    import org.jfree.chart.axis.AxisState;
    079    import org.jfree.chart.axis.NumberTick;
    080    import org.jfree.chart.axis.NumberTickUnit;
    081    import org.jfree.chart.axis.TickUnit;
    082    import org.jfree.chart.axis.ValueAxis;
    083    import org.jfree.chart.event.PlotChangeEvent;
    084    import org.jfree.chart.event.RendererChangeEvent;
    085    import org.jfree.chart.event.RendererChangeListener;
    086    import org.jfree.chart.renderer.PolarItemRenderer;
    087    import org.jfree.data.Range;
    088    import org.jfree.data.general.DatasetChangeEvent;
    089    import org.jfree.data.general.DatasetUtilities;
    090    import org.jfree.data.xy.XYDataset;
    091    import org.jfree.io.SerialUtilities;
    092    import org.jfree.text.TextUtilities;
    093    import org.jfree.ui.RectangleEdge;
    094    import org.jfree.ui.RectangleInsets;
    095    import org.jfree.ui.TextAnchor;
    096    import org.jfree.util.ObjectUtilities;
    097    import org.jfree.util.PaintUtilities;
    098
    099    /**
    100     * Plots data that is in (theta, radius) pairs where
    101     * theta equal to zero is due north and increases clockwise.
    102     */
    103    public class PolarPlot extends Plot implements ValueAxisPlot, Zoomable,
    104            RendererChangeListener, Cloneable, Serializable {
    105
    106        /** For serialization. */
    107        private static final long serialVersionUID = 3794383185924179525L;
    108
    109        /** The default margin. */
    110        private static final int MARGIN = 20;
    111
    112        /** The annotation margin. */
    113        private static final double ANNOTATION_MARGIN = 7.0;
    114
    115        /**
    116         * The default angle tick unit size.
    117         *
    118         * @since 1.0.10
    119         */
    120        public static final double DEFAULT_ANGLE_TICK_UNIT_SIZE = 45.0;
    121
    122        /** The default grid line stroke. */
    123        public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(
    124                0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
    125                0.0f, new float[]{2.0f, 2.0f}, 0.0f);
    126
    127        /** The default grid line paint. */
    128        public static final Paint DEFAULT_GRIDLINE_PAINT = Color.gray;
    129
    130        /** The resourceBundle for the localization. */
    131        protected static ResourceBundle localizationResources
    132            = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
    133
    134        /** The angles that are marked with gridlines. */
    135        private List angleTicks;
    136
    137        /** The axis (used for the y-values). */
    138        private ValueAxis axis;
    139
    140        /** The dataset. */
    141        private XYDataset dataset;
    142
    143        /**
    144         * Object responsible for drawing the visual representation of each point
    145         * on the plot.
    146         */
    147        private PolarItemRenderer renderer;
    148
    149        /**
    150         * The tick unit that controls the spacing between the angular grid lines.
    151         *
    152         * @since 1.0.10
    153         */
    154        private TickUnit angleTickUnit;
    155
    156        /** A flag that controls whether or not the angle labels are visible. */
    157        private boolean angleLabelsVisible = true;
    158
    159        /** The font used to display the angle labels - never null. */
    160        private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12);
    161
    162        /** The paint used to display the angle labels. */
    163        private transient Paint angleLabelPaint = Color.black;
    164
    165        /** A flag that controls whether the angular grid-lines are visible. */
    166        private boolean angleGridlinesVisible;
    167
    168        /** The stroke used to draw the angular grid-lines. */
    169        private transient Stroke angleGridlineStroke;
    170
    171        /** The paint used to draw the angular grid-lines. */
    172        private transient Paint angleGridlinePaint;
    173
    174        /** A flag that controls whether the radius grid-lines are visible. */
    175        private boolean radiusGridlinesVisible;
    176
    177        /** The stroke used to draw the radius grid-lines. */
    178        private transient Stroke radiusGridlineStroke;
    179
    180        /** The paint used to draw the radius grid-lines. */
    181        private transient Paint radiusGridlinePaint;
    182
    183        /** The annotations for the plot. */
    184        private List cornerTextItems = new ArrayList();
    185
    186        /**
    187         * Default constructor.
    188         */
    189        public PolarPlot() {
    190            this(null, null, null);
    191        }
    192
    193       /**
    194         * Creates a new plot.
    195         *
    196         * @param dataset  the dataset (<code>null</code> permitted).
    197         * @param radiusAxis  the radius axis (<code>null</code> permitted).
    198         * @param renderer  the renderer (<code>null</code> permitted).
    199         */
    200        public PolarPlot(XYDataset dataset,
    201                         ValueAxis radiusAxis,
    202                         PolarItemRenderer renderer) {
    203
    204            super();
    205
    206            this.dataset = dataset;
    207            if (this.dataset != null) {
    208                this.dataset.addChangeListener(this);
    209            }
    210            this.angleTickUnit = new NumberTickUnit(DEFAULT_ANGLE_TICK_UNIT_SIZE);
    211
    212            this.axis = radiusAxis;
    213            if (this.axis != null) {
    214                this.axis.setPlot(this);
    215                this.axis.addChangeListener(this);
    216            }
    217
    218            this.renderer = renderer;
    219            if (this.renderer != null) {
    220                this.renderer.setPlot(this);
    221                this.renderer.addChangeListener(this);
    222            }
    223
    224            this.angleGridlinesVisible = true;
    225            this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE;
    226            this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT;
    227
    228            this.radiusGridlinesVisible = true;
    229            this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE;
    230            this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT;
    231        }
    232
    233        /**
    234         * Add text to be displayed in the lower right hand corner and sends a
    235         * {@link PlotChangeEvent} to all registered listeners.
    236         *
    237         * @param text  the text to display (<code>null</code> not permitted).
    238         *
    239         * @see #removeCornerTextItem(String)
    240         */
    241        public void addCornerTextItem(String text) {
    242            if (text == null) {
    243                throw new IllegalArgumentException("Null 'text' argument.");
    244            }
    245            this.cornerTextItems.add(text);
    246            fireChangeEvent();
    247        }
    248
    249        /**
    250         * Remove the given text from the list of corner text items and
    251         * sends a {@link PlotChangeEvent} to all registered listeners.
    252         *
    253         * @param text  the text to remove (<code>null</code> ignored).
    254         *
    255         * @see #addCornerTextItem(String)
    256         */
    257        public void removeCornerTextItem(String text) {
    258            boolean removed = this.cornerTextItems.remove(text);
    259            if (removed) {
    260                fireChangeEvent();
    261            }
    262        }
    263
    264        /**
    265         * Clear the list of corner text items and sends a {@link PlotChangeEvent}
    266         * to all registered listeners.
    267         *
    268         * @see #addCornerTextItem(String)
    269         * @see #removeCornerTextItem(String)
    270         */
    271        public void clearCornerTextItems() {
    272            if (this.cornerTextItems.size() > 0) {
    273                this.cornerTextItems.clear();
    274                fireChangeEvent();
    275            }
    276        }
    277
    278        /**
    279         * Returns the plot type as a string.
    280         *
    281         * @return A short string describing the type of plot.
    282         */
    283        public String getPlotType() {
    284           return PolarPlot.localizationResources.getString("Polar_Plot");
    285        }
    286
    287        /**
    288         * Returns the axis for the plot.
    289         *
    290         * @return The radius axis (possibly <code>null</code>).
    291         *
    292         * @see #setAxis(ValueAxis)
    293         */
    294        public ValueAxis getAxis() {
    295            return this.axis;
    296        }
    297
    298        /**
    299         * Sets the axis for the plot and sends a {@link PlotChangeEvent} to all
    300         * registered listeners.
    301         *
    302         * @param axis  the new axis (<code>null</code> permitted).
    303         */
    304        public void setAxis(ValueAxis axis) {
    305            if (axis != null) {
    306                axis.setPlot(this);
    307            }
    308
    309            // plot is likely registered as a listener with the existing axis...
    310            if (this.axis != null) {
    311                this.axis.removeChangeListener(this);
    312            }
    313
    314            this.axis = axis;
    315            if (this.axis != null) {
    316                this.axis.configure();
    317                this.axis.addChangeListener(this);
    318            }
    319            fireChangeEvent();
    320        }
    321
    322        /**
    323         * Returns the primary dataset for the plot.
    324         *
    325         * @return The primary dataset (possibly <code>null</code>).
    326         *
    327         * @see #setDataset(XYDataset)
    328         */
    329        public XYDataset getDataset() {
    330            return this.dataset;
    331        }
    332
    333        /**
    334         * Sets the dataset for the plot, replacing the existing dataset if there
    335         * is one.
    336         *
    337         * @param dataset  the dataset (<code>null</code> permitted).
    338         *
    339         * @see #getDataset()
    340         */
    341        public void setDataset(XYDataset dataset) {
    342            // if there is an existing dataset, remove the plot from the list of
    343            // change listeners...
    344            XYDataset existing = this.dataset;
    345            if (existing != null) {
    346                existing.removeChangeListener(this);
    347            }
    348
    349            // set the new m_Dataset, and register the chart as a change listener...
    350            this.dataset = dataset;
    351            if (this.dataset != null) {
    352                setDatasetGroup(this.dataset.getGroup());
    353                this.dataset.addChangeListener(this);
    354            }
    355
    356            // send a m_Dataset change event to self...
    357            DatasetChangeEvent event = new DatasetChangeEvent(this, this.dataset);
    358            datasetChanged(event);
    359        }
    360
    361        /**
    362         * Returns the item renderer.
    363         *
    364         * @return The renderer (possibly <code>null</code>).
    365         *
    366         * @see #setRenderer(PolarItemRenderer)
    367         */
    368        public PolarItemRenderer getRenderer() {
    369            return this.renderer;
    370        }
    371
    372        /**
    373         * Sets the item renderer, and notifies all listeners of a change to the
    374         * plot.
    375         * <P>
    376         * If the renderer is set to <code>null</code>, no chart will be drawn.
    377         *
    378         * @param renderer  the new renderer (<code>null</code> permitted).
    379         *
    380         * @see #getRenderer()
    381         */
    382        public void setRenderer(PolarItemRenderer renderer) {
    383            if (this.renderer != null) {
    384                this.renderer.removeChangeListener(this);
    385            }
    386
    387            this.renderer = renderer;
    388            if (this.renderer != null) {
    389                this.renderer.setPlot(this);
    390            }
    391            fireChangeEvent();
    392        }
    393
    394        /**
    395         * Returns the tick unit that controls the spacing of the angular grid
    396         * lines.
    397         *
    398         * @return The tick unit (never <code>null</code>).
    399         *
    400         * @since 1.0.10
    401         */
    402        public TickUnit getAngleTickUnit() {
    403            return this.angleTickUnit;
    404        }
    405
    406        /**
    407         * Sets the tick unit that controls the spacing of the angular grid
    408         * lines, and sends a {@link PlotChangeEvent} to all registered listeners.
    409         *
    410         * @param unit  the tick unit (<code>null</code> not permitted).
    411         *
    412         * @since 1.0.10
    413         */
    414        public void setAngleTickUnit(TickUnit unit) {
    415            if (unit == null) {
    416                throw new IllegalArgumentException("Null 'unit' argument.");
    417            }
    418            this.angleTickUnit = unit;
    419            fireChangeEvent();
    420        }
    421
    422        /**
    423         * Returns a flag that controls whether or not the angle labels are visible.
    424         *
    425         * @return A boolean.
    426         *
    427         * @see #setAngleLabelsVisible(boolean)
    428         */
    429        public boolean isAngleLabelsVisible() {
    430            return this.angleLabelsVisible;
    431        }
    432
    433        /**
    434         * Sets the flag that controls whether or not the angle labels are visible,
    435         * and sends a {@link PlotChangeEvent} to all registered listeners.
    436         *
    437         * @param visible  the flag.
    438         *
    439         * @see #isAngleLabelsVisible()
    440         */
    441        public void setAngleLabelsVisible(boolean visible) {
    442            if (this.angleLabelsVisible != visible) {
    443                this.angleLabelsVisible = visible;
    444                fireChangeEvent();
    445            }
    446        }
    447
    448        /**
    449         * Returns the font used to display the angle labels.
    450         *
    451         * @return A font (never <code>null</code>).
    452         *
    453         * @see #setAngleLabelFont(Font)
    454         */
    455        public Font getAngleLabelFont() {
    456            return this.angleLabelFont;
    457        }
    458
    459        /**
    460         * Sets the font used to display the angle labels and sends a
    461         * {@link PlotChangeEvent} to all registered listeners.
    462         *
    463         * @param font  the font (<code>null</code> not permitted).
    464         *
    465         * @see #getAngleLabelFont()
    466         */
    467        public void setAngleLabelFont(Font font) {
    468            if (font == null) {
    469                throw new IllegalArgumentException("Null 'font' argument.");
    470            }
    471            this.angleLabelFont = font;
    472            fireChangeEvent();
    473        }
    474
    475        /**
    476         * Returns the paint used to display the angle labels.
    477         *
    478         * @return A paint (never <code>null</code>).
    479         *
    480         * @see #setAngleLabelPaint(Paint)
    481         */
    482        public Paint getAngleLabelPaint() {
    483            return this.angleLabelPaint;
    484        }
    485
    486        /**
    487         * Sets the paint used to display the angle labels and sends a
    488         * {@link PlotChangeEvent} to all registered listeners.
    489         *
    490         * @param paint  the paint (<code>null</code> not permitted).
    491         */
    492        public void setAngleLabelPaint(Paint paint) {
    493            if (paint == null) {
    494                throw new IllegalArgumentException("Null 'paint' argument.");
    495            }
    496            this.angleLabelPaint = paint;
    497            fireChangeEvent();
    498        }
    499
    500        /**
    501         * Returns <code>true</code> if the angular gridlines are visible, and
    502         * <code>false<code> otherwise.
    503         *
    504         * @return <code>true</code> or <code>false</code>.
    505         *
    506         * @see #setAngleGridlinesVisible(boolean)
    507         */
    508        public boolean isAngleGridlinesVisible() {
    509            return this.angleGridlinesVisible;
    510        }
    511
    512        /**
    513         * Sets the flag that controls whether or not the angular grid-lines are
    514         * visible.
    515         * <p>
    516         * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
    517         * registered listeners.
    518         *
    519         * @param visible  the new value of the flag.
    520         *
    521         * @see #isAngleGridlinesVisible()
    522         */
    523        public void setAngleGridlinesVisible(boolean visible) {
    524            if (this.angleGridlinesVisible != visible) {
    525                this.angleGridlinesVisible = visible;
    526                fireChangeEvent();
    527            }
    528        }
    529
    530        /**
    531         * Returns the stroke for the grid-lines (if any) plotted against the
    532         * angular axis.
    533         *
    534         * @return The stroke (possibly <code>null</code>).
    535         *
    536         * @see #setAngleGridlineStroke(Stroke)
    537         */
    538        public Stroke getAngleGridlineStroke() {
    539            return this.angleGridlineStroke;
    540        }
    541
    542        /**
    543         * Sets the stroke for the grid lines plotted against the angular axis and
    544         * sends a {@link PlotChangeEvent} to all registered listeners.
    545         * <p>
    546         * If you set this to <code>null</code>, no grid lines will be drawn.
    547         *
    548         * @param stroke  the stroke (<code>null</code> permitted).
    549         *
    550         * @see #getAngleGridlineStroke()
    551         */
    552        public void setAngleGridlineStroke(Stroke stroke) {
    553            this.angleGridlineStroke = stroke;
    554            fireChangeEvent();
    555        }
    556
    557        /**
    558         * Returns the paint for the grid lines (if any) plotted against the
    559         * angular axis.
    560         *
    561         * @return The paint (possibly <code>null</code>).
    562         *
    563         * @see #setAngleGridlinePaint(Paint)
    564         */
    565        public Paint getAngleGridlinePaint() {
    566            return this.angleGridlinePaint;
    567        }
    568
    569        /**
    570         * Sets the paint for the grid lines plotted against the angular axis.
    571         * <p>
    572         * If you set this to <code>null</code>, no grid lines will be drawn.
    573         *
    574         * @param paint  the paint (<code>null</code> permitted).
    575         *
    576         * @see #getAngleGridlinePaint()
    577         */
    578        public void setAngleGridlinePaint(Paint paint) {
    579            this.angleGridlinePaint = paint;
    580            fireChangeEvent();
    581        }
    582
    583        /**
    584         * Returns <code>true</code> if the radius axis grid is visible, and
    585         * <code>false<code> otherwise.
    586         *
    587         * @return <code>true</code> or <code>false</code>.
    588         *
    589         * @see #setRadiusGridlinesVisible(boolean)
    590         */
    591        public boolean isRadiusGridlinesVisible() {
    592            return this.radiusGridlinesVisible;
    593        }
    594
    595        /**
    596         * Sets the flag that controls whether or not the radius axis grid lines
    597         * are visible.
    598         * <p>
    599         * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
    600         * registered listeners.
    601         *
    602         * @param visible  the new value of the flag.
    603         *
    604         * @see #isRadiusGridlinesVisible()
    605         */
    606        public void setRadiusGridlinesVisible(boolean visible) {
    607            if (this.radiusGridlinesVisible != visible) {
    608                this.radiusGridlinesVisible = visible;
    609                fireChangeEvent();
    610            }
    611        }
    612
    613        /**
    614         * Returns the stroke for the grid lines (if any) plotted against the
    615         * radius axis.
    616         *
    617         * @return The stroke (possibly <code>null</code>).
    618         *
    619         * @see #setRadiusGridlineStroke(Stroke)
    620         */
    621        public Stroke getRadiusGridlineStroke() {
    622            return this.radiusGridlineStroke;
    623        }
    624
    625        /**
    626         * Sets the stroke for the grid lines plotted against the radius axis and
    627         * sends a {@link PlotChangeEvent} to all registered listeners.
    628         * <p>
    629         * If you set this to <code>null</code>, no grid lines will be drawn.
    630         *
    631         * @param stroke  the stroke (<code>null</code> permitted).
    632         *
    633         * @see #getRadiusGridlineStroke()
    634         */
    635        public void setRadiusGridlineStroke(Stroke stroke) {
    636            this.radiusGridlineStroke = stroke;
    637            fireChangeEvent();
    638        }
    639
    640        /**
    641         * Returns the paint for the grid lines (if any) plotted against the radius
    642         * axis.
    643         *
    644         * @return The paint (possibly <code>null</code>).
    645         *
    646         * @see #setRadiusGridlinePaint(Paint)
    647         */
    648        public Paint getRadiusGridlinePaint() {
    649            return this.radiusGridlinePaint;
    650        }
    651
    652        /**
    653         * Sets the paint for the grid lines plotted against the radius axis and
    654         * sends a {@link PlotChangeEvent} to all registered listeners.
    655         * <p>
    656         * If you set this to <code>null</code>, no grid lines will be drawn.
    657         *
    658         * @param paint  the paint (<code>null</code> permitted).
    659         *
    660         * @see #getRadiusGridlinePaint()
    661         */
    662        public void setRadiusGridlinePaint(Paint paint) {
    663            this.radiusGridlinePaint = paint;
    664            fireChangeEvent();
    665        }
    666
    667        /**
    668         * Generates a list of tick values for the angular tick marks.
    669         *
    670         * @return A list of {@link NumberTick} instances.
    671         *
    672         * @since 1.0.10
    673         */
    674        protected List refreshAngleTicks() {
    675            List ticks = new ArrayList();
    676            for (double currentTickVal = 0.0; currentTickVal < 360.0;
    677                    currentTickVal += this.angleTickUnit.getSize()) {
    678                NumberTick tick = new NumberTick(new Double(currentTickVal),
    679                    this.angleTickUnit.valueToString(currentTickVal),
    680                    TextAnchor.CENTER, TextAnchor.CENTER, 0.0);
    681                ticks.add(tick);
    682            }
    683            return ticks;
    684        }
    685
    686        /**
    687         * Draws the plot on a Java 2D graphics device (such as the screen or a
    688         * printer).
    689         * <P>
    690         * This plot relies on a {@link PolarItemRenderer} to draw each
    691         * item in the plot.  This allows the visual representation of the data to
    692         * be changed easily.
    693         * <P>
    694         * The optional info argument collects information about the rendering of
    695         * the plot (dimensions, tooltip information etc).  Just pass in
    696         * <code>null</code> if you do not need this information.
    697         *
    698         * @param g2  the graphics device.
    699         * @param area  the area within which the plot (including axes and
    700         *              labels) should be drawn.
    701         * @param anchor  the anchor point (<code>null</code> permitted).
    702         * @param parentState  ignored.
    703         * @param info  collects chart drawing information (<code>null</code>
    704         *              permitted).
    705         */
    706        public void draw(Graphics2D g2,
    707                         Rectangle2D area,
    708                         Point2D anchor,
    709                         PlotState parentState,
    710                         PlotRenderingInfo info) {
    711
    712            // if the plot area is too small, just return...
    713            boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
    714            boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
    715            if (b1 || b2) {
    716                return;
    717            }
    718
    719            // record the plot area...
    720            if (info != null) {
    721                info.setPlotArea(area);
    722            }
    723
    724            // adjust the drawing area for the plot insets (if any)...
    725            RectangleInsets insets = getInsets();
    726            insets.trim(area);
    727
    728            Rectangle2D dataArea = area;
    729            if (info != null) {
    730                info.setDataArea(dataArea);
    731            }
    732
    733            // draw the plot background and axes...
    734            drawBackground(g2, dataArea);
    735            double h = Math.min(dataArea.getWidth() / 2.0,
    736                    dataArea.getHeight() / 2.0) - MARGIN;
    737            Rectangle2D quadrant = new Rectangle2D.Double(dataArea.getCenterX(),
    738                    dataArea.getCenterY(), h, h);
    739            AxisState state = drawAxis(g2, area, quadrant);
    740            if (this.renderer != null) {
    741                Shape originalClip = g2.getClip();
    742                Composite originalComposite = g2.getComposite();
    743
    744                g2.clip(dataArea);
    745                g2.setComposite(AlphaComposite.getInstance(
    746                        AlphaComposite.SRC_OVER, getForegroundAlpha()));
    747
    748                this.angleTicks = refreshAngleTicks();
    749                drawGridlines(g2, dataArea, this.angleTicks, state.getTicks());
    750
    751                // draw...
    752                render(g2, dataArea, info);
    753
    754                g2.setClip(originalClip);
    755                g2.setComposite(originalComposite);
    756            }
    757            drawOutline(g2, dataArea);
    758            drawCornerTextItems(g2, dataArea);
    759        }
    760
    761        /**
    762         * Draws the corner text items.
    763         *
    764         * @param g2  the drawing surface.
    765         * @param area  the area.
    766         */
    767        protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) {
    768            if (this.cornerTextItems.isEmpty()) {
    769                return;
    770            }
    771
    772            g2.setColor(Color.black);
    773            double width = 0.0;
    774            double height = 0.0;
    775            for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
    776                String msg = (String) it.next();
    777                FontMetrics fm = g2.getFontMetrics();
    778                Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm);
    779                width = Math.max(width, bounds.getWidth());
    780                height += bounds.getHeight();
    781            }
    782
    783            double xadj = ANNOTATION_MARGIN * 2.0;
    784            double yadj = ANNOTATION_MARGIN;
    785            width += xadj;
    786            height += yadj;
    787
    788            double x = area.getMaxX() - width;
    789            double y = area.getMaxY() - height;
    790            g2.drawRect((int) x, (int) y, (int) width, (int) height);
    791            x += ANNOTATION_MARGIN;
    792            for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
    793                String msg = (String) it.next();
    794                Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2,
    795                        g2.getFontMetrics());
    796                y += bounds.getHeight();
    797                g2.drawString(msg, (int) x, (int) y);
    798            }
    799        }
    800
    801        /**
    802         * A utility method for drawing the axes.
    803         *
    804         * @param g2  the graphics device.
    805         * @param plotArea  the plot area.
    806         * @param dataArea  the data area.
    807         *
    808         * @return A map containing the axis states.
    809         */
    810        protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea,
    811                                     Rectangle2D dataArea) {
    812            return this.axis.draw(g2, dataArea.getMinY(), plotArea, dataArea,
    813                    RectangleEdge.TOP, null);
    814        }
    815
    816        /**
    817         * Draws a representation of the data within the dataArea region, using the
    818         * current m_Renderer.
    819         *
    820         * @param g2  the graphics device.
    821         * @param dataArea  the region in which the data is to be drawn.
    822         * @param info  an optional object for collection dimension
    823         *              information (<code>null</code> permitted).
    824         */
    825        protected void render(Graphics2D g2,
    826                           Rectangle2D dataArea,
    827                           PlotRenderingInfo info) {
    828
    829            // now get the data and plot it (the visual representation will depend
    830            // on the m_Renderer that has been set)...
    831            if (!DatasetUtilities.isEmptyOrNull(this.dataset)) {
    832                int seriesCount = this.dataset.getSeriesCount();
    833                for (int series = 0; series < seriesCount; series++) {
    834                    this.renderer.drawSeries(g2, dataArea, info, this,
    835                            this.dataset, series);
    836                }
    837            }
    838            else {
    839                drawNoDataMessage(g2, dataArea);
    840            }
    841        }
    842
    843        /**
    844         * Draws the gridlines for the plot, if they are visible.
    845         *
    846         * @param g2  the graphics device.
    847         * @param dataArea  the data area.
    848         * @param angularTicks  the ticks for the angular axis.
    849         * @param radialTicks  the ticks for the radial axis.
    850         */
    851        protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea,
    852                                     List angularTicks, List radialTicks) {
    853
    854            // no renderer, no gridlines...
    855            if (this.renderer == null) {
    856                return;
    857            }
    858
    859            // draw the domain grid lines, if any...
    860            if (isAngleGridlinesVisible()) {
    861                Stroke gridStroke = getAngleGridlineStroke();
    862                Paint gridPaint = getAngleGridlinePaint();
    863                if ((gridStroke != null) && (gridPaint != null)) {
    864                    this.renderer.drawAngularGridLines(g2, this, angularTicks,
    865                            dataArea);
    866                }
    867            }
    868
    869            // draw the radius grid lines, if any...
    870            if (isRadiusGridlinesVisible()) {
    871                Stroke gridStroke = getRadiusGridlineStroke();
    872                Paint gridPaint = getRadiusGridlinePaint();
    873                if ((gridStroke != null) && (gridPaint != null)) {
    874                    this.renderer.drawRadialGridLines(g2, this, this.axis,
    875                            radialTicks, dataArea);
    876                }
    877            }
    878        }
    879
    880        /**
    881         * Zooms the axis ranges by the specified percentage about the anchor point.
    882         *
    883         * @param percent  the amount of the zoom.
    884         */
    885        public void zoom(double percent) {
    886            if (percent > 0.0) {
    887                double radius = getMaxRadius();
    888                double scaledRadius = radius * percent;
    889                this.axis.setUpperBound(scaledRadius);
    890                getAxis().setAutoRange(false);
    891            }
    892            else {
    893                getAxis().setAutoRange(true);
    894            }
    895        }
    896
    897        /**
    898         * Returns the range for the specified axis.
    899         *
    900         * @param axis  the axis.
    901         *
    902         * @return The range.
    903         */
    904        public Range getDataRange(ValueAxis axis) {
    905            Range result = null;
    906            if (this.dataset != null) {
    907                result = Range.combine(result,
    908                        DatasetUtilities.findRangeBounds(this.dataset));
    909            }
    910            return result;
    911        }
    912
    913        /**
    914         * Receives notification of a change to the plot's m_Dataset.
    915         * <P>
    916         * The axis ranges are updated if necessary.
    917         *
    918         * @param event  information about the event (not used here).
    919         */
    920        public void datasetChanged(DatasetChangeEvent event) {
    921
    922            if (this.axis != null) {
    923                this.axis.configure();
    924            }
    925
    926            if (getParent() != null) {
    927                getParent().datasetChanged(event);
    928            }
    929            else {
    930                super.datasetChanged(event);
    931            }
    932        }
    933
    934        /**
    935         * Notifies all registered listeners of a property change.
    936         * <P>
    937         * One source of property change events is the plot's m_Renderer.
    938         *
    939         * @param event  information about the property change.
    940         */
    941        public void rendererChanged(RendererChangeEvent event) {
    942            fireChangeEvent();
    943        }
    944
    945        /**
    946         * Returns the number of series in the dataset for this plot.  If the
    947         * dataset is <code>null</code>, the method returns 0.
    948         *
    949         * @return The series count.
    950         */
    951        public int getSeriesCount() {
    952            int result = 0;
    953
    954            if (this.dataset != null) {
    955                result = this.dataset.getSeriesCount();
    956            }
    957            return result;
    958        }
    959
    960        /**
    961         * Returns the legend items for the plot.  Each legend item is generated by
    962         * the plot's m_Renderer, since the m_Renderer is responsible for the visual
    963         * representation of the data.
    964         *
    965         * @return The legend items.
    966         */
    967        public LegendItemCollection getLegendItems() {
    968            LegendItemCollection result = new LegendItemCollection();
    969
    970            // get the legend items for the main m_Dataset...
    971            if (this.dataset != null) {
    972                if (this.renderer != null) {
    973                    int seriesCount = this.dataset.getSeriesCount();
    974                    for (int i = 0; i < seriesCount; i++) {
    975                        LegendItem item = this.renderer.getLegendItem(i);
    976                        result.add(item);
    977                    }
    978                }
    979            }
    980            return result;
    981        }
    982
    983        /**
    984         * Tests this plot for equality with another object.
    985         *
    986         * @param obj  the object (<code>null</code> permitted).
    987         *
    988         * @return <code>true</code> or <code>false</code>.
    989         */
    990        public boolean equals(Object obj) {
    991            if (obj == this) {
    992                return true;
    993            }
    994            if (!(obj instanceof PolarPlot)) {
    995                return false;
    996            }
    997            PolarPlot that = (PolarPlot) obj;
    998            if (!ObjectUtilities.equal(this.axis, that.axis)) {
    999                return false;
    1000            }
    1001            if (!ObjectUtilities.equal(this.renderer, that.renderer)) {
    1002                return false;
    1003            }
    1004            if (!this.angleTickUnit.equals(that.angleTickUnit)) {
    1005                return false;
    1006            }
    1007            if (this.angleGridlinesVisible != that.angleGridlinesVisible) {
    1008                return false;
    1009            }
    1010            if (this.angleLabelsVisible != that.angleLabelsVisible) {
    1011                return false;
    1012            }
    1013            if (!this.angleLabelFont.equals(that.angleLabelFont)) {
    1014                return false;
    1015            }
    1016            if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) {
    1017                return false;
    1018            }
    1019            if (!ObjectUtilities.equal(this.angleGridlineStroke,
    1020                    that.angleGridlineStroke)) {
    1021                return false;
    1022            }
    1023            if (!PaintUtilities.equal(
    1024                this.angleGridlinePaint, that.angleGridlinePaint
    1025            )) {
    1026                return false;
    1027            }
    1028            if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) {
    1029                return false;
    1030            }
    1031            if (!ObjectUtilities.equal(this.radiusGridlineStroke,
    1032                    that.radiusGridlineStroke)) {
    1033                return false;
    1034            }
    1035            if (!PaintUtilities.equal(this.radiusGridlinePaint,
    1036                    that.radiusGridlinePaint)) {
    1037                return false;
    1038            }
    1039            if (!this.cornerTextItems.equals(that.cornerTextItems)) {
    1040                return false;
    1041            }
    1042            return super.equals(obj);
    1043        }
    1044
    1045        /**
    1046         * Returns a clone of the plot.
    1047         *
    1048         * @return A clone.
    1049         *
    1050         * @throws CloneNotSupportedException  this can occur if some component of
    1051         *         the plot cannot be cloned.
    1052         */
    1053        public Object clone() throws CloneNotSupportedException {
    1054
    1055            PolarPlot clone = (PolarPlot) super.clone();
    1056            if (this.axis != null) {
    1057                clone.axis = (ValueAxis) ObjectUtilities.clone(this.axis);
    1058                clone.axis.setPlot(clone);
    1059                clone.axis.addChangeListener(clone);
    1060            }
    1061
    1062            if (clone.dataset != null) {
    1063                clone.dataset.addChangeListener(clone);
    1064            }
    1065
    1066            if (this.renderer != null) {
    1067                clone.renderer
    1068                    = (PolarItemRenderer) ObjectUtilities.clone(this.renderer);
    1069            }
    1070
    1071            clone.cornerTextItems = new ArrayList(this.cornerTextItems);
    1072
    1073            return clone;
    1074        }
    1075
    1076        /**
    1077         * Provides serialization support.
    1078         *
    1079         * @param stream  the output stream.
    1080         *
    1081         * @throws IOException  if there is an I/O error.
    1082         */
    1083        private void writeObject(ObjectOutputStream stream) throws IOException {
    1084            stream.defaultWriteObject();
    1085            SerialUtilities.writeStroke(this.angleGridlineStroke, stream);
    1086            SerialUtilities.writePaint(this.angleGridlinePaint, stream);
    1087            SerialUtilities.writeStroke(this.radiusGridlineStroke, stream);
    1088            SerialUtilities.writePaint(this.radiusGridlinePaint, stream);
    1089            SerialUtilities.writePaint(this.angleLabelPaint, stream);
    1090        }
    1091
    1092        /**
    1093         * Provides serialization support.
    1094         *
    1095         * @param stream  the input stream.
    1096         *
    1097         * @throws IOException  if there is an I/O error.
    1098         * @throws ClassNotFoundException  if there is a classpath problem.
    1099         */
    1100        private void readObject(ObjectInputStream stream)
    1101            throws IOException, ClassNotFoundException {
    1102
    1103            stream.defaultReadObject();
    1104            this.angleGridlineStroke = SerialUtilities.readStroke(stream);
    1105            this.angleGridlinePaint = SerialUtilities.readPaint(stream);
    1106            this.radiusGridlineStroke = SerialUtilities.readStroke(stream);
    1107            this.radiusGridlinePaint = SerialUtilities.readPaint(stream);
    1108            this.angleLabelPaint = SerialUtilities.readPaint(stream);
    1109
    1110            if (this.axis != null) {
    1111                this.axis.setPlot(this);
    1112                this.axis.addChangeListener(this);
    1113            }
    1114
    1115            if (this.dataset != null) {
    1116                this.dataset.addChangeListener(this);
    1117            }
    1118        }
    1119
    1120        /**
    1121         * This method is required by the {@link Zoomable} interface, but since
    1122         * the plot does not have any domain axes, it does nothing.
    1123         *
    1124         * @param factor  the zoom factor.
    1125         * @param state  the plot state.
    1126         * @param source  the source point (in Java2D coordinates).
    1127         */
    1128        public void zoomDomainAxes(double factor, PlotRenderingInfo state,
    1129                                   Point2D source) {
    1130            // do nothing
    1131        }
    1132
    1133        /**
    1134         * This method is required by the {@link Zoomable} interface, but since
    1135         * the plot does not have any domain axes, it does nothing.
    1136         *
    1137         * @param factor  the zoom factor.
    1138         * @param state  the plot state.
    1139         * @param source  the source point (in Java2D coordinates).
    1140         * @param useAnchor  use source point as zoom anchor?
    1141         *
    1142         * @since 1.0.7
    1143         */
    1144        public void zoomDomainAxes(double factor, PlotRenderingInfo state,
    1145                                   Point2D source, boolean useAnchor) {
    1146            // do nothing
    1147        }
    1148
    1149        /**
    1150         * This method is required by the {@link Zoomable} interface, but since
    1151         * the plot does not have any domain axes, it does nothing.
    1152         *
    1153         * @param lowerPercent  the new lower bound.
    1154         * @param upperPercent  the new upper bound.
    1155         * @param state  the plot state.
    1156         * @param source  the source point (in Java2D coordinates).
    1157         */
    1158        public void zoomDomainAxes(double lowerPercent, double upperPercent,
    1159                                   PlotRenderingInfo state, Point2D source) {
    1160            // do nothing
    1161        }
    1162
    1163        /**
    1164         * Multiplies the range on the range axis/axes by the specified factor.
    1165         *
    1166         * @param factor  the zoom factor.
    1167         * @param state  the plot state.
    1168         * @param source  the source point (in Java2D coordinates).
    1169         */
    1170        public void zoomRangeAxes(double factor, PlotRenderingInfo state,
    1171                                  Point2D source) {
    1172            zoom(factor);
    1173        }
    1174
    1175        /**
    1176         * Multiplies the range on the range axis by the specified factor.
    1177         *
    1178         * @param factor  the zoom factor.
    1179         * @param info  the plot rendering info.
    1180         * @param source  the source point (in Java2D space).
    1181         * @param useAnchor  use source point as zoom anchor?
    1182         *
    1183         * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
    1184         *
    1185         * @since 1.0.7
    1186         */
    1187        public void zoomRangeAxes(double factor, PlotRenderingInfo info,
    1188                                  Point2D source, boolean useAnchor) {
    1189
    1190            if (useAnchor) {
    1191                // get the source coordinate - this plot has always a VERTICAL
    1192                // orientation
    1193                double sourceX = source.getX();
    1194                double anchorX = this.axis.java2DToValue(sourceX,
    1195                        info.getDataArea(), RectangleEdge.BOTTOM);
    1196                this.axis.resizeRange(factor, anchorX);
    1197            }
    1198            else {
    1199                this.axis.resizeRange(factor);
    1200            }
    1201
    1202        }
    1203
    1204        /**
    1205         * Zooms in on the range axes.
    1206         *
    1207         * @param lowerPercent  the new lower bound.
    1208         * @param upperPercent  the new upper bound.
    1209         * @param state  the plot state.
    1210         * @param source  the source point (in Java2D coordinates).
    1211         */
    1212        public void zoomRangeAxes(double lowerPercent, double upperPercent,
    1213                                  PlotRenderingInfo state, Point2D source) {
    1214            zoom((upperPercent + lowerPercent) / 2.0);
    1215        }
    1216
    1217        /**
    1218         * Returns <code>false</code> always.
    1219         *
    1220         * @return <code>false</code> always.
    1221         */
    1222        public boolean isDomainZoomable() {
    1223            return false;
    1224        }
    1225
    1226        /**
    1227         * Returns <code>true</code> to indicate that the range axis is zoomable.
    1228         *
    1229         * @return <code>true</code>.
    1230         */
    1231        public boolean isRangeZoomable() {
    1232            return true;
    1233        }
    1234
    1235        /**
    1236         * Returns the orientation of the plot.
    1237         *
    1238         * @return The orientation.
    1239         */
    1240        public PlotOrientation getOrientation() {
    1241            return PlotOrientation.HORIZONTAL;
    1242        }
    1243
    1244        /**
    1245         * Returns the upper bound of the radius axis.
    1246         *
    1247         * @return The upper bound.
    1248         */
    1249        public double getMaxRadius() {
    1250            return this.axis.getUpperBound();
    1251        }
    1252
    1253        /**
    1254         * Translates a (theta, radius) pair into Java2D coordinates.  If
    1255         * <code>radius</code> is less than the lower bound of the axis, then
    1256         * this method returns the centre point.
    1257         *
    1258         * @param angleDegrees  the angle in degrees.
    1259         * @param radius  the radius.
    1260         * @param dataArea  the data area.
    1261         *
    1262         * @return A point in Java2D space.
    1263         */
    1264        public Point translateValueThetaRadiusToJava2D(double angleDegrees,
    1265                                                       double radius,
    1266                                                       Rectangle2D dataArea) {
    1267
    1268            double radians = Math.toRadians(angleDegrees - 90.0);
    1269
    1270            double minx = dataArea.getMinX() + MARGIN;
    1271            double maxx = dataArea.getMaxX() - MARGIN;
    1272            double miny = dataArea.getMinY() + MARGIN;
    1273            double maxy = dataArea.getMaxY() - MARGIN;
    1274
    1275            double lengthX = maxx - minx;
    1276            double lengthY = maxy - miny;
    1277            double length = Math.min(lengthX, lengthY);
    1278
    1279            double midX = minx + lengthX / 2.0;
    1280            double midY = miny + lengthY / 2.0;
    1281
    1282            double axisMin = this.axis.getLowerBound();
    1283            double axisMax =  getMaxRadius();
    1284            double adjustedRadius = Math.max(radius, axisMin);
    1285
    1286            double xv = length / 2.0 * Math.cos(radians);
    1287            double yv = length / 2.0 * Math.sin(radians);
    1288
    1289            float x = (float) (midX + (xv * (adjustedRadius - axisMin)
    1290                    / (axisMax - axisMin)));
    1291            float y = (float) (midY + (yv * (adjustedRadius - axisMin)
    1292                    / (axisMax - axisMin)));
    1293
    1294            int ix = Math.round(x);
    1295            int iy = Math.round(y);
    1296
    1297            Point p = new Point(ix, iy);
    1298            return p;
    1299
    1300        }
    1301
    1302    }