001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jfreechart/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * --------------------
028: * FastScatterPlot.java
029: * --------------------
030: * (C) Copyright 2002-2007, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): Arnaud Lelievre;
034: *
035: * $Id: FastScatterPlot.java,v 1.11.2.5 2007/01/11 11:06:04 mungady Exp $
036: *
037: * Changes (from 29-Oct-2002)
038: * --------------------------
039: * 29-Oct-2002 : Added standard header (DG);
040: * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG);
041: * 26-Mar-2003 : Implemented Serializable (DG);
042: * 19-Aug-2003 : Implemented Cloneable (DG);
043: * 08-Sep-2003 : Added internationalization via use of properties
044: * resourceBundle (RFE 690236) (AL);
045: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
046: * 12-Nov-2003 : Implemented zooming (DG);
047: * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
048: * 26-Jan-2004 : Added domain and range grid lines (DG);
049: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
050: * 29-Sep-2004 : Removed hard-coded color (DG);
051: * 04-Oct-2004 : Reworked equals() method and renamed ArrayUtils
052: * --> ArrayUtilities (DG);
053: * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
054: * 05-May-2005 : Updated draw() method parameters (DG);
055: * 16-Jun-2005 : Added get/setData() methods (DG);
056: * ------------- JFREECHART 1.0.x ---------------------------------------------
057: * 10-Nov-2006 : Fixed bug 1593150, by not allowing null axes, and added
058: * setDomainAxis() and setRangeAxis() methods (DG);
059: *
060: */
061:
062: package org.jfree.chart.plot;
063:
064: import java.awt.AlphaComposite;
065: import java.awt.BasicStroke;
066: import java.awt.Color;
067: import java.awt.Composite;
068: import java.awt.Graphics2D;
069: import java.awt.Paint;
070: import java.awt.Shape;
071: import java.awt.Stroke;
072: import java.awt.geom.Line2D;
073: import java.awt.geom.Point2D;
074: import java.awt.geom.Rectangle2D;
075: import java.io.IOException;
076: import java.io.ObjectInputStream;
077: import java.io.ObjectOutputStream;
078: import java.io.Serializable;
079: import java.util.Iterator;
080: import java.util.List;
081: import java.util.ResourceBundle;
082:
083: import org.jfree.chart.axis.AxisSpace;
084: import org.jfree.chart.axis.AxisState;
085: import org.jfree.chart.axis.NumberAxis;
086: import org.jfree.chart.axis.ValueAxis;
087: import org.jfree.chart.axis.ValueTick;
088: import org.jfree.chart.event.PlotChangeEvent;
089: import org.jfree.data.Range;
090: import org.jfree.io.SerialUtilities;
091: import org.jfree.ui.RectangleEdge;
092: import org.jfree.ui.RectangleInsets;
093: import org.jfree.util.ArrayUtilities;
094: import org.jfree.util.ObjectUtilities;
095: import org.jfree.util.PaintUtilities;
096:
097: /**
098: * A fast scatter plot.
099: */
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(
108: 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f,
109: new float[] { 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 = ResourceBundle
152: .getBundle("org.jfree.chart.plot.LocalizationBundle");
153:
154: /**
155: * Creates a new instance of <code>FastScatterPlot</code> with default
156: * axes.
157: */
158: public FastScatterPlot() {
159: this (null, new NumberAxis("X"), new NumberAxis("Y"));
160: }
161:
162: /**
163: * Creates a new fast scatter plot.
164: * <p>
165: * The data is an array of x, y values: data[0][i] = x, data[1][i] = y.
166: *
167: * @param data the data (<code>null</code> permitted).
168: * @param domainAxis the domain (x) axis (<code>null</code> not permitted).
169: * @param rangeAxis the range (y) axis (<code>null</code> not permitted).
170: */
171: public FastScatterPlot(float[][] data, ValueAxis domainAxis,
172: ValueAxis rangeAxis) {
173:
174: super ();
175: if (domainAxis == null) {
176: throw new IllegalArgumentException(
177: "Null 'domainAxis' argument.");
178: }
179: if (rangeAxis == null) {
180: throw new IllegalArgumentException(
181: "Null 'rangeAxis' argument.");
182: }
183:
184: this .data = data;
185: this .xDataRange = calculateXDataRange(data);
186: this .yDataRange = calculateYDataRange(data);
187: this .domainAxis = domainAxis;
188: this .domainAxis.setPlot(this );
189: this .domainAxis.addChangeListener(this );
190: this .rangeAxis = rangeAxis;
191: this .rangeAxis.setPlot(this );
192: this .rangeAxis.addChangeListener(this );
193:
194: this .paint = Color.red;
195:
196: this .domainGridlinesVisible = true;
197: this .domainGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
198: this .domainGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
199:
200: this .rangeGridlinesVisible = true;
201: this .rangeGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
202: this .rangeGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
203:
204: }
205:
206: /**
207: * Returns a short string describing the plot type.
208: *
209: * @return A short string describing the plot type.
210: */
211: public String getPlotType() {
212: return localizationResources.getString("Fast_Scatter_Plot");
213: }
214:
215: /**
216: * Returns the data array used by the plot.
217: *
218: * @return The data array (possibly <code>null</code>).
219: *
220: * @see #setData(float[][])
221: */
222: public float[][] getData() {
223: return this .data;
224: }
225:
226: /**
227: * Sets the data array used by the plot and sends a {@link PlotChangeEvent}
228: * to all registered listeners.
229: *
230: * @param data the data array (<code>null</code> permitted).
231: *
232: * @see #getData()
233: */
234: public void setData(float[][] data) {
235: this .data = data;
236: notifyListeners(new PlotChangeEvent(this ));
237: }
238:
239: /**
240: * Returns the orientation of the plot.
241: *
242: * @return The orientation (always {@link PlotOrientation#VERTICAL}).
243: */
244: public PlotOrientation getOrientation() {
245: return PlotOrientation.VERTICAL;
246: }
247:
248: /**
249: * Returns the domain axis for the plot.
250: *
251: * @return The domain axis (never <code>null</code>).
252: *
253: * @see #setDomainAxis(ValueAxis)
254: */
255: public ValueAxis getDomainAxis() {
256: return this .domainAxis;
257: }
258:
259: /**
260: * Sets the domain axis and sends a {@link PlotChangeEvent} to all
261: * registered listeners.
262: *
263: * @param axis the axis (<code>null</code> not permitted).
264: *
265: * @since 1.0.3
266: *
267: * @see #getDomainAxis()
268: */
269: public void setDomainAxis(ValueAxis axis) {
270: if (axis == null) {
271: throw new IllegalArgumentException("Null 'axis' argument.");
272: }
273: this .domainAxis = axis;
274: notifyListeners(new PlotChangeEvent(this ));
275: }
276:
277: /**
278: * Returns the range axis for the plot.
279: *
280: * @return The range axis (never <code>null</code>).
281: *
282: * @see #setRangeAxis(ValueAxis)
283: */
284: public ValueAxis getRangeAxis() {
285: return this .rangeAxis;
286: }
287:
288: /**
289: * Sets the range axis and sends a {@link PlotChangeEvent} to all
290: * registered listeners.
291: *
292: * @param axis the axis (<code>null</code> not permitted).
293: *
294: * @since 1.0.3
295: *
296: * @see #getRangeAxis()
297: */
298: public void setRangeAxis(ValueAxis axis) {
299: if (axis == null) {
300: throw new IllegalArgumentException("Null 'axis' argument.");
301: }
302: this .rangeAxis = axis;
303: notifyListeners(new PlotChangeEvent(this ));
304: }
305:
306: /**
307: * Returns the paint used to plot data points. The default is
308: * <code>Color.red</code>.
309: *
310: * @return The paint.
311: *
312: * @see #setPaint(Paint)
313: */
314: public Paint getPaint() {
315: return this .paint;
316: }
317:
318: /**
319: * Sets the color for the data points and sends a {@link PlotChangeEvent}
320: * to all registered listeners.
321: *
322: * @param paint the paint (<code>null</code> not permitted).
323: *
324: * @see #getPaint()
325: */
326: public void setPaint(Paint paint) {
327: if (paint == null) {
328: throw new IllegalArgumentException("Null 'paint' argument.");
329: }
330: this .paint = paint;
331: notifyListeners(new PlotChangeEvent(this ));
332: }
333:
334: /**
335: * Returns <code>true</code> if the domain gridlines are visible, and
336: * <code>false<code> otherwise.
337: *
338: * @return <code>true</code> or <code>false</code>.
339: *
340: * @see #setDomainGridlinesVisible(boolean)
341: * @see #setDomainGridlinePaint(Paint)
342: */
343: public boolean isDomainGridlinesVisible() {
344: return this .domainGridlinesVisible;
345: }
346:
347: /**
348: * Sets the flag that controls whether or not the domain grid-lines are
349: * visible. If the flag value is changed, a {@link PlotChangeEvent} is
350: * sent to all registered listeners.
351: *
352: * @param visible the new value of the flag.
353: *
354: * @see #getDomainGridlinePaint()
355: */
356: public void setDomainGridlinesVisible(boolean visible) {
357: if (this .domainGridlinesVisible != visible) {
358: this .domainGridlinesVisible = visible;
359: notifyListeners(new PlotChangeEvent(this ));
360: }
361: }
362:
363: /**
364: * Returns the stroke for the grid-lines (if any) plotted against the
365: * domain axis.
366: *
367: * @return The stroke (never <code>null</code>).
368: *
369: * @see #setDomainGridlineStroke(Stroke)
370: */
371: public Stroke getDomainGridlineStroke() {
372: return this .domainGridlineStroke;
373: }
374:
375: /**
376: * Sets the stroke for the grid lines plotted against the domain axis and
377: * sends a {@link PlotChangeEvent} to all registered listeners.
378: *
379: * @param stroke the stroke (<code>null</code> not permitted).
380: *
381: * @see #getDomainGridlineStroke()
382: */
383: public void setDomainGridlineStroke(Stroke stroke) {
384: if (stroke == null) {
385: throw new IllegalArgumentException(
386: "Null 'stroke' argument.");
387: }
388: this .domainGridlineStroke = stroke;
389: notifyListeners(new PlotChangeEvent(this ));
390: }
391:
392: /**
393: * Returns the paint for the grid lines (if any) plotted against the domain
394: * axis.
395: *
396: * @return The paint (never <code>null</code>).
397: *
398: * @see #setDomainGridlinePaint(Paint)
399: */
400: public Paint getDomainGridlinePaint() {
401: return this .domainGridlinePaint;
402: }
403:
404: /**
405: * Sets the paint for the grid lines plotted against the domain axis and
406: * sends a {@link PlotChangeEvent} to all registered listeners.
407: *
408: * @param paint the paint (<code>null</code> not permitted).
409: *
410: * @see #getDomainGridlinePaint()
411: */
412: public void setDomainGridlinePaint(Paint paint) {
413: if (paint == null) {
414: throw new IllegalArgumentException("Null 'paint' argument.");
415: }
416: this .domainGridlinePaint = paint;
417: notifyListeners(new PlotChangeEvent(this ));
418: }
419:
420: /**
421: * Returns <code>true</code> if the range axis grid is visible, and
422: * <code>false<code> otherwise.
423: *
424: * @return <code>true</code> or <code>false</code>.
425: *
426: * @see #setRangeGridlinesVisible(boolean)
427: */
428: public boolean isRangeGridlinesVisible() {
429: return this .rangeGridlinesVisible;
430: }
431:
432: /**
433: * Sets the flag that controls whether or not the range axis grid lines are
434: * visible. If the flag value is changed, a {@link PlotChangeEvent} is
435: * sent to all registered listeners.
436: *
437: * @param visible the new value of the flag.
438: *
439: * @see #isRangeGridlinesVisible()
440: */
441: public void setRangeGridlinesVisible(boolean visible) {
442: if (this .rangeGridlinesVisible != visible) {
443: this .rangeGridlinesVisible = visible;
444: notifyListeners(new PlotChangeEvent(this ));
445: }
446: }
447:
448: /**
449: * Returns the stroke for the grid lines (if any) plotted against the range
450: * axis.
451: *
452: * @return The stroke (never <code>null</code>).
453: *
454: * @see #setRangeGridlineStroke(Stroke)
455: */
456: public Stroke getRangeGridlineStroke() {
457: return this .rangeGridlineStroke;
458: }
459:
460: /**
461: * Sets the stroke for the grid lines plotted against the range axis and
462: * sends a {@link PlotChangeEvent} to all registered listeners.
463: *
464: * @param stroke the stroke (<code>null</code> permitted).
465: *
466: * @see #getRangeGridlineStroke()
467: */
468: public void setRangeGridlineStroke(Stroke stroke) {
469: if (stroke == null) {
470: throw new IllegalArgumentException(
471: "Null 'stroke' argument.");
472: }
473: this .rangeGridlineStroke = stroke;
474: notifyListeners(new PlotChangeEvent(this ));
475: }
476:
477: /**
478: * Returns the paint for the grid lines (if any) plotted against the range
479: * axis.
480: *
481: * @return The paint (never <code>null</code>).
482: *
483: * @see #setRangeGridlinePaint(Paint)
484: */
485: public Paint getRangeGridlinePaint() {
486: return this .rangeGridlinePaint;
487: }
488:
489: /**
490: * Sets the paint for the grid lines plotted against the range axis and
491: * sends a {@link PlotChangeEvent} to all registered listeners.
492: *
493: * @param paint the paint (<code>null</code> not permitted).
494: *
495: * @see #getRangeGridlinePaint()
496: */
497: public void setRangeGridlinePaint(Paint paint) {
498: if (paint == null) {
499: throw new IllegalArgumentException("Null 'paint' argument.");
500: }
501: this .rangeGridlinePaint = paint;
502: notifyListeners(new PlotChangeEvent(this ));
503: }
504:
505: /**
506: * Draws the fast scatter plot on a Java 2D graphics device (such as the
507: * screen or a printer).
508: *
509: * @param g2 the graphics device.
510: * @param area the area within which the plot (including axis labels)
511: * should be drawn.
512: * @param anchor the anchor point (<code>null</code> permitted).
513: * @param parentState the state from the parent plot (ignored).
514: * @param info collects chart drawing information (<code>null</code>
515: * permitted).
516: */
517: public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
518: PlotState parentState, PlotRenderingInfo info) {
519:
520: // set up info collection...
521: if (info != null) {
522: info.setPlotArea(area);
523: }
524:
525: // adjust the drawing area for plot insets (if any)...
526: RectangleInsets insets = getInsets();
527: insets.trim(area);
528:
529: AxisSpace space = new AxisSpace();
530: space = this .domainAxis.reserveSpace(g2, this , area,
531: RectangleEdge.BOTTOM, space);
532: space = this .rangeAxis.reserveSpace(g2, this , area,
533: RectangleEdge.LEFT, space);
534: Rectangle2D dataArea = space.shrink(area, null);
535:
536: if (info != null) {
537: info.setDataArea(dataArea);
538: }
539:
540: // draw the plot background and axes...
541: drawBackground(g2, dataArea);
542:
543: AxisState domainAxisState = this .domainAxis.draw(g2, dataArea
544: .getMaxY(), area, dataArea, RectangleEdge.BOTTOM, info);
545: AxisState rangeAxisState = this .rangeAxis.draw(g2, dataArea
546: .getMinX(), area, dataArea, RectangleEdge.LEFT, info);
547: drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
548: drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
549:
550: Shape originalClip = g2.getClip();
551: Composite originalComposite = g2.getComposite();
552:
553: g2.clip(dataArea);
554: g2.setComposite(AlphaComposite.getInstance(
555: AlphaComposite.SRC_OVER, getForegroundAlpha()));
556:
557: render(g2, dataArea, info, null);
558:
559: g2.setClip(originalClip);
560: g2.setComposite(originalComposite);
561: drawOutline(g2, dataArea);
562:
563: }
564:
565: /**
566: * Draws a representation of the data within the dataArea region. The
567: * <code>info</code> and <code>crosshairState</code> arguments may be
568: * <code>null</code>.
569: *
570: * @param g2 the graphics device.
571: * @param dataArea the region in which the data is to be drawn.
572: * @param info an optional object for collection dimension information.
573: * @param crosshairState collects crosshair information (<code>null</code>
574: * permitted).
575: */
576: public void render(Graphics2D g2, Rectangle2D dataArea,
577: PlotRenderingInfo info, CrosshairState crosshairState) {
578:
579: //long start = System.currentTimeMillis();
580: //System.out.println("Start: " + start);
581: g2.setPaint(this .paint);
582:
583: // if the axes use a linear scale, you can uncomment the code below and
584: // switch to the alternative transX/transY calculation inside the loop
585: // that follows - it is a little bit faster then.
586: //
587: // int xx = (int) dataArea.getMinX();
588: // int ww = (int) dataArea.getWidth();
589: // int yy = (int) dataArea.getMaxY();
590: // int hh = (int) dataArea.getHeight();
591: // double domainMin = this.domainAxis.getLowerBound();
592: // double domainLength = this.domainAxis.getUpperBound() - domainMin;
593: // double rangeMin = this.rangeAxis.getLowerBound();
594: // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin;
595:
596: if (this .data != null) {
597: for (int i = 0; i < this .data[0].length; i++) {
598: float x = this .data[0][i];
599: float y = this .data[1][i];
600:
601: //int transX = (int) (xx + ww * (x - domainMin) / domainLength);
602: //int transY = (int) (yy - hh * (y - rangeMin) / rangeLength);
603: int transX = (int) this .domainAxis.valueToJava2D(x,
604: dataArea, RectangleEdge.BOTTOM);
605: int transY = (int) this .rangeAxis.valueToJava2D(y,
606: dataArea, RectangleEdge.LEFT);
607: g2.fillRect(transX, transY, 1, 1);
608: }
609: }
610: //long finish = System.currentTimeMillis();
611: //System.out.println("Finish: " + finish);
612: //System.out.println("Time: " + (finish - start));
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,
624: Rectangle2D dataArea, List ticks) {
625:
626: // draw the domain grid lines, if the flag says they're visible...
627: if (isDomainGridlinesVisible()) {
628: Iterator iterator = ticks.iterator();
629: while (iterator.hasNext()) {
630: ValueTick tick = (ValueTick) iterator.next();
631: double v = this .domainAxis.valueToJava2D(tick
632: .getValue(), dataArea, RectangleEdge.BOTTOM);
633: Line2D line = new Line2D.Double(v, dataArea.getMinY(),
634: v, dataArea.getMaxY());
635: g2.setPaint(getDomainGridlinePaint());
636: g2.setStroke(getDomainGridlineStroke());
637: g2.draw(line);
638: }
639: }
640: }
641:
642: /**
643: * Draws the gridlines for the plot, if they are visible.
644: *
645: * @param g2 the graphics device.
646: * @param dataArea the data area.
647: * @param ticks the ticks.
648: */
649: protected void drawRangeGridlines(Graphics2D g2,
650: Rectangle2D dataArea, List ticks) {
651:
652: // draw the range grid lines, if the flag says they're visible...
653: if (isRangeGridlinesVisible()) {
654: Iterator iterator = ticks.iterator();
655: while (iterator.hasNext()) {
656: ValueTick tick = (ValueTick) iterator.next();
657: double v = this .rangeAxis.valueToJava2D(
658: tick.getValue(), dataArea, RectangleEdge.LEFT);
659: Line2D line = new Line2D.Double(dataArea.getMinX(), v,
660: dataArea.getMaxX(), v);
661: g2.setPaint(getRangeGridlinePaint());
662: g2.setStroke(getRangeGridlineStroke());
663: g2.draw(line);
664: }
665: }
666:
667: }
668:
669: /**
670: * Returns the range of data values to be plotted along the axis, or
671: * <code>null</code> if the specified axis isn't the domain axis or the
672: * range axis for the plot.
673: *
674: * @param axis the axis (<code>null</code> permitted).
675: *
676: * @return The range (possibly <code>null</code>).
677: */
678: public Range getDataRange(ValueAxis axis) {
679: Range result = null;
680: if (axis == this .domainAxis) {
681: result = this .xDataRange;
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/axes 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: * Zooms in on the domain axes.
765: *
766: * @param lowerPercent the new lower bound as a percentage of the current
767: * range.
768: * @param upperPercent the new upper bound as a percentage of the current
769: * range.
770: * @param info the plot rendering info.
771: * @param source the source point.
772: */
773: public void zoomDomainAxes(double lowerPercent,
774: double upperPercent, PlotRenderingInfo info, Point2D source) {
775: this .domainAxis.zoomRange(lowerPercent, upperPercent);
776: }
777:
778: /**
779: * Multiplies the range on the range axis/axes by the specified factor.
780: *
781: * @param factor the zoom factor.
782: * @param info the plot rendering info.
783: * @param source the source point.
784: */
785: public void zoomRangeAxes(double factor, PlotRenderingInfo info,
786: Point2D source) {
787: this .rangeAxis.resizeRange(factor);
788: }
789:
790: /**
791: * Zooms in on the range axes.
792: *
793: * @param lowerPercent the new lower bound as a percentage of the current
794: * range.
795: * @param upperPercent the new upper bound as a percentage of the current
796: * range.
797: * @param info the plot rendering info.
798: * @param source the source point.
799: */
800: public void zoomRangeAxes(double lowerPercent, double upperPercent,
801: PlotRenderingInfo info, Point2D source) {
802: this .rangeAxis.zoomRange(lowerPercent, upperPercent);
803: }
804:
805: /**
806: * Returns <code>true</code>.
807: *
808: * @return A boolean.
809: */
810: public boolean isDomainZoomable() {
811: return true;
812: }
813:
814: /**
815: * Returns <code>true</code>.
816: *
817: * @return A boolean.
818: */
819: public boolean isRangeZoomable() {
820: return true;
821: }
822:
823: /**
824: * Tests an object for equality with this instance.
825: *
826: * @param obj the object (<code>null</code> permitted).
827: *
828: * @return A boolean.
829: */
830: public boolean equals(Object obj) {
831: if (obj == this ) {
832: return true;
833: }
834: if (!super .equals(obj)) {
835: return false;
836: }
837: if (!(obj instanceof FastScatterPlot)) {
838: return false;
839: }
840: FastScatterPlot that = (FastScatterPlot) obj;
841: if (!ArrayUtilities.equal(this .data, that.data)) {
842: return false;
843: }
844: if (!ObjectUtilities.equal(this .domainAxis, that.domainAxis)) {
845: return false;
846: }
847: if (!ObjectUtilities.equal(this .rangeAxis, that.rangeAxis)) {
848: return false;
849: }
850: if (!PaintUtilities.equal(this .paint, that.paint)) {
851: return false;
852: }
853: if (this .domainGridlinesVisible != that.domainGridlinesVisible) {
854: return false;
855: }
856: if (!PaintUtilities.equal(this .domainGridlinePaint,
857: that.domainGridlinePaint)) {
858: return false;
859: }
860: if (!ObjectUtilities.equal(this .domainGridlineStroke,
861: that.domainGridlineStroke)) {
862: return false;
863: }
864: if (!this .rangeGridlinesVisible == that.rangeGridlinesVisible) {
865: return false;
866: }
867: if (!PaintUtilities.equal(this .rangeGridlinePaint,
868: that.rangeGridlinePaint)) {
869: return false;
870: }
871: if (!ObjectUtilities.equal(this .rangeGridlineStroke,
872: that.rangeGridlineStroke)) {
873: return false;
874: }
875: return true;
876: }
877:
878: /**
879: * Returns a clone of the plot.
880: *
881: * @return A clone.
882: *
883: * @throws CloneNotSupportedException if some component of the plot does
884: * not support cloning.
885: */
886: public Object clone() throws CloneNotSupportedException {
887:
888: FastScatterPlot clone = (FastScatterPlot) super .clone();
889:
890: if (this .data != null) {
891: clone.data = ArrayUtilities.clone(this .data);
892: }
893:
894: if (this .domainAxis != null) {
895: clone.domainAxis = (ValueAxis) this .domainAxis.clone();
896: clone.domainAxis.setPlot(clone);
897: clone.domainAxis.addChangeListener(clone);
898: }
899:
900: if (this .rangeAxis != null) {
901: clone.rangeAxis = (ValueAxis) this .rangeAxis.clone();
902: clone.rangeAxis.setPlot(clone);
903: clone.rangeAxis.addChangeListener(clone);
904: }
905:
906: return clone;
907:
908: }
909:
910: /**
911: * Provides serialization support.
912: *
913: * @param stream the output stream.
914: *
915: * @throws IOException if there is an I/O error.
916: */
917: private void writeObject(ObjectOutputStream stream)
918: throws IOException {
919: stream.defaultWriteObject();
920: SerialUtilities.writePaint(this .paint, stream);
921: SerialUtilities.writeStroke(this .domainGridlineStroke, stream);
922: SerialUtilities.writePaint(this .domainGridlinePaint, stream);
923: SerialUtilities.writeStroke(this .rangeGridlineStroke, stream);
924: SerialUtilities.writePaint(this .rangeGridlinePaint, stream);
925: }
926:
927: /**
928: * Provides serialization support.
929: *
930: * @param stream the input stream.
931: *
932: * @throws IOException if there is an I/O error.
933: * @throws ClassNotFoundException if there is a classpath problem.
934: */
935: private void readObject(ObjectInputStream stream)
936: throws IOException, ClassNotFoundException {
937: stream.defaultReadObject();
938:
939: this.paint = SerialUtilities.readPaint(stream);
940: this.domainGridlineStroke = SerialUtilities.readStroke(stream);
941: this.domainGridlinePaint = SerialUtilities.readPaint(stream);
942:
943: this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
944: this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
945:
946: if (this.domainAxis != null) {
947: this.domainAxis.addChangeListener(this);
948: }
949:
950: if (this.rangeAxis != null) {
951: this.rangeAxis.addChangeListener(this);
952: }
953: }
954:
955: }
|