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