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    }