I Tried Streamlit for the First Time and Built an MLB Bat Tracking Dashboard

Published: (February 11, 2026 at 11:49 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

What I Built

I built a web dashboard to visualize MLB bat tracking data from Baseball Savant.

👉 MLB Bat Tracking Dashboard

Dashboard top page

The dashboard has five tabs:

TabWhat it shows
LeaderboardRankings by Bat Speed, Attack Angle, and more
Player ComparisonRadar & bar charts comparing up to 6 players
WBC Country StrengthBatting & pitching scores for all 20 WBC 2026 nations
Team Lineup BuilderBat‑tracking metrics for each MLB team’s 9‑man lineup
Monthly TrendMonth‑by‑month bat speed trend for any player

It also supports English/Japanese language switching.

Data Source

I used my own open‑source library savant‑extras to fetch the data.

pip install savant-extras
from savant_extras import bat_tracking, bat_tracking_monthly

# Batter bat tracking data for 2025 season
df = bat_tracking(year=2025, player_type="batter")

# Monthly breakdown
df_monthly = bat_tracking_monthly(year=2025)
  • GitHub:
  • PyPI:

Why I Chose Streamlit

I wanted to share my data analysis in a visual, accessible format. Streamlit appealed for three reasons:

  • Pure Python – no HTML, CSS, or JavaScript needed.
  • Free hosting on Streamlit Community Cloud (just connect your GitHub repo).
  • One‑line UI components – e.g., st.selectbox(), st.slider().

Example: adding a dropdown to the sidebar.

import streamlit as st

year = st.sidebar.selectbox("Season", [2024, 2025], index=1)
player_type = st.sidebar.selectbox("Player type", ["batter", "pitcher"])

Key Implementation Points

Caching API Calls

Fetching data on every interaction would be too slow. @st.cache_data caches results, returning instantly for repeated calls with the same arguments.

@st.cache_data(ttl=3600)
def load_bat_data(year: int, player_type: str):
    return bat_tracking(year=year, player_type=player_type)

Sharing Data Across Tabs

Streamlit reruns the script on each interaction. Use st.session_state to keep data loaded after a button press.

if load_btn:
    st.session_state["df_raw"] = load_bat_data(year, player_type)

if "df_raw" in st.session_state:
    df = st.session_state["df_raw"]

Japanese Font Support

To render Japanese labels in Matplotlib charts, I used matplotlib-fontja.

import matplotlib_fontja  # noqa: F401  ← importing enables Japanese fonts

A Problem I Hit: japanize_matplotlib on Python 3.13

Deploying to Streamlit Community Cloud raised an error because distutils was removed in Python 3.13.

File "japanize_matplotlib/japanize_matplotlib.py", line 5, in 
    from distutils.version import LooseVersion
ModuleNotFoundError

The fix was to replace japanize-matplotlib with matplotlib-fontja.

- japanize-matplotlib>=1.1
+ matplotlib-fontja

Data Caveats

  • Only MLB‑rostered players are included; NPB and minor‑league players are omitted.
  • Name matching limitations – spelling variations or shared names may not match correctly.
  • WBC 2026 scores are provisional – rosters are based on Baseball America’s February 2025 projections and may differ from actual rosters.
  • Bat‑tracking accuracy depends on Baseball Savant’s data quality.

Treat the results as a reference, not a definitive source.

Summary

  • Used savant‑extras (my own OSS library) for data fetching.
  • Built a 5‑tab dashboard with Streamlit.
  • Deployed for free on Streamlit Community Cloud.

Streamlit was easier than expected. Tabs with st.tabs(), accordions with st.expander(), and column layouts with st.columns() covered most of the needed functionality. If you want to turn your data analysis into something shareable, Streamlit is a great option.

  • App:
  • savant‑extras (PyPI):
  • savant‑extras (GitHub):
  • Baseball Savant:
0 views
Back to Blog

Related posts

Read more »

Python OOP for Java Developers

Converting a Simple Java Square Class to Python Below is a step‑by‑step conversion of a basic Square class written in Java into an equivalent Python implementa...