A D3 scrollytelling piece about one barrel of crude oil, from a kilometre underground to the objects in your day.
The claim: open one barrel and your whole day falls out.
This repository is the full Milestone 3 submission. Two pieces matter most:
- The website is the interactive visualization itself: a D3 + Scrollytelling site that walks one barrel through eight scenes. The source is in
src/, it is built with Vite, and it is live at the link below. - The process book is
process_book.pdfat the repo root: the design narrative, showing how the project moved from question to data to sketches to the shipped site, with the decisions and trade-offs along the way.
| Live website | https://challenger-com480.vercel.app |
| Process book | process_book.pdf |
| Screencast (2 min) | https://drive.google.com/drive/folders/1gHq0IOGoLkX5_XWk-w3Ht3pAgGncQrx8?usp=sharing` |
Course: EPFL COM-480 Data Visualization, Milestone 3.
| Student | SCIPER | Focus |
|---|---|---|
| Kamel Charaf | 395501 | Technical implementation & visualization |
| Krish Chawla | 357550 | Story, screencast & process book |
Needs: Node.js 18+ and npm. The processed data is committed, so there is no Python step to run the site.
npm install # install dependencies
npm run dev # dev server -> http://localhost:5173
npm run build # production build -> dist/
npm run preview # preview the build -> http://localhost:4173One barrel of crude oil is 159 litres. The site follows that single barrel through eight scrolling scenes, shaped as a Freytag pyramid in a martini glass structure: author-led through the climax, then open for free re-exploration.
| # | Scene (src/scenes/) |
Arc | What it shows |
|---|---|---|---|
| 1 | Intro (hero.js) |
Exposition | "159 litres, where does it all go?", over a barrel that pours open on click. |
| 2 | Extraction (extraction.js) |
Rising | A click-to-pump well rig filling a refinery tank. Where the barrel comes from. |
| 3 | Refinery (refinery.js) |
Rising | A distillation column sorting the barrel into ten fractions, ending on the +5.9% processing gain (100 in becomes ~106 out). |
| 4 | Uses (sankey.js) |
Rising | A time-aware Sankey from fractions to end-use sectors. A 1993–2025 scrubber shifts the shares (diesel climbs +8.1 pp). |
| 5 | Your Day (climax.js) |
Climax | A grid of everyday objects, each tracing a ribbon back to its parent fraction. The main interaction. |
| 6 | Demand (counter.js) |
Falling | A live counter at ~1,205 barrels per second, plus a per-country consumption bar. |
| 7 | Crisis (crisis.js) |
Coda | The May 2026 Hormuz crisis and the IEA/EIA/OPEC forecast split (-420 / +190 / +1,200 kb/d). |
| 8 | Recap (closing.js) |
Resolution | Restates the claim and loops back to the opening question. |
- Vite 5 for build and dev server.
- D3 v7, imported as scoped sub-modules so the bundle stays lean (~158 KB raw, ~53 KB gzipped).
- Vanilla JS, ES modules. No framework, no UI library, no runtime dependency beyond D3.
- IntersectionObserver scroll engine: plays a scene on enter, stops it on exit.
- CSS custom properties (
src/styles/tokens.css) as the one source of truth for palette, type scale, and motion. - Vercel for hosting. Self-hosted fonts, no third-party analytics.
.
├── index.html Vite entry, font preloads, #story mount point
├── vite.config.js base "./", data-serving plugin
├── src/
│ ├── main.js builds the shell, loads data, mounts + wires scenes
│ ├── scenes/ one module per scene (see The story table)
│ ├── components/ barrel SVG, climax icon dictionary
│ ├── lib/ constants, data loader, formatters, motion
│ └── styles/ tokens, base, per-scene CSS
├── data/
│ ├── raw/ downloaded sources (xlsx, csv, pdf)
│ └── processed/ CSV/JSON shipped to the site (incl. figures.json)
├── notebooks/ Python data pipeline (01 explore, 02 steo, 03 figures)
├── public/fonts/ self-hosted Fraunces, Inter, JetBrains Mono
├── docs/ M1 and M2 write-ups (md + pdf)
├── process_book.pdf the process book (M3 deliverable)
└── screencast.md the two-minute screencast script
Many small single-purpose modules over god-files. Scenes are presentational and never mutate the data passed to them. Every scene exports one mount(el, data, scrollToScene) returning a { play(), stop() } handle that the scroll controller drives.
Four datasets back the story, three public and one hand-authored and cited.
| Source | Used for | License |
|---|---|---|
| EIA Refinery Yield (1993–2025) | Scene 3 composition, Scene 4 Sankey scrubber | US Gov, public domain |
| EIA Short-Term Energy Outlook (May 2026) | Scene 6 counter rate, Scene 7 forecast | US Gov, public domain |
| Our World in Data, Oil consumption by country | Scene 6 per-country bar | CC-BY 4.0 |
product_sector_mapping.json (hand-curated) |
Scenes 4, 5 fraction-to-sector weights | Editorial, cited per row |
Raw sources go through Python notebooks to data/processed/. figures.json is imported into src/lib/constants.js, so every headline number traces back to a source file. Update the notebooks, never the constants.
To regenerate (only if data/raw/ changes):
python3 notebooks/01_explore.py # EIA yield + Our World in Data consumption
python3 notebooks/02_steo.py # EIA Short-Term Energy Outlook
python3 notebooks/03_figures.py # derives figures.jsonHonest caveats (also stated on-site): the fraction-to-sector weights are an editorial model, not a measurement; objects whose path runs via natural-gas liquids carry a tooltip caveat; two demand bases coexist by design (89 Mb/d for refinery history, ~104 Mb/d for the live counter), stated wherever load-bearing.
- Tufte hygiene: y-axes start at 0, categories ordered by value or meaning, no 3D, no drop shadows, no decorative gradients.
- Colour as meaning: one amber accent highlights the series that matters, the rest go grey. The fraction palette encodes boiling point and never relies on hue alone.
- Motion stays on
transformandopacity, andprefers-reduced-motionsnaps straight to the final state. - Semantics: interactive SVGs carry
role="img"with a label, tab order follows reading order, focus rings use the accent token.
This repository is the Milestone 3 deliverable. The earlier write-ups are kept in docs/ for the full evolution: docs/milestone1.md (dataset, problematic, EDA) and docs/milestone2.md (narrative spine and sketches). The M3 process book is process_book.pdf at the repo root.
Built for EPFL COM-480 Data Visualization. Data from the U.S. EIA, the Energy Institute Statistical Review 2025 via Our World in Data, the IEA, and OPEC. Type set in Fraunces, Inter, and JetBrains Mono.