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