code / public /Figure_9.html
Laura Wagner
to commit or not commit that is the question
5f5806d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Updated 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; margin: 0; padding: 0; }
svg { font: bold 14px sans-serif; }
</style>
</head>
<body>
<svg id="sankey"></svg>
<button onclick="saveSVG()" style="margin: 10px; padding: 8px 16px;">Download SVG</button>
<svg id="sankey"></svg>
<script>
const data = [
{ source: "Total", target: "Checkpoint", value: 4520 },
{ source: "Total", target: "TextualInversion", value: 1590 },
{ source: "Total", target: "LoRA", value: 30687 },
{ source: "Total", target: "Other", value: 1327 },
{ source: "Total", target: "LOCON", value: 1876 },
{ source: "LoRA", target: "Adapters", value: 30687 },
{ source: "LOCON", target: "Adapters", value: 1876 },
{ source: "TextualInversion", target: "Adapters", value: 1590 },
{ source: "Adapters", target: "has_no_tags", value: 23002 },
{ source: "Adapters", target: "has_tags", value: 11151 },
{ source: "has_tags", target: "has_tags + POI True", value: 2327 },
{ source: "has_tags + POI True", target: "Danbooru", value: 991 },
{ source: "Danbooru", target: "explicit", value: 233 },
{ source: "Danbooru", target: "non-explicit", value: 758 },
{ source: "has_tags + POI True", target: "CLIP Interrogator", value: 772 },
{ source: "CLIP Interrogator", target: "explicit", value: 5 },
{ source: "CLIP Interrogator", target: "non-explicit", value: 767 },
{ source: "has_tags + POI True", target: "unknown", value: 564 },
{ source: "unknown", target: "non-explicit", value: 564 },
{ source: "has_tags", target: "has_tags + POI False", value: 8824 },
{ source: "has_tags + POI False", target: "Danbooru", value: 6839 },
{ source: "Danbooru", target: "explicit", value: 4692 },
{ source: "Danbooru", target: "non-explicit", value: 2147 },
{ source: "has_tags + POI False", target: "CLIP Interrogator", value: 800 },
{ source: "CLIP Interrogator", target: "explicit", value: 37 },
{ source: "CLIP Interrogator", target: "non-explicit", value: 763 },
{ source: "has_tags + POI False", target: "unknown", value: 1185 },
{ source: "unknown", target: "explicit", value: 3 },
{ source: "unknown", target: "non-explicit", value: 1182 },
{ source: "explicit", target: "explicit_keyword_1", value: 558 },
{ source: "explicit", target: "explicit_keyword_2", value: 69 },
{ source: "explicit", target: "explicit_keyword_3", value: 189 }
];
const width = 1600;
const height = 800;
const svg = d3.select("#sankey")
.attr("width", width)
.attr("height", height);
// Extract unique node names
const nodes = Array.from(
new Set(data.flatMap(d => [d.source, d.target])),
name => ({ name })
);
const sankeyData = {
nodes,
links: data.map(d => Object.assign({}, d))
};
// Sankey layout
const sankey = d3.sankey()
.nodeId(d => d.name)
.nodeAlign(d3.sankeyLeft)
.nodeWidth(10)
.nodePadding(60)
.extent([[1, 1], [width - 1, height - 6]]);
const { nodes: layoutNodes, links: layoutLinks } = sankey(sankeyData);
// Color map (updated)
const color = name => {
const map = {
"Total": "#C0C0C0",
"Adapters": "rosybrown",
"Checkpoint": "#C0C0C0",
"LoRA": "rosybrown",
"LOCON": "rosybrown",
"Danbooru": "crimson",
"Other": "#C0C0C0",
"Textual Training Data": "coral",
"No Textual Training Data": "#ccc",
"has_tags + POI False": "silver",
"has_tags + POI True": "coral",
"Real person": "FF7F50",
"POI False": "#BC8F8F",
"explicit": "maroon",
"non-explicit": "#C0C0C0",
"has_tags": "coral",
"explicit_keyword_1": "#8A2BE2",
"explicit_keyword_2": "#8A2BE2",
"explicit_keyword_3": "#8A2BE2"
};
return map[name] || "#ccc";
};
// Label map (optional, can extend if needed)
const labelMap = {
"has_tags + POI True": "Real person",
"has_tags + POI False": "Not real person",
"has_no_tags": "No text training data",
"explicit_keyword_1": "Loli",
"explicit_keyword_2": "Shota",
"explicit_keyword_3": "Rape",
"has_tags": "Textual training data",
"explicit": "Explicit",
"non-explicit": "Not explicit",
// keep other mappings if needed
};
const defs = svg.append("defs");
layoutLinks.forEach((d, i) => {
const grad = defs.append("linearGradient")
.attr("id", d.uid = `link-gradient-${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));
});
// Draw links
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));
// Draw nodes
svg.append("g")
// .attr("stroke", "#000")
.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}`);
// Draw labels
svg.append("g")
.selectAll("text")
.data(layoutNodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 10 : d.x0 - 10)
.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", "22px")
.each(function(d) {
const text = d3.select(this);
const label = labelMap[d.name] || d.name;
const lines = label.split(/(?<=\w)\s+(?=\w)/);
lines.forEach((line, i) => {
text.append("tspan")
.attr("x", d.x0 < width / 2 ? d.x1 + 10 : d.x0 - 10)
.attr("dy", i === 0 ? "0.35em" : "1.1em")
.text(line);
});
const format = d3.format(",");
text.append("tspan")
.attr("x", d.x0 < width / 2 ? d.x1 + 10 : d.x0 - 10)
.attr("dy", "1.5em")
.style("font-size", "20px")
.style("fill", "#555")
.text(`${format(d.value)}`);
});
function inlineStyles(svgElement) {
const styles = `
text { font-family: sans-serif; fill: black; font-weight: bold; }
rect { stroke: none; }
path { stroke-opacity: 0.5; }
`;
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 inline CSS
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);
}
</script>
</body>
</html>