Figure Manipulation¶
After creating a figure with xpx(), you can manipulate it using standard Plotly methods.
This notebook shows what works out of the box, and where update_traces from xarray-plotly helps.
In [1]:
Copied!
import numpy as np
import plotly.express as px
import xarray as xr
from xarray_plotly import config, update_traces, xpx
config.notebook()
import numpy as np
import plotly.express as px
import xarray as xr
from xarray_plotly import config, update_traces, xpx
config.notebook()
In [2]:
Copied!
# 4D DataArray: scenario x metric x year x country
df_gap = px.data.gapminder()
countries = ["United States", "China", "Germany", "Brazil"]
metrics = ["lifeExp", "gdpPercap"]
# Build base 3D array (metric x year x country)
arrays = []
for metric in metrics:
df_pivot = df_gap[df_gap["country"].isin(countries)].pivot(
index="year", columns="country", values=metric
)
arrays.append(df_pivot.values)
base_3d = np.stack(arrays)
# Add scenario dimension (4D): original + 10% higher
scenarios = ["baseline", "optimistic"]
data_4d = np.stack([base_3d, base_3d * 1.1])
da = xr.DataArray(
data_4d,
dims=["scenario", "metric", "year", "country"],
coords={
"scenario": scenarios,
"metric": metrics,
"year": df_pivot.index.tolist(),
"country": df_pivot.columns.tolist(),
},
name="value",
)
da
# 4D DataArray: scenario x metric x year x country
df_gap = px.data.gapminder()
countries = ["United States", "China", "Germany", "Brazil"]
metrics = ["lifeExp", "gdpPercap"]
# Build base 3D array (metric x year x country)
arrays = []
for metric in metrics:
df_pivot = df_gap[df_gap["country"].isin(countries)].pivot(
index="year", columns="country", values=metric
)
arrays.append(df_pivot.values)
base_3d = np.stack(arrays)
# Add scenario dimension (4D): original + 10% higher
scenarios = ["baseline", "optimistic"]
data_4d = np.stack([base_3d, base_3d * 1.1])
da = xr.DataArray(
data_4d,
dims=["scenario", "metric", "year", "country"],
coords={
"scenario": scenarios,
"metric": metrics,
"year": df_pivot.index.tolist(),
"country": df_pivot.columns.tolist(),
},
name="value",
)
da
Out[2]:
<xarray.DataArray 'value' (scenario: 2, metric: 2, year: 12, country: 4)> Size: 2kB
array([[[[5.09170000e+01, 4.40000000e+01, 6.75000000e+01,
6.84400000e+01],
[5.32850000e+01, 5.05489600e+01, 6.91000000e+01,
6.94900000e+01],
[5.56650000e+01, 4.45013600e+01, 7.03000000e+01,
7.02100000e+01],
[5.76320000e+01, 5.83811200e+01, 7.08000000e+01,
7.07600000e+01],
[5.95040000e+01, 6.31188800e+01, 7.10000000e+01,
7.13400000e+01],
[6.14890000e+01, 6.39673600e+01, 7.25000000e+01,
7.33800000e+01],
[6.33360000e+01, 6.55250000e+01, 7.38000000e+01,
7.46500000e+01],
[6.52050000e+01, 6.72740000e+01, 7.48470000e+01,
7.50200000e+01],
[6.70570000e+01, 6.86900000e+01, 7.60700000e+01,
7.60900000e+01],
[6.93880000e+01, 7.04260000e+01, 7.73400000e+01,
7.68100000e+01],
...
[3.67024438e+03, 5.36441420e+02, 1.41927092e+04,
1.77904604e+04],
[3.77285079e+03, 6.73976263e+02, 1.62201882e+04,
2.14834021e+04],
[5.48428261e+03, 7.44590101e+02, 1.98177983e+04,
2.39866395e+04],
[7.32613052e+03, 8.15361217e+02, 2.25642134e+04,
2.64798953e+04],
[7.73391947e+03, 1.05866352e+03, 2.42346860e+04,
2.75105151e+04],
[8.58780540e+03, 1.51679442e+03, 2.71031042e+04,
3.28727855e+04],
[7.64531132e+03, 1.82136257e+03, 2.91558335e+04,
3.52043255e+04],
[8.75377891e+03, 2.51815755e+03, 3.05677726e+04,
3.93441763e+04],
[8.94433413e+03, 3.43120899e+03, 3.30393822e+04,
4.30068095e+04],
[9.97238091e+03, 5.45502634e+03, 3.53874119e+04,
4.72468184e+04]]]])
Coordinates:
* scenario (scenario) <U10 80B 'baseline' 'optimistic'
* metric (metric) <U9 72B 'lifeExp' 'gdpPercap'
* year (year) int64 96B 1952 1957 1962 1967 1972 ... 1992 1997 2002 2007
* country (country) <U13 208B 'Brazil' 'China' 'Germany' 'United States'Standard Plotly Methods¶
All standard Plotly manipulation methods work on figures created with xpx().
In [3]:
Copied!
# Simple 2D slice
fig = xpx(da.sel(scenario="baseline", metric="lifeExp")).line()
fig
# Simple 2D slice
fig = xpx(da.sel(scenario="baseline", metric="lifeExp")).line()
fig
In [4]:
Copied!
# Layout
fig.update_layout(title="Life Expectancy Over Time", template="plotly_white")
# All traces
fig.update_traces(line_width=3)
# Specific trace by name
fig.update_traces(line_dash="dot", selector={"name": "Germany"})
# Axes
fig.update_xaxes(title="Year", showgrid=False)
fig.update_yaxes(title="Life Expectancy (years)", range=[40, 85])
# Reference line
fig.add_hline(y=70, line_dash="dash", line_color="gray", annotation_text="Target")
fig
# Layout
fig.update_layout(title="Life Expectancy Over Time", template="plotly_white")
# All traces
fig.update_traces(line_width=3)
# Specific trace by name
fig.update_traces(line_dash="dot", selector={"name": "Germany"})
# Axes
fig.update_xaxes(title="Year", showgrid=False)
fig.update_yaxes(title="Life Expectancy (years)", range=[40, 85])
# Reference line
fig.add_hline(y=70, line_dash="dash", line_color="gray", annotation_text="Target")
fig
In [5]:
Copied!
# Facet by metric, color by country
fig = xpx(da.sel(scenario="baseline")).line(facet_col="metric")
fig
# Facet by metric, color by country
fig = xpx(da.sel(scenario="baseline")).line(facet_col="metric")
fig
In [6]:
Copied!
# Update ALL traces across all facets
fig.update_traces(line_width=2)
# Update ALL axes
fig.update_xaxes(showgrid=False)
# Target specific facet (1-indexed)
fig.update_yaxes(type="log", col=2) # log scale only for gdpPercap
fig
# Update ALL traces across all facets
fig.update_traces(line_width=2)
# Update ALL axes
fig.update_xaxes(showgrid=False)
# Target specific facet (1-indexed)
fig.update_yaxes(type="log", col=2) # log scale only for gdpPercap
fig
Grid layout with facet_row¶
In [7]:
Copied!
# 2x2 grid: scenario x metric
fig = xpx(da).line(facet_col="metric", facet_row="scenario")
fig.update_traces(line_width=2)
fig.update_yaxes(type="log", col=2) # log scale for gdpPercap column
fig
# 2x2 grid: scenario x metric
fig = xpx(da).line(facet_col="metric", facet_row="scenario")
fig.update_traces(line_width=2)
fig.update_yaxes(type="log", col=2) # log scale for gdpPercap column
fig
Animation: The Pain Point¶
Plotly's fig.update_traces() does not update animation frames. This is the main gotcha.
In [8]:
Copied!
# Animated bar chart
fig = xpx(da.sel(scenario="baseline", metric="gdpPercap")).bar(animation_frame="year")
fig
# Animated bar chart
fig = xpx(da.sel(scenario="baseline", metric="gdpPercap")).bar(animation_frame="year")
fig
In [9]:
Copied!
# This only affects the INITIAL view!
fig.update_traces(marker_color="red")
print(f"Base trace color: {fig.data[0].marker.color}")
print(f"Frame 0 trace color: {fig.frames[0].data[0].marker.color}")
# This only affects the INITIAL view!
fig.update_traces(marker_color="red")
print(f"Base trace color: {fig.data[0].marker.color}")
print(f"Frame 0 trace color: {fig.frames[0].data[0].marker.color}")
Base trace color: red Frame 0 trace color: #636efa
In [10]:
Copied!
# Play the animation - it reverts to original colors
fig
# Play the animation - it reverts to original colors
fig
Solution: update_traces from xarray-plotly¶
This helper updates both base traces and all animation frames.
In [11]:
Copied!
fig = xpx(da.sel(scenario="baseline", metric="gdpPercap")).bar(animation_frame="year")
update_traces(fig, marker_color="red", marker_opacity=0.8)
print(f"Base trace color: {fig.data[0].marker.color}")
print(f"Frame 0 trace color: {fig.frames[0].data[0].marker.color}")
fig = xpx(da.sel(scenario="baseline", metric="gdpPercap")).bar(animation_frame="year")
update_traces(fig, marker_color="red", marker_opacity=0.8)
print(f"Base trace color: {fig.data[0].marker.color}")
print(f"Frame 0 trace color: {fig.frames[0].data[0].marker.color}")
Base trace color: red Frame 0 trace color: red
In [12]:
Copied!
# Now the style persists through animation
fig
# Now the style persists through animation
fig
Selective updates with selector¶
In [13]:
Copied!
fig = xpx(da.sel(scenario="baseline", metric="lifeExp")).line(x="year")
# Highlight specific countries
update_traces(fig, selector={"name": "China"}, line_color="red", line_width=4)
update_traces(fig, selector={"name": "United States"}, line_color="blue", line_width=4)
fig
fig = xpx(da.sel(scenario="baseline", metric="lifeExp")).line(x="year")
# Highlight specific countries
update_traces(fig, selector={"name": "China"}, line_color="red", line_width=4)
update_traces(fig, selector={"name": "United States"}, line_color="blue", line_width=4)
fig
Unified hover with animation¶
A common pattern: unified hover mode with custom formatting.
- Layout (
hovermode, spikes): Standard Plotly works fine - Traces (
hovertemplate): Useupdate_traces()for animation support
In [14]:
Copied!
fig = xpx(da.sel(metric="gdpPercap")).line(x="year", animation_frame="scenario")
# Layout settings - standard Plotly
fig.update_layout(hovermode="x unified")
fig.update_xaxes(showspikes=True, spikecolor="gray", spikethickness=1)
# Trace settings - use update_traces for animation support
update_traces(fig, hovertemplate="<b>%{fullData.name}</b>: $%{y:,.0f}<extra></extra>")
fig
fig = xpx(da.sel(metric="gdpPercap")).line(x="year", animation_frame="scenario")
# Layout settings - standard Plotly
fig.update_layout(hovermode="x unified")
fig.update_xaxes(showspikes=True, spikecolor="gray", spikethickness=1)
# Trace settings - use update_traces for animation support
update_traces(fig, hovertemplate="%{fullData.name}: $%{y:,.0f} ")
fig
Facets + Animation¶
In [15]:
Copied!
# Facet by metric, animate by scenario
fig = xpx(da).line(facet_col="metric", animation_frame="scenario")
# Standard Plotly for layout
fig.update_yaxes(type="log", col=2)
# update_traces for trace properties with animation
update_traces(fig, line_width=3)
update_traces(fig, selector={"name": "China"}, line_dash="dot")
fig
# Facet by metric, animate by scenario
fig = xpx(da).line(facet_col="metric", animation_frame="scenario")
# Standard Plotly for layout
fig.update_yaxes(type="log", col=2)
# update_traces for trace properties with animation
update_traces(fig, line_width=3)
update_traces(fig, selector={"name": "China"}, line_dash="dot")
fig
Summary¶
| Method | Static/Faceted | Animated |
|---|---|---|
fig.update_layout() |
✅ | ✅ |
fig.update_xaxes() / fig.update_yaxes() |
✅ | ✅ |
fig.add_hline() / fig.add_vline() |
✅ | ✅ |
fig.update_traces() |
✅ | ❌ base only |
update_traces(fig, ...) |
✅ | ✅ all frames |