Plotly Express: Interactive Charts in Python (2026)
Static charts are great for reports, but when you want to hover over a point, zoom into a date range or switch a series on and off, you need an interactive chart. Plotly is the most popular open-source library for interactive charts in Python, and its high-level Plotly Express module (plotly.express, usually imported as px) builds a complete figure from a pandas DataFrame in one line. This tutorial was tested with plotly 7.1.0, Kaleido 1.5.0, pandas 3.1.0 and NumPy 2.5.4 on Python 3.13.5, and every output block below is the real output of the scripts shown.
Why Plotly Express in 2026?
- One function call per chart:
px.scatter,px.line,px.bar,px.histogram,px.box,px.imshow,px.timelineand many more. - Columns map straight to visual channels:
x,y,color,size,facet_col,hover_nameandanimation_frame. - Charts are interactive by default: hover tooltips, zoom, pan, legend toggling and PNG download, with no JavaScript to write.
- Export a figure as a standalone HTML file you can email or embed, or as a static PNG, SVG or PDF through Kaleido.
- The same figures drop into Jupyter, Dash and Streamlit apps.
If you already plot with pandas, Matplotlib and Seaborn, Plotly Express will feel familiar: you pass a DataFrame and column names, and it handles the legend, colours and axes for you.
What is new in Plotly 7
plotly.py 7.0 (August 2026) upgraded the bundled Plotly.js to version 4. Most code runs unchanged, but note these points if you are upgrading from version 6:
- The Mapbox functions are gone. Use
px.scatter_map,px.line_map,px.choropleth_mapandpx.density_map(MapLibre based), andmap_styleinstead ofmapbox_style. - Static export now needs Kaleido 1.0 or newer. Orca and the
engineargument ofwrite_image()were removed. - Deprecated figure factories such as
create_distplotandcreate_ganttwere removed; usepx.histogramandpx.timeline. - Colour strings follow CSS Color 4, so
hsv()and fractionalrgb(0.5, 0.5, 0.5)values no longer work.
Install
pip install plotly pandas kaleido
Kaleido is only needed for static image export. Kaleido 1.x drives a Chrome or Chromium browser; if none is installed, run plotly_get_chrome once after installing.
Step 1: Interactive scatter and bar charts
The script below uses the Gapminder sample dataset that ships with plotly. It draws a bubble chart of life expectancy against GDP per capita for 2007, with bubble size for population, colour for continent and a log x axis, then a bar chart of the average life expectancy per continent. Both are saved as HTML files that open in any browser.
import plotly
import plotly.express as px
df = px.data.gapminder() # sample data bundled with plotly
df07 = df.query("year == 2007")
print("plotly", plotly.__version__, "| rows:", len(df), "| 2007 rows:", len(df07))
fig = px.scatter(
df07, x="gdpPercap", y="lifeExp", size="pop", color="continent",
hover_name="country", log_x=True, size_max=55,
title="Life expectancy vs GDP per capita (2007)",
)
print("traces:", len(fig.data), [t.name for t in fig.data])
print("x axis type:", fig.layout.xaxis.type)
fig.write_html("gapminder_scatter.html", include_plotlyjs="cdn")
print("saved gapminder_scatter.html")
avg = (df07.groupby("continent", as_index=False)["lifeExp"].mean()
.round(1).sort_values("lifeExp", ascending=False))
print(avg.to_string(index=False))
bar = px.bar(avg, x="continent", y="lifeExp", text="lifeExp", color="continent",
title="Average life expectancy by continent (2007)")
bar.update_traces(textposition="outside")
bar.write_html("life_exp_bar.html", include_plotlyjs="cdn")
print("saved life_exp_bar.html")
Real output (scatter and bar)
Running python plotly_express_basics.py printed:
plotly 7.1.0 | rows: 1704 | 2007 rows: 142
traces: 5 ['Asia', 'Europe', 'Africa', 'Americas', 'Oceania']
x axis type: log
saved gapminder_scatter.html
continent lifeExp
Oceania 80.7
Europe 77.6
Americas 73.6
Asia 70.7
Africa 54.8
saved life_exp_bar.html
Plotly Express created one trace per continent, which is why the legend lets you hide or show each continent with a click. log_x=True set the x axis type to log, which spreads out the poorer countries that would otherwise be squashed on the left. The averages show Oceania at 80.7 years and Africa at 54.8 years in 2007. Open gapminder_scatter.html and hover over any bubble to see the country name, GDP, life expectancy and population. Using include_plotlyjs="cdn" keeps each file small (about 16 KB here) by loading Plotly.js from a CDN; use the default True if the file must work offline.
Step 2: Customize the layout and export PNG images
Every Plotly Express call returns a normal plotly.graph_objects.Figure, so you can keep customizing it. Here we reshape the bundled stock prices to long format, draw one line per ticker, add a dashed reference line, switch to the plotly_white template with a unified hover label, and export the chart plus a go.Indicator number card to PNG files with Kaleido.
from importlib.metadata import version
from pathlib import Path
import plotly.express as px
import plotly.graph_objects as go
df = px.data.stocks() # daily prices, normalised to 1.0
long = df.melt(id_vars="date", var_name="ticker", value_name="price")
print("tickers:", sorted(long["ticker"].unique()))
fig = px.line(long, x="date", y="price", color="ticker",
title="Tech stocks, growth of 1 dollar (2018-2019)")
fig.add_hline(y=1.0, line_dash="dash", line_color="gray",
annotation_text="start value")
fig.update_layout(template="plotly_white", hovermode="x unified",
legend_title_text="Ticker", width=1000, height=500)
print("template:", fig.layout.template.layout.paper_bgcolor,
"| hovermode:", fig.layout.hovermode)
last = long[long["date"] == long["date"].max()].sort_values("price", ascending=False)
print("final values:", ", ".join(f"{t} {p:.2f}" for t, p in zip(last.ticker, last.price)))
gauge = go.Figure(go.Indicator(mode="number+delta", value=last.price.iloc[0],
delta={"reference": 1.0}, title={"text": last.ticker.iloc[0]}))
fig.write_image("stocks_line.png", scale=1)
gauge.write_image("best_stock.png", width=400, height=250)
for name in ("stocks_line.png", "best_stock.png"):
print(f"wrote {name}: {Path(name).stat().st_size:,} bytes")
print("kaleido", version("kaleido"))
Real output (customize and export)
tickers: ['AAPL', 'AMZN', 'FB', 'GOOG', 'MSFT', 'NFLX']
template: white | hovermode: x unified
final values: MSFT 1.79, AAPL 1.68, NFLX 1.54, AMZN 1.50, GOOG 1.21, FB 1.10
wrote stocks_line.png: 123,087 bytes
wrote best_stock.png: 7,007 bytes
kaleido 1.5.0
The stock data is normalised so every ticker starts at 1.0. By the end of 2019, one dollar in MSFT had grown to 1.79 while FB reached 1.10. add_hline() drew the dashed start value line, and hovermode="x unified" shows all six prices in one tooltip when you hover over a date. write_image() produced a 1000 by 500 PNG of the line chart and a small indicator card for the best stock, ready for slides, emails or a PDF report.
Plotly Express cheat sheet
px.histogram(df, x="lifeExp", color="continent", nbins=30) # distributions
px.box(df07, x="continent", y="lifeExp", points="all") # spread and outliers
px.scatter(df, x="gdpPercap", y="lifeExp", animation_frame="year",
size="pop", color="continent", log_x=True) # animated chart
px.line(long, x="date", y="price", facet_col="ticker", facet_col_wrap=3)
px.imshow(df.select_dtypes("number").corr(), text_auto=".2f") # heatmap
fig.update_xaxes(rangeslider_visible=True) # date range slider
fig.show() # browser or notebook
Production tips
- Keep your data in tidy, long format (one row per observation) so
colorandfacet_colwork with a single column name. - Validate the DataFrame before charting, for example with pandera, so a bad column type does not silently produce an empty or wrong chart.
- Use
hover_dataandlabelsto give columns friendly names in tooltips and axis titles. - For many points (hundreds of thousands), use
render_mode="webgl"inpx.scatteror aggregate first withpx.density_heatmap. - Set
width,heightandscaleinwrite_image()for sharp, consistent static images. - Pin plotly and Kaleido versions in your requirements file, because major releases such as 7.0 can remove old functions.
Conclusion
Plotly Express turns a pandas DataFrame into an interactive chart in one line. With plotly 7.1 and Kaleido 1.5 on Python 3.13, you can build scatter, bar and line charts, customize templates and hover labels, save shareable HTML files and export static PNG images for reports, all from the same figure object.