Skip to content

Instantly share code, notes, and snippets.

@Merovius
Created May 16, 2019 15:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Merovius/45a5eab3d310ec75ebde6b50079e1431 to your computer and use it in GitHub Desktop.
Save Merovius/45a5eab3d310ec75ebde6b50079e1431 to your computer and use it in GitHub Desktop.
Force-directed graph drawing
function Graph(div, edges, update) {
let g = this;
let c = document.createElement("canvas");
c.width = '800';
c.height = '400';
c.style.background = '#ffffff';
div.appendChild(c);
div.appendChild(document.createElement("br"));
let runSim = document.createElement("input");
runSim.type = "checkbox";
div.appendChild(runSim);
div.appendChild(document.createTextNode("run simulation"));
div.appendChild(document.createElement("br"));
let shuffle = document.createElement("input");
shuffle.type = "button";
shuffle.onclick = function() {
g.nodes.forEach(function(n) {
[n.x, n.y, n.vx, n.vy] = [Math.random()*c.width, Math.random()*c.height, 0, 0];
})
}
shuffle.value = "shuffle";
div.appendChild(shuffle);
let ctx = c.getContext("2d");
let n = -1;
edges.forEach(function(l) {
l.forEach(function(i) {
n = Math.max(i, n);
})
});
n++;
this.edges = edges;
this.nodes = [];
for (let i = 0; i < n; i++) {
this.nodes.push({
x: Math.random() * c.width,
y: Math.random() * c.height,
vx: 0,
vy: 0,
ax: 0,
ay: 0
});
}
this.width = c.width;
this.height = c.height;
this._update = function() {
if (!runSim.checked) {
return;
}
this.nodes.forEach(function(n) {
[n.ax, n.ay] = [0, 0];
})
update(this);
for (let i = 0; i < n; i++) {
let n = this.nodes[i];
n.vx += n.ax;
n.vy += n.ay;
n.x += n.vx;
n.y += n.vy;
if (n.x < 0) {
n.x = 0;
n.vx = Math.max(n.vx, -n.vx);
}
if (n.x > c.width-1) {
n.x = c.width-1;
n.vx = Math.min(n.vx, -n.vx);
}
if (n.y < 0) {
n.y = 0;
n.vy = Math.max(n.vy, -n.vy);
}
if (n.y > c.height-1) {
n.y = c.height-1;
n.vy = Math.min(n.vy, -n.vy);
}
}
};
this._draw = function() {
ctx.clearRect(0, 0, c.width, c.height);
for (let i = 0; i < n; i++) {
for (let j = i+1; j < n; j++) {
if (!this.edges[i].includes(j)) {
continue;
}
let [n, m] = [this.nodes[i], this.nodes[j]];
ctx.beginPath();
ctx.moveTo(n.x, n.y);
ctx.lineTo(m.x, m.y);
ctx.stroke();
}
}
for (let i = 0; i < n; i++) {
let n = this.nodes[i];
ctx.beginPath();
ctx.arc(n.x, n.y, 10, 0, 2*Math.PI);
ctx.fill();
}
};
window.setInterval(function() { g._update(); g._draw(); }, 33.33333);
return this;
};
<!DOCTYPE html>
<head>
<script src="fdg.js"></script>
</head>
<body>
<div id="graph"></div>
<script>
let graph = [
[1,2,3,7],
[0,4,5],
[0,5,6],
[0,4,10],
[1,3,8],
[1,2,8,9],
[2,7,9],
[0,6,10],
[4,5,10],
[5,6,10],
[3,7,8,9],
];
let g = new Graph(document.querySelector(".graph#electric"), graph, function(g) {
const springK = 0.001;
const drag = 0.1;
const electricK = 1000;
for (let i = 0; i < g.nodes.length; i++) {
let n = g.nodes[i];
g.edges[i].forEach(function(j) {
let m = g.nodes[j];
let [dx, dy] = [n.x-m.x, n.y-m.y];
n.ax -= springK * dx;
n.ay -= springK * dy;
});
for (let j = 0; j < g.nodes.length; j++) {
if (i == j) { continue; }
let m = g.nodes[j];
let [dx, dy] = [n.x-m.x, n.y-m.y];
let d = Math.sqrt(dx*dx+dy*dy);
if (d == 0) { continue; }
let F = electricK / d**3;
n.ax += dx * F;
n.ay += dy * F;
}
n.ax -= n.vx * drag;
n.ay -= n.vy * drag;
}
});
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment