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,
102 Cloneable, Serializable {
103
104 /** For serialization. */
105 private static final long serialVersionUID = 7871545897358563521L;
106
107 /** The default grid line stroke. */
108 public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
109 BasicStroke.CAP_BUTT,
110 BasicStroke.JOIN_BEVEL,
111 0.0f,
112 new float[] {2.0f, 2.0f},
113 0.0f);
114
115 /** The default grid line paint. */
116 public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
117
118 /** The data. */
119 private float[][] data;
120
121 /** The x data range. */
122 private Range xDataRange;
123
124 /** The y data range. */
125 private Range yDataRange;
126
127 /** The domain axis (used for the x-values). */
128 private ValueAxis domainAxis;
129
130 /** The range axis (used for the y-values). */
131 private ValueAxis rangeAxis;
132
133 /** The paint used to plot data points. */
134 private transient Paint paint;
135
136 /** A flag that controls whether the domain grid-lines are visible. */
137 private boolean domainGridlinesVisible;
138
139 /** The stroke used to draw the domain grid-lines. */
140 private transient Stroke domainGridlineStroke;
141
142 /** The paint used to draw the domain grid-lines. */
143 private transient Paint domainGridlinePaint;
144
145 /** A flag that controls whether the range grid-lines are visible. */
146 private boolean rangeGridlinesVisible;
147
148 /** The stroke used to draw the range grid-lines. */
149 private transient Stroke rangeGridlineStroke;
150
151 /** The paint used to draw the range grid-lines. */
152 private transient Paint rangeGridlinePaint;
153
154 /** The resourceBundle for the localization. */
155 protected static ResourceBundle localizationResources =
156 ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
157
158 /**
159 * Creates a new instance of <code>FastScatterPlot</code> with default
160 * axes.
161 */
162 public FastScatterPlot() {
163 this(null, new NumberAxis("X"), new NumberAxis("Y"));
164 }
165
166 /**
167 * Creates a new fast scatter plot.
168 * <p>
169 * The data is an array of x, y values: data[0][i] = x, data[1][i] = y.
170 *
171 * @param data the data (<code>null</code> permitted).
172 * @param domainAxis the domain (x) axis (<code>null</code> not permitted).
173 * @param rangeAxis the range (y) axis (<code>null</code> not permitted).
174 */
175 public FastScatterPlot(float[][] data,
176 ValueAxis domainAxis, ValueAxis rangeAxis) {
177
178 super();
179 if (domainAxis == null) {
180 throw new IllegalArgumentException("Null 'domainAxis' argument.");
181 }
182 if (rangeAxis == null) {
183 throw new IllegalArgumentException("Null 'rangeAxis' argument.");
184 }
185
186 this.data = data;
187 this.xDataRange = calculateXDataRange(data);
188 this.yDataRange = calculateYDataRange(data);
189 this.domainAxis = domainAxis;
190 this.domainAxis.setPlot(this);
191 this.domainAxis.addChangeListener(this);
192 this.rangeAxis = rangeAxis;
193 this.rangeAxis.setPlot(this);
194 this.rangeAxis.addChangeListener(this);
195
196 this.paint = Color.red;
197
198 this.domainGridlinesVisible = true;
199 this.domainGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
200 this.domainGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
201
202 this.rangeGridlinesVisible = true;
203 this.rangeGridlinePaint = FastScatterPlot.DEFAULT_GRIDLINE_PAINT;
204 this.rangeGridlineStroke = FastScatterPlot.DEFAULT_GRIDLINE_STROKE;
205
206 }
207
208 /**
209 * Returns a short string describing the plot type.
210 *
211 * @return A short string describing the plot type.
212 */
213 public String getPlotType() {
214 return localizationResources.getString("Fast_Scatter_Plot");
215 }
216
217 /**
218 * Returns the data array used by the plot.
219 *
220 * @return The data array (possibly <code>null</code>).
221 *
222 * @see #setData(float[][])
223 */
224 public float[][] getData() {
225 return this.data;
226 }
227
228 /**
229 * Sets the data array used by the plot and sends a {@link PlotChangeEvent}
230 * to all registered listeners.
231 *
232 * @param data the data array (<code>null</code> permitted).
233 *
234 * @see #getData()
235 */
236 public void setData(float[][] data) {
237 this.data = data;
238 notifyListeners(new PlotChangeEvent(this));
239 }
240
241 /**
242 * Returns the orientation of the plot.
243 *
244 * @return The orientation (always {@link PlotOrientation#VERTICAL}).
245 */
246 public PlotOrientation getOrientation() {
247 return PlotOrientation.VERTICAL;
248 }
249
250 /**
251 * Returns the domain axis for the plot.
252 *
253 * @return The domain axis (never <code>null</code>).
254 *
255 * @see #setDomainAxis(ValueAxis)
256 */
257 public ValueAxis getDomainAxis() {
258 return this.domainAxis;
259 }
260
261 /**
262 * Sets the domain axis and sends a {@link PlotChangeEvent} to all
263 * registered listeners.
264 *
265 * @param axis the axis (<code>null</code> not permitted).
266 *
267 * @since 1.0.3
268 *
269 * @see #getDomainAxis()
270 */
271 public void setDomainAxis(ValueAxis axis) {
272 if (axis == null) {
273 throw new IllegalArgumentException("Null 'axis' argument.");
274 }
275 this.domainAxis = axis;
276 notifyListeners(new PlotChangeEvent(this));
277 }
278
279 /**
280 * Returns the range axis for the plot.
281 *
282 * @return The range axis (never <code>null</code>).
283 *
284 * @see #setRangeAxis(ValueAxis)
285 */
286 public ValueAxis getRangeAxis() {
287 return this.rangeAxis;
288 }
289
290 /**
291 * Sets the range axis and sends a {@link PlotChangeEvent} to all
292 * registered listeners.
293 *
294 * @param axis the axis (<code>null</code> not permitted).
295 *
296 * @since 1.0.3
297 *
298 * @see #getRangeAxis()
299 */
300 public void setRangeAxis(ValueAxis axis) {
301 if (axis == null) {
302 throw new IllegalArgumentException("Null 'axis' argument.");
303 }
304 this.rangeAxis = axis;
305 notifyListeners(new PlotChangeEvent(this));
306 }
307
308 /**
309 * Returns the paint used to plot data points. The default is
310 * <code>Color.red</code>.
311 *
312 * @return The paint.
313 *
314 * @see #setPaint(Paint)
315 */
316 public Paint getPaint() {
317 return this.paint;
318 }
319
320 /**
321 * Sets the color for the data points and sends a {@link PlotChangeEvent}
322 * to all registered listeners.
323 *
324 * @param paint the paint (<code>null</code> not permitted).
325 *
326 * @see #getPaint()
327 */
328 public void setPaint(Paint paint) {
329 if (paint == null) {
330 throw new IllegalArgumentException("Null 'paint' argument.");
331 }
332 this.paint = paint;
333 notifyListeners(new PlotChangeEvent(this));
334 }
335
336 /**
337 * Returns <code>true</code> if the domain gridlines are visible, and
338 * <code>false<code> otherwise.
339 *
340 * @return <code>true</code> or <code>false</code>.
341 *
342 * @see #setDomainGridlinesVisible(boolean)
343 * @see #setDomainGridlinePaint(Paint)
344 */
345 public boolean isDomainGridlinesVisible() {
346 return this.domainGridlinesVisible;
347 }
348
349 /**
350 * Sets the flag that controls whether or not the domain grid-lines are
351 * visible. If the flag value is changed, a {@link PlotChangeEvent} is
352 * sent to all registered listeners.
353 *
354 * @param visible the new value of the flag.
355 *
356 * @see #getDomainGridlinePaint()
357 */
358 public void setDomainGridlinesVisible(boolean visible) {
359 if (this.domainGridlinesVisible != visible) {
360 this.domainGridlinesVisible = visible;
361 notifyListeners(new PlotChangeEvent(this));
362 }
363 }
364
365 /**
366 * Returns the stroke for the grid-lines (if any) plotted against the
367 * domain axis.
368 *
369 * @return The stroke (never <code>null</code>).
370 *
371 * @see #setDomainGridlineStroke(Stroke)
372 */
373 public Stroke getDomainGridlineStroke() {
374 return this.domainGridlineStroke;
375 }
376
377 /**
378 * Sets the stroke for the grid lines plotted against the domain axis and
379 * sends a {@link PlotChangeEvent} to all registered listeners.
380 *
381 * @param stroke the stroke (<code>null</code> not permitted).
382 *
383 * @see #getDomainGridlineStroke()
384 */
385 public void setDomainGridlineStroke(Stroke stroke) {
386 if (stroke == null) {
387 throw new IllegalArgumentException("Null 'stroke' argument.");
388 }
389 this.domainGridlineStroke = stroke;
390 notifyListeners(new PlotChangeEvent(this));
391 }
392
393 /**
394 * Returns the paint for the grid lines (if any) plotted against the domain
395 * axis.
396 *
397 * @return The paint (never <code>null</code>).
398 *
399 * @see #setDomainGridlinePaint(Paint)
400 */
401 public Paint getDomainGridlinePaint() {
402 return this.domainGridlinePaint;
403 }
404
405 /**
406 * Sets the paint for the grid lines plotted against the domain axis and
407 * sends a {@link PlotChangeEvent} to all registered listeners.
408 *
409 * @param paint the paint (<code>null</code> not permitted).
410 *
411 * @see #getDomainGridlinePaint()
412 */
413 public void setDomainGridlinePaint(Paint paint) {
414 if (paint == null) {
415 throw new IllegalArgumentException("Null 'paint' argument.");
416 }
417 this.domainGridlinePaint = paint;
418 notifyListeners(new PlotChangeEvent(this));
419 }
420
421 /**
422 * Returns <code>true</code> if the range axis grid is visible, and
423 * <code>false<code> otherwise.
424 *
425 * @return <code>true</code> or <code>false</code>.
426 *
427 * @see #setRangeGridlinesVisible(boolean)
428 */
429 public boolean isRangeGridlinesVisible() {
430 return this.rangeGridlinesVisible;
431 }
432
433 /**
434 * Sets the flag that controls whether or not the range axis grid lines are
435 * visible. If the flag value is changed, a {@link PlotChangeEvent} is
436 * sent to all registered listeners.
437 *
438 * @param visible the new value of the flag.
439 *
440 * @see #isRangeGridlinesVisible()
441 */
442 public void setRangeGridlinesVisible(boolean visible) {
443 if (this.rangeGridlinesVisible != visible) {
444 this.rangeGridlinesVisible = visible;
445 notifyListeners(new PlotChangeEvent(this));
446 }
447 }
448
449 /**
450 * Returns the stroke for the grid lines (if any) plotted against the range
451 * axis.
452 *
453 * @return The stroke (never <code>null</code>).
454 *
455 * @see #setRangeGridlineStroke(Stroke)
456 */
457 public Stroke getRangeGridlineStroke() {
458 return this.rangeGridlineStroke;
459 }
460
461 /**
462 * Sets the stroke for the grid lines plotted against the range axis and
463 * sends a {@link PlotChangeEvent} to all registered listeners.
464 *
465 * @param stroke the stroke (<code>null</code> permitted).
466 *
467 * @see #getRangeGridlineStroke()
468 */
469 public void setRangeGridlineStroke(Stroke stroke) {
470 if (stroke == null) {
471 throw new IllegalArgumentException("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,
519 PlotRenderingInfo info) {
520
521 // set up info collection...
522 if (info != null) {
523 info.setPlotArea(area);
524 }
525
526 // adjust the drawing area for plot insets (if any)...
527 RectangleInsets insets = getInsets();
528 insets.trim(area);
529
530 AxisSpace space = new AxisSpace();
531 space = this.domainAxis.reserveSpace(g2, this, area,
532 RectangleEdge.BOTTOM, space);
533 space = this.rangeAxis.reserveSpace(g2, this, area, RectangleEdge.LEFT,
534 space);
535 Rectangle2D dataArea = space.shrink(area, null);
536
537 if (info != null) {
538 info.setDataArea(dataArea);
539 }
540
541 // draw the plot background and axes...
542 drawBackground(g2, dataArea);
543
544 AxisState domainAxisState = this.domainAxis.draw(g2,
545 dataArea.getMaxY(), area, dataArea, RectangleEdge.BOTTOM, info);
546 AxisState rangeAxisState = this.rangeAxis.draw(g2, dataArea.getMinX(),
547 area, dataArea, RectangleEdge.LEFT, info);
548 drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
549 drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
550
551 Shape originalClip = g2.getClip();
552 Composite originalComposite = g2.getComposite();
553
554 g2.clip(dataArea);
555 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
556 getForegroundAlpha()));
557
558 render(g2, dataArea, info, null);
559
560 g2.setClip(originalClip);
561 g2.setComposite(originalComposite);
562 drawOutline(g2, dataArea);
563
564 }
565
566 /**
567 * Draws a representation of the data within the dataArea region. The
568 * <code>info</code> and <code>crosshairState</code> arguments may be
569 * <code>null</code>.
570 *
571 * @param g2 the graphics device.
572 * @param dataArea the region in which the data is to be drawn.
573 * @param info an optional object for collection dimension information.
574 * @param crosshairState collects crosshair information (<code>null</code>
575 * permitted).
576 */
577 public void render(Graphics2D g2, Rectangle2D dataArea,
578 PlotRenderingInfo info, CrosshairState crosshairState) {
579
580
581 //long start = System.currentTimeMillis();
582 //System.out.println("Start: " + start);
583 g2.setPaint(this.paint);
584
585 // if the axes use a linear scale, you can uncomment the code below and
586 // switch to the alternative transX/transY calculation inside the loop
587 // that follows - it is a little bit faster then.
588 //
589 // int xx = (int) dataArea.getMinX();
590 // int ww = (int) dataArea.getWidth();
591 // int yy = (int) dataArea.getMaxY();
592 // int hh = (int) dataArea.getHeight();
593 // double domainMin = this.domainAxis.getLowerBound();
594 // double domainLength = this.domainAxis.getUpperBound() - domainMin;
595 // double rangeMin = this.rangeAxis.getLowerBound();
596 // double rangeLength = this.rangeAxis.getUpperBound() - rangeMin;
597
598 if (this.data != null) {
599 for (int i = 0; i < this.data[0].length; i++) {
600 float x = this.data[0][i];
601 float y = this.data[1][i];
602
603 //int transX = (int) (xx + ww * (x - domainMin) / domainLength);
604 //int transY = (int) (yy - hh * (y - rangeMin) / rangeLength);
605 int transX = (int) this.domainAxis.valueToJava2D(x, dataArea,
606 RectangleEdge.BOTTOM);
607 int transY = (int) this.rangeAxis.valueToJava2D(y, dataArea,
608 RectangleEdge.LEFT);
609 g2.fillRect(transX, transY, 1, 1);
610 }
611 }
612 //long finish = System.currentTimeMillis();
613 //System.out.println("Finish: " + finish);
614 //System.out.println("Time: " + (finish - start));
615
616 }
617
618 /**
619 * Draws the gridlines for the plot, if they are visible.
620 *
621 * @param g2 the graphics device.
622 * @param dataArea the data area.
623 * @param ticks the ticks.
624 */
625 protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea,
626 List ticks) {
627
628 // draw the domain grid lines, if the flag says they're visible...
629 if (isDomainGridlinesVisible()) {
630 Iterator iterator = ticks.iterator();
631 while (iterator.hasNext()) {
632 ValueTick tick = (ValueTick) iterator.next();
633 double v = this.domainAxis.valueToJava2D(tick.getValue(),
634 dataArea, RectangleEdge.BOTTOM);
635 Line2D line = new Line2D.Double(v, dataArea.getMinY(), v,
636 dataArea.getMaxY());
637 g2.setPaint(getDomainGridlinePaint());
638 g2.setStroke(getDomainGridlineStroke());
639 g2.draw(line);
640 }
641 }
642 }
643
644 /**
645 * Draws the gridlines for the plot, if they are visible.
646 *
647 * @param g2 the graphics device.
648 * @param dataArea the data area.
649 * @param ticks the ticks.
650 */
651 protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea,
652 List ticks) {
653
654 // draw the range grid lines, if the flag says they're visible...
655 if (isRangeGridlinesVisible()) {
656 Iterator iterator = ticks.iterator();
657 while (iterator.hasNext()) {
658 ValueTick tick = (ValueTick) iterator.next();
659 double v = this.rangeAxis.valueToJava2D(tick.getValue(),
660 dataArea, RectangleEdge.LEFT);
661 Line2D line = new Line2D.Double(dataArea.getMinX(), v,
662 dataArea.getMaxX(), v);
663 g2.setPaint(getRangeGridlinePaint());
664 g2.setStroke(getRangeGridlineStroke());
665 g2.draw(line);
666 }
667 }
668
669 }
670
671 /**
672 * Returns the range of data values to be plotted along the axis, or
673 * <code>null</code> if the specified axis isn't the domain axis or the
674 * range axis for the plot.
675 *
676 * @param axis the axis (<code>null</code> permitted).
677 *
678 * @return The range (possibly <code>null</code>).
679 */
680 public Range getDataRange(ValueAxis axis) {
681 Range result = null;
682 if (axis == this.domainAxis) {
683 result = this.xDataRange;
684 }
685 else if (axis == this.rangeAxis) {
686 result = this.yDataRange;
687 }
688 return result;
689 }
690
691 /**
692 * Calculates the X data range.
693 *
694 * @param data the data (<code>null</code> permitted).
695 *
696 * @return The range.
697 */
698 private Range calculateXDataRange(float[][] data) {
699
700 Range result = null;
701
702 if (data != null) {
703 float lowest = Float.POSITIVE_INFINITY;
704 float highest = Float.NEGATIVE_INFINITY;
705 for (int i = 0; i < data[0].length; i++) {
706 float v = data[0][i];
707 if (v < lowest) {
708 lowest = v;
709 }
710 if (v > highest) {
711 highest = v;
712 }
713 }
714 if (lowest <= highest) {
715 result = new Range(lowest, highest);
716 }
717 }
718
719 return result;
720
721 }
722
723 /**
724 * Calculates the Y data range.
725 *
726 * @param data the data (<code>null</code> permitted).
727 *
728 * @return The range.
729 */
730 private Range calculateYDataRange(float[][] data) {
731
732 Range result = null;
733
734 if (data != null) {
735 float lowest = Float.POSITIVE_INFINITY;
736 float highest = Float.NEGATIVE_INFINITY;
737 for (int i = 0; i < data[0].length; i++) {
738 float v = data[1][i];
739 if (v < lowest) {
740 lowest = v;
741 }
742 if (v > highest) {
743 highest = v;
744 }
745 }
746 if (lowest <= highest) {
747 result = new Range(lowest, highest);
748 }
749 }
750 return result;
751
752 }
753
754 /**
755 * Multiplies the range on the domain axis/axes by the specified factor.
756 *
757 * @param factor the zoom factor.
758 * @param info the plot rendering info.
759 * @param source the source point.
760 */
761 public void zoomDomainAxes(double factor, PlotRenderingInfo info,
762 Point2D source) {
763 this.domainAxis.resizeRange(factor);
764 }
765
766 /**
767 * Zooms in on the domain axes.
768 *
769 * @param lowerPercent the new lower bound as a percentage of the current
770 * range.
771 * @param upperPercent the new upper bound as a percentage of the current
772 * range.
773 * @param info the plot rendering info.
774 * @param source the source point.
775 */
776 public void zoomDomainAxes(double lowerPercent, double upperPercent,
777 PlotRenderingInfo info, Point2D source) {
778 this.domainAxis.zoomRange(lowerPercent, upperPercent);
779 }
780
781 /**
782 * Multiplies the range on the range axis/axes by the specified factor.
783 *
784 * @param factor the zoom factor.
785 * @param info the plot rendering info.
786 * @param source the source point.
787 */
788 public void zoomRangeAxes(double factor,
789 PlotRenderingInfo info, Point2D source) {
790 this.rangeAxis.resizeRange(factor);
791 }
792
793 /**
794 * Zooms in on the range axes.
795 *
796 * @param lowerPercent the new lower bound as a percentage of the current
797 * range.
798 * @param upperPercent the new upper bound as a percentage of the current
799 * range.
800 * @param info the plot rendering info.
801 * @param source the source point.
802 */
803 public void zoomRangeAxes(double lowerPercent, double upperPercent,
804 PlotRenderingInfo info, Point2D source) {
805 this.rangeAxis.zoomRange(lowerPercent, upperPercent);
806 }
807
808 /**
809 * Returns <code>true</code>.
810 *
811 * @return A boolean.
812 */
813 public boolean isDomainZoomable() {
814 return true;
815 }
816
817 /**
818 * Returns <code>true</code>.
819 *
820 * @return A boolean.
821 */
822 public boolean isRangeZoomable() {
823 return true;
824 }
825
826 /**
827 * Tests an object for equality with this instance.
828 *
829 * @param obj the object (<code>null</code> permitted).
830 *
831 * @return A boolean.
832 */
833 public boolean equals(Object obj) {
834 if (obj == this) {
835 return true;
836 }
837 if (!super.equals(obj)) {
838 return false;
839 }
840 if (!(obj instanceof FastScatterPlot)) {
841 return false;
842 }
843 FastScatterPlot that = (FastScatterPlot) obj;
844 if (!ArrayUtilities.equal(this.data, that.data)) {
845 return false;
846 }
847 if (!ObjectUtilities.equal(this.domainAxis, that.domainAxis)) {
848 return false;
849 }
850 if (!ObjectUtilities.equal(this.rangeAxis, that.rangeAxis)) {
851 return false;
852 }
853 if (!PaintUtilities.equal(this.paint, that.paint)) {
854 return false;
855 }
856 if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
857 return false;
858 }
859 if (!PaintUtilities.equal(this.domainGridlinePaint,
860 that.domainGridlinePaint)) {
861 return false;
862 }
863 if (!ObjectUtilities.equal(this.domainGridlineStroke,
864 that.domainGridlineStroke)) {
865 return false;
866 }
867 if (!this.rangeGridlinesVisible == that.rangeGridlinesVisible) {
868 return false;
869 }
870 if (!PaintUtilities.equal(this.rangeGridlinePaint,
871 that.rangeGridlinePaint)) {
872 return false;
873 }
874 if (!ObjectUtilities.equal(this.rangeGridlineStroke,
875 that.rangeGridlineStroke)) {
876 return false;
877 }
878 return true;
879 }
880
881 /**
882 * Returns a clone of the plot.
883 *
884 * @return A clone.
885 *
886 * @throws CloneNotSupportedException if some component of the plot does
887 * not support cloning.
888 */
889 public Object clone() throws CloneNotSupportedException {
890
891 FastScatterPlot clone = (FastScatterPlot) super.clone();
892
893 if (this.data != null) {
894 clone.data = ArrayUtilities.clone(this.data);
895 }
896
897 if (this.domainAxis != null) {
898 clone.domainAxis = (ValueAxis) this.domainAxis.clone();
899 clone.domainAxis.setPlot(clone);
900 clone.domainAxis.addChangeListener(clone);
901 }
902
903 if (this.rangeAxis != null) {
904 clone.rangeAxis = (ValueAxis) this.rangeAxis.clone();
905 clone.rangeAxis.setPlot(clone);
906 clone.rangeAxis.addChangeListener(clone);
907 }
908
909 return clone;
910
911 }
912
913 /**
914 * Provides serialization support.
915 *
916 * @param stream the output stream.
917 *
918 * @throws IOException if there is an I/O error.
919 */
920 private void writeObject(ObjectOutputStream stream) throws IOException {
921 stream.defaultWriteObject();
922 SerialUtilities.writePaint(this.paint, stream);
923 SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
924 SerialUtilities.writePaint(this.domainGridlinePaint, stream);
925 SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
926 SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
927 }
928
929 /**
930 * Provides serialization support.
931 *
932 * @param stream the input stream.
933 *
934 * @throws IOException if there is an I/O error.
935 * @throws ClassNotFoundException if there is a classpath problem.
936 */
937 private void readObject(ObjectInputStream stream)
938 throws IOException, ClassNotFoundException {
939 stream.defaultReadObject();
940
941 this.paint = SerialUtilities.readPaint(stream);
942 this.domainGridlineStroke = SerialUtilities.readStroke(stream);
943 this.domainGridlinePaint = SerialUtilities.readPaint(stream);
944
945 this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
946 this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
947
948 if (this.domainAxis != null) {
949 this.domainAxis.addChangeListener(this);
950 }
951
952 if (this.rangeAxis != null) {
953 this.rangeAxis.addChangeListener(this);
954 }
955 }
956
957 }