A group-in-a-box layout showing interactions between Les Miserables characters, where the characters are grouped by their communities (determined using the Louvain modularity community detection algorithm). Within each group, nodes are positioned using a force-directed layout.
In contrast with a pure force-directed layout, group-in-a-box layouts can clearly show connections within and between clusters. Matrix diagrams can also be used for this purpose, but group-in-a-box can be more effective for showing connections within and between categories of nodes that do not form clear clusters.
forked from rpgove's block: Group-in-a-box layout
xxxxxxxxxx
<meta charset="utf-8">
<style>
rect.cell {
fill: none;
stroke: #ddd;
stroke-width: 2px;
}
.links line {
stroke: #999;
stroke-opacity: 0.7;
}
.nodes circle {
fill: #d62333;
stroke: #fff;
stroke-width: 2px;
}
</style>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="forceInABox.js"></script>
<script>
var width = 600;
var height = 300;
var nodeRadius = d3.scaleSqrt().range([4, 10]);
var linkWidth = d3.scaleLinear().range([1, 2 * nodeRadius.range()[0]]);
var drag = d3.drag()
.on('start', dragStart)
.on('drag', dragging)
.on('end', dragEnd);
var svg = d3.select('svg')
.attr('width', width + 2)
.attr('height', height + 2)
.append('g')
.attr('transform', 'translate(1,1)');
var groupingForce = forceInABox()
.strength(0.1)
.template('treemap')
.groupBy('community')
.size([width, height]);
var forceSim = d3.forceSimulation()
.force('link', d3.forceLink()
.id(function(d) { return d.id; })
.distance(50)
.strength(groupingForce.getLinkStrength)
)
.force('group', groupingForce)
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(width/2, height/2))
.force('x', d3.forceX(width/2).strength(0.02))
.force('y', d3.forceY(height/2).strength(0.04));
d3.json('jean.json', function (error, graph) {
if (error) throw error;
// Make sure small nodes are drawn on top of larger nodes
graph.nodes.sort(function (a, b) { return b.chapters.length - a.chapters.length; });
nodeRadius.domain([graph.nodes[graph.nodes.length-1].chapters.length, graph.nodes[0].chapters.length]);
linkWidth.domain(d3.extent(graph.links, function (d) { return d.chapters.length; }));
forceSim.nodes(graph.nodes)
.on('tick', tick);
forceSim.force('link')
.links(graph.links);
groupingForce.links(graph.links)
.drawTreemap(svg);
var link = svg.append('g')
.attr('class', 'links')
.selectAll('line')
.data(graph.links)
.enter().append('line')
.attr('stroke-width', function (d) { return linkWidth(d.chapters.length); });
var node = svg.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(graph.nodes)
.enter().append('circle')
.attr('r', function (d) { return nodeRadius(d.chapters.length); })
.call(drag);
node.append('title').text(function (d) { return d.name; });
function tick () {
link
.attr('x1', function (d) { return d.source.x; })
.attr('x2', function (d) { return d.target.x; })
.attr('y1', function (d) { return d.source.y; })
.attr('y2', function (d) { return d.target.y; });
node
.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
}
});
function dragStart (d) {
if (!d3.event.active) forceSim.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragging (d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragEnd (d) {
if (!d3.event.active) forceSim.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
https://d3js.org/d3.v4.min.js