Skip to content

Commit af8eb37

Browse files
authored
Merge pull request #36 from com-480-data-visualization/fix/timeline-detail-cards
feat: change the curators note to 1 column paragraph and data country…
2 parents e50d7cb + 06c4295 commit af8eb37

4 files changed

Lines changed: 185 additions & 25 deletions

File tree

docs/country_timeline.html

Lines changed: 107 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
1515
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
16+
<script src="js/data.js"></script>
1617

1718
<style>
1819
:root {
@@ -184,6 +185,18 @@
184185
pointer-events: none;
185186
transition: opacity .6s ease;
186187
}
188+
.intro::before {
189+
content: '';
190+
position: absolute;
191+
top: 50%;
192+
left: 50%;
193+
transform: translate(-50%, -50%);
194+
width: 1280px;
195+
height: 520px;
196+
background: radial-gradient(ellipse at center, rgba(9, 26, 39, 0.96) 0%, rgba(22, 37, 56, 0.8) 30%, rgba(11, 26, 29, 0.4) 60%, transparent 70%);
197+
pointer-events: none;
198+
z-index: -1;
199+
}
187200
.intro .kicker {
188201
font-size: 11px;
189202
letter-spacing: 0.32em;
@@ -235,6 +248,14 @@
235248
50% { transform: translateY(6px); }
236249
}
237250

251+
/* ---------- Particle background ---------- */
252+
#bgParticles {
253+
position: fixed;
254+
inset: 0;
255+
z-index: 0;
256+
pointer-events: none;
257+
}
258+
238259
/* ---------- The Hallway ---------- */
239260
.webgl-stage {
240261
position: fixed;
@@ -752,11 +773,12 @@
752773
font-style: italic;
753774
}
754775

755-
/* Chart col: pie + legend side by side */
776+
/* Chart col: pie on top, legend below */
756777
.dc-chart-inner {
757778
display: flex;
758-
align-items: flex-start;
759-
gap: 28px;
779+
flex-direction: column;
780+
align-items: center;
781+
gap: 24px;
760782
}
761783
.pie { width: 200px; height: 200px; flex-shrink: 0; position: relative; }
762784
.pie-svg { width: 100%; height: 100%; }
@@ -774,7 +796,7 @@
774796
font-size: 9.5px; letter-spacing: 0.25em;
775797
text-transform: uppercase; color: var(--text-dim); margin-top: 4px;
776798
}
777-
.pie-legend { display: flex; flex-direction: column; flex: 1; align-self: center; }
799+
.pie-legend { display: flex; flex-direction: column; width: 100%; }
778800
.pie-legend .item {
779801
display: flex; align-items: center; gap: 10px;
780802
font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
@@ -821,20 +843,21 @@
821843
.dc-curator-lbl {
822844
font-size: 10.5px; letter-spacing: 0.32em;
823845
text-transform: uppercase; color: var(--text-dim);
824-
text-align: center; margin-bottom: 24px;
846+
text-align: center; margin-bottom: 10px;
825847
}
826848
.narrative {
827849
font-family: 'Cormorant Garamond', serif;
828850
font-size: 18px; line-height: 1.65;
829851
color: var(--text-mid);
830-
column-count: 2; column-gap: 48px;
852+
column-count: 1; max-width: 720px; margin: 0 auto;
831853
font-weight: 300;
832854
}
833855
.narrative::first-letter {
834856
font-family: 'Cormorant Garamond', serif;
835857
font-size: 64px; font-weight: 400;
836-
float: left; line-height: 0.85;
837-
margin: 4px 10px 0 0;
858+
line-height: 1;
859+
vertical-align: baseline;
860+
margin-right: 4px;
838861
color: var(--bluesky); font-style: italic;
839862
}
840863

@@ -858,7 +881,7 @@
858881
.dc-body { grid-template-columns: 1fr; }
859882
.dc-vdivider { height: 1px; width: auto; }
860883
.dc-col { padding: 24px 20px; }
861-
.dc-chart-inner { flex-direction: column; align-items: center; }
884+
862885
.dc-curator { padding: 24px 20px 32px; }
863886
.narrative { column-count: 1; }
864887
}
@@ -880,6 +903,8 @@
880903
</head>
881904
<body>
882905

906+
<canvas id="bgParticles"></canvas>
907+
883908
<div class="loading-overlay" id="loadingOverlay">
884909
<div class="spinner"></div>
885910
<p>Loading the archive…</p>
@@ -964,7 +989,7 @@ <h2 class="dc-title" id="detailTitle">—</h2>
964989

965990
<div class="dc-body">
966991
<div class="dc-col dc-col-chart">
967-
<div class="dc-section-lbl">-- Object &nbsp;·&nbsp; Distribution of Events --</div>
992+
<div class="dc-section-lbl"> Object &nbsp;·&nbsp; Distribution of Events </div>
968993
<div class="dc-chart-inner">
969994
<div class="pie">
970995
<svg class="pie-svg" viewBox="0 0 100 100" id="pieSvg"></svg>
@@ -980,7 +1005,7 @@ <h2 class="dc-title" id="detailTitle">—</h2>
9801005
<div class="dc-vdivider"></div>
9811006

9821007
<div class="dc-col dc-col-numbers">
983-
<div class="dc-section-lbl">-- Object &nbsp;·&nbsp; Numbers --</div>
1008+
<div class="dc-section-lbl"> Object &nbsp;·&nbsp; Numbers </div>
9841009
<div class="ledger">
9851010
<div class="ledger-row">
9861011
<div class="lbl">Total Deaths</div>
@@ -1026,5 +1051,76 @@ <h2 class="dc-title" id="detailTitle">—</h2>
10261051
}
10271052
})();
10281053
</script>
1054+
1055+
<script>
1056+
(function () {
1057+
const canvas = document.getElementById('bgParticles');
1058+
const ctx = canvas.getContext('2d');
1059+
const N = 260;
1060+
let W, H, dpr, parts;
1061+
1062+
function resize() {
1063+
dpr = window.devicePixelRatio || 1;
1064+
W = window.innerWidth;
1065+
H = window.innerHeight;
1066+
canvas.width = W * dpr;
1067+
canvas.height = H * dpr;
1068+
canvas.style.width = W + 'px';
1069+
canvas.style.height = H + 'px';
1070+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
1071+
}
1072+
1073+
function rand(a, b) { return a + Math.random() * (b - a); }
1074+
1075+
function spawn() {
1076+
return {
1077+
x: rand(0, W),
1078+
y: rand(0, H),
1079+
r: rand(0.4, 1.6),
1080+
vx: rand(-0.06, 0.06),
1081+
vy: rand(-0.04, 0.04),
1082+
base: rand(0.08, 0.55),
1083+
phase: rand(0, Math.PI * 2),
1084+
speed: rand(0.004, 0.012)
1085+
};
1086+
}
1087+
1088+
function init() {
1089+
resize();
1090+
parts = Array.from({ length: N }, spawn);
1091+
}
1092+
1093+
let t = 0;
1094+
function draw() {
1095+
t++;
1096+
ctx.clearRect(0, 0, W, H);
1097+
ctx.fillStyle = '#0C1116';
1098+
ctx.fillRect(0, 0, W, H);
1099+
1100+
for (const p of parts) {
1101+
p.x += p.vx;
1102+
p.y += p.vy;
1103+
1104+
// wrap around edges
1105+
if (p.x < -2) p.x = W + 2;
1106+
if (p.x > W+2) p.x = -2;
1107+
if (p.y < -2) p.y = H + 2;
1108+
if (p.y > H+2) p.y = -2;
1109+
1110+
const alpha = p.base * (0.6 + 0.4 * Math.sin(p.phase + t * p.speed));
1111+
ctx.beginPath();
1112+
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
1113+
ctx.fillStyle = `rgba(180, 210, 255, ${alpha.toFixed(3)})`;
1114+
ctx.fill();
1115+
}
1116+
1117+
requestAnimationFrame(draw);
1118+
}
1119+
1120+
init();
1121+
draw();
1122+
window.addEventListener('resize', () => { resize(); });
1123+
})();
1124+
</script>
10291125
</body>
10301126
</html>

docs/js/data.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,78 @@ const DISASTER_COLORS = Object.fromEntries(
3838
Object.entries(DISASTER_TYPE_META).map(([type, meta]) => [type, meta.color])
3939
);
4040

41+
// Historical ISO codes → current canonical ISO codes (matches globe logic)
42+
const ISO_ALIASES = {
43+
CSK: 'CZE',
44+
DDR: 'DEU',
45+
DFR: 'DEU',
46+
SCG: 'SRB',
47+
SUN: 'RUS',
48+
YMD: 'YEM',
49+
YMN: 'YEM',
50+
YUG: 'SRB'
51+
};
52+
53+
function canonicalIso(iso) {
54+
return ISO_ALIASES[iso] || iso;
55+
}
56+
57+
const COUNTRY_NAME_ALIASES = {
58+
'bolivia plurinational state of': 'Bolivia',
59+
'brunei darussalam': 'Brunei',
60+
'central african rep': 'Central African Republic',
61+
'czech republic': 'Czechia',
62+
'dem rep congo': 'DR Congo',
63+
'dominican rep': 'Dominican Republic',
64+
'eq guinea': 'Equatorial Guinea',
65+
'iran islamic republic of': 'Iran',
66+
'lao peoples democratic republic': 'Laos',
67+
'macedonia': 'North Macedonia',
68+
'micronesia federated states of': 'Micronesia',
69+
'netherlands kingdom of the': 'Netherlands',
70+
'republic of congo': 'Congo',
71+
'republic of korea': 'South Korea',
72+
'russian federation': 'Russia',
73+
's korea': 'South Korea',
74+
's sudan': 'South Sudan',
75+
'solomon is': 'Solomon Islands',
76+
'state of palestine': 'Palestine',
77+
'swaziland': 'Eswatini',
78+
'syrian arab republic': 'Syria',
79+
'tanzanian united republic': 'Tanzania',
80+
'united republic of tanzania': 'Tanzania',
81+
'turkiye': 'Turkey',
82+
'united kingdom of great britain and northern ireland': 'United Kingdom',
83+
'venezuela bolivarian republic of': 'Venezuela',
84+
'viet nam': 'Vietnam',
85+
'democratic peoples republic of korea': 'North Korea',
86+
'korea democratic peoples republic of': 'North Korea',
87+
'korea republic of': 'South Korea',
88+
'moldova republic of': 'Moldova',
89+
'taiwan province of china': 'Taiwan',
90+
'hong kong special administrative region of china': 'Hong Kong',
91+
'macao special administrative region of china': 'Macao',
92+
'virgin islands british': 'British Virgin Islands',
93+
'virgin islands us': 'US Virgin Islands',
94+
'saint helena ascension and tristan da cunha': 'Saint Helena',
95+
'saint kitts and nevis': 'St Kitts and Nevis',
96+
'saint lucia': 'St Lucia',
97+
'saint vincent and the grenadines': 'St Vincent',
98+
'sao tome and principe': 'São Tomé and Príncipe',
99+
'turks and caicos islands': 'Turks and Caicos',
100+
'wallis and futuna islands': 'Wallis and Futuna',
101+
'western sahara': 'Western Sahara',
102+
'british indian ocean territory': 'BIOT'
103+
};
104+
105+
function normalizeCountryName(raw) {
106+
if (!raw) return raw;
107+
const key = raw.toLowerCase().replace(/[^a-z\s]/g, ' ').replace(/\s+/g, ' ').trim();
108+
if (COUNTRY_NAME_ALIASES[key]) return COUNTRY_NAME_ALIASES[key];
109+
// Title-case the original if no alias found
110+
return raw.replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase());
111+
}
112+
41113
// Initialize data from the cleaned EM-DAT CSV.
42114
async function initData(csvPath = 'data/emdat_clean.csv') {
43115
const csvCandidates = Array.from(new Set([

docs/js/scene2_globe.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,8 @@ const GLOBE_COUNTRY_NAME_ALIASES = {
3636
'viet nam': 'vietnam'
3737
};
3838

39-
const GLOBE_ISO_CENTER_ALIASES = {
40-
CSK: 'CZE',
41-
DDR: 'DEU',
42-
DFR: 'DEU',
43-
SCG: 'SRB',
44-
SUN: 'RUS',
45-
YMD: 'YEM',
46-
YMN: 'YEM',
47-
YUG: 'SRB'
48-
};
39+
// ISO_ALIASES and canonicalIso() are defined in data.js (loaded first)
40+
const GLOBE_ISO_CENTER_ALIASES = ISO_ALIASES;
4941

5042
const GLOBE_FALLBACK_CENTERS = {
5143
AIA: { lat: 18.22, lng: -63.06 },
@@ -296,7 +288,7 @@ const Scene2 = {
296288
},
297289

298290
canonicalIso(iso) {
299-
return GLOBE_ISO_CENTER_ALIASES[iso] || iso;
291+
return canonicalIso(iso);
300292
},
301293

302294
getCountryLabel(country) {

docs/js/scene3_timeline.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,7 @@ async function init() {
871871
} catch (e) { /* try next */ }
872872
}
873873

874-
const events = rawRows.filter(d => d.iso === ISO).map(d => ({
874+
const events = rawRows.filter(d => canonicalIso(d.iso) === ISO).map(d => ({
875875
year: +d.year, type: d.type || 'unknown', name: d.name || 'Unnamed Event',
876876
deaths: +d.deaths || 0, affected: +d.affected || 0, damage: +d.damage_usd_thousands || 0, region: d.region || ''
877877
}));
@@ -884,8 +884,8 @@ async function init() {
884884
return;
885885
}
886886

887-
const refRow = rawRows.find(d => d.iso === ISO);
888-
COUNTRY_NAME = refRow ? refRow.country : ISO;
887+
const refRow = rawRows.find(d => canonicalIso(d.iso) === ISO);
888+
COUNTRY_NAME = normalizeCountryName(refRow ? refRow.country : ISO);
889889

890890
const allYears = [...new Set(events.map(d => d.year))].sort((a, b) => a - b);
891891
const yearDataArr = allYears.map(year => {

0 commit comments

Comments
 (0)