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