| | <!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/[email protected]/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); |
| | |
| | |
| | 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)) |
| | }; |
| | |
| | |
| | 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); |
| | |
| | |
| | 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"; |
| | }; |
| | |
| | |
| | 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", |
| | |
| | |
| | }; |
| | |
| | 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)); |
| | }); |
| | |
| | |
| | |
| | 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)); |
| | |
| | |
| | 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 + 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); |
| | 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> |
| |
|