Metal Globe
Live usableI put 195,679 metal bands on a 3D globe, filterable by country, genre and decade.
195,679 metal bands from 153 countries on a navigable 3D globe: filter by country, genre and decade, with country sheets and a treemap that make an otherwise unreadable catalog legible.
Technology
- Frontend
- Data viz
- Scraping
Sector
- Music & culture
Problem
Encyclopaedia Metallum has the data — nearly 200,000 bands — but its own site is a flat, unfilterable list. There is no way to see where a scene comes from, how a country's metal output compares to its neighbors, or how genres shift across regions without scrolling through hundreds of paginated tables.
Approach
A custom scraper over Encyclopaedia Metallum, plus a data pipeline and a framework-free visualization built to survive both a slow connection and a demanding 3D scene at once.
Metal Archives has no public API, and as of this build its pages sit behind a Cloudflare
Turnstile challenge that blocks requests and headless browsers alike. The working path was
Scrapling on top of Camoufox (a stealth Firefox build): a human solves the challenge once, and
that same warmed-up browser session then sweeps every country page without being challenged
again — a new browser launch gets a new fingerprint and gets blocked again, so the scrape runs
as one continuous local session rather than a distributed job.
The scraped bands, genres and city-level metadata feed a Python pipeline that resolves genre taxonomy, geocodes countries, and assembles a single JSON the frontend consumes. The globe itself is hand-written WebGL through three.js: real TopoJSON borders projected onto a sphere, per-city pins, and level-of-detail that swaps in named countries and treemap panels as the camera zooms from planetary to continental to country view — no chart or globe library beyond three.js and ECharts for the side panels.
The original 4,408-line index.html shipped everything — markup, styles and app logic — in one
file. It now splits cleanly into index.html, styles.css, data.js and app.js, with flags
lazy-loaded per country instead of bundled up front.
Result
- bands indexed
- 195,679
- countries covered
- 153
- main HTML lines after split
- 496
web/data/bands_index.json
web/data/atlas_data.json
web/index.html
Standards
| Software quality (ISO/IEC 25010) | Not run through a formal ISO/IEC 25010 checklist. Maintainability was addressed directly: the 4,408-line monolith was split into index.html + styles.css + data.js + app.js without changing a pixel. |
|---|---|
| Web performance (Core Web Vitals) | not measured |
| Accessibility (WCAG 2.2) | not measured |
| Research software (FAIR4RS) | n/a — not research software |
| Security hygiene | No secrets in the repo. Browser session state under data/cache/*_state.json is gitignored before the repo goes public. |
| Reproducibility | The scraping pipeline is fully scripted (scraper/*.py), but Cloudflare Turnstile needs one human click per session, so a clean-clone rerun is not push-button end to end. |
| Benchmark methodology | no benchmark claimed |
| Data / model documentation | n/a |
| Versioning & change log | No SemVer or CHANGELOG; plain commit history on GitHub. |
What I'd do next
- Split app.js (2,959 lines) the same way index.html already was, into feature modules.
- Add the genre × country filter (currently only one-directional) proposed as the next UI iteration.
- Move the scraping session past its one manual Cloudflare click, or document it as a permanent constraint instead of a TODO.