| | <!DOCTYPE html> |
| | <meta charset="utf-8"> |
| | <style> |
| | body { |
| | font-family: "Segoe UI", sans-serif; |
| | margin: 20px; |
| | } |
| | |
| | svg { |
| | font: 12px sans-serif; |
| | } |
| | |
| | .bar { |
| | cursor: pointer; |
| | } |
| | |
| | .bar:hover { |
| | opacity: 0.8; |
| | } |
| | |
| | .axis text { |
| | font-size: 11px; |
| | } |
| | |
| | .legend { |
| | font-size: 12px; |
| | } |
| | |
| | .legend rect { |
| | stroke-width: 1; |
| | stroke: #000; |
| | } |
| | |
| | h2 { |
| | text-align: center; |
| | } |
| | |
| | #downloadBtn { |
| | display: block; |
| | margin: 0 auto 20px; |
| | } |
| | </style> |
| | <body> |
| | <h2>Bar Chart: Country → Profession</h2> |
| | <button id="downloadBtn">Download SVG</button> |
| | <svg id="chart"></svg> |
| | <script src="https://d3js.org/d3.v6.min.js"></script> |
| | <script> |
| | |
| | |
| | |
| | const colorMap = { |
| | "Adult Performer": "#8A2BE2", |
| | "Model": "#DC143C", |
| | "Actor": "#FF7F50", |
| | "Public Figure": "#20B2AA", |
| | "Singer, Musician": "wheat", |
| | "Sports Professional": "gold", |
| | "Voice Actor": "lightgreen", |
| | "Online Personality": "#4682B4", |
| | "Other": "#ccc" |
| | }; |
| | |
| | |
| | |
| | |
| | d3.json("json/sunburst_countries_A.json").then(data => { |
| | |
| | const flatData = []; |
| | |
| | data.children.forEach(country => { |
| | if (country.children) { |
| | country.children.forEach(prof => { |
| | flatData.push({ |
| | country: country.name, |
| | profession: prof.name, |
| | value: prof.value |
| | }); |
| | }); |
| | } |
| | }); |
| | |
| | |
| | |
| | |
| | const professionCounts = {}; |
| | flatData.forEach(d => { |
| | professionCounts[d.profession] = (professionCounts[d.profession] || 0) + d.value; |
| | }); |
| | |
| | const professionOrder = Object.entries(professionCounts) |
| | .filter(entry => entry[0] !== "Other") |
| | .sort((a, b) => b[1] - a[1]) |
| | .map(entry => entry[0]); |
| | |
| | if (professionCounts["Other"]) { |
| | professionOrder.push("Other"); |
| | } |
| | |
| | |
| | |
| | |
| | const countryData = d3.rollup( |
| | flatData, |
| | v => ({ |
| | total: d3.sum(v, d => d.value), |
| | professions: v |
| | }), |
| | d => d.country |
| | ); |
| | |
| | |
| | |
| | |
| | let countries = Array.from(countryData.entries()) |
| | .sort((a, b) => b[1].total - a[1].total) |
| | .map(d => d[0]); |
| | |
| | |
| | countries = countries.filter(c => c !== "Other"); |
| | if (countryData.has("Other")) { |
| | countries.push("Other"); |
| | } |
| | |
| | const topCountries = countries.slice(0, 25); |
| | |
| | |
| | |
| | |
| | const margin = {top: 40, right: 200, bottom: 150, left: 80}; |
| | const width = Math.max(800, window.innerWidth - 100) - margin.left - margin.right; |
| | const height = 800 - margin.top - margin.bottom; |
| | |
| | const svg = d3.select("#chart") |
| | .attr("width", width + margin.left + margin.right) |
| | .attr("height", height + margin.top + margin.bottom) |
| | .append("g") |
| | .attr("transform", `translate(${margin.left},${margin.top})`); |
| | |
| | |
| | |
| | |
| | const stack = d3.stack() |
| | .keys(professionOrder) |
| | .value((d, key) => { |
| | const prof = d[1].professions.find(p => p.profession === key); |
| | return prof ? prof.value : 0; |
| | }); |
| | |
| | const series = stack(Array.from(countryData)); |
| | |
| | |
| | |
| | |
| | const x = d3.scaleBand() |
| | .domain(topCountries) |
| | .range([0, width]) |
| | .padding(0.3); |
| | |
| | const y = d3.scaleLinear() |
| | .domain([0, d3.max(Array.from(countryData.values()), d => d.total)]) |
| | .nice() |
| | .range([height, 0]); |
| | |
| | svg.append("g") |
| | .attr("transform", `translate(0,${height})`) |
| | .call(d3.axisBottom(x)) |
| | .selectAll("text") |
| | .attr("transform", "rotate(-90)") |
| | .style("text-anchor", "end") |
| | .style("font-weight", "bold") |
| | .attr("dx", "-0.5em") |
| | .attr("dy", "-0.5em"); |
| | |
| | svg.append("g") |
| | .call(d3.axisLeft(y)); |
| | |
| | svg.append("text") |
| | .attr("transform", "rotate(-90)") |
| | .attr("y", 0 - margin.left + 20) |
| | .attr("x", 0 - height / 2) |
| | .style("font-weight", "bold") |
| | .text("Count"); |
| | |
| | |
| | |
| | |
| | svg.append("g") |
| | .selectAll("g") |
| | .data(series) |
| | .join("g") |
| | .attr("fill", d => colorMap[d.key]) |
| | .selectAll("rect") |
| | .data(d => d) |
| | .join("rect") |
| | .attr("class", "bar") |
| | .attr("x", d => x(d.data[0])) |
| | .attr("y", d => y(d[1])) |
| | .attr("height", d => y(d[0]) - y(d[1])) |
| | .attr("width", x.bandwidth()) |
| | .append("title") |
| | .text(d => { |
| | const profKey = series.find(s => s.includes(d))?.key; |
| | return `${d.data[0]} – ${profKey}: ${d[1] - d[0]}`; |
| | }); |
| | |
| | |
| | |
| | |
| | const legend = svg.append("g") |
| | .attr("transform", `translate(${width + 20}, 0)`); |
| | |
| | professionOrder.forEach((prof, i) => { |
| | const row = legend.append("g").attr("transform", `translate(0,${i * 22})`); |
| | |
| | row.append("rect") |
| | .attr("width", 18) |
| | .attr("height": 18) |
| | .attr("fill": colorMap[prof]); |
| | |
| | row.append("text") |
| | .attr("x": 24) |
| | .attr("y": 9) |
| | .attr("dy": "0.35em") |
| | .text(`${prof} (${professionCounts[prof] || 0})`); |
| | }); |
| | |
| | |
| | |
| | |
| | document.getElementById("downloadBtn").addEventListener("click", () => { |
| | const svgNode = document.querySelector("#chart"); |
| | const clone = svgNode.cloneNode(true); |
| | |
| | clone.setAttribute("xmlns", "http://www.w3.org/2000/svg"); |
| | |
| | const all = clone.querySelectorAll("*"); |
| | all.forEach(el => { |
| | const style = window.getComputedStyle(el); |
| | el.setAttribute("style", `font:${style.font}; fill:${style.fill}; stroke:${style.stroke};`); |
| | }); |
| | |
| | const svgData = new XMLSerializer().serializeToString(clone); |
| | const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" }); |
| | const url = URL.createObjectURL(blob); |
| | const a = document.createElement("a"); |
| | |
| | a.href = url; |
| | a.download = "bar_chart.svg"; |
| | a.click(); |
| | URL.revokeObjectURL(url); |
| | }); |
| | |
| | }); |
| | </script> |
| |
|
| |
|
| | </body> |
| | </html> |