Based on Mike Bostock's force-directed graph example showing character co-occurence in Les Misérables, this gist shows how to toggle between the animated force-directed graph and a circle formation akin to a chord diagram.
forked from bricof's block: Force Directed Graph Toggle to Circle
xxxxxxxxxx
<meta charset="utf-8">
<style>
#controls {
position: absolute;
width: 940px;
padding: 10px;
z-index: 1;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
}
</style>
<body>
<div id="controls">
<input name="mode" type="radio" value="force" id="mode-force" checked>
<label for="mode-force"> Force-Directed</label>
<input name="mode" type="radio" value="circle" id="mode-circle">
<label for="mode-circle"> Circle</label>
</div>
<div id="graph"></div>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500,
margin = 100;
var x_center = width / 2,
y_center = height / 2,
radius = (height - 2 * margin) / 2;
var n_elements;
function index_to_rad(index) {
return 2 * Math.PI * index / n_elements;
}
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var x_scale = d3.scale.linear()
.domain([0,1])
.range([x_center, x_center + radius]);
var y_scale = d3.scale.linear()
.domain([0,1])
.range([y_center, y_center + radius]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("miserables.json", function(error, graph) {
if (error) throw error;
graph.nodes = graph.nodes.sort(function(a, b) { return d3.ascending(a.group, b.group); });
n_elements = graph.nodes.length;
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var max_value = d3.max(graph.links, function(d){ return d.value; });
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("opacity", function(d) { return 0.2 + 0.8 * d.value / max_value; });
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.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; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
d3.selectAll("#controls input[name=mode]").on("change", function(){
if (this.value == 'circle') {
force.stop();
var circles = svg.selectAll('circle')[0];
svg.selectAll('circle').data().forEach(function(d,i){
d.x_resume = circles[i].cx.animVal.value;
d.y_resume = circles[i].cy.animVal.value
});
svg.selectAll('line')
.transition().duration(1000)
.attr('x1', function(d){ return x_scale(Math.sin(index_to_rad(d.source.index))); })
.attr('x2', function(d){ return x_scale(Math.sin(index_to_rad(d.target.index))); })
.attr('y1', function(d){ return y_scale(Math.cos(index_to_rad(d.source.index))); })
.attr('y2', function(d){ return y_scale(Math.cos(index_to_rad(d.target.index))); });
svg.selectAll('circle')
.transition().duration(1000)
.attr('cx', function(d,i){ return x_scale(Math.sin(index_to_rad(i))); })
.attr('cy', function(d,i){ return y_scale(Math.cos(index_to_rad(i))); });
} else {
svg.selectAll('line')
.transition().duration(1000)
.attr('x1', function(d){ return d.source.x_resume; })
.attr('y1', function(d){ return d.source.y_resume; })
.attr('x2', function(d){ return d.target.x_resume; })
.attr('y2', function(d){ return d.target.y_resume; });
svg.selectAll('circle')
.transition().duration(1000)
.attr('cx', function(d){ return d.x_resume; })
.attr('cy', function(d){ return d.y_resume; });
setTimeout(function(){
force.resume();
},1000);
}
});
});
</script>
https://d3js.org/d3.v3.min.js