Laura Wagner commited on
Commit
7e9ef53
·
1 Parent(s): 03843c0

added figure 8 barchart

Browse files
Files changed (1) hide show
  1. public/Figure_8b_barchart.html +276 -0
public/Figure_8b_barchart.html ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <meta charset="utf-8">
3
+ <style>
4
+ body {
5
+ font-family: "Segoe UI", sans-serif;
6
+ margin: 20px;
7
+ }
8
+
9
+ svg {
10
+ font: 12px sans-serif;
11
+ }
12
+
13
+ .bar {
14
+ cursor: pointer;
15
+ }
16
+
17
+ .bar:hover {
18
+ opacity: 0.8;
19
+ }
20
+
21
+ .axis text {
22
+ font-size: 11px;
23
+ }
24
+
25
+ .legend {
26
+ font-size: 12px;
27
+ }
28
+
29
+ .legend rect {
30
+ stroke-width: 1;
31
+ stroke: #000;
32
+ }
33
+
34
+ h2 {
35
+ text-align: center;
36
+ }
37
+
38
+ #downloadBtn {
39
+ display: block;
40
+ margin: 0 auto 20px;
41
+ }
42
+ </style>
43
+ <body>
44
+ <h2>Bar Chart: Country → Profession</h2>
45
+ <button id="downloadBtn">Download SVG</button>
46
+ <svg id="chart"></svg>
47
+ <script src="https://d3js.org/d3.v6.min.js"></script>
48
+ <script>
49
+ const colorMap = {
50
+ "Adult Performer": "#8A2BE2",
51
+ "Model": "#DC143C",
52
+ "Actor": "#FF7F50",
53
+ "Performer": "magenta",
54
+ "Singer, Musician": "wheat",
55
+ "Sports Professional": "gold",
56
+ "Voice Actor": "lightgreen",
57
+ "Online Personality": "#4682B4",
58
+ "Other": "#ccc"
59
+ };
60
+
61
+ const professionOrder = [
62
+ "Actor", "Adult Performer", "Singer, Musician", "Model", "Online Personality", "Sports Professional", "Voice Actor", "Other"
63
+ ];
64
+
65
+ const manualOrder = [
66
+ "United States",
67
+ "Japan",
68
+ "United Kingdom",
69
+ "South Korea",
70
+ "Russia",
71
+ "India",
72
+ "France",
73
+ "China",
74
+ "Canada",
75
+ "Brazil",
76
+ "Germany",
77
+ "Italy",
78
+ "Australia",
79
+ "Spain",
80
+ "Ukraine",
81
+ "Turkey",
82
+ "Netherlands",
83
+ "Indonesia",
84
+ "Poland",
85
+ "Czech Republic",
86
+ "Argentina",
87
+ "Sweden",
88
+ "Mexico",
89
+ "Taiwan",
90
+ "Thailand",
91
+ "Ireland",
92
+ "Philippines",
93
+ "Hong Kong",
94
+ "Macau",
95
+ "British Virgin Islands",
96
+ "Other"
97
+ ];
98
+
99
+ d3.json("json/sunburst_countries_A.json").then(data => {
100
+ // Transform hierarchical data into flat array
101
+ const flatData = [];
102
+
103
+ data.children.forEach(country => {
104
+ if (country.children) {
105
+ country.children.forEach(profession => {
106
+ flatData.push({
107
+ country: country.name,
108
+ profession: profession.name,
109
+ value: profession.value
110
+ });
111
+ });
112
+ }
113
+ });
114
+
115
+ // Group by country and calculate totals
116
+ const countryData = d3.rollup(
117
+ flatData,
118
+ v => ({
119
+ total: d3.sum(v, d => d.value),
120
+ professions: v
121
+ }),
122
+ d => d.country
123
+ );
124
+
125
+ // Convert to array and sort by manual order
126
+ const countries = Array.from(countryData.keys()).sort((a, b) => {
127
+ const aIndex = manualOrder.indexOf(a);
128
+ const bIndex = manualOrder.indexOf(b);
129
+ return (aIndex === -1 ? Infinity : aIndex) - (bIndex === -1 ? Infinity : bIndex);
130
+ });
131
+
132
+ // Set up dimensions - responsive to window size
133
+ const margin = {top: 40, right: 200, bottom: 150, left: 80};
134
+ const width = Math.max(800, window.innerWidth - 100) - margin.left - margin.right;
135
+ const height = 800 - margin.top - margin.bottom;
136
+
137
+ const svg = d3.select("#chart")
138
+ .attr("width", width + margin.left + margin.right)
139
+ .attr("height", height + margin.top + margin.bottom)
140
+ .append("g")
141
+ .attr("transform", `translate(${margin.left},${margin.top})`);
142
+
143
+ // Create stacked data
144
+ const stack = d3.stack()
145
+ .keys(professionOrder)
146
+ .value((d, key) => {
147
+ const prof = d[1].professions.find(p => p.profession === key);
148
+ return prof ? prof.value : 0;
149
+ });
150
+
151
+ const series = stack(Array.from(countryData));
152
+
153
+ // Scales - only show top 25 countries
154
+ const top25Countries = countries.slice(0, 25);
155
+
156
+ const x = d3.scaleBand()
157
+ .domain(top25Countries)
158
+ .range([0, width])
159
+ .padding(0.3);
160
+
161
+ const y = d3.scaleLinear()
162
+ .domain([0, d3.max(Array.from(countryData.values()), d => d.total)])
163
+ .nice()
164
+ .range([height, 0]);
165
+
166
+ // X axis
167
+ svg.append("g")
168
+ .attr("class", "axis")
169
+ .attr("transform", `translate(0,${height})`)
170
+ .call(d3.axisBottom(x))
171
+ .selectAll("text")
172
+ .attr("transform", "rotate(-90)")
173
+ .style("text-anchor", "end")
174
+ .style("font-size", "11px")
175
+ .style("font-weight", "bold")
176
+ .attr("dx", "-0.5em")
177
+ .attr("dy", "-0.5em");
178
+
179
+ // Y axis
180
+ svg.append("g")
181
+ .attr("class", "axis")
182
+ .call(d3.axisLeft(y));
183
+
184
+ // Y axis label
185
+ svg.append("text")
186
+ .attr("transform", "rotate(-90)")
187
+ .attr("y", 0 - margin.left + 20)
188
+ .attr("x", 0 - (height / 2))
189
+ .attr("dy", "1em")
190
+ .style("text-anchor", "middle")
191
+ .style("font-size", "14px")
192
+ .style("font-weight", "bold")
193
+ .text("Count");
194
+
195
+ // Draw bars
196
+ svg.append("g")
197
+ .selectAll("g")
198
+ .data(series)
199
+ .join("g")
200
+ .attr("fill", d => colorMap[d.key])
201
+ .selectAll("rect")
202
+ .data(d => d)
203
+ .join("rect")
204
+ .attr("class", "bar")
205
+ .attr("x", d => x(d.data[0]))
206
+ .attr("y", d => y(d[1]))
207
+ .attr("height", d => y(d[0]) - y(d[1]))
208
+ .attr("width", x.bandwidth())
209
+ .append("title")
210
+ .text(d => {
211
+ const profession = series.find(s => s.find(item => item === d))?.key;
212
+ return `${d.data[0]} - ${profession}: ${d[1] - d[0]}`;
213
+ });
214
+
215
+ // Count total values per profession
216
+ const professionCounts = {};
217
+ flatData.forEach(d => {
218
+ professionCounts[d.profession] = (professionCounts[d.profession] || 0) + d.value;
219
+ });
220
+
221
+ // Legend
222
+ const legend = svg.append("g")
223
+ .attr("class", "legend")
224
+ .attr("transform", `translate(${width + 20}, 0)`);
225
+
226
+ professionOrder.forEach((prof, i) => {
227
+ const row = legend.append("g")
228
+ .attr("transform", `translate(0,${i * 22})`);
229
+
230
+ row.append("rect")
231
+ .attr("width", 18)
232
+ .attr("height", 18)
233
+ .attr("fill", colorMap[prof]);
234
+
235
+ row.append("text")
236
+ .attr("x", 24)
237
+ .attr("y", 9)
238
+ .attr("dy", "0.35em")
239
+ .style("font-size", "12px")
240
+ .text(`${prof} (${professionCounts[prof] || 0})`);
241
+ });
242
+
243
+ // Download button functionality
244
+ document.getElementById("downloadBtn").addEventListener("click", () => {
245
+ const svgNode = document.querySelector("#chart");
246
+ const clonedSvg = svgNode.cloneNode(true);
247
+
248
+ clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
249
+
250
+ // Inline styles
251
+ const allElements = clonedSvg.querySelectorAll("*");
252
+ allElements.forEach(el => {
253
+ const style = window.getComputedStyle(el);
254
+ el.setAttribute("style", `
255
+ font: ${style.font};
256
+ fill: ${style.fill};
257
+ stroke: ${style.stroke};
258
+ stroke-width: ${style.strokeWidth};
259
+ `);
260
+ });
261
+
262
+ const svgData = new XMLSerializer().serializeToString(clonedSvg);
263
+ const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
264
+ const url = URL.createObjectURL(svgBlob);
265
+ const a = document.createElement("a");
266
+ a.href = url;
267
+ a.download = "bar_chart.svg";
268
+ document.body.appendChild(a);
269
+ a.click();
270
+ document.body.removeChild(a);
271
+ URL.revokeObjectURL(url);
272
+ });
273
+ });
274
+ </script>
275
+ </body>
276
+ </html>