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