package org.seqcode.gseutils.graphs.ui;
import java.util.*;
import java.awt.Graphics;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import org.seqcode.gseutils.graphs.*;
public class Visualizer {
public static void main(String[] args) {
DirectedGraph dg = new DirectedGraph();
Frame f = new Frame(dg);
}
public static class Frame extends JFrame {
private JButton in,out,left,right,up,down,addVert,addEdge,recenter;
private Graph graph;
private Visualizer viz;
public Frame(Graph g) {
super("Graph Frame");
graph = g;
viz = new Visualizer(graph, (graph instanceof DirectedGraph));
in = new JButton("+");
out = new JButton("-");
left = new JButton("<<");
right = new JButton(">>");
up = new JButton("^^");
down = new JButton("vv");
addVert = new JButton("VERT+");
addEdge = new JButton("EDGE+");
recenter = new JButton("Arr");
JPanel zoomPanel = new JPanel(); zoomPanel.setLayout(new GridLayout(1, 2));
zoomPanel.add(out); zoomPanel.add(in);
JPanel movePanel = new JPanel();
movePanel.setLayout(new BorderLayout());
movePanel.add(left, BorderLayout.WEST);
movePanel.add(right, BorderLayout.EAST);
movePanel.add(up, BorderLayout.NORTH);
movePanel.add(down,BorderLayout.SOUTH);
movePanel.add(zoomPanel, BorderLayout.CENTER);
JPanel buttons = new JPanel(); buttons.setLayout(new GridLayout(1, 4));
buttons.add(movePanel);
buttons.add(addVert);
buttons.add(addEdge);
buttons.add(recenter);
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
mainPanel.add(buttons, BorderLayout.SOUTH);
mainPanel.add(new Panel(viz), BorderLayout.CENTER);
Container c = (Container)getContentPane();
c.setLayout(new BorderLayout());
c.add(mainPanel, BorderLayout.CENTER);
in.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
viz.addAction(new ScaleAction(ScaleAction.ZOOM_IN));
repaint();
}
});
out.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
viz.addAction(new ScaleAction(ScaleAction.ZOOM_OUT));
repaint();
}
});
up.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
viz.addAction(new ScaleAction(ScaleAction.SHIFT_UP));
repaint();
}
});
down.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
viz.addAction(new ScaleAction(ScaleAction.SHIFT_DOWN));
repaint();
}
});
left.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
viz.addAction(new ScaleAction(ScaleAction.SHIFT_LEFT));
repaint();
}
});
right.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
viz.addAction(new ScaleAction(ScaleAction.SHIFT_RIGHT));
repaint();
}
});
addVert.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
int nodeNum = graph.getVertices().size() + 1;
String name = "Node#" + nodeNum;
viz.addVertex(name);
repaint();
}
});
addEdge.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Random rand = new Random();
Vector<String> names = new Vector<String>(graph.getVertices());
String n1 = names.get(rand.nextInt(names.size()));
String n2 = names.get(rand.nextInt(names.size()));
viz.addEdge(n1, n2);
repaint();
}
});
recenter.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
viz.recenterVertices();
repaint();
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
pack();
}
}
public static class Panel extends JPanel {
private Visualizer viz;
private String selected;
public Panel(Visualizer v) {
super();
viz = v;
selected = null;
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
selected = viz.getVertexAtPoint(e.getPoint());
}
public void mouseReleased(MouseEvent e) {
viz.saveOverrides();
viz.clearOverrides();
repaint();
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
if(selected != null) {
viz.setOverride(selected, e.getPoint());
repaint();
}
}
});
}
public Dimension getPreferredSize() {
Dimension pd = super.getPreferredSize();
return new Dimension(Math.max(100, pd.width), Math.max(100, pd.height));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.white);
int w = getWidth(), h = getHeight();
g.fillRect(0, 0, w, h);
viz.paintItem(g, 0, 0, w, h);
}
}
private static Random rand;
static {
rand = new Random();
}
private boolean directed;
private Graph graph;
private Map<String,VizVertex> vizVertices;
private Map<String,Point> overridePoints;
private LinkedList<ScaleAction> actions;
private Point ulPoint;
private double scale;
private int lastWidth, lastHeight;
private ScaleWindow lastWin;
public Visualizer(Graph g, boolean dir) {
graph = g;
directed = dir;
vizVertices = new HashMap<String,VizVertex>();
overridePoints = new HashMap<String,Point>();
ulPoint = new Point(0, 0);
lastWidth = lastHeight = 100;
lastWin = null;
scale = 1.0;
actions = new LinkedList<ScaleAction>();
int diam = 6;
for(String vertex : graph.getVertices()) {
int x = (int)Math.floor(rand.nextDouble() * (double)lastWidth);
int y = (int)Math.floor(rand.nextDouble() * (double)lastHeight);
vizVertices.put(vertex, new VizVertex(vertex, x, y, diam));
}
}
public void addAction(ScaleAction sa) { actions.addLast(sa); }
public void removeAction() { if(!actions.isEmpty()) { actions.removeLast(); } }
public void setOverride(String n, Point pt) { overridePoints.put(n, pt); }
public void clearOverrides() { overridePoints.clear(); }
public void saveOverrides() {
if(lastWin != null && ulPoint != null) {
int lx = ulPoint.x, uy = ulPoint.y;
int rx = lx + lastWidth, ly = uy + lastHeight;
for(String n : overridePoints.keySet()) {
VizVertex vv = vizVertices.get(n);
Point unpt = lastWin.getUnscaledPoint(overridePoints.get(n), lx, uy, rx, ly);
vv.setPoint(unpt);
}
}
}
public String getVertexAtPoint(Point pt) {
for(String name : vizVertices.keySet()) {
VizVertex vv = vizVertices.get(name);
int rad = vv.getScaledDiam() / 2;
Point vpt = vv.getLastPoint();
if(Math.abs(pt.x - vpt.x) <= rad && Math.abs(pt.y - vpt.y) <= rad) {
return name;
}
}
return null;
}
public void recenterVertices() {
if(lastWin != null && ulPoint != null) {
Point pt = lastWin.getUpperLeftPoint();
int w = lastWin.getWidth(), h = lastWin.getHeight();
for(String n : vizVertices.keySet()) {
Point p = new Point(vizVertices.get(n).getX(), vizVertices.get(n).getY());
if(!lastWin.containsPoint(p)) {
int x = rand.nextInt(w) + pt.x;
int y = rand.nextInt(h) + pt.y;
vizVertices.get(n).setPoint(new Point(x, y));
}
}
}
}
public void addVertex(String name) {
if(directed) {
DirectedGraph dg = (DirectedGraph)graph;
dg.addVertex(name);
} else {
UndirectedGraph ug = (UndirectedGraph)graph;
ug.addVertex(name);
}
int diam = 6;
int w = lastWin != null ? lastWin.getWidth() : 100;
int h = lastWin != null ? lastWin.getHeight() : 100;
int wx = lastWin != null ? lastWin.getUpperLeftPoint().x : 0;
int wy = lastWin != null ? lastWin.getUpperLeftPoint().y : 0;
int x = wx + (int)Math.floor(rand.nextDouble() * (double)w);
int y = wy + (int)Math.floor(rand.nextDouble() * (double)h);
vizVertices.put(name, new VizVertex(name, x, y, diam));
}
public void addEdge(String n1, String n2) {
if(directed) {
DirectedGraph dg = (DirectedGraph)graph;
dg.addEdge(n1, n2);
} else {
UndirectedGraph ug = (UndirectedGraph)graph;
ug.addEdge(n1, n2);
}
}
public void paintItem(Graphics g, int x1, int y1, int x2, int y2) {
Graphics2D g2 = (Graphics2D)g;
int w = x2 - x1, h = y2 - y1;
lastWidth = w; lastHeight = h;
ScaleWindow win = new ScaleWindow(ulPoint.x, ulPoint.y, ulPoint.x + w, ulPoint.y + h, scale);
for(ScaleAction action : actions) {
win.handleAction(action);
}
lastWin = win;
int minDiam = -1;
// This locates each vertex, according to the scale of the window.
for(String vn : vizVertices.keySet()) {
VizVertex vv = vizVertices.get(vn);
vv.locateVertex(x1, y1, x2, y2, win);
if(minDiam == -1 || vv.getScaledDiam() < minDiam) {
minDiam = vv.getScaledDiam();
}
}
Stroke oldStroke = g2.getStroke();
float thickness = (float)(Math.max(minDiam / 8, 1));
if(minDiam > 8) { g2.setStroke(new BasicStroke(thickness)); }
// This draws the edges.
g.setColor(Color.black);
for(String vn : vizVertices.keySet()) {
VizVertex vvn = vizVertices.get(vn);
Point s = vvn.getLastPoint();
if(overridePoints.containsKey(vn)) {
s = overridePoints.get(vn);
}
for(String vt : graph.getNeighbors(vn)) {
VizVertex vvt = vizVertices.get(vt);
Point e = vvt.getLastPoint();
if(overridePoints.containsKey(vt)) {
e = overridePoints.get(vt);
}
g.drawLine(s.x, s.y, e.x, e.y);
if(directed) {
int dx = e.x - s.x, dy = e.y - s.y;
double dist = Math.sqrt((double)((dx * dx) + (dy * dy)));
if(dist > 0.0) {
double theta = Math.asin((double)dy / dist);
if(dx < 0) { theta = Math.PI - theta; }
g2.translate(s.x, s.y);
g2.rotate(theta);
int nx = (int)Math.round(dist);
int targetRad = vvt.getScaledDiam() / 2;
int ah = targetRad/2;
int ax = nx - targetRad;
g.drawLine(ax, 0, ax-ah, ah);
g.drawLine(ax, 0, ax-ah, -ah);
//g.drawLine(ax-ah, ah, ax-ah, -ah);
g2.rotate(-theta);
g2.translate(-s.x, -s.y);
}
}
}
}
g2.setStroke(oldStroke);
// This draws the vertices themselves.
for(String vn : vizVertices.keySet()) {
VizVertex vv = vizVertices.get(vn);
if(overridePoints.containsKey(vn)) {
vv.paintAt(g, overridePoints.get(vn));
} else {
vv.paint(g);
}
}
}
}
class VizVertex {
private int cx, cy;
private int diam;
private String name;
private Point lastPoint;
private int scaledDiam;
public VizVertex(String n, int x, int y, int d) {
name = n;
cx = x; cy = y;
diam = d;
lastPoint = null;
scaledDiam = diam;
}
public int getX() { return cx; }
public int getY() { return cy; }
public int getDiam() { return diam; }
public String getName() { return name; }
public void setPoint(Point p) { cx = p.x; cy = p.y; }
public Point getLastPoint() { return lastPoint; }
public int getScaledDiam() { return scaledDiam; }
public boolean containsSpot(int x, int y) {
if(lastPoint != null) {
int rad = Math.max(1, scaledDiam / 2);
return Math.abs(x-lastPoint.x) <= rad && Math.abs(y-lastPoint.y) <= rad;
}
return false;
}
public void locateVertex(int x1, int y1, int x2, int y2, ScaleWindow win) {
lastPoint = win.getScaledPoint(new Point(cx, cy), x1, y1, x2, y2);
scaledDiam = win.getScaledDiameter(diam);
}
public void paint(Graphics g) {
paintAt(g, lastPoint);
}
public void paintAt(Graphics g, Point pt) {
Graphics2D g2 = (Graphics2D)g;
Stroke oldStroke = g2.getStroke();
float thickness = (float)Math.max(1, scaledDiam / 10);
g2.setStroke(new BasicStroke(thickness));
int rad = Math.max(1, scaledDiam/2);
g.setColor(Color.pink);
g.fillOval(pt.x-rad, pt.y-rad, scaledDiam, scaledDiam);
g.setColor(Color.black);
g.drawOval(pt.x-rad, pt.y-rad, scaledDiam, scaledDiam);
g2.setStroke(oldStroke);
}
}
class ScaleAction {
public static final int ZOOM_IN = 0;
public static final int ZOOM_OUT = 1;
public static final int SHIFT_LEFT = 2;
public static final int SHIFT_RIGHT = 3;
public static final int SHIFT_UP = 4;
public static final int SHIFT_DOWN = 5;
public int type;
public ScaleAction(int t) {
type = t;
}
}
class ScaleWindow {
private int x1, y1, x2, y2;
private double scale;
public ScaleWindow(int x1, int y1, int x2, int y2, double sc) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
scale = sc;
}
public Point getUpperLeftPoint() { return new Point(x1, y1); }
public int getWidth() { return x2 - x1; }
public int getHeight() { return y2 - y1; }
public boolean containsPoint(Point p) {
return p.x >= x1 && p.x < x2 && p.y >= y1 && p.y < y2;
}
public Point getUnscaledPoint(Point p, int lx, int uy, int rx, int ly) {
int ww = x2 - x1, wh = y2 - y1;
int fw = rx - lx, fh = ly - uy;
double xf = (double)(p.x-lx) / (double)fw;
double yf = (double)(p.y-uy) / (double)fh;
int px = x1 + (int)Math.round(xf * (double)ww);
int py = y1 + (int)Math.round(yf * (double)wh);
return new Point(px, py);
}
public Point getScaledPoint(Point p, int lx, int uy, int rx, int ly) {
int ww = x2 - x1, wh = y2 - y1;
int fw = rx - lx, fh = ly - uy;
double xf = (double)(p.x-x1) / (double)ww;
double yf = (double)(p.y-y1) / (double)wh;
int px = lx + (int)Math.round(xf * (double)fw);
int py = uy + (int)Math.round(yf * (double)fh);
return new Point(px, py);
}
public void handleAction(ScaleAction sa) {
switch(sa.type) {
case ScaleAction.ZOOM_IN:
zoomIn();
break;
case ScaleAction.ZOOM_OUT:
zoomOut();
break;
case ScaleAction.SHIFT_LEFT:
shiftLeft();
break;
case ScaleAction.SHIFT_RIGHT:
shiftRight();
break;
case ScaleAction.SHIFT_UP:
shiftUp();
break;
case ScaleAction.SHIFT_DOWN:
shiftDown();
break;
}
}
public void translate(int dx, int dy) {
x1 += dx; x2 += dx;
y1 += dy; y2 += dy;
}
public void shiftLeft() { translate(-(x2 - x1) / 4, 0); }
public void shiftRight() { translate((x2 - x1) / 4, 0); }
public void shiftUp() { translate(0, -(y2 - y1) / 4); }
public void shiftDown() { translate(0, (y2 - y1) / 4); }
public void resize(int dx, int dy) {
x1 -= dx;
y1 -= dy;
}
public void zoomIn() {
int w = x2 - x1, h = y2 - y1;
int w4 = Math.max(1, (int)Math.floor((double)w / 4.0));
int h4 = Math.max(1, (int)Math.floor((double)h / 4.0));
if(w4 >= 1 && h4 >= 1) {
x1 += w4; x2 -= w4;
y1 += h4; y2 -= h4;
int nw = x2 - x1, nh = y2 - y1;
if(nw >= nh) {
scale *= (double)w / (double)nw;
} else {
scale *= (double)h / (double)nh;
}
}
}
public void zoomOut() {
int w = x2 - x1, h = y2 - y1;
int w2 = Math.max(1, (int)Math.floor((double)w / 2.0));
int h2 = Math.max(1, (int)Math.floor((double)h / 2.0));
x1 -= w2; x2 += w2;
y1 -= h2; y2 += h2;
int nw = x2 - x1, nh = y2 - y1;
if(nw >= nh) {
scale *= (double)w / (double)nw;
} else {
scale *= (double)h / (double)nh;
}
}
public int getScaledDiameter(int diam) {
return (int)Math.round(diam * scale);
}
}