A dashboard does not always need a Python server. For this project, I built one focused interactive view of daily solar radiation and air temperature in Tacna, Peru, then published it as a standalone page with GitHub Actions.
Live demo: https://andre-carbajal.github.io/tacna-solar-climate-explorer/
Source: https://github.com/andre-carbajal/tacna-solar-climate-explorer
Workflow: https://github.com/andre-carbajal/tacna-solar-climate-explorer/actions
The aim was intentionally narrower than building the same dashboard in every visualization framework: use a real, locally relevant question to decide which tool fits, and make the resulting artifact reproducible and public.
The question and the data
Tacna sits on Peru’s dry southern coast. I wanted a quick way to inspect two signals together over time: how daily incoming shortwave solar radiation changes through the year, and how near-surface air temperature moves alongside it.
The app uses NASA POWER’s Daily Point API for coordinates 18.0066° S, 70.2463° W, from January 1, 2015 through December 31, 2024. It requests ALLSKY_SFC_SW_DWN and T2M in metric units. The dataset contains 3,653 daily rows. NASA POWER provides gridded/reanalysis-derived estimates; these are not measurements from a weather station at the selected point, nor a bankable photovoltaic-yield forecast. That distinction belongs next to the chart, not buried in a footnote.
Sources: NASA POWER API · NASA POWER
Why Bokeh—and why not a server framework?
The interaction needed here is small: choose one of two measures and a year, then compare daily values with a monthly mean. Bokeh can serialize plots, widgets, and data into standalone HTML. Its CustomJS callbacks run in the browser, so the published dashboard does not need a Python process, a database, or a live API key.
A tracked CSV snapshot makes the page deterministic: rebuilding the same commit does not depend on NASA’s API being available at build time. The trade-off is that data refreshes are deliberate; update the snapshot and metadata when you want newer observations.
Other tools solve different parts of the problem:
- Seaborn is excellent for statistical charts in notebooks and reports, but it is not the dashboard runtime. Use it to explore distributions before choosing what should become interactive.
- Bokeh fits this requirement because its standalone document supports browser-side interactivity without a Python server.
- Dash and Shiny make more sense when an application needs server callbacks, shared state, or richer data workflows. That power also means hosting and maintaining a running service.
- Streamlit is an approachable way to turn Python scripts into interactive apps, especially for data-centric prototypes. It likewise expects an app runtime on its hosting platform.
This is not a benchmark or universal ranking. It is a deployment decision: for two controls and a bundled dataset, the static artifact removes operational pieces the app does not need.
Build the dashboard
The repository keeps the moving parts visible:
build_dashboard.py # reads CSV and writes index.html
requirements.txt # Bokeh dependency
data/tacna_daily.csv # pinned data snapshot
data/metadata.json # source, units, coordinates, period
.github/workflows/pages.yml
The Python build script loads the CSV, prepares the selected year’s series and monthly averages, and wires both selectors to one CustomJS callback. When a control changes, the callback filters embedded arrays and updates the chart data sources. No request goes back to Python after the page loads.
To reproduce locally:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python build_dashboard.py
python -m http.server 8000
Open http://localhost:8000. The generated index.html is a Bokeh document that loads BokehJS from its CDN; app data itself is embedded in the HTML.
Automate the build and deployment
A GitHub Actions workflow builds on every pull request, checks that the generated page exists and contains the standalone plot, and uploads it as a Pages artifact. On pushes to main (or a manual dispatch), a second job deploys that artifact to GitHub Pages. The workflow grants the Pages and OIDC permissions needed for deployment and keeps contents permission read-only.
The deployment pipeline is part of the example, not a hand-waved last step: inspect the workflow runs. The public result is hosted at GitHub Pages, a free static hosting option for a public repository. There is no container, server instance, cloud credential, or Azure bill to maintain.
What this approach gives up
A static page cannot securely hold private data, run Python callbacks after loading, or provide shared user sessions. The complete dataset is public and bundled into the artifact. For larger or confidential datasets—or server-side filtering, authentication, and live API integration—I would switch to Dash, Shiny, Streamlit, or a Bokeh server and deploy to a service designed to run an application process.
For this small public dataset, fewer runtime components are a feature. There is one published HTML file, one committed data snapshot, and one automated path from commit to site.
Try it and adapt it
Fork the repository, replace the CSV with a dataset you are allowed to publish, update data/metadata.json, and push to main. The page will rebuild and deploy. The project code is MIT-licensed; check the source provider’s terms and attribution requirements before reusing data.
Disclosure: This article was prepared with AI assistance and reviewed against the public repository, workflow, and deployed page.
Top comments (0)