001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * -------------------- 028 * FastScatterPlot.java 029 * -------------------- 030 * (C) Copyright 2002-2007, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Arnaud Lelievre; 034 * 035 * $Id: FastScatterPlot.java,v 1.11.2.5 2007/01/11 11:06:04 mungady Exp $ 036 * 037 * Changes (from 29-Oct-2002) 038 * -------------------------- 039 * 29-Oct-2002 : Added standard header (DG); 040 * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG); 041 * 26-Mar-2003 : Implemented Serializable (DG); 042 * 19-Aug-2003 : Implemented Cloneable (DG); 043 * 08-Sep-2003 : Added internationalization via use of properties 044 * resourceBundle (RFE 690236) (AL); 045 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 046 * 12-Nov-2003 : Implemented zooming (DG); 047 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 048 * 26-Jan-2004 : Added domain and range grid lines (DG); 049 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 050 * 29-Sep-2004 : Removed hard-coded color (DG); 051 * 04-Oct-2004 : Reworked equals() method and renamed ArrayUtils 052 * --> ArrayUtilities (DG); 053 * 12-Nov-2004 : Implemented the new Zoomable interface (DG); 054 * 05-May-2005 : Updated draw() method parameters (DG); 055 * 16-Jun-2005 : Added get/setData() methods (DG); 056 * ------------- JFREECHART 1.0.x --------------------------------------------- 057 * 10-Nov-2006 : Fixed bug 1593150, by not allowing null axes, and added 058 * setDomainAxis() and setRangeAxis() methods (DG); 059 * 060 */ 061 062 package org.jfree.chart.plot; 063 064 import java.awt.AlphaComposite; 065 import java.awt.BasicStroke; 066 import java.awt.Color; 067 import java.awt.Composite; 068 import java.awt.Graphics2D; 069 import java.awt.Paint; 070 import java.awt.Shape; 071 import java.awt.Stroke; 072 import java.awt.geom.Line2D; 073 import java.awt.geom.Point2D; 074 import java.awt.geom.Rectangle2D; 075 import java.io.IOException; 076 import java.io.ObjectInputStream; 077 import java.io.ObjectOutputStream; 078 import java.io.Serializable; 079 import java.util.Iterator; 080 import java.util.List; 081 import java.util.ResourceBundle; 082 083 import org.jfree.chart.axis.AxisSpace; 084 import org.jfree.chart.axis.AxisState; 085 import org.jfree.chart.axis.NumberAxis; 086 import org.jfree.chart.axis.ValueAxis; 087 import org.jfree.chart.axis.ValueTick; 088 import org.jfree.chart.event.PlotChangeEvent; 089 import org.jfree.data.Range; 090 import org.jfree.io.SerialUtilities; 091 import org.jfree.ui.RectangleEdge; 092 import org.jfree.ui.RectangleInsets; 093 import org.jfree.util.ArrayUtilities; 094 import org.jfree.util.ObjectUtilities; 095 import org.jfree.util.PaintUtilities; 096 097 /** 098 * A fast scatter plot. 099 */ 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 }