Data storytelling is no longer about showing a few colorful charts, it’s about creating experiences.
Modern audiences don’t just want to see numbers; they want to explore, filter, and understand them interactively.
Static dashboards or PowerPoint slides can show metrics, but they fail to answer the question every stakeholder cares about:
“What does this data mean for me?”
That’s where tools like Streamlit and Plotly redefine what data storytelling can be. They turn passive data presentations into interactive narratives, allowing users to ask their own questions, explore relationships, and uncover insights dynamically.
In this article, I’ll walk you through how I built a data storytelling dashboard, a full-scale interactive app that transforms a simple e-commerce dataset into a living, explorable story.
Streamlit and Plotly are a dream combination for data analysts and data scientists.
| Tool | Role in the Storytelling Stack |
|---|---|
| Streamlit | Frontend & interaction layer: creates web apps directly from Python scripts. |
| Plotly | Visualization engine: renders fully interactive charts (hover, zoom, filter). |
| Pandas / NumPy | Data processing, cleaning, feature engineering. |
| Scikit-learn / Statsmodels (optional) | Advanced analytics: clustering, forecasting. |
Together, they form a narrative pipeline:
Data → Transformation → Visualization → Interaction → Insight → Action
Unlike Power BI or Tableau, you control the logic end-to-end.
It’s not just a dashboard, it’s an intelligent interface that grows with your story.
To demonstrate, I created a synthetic e-commerce dataset containing:
- 4,000+ orders
- 1,600+ unique customers
- 10 countries and 5 product categories
- Multiple sales channels: Web, Mobile App, Retail Store, Marketplace
Each record includes variables like:
order_id, order_date, customer_id, country, city,
channel, category, subcategory, quantity, unit_price, discount, revenue, cost
From this, we can derive core business KPIs:
- Revenue = unit_price × quantity × (1 - discount)
- Profit = revenue - cost
- Margin% = profit ÷ revenue
- AOV (Average Order Value) = revenue per order
The goal was simple:
Create a dashboard where users could see the big picture but also drill down into specifics, by time, country, category, or channel.
A good dashboard has a narrative flow, just like a story:
- Overview | What’s happening overall?
- Trends | How is performance changing over time?
- Breakdowns | Where do results differ by category, product, or region?
- Behavior | Who are the customers driving those numbers?
- Takeaway | What actions or patterns emerge?
Each section uses a different combination of Plotly visualizations to tell that part of the story.
Install dependencies:
pip install streamlit plotly pandas numpyCreate the basic project structure:
data_storytelling_dashboard/
├── data/
│ └── orders.csv
├── app/
│ ├── app.py
│ └── utils/
│ └── data_utils.py
└── requirements.txt
A storytelling dashboard isn’t about dumping all data, it’s about guiding users through controlled exploration.
In Streamlit, we add sidebar filters like this:
import streamlit as st
import pandas as pd
df = pd.read_csv("data/orders.csv", parse_dates=["order_date"])
st.sidebar.header("Filters")
countries = st.sidebar.multiselect("Country", sorted(df["country"].unique()))
channels = st.sidebar.multiselect("Channel", sorted(df["channel"].unique()))
categories = st.sidebar.multiselect("Category", sorted(df["category"].unique()))
mask = (df["country"].isin(countries) if countries else True) & (df["channel"].isin(channels) if channels else True) & (df["category"].isin(categories) if categories else True)
filtered_df = df[mask]Each user interaction redefines the context, they become the storyteller.
Dashboards must start with quick-glance insights, the numbers that tell the story upfront:
total_revenue = filtered_df["revenue"].sum()
total_profit = filtered_df["profit"].sum()
total_orders = filtered_df["order_id"].nunique()
aov = filtered_df.groupby("order_id")["revenue"].sum().mean()
margin = (total_profit / total_revenue) * 100
col1, col2, col3 = st.columns(3)
col1.metric("Revenue", f"${total_revenue:,.0f}")
col2.metric("Profit", f"${total_profit:,.0f}")
col3.metric("AOV", f"${aov:,.2f}")
st.metric("Margin", f"{margin:.2f}%")This acts like the headline of a news article, a summary before the deep dive.
Trends reveal change, and change drives stories.
import plotly.express as px
filtered_df["month"] = filtered_df["order_date"].dt.to_period("M").dt.to_timestamp()
monthly = filtered_df.groupby("month")[["revenue", "profit"]].sum().reset_index()
fig = px.line(monthly, x="month", y=["revenue", "profit"],
labels={"value": "Amount ($)", "month": "Month"},
title="Monthly Revenue and Profit Trends")
st.plotly_chart(fig, use_container_width=True)Users can hover over any month to see exact values or zoom in to investigate seasonal peaks.
That zooming in moment, when a manager discovers why Q3 revenue jumped, is storytelling in motion.
Understanding which products drive performance adds texture to the story.
cat_rev = filtered_df.groupby("category")["revenue"].sum().reset_index()
fig_cat = px.bar(cat_rev, x="category", y="revenue", title="Revenue by Category")
st.plotly_chart(fig_cat, use_container_width=True)Users might then ask:
“Why did Electronics outperform Home last quarter?”
They filter by quarter or country and the narrative deepens.
geo = filtered_df.groupby(["country","city"])["revenue"].sum().reset_index()
fig_geo = px.treemap(geo, path=["country","city"], values="revenue",
title="Revenue by Geography (Country → City)")
st.plotly_chart(fig_geo, use_container_width=True)The treemap visually tells a hierarchy, countries as chapters, cities as paragraphs.
It’s a spatial story, turning geography into insight.
Use RFM segmentation (Recency, Frequency, Monetary value) to classify customers:
from datetime import datetime
snapshot_date = filtered_df["order_date"].max() + pd.Timedelta(days=1)
rfm = filtered_df.groupby("customer_id").agg({
"order_date": lambda x: (snapshot_date - x.max()).days,
"order_id": "nunique",
"revenue": "sum"
}).rename(columns={"order_date": "Recency", "order_id": "Frequency", "revenue": "Monetary"})
rfm["Segment"] = pd.qcut(rfm["Monetary"], q=3, labels=["Low", "Mid", "High"])
fig_rfm = px.bar(rfm["Segment"].value_counts().reset_index(),
x="index", y="Segment", title="Customers by Value Segment")
st.plotly_chart(fig_rfm, use_container_width=True)This shows who drives your business.
It’s not just about data, it’s about characters in your story: loyal customers, new buyers, and inactive ones.
Cohort analysis visualizes how long customers stay engaged.
filtered_df["order_month"] = filtered_df["order_date"].dt.to_period("M")
first_purchase = filtered_df.groupby("customer_id")["order_month"].min()
filtered_df["CohortMonth"] = filtered_df["customer_id"].map(first_purchase)
filtered_df["CohortIndex"] = (filtered_df["order_month"].dt.year - filtered_df["CohortMonth"].dt.year) * 12 + (filtered_df["order_month"].dt.month - filtered_df["CohortMonth"].dt.month) + 1
cohort = filtered_df.groupby(["CohortMonth", "CohortIndex"])["customer_id"].nunique().reset_index()
cohort_pivot = cohort.pivot(index="CohortMonth", columns="CohortIndex", values="customer_id")
fig_cohort = px.imshow(cohort_pivot, color_continuous_scale="Greens", aspect="auto",
title="Customer Retention by Cohort")
st.plotly_chart(fig_cohort, use_container_width=True)Each cohort row becomes a chapter in the customer story you can literally see how long users stay active after their first purchase.
A well-designed dashboard is not a wall of charts, it’s a guided tour.
Show the most important story first (KPIs), then move to supporting details.
Arrange visuals in a sequence: Overview → Trends → Segments → Details.
Filters, sliders, and dropdowns invite curiosity, they make users think.
Design with empathy: colors, spacing, and icons all affect readability and mood.
Once you have a working interactive dashboard, the possibilities expand:
- Forecasting: Add a Prophet model to predict future revenue.
- Anomaly Detection: Flag sudden drops in KPIs automatically.
- Natural Language Narratives: Use LLMs to auto-generate summaries like:
“Revenue grew by 18% MoM, led by Electronics and India.”
- Custom Exports: Allow users to download filtered datasets as CSV or PDF.
These transform your dashboard into a decision assistant, not just a report.
You can host your dashboard easily:
| Platform | Description |
|---|---|
| Streamlit Cloud | One-click deployment from GitHub |
| Render / Hugging Face Spaces | Free-tier hosting options |
| Docker + AWS / GCP | For enterprise-grade performance |
Add authentication (streamlit-authenticator) if you want private dashboards for internal teams.
- Interactivity drives insight. People engage more when they can explore.
- Context beats complexity. Always show comparisons, not isolated metrics.
- Every chart should answer a question. If it doesn’t, it’s noise.
- Storytelling = design + psychology + analytics.
- Build for clarity, not for impressiveness. Less is often more.
“Charts inform, but interactions enlighten.”
With Streamlit and Plotly, data storytelling evolves from passive reporting to active exploration.
Users don’t just see the data, they feel it, question it, and remember it.
That’s the essence of interactive storytelling, beyond charts.