    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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     * FastScatterPlot.java
    029     * --------------------
    030     * (C) Copyright 2002-2005, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Arnaud Lelievre;
    034     *
    035     * $Id: FastScatterPlot.java,v 1.11.2.3 2005/10/25 20:52:08 mungady Exp $
    036     *
    037     * Changes (from 29-Oct-2002)
    038     * --------------------------
    039     * 29-Oct-2002 : Added standard header (DG);
    040     * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG);
    041     * 26-Mar-2003 : Implemented Serializable (DG);
    042     * 19-Aug-2003 : Implemented Cloneable (DG);
    043     * 08-Sep-2003 : Added internationalization via use of properties
    044     *               resourceBundle (RFE 690236) (AL);
    045     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    046     * 12-Nov-2003 : Implemented zooming (DG);
    047     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
    048     * 26-Jan-2004 : Added domain and range grid lines (DG);
    049     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    050     * 29-Sep-2004 : Removed hard-coded color (DG);
    051     * 04-Oct-2004 : Reworked equals() method and renamed ArrayUtils
    052     *               --> ArrayUtilities (DG);
    053     * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
    054     * 05-May-2005 : Updated draw() method parameters (DG);
    055     * 16-Jun-2005 : Added get/setData() methods (DG);
    056     *
    057     */
    058
    059    package org.jfree.chart.plot;
    060
    061    import java.awt.AlphaComposite;
    062    import java.awt.BasicStroke;
    063    import java.awt.Color;
    064    import java.awt.Composite;
    065    import java.awt.Graphics2D;
    066    import java.awt.Paint;
    067    import java.awt.Shape;
    068    import java.awt.Stroke;
    069    import java.awt.geom.Line2D;
    070    import java.awt.geom.Point2D;
    071    import java.awt.geom.Rectangle2D;
    072    import java.io.IOException;
    073    import java.io.ObjectInputStream;
    074    import java.io.ObjectOutputStream;
    075    import java.io.Serializable;
    076    import java.util.Iterator;
    077    import java.util.List;
    078    import java.util.ResourceBundle;
    079
    080    import org.jfree.chart.axis.AxisSpace;
    081    import org.jfree.chart.axis.AxisState;
    082    import org.jfree.chart.axis.ValueAxis;
    083    import org.jfree.chart.axis.ValueTick;
    084    import org.jfree.chart.event.PlotChangeEvent;
    085    import org.jfree.data.Range;
    086    import org.jfree.io.SerialUtilities;
    087    import org.jfree.ui.RectangleEdge;
    088    import org.jfree.ui.RectangleInsets;
    089    import org.jfree.util.ArrayUtilities;
    090    import org.jfree.util.ObjectUtilities;
    091    import org.jfree.util.PaintUtilities;
    092
    093    /**
    094     * A fast scatter plot.
    095     */
    096    public class FastScatterPlot extends Plot implements ValueAxisPlot,
    097                                                         Zoomable,
    098                                                         Cloneable, Serializable {
    099
    100        /** For serialization. */
    101        private static final long serialVersionUID = 7871545897358563521L;
    102
    103        /** The default grid line stroke. */
    104        public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
    105                BasicStroke.CAP_BUTT,
    106                BasicStroke.JOIN_BEVEL,
    107                0.0f,
    108                new float[] {2.0f, 2.0f},
    109                0.0f);
    110
    111        /** The default grid line paint. */
    112        public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
    113
    114        /** The data. */
    115        private float[][] data;
    116
    117        /** The x data range. */
    118        private Range xDataRange;
    119
    120        /** The y data range. */
    121        private Range yDataRange;
    122
    123        /** The domain axis (used for the x-values). */
    124        private ValueAxis domainAxis;
    125
    126        /** The range axis (used for the y-values). */
    127        private ValueAxis rangeAxis;
    128
    129        /** The paint used to plot data points. */
    130        private transient Paint paint;
    131
    132        /** A flag that controls whether the domain grid-lines are visible. */
    133        private boolean domainGridlinesVisible;
    134
    135        /** The stroke used to draw the domain grid-lines. */
    136        private transient Stroke domainGridlineStroke;
    137
    138        /** The paint used to draw the domain grid-lines. */
    139        private transient Paint domainGridlinePaint;
    140
    141        /** A flag that controls whether the range grid-lines are visible. */
    142        private boolean rangeGridlinesVisible;
    143
    144        /** The stroke used to draw the range grid-lines. */
    145        private transient Stroke rangeGridlineStroke;
    146
    147        /** The paint used to draw the range grid-lines. */
    148        private transient Paint rangeGridlinePaint;
    149
    150        /** The resourceBundle for the localization. */
    151        protected static ResourceBundle localizationResources =
    152            ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
    153
    154        /**
    155         * Creates an empty plot.
    156         */
    157        public FastScatterPlot() {
    158            this(null, null, null);
    159        }
    160
    161        /**
    162         * Creates a new fast scatter plot.
    163         * <P>
    164         * The data is an array of x, y values:  data[0][i] = x, data[1][i] = y.
    165         *
    166         * @param data  the data.
    167         * @param domainAxis  the domain (x) axis.
    168         * @param rangeAxis  the range (y) axis.
    169         */
    170        public FastScatterPlot(float[][] data,
    171                               ValueAxis domainAxis, ValueAxis rangeAxis) {
    172
    173            super();
    174
    175            this.data = data;
    176            this.xDataRange = calculateXDataRange(data);
    177            this.yDataRange = calculateYDataRange(data);
    178            this.domainAxis = domainAxis;
    179            if (domainAxis != null) {
    180                domainAxis.setPlot(this);
    181                domainAxis.addChangeListener(this);
    182            }
    183
    184            this.rangeAxis = rangeAxis;
    185            if (rangeAxis != null) {
    186                rangeAxis.setPlot(this);
    187                rangeAxis.addChangeListener(this);
    188            }
    189
    190            this.paint = Color.red;
    191
    192            this.domainGridlinesVisible = true;
    193            this.domainGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
    194            this.domainGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
    195
    196            this.rangeGridlinesVisible = true;
    197            this.rangeGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
    198            this.rangeGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
    199
    200        }
    201
    202        /**
    203         * Returns a short string describing the plot type.
    204         *
    205         * @return A short string describing the plot type.
    206         */
    207        public String getPlotType() {
    208            return localizationResources.getString("Fast_Scatter_Plot");
    209        }
    210
    211        /**
    212         * Returns the data array used by the plot.
    213         *
    214         * @return The data array (possibly <code>null</code>).
    215         */
    216        public float[][] getData() {
    217            return this.data;
    218        }
    219
    220        /**
    221         * Sets the data array used by the plot and sends a {@link PlotChangeEvent}
    222         * to all registered listeners.
    223         *
    224         * @param data  the data array (<code>null</code> permitted).
    225         */
    226        public void setData(float[][] data) {
    227            this.data = data;
    228            notifyListeners(new PlotChangeEvent(this));
    229        }
    230
    231        /**
    232         * Returns the orientation of the plot.
    233         *
    234         * @return The orientation (always {@link PlotOrientation#VERTICAL}).
    235         */
    236        public PlotOrientation getOrientation() {
    237            return PlotOrientation.VERTICAL;
    238        }
    239
    240        /**
    241         * Returns the domain axis for the plot.  If the domain axis for this plot
    242         * is null, then the method will return the parent plot's domain axis (if
    243         * there is a parent plot).
    244         *
    245         * @return The domain axis.
    246         */
    247        public ValueAxis getDomainAxis() {
    248            return this.domainAxis;
    249        }
    250
    251        /**
    252         * Returns the range axis for the plot.  If the range axis for this plot is
    253         * null, then the method will return the parent plot's range axis (if
    254         * there is a parent plot).
    255         *
    256         * @return The range axis.
    257         */
    258        public ValueAxis getRangeAxis() {
    259            return this.rangeAxis;
    260        }
    261
    262        /**
    263         * Returns the paint used to plot data points.
    264         *
    265         * @return The paint.
    266         */
    267        public Paint getPaint() {
    268            return this.paint;
    269        }
    270
    271        /**
    272         * Sets the color for the data points and sends a {@link PlotChangeEvent}
    273         * to all registered listeners.
    274         *
    275         * @param paint  the paint (<code>null</code> not permitted).
    276         */
    277        public void setPaint(Paint paint) {
    278            if (paint == null) {
    279                throw new IllegalArgumentException("Null 'paint' argument.");
    280            }
    281            this.paint = paint;
    282            notifyListeners(new PlotChangeEvent(this));
    283        }
    284
    285        /**
    286         * Returns <code>true</code> if the domain gridlines are visible, and
    287         * <code>false<code> otherwise.
    288         *
    289         * @return <code>true</code> or <code>false</code>.
    290         */
    291        public boolean isDomainGridlinesVisible() {
    292            return this.domainGridlinesVisible;
    293        }
    294
    295        /**
    296         * Sets the flag that controls whether or not the domain grid-lines are
    297         * visible.  If the flag value is changed, a {@link PlotChangeEvent} is
    298         * sent to all registered listeners.
    299         *
    300         * @param visible  the new value of the flag.
    301         */
    302        public void setDomainGridlinesVisible(boolean visible) {
    303            if (this.domainGridlinesVisible != visible) {
    304                this.domainGridlinesVisible = visible;
    305                notifyListeners(new PlotChangeEvent(this));
    306            }
    307        }
    308
    309        /**
    310         * Returns the stroke for the grid-lines (if any) plotted against the
    311         * domain axis.
    312         *
    313         * @return The stroke.
    314         */
    315        public Stroke getDomainGridlineStroke() {
    316            return this.domainGridlineStroke;
    317        }
    318
    319        /**
    320         * Sets the stroke for the grid lines plotted against the domain axis.
    321         * <p>
    322         * If you set this to <code>null</code>, no grid lines will be drawn.
    323         *
    324         * @param stroke  the stroke (<code>null</code> permitted).
    325         */
    326        public void setDomainGridlineStroke(Stroke stroke) {
    327            this.domainGridlineStroke = stroke;
    328            notifyListeners(new PlotChangeEvent(this));
    329        }
    330
    331        /**
    332         * Returns the paint for the grid lines (if any) plotted against the domain
    333         * axis.
    334         *
    335         * @return The paint.
    336         */
    337        public Paint getDomainGridlinePaint() {
    338            return this.domainGridlinePaint;
    339        }
    340
    341        /**
    342         * Sets the paint for the grid lines plotted against the domain axis.
    343         * <p>
    344         * If you set this to <code>null</code>, no grid lines will be drawn.
    345         *
    346         * @param paint  the paint (<code>null</code> permitted).
    347         */
    348        public void setDomainGridlinePaint(Paint paint) {
    349            this.domainGridlinePaint = paint;
    350            notifyListeners(new PlotChangeEvent(this));
    351        }
    352
    353        /**
    354         * Returns <code>true</code> if the range axis grid is visible, and
    355         * <code>false<code> otherwise.
    356         *
    357         * @return <code>true</code> or <code>false</code>.
    358         */
    359        public boolean isRangeGridlinesVisible() {
    360            return this.rangeGridlinesVisible;
    361        }
    362
    363        /**
    364         * Sets the flag that controls whether or not the range axis grid lines are
    365         * visible.  If the flag value is changed, a {@link PlotChangeEvent} is
    366         * sent to all registered listeners.
    367         *
    368         * @param visible  the new value of the flag.
    369         */
    370        public void setRangeGridlinesVisible(boolean visible) {
    371            if (this.rangeGridlinesVisible != visible) {
    372                this.rangeGridlinesVisible = visible;
    373                notifyListeners(new PlotChangeEvent(this));
    374            }
    375        }
    376
    377        /**
    378         * Returns the stroke for the grid lines (if any) plotted against the range
    379         * axis.
    380         *
    381         * @return The stroke.
    382         */
    383        public Stroke getRangeGridlineStroke() {
    384            return this.rangeGridlineStroke;
    385        }
    386
    387        /**
    388         * Sets the stroke for the grid lines plotted against the range axis.
    389         * <p>
    390         * If you set this to <code>null</code>, no grid lines will be drawn.
    391         *
    392         * @param stroke  the stroke (<code>null</code> permitted).
    393         */
    394        public void setRangeGridlineStroke(Stroke stroke) {
    395            this.rangeGridlineStroke = stroke;
    396            notifyListeners(new PlotChangeEvent(this));
    397        }
    398
    399        /**
    400         * Returns the paint for the grid lines (if any) plotted against the range
    401         * axis.
    402         *
    403         * @return The paint.
    404         */
    405        public Paint getRangeGridlinePaint() {
    406            return this.rangeGridlinePaint;
    407        }
    408
    409        /**
    410         * Sets the paint for the grid lines plotted against the range axis.
    411         * <p>
    412         * If you set this to <code>null</code>, no grid lines will be drawn.
    413         *
    414         * @param paint  the paint (<code>null</code> permitted).
    415         */
    416        public void setRangeGridlinePaint(Paint paint) {
    417            this.rangeGridlinePaint = paint;
    418            notifyListeners(new PlotChangeEvent(this));
    419        }
    420
    421        /**
    422         * Draws the fast scatter plot on a Java 2D graphics device (such as the
    423         * screen or a printer).
    424         *
    425         * @param g2  the graphics device.
    426         * @param area   the area within which the plot (including axis labels)
    427         *                   should be drawn.
    428         * @param anchor  the anchor point (<code>null</code> permitted).
    429         * @param parentState  the state from the parent plot, if there is one.
    430         * @param info  collects chart drawing information (<code>null</code>
    431         *              permitted).
    432         */
    433        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
    434                         PlotState parentState,
    435                         PlotRenderingInfo info) {
    436
    437            // set up info collection...
    438            if (info != null) {
    439                info.setPlotArea(area);
    440            }
    441
    442            // adjust the drawing area for plot insets (if any)...
    443            RectangleInsets insets = getInsets();
    444            insets.trim(area);
    445
    446            AxisSpace space = new AxisSpace();
    447            space = this.domainAxis.reserveSpace(
    448                g2, this, area, RectangleEdge.BOTTOM, space
    449            );
    450            space = this.rangeAxis.reserveSpace(
    451                g2, this, area, RectangleEdge.LEFT, space
    452            );
    453            Rectangle2D dataArea = space.shrink(area, null);
    454
    455            if (info != null) {
    456                info.setDataArea(dataArea);
    457            }
    458
    459            // draw the plot background and axes...
    460            drawBackground(g2, dataArea);
    461
    462            AxisState domainAxisState = null;
    463            AxisState rangeAxisState = null;
    464            if (this.domainAxis != null) {
    465                domainAxisState = this.domainAxis.draw(
    466                    g2, dataArea.getMaxY(), area, dataArea,
    467                    RectangleEdge.BOTTOM, info
    468                );
    469            }
    470            if (this.rangeAxis != null) {
    471                rangeAxisState = this.rangeAxis.draw(
    472                    g2, dataArea.getMinX(), area, dataArea,
    473                    RectangleEdge.LEFT, info
    474                );
    475            }
    476            drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
    477            drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
    478
    479            Shape originalClip = g2.getClip();
    480            Composite originalComposite = g2.getComposite();
    481
    482            g2.clip(dataArea);
    483            g2.setComposite(
    484                AlphaComposite.getInstance(
    485                    AlphaComposite.SRC_OVER, getForegroundAlpha()
    486                )
    487            );
    488
    489            render(g2, dataArea, info, null);
    490
    491            g2.setClip(originalClip);
    492            g2.setComposite(originalComposite);
    493            drawOutline(g2, dataArea);
    494
    495        }
    496
    497        /**
    498         * Draws a representation of the data within the dataArea region.  The
    499         * <code>info</code> and <code>crosshairState</code> arguments may be
    500         * <code>null</code>.
    501         *
    502         * @param g2  the graphics device.
    503         * @param dataArea  the region in which the data is to be drawn.
    504         * @param info  an optional object for collection dimension information.
    505         * @param crosshairState  collects crosshair information (<code>null</code>
    506         *                        permitted).
    507         */
    508        public void render(Graphics2D g2, Rectangle2D dataArea,
    509                           PlotRenderingInfo info, CrosshairState crosshairState) {
    510
    511
    512            //long start = System.currentTimeMillis();
    513            //System.out.println("Start: " + start);
    514            g2.setPaint(this.paint);
    515
    516            // if the axes use a linear scale, you can uncomment the code below and
    517            // switch to the alternative transX/transY calculation inside the loop
    518            // that follows - it is a little bit faster then.
    519            //
    520            // int xx = (int) dataArea.getMinX();
    521            // int ww = (int) dataArea.getWidth();
    522            // int yy = (int) dataArea.getMaxY();
    523            // int hh = (int) dataArea.getHeight();
    524            // double domainMin = this.domainAxis.getLowerBound();
    525            // double domainLength = this.domainAxis.getUpperBound() - domainMin;
    526            // double rangeMin = this.rangeAxis.getLowerBound();
    527            // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin;
    528
    529            if (this.data != null) {
    530                for (int i = 0; i < this.data[0].length; i++) {
    531                    float x = this.data[0][i];
    532                    float y = this.data[1][i];
    533
    534                    //int transX = (int) (xx + ww * (x - domainMin) / domainLength);
    535                    //int transY = (int) (yy - hh * (y - rangeMin) / rangeLength);
    536                    int transX = (int) this.domainAxis.valueToJava2D(
    537                        x, dataArea, RectangleEdge.BOTTOM
    538                    );
    539                    int transY = (int) this.rangeAxis.valueToJava2D(
    540                        y, dataArea, RectangleEdge.LEFT
    541                    );
    542                    g2.fillRect(transX, transY, 1, 1);
    543                }
    544            }
    545            //long finish = System.currentTimeMillis();
    546            //System.out.println("Finish: " + finish);
    547            //System.out.println("Time: " + (finish - start));
    548
    549        }
    550
    551        /**
    552         * Draws the gridlines for the plot, if they are visible.
    553         *
    554         * @param g2  the graphics device.
    555         * @param dataArea  the data area.
    556         * @param ticks  the ticks.
    557         */
    558        protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea,
    559                                           List ticks) {
    560
    561            // draw the domain grid lines, if any...
    562            if (isDomainGridlinesVisible()) {
    563                Stroke gridStroke = getDomainGridlineStroke();
    564                Paint gridPaint = getDomainGridlinePaint();
    565                if ((gridStroke != null) && (gridPaint != null)) {
    566                    Iterator iterator = ticks.iterator();
    567                    while (iterator.hasNext()) {
    568                        ValueTick tick = (ValueTick) iterator.next();
    569                        double v = this.domainAxis.valueToJava2D(
    570                            tick.getValue(), dataArea, RectangleEdge.BOTTOM
    571                        );
    572                        Line2D line = new Line2D.Double(
    573                            v, dataArea.getMinY(), v, dataArea.getMaxY()
    574                        );
    575                        g2.setPaint(gridPaint);
    576                        g2.setStroke(gridStroke);
    577                        g2.draw(line);
    578                    }
    579                }
    580            }
    581        }
    582
    583        /**
    584         * Draws the gridlines for the plot, if they are visible.
    585         *
    586         * @param g2  the graphics device.
    587         * @param dataArea  the data area.
    588         * @param ticks  the ticks.
    589         */
    590        protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea,
    591                                          List ticks) {
    592
    593            // draw the range grid lines, if any...
    594            if (isRangeGridlinesVisible()) {
    595                Stroke gridStroke = getRangeGridlineStroke();
    596                Paint gridPaint = getRangeGridlinePaint();
    597                if ((gridStroke != null) && (gridPaint != null)) {
    598                    Iterator iterator = ticks.iterator();
    599                    while (iterator.hasNext()) {
    600                        ValueTick tick = (ValueTick) iterator.next();
    601                        double v = this.rangeAxis.valueToJava2D(
    602                            tick.getValue(), dataArea, RectangleEdge.LEFT
    603                        );
    604                        Line2D line = new Line2D.Double(
    605                            dataArea.getMinX(), v, dataArea.getMaxX(), v
    606                        );
    607                        g2.setPaint(gridPaint);
    608                        g2.setStroke(gridStroke);
    609                        g2.draw(line);
    610                    }
    611                }
    612            }
    613
    614        }
    615
    616        /**
    617         * Returns the range of data values to be plotted along the axis.
    618         *
    619         * @param axis  the axis.
    620         *
    621         * @return The range.
    622         */
    623        public Range getDataRange(ValueAxis axis) {
    624
    625            Range result = null;
    626            if (axis == this.domainAxis) {
    627                result = this.xDataRange;
    628            }
    629            else if (axis == this.rangeAxis) {
    630                result = this.yDataRange;
    631            }
    632            return result;
    633        }
    634
    635        /**
    636         * Calculates the X data range.
    637         *
    638         * @param data  the data.
    639         *
    640         * @return The range.
    641         */
    642        private Range calculateXDataRange(float[][] data) {
    643
    644            Range result = null;
    645
    646            if (data != null) {
    647                float lowest = Float.POSITIVE_INFINITY;
    648                float highest = Float.NEGATIVE_INFINITY;
    649                for (int i = 0; i < data[0].length; i++) {
    650                    float v = data[0][i];
    651                    if (v < lowest) {
    652                        lowest = v;
    653                    }
    654                    if (v > highest) {
    655                        highest = v;
    656                    }
    657                }
    658                if (lowest <= highest) {
    659                    result = new Range(lowest, highest);
    660                }
    661            }
    662
    663            return result;
    664
    665        }
    666
    667        /**
    668         * Calculates the Y data range.
    669         *
    670         * @param data  the data.
    671         *
    672         * @return The range.
    673         */
    674        private Range calculateYDataRange(float[][] data) {
    675
    676            Range result = null;
    677
    678            if (data != null) {
    679                float lowest = Float.POSITIVE_INFINITY;
    680                float highest = Float.NEGATIVE_INFINITY;
    681                for (int i = 0; i < data[0].length; i++) {
    682                    float v = data[1][i];
    683                    if (v < lowest) {
    684                        lowest = v;
    685                    }
    686                    if (v > highest) {
    687                        highest = v;
    688                    }
    689                }
    690                if (lowest <= highest) {
    691                    result = new Range(lowest, highest);
    692                }
    693            }
    694            return result;
    695
    696        }
    697
    698        /**
    699         * Multiplies the range on the domain axis/axes by the specified factor.
    700         *
    701         * @param factor  the zoom factor.
    702         * @param info  the plot rendering info.
    703         * @param source  the source point.
    704         */
    705        public void zoomDomainAxes(double factor, PlotRenderingInfo info,
    706                                   Point2D source) {
    707            this.domainAxis.resizeRange(factor);
    708        }
    709
    710        /**
    711         * Zooms in on the domain axes.
    712         *
    713         * @param lowerPercent  the new lower bound as a percentage of the current
    714         *                      range.
    715         * @param upperPercent  the new upper bound as a percentage of the current
    716         *                      range.
    717         * @param info  the plot rendering info.
    718         * @param source  the source point.
    719         */
    720        public void zoomDomainAxes(double lowerPercent, double upperPercent,
    721                                   PlotRenderingInfo info, Point2D source) {
    722            this.domainAxis.zoomRange(lowerPercent, upperPercent);
    723        }
    724
    725        /**
    726         * Multiplies the range on the range axis/axes by the specified factor.
    727         *
    728         * @param factor  the zoom factor.
    729         * @param info  the plot rendering info.
    730         * @param source  the source point.
    731         */
    732        public void zoomRangeAxes(double factor,
    733                                  PlotRenderingInfo info, Point2D source) {
    734            this.rangeAxis.resizeRange(factor);
    735        }
    736
    737        /**
    738         * Zooms in on the range axes.
    739         *
    740         * @param lowerPercent  the new lower bound as a percentage of the current
    741         *                      range.
    742         * @param upperPercent  the new upper bound as a percentage of the current
    743         *                      range.
    744         * @param info  the plot rendering info.
    745         * @param source  the source point.
    746         */
    747        public void zoomRangeAxes(double lowerPercent, double upperPercent,
    748                                  PlotRenderingInfo info, Point2D source) {
    749            this.rangeAxis.zoomRange(lowerPercent, upperPercent);
    750        }
    751
    752        /**
    753         * Returns <code>true</code>.
    754         *
    755         * @return A boolean.
    756         */
    757        public boolean isDomainZoomable() {
    758            return true;
    759        }
    760
    761        /**
    762         * Returns <code>true</code>.
    763         *
    764         * @return A boolean.
    765         */
    766        public boolean isRangeZoomable() {
    767            return true;
    768        }
    769
    770        /**
    771         * Tests an object for equality with this instance.
    772         *
    773         * @param obj  the object (<code>null</code> permitted).
    774         *
    775         * @return A boolean.
    776         */
    777        public boolean equals(Object obj) {
    778            if (obj == this) {
    779                return true;
    780            }
    781            if (!super.equals(obj)) {
    782                return false;
    783            }
    784            if (!(obj instanceof FastScatterPlot)) {
    785                return false;
    786            }
    787            FastScatterPlot that = (FastScatterPlot) obj;
    788            if (!ArrayUtilities.equal(this.data, that.data)) {
    789                return false;
    790            }
    791            if (!ObjectUtilities.equal(this.domainAxis, that.domainAxis)) {
    792                return false;
    793            }
    794            if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
    795                return false;
    796            }
    797            if (!PaintUtilities.equal(this.paint, that.paint)) {
    798                return false;
    799            }
    800            if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
    801                return false;
    802            }
    803            if (!PaintUtilities.equal(this.domainGridlinePaint,
    804                    that.domainGridlinePaint)) {
    805                return false;
    806            }
    807            if (!ObjectUtilities.equal(this.domainGridlineStroke,
    808                    that.domainGridlineStroke)) {
    809                return false;
    810            }
    811            if (!this.rangeGridlinesVisible == that.rangeGridlinesVisible) {
    812                return false;
    813            }
    814            if (!PaintUtilities.equal(this.rangeGridlinePaint,
    815                    that.rangeGridlinePaint)) {
    816                return false;
    817            }
    818            if (!ObjectUtilities.equal(this.rangeGridlineStroke,
    819                    that.rangeGridlineStroke)) {
    820                return false;
    821            }
    822            return true;
    823        }
    824
    825        /**
    826         * Returns a clone of the plot.
    827         *
    828         * @return A clone.
    829         *
    830         * @throws CloneNotSupportedException if some component of the plot does
    831         *                                    not support cloning.
    832         */
    833        public Object clone() throws CloneNotSupportedException {
    834
    835            FastScatterPlot clone = (FastScatterPlot) super.clone();
    836
    837            if (this.data != null) {
    838                clone.data = ArrayUtilities.clone(this.data);
    839            }
    840
    841            if (this.domainAxis != null) {
    842                clone.domainAxis = (ValueAxis) this.domainAxis.clone();
    843                clone.domainAxis.setPlot(clone);
    844                clone.domainAxis.addChangeListener(clone);
    845            }
    846
    847            if (this.rangeAxis != null) {
    848                clone.rangeAxis = (ValueAxis) this.rangeAxis.clone();
    849                clone.rangeAxis.setPlot(clone);
    850                clone.rangeAxis.addChangeListener(clone);
    851            }
    852
    853            return clone;
    854
    855        }
    856
    857        /**
    858         * Provides serialization support.
    859         *
    860         * @param stream  the output stream.
    861         *
    862         * @throws IOException  if there is an I/O error.
    863         */
    864        private void writeObject(ObjectOutputStream stream) throws IOException {
    865            stream.defaultWriteObject();
    866            SerialUtilities.writePaint(this.paint, stream);
    867            SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
    868            SerialUtilities.writePaint(this.domainGridlinePaint, stream);
    869            SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
    870            SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
    871        }
    872
    873        /**
    874         * Provides serialization support.
    875         *
    876         * @param stream  the input stream.
    877         *
    878         * @throws IOException  if there is an I/O error.
    879         * @throws ClassNotFoundException  if there is a classpath problem.
    880         */
    881        private void readObject(ObjectInputStream stream)
    882                throws IOException, ClassNotFoundException {
    883            stream.defaultReadObject();
    884
    885            this.paint = SerialUtilities.readPaint(stream);
    886            this.domainGridlineStroke = SerialUtilities.readStroke(stream);
    887            this.domainGridlinePaint = SerialUtilities.readPaint(stream);
    888
    889            this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
    890            this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
    891
    892            if (this.domainAxis != null) {
    893                this.domainAxis.addChangeListener(this);
    894            }
    895
    896            if (this.rangeAxis != null) {
    897                this.rangeAxis.addChangeListener(this);
    898            }
    899        }
    900
    901    }