Download public/Figure_8a_sunburst.html from latentcanon/code: direct link, hf CLI and curl.
- Browser
- Download file 6.16 kB
-
https://huggingface.co/latentcanon/code/resolve/main/public/Figure_8a_sunburst.html
- Command line
-
hf download hf://latentcanon/code/public/Figure_8a_sunburst.html
-
curl -L -o Figure_8a_sunburst.html https://huggingface.co/latentcanon/code/resolve/main/public/Figure_8a_sunburst.html
6.16 kB
| <meta charset="utf-8"> | |
| <style> | |
| body { | |
| font-family: "Segoe UI", sans-serif; | |
| text-align: center; | |
| margin-top: 20px; | |
| } | |
| svg { | |
| font: 10px sans-serif; | |
| } | |
| .label { | |
| font-weight: bold; | |
| font-size: 12px; | |
| text-anchor: middle; | |
| } | |
| .legend { | |
| font-size: 12px; | |
| text-anchor: start; | |
| } | |
| .legend rect { | |
| stroke-width: 1; | |
| stroke: #000; | |
| } | |
| </style> | |
| <body> | |
| <h2>Sunburst Chart: Country → Profession</h2> | |
| <button id="downloadBtn">Download SVG</button> | |
| <svg width="800" height="800"></svg> | |
| <script src="https://d3js.org/d3.v6.min.js"></script> | |
| <script> | |
| const width = 800; | |
| const radius = width / 3; | |
| const colorMap = { | |
| "Adult Performer": "#8A2BE2", | |
| "Model": "#DC143C", | |
| "Actor": "#FF7F50", | |
| "Performer": "magenta", | |
| "Singer, Musician": "wheat", | |
| "TV Personality": "#708090", | |
| "Sports Professional": "gold", | |
| "Public Figure": "maroon", | |
| "Voice Actor": "lightgreen", | |
| "Online Personality": "#4682B4", | |
| "Other": "#ccc" | |
| }; | |
| // Count total values per gender (assuming gender is at depth 1) | |
| const professionOrder = [ | |
| "Adult Performer", "Actor", "Singer, Musician", "Model", "Online Personality", "Public Figure", "Sports Professional", "Voice Actor", "TV Personality", "Other" | |
| ]; | |
| const manualOrder = [ | |
| "United States", "Japan", "South Korea", "United Kingdom", | |
| "Russia", "China", "Canada", "India", "Australia", "France", "Germany", "Other" | |
| ]; | |
| function customSort(a, b, order) { | |
| const aIndex = order.indexOf(a.data.name); | |
| const bIndex = order.indexOf(b.data.name); | |
| return (aIndex === -1 ? Infinity : aIndex) - (bIndex === -1 ? Infinity : bIndex); | |
| } | |
| function partition(data) { | |
| const root = d3.hierarchy(data).sum(d => d.value); | |
| // Sort countries by manual order before partitioning | |
| root.children.sort((a, b) => customSort(a, b, manualOrder)); | |
| // Sort professions by value (descending), with "Other" always last | |
| root.children.forEach(country => { | |
| if (country.children) { | |
| country.children.sort((a, b) => { | |
| if (a.data.name === "Other") return 1; | |
| if (b.data.name === "Other") return -1; | |
| return b.value - a.value; | |
| }); | |
| } | |
| }); | |
| return d3.partition().size([2 * Math.PI, root.height + 1])(root); | |
| } | |
| d3.json("json/sunburst_gender.json").then(data => { | |
| const root = partition(data); | |
| root.each(d => d.current = d); | |
| // Sort countries with 'Other' last | |
| root.children.sort((a, b) => customSort(a, b, manualOrder)); | |
| // Sort professions within countries with 'Other' last | |
| root.children.forEach(country => { | |
| if (country.children) { | |
| country.children.sort((a, b) => customSort(a, b, professionOrder)); | |
| } | |
| }); | |
| const svg = d3.select("svg") | |
| .attr("viewBox", [0, 0, width, width]) | |
| .style("font", "10px sans-serif"); | |
| const g = svg.append("g") | |
| .attr("transform", `translate(${width / 2},${width / 2})`); | |
| const angleOffset = -80 * Math.PI / 180; | |
| const arc = d3.arc() | |
| .startAngle(d => d.x0 + angleOffset) | |
| .endAngle(d => d.x1 + angleOffset) | |
| .innerRadius(d => { | |
| if (d.depth === 1) return radius * 0.1; | |
| if (d.depth === 2) return radius * 0.5; | |
| }) | |
| .outerRadius(d => { | |
| if (d.depth === 1) return radius * 0.5; | |
| if (d.depth === 2) return radius * 0.65; | |
| }); | |
| const path = g.append("g") | |
| .selectAll("path") | |
| .data(root.descendants().slice(1)) | |
| .join("path") | |
| .attr("fill", d => { | |
| if (d.depth === 2) return colorMap[d.data.name] || "#ccc"; | |
| if (d.depth === 1) return "#fff"; | |
| return "none"; | |
| }) | |
| .attr("stroke", d => d.depth === 1 ? "#000" : null) | |
| .attr("stroke-width", d => d.depth > 0 ? 2 : null) | |
| .attr("d", arc); | |
| path.append("title") | |
| .text(d => `${d.ancestors().map(d => d.data.name).reverse().join(" → ")}\n${d.value}`); | |
| // Count total values per gender (assuming gender is at depth 1) | |
| const genderCounts = {}; | |
| root.children.forEach(d => { | |
| const gender = d.data.name; | |
| genderCounts[gender] = d.value; | |
| }); | |
| // Add bold labels to country segments | |
| g.selectAll("text") | |
| .data(root.children) | |
| .join("text") | |
| .attr("transform", function(d) { | |
| const angle = ((d.x0 + d.x1) / 2) + angleOffset; | |
| const x = Math.cos(angle - Math.PI / 2) * (radius / root.height + -50); | |
| const y = Math.sin(angle - Math.PI / 2) * (radius / root.height + -50); | |
| return `translate(${x},${y}) rotate(${(angle / Math.PI)})`; | |
| }) | |
| .attr("dy", "0.35em") | |
| .attr("class", "label") | |
| .text(d => `${d.data.name}: ${genderCounts[d.data.name] || 0}`); | |
| // Count total values per profession (depth 2) | |
| const professionCounts = {}; | |
| root.descendants().forEach(d => { | |
| if (d.depth === 2) { | |
| const name = d.data.name; | |
| professionCounts[name] = (professionCounts[name] || 0) + d.value; | |
| } | |
| }); | |
| // Legend for profession colors | |
| const legend = svg.append("g") | |
| .attr("class", "legend") | |
| .attr("transform", `translate(20,20)`); | |
| professionOrder.forEach((prof, i) => { | |
| const row = legend.append("g") | |
| .attr("transform", `translate(0,${i * 20})`); | |
| row.append("rect") | |
| .attr("width", 16) | |
| .attr("height", 16) | |
| .attr("fill", colorMap[prof]); | |
| row.append("text") | |
| .attr("x", 22) | |
| .attr("y", 12) | |
| .text(`${prof} (${professionCounts[prof] || 0})`); | |
| }); | |
| }); | |
| 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 = "sunburst_chart.svg"; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }); | |
| </script> | |
| </body> | |