Skip to content
CASE · 2025–2026

F1 Dashboard

Role
Side project — solo
Period
2025–2026
Stack
Python · FastF1 · Streamlit
Featured
Yes
3
Dashboard pages
2
Data sources
22+
Seasons of data

Why this project

F1 produces more data per race than most companies generate in a month. Lap-level telemetry, sector splits, tyre compounds, pit windows — and almost all of it is publicly available via FastF1. I wanted a side project that was genuinely data-rich and had nothing to do with client work. This was it.

What it does

Lap Time Explorer — Pick a season, Grand Prix, and session type. See every driver’s lap progression across the race, colored by tyre compound. Fastest lap per driver in a ranked table. Pace degradation as rubber wears becomes visible.

Qualifying Deep Dive — Official Q1/Q2/Q3 best times, gap-to-pole bar chart, full elimination table, and a radar chart comparing any two drivers across all three rounds.

Season Dashboard — Full championship standings, points progression across every round, constructor battle, race winners by round. Works for any season from 2003 to 2025.

Two data sources, one decision

Two sources with different access patterns:

FastF1 wraps Formula 1’s live timing data and returns pandas DataFrames. It covers lap-level detail from 2018 onward. The catch: first load takes 20–30 seconds while it fetches and caches. After that, instant.

Jolpica API (the open-source successor to the deprecated Ergast database) covers season-level statistics all the way back to 1950. No wrapper, no API key — raw requests calls and JSON parsing.

The rule: lap-level analysis → FastF1. Season-level statistics → Jolpica. Trying to use FastF1’s built-in Ergast wrapper hit a 403 Forbidden immediately — the wrapper was pointing at a deprecated endpoint. Switching to direct Jolpica calls took ten minutes.

Engineering decisions

Custom F1 font via base64. The official Formula 1 typeface is available as a .ttf file, but Streamlit doesn’t serve static assets the way a normal web server does. Solution: encode the font files as base64 and embed them directly in a @font-face CSS block as data URLs. One inject_font() function called once per page.

Pagination on the Jolpica API. The API returns max 100 rows per call. An F1 season has ~24 races × 20 drivers = ~480 rows. Without pagination, standings silently come out wrong. Standard offset loop reading the total field from every response.

Plotly color handling. FastF1 returns team colors as 6-digit hex strings. Plotly doesn’t accept 8-digit hex for transparency — it needs rgba(). One small helper function, used everywhere.

What’s next

Page 4 is telemetry: speed traces for two drivers on the same lap, throttle and brake heatmaps, a track map colored by speed. This requires session.load(telemetry=True) and working with GPS coordinates — a step up in complexity from Pages 1–3.

After that, deployment on Streamlit Community Cloud and potentially a BigQuery backend to pre-cache sessions and make load times instant.

Full write-up of what went wrong (and right) while building this: read the post →

Full stack
  • Python
  • FastF1
  • Streamlit
  • Plotly
  • Pandas
  • Jolpica API