/*
* Author: Timothy Danford
* Date: Jul 29, 2008
*/
package org.seqcode.viz.paintable;
import java.awt.*;
import java.lang.reflect.*;
import java.util.*;
import org.seqcode.viz.paintable.*;
import org.seqcode.viz.paintable.layout.InsetPaintable;
import org.seqcode.viz.paintable.layout.LayeredPaintable;
/**
* @author tdanford
*/
public class ScatterPlot extends AbstractPaintable implements PaintableScaleListener {
/**
* Testing and debugging.
*/
public static void main(String[] args) {
Random rand = new Random();
Paintable p = createSquareScatterPaintable(randPoints(rand, 1000));
PaintableFrame pf = new PaintableFrame("Scatter Plot", p);
}
/**
* A general-purpose method to set up a ScatterPlot as well as a vertical and horizontal scale painter.
* @param pts
* @return
*/
public static Paintable createScatterPaintable(Collection<double[]> pts) {
PaintableScale xScale = new PaintableScale(0.0, 1.0);
PaintableScale yScale = new PaintableScale(0.0, 1.0);
Paintable xPainter = new HorizontalScalePainter(xScale);
Paintable yPainter = new VerticalScalePainter(yScale);
Paintable scatter = new ScatterPlot(xScale, yScale, pts);
Paintable p = new LayeredPaintable(xPainter, yPainter, scatter);
p = new InsetPaintable(0.1, 0.1, p);
return p;
}
/**
* Same as above, except that the ScatterPlot display is to share the same x- and y-scales.
*
* @param pts
* @return
*/
public static Paintable createSquareScatterPaintable(Collection<double[]> pts) {
PaintableScale xScale = new PaintableScale(0.0, 1.0);
PaintableScale yScale = xScale;
Paintable xPainter = new HorizontalScalePainter(xScale);
Paintable yPainter = new VerticalScalePainter(yScale);
Paintable scatter = new ScatterPlot(xScale, yScale, pts);
Paintable p = new LayeredPaintable(xPainter, yPainter, scatter);
p = new InsetPaintable(0.1, 0.1, p);
return p;
}
/**
* Generates a set of random points, linearly related to each other, for testing purposes.
* @param rand
* @param count
* @return
*/
public static Collection<double[]> randPoints(Random rand, int count) {
LinkedList<double[]> pts = new LinkedList<double[]>();
for(int i = 0; i < count; i++) {
double x = rand.nextDouble();
double y = x + rand.nextDouble();
double[] p = new double[] { x, y };
pts.add(p);
}
return pts;
}
/**
* Uses the findPoints() reflective method below, but with the default parameters for the fields names of "x" and "y".
* @param models
* @return
*/
public static Collection<double[]> findPoints(Collection models) {
return findPoints(models, "x", "y");
}
/**
* Uses reflection to pick through a list of objects, and pulls out the values under the two given field names
* concatenates them as a double[] array (a point), and returns a list of them. Ignores any non-Double or inaccessible
* fields.
*
* @param models
* @param xName
* @param yName
* @return
*/
public static Collection<double[]> findPoints(Collection models, String xName, String yName) {
LinkedList<double[]> pts = new LinkedList<double[]>();
for(Object value : models) {
Class valueClass = value.getClass();
try {
Field xfield = valueClass.getField(xName);
Field yfield = valueClass.getField(yName);
Object xValue = xfield.get(value);
Object yValue = yfield.get(value);
if(xValue instanceof Double && yValue instanceof Double) {
double[] pt = new double[] { (Double)xValue, (Double)yValue };
pts.add(pt);
}
} catch (NoSuchFieldException e) {
// do nothing.
} catch (IllegalAccessException e) {
// do nothing.
}
}
return pts;
}
/**
* Turns a pair of arrays into a collection of pairs. Used in the constructor to ScatterPlot, below.
*
* @param x
* @param y
* @return
*/
public static Collection<double[]> collectPointArrays(double[] x, double[] y) {
if(x.length != y.length) {
throw new IllegalArgumentException(String.format("%d != %d", x.length, y.length));
}
LinkedList<double[]> pts = new LinkedList<double[]>();
for(int i = 0; i < x.length; i++) {
pts.add(new double[] { x[i], y[i] });
}
return pts;
}
private Vector<double[]> pts;
private PaintableScale xScale, yScale;
private int radius;
private Color color;
public ScatterPlot(PaintableScale xs, PaintableScale ys, double[] x, double[] y) {
this(xs, ys, collectPointArrays(x, y));
}
public ScatterPlot(PaintableScale xscale, PaintableScale yscale, Collection<double[]> points) {
xScale = xscale;
yScale = yscale;
xScale.addPaintableScaleListener(this);
yScale.addPaintableScaleListener(this);
radius = 2;
color = Color.red;
pts = new Vector<double[]>();
for(double[] pt : points) {
pts.add(pt.clone());
xScale.updateScale(pt[0]);
yScale.updateScale(pt[1]);
}
}
public void addPoint(double[] pt) {
pts.add(pt.clone());
xScale.updateScale(pt[0]);
yScale.updateScale(pt[1]);
dispatchChangedEvent();
}
public void setColor(Color c) {
color = c;
dispatchChangedEvent();
}
public void setRadius(int rad) {
radius = rad;
dispatchChangedEvent();
}
public void paintItem(Graphics g, int x1, int y1, int x2, int y2) {
int w = x2-x1, h = y2-y1;
int diam = radius*2;
for(double[] pt : pts) {
double fx = xScale.fractionalOffset(pt[0]);
double fy = yScale.fractionalOffset(pt[1]);
int fxx = x1 + (int)Math.round(fx * (double)w);
int fyy = y2 - (int)Math.round(fy * (double)h);
g.setColor(color);
g.fillOval(fxx-radius, fyy-radius, diam, diam);
}
}
public void paintableScaleChanged(PaintableScaleChangedEvent evt) {
dispatchChangedEvent();
}
}