Skip to content

Instantly share code, notes, and snippets.

@Phaiax
Created December 14, 2019 10:39
Show Gist options
  • Save Phaiax/c0af54d2cb8615614c188d80ca02ef39 to your computer and use it in GitHub Desktop.
Save Phaiax/c0af54d2cb8615614c188d80ca02ef39 to your computer and use it in GitHub Desktop.
d4
<html>
<head>
<style>
canvas {
margin: 10 auto;
border: 1px solid gray;
display:block;
}
div#info {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 50px;
background: #44444444;
/*opacity: 0.5;*/
}
</style>
</head>
<body>
<canvas id="canvas" width="1200" height="900"></canvas>
<div id="info"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-collection.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-quadtree.v1.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<script src="https://d3js.org/d3-force.v1.min.js"></script>
<script>
function generateData() {
var abcdes = [];
for (var i = 0; i < 1000; i++) {
id = "A-" + i;
abcdes.push({"id": id,
"label": id,
"state": "A",
});
}
var fghijs = [];
for (var i = 0; i < 1600; i++) {
id = "5065" + (''+i).padStart(4, '0');
fghijs.push({"id": id,
"label": id,
"state": (i%3==0)? "B" : "C",
});
}
var links = [];
var curr_abcde_idx = 0;
var curr_fghij_idx = 0;
while (true) {
var subbox_abcde_num = Math.floor(Math.random() * abcdes.length / 50);
var subbox_fghij_num = Math.floor(Math.random() * fghijs.length / 50);
if ( subbox_abcde_num + curr_abcde_idx >= abcdes.length
|| subbox_fghij_num + curr_fghij_idx >= fghijs.length) {
// remaining
subbox_abcde_num = abcdes.length - curr_abcde_idx;
subbox_fghij_num = fghijs.length - curr_fghij_idx;
}
var num_links = Math.floor(subbox_abcde_num * subbox_fghij_num
* (Math.random() / 2 + 0.25));
console.log(subbox_abcde_num, subbox_fghij_num, num_links);
for (var i = 0; i < num_links; i++) {
var abcde_idx = curr_abcde_idx + Math.floor(Math.random() * subbox_abcde_num);
var fghij_idx = curr_fghij_idx + Math.floor(Math.random() * subbox_fghij_num);
links.push({
"source": abcdes[abcde_idx].id,
"target": fghijs[fghij_idx].id,
"state": (Math.random() > 0.7) ? "D" : "E",
//"defghijion": "Link " + i
})
}
curr_abcde_idx = Math.min(abcdes.length, curr_abcde_idx + subbox_abcde_num);
curr_fghij_idx = Math.min(fghijs.length, curr_fghij_idx + subbox_fghij_num);
if (curr_abcde_idx >= abcdes.length || curr_fghij_idx >= fghijs.length) {
break;
}
}
// add a few random links
for (var i = 0; i < 6; i++) {
//continue;
var abcde_idx = Math.floor(Math.random() * abcdes.length);
var fghij_idx = Math.floor(Math.random() * fghijs.length);
links.push({
"source": abcdes[abcde_idx].id,
"target": fghijs[fghij_idx].id,
"state": (Math.random() > 0.7) ? "D" : "E",
//"defghijion": "Link " + i,
})
}
return [abcdes.concat(fghijs), links]
}
function makeGraph(canvas, nodes, links, setInfo) {
var graphctx = {
sim : null,
nodes : nodes,
links : links,
zoomTransform: d3.zoomIdentity,
};
var context = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
function drawLink(link) {
context.moveTo(link.source.x, link.source.y);
context.lineTo(link.target.x, link.target.y);
}
function drawLinks(state, color) {
context.beginPath();
context.strokeStyle = color;
graphctx.links
.filter(link => link.state == state)
.forEach(drawLink);
context.stroke();
}
function drawNode(node) {
context.moveTo(node.x + 3, node.y);
context.arc(node.x, node.y, 3, 0, 2 * Math.PI);
}
function drawNodes(state, color) {
context.beginPath();
context.fillStyle = color;
graphctx.nodes
.filter(node => node.state == state)
.forEach(drawNode);
context.fill();
//context.strokeStyle = node.color;
//context.stroke();
}
function ticked() {
context.save();
context.clearRect(0, 0, width, height);
context.translate(graphctx.zoomTransform.x, graphctx.zoomTransform.y);
context.scale(graphctx.zoomTransform.k, graphctx.zoomTransform.k);
drawLinks("D", "#00FF00");
drawLinks("E", "#444444");
drawNodes("A", "#00FF00");
drawNodes("B", "#FF00FF");
drawNodes("C", "#444444");
context.beginPath();
context.strokeStyle = "#000000";
context.moveTo(-10, 0);
context.lineTo(10, 0);
context.moveTo(0, -10);
context.lineTo(0, 10);
context.stroke();
context.restore();
}
var sim = d3.forceSimulation()
.nodes(graphctx.nodes)
.on("tick.mytick", ticked)
.stop();
var steps=600;
sim.alphaDecay(1 - Math.pow(sim.alphaMin(), 1 / steps));
sim.velocityDecay(0.5);
sim.force("linkforce",
d3.forceLink(graphctx.links)
// allow target and source to be ids:
.id( function(node) { return node.id; } )
.distance(function (link) { return 30; })
//.strength(function strength(link) {
// count(node) returns number of connections
//return 1 / Math.min(count(link.source), count(link.target));
//return 0.1;
//})
);
sim.force("center",
d3.forceCenter()
.x(width/2)
.y(height/2)
);
sim.force("charge",
d3.forceManyBody()
.strength(function strength() {
return -10;
})
//.distanceMin()
//.distanceMax(100)
);
sim.force("gravity",
d3.forceManyBody()
.strength(function strength() {
return 10;
})
.distanceMin(600)
//.distanceMax()
);
function getNearest(x, y) {
return sim.find(graphctx.zoomTransform.invertX(x),
graphctx.zoomTransform.invertY(y),
30);
}
function dragstarted() {
if (!d3.event.active) sim.alphaTarget(0.3).restart();
//console.log("start subject xy", d3.event.subject.x, d3.event.subject.y, "event xy", //d3.event.x, d3.event.y);
d3.event.subject.drag_start_x = d3.event.x;
d3.event.subject.drag_start_y = d3.event.y;
d3.event.subject.fx = d3.event.subject.x;
d3.event.subject.fy = d3.event.subject.y;
}
function dragged() {
d3.event.subject.fx = d3.event.subject.drag_start_x
+ (d3.event.x - d3.event.subject.drag_start_x) / graphctx.zoomTransform.k;
d3.event.subject.fy = d3.event.subject.drag_start_y
+ (d3.event.y - d3.event.subject.drag_start_y) / graphctx.zoomTransform.k;
//console.log("event", d3.event.x, d3.event.y, "node", d3.event.subject.fx, d3.event.subject.fy, "transform", graphctx.zoomTransform.x, graphctx.zoomTransform.y, graphctx.zoomTransform.k);
}
function dragended() {
if (!d3.event.active) sim.alphaTarget(0);
d3.event.subject.fx = null;
d3.event.subject.fy = null;
}
function dragsubject() {
return getNearest(d3.event.x, d3.event.y)
}
d3.select(canvas)
.call(d3.drag()
.container(canvas)
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
function zoomed(transform) {
graphctx.zoomTransform = transform;
}
d3.select(canvas).call(d3.zoom()
.scaleExtent([1 / 8, 4])
.on("zoom", () => zoomed(d3.event.transform))
//.on("wheel.zoom", pan)
);
d3.select(canvas).on("mousemove.my", function() {
var node = getNearest(d3.event.x, d3.event.y);
if (node != null) {
setInfo(node);
}
});
graphctx.sim = sim;
return graphctx;
}
function restart() {
var [nodesArray, linksArray] = generateData();
var canvas = document.querySelector("canvas#canvas");
var infodiv = document.querySelector("#info");
var setInfo = function(item) {
info.textContent = item.id;
};
var graphctx = makeGraph(canvas, nodesArray, linksArray, setInfo);
graphctx.sim.restart();
return graphctx;
}
graphctx = restart();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment