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