xarray-plotly 0.0.6__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.
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/PKG-INFO +2 -2
- xarray_plotly-0.0.8/docs/examples/combining.ipynb +646 -0
- xarray_plotly-0.0.8/docs/examples/fast_bar.ipynb +297 -0
- xarray_plotly-0.0.8/docs/examples/manipulation.ipynb +383 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/mkdocs.yml +3 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/pyproject.toml +1 -1
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/tests/test_accessor.py +60 -0
- xarray_plotly-0.0.8/tests/test_figures.py +590 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly/__init__.py +8 -2
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly/accessor.py +78 -2
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly/config.py +1 -0
- xarray_plotly-0.0.8/xarray_plotly/figures.py +448 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly/plotting.py +174 -1
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/PKG-INFO +2 -2
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/SOURCES.txt +5 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/requires.txt +1 -1
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/.github/dependabot.yml +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/.github/workflows/ci.yml +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/.github/workflows/dependabot-auto-merge.yml +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/.github/workflows/docs.yml +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/.github/workflows/release.yml +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/.gitignore +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/.pre-commit-config.yaml +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/CONTRIBUTING.md +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/LICENSE +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/README.md +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/docs/api.md +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/docs/examples/datasets.ipynb +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/docs/examples/dimensions.ipynb +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/docs/examples/figure.ipynb +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/docs/examples/kwargs.ipynb +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/docs/examples/plot-types.ipynb +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/docs/getting-started.ipynb +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/docs/index.md +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/setup.cfg +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/tests/__init__.py +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/tests/test_common.py +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/tests/test_config.py +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly/common.py +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly/py.typed +0 -0
- {xarray_plotly-0.0.6 → xarray_plotly-0.0.8}/xarray_plotly.egg-info/dependency_links.txt +0 -0
- {xarray_plotly-0.0.6 → 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.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: Interactive Plotly Express plotting accessor for xarray
|
|
5
5
|
Author: Felix
|
|
6
6
|
License: MIT
|
|
@@ -28,7 +28,7 @@ Provides-Extra: dev
|
|
|
28
28
|
Requires-Dist: pytest==9.0.2; extra == "dev"
|
|
29
29
|
Requires-Dist: pytest-cov==7.0.0; extra == "dev"
|
|
30
30
|
Requires-Dist: mypy==1.19.1; extra == "dev"
|
|
31
|
-
Requires-Dist: ruff==0.14.
|
|
31
|
+
Requires-Dist: ruff==0.14.13; extra == "dev"
|
|
32
32
|
Requires-Dist: pre-commit==4.5.1; extra == "dev"
|
|
33
33
|
Requires-Dist: nbstripout==0.8.2; extra == "dev"
|
|
34
34
|
Provides-Extra: docs
|
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"id": "0",
|
|
6
|
+
"metadata": {},
|
|
7
|
+
"source": [
|
|
8
|
+
"# Combining Figures\n",
|
|
9
|
+
"\n",
|
|
10
|
+
"xarray-plotly provides helper functions to combine multiple figures:\n",
|
|
11
|
+
"\n",
|
|
12
|
+
"- **`overlay`**: Overlay traces on the same axes\n",
|
|
13
|
+
"- **`add_secondary_y`**: Plot with two independent y-axes"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"cell_type": "code",
|
|
18
|
+
"execution_count": null,
|
|
19
|
+
"id": "1",
|
|
20
|
+
"metadata": {},
|
|
21
|
+
"outputs": [],
|
|
22
|
+
"source": [
|
|
23
|
+
"import plotly.express as px\n",
|
|
24
|
+
"import xarray as xr\n",
|
|
25
|
+
"\n",
|
|
26
|
+
"from xarray_plotly import add_secondary_y, config, overlay, xpx\n",
|
|
27
|
+
"\n",
|
|
28
|
+
"config.notebook()"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"cell_type": "markdown",
|
|
33
|
+
"id": "2",
|
|
34
|
+
"metadata": {},
|
|
35
|
+
"source": [
|
|
36
|
+
"## Load Sample Data"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"cell_type": "code",
|
|
41
|
+
"execution_count": null,
|
|
42
|
+
"id": "3",
|
|
43
|
+
"metadata": {},
|
|
44
|
+
"outputs": [],
|
|
45
|
+
"source": [
|
|
46
|
+
"# Stock prices\n",
|
|
47
|
+
"df_stocks = px.data.stocks().set_index(\"date\")\n",
|
|
48
|
+
"df_stocks.index = df_stocks.index.astype(\"datetime64[ns]\")\n",
|
|
49
|
+
"\n",
|
|
50
|
+
"stocks = xr.DataArray(\n",
|
|
51
|
+
" df_stocks.values,\n",
|
|
52
|
+
" dims=[\"date\", \"company\"],\n",
|
|
53
|
+
" coords={\"date\": df_stocks.index, \"company\": df_stocks.columns.tolist()},\n",
|
|
54
|
+
" name=\"price\",\n",
|
|
55
|
+
")\n",
|
|
56
|
+
"\n",
|
|
57
|
+
"# Gapminder data (subset: a few countries)\n",
|
|
58
|
+
"df_gap = px.data.gapminder()\n",
|
|
59
|
+
"countries = [\"United States\", \"China\", \"Germany\", \"Brazil\"]\n",
|
|
60
|
+
"df_gap = df_gap[df_gap[\"country\"].isin(countries)]\n",
|
|
61
|
+
"\n",
|
|
62
|
+
"# Convert to xarray\n",
|
|
63
|
+
"gap_pop = df_gap.pivot(index=\"year\", columns=\"country\", values=\"pop\")\n",
|
|
64
|
+
"gap_gdp = df_gap.pivot(index=\"year\", columns=\"country\", values=\"gdpPercap\")\n",
|
|
65
|
+
"gap_life = df_gap.pivot(index=\"year\", columns=\"country\", values=\"lifeExp\")\n",
|
|
66
|
+
"\n",
|
|
67
|
+
"population = xr.DataArray(\n",
|
|
68
|
+
" gap_pop.values,\n",
|
|
69
|
+
" dims=[\"year\", \"country\"],\n",
|
|
70
|
+
" coords={\"year\": gap_pop.index.values, \"country\": gap_pop.columns.tolist()},\n",
|
|
71
|
+
" name=\"Population\",\n",
|
|
72
|
+
")\n",
|
|
73
|
+
"\n",
|
|
74
|
+
"gdp_per_capita = xr.DataArray(\n",
|
|
75
|
+
" gap_gdp.values,\n",
|
|
76
|
+
" dims=[\"year\", \"country\"],\n",
|
|
77
|
+
" coords={\"year\": gap_gdp.index.values, \"country\": gap_gdp.columns.tolist()},\n",
|
|
78
|
+
" name=\"GDP per Capita\",\n",
|
|
79
|
+
")\n",
|
|
80
|
+
"\n",
|
|
81
|
+
"life_expectancy = xr.DataArray(\n",
|
|
82
|
+
" gap_life.values,\n",
|
|
83
|
+
" dims=[\"year\", \"country\"],\n",
|
|
84
|
+
" coords={\"year\": gap_life.index.values, \"country\": gap_life.columns.tolist()},\n",
|
|
85
|
+
" name=\"Life Expectancy\",\n",
|
|
86
|
+
")"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"cell_type": "markdown",
|
|
91
|
+
"id": "4",
|
|
92
|
+
"metadata": {},
|
|
93
|
+
"source": [
|
|
94
|
+
"## overlay\n",
|
|
95
|
+
"\n",
|
|
96
|
+
"Overlay multiple figures on the same axes. Useful for showing data with a trend line, moving average, or different visualizations of related data."
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"cell_type": "markdown",
|
|
101
|
+
"id": "5",
|
|
102
|
+
"metadata": {},
|
|
103
|
+
"source": [
|
|
104
|
+
"### Stock Price with Moving Average"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"cell_type": "code",
|
|
109
|
+
"execution_count": null,
|
|
110
|
+
"id": "6",
|
|
111
|
+
"metadata": {},
|
|
112
|
+
"outputs": [],
|
|
113
|
+
"source": [
|
|
114
|
+
"# Select one company\n",
|
|
115
|
+
"goog = stocks.sel(company=\"GOOG\")\n",
|
|
116
|
+
"\n",
|
|
117
|
+
"# Calculate 20-day moving average\n",
|
|
118
|
+
"goog_ma = goog.rolling(date=20, center=True).mean()\n",
|
|
119
|
+
"goog_ma.name = \"20-day MA\"\n",
|
|
120
|
+
"\n",
|
|
121
|
+
"# Raw prices as scatter\n",
|
|
122
|
+
"price_fig = xpx(goog).scatter()\n",
|
|
123
|
+
"price_fig.update_traces(marker={\"size\": 4, \"opacity\": 0.5}, name=\"Daily Price\")\n",
|
|
124
|
+
"\n",
|
|
125
|
+
"# Moving average as line\n",
|
|
126
|
+
"ma_fig = xpx(goog_ma).line()\n",
|
|
127
|
+
"ma_fig.update_traces(line={\"color\": \"red\", \"width\": 3}, name=\"20-day MA\")\n",
|
|
128
|
+
"\n",
|
|
129
|
+
"combined = overlay(price_fig, ma_fig)\n",
|
|
130
|
+
"combined.update_layout(title=\"GOOG: Daily Price with Moving Average\")\n",
|
|
131
|
+
"combined"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"cell_type": "markdown",
|
|
136
|
+
"id": "7",
|
|
137
|
+
"metadata": {},
|
|
138
|
+
"source": [
|
|
139
|
+
"### Multiple Companies with Moving Averages"
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"cell_type": "code",
|
|
144
|
+
"execution_count": null,
|
|
145
|
+
"id": "8",
|
|
146
|
+
"metadata": {},
|
|
147
|
+
"outputs": [],
|
|
148
|
+
"source": [
|
|
149
|
+
"# Select a few companies\n",
|
|
150
|
+
"subset = stocks.sel(company=[\"GOOG\", \"AAPL\", \"MSFT\"])\n",
|
|
151
|
+
"subset_ma = subset.rolling(date=20, center=True).mean()\n",
|
|
152
|
+
"\n",
|
|
153
|
+
"# Raw as scatter (faded)\n",
|
|
154
|
+
"raw_fig = xpx(subset).scatter()\n",
|
|
155
|
+
"raw_fig.update_traces(marker={\"size\": 3, \"opacity\": 0.3})\n",
|
|
156
|
+
"\n",
|
|
157
|
+
"# MA as lines (bold)\n",
|
|
158
|
+
"ma_fig = xpx(subset_ma).line()\n",
|
|
159
|
+
"ma_fig.update_traces(line={\"width\": 3})\n",
|
|
160
|
+
"\n",
|
|
161
|
+
"combined = overlay(raw_fig, ma_fig)\n",
|
|
162
|
+
"combined.update_layout(title=\"Tech Stocks: Raw Prices + Moving Averages\")\n",
|
|
163
|
+
"combined"
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"cell_type": "markdown",
|
|
168
|
+
"id": "9",
|
|
169
|
+
"metadata": {},
|
|
170
|
+
"source": [
|
|
171
|
+
"### With Facets\n",
|
|
172
|
+
"\n",
|
|
173
|
+
"`overlay` works with faceted figures as long as both have the same structure."
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"cell_type": "code",
|
|
178
|
+
"execution_count": null,
|
|
179
|
+
"id": "10",
|
|
180
|
+
"metadata": {},
|
|
181
|
+
"outputs": [],
|
|
182
|
+
"source": [
|
|
183
|
+
"# Faceted by company\n",
|
|
184
|
+
"raw_faceted = xpx(subset).scatter(facet_col=\"company\")\n",
|
|
185
|
+
"raw_faceted.update_traces(marker={\"size\": 3, \"opacity\": 0.4})\n",
|
|
186
|
+
"\n",
|
|
187
|
+
"ma_faceted = xpx(subset_ma).line(facet_col=\"company\")\n",
|
|
188
|
+
"ma_faceted.update_traces(line={\"color\": \"red\", \"width\": 2})\n",
|
|
189
|
+
"\n",
|
|
190
|
+
"combined = overlay(raw_faceted, ma_faceted)\n",
|
|
191
|
+
"combined.update_layout(title=\"Faceted: Price + Moving Average per Company\")\n",
|
|
192
|
+
"combined"
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"cell_type": "markdown",
|
|
197
|
+
"id": "11",
|
|
198
|
+
"metadata": {},
|
|
199
|
+
"source": [
|
|
200
|
+
"### With Animation\n",
|
|
201
|
+
"\n",
|
|
202
|
+
"Overlay animated figures - frames are merged correctly."
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"cell_type": "code",
|
|
207
|
+
"execution_count": null,
|
|
208
|
+
"id": "12",
|
|
209
|
+
"metadata": {},
|
|
210
|
+
"outputs": [],
|
|
211
|
+
"source": [
|
|
212
|
+
"# Animate through countries, showing population bar + GDP line\n",
|
|
213
|
+
"# Both use the same animation dimension\n",
|
|
214
|
+
"pop_anim = xpx(population).bar(animation_frame=\"country\")\n",
|
|
215
|
+
"pop_anim.update_traces(marker={\"opacity\": 0.6})\n",
|
|
216
|
+
"\n",
|
|
217
|
+
"# Create a \"target\" line (e.g., some reference value)\n",
|
|
218
|
+
"pop_smooth = population.rolling(year=3, center=True).mean()\n",
|
|
219
|
+
"smooth_anim = xpx(pop_smooth).line(animation_frame=\"country\")\n",
|
|
220
|
+
"smooth_anim.update_traces(line={\"color\": \"red\", \"width\": 3})\n",
|
|
221
|
+
"\n",
|
|
222
|
+
"combined = overlay(pop_anim, smooth_anim)\n",
|
|
223
|
+
"combined.update_layout(title=\"Population: Raw + Smoothed (animated by country)\")\n",
|
|
224
|
+
"combined"
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"cell_type": "markdown",
|
|
229
|
+
"id": "13",
|
|
230
|
+
"metadata": {},
|
|
231
|
+
"source": [
|
|
232
|
+
"### Static Overlay on Animated Base\n",
|
|
233
|
+
"\n",
|
|
234
|
+
"A static figure can be overlaid on an animated one - the static traces appear in all frames."
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"cell_type": "code",
|
|
239
|
+
"execution_count": null,
|
|
240
|
+
"id": "14",
|
|
241
|
+
"metadata": {},
|
|
242
|
+
"outputs": [],
|
|
243
|
+
"source": [
|
|
244
|
+
"# Animated population\n",
|
|
245
|
+
"pop_anim = xpx(population).bar(animation_frame=\"country\")\n",
|
|
246
|
+
"pop_anim.update_traces(marker={\"opacity\": 0.7})\n",
|
|
247
|
+
"\n",
|
|
248
|
+
"# Static reference line (global average across all countries)\n",
|
|
249
|
+
"global_avg = population.mean(dim=\"country\")\n",
|
|
250
|
+
"avg_fig = xpx(global_avg).line()\n",
|
|
251
|
+
"avg_fig.update_traces(line={\"color\": \"black\", \"width\": 2, \"dash\": \"dash\"}, name=\"Global Avg\")\n",
|
|
252
|
+
"\n",
|
|
253
|
+
"combined = overlay(pop_anim, avg_fig)\n",
|
|
254
|
+
"combined.update_layout(title=\"Population by Country vs Global Average\")\n",
|
|
255
|
+
"combined"
|
|
256
|
+
]
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"cell_type": "markdown",
|
|
260
|
+
"id": "15",
|
|
261
|
+
"metadata": {},
|
|
262
|
+
"source": [
|
|
263
|
+
"## add_secondary_y\n",
|
|
264
|
+
"\n",
|
|
265
|
+
"Plot two variables with different scales using independent y-axes. Essential when values have different magnitudes."
|
|
266
|
+
]
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"cell_type": "markdown",
|
|
270
|
+
"id": "16",
|
|
271
|
+
"metadata": {},
|
|
272
|
+
"source": [
|
|
273
|
+
"### Population vs GDP per Capita"
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"cell_type": "code",
|
|
278
|
+
"execution_count": null,
|
|
279
|
+
"id": "17",
|
|
280
|
+
"metadata": {},
|
|
281
|
+
"outputs": [],
|
|
282
|
+
"source": [
|
|
283
|
+
"# Select one country\n",
|
|
284
|
+
"us_pop = population.sel(country=\"United States\")\n",
|
|
285
|
+
"us_gdp = gdp_per_capita.sel(country=\"United States\")\n",
|
|
286
|
+
"\n",
|
|
287
|
+
"pop_fig = xpx(us_pop).bar()\n",
|
|
288
|
+
"pop_fig.update_traces(marker={\"color\": \"steelblue\", \"opacity\": 0.7}, name=\"Population\")\n",
|
|
289
|
+
"\n",
|
|
290
|
+
"gdp_fig = xpx(us_gdp).line()\n",
|
|
291
|
+
"gdp_fig.update_traces(line={\"color\": \"red\", \"width\": 3}, name=\"GDP per Capita\")\n",
|
|
292
|
+
"\n",
|
|
293
|
+
"combined = add_secondary_y(pop_fig, gdp_fig, secondary_y_title=\"GDP per Capita ($)\")\n",
|
|
294
|
+
"combined.update_layout(\n",
|
|
295
|
+
" title=\"United States: Population vs GDP per Capita\",\n",
|
|
296
|
+
" yaxis_title=\"Population\",\n",
|
|
297
|
+
")\n",
|
|
298
|
+
"combined"
|
|
299
|
+
]
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"cell_type": "markdown",
|
|
303
|
+
"id": "18",
|
|
304
|
+
"metadata": {},
|
|
305
|
+
"source": [
|
|
306
|
+
"### Why Secondary Y-Axis Matters\n",
|
|
307
|
+
"\n",
|
|
308
|
+
"Without it, one variable dominates due to scale mismatch:"
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"cell_type": "code",
|
|
313
|
+
"execution_count": null,
|
|
314
|
+
"id": "19",
|
|
315
|
+
"metadata": {},
|
|
316
|
+
"outputs": [],
|
|
317
|
+
"source": [
|
|
318
|
+
"# Same data on single y-axis - GDP looks flat because population is ~1e8, GDP is ~1e4\n",
|
|
319
|
+
"pop_fig = xpx(us_pop).line()\n",
|
|
320
|
+
"pop_fig.update_traces(name=\"Population\", line={\"color\": \"blue\"})\n",
|
|
321
|
+
"\n",
|
|
322
|
+
"gdp_fig = xpx(us_gdp).line()\n",
|
|
323
|
+
"gdp_fig.update_traces(name=\"GDP per Capita\", line={\"color\": \"red\"})\n",
|
|
324
|
+
"\n",
|
|
325
|
+
"bad = overlay(pop_fig, gdp_fig)\n",
|
|
326
|
+
"bad.update_layout(title=\"overlay: GDP invisible (scale mismatch)\")\n",
|
|
327
|
+
"bad"
|
|
328
|
+
]
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"cell_type": "code",
|
|
332
|
+
"execution_count": null,
|
|
333
|
+
"id": "20",
|
|
334
|
+
"metadata": {},
|
|
335
|
+
"outputs": [],
|
|
336
|
+
"source": [
|
|
337
|
+
"# With add_secondary_y - both variables visible\n",
|
|
338
|
+
"pop_fig = xpx(us_pop).line()\n",
|
|
339
|
+
"pop_fig.update_traces(name=\"Population\", line={\"color\": \"blue\", \"width\": 2})\n",
|
|
340
|
+
"\n",
|
|
341
|
+
"gdp_fig = xpx(us_gdp).line()\n",
|
|
342
|
+
"gdp_fig.update_traces(name=\"GDP per Capita\", line={\"color\": \"red\", \"width\": 2})\n",
|
|
343
|
+
"\n",
|
|
344
|
+
"good = add_secondary_y(pop_fig, gdp_fig, secondary_y_title=\"GDP per Capita ($)\")\n",
|
|
345
|
+
"good.update_layout(title=\"add_secondary_y: Both variables clearly visible\")\n",
|
|
346
|
+
"good"
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"cell_type": "markdown",
|
|
351
|
+
"id": "21",
|
|
352
|
+
"metadata": {},
|
|
353
|
+
"source": [
|
|
354
|
+
"### With Animation\n",
|
|
355
|
+
"\n",
|
|
356
|
+
"`add_secondary_y` supports animated figures with matching frames."
|
|
357
|
+
]
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"cell_type": "code",
|
|
361
|
+
"execution_count": null,
|
|
362
|
+
"id": "22",
|
|
363
|
+
"metadata": {},
|
|
364
|
+
"outputs": [],
|
|
365
|
+
"source": [
|
|
366
|
+
"# Animate through countries\n",
|
|
367
|
+
"pop_anim = xpx(population).bar(animation_frame=\"country\")\n",
|
|
368
|
+
"pop_anim.update_traces(marker={\"color\": \"steelblue\", \"opacity\": 0.7})\n",
|
|
369
|
+
"\n",
|
|
370
|
+
"gdp_anim = xpx(gdp_per_capita).line(animation_frame=\"country\")\n",
|
|
371
|
+
"gdp_anim.update_traces(line={\"color\": \"red\", \"width\": 3})\n",
|
|
372
|
+
"\n",
|
|
373
|
+
"combined = add_secondary_y(pop_anim, gdp_anim, secondary_y_title=\"GDP per Capita ($)\")\n",
|
|
374
|
+
"combined.update_layout(title=\"Population vs GDP (animated by country)\")\n",
|
|
375
|
+
"combined"
|
|
376
|
+
]
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"cell_type": "markdown",
|
|
380
|
+
"id": "23",
|
|
381
|
+
"metadata": {},
|
|
382
|
+
"source": [
|
|
383
|
+
"### Static Secondary on Animated Base\n",
|
|
384
|
+
"\n",
|
|
385
|
+
"A static secondary figure is replicated to all animation frames."
|
|
386
|
+
]
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"cell_type": "code",
|
|
390
|
+
"execution_count": null,
|
|
391
|
+
"id": "24",
|
|
392
|
+
"metadata": {},
|
|
393
|
+
"outputs": [],
|
|
394
|
+
"source": [
|
|
395
|
+
"# Animated population\n",
|
|
396
|
+
"pop_anim = xpx(population).bar(animation_frame=\"country\")\n",
|
|
397
|
+
"pop_anim.update_traces(marker={\"color\": \"steelblue\", \"opacity\": 0.7})\n",
|
|
398
|
+
"\n",
|
|
399
|
+
"# Static GDP reference (US only, shown in all frames)\n",
|
|
400
|
+
"us_gdp_static = xpx(us_gdp).line()\n",
|
|
401
|
+
"us_gdp_static.update_traces(\n",
|
|
402
|
+
" line={\"color\": \"red\", \"width\": 2, \"dash\": \"dash\"}, name=\"US GDP (reference)\"\n",
|
|
403
|
+
")\n",
|
|
404
|
+
"\n",
|
|
405
|
+
"combined = add_secondary_y(pop_anim, us_gdp_static, secondary_y_title=\"GDP per Capita ($)\")\n",
|
|
406
|
+
"combined.update_layout(title=\"Population (animated) vs US GDP (static reference)\")\n",
|
|
407
|
+
"combined"
|
|
408
|
+
]
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"cell_type": "markdown",
|
|
412
|
+
"id": "25",
|
|
413
|
+
"metadata": {},
|
|
414
|
+
"source": [
|
|
415
|
+
"### With Facets\n",
|
|
416
|
+
"\n",
|
|
417
|
+
"`add_secondary_y` works with faceted figures when both have the same facet structure."
|
|
418
|
+
]
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"cell_type": "code",
|
|
422
|
+
"execution_count": null,
|
|
423
|
+
"id": "26",
|
|
424
|
+
"metadata": {},
|
|
425
|
+
"outputs": [],
|
|
426
|
+
"source": [
|
|
427
|
+
"# Faceted by country - both figures must have same facet structure\n",
|
|
428
|
+
"pop_faceted = xpx(population).bar(facet_col=\"country\")\n",
|
|
429
|
+
"pop_faceted.update_traces(marker={\"color\": \"steelblue\", \"opacity\": 0.7})\n",
|
|
430
|
+
"\n",
|
|
431
|
+
"gdp_faceted = xpx(gdp_per_capita).line(facet_col=\"country\")\n",
|
|
432
|
+
"gdp_faceted.update_traces(line={\"color\": \"red\", \"width\": 3})\n",
|
|
433
|
+
"\n",
|
|
434
|
+
"combined = add_secondary_y(pop_faceted, gdp_faceted, secondary_y_title=\"GDP per Capita ($)\")\n",
|
|
435
|
+
"combined.update_layout(title=\"Population vs GDP per Capita (faceted by country)\")\n",
|
|
436
|
+
"combined"
|
|
437
|
+
]
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"cell_type": "markdown",
|
|
441
|
+
"id": "27",
|
|
442
|
+
"metadata": {},
|
|
443
|
+
"source": [
|
|
444
|
+
"---\n",
|
|
445
|
+
"\n",
|
|
446
|
+
"## Limitations (with examples)\n",
|
|
447
|
+
"\n",
|
|
448
|
+
"Both functions validate inputs and raise clear errors when constraints are violated."
|
|
449
|
+
]
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"cell_type": "markdown",
|
|
453
|
+
"id": "28",
|
|
454
|
+
"metadata": {},
|
|
455
|
+
"source": [
|
|
456
|
+
"### overlay: Mismatched Facet Structure\n",
|
|
457
|
+
"\n",
|
|
458
|
+
"Overlay cannot have subplots that don't exist in base."
|
|
459
|
+
]
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"cell_type": "code",
|
|
463
|
+
"execution_count": null,
|
|
464
|
+
"id": "29",
|
|
465
|
+
"metadata": {},
|
|
466
|
+
"outputs": [],
|
|
467
|
+
"source": [
|
|
468
|
+
"# Base: no facets\n",
|
|
469
|
+
"base = xpx(stocks.sel(company=\"GOOG\")).line()\n",
|
|
470
|
+
"\n",
|
|
471
|
+
"# Overlay: has facets\n",
|
|
472
|
+
"overlay_fig = xpx(stocks.sel(company=[\"GOOG\", \"AAPL\"])).line(facet_col=\"company\")\n",
|
|
473
|
+
"\n",
|
|
474
|
+
"try:\n",
|
|
475
|
+
" overlay(base, overlay_fig)\n",
|
|
476
|
+
"except ValueError as e:\n",
|
|
477
|
+
" print(f\"ValueError: {e}\")"
|
|
478
|
+
]
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"cell_type": "markdown",
|
|
482
|
+
"id": "30",
|
|
483
|
+
"metadata": {},
|
|
484
|
+
"source": [
|
|
485
|
+
"### overlay: Animated Overlay on Static Base\n",
|
|
486
|
+
"\n",
|
|
487
|
+
"Cannot add an animated overlay to a static base figure."
|
|
488
|
+
]
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"cell_type": "code",
|
|
492
|
+
"execution_count": null,
|
|
493
|
+
"id": "31",
|
|
494
|
+
"metadata": {},
|
|
495
|
+
"outputs": [],
|
|
496
|
+
"source": [
|
|
497
|
+
"# Base: static\n",
|
|
498
|
+
"static_base = xpx(population.sel(country=\"United States\")).line()\n",
|
|
499
|
+
"\n",
|
|
500
|
+
"# Overlay: animated\n",
|
|
501
|
+
"animated_overlay = xpx(population).line(animation_frame=\"country\")\n",
|
|
502
|
+
"\n",
|
|
503
|
+
"try:\n",
|
|
504
|
+
" overlay(static_base, animated_overlay)\n",
|
|
505
|
+
"except ValueError as e:\n",
|
|
506
|
+
" print(f\"ValueError: {e}\")"
|
|
507
|
+
]
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"cell_type": "markdown",
|
|
511
|
+
"id": "32",
|
|
512
|
+
"metadata": {},
|
|
513
|
+
"source": [
|
|
514
|
+
"### overlay: Mismatched Animation Frames\n",
|
|
515
|
+
"\n",
|
|
516
|
+
"Animation frame names must match exactly."
|
|
517
|
+
]
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
"cell_type": "code",
|
|
521
|
+
"execution_count": null,
|
|
522
|
+
"id": "33",
|
|
523
|
+
"metadata": {},
|
|
524
|
+
"outputs": [],
|
|
525
|
+
"source": [
|
|
526
|
+
"# Different countries selected = different frame names\n",
|
|
527
|
+
"fig1 = xpx(population.sel(country=[\"United States\", \"China\"])).line(animation_frame=\"country\")\n",
|
|
528
|
+
"fig2 = xpx(population.sel(country=[\"Germany\", \"Brazil\"])).line(animation_frame=\"country\")\n",
|
|
529
|
+
"\n",
|
|
530
|
+
"try:\n",
|
|
531
|
+
" overlay(fig1, fig2)\n",
|
|
532
|
+
"except ValueError as e:\n",
|
|
533
|
+
" print(f\"ValueError: {e}\")"
|
|
534
|
+
]
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
"cell_type": "markdown",
|
|
538
|
+
"id": "34",
|
|
539
|
+
"metadata": {},
|
|
540
|
+
"source": [
|
|
541
|
+
"### add_secondary_y: Mismatched Facet Structure\n",
|
|
542
|
+
"\n",
|
|
543
|
+
"Both figures must have the same facet structure."
|
|
544
|
+
]
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"cell_type": "code",
|
|
548
|
+
"execution_count": null,
|
|
549
|
+
"id": "35",
|
|
550
|
+
"metadata": {},
|
|
551
|
+
"outputs": [],
|
|
552
|
+
"source": [
|
|
553
|
+
"# Base with facets\n",
|
|
554
|
+
"pop_faceted = xpx(population).bar(facet_col=\"country\")\n",
|
|
555
|
+
"\n",
|
|
556
|
+
"# Secondary without facets (different structure)\n",
|
|
557
|
+
"gdp_single = xpx(gdp_per_capita.sel(country=\"United States\")).line()\n",
|
|
558
|
+
"\n",
|
|
559
|
+
"try:\n",
|
|
560
|
+
" add_secondary_y(pop_faceted, gdp_single)\n",
|
|
561
|
+
"except ValueError as e:\n",
|
|
562
|
+
" print(f\"ValueError: {e}\")"
|
|
563
|
+
]
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
"cell_type": "markdown",
|
|
567
|
+
"id": "36",
|
|
568
|
+
"metadata": {},
|
|
569
|
+
"source": [
|
|
570
|
+
"### add_secondary_y: Animated Secondary on Static Base\n",
|
|
571
|
+
"\n",
|
|
572
|
+
"Cannot add animated secondary to static base."
|
|
573
|
+
]
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
"cell_type": "code",
|
|
577
|
+
"execution_count": null,
|
|
578
|
+
"id": "37",
|
|
579
|
+
"metadata": {},
|
|
580
|
+
"outputs": [],
|
|
581
|
+
"source": [
|
|
582
|
+
"# Static base\n",
|
|
583
|
+
"static_pop = xpx(population.sel(country=\"United States\")).bar()\n",
|
|
584
|
+
"\n",
|
|
585
|
+
"# Animated secondary\n",
|
|
586
|
+
"animated_gdp = xpx(gdp_per_capita).line(animation_frame=\"country\")\n",
|
|
587
|
+
"\n",
|
|
588
|
+
"try:\n",
|
|
589
|
+
" add_secondary_y(static_pop, animated_gdp)\n",
|
|
590
|
+
"except ValueError as e:\n",
|
|
591
|
+
" print(f\"ValueError: {e}\")"
|
|
592
|
+
]
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
"cell_type": "markdown",
|
|
596
|
+
"id": "38",
|
|
597
|
+
"metadata": {},
|
|
598
|
+
"source": [
|
|
599
|
+
"### add_secondary_y: Mismatched Animation Frames"
|
|
600
|
+
]
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
"cell_type": "code",
|
|
604
|
+
"execution_count": null,
|
|
605
|
+
"id": "39",
|
|
606
|
+
"metadata": {},
|
|
607
|
+
"outputs": [],
|
|
608
|
+
"source": [
|
|
609
|
+
"# Different countries = different frames\n",
|
|
610
|
+
"pop_some = xpx(population.sel(country=[\"United States\", \"China\"])).bar(animation_frame=\"country\")\n",
|
|
611
|
+
"gdp_other = xpx(gdp_per_capita.sel(country=[\"Germany\", \"Brazil\"])).line(animation_frame=\"country\")\n",
|
|
612
|
+
"\n",
|
|
613
|
+
"try:\n",
|
|
614
|
+
" add_secondary_y(pop_some, gdp_other)\n",
|
|
615
|
+
"except ValueError as e:\n",
|
|
616
|
+
" print(f\"ValueError: {e}\")"
|
|
617
|
+
]
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"cell_type": "markdown",
|
|
621
|
+
"id": "40",
|
|
622
|
+
"metadata": {},
|
|
623
|
+
"source": [
|
|
624
|
+
"## Summary\n",
|
|
625
|
+
"\n",
|
|
626
|
+
"| Function | Facets | Animation | Static + Animated |\n",
|
|
627
|
+
"|----------|--------|-----------|-------------------|\n",
|
|
628
|
+
"| `overlay` | Yes (must match) | Yes (frames must match) | Static overlay on animated base OK |\n",
|
|
629
|
+
"| `add_secondary_y` | Yes (must match) | Yes (frames must match) | Static secondary on animated base OK |"
|
|
630
|
+
]
|
|
631
|
+
}
|
|
632
|
+
],
|
|
633
|
+
"metadata": {
|
|
634
|
+
"kernelspec": {
|
|
635
|
+
"display_name": "Python 3",
|
|
636
|
+
"language": "python",
|
|
637
|
+
"name": "python3"
|
|
638
|
+
},
|
|
639
|
+
"language_info": {
|
|
640
|
+
"name": "python",
|
|
641
|
+
"version": "3.12.0"
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
"nbformat": 4,
|
|
645
|
+
"nbformat_minor": 5
|
|
646
|
+
}
|