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