package org.seqcode.gseutils.graphs; import java.util.*; import java.awt.*; import java.awt.event.*; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import javax.swing.*; import javax.swing.border.*; import org.seqcode.gseutils.Pair; public class GraphVisualizer extends JPanel { private Graph graph; private Map<String,Object> parameters; private LinkedList<Pair<Rectangle,String>> nodeBounds; private String movingVertex; public GraphVisualizer(Graph g) { graph = g; parameters = new HashMap<String,Object>(); parameters.put("showDirected", true); nodeBounds = new LinkedList<Pair<Rectangle,String>>(); movingVertex = null; rebuildLocations(); addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { movingVertex = retrieveVertexAtPoint(e.getPoint()); } public void mouseReleased(MouseEvent e) { if(movingVertex != null) { Point p = (Point)parameters.get("location:" + movingVertex); int radius = (Integer)parameters.get("radius:" + movingVertex); Rectangle rect = new Rectangle(p.x-radius, p.y-radius, 2*radius, 2*radius); nodeBounds.addFirst(new Pair<Rectangle,String>(rect, movingVertex)); movingVertex = null; repaint(); } } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { if(movingVertex != null) { parameters.put("location:" + movingVertex, e.getPoint()); repaint(); } } }); } public JFrame putInFrame() { JFrame f = new JFrame(); Container c = (Container)f.getContentPane(); c.setLayout(new BorderLayout()); c.add(this, BorderLayout.CENTER); f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); f.setVisible(true); f.pack(); return f; } private String retrieveVertexAtPoint(Point p) { Iterator<Pair<Rectangle,String>> itr = nodeBounds.iterator(); while(itr.hasNext()) { Pair<Rectangle,String> pair = itr.next(); if(pair.getFirst().contains(p)) { itr.remove(); return pair.getLast(); } } return null; } public String getVertexAtPoint(Point p) { for(Pair<Rectangle,String> pr : nodeBounds) { if(pr.getFirst().contains(p)) { return pr.getLast(); } } return null; } public Dimension getPreferredSize() { return new Dimension(400, 200); } public void rebuildLocations() { int w = getWidth(), h = getHeight(); w = Math.max(w, 100); h = Math.max(h, 100); Random r = new Random(); nodeBounds.clear(); for(String vertex : graph.getVertices()) { String key = "location:" + vertex; if(!parameters.containsKey(key)) { Point p = new Point(r.nextInt(w), r.nextInt(h)); parameters.put(key, p); parameters.put("radius:" + vertex, 10); } Point p = (Point)parameters.get("location:" + vertex); int radius = (Integer)parameters.get("radius:" + vertex); Rectangle rect = new Rectangle(p.x-radius, p.y-radius, 2*radius, 2*radius); nodeBounds.addLast(new Pair<Rectangle,String>(rect, vertex)); } } protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; Map oldHints = g2.getRenderingHints(); Map newHints = new HashMap(oldHints); newHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHints(newHints); int w = getWidth(), h = getHeight(); g.setColor(Color.white); g.fillRect(0, 0, w, h); boolean showDirected = (Boolean)parameters.get("showDirected"); for(String head : graph.getVertices()) { for(String tail : graph.getNeighbors(head)) { if(head.compareTo(tail) < 1 || showDirected) { paintEdge((Graphics2D)g, head, tail); } } } Stroke oldStroke = g2.getStroke(); g2.setStroke(new BasicStroke((float)2.0)); for(String v : graph.getVertices()) { paintNode(g2, v); } g2.setStroke(oldStroke); g2.setRenderingHints(oldHints); } private void paintNode(Graphics2D g, String v) { Point p = (Point)parameters.get("location:" + v); int radius = (Integer)parameters.get("radius:" + v); g.setColor(Color.white); g.fillOval(p.x-radius, p.y-radius, 2*radius, 2*radius); g.setColor(Color.black); g.drawOval(p.x-radius, p.y-radius, 2*radius, 2*radius); g.drawString(v, p.x-radius+2, p.y); } private static final double twopi = 2.0 * Math.PI; private void paintEdge(Graphics2D g, String h, String t) { Point p1 = (Point)parameters.get("location:" + h); Point p2 = (Point)parameters.get("location:" + t); int rad = (Integer)parameters.get("radius:" + h); int dx = p2.x - p1.x, dy = p1.y - p2.y; double theta = 0.0; if((dx != 0 || dy != 0)) { int cx = (p1.x + p2.x)/2, cy = (p1.y + p2.y)/2; double r = Math.sqrt(dx*dx + dy * dy); int r2 = (int)Math.round(r / 2.0); if(dx != 0) { if(dx > 0) { theta = Math.atan((double)dy / (double)dx); theta = twopi - theta; } else { theta = - Math.atan((double)dy / (double)dx) - Math.PI; } } else { theta = dy > 0 ? Math.PI / 2.0 : 3.0 * Math.PI / 2.0; theta += Math.PI; } g.translate(cx, cy); g.rotate(theta); int depth = 3; int sep = depth*2; int offset = r2/2; Stroke oldStroke = g.getStroke(); g.setStroke(new BasicStroke((float)3.0)); depth = rad; g.setColor(Color.cyan); int ptx = r2-(int)(rad*1.5); g.drawLine(-r2, 0, ptx, 0); g.drawLine(ptx, 0, ptx-depth, -depth); g.drawLine(ptx, 0, ptx-depth, depth); g.setStroke(oldStroke); g.rotate(-theta); g.translate(-cx, -cy); } } }