Force-Directed Edge Bundling
 All Classes Functions
CanvasPanel.java
1 import javax.swing.JPanel;
2 
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Graphics;
6 import java.awt.Graphics2D;
7 import java.awt.event.MouseEvent;
8 import java.awt.event.MouseListener;
9 import java.awt.event.MouseMotionListener;
10 import java.awt.geom.Line2D;
11 import java.awt.image.BufferedImage;
12 import java.util.ArrayList;
13 
20 public class CanvasPanel extends JPanel {
21 
22  private boolean bundlingActive;
23  private int mousePosX, mousePosY;
24  private int radius = 5;
25  private ArrayList<Edge> edges;
26  private ArrayList<Node> nodes;
27  private MainWindow window;
28  private Node selectedNode;
29 
33  public CanvasPanel(MainWindow window) {
34  this.window = window;
35  setBackground(Color.WHITE);
36 
37  edges = new ArrayList<Edge>();
38  nodes = new ArrayList<Node>();
39  bundlingActive = false;
40  addListeners();
41  }
42 
46  private void addListeners(){
47  addMouseListener(new MouseListener() {
48 
49  @Override
50  public void mouseReleased(MouseEvent e) {
51  // Create or move a Node of the graph.
52  if(window.getMouseMode() == MouseMode.NODE){
53  if(selectedNode != null){
54  boolean occupied = false;
55  for(Node node : nodes){
56  if(clickedNode(e, node) && !node.equals(selectedNode)){
57  occupied = true;
58  break;
59  }
60  }
61  if(!occupied)
62  selectedNode.setCoords(mousePosX, mousePosY);
63  selectedNode = null;
64  }
65  else
66  nodes.add(new Node(e.getX(), e.getY()));
67  }
68  // Create an Edge of the graph.
69  if(window.getMouseMode() == MouseMode.EDGE){
70  for(Node node : nodes){
71  if(clickedNode(e, node)){
72  if(!node.equals(selectedNode)){
73  edges.add(new Edge(selectedNode, node));
74  break;
75  }
76  }
77  }
78  selectedNode = null;
79  }
80  // Delete a Node or Edge of the graph.
81  if(window.getMouseMode() == MouseMode.DELETE){
82  for(Node node : nodes){
83  if(clickedNode(e, node)){
84  nodes.remove(node);
85  ArrayList<Edge> edgesToDelete = new ArrayList<Edge>();
86  for(Edge edge : edges){
87  if(edge.isIncident(node))
88  edgesToDelete.add(edge);
89  }
90  edges.removeAll(edgesToDelete);
91  break;
92  }
93  }
94  for(Edge edge : edges){
95  if(clickedEdge(e, edge)){
96  edges.remove(edge);
97  break;
98  }
99  }
100  }
101  paintComponent(getGraphics());
102  }
103 
104  @Override
105  public void mousePressed(MouseEvent e) {
106  // Let the user create an Edge of the graph.
107  if(window.getMouseMode() == MouseMode.EDGE){
108  for(Node node : nodes){
109  if(clickedNode(e, node)){
110  selectedNode = node;
111  break;
112  }
113  }
114  }
115  // Let the user move a Node of the graph.
116  if(window.getMouseMode() == MouseMode.NODE){
117  for(Node node : nodes){
118  if(clickedNode(e, node)){
119  selectedNode = node;
120  break;
121  }
122  }
123  }
124  }
125 
126  @Override
127  public void mouseExited(MouseEvent e) {
128  }
129 
130  @Override
131  public void mouseEntered(MouseEvent e) {
132  }
133 
134  @Override
135  public void mouseClicked(MouseEvent e) {
136  }
137  });
138 
139  addMouseMotionListener(new MouseMotionListener() {
140 
141  @Override
142  public void mouseMoved(MouseEvent e) {
143  }
144 
145  @Override
146  // Draw the Edge or Node that the user is holding.
147  public void mouseDragged(MouseEvent e) {
148  if(selectedNode != null){
149  mousePosX = e.getX();
150  mousePosY = e.getY();
151  paintComponent(getGraphics());
152  }
153  }
154  });
155  }
156 
157  @Override
158  public void paintComponent(Graphics g){
159  super.paintComponent(g);
160  BufferedImage bimg = new BufferedImage(getSize().width, getSize().width,
161  BufferedImage.TYPE_INT_ARGB);
162  Graphics2D g2d = bimg.createGraphics();
163 
164  // Draw the Edges of the graph.
165  g2d.setColor(new Color(0, 0, 0, 0.10f));
166  g2d.setStroke(new BasicStroke(3));
167  for(Edge edge : edges){
168  if(bundlingActive){
169  g2d.drawPolyline(edge.subNodesXInt(), edge.subNodesYInt(), edge.getSubNodes().size());
170  }
171  else
172  g2d.drawLine(edge.getStartNode().getXInt(), edge.getStartNode().getYInt(),
173  edge.getEndNode().getXInt(), edge.getEndNode().getYInt());
174  }
175 
176  // Color the Edges of the graph.
177  int[] pixels = bimg.getRGB(0, 0, getSize().width, getSize().height, null, 0, getSize().width);
178  for(int p=0; p < getSize().width*getSize().height; p++){
179  int alpha = pixels[p]>>24 & 255;
180  if(alpha > 0)
181  pixels[p] = alpha<<24 | alpha<<16 | (255-alpha);
182  }
183  bimg.setRGB(0, 0, getSize().width, getSize().height, pixels, 0, getSize().width);
184 
185  // Draw the Nodes of the graph.
186  g2d.setColor(Color.BLACK);
187  for(Node node : nodes){
188  if(node.equals(selectedNode)){
189  if(window.getMouseMode() == MouseMode.NODE)
190  g2d.fillOval(mousePosX-radius, mousePosY-radius, radius*2, radius*2);
191  else{
192  g2d.fillOval(node.getXInt()-radius, node.getYInt()-radius, radius*2, radius*2);
193  g2d.drawLine(node.getXInt(), node.getYInt(), mousePosX, mousePosY);
194  }
195  }
196  else
197  g2d.fillOval(node.getXInt()-radius, node.getYInt()-radius, radius*2, radius*2);
198  }
199  this.getGraphics().drawImage(bimg, 0, 0, null);
200  }
201 
207  public void addEdge(int startNodeIndex, int endNodeIndex){
208  edges.add(new Edge(nodes.get(startNodeIndex), nodes.get(endNodeIndex)));
209  }
210 
216  public void addNode(int x, int y){
217  nodes.add(new Node(x, y));
218  }
219 
224  public ArrayList<Edge> getEdges(){
225  return edges;
226  }
227 
232  public ArrayList<Node> getNodes(){
233  return nodes;
234  }
235 
241  public void bundleEdges(int stiffness, boolean animate){
242  int cycles = 5;
243  int iterations = 50;
244  double stepSize = 0.4;
245  double progress = 0;
246  for(int c=0; c < cycles; c++){
247  for(Edge e : edges)
248  e.increaseSubNodes();
249  for(int i=0; i < iterations; i++){
250  for(Edge e : edges){
251  double kp = stiffness/e.magnitude()*(e.getSubNodes().size()-1);
252  ArrayList<Node> tempSubNodes = new ArrayList<Node>();
253  tempSubNodes.add(e.getStartNode());
254  for(int n=1; n < e.getSubNodes().size()-1; n++){
255  double esForceX = 0;
256  double esForceY = 0;
257  Node s = e.getSubNodes().get(n);
258  Node pre = e.getSubNodes().get(n-1);
259  Node suc = e.getSubNodes().get(n+1);
260 
261  // Calculate electrostatic force
262  for(Edge f : edges){
263  if(e != f){
264  double comp = e.compatibility(f);
265  if(comp > 0.1){
266  Node t = f.getSubNodes().get(n);
267  double nDist = Double.POSITIVE_INFINITY;
268  for(Node u : f.getSubNodes()){
269  if(s.distance(u) < nDist){
270  nDist = s.distance(u);
271  t = u;
272  }
273  }
274  if(s.distance(t) > 0){
275  esForceX += comp * ((t.getX()-s.getX()) / s.distance(t));
276  esForceY += comp * ((t.getY()-s.getY()) / s.distance(t));
277  }
278  }
279  }
280  }
281  // Calculate spring force and move sub-Nodes
282  double springForceX = kp * ((pre.getX()-s.getX()))
283  + kp * ((suc.getX()-s.getX()));
284  double springForceY = kp * ((pre.getY()-s.getY()))
285  + kp * ((suc.getY()-s.getY()));
286  tempSubNodes.add(new Node(s.getX()+esForceX*stepSize+springForceX*stepSize,
287  s.getY()+esForceY*stepSize+springForceY*stepSize));
288  }
289  tempSubNodes.add(e.getEndNode());
290  e.setTempSubNodes(tempSubNodes);
291  }
292  for(Edge e : edges)
293  e.finalizeSubNodes();
294  bundlingActive = true;
295  progress += (100.0/cycles)/iterations;
296  window.updateProgress((int)progress);
297  if(animate)
298  paintComponent(getGraphics());
299  }
300  // Decrease iterations and stepSize for next cycle
301  iterations = (int)Math.round(iterations*2/3);
302  stepSize = stepSize/2;
303  }
304  bundlingActive = true;
305  window.updateProgress(0);
306  paintComponent(getGraphics());
307  }
308 
312  public void clearAll(){
313  nodes.clear();
314  edges.clear();
315  paintComponent(getGraphics());
316  }
317 
321  public void connectAll(){
322  edges.clear();
323  for(int i=0; i<nodes.size(); i++)
324  for(int j=i+1; j<nodes.size(); j++)
325  edges.add(new Edge(nodes.get(i), nodes.get(j)));
326  paintComponent(getGraphics());
327  }
328 
332  public void unbundleEdges(){
333  bundlingActive = false;
334  for(Edge edge: edges)
335  edge.deleteSubNodes();
336  paintComponent(getGraphics());
337  }
338 
345  private boolean clickedEdge(MouseEvent e, Edge edge){
346  return (Line2D.ptSegDist(edge.getStartNode().getX(), edge.getStartNode().getY(),
347  edge.getEndNode().getX(), edge.getEndNode().getY(), e.getX(), e.getY()) < 2.0);
348  }
349 
356  private boolean clickedNode(MouseEvent e, Node node){
357  return (e.getX() >= node.getX()-radius && e.getX() <= node.getX()+radius &&
358  e.getY() >= node.getY()-radius && e.getY() <= node.getY()+radius);
359  }
360 }