Interactive world map showing how a news event spreads across countries over time and how each country's media feels about it, using GDELT data.
- User picks a topic (e.g. "Elon Musk", "Brexit")
- Map shows, day by day, how coverage spreads to different countries
- Color = sentiment (positive/negative tone) of that country's media about the topic
- Timeline has event markers at key moments that shift perception
- The unit of analysis is: how does country X's media feel about topic Y on day Z
- App: Next.js (App Router) + TypeScript + Tailwind + MapLibre GL + react-map-gl
- Data layer: DuckDB (Node.js) reading parquet files via API routes
- Pipeline: Python + Google BigQuery (GDELT source)
- Deployment: Run locally with
npm run dev(parquet files required inapp/public/data/)
dataviz-project/
├── app/ # Next.js web app
│ ├── public/data/ # Parquet files served to the app (NOT in git — copy manually)
│ └── src/
│ ├── app/
│ │ └── api/sentiment/ # /sentiment, /dates, /timeline, /era-countries, /country-timeline
│ ├── components/
│ ├── lib/
│ │ └── queries.ts # hyparquet reads, all data access functions
│ └── types/
└── data-pipeline/ # Python pipeline (BigQuery → parquet)
├── src/
│ ├── config.py # GCP project ID, output dir
│ ├── fetch.py # BigQuery query + raw data cache
│ ├── transform.py # Source country mapping, daily aggregation
│ ├── export.py # Write parquet to output/
│ └── domains.py # TLD + domain → ISO3 country mapping
├── output/ # Pipeline staging area (gitignored)
│ └── cache/ # Raw BigQuery results cache (gitignored)
├── eda.py # Quick EDA charts for any output parquet
├── run.py # CLI entry point
└── requirements.txt
GDELT (BigQuery)
↓ data-pipeline/src/fetch.py — keywords + date range query, cached locally
↓ data-pipeline/src/transform.py — source country via domain mapping, daily aggregation
↓ data-pipeline/output/ — staging parquet (gitignored)
↓ cp output/<topic>.parquet app/public/data/ — manual copy step
↓ app/src/lib/db.ts — DuckDB reads parquet at runtime
↓ app/src/app/api/sentiment/ — API routes serve per-date data
↓ React hooks → Map component
| Column | Type | Description |
|---|---|---|
date |
Date | Day (e.g. 2022-02-24) |
country_iso3 |
String | ISO 3166-1 alpha-3 (e.g. GBR, DEU, UKR) |
avg_tone |
Float64 | Mean sentiment that day. Negative = negative coverage |
article_count |
UInt32 | Unique articles from that country |
App (local dev with real data):
cd app && npm install && npm run dev
# Open http://localhost:3000API routes require the parquet files to be present in
app/public/data/. They are not in git — copy them manually (see below).
Copy parquet files after running the pipeline:
cp data-pipeline/output/<topic>.parquet app/public/data/Pipeline:
cd data-pipeline
source .venv/bin/activate
# Check query cost first
python run.py --topic "elon-musk" --keywords "elon musk" \
--start 2015-01 --end 2026-05 \
--credentials dataviz-490213-eb4214bb9d4b.json --dry-run
# Run for real
python run.py --topic "elon-musk" --keywords "elon musk" \
--start 2015-01 --end 2026-05 \
--credentials dataviz-490213-eb4214bb9d4b.json
# Copy to app when ready
cp output/elon-musk-2015-01-2026-05.parquet ../app/public/data/EDA on any parquet:
cd data-pipeline && source .venv/bin/activate
python eda.py output/elon-musk-2015-01-2026-05.parquet- Run the pipeline to generate a parquet in
data-pipeline/output/ - Copy the parquet to
app/public/data/ - Add one entry to the
TOPICSarray inapp/src/app/page.tsx
| File | Date range | Keywords |
|---|---|---|
elon-musk-2015-01-2026-05.parquet |
Jan 2015 – May 2026 | elon musk |
EPFL COM-480 Data Visualization — Milestone 3 due 2026-05-29