001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-2008, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Arnaud Lelievre;
034 *
035 * Changes
036 * -------
037 * 29-Oct-2002 : Added standard header (DG);
038 * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG);
039 * 26-Mar-2003 : Implemented Serializable (DG);
040 * 19-Aug-2003 : Implemented Cloneable (DG);
041 * 08-Sep-2003 : Added internationalization via use of properties
042 * resourceBundle (RFE 690236) (AL);
043 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
044 * 12-Nov-2003 : Implemented zooming (DG);
045 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
046 * 26-Jan-2004 : Added domain and range grid lines (DG);
047 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
048 * 29-Sep-2004 : Removed hard-coded color (DG);
049 * 04-Oct-2004 : Reworked equals() method and renamed ArrayUtils
050 * --> ArrayUtilities (DG);
051 * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
052 * 05-May-2005 : Updated draw() method parameters (DG);
053 * 16-Jun-2005 : Added get/setData() methods (DG);
054 * ------------- JFREECHART 1.0.x ---------------------------------------------
055 * 10-Nov-2006 : Fixed bug 1593150, by not allowing null axes, and added
056 * setDomainAxis() and setRangeAxis() methods (DG);
057 * 24-Sep-2007 : Implemented new zooming methods (DG);
058 * 25-Mar-2008 : Make use of new fireChangeEvent() method (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(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 }