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