Consistent styling and clear tooltips are what make interactive charts feel professional rather than default.
fig.update_traces(...) sets properties on every trace, or only those matched by a selector:
fig.update_traces(marker_size=8, selector=dict(type="scatter"))
fig.update_traces(line_width=3, selector=dict(name="forecast"))fig.for_each_trace(fn) calls a function per trace, useful for logic like renaming: fig.for_each_trace(lambda t: t.update(name=t.name.title())). Direct access also works: fig.data[0].marker.color = "red".
Hover templates define the tooltip exactly. Inside hovertemplate, %{x}, %{y}, %{text} and %{customdata[0]} insert values, with optional d3 formats after a colon: %{y:$,.2f}, %{x|%b %d, %Y} for dates. <br> breaks lines and <b> makes text bold. The secondary box with the trace name is controlled by <extra></extra>: leaving it empty hides that box.
customdata attaches extra per-point values to a trace, usually a 2D array of columns, so tooltips can show fields that are not plotted. Express does this automatically for hover_data columns.
Related layout options: hovermode ("closest", "x", "x unified"), hoverlabel=dict(bgcolor="white", font_size=12), and hoverinfo="skip" on a trace that should never show a tooltip.
Express already writes a hovertemplate; update_traces(hovertemplate=...) replaces it completely, so include every value you want shown.
Example
import plotly.express as px
df = px.data.gapminder().query("year == 2007 and continent == 'Europe'")
fig = px.scatter(df, x="gdpPercap", y="lifeExp", text="iso_alpha",
custom_data=["country", "pop"])
fig.update_traces(
marker=dict(size=10, color="#3F4F75", line=dict(width=1, color="white")),
textposition="top center",
hovertemplate=("<b>%{customdata[0]}</b><br>"
"GDP: %{x:$,.0f}<br>Life exp.: %{y:.1f} yrs<br>"
"Pop: %{customdata[1]:,}<extra></extra>"),
selector=dict(type="scatter"),
)
t = fig.data[0]
print(t.marker.size, t.customdata.shape) # 10 (30, 2)
print(t.hovertemplate.endswith("<extra></extra>")) # True
fig.write_html("europe.html")Remember
update_traceswithselector=styles some or all traces at oncehovertemplateuses%{field:format}and<extra></extra>hides the trace boxcustomdatacarries extra per-point values into tooltips
Watch out for
- Replacing an Express hovertemplate and forgetting the fields it used to show.