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