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