code / public /Figure_8a_barchart.html
Laura Wagner
to commit or not commit that is the question
5f5806d
<!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: Gender → Profession</h2>
<button id="downloadBtn">Download SVG</button>
<svg id="chart"></svg>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
/* --------------------------------------------------------
1. PROFESSION COLORS
-------------------------------------------------------- */
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"
};
/* --------------------------------------------------------
2. GENDER ORDER
-------------------------------------------------------- */
const genderOrder = ["Female", "Male", "Other"];
/* --------------------------------------------------------
3. LOAD JSON + TRANSFORM IT
-------------------------------------------------------- */
d3.json("json/sunburst_gender_A.json").then(data => {
const flatData = [];
data.children.forEach(gender => {
if (gender.children) {
gender.children.forEach(prof => {
flatData.push({
gender: gender.name,
profession: prof.name,
value: prof.value
});
});
}
});
/* --------------------------------------------------------
3a. CALCULATE PROFESSION ORDER BY TOTAL OCCURRENCE
-------------------------------------------------------- */
const professionCounts = {};
flatData.forEach(d => {
professionCounts[d.profession] = (professionCounts[d.profession] || 0) + d.value;
});
// Sort professions by count (descending), but put "Other" last
const professionOrder = Object.entries(professionCounts)
.sort((a, b) => {
// If either is "Other", it goes last
if (a[0] === "Other") return 1;
if (b[0] === "Other") return -1;
// Otherwise sort by count descending
return b[1] - a[1];
})
.map(entry => entry[0]);
// Group per gender
const genderData = d3.rollup(
flatData,
v => ({
total: d3.sum(v, d => d.value),
professions: v
}),
d => d.gender
);
// Sort according to gender ordering
const genders = Array.from(genderData.keys()).sort((a, b) => {
const ai = genderOrder.indexOf(a);
const bi = genderOrder.indexOf(b);
return (ai === -1 ? Infinity : ai) - (bi === -1 ? Infinity : bi);
});
/* --------------------------------------------------------
4. SVG SETUP
-------------------------------------------------------- */
const margin = {top: 40, right: 200, bottom: 80, left: 80};
const width = 800 - margin.left - margin.right;
const height = 600 - 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})`);
/* --------------------------------------------------------
5. STACKED DATA
-------------------------------------------------------- */
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(genderData));
/* --------------------------------------------------------
6. AXES
-------------------------------------------------------- */
const x = d3.scaleBand()
.domain(genders)
.range([0, width])
.padding(0.3);
const y = d3.scaleLinear()
.domain([0, d3.max(Array.from(genderData.values()), d => d.total)])
.nice()
.range([height, 0]);
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x))
.selectAll("text")
.style("text-anchor", "middle")
.style("font-weight", "bold")
.style("font-size", "14px");
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");
/* --------------------------------------------------------
7. DRAW BARS
-------------------------------------------------------- */
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]}`;
});
/* --------------------------------------------------------
8. LEGEND (ordered by occurrence)
-------------------------------------------------------- */
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})`);
});
/* --------------------------------------------------------
9. DOWNLOAD SVG BUTTON
-------------------------------------------------------- */
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 = "gender_bar_chart.svg";
a.click();
URL.revokeObjectURL(url);
});
});
</script>
</body>
</html>