    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * -------------
    028 * 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
    046package org.jfree.chart.plot.dial;
    047
    048import java.awt.Graphics2D;
    049import java.awt.Shape;
    050import java.awt.geom.Point2D;
    051import java.awt.geom.Rectangle2D;
    052import java.io.IOException;
    053import java.io.ObjectInputStream;
    054import java.io.ObjectOutputStream;
    055import java.util.Iterator;
    056import java.util.List;
    057
    058import org.jfree.chart.JFreeChart;
    059import org.jfree.chart.event.PlotChangeEvent;
    060import org.jfree.chart.plot.Plot;
    061import org.jfree.chart.plot.PlotRenderingInfo;
    062import org.jfree.chart.plot.PlotState;
    063import org.jfree.data.general.DatasetChangeEvent;
    064import org.jfree.data.general.ValueDataset;
    065import org.jfree.util.ObjectList;
    066import 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 */
    078public 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}