ba_sim = function(initial_nodes, new_connections = 1) {
var height = 700
var m = new_connections
var svg = d3.select(DOM.svg(width, height))
var edgesG = svg.append("g")
var nodesG = svg.append("g")
var nodesD = [{"id": 1, "weight": 0}]
var nodesWeighted = [1]
var newNode = 1;
var edgesD = []
var len = 0;
var rScale = d3.scalePow()
.exponent(0.5)
.domain([0,10])
.range([1,15])
for (var i = 0; i < initial_nodes; i++) {
everyInterval(false)
}
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-20))
simulation
.nodes(nodesD)
.on("tick", ticked);
simulation.force("link")
.links(edgesD);
// this will create the initial display, afterwards, it will automatically add a new node every 2 seconds and update()
update()
// add a new node every two seconds
var twoSeconds = d3.interval(everyInterval, 1000);
function everyInterval (upd = true) {
newNode += 1;
nodesD.push({"id": newNode, "weight": m}); // add new node
for (var k=0; k<m; k++) {
var tgt = chooseTarget(newNode-1)
edgesD.push({source: newNode, target: tgt}); // add new link
nodesWeighted.push(newNode, tgt) // add nodes to weighted list because they each have one more link now
nodesD[tgt-1].weight += 1
}
if (upd) { update() }
}
function update() {
// update nodes and edges with the updated dataset, restart the simulation
nodesG.selectAll(".node")
.data(nodesD)
.enter()
.append("circle")
.attr("class", "node")
.attr("r", function(d) {return rScale(d.weight)})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
d3.selectAll(".node").transition().attr("r", function(d) {return rScale(d.weight)})
edgesG.selectAll(".edge")
.data(edgesD)
.enter()
.append("line")
.attr("class", "edge")
// restart force layout
simulation.nodes(nodesD);
simulation.force("link").links(edgesD);
simulation.alpha(1).restart();
}
function ticked() {
// assign updated positions to nodes and edges
edgesG.selectAll(".edge")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
nodesG.selectAll(".node")
.attr("cx", function(d) { return d.x = Math.max(rScale(d.weight), Math.min(width - rScale(d.weight), d.x)); }) //
.attr("cy", function(d) { return d.y = Math.max(rScale(d.weight), Math.min(height - rScale(d.weight), d.y)); });
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function chooseTarget() {
// choose a target for an incoming node
var chosen = nodesWeighted[Math.floor((Math.random() * nodesWeighted.length))];
return chosen
}
return svg.node()
}