155 lines
4.6 KiB
HTML
155 lines
4.6 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html lang="">
|
||
|
|
||
|
<head>
|
||
|
<meta charset="utf-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
<title></title>
|
||
|
<style>
|
||
|
body {
|
||
|
padding: 0;
|
||
|
margin: 0;
|
||
|
background-color: white;
|
||
|
}
|
||
|
</style>
|
||
|
<script src="../js/d3.v7.js"></script>
|
||
|
|
||
|
</head>
|
||
|
|
||
|
<body>
|
||
|
<div id="graph"></div>
|
||
|
</body>
|
||
|
|
||
|
<script type="module">
|
||
|
|
||
|
|
||
|
fetch("../data/parsed_requests.json")
|
||
|
.then(res => res.json())
|
||
|
.then(out =>
|
||
|
loadData(out))
|
||
|
.catch(err => { throw err });
|
||
|
|
||
|
function loadData(input) {
|
||
|
|
||
|
const data = input['edges'].map((edge) => {
|
||
|
return { 'source': edge['Owning Library Name'], 'target': edge['Pickup Location'], 'value': 1 }
|
||
|
})
|
||
|
|
||
|
const width = 1080;
|
||
|
const height = width;
|
||
|
const innerRadius = Math.min(width, height) * 0.5 - 30;
|
||
|
const outerRadius = innerRadius + 6;
|
||
|
|
||
|
|
||
|
// Compute a dense matrix from the weighted links in data.
|
||
|
const names = Array.from(d3.union(data.flatMap(d => [d.source, d.target])));
|
||
|
const index = new Map(names.map((name, i) => [name, i]));
|
||
|
const matrix = Array.from(index, () => new Array(names.length).fill(0));
|
||
|
for (const { source, target, value } of data) matrix[index.get(source)][index.get(target)] += value;
|
||
|
|
||
|
|
||
|
// // Compute a dense matrix from the weighted links in data.
|
||
|
// const names = data['nodes'].map((node) => node['name'])
|
||
|
// const index = new Map(names.map((name, i) => [name, i]));
|
||
|
// const matrix = Array.from(index, () => new Array(names.length).fill(0));
|
||
|
// for (const edge of data['edges']) {
|
||
|
// const source = edge['Owning Library Name']
|
||
|
// const target = edge['Pickup Location']
|
||
|
// matrix[index.get(source)][index.get(target)] += 1;
|
||
|
// }
|
||
|
|
||
|
const chord = d3.chordDirected()
|
||
|
.padAngle(50 / innerRadius)
|
||
|
.sortSubgroups(d3.descending)
|
||
|
.sortChords(d3.descending);
|
||
|
|
||
|
const arc = d3.arc()
|
||
|
.innerRadius(innerRadius)
|
||
|
.outerRadius(outerRadius);
|
||
|
|
||
|
const ribbon = d3.ribbonArrow()
|
||
|
.radius(innerRadius - 5.5) // padding with circle
|
||
|
.padAngle(10 / innerRadius); // padding between arrows
|
||
|
|
||
|
// const colors = d3.schemeCategory10;
|
||
|
const colors = ["#ffc259",
|
||
|
"#20066b",
|
||
|
"#8deb70",
|
||
|
"#ff89f8",
|
||
|
"#90bb2c",
|
||
|
"#bc89ff",
|
||
|
"#ddff7d",
|
||
|
"#584a9f",
|
||
|
"#abff98",
|
||
|
"#980064",
|
||
|
"#00eaa9",
|
||
|
"#ff5aa4",
|
||
|
"#007e22",
|
||
|
"#ff6e70",
|
||
|
"#6ec478",
|
||
|
"#912900",
|
||
|
"#5d7800",
|
||
|
"#ff9350",
|
||
|
"#d9af54",
|
||
|
"#d08500"];
|
||
|
|
||
|
const formatValue = x => `${x.toFixed(0)}`;
|
||
|
|
||
|
const svg = d3.create("svg")
|
||
|
.attr("width", width)
|
||
|
.attr("height", height)
|
||
|
.attr("viewBox", [-width / 2, -height / 2, width, height])
|
||
|
.attr("style", "width: 100%; height: auto; font: 10px sans-serif;");
|
||
|
|
||
|
const chords = chord(matrix);
|
||
|
|
||
|
// const textId = DOM.uid("text");
|
||
|
|
||
|
svg.append("path")
|
||
|
.attr("id", "somepath")
|
||
|
.attr("fill", "none")
|
||
|
.attr("d", d3.arc()({ outerRadius, startAngle: 0, endAngle: 2 * Math.PI }));
|
||
|
|
||
|
svg.append("g")
|
||
|
.attr("fill-opacity", 0.75)
|
||
|
.selectAll()
|
||
|
.data(chords)
|
||
|
.join("path")
|
||
|
.attr("d", ribbon)
|
||
|
.attr("fill", d => colors[d.target.index])
|
||
|
.attr("stroke", "black")
|
||
|
.style("mix-blend-mode", "multiply")
|
||
|
.append("title")
|
||
|
.text(d => `${formatValue(d.source.value)} items from ${names[d.source.index]} to ${names[d.target.index]}`);
|
||
|
|
||
|
const g = svg.append("g")
|
||
|
.selectAll()
|
||
|
.data(chords.groups)
|
||
|
.join("g");
|
||
|
|
||
|
g.append("path")
|
||
|
.attr("d", arc)
|
||
|
.attr("fill", d => colors[d.index])
|
||
|
.attr("stroke", "black")
|
||
|
// .attr("stroke", "#fff")
|
||
|
;
|
||
|
|
||
|
g.append("text")
|
||
|
.attr("dy", -3)
|
||
|
.append("textPath")
|
||
|
.attr("xlink:href", "#somepath")
|
||
|
.attr("startOffset", d => d.startAngle * outerRadius)
|
||
|
.text(d => names[d.index]);
|
||
|
|
||
|
// g.append("title")
|
||
|
// .text(d => `${names[d.index]}
|
||
|
// owes ${formatValue(d3.sum(matrix[d.index]))}
|
||
|
// is owed ${formatValue(d3.sum(matrix, row => row[d.index]))}`);
|
||
|
|
||
|
|
||
|
|
||
|
graph.append(svg.node());
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
</html>
|