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