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 * Changes 036 * ------- 037 * 29-Oct-2002 : Added standard header (DG); 038 * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG); 039 * 26-Mar-2003 : Implemented Serializable (DG); 040 * 19-Aug-2003 : Implemented Cloneable (DG); 041 * 08-Sep-2003 : Added internationalization via use of properties 042 * resourceBundle (RFE 690236) (AL); 043 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 044 * 12-Nov-2003 : Implemented zooming (DG); 045 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 046 * 26-Jan-2004 : Added domain and range grid lines (DG); 047 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 048 * 29-Sep-2004 : Removed hard-coded color (DG); 049 * 04-Oct-2004 : Reworked equals() method and renamed ArrayUtils 050 * --> ArrayUtilities (DG); 051 * 12-Nov-2004 : Implemented the new Zoomable interface (DG); 052 * 05-May-2005 : Updated draw() method parameters (DG); 053 * 16-Jun-2005 : Added get/setData() methods (DG); 054 * ------------- JFREECHART 1.0.x --------------------------------------------- 055 * 10-Nov-2006 : Fixed bug 1593150, by not allowing null axes, and added 056 * setDomainAxis() and setRangeAxis() methods (DG); 057 * 24-Sep-2007 : Implemented new zooming methods (DG); 058 * 059 */ 060 061 package org.jfree.chart.plot; 062 063 import java.awt.AlphaComposite; 064 import java.awt.BasicStroke; 065 import java.awt.Color; 066 import java.awt.Composite; 067 import java.awt.Graphics2D; 068 import java.awt.Paint; 069 import java.awt.Shape; 070 import java.awt.Stroke; 071 import java.awt.geom.Line2D; 072 import java.awt.geom.Point2D; 073 import java.awt.geom.Rectangle2D; 074 import java.io.IOException; 075 import java.io.ObjectInputStream; 076 import java.io.ObjectOutputStream; 077 import java.io.Serializable; 078 import java.util.Iterator; 079 import java.util.List; 080 import java.util.ResourceBundle; 081 082 import org.jfree.chart.axis.AxisSpace; 083 import org.jfree.chart.axis.AxisState; 084 import org.jfree.chart.axis.NumberAxis; 085 import org.jfree.chart.axis.ValueAxis; 086 import org.jfree.chart.axis.ValueTick; 087 import org.jfree.chart.event.PlotChangeEvent; 088 import org.jfree.data.Range; 089 import org.jfree.io.SerialUtilities; 090 import org.jfree.ui.RectangleEdge; 091 import org.jfree.ui.RectangleInsets; 092 import org.jfree.util.ArrayUtilities; 093 import org.jfree.util.ObjectUtilities; 094 import org.jfree.util.PaintUtilities; 095 096 /** 097 * A fast scatter plot. 098 */ 099 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 }