Built with blockbuilder.org
forked from tomshanley's block: Force with links
license: mit |
Built with blockbuilder.org
forked from tomshanley's block: Force with links
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { margin:0;top:0;right:0;bottom:0;left:0; } | |
path { stroke: black; stroke-width: 1px } | |
</style> | |
</head> | |
<body> | |
<script> | |
var nodes = [ | |
{ "type": "country", "name": "A", "value": 15 }, | |
{ "type": "country", "name": "B", "value": 5 }, | |
{ "type": "country", "name": "C", "value": 25 }, | |
{ "type": "club", "name": "D", "value": 50 }, | |
{ "type": "club", "name": "E", "value": 10 } | |
] | |
var links = [ | |
{ "source": "A", "target": "D", "value": 1 }, | |
{ "source": "A", "target": "E", "value": 15 }, | |
{ "source": "B", "target": "D", "value": 5 }, | |
{ "source": "B", "target": "E", "value": 50 }, | |
{ "source": "C", "target": "D", "value": 15 }, | |
] | |
let maxRValue = d3.max(nodes, (d) => d.value) | |
let maxLinkValue = d3.max(links, (d) => d.value) | |
var w = 800, h = 800, r = 350 | |
const radians = 0.0174532925 | |
let linkScale = d3.scaleLinear() | |
.domain([0, maxLinkValue]) | |
.range([0, r]) | |
let rScale = d3.scaleSqrt() | |
.domain([0, maxRValue]) | |
.range([10, 75]) | |
var numberOfCountries = 3 | |
//fix the circles to the radius | |
nodes.forEach(function(node, i){ | |
if (node.type == "country") { | |
let a = (360/numberOfCountries) * i | |
node.fx = w/2 + x(a, r) | |
node.fy = h/2 + y(a, r) | |
} | |
}) | |
var g = d3.select("body").append("svg") | |
.attr("width", w) | |
.attr("height", h) | |
.append("g") | |
g.append("circle") | |
.attr("cx", w/2) | |
.attr("cy", h/2) | |
.attr("r", r) | |
.style("fill", "none") | |
.style("stroke", "grey") | |
var simulation = d3.forceSimulation() | |
.force("link", d3.forceLink().id(function(d) { return d.name; })) | |
var link = g.append("g") | |
.attr("class", "links") | |
.selectAll("path") | |
.data(links) | |
.enter() | |
.append("path"); | |
var node = g.append("g") | |
.attr("class", "nodes") | |
.selectAll("circle") | |
.data(nodes) | |
.enter() | |
.append("g") | |
node.append("circle") | |
.attr("r", (d) => rScale(d.value)) | |
.style("fill", function(d){ | |
return d.type == "country" ? "PaleVioletRed" : "MediumSeaGreen" | |
}) | |
.style("stroke", "white") | |
.style("stroke-width", 3) | |
node.append("text") | |
.text((d) => d.name) | |
.style("text-anchor", "middle") | |
.attr("dy", "0.35em") | |
.style("stroke", "white") | |
.style("stroke-width", 3) | |
node.append("text") | |
.text((d) => d.name) | |
.style("text-anchor", "middle") | |
.attr("dy", "0.35em") | |
simulation | |
.nodes(nodes) | |
.on("tick", ticked) | |
simulation.force("link") | |
.links(links) | |
.distance((d) => linkScale(d.value)) | |
.strength(0.5) | |
function ticked() { | |
link.attr("d", function(d) { | |
return "M" + d.source.x + " " + d.source.y | |
+ " L" + d.target.x + " " + d.target.y; | |
}) | |
node.attr("transform", function(d) { | |
return "translate(" + d.x + "," + d.y + ")" | |
}) | |
} | |
function x (angle, radius) { | |
// rotate 90 | |
let a = 90 - angle | |
return radius * Math.sin(a * radians) | |
} | |
function y (angle, radius) { | |
// rotate 90 | |
let a = 90 - angle | |
return radius * Math.cos(a * radians) | |
} | |
</script> | |
</body> |