/* * Author: tdanford * Date: Sep 16, 2008 */ package org.seqcode.viz.eye; import java.awt.*; import java.lang.reflect.*; import java.util.*; import javax.swing.*; import org.seqcode.gseutils.Pair; import org.seqcode.gseutils.models.Model; import org.seqcode.gseutils.models.ModelFieldAnalysis; import org.seqcode.viz.paintable.*; import java.awt.*; import java.awt.event.*; public class ModelScatter extends AbstractModelPaintable { public static final String xScaleKey = "x-scale"; public static final String yScaleKey = "y-scale"; public static final String radiusKey = "radius"; public static final String colorKey = "color"; public static final String strokeKey = "stroke"; public static final String axisColorKey = "axis-color"; public static final String showScaleKey = "show-scales"; private String xFieldName, yFieldName; private Vector<ScatterPoint> points; private Vector<Model> models; public ModelScatter() { xFieldName = "x"; yFieldName = "y"; points = new Vector<ScatterPoint>(); models = new Vector<Model>(); initProperty(new PropertyValueWrapper<PaintableScale>(xScaleKey, new PaintableScale(0.0, 1.0))); initProperty(new PropertyValueWrapper<PaintableScale>(yScaleKey, new PaintableScale(0.0, 1.0))); initProperty(new PropertyValueWrapper<Integer>(radiusKey, 3)); initProperty(new PropertyValueWrapper<Color>(colorKey, Color.red)); initProperty(new PropertyValueWrapper<Float>(strokeKey, (float)3.0)); initProperty(new PropertyValueWrapper<Boolean>(showScaleKey, Boolean.TRUE)); startDrawingPoints(); } public ModelScatter(String xfield, String yfield) { this(); xFieldName = xfield; yFieldName = yfield; } public void addModel(Model m) { Class modelClass = m.getClass(); ModelFieldAnalysis analysis = new ModelFieldAnalysis(modelClass); Field xfield = analysis.findField(xFieldName); Field yfield = analysis.findField(yFieldName); Field vectorField = analysis.findTypedField("vector", Boolean.class); Field colorField = analysis.findTypedField("color", Color.class); if(xfield != null && yfield != null) { try { Object xvalue = xfield.get(m); Object yvalue = yfield.get(m); if(xvalue != null && yvalue != null) { Class xclass = xvalue.getClass(); Class yclass = yvalue.getClass(); if(!Model.isSubclass(xclass, Number.class)) { throw new IllegalArgumentException("X value must be a Number"); } if(!Model.isSubclass(yclass, Number.class)) { throw new IllegalArgumentException("Y value must be a Number"); } Number xnumber = (Number)xvalue; Number ynumber = (Number)yvalue; double x = xnumber.doubleValue(); double y = ynumber.doubleValue(); PaintableScale xScale = getPropertyValue(xScaleKey); PaintableScale yScale = getPropertyValue(yScaleKey); ScatterPoint sp = new ScatterPoint(x,y); if(vectorField != null) { Boolean isVector = (Boolean)vectorField.get(m); if(isVector != null && isVector) { sp.vector = true; } } if(colorField != null) { sp.color = (Color)colorField.get(m); } points.add(sp); models.add(m); xScale.updateScale(x); yScale.updateScale(y); if(sp.vector) { xScale.updateScale(0.0); yScale.updateScale(0.0); } dispatchChangedEvent(); } else { throw new IllegalArgumentException("x or y value was null"); } } catch (IllegalAccessException e) { throw new IllegalArgumentException("x or y field was inaccessible", e); } } else { String msg = "No Fields:"; if(xfield == null) { msg += String.format(" %s", xFieldName); } if(yfield == null) { msg += String.format(" %s", yFieldName); } throw new IllegalArgumentException(msg); } } public void addModels(Iterator<? extends Model> itr) { while(itr.hasNext()) { addModel(itr.next()); } } public <T> Set<T> findTags(double x1, double y1, double x2, double y2, String tagName) { System.out.println(String.format("findTags(%.2f,%.2f,%.2f,%.2f,%s)", x1,y1, x2,y2, tagName)); Set<T> tags = new HashSet<T>(); Collection<Model> contained = findModels(x1, y1, x2, y2); for(Model m : contained) { try { Field f = m.getClass().getField(tagName); T value = (T)f.get(m); tags.add(value); } catch (NoSuchFieldException e) { // silently fail. } catch (IllegalAccessException e) { e.printStackTrace(); } } return tags; } public Collection<Model> findModels(double x1, double y1, double x2, double y2) { ArrayList<Model> ms = new ArrayList<Model>(); for(int i = 0; i < points.size(); i++) { ScatterPoint pt = points.get(i); Double x = pt.x, y = pt.y; if(x >= x1 && y >= y1 && x < x2 && y < y2) { ms.add(models.get(i)); } } return ms; } public void clearModels() { points.clear(); models.clear(); setProperty(new PropertyValueWrapper<PaintableScale>(xScaleKey, new PaintableScale(0.0, 1.0))); setProperty(new PropertyValueWrapper<PaintableScale>(yScaleKey, new PaintableScale(0.0, 1.0))); dispatchChangedEvent(); } public void paintItem(Graphics g, int x1, int y1, int x2, int y2) { PaintableScale xScale = getPropertyValue(xScaleKey); PaintableScale yScale = getPropertyValue(yScaleKey); int radius = getPropertyValue(radiusKey, 3); Color color = getPropertyValue(colorKey, Color.red); float stroke = getPropertyValue(strokeKey, (float)1.0); Color axisColor = getPropertyValue(axisColorKey, Color.black); Boolean showScales = getPropertyValue(showScaleKey, Boolean.TRUE); int diam = radius*2; int w = x2-x1, h = y2-y1; Graphics2D g2 = (Graphics2D)g; Stroke oldStroke = g2.getStroke(); g2.setStroke(new BasicStroke(stroke)); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); /** Painting Code **/ VerticalScalePainter vsp = new VerticalScalePainter(yScale); HorizontalScalePainter hsp = new HorizontalScalePainter(xScale); clearDrawnPoints(); // We may need the coordinates for the origin, so let's go ahead and // get their scale and coordinates now. double zxf = xScale.fractionalOffset(0.0); double zyf = yScale.fractionalOffset(0.0); int zx = x1 + (int)Math.round(zxf * (double)w); int zy = y2 - (int)Math.round(zyf * (double)h); if(showScales) { g.setColor(Color.black); g.drawLine(x1, zy, x2, zy); hsp.paintItem(g, x1, zy, x2, y2); g.setColor(Color.black); g.drawLine(zx, y1, zx, y2); vsp.paintItem(g, zx, y1, x2, y2); } // Points for(int i = 0; i < points.size(); i++) { ScatterPoint p = points.get(i); Model m = models.get(i); double xf = xScale.fractionalOffset(p.x); double yf = yScale.fractionalOffset(p.y); int px = x1 + (int)Math.round(xf * (double)w); int py = y2 - (int)Math.round(yf * (double)h); g2.setColor(p.color != null ? p.color : color); if(p.vector) { // Some points are "vectors", and get an arrow drawn for them. int dx = px-zx, dy = py-zy; double len = Math.sqrt((double)(dx*dx + dy*dy)); double rot = p.y >= 0.0 ? Math.acos((double)dx/len) : Math.PI + Math.acos((double)dx/len); g2.translate(zx, zy); g2.rotate(rot); int off = (int)Math.round(len); int arrow = Math.max(5, Math.min(15, off/20)); g2.drawLine(0, 0, off, 0); g2.drawLine(off, 0, off-arrow, -arrow); g2.drawLine(off, 0, off-arrow, arrow); g2.rotate(-rot); g2.translate(-zx, -zy); } else { // Usually, we just draw a normal point. g2.drawOval(px-radius, py-radius, diam, diam); } if(m != null) { drawPoint(new Point(px, py), m); } } g2.setStroke(oldStroke); } public static class InteractiveFrame extends JFrame { private InteractivePanel panel; private ModelScatter scatter; public InteractiveFrame(ModelScatter sc, String tagName) { super(tagName); Container c = (Container)getContentPane(); c.setLayout(new BorderLayout()); scatter = sc; c.add(panel = new InteractivePanel(scatter, tagName), BorderLayout.CENTER); panel.setPreferredSize(new Dimension(300, 300)); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); SwingUtilities.invokeLater(new Runnable() { public void run() { setVisible(true); pack(); } }); } } public static class InteractivePanel extends PaintablePanel { private ModelScatter scatter; private int lastWidth, lastHeight; private String tagName; private Point p1, p2; private Point mouse, models; private Set<Model> mouseModels; public InteractivePanel(ModelScatter sc, String tagn) { //super(new DoubleBufferedPaintable(sc)); super(new DoubleBufferedPaintable(new OverlayModelPaintable(sc))); scatter = sc; lastWidth = lastHeight = 1; tagName = tagn; p1 = p2 = null; addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if(tagName != null) { p1 = e.getPoint(); p2 = p1; repaint(); } } public void mouseReleased(MouseEvent e) { if(p1 != null && p2 != null && tagName !=null) { System.out.println(String.format("Searching %s, %s", p1.toString(), p2.toString())); Set tags = findTags(p1, p2, tagName); System.out.println("Found: " + tags.size()); for(Object t : tags) { System.out.println(t.toString()); } } } public void mouseClicked(MouseEvent e) { p1 = p2 = null; repaint(); } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { if(p1 != null) { p2 = e.getPoint(); repaint(); } } public void mouseMoved(MouseEvent e) { if(e.getButton() == MouseEvent.NOBUTTON) { mouse = e.getPoint(); Pair<Point,Set<Model>> nearby = scatter.findNearestDrawnPoint(mouse); models = nearby.getFirst(); mouseModels = nearby.getLast(); repaint(); } else { mouse = models = null; mouseModels = null; } } }); } public <T> Set<T> findTags(Point p1, Point p2, String tagName) { int minX = Math.min(p1.x, p2.x), maxX = Math.max(p1.x, p2.x); int minY = Math.min(p1.y, p2.y), maxY = Math.max(p1.y, p2.y); double x1 = xPixToCoord(minX), x2 = xPixToCoord(maxX); double y1 = yPixToCoord(lastHeight-maxY), y2 = yPixToCoord(lastHeight-minY); return scatter.findTags(x1, y1, x2, y2, tagName); } private double xPixToCoord(int x) { PaintableScale scale = scatter.getPropertyValue(ModelScatter.xScaleKey); double f = (double)x / (double)lastWidth; double range = scale.getRange(); return scale.getMin() + f*range; } private double yPixToCoord(int y) { PaintableScale scale = scatter.getPropertyValue(ModelScatter.yScaleKey); double f = (double)y / (double)lastHeight; double range = scale.getRange(); return scale.getMin() + f*range; } protected void paintComponent(Graphics g) { super.paintComponent(g); lastWidth = getWidth(); lastHeight = getHeight(); if(p1 != null && p2 != null) { int x1 = Math.min(p1.x, p2.x), x2 = Math.max(p1.x, p2.x); int y1 = Math.min(p1.y, p2.y), y2 = Math.max(p1.y, p2.y); g.setColor(Color.blue); g.drawRect(x1, y1, x2-x1, y2-y1); } if(mouse != null && models != null) { g.setColor(Color.black); g.drawLine(mouse.x, mouse.y, models.x, models.y); g.drawString(mouseModels.toString(), mouse.x, mouse.y); } } } public static class ScatterPoint extends Model { public Double x, y; public Boolean vector; public Color color; public String name; public ScatterPoint() {} public ScatterPoint(Double _x, Double _y) { x = _x; y = _y; vector = false; color = null; name = super.toString(); } public ScatterPoint(Double x, Double y, Color c, String n) { this(x, y); color = c; name = n; } public String toString() { return name; } } }