|
|
<!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; |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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]); |
|
|
|
|
|
|
|
|
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"; |
|
|
} |
|
|
|
|
|
|
|
|
svg.selectAll(".link") |
|
|
.data(root.links()) |
|
|
.join("path") |
|
|
.attr("class", "link") |
|
|
.attr("d", d3.linkHorizontal() |
|
|
.x(d => d.y) |
|
|
.y(d => d.x) |
|
|
); |
|
|
|
|
|
|
|
|
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"); |
|
|
|
|
|
|
|
|
const clonedSvg = svgNode.cloneNode(true); |
|
|
const outer = document.createElement("div"); |
|
|
outer.appendChild(clonedSvg); |
|
|
|
|
|
|
|
|
clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); |
|
|
|
|
|
|
|
|
const svgData = new XMLSerializer().serializeToString(clonedSvg); |
|
|
const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" }); |
|
|
|
|
|
|
|
|
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> |
|
|
|