code / public /Figure_13.html
Laura Wagner
to commit or not commit that is the question
5f5806d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Danbooru Tree from JSON</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
body {
font-family: sans-serif;
}
svg {
width: 100%;
height: 1500px;
}
.node circle {
fill: steelblue;
}
.node text {
font-size: 12px;
fill: #333;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<h2>Danbooru Categories</h2>
<button id="downloadBtn">Download SVG</button>
<svg></svg>
<script>
d3.json("json/danbooru_flat.json").then(function(data) {
const svg = d3.select("svg");
const width = 750;
const height = 1200;
// ---- STEP 1: Limit second-level children ----
function limitSecondLevel(node, depth = 0) {
if (node.children && depth === 1 && node.children.length > 5) {
const visible = node.children.slice(0, 5);
visible.push({ name: "...", children: [] });
node.children = visible;
}
if (node.children) {
node.children.forEach(child => limitSecondLevel(child, depth + 1));
}
return node;
}
const limited = limitSecondLevel(structuredClone(data));
const root = d3.hierarchy(limited, d => d.children);
// ---- STEP 2: Track root category for coloring ----
root.eachAfter(d => {
if (d.depth === 1) {
d.data.rootCategory = d.data.name;
} else if (d.parent) {
d.data.rootCategory = d.parent.data.rootCategory;
}
});
const treeLayout = d3.tree().size([height, width]);
treeLayout(root);
// ---- STEP 3: Scale for tag_count → radius ----
const allTagCounts = root.descendants()
.filter(d => d.depth > 0)
.map(d => d.data.tag_count || 0);
const sizeScale = d3.scaleSqrt()
.domain([0, d3.max(allTagCounts)])
.range([4, 20]);
// ---- STEP 4: Color Map ----
const categoryColors = {
"attire": "#f4a261",
"body": "#e76f51",
"characters": "#2a9d8f",
"copyrights": "#264653",
"creatures": "#8ecae6",
"drawing software": "#219ebc",
"games": "#3a86ff",
"metatags": "#ffbe0b",
"more": "#b5179e",
"objects": "#6d6875",
"plant": "#7cb518",
"real_world": "#a5a58d",
"sex": "#ef476f",
"visual_characteristics": "#06d6a0",
"subject": "#ffd166",
"uncategorized": "#adb5bd",
"actions_and_expressions": "#d00000",
"objects_and_backgrounds": "#118ab2"
};
function getColor(d) {
if (d.data.name === "...") return "gray";
const category = d.data.rootCategory?.toLowerCase();
return categoryColors[category] || "steelblue";
}
// ---- STEP 5: Draw Links ----
svg.selectAll(".link")
.data(root.links())
.join("path")
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x)
);
// ---- STEP 6: Draw Nodes ----
const node = svg.selectAll(".node")
.data(root.descendants())
.join("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.y},${d.x})`);
node.append("circle")
.attr("r", d => d.depth === 0 ? 6 : sizeScale(d.data.tag_count || 0))
.style("fill", d => d.data.color || "steelblue");
node.append("title")
.text(d => `${d.data.name}\nTags: ${d.data.tag_count || 0}`);
node.append("text")
.attr("x", 10)
.style("font-weight", "bold")
.attr("dy", "0.32em")
.text(d => d.data.name);
});
document.getElementById("downloadBtn").addEventListener("click", () => {
const svgNode = document.querySelector("svg");
// Clone the SVG node to preserve original
const clonedSvg = svgNode.cloneNode(true);
const outer = document.createElement("div");
outer.appendChild(clonedSvg);
// Add xmlns so it can be saved properly
clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
// Serialize the SVG
const svgData = new XMLSerializer().serializeToString(clonedSvg);
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
// Create download link
const url = URL.createObjectURL(svgBlob);
const a = document.createElement("a");
a.href = url;
a.download = "danbooru_tree.svg";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
});
</script>
</body>
</html>