xarray-plotly 0.0.1__py3-none-any.whl
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/__init__.py +73 -0
- xarray_plotly/accessor.py +336 -0
- xarray_plotly/common.py +247 -0
- xarray_plotly/config.py +203 -0
- xarray_plotly/plotting.py +448 -0
- xarray_plotly/py.typed +0 -0
- xarray_plotly-0.0.1.dist-info/METADATA +175 -0
- xarray_plotly-0.0.1.dist-info/RECORD +11 -0
- xarray_plotly-0.0.1.dist-info/WHEEL +5 -0
- xarray_plotly-0.0.1.dist-info/licenses/LICENSE +21 -0
- xarray_plotly-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
xarray_plotly: Interactive Plotly Express plotting for xarray.
|
|
3
|
+
|
|
4
|
+
This package provides interactive plotting for xarray DataArray objects
|
|
5
|
+
using Plotly Express.
|
|
6
|
+
|
|
7
|
+
Examples
|
|
8
|
+
--------
|
|
9
|
+
>>> import xarray as xr
|
|
10
|
+
>>> import numpy as np
|
|
11
|
+
>>> from xarray_plotly import xpx
|
|
12
|
+
|
|
13
|
+
>>> da = xr.DataArray(
|
|
14
|
+
... np.random.rand(10, 3, 2),
|
|
15
|
+
... dims=["time", "city", "scenario"],
|
|
16
|
+
... )
|
|
17
|
+
|
|
18
|
+
>>> # Auto-assignment: time->x, city->color, scenario->facet_col
|
|
19
|
+
>>> fig = xpx(da).line()
|
|
20
|
+
|
|
21
|
+
>>> # Explicit assignment
|
|
22
|
+
>>> fig = xpx(da).line(x="time", color="scenario", facet_col="city")
|
|
23
|
+
|
|
24
|
+
>>> # Skip a slot
|
|
25
|
+
>>> fig = xpx(da).line(color=None) # time->x, city->facet_col, scenario->facet_row
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from importlib.metadata import version
|
|
29
|
+
|
|
30
|
+
from xarray import DataArray, register_dataarray_accessor
|
|
31
|
+
|
|
32
|
+
from xarray_plotly import config
|
|
33
|
+
from xarray_plotly.accessor import DataArrayPlotlyAccessor
|
|
34
|
+
from xarray_plotly.common import SLOT_ORDERS, auto
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"SLOT_ORDERS",
|
|
38
|
+
"DataArrayPlotlyAccessor",
|
|
39
|
+
"auto",
|
|
40
|
+
"config",
|
|
41
|
+
"xpx",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def xpx(da: DataArray) -> DataArrayPlotlyAccessor:
|
|
46
|
+
"""
|
|
47
|
+
Get the plotly accessor for a DataArray with full IDE code completion.
|
|
48
|
+
|
|
49
|
+
This is an alternative to `da.plotly` that provides proper type hints
|
|
50
|
+
and code completion in IDEs.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
da : DataArray
|
|
55
|
+
The DataArray to plot.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
DataArrayPlotlyAccessor
|
|
60
|
+
The accessor with plotting methods.
|
|
61
|
+
|
|
62
|
+
Examples
|
|
63
|
+
--------
|
|
64
|
+
>>> from xarray_plotly import xpx
|
|
65
|
+
>>> fig = xpx(da).line() # Full code completion works here
|
|
66
|
+
"""
|
|
67
|
+
return DataArrayPlotlyAccessor(da)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
__version__ = version("xarray_plotly")
|
|
71
|
+
|
|
72
|
+
# Register the accessor
|
|
73
|
+
register_dataarray_accessor("plotly")(DataArrayPlotlyAccessor)
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Accessor classes for Plotly Express plotting on DataArray and Dataset.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, ClassVar
|
|
6
|
+
|
|
7
|
+
import plotly.graph_objects as go
|
|
8
|
+
from xarray import DataArray
|
|
9
|
+
|
|
10
|
+
from xarray_plotly import plotting
|
|
11
|
+
from xarray_plotly.common import SlotValue, auto
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataArrayPlotlyAccessor:
|
|
15
|
+
"""
|
|
16
|
+
Enables use of Plotly Express plotting functions on a DataArray.
|
|
17
|
+
|
|
18
|
+
Methods return Plotly Figure objects for interactive visualization.
|
|
19
|
+
|
|
20
|
+
Examples
|
|
21
|
+
--------
|
|
22
|
+
>>> import xarray as xr
|
|
23
|
+
>>> import numpy as np
|
|
24
|
+
>>> da = xr.DataArray(np.random.rand(10, 3), dims=["time", "city"])
|
|
25
|
+
>>> fig = da.plotly.line() # Auto-assign dims: time->x, city->color
|
|
26
|
+
>>> fig.show()
|
|
27
|
+
|
|
28
|
+
>>> fig = da.plotly.line(x="time", color=None) # Explicit assignment
|
|
29
|
+
>>> fig.update_layout(title="My Plot")
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
__all__: ClassVar = ["line", "bar", "area", "scatter", "box", "imshow"]
|
|
33
|
+
|
|
34
|
+
def __init__(self, darray: DataArray) -> None:
|
|
35
|
+
self._da = darray
|
|
36
|
+
|
|
37
|
+
def __dir__(self) -> list[str]:
|
|
38
|
+
"""List available plot methods."""
|
|
39
|
+
return list(self.__all__) + list(super().__dir__())
|
|
40
|
+
|
|
41
|
+
def line(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
x: SlotValue = auto,
|
|
45
|
+
color: SlotValue = auto,
|
|
46
|
+
line_dash: SlotValue = auto,
|
|
47
|
+
symbol: SlotValue = auto,
|
|
48
|
+
facet_col: SlotValue = auto,
|
|
49
|
+
facet_row: SlotValue = auto,
|
|
50
|
+
animation_frame: SlotValue = auto,
|
|
51
|
+
**px_kwargs: Any,
|
|
52
|
+
) -> go.Figure:
|
|
53
|
+
"""
|
|
54
|
+
Create an interactive line plot using Plotly Express.
|
|
55
|
+
|
|
56
|
+
The y-axis always shows the DataArray values. Dimensions are assigned
|
|
57
|
+
to other slots by their order:
|
|
58
|
+
x -> color -> line_dash -> symbol -> facet_col -> facet_row -> animation_frame
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
x
|
|
63
|
+
Dimension for x-axis. Default: first dimension.
|
|
64
|
+
color
|
|
65
|
+
Dimension for color grouping. Default: second dimension.
|
|
66
|
+
line_dash
|
|
67
|
+
Dimension for line dash style. Default: third dimension.
|
|
68
|
+
symbol
|
|
69
|
+
Dimension for marker symbol. Default: fourth dimension.
|
|
70
|
+
facet_col
|
|
71
|
+
Dimension for subplot columns. Default: fifth dimension.
|
|
72
|
+
facet_row
|
|
73
|
+
Dimension for subplot rows. Default: sixth dimension.
|
|
74
|
+
animation_frame
|
|
75
|
+
Dimension for animation. Default: seventh dimension.
|
|
76
|
+
**px_kwargs
|
|
77
|
+
Additional arguments passed to `plotly.express.line()`.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
plotly.graph_objects.Figure
|
|
82
|
+
"""
|
|
83
|
+
return plotting.line(
|
|
84
|
+
self._da,
|
|
85
|
+
x=x,
|
|
86
|
+
color=color,
|
|
87
|
+
line_dash=line_dash,
|
|
88
|
+
symbol=symbol,
|
|
89
|
+
facet_col=facet_col,
|
|
90
|
+
facet_row=facet_row,
|
|
91
|
+
animation_frame=animation_frame,
|
|
92
|
+
**px_kwargs,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def bar(
|
|
96
|
+
self,
|
|
97
|
+
*,
|
|
98
|
+
x: SlotValue = auto,
|
|
99
|
+
color: SlotValue = auto,
|
|
100
|
+
pattern_shape: SlotValue = auto,
|
|
101
|
+
facet_col: SlotValue = auto,
|
|
102
|
+
facet_row: SlotValue = auto,
|
|
103
|
+
animation_frame: SlotValue = auto,
|
|
104
|
+
**px_kwargs: Any,
|
|
105
|
+
) -> go.Figure:
|
|
106
|
+
"""
|
|
107
|
+
Create an interactive bar chart using Plotly Express.
|
|
108
|
+
|
|
109
|
+
The y-axis always shows the DataArray values. Dimensions are assigned
|
|
110
|
+
to other slots by their order:
|
|
111
|
+
x -> color -> pattern_shape -> facet_col -> facet_row -> animation_frame
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
x
|
|
116
|
+
Dimension for x-axis. Default: first dimension.
|
|
117
|
+
color
|
|
118
|
+
Dimension for color grouping. Default: second dimension.
|
|
119
|
+
pattern_shape
|
|
120
|
+
Dimension for bar fill pattern. Default: third dimension.
|
|
121
|
+
facet_col
|
|
122
|
+
Dimension for subplot columns. Default: fourth dimension.
|
|
123
|
+
facet_row
|
|
124
|
+
Dimension for subplot rows. Default: fifth dimension.
|
|
125
|
+
animation_frame
|
|
126
|
+
Dimension for animation. Default: sixth dimension.
|
|
127
|
+
**px_kwargs
|
|
128
|
+
Additional arguments passed to `plotly.express.bar()`.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
plotly.graph_objects.Figure
|
|
133
|
+
"""
|
|
134
|
+
return plotting.bar(
|
|
135
|
+
self._da,
|
|
136
|
+
x=x,
|
|
137
|
+
color=color,
|
|
138
|
+
pattern_shape=pattern_shape,
|
|
139
|
+
facet_col=facet_col,
|
|
140
|
+
facet_row=facet_row,
|
|
141
|
+
animation_frame=animation_frame,
|
|
142
|
+
**px_kwargs,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def area(
|
|
146
|
+
self,
|
|
147
|
+
*,
|
|
148
|
+
x: SlotValue = auto,
|
|
149
|
+
color: SlotValue = auto,
|
|
150
|
+
pattern_shape: SlotValue = auto,
|
|
151
|
+
facet_col: SlotValue = auto,
|
|
152
|
+
facet_row: SlotValue = auto,
|
|
153
|
+
animation_frame: SlotValue = auto,
|
|
154
|
+
**px_kwargs: Any,
|
|
155
|
+
) -> go.Figure:
|
|
156
|
+
"""
|
|
157
|
+
Create an interactive stacked area chart using Plotly Express.
|
|
158
|
+
|
|
159
|
+
The y-axis always shows the DataArray values. Dimensions are assigned
|
|
160
|
+
to other slots by their order:
|
|
161
|
+
x -> color -> pattern_shape -> facet_col -> facet_row -> animation_frame
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
x
|
|
166
|
+
Dimension for x-axis. Default: first dimension.
|
|
167
|
+
color
|
|
168
|
+
Dimension for color/stacking. Default: second dimension.
|
|
169
|
+
pattern_shape
|
|
170
|
+
Dimension for fill pattern. Default: third dimension.
|
|
171
|
+
facet_col
|
|
172
|
+
Dimension for subplot columns. Default: fourth dimension.
|
|
173
|
+
facet_row
|
|
174
|
+
Dimension for subplot rows. Default: fifth dimension.
|
|
175
|
+
animation_frame
|
|
176
|
+
Dimension for animation. Default: sixth dimension.
|
|
177
|
+
**px_kwargs
|
|
178
|
+
Additional arguments passed to `plotly.express.area()`.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
plotly.graph_objects.Figure
|
|
183
|
+
"""
|
|
184
|
+
return plotting.area(
|
|
185
|
+
self._da,
|
|
186
|
+
x=x,
|
|
187
|
+
color=color,
|
|
188
|
+
pattern_shape=pattern_shape,
|
|
189
|
+
facet_col=facet_col,
|
|
190
|
+
facet_row=facet_row,
|
|
191
|
+
animation_frame=animation_frame,
|
|
192
|
+
**px_kwargs,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def scatter(
|
|
196
|
+
self,
|
|
197
|
+
*,
|
|
198
|
+
x: SlotValue = auto,
|
|
199
|
+
y: SlotValue | str = "value",
|
|
200
|
+
color: SlotValue = auto,
|
|
201
|
+
symbol: SlotValue = auto,
|
|
202
|
+
facet_col: SlotValue = auto,
|
|
203
|
+
facet_row: SlotValue = auto,
|
|
204
|
+
animation_frame: SlotValue = auto,
|
|
205
|
+
**px_kwargs: Any,
|
|
206
|
+
) -> go.Figure:
|
|
207
|
+
"""
|
|
208
|
+
Create an interactive scatter plot using Plotly Express.
|
|
209
|
+
|
|
210
|
+
By default, y-axis shows the DataArray values. Set y to a dimension
|
|
211
|
+
name to create dimension-vs-dimension plots (e.g., lat vs lon colored
|
|
212
|
+
by value).
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
x
|
|
217
|
+
Dimension for x-axis. Default: first dimension.
|
|
218
|
+
y
|
|
219
|
+
What to plot on y-axis. Default "value" uses DataArray values.
|
|
220
|
+
Can be a dimension name for dimension vs dimension plots.
|
|
221
|
+
color
|
|
222
|
+
Dimension for color grouping. Default: second dimension.
|
|
223
|
+
Use "value" to color by DataArray values (useful with y=dimension).
|
|
224
|
+
symbol
|
|
225
|
+
Dimension for marker symbol. Default: third dimension.
|
|
226
|
+
facet_col
|
|
227
|
+
Dimension for subplot columns. Default: fourth dimension.
|
|
228
|
+
facet_row
|
|
229
|
+
Dimension for subplot rows. Default: fifth dimension.
|
|
230
|
+
animation_frame
|
|
231
|
+
Dimension for animation. Default: sixth dimension.
|
|
232
|
+
**px_kwargs
|
|
233
|
+
Additional arguments passed to `plotly.express.scatter()`.
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
plotly.graph_objects.Figure
|
|
238
|
+
"""
|
|
239
|
+
return plotting.scatter(
|
|
240
|
+
self._da,
|
|
241
|
+
x=x,
|
|
242
|
+
y=y,
|
|
243
|
+
color=color,
|
|
244
|
+
symbol=symbol,
|
|
245
|
+
facet_col=facet_col,
|
|
246
|
+
facet_row=facet_row,
|
|
247
|
+
animation_frame=animation_frame,
|
|
248
|
+
**px_kwargs,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def box(
|
|
252
|
+
self,
|
|
253
|
+
*,
|
|
254
|
+
x: SlotValue = auto,
|
|
255
|
+
color: SlotValue = None,
|
|
256
|
+
facet_col: SlotValue = None,
|
|
257
|
+
facet_row: SlotValue = None,
|
|
258
|
+
animation_frame: SlotValue = None,
|
|
259
|
+
**px_kwargs: Any,
|
|
260
|
+
) -> go.Figure:
|
|
261
|
+
"""
|
|
262
|
+
Create an interactive box plot using Plotly Express.
|
|
263
|
+
|
|
264
|
+
The y-axis always shows the DataArray values. By default, only the
|
|
265
|
+
first dimension is assigned to x; all other dimensions are aggregated
|
|
266
|
+
into the box statistics.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
x
|
|
271
|
+
Dimension for x-axis categories. Default: first dimension.
|
|
272
|
+
color
|
|
273
|
+
Dimension for color grouping. Default: None (aggregated).
|
|
274
|
+
facet_col
|
|
275
|
+
Dimension for subplot columns. Default: None (aggregated).
|
|
276
|
+
facet_row
|
|
277
|
+
Dimension for subplot rows. Default: None (aggregated).
|
|
278
|
+
animation_frame
|
|
279
|
+
Dimension for animation. Default: None (aggregated).
|
|
280
|
+
**px_kwargs
|
|
281
|
+
Additional arguments passed to `plotly.express.box()`.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
plotly.graph_objects.Figure
|
|
286
|
+
"""
|
|
287
|
+
return plotting.box(
|
|
288
|
+
self._da,
|
|
289
|
+
x=x,
|
|
290
|
+
color=color,
|
|
291
|
+
facet_col=facet_col,
|
|
292
|
+
facet_row=facet_row,
|
|
293
|
+
animation_frame=animation_frame,
|
|
294
|
+
**px_kwargs,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def imshow(
|
|
298
|
+
self,
|
|
299
|
+
*,
|
|
300
|
+
x: SlotValue = auto,
|
|
301
|
+
y: SlotValue = auto,
|
|
302
|
+
facet_col: SlotValue = auto,
|
|
303
|
+
animation_frame: SlotValue = auto,
|
|
304
|
+
**px_kwargs: Any,
|
|
305
|
+
) -> go.Figure:
|
|
306
|
+
"""
|
|
307
|
+
Create an interactive heatmap image using Plotly Express.
|
|
308
|
+
|
|
309
|
+
Dimensions are assigned to plot slots by their order:
|
|
310
|
+
y (rows) -> x (columns) -> facet_col -> animation_frame
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
x
|
|
315
|
+
Dimension for x-axis (columns). Default: second dimension.
|
|
316
|
+
y
|
|
317
|
+
Dimension for y-axis (rows). Default: first dimension.
|
|
318
|
+
facet_col
|
|
319
|
+
Dimension for subplot columns. Default: third dimension.
|
|
320
|
+
animation_frame
|
|
321
|
+
Dimension for animation. Default: fourth dimension.
|
|
322
|
+
**px_kwargs
|
|
323
|
+
Additional arguments passed to `plotly.express.imshow()`.
|
|
324
|
+
|
|
325
|
+
Returns
|
|
326
|
+
-------
|
|
327
|
+
plotly.graph_objects.Figure
|
|
328
|
+
"""
|
|
329
|
+
return plotting.imshow(
|
|
330
|
+
self._da,
|
|
331
|
+
x=x,
|
|
332
|
+
y=y,
|
|
333
|
+
facet_col=facet_col,
|
|
334
|
+
animation_frame=animation_frame,
|
|
335
|
+
**px_kwargs,
|
|
336
|
+
)
|
xarray_plotly/common.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common utilities for Plotly Express plotting.
|
|
3
|
+
|
|
4
|
+
This module provides the dimension-to-slot assignment algorithm and
|
|
5
|
+
shared utilities for converting xarray data to Plotly-compatible formats.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from xarray_plotly.config import DEFAULT_SLOT_ORDERS, _options
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Hashable, Sequence
|
|
16
|
+
|
|
17
|
+
import pandas as pd
|
|
18
|
+
from xarray import DataArray
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _AUTO:
|
|
22
|
+
"""Sentinel value for automatic slot assignment."""
|
|
23
|
+
|
|
24
|
+
__slots__ = ()
|
|
25
|
+
|
|
26
|
+
def __repr__(self) -> str:
|
|
27
|
+
return "auto"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
auto = _AUTO()
|
|
31
|
+
|
|
32
|
+
SlotValue = _AUTO | str | None
|
|
33
|
+
"""Type alias for slot values: auto, explicit dimension name, or None (skip)."""
|
|
34
|
+
|
|
35
|
+
# Re-export for backward compatibility
|
|
36
|
+
SLOT_ORDERS = DEFAULT_SLOT_ORDERS
|
|
37
|
+
"""Slot orders per plot type.
|
|
38
|
+
|
|
39
|
+
For most plots, y-axis shows DataArray values (not a dimension slot).
|
|
40
|
+
For imshow, both y and x are dimensions (rows and columns of the heatmap).
|
|
41
|
+
|
|
42
|
+
Note: To customize slot orders, use `set_options(slot_orders=...)`.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def assign_slots(
|
|
47
|
+
dims: Sequence[Hashable],
|
|
48
|
+
plot_type: str,
|
|
49
|
+
*,
|
|
50
|
+
allow_unassigned: bool = False,
|
|
51
|
+
**slot_kwargs: SlotValue,
|
|
52
|
+
) -> dict[str, Hashable]:
|
|
53
|
+
"""
|
|
54
|
+
Assign dimensions to plot slots based on position.
|
|
55
|
+
|
|
56
|
+
Positional assignment: dimensions fill slots in order.
|
|
57
|
+
- Explicit assignments lock a dimension to a slot
|
|
58
|
+
- None skips a slot
|
|
59
|
+
- Remaining dims fill remaining slots by position
|
|
60
|
+
- Error if dims left over after all slots filled (unless allow_unassigned=True)
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
dims
|
|
65
|
+
Dimension names.
|
|
66
|
+
plot_type
|
|
67
|
+
Type of plot (line, bar, area, scatter, box, imshow).
|
|
68
|
+
allow_unassigned
|
|
69
|
+
If True, allow dimensions to remain unassigned. Default False.
|
|
70
|
+
**slot_kwargs
|
|
71
|
+
Explicit slot assignments. Use `auto` for positional assignment,
|
|
72
|
+
a dimension name for explicit assignment, or `None` to skip the slot.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
dict
|
|
77
|
+
Mapping of slot names to dimension names.
|
|
78
|
+
|
|
79
|
+
Raises
|
|
80
|
+
------
|
|
81
|
+
ValueError
|
|
82
|
+
If plot_type is unknown, a dimension doesn't exist, or dimensions
|
|
83
|
+
are left unassigned (unless allow_unassigned=True).
|
|
84
|
+
"""
|
|
85
|
+
slot_orders = _options.slot_orders
|
|
86
|
+
if plot_type not in slot_orders:
|
|
87
|
+
msg = f"Unknown plot type: {plot_type!r}. Available types: {list(slot_orders.keys())}"
|
|
88
|
+
raise ValueError(msg)
|
|
89
|
+
|
|
90
|
+
slot_order = slot_orders[plot_type]
|
|
91
|
+
dims_list = list(dims)
|
|
92
|
+
|
|
93
|
+
slots: dict[str, Hashable] = {}
|
|
94
|
+
used_dims: set[Hashable] = set()
|
|
95
|
+
available_slots = list(slot_order)
|
|
96
|
+
|
|
97
|
+
# Pass 1: Process explicit assignments (non-auto, non-None)
|
|
98
|
+
for slot in slot_order:
|
|
99
|
+
value = slot_kwargs.get(slot, auto)
|
|
100
|
+
|
|
101
|
+
if value is None:
|
|
102
|
+
# Skip this slot
|
|
103
|
+
if slot in available_slots:
|
|
104
|
+
available_slots.remove(slot)
|
|
105
|
+
elif not isinstance(value, _AUTO):
|
|
106
|
+
# Explicit assignment - can be a dimension name or "value" (DataArray values)
|
|
107
|
+
if value == "value":
|
|
108
|
+
slots[slot] = "value"
|
|
109
|
+
elif value not in dims_list:
|
|
110
|
+
msg = (
|
|
111
|
+
f"Dimension {value!r} assigned to slot {slot!r} "
|
|
112
|
+
f"is not in the data dimensions: {dims_list}"
|
|
113
|
+
)
|
|
114
|
+
raise ValueError(msg)
|
|
115
|
+
else:
|
|
116
|
+
slots[slot] = value
|
|
117
|
+
used_dims.add(value)
|
|
118
|
+
if slot in available_slots:
|
|
119
|
+
available_slots.remove(slot)
|
|
120
|
+
|
|
121
|
+
# Pass 2: Fill remaining slots with remaining dims (by position)
|
|
122
|
+
remaining_dims = [d for d in dims_list if d not in used_dims]
|
|
123
|
+
for slot, dim in zip(available_slots, remaining_dims, strict=False):
|
|
124
|
+
slots[slot] = dim
|
|
125
|
+
used_dims.add(dim)
|
|
126
|
+
|
|
127
|
+
# Check for unassigned dimensions
|
|
128
|
+
unassigned = [d for d in dims_list if d not in used_dims]
|
|
129
|
+
if unassigned and not allow_unassigned:
|
|
130
|
+
msg = (
|
|
131
|
+
f"Unassigned dimension(s): {unassigned}. "
|
|
132
|
+
"Reduce with .sel(), .isel(), or .mean() before plotting."
|
|
133
|
+
)
|
|
134
|
+
raise ValueError(msg)
|
|
135
|
+
|
|
136
|
+
return slots
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_value_col(darray: DataArray) -> str:
|
|
140
|
+
"""Get the column name for DataArray values."""
|
|
141
|
+
return str(darray.name) if darray.name is not None else "value"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def to_dataframe(darray: DataArray) -> pd.DataFrame:
|
|
145
|
+
"""Convert a DataArray to a long-form DataFrame for Plotly Express."""
|
|
146
|
+
|
|
147
|
+
if darray.name is None:
|
|
148
|
+
darray = darray.rename("value")
|
|
149
|
+
df: pd.DataFrame = darray.to_dataframe().reset_index()
|
|
150
|
+
return df
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _get_label_from_attrs(attrs: dict, fallback: str) -> str:
|
|
154
|
+
"""
|
|
155
|
+
Extract a label from xarray attributes based on current config.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
attrs
|
|
160
|
+
Attributes dictionary from DataArray or coordinate.
|
|
161
|
+
fallback
|
|
162
|
+
Fallback label if no attributes match.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
str
|
|
167
|
+
The formatted label.
|
|
168
|
+
"""
|
|
169
|
+
label = None
|
|
170
|
+
|
|
171
|
+
if _options.label_use_long_name:
|
|
172
|
+
label = attrs.get("long_name")
|
|
173
|
+
|
|
174
|
+
if label is None and _options.label_use_standard_name:
|
|
175
|
+
label = attrs.get("standard_name")
|
|
176
|
+
|
|
177
|
+
if label is None:
|
|
178
|
+
return fallback
|
|
179
|
+
|
|
180
|
+
if _options.label_include_units:
|
|
181
|
+
units = attrs.get("units")
|
|
182
|
+
if units:
|
|
183
|
+
return f"{label} {_options.label_unit_format.format(units=units)}"
|
|
184
|
+
|
|
185
|
+
return str(label)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def get_label(darray: DataArray, name: Hashable) -> str:
|
|
189
|
+
"""
|
|
190
|
+
Get a human-readable label for a dimension or the value column.
|
|
191
|
+
|
|
192
|
+
Uses long_name/standard_name and units from attributes based on
|
|
193
|
+
current configuration (see `set_options`).
|
|
194
|
+
"""
|
|
195
|
+
# Check if it's asking for the value column label
|
|
196
|
+
value_col = get_value_col(darray)
|
|
197
|
+
if str(name) == value_col or name == "value":
|
|
198
|
+
return _get_label_from_attrs(darray.attrs, value_col)
|
|
199
|
+
|
|
200
|
+
# It's a dimension/coordinate
|
|
201
|
+
if name in darray.coords:
|
|
202
|
+
coord = darray.coords[name]
|
|
203
|
+
return _get_label_from_attrs(coord.attrs, str(name))
|
|
204
|
+
|
|
205
|
+
return str(name)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def build_labels(
|
|
209
|
+
darray: DataArray,
|
|
210
|
+
slots: dict[str, Hashable],
|
|
211
|
+
value_col: str,
|
|
212
|
+
*,
|
|
213
|
+
include_value: bool = True,
|
|
214
|
+
) -> dict[str, str]:
|
|
215
|
+
"""
|
|
216
|
+
Build a labels dict for Plotly Express from slot assignments.
|
|
217
|
+
|
|
218
|
+
Parameters
|
|
219
|
+
----------
|
|
220
|
+
darray
|
|
221
|
+
The source DataArray.
|
|
222
|
+
slots
|
|
223
|
+
Slot assignments from assign_slots().
|
|
224
|
+
value_col
|
|
225
|
+
The name of the value column in the DataFrame.
|
|
226
|
+
include_value
|
|
227
|
+
Whether to include a label for the value column.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
dict
|
|
232
|
+
Mapping of column names to human-readable labels.
|
|
233
|
+
"""
|
|
234
|
+
labels: dict[str, str] = {}
|
|
235
|
+
|
|
236
|
+
# Add labels for assigned dimensions
|
|
237
|
+
for slot_value in slots.values():
|
|
238
|
+
if slot_value and slot_value != "value":
|
|
239
|
+
key = str(slot_value)
|
|
240
|
+
if key not in labels:
|
|
241
|
+
labels[key] = get_label(darray, slot_value)
|
|
242
|
+
|
|
243
|
+
# Add label for value column
|
|
244
|
+
if include_value and value_col not in labels:
|
|
245
|
+
labels[value_col] = get_label(darray, "value")
|
|
246
|
+
|
|
247
|
+
return labels
|