Skip to content

Instantly share code, notes, and snippets.

@newsummit
Last active May 29, 2019 14:36
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 newsummit/23284230d6ee3c33b60a14d27ae3c9c6 to your computer and use it in GitHub Desktop.
Save newsummit/23284230d6ee3c33b60a14d27ae3c9c6 to your computer and use it in GitHub Desktop.
Bounding box force directed graph 3 (with metadata)
license: mit
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #d8d8d8;
stroke-opacity: 0.6;
stroke-width: 2;
stroke-dasharray: 10, 6;
}
.nodes circle {
fill: #00ceb9;
stroke: white ;
stroke-width: 5px;
}
.label__node {
font-family: sans-serif;
font-size: 19px;
stroke: #dadada;
opacity: .4;
background: white;
font-weight: bold;
}
.label__error {
font-family: sans-serif;
font-size: 14px;
fill: white;
font-weight: bold;
}
svg {
border: 1px solid #d8d8d8;
}
</style>
<svg width="1000" height="400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
const animate = true;
const nodeRadius = 23;
const data = {
nodes: [
{"name": "Gordon", "sex": "M"},
{"name": "Sylvester", "sex": "M"},
{"name": "Lillian", "sex": "F"},
{"name": "Mary", "sex": "F"},
{"name": "Helen", "sex": "F"},
{"name": "Jamie", "sex": "M"},
{"name": "Jessie", "sex": "F"},
{"name": "Ashton", "sex": "M"},
{"name": "Duncan", "sex": "M"},
{"name": "Evette", "sex": "F"}
],
links: [
{"source": "Sylvester", "target": "Lillian", "failures":"10" },
{"source": "Gordon", "target": "Lillian", "failures":"1290" },
{"source": "Lillian", "target": "Mary", "failures":"0"},
{"source": "Mary", "target": "Helen", "failures":"0"},
{"source": "Helen", "target": "Jessie", "failures":"1.8k"},
{"source": "Sylvester", "target": "Helen", "failures":"0"},
{"source": "Helen", "target": "Jamie", "failures":"0"},
{"source": "Jamie", "target": "Jessie", "failures":"2.1k"},
{"source": "Ashton", "target": "Jessie", "failures":"0"},
{"source": "Ashton", "target": "Jamie", "failures":"0"},
{"source": "Gordon", "target": "Jessie", "failures":"0"},
{"source": "Jessie", "target": "Duncan", "failures":"0"},
{"source": "Jessie", "target": "Evette", "failures":"0"}
]
};
// Set up the simulation
const strengthX = -0.08;
const strengthY = 0.017;
const simulation = d3.forceSimulation().nodes(data.nodes);
const forceX = d3.forceX(width/2).strength(strengthX);
const forceY = d3.forceY(height/2).strength(strengthY);
const chargeForce = d3.forceManyBody().strength(-1751);
const centerForce = d3.forceCenter(width / 2, height / 2);
const linkForce = d3.forceLink(data.links).id(d => d.name).distance(180).strength(1.656);
//const widthScale = d3.scaleLinear.domain([24,])
// Add positioning forces to the simulation
simulation
.force('xAxis', forceX)
.force('yAxis', forceY)
.force("charge_force", chargeForce)
.force("center_force", centerForce)
.force("links", linkForce);
//draw lines for the links
const link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(data.links)
.enter().append("line");
//draw circles for the nodes
const node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(data.nodes)
.enter().append("g");
const circles = node
.append("circle")
.attr("r", nodeRadius);
const labels = node.append("text")
.text(d => d.name)
.attr("class", "label__node")
.attr('x', d => 0 - nodeRadius - 4 - d.name.length/2)
.attr('y', 0 - nodeRadius - 8);
const statusBox = svg.append("g")
.attr("class", "boxes")
.selectAll("g")
.data(data.links)
.enter().append("g");
statusBox.append("rect")
.attr("x", -2).attr("y", -23)
.attr("rx", 5).attr("ry", 5)
.attr("width", d => {
return d.failures.length + 30 + Math.log(d.failures.length) * 13;
})
.attr("height", 30)
.style("fill", d => {
return d.failures === '0' ? '#e3e3e3' : '#f9507f';
})
.attr("class", "status-box");
statusBox.append("text")
.text(d => d.failures)
.attr("x", d => 9 + (Math.log(d.failures.length) * 0.2))
.attr("y", -3)
.attr("class", "label__error")
if (!animate) {
simulation.stop();
const min = Math.log(simulation.alphaMin());
const decay = Math.log(1 - simulation.alphaDecay());
const numTicks = Math.ceil(min / decay);
// Run the simulation enough times to position nodes
for (let i = 0; i < numTicks; ++i) {
simulation.tick();
};
// Position the nodes
tickActions()
} else {
simulation.on("tick", tickActions );
}
function tickActions() {
// constrains the nodes to be within a box
node.attr("transform", d => {
const x = Math.max(nodeRadius, Math.min(width, d.x));
const y = Math.max(nodeRadius, Math.min(height, d.y));
return "translate(" + x + "," + y + ")";
});
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
statusBox.attr("transform", d => {
const x = (d.target.x + d.source.x) / 2;
const y = (d.target.y + d.source.y) / 2;
return "translate(" + x + "," + y + ")";
});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment