D3 force simulation on canvas with colour, stroke etc filtering for the nodes.
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<script>
var width = 960;
var height = 600;
// append canvas
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
//create random data.
var data = d3.range(500).map(function(d) {
return {
"fill": d % 3 ? "steelblue" : "ccc",
"stroke": d % 2 ? "#fff" : "#3a3a3a",
"radius": d % 4 + 2,
"alpha": Math.random(),
"direction": d % 2,
}
});
var charge = -10;
var nodePadding = 2.5;
var simulation;
function isolate(force, filter) {
var initialize = force.initialize;
force.initialize = function() { initialize.call(force, data.filter(filter));};
return force;
}
function restartSimulation(newSim) {
if (simulation)
simulation.stop();
if (newSim != undefined) {
simulation = newSim;
}
simulation.nodes(data);
simulation.restart();
};
function drawNode(d, fillFilter, alphaFilter, radiusFilter, strokeFilter) {
context.beginPath();
context.globalAlpha = alphaFilter(d);
context.moveTo(d.x + 3, d.y);
context.arc(d.x, d.y, radiusFilter(d), 0, 2 * Math.PI);
context.fillStyle = fillFilter(d);
context.strokeStyle = strokeFilter(d);
context.stroke();
context.closePath();
context.fill();
context.globalAlpha = 1;
}
var ticked = function(fillFilter, alphaFilter, radiusFilter, strokeFilter) {
context.clearRect(0, 0, width, height);
context.save();
data.forEach(function(d) {
drawNode(d, fillFilter, alphaFilter, radiusFilter, strokeFilter)
});
context.restore();
};
// default filters
var radiusFilter = function(d) {
return 2;
}
var fillFilter = function(d) {
return "#ccc";
}
var strokeFilter = function(d) {
return "#fff";
}
var alphaFilter = function(d) {
return 1;
}
function initial() {
var firstSim = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(charge))
.force("x", d3.forceX(width/2))
.force("y", d3.forceY(height/2))
.on("tick", function(e) {
ticked(fillFilter, alphaFilter, radiusFilter, strokeFilter);
})
.nodes(data);
restartSimulation(firstSim);
}
function filtered() {
var radiusFilter = function(d) {
return d.radius;
}
var fillFilter = function(d) {
return d.fill;
}
var strokeFilter = function(d) {
return d.stroke;
}
var alphaFilter = function(d) {
return d.alpha;
}
var newSim = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(charge))
.force("y", d3.forceY(height/2))
.force("collide", d3.forceCollide().strength(.5).radius(function(d){ d.radius + nodePadding; }).iterations(1))
.force("left", isolate(d3.forceX(width/2.5), function(d) { return d.direction; }))
.force("right", isolate(d3.forceX(1.5 * width / 2.5), function(d) { return !d.direction; }))
.on("tick", function(e) {
ticked(fillFilter, alphaFilter, radiusFilter, strokeFilter);
})
.nodes(data);
restartSimulation(newSim);
}
var alternate = [initial, filtered];
var iteration = 1;
initial();
setInterval(function(d) {
alternate[iteration % 2]();
iteration += 1;
}, 5000)
</script>
</body>
https://d3js.org/d3.v4.min.js