DEV Community

YMori
YMori

Posted on Edited on

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

What I Built

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

👉 MLB Bat Tracking Dashboard

Dashboard top page

It has 5 tabs:

Tab What it shows
Leaderboard Rankings by Bat Speed, Attack Angle, and more
Player Comparison Radar & bar charts comparing up to 6 players
WBC Country Strength Batting & pitching scores for all 20 WBC 2026 nations
Team Lineup Builder Bat tracking metrics for each MLB team's 9-man lineup
Monthly Trend Month-by-month bat speed trend for any player

It also supports English/Japanese language switching.

Data Source: savant-extras

I used my own OSS library savant-extras to fetch the data.

pip install savant-extras
Enter fullscreen mode Exit fullscreen mode

It's a Python library for fetching bat tracking data from Baseball Savant. I built it because the existing pybaseball library didn't support date range filtering for this endpoint.

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)
Enter fullscreen mode Exit fullscreen mode

Why I Chose Streamlit

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

  • Pure Python — no HTML, CSS, or JavaScript needed
  • Free hosting on Streamlit Community Cloud (just connect your GitHub repo)
  • UI components in one line — st.selectbox(), st.slider(), done

For example, this is all you need to add a dropdown to your sidebar:

import streamlit as st

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

Key Implementation Points

@st.cache_data to avoid repeated API calls

Fetching data every time a user interacts with the page would be too slow. @st.cache_data caches the result so repeated calls with the same arguments return instantly.

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

session_state to share data across tabs

Streamlit reruns the entire script on every user interaction. To keep data loaded after pressing a button, use st.session_state:

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"]
Enter fullscreen mode Exit fullscreen mode

matplotlib-fontja for Japanese font support

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

import matplotlib_fontja  # noqa: F401  ← just importing this enables Japanese fonts
Enter fullscreen mode Exit fullscreen mode

A Problem I Hit: japanize_matplotlib Broke on Python 3.13

After deploying to Streamlit Community Cloud, I got this error:

File "japanize_matplotlib/japanize_matplotlib.py", line 5, in <module>
    from distutils.version import LooseVersion
ModuleNotFoundError
Enter fullscreen mode Exit fullscreen mode

distutils was deprecated in Python 3.12 and fully removed in 3.13. I tried pinning the Python version with a runtime.txt file (python-3.11), but it didn't work — Streamlit Cloud still used 3.13.

The fix was simple: switch to matplotlib-fontja, which doesn't use distutils.

- japanize-matplotlib>=1.1
+ matplotlib-fontja
Enter fullscreen mode Exit fullscreen mode

Data Caveats

A few things to keep in mind about this dashboard:

  • Only MLB-rostered players are included. NPB and minor league players are not
  • Name matching has 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

Please treat the results as a reference, not a definitive source.

Things I Added Later

Mobile support

The sidebar was always open on mobile and got in the way. Fixed with two changes:

st.set_page_config(
    ...,
    initial_sidebar_state="collapsed",  # start closed on mobile
)

st.markdown("""
<style>
@media (max-width: 768px) {
    .block-container { padding: 1rem 0.5rem !important; }
    [data-testid="column"] { min-width: 45% !important; flex: 1 1 45% !important; }
    [data-testid="stHorizontalBlock"] { flex-wrap: wrap !important; }
}
</style>
""", unsafe_allow_html=True)
Enter fullscreen mode Exit fullscreen mode

Also added use_container_width=True to every st.pyplot() call. Without this, charts don't resize to the container width and overflow on small screens.

Auto-load on first visit

Originally you had to open the sidebar and click "Load Data". Changed it so data loads automatically on first visit:

# Before: only load when button is clicked
if load_btn:
    ...

# After: also auto-load on first visit
if load_btn or "df_raw" not in st.session_state:
    ...
Enter fullscreen mode Exit fullscreen mode

Since @st.cache_data caches the result, subsequent tab switches and filter changes use the cache — no waiting after the first load.

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 I expected. Tabs with st.tabs(), accordions with st.expander(), column layouts with st.columns() — learning just those three got me most of the way there.

If you want to turn your data analysis into something you can actually share, Streamlit is a great option.

Links

Top comments (5)

Collapse
 
maame-codes profile image
Maame Afua A. P. Fordjour •

How long did it take you to get the hang of Streamlit compared to other tools you've used?

Collapse
 
yasumorishima profile image
YMori •

Honestly, Streamlit was surprisingly quick to pick up — I had a basic interactive chart running within a day. The hardest part wasn't Streamlit itself but figuring out the data side (filtering, state management, etc.). If you're already comfortable with pandas, I think you can build something useful in a weekend. Definitely worth trying!

Collapse
 
maame-codes profile image
Maame Afua A. P. Fordjour •

Thank you for the feedback will keep that in mind :)

Thread Thread
 
yasumorishima profile image
YMori •

Good luck with your projects! 😊

Thread Thread
 
maame-codes profile image
Maame Afua A. P. Fordjour •

Thank you YMori!