Published
Edited
Mar 21, 2019
Insert cell
Insert cell
Insert cell
ba_sim = function(initial_nodes, new_connections = 1) {
var height = 700

var m = new_connections

// create svg
var svg = d3.select(DOM.svg(width, height))

// create g elements for edges and nodes
var edgesG = svg.append("g")
var nodesG = svg.append("g")

// initial graph: one node without connections
var nodesD = [{"id": 1, "weight": 0}]
var nodesWeighted = [1]
var newNode = 1; // ids of existing nodes
var edgesD = []


// initialise variable to store max degree measured
var len = 0;

// scale area of nodes to their degree
var rScale = d3.scalePow()
.exponent(0.5)
.domain([0,10])
.range([1,15])

for (var i = 0; i < initial_nodes; i++) {
everyInterval(false)
}
// initialise simulation
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()
}
Insert cell
html`
<style>
.edge {
stroke: #bbb;
stroke-opacity: 1;
stroke-width: 1px;
}

.node {
stroke: #fff;
stroke-width: 1.5px;
fill: #8395ad;
}
</style>
`
Insert cell
d3 = require("d3@5")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more