    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     * DialPlot.java
    029     * -------------
    030     * (C) Copyright 2006-2008, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * Changes
    036     * -------
    037     * 03-Nov-2006 : Version 1 (DG);
    038     * 08-Mar-2007 : Fix in hashCode() (DG);
    039     * 17-Oct-2007 : Fixed listener registration/deregistration bugs (DG);
    040     * 24-Oct-2007 : Maintain pointers in their own list, so they can be
    041     *               drawn after other layers (DG);
    042     * 15-Feb-2007 : Fixed clipping bug (1873160) (DG);
    043     *
    044     */
    045
    046    package org.jfree.chart.plot.dial;
    047
    048    import java.awt.Graphics2D;
    049    import java.awt.Shape;
    050    import java.awt.geom.Point2D;
    051    import java.awt.geom.Rectangle2D;
    052    import java.io.IOException;
    053    import java.io.ObjectInputStream;
    054    import java.io.ObjectOutputStream;
    055    import java.util.Iterator;
    056    import java.util.List;
    057
    058    import org.jfree.chart.JFreeChart;
    059    import org.jfree.chart.event.PlotChangeEvent;
    060    import org.jfree.chart.plot.Plot;
    061    import org.jfree.chart.plot.PlotRenderingInfo;
    062    import org.jfree.chart.plot.PlotState;
    063    import org.jfree.data.general.DatasetChangeEvent;
    064    import org.jfree.data.general.ValueDataset;
    065    import org.jfree.util.ObjectList;
    066    import org.jfree.util.ObjectUtilities;
    067
    068    /**
    069     * A dial plot composed of user-definable layers.
    070     * The example shown here is generated by the <code>DialDemo2.java</code>
    071     * program included in the JFreeChart Demo Collection:
    072     * <br><br>
    073     * <img src="../../../../../images/DialPlotSample.png"
    074     * alt="DialPlotSample.png" />
    075     *
    076     * @since 1.0.7
    077     */
    078    public class DialPlot extends Plot implements DialLayerChangeListener {
    079
    080        /**
    081         * The background layer (optional).
    082         */
    083        private DialLayer background;
    084
    085        /**
    086         * The needle cap (optional).
    087         */
    088        private DialLayer cap;
    089
    090        /**
    091         * The dial frame.
    092         */
    093        private DialFrame dialFrame;
    094
    095        /**
    096         * The dataset(s) for the dial plot.
    097         */
    098        private ObjectList datasets;
    099
    100        /**
    101         * The scale(s) for the dial plot.
    102         */
    103        private ObjectList scales;
    104
    105        /** Storage for keys that map datasets to scales. */
    106        private ObjectList datasetToScaleMap;
    107
    108        /**
    109         * The drawing layers for the dial plot.
    110         */
    111        private List layers;
    112
    113        /**
    114         * The pointer(s) for the dial.
    115         */
    116        private List pointers;
    117
    118        /**
    119         * The x-coordinate for the view window.
    120         */
    121        private double viewX;
    122
    123        /**
    124         * The y-coordinate for the view window.
    125         */
    126        private double viewY;
    127
    128        /**
    129         * The width of the view window, expressed as a percentage.
    130         */
    131        private double viewW;
    132
    133        /**
    134         * The height of the view window, expressed as a percentage.
    135         */
    136        private double viewH;
    137
    138        /**
    139         * Creates a new instance of <code>DialPlot</code>.
    140         */
    141        public DialPlot() {
    142            this(null);
    143        }
    144
    145        /**
    146         * Creates a new instance of <code>DialPlot</code>.
    147         *
    148         * @param dataset  the dataset (<code>null</code> permitted).
    149         */
    150        public DialPlot(ValueDataset dataset) {
    151            this.background = null;
    152            this.cap = null;
    153            this.dialFrame = new ArcDialFrame();
    154            this.datasets = new ObjectList();
    155            if (dataset != null) {
    156                setDataset(dataset);
    157            }
    158            this.scales = new ObjectList();
    159            this.datasetToScaleMap = new ObjectList();
    160            this.layers = new java.util.ArrayList();
    161            this.pointers = new java.util.ArrayList();
    162            this.viewX = 0.0;
    163            this.viewY = 0.0;
    164            this.viewW = 1.0;
    165            this.viewH = 1.0;
    166        }
    167
    168        /**
    169         * Returns the background.
    170         *
    171         * @return The background (possibly <code>null</code>).
    172         *
    173         * @see #setBackground(DialLayer)
    174         */
    175        public DialLayer getBackground() {
    176            return this.background;
    177        }
    178
    179        /**
    180         * Sets the background layer and sends a {@link PlotChangeEvent} to all
    181         * registered listeners.
    182         *
    183         * @param background  the background layer (<code>null</code> permitted).
    184         *
    185         * @see #getBackground()
    186         */
    187        public void setBackground(DialLayer background) {
    188            if (this.background != null) {
    189                this.background.removeChangeListener(this);
    190            }
    191            this.background = background;
    192            if (background != null) {
    193                background.addChangeListener(this);
    194            }
    195            fireChangeEvent();
    196        }
    197
    198        /**
    199         * Returns the cap.
    200         *
    201         * @return The cap (possibly <code>null</code>).
    202         *
    203         * @see #setCap(DialLayer)
    204         */
    205        public DialLayer getCap() {
    206            return this.cap;
    207        }
    208
    209        /**
    210         * Sets the cap and sends a {@link PlotChangeEvent} to all registered
    211         * listeners.
    212         *
    213         * @param cap  the cap (<code>null</code> permitted).
    214         *
    215         * @see #getCap()
    216         */
    217        public void setCap(DialLayer cap) {
    218            if (this.cap != null) {
    219                this.cap.removeChangeListener(this);
    220            }
    221            this.cap = cap;
    222            if (cap != null) {
    223                cap.addChangeListener(this);
    224            }
    225            fireChangeEvent();
    226        }
    227
    228        /**
    229         * Returns the dial's frame.
    230         *
    231         * @return The dial's frame (never <code>null</code>).
    232         *
    233         * @see #setDialFrame(DialFrame)
    234         */
    235        public DialFrame getDialFrame() {
    236            return this.dialFrame;
    237        }
    238
    239        /**
    240         * Sets the dial's frame and sends a {@link PlotChangeEvent} to all
    241         * registered listeners.
    242         *
    243         * @param frame  the frame (<code>null</code> not permitted).
    244         *
    245         * @see #getDialFrame()
    246         */
    247        public void setDialFrame(DialFrame frame) {
    248            if (frame == null) {
    249                throw new IllegalArgumentException("Null 'frame' argument.");
    250            }
    251            this.dialFrame.removeChangeListener(this);
    252            this.dialFrame = frame;
    253            frame.addChangeListener(this);
    254            fireChangeEvent();
    255        }
    256
    257        /**
    258         * Returns the x-coordinate of the viewing rectangle.  This is specified
    259         * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
    260         *
    261         * @return The x-coordinate of the viewing rectangle.
    262         *
    263         * @see #setView(double, double, double, double)
    264         */
    265        public double getViewX() {
    266            return this.viewX;
    267        }
    268
    269        /**
    270         * Returns the y-coordinate of the viewing rectangle.  This is specified
    271         * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
    272         *
    273         * @return The y-coordinate of the viewing rectangle.
    274         *
    275         * @see #setView(double, double, double, double)
    276         */
    277        public double getViewY() {
    278            return this.viewY;
    279        }
    280
    281        /**
    282         * Returns the width of the viewing rectangle.  This is specified
    283         * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
    284         *
    285         * @return The width of the viewing rectangle.
    286         *
    287         * @see #setView(double, double, double, double)
    288         */
    289        public double getViewWidth() {
    290            return this.viewW;
    291        }
    292
    293        /**
    294         * Returns the height of the viewing rectangle.  This is specified
    295         * in the range 0.0 to 1.0, relative to the dial's framing rectangle.
    296         *
    297         * @return The height of the viewing rectangle.
    298         *
    299         * @see #setView(double, double, double, double)
    300         */
    301        public double getViewHeight() {
    302            return this.viewH;
    303        }
    304
    305        /**
    306         * Sets the viewing rectangle, relative to the dial's framing rectangle,
    307         * and sends a {@link PlotChangeEvent} to all registered listeners.
    308         *
    309         * @param x  the x-coordinate (in the range 0.0 to 1.0).
    310         * @param y  the y-coordinate (in the range 0.0 to 1.0).
    311         * @param w  the width (in the range 0.0 to 1.0).
    312         * @param h  the height (in the range 0.0 to 1.0).
    313         *
    314         * @see #getViewX()
    315         * @see #getViewY()
    316         * @see #getViewWidth()
    317         * @see #getViewHeight()
    318         */
    319        public void setView(double x, double y, double w, double h) {
    320            this.viewX = x;
    321            this.viewY = y;
    322            this.viewW = w;
    323            this.viewH = h;
    324            fireChangeEvent();
    325        }
    326
    327        /**
    328         * Adds a layer to the plot and sends a {@link PlotChangeEvent} to all
    329         * registered listeners.
    330         *
    331         * @param layer  the layer (<code>null</code> not permitted).
    332         */
    333        public void addLayer(DialLayer layer) {
    334            if (layer == null) {
    335                throw new IllegalArgumentException("Null 'layer' argument.");
    336            }
    337            this.layers.add(layer);
    338            layer.addChangeListener(this);
    339            fireChangeEvent();
    340        }
    341
    342        /**
    343         * Returns the index for the specified layer.
    344         *
    345         * @param layer  the layer (<code>null</code> not permitted).
    346         *
    347         * @return The layer index.
    348         */
    349        public int getLayerIndex(DialLayer layer) {
    350            if (layer == null) {
    351                throw new IllegalArgumentException("Null 'layer' argument.");
    352            }
    353            return this.layers.indexOf(layer);
    354        }
    355
    356        /**
    357         * Removes the layer at the specified index and sends a
    358         * {@link PlotChangeEvent} to all registered listeners.
    359         *
    360         * @param index  the index.
    361         */
    362        public void removeLayer(int index) {
    363            DialLayer layer = (DialLayer) this.layers.get(index);
    364            if (layer != null) {
    365                layer.removeChangeListener(this);
    366            }
    367            this.layers.remove(index);
    368            fireChangeEvent();
    369        }
    370
    371        /**
    372         * Removes the specified layer and sends a {@link PlotChangeEvent} to all
    373         * registered listeners.
    374         *
    375         * @param layer  the layer (<code>null</code> not permitted).
    376         */
    377        public void removeLayer(DialLayer layer) {
    378            // defer argument checking
    379            removeLayer(getLayerIndex(layer));
    380        }
    381
    382        /**
    383         * Adds a pointer to the plot and sends a {@link PlotChangeEvent} to all
    384         * registered listeners.
    385         *
    386         * @param pointer  the pointer (<code>null</code> not permitted).
    387         */
    388        public void addPointer(DialPointer pointer) {
    389            if (pointer == null) {
    390                throw new IllegalArgumentException("Null 'pointer' argument.");
    391            }
    392            this.pointers.add(pointer);
    393            pointer.addChangeListener(this);
    394            fireChangeEvent();
    395        }
    396
    397        /**
    398         * Returns the index for the specified pointer.
    399         *
    400         * @param pointer  the pointer (<code>null</code> not permitted).
    401         *
    402         * @return The pointer index.
    403         */
    404        public int getPointerIndex(DialPointer pointer) {
    405            if (pointer == null) {
    406                throw new IllegalArgumentException("Null 'pointer' argument.");
    407            }
    408            return this.pointers.indexOf(pointer);
    409        }
    410
    411        /**
    412         * Removes the pointer at the specified index and sends a
    413         * {@link PlotChangeEvent} to all registered listeners.
    414         *
    415         * @param index  the index.
    416         */
    417        public void removePointer(int index) {
    418            DialPointer pointer = (DialPointer) this.pointers.get(index);
    419            if (pointer != null) {
    420                pointer.removeChangeListener(this);
    421            }
    422            this.pointers.remove(index);
    423            fireChangeEvent();
    424        }
    425
    426        /**
    427         * Removes the specified pointer and sends a {@link PlotChangeEvent} to all
    428         * registered listeners.
    429         *
    430         * @param pointer  the pointer (<code>null</code> not permitted).
    431         */
    432        public void removePointer(DialPointer pointer) {
    433            // defer argument checking
    434            removeLayer(getPointerIndex(pointer));
    435        }
    436
    437        /**
    438         * Returns the dial pointer that is associated with the specified
    439         * dataset, or <code>null</code>.
    440         *
    441         * @param datasetIndex  the dataset index.
    442         *
    443         * @return The pointer.
    444         */
    445        public DialPointer getPointerForDataset(int datasetIndex) {
    446            DialPointer result = null;
    447            Iterator iterator = this.pointers.iterator();
    448            while (iterator.hasNext()) {
    449                DialPointer p = (DialPointer) iterator.next();
    450                if (p.getDatasetIndex() == datasetIndex) {
    451                    return p;
    452                }
    453            }
    454            return result;
    455        }
    456
    457        /**
    458         * Returns the primary dataset for the plot.
    459         *
    460         * @return The primary dataset (possibly <code>null</code>).
    461         */
    462        public ValueDataset getDataset() {
    463            return getDataset(0);
    464        }
    465
    466        /**
    467         * Returns the dataset at the given index.
    468         *
    469         * @param index  the dataset index.
    470         *
    471         * @return The dataset (possibly <code>null</code>).
    472         */
    473        public ValueDataset getDataset(int index) {
    474            ValueDataset result = null;
    475            if (this.datasets.size() > index) {
    476                result = (ValueDataset) this.datasets.get(index);
    477            }
    478            return result;
    479        }
    480
    481        /**
    482         * Sets the dataset for the plot, replacing the existing dataset, if there
    483         * is one, and sends a {@link PlotChangeEvent} to all registered
    484         * listeners.
    485         *
    486         * @param dataset  the dataset (<code>null</code> permitted).
    487         */
    488        public void setDataset(ValueDataset dataset) {
    489            setDataset(0, dataset);
    490        }
    491
    492        /**
    493         * Sets a dataset for the plot.
    494         *
    495         * @param index  the dataset index.
    496         * @param dataset  the dataset (<code>null</code> permitted).
    497         */
    498        public void setDataset(int index, ValueDataset dataset) {
    499
    500            ValueDataset existing = (ValueDataset) this.datasets.get(index);
    501            if (existing != null) {
    502                existing.removeChangeListener(this);
    503            }
    504            this.datasets.set(index, dataset);
    505            if (dataset != null) {
    506                dataset.addChangeListener(this);
    507            }
    508
    509            // send a dataset change event to self...
    510            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
    511            datasetChanged(event);
    512
    513        }
    514
    515        /**
    516         * Returns the number of datasets.
    517         *
    518         * @return The number of datasets.
    519         */
    520        public int getDatasetCount() {
    521            return this.datasets.size();
    522        }
    523
    524        /**
    525         * Draws the plot.  This method is usually called by the {@link JFreeChart}
    526         * instance that manages the plot.
    527         *
    528         * @param g2  the graphics target.
    529         * @param area  the area in which the plot should be drawn.
    530         * @param anchor  the anchor point (typically the last point that the
    531         *     mouse clicked on, <code>null</code> is permitted).
    532         * @param parentState  the state for the parent plot (if any).
    533         * @param info  used to collect plot rendering info (<code>null</code>
    534         *     permitted).
    535         */
    536        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
    537                PlotState parentState, PlotRenderingInfo info) {
    538
    539            Shape origClip = g2.getClip();
    540            g2.setClip(area);
    541
    542            // first, expand the viewing area into a drawing frame
    543            Rectangle2D frame = viewToFrame(area);
    544
    545            // draw the background if there is one...
    546            if (this.background != null && this.background.isVisible()) {
    547                if (this.background.isClippedToWindow()) {
    548                    Shape savedClip = g2.getClip();
    549                    g2.clip(this.dialFrame.getWindow(frame));
    550                    this.background.draw(g2, this, frame, area);
    551                    g2.setClip(savedClip);
    552                }
    553                else {
    554                    this.background.draw(g2, this, frame, area);
    555                }
    556            }
    557
    558            Iterator iterator = this.layers.iterator();
    559            while (iterator.hasNext()) {
    560                DialLayer current = (DialLayer) iterator.next();
    561                if (current.isVisible()) {
    562                    if (current.isClippedToWindow()) {
    563                        Shape savedClip = g2.getClip();
    564                        g2.clip(this.dialFrame.getWindow(frame));
    565                        current.draw(g2, this, frame, area);
    566                        g2.setClip(savedClip);
    567                    }
    568                    else {
    569                        current.draw(g2, this, frame, area);
    570                    }
    571                }
    572            }
    573
    574            // draw the pointers
    575            iterator = this.pointers.iterator();
    576            while (iterator.hasNext()) {
    577                DialPointer current = (DialPointer) iterator.next();
    578                if (current.isVisible()) {
    579                    if (current.isClippedToWindow()) {
    580                        Shape savedClip = g2.getClip();
    581                        g2.clip(this.dialFrame.getWindow(frame));
    582                        current.draw(g2, this, frame, area);
    583                        g2.setClip(savedClip);
    584                    }
    585                    else {
    586                        current.draw(g2, this, frame, area);
    587                    }
    588                }
    589            }
    590
    591            // draw the cap if there is one...
    592            if (this.cap != null && this.cap.isVisible()) {
    593                if (this.cap.isClippedToWindow()) {
    594                    Shape savedClip = g2.getClip();
    595                    g2.clip(this.dialFrame.getWindow(frame));
    596                    this.cap.draw(g2, this, frame, area);
    597                    g2.setClip(savedClip);
    598                }
    599                else {
    600                    this.cap.draw(g2, this, frame, area);
    601                }
    602            }
    603
    604            if (this.dialFrame.isVisible()) {
    605                this.dialFrame.draw(g2, this, frame, area);
    606            }
    607
    608            g2.setClip(origClip);
    609
    610        }
    611
    612        /**
    613         * Returns the frame surrounding the specified view rectangle.
    614         *
    615         * @param view  the view rectangle (<code>null</code> not permitted).
    616         *
    617         * @return The frame rectangle.
    618         */
    619        private Rectangle2D viewToFrame(Rectangle2D view) {
    620            double width = view.getWidth() / this.viewW;
    621            double height = view.getHeight() / this.viewH;
    622            double x = view.getX() - (width * this.viewX);
    623            double y = view.getY() - (height * this.viewY);
    624            return new Rectangle2D.Double(x, y, width, height);
    625        }
    626
    627        /**
    628         * Returns the value from the specified dataset.
    629         *
    630         * @param datasetIndex  the dataset index.
    631         *
    632         * @return The data value.
    633         */
    634        public double getValue(int datasetIndex) {
    635            double result = Double.NaN;
    636            ValueDataset dataset = getDataset(datasetIndex);
    637            if (dataset != null) {
    638                Number n = dataset.getValue();
    639                if (n != null) {
    640                    result = n.doubleValue();
    641                }
    642            }
    643            return result;
    644        }
    645
    646        /**
    647         * Adds a dial scale to the plot and sends a {@link PlotChangeEvent} to
    648         * all registered listeners.
    649         *
    650         * @param index  the scale index.
    651         * @param scale  the scale (<code>null</code> not permitted).
    652         */
    653        public void addScale(int index, DialScale scale) {
    654            if (scale == null) {
    655                throw new IllegalArgumentException("Null 'scale' argument.");
    656            }
    657            DialScale existing = (DialScale) this.scales.get(index);
    658            if (existing != null) {
    659                removeLayer(existing);
    660            }
    661            this.layers.add(scale);
    662            this.scales.set(index, scale);
    663            scale.addChangeListener(this);
    664            fireChangeEvent();
    665        }
    666
    667        /**
    668         * Returns the scale at the given index.
    669         *
    670         * @param index  the scale index.
    671         *
    672         * @return The scale (possibly <code>null</code>).
    673         */
    674        public DialScale getScale(int index) {
    675            DialScale result = null;
    676            if (this.scales.size() > index) {
    677                result = (DialScale) this.scales.get(index);
    678            }
    679            return result;
    680        }
    681
    682        /**
    683         * Maps a dataset to a particular scale.
    684         *
    685         * @param index  the dataset index (zero-based).
    686         * @param scaleIndex  the scale index (zero-based).
    687         */
    688        public void mapDatasetToScale(int index, int scaleIndex) {
    689            this.datasetToScaleMap.set(index, new Integer(scaleIndex));
    690            fireChangeEvent();
    691        }
    692
    693        /**
    694         * Returns the dial scale for a specific dataset.
    695         *
    696         * @param datasetIndex  the dataset index.
    697         *
    698         * @return The dial scale.
    699         */
    700        public DialScale getScaleForDataset(int datasetIndex) {
    701            DialScale result = (DialScale) this.scales.get(0);
    702            Integer scaleIndex = (Integer) this.datasetToScaleMap.get(datasetIndex);
    703            if (scaleIndex != null) {
    704                result = getScale(scaleIndex.intValue());
    705            }
    706            return result;
    707        }
    708
    709        /**
    710         * A utility method that computes a rectangle using relative radius values.
    711         *
    712         * @param rect  the reference rectangle (<code>null</code> not permitted).
    713         * @param radiusW  the width radius (must be > 0.0)
    714         * @param radiusH  the height radius.
    715         *
    716         * @return A new rectangle.
    717         */
    718        public static Rectangle2D rectangleByRadius(Rectangle2D rect,
    719                double radiusW, double radiusH) {
    720            if (rect == null) {
    721                throw new IllegalArgumentException("Null 'rect' argument.");
    722            }
    723            double x = rect.getCenterX();
    724            double y = rect.getCenterY();
    725            double w = rect.getWidth() * radiusW;
    726            double h = rect.getHeight() * radiusH;
    727            return new Rectangle2D.Double(x - w / 2.0, y - h / 2.0, w, h);
    728        }
    729
    730        /**
    731         * Receives notification when a layer has changed, and responds by
    732         * forwarding a {@link PlotChangeEvent} to all registered listeners.
    733         *
    734         * @param event  the event.
    735         */
    736        public void dialLayerChanged(DialLayerChangeEvent event) {
    737            fireChangeEvent();
    738        }
    739
    740        /**
    741         * Tests this <code>DialPlot</code> instance for equality with an
    742         * arbitrary object.  The plot's dataset(s) is (are) not included in
    743         * the test.
    744         *
    745         * @param obj  the object (<code>null</code> permitted).
    746         *
    747         * @return A boolean.
    748         */
    749        public boolean equals(Object obj) {
    750            if (obj == this) {
    751                return true;
    752            }
    753            if (!(obj instanceof DialPlot)) {
    754                return false;
    755            }
    756            DialPlot that = (DialPlot) obj;
    757            if (!ObjectUtilities.equal(this.background, that.background)) {
    758                return false;
    759            }
    760            if (!ObjectUtilities.equal(this.cap, that.cap)) {
    761                return false;
    762            }
    763            if (!this.dialFrame.equals(that.dialFrame)) {
    764                return false;
    765            }
    766            if (this.viewX != that.viewX) {
    767                return false;
    768            }
    769            if (this.viewY != that.viewY) {
    770                return false;
    771            }
    772            if (this.viewW != that.viewW) {
    773                return false;
    774            }
    775            if (this.viewH != that.viewH) {
    776                return false;
    777            }
    778            if (!this.layers.equals(that.layers)) {
    779                return false;
    780            }
    781            if (!this.pointers.equals(that.pointers)) {
    782                return false;
    783            }
    784            return super.equals(obj);
    785        }
    786
    787        /**
    788         * Returns a hash code for this instance.
    789         *
    790         * @return The hash code.
    791         */
    792        public int hashCode() {
    793            int result = 193;
    794            result = 37 * result + ObjectUtilities.hashCode(this.background);
    795            result = 37 * result + ObjectUtilities.hashCode(this.cap);
    796            result = 37 * result + this.dialFrame.hashCode();
    797            long temp = Double.doubleToLongBits(this.viewX);
    798            result = 37 * result + (int) (temp ^ (temp >>> 32));
    799            temp = Double.doubleToLongBits(this.viewY);
    800            result = 37 * result + (int) (temp ^ (temp >>> 32));
    801            temp = Double.doubleToLongBits(this.viewW);
    802            result = 37 * result + (int) (temp ^ (temp >>> 32));
    803            temp = Double.doubleToLongBits(this.viewH);
    804            result = 37 * result + (int) (temp ^ (temp >>> 32));
    805            return result;
    806        }
    807
    808        /**
    809         * Returns the plot type.
    810         *
    811         * @return <code>"DialPlot"</code>
    812         */
    813        public String getPlotType() {
    814            return "DialPlot";
    815        }
    816
    817        /**
    818         * Provides serialization support.
    819         *
    820         * @param stream  the output stream.
    821         *
    822         * @throws IOException  if there is an I/O error.
    823         */
    824        private void writeObject(ObjectOutputStream stream) throws IOException {
    825            stream.defaultWriteObject();
    826        }
    827
    828        /**
    829         * Provides serialization support.
    830         *
    831         * @param stream  the input stream.
    832         *
    833         * @throws IOException  if there is an I/O error.
    834         * @throws ClassNotFoundException  if there is a classpath problem.
    835         */
    836        private void readObject(ObjectInputStream stream)
    837                throws IOException, ClassNotFoundException {
    838            stream.defaultReadObject();
    839        }
    840
    841
    842    }