An arc diagram of Les Miserables characters ordered by the first chapter they appear in. Arcs represent characters that interact with each other. This can be seen as a 1D semantic substrate.
Arc diagrams can be useful for examining relationships between an ordered variable, like chapters in a book, and the connections between nodes. However, because of limits in screen size, arc diagrams are often limited to graphs with a few hundred nodes at most. It can also be difficult to identify high-level patterns in connections and clusters, depending on the sort order.
xxxxxxxxxx
<meta charset="utf-8">
<style>
.links path {
fill: none;
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
fill: #d62333;
stroke: #fff;
stroke-width: 1px;
}
</style>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var i, j, node;
var groupSep = 10;
var nodeRadius = d3.scaleSqrt().range([3, 7]);
var linkWidth = d3.scaleLinear().range([1.5, 2 * nodeRadius.range()[0]]);
var margin = {
top: nodeRadius.range()[1] + 1,
right: nodeRadius.range()[1] + 1,
bottom: nodeRadius.range()[1] + 1,
left: nodeRadius.range()[1] + 1
};
var width = 960 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var svg = d3.select('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
d3.json('jean.json', function (error, graph) {
if (error) throw error;
var idToNode = {};
graph.nodes.forEach(function (n) {
idToNode[n.id] = n;
});
graph.links.forEach(function (e) {
e.source = idToNode[e.source];
e.target = idToNode[e.target];
});
// Sort nodes by first appearance (volume, book, chapter)
graph.nodes.forEach(function (n) {
n.chapters = n.chapters.map(function (chaps) { return chaps.split('.').map(function (c) { return parseInt(c); }); });
n.chapters.sort(chapterCompare).reverse();
n.firstChapter = n.chapters[0];
});
graph.nodes.sort(function (a, b) {
return chapterCompare(a.firstChapter, b.firstChapter);
}).reverse();
// Compute x,y coordinates (have a little extra separation when we switch volumes)
for (i = 0, j = 0; i < graph.nodes.length; ++i) {
node = graph.nodes[i];
if (i > 0 && graph.nodes[i-1].firstChapter[0] != node.firstChapter[0]) ++j;
node.x = j * groupSep + i * (width - 4 * groupSep) / (graph.nodes.length - 1);
node.y = height;
}
nodeRadius.domain(d3.extent(graph.nodes, function (d) { return d.chapters.length; }));
linkWidth.domain(d3.extent(graph.links, function (d) { return d.chapters.length; }));
var link = svg.append('g')
.attr('class', 'links')
.selectAll('path')
.data(graph.links)
.enter().append('path')
.attr('d', function (d) {
return ['M', d.source.x, height, 'A',
(d.source.x - d.target.x)/2, ',',
(d.source.x - d.target.x)/2, 0, 0, ',',
d.source.x < d.target.x ? 1 : 0, d.target.x, ',', height]
.join(' ');
})
.attr('stroke-width', function (d) { return linkWidth(d.chapters.length); })
.on('mouseover', function (d) {
link.style('stroke', null);
d3.select(this).style('stroke', '#d62333');
node.style('fill', function (node_d) {
return node_d === d.source || node_d === d.target ? 'black' : null;
});
})
.on('mouseout', function (d) {
link.style('stroke', null);
node.style('fill', null);
});
var node = svg.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(graph.nodes)
.enter().append('circle')
.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; })
.attr('r', function (d) { return nodeRadius(d.chapters.length); })
.on('mouseover', function (d) {
node.style('fill', null);
d3.select(this).style('fill', 'black');
var nodesToHighlight = graph.links.map(function (e) { return e.source === d ? e.target : e.target === d ? e.source : 0})
.filter(function (d) { return d; });
node.filter(function (d) { return nodesToHighlight.indexOf(d) >= 0; })
.style('fill', '#555');
link.style('stroke', function (link_d) {
return link_d.source === d | link_d.target === d ? '#d62333' : null;
});
})
.on('mouseout', function (d) {
node.style('fill', null);
link.style('stroke', null);
});
node.append('title').text(function (d) { return d.name + ' - ' + d.firstChapter.join('.'); });
function chapterCompare (aChaps, bChaps) {
if (aChaps[0] != bChaps[0])
return bChaps[0] - aChaps[0];
else if (aChaps[1] != bChaps[0])
return bChaps[1] - aChaps[1];
else if (aChaps[2] != bChaps[2])
return bChaps[2] - aChaps[2];
return 0;
}
});
</script>
https://d3js.org/d3.v4.min.js