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