Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * -------------------- 28: * FastScatterPlot.java 29: * -------------------- 30: * (C) Copyright 2002-2008, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Arnaud Lelievre; 34: * 35: * Changes 36: * ------- 37: * 29-Oct-2002 : Added standard header (DG); 38: * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG); 39: * 26-Mar-2003 : Implemented Serializable (DG); 40: * 19-Aug-2003 : Implemented Cloneable (DG); 41: * 08-Sep-2003 : Added internationalization via use of properties 42: * resourceBundle (RFE 690236) (AL); 43: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 44: * 12-Nov-2003 : Implemented zooming (DG); 45: * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 46: * 26-Jan-2004 : Added domain and range grid lines (DG); 47: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 48: * 29-Sep-2004 : Removed hard-coded color (DG); 49: * 04-Oct-2004 : Reworked equals() method and renamed ArrayUtils 50: * --> ArrayUtilities (DG); 51: * 12-Nov-2004 : Implemented the new Zoomable interface (DG); 52: * 05-May-2005 : Updated draw() method parameters (DG); 53: * 16-Jun-2005 : Added get/setData() methods (DG); 54: * ------------- JFREECHART 1.0.x --------------------------------------------- 55: * 10-Nov-2006 : Fixed bug 1593150, by not allowing null axes, and added 56: * setDomainAxis() and setRangeAxis() methods (DG); 57: * 24-Sep-2007 : Implemented new zooming methods (DG); 58: * 25-Mar-2008 : Make use of new fireChangeEvent() method (DG); 59: * 60: */ 61: 62: package org.jfree.chart.plot; 63: 64: import java.awt.AlphaComposite; 65: import java.awt.BasicStroke; 66: import java.awt.Color; 67: import java.awt.Composite; 68: import java.awt.Graphics2D; 69: import java.awt.Paint; 70: import java.awt.Shape; 71: import java.awt.Stroke; 72: import java.awt.geom.Line2D; 73: import java.awt.geom.Point2D; 74: import java.awt.geom.Rectangle2D; 75: import java.io.IOException; 76: import java.io.ObjectInputStream; 77: import java.io.ObjectOutputStream; 78: import java.io.Serializable; 79: import java.util.Iterator; 80: import java.util.List; 81: import java.util.ResourceBundle; 82: 83: import org.jfree.chart.axis.AxisSpace; 84: import org.jfree.chart.axis.AxisState; 85: import org.jfree.chart.axis.NumberAxis; 86: import org.jfree.chart.axis.ValueAxis; 87: import org.jfree.chart.axis.ValueTick; 88: import org.jfree.chart.event.PlotChangeEvent; 89: import org.jfree.data.Range; 90: import org.jfree.io.SerialUtilities; 91: import org.jfree.ui.RectangleEdge; 92: import org.jfree.ui.RectangleInsets; 93: import org.jfree.util.ArrayUtilities; 94: import org.jfree.util.ObjectUtilities; 95: import org.jfree.util.PaintUtilities; 96: 97: /** 98: * A fast scatter plot. 99: */ 100: public class FastScatterPlot extends Plot implements ValueAxisPlot, 101: Zoomable, Cloneable, Serializable { 102: 103: /** For serialization. */ 104: private static final long serialVersionUID = 7871545897358563521L; 105: 106: /** The default grid line stroke. */ 107: public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, 108: BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[] 109: {2.0f, 2.0f}, 0.0f); 110: 111: /** The default grid line paint. */ 112: public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray; 113: 114: /** The data. */ 115: private float[][] data; 116: 117: /** The x data range. */ 118: private Range xDataRange; 119: 120: /** The y data range. */ 121: private Range yDataRange; 122: 123: /** The domain axis (used for the x-values). */ 124: private ValueAxis domainAxis; 125: 126: /** The range axis (used for the y-values). */ 127: private ValueAxis rangeAxis; 128: 129: /** The paint used to plot data points. */ 130: private transient Paint paint; 131: 132: /** A flag that controls whether the domain grid-lines are visible. */ 133: private boolean domainGridlinesVisible; 134: 135: /** The stroke used to draw the domain grid-lines. */ 136: private transient Stroke domainGridlineStroke; 137: 138: /** The paint used to draw the domain grid-lines. */ 139: private transient Paint domainGridlinePaint; 140: 141: /** A flag that controls whether the range grid-lines are visible. */ 142: private boolean rangeGridlinesVisible; 143: 144: /** The stroke used to draw the range grid-lines. */ 145: private transient Stroke rangeGridlineStroke; 146: 147: /** The paint used to draw the range grid-lines. */ 148: private transient Paint rangeGridlinePaint; 149: 150: /** The resourceBundle for the localization. */ 151: protected static ResourceBundle localizationResources = 152: ResourceBundle.getBundle( 153: "org.jfree.chart.plot.LocalizationBundle"); 154: 155: /** 156: * Creates a new instance of <code>FastScatterPlot</code> with default 157: * axes. 158: */ 159: public FastScatterPlot() { 160: this(null, new NumberAxis("X"), new NumberAxis("Y")); 161: } 162: 163: /** 164: * Creates a new fast scatter plot. 165: * <p> 166: * The data is an array of x, y values: data[0][i] = x, data[1][i] = y. 167: * 168: * @param data the data (<code>null</code> permitted). 169: * @param domainAxis the domain (x) axis (<code>null</code> not permitted). 170: * @param rangeAxis the range (y) axis (<code>null</code> not permitted). 171: */ 172: public FastScatterPlot(float[][] data, 173: ValueAxis domainAxis, ValueAxis rangeAxis) { 174: 175: super(); 176: if (domainAxis == null) { 177: throw new IllegalArgumentException("Null 'domainAxis' argument."); 178: } 179: if (rangeAxis == null) { 180: throw new IllegalArgumentException("Null 'rangeAxis' argument."); 181: } 182: 183: this.data = data; 184: this.xDataRange = calculateXDataRange(data); 185: this.yDataRange = calculateYDataRange(data); 186: this.domainAxis = domainAxis; 187: this.domainAxis.setPlot(this); 188: this.domainAxis.addChangeListener(this); 189: this.rangeAxis = rangeAxis; 190: this.rangeAxis.setPlot(this); 191: this.rangeAxis.addChangeListener(this); 192: 193: this.paint = Color.red; 194: 195: this.domainGridlinesVisible = true; 196: this.domainGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT; 197: this.domainGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE; 198: 199: this.rangeGridlinesVisible = true; 200: this.rangeGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT; 201: this.rangeGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE; 202: 203: } 204: 205: /** 206: * Returns a short string describing the plot type. 207: * 208: * @return A short string describing the plot type. 209: */ 210: public String getPlotType() { 211: return localizationResources.getString("Fast_Scatter_Plot"); 212: } 213: 214: /** 215: * Returns the data array used by the plot. 216: * 217: * @return The data array (possibly <code>null</code>). 218: * 219: * @see #setData(float[][]) 220: */ 221: public float[][] getData() { 222: return this.data; 223: } 224: 225: /** 226: * Sets the data array used by the plot and sends a {@link PlotChangeEvent} 227: * to all registered listeners. 228: * 229: * @param data the data array (<code>null</code> permitted). 230: * 231: * @see #getData() 232: */ 233: public void setData(float[][] data) { 234: this.data = data; 235: fireChangeEvent(); 236: } 237: 238: /** 239: * Returns the orientation of the plot. 240: * 241: * @return The orientation (always {@link PlotOrientation#VERTICAL}). 242: */ 243: public PlotOrientation getOrientation() { 244: return PlotOrientation.VERTICAL; 245: } 246: 247: /** 248: * Returns the domain axis for the plot. 249: * 250: * @return The domain axis (never <code>null</code>). 251: * 252: * @see #setDomainAxis(ValueAxis) 253: */ 254: public ValueAxis getDomainAxis() { 255: return this.domainAxis; 256: } 257: 258: /** 259: * Sets the domain axis and sends a {@link PlotChangeEvent} to all 260: * registered listeners. 261: * 262: * @param axis the axis (<code>null</code> not permitted). 263: * 264: * @since 1.0.3 265: * 266: * @see #getDomainAxis() 267: */ 268: public void setDomainAxis(ValueAxis axis) { 269: if (axis == null) { 270: throw new IllegalArgumentException("Null 'axis' argument."); 271: } 272: this.domainAxis = axis; 273: fireChangeEvent(); 274: } 275: 276: /** 277: * Returns the range axis for the plot. 278: * 279: * @return The range axis (never <code>null</code>). 280: * 281: * @see #setRangeAxis(ValueAxis) 282: */ 283: public ValueAxis getRangeAxis() { 284: return this.rangeAxis; 285: } 286: 287: /** 288: * Sets the range axis and sends a {@link PlotChangeEvent} to all 289: * registered listeners. 290: * 291: * @param axis the axis (<code>null</code> not permitted). 292: * 293: * @since 1.0.3 294: * 295: * @see #getRangeAxis() 296: */ 297: public void setRangeAxis(ValueAxis axis) { 298: if (axis == null) { 299: throw new IllegalArgumentException("Null 'axis' argument."); 300: } 301: this.rangeAxis = axis; 302: fireChangeEvent(); 303: } 304: 305: /** 306: * Returns the paint used to plot data points. The default is 307: * <code>Color.red</code>. 308: * 309: * @return The paint. 310: * 311: * @see #setPaint(Paint) 312: */ 313: public Paint getPaint() { 314: return this.paint; 315: } 316: 317: /** 318: * Sets the color for the data points and sends a {@link PlotChangeEvent} 319: * to all registered listeners. 320: * 321: * @param paint the paint (<code>null</code> not permitted). 322: * 323: * @see #getPaint() 324: */ 325: public void setPaint(Paint paint) { 326: if (paint == null) { 327: throw new IllegalArgumentException("Null 'paint' argument."); 328: } 329: this.paint = paint; 330: fireChangeEvent(); 331: } 332: 333: /** 334: * Returns <code>true</code> if the domain gridlines are visible, and 335: * <code>false<code> otherwise. 336: * 337: * @return <code>true</code> or <code>false</code>. 338: * 339: * @see #setDomainGridlinesVisible(boolean) 340: * @see #setDomainGridlinePaint(Paint) 341: */ 342: public boolean isDomainGridlinesVisible() { 343: return this.domainGridlinesVisible; 344: } 345: 346: /** 347: * Sets the flag that controls whether or not the domain grid-lines are 348: * visible. If the flag value is changed, a {@link PlotChangeEvent} is 349: * sent to all registered listeners. 350: * 351: * @param visible the new value of the flag. 352: * 353: * @see #getDomainGridlinePaint() 354: */ 355: public void setDomainGridlinesVisible(boolean visible) { 356: if (this.domainGridlinesVisible != visible) { 357: this.domainGridlinesVisible = visible; 358: fireChangeEvent(); 359: } 360: } 361: 362: /** 363: * Returns the stroke for the grid-lines (if any) plotted against the 364: * domain axis. 365: * 366: * @return The stroke (never <code>null</code>). 367: * 368: * @see #setDomainGridlineStroke(Stroke) 369: */ 370: public Stroke getDomainGridlineStroke() { 371: return this.domainGridlineStroke; 372: } 373: 374: /** 375: * Sets the stroke for the grid lines plotted against the domain axis and 376: * sends a {@link PlotChangeEvent} to all registered listeners. 377: * 378: * @param stroke the stroke (<code>null</code> not permitted). 379: * 380: * @see #getDomainGridlineStroke() 381: */ 382: public void setDomainGridlineStroke(Stroke stroke) { 383: if (stroke == null) { 384: throw new IllegalArgumentException("Null 'stroke' argument."); 385: } 386: this.domainGridlineStroke = stroke; 387: fireChangeEvent(); 388: } 389: 390: /** 391: * Returns the paint for the grid lines (if any) plotted against the domain 392: * axis. 393: * 394: * @return The paint (never <code>null</code>). 395: * 396: * @see #setDomainGridlinePaint(Paint) 397: */ 398: public Paint getDomainGridlinePaint() { 399: return this.domainGridlinePaint; 400: } 401: 402: /** 403: * Sets the paint for the grid lines plotted against the domain axis and 404: * sends a {@link PlotChangeEvent} to all registered listeners. 405: * 406: * @param paint the paint (<code>null</code> not permitted). 407: * 408: * @see #getDomainGridlinePaint() 409: */ 410: public void setDomainGridlinePaint(Paint paint) { 411: if (paint == null) { 412: throw new IllegalArgumentException("Null 'paint' argument."); 413: } 414: this.domainGridlinePaint = paint; 415: fireChangeEvent(); 416: } 417: 418: /** 419: * Returns <code>true</code> if the range axis grid is visible, and 420: * <code>false<code> otherwise. 421: * 422: * @return <code>true</code> or <code>false</code>. 423: * 424: * @see #setRangeGridlinesVisible(boolean) 425: */ 426: public boolean isRangeGridlinesVisible() { 427: return this.rangeGridlinesVisible; 428: } 429: 430: /** 431: * Sets the flag that controls whether or not the range axis grid lines are 432: * visible. If the flag value is changed, a {@link PlotChangeEvent} is 433: * sent to all registered listeners. 434: * 435: * @param visible the new value of the flag. 436: * 437: * @see #isRangeGridlinesVisible() 438: */ 439: public void setRangeGridlinesVisible(boolean visible) { 440: if (this.rangeGridlinesVisible != visible) { 441: this.rangeGridlinesVisible = visible; 442: fireChangeEvent(); 443: } 444: } 445: 446: /** 447: * Returns the stroke for the grid lines (if any) plotted against the range 448: * axis. 449: * 450: * @return The stroke (never <code>null</code>). 451: * 452: * @see #setRangeGridlineStroke(Stroke) 453: */ 454: public Stroke getRangeGridlineStroke() { 455: return this.rangeGridlineStroke; 456: } 457: 458: /** 459: * Sets the stroke for the grid lines plotted against the range axis and 460: * sends a {@link PlotChangeEvent} to all registered listeners. 461: * 462: * @param stroke the stroke (<code>null</code> permitted). 463: * 464: * @see #getRangeGridlineStroke() 465: */ 466: public void setRangeGridlineStroke(Stroke stroke) { 467: if (stroke == null) { 468: throw new IllegalArgumentException("Null 'stroke' argument."); 469: } 470: this.rangeGridlineStroke = stroke; 471: fireChangeEvent(); 472: } 473: 474: /** 475: * Returns the paint for the grid lines (if any) plotted against the range 476: * axis. 477: * 478: * @return The paint (never <code>null</code>). 479: * 480: * @see #setRangeGridlinePaint(Paint) 481: */ 482: public Paint getRangeGridlinePaint() { 483: return this.rangeGridlinePaint; 484: } 485: 486: /** 487: * Sets the paint for the grid lines plotted against the range axis and 488: * sends a {@link PlotChangeEvent} to all registered listeners. 489: * 490: * @param paint the paint (<code>null</code> not permitted). 491: * 492: * @see #getRangeGridlinePaint() 493: */ 494: public void setRangeGridlinePaint(Paint paint) { 495: if (paint == null) { 496: throw new IllegalArgumentException("Null 'paint' argument."); 497: } 498: this.rangeGridlinePaint = paint; 499: fireChangeEvent(); 500: } 501: 502: /** 503: * Draws the fast scatter plot on a Java 2D graphics device (such as the 504: * screen or a printer). 505: * 506: * @param g2 the graphics device. 507: * @param area the area within which the plot (including axis labels) 508: * should be drawn. 509: * @param anchor the anchor point (<code>null</code> permitted). 510: * @param parentState the state from the parent plot (ignored). 511: * @param info collects chart drawing information (<code>null</code> 512: * permitted). 513: */ 514: public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 515: PlotState parentState, 516: PlotRenderingInfo info) { 517: 518: // set up info collection... 519: if (info != null) { 520: info.setPlotArea(area); 521: } 522: 523: // adjust the drawing area for plot insets (if any)... 524: RectangleInsets insets = getInsets(); 525: insets.trim(area); 526: 527: AxisSpace space = new AxisSpace(); 528: space = this.domainAxis.reserveSpace(g2, this, area, 529: RectangleEdge.BOTTOM, space); 530: space = this.rangeAxis.reserveSpace(g2, this, area, RectangleEdge.LEFT, 531: space); 532: Rectangle2D dataArea = space.shrink(area, null); 533: 534: if (info != null) { 535: info.setDataArea(dataArea); 536: } 537: 538: // draw the plot background and axes... 539: drawBackground(g2, dataArea); 540: 541: AxisState domainAxisState = this.domainAxis.draw(g2, 542: dataArea.getMaxY(), area, dataArea, RectangleEdge.BOTTOM, info); 543: AxisState rangeAxisState = this.rangeAxis.draw(g2, dataArea.getMinX(), 544: area, dataArea, RectangleEdge.LEFT, info); 545: drawDomainGridlines(g2, dataArea, domainAxisState.getTicks()); 546: drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); 547: 548: Shape originalClip = g2.getClip(); 549: Composite originalComposite = g2.getComposite(); 550: 551: g2.clip(dataArea); 552: g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 553: getForegroundAlpha())); 554: 555: render(g2, dataArea, info, null); 556: 557: g2.setClip(originalClip); 558: g2.setComposite(originalComposite); 559: drawOutline(g2, dataArea); 560: 561: } 562: 563: /** 564: * Draws a representation of the data within the dataArea region. The 565: * <code>info</code> and <code>crosshairState</code> arguments may be 566: * <code>null</code>. 567: * 568: * @param g2 the graphics device. 569: * @param dataArea the region in which the data is to be drawn. 570: * @param info an optional object for collection dimension information. 571: * @param crosshairState collects crosshair information (<code>null</code> 572: * permitted). 573: */ 574: public void render(Graphics2D g2, Rectangle2D dataArea, 575: PlotRenderingInfo info, CrosshairState crosshairState) { 576: 577: 578: //long start = System.currentTimeMillis(); 579: //System.out.println("Start: " + start); 580: g2.setPaint(this.paint); 581: 582: // if the axes use a linear scale, you can uncomment the code below and 583: // switch to the alternative transX/transY calculation inside the loop 584: // that follows - it is a little bit faster then. 585: // 586: // int xx = (int) dataArea.getMinX(); 587: // int ww = (int) dataArea.getWidth(); 588: // int yy = (int) dataArea.getMaxY(); 589: // int hh = (int) dataArea.getHeight(); 590: // double domainMin = this.domainAxis.getLowerBound(); 591: // double domainLength = this.domainAxis.getUpperBound() - domainMin; 592: // double rangeMin = this.rangeAxis.getLowerBound(); 593: // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin; 594: 595: if (this.data != null) { 596: for (int i = 0; i < this.data[0].length; i++) { 597: float x = this.data[0][i]; 598: float y = this.data[1][i]; 599: 600: //int transX = (int) (xx + ww * (x - domainMin) / domainLength); 601: //int transY = (int) (yy - hh * (y - rangeMin) / rangeLength); 602: int transX = (int) this.domainAxis.valueToJava2D(x, dataArea, 603: RectangleEdge.BOTTOM); 604: int transY = (int) this.rangeAxis.valueToJava2D(y, dataArea, 605: RectangleEdge.LEFT); 606: g2.fillRect(transX, transY, 1, 1); 607: } 608: } 609: //long finish = System.currentTimeMillis(); 610: //System.out.println("Finish: " + finish); 611: //System.out.println("Time: " + (finish - start)); 612: 613: } 614: 615: /** 616: * Draws the gridlines for the plot, if they are visible. 617: * 618: * @param g2 the graphics device. 619: * @param dataArea the data area. 620: * @param ticks the ticks. 621: */ 622: protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea, 623: List ticks) { 624: 625: // draw the domain grid lines, if the flag says they're visible... 626: if (isDomainGridlinesVisible()) { 627: Iterator iterator = ticks.iterator(); 628: while (iterator.hasNext()) { 629: ValueTick tick = (ValueTick) iterator.next(); 630: double v = this.domainAxis.valueToJava2D(tick.getValue(), 631: dataArea, RectangleEdge.BOTTOM); 632: Line2D line = new Line2D.Double(v, dataArea.getMinY(), v, 633: dataArea.getMaxY()); 634: g2.setPaint(getDomainGridlinePaint()); 635: g2.setStroke(getDomainGridlineStroke()); 636: g2.draw(line); 637: } 638: } 639: } 640: 641: /** 642: * Draws the gridlines for the plot, if they are visible. 643: * 644: * @param g2 the graphics device. 645: * @param dataArea the data area. 646: * @param ticks the ticks. 647: */ 648: protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, 649: List ticks) { 650: 651: // draw the range grid lines, if the flag says they're visible... 652: if (isRangeGridlinesVisible()) { 653: Iterator iterator = ticks.iterator(); 654: while (iterator.hasNext()) { 655: ValueTick tick = (ValueTick) iterator.next(); 656: double v = this.rangeAxis.valueToJava2D(tick.getValue(), 657: dataArea, RectangleEdge.LEFT); 658: Line2D line = new Line2D.Double(dataArea.getMinX(), v, 659: dataArea.getMaxX(), v); 660: g2.setPaint(getRangeGridlinePaint()); 661: g2.setStroke(getRangeGridlineStroke()); 662: g2.draw(line); 663: } 664: } 665: 666: } 667: 668: /** 669: * Returns the range of data values to be plotted along the axis, or 670: * <code>null</code> if the specified axis isn't the domain axis or the 671: * range axis for the plot. 672: * 673: * @param axis the axis (<code>null</code> permitted). 674: * 675: * @return The range (possibly <code>null</code>). 676: */ 677: public Range getDataRange(ValueAxis axis) { 678: Range result = null; 679: if (axis == this.domainAxis) { 680: result = this.xDataRange; 681: } 682: else if (axis == this.rangeAxis) { 683: result = this.yDataRange; 684: } 685: return result; 686: } 687: 688: /** 689: * Calculates the X data range. 690: * 691: * @param data the data (<code>null</code> permitted). 692: * 693: * @return The range. 694: */ 695: private Range calculateXDataRange(float[][] data) { 696: 697: Range result = null; 698: 699: if (data != null) { 700: float lowest = Float.POSITIVE_INFINITY; 701: float highest = Float.NEGATIVE_INFINITY; 702: for (int i = 0; i < data[0].length; i++) { 703: float v = data[0][i]; 704: if (v < lowest) { 705: lowest = v; 706: } 707: if (v > highest) { 708: highest = v; 709: } 710: } 711: if (lowest <= highest) { 712: result = new Range(lowest, highest); 713: } 714: } 715: 716: return result; 717: 718: } 719: 720: /** 721: * Calculates the Y data range. 722: * 723: * @param data the data (<code>null</code> permitted). 724: * 725: * @return The range. 726: */ 727: private Range calculateYDataRange(float[][] data) { 728: 729: Range result = null; 730: 731: if (data != null) { 732: float lowest = Float.POSITIVE_INFINITY; 733: float highest = Float.NEGATIVE_INFINITY; 734: for (int i = 0; i < data[0].length; i++) { 735: float v = data[1][i]; 736: if (v < lowest) { 737: lowest = v; 738: } 739: if (v > highest) { 740: highest = v; 741: } 742: } 743: if (lowest <= highest) { 744: result = new Range(lowest, highest); 745: } 746: } 747: return result; 748: 749: } 750: 751: /** 752: * Multiplies the range on the domain axis by the specified factor. 753: * 754: * @param factor the zoom factor. 755: * @param info the plot rendering info. 756: * @param source the source point. 757: */ 758: public void zoomDomainAxes(double factor, PlotRenderingInfo info, 759: Point2D source) { 760: this.domainAxis.resizeRange(factor); 761: } 762: 763: /** 764: * Multiplies the range on the domain axis by the specified factor. 765: * 766: * @param factor the zoom factor. 767: * @param info the plot rendering info. 768: * @param source the source point (in Java2D space). 769: * @param useAnchor use source point as zoom anchor? 770: * 771: * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean) 772: * 773: * @since 1.0.7 774: */ 775: public void zoomDomainAxes(double factor, PlotRenderingInfo info, 776: Point2D source, boolean useAnchor) { 777: 778: if (useAnchor) { 779: // get the source coordinate - this plot has always a VERTICAL 780: // orientation 781: double sourceX = source.getX(); 782: double anchorX = this.domainAxis.java2DToValue(sourceX, 783: info.getDataArea(), RectangleEdge.BOTTOM); 784: this.domainAxis.resizeRange(factor, anchorX); 785: } 786: else { 787: this.domainAxis.resizeRange(factor); 788: } 789: 790: } 791: 792: /** 793: * Zooms in on the domain axes. 794: * 795: * @param lowerPercent the new lower bound as a percentage of the current 796: * range. 797: * @param upperPercent the new upper bound as a percentage of the current 798: * range. 799: * @param info the plot rendering info. 800: * @param source the source point. 801: */ 802: public void zoomDomainAxes(double lowerPercent, double upperPercent, 803: PlotRenderingInfo info, Point2D source) { 804: this.domainAxis.zoomRange(lowerPercent, upperPercent); 805: } 806: 807: /** 808: * Multiplies the range on the range axis/axes by the specified factor. 809: * 810: * @param factor the zoom factor. 811: * @param info the plot rendering info. 812: * @param source the source point. 813: */ 814: public void zoomRangeAxes(double factor, 815: PlotRenderingInfo info, Point2D source) { 816: this.rangeAxis.resizeRange(factor); 817: } 818: 819: /** 820: * Multiplies the range on the range axis by the specified factor. 821: * 822: * @param factor the zoom factor. 823: * @param info the plot rendering info. 824: * @param source the source point (in Java2D space). 825: * @param useAnchor use source point as zoom anchor? 826: * 827: * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) 828: * 829: * @since 1.0.7 830: */ 831: public void zoomRangeAxes(double factor, PlotRenderingInfo info, 832: Point2D source, boolean useAnchor) { 833: 834: if (useAnchor) { 835: // get the source coordinate - this plot has always a VERTICAL 836: // orientation 837: double sourceX = source.getX(); 838: double anchorX = this.rangeAxis.java2DToValue(sourceX, 839: info.getDataArea(), RectangleEdge.LEFT); 840: this.rangeAxis.resizeRange(factor, anchorX); 841: } 842: else { 843: this.rangeAxis.resizeRange(factor); 844: } 845: 846: } 847: 848: /** 849: * Zooms in on the range axes. 850: * 851: * @param lowerPercent the new lower bound as a percentage of the current 852: * range. 853: * @param upperPercent the new upper bound as a percentage of the current 854: * range. 855: * @param info the plot rendering info. 856: * @param source the source point. 857: */ 858: public void zoomRangeAxes(double lowerPercent, double upperPercent, 859: PlotRenderingInfo info, Point2D source) { 860: this.rangeAxis.zoomRange(lowerPercent, upperPercent); 861: } 862: 863: /** 864: * Returns <code>true</code>. 865: * 866: * @return A boolean. 867: */ 868: public boolean isDomainZoomable() { 869: return true; 870: } 871: 872: /** 873: * Returns <code>true</code>. 874: * 875: * @return A boolean. 876: */ 877: public boolean isRangeZoomable() { 878: return true; 879: } 880: 881: /** 882: * Tests an object for equality with this instance. 883: * 884: * @param obj the object (<code>null</code> permitted). 885: * 886: * @return A boolean. 887: */ 888: public boolean equals(Object obj) { 889: if (obj == this) { 890: return true; 891: } 892: if (!super.equals(obj)) { 893: return false; 894: } 895: if (!(obj instanceof FastScatterPlot)) { 896: return false; 897: } 898: FastScatterPlot that = (FastScatterPlot) obj; 899: if (!ArrayUtilities.equal(this.data, that.data)) { 900: return false; 901: } 902: if (!ObjectUtilities.equal(this.domainAxis, that.domainAxis)) { 903: return false; 904: } 905: if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) { 906: return false; 907: } 908: if (!PaintUtilities.equal(this.paint, that.paint)) { 909: return false; 910: } 911: if (this.domainGridlinesVisible != that.domainGridlinesVisible) { 912: return false; 913: } 914: if (!PaintUtilities.equal(this.domainGridlinePaint, 915: that.domainGridlinePaint)) { 916: return false; 917: } 918: if (!ObjectUtilities.equal(this.domainGridlineStroke, 919: that.domainGridlineStroke)) { 920: return false; 921: } 922: if (!this.rangeGridlinesVisible == that.rangeGridlinesVisible) { 923: return false; 924: } 925: if (!PaintUtilities.equal(this.rangeGridlinePaint, 926: that.rangeGridlinePaint)) { 927: return false; 928: } 929: if (!ObjectUtilities.equal(this.rangeGridlineStroke, 930: that.rangeGridlineStroke)) { 931: return false; 932: } 933: return true; 934: } 935: 936: /** 937: * Returns a clone of the plot. 938: * 939: * @return A clone. 940: * 941: * @throws CloneNotSupportedException if some component of the plot does 942: * not support cloning. 943: */ 944: public Object clone() throws CloneNotSupportedException { 945: 946: FastScatterPlot clone = (FastScatterPlot) super.clone(); 947: 948: if (this.data != null) { 949: clone.data = ArrayUtilities.clone(this.data); 950: } 951: 952: if (this.domainAxis != null) { 953: clone.domainAxis = (ValueAxis) this.domainAxis.clone(); 954: clone.domainAxis.setPlot(clone); 955: clone.domainAxis.addChangeListener(clone); 956: } 957: 958: if (this.rangeAxis != null) { 959: clone.rangeAxis = (ValueAxis) this.rangeAxis.clone(); 960: clone.rangeAxis.setPlot(clone); 961: clone.rangeAxis.addChangeListener(clone); 962: } 963: 964: return clone; 965: 966: } 967: 968: /** 969: * Provides serialization support. 970: * 971: * @param stream the output stream. 972: * 973: * @throws IOException if there is an I/O error. 974: */ 975: private void writeObject(ObjectOutputStream stream) throws IOException { 976: stream.defaultWriteObject(); 977: SerialUtilities.writePaint(this.paint, stream); 978: SerialUtilities.writeStroke(this.domainGridlineStroke, stream); 979: SerialUtilities.writePaint(this.domainGridlinePaint, stream); 980: SerialUtilities.writeStroke(this.rangeGridlineStroke, stream); 981: SerialUtilities.writePaint(this.rangeGridlinePaint, stream); 982: } 983: 984: /** 985: * Provides serialization support. 986: * 987: * @param stream the input stream. 988: * 989: * @throws IOException if there is an I/O error. 990: * @throws ClassNotFoundException if there is a classpath problem. 991: */ 992: private void readObject(ObjectInputStream stream) 993: throws IOException, ClassNotFoundException { 994: stream.defaultReadObject(); 995: 996: this.paint = SerialUtilities.readPaint(stream); 997: this.domainGridlineStroke = SerialUtilities.readStroke(stream); 998: this.domainGridlinePaint = SerialUtilities.readPaint(stream); 999: 1000: this.rangeGridlineStroke = SerialUtilities.readStroke(stream); 1001: this.rangeGridlinePaint = SerialUtilities.readPaint(stream); 1002: 1003: if (this.domainAxis != null) { 1004: this.domainAxis.addChangeListener(this); 1005: } 1006: 1007: if (this.rangeAxis != null) { 1008: this.rangeAxis.addChangeListener(this); 1009: } 1010: } 1011: 1012: }