The idea
A Streamlit app is often "a few widgets, then a chart that reacts to them". Streamlit gives you two ways to draw:
- Built-in charts (
st.line_chart,st.bar_chart,st.area_chart,st.scatter_chart): one line, no configuration, good for quick looks. st.plotly_chart(fig): any Plotly figure, with everything you learned in the Plotly track: labels, colours, hover, annotations.
Use built-ins while exploring; use Plotly for anything a stakeholder will see.
Built-in charts
They take a DataFrame and draw numeric columns against the index (or against x= if you name one):
import streamlit as st
import pandas as pd
sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]
monthly = (completed.assign(month=completed["order_date"].str[:7])
.groupby("month")["revenue"].sum())
st.subheader("Completed revenue per month")
st.line_chart(monthly)
monthly is a Series indexed by month, so the months become the x-axis. With a regular DataFrame, name the columns:
import streamlit as st
import pandas as pd
sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]
by_category = completed.groupby("category", as_index=False)["revenue"].sum()
st.bar_chart(by_category, x="category", y="revenue")
Several numeric columns become several series. A wide table (one column per channel) draws one line per channel:
import streamlit as st
import pandas as pd
sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]
wide = (completed.assign(month=completed["order_date"].str[:7])
.pivot_table(index="month", columns="channel", values="revenue", aggfunc="sum", fill_value=0))
st.line_chart(wide)
st.dataframe(wide)
Note the shapes: Streamlit's built-ins like wide data (a column per series), while Plotly Express likes long data (a channel column plus color="channel"). pivot_table and melt convert between them.
Plotly charts
For control over titles, labels, hover and colours, build a Plotly figure and hand it over:
import streamlit as st
import pandas as pd
import plotly.express as px
sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]
by_category = (completed.groupby("category", as_index=False)["revenue"].sum()
.sort_values("revenue"))
fig = px.bar(by_category, x="revenue", y="category", orientation="h", text_auto=".2s",
title="Electronics leads completed revenue",
labels={"revenue": "Revenue (Rp)", "category": ""})
st.plotly_chart(fig, use_container_width=True)
use_container_width=True stretches the chart to the column or page width, which is what you want in layouts with columns. Hover, zoom and download all work as in a notebook.
Your chart functions from the Plotly track (ranking_bar, trend_line) plug straight in: st.plotly_chart(ranking_bar(...)). That's the payoff of returning figures instead of showing them.
Widgets drive charts
The rerun model makes interactive charts trivial: read a widget, filter, draw.
import streamlit as st
import pandas as pd
import plotly.express as px
sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"].assign(month=lambda d: d["order_date"].str[:7])
metric = st.radio("Measure", ["revenue", "quantity"], horizontal=True)
channel = st.selectbox("Channel", ["all", "web", "app"])
rows = completed if channel == "all" else completed[completed["channel"] == channel]
monthly = rows.groupby("month", as_index=False)[metric].sum()
fig = px.line(monthly, x="month", y=metric, markers=True,
title=f"Monthly {metric} ({channel})")
st.plotly_chart(fig, use_container_width=True)
Every change reruns the script, rebuilding monthly and the figure from the new inputs. No update functions anywhere.
Tables that read well
st.dataframe takes a few options that make tables easier to read:
import streamlit as st
import pandas as pd
sales = pd.read_csv("sales.csv")
completed = sales[sales["status"] == "completed"]
top = (completed.groupby("product", as_index=False)
.agg(units=("quantity", "sum"), revenue=("revenue", "sum"))
.sort_values("revenue", ascending=False)
.head(5))
st.dataframe(top, hide_index=True, use_container_width=True)
Prepare the table in pandas (aggregate, sort, rename columns to friendly names) and let st.dataframe display it. Formatting in pandas (.round(), renaming columns) keeps the display code minimal.
Why this matters for clean code
The same separation as in the Plotly track applies: data prep → figure → display. Keep each step in its own few lines (or functions), so the Streamlit part is just st.plotly_chart(fig). When the chart looks wrong, you can check the DataFrame and the figure separately, without clicking through the app.
Recap
st.line_chart,st.bar_chart,st.area_chart,st.scatter_chartdraw numeric columns; usex=/y=or the index.- Built-ins like wide data; Plotly Express likes long data; convert with
pivot_table/melt. st.plotly_chart(fig, use_container_width=True)shows any Plotly figure.- Widgets → filter → figure →
st.plotly_chart; the rerun model keeps it in sync. - Prepare tables in pandas; display with
st.dataframe(..., hide_index=True).