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