package iiuf.swing.graph; import java.awt.Dimension; import java.awt.Component; import iiuf.util.Util; import iiuf.util.AttributeFactory; import iiuf.util.Attributable; import iiuf.util.graph.GraphNode; import iiuf.util.graph.GraphModel; import iiuf.util.graph.GraphEdge; import iiuf.util.Util; /** Force directred node layout node layouter. (c) 2000, 2001, IIUF, DIUF<p> @author $Author: ohitz $ @version $Name: $ $Revision: 1.1 $ */ public class ForceDirectedNL extends Thread implements NodeLayouter { private int NODE_INFO; private GraphModel gca; private Dimension size = new Dimension(1500, 1500); private GraphNode[] gnodes; private GraphEdge[] gedges; private Component[] cmps; private int COMPONENT; private boolean running; private boolean inited; public ForceDirectedNL() { setPriority(MIN_PRIORITY); start(); } public boolean allowsNodeLocationChange() { return true; } public Dimension layout(GraphPanel panel, GraphModel graph) { boolean reinit; synchronized(this) { reinit = gca != graph; if(reinit) { NODE_INFO = graph.nodeAttribute("node_info", new AttributeFactory() { public Object newAttribute(Attributable attributable, Object[] args) { return new NodeInfo(); } }); gca = graph; } COMPONENT = panel.COMPONENT; gnodes = (GraphNode[])graph.nodes().toArray(new GraphNode[graph.nodes().size()]); gedges = (GraphEdge[])graph.edges().toArray(new GraphEdge[graph.edges().size()]); cmps = new Component[gnodes.length]; } int fixedc = 0; for(int i = 0; i < cmps.length; i++) { cmps[i] = (Component)gnodes[i].get(COMPONENT); NodeInfo info = (NodeInfo)gnodes[i].get(NODE_INFO); if(info.fixed) fixedc++; if(reinit) { Dimension d = cmps[i].getPreferredSize(); cmps[i].setBounds(Util.intRandom(size.width), Util.intRandom(size.height), d.width, d.height); } info.x = cmps[i].getX(); info.y = cmps[i].getY(); } inited = true; activate(); return size; } public void setFixed(GraphNode node, boolean state) { ((NodeInfo)node.get(NODE_INFO)).fixed = state; } public synchronized void activate() { running = true; notify(); } public synchronized void deactivate() { running = false; inited = false; } class NodeInfo { double dx; double dy; double x; double y; boolean fixed; } public void run() { for(;;) { synchronized(this) { try{wait();} catch(InterruptedException e) {Util.printStackTrace(e);} if(!inited) continue; } while(running) { Util.delay(100); GraphPanel gp = null; synchronized(this) { relax(); if(cmps[0] == null) continue; gp = (GraphPanel)cmps[0].getParent(); if(gp == null) continue; } for(int i = 0; i < gnodes.length; i++) { NodeInfo info = (NodeInfo)gnodes[i].get(NODE_INFO); if(gp.getSelectionModel().isSelected(gnodes[i]) || gp.isDragging(gnodes[i])) { info.x = cmps[i].getX(); info.y = cmps[i].getY(); } else cmps[i].setLocation((int)info.x, (int)info.y); } gp.repaint(); } } } private void relax() { int nnodes = gnodes.length; for (int i = 0 ; i < gedges.length; i++) { GraphEdge e = gedges[i]; GraphNode[] nodes = e.getNodes(); Component to = (Component)nodes[e.TO].get(COMPONENT); Component from = (Component)nodes[e.FROM].get(COMPONENT); double vx = to.getX() - from.getX(); double vy = to.getY() - from.getY(); double len = Math.sqrt(vx * vx + vy * vy); len = (len == 0) ? .0001 : len; double f = (e.getWeight() - len) / (len * 3); double dx = f * vx; double dy = f * vy; NodeInfo toi = (NodeInfo)nodes[e.TO].get(NODE_INFO); toi.dx += dx; toi.dy += dy; NodeInfo fromi = (NodeInfo)nodes[e.FROM].get(NODE_INFO); fromi.dx += -dx; fromi.dy += -dy; } for (int i = 0 ; i < nnodes ; i++) { GraphNode n1 = gnodes[i]; NodeInfo n1i = (NodeInfo)n1.get(NODE_INFO); double dx = 0; double dy = 0; for (int j = 0 ; j < nnodes ; j++) { if (i == j) { continue; } GraphNode n2 = gnodes[j]; NodeInfo n2i = (NodeInfo)n2.get(NODE_INFO); double vx = n1i.x - n2i.x; double vy = n1i.y - n2i.y; double len = vx * vx + vy * vy; if (len == 0) { dx += Math.random(); dy += Math.random(); } else if (len < 100*100) { dx += vx / len; dy += vy / len; } } double dlen = dx * dx + dy * dy; if (dlen > 0) { dlen = Math.sqrt(dlen) / 2; n1i.dx += dx / dlen; n1i.dy += dy / dlen; } } for (int i = 0 ; i < nnodes ; i++) { GraphNode n = gnodes[i]; NodeInfo ni = (NodeInfo)n.get(NODE_INFO); if (!ni.fixed) { ni.x += Math.max(-5, Math.min(5, ni.dx)); ni.y += Math.max(-5, Math.min(5, ni.dy)); } if (ni.x < 0) { ni.x = 0; } else if (ni.x > size.width) { ni.x = size.width; } if (ni.y < 0) { ni.y = 0; } else if (ni.y > size.height) { ni.y = size.height; } ni.dx /= 2; ni.dy /= 2; } } } /* $Log: ForceDirectedNL.java,v $ Revision 1.1 2002/07/11 12:09:52 ohitz Initial checkin Revision 1.1 2001/02/17 09:54:21 schubige moved graph stuff to iiuf.swing.graph, started work on rotatable GraphNodeComponents Revision 1.5 2001/01/04 16:28:38 schubige Header update for 2001 and DIUF Revision 1.4 2000/12/28 09:29:10 schubige SourceWatch beta Revision 1.3 2000/12/18 12:39:09 schubige Added ports to iiuf.util.graph Revision 1.2 2000/11/10 10:46:53 schubige iiuf tree cleanup iter 3 Revision 1.1 2000/08/17 16:22:14 schubige Swing cleanup & TreeView added Revision 1.1 2000/07/28 12:07:58 schubige Graph stuff update */