code / public /Figure_9_old.html
Laura Wagner
to commit or not commit that is the question
5f5806d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Unified Sankey Diagram</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-sankey@0.12.3/dist/d3-sankey.min.js"></script>
<style>
body { font-family: sans-serif; }
svg { font: bold 14px sans-serif; }
</style>
</head>
<body>
<svg id="sankey"></svg>
<script>
const data = [
{ source: "Total", target: "Checkpoint", value: 4520 },
{ source: "Total", target: "Other", value: 1327 },
{ source: "Total", target: "Adapters", value: 34153 },
{ source: "Adapters", target: "Textual Training Data", value: 11151 },
{ source: "Adapters", target: "Unknown Textual Training Data", value: 23002 },
{ source: "Textual Training Data", target: "POI True", value: 2327 },
{ source: "Textual Training Data", target: "POI False", value: 8824 },
{ source: "POI True", target: "explicit", value: 238 },
{ source: "POI True", target: "non-explicit", value: 2089 },
{ source: "POI False", target: "explicit", value: 4732 },
{ source: "POI False", target: "non-explicit", value: 4092 },
{ source: "explicit", target: "loli", value: 558 },
{ source: "explicit", target: "shota", value: 69 },
{ source: "explicit", target: "rape", value: 189 }
];
////////////////////////////////////////// SAVE SVG //////////////////////////////////////////
function inlineStyles(svgElement) {
const styles = `
text { font-family: sans-serif; fill: black; font-weight: bold; }
rect { stroke: none; }
`;
const styleElem = document.createElementNS("http://www.w3.org/2000/svg", "style");
styleElem.textContent = styles;
svgElement.insertBefore(styleElem, svgElement.firstChild);
}
function saveSVG() {
const svgElement = document.querySelector("svg");
inlineStyles(svgElement); // inject style
const serializer = new XMLSerializer();
const source = serializer.serializeToString(svgElement);
const svgBlob = new Blob([source], { type: "image/svg+xml;charset=utf-8" });
const svgUrl = URL.createObjectURL(svgBlob);
const downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = "sankey-diagram.svg";
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
window.addEventListener("keydown", function (e) {
if (e.code === "Space") {
e.preventDefault(); // prevent page scrolling
saveSVG();
}
});
////////////////////////////////////////// SAVE SVG //////////////////////////////////////////
const width = 680;
const height = 400;
const svg = d3.select("#sankey")
.attr("width", width)
.attr("height", height);
const sankey = d3.sankey()
.nodeId(d => d.name)
.nodeAlign(d3.sankeyLeft)
.nodeWidth(15)
.nodePadding(20)
.extent([[1, 1], [width - 1, height - 6]]);
const nodeSet = new Set();
data.forEach(d => {
nodeSet.add(d.source);
nodeSet.add(d.target);
});
const nodes = Array.from(nodeSet).map(name => ({ name }));
const { nodes: layoutNodes, links: layoutLinks } = sankey({
nodes: nodes.map(d => Object.assign({}, d)),
links: data.map(d => Object.assign({}, d))
});
const color = name => {
const map = {
"Total": "#C0C0C0",
"Adapters": "#C0C0C0",
"Checkpoint": "#C0C0C0",
"Other": "#C0C0C0",
"Textual Training Data": "#BC8F8F",
"No Textual Training Data": "#ccc",
"POI True": "#FFA07A",
"POI False": "#BC8F8F",
"explicit": "#DC143C",
"non-explicit": "#C0C0C0",
"loli": "#8A2BE2",
"shota": "#8A2BE2",
"rape": "#8A2BE2"
};
return map[name] || "#ccc";
};
const labelMap = {
"Textual Training Data": "Textual training data found",
"No Textual Training Data": "No textual training data",
"POI True": "POI true",
"POI False": "POI false",
"non-explicit": "Non-explicit",
"explicit": "Explicit",
"Total": "Total",
"Adapters": "Adapters",
"Checkpoint": "Checkpoint",
"Other": "Other",
"loli": "Loli",
"shota": "Shota",
"rape": "Rape"
};
const defs = svg.append("defs");
layoutLinks.forEach((d, i) => {
const grad = defs.append("linearGradient")
.attr("id", d.uid = `link-${i}`)
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", d.source.x1)
.attr("x2", d.target.x0);
grad.append("stop").attr("offset", "0%").attr("stop-color", color(d.source.name));
grad.append("stop").attr("offset", "100%").attr("stop-color", color(d.target.name));
});
svg.append("g")
.attr("fill", "none")
.attr("stroke-opacity", 0.5)
.selectAll("path")
.data(layoutLinks)
.join("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke", d => `url(#${d.uid})`)
.attr("stroke-width", d => Math.max(1, d.width))
.append("title")
.text(d => `${d.source.name}${d.target.name}\n${d.value}`);
svg.append("g")
.selectAll("rect")
.data(layoutNodes)
.join("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("height", d => d.y1 - d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("fill", d => color(d.name))
.append("title")
.text(d => `${d.name}\n${d.value}`);
svg.append("g")
.selectAll("text")
.data(layoutNodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("y", d => (d.y1 + d.y0) / 2)
.attr("dy", "0.35em")
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.style("font-size", "16px")
.each(function(d) {
const text = d3.select(this);
const label = labelMap[d.name] || d.name;
const lines = label.split(/(?<=\w)\s+(?=\w)/); // split long words
lines.forEach((line, i) => {
text.append("tspan")
.attr("x", d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("dy", i === 0 ? "0.35em" : "1.2em")
.text(line);
});
/* const base = layoutNodes.find(n => n.name === "Total"); /// Percentages
if (base && base.value > 0) {
const pct = ((d.value / base.value) * 100).toFixed(1);
text.append("tspan")
.attr("x", d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("dy", "1.5em")
.style("font-size", "14px")
.style("fill", "#333")
.text(`${pct}%`); */
const format = d3.format(",");
text.append("tspan")
.attr("x", d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("dy", "1.5em")
.style("font-size", "14px")
.style("fill", "#333")
.text(`${format(d.value)}`);
});
</script>
</body>
</html>