    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * --------------------
    028 * FastScatterPlot.java
    029 * --------------------
    030 * (C) Copyright 2002-2011, 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
    065package org.jfree.chart.plot;
    066
    067import java.awt.AlphaComposite;
    068import java.awt.BasicStroke;
    069import java.awt.Color;
    070import java.awt.Composite;
    071import java.awt.Graphics2D;
    072import java.awt.Paint;
    073import java.awt.Shape;
    074import java.awt.Stroke;
    075import java.awt.geom.Line2D;
    076import java.awt.geom.Point2D;
    077import java.awt.geom.Rectangle2D;
    078import java.io.IOException;
    079import java.io.ObjectInputStream;
    080import java.io.ObjectOutputStream;
    081import java.io.Serializable;
    082import java.util.Iterator;
    083import java.util.List;
    084import java.util.ResourceBundle;
    085
    086import org.jfree.chart.axis.AxisSpace;
    087import org.jfree.chart.axis.AxisState;
    088import org.jfree.chart.axis.NumberAxis;
    089import org.jfree.chart.axis.ValueAxis;
    090import org.jfree.chart.axis.ValueTick;
    091import org.jfree.chart.event.PlotChangeEvent;
    092import org.jfree.chart.util.ResourceBundleWrapper;
    093import org.jfree.data.Range;
    094import org.jfree.io.SerialUtilities;
    095import org.jfree.ui.RectangleEdge;
    096import org.jfree.ui.RectangleInsets;
    097import org.jfree.util.ArrayUtilities;
    098import org.jfree.util.ObjectUtilities;
    099import org.jfree.util.PaintUtilities;
    100
    101/**
    102 * A fast scatter plot.
    103 */
    104public 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}