001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-2008, 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 * 25-Mar-2008 : Make use of new fireChangeEvent() method (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, 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 }