| <!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> |
|
|