Skip to content

Instantly share code, notes, and snippets.

@christianbriggs
Created June 4, 2017 14:51
Show Gist options
  • Save christianbriggs/3f6cb5d44025e0107aa60d6d363efb41 to your computer and use it in GitHub Desktop.
Save christianbriggs/3f6cb5d44025e0107aa60d6d363efb41 to your computer and use it in GitHub Desktop.
force directed text fit labels
license: gpl-3.0
name parent id data text
a 1 200 total budget
p 1 2 70 operations
q 1 3 30 finance
p1 2 4 4 text
p2 2 5 10 text
p3 2 6 13 text
p4 2 7 1 text
p5 2 8 7 text
p6 2 9 7 text
p7 2 10 7 text
p8 2 11 7 text
p9 2 12 7 text
p10 2 13 7 text
q1 3 14 8 text
q2 3 15 7 text
q3 3 16 8 text
q4 3 17 7 text
r 1 18 10 other
s 1 19 90 policy
r1 18 20 3 text
r2 18 21 3 text
r3 18 22 2 text
r4 18 23 2 text
s1 19 24 30 text
s2 19 25 20 text
s3 19 26 10 text
s4 19 27 10 text
s5 19 28 10 text
s6 19 29 10 text
s7 19 30 30 hello hello
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="
https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
line {stroke: black}
text {
font: 24px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-anchor: middle;
pointer-events: none;
}
</style>
</head>
<body>
<script>
var height = 500
var width = 500
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + 0 + "," + 0 + ")");
var circle_size = 4;
var colour_scale = d3.scaleOrdinal(d3.schemeCategory10);
function flatten(root) {
var nodes = [],
i = 0;
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
function add_label_leaves(csv_data, root) {
function recurse(tree) {
if (!(tree.children)) {
//add to csv data with correct parent id and uniform size.
new_record = {}
new_record["name"] = tree.data.name + "_label"
new_record["parent"] = tree.data.id
new_record["id"] = tree.data.id + "_label"
new_record["data"] = 20
new_record["text"] = tree.data.text
csv_data.push(new_record)
} else {
_.each(tree.children, function(this_tree) {
recurse(this_tree)
})
}
}
recurse(root)
}
d3.csv("data2.csv", function(csv_data) {
var root_fn = d3.stratify()
.id(function(d) {
return d.id;
})
.parentId(function(d) {
return d.parent;
})
var root = root_fn(csv_data);
//Recurse into root's children, and when we find
var links = root.links()
var nodes = flatten(root)
add_label_leaves(csv_data, root)
var root = root_fn(csv_data)
var links = root.links()
var nodes = flatten(root)
var simulation = d3.forceSimulation(nodes)
.force("link",
d3.forceLink(links)
.distance(function(d) {
//We want the distance to be equal so they are spaced in a circle around the parent
return Math.pow(d.source.data.data, 0.5) * circle_size*1.5;
})
.strength(function(d) {
//Strength just needs to be enough so that length is uniform
return 0.1
})
)
.force("charge", d3.forceManyBody()
.strength(function(d) {
var force = -Math.pow(d.data.data,0.5)*circle_size;
return force})
.distanceMin(0)
.distanceMax(200)
)
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collide", d3.forceCollide(function(d) {return Math.pow(d.data.data,0.5)*circle_size}))
.velocityDecay(0.05)
.alphaMin(0.0001)
.alphaDecay(0.01)
.on("tick", ticked)
function ticked() {
var selection = svg.selectAll(".my_links")
.data(links)
selection.enter()
.append("line")
.attr("class", "my_links")
.merge(selection)
.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;
})
.attr("stroke-width", function(d) {
if (d.target.children) {
return 1
} else {
return 0
}
})
selection.exit().remove();
// Update the nodes…
selection = svg.selectAll(".my_nodes")
.data(nodes, function(d) {
return d.id;
})
//Entering
enterSelection = selection
.enter()
.append("g")
.attr("class", "my_nodes")
circles = enterSelection.append("circle")
rectangles = enterSelection.append("text")
.text(function(d) { return d.data.text; })
.style("font-size", function(d) {
var r = Math.pow(d.data.data, 0.5)*circle_size
return Math.min(2 * r, (2 * r - 8) / this.getComputedTextLength() * 24) + "px"; })
.attr("dy", ".35em")
.style("fill", function(d) {
if (d.children) {
return "white"
} else {
return "black"
}
})
//Update
enterSelection.merge(selection)
.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")" });
enterSelection.merge(selection).select("circle")
.attr("r", function(d) {
return Math.pow(d.data.data, 0.5)*circle_size;
})
.attr("fill", function(d,i) {
if (d.children) {
return colour_scale(i)
} else {
return "white"
}
})
}
});
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment