Skip to content

Commit 03839da

Browse files
committed
Enhance data story presentation with new guided sections, findings panel, and interactive story steps. Update main.js to integrate story controller and presets for improved narrative flow. Revise regional map description for clarity. Add story-related styles in story.css.
1 parent 7a50485 commit 03839da

8 files changed

Lines changed: 948 additions & 18 deletions

File tree

index.html

Lines changed: 193 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,172 @@ <h1>
1919
Is Europe <em>really</em> falling behind?
2020
</h1>
2121
<p class="lede">
22-
Choose an economic indicator, animate it through time, and compare a
23-
handful of countries across the metrics that matter.
22+
Not in the simple way the headline suggests. Since 2000, European
23+
economies kept growing, but public markets compounded much more
24+
aggressively in the United States and selected Asian markets.
25+
</p>
26+
<p class="story-cue">
27+
Read the charts from left to right: first the real economy, then the
28+
investable market proxy, then the global index weight that investors
29+
actually experience.
2430
</p>
2531
</header>
2632

33+
<section class="findings-panel" aria-labelledby="findings-title">
34+
<div class="section-kicker">
35+
<p class="eyebrow">Key findings</p>
36+
<h2 id="findings-title">Europe grew. Markets repriced the world elsewhere.</h2>
37+
</div>
38+
<div class="finding-grid">
39+
<article class="finding-card">
40+
<span class="finding-card__label">Real economy</span>
41+
<strong>2.48x</strong>
42+
<p>Average European GDP growth by 2023, with GDP per capita also more than doubling.</p>
43+
</article>
44+
<article class="finding-card">
45+
<span class="finding-card__label">Market proxy</span>
46+
<strong>1.15x</strong>
47+
<p>Average European country-ETF growth, versus 2.28x for North America.</p>
48+
</article>
49+
<article class="finding-card">
50+
<span class="finding-card__label">Clearest split</span>
51+
<strong>0.90x vs 2.51x</strong>
52+
<p>Germany's ETF ended below its 2000 level, while the US ETF more than doubled.</p>
53+
</article>
54+
<article class="finding-card">
55+
<span class="finding-card__label">Global weight</span>
56+
<strong>63.7%</strong>
57+
<p>The US share of MSCI ACWI in May 2026, up from 53.0% in April 2015.</p>
58+
</article>
59+
</div>
60+
</section>
61+
2762
<main class="mvp-layout">
28-
<section class="map-panel" aria-label="Interactive map">
63+
<section class="guided-story" aria-label="Guided data story">
64+
<div class="story-scroll">
65+
<div class="scroll-hint" aria-hidden="true">
66+
<span>Scroll</span>
67+
<i class="scroll-hint__arrow"></i>
68+
</div>
69+
70+
<article class="story-step" data-story-preset="real-economy">
71+
<p class="eyebrow">Act 1 &middot; Real economy</p>
72+
<h2>Europe did not stop growing.</h2>
73+
<p>
74+
Start with GDP in regional mode. By 2023, Europe is around
75+
2.48x its 2000 GDP baseline, and GDP per capita also more than
76+
doubled. The first lesson is that the story is not collapse.
77+
</p>
78+
<div class="story-stat-row">
79+
<span><strong>2.48x</strong> Europe GDP</span>
80+
<span><strong>2.18x</strong> Europe GDP per capita</span>
81+
</div>
82+
<button class="story-preset-button" type="button" data-story-action="real-economy">
83+
Show GDP view
84+
</button>
85+
</article>
86+
87+
<article class="story-step" data-story-preset="market-gap">
88+
<p class="eyebrow">Act 2 &middot; Market proxy</p>
89+
<h2>The market line tells a harsher story.</h2>
90+
<p>
91+
Switch to country ETF prices and the gap opens. Germany and the
92+
UK sit below their 2000 ETF baselines, while the US ETF more
93+
than doubles and Korea becomes the key counterexample.
94+
</p>
95+
<div class="story-stat-row">
96+
<span><strong>0.90x</strong> Germany ETF</span>
97+
<span><strong>0.62x</strong> UK ETF</span>
98+
<span><strong>2.51x</strong> US ETF</span>
99+
</div>
100+
<button class="story-preset-button" type="button" data-story-action="market-gap">
101+
Show ETF view
102+
</button>
103+
</article>
104+
105+
<article class="story-step" data-story-preset="scatter-gap">
106+
<p class="eyebrow">Act 3 &middot; Uneven gap</p>
107+
<h2>Prosperity and markets separate.</h2>
108+
<p>
109+
The scatter view fixes the comparison: GDP per capita growth on
110+
one axis, ETF growth on the other. Countries to the upper right
111+
turned prosperity into investable market returns; Europe is more
112+
mixed.
113+
</p>
114+
<div class="story-stat-row">
115+
<span><strong>Korea</strong> high market response</span>
116+
<span><strong>Germany</strong> GDP without ETF lift</span>
117+
</div>
118+
<button class="story-preset-button" type="button" data-story-action="scatter-gap">
119+
Show scatter view
120+
</button>
121+
</article>
122+
123+
<article class="story-step" data-story-preset="market-cap">
124+
<p class="eyebrow">Act 4 &middot; Market cap</p>
125+
<h2>Listed-company value compounds elsewhere.</h2>
126+
<p>
127+
Market capitalization makes the asymmetry harder to miss. The
128+
US compounds strongly, Korea is exceptional, and several major
129+
European markets trail despite real-economy growth.
130+
</p>
131+
<div class="story-stat-row">
132+
<span><strong>11.49x</strong> Korea market cap</span>
133+
<span><strong>3.24x</strong> US market cap</span>
134+
<span><strong>1.14x</strong> UK market cap</span>
135+
</div>
136+
<button class="story-preset-button" type="button" data-story-action="market-cap">
137+
Show market-cap view
138+
</button>
139+
</article>
140+
141+
<article class="story-step" data-story-preset="acwi-latest">
142+
<p class="eyebrow">Act 5 &middot; Global index</p>
143+
<h2>The index investors buy became more concentrated.</h2>
144+
<p>
145+
The ACWI snapshots translate the country story into portfolio
146+
exposure. From 2015 to 2026, US weight grows and technology
147+
becomes the dominant sector block.
148+
</p>
149+
<div class="story-stat-row">
150+
<span><strong>63.7%</strong> US ACWI weight</span>
151+
<span><strong>30.7%</strong> information technology</span>
152+
</div>
153+
<div class="story-button-row">
154+
<button class="story-preset-button" type="button" data-story-action="acwi-first">
155+
Show 2015 ACWI
156+
</button>
157+
<button class="story-preset-button" type="button" data-story-action="acwi-latest">
158+
Show latest ACWI
159+
</button>
160+
</div>
161+
</article>
162+
163+
<article class="story-step story-step--explore" data-story-preset="explore">
164+
<p class="eyebrow">Explore yourself</p>
165+
<h2>Now test the thesis.</h2>
166+
<p>
167+
Use the controls to change the indicator, year, geography, and
168+
scale. The guided sections set the argument; the explorer lets
169+
you look for exceptions.
170+
</p>
171+
<button class="story-preset-button" type="button" data-story-action="explore">
172+
Reset explorer
173+
</button>
174+
</article>
175+
</div>
176+
177+
<section class="map-panel story-stage" aria-label="Interactive story stage">
178+
<div class="section-kicker section-kicker--panel">
179+
<p class="eyebrow">Evidence explorer</p>
180+
<h2>Compare the growth story from four angles</h2>
181+
<p>
182+
Start with GDP to see that Europe is not standing still, then
183+
switch to ETF Price and Market Cap to see where the gap opens.
184+
Use countries for concrete examples, or regions for the broader pattern.
185+
</p>
186+
</div>
187+
29188
<div class="control-bar">
30189
<div class="control-group control-group--inputs">
31190
<label class="control-field">
@@ -72,6 +231,12 @@ <h1>
72231
</div>
73232

74233
<div id="map-chart" class="map-chart"></div>
234+
<p class="chart-note">
235+
Map mode shows where each indicator has grown since 2000. Scatter
236+
mode fixes the question to prosperity growth versus investable
237+
market growth, making outliers like the US, Korea, Germany, and the
238+
UK easier to compare.
239+
</p>
75240

76241
<aside id="spider-panel" class="spider-overlay" aria-label="Spider comparison" hidden>
77242
<div class="spider-heading">
@@ -89,15 +254,25 @@ <h2>Profile comparison</h2>
89254
<div>
90255
<p class="eyebrow">Evolution</p>
91256
<h2 id="evolution-title">Indicator trajectory</h2>
257+
<p class="panel-copy">
258+
The line chart turns the selected year into a trajectory. Look
259+
for whether the gap appears gradually, after shocks, or only in
260+
the latest period.
261+
</p>
92262
</div>
93263
<div id="evolution-chart" class="chart"></div>
94264
</section>
95265

96266
<section class="treemap-panel" aria-label="Country / sector treemap">
97267
<div class="treemap-panel__heading">
98268
<div>
99-
<p class="eyebrow">Market composition</p>
100-
<h2 id="treemap-title">ACWI weights by country and sector</h2>
269+
<p class="eyebrow">Final act</p>
270+
<h2 id="treemap-title">Where global equity weight moved</h2>
271+
<p class="panel-copy">
272+
The ACWI snapshots show why the market gap matters: global
273+
equity exposure became more concentrated in the US and in
274+
technology-heavy sectors.
275+
</p>
101276
</div>
102277
<label class="control-field control-field--treemap-time">
103278
<span class="control-label">
@@ -110,7 +285,7 @@ <h2 id="treemap-title">ACWI weights by country and sector</h2>
110285
<section class="treemap-card" aria-label="Country treemap">
111286
<div class="treemap-card__heading">
112287
<h3>MSCI ACWI Countries</h3>
113-
<p>Hover for sector breakdowns</p>
288+
<p>Hover for sector breakdowns. Watch the US tile expand over time.</p>
114289
</div>
115290
<div id="treemap-chart" class="chart"></div>
116291
</section>
@@ -122,14 +297,23 @@ <h3>MSCI ACWI sectors</h3>
122297
</section>
123298
</div>
124299
</section>
300+
</section>
125301
</section>
126302
</main>
127303

128304
<footer>
129305
<p>
130-
Growth mode normalizes every country to its year-2000 baseline.
131-
Absolute mode reveals economic scale where the underlying units are
132-
comparable; ETF prices remain a normalized public-market proxy.
306+
Methodology: the main comparison covers 20 countries with comparable
307+
country ETF histories from 2000 to 2023. Growth mode normalizes each
308+
country to its year-2000 baseline. Absolute mode is shown only where
309+
units are comparable; ETF prices remain a normalized investable-market
310+
proxy.
311+
</p>
312+
<p>
313+
Market capitalization comes from World Bank listed-company market cap
314+
data where reliable. Recent gaps are filled with the matching iShares
315+
MSCI country ETF price as a proxy; Russia, China, India, and Sweden
316+
are excluded from the market-cap comparison because of spotty data.
133317
</p>
134318
</footer>
135319
</div>

src/main.js

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/**
2-
* Application entry point for the Milestone 2 MVP: an animated indicator map
3-
* plus a spider graph for a small selected set of countries.
2+
* Application entry point for the data story: linked economic views, timeline
3+
* controls, country and region selection, and ACWI market-composition treemaps.
44
*/
55

66
import "./style.css";
7+
import "./story.css";
78
import * as d3 from "d3";
89
import {
910
DEFAULT_SELECTION,
@@ -17,6 +18,15 @@ import {
1718
import { createEvolutionChart } from "./evolution-chart.js";
1819
import { createIndicatorMap } from "./regional-map.js";
1920
import { createSpiderChart } from "./spider.js";
21+
import { createStoryController } from "./story-controller.js";
22+
import { DEFAULT_STORY_PRESET_ID, STORY_PRESETS } from "./story-presets.js";
23+
import {
24+
clampYear,
25+
getTreemapSnapshotIndex,
26+
getValidCountries,
27+
getValidRegions,
28+
setStageFocus,
29+
} from "./story-state.js";
2030
import { createSectorTreemap, createTreemap } from "./treemap.js";
2131

2232
const ANIMATION_MS = 650;
@@ -53,6 +63,8 @@ async function main() {
5363
const evolutionContainer = getRequiredElement("evolution-chart");
5464
const treemapSlider = getRequiredElement("treemap-slider");
5565
const treemapDateLabel = getRequiredElement("treemap-date-label");
66+
const storyStage = document.querySelector(".story-stage");
67+
const treemapPanel = document.querySelector(".treemap-panel");
5668

5769
let selectedIso3 = DEFAULT_SELECTION.filter((iso3) => iso3 in data.countries);
5870
let selectedRegions = [...DEFAULT_REGIONS];
@@ -61,6 +73,7 @@ async function main() {
6173
let valueMode = "growth";
6274
let isPlaying = false;
6375
let animationId = 0;
76+
let activeStoryPresetId = DEFAULT_STORY_PRESET_ID;
6477

6578
populateIndicatorSelect(indicatorSelect, data);
6679
yearSlider.min = String(data.baselineYear);
@@ -76,8 +89,6 @@ async function main() {
7689
});
7790
const updateEvolution = createEvolutionChart(evolutionContainer, data);
7891

79-
// Load treemap data (preprocessed JSON) and initialize treemap component.
80-
// Treemap: load data and initialize, but keep it fully independent (no selection, no axis, just year)
8192
let treemap = null;
8293
let sectorTreemap = null;
8394
let treemapData = null;
@@ -161,8 +172,8 @@ async function main() {
161172
);
162173
spiderCaption.textContent =
163174
selectionMode === "regions"
164-
? "Click countries to select their whole region. The spider graph shows regional averages."
165-
: "Click countries on the map to compare their full normalized profiles.";
175+
? "Click countries to select their whole region. The spider graph shows how real-economy growth compares with market growth across regional averages."
176+
: "Click countries on the map to compare their full normalized profiles and see where GDP growth diverges from ETF and market-cap growth.";
166177
evolutionTitle.textContent =
167178
selectionMode === "regions"
168179
? `${axis}: ${valueMode} by region`
@@ -214,6 +225,43 @@ async function main() {
214225
sectorTreemap?.renderKey(snapshot.key);
215226
}
216227

228+
/**
229+
* Applies a named story preset to all linked views.
230+
*
231+
* @param {string} presetId Story preset identifier.
232+
* @param {{ force?: boolean }} [options] Preset options.
233+
*/
234+
function applyStoryPreset(presetId, options = {}) {
235+
const preset = STORY_PRESETS[presetId];
236+
if (!preset || (activeStoryPresetId === presetId && !options.force)) return;
237+
238+
stopAnimation();
239+
activeStoryPresetId = presetId;
240+
indicatorSelect.value = data.axes.includes(preset.axis) ? preset.axis : data.axes[0];
241+
yearSlider.value = String(clampYear(data, preset.year ?? data.latestYear));
242+
updateRangeProgress(yearSlider);
243+
viewMode = preset.viewMode ?? viewMode;
244+
selectionMode = preset.selectionMode ?? selectionMode;
245+
valueMode = preset.valueMode ?? valueMode;
246+
selectedIso3 = getValidCountries(preset.selectedIso3, data, DEFAULT_SELECTION);
247+
selectedRegions = getValidRegions(preset.selectedRegions, DEFAULT_REGIONS);
248+
setTreemapSnapshot(preset.treemapSnapshot);
249+
render();
250+
setStageFocus(storyStage, treemapPanel, preset.stageFocus);
251+
}
252+
253+
/**
254+
* Moves the ACWI treemap slider to a semantic snapshot.
255+
*
256+
* @param {"first" | "latest" | string | undefined} snapshot Target snapshot.
257+
*/
258+
function setTreemapSnapshot(snapshot) {
259+
if (!treemapSnapshots.length || !snapshot) return;
260+
const snapshotIndex = getTreemapSnapshotIndex(treemapSnapshots, snapshot);
261+
treemapSlider.value = String(snapshotIndex);
262+
updateRangeProgress(treemapSlider);
263+
}
264+
217265
/**
218266
* Starts or stops year-by-year animation.
219267
*/
@@ -231,6 +279,20 @@ async function main() {
231279
else window.clearTimeout(animationId);
232280
}
233281

282+
/**
283+
* Stops timeline playback and restores play-button state.
284+
*/
285+
function stopAnimation() {
286+
if (!isPlaying) return;
287+
isPlaying = false;
288+
window.clearTimeout(animationId);
289+
const label = playButton.querySelector(".play-button__label");
290+
if (label) label.textContent = "Play";
291+
else playButton.textContent = "Play";
292+
playButton.dataset.playing = "false";
293+
playButton.setAttribute("aria-label", "Play timeline");
294+
}
295+
234296
/**
235297
* Advances the selected year while playback is active.
236298
*/
@@ -299,6 +361,9 @@ async function main() {
299361
});
300362

301363
render();
364+
createStoryController({ applyPreset: applyStoryPreset });
365+
activeStoryPresetId = "";
366+
applyStoryPreset(DEFAULT_STORY_PRESET_ID);
302367
}
303368

304369
/**

0 commit comments

Comments
 (0)