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