Source for org.jfree.chart.plot.FastScatterPlot

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * --------------------
  28:  * FastScatterPlot.java
  29:  * --------------------
  30:  * (C) Copyright 2002-2005, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Arnaud Lelievre;
  34:  *
  35:  * $Id: FastScatterPlot.java,v 1.11.2.3 2005/10/25 20:52:08 mungady Exp $
  36:  *
  37:  * Changes (from 29-Oct-2002)
  38:  * --------------------------
  39:  * 29-Oct-2002 : Added standard header (DG);
  40:  * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG);
  41:  * 26-Mar-2003 : Implemented Serializable (DG);
  42:  * 19-Aug-2003 : Implemented Cloneable (DG);
  43:  * 08-Sep-2003 : Added internationalization via use of properties 
  44:  *               resourceBundle (RFE 690236) (AL); 
  45:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  46:  * 12-Nov-2003 : Implemented zooming (DG);
  47:  * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
  48:  * 26-Jan-2004 : Added domain and range grid lines (DG);
  49:  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  50:  * 29-Sep-2004 : Removed hard-coded color (DG);
  51:  * 04-Oct-2004 : Reworked equals() method and renamed ArrayUtils 
  52:  *               --> ArrayUtilities (DG);
  53:  * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
  54:  * 05-May-2005 : Updated draw() method parameters (DG);
  55:  * 16-Jun-2005 : Added get/setData() methods (DG);
  56:  *
  57:  */
  58: 
  59: package org.jfree.chart.plot;
  60: 
  61: import java.awt.AlphaComposite;
  62: import java.awt.BasicStroke;
  63: import java.awt.Color;
  64: import java.awt.Composite;
  65: import java.awt.Graphics2D;
  66: import java.awt.Paint;
  67: import java.awt.Shape;
  68: import java.awt.Stroke;
  69: import java.awt.geom.Line2D;
  70: import java.awt.geom.Point2D;
  71: import java.awt.geom.Rectangle2D;
  72: import java.io.IOException;
  73: import java.io.ObjectInputStream;
  74: import java.io.ObjectOutputStream;
  75: import java.io.Serializable;
  76: import java.util.Iterator;
  77: import java.util.List;
  78: import java.util.ResourceBundle;
  79: 
  80: import org.jfree.chart.axis.AxisSpace;
  81: import org.jfree.chart.axis.AxisState;
  82: import org.jfree.chart.axis.ValueAxis;
  83: import org.jfree.chart.axis.ValueTick;
  84: import org.jfree.chart.event.PlotChangeEvent;
  85: import org.jfree.data.Range;
  86: import org.jfree.io.SerialUtilities;
  87: import org.jfree.ui.RectangleEdge;
  88: import org.jfree.ui.RectangleInsets;
  89: import org.jfree.util.ArrayUtilities;
  90: import org.jfree.util.ObjectUtilities;
  91: import org.jfree.util.PaintUtilities;
  92: 
  93: /**
  94:  * A fast scatter plot.
  95:  */
  96: public class FastScatterPlot extends Plot implements ValueAxisPlot, 
  97:                                                      Zoomable, 
  98:                                                      Cloneable, Serializable {
  99: 
 100:     /** For serialization. */
 101:     private static final long serialVersionUID = 7871545897358563521L;
 102:     
 103:     /** The default grid line stroke. */
 104:     public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
 105:             BasicStroke.CAP_BUTT,
 106:             BasicStroke.JOIN_BEVEL,
 107:             0.0f,
 108:             new float[] {2.0f, 2.0f},
 109:             0.0f);
 110: 
 111:     /** The default grid line paint. */
 112:     public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
 113: 
 114:     /** The data. */
 115:     private float[][] data;
 116: 
 117:     /** The x data range. */
 118:     private Range xDataRange;
 119: 
 120:     /** The y data range. */
 121:     private Range yDataRange;
 122: 
 123:     /** The domain axis (used for the x-values). */
 124:     private ValueAxis domainAxis;
 125: 
 126:     /** The range axis (used for the y-values). */
 127:     private ValueAxis rangeAxis;
 128: 
 129:     /** The paint used to plot data points. */
 130:     private transient Paint paint;
 131: 
 132:     /** A flag that controls whether the domain grid-lines are visible. */
 133:     private boolean domainGridlinesVisible;
 134: 
 135:     /** The stroke used to draw the domain grid-lines. */
 136:     private transient Stroke domainGridlineStroke;
 137: 
 138:     /** The paint used to draw the domain grid-lines. */
 139:     private transient Paint domainGridlinePaint;
 140: 
 141:     /** A flag that controls whether the range grid-lines are visible. */
 142:     private boolean rangeGridlinesVisible;
 143: 
 144:     /** The stroke used to draw the range grid-lines. */
 145:     private transient Stroke rangeGridlineStroke;
 146: 
 147:     /** The paint used to draw the range grid-lines. */
 148:     private transient Paint rangeGridlinePaint;
 149: 
 150:     /** The resourceBundle for the localization. */
 151:     protected static ResourceBundle localizationResources = 
 152:         ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
 153: 
 154:     /**
 155:      * Creates an empty plot.
 156:      */
 157:     public FastScatterPlot() {
 158:         this(null, null, null);    
 159:     }
 160:     
 161:     /**
 162:      * Creates a new fast scatter plot.
 163:      * <P>
 164:      * The data is an array of x, y values:  data[0][i] = x, data[1][i] = y.
 165:      *
 166:      * @param data  the data.
 167:      * @param domainAxis  the domain (x) axis.
 168:      * @param rangeAxis  the range (y) axis.
 169:      */
 170:     public FastScatterPlot(float[][] data, 
 171:                            ValueAxis domainAxis, ValueAxis rangeAxis) {
 172: 
 173:         super();
 174: 
 175:         this.data = data;
 176:         this.xDataRange = calculateXDataRange(data);
 177:         this.yDataRange = calculateYDataRange(data);
 178:         this.domainAxis = domainAxis;
 179:         if (domainAxis != null) {
 180:             domainAxis.setPlot(this);
 181:             domainAxis.addChangeListener(this);
 182:         }
 183: 
 184:         this.rangeAxis = rangeAxis;
 185:         if (rangeAxis != null) {
 186:             rangeAxis.setPlot(this);
 187:             rangeAxis.addChangeListener(this);
 188:         }
 189: 
 190:         this.paint = Color.red;
 191:         
 192:         this.domainGridlinesVisible = true;
 193:         this.domainGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
 194:         this.domainGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
 195: 
 196:         this.rangeGridlinesVisible = true;
 197:         this.rangeGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
 198:         this.rangeGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
 199:     
 200:     }
 201: 
 202:     /**
 203:      * Returns a short string describing the plot type.
 204:      *
 205:      * @return A short string describing the plot type.
 206:      */
 207:     public String getPlotType() {
 208:         return localizationResources.getString("Fast_Scatter_Plot");
 209:     }
 210: 
 211:     /**
 212:      * Returns the data array used by the plot.
 213:      * 
 214:      * @return The data array (possibly <code>null</code>).
 215:      */
 216:     public float[][] getData() {
 217:         return this.data;   
 218:     }
 219:     
 220:     /**
 221:      * Sets the data array used by the plot and sends a {@link PlotChangeEvent}
 222:      * to all registered listeners.
 223:      * 
 224:      * @param data  the data array (<code>null</code> permitted).
 225:      */
 226:     public void setData(float[][] data) {
 227:         this.data = data;
 228:         notifyListeners(new PlotChangeEvent(this));
 229:     }
 230:     
 231:     /**
 232:      * Returns the orientation of the plot.
 233:      * 
 234:      * @return The orientation (always {@link PlotOrientation#VERTICAL}).
 235:      */
 236:     public PlotOrientation getOrientation() {
 237:         return PlotOrientation.VERTICAL;    
 238:     }
 239:     
 240:     /**
 241:      * Returns the domain axis for the plot.  If the domain axis for this plot
 242:      * is null, then the method will return the parent plot's domain axis (if
 243:      * there is a parent plot).
 244:      *
 245:      * @return The domain axis.
 246:      */
 247:     public ValueAxis getDomainAxis() {
 248:         return this.domainAxis;
 249:     }
 250: 
 251:     /**
 252:      * Returns the range axis for the plot.  If the range axis for this plot is
 253:      * null, then the method will return the parent plot's range axis (if
 254:      * there is a parent plot).
 255:      *
 256:      * @return The range axis.
 257:      */
 258:     public ValueAxis getRangeAxis() {
 259:         return this.rangeAxis;
 260:     }
 261: 
 262:     /**
 263:      * Returns the paint used to plot data points.
 264:      *
 265:      * @return The paint.
 266:      */
 267:     public Paint getPaint() {
 268:         return this.paint;
 269:     }
 270: 
 271:     /**
 272:      * Sets the color for the data points and sends a {@link PlotChangeEvent} 
 273:      * to all registered listeners.
 274:      *
 275:      * @param paint  the paint (<code>null</code> not permitted).
 276:      */
 277:     public void setPaint(Paint paint) {
 278:         if (paint == null) {
 279:             throw new IllegalArgumentException("Null 'paint' argument.");
 280:         }
 281:         this.paint = paint;
 282:         notifyListeners(new PlotChangeEvent(this));
 283:     }
 284: 
 285:     /**
 286:      * Returns <code>true</code> if the domain gridlines are visible, and 
 287:      * <code>false<code> otherwise.
 288:      *
 289:      * @return <code>true</code> or <code>false</code>.
 290:      */
 291:     public boolean isDomainGridlinesVisible() {
 292:         return this.domainGridlinesVisible;
 293:     }
 294: 
 295:     /**
 296:      * Sets the flag that controls whether or not the domain grid-lines are 
 297:      * visible.  If the flag value is changed, a {@link PlotChangeEvent} is 
 298:      * sent to all registered listeners.
 299:      *
 300:      * @param visible  the new value of the flag.
 301:      */
 302:     public void setDomainGridlinesVisible(boolean visible) {
 303:         if (this.domainGridlinesVisible != visible) {
 304:             this.domainGridlinesVisible = visible;
 305:             notifyListeners(new PlotChangeEvent(this));
 306:         }
 307:     }
 308: 
 309:     /**
 310:      * Returns the stroke for the grid-lines (if any) plotted against the 
 311:      * domain axis.
 312:      *
 313:      * @return The stroke.
 314:      */
 315:     public Stroke getDomainGridlineStroke() {
 316:         return this.domainGridlineStroke;
 317:     }
 318: 
 319:     /**
 320:      * Sets the stroke for the grid lines plotted against the domain axis.
 321:      * <p>
 322:      * If you set this to <code>null</code>, no grid lines will be drawn.
 323:      *
 324:      * @param stroke  the stroke (<code>null</code> permitted).
 325:      */
 326:     public void setDomainGridlineStroke(Stroke stroke) {
 327:         this.domainGridlineStroke = stroke;
 328:         notifyListeners(new PlotChangeEvent(this));
 329:     }
 330: 
 331:     /**
 332:      * Returns the paint for the grid lines (if any) plotted against the domain
 333:      * axis.
 334:      *
 335:      * @return The paint.
 336:      */
 337:     public Paint getDomainGridlinePaint() {
 338:         return this.domainGridlinePaint;
 339:     }
 340: 
 341:     /**
 342:      * Sets the paint for the grid lines plotted against the domain axis.
 343:      * <p>
 344:      * If you set this to <code>null</code>, no grid lines will be drawn.
 345:      *
 346:      * @param paint  the paint (<code>null</code> permitted).
 347:      */
 348:     public void setDomainGridlinePaint(Paint paint) {
 349:         this.domainGridlinePaint = paint;
 350:         notifyListeners(new PlotChangeEvent(this));
 351:     }
 352: 
 353:     /**
 354:      * Returns <code>true</code> if the range axis grid is visible, and 
 355:      * <code>false<code> otherwise.
 356:      *
 357:      * @return <code>true</code> or <code>false</code>.
 358:      */
 359:     public boolean isRangeGridlinesVisible() {
 360:         return this.rangeGridlinesVisible;
 361:     }
 362: 
 363:     /**
 364:      * Sets the flag that controls whether or not the range axis grid lines are
 365:      * visible.  If the flag value is changed, a {@link PlotChangeEvent} is 
 366:      * sent to all registered listeners.
 367:      *
 368:      * @param visible  the new value of the flag.
 369:      */
 370:     public void setRangeGridlinesVisible(boolean visible) {
 371:         if (this.rangeGridlinesVisible != visible) {
 372:             this.rangeGridlinesVisible = visible;
 373:             notifyListeners(new PlotChangeEvent(this));
 374:         }
 375:     }
 376: 
 377:     /**
 378:      * Returns the stroke for the grid lines (if any) plotted against the range
 379:      * axis.
 380:      *
 381:      * @return The stroke.
 382:      */
 383:     public Stroke getRangeGridlineStroke() {
 384:         return this.rangeGridlineStroke;
 385:     }
 386: 
 387:     /**
 388:      * Sets the stroke for the grid lines plotted against the range axis.
 389:      * <p>
 390:      * If you set this to <code>null</code>, no grid lines will be drawn.
 391:      *
 392:      * @param stroke  the stroke (<code>null</code> permitted).
 393:      */
 394:     public void setRangeGridlineStroke(Stroke stroke) {
 395:         this.rangeGridlineStroke = stroke;
 396:         notifyListeners(new PlotChangeEvent(this));
 397:     }
 398: 
 399:     /**
 400:      * Returns the paint for the grid lines (if any) plotted against the range 
 401:      * axis.
 402:      *
 403:      * @return The paint.
 404:      */
 405:     public Paint getRangeGridlinePaint() {
 406:         return this.rangeGridlinePaint;
 407:     }
 408: 
 409:     /**
 410:      * Sets the paint for the grid lines plotted against the range axis.
 411:      * <p>
 412:      * If you set this to <code>null</code>, no grid lines will be drawn.
 413:      *
 414:      * @param paint  the paint (<code>null</code> permitted).
 415:      */
 416:     public void setRangeGridlinePaint(Paint paint) {
 417:         this.rangeGridlinePaint = paint;
 418:         notifyListeners(new PlotChangeEvent(this));
 419:     }
 420: 
 421:     /**
 422:      * Draws the fast scatter plot on a Java 2D graphics device (such as the 
 423:      * screen or a printer).
 424:      *
 425:      * @param g2  the graphics device.
 426:      * @param area   the area within which the plot (including axis labels)
 427:      *                   should be drawn.
 428:      * @param anchor  the anchor point (<code>null</code> permitted).
 429:      * @param parentState  the state from the parent plot, if there is one.
 430:      * @param info  collects chart drawing information (<code>null</code> 
 431:      *              permitted).
 432:      */
 433:     public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
 434:                      PlotState parentState,
 435:                      PlotRenderingInfo info) {
 436: 
 437:         // set up info collection...
 438:         if (info != null) {
 439:             info.setPlotArea(area);
 440:         }
 441: 
 442:         // adjust the drawing area for plot insets (if any)...
 443:         RectangleInsets insets = getInsets();
 444:         insets.trim(area);
 445: 
 446:         AxisSpace space = new AxisSpace();
 447:         space = this.domainAxis.reserveSpace(
 448:             g2, this, area, RectangleEdge.BOTTOM, space
 449:         );
 450:         space = this.rangeAxis.reserveSpace(
 451:             g2, this, area, RectangleEdge.LEFT, space
 452:         );
 453:         Rectangle2D dataArea = space.shrink(area, null);
 454: 
 455:         if (info != null) {
 456:             info.setDataArea(dataArea);
 457:         }
 458: 
 459:         // draw the plot background and axes...
 460:         drawBackground(g2, dataArea);
 461: 
 462:         AxisState domainAxisState = null;
 463:         AxisState rangeAxisState = null;
 464:         if (this.domainAxis != null) {
 465:             domainAxisState = this.domainAxis.draw(
 466:                 g2, dataArea.getMaxY(), area, dataArea, 
 467:                 RectangleEdge.BOTTOM, info
 468:             );
 469:         }
 470:         if (this.rangeAxis != null) {
 471:             rangeAxisState = this.rangeAxis.draw(
 472:                 g2, dataArea.getMinX(), area, dataArea, 
 473:                 RectangleEdge.LEFT, info
 474:             );
 475:         }
 476:         drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
 477:         drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
 478:         
 479:         Shape originalClip = g2.getClip();
 480:         Composite originalComposite = g2.getComposite();
 481: 
 482:         g2.clip(dataArea);
 483:         g2.setComposite(
 484:             AlphaComposite.getInstance(
 485:                 AlphaComposite.SRC_OVER, getForegroundAlpha()
 486:             )
 487:         );
 488: 
 489:         render(g2, dataArea, info, null);
 490: 
 491:         g2.setClip(originalClip);
 492:         g2.setComposite(originalComposite);
 493:         drawOutline(g2, dataArea);
 494: 
 495:     }
 496: 
 497:     /**
 498:      * Draws a representation of the data within the dataArea region.  The 
 499:      * <code>info</code> and <code>crosshairState</code> arguments may be 
 500:      * <code>null</code>.
 501:      *
 502:      * @param g2  the graphics device.
 503:      * @param dataArea  the region in which the data is to be drawn.
 504:      * @param info  an optional object for collection dimension information.
 505:      * @param crosshairState  collects crosshair information (<code>null</code>
 506:      *                        permitted).
 507:      */
 508:     public void render(Graphics2D g2, Rectangle2D dataArea,
 509:                        PlotRenderingInfo info, CrosshairState crosshairState) {
 510:     
 511:  
 512:         //long start = System.currentTimeMillis();
 513:         //System.out.println("Start: " + start);
 514:         g2.setPaint(this.paint);
 515: 
 516:         // if the axes use a linear scale, you can uncomment the code below and
 517:         // switch to the alternative transX/transY calculation inside the loop 
 518:         // that follows - it is a little bit faster then.
 519:         // 
 520:         // int xx = (int) dataArea.getMinX();
 521:         // int ww = (int) dataArea.getWidth();
 522:         // int yy = (int) dataArea.getMaxY();
 523:         // int hh = (int) dataArea.getHeight();
 524:         // double domainMin = this.domainAxis.getLowerBound();
 525:         // double domainLength = this.domainAxis.getUpperBound() - domainMin;
 526:         // double rangeMin = this.rangeAxis.getLowerBound();
 527:         // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin;
 528: 
 529:         if (this.data != null) {
 530:             for (int i = 0; i < this.data[0].length; i++) {
 531:                 float x = this.data[0][i];
 532:                 float y = this.data[1][i];
 533:                 
 534:                 //int transX = (int) (xx + ww * (x - domainMin) / domainLength);
 535:                 //int transY = (int) (yy - hh * (y - rangeMin) / rangeLength); 
 536:                 int transX = (int) this.domainAxis.valueToJava2D(
 537:                     x, dataArea, RectangleEdge.BOTTOM
 538:                 );
 539:                 int transY = (int) this.rangeAxis.valueToJava2D(
 540:                     y, dataArea, RectangleEdge.LEFT
 541:                 );
 542:                 g2.fillRect(transX, transY, 1, 1);
 543:             }
 544:         }
 545:         //long finish = System.currentTimeMillis();
 546:         //System.out.println("Finish: " + finish);
 547:         //System.out.println("Time: " + (finish - start));
 548: 
 549:     }
 550: 
 551:     /**
 552:      * Draws the gridlines for the plot, if they are visible.
 553:      *
 554:      * @param g2  the graphics device.
 555:      * @param dataArea  the data area.
 556:      * @param ticks  the ticks.
 557:      */
 558:     protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea, 
 559:                                        List ticks) {
 560: 
 561:         // draw the domain grid lines, if any...
 562:         if (isDomainGridlinesVisible()) {
 563:             Stroke gridStroke = getDomainGridlineStroke();
 564:             Paint gridPaint = getDomainGridlinePaint();
 565:             if ((gridStroke != null) && (gridPaint != null)) {
 566:                 Iterator iterator = ticks.iterator();
 567:                 while (iterator.hasNext()) {
 568:                     ValueTick tick = (ValueTick) iterator.next();
 569:                     double v = this.domainAxis.valueToJava2D(
 570:                         tick.getValue(), dataArea, RectangleEdge.BOTTOM
 571:                     );
 572:                     Line2D line = new Line2D.Double(
 573:                         v, dataArea.getMinY(), v, dataArea.getMaxY()
 574:                     );
 575:                     g2.setPaint(gridPaint);
 576:                     g2.setStroke(gridStroke);
 577:                     g2.draw(line);                
 578:                 }
 579:             }
 580:         }
 581:     }
 582:     
 583:     /**
 584:      * Draws the gridlines for the plot, if they are visible.
 585:      *
 586:      * @param g2  the graphics device.
 587:      * @param dataArea  the data area.
 588:      * @param ticks  the ticks.
 589:      */
 590:     protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, 
 591:                                       List ticks) {
 592: 
 593:         // draw the range grid lines, if any...
 594:         if (isRangeGridlinesVisible()) {
 595:             Stroke gridStroke = getRangeGridlineStroke();
 596:             Paint gridPaint = getRangeGridlinePaint();
 597:             if ((gridStroke != null) && (gridPaint != null)) {
 598:                 Iterator iterator = ticks.iterator();
 599:                 while (iterator.hasNext()) {
 600:                     ValueTick tick = (ValueTick) iterator.next();
 601:                     double v = this.rangeAxis.valueToJava2D(
 602:                         tick.getValue(), dataArea, RectangleEdge.LEFT
 603:                     );
 604:                     Line2D line = new Line2D.Double(
 605:                         dataArea.getMinX(), v, dataArea.getMaxX(), v
 606:                     );
 607:                     g2.setPaint(gridPaint);
 608:                     g2.setStroke(gridStroke);
 609:                     g2.draw(line);                
 610:                 }
 611:             }
 612:         }
 613: 
 614:     }
 615: 
 616:     /**
 617:      * Returns the range of data values to be plotted along the axis.
 618:      *
 619:      * @param axis  the axis.
 620:      *
 621:      * @return The range.
 622:      */
 623:     public Range getDataRange(ValueAxis axis) {
 624: 
 625:         Range result = null;
 626:         if (axis == this.domainAxis) {
 627:             result = this.xDataRange;
 628:         }
 629:         else if (axis == this.rangeAxis) {
 630:             result = this.yDataRange;
 631:         }
 632:         return result;
 633:     }
 634: 
 635:     /**
 636:      * Calculates the X data range.
 637:      *
 638:      * @param data  the data.
 639:      *
 640:      * @return The range.
 641:      */
 642:     private Range calculateXDataRange(float[][] data) {
 643:         
 644:         Range result = null;
 645:         
 646:         if (data != null) {
 647:             float lowest = Float.POSITIVE_INFINITY;
 648:             float highest = Float.NEGATIVE_INFINITY;
 649:             for (int i = 0; i < data[0].length; i++) {
 650:                 float v = data[0][i];
 651:                 if (v < lowest) {
 652:                     lowest = v;
 653:                 }
 654:                 if (v > highest) {
 655:                     highest = v;
 656:                 }
 657:             }
 658:             if (lowest <= highest) {
 659:                 result = new Range(lowest, highest);
 660:             }
 661:         }
 662:         
 663:         return result;
 664:         
 665:     }
 666: 
 667:     /**
 668:      * Calculates the Y data range.
 669:      *
 670:      * @param data  the data.
 671:      *
 672:      * @return The range.
 673:      */
 674:     private Range calculateYDataRange(float[][] data) {
 675:         
 676:         Range result = null;
 677:         
 678:         if (data != null) {
 679:             float lowest = Float.POSITIVE_INFINITY;
 680:             float highest = Float.NEGATIVE_INFINITY;
 681:             for (int i = 0; i < data[0].length; i++) {
 682:                 float v = data[1][i];
 683:                 if (v < lowest) {
 684:                     lowest = v;
 685:                 }
 686:                 if (v > highest) {
 687:                     highest = v;
 688:                 }
 689:             }
 690:             if (lowest <= highest) {
 691:                 result = new Range(lowest, highest);
 692:             }
 693:         }
 694:         return result;
 695:         
 696:     }
 697: 
 698:     /**
 699:      * Multiplies the range on the domain axis/axes by the specified factor.
 700:      *
 701:      * @param factor  the zoom factor.
 702:      * @param info  the plot rendering info.
 703:      * @param source  the source point.
 704:      */
 705:     public void zoomDomainAxes(double factor, PlotRenderingInfo info, 
 706:                                Point2D source) {
 707:         this.domainAxis.resizeRange(factor);
 708:     }
 709: 
 710:     /**
 711:      * Zooms in on the domain axes.
 712:      * 
 713:      * @param lowerPercent  the new lower bound as a percentage of the current 
 714:      *                      range.
 715:      * @param upperPercent  the new upper bound as a percentage of the current
 716:      *                      range.
 717:      * @param info  the plot rendering info.
 718:      * @param source  the source point.
 719:      */
 720:     public void zoomDomainAxes(double lowerPercent, double upperPercent, 
 721:                                PlotRenderingInfo info, Point2D source) {
 722:         this.domainAxis.zoomRange(lowerPercent, upperPercent);
 723:     }
 724: 
 725:     /**
 726:      * Multiplies the range on the range axis/axes by the specified factor.
 727:      *
 728:      * @param factor  the zoom factor.
 729:      * @param info  the plot rendering info.
 730:      * @param source  the source point.
 731:      */
 732:     public void zoomRangeAxes(double factor,
 733:                               PlotRenderingInfo info, Point2D source) {
 734:         this.rangeAxis.resizeRange(factor);
 735:     }
 736: 
 737:     /**
 738:      * Zooms in on the range axes.
 739:      * 
 740:      * @param lowerPercent  the new lower bound as a percentage of the current 
 741:      *                      range.
 742:      * @param upperPercent  the new upper bound as a percentage of the current 
 743:      *                      range.
 744:      * @param info  the plot rendering info.
 745:      * @param source  the source point.
 746:      */
 747:     public void zoomRangeAxes(double lowerPercent, double upperPercent,
 748:                               PlotRenderingInfo info, Point2D source) {
 749:         this.rangeAxis.zoomRange(lowerPercent, upperPercent);
 750:     }
 751: 
 752:     /**
 753:      * Returns <code>true</code>.
 754:      * 
 755:      * @return A boolean.
 756:      */
 757:     public boolean isDomainZoomable() {
 758:         return true;
 759:     }
 760:     
 761:     /**
 762:      * Returns <code>true</code>.
 763:      * 
 764:      * @return A boolean.
 765:      */
 766:     public boolean isRangeZoomable() {
 767:         return true;
 768:     }
 769: 
 770:     /**
 771:      * Tests an object for equality with this instance.
 772:      * 
 773:      * @param obj  the object (<code>null</code> permitted).
 774:      * 
 775:      * @return A boolean.
 776:      */
 777:     public boolean equals(Object obj) {
 778:         if (obj == this) {
 779:             return true;
 780:         }
 781:         if (!super.equals(obj)) {
 782:             return false;
 783:         }
 784:         if (!(obj instanceof FastScatterPlot)) {
 785:             return false;
 786:         }
 787:         FastScatterPlot that = (FastScatterPlot) obj;
 788:         if (!ArrayUtilities.equal(this.data, that.data)) {
 789:             return false;
 790:         }
 791:         if (!ObjectUtilities.equal(this.domainAxis, that.domainAxis)) {
 792:             return false;
 793:         }
 794:         if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
 795:             return false;
 796:         }
 797:         if (!PaintUtilities.equal(this.paint, that.paint)) {
 798:             return false;
 799:         }
 800:         if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
 801:             return false;
 802:         }
 803:         if (!PaintUtilities.equal(this.domainGridlinePaint, 
 804:                 that.domainGridlinePaint)) {
 805:             return false;
 806:         }
 807:         if (!ObjectUtilities.equal(this.domainGridlineStroke, 
 808:                 that.domainGridlineStroke)) {
 809:             return false;
 810:         }  
 811:         if (!this.rangeGridlinesVisible == that.rangeGridlinesVisible) {
 812:             return false;
 813:         }
 814:         if (!PaintUtilities.equal(this.rangeGridlinePaint, 
 815:                 that.rangeGridlinePaint)) {
 816:             return false;
 817:         }
 818:         if (!ObjectUtilities.equal(this.rangeGridlineStroke, 
 819:                 that.rangeGridlineStroke)) {
 820:             return false;
 821:         }              
 822:         return true;
 823:     }
 824:     
 825:     /**
 826:      * Returns a clone of the plot.
 827:      * 
 828:      * @return A clone.
 829:      * 
 830:      * @throws CloneNotSupportedException if some component of the plot does 
 831:      *                                    not support cloning.
 832:      */
 833:     public Object clone() throws CloneNotSupportedException {
 834:     
 835:         FastScatterPlot clone = (FastScatterPlot) super.clone();    
 836:         
 837:         if (this.data != null) {
 838:             clone.data = ArrayUtilities.clone(this.data);    
 839:         }
 840:         
 841:         if (this.domainAxis != null) {
 842:             clone.domainAxis = (ValueAxis) this.domainAxis.clone();
 843:             clone.domainAxis.setPlot(clone);
 844:             clone.domainAxis.addChangeListener(clone);
 845:         }
 846:         
 847:         if (this.rangeAxis != null) {
 848:             clone.rangeAxis = (ValueAxis) this.rangeAxis.clone();
 849:             clone.rangeAxis.setPlot(clone);
 850:             clone.rangeAxis.addChangeListener(clone);
 851:         }
 852:             
 853:         return clone;
 854:         
 855:     }
 856: 
 857:     /**
 858:      * Provides serialization support.
 859:      *
 860:      * @param stream  the output stream.
 861:      *
 862:      * @throws IOException  if there is an I/O error.
 863:      */
 864:     private void writeObject(ObjectOutputStream stream) throws IOException {
 865:         stream.defaultWriteObject();
 866:         SerialUtilities.writePaint(this.paint, stream);
 867:         SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
 868:         SerialUtilities.writePaint(this.domainGridlinePaint, stream);
 869:         SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
 870:         SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
 871:     }
 872: 
 873:     /**
 874:      * Provides serialization support.
 875:      *
 876:      * @param stream  the input stream.
 877:      *
 878:      * @throws IOException  if there is an I/O error.
 879:      * @throws ClassNotFoundException  if there is a classpath problem.
 880:      */
 881:     private void readObject(ObjectInputStream stream) 
 882:             throws IOException, ClassNotFoundException {
 883:         stream.defaultReadObject();
 884: 
 885:         this.paint = SerialUtilities.readPaint(stream);
 886:         this.domainGridlineStroke = SerialUtilities.readStroke(stream);
 887:         this.domainGridlinePaint = SerialUtilities.readPaint(stream);
 888: 
 889:         this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
 890:         this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
 891: 
 892:         if (this.domainAxis != null) {
 893:             this.domainAxis.addChangeListener(this);
 894:         }
 895: 
 896:         if (this.rangeAxis != null) {
 897:             this.rangeAxis.addChangeListener(this);
 898:         }
 899:     }
 900:     
 901: }