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-2007, 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-2007, 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.5 2007/01/11 11:06:04 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:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  57:  * 10-Nov-2006 : Fixed bug 1593150, by not allowing null axes, and added
  58:  *               setDomainAxis() and setRangeAxis() methods (DG);
  59:  *
  60:  */
  61: 
  62: package org.jfree.chart.plot;
  63: 
  64: import java.awt.AlphaComposite;
  65: import java.awt.BasicStroke;
  66: import java.awt.Color;
  67: import java.awt.Composite;
  68: import java.awt.Graphics2D;
  69: import java.awt.Paint;
  70: import java.awt.Shape;
  71: import java.awt.Stroke;
  72: import java.awt.geom.Line2D;
  73: import java.awt.geom.Point2D;
  74: import java.awt.geom.Rectangle2D;
  75: import java.io.IOException;
  76: import java.io.ObjectInputStream;
  77: import java.io.ObjectOutputStream;
  78: import java.io.Serializable;
  79: import java.util.Iterator;
  80: import java.util.List;
  81: import java.util.ResourceBundle;
  82: 
  83: import org.jfree.chart.axis.AxisSpace;
  84: import org.jfree.chart.axis.AxisState;
  85: import org.jfree.chart.axis.NumberAxis;
  86: import org.jfree.chart.axis.ValueAxis;
  87: import org.jfree.chart.axis.ValueTick;
  88: import org.jfree.chart.event.PlotChangeEvent;
  89: import org.jfree.data.Range;
  90: import org.jfree.io.SerialUtilities;
  91: import org.jfree.ui.RectangleEdge;
  92: import org.jfree.ui.RectangleInsets;
  93: import org.jfree.util.ArrayUtilities;
  94: import org.jfree.util.ObjectUtilities;
  95: import org.jfree.util.PaintUtilities;
  96: 
  97: /**
  98:  * A fast scatter plot.
  99:  */
 100: public class FastScatterPlot extends Plot implements ValueAxisPlot, 
 101:                                                      Zoomable, 
 102:                                                      Cloneable, Serializable {
 103: 
 104:     /** For serialization. */
 105:     private static final long serialVersionUID = 7871545897358563521L;
 106:     
 107:     /** The default grid line stroke. */
 108:     public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
 109:             BasicStroke.CAP_BUTT,
 110:             BasicStroke.JOIN_BEVEL,
 111:             0.0f,
 112:             new float[] {2.0f, 2.0f},
 113:             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:     /** The resourceBundle for the localization. */
 155:     protected static ResourceBundle localizationResources = 
 156:         ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
 157: 
 158:     /**
 159:      * Creates a new instance of <code>FastScatterPlot</code> with default 
 160:      * axes.
 161:      */
 162:     public FastScatterPlot() {
 163:         this(null, new NumberAxis("X"), new NumberAxis("Y"));    
 164:     }
 165:     
 166:     /**
 167:      * Creates a new fast scatter plot.
 168:      * <p>
 169:      * The data is an array of x, y values:  data[0][i] = x, data[1][i] = y.
 170:      * 
 171:      * @param data  the data (<code>null</code> permitted).
 172:      * @param domainAxis  the domain (x) axis (<code>null</code> not permitted).
 173:      * @param rangeAxis  the range (y) axis (<code>null</code> not permitted).
 174:      */
 175:     public FastScatterPlot(float[][] data, 
 176:                            ValueAxis domainAxis, ValueAxis rangeAxis) {
 177: 
 178:         super();
 179:         if (domainAxis == null) {
 180:             throw new IllegalArgumentException("Null 'domainAxis' argument.");
 181:         }
 182:         if (rangeAxis == null) {
 183:             throw new IllegalArgumentException("Null 'rangeAxis' argument.");
 184:         }
 185:         
 186:         this.data = data;
 187:         this.xDataRange = calculateXDataRange(data);
 188:         this.yDataRange = calculateYDataRange(data);
 189:         this.domainAxis = domainAxis;
 190:         this.domainAxis.setPlot(this);
 191:         this.domainAxis.addChangeListener(this);
 192:         this.rangeAxis = rangeAxis;
 193:         this.rangeAxis.setPlot(this);
 194:         this.rangeAxis.addChangeListener(this);
 195: 
 196:         this.paint = Color.red;
 197:         
 198:         this.domainGridlinesVisible = true;
 199:         this.domainGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
 200:         this.domainGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
 201: 
 202:         this.rangeGridlinesVisible = true;
 203:         this.rangeGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
 204:         this.rangeGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
 205:     
 206:     }
 207: 
 208:     /**
 209:      * Returns a short string describing the plot type.
 210:      *
 211:      * @return A short string describing the plot type.
 212:      */
 213:     public String getPlotType() {
 214:         return localizationResources.getString("Fast_Scatter_Plot");
 215:     }
 216: 
 217:     /**
 218:      * Returns the data array used by the plot.
 219:      * 
 220:      * @return The data array (possibly <code>null</code>).
 221:      * 
 222:      * @see #setData(float[][])
 223:      */
 224:     public float[][] getData() {
 225:         return this.data;   
 226:     }
 227:     
 228:     /**
 229:      * Sets the data array used by the plot and sends a {@link PlotChangeEvent}
 230:      * to all registered listeners.
 231:      * 
 232:      * @param data  the data array (<code>null</code> permitted).
 233:      * 
 234:      * @see #getData()
 235:      */
 236:     public void setData(float[][] data) {
 237:         this.data = data;
 238:         notifyListeners(new PlotChangeEvent(this));
 239:     }
 240:     
 241:     /**
 242:      * Returns the orientation of the plot.
 243:      * 
 244:      * @return The orientation (always {@link PlotOrientation#VERTICAL}).
 245:      */
 246:     public PlotOrientation getOrientation() {
 247:         return PlotOrientation.VERTICAL;    
 248:     }
 249:     
 250:     /**
 251:      * Returns the domain axis for the plot.
 252:      *
 253:      * @return The domain axis (never <code>null</code>).
 254:      * 
 255:      * @see #setDomainAxis(ValueAxis)
 256:      */
 257:     public ValueAxis getDomainAxis() {
 258:         return this.domainAxis;
 259:     }
 260:     
 261:     /**
 262:      * Sets the domain axis and sends a {@link PlotChangeEvent} to all 
 263:      * registered listeners.
 264:      * 
 265:      * @param axis  the axis (<code>null</code> not permitted).
 266:      * 
 267:      * @since 1.0.3
 268:      * 
 269:      * @see #getDomainAxis()
 270:      */
 271:     public void setDomainAxis(ValueAxis axis) {
 272:         if (axis == null) {
 273:             throw new IllegalArgumentException("Null 'axis' argument.");
 274:         }
 275:         this.domainAxis = axis;
 276:         notifyListeners(new PlotChangeEvent(this));
 277:     }
 278: 
 279:     /**
 280:      * Returns the range axis for the plot.
 281:      *
 282:      * @return The range axis (never <code>null</code>).
 283:      * 
 284:      * @see #setRangeAxis(ValueAxis)
 285:      */
 286:     public ValueAxis getRangeAxis() {
 287:         return this.rangeAxis;
 288:     }
 289: 
 290:     /**
 291:      * Sets the range axis and sends a {@link PlotChangeEvent} to all 
 292:      * registered listeners.
 293:      * 
 294:      * @param axis  the axis (<code>null</code> not permitted).
 295:      * 
 296:      * @since 1.0.3
 297:      * 
 298:      * @see #getRangeAxis()
 299:      */
 300:     public void setRangeAxis(ValueAxis axis) {
 301:         if (axis == null) {
 302:             throw new IllegalArgumentException("Null 'axis' argument.");
 303:         }
 304:         this.rangeAxis = axis;
 305:         notifyListeners(new PlotChangeEvent(this));
 306:     }
 307: 
 308:     /**
 309:      * Returns the paint used to plot data points.  The default is 
 310:      * <code>Color.red</code>.
 311:      *
 312:      * @return The paint.
 313:      * 
 314:      * @see #setPaint(Paint)
 315:      */
 316:     public Paint getPaint() {
 317:         return this.paint;
 318:     }
 319: 
 320:     /**
 321:      * Sets the color for the data points and sends a {@link PlotChangeEvent} 
 322:      * to all registered listeners.
 323:      *
 324:      * @param paint  the paint (<code>null</code> not permitted).
 325:      * 
 326:      * @see #getPaint()
 327:      */
 328:     public void setPaint(Paint paint) {
 329:         if (paint == null) {
 330:             throw new IllegalArgumentException("Null 'paint' argument.");
 331:         }
 332:         this.paint = paint;
 333:         notifyListeners(new PlotChangeEvent(this));
 334:     }
 335: 
 336:     /**
 337:      * Returns <code>true</code> if the domain gridlines are visible, and 
 338:      * <code>false<code> otherwise.
 339:      *
 340:      * @return <code>true</code> or <code>false</code>.
 341:      * 
 342:      * @see #setDomainGridlinesVisible(boolean)
 343:      * @see #setDomainGridlinePaint(Paint)
 344:      */
 345:     public boolean isDomainGridlinesVisible() {
 346:         return this.domainGridlinesVisible;
 347:     }
 348: 
 349:     /**
 350:      * Sets the flag that controls whether or not the domain grid-lines are 
 351:      * visible.  If the flag value is changed, a {@link PlotChangeEvent} is 
 352:      * sent to all registered listeners.
 353:      *
 354:      * @param visible  the new value of the flag.
 355:      * 
 356:      * @see #getDomainGridlinePaint()
 357:      */
 358:     public void setDomainGridlinesVisible(boolean visible) {
 359:         if (this.domainGridlinesVisible != visible) {
 360:             this.domainGridlinesVisible = visible;
 361:             notifyListeners(new PlotChangeEvent(this));
 362:         }
 363:     }
 364: 
 365:     /**
 366:      * Returns the stroke for the grid-lines (if any) plotted against the 
 367:      * domain axis.
 368:      *
 369:      * @return The stroke (never <code>null</code>).
 370:      * 
 371:      * @see #setDomainGridlineStroke(Stroke)
 372:      */
 373:     public Stroke getDomainGridlineStroke() {
 374:         return this.domainGridlineStroke;
 375:     }
 376: 
 377:     /**
 378:      * Sets the stroke for the grid lines plotted against the domain axis and
 379:      * sends a {@link PlotChangeEvent} to all registered listeners.
 380:      *
 381:      * @param stroke  the stroke (<code>null</code> not permitted).
 382:      * 
 383:      * @see #getDomainGridlineStroke()
 384:      */
 385:     public void setDomainGridlineStroke(Stroke stroke) {
 386:         if (stroke == null) {
 387:             throw new IllegalArgumentException("Null 'stroke' argument.");
 388:         }
 389:         this.domainGridlineStroke = stroke;
 390:         notifyListeners(new PlotChangeEvent(this));
 391:     }
 392: 
 393:     /**
 394:      * Returns the paint for the grid lines (if any) plotted against the domain
 395:      * axis.
 396:      *
 397:      * @return The paint (never <code>null</code>).
 398:      * 
 399:      * @see #setDomainGridlinePaint(Paint)
 400:      */
 401:     public Paint getDomainGridlinePaint() {
 402:         return this.domainGridlinePaint;
 403:     }
 404: 
 405:     /**
 406:      * Sets the paint for the grid lines plotted against the domain axis and
 407:      * sends a {@link PlotChangeEvent} to all registered listeners.
 408:      *
 409:      * @param paint  the paint (<code>null</code> not permitted).
 410:      * 
 411:      * @see #getDomainGridlinePaint()
 412:      */
 413:     public void setDomainGridlinePaint(Paint paint) {
 414:         if (paint == null) {
 415:             throw new IllegalArgumentException("Null 'paint' argument.");
 416:         }
 417:         this.domainGridlinePaint = paint;
 418:         notifyListeners(new PlotChangeEvent(this));
 419:     }
 420: 
 421:     /**
 422:      * Returns <code>true</code> if the range axis grid is visible, and 
 423:      * <code>false<code> otherwise.
 424:      *
 425:      * @return <code>true</code> or <code>false</code>.
 426:      * 
 427:      * @see #setRangeGridlinesVisible(boolean)
 428:      */
 429:     public boolean isRangeGridlinesVisible() {
 430:         return this.rangeGridlinesVisible;
 431:     }
 432: 
 433:     /**
 434:      * Sets the flag that controls whether or not the range axis grid lines are
 435:      * visible.  If the flag value is changed, a {@link PlotChangeEvent} is 
 436:      * sent to all registered listeners.
 437:      *
 438:      * @param visible  the new value of the flag.
 439:      * 
 440:      * @see #isRangeGridlinesVisible()
 441:      */
 442:     public void setRangeGridlinesVisible(boolean visible) {
 443:         if (this.rangeGridlinesVisible != visible) {
 444:             this.rangeGridlinesVisible = visible;
 445:             notifyListeners(new PlotChangeEvent(this));
 446:         }
 447:     }
 448: 
 449:     /**
 450:      * Returns the stroke for the grid lines (if any) plotted against the range
 451:      * axis.
 452:      *
 453:      * @return The stroke (never <code>null</code>).
 454:      * 
 455:      * @see #setRangeGridlineStroke(Stroke)
 456:      */
 457:     public Stroke getRangeGridlineStroke() {
 458:         return this.rangeGridlineStroke;
 459:     }
 460: 
 461:     /**
 462:      * Sets the stroke for the grid lines plotted against the range axis and 
 463:      * sends a {@link PlotChangeEvent} to all registered listeners.
 464:      *
 465:      * @param stroke  the stroke (<code>null</code> permitted).
 466:      * 
 467:      * @see #getRangeGridlineStroke()
 468:      */
 469:     public void setRangeGridlineStroke(Stroke stroke) {
 470:         if (stroke == null) {
 471:             throw new IllegalArgumentException("Null 'stroke' argument.");
 472:         }
 473:         this.rangeGridlineStroke = stroke;
 474:         notifyListeners(new PlotChangeEvent(this));
 475:     }
 476: 
 477:     /**
 478:      * Returns the paint for the grid lines (if any) plotted against the range 
 479:      * axis.
 480:      *
 481:      * @return The paint (never <code>null</code>).
 482:      * 
 483:      * @see #setRangeGridlinePaint(Paint)
 484:      */
 485:     public Paint getRangeGridlinePaint() {
 486:         return this.rangeGridlinePaint;
 487:     }
 488: 
 489:     /**
 490:      * Sets the paint for the grid lines plotted against the range axis and 
 491:      * sends a {@link PlotChangeEvent} to all registered listeners.
 492:      *
 493:      * @param paint  the paint (<code>null</code> not permitted).
 494:      * 
 495:      * @see #getRangeGridlinePaint()
 496:      */
 497:     public void setRangeGridlinePaint(Paint paint) {
 498:         if (paint == null) {
 499:             throw new IllegalArgumentException("Null 'paint' argument.");
 500:         }
 501:         this.rangeGridlinePaint = paint;
 502:         notifyListeners(new PlotChangeEvent(this));
 503:     }
 504: 
 505:     /**
 506:      * Draws the fast scatter plot on a Java 2D graphics device (such as the 
 507:      * screen or a printer).
 508:      *
 509:      * @param g2  the graphics device.
 510:      * @param area   the area within which the plot (including axis labels)
 511:      *                   should be drawn.
 512:      * @param anchor  the anchor point (<code>null</code> permitted).
 513:      * @param parentState  the state from the parent plot (ignored).
 514:      * @param info  collects chart drawing information (<code>null</code> 
 515:      *              permitted).
 516:      */
 517:     public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
 518:                      PlotState parentState,
 519:                      PlotRenderingInfo info) {
 520: 
 521:         // set up info collection...
 522:         if (info != null) {
 523:             info.setPlotArea(area);
 524:         }
 525: 
 526:         // adjust the drawing area for plot insets (if any)...
 527:         RectangleInsets insets = getInsets();
 528:         insets.trim(area);
 529: 
 530:         AxisSpace space = new AxisSpace();
 531:         space = this.domainAxis.reserveSpace(g2, this, area, 
 532:                 RectangleEdge.BOTTOM, space);
 533:         space = this.rangeAxis.reserveSpace(g2, this, area, RectangleEdge.LEFT, 
 534:                 space);
 535:         Rectangle2D dataArea = space.shrink(area, null);
 536: 
 537:         if (info != null) {
 538:             info.setDataArea(dataArea);
 539:         }
 540: 
 541:         // draw the plot background and axes...
 542:         drawBackground(g2, dataArea);
 543: 
 544:         AxisState domainAxisState = this.domainAxis.draw(g2, 
 545:                 dataArea.getMaxY(), area, dataArea, RectangleEdge.BOTTOM, info);
 546:         AxisState rangeAxisState = this.rangeAxis.draw(g2, dataArea.getMinX(), 
 547:                 area, dataArea, RectangleEdge.LEFT, info);
 548:         drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
 549:         drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
 550:         
 551:         Shape originalClip = g2.getClip();
 552:         Composite originalComposite = g2.getComposite();
 553: 
 554:         g2.clip(dataArea);
 555:         g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 
 556:                 getForegroundAlpha()));
 557: 
 558:         render(g2, dataArea, info, null);
 559: 
 560:         g2.setClip(originalClip);
 561:         g2.setComposite(originalComposite);
 562:         drawOutline(g2, dataArea);
 563: 
 564:     }
 565: 
 566:     /**
 567:      * Draws a representation of the data within the dataArea region.  The 
 568:      * <code>info</code> and <code>crosshairState</code> arguments may be 
 569:      * <code>null</code>.
 570:      *
 571:      * @param g2  the graphics device.
 572:      * @param dataArea  the region in which the data is to be drawn.
 573:      * @param info  an optional object for collection dimension information.
 574:      * @param crosshairState  collects crosshair information (<code>null</code>
 575:      *                        permitted).
 576:      */
 577:     public void render(Graphics2D g2, Rectangle2D dataArea,
 578:                        PlotRenderingInfo info, CrosshairState crosshairState) {
 579:     
 580:  
 581:         //long start = System.currentTimeMillis();
 582:         //System.out.println("Start: " + start);
 583:         g2.setPaint(this.paint);
 584: 
 585:         // if the axes use a linear scale, you can uncomment the code below and
 586:         // switch to the alternative transX/transY calculation inside the loop 
 587:         // that follows - it is a little bit faster then.
 588:         // 
 589:         // int xx = (int) dataArea.getMinX();
 590:         // int ww = (int) dataArea.getWidth();
 591:         // int yy = (int) dataArea.getMaxY();
 592:         // int hh = (int) dataArea.getHeight();
 593:         // double domainMin = this.domainAxis.getLowerBound();
 594:         // double domainLength = this.domainAxis.getUpperBound() - domainMin;
 595:         // double rangeMin = this.rangeAxis.getLowerBound();
 596:         // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin;
 597: 
 598:         if (this.data != null) {
 599:             for (int i = 0; i < this.data[0].length; i++) {
 600:                 float x = this.data[0][i];
 601:                 float y = this.data[1][i];
 602:                 
 603:                 //int transX = (int) (xx + ww * (x - domainMin) / domainLength);
 604:                 //int transY = (int) (yy - hh * (y - rangeMin) / rangeLength); 
 605:                 int transX = (int) this.domainAxis.valueToJava2D(x, dataArea, 
 606:                         RectangleEdge.BOTTOM);
 607:                 int transY = (int) this.rangeAxis.valueToJava2D(y, dataArea, 
 608:                         RectangleEdge.LEFT);
 609:                 g2.fillRect(transX, transY, 1, 1);
 610:             }
 611:         }
 612:         //long finish = System.currentTimeMillis();
 613:         //System.out.println("Finish: " + finish);
 614:         //System.out.println("Time: " + (finish - start));
 615: 
 616:     }
 617: 
 618:     /**
 619:      * Draws the gridlines for the plot, if they are visible.
 620:      *
 621:      * @param g2  the graphics device.
 622:      * @param dataArea  the data area.
 623:      * @param ticks  the ticks.
 624:      */
 625:     protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea, 
 626:                                        List ticks) {
 627: 
 628:         // draw the domain grid lines, if the flag says they're visible...
 629:         if (isDomainGridlinesVisible()) {
 630:             Iterator iterator = ticks.iterator();
 631:             while (iterator.hasNext()) {
 632:                 ValueTick tick = (ValueTick) iterator.next();
 633:                 double v = this.domainAxis.valueToJava2D(tick.getValue(), 
 634:                         dataArea, RectangleEdge.BOTTOM);
 635:                 Line2D line = new Line2D.Double(v, dataArea.getMinY(), v, 
 636:                         dataArea.getMaxY());
 637:                 g2.setPaint(getDomainGridlinePaint());
 638:                 g2.setStroke(getDomainGridlineStroke());
 639:                 g2.draw(line);                
 640:             }
 641:         }
 642:     }
 643:     
 644:     /**
 645:      * Draws the gridlines for the plot, if they are visible.
 646:      *
 647:      * @param g2  the graphics device.
 648:      * @param dataArea  the data area.
 649:      * @param ticks  the ticks.
 650:      */
 651:     protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, 
 652:                                       List ticks) {
 653: 
 654:         // draw the range grid lines, if the flag says they're visible...
 655:         if (isRangeGridlinesVisible()) {
 656:             Iterator iterator = ticks.iterator();
 657:             while (iterator.hasNext()) {
 658:                 ValueTick tick = (ValueTick) iterator.next();
 659:                 double v = this.rangeAxis.valueToJava2D(tick.getValue(), 
 660:                         dataArea, RectangleEdge.LEFT);
 661:                 Line2D line = new Line2D.Double(dataArea.getMinX(), v, 
 662:                         dataArea.getMaxX(), v);
 663:                 g2.setPaint(getRangeGridlinePaint());
 664:                 g2.setStroke(getRangeGridlineStroke());
 665:                 g2.draw(line);                
 666:             }
 667:         }
 668: 
 669:     }
 670: 
 671:     /**
 672:      * Returns the range of data values to be plotted along the axis, or
 673:      * <code>null</code> if the specified axis isn't the domain axis or the
 674:      * range axis for the plot.
 675:      *
 676:      * @param axis  the axis (<code>null</code> permitted).
 677:      *
 678:      * @return The range (possibly <code>null</code>).
 679:      */
 680:     public Range getDataRange(ValueAxis axis) {
 681:         Range result = null;
 682:         if (axis == this.domainAxis) {
 683:             result = this.xDataRange;
 684:         }
 685:         else if (axis == this.rangeAxis) {
 686:             result = this.yDataRange;
 687:         }
 688:         return result;
 689:     }
 690: 
 691:     /**
 692:      * Calculates the X data range.
 693:      *
 694:      * @param data  the data (<code>null</code> permitted).
 695:      *
 696:      * @return The range.
 697:      */
 698:     private Range calculateXDataRange(float[][] data) {
 699:         
 700:         Range result = null;
 701:         
 702:         if (data != null) {
 703:             float lowest = Float.POSITIVE_INFINITY;
 704:             float highest = Float.NEGATIVE_INFINITY;
 705:             for (int i = 0; i < data[0].length; i++) {
 706:                 float v = data[0][i];
 707:                 if (v < lowest) {
 708:                     lowest = v;
 709:                 }
 710:                 if (v > highest) {
 711:                     highest = v;
 712:                 }
 713:             }
 714:             if (lowest <= highest) {
 715:                 result = new Range(lowest, highest);
 716:             }
 717:         }
 718:         
 719:         return result;
 720:         
 721:     }
 722: 
 723:     /**
 724:      * Calculates the Y data range.
 725:      *
 726:      * @param data  the data (<code>null</code> permitted).
 727:      *
 728:      * @return The range.
 729:      */
 730:     private Range calculateYDataRange(float[][] data) {
 731:         
 732:         Range result = null;
 733:         
 734:         if (data != null) {
 735:             float lowest = Float.POSITIVE_INFINITY;
 736:             float highest = Float.NEGATIVE_INFINITY;
 737:             for (int i = 0; i < data[0].length; i++) {
 738:                 float v = data[1][i];
 739:                 if (v < lowest) {
 740:                     lowest = v;
 741:                 }
 742:                 if (v > highest) {
 743:                     highest = v;
 744:                 }
 745:             }
 746:             if (lowest <= highest) {
 747:                 result = new Range(lowest, highest);
 748:             }
 749:         }
 750:         return result;
 751:         
 752:     }
 753: 
 754:     /**
 755:      * Multiplies the range on the domain axis/axes by the specified factor.
 756:      *
 757:      * @param factor  the zoom factor.
 758:      * @param info  the plot rendering info.
 759:      * @param source  the source point.
 760:      */
 761:     public void zoomDomainAxes(double factor, PlotRenderingInfo info, 
 762:                                Point2D source) {
 763:         this.domainAxis.resizeRange(factor);
 764:     }
 765: 
 766:     /**
 767:      * Zooms in on the domain axes.
 768:      * 
 769:      * @param lowerPercent  the new lower bound as a percentage of the current 
 770:      *                      range.
 771:      * @param upperPercent  the new upper bound as a percentage of the current
 772:      *                      range.
 773:      * @param info  the plot rendering info.
 774:      * @param source  the source point.
 775:      */
 776:     public void zoomDomainAxes(double lowerPercent, double upperPercent, 
 777:                                PlotRenderingInfo info, Point2D source) {
 778:         this.domainAxis.zoomRange(lowerPercent, upperPercent);
 779:     }
 780: 
 781:     /**
 782:      * Multiplies the range on the range axis/axes by the specified factor.
 783:      *
 784:      * @param factor  the zoom factor.
 785:      * @param info  the plot rendering info.
 786:      * @param source  the source point.
 787:      */
 788:     public void zoomRangeAxes(double factor,
 789:                               PlotRenderingInfo info, Point2D source) {
 790:         this.rangeAxis.resizeRange(factor);
 791:     }
 792: 
 793:     /**
 794:      * Zooms in on the range axes.
 795:      * 
 796:      * @param lowerPercent  the new lower bound as a percentage of the current 
 797:      *                      range.
 798:      * @param upperPercent  the new upper bound as a percentage of the current 
 799:      *                      range.
 800:      * @param info  the plot rendering info.
 801:      * @param source  the source point.
 802:      */
 803:     public void zoomRangeAxes(double lowerPercent, double upperPercent,
 804:                               PlotRenderingInfo info, Point2D source) {
 805:         this.rangeAxis.zoomRange(lowerPercent, upperPercent);
 806:     }
 807: 
 808:     /**
 809:      * Returns <code>true</code>.
 810:      * 
 811:      * @return A boolean.
 812:      */
 813:     public boolean isDomainZoomable() {
 814:         return true;
 815:     }
 816:     
 817:     /**
 818:      * Returns <code>true</code>.
 819:      * 
 820:      * @return A boolean.
 821:      */
 822:     public boolean isRangeZoomable() {
 823:         return true;
 824:     }
 825: 
 826:     /**
 827:      * Tests an object for equality with this instance.
 828:      * 
 829:      * @param obj  the object (<code>null</code> permitted).
 830:      * 
 831:      * @return A boolean.
 832:      */
 833:     public boolean equals(Object obj) {
 834:         if (obj == this) {
 835:             return true;
 836:         }
 837:         if (!super.equals(obj)) {
 838:             return false;
 839:         }
 840:         if (!(obj instanceof FastScatterPlot)) {
 841:             return false;
 842:         }
 843:         FastScatterPlot that = (FastScatterPlot) obj;
 844:         if (!ArrayUtilities.equal(this.data, that.data)) {
 845:             return false;
 846:         }
 847:         if (!ObjectUtilities.equal(this.domainAxis, that.domainAxis)) {
 848:             return false;
 849:         }
 850:         if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
 851:             return false;
 852:         }
 853:         if (!PaintUtilities.equal(this.paint, that.paint)) {
 854:             return false;
 855:         }
 856:         if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
 857:             return false;
 858:         }
 859:         if (!PaintUtilities.equal(this.domainGridlinePaint, 
 860:                 that.domainGridlinePaint)) {
 861:             return false;
 862:         }
 863:         if (!ObjectUtilities.equal(this.domainGridlineStroke, 
 864:                 that.domainGridlineStroke)) {
 865:             return false;
 866:         }  
 867:         if (!this.rangeGridlinesVisible == that.rangeGridlinesVisible) {
 868:             return false;
 869:         }
 870:         if (!PaintUtilities.equal(this.rangeGridlinePaint, 
 871:                 that.rangeGridlinePaint)) {
 872:             return false;
 873:         }
 874:         if (!ObjectUtilities.equal(this.rangeGridlineStroke, 
 875:                 that.rangeGridlineStroke)) {
 876:             return false;
 877:         }              
 878:         return true;
 879:     }
 880:     
 881:     /**
 882:      * Returns a clone of the plot.
 883:      * 
 884:      * @return A clone.
 885:      * 
 886:      * @throws CloneNotSupportedException if some component of the plot does 
 887:      *                                    not support cloning.
 888:      */
 889:     public Object clone() throws CloneNotSupportedException {
 890:     
 891:         FastScatterPlot clone = (FastScatterPlot) super.clone();    
 892:         
 893:         if (this.data != null) {
 894:             clone.data = ArrayUtilities.clone(this.data);    
 895:         }
 896:         
 897:         if (this.domainAxis != null) {
 898:             clone.domainAxis = (ValueAxis) this.domainAxis.clone();
 899:             clone.domainAxis.setPlot(clone);
 900:             clone.domainAxis.addChangeListener(clone);
 901:         }
 902:         
 903:         if (this.rangeAxis != null) {
 904:             clone.rangeAxis = (ValueAxis) this.rangeAxis.clone();
 905:             clone.rangeAxis.setPlot(clone);
 906:             clone.rangeAxis.addChangeListener(clone);
 907:         }
 908:             
 909:         return clone;
 910:         
 911:     }
 912: 
 913:     /**
 914:      * Provides serialization support.
 915:      *
 916:      * @param stream  the output stream.
 917:      *
 918:      * @throws IOException  if there is an I/O error.
 919:      */
 920:     private void writeObject(ObjectOutputStream stream) throws IOException {
 921:         stream.defaultWriteObject();
 922:         SerialUtilities.writePaint(this.paint, stream);
 923:         SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
 924:         SerialUtilities.writePaint(this.domainGridlinePaint, stream);
 925:         SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
 926:         SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
 927:     }
 928: 
 929:     /**
 930:      * Provides serialization support.
 931:      *
 932:      * @param stream  the input stream.
 933:      *
 934:      * @throws IOException  if there is an I/O error.
 935:      * @throws ClassNotFoundException  if there is a classpath problem.
 936:      */
 937:     private void readObject(ObjectInputStream stream) 
 938:             throws IOException, ClassNotFoundException {
 939:         stream.defaultReadObject();
 940: 
 941:         this.paint = SerialUtilities.readPaint(stream);
 942:         this.domainGridlineStroke = SerialUtilities.readStroke(stream);
 943:         this.domainGridlinePaint = SerialUtilities.readPaint(stream);
 944: 
 945:         this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
 946:         this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
 947: 
 948:         if (this.domainAxis != null) {
 949:             this.domainAxis.addChangeListener(this);
 950:         }
 951: 
 952:         if (this.rangeAxis != null) {
 953:             this.rangeAxis.addChangeListener(this);
 954:         }
 955:     }
 956:     
 957: }