xarray-plotly 0.0.7__tar.gz → 0.0.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/PKG-INFO +1 -1
  2. xarray_plotly-0.0.8/docs/examples/fast_bar.ipynb +297 -0
  3. xarray_plotly-0.0.8/docs/examples/manipulation.ipynb +383 -0
  4. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/mkdocs.yml +3 -0
  5. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/tests/test_accessor.py +60 -0
  6. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly/accessor.py +73 -2
  7. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly/config.py +1 -0
  8. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly/figures.py +25 -9
  9. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly/plotting.py +165 -0
  10. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/PKG-INFO +1 -1
  11. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/SOURCES.txt +1 -0
  12. xarray_plotly-0.0.7/docs/examples/manipulation.ipynb +0 -708
  13. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/.github/dependabot.yml +0 -0
  14. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/.github/workflows/ci.yml +0 -0
  15. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/.github/workflows/dependabot-auto-merge.yml +0 -0
  16. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/.github/workflows/docs.yml +0 -0
  17. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/.github/workflows/release.yml +0 -0
  18. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/.gitignore +0 -0
  19. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/.pre-commit-config.yaml +0 -0
  20. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/CONTRIBUTING.md +0 -0
  21. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/LICENSE +0 -0
  22. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/README.md +0 -0
  23. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/api.md +0 -0
  24. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/examples/combining.ipynb +0 -0
  25. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/examples/datasets.ipynb +0 -0
  26. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/examples/dimensions.ipynb +0 -0
  27. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/examples/figure.ipynb +0 -0
  28. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/examples/kwargs.ipynb +0 -0
  29. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/examples/plot-types.ipynb +0 -0
  30. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/getting-started.ipynb +0 -0
  31. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/docs/index.md +0 -0
  32. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/pyproject.toml +0 -0
  33. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/setup.cfg +0 -0
  34. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/tests/__init__.py +0 -0
  35. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/tests/test_common.py +0 -0
  36. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/tests/test_config.py +0 -0
  37. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/tests/test_figures.py +0 -0
  38. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly/__init__.py +0 -0
  39. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly/common.py +0 -0
  40. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly/py.typed +0 -0
  41. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/dependency_links.txt +0 -0
  42. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/requires.txt +0 -0
  43. {xarray_plotly-0.0.7 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray_plotly
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Interactive Plotly Express plotting accessor for xarray
5
5
  Author: Felix
6
6
  License: MIT
@@ -0,0 +1,297 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "0",
6
+ "metadata": {},
7
+ "source": [
8
+ "# Fast Bar Charts\n",
9
+ "\n",
10
+ "The `fast_bar()` method creates bar-like visualizations using stacked areas. This renders much faster than actual bar charts for large datasets because it uses a single polygon per trace instead of individual rectangles."
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": null,
16
+ "id": "1",
17
+ "metadata": {},
18
+ "outputs": [],
19
+ "source": [
20
+ "import numpy as np\n",
21
+ "import xarray as xr\n",
22
+ "\n",
23
+ "from xarray_plotly import config, xpx\n",
24
+ "\n",
25
+ "config.notebook()"
26
+ ]
27
+ },
28
+ {
29
+ "cell_type": "markdown",
30
+ "id": "2",
31
+ "metadata": {},
32
+ "source": [
33
+ "## Basic Example"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": null,
39
+ "id": "3",
40
+ "metadata": {},
41
+ "outputs": [],
42
+ "source": [
43
+ "# Quarterly revenue data by product and region\n",
44
+ "np.random.seed(42)\n",
45
+ "da = xr.DataArray(\n",
46
+ " np.random.rand(4, 3, 2) * 100 + 50,\n",
47
+ " dims=[\"quarter\", \"product\", \"region\"],\n",
48
+ " coords={\n",
49
+ " \"quarter\": [\"Q1\", \"Q2\", \"Q3\", \"Q4\"],\n",
50
+ " \"product\": [\"Widgets\", \"Gadgets\", \"Gizmos\"],\n",
51
+ " \"region\": [\"North\", \"South\"],\n",
52
+ " },\n",
53
+ " name=\"revenue\",\n",
54
+ ")\n",
55
+ "\n",
56
+ "xpx(da).fast_bar()"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "code",
61
+ "execution_count": null,
62
+ "id": "4",
63
+ "metadata": {},
64
+ "outputs": [],
65
+ "source": [
66
+ "# Comparison with regular bar()\n",
67
+ "xpx(da).bar()"
68
+ ]
69
+ },
70
+ {
71
+ "cell_type": "markdown",
72
+ "id": "5",
73
+ "metadata": {},
74
+ "source": [
75
+ "## With Faceting"
76
+ ]
77
+ },
78
+ {
79
+ "cell_type": "code",
80
+ "execution_count": null,
81
+ "id": "6",
82
+ "metadata": {},
83
+ "outputs": [],
84
+ "source": [
85
+ "xpx(da).fast_bar(facet_col=\"region\")"
86
+ ]
87
+ },
88
+ {
89
+ "cell_type": "markdown",
90
+ "id": "7",
91
+ "metadata": {},
92
+ "source": [
93
+ "## With Animation"
94
+ ]
95
+ },
96
+ {
97
+ "cell_type": "code",
98
+ "execution_count": null,
99
+ "id": "8",
100
+ "metadata": {},
101
+ "outputs": [],
102
+ "source": [
103
+ "# Multi-year data for animation\n",
104
+ "np.random.seed(123)\n",
105
+ "da_anim = xr.DataArray(\n",
106
+ " np.random.rand(4, 3, 5) * 100 + 20,\n",
107
+ " dims=[\"quarter\", \"product\", \"year\"],\n",
108
+ " coords={\n",
109
+ " \"quarter\": [\"Q1\", \"Q2\", \"Q3\", \"Q4\"],\n",
110
+ " \"product\": [\"Widgets\", \"Gadgets\", \"Gizmos\"],\n",
111
+ " \"year\": [2020, 2021, 2022, 2023, 2024],\n",
112
+ " },\n",
113
+ " name=\"revenue\",\n",
114
+ ")\n",
115
+ "\n",
116
+ "xpx(da_anim).fast_bar(animation_frame=\"year\")"
117
+ ]
118
+ },
119
+ {
120
+ "cell_type": "markdown",
121
+ "id": "9",
122
+ "metadata": {},
123
+ "source": [
124
+ "## Faceting + Animation"
125
+ ]
126
+ },
127
+ {
128
+ "cell_type": "code",
129
+ "execution_count": null,
130
+ "id": "10",
131
+ "metadata": {},
132
+ "outputs": [],
133
+ "source": [
134
+ "# 4D data: quarter x product x region x year\n",
135
+ "np.random.seed(456)\n",
136
+ "da_4d = xr.DataArray(\n",
137
+ " np.random.rand(4, 3, 2, 4) * 80 + 30,\n",
138
+ " dims=[\"quarter\", \"product\", \"region\", \"year\"],\n",
139
+ " coords={\n",
140
+ " \"quarter\": [\"Q1\", \"Q2\", \"Q3\", \"Q4\"],\n",
141
+ " \"product\": [\"Widgets\", \"Gadgets\", \"Gizmos\"],\n",
142
+ " \"region\": [\"North\", \"South\"],\n",
143
+ " \"year\": [2021, 2022, 2023, 2024],\n",
144
+ " },\n",
145
+ " name=\"revenue\",\n",
146
+ ")\n",
147
+ "\n",
148
+ "xpx(da_4d).fast_bar(facet_col=\"region\", animation_frame=\"year\")"
149
+ ]
150
+ },
151
+ {
152
+ "cell_type": "markdown",
153
+ "id": "11",
154
+ "metadata": {},
155
+ "source": [
156
+ "## Positive and Negative Values\n",
157
+ "\n",
158
+ "`fast_bar()` classifies each trace by its values:\n",
159
+ "- **Purely positive** → stacks upward\n",
160
+ "- **Purely negative** → stacks downward\n",
161
+ "- **Mixed signs** → warning + dashed line (use `bar()` instead)"
162
+ ]
163
+ },
164
+ {
165
+ "cell_type": "code",
166
+ "execution_count": null,
167
+ "id": "12",
168
+ "metadata": {},
169
+ "outputs": [],
170
+ "source": [
171
+ "# Profit (positive) and Loss (negative) - stacks correctly\n",
172
+ "np.random.seed(789)\n",
173
+ "da_split = xr.DataArray(\n",
174
+ " np.column_stack(\n",
175
+ " [\n",
176
+ " np.random.rand(6) * 80 + 20, # Revenue: positive\n",
177
+ " -np.random.rand(6) * 50 - 10, # Costs: negative\n",
178
+ " ]\n",
179
+ " ),\n",
180
+ " dims=[\"month\", \"category\"],\n",
181
+ " coords={\n",
182
+ " \"month\": [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"],\n",
183
+ " \"category\": [\"Revenue\", \"Costs\"],\n",
184
+ " },\n",
185
+ " name=\"financials\",\n",
186
+ ")\n",
187
+ "\n",
188
+ "xpx(da_split).fast_bar()"
189
+ ]
190
+ },
191
+ {
192
+ "cell_type": "code",
193
+ "execution_count": null,
194
+ "id": "13",
195
+ "metadata": {},
196
+ "outputs": [],
197
+ "source": [
198
+ "# With animation - sign classification is consistent across frames\n",
199
+ "np.random.seed(321)\n",
200
+ "da_split_anim = xr.DataArray(\n",
201
+ " np.stack(\n",
202
+ " [\n",
203
+ " np.column_stack([np.random.rand(6) * 80 + 20, -np.random.rand(6) * 50 - 10])\n",
204
+ " for _ in range(4)\n",
205
+ " ],\n",
206
+ " axis=-1,\n",
207
+ " ),\n",
208
+ " dims=[\"month\", \"category\", \"year\"],\n",
209
+ " coords={\n",
210
+ " \"month\": [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"],\n",
211
+ " \"category\": [\"Revenue\", \"Costs\"],\n",
212
+ " \"year\": [2021, 2022, 2023, 2024],\n",
213
+ " },\n",
214
+ " name=\"financials\",\n",
215
+ ")\n",
216
+ "\n",
217
+ "xpx(da_split_anim).fast_bar(animation_frame=\"year\")"
218
+ ]
219
+ },
220
+ {
221
+ "cell_type": "markdown",
222
+ "id": "14",
223
+ "metadata": {},
224
+ "source": [
225
+ "## Mixed Sign Warning\n",
226
+ "\n",
227
+ "When a trace has both positive and negative values, `fast_bar()` shows a warning and displays it as a dashed line:"
228
+ ]
229
+ },
230
+ {
231
+ "cell_type": "code",
232
+ "execution_count": null,
233
+ "id": "15",
234
+ "metadata": {},
235
+ "outputs": [],
236
+ "source": [
237
+ "# Both columns have mixed signs - triggers warning\n",
238
+ "da_mixed = xr.DataArray(\n",
239
+ " np.array(\n",
240
+ " [\n",
241
+ " [50, -30],\n",
242
+ " [-40, 60],\n",
243
+ " [30, -50],\n",
244
+ " [-20, 40],\n",
245
+ " ]\n",
246
+ " ),\n",
247
+ " dims=[\"month\", \"category\"],\n",
248
+ " coords={\n",
249
+ " \"month\": [\"Jan\", \"Feb\", \"Mar\", \"Apr\"],\n",
250
+ " \"category\": [\"A\", \"B\"],\n",
251
+ " },\n",
252
+ ")\n",
253
+ "\n",
254
+ "# This will show a warning\n",
255
+ "xpx(da_mixed).fast_bar()"
256
+ ]
257
+ },
258
+ {
259
+ "cell_type": "code",
260
+ "execution_count": null,
261
+ "id": "16",
262
+ "metadata": {},
263
+ "outputs": [],
264
+ "source": [
265
+ "# For mixed data, use bar() instead\n",
266
+ "xpx(da_mixed).bar()"
267
+ ]
268
+ },
269
+ {
270
+ "cell_type": "markdown",
271
+ "id": "17",
272
+ "metadata": {},
273
+ "source": [
274
+ "## When to Use\n",
275
+ "\n",
276
+ "| Method | Use when... |\n",
277
+ "|--------|-------------|\n",
278
+ "| `fast_bar()` | Large datasets, animations, performance matters, data is same-sign per trace |\n",
279
+ "| `bar()` | Need grouped bars, pattern fills, or have mixed +/- values per trace |\n",
280
+ "| `area()` | Want smooth continuous fills |"
281
+ ]
282
+ }
283
+ ],
284
+ "metadata": {
285
+ "kernelspec": {
286
+ "display_name": "Python 3",
287
+ "language": "python",
288
+ "name": "python3"
289
+ },
290
+ "language_info": {
291
+ "name": "python",
292
+ "version": "3.12.0"
293
+ }
294
+ },
295
+ "nbformat": 4,
296
+ "nbformat_minor": 5
297
+ }
@@ -0,0 +1,383 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "0",
6
+ "metadata": {},
7
+ "source": [
8
+ "# Figure Manipulation\n",
9
+ "\n",
10
+ "After creating a figure with `xpx()`, you can manipulate it using standard Plotly methods.\n",
11
+ "This notebook shows what works out of the box, and where `update_traces` from xarray-plotly helps."
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": null,
17
+ "id": "1",
18
+ "metadata": {},
19
+ "outputs": [],
20
+ "source": [
21
+ "import numpy as np\n",
22
+ "import plotly.express as px\n",
23
+ "import xarray as xr\n",
24
+ "\n",
25
+ "from xarray_plotly import config, update_traces, xpx\n",
26
+ "\n",
27
+ "config.notebook()"
28
+ ]
29
+ },
30
+ {
31
+ "cell_type": "code",
32
+ "execution_count": null,
33
+ "id": "2",
34
+ "metadata": {},
35
+ "outputs": [],
36
+ "source": [
37
+ "# 4D DataArray: scenario x metric x year x country\n",
38
+ "df_gap = px.data.gapminder()\n",
39
+ "countries = [\"United States\", \"China\", \"Germany\", \"Brazil\"]\n",
40
+ "metrics = [\"lifeExp\", \"gdpPercap\"]\n",
41
+ "\n",
42
+ "# Build base 3D array (metric x year x country)\n",
43
+ "arrays = []\n",
44
+ "for metric in metrics:\n",
45
+ " df_pivot = df_gap[df_gap[\"country\"].isin(countries)].pivot(\n",
46
+ " index=\"year\", columns=\"country\", values=metric\n",
47
+ " )\n",
48
+ " arrays.append(df_pivot.values)\n",
49
+ "\n",
50
+ "base_3d = np.stack(arrays)\n",
51
+ "\n",
52
+ "# Add scenario dimension (4D): original + 10% higher\n",
53
+ "scenarios = [\"baseline\", \"optimistic\"]\n",
54
+ "data_4d = np.stack([base_3d, base_3d * 1.1])\n",
55
+ "\n",
56
+ "da = xr.DataArray(\n",
57
+ " data_4d,\n",
58
+ " dims=[\"scenario\", \"metric\", \"year\", \"country\"],\n",
59
+ " coords={\n",
60
+ " \"scenario\": scenarios,\n",
61
+ " \"metric\": metrics,\n",
62
+ " \"year\": df_pivot.index.tolist(),\n",
63
+ " \"country\": df_pivot.columns.tolist(),\n",
64
+ " },\n",
65
+ " name=\"value\",\n",
66
+ ")\n",
67
+ "da"
68
+ ]
69
+ },
70
+ {
71
+ "cell_type": "markdown",
72
+ "id": "3",
73
+ "metadata": {},
74
+ "source": [
75
+ "---\n",
76
+ "## Standard Plotly Methods\n",
77
+ "\n",
78
+ "All standard Plotly manipulation methods work on figures created with `xpx()`."
79
+ ]
80
+ },
81
+ {
82
+ "cell_type": "code",
83
+ "execution_count": null,
84
+ "id": "4",
85
+ "metadata": {},
86
+ "outputs": [],
87
+ "source": [
88
+ "# Simple 2D slice\n",
89
+ "fig = xpx(da.sel(scenario=\"baseline\", metric=\"lifeExp\")).line()\n",
90
+ "fig"
91
+ ]
92
+ },
93
+ {
94
+ "cell_type": "code",
95
+ "execution_count": null,
96
+ "id": "5",
97
+ "metadata": {},
98
+ "outputs": [],
99
+ "source": [
100
+ "# Layout\n",
101
+ "fig.update_layout(title=\"Life Expectancy Over Time\", template=\"plotly_white\")\n",
102
+ "\n",
103
+ "# All traces\n",
104
+ "fig.update_traces(line_width=3)\n",
105
+ "\n",
106
+ "# Specific trace by name\n",
107
+ "fig.update_traces(line_dash=\"dot\", selector={\"name\": \"Germany\"})\n",
108
+ "\n",
109
+ "# Axes\n",
110
+ "fig.update_xaxes(title=\"Year\", showgrid=False)\n",
111
+ "fig.update_yaxes(title=\"Life Expectancy (years)\", range=[40, 85])\n",
112
+ "\n",
113
+ "# Reference line\n",
114
+ "fig.add_hline(y=70, line_dash=\"dash\", line_color=\"gray\", annotation_text=\"Target\")\n",
115
+ "\n",
116
+ "fig"
117
+ ]
118
+ },
119
+ {
120
+ "cell_type": "markdown",
121
+ "id": "6",
122
+ "metadata": {},
123
+ "source": [
124
+ "---\n",
125
+ "## Faceted Plots\n",
126
+ "\n",
127
+ "`update_traces`, `update_xaxes`, `update_yaxes` work across all facets."
128
+ ]
129
+ },
130
+ {
131
+ "cell_type": "code",
132
+ "execution_count": null,
133
+ "id": "7",
134
+ "metadata": {},
135
+ "outputs": [],
136
+ "source": [
137
+ "# Facet by metric, color by country\n",
138
+ "fig = xpx(da.sel(scenario=\"baseline\")).line(facet_col=\"metric\")\n",
139
+ "fig"
140
+ ]
141
+ },
142
+ {
143
+ "cell_type": "code",
144
+ "execution_count": null,
145
+ "id": "8",
146
+ "metadata": {},
147
+ "outputs": [],
148
+ "source": [
149
+ "# Update ALL traces across all facets\n",
150
+ "fig.update_traces(line_width=2)\n",
151
+ "\n",
152
+ "# Update ALL axes\n",
153
+ "fig.update_xaxes(showgrid=False)\n",
154
+ "\n",
155
+ "# Target specific facet (1-indexed)\n",
156
+ "fig.update_yaxes(type=\"log\", col=2) # log scale only for gdpPercap\n",
157
+ "\n",
158
+ "fig"
159
+ ]
160
+ },
161
+ {
162
+ "cell_type": "markdown",
163
+ "id": "9",
164
+ "metadata": {},
165
+ "source": [
166
+ "### Grid layout with facet_row"
167
+ ]
168
+ },
169
+ {
170
+ "cell_type": "code",
171
+ "execution_count": null,
172
+ "id": "10",
173
+ "metadata": {},
174
+ "outputs": [],
175
+ "source": [
176
+ "# 2x2 grid: scenario x metric\n",
177
+ "fig = xpx(da).line(facet_col=\"metric\", facet_row=\"scenario\")\n",
178
+ "\n",
179
+ "fig.update_traces(line_width=2)\n",
180
+ "fig.update_yaxes(type=\"log\", col=2) # log scale for gdpPercap column\n",
181
+ "fig"
182
+ ]
183
+ },
184
+ {
185
+ "cell_type": "markdown",
186
+ "id": "11",
187
+ "metadata": {},
188
+ "source": [
189
+ "---\n",
190
+ "## Animation: The Pain Point\n",
191
+ "\n",
192
+ "Plotly's `fig.update_traces()` does **not** update animation frames. This is the main gotcha."
193
+ ]
194
+ },
195
+ {
196
+ "cell_type": "code",
197
+ "execution_count": null,
198
+ "id": "12",
199
+ "metadata": {},
200
+ "outputs": [],
201
+ "source": [
202
+ "# Animated bar chart\n",
203
+ "fig = xpx(da.sel(scenario=\"baseline\", metric=\"gdpPercap\")).bar(animation_frame=\"year\")\n",
204
+ "fig"
205
+ ]
206
+ },
207
+ {
208
+ "cell_type": "code",
209
+ "execution_count": null,
210
+ "id": "13",
211
+ "metadata": {},
212
+ "outputs": [],
213
+ "source": [
214
+ "# This only affects the INITIAL view!\n",
215
+ "fig.update_traces(marker_color=\"red\")\n",
216
+ "\n",
217
+ "print(f\"Base trace color: {fig.data[0].marker.color}\")\n",
218
+ "print(f\"Frame 0 trace color: {fig.frames[0].data[0].marker.color}\")"
219
+ ]
220
+ },
221
+ {
222
+ "cell_type": "code",
223
+ "execution_count": null,
224
+ "id": "14",
225
+ "metadata": {},
226
+ "outputs": [],
227
+ "source": [
228
+ "# Play the animation - it reverts to original colors\n",
229
+ "fig"
230
+ ]
231
+ },
232
+ {
233
+ "cell_type": "markdown",
234
+ "id": "15",
235
+ "metadata": {},
236
+ "source": [
237
+ "### Solution: `update_traces` from xarray-plotly\n",
238
+ "\n",
239
+ "This helper updates both base traces and all animation frames."
240
+ ]
241
+ },
242
+ {
243
+ "cell_type": "code",
244
+ "execution_count": null,
245
+ "id": "16",
246
+ "metadata": {},
247
+ "outputs": [],
248
+ "source": [
249
+ "fig = xpx(da.sel(scenario=\"baseline\", metric=\"gdpPercap\")).bar(animation_frame=\"year\")\n",
250
+ "\n",
251
+ "update_traces(fig, marker_color=\"red\", marker_opacity=0.8)\n",
252
+ "\n",
253
+ "print(f\"Base trace color: {fig.data[0].marker.color}\")\n",
254
+ "print(f\"Frame 0 trace color: {fig.frames[0].data[0].marker.color}\")"
255
+ ]
256
+ },
257
+ {
258
+ "cell_type": "code",
259
+ "execution_count": null,
260
+ "id": "17",
261
+ "metadata": {},
262
+ "outputs": [],
263
+ "source": [
264
+ "# Now the style persists through animation\n",
265
+ "fig"
266
+ ]
267
+ },
268
+ {
269
+ "cell_type": "markdown",
270
+ "id": "18",
271
+ "metadata": {},
272
+ "source": [
273
+ "### Selective updates with selector"
274
+ ]
275
+ },
276
+ {
277
+ "cell_type": "code",
278
+ "execution_count": null,
279
+ "id": "19",
280
+ "metadata": {},
281
+ "outputs": [],
282
+ "source": [
283
+ "fig = xpx(da.sel(scenario=\"baseline\", metric=\"lifeExp\")).line(x=\"year\")\n",
284
+ "\n",
285
+ "# Highlight specific countries\n",
286
+ "update_traces(fig, selector={\"name\": \"China\"}, line_color=\"red\", line_width=4)\n",
287
+ "update_traces(fig, selector={\"name\": \"United States\"}, line_color=\"blue\", line_width=4)\n",
288
+ "\n",
289
+ "fig"
290
+ ]
291
+ },
292
+ {
293
+ "cell_type": "markdown",
294
+ "id": "20",
295
+ "metadata": {},
296
+ "source": [
297
+ "### Unified hover with animation\n",
298
+ "\n",
299
+ "A common pattern: unified hover mode with custom formatting.\n",
300
+ "\n",
301
+ "- **Layout** (`hovermode`, spikes): Standard Plotly works fine\n",
302
+ "- **Traces** (`hovertemplate`): Use `update_traces()` for animation support"
303
+ ]
304
+ },
305
+ {
306
+ "cell_type": "code",
307
+ "execution_count": null,
308
+ "id": "21",
309
+ "metadata": {},
310
+ "outputs": [],
311
+ "source": [
312
+ "fig = xpx(da.sel(metric=\"gdpPercap\")).line(x=\"year\", animation_frame=\"scenario\")\n",
313
+ "\n",
314
+ "# Layout settings - standard Plotly\n",
315
+ "fig.update_layout(hovermode=\"x unified\")\n",
316
+ "fig.update_xaxes(showspikes=True, spikecolor=\"gray\", spikethickness=1)\n",
317
+ "\n",
318
+ "# Trace settings - use update_traces for animation support\n",
319
+ "update_traces(fig, hovertemplate=\"<b>%{fullData.name}</b>: $%{y:,.0f}<extra></extra>\")\n",
320
+ "\n",
321
+ "fig"
322
+ ]
323
+ },
324
+ {
325
+ "cell_type": "markdown",
326
+ "id": "22",
327
+ "metadata": {},
328
+ "source": [
329
+ "### Facets + Animation"
330
+ ]
331
+ },
332
+ {
333
+ "cell_type": "code",
334
+ "execution_count": null,
335
+ "id": "23",
336
+ "metadata": {},
337
+ "outputs": [],
338
+ "source": [
339
+ "# Facet by metric, animate by scenario\n",
340
+ "fig = xpx(da).line(facet_col=\"metric\", animation_frame=\"scenario\")\n",
341
+ "\n",
342
+ "# Standard Plotly for layout\n",
343
+ "fig.update_yaxes(type=\"log\", col=2)\n",
344
+ "\n",
345
+ "# update_traces for trace properties with animation\n",
346
+ "update_traces(fig, line_width=3)\n",
347
+ "update_traces(fig, selector={\"name\": \"China\"}, line_dash=\"dot\")\n",
348
+ "\n",
349
+ "fig"
350
+ ]
351
+ },
352
+ {
353
+ "cell_type": "markdown",
354
+ "id": "24",
355
+ "metadata": {},
356
+ "source": [
357
+ "---\n",
358
+ "## Summary\n",
359
+ "\n",
360
+ "| Method | Static/Faceted | Animated |\n",
361
+ "|--------|----------------|----------|\n",
362
+ "| `fig.update_layout()` | ✅ | ✅ |\n",
363
+ "| `fig.update_xaxes()` / `fig.update_yaxes()` | ✅ | ✅ |\n",
364
+ "| `fig.add_hline()` / `fig.add_vline()` | ✅ | ✅ |\n",
365
+ "| `fig.update_traces()` | ✅ | ❌ base only |\n",
366
+ "| `update_traces(fig, ...)` | ✅ | ✅ all frames |"
367
+ ]
368
+ }
369
+ ],
370
+ "metadata": {
371
+ "kernelspec": {
372
+ "display_name": "Python 3",
373
+ "language": "python",
374
+ "name": "python3"
375
+ },
376
+ "language_info": {
377
+ "name": "python",
378
+ "version": "3.12.0"
379
+ }
380
+ },
381
+ "nbformat": 4,
382
+ "nbformat_minor": 5
383
+ }
@@ -73,4 +73,7 @@ nav:
73
73
  - Dimensions & Facets: examples/dimensions.ipynb
74
74
  - Plotly Express Options: examples/kwargs.ipynb
75
75
  - Figure Customization: examples/figure.ipynb
76
+ - Combining Figures: examples/combining.ipynb
77
+ - Figure Manipulation: examples/manipulation.ipynb
78
+ - Fast Bar Charts: examples/fast_bar.ipynb
76
79
  - API Reference: api.md