Plotly Express: Interactive Scatter, Line, Bar, Histogram, Pie, Sunburst, Heatmaps, 3D Charts, Facets, and Subplots
Plotly helps you build interactive charts in Python.
Matplotlib is excellent when you want full control over static figures. Plotly is excellent when you want charts that users can hover, zoom, pan, filter, and inspect in a browser or notebook.
In this guide, you will learn Plotly Express through practical examples:
- interactive scatter plots
- color, size, hover labels, and tooltips
- log-scaled axes
- animated charts
- line charts
- single, grouped, and stacked bar charts
- histograms
- pie and donut charts
- sunburst and treemap charts
- heatmaps with
px.imshow - 3D scatter plots
- scatter matrix plots
- facet grids
- mixed dashboards with
make_subplots
The examples use original synthetic datasets, not copied notebook, course, sports, or public tutorial data.
Files Used In This Guide
Use these CSV files:
plotly_country_metrics.csvplotly_cafe_orders.csv
Place them in the same folder as your notebook or script.
If you keep them in a data/ folder, change the paths like this:
country = pd.read_csv("data/plotly_country_metrics.csv")
orders = pd.read_csv("data/plotly_cafe_orders.csv")What You Will Learn
By the end, you should be able to:
- create a Plotly Express figure from a Pandas DataFrame
- decide when to use Plotly Express and when to use Graph Objects
- add hover labels and custom tooltips
- map columns to color, size, symbol, and animation
- use log axes for values with large ranges
- build grouped and stacked bar charts
- create distribution charts with histograms
- use pie charts only when they are appropriate
- visualize hierarchy with sunburst and treemap charts
- convert pivot tables into heatmaps
- create 3D charts without manual JavaScript
- split a chart into facets
- combine multiple charts with
make_subplots
1. Setup
Install Plotly if needed:
pip install plotly pandasImport the libraries:
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplotsLoad the datasets:
country = pd.read_csv("plotly_country_metrics.csv")
orders = pd.read_csv("plotly_cafe_orders.csv")
orders["date"] = pd.to_datetime(orders["date"])
print(country.head())
print(orders.head())The country table contains fictional country-level metrics across multiple years.
The orders table contains fictional cafe orders across cities, products, channels, and customer types.
2. Plotly Express vs Graph Objects
Plotly has two common APIs:
plotly.expressfor quick charts from tidy DataFramesplotly.graph_objectsfor lower-level figure construction
Most beginner and intermediate work should start with Plotly Express.
Example with Graph Objects:
auralia = country[country["country"] == "Auralia"]
fig = go.Figure()
fig.add_trace(
go.Scatter(
x=auralia["year"],
y=auralia["internet_users_pct"],
mode="lines+markers",
name="Auralia",
)
)
fig.update_layout(
title="Internet Adoption In Auralia",
xaxis_title="Year",
yaxis_title="Internet Users (%)",
)
fig.show()The same chart with Plotly Express:
fig = px.line(
auralia,
x="year",
y="internet_users_pct",
markers=True,
title="Internet Adoption In Auralia",
)
fig.show()Use Plotly Express when your data is already in a DataFrame and you want a fast chart.
Use Graph Objects when you need precise control over traces, subplots, annotations, or custom dashboards.
3. First Interactive Scatter Plot
A scatter plot compares two numeric columns.
This chart compares life expectancy and GDP per person for the latest year:
latest = country[country["year"] == country["year"].max()]
fig = px.scatter(
latest,
x="life_expectancy",
y="gdp_per_capita",
title="Life Expectancy vs GDP Per Person",
)
fig.show()Plotly charts are interactive by default.
You can hover over a point, zoom into a region, pan around the chart, and save the image from the chart toolbar.
4. Color, Size, And Hover Labels
Plotly Express becomes powerful when you map DataFrame columns to visual encodings.
Example:
- x-axis: life expectancy
- y-axis: GDP per person
- color: continent
- size: population
- hover label: country
fig = px.scatter(
latest,
x="life_expectancy",
y="gdp_per_capita",
color="continent",
size="population_millions",
hover_name="country",
title="Country Metrics With Color, Size, And Hover",
labels={
"life_expectancy": "Life Expectancy",
"gdp_per_capita": "GDP Per Person",
"population_millions": "Population (Millions)",
},
)
fig.show()Use hover_name for the main label users should see first.
Use labels to make axis names readable.
5. Custom Hover Data
By default, Plotly shows columns used in the chart.
Use hover_data when you want to add or hide details.
fig = px.scatter(
latest,
x="internet_users_pct",
y="education_index",
color="continent",
size="population_millions",
hover_name="country",
hover_data={
"population_millions": ":.1f",
"gdp_per_capita": ":,.0f",
"carbon_tons_per_person": ":.2f",
},
title="Education And Internet Access",
)
fig.show()The format strings make tooltips easier to scan.
6. Log Axes
Use a log axis when values have a wide range.
GDP per person can vary a lot, so a log-scaled y-axis can make the pattern easier to read.
fig = px.scatter(
latest,
x="life_expectancy",
y="gdp_per_capita",
color="continent",
size="population_millions",
hover_name="country",
log_y=True,
title="Life Expectancy vs GDP Per Person On A Log Scale",
)
fig.show()Do not use log scales silently. Make sure the chart title, axis, or surrounding explanation makes the scale clear.
7. Animated Scatter Plot
Animation helps when the same entities repeat across time.
fig = px.scatter(
country,
x="life_expectancy",
y="gdp_per_capita",
color="continent",
size="population_millions",
hover_name="country",
animation_frame="year",
animation_group="country",
range_x=[65, 84],
range_y=[12000, 62000],
title="Country Development Metrics Over Time",
)
fig.show()animation_group tells Plotly that the same country appears in multiple frames.
Fixed ranges prevent the chart from jumping as the animation moves between years.
8. Line Charts
Line charts are useful for trends.
fig = px.line(
country,
x="year",
y="internet_users_pct",
color="country",
markers=True,
title="Internet Adoption By Country",
)
fig.show()If too many lines make the chart noisy, filter the data first:
focus_countries = ["Auralia", "Borevia", "Eldora", "Harboris"]
focus = country[country["country"].isin(focus_countries)]
fig = px.line(
focus,
x="year",
y="life_expectancy",
color="country",
markers=True,
title="Life Expectancy Trend For Selected Countries",
)
fig.show()9. Bar Charts
A bar chart compares categories.
Create a continent-level summary:
continent_summary = (
latest
.groupby("continent", as_index=False)
.agg(
population_millions=("population_millions", "sum"),
avg_gdp_per_capita=("gdp_per_capita", "mean"),
avg_life_expectancy=("life_expectancy", "mean"),
)
)
fig = px.bar(
continent_summary,
x="continent",
y="population_millions",
color="continent",
text_auto=".1f",
title="Population By Continent Group",
)
fig.show()text_auto writes values on the bars.
Use it when the chart has enough space.
10. Grouped Bar Chart
Grouped bars compare multiple categories side by side.
city_category = (
orders
.groupby(["city", "category"], as_index=False)
.agg(revenue=("total_bill", "sum"))
)
fig = px.bar(
city_category,
x="city",
y="revenue",
color="category",
barmode="group",
text_auto=".0f",
title="Cafe Revenue By City And Category",
)
fig.show()Grouped bars work well when each x-axis category has a small number of groups.
11. Stacked Bar Chart
Stacked bars show composition.
city_channel = (
orders
.groupby(["city", "channel"], as_index=False)
.agg(revenue=("total_bill", "sum"))
)
fig = px.bar(
city_channel,
x="city",
y="revenue",
color="channel",
title="Revenue Composition By Sales Channel",
)
fig.show()The default px.bar behavior stacks bars when multiple rows share the same x-axis category.
Use stacked bars for part-to-whole comparison.
Use grouped bars for exact side-by-side comparison.
12. Log-Scaled Bar Chart
If one category is much larger than the others, a log axis can help.
product_revenue = (
orders
.groupby("product", as_index=False)
.agg(revenue=("total_bill", "sum"))
.sort_values("revenue", ascending=False)
)
fig = px.bar(
product_revenue,
x="product",
y="revenue",
log_y=True,
text_auto=".0f",
title="Product Revenue On A Log Scale",
)
fig.update_layout(xaxis_tickangle=-35)
fig.show()Rotate long labels when they crowd the x-axis.
13. Histograms
Histograms show distributions.
fig = px.histogram(
orders,
x="total_bill",
nbins=14,
color="channel",
marginal="box",
title="Distribution Of Order Values",
)
fig.show()marginal="box" adds a compact box plot above the histogram.
Other useful marginal options include:
"rug"
"violin"
"box"14. Histogram With Facets
Facets split one chart into small multiples.
fig = px.histogram(
orders,
x="service_rating",
color="customer_type",
facet_col="channel",
nbins=10,
title="Service Ratings By Channel",
)
fig.show()Facets are often better than putting too many colors into one chart.
15. Pie And Donut Charts
Pie charts are useful only for simple part-to-whole comparisons.
Use them when:
- there are few categories
- the values add up to a meaningful whole
- exact comparison is not the main goal
category_share = (
orders
.groupby("category", as_index=False)
.agg(revenue=("total_bill", "sum"))
)
fig = px.pie(
category_share,
values="revenue",
names="category",
title="Revenue Share By Category",
)
fig.show()A donut chart is a pie chart with a hole:
fig = px.pie(
category_share,
values="revenue",
names="category",
hole=0.45,
title="Revenue Share By Category",
)
fig.show()If you need accurate category comparison, prefer a bar chart.
16. Sunburst Charts
Sunburst charts show hierarchy.
This example shows revenue from city to category to product:
order_hierarchy = (
orders
.groupby(["city", "category", "product"], as_index=False)
.agg(
revenue=("total_bill", "sum"),
avg_rating=("service_rating", "mean"),
)
)
fig = px.sunburst(
order_hierarchy,
path=["city", "category", "product"],
values="revenue",
color="avg_rating",
color_continuous_scale="Blues",
title="Cafe Revenue Hierarchy",
)
fig.show()Sunburst charts are useful when hierarchy matters more than exact bar-to-bar comparison.
17. Treemap Charts
A treemap uses rectangles instead of radial slices.
fig = px.treemap(
order_hierarchy,
path=["city", "category", "product"],
values="revenue",
color="avg_rating",
color_continuous_scale="Greens",
title="Cafe Revenue Treemap",
)
fig.show()Treemaps often use screen space more efficiently than sunburst charts.
18. Heatmaps With px.imshow
Heatmaps are useful for matrix-shaped summaries.
Create a pivot table:
rating_matrix = orders.pivot_table(
index="city",
columns="category",
values="service_rating",
aggfunc="mean",
)
fig = px.imshow(
rating_matrix,
text_auto=".2f",
color_continuous_scale="Blues",
title="Average Service Rating By City And Category",
)
fig.show()The input to px.imshow can be a NumPy array, a list of lists, or a Pandas DataFrame.
A DataFrame is convenient because it keeps row and column labels.
19. Another Heatmap: Fulfillment Time
time_matrix = orders.pivot_table(
index="city",
columns="channel",
values="delivery_minutes",
aggfunc="mean",
)
fig = px.imshow(
time_matrix,
text_auto=".1f",
color_continuous_scale="Oranges",
title="Average Fulfillment Minutes By City And Channel",
)
fig.show()Use heatmaps when the same metric is measured across two category axes.
20. 3D Scatter Plot
3D charts can be helpful for exploration, but they are not always the best presentation choice.
Use them when rotation helps users inspect the data.
fig = px.scatter_3d(
latest,
x="internet_users_pct",
y="education_index",
z="gdp_per_capita",
color="continent",
size="population_millions",
hover_name="country",
title="3D Country Metrics",
)
fig.show()Do not use 3D just because it looks impressive.
For reports, a 2D scatter plot is often easier to read.
21. Scatter Matrix
A scatter matrix compares multiple numeric columns pairwise.
fig = px.scatter_matrix(
latest,
dimensions=[
"life_expectancy",
"gdp_per_capita",
"internet_users_pct",
"education_index",
],
color="continent",
hover_name="country",
title="Scatter Matrix For Country Metrics",
)
fig.update_traces(diagonal_visible=False)
fig.show()Use a scatter matrix for quick relationship scanning.
It can become crowded if you include too many columns.
22. Facet Columns
Facets create one small chart per category.
fig = px.scatter(
country,
x="life_expectancy",
y="gdp_per_capita",
color="continent",
hover_name="country",
facet_col="year",
facet_col_wrap=3,
title="Life Expectancy vs GDP Across Years",
)
fig.show()facet_col_wrap=3 places three facet panels per row.
23. Facet Rows And Columns
You can facet by two category variables at once.
fig = px.scatter(
orders,
x="total_bill",
y="tip",
color="customer_type",
facet_col="channel",
facet_row="city",
hover_name="product",
title="Tips vs Order Value By City And Channel",
)
fig.show()This can produce many panels.
Use it only when the grid remains readable.
24. Updating Layout
Every Plotly Express chart returns a Figure object.
You can update that object before showing it.
fig = px.scatter(
latest,
x="internet_users_pct",
y="life_expectancy",
color="continent",
hover_name="country",
title="Internet Access And Life Expectancy",
)
fig.update_layout(
template="plotly_white",
legend_title_text="Continent Group",
title_x=0.02,
)
fig.update_xaxes(title="Internet Users (%)")
fig.update_yaxes(title="Life Expectancy")
fig.show()Common layout changes:
fig.update_layout(template="plotly_white")
fig.update_layout(height=550, width=900)
fig.update_layout(legend_title_text="Group")
fig.update_xaxes(tickangle=-30)25. Saving Interactive Charts
Save an interactive chart as HTML:
fig.write_html("country_scatter.html")This creates a file that can be opened in a browser.
Save a static image only if you installed the static export dependency:
pip install kaleidoThen:
fig.write_image("country_scatter.png")Use HTML when interactivity matters.
Use PNG or SVG for static reports, slide decks, and documents.
26. Subplots With make_subplots
Plotly Express is convenient, but mixed dashboards often use Graph Objects.
Create summaries:
monthly_revenue = (
orders
.assign(month=orders["date"].dt.to_period("M").astype(str))
.groupby("month", as_index=False)
.agg(revenue=("total_bill", "sum"))
)
rating_by_city = (
orders
.groupby("city", as_index=False)
.agg(avg_rating=("service_rating", "mean"))
)Build a 2x2 dashboard:
fig = make_subplots(
rows=2,
cols=2,
subplot_titles=[
"Monthly Revenue",
"Average Rating By City",
"Order Value Distribution",
"Internet Adoption Trend",
],
)
fig.add_trace(
go.Scatter(
x=monthly_revenue["month"],
y=monthly_revenue["revenue"],
mode="lines+markers",
name="Revenue",
),
row=1,
col=1,
)
fig.add_trace(
go.Bar(
x=rating_by_city["city"],
y=rating_by_city["avg_rating"],
name="Rating",
),
row=1,
col=2,
)
fig.add_trace(
go.Histogram(
x=orders["total_bill"],
name="Order Values",
),
row=2,
col=1,
)
for country_name in ["Auralia", "Eldora", "Harboris"]:
temp = country[country["country"] == country_name]
fig.add_trace(
go.Scatter(
x=temp["year"],
y=temp["internet_users_pct"],
mode="lines+markers",
name=country_name,
),
row=2,
col=2,
)
fig.update_layout(
title="Cafe And Country Metrics Dashboard",
height=750,
showlegend=True,
)
fig.show()This pattern is useful when one dashboard needs different chart types.
27. Practice Problems
Use the two CSV files to solve these problems.
Problem 1: Best GDP Scatter Plot
Create a scatter plot with:
- x-axis:
life_expectancy - y-axis:
gdp_per_capita - color:
continent - size:
population_millions - hover label:
country - only the latest year
Add a clear title and readable axis labels.
Problem 2: Animated Internet Adoption
Create an animated scatter plot with:
- x-axis:
internet_users_pct - y-axis:
education_index - animation by
year - animation group by
country - color by
continent
Use fixed x and y ranges.
Problem 3: Cafe Revenue Bars
Create a grouped bar chart showing revenue by:
- x-axis:
city - y-axis: total
total_bill - color:
category
Use text_auto.
Problem 4: Channel Composition
Create a stacked bar chart showing revenue by:
- x-axis:
city - color:
channel
Explain which city has the strongest delivery contribution.
Problem 5: Order Value Histogram
Create a histogram of total_bill.
Add:
color="customer_type"nbins=12marginal="box"
Problem 6: Category Donut Chart
Create a donut chart showing revenue share by product category.
Then create a bar chart with the same data and compare which chart is easier to read.
Problem 7: Sunburst Hierarchy
Create a sunburst chart with:
citycategoryproduct
Use revenue as the value and average rating as the color.
Problem 8: Heatmap From A Pivot Table
Create a pivot table where:
- rows are
city - columns are
channel - values are average
delivery_minutes
Display it with px.imshow.
Problem 9: 3D Country Metrics
Create a 3D scatter plot with:
- x-axis:
internet_users_pct - y-axis:
education_index - z-axis:
gdp_per_capita - color:
continent - size:
population_millions
Write one sentence explaining whether the 3D version adds insight.
Problem 10: Faceted Tips Chart
Create a faceted scatter plot using orders:
- x-axis:
total_bill - y-axis:
tip - color:
customer_type - facet column:
channel
Then add facet_row="city" and decide whether the result is too dense.
28. Plotly Express Checklist
Use this checklist when building Plotly charts:
- Is the data in a tidy DataFrame?
- Does each axis have a clear label?
- Does color encode a real variable?
- Is the hover information helpful but not noisy?
- Would a log scale make the pattern clearer?
- Are there too many categories for one chart?
- Would facets make the comparison easier?
- Is a pie chart really better than a bar chart?
- Does a 3D chart add insight, or only visual complexity?
- Should the output be saved as HTML for interactivity?
29. Key Takeaways
Plotly Express lets you build interactive charts quickly from Pandas DataFrames.
Start with px.scatter, px.line, px.bar, and px.histogram.
Then add color, size, hover labels, facets, animation, and layout updates.
Use hierarchy charts, heatmaps, 3D charts, and subplots when the data actually needs them.
The goal is not to use every Plotly feature.
The goal is to make data easier to explore and explain.
