Skip to content

Instantly share code, notes, and snippets.

@plmrry
Last active December 16, 2015 21:13
Show Gist options
  • Save plmrry/67157e92d61ebe1cff75 to your computer and use it in GitHub Desktop.
Save plmrry/67157e92d61ebe1cff75 to your computer and use it in GitHub Desktop.
Tree-to-Force Interpolation

Move the slider to interpolate between a tree layout and a force-directed layout.

Based on Jim Vallandingham's talk, Abusing The Force.

<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<meta charset="utf-8">
</head>
<body>
<script src="script.js"></script>
</body>
</html>
var width = window.innerWidth * 0.5,
height = window.innerHeight * 0.5,
margin = { top: 50, left: 50, bottom: 50, right: 50 };
var rangeScale = d3.scale.pow()
.exponent(10)
.clamp(true)
d3.select("body").append("input")
.attr({ type: "range", value: 0, min: 0, max: 1, step: 0.01 })
.on("input", function() {
mix = rangeScale(this.value);
force.start();
});
var g = d3.select("body")
.append("svg")
.attr({
width: width + margin.left + margin.right,
height: height + margin.top + margin.bottom
})
.append("g")
.attr({
transform: "translate(" + [margin.left, margin.top] + ")"
});
var hierarchyA = function() {
return {
name: "a",
children: [
{
name: "b",
children: [ {name: "d"}, {name: "e"} ]
},
{
name: "c",
children: [
{name: "f", children: [ {name:"t"}, {name:"u"} ] },
{name: "g"},
{name: "h"}
]
},
{
name: "k",
children: [
{name: "j", children: [
{name:"x", children: [{name:"y"}]}
]}
]
}
]
};
};
var hierarchyB = function() {
return {
name: "a",
children: [
{
name: "b",
children: [ {name: "d"}, {name: "e"} ]
},
{
name: "c",
children: [ {name: "f"}, {name: "g"} ]
},
{
name: "k",
children: [ {name: "j"} ]
},
{
name: "n",
children: [ {name: "m"}, {name: "q"} ]
}
]
};
};
var hierarchy = hierarchyA();
var force = d3.layout.force()
.size([ width, height ])
.charge(-100)
.linkDistance(40)
.on("tick", draw);
var tree = d3.layout.tree()
.size([ width, height ]);
var newNodes = tree(hierarchy);
var node = g.selectAll(".node");
var toggle = true;
function update() {
toggle = !toggle;
hierarchy = (toggle) ? hierarchyB() : hierarchyA();
var oldNodes = node.data();
var newNodes = tree(hierarchy)
.map(function(d) {
d.treeX = d.x; d.treeY = d.y;
var i = oldNodes.map(function(d) { return d.name; }).indexOf(d.name);
if (i == -1) {
d.x = width / 2 + Math.random();
d.y = height / 2 + Math.random();
return d;
}
d.x = oldNodes[i].x;
d.y = oldNodes[i].y;
return d;
});
node = g.selectAll(".node").data(newNodes, function(d) { return d.name; });
node.enter().append("g").classed("node", true)
.append("circle").attr({ r: 1 })
.style({ fill: "#999", stroke: "#444" })
.transition().duration(1000)
.attr({ r: 10 });
node.exit().remove();
node.call(force.drag);
var newLinks = tree.links(newNodes);
link = g.selectAll(".link").data(newLinks);
link.enter().insert("g", "g.node").classed("link", true)
.append("line").style({ stroke: "#444" });
link.exit().remove();
force.nodes(node.data()).links(link.data()).start();
}
update();
window.setInterval(update, 2000);
var mix = 0;
function draw(event) {
node.each(function(d) {
d.x += (d.treeX - d.x) * mix * event.alpha;
d.y += (d.treeY - d.y) * mix * event.alpha;
});
node.attr({
transform: function(d) { return "translate(" + d.x + "," + d.y + ")"; }
});
link.select("line")
.attr({
x1: function(d) { return d.source.x; },
x2: function(d) { return d.target.x; },
y1: function(d) { return d.source.y; },
y2: function(d) { return d.target.y; }
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment