forked from mbostock's block: Chord Diagram
forked from kafunk's block: Chord Diagram d3.v5 with self-wrapping text
forked from kafunk's block: noun&noun chord diagram single city
xxxxxxxxxx
<head>
<meta charset="utf-8">
<style>
body { position:absolute; top:0; bottom:0; right:0; left:0; }
text { font-size:12px; font-family:monospace; }
.chord { fill-opacity:0.4; }
#title { font-size: 36px; opacity:0.6; }
#subtitle { font-size: 18px; opacity: 0.7; }
#subsubtitle { font-size: 12px; opacity: 0.8; }
</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 titleGrp = svg.append("g");
let title = titleGrp.append("text")
.attr("id", "title")
.attr("x", -outerRadius + 36)
.attr("y", -outerRadius - 10),
subtitle = titleGrp.append("text")
.attr("id", "subtitle")
.attr("x", -outerRadius + 36)
.attr("y", -outerRadius + 48),
subsubtitle = titleGrp.append("text")
.attr("id", "subsubtitle")
.attr("x", -outerRadius + 36)
.attr("y", -outerRadius + 96);
wrap("noun & noun", (width/2), title);
wrap("business titles pulled from Yelp data", (width/4), subtitle);
wrap("for minneapolis, denver, chicago, & austin", (width/6), subsubtitle);
// 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
let denver = d3.json("denver_nouns.json"),
chicago = d3.json("chicago_nouns.json"),
austin = d3.json("austin_nouns.json"),
minneapolis = d3.json("minneapolis_nouns.json");
Promise.all([denver,chicago,austin,minneapolis]).then(draw,logErr);
// MAIN FUNCTION
function draw(data) {
let allData = data[0].concat(data[1],data[2],data[3]);
let allNames = allData.map(d => d.name);
console.log(allNames)
let firstNouns = allNames.map(d => ({noun: d.split(" ")[0], assoc: [d.split(" ")[2]]})),
connectors = allNames.map(d => ({connector: d.split(" ")[1], assoc: [d.split(" ")[0], d.split(" ")[2]]})),
secondNouns = allNames.map(d => ({noun: d.split(" ")[2], assoc: [d.split(" ")[0]]}));
let allNouns = firstNouns.concat(secondNouns);
// .filter(onlyUnique);
// initial sort
allNouns.sort(function(a,b) { return a.noun > b.noun });
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.
allNouns.forEach(function(d) {
if (!indexByName.has(d = d.noun)) {
nameByIndex.set(n, d);
indexByName.set(d, n++);
}
});
// Construct a square matrix counting package imports.
allNouns.forEach(function(d) {
var source = indexByName.get(d.noun),
row = matrix[source];
if (!row) {
row = matrix[source] = [];
for (var i = -1; ++i < n;) row[i] = 0;
}
d.assoc.forEach(d => { row[indexByName.get(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 d3.rgb(fill(d.index)).brighter(); })
.style("stroke", "dimgray")
.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)); })
.style("fill", d => { return d3.rgb(fill(d.source.index)).brighter(); })
.attr("d", ribbon)
// .on("mouseover", fade(0.1))
// .on("mouseout", fade(1));
} // end draw()
// COMBAK
function fade(e,level) {
console.log(e);
console.log(e.target);
console.log(this)
}
// unique values filter
function onlyUnique(value,index,self) {
return self.indexOf(value) === index;
}
// 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