forked from mbostock's block: Chord Diagram
forked from kafunk's block: Chord Diagram d3.v5 with self-wrapping text
xxxxxxxxxx
<head>
<meta charset="utf-8">
<style>
body { position:absolute; top:0; bottom:0; right:0; left:0; }
text { font-size:8px; font-family:monospace; }
.chord { fill-opacity:0.4; }
#title { font-size: 24px; opacity:0.6;}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script>
// DIMENSIONS
let height = 860,
width = 1080;
var outerRadius = height / 2,
innerRadius = outerRadius - 148;
// SVG
var svg = d3.select("body").append("svg")
.attr("height", height)
.attr("width", width)
.append("g")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
// WRAPPED TITLE
let title = svg.append("text")
.attr("id", "title")
.attr("x", -outerRadius + 26)
.attr("y", -outerRadius)
wrap("flare class dependencies", (width/4), title);
// COLORS
var fill = d3.scaleOrdinal(d3.schemeDark2);
// FUNCTION EXPRESSIONS
var chord = d3.chord()
.padAngle(.06)
.sortSubgroups(d3.descending)
.sortChords(d3.descending);
var ribbon = d3.ribbon()
.radius(innerRadius)
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(innerRadius + 20);
// PULL IN DATA
d3.json("origdata.json").then(draw,logErr);
// MAIN FUNCTION
function draw(data) {
var indexByName = d3.map(),
nameByIndex = d3.map(),
matrix = [],
n = 0;
// Returns the Flare package name for the given class name.
function name(name) {
return name.substring(0, name.lastIndexOf(".")).substring(6);
}
// Compute a unique index for each package name.
data.forEach(function(d) {
if (!indexByName.has(d = name(d.name))) {
nameByIndex.set(n, d);
indexByName.set(d, n++);
}
});
// Construct a square matrix counting package imports.
data.forEach(function(d) {
var source = indexByName.get(name(d.name)),
row = matrix[source];
if (!row) {
row = matrix[source] = [];
for (var i = -1; ++i < n;) row[i] = 0;
}
d.imports.forEach(function(d) { row[indexByName.get(name(d))]++; });
});
// CHORD GROUPS => ARCS
let groups = svg.selectAll("g.group")
.data(chord(matrix).groups)
.enter().append("g")
.classed("group", true);
// Arcs
groups.append("path")
.style("fill", d => { return fill(d.index); })
.style("stroke", d => { return fill(d.index); })
.attr("d", arc);
// Self-wrapping arc labels
groups.append("text")
.each(d => { d.angle = (d.startAngle + d.endAngle) / 2; })
.attr("dy", ".35em")
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" + "translate(" + (innerRadius + 26) + ")" + (d.angle > Math.PI ? "rotate(180)" : "");
})
.style("text-anchor", d => { return d.angle > Math.PI ? "end" : null; })
.each(d => { wrap(nameByIndex.get(d.index), innerRadius/3, d3.select(groups.selectAll("text").nodes()[d.index])); })
// .text(d => { return nameByIndex.get(d.index) })
// CHORDS => RIBBONS
let chords = svg.selectAll("path.chord")
.data(chord(matrix))
.enter().append("path")
.classed("chord", true)
.style("stroke", d => { return d3.rgb(fill(d.source.index)).darker(); })
.style("fill", d => { return fill(d.source.index); })
.attr("d", ribbon);
} // end draw()
// WRAP TEXT
function wrap(text, width, parent) {
let words = text.split(/\s/).reverse();
if (words.length > 1) {
let word,
line = [],
lineNumber = 0,
lineHeight = 1, // ems
x = getX(parent),
y = getY(parent),
dy = 1.6,
tspan = parent.text(null).append("tspan").attr("x", x).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
console.log(tspan)
tspan = parent.append("tspan").attr("x", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
} else {
parent.text(text)
}
function getX(parent) {
if (parent.attr("x")) {
return parent.attr("x")
} else if (parent.attr("angle")) {
return innerRadius + 26
} else {
return 0
}
}
function getY(parent) {
if (parent.attr("y")) {
return parent.attr("y")
} else if (parent.attr("angle")) {
if (d.angle > Math.PI) {
return "rotate(180)"
} else {
return -26
}
} else {
return ""
}
}
}
function logErr(error) {
console.log(error)
}
d3.select(self.frameElement).style("height", outerRadius * 2 + "px");
</script>
</body>
https://d3js.org/d3.v5.min.js
https://d3js.org/d3-scale-chromatic.v1.min.js