xarray-plotly 0.0.3__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 +81 -0
- xarray_plotly/accessor.py +275 -0
- xarray_plotly/common.py +229 -0
- xarray_plotly/config.py +178 -0
- xarray_plotly/plotting.py +448 -0
- xarray_plotly/py.typed +0 -0
- xarray_plotly-0.0.3.dist-info/METADATA +89 -0
- xarray_plotly-0.0.3.dist-info/RECORD +11 -0
- xarray_plotly-0.0.3.dist-info/WHEEL +5 -0
- xarray_plotly-0.0.3.dist-info/licenses/LICENSE +21 -0
- xarray_plotly-0.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Interactive Plotly Express plotting for xarray.
|
|
2
|
+
|
|
3
|
+
This package provides a `plotly` accessor for xarray DataArray objects,
|
|
4
|
+
enabling interactive visualization with Plotly Express.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- **Interactive plots**: Zoom, pan, hover, toggle traces
|
|
8
|
+
- **Automatic dimension assignment**: Dimensions fill slots (x, color, facet) by position
|
|
9
|
+
- **Multiple plot types**: line, bar, area, scatter, box, imshow
|
|
10
|
+
- **Faceting and animation**: Built-in subplot grids and animated plots
|
|
11
|
+
- **Customizable**: Returns Plotly Figure objects for further modification
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
Accessor style::
|
|
15
|
+
|
|
16
|
+
import xarray_plotly
|
|
17
|
+
fig = da.plotly.line()
|
|
18
|
+
|
|
19
|
+
Function style (recommended for IDE completion)::
|
|
20
|
+
|
|
21
|
+
from xarray_plotly import xpx
|
|
22
|
+
fig = xpx(da).line()
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
```python
|
|
26
|
+
import xarray as xr
|
|
27
|
+
import numpy as np
|
|
28
|
+
from xarray_plotly import xpx
|
|
29
|
+
|
|
30
|
+
da = xr.DataArray(
|
|
31
|
+
np.random.rand(10, 3, 2),
|
|
32
|
+
dims=["time", "city", "scenario"],
|
|
33
|
+
)
|
|
34
|
+
fig = xpx(da).line() # Auto: time->x, city->color, scenario->facet_col
|
|
35
|
+
fig = xpx(da).line(x="time", color="scenario") # Explicit
|
|
36
|
+
fig = xpx(da).line(color=None) # Skip slot
|
|
37
|
+
```
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from importlib.metadata import version
|
|
41
|
+
|
|
42
|
+
from xarray import DataArray, register_dataarray_accessor
|
|
43
|
+
|
|
44
|
+
from xarray_plotly import config
|
|
45
|
+
from xarray_plotly.accessor import DataArrayPlotlyAccessor
|
|
46
|
+
from xarray_plotly.common import SLOT_ORDERS, auto
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"SLOT_ORDERS",
|
|
50
|
+
"DataArrayPlotlyAccessor",
|
|
51
|
+
"auto",
|
|
52
|
+
"config",
|
|
53
|
+
"xpx",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def xpx(da: DataArray) -> DataArrayPlotlyAccessor:
|
|
58
|
+
"""Get the plotly accessor for a DataArray with full IDE code completion.
|
|
59
|
+
|
|
60
|
+
This is an alternative to `da.plotly` that provides proper type hints
|
|
61
|
+
and code completion in IDEs.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
da: The DataArray to plot.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The accessor with plotting methods (line, bar, area, scatter, box, imshow).
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
```python
|
|
71
|
+
from xarray_plotly import xpx
|
|
72
|
+
fig = xpx(da).line() # Full code completion works here
|
|
73
|
+
```
|
|
74
|
+
"""
|
|
75
|
+
return DataArrayPlotlyAccessor(da)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
__version__ = version("xarray_plotly")
|
|
79
|
+
|
|
80
|
+
# Register the accessor
|
|
81
|
+
register_dataarray_accessor("plotly")(DataArrayPlotlyAccessor)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""Accessor classes for Plotly Express plotting on DataArray."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
|
|
5
|
+
import plotly.graph_objects as go
|
|
6
|
+
from xarray import DataArray
|
|
7
|
+
|
|
8
|
+
from xarray_plotly import plotting
|
|
9
|
+
from xarray_plotly.common import SlotValue, auto
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataArrayPlotlyAccessor:
|
|
13
|
+
"""Plotly Express plotting accessor for xarray DataArray.
|
|
14
|
+
|
|
15
|
+
Dimensions are automatically assigned to plot slots by position.
|
|
16
|
+
All methods return Plotly Figure objects for interactive visualization.
|
|
17
|
+
|
|
18
|
+
Available methods: line, bar, area, scatter, box, imshow
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
darray: The DataArray to plot.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
```python
|
|
25
|
+
import xarray as xr
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
da = xr.DataArray(np.random.rand(10, 3), dims=["time", "city"])
|
|
29
|
+
fig = da.plotly.line() # Auto: time->x, city->color
|
|
30
|
+
fig = da.plotly.line(color="time", x="city") # Explicit
|
|
31
|
+
fig = da.plotly.line(color=None) # Skip slot
|
|
32
|
+
fig.update_layout(title="My Plot") # Customize
|
|
33
|
+
```
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__all__: ClassVar = ["line", "bar", "area", "scatter", "box", "imshow"]
|
|
37
|
+
|
|
38
|
+
def __init__(self, darray: DataArray) -> None:
|
|
39
|
+
self._da = darray
|
|
40
|
+
|
|
41
|
+
def __dir__(self) -> list[str]:
|
|
42
|
+
"""List available plot methods."""
|
|
43
|
+
return list(self.__all__) + list(super().__dir__())
|
|
44
|
+
|
|
45
|
+
def line(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
x: SlotValue = auto,
|
|
49
|
+
color: SlotValue = auto,
|
|
50
|
+
line_dash: SlotValue = auto,
|
|
51
|
+
symbol: SlotValue = auto,
|
|
52
|
+
facet_col: SlotValue = auto,
|
|
53
|
+
facet_row: SlotValue = auto,
|
|
54
|
+
animation_frame: SlotValue = auto,
|
|
55
|
+
**px_kwargs: Any,
|
|
56
|
+
) -> go.Figure:
|
|
57
|
+
"""Create an interactive line plot.
|
|
58
|
+
|
|
59
|
+
Slot order: x -> color -> line_dash -> symbol -> facet_col -> facet_row -> animation_frame
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
x: Dimension for x-axis. Default: first dimension.
|
|
63
|
+
color: Dimension for color grouping. Default: second dimension.
|
|
64
|
+
line_dash: Dimension for line dash style. Default: third dimension.
|
|
65
|
+
symbol: Dimension for marker symbol. Default: fourth dimension.
|
|
66
|
+
facet_col: Dimension for subplot columns. Default: fifth dimension.
|
|
67
|
+
facet_row: Dimension for subplot rows. Default: sixth dimension.
|
|
68
|
+
animation_frame: Dimension for animation. Default: seventh dimension.
|
|
69
|
+
**px_kwargs: Additional arguments passed to `plotly.express.line()`.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Interactive Plotly Figure.
|
|
73
|
+
"""
|
|
74
|
+
return plotting.line(
|
|
75
|
+
self._da,
|
|
76
|
+
x=x,
|
|
77
|
+
color=color,
|
|
78
|
+
line_dash=line_dash,
|
|
79
|
+
symbol=symbol,
|
|
80
|
+
facet_col=facet_col,
|
|
81
|
+
facet_row=facet_row,
|
|
82
|
+
animation_frame=animation_frame,
|
|
83
|
+
**px_kwargs,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def bar(
|
|
87
|
+
self,
|
|
88
|
+
*,
|
|
89
|
+
x: SlotValue = auto,
|
|
90
|
+
color: SlotValue = auto,
|
|
91
|
+
pattern_shape: SlotValue = auto,
|
|
92
|
+
facet_col: SlotValue = auto,
|
|
93
|
+
facet_row: SlotValue = auto,
|
|
94
|
+
animation_frame: SlotValue = auto,
|
|
95
|
+
**px_kwargs: Any,
|
|
96
|
+
) -> go.Figure:
|
|
97
|
+
"""Create an interactive bar chart.
|
|
98
|
+
|
|
99
|
+
Slot order: x -> color -> pattern_shape -> facet_col -> facet_row -> animation_frame
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
x: Dimension for x-axis. Default: first dimension.
|
|
103
|
+
color: Dimension for color grouping. Default: second dimension.
|
|
104
|
+
pattern_shape: Dimension for bar fill pattern. Default: third dimension.
|
|
105
|
+
facet_col: Dimension for subplot columns. Default: fourth dimension.
|
|
106
|
+
facet_row: Dimension for subplot rows. Default: fifth dimension.
|
|
107
|
+
animation_frame: Dimension for animation. Default: sixth dimension.
|
|
108
|
+
**px_kwargs: Additional arguments passed to `plotly.express.bar()`.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Interactive Plotly Figure.
|
|
112
|
+
"""
|
|
113
|
+
return plotting.bar(
|
|
114
|
+
self._da,
|
|
115
|
+
x=x,
|
|
116
|
+
color=color,
|
|
117
|
+
pattern_shape=pattern_shape,
|
|
118
|
+
facet_col=facet_col,
|
|
119
|
+
facet_row=facet_row,
|
|
120
|
+
animation_frame=animation_frame,
|
|
121
|
+
**px_kwargs,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def area(
|
|
125
|
+
self,
|
|
126
|
+
*,
|
|
127
|
+
x: SlotValue = auto,
|
|
128
|
+
color: SlotValue = auto,
|
|
129
|
+
pattern_shape: SlotValue = auto,
|
|
130
|
+
facet_col: SlotValue = auto,
|
|
131
|
+
facet_row: SlotValue = auto,
|
|
132
|
+
animation_frame: SlotValue = auto,
|
|
133
|
+
**px_kwargs: Any,
|
|
134
|
+
) -> go.Figure:
|
|
135
|
+
"""Create an interactive stacked area chart.
|
|
136
|
+
|
|
137
|
+
Slot order: x -> color -> pattern_shape -> facet_col -> facet_row -> animation_frame
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
x: Dimension for x-axis. Default: first dimension.
|
|
141
|
+
color: Dimension for color/stacking. Default: second dimension.
|
|
142
|
+
pattern_shape: Dimension for fill pattern. Default: third dimension.
|
|
143
|
+
facet_col: Dimension for subplot columns. Default: fourth dimension.
|
|
144
|
+
facet_row: Dimension for subplot rows. Default: fifth dimension.
|
|
145
|
+
animation_frame: Dimension for animation. Default: sixth dimension.
|
|
146
|
+
**px_kwargs: Additional arguments passed to `plotly.express.area()`.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Interactive Plotly Figure.
|
|
150
|
+
"""
|
|
151
|
+
return plotting.area(
|
|
152
|
+
self._da,
|
|
153
|
+
x=x,
|
|
154
|
+
color=color,
|
|
155
|
+
pattern_shape=pattern_shape,
|
|
156
|
+
facet_col=facet_col,
|
|
157
|
+
facet_row=facet_row,
|
|
158
|
+
animation_frame=animation_frame,
|
|
159
|
+
**px_kwargs,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def scatter(
|
|
163
|
+
self,
|
|
164
|
+
*,
|
|
165
|
+
x: SlotValue = auto,
|
|
166
|
+
y: SlotValue | str = "value",
|
|
167
|
+
color: SlotValue = auto,
|
|
168
|
+
symbol: SlotValue = auto,
|
|
169
|
+
facet_col: SlotValue = auto,
|
|
170
|
+
facet_row: SlotValue = auto,
|
|
171
|
+
animation_frame: SlotValue = auto,
|
|
172
|
+
**px_kwargs: Any,
|
|
173
|
+
) -> go.Figure:
|
|
174
|
+
"""Create an interactive scatter plot.
|
|
175
|
+
|
|
176
|
+
By default, y-axis shows the DataArray values. Set y to a dimension
|
|
177
|
+
name to create dimension-vs-dimension plots (e.g., lat vs lon).
|
|
178
|
+
|
|
179
|
+
Slot order: x -> color -> symbol -> facet_col -> facet_row -> animation_frame
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
x: Dimension for x-axis. Default: first dimension.
|
|
183
|
+
y: What to plot on y-axis. Default "value" uses DataArray values.
|
|
184
|
+
Can be a dimension name for dimension vs dimension plots.
|
|
185
|
+
color: Dimension for color grouping, or "value" for DataArray values.
|
|
186
|
+
symbol: Dimension for marker symbol. Default: third dimension.
|
|
187
|
+
facet_col: Dimension for subplot columns. Default: fourth dimension.
|
|
188
|
+
facet_row: Dimension for subplot rows. Default: fifth dimension.
|
|
189
|
+
animation_frame: Dimension for animation. Default: sixth dimension.
|
|
190
|
+
**px_kwargs: Additional arguments passed to `plotly.express.scatter()`.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Interactive Plotly Figure.
|
|
194
|
+
"""
|
|
195
|
+
return plotting.scatter(
|
|
196
|
+
self._da,
|
|
197
|
+
x=x,
|
|
198
|
+
y=y,
|
|
199
|
+
color=color,
|
|
200
|
+
symbol=symbol,
|
|
201
|
+
facet_col=facet_col,
|
|
202
|
+
facet_row=facet_row,
|
|
203
|
+
animation_frame=animation_frame,
|
|
204
|
+
**px_kwargs,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def box(
|
|
208
|
+
self,
|
|
209
|
+
*,
|
|
210
|
+
x: SlotValue = auto,
|
|
211
|
+
color: SlotValue = None,
|
|
212
|
+
facet_col: SlotValue = None,
|
|
213
|
+
facet_row: SlotValue = None,
|
|
214
|
+
animation_frame: SlotValue = None,
|
|
215
|
+
**px_kwargs: Any,
|
|
216
|
+
) -> go.Figure:
|
|
217
|
+
"""Create an interactive box plot.
|
|
218
|
+
|
|
219
|
+
By default, only the first dimension is assigned to x; all other
|
|
220
|
+
dimensions are aggregated into the box statistics.
|
|
221
|
+
|
|
222
|
+
Slot order: x -> color -> facet_col -> facet_row -> animation_frame
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
x: Dimension for x-axis categories. Default: first dimension.
|
|
226
|
+
color: Dimension for color grouping. Default: None (aggregated).
|
|
227
|
+
facet_col: Dimension for subplot columns. Default: None (aggregated).
|
|
228
|
+
facet_row: Dimension for subplot rows. Default: None (aggregated).
|
|
229
|
+
animation_frame: Dimension for animation. Default: None (aggregated).
|
|
230
|
+
**px_kwargs: Additional arguments passed to `plotly.express.box()`.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Interactive Plotly Figure.
|
|
234
|
+
"""
|
|
235
|
+
return plotting.box(
|
|
236
|
+
self._da,
|
|
237
|
+
x=x,
|
|
238
|
+
color=color,
|
|
239
|
+
facet_col=facet_col,
|
|
240
|
+
facet_row=facet_row,
|
|
241
|
+
animation_frame=animation_frame,
|
|
242
|
+
**px_kwargs,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def imshow(
|
|
246
|
+
self,
|
|
247
|
+
*,
|
|
248
|
+
x: SlotValue = auto,
|
|
249
|
+
y: SlotValue = auto,
|
|
250
|
+
facet_col: SlotValue = auto,
|
|
251
|
+
animation_frame: SlotValue = auto,
|
|
252
|
+
**px_kwargs: Any,
|
|
253
|
+
) -> go.Figure:
|
|
254
|
+
"""Create an interactive heatmap image.
|
|
255
|
+
|
|
256
|
+
Slot order: y (rows) -> x (columns) -> facet_col -> animation_frame
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
x: Dimension for x-axis (columns). Default: second dimension.
|
|
260
|
+
y: Dimension for y-axis (rows). Default: first dimension.
|
|
261
|
+
facet_col: Dimension for subplot columns. Default: third dimension.
|
|
262
|
+
animation_frame: Dimension for animation. Default: fourth dimension.
|
|
263
|
+
**px_kwargs: Additional arguments passed to `plotly.express.imshow()`.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Interactive Plotly Figure.
|
|
267
|
+
"""
|
|
268
|
+
return plotting.imshow(
|
|
269
|
+
self._da,
|
|
270
|
+
x=x,
|
|
271
|
+
y=y,
|
|
272
|
+
facet_col=facet_col,
|
|
273
|
+
animation_frame=animation_frame,
|
|
274
|
+
**px_kwargs,
|
|
275
|
+
)
|
xarray_plotly/common.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Common utilities for dimension-to-slot assignment and data conversion."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from xarray_plotly.config import DEFAULT_SLOT_ORDERS, _options
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Hashable, Sequence
|
|
11
|
+
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from xarray import DataArray
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _AUTO:
|
|
17
|
+
"""Sentinel value for automatic slot assignment."""
|
|
18
|
+
|
|
19
|
+
__slots__ = ()
|
|
20
|
+
|
|
21
|
+
def __repr__(self) -> str:
|
|
22
|
+
return "auto"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
auto = _AUTO()
|
|
26
|
+
|
|
27
|
+
SlotValue = _AUTO | str | None
|
|
28
|
+
"""Type alias for slot values: auto, explicit dimension name, or None (skip)."""
|
|
29
|
+
|
|
30
|
+
# Re-export for backward compatibility
|
|
31
|
+
SLOT_ORDERS = DEFAULT_SLOT_ORDERS
|
|
32
|
+
"""Slot orders per plot type.
|
|
33
|
+
|
|
34
|
+
For most plots, y-axis shows DataArray values (not a dimension slot).
|
|
35
|
+
For imshow, both y and x are dimensions (rows and columns of the heatmap).
|
|
36
|
+
|
|
37
|
+
Note:
|
|
38
|
+
To customize slot orders, use `config.set_options(slot_orders=...)`.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def assign_slots(
|
|
43
|
+
dims: Sequence[Hashable],
|
|
44
|
+
plot_type: str,
|
|
45
|
+
*,
|
|
46
|
+
allow_unassigned: bool = False,
|
|
47
|
+
**slot_kwargs: SlotValue,
|
|
48
|
+
) -> dict[str, Hashable]:
|
|
49
|
+
"""Assign dimensions to plot slots based on position.
|
|
50
|
+
|
|
51
|
+
Positional assignment: dimensions fill slots in order.
|
|
52
|
+
- Explicit assignments lock a dimension to a slot
|
|
53
|
+
- None skips a slot
|
|
54
|
+
- Remaining dims fill remaining slots by position
|
|
55
|
+
- Error if dims left over after all slots filled (unless allow_unassigned=True)
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
dims: Dimension names from the DataArray.
|
|
59
|
+
plot_type: Type of plot (line, bar, area, scatter, box, imshow).
|
|
60
|
+
allow_unassigned: If True, allow dimensions to remain unassigned.
|
|
61
|
+
**slot_kwargs: Explicit slot assignments. Use `auto` for positional,
|
|
62
|
+
a dimension name for explicit, or `None` to skip.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Mapping of slot names to dimension names.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: If plot_type is unknown, dimension doesn't exist, or
|
|
69
|
+
dimensions are left unassigned (unless allow_unassigned=True).
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
```python
|
|
73
|
+
assign_slots(["time", "city", "scenario"], "line")
|
|
74
|
+
# {'x': 'time', 'color': 'city', 'line_dash': 'scenario'}
|
|
75
|
+
|
|
76
|
+
assign_slots(["time", "city"], "line", color="time", x="city")
|
|
77
|
+
# {'x': 'city', 'color': 'time'}
|
|
78
|
+
|
|
79
|
+
assign_slots(["time", "city", "scenario"], "line", color=None)
|
|
80
|
+
# {'x': 'time', 'line_dash': 'city', 'symbol': 'scenario'}
|
|
81
|
+
```
|
|
82
|
+
"""
|
|
83
|
+
slot_orders = _options.slot_orders
|
|
84
|
+
if plot_type not in slot_orders:
|
|
85
|
+
msg = f"Unknown plot type: {plot_type!r}. Available types: {list(slot_orders.keys())}"
|
|
86
|
+
raise ValueError(msg)
|
|
87
|
+
|
|
88
|
+
slot_order = slot_orders[plot_type]
|
|
89
|
+
dims_list = list(dims)
|
|
90
|
+
|
|
91
|
+
slots: dict[str, Hashable] = {}
|
|
92
|
+
used_dims: set[Hashable] = set()
|
|
93
|
+
available_slots = list(slot_order)
|
|
94
|
+
|
|
95
|
+
# Pass 1: Process explicit assignments (non-auto, non-None)
|
|
96
|
+
for slot in slot_order:
|
|
97
|
+
value = slot_kwargs.get(slot, auto)
|
|
98
|
+
|
|
99
|
+
if value is None:
|
|
100
|
+
# Skip this slot
|
|
101
|
+
if slot in available_slots:
|
|
102
|
+
available_slots.remove(slot)
|
|
103
|
+
elif not isinstance(value, _AUTO):
|
|
104
|
+
# Explicit assignment - can be a dimension name or "value" (DataArray values)
|
|
105
|
+
if value == "value":
|
|
106
|
+
slots[slot] = "value"
|
|
107
|
+
elif value not in dims_list:
|
|
108
|
+
msg = (
|
|
109
|
+
f"Dimension {value!r} assigned to slot {slot!r} "
|
|
110
|
+
f"is not in the data dimensions: {dims_list}"
|
|
111
|
+
)
|
|
112
|
+
raise ValueError(msg)
|
|
113
|
+
else:
|
|
114
|
+
slots[slot] = value
|
|
115
|
+
used_dims.add(value)
|
|
116
|
+
if slot in available_slots:
|
|
117
|
+
available_slots.remove(slot)
|
|
118
|
+
|
|
119
|
+
# Pass 2: Fill remaining slots with remaining dims (by position)
|
|
120
|
+
remaining_dims = [d for d in dims_list if d not in used_dims]
|
|
121
|
+
for slot, dim in zip(available_slots, remaining_dims, strict=False):
|
|
122
|
+
slots[slot] = dim
|
|
123
|
+
used_dims.add(dim)
|
|
124
|
+
|
|
125
|
+
# Check for unassigned dimensions
|
|
126
|
+
unassigned = [d for d in dims_list if d not in used_dims]
|
|
127
|
+
if unassigned and not allow_unassigned:
|
|
128
|
+
msg = (
|
|
129
|
+
f"Unassigned dimension(s): {unassigned}. "
|
|
130
|
+
"Reduce with .sel(), .isel(), or .mean() before plotting."
|
|
131
|
+
)
|
|
132
|
+
raise ValueError(msg)
|
|
133
|
+
|
|
134
|
+
return slots
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_value_col(darray: DataArray) -> str:
|
|
138
|
+
"""Get the column name for DataArray values."""
|
|
139
|
+
return str(darray.name) if darray.name is not None else "value"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def to_dataframe(darray: DataArray) -> pd.DataFrame:
|
|
143
|
+
"""Convert a DataArray to a long-form DataFrame for Plotly Express."""
|
|
144
|
+
if darray.name is None:
|
|
145
|
+
darray = darray.rename("value")
|
|
146
|
+
df: pd.DataFrame = darray.to_dataframe().reset_index()
|
|
147
|
+
return df
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _get_label_from_attrs(attrs: dict, fallback: str) -> str:
|
|
151
|
+
"""Extract a label from xarray attributes based on current config.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
attrs: Attributes dictionary from DataArray or coordinate.
|
|
155
|
+
fallback: Fallback label if no attributes match.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
The formatted label.
|
|
159
|
+
"""
|
|
160
|
+
label = None
|
|
161
|
+
|
|
162
|
+
if _options.label_use_long_name:
|
|
163
|
+
label = attrs.get("long_name")
|
|
164
|
+
|
|
165
|
+
if label is None and _options.label_use_standard_name:
|
|
166
|
+
label = attrs.get("standard_name")
|
|
167
|
+
|
|
168
|
+
if label is None:
|
|
169
|
+
return fallback
|
|
170
|
+
|
|
171
|
+
if _options.label_include_units:
|
|
172
|
+
units = attrs.get("units")
|
|
173
|
+
if units:
|
|
174
|
+
return f"{label} {_options.label_unit_format.format(units=units)}"
|
|
175
|
+
|
|
176
|
+
return str(label)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_label(darray: DataArray, name: Hashable) -> str:
|
|
180
|
+
"""Get a human-readable label for a dimension or the value column.
|
|
181
|
+
|
|
182
|
+
Uses long_name/standard_name and units from attributes based on
|
|
183
|
+
current configuration (see `config.set_options`).
|
|
184
|
+
"""
|
|
185
|
+
# Check if it's asking for the value column label
|
|
186
|
+
value_col = get_value_col(darray)
|
|
187
|
+
if str(name) == value_col or name == "value":
|
|
188
|
+
return _get_label_from_attrs(darray.attrs, value_col)
|
|
189
|
+
|
|
190
|
+
# It's a dimension/coordinate
|
|
191
|
+
if name in darray.coords:
|
|
192
|
+
coord = darray.coords[name]
|
|
193
|
+
return _get_label_from_attrs(coord.attrs, str(name))
|
|
194
|
+
|
|
195
|
+
return str(name)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def build_labels(
|
|
199
|
+
darray: DataArray,
|
|
200
|
+
slots: dict[str, Hashable],
|
|
201
|
+
value_col: str,
|
|
202
|
+
*,
|
|
203
|
+
include_value: bool = True,
|
|
204
|
+
) -> dict[str, str]:
|
|
205
|
+
"""Build a labels dict for Plotly Express from slot assignments.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
darray: The source DataArray.
|
|
209
|
+
slots: Slot assignments from assign_slots().
|
|
210
|
+
value_col: The name of the value column in the DataFrame.
|
|
211
|
+
include_value: Whether to include a label for the value column.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Mapping of column names to human-readable labels.
|
|
215
|
+
"""
|
|
216
|
+
labels: dict[str, str] = {}
|
|
217
|
+
|
|
218
|
+
# Add labels for assigned dimensions
|
|
219
|
+
for slot_value in slots.values():
|
|
220
|
+
if slot_value and slot_value != "value":
|
|
221
|
+
key = str(slot_value)
|
|
222
|
+
if key not in labels:
|
|
223
|
+
labels[key] = get_label(darray, slot_value)
|
|
224
|
+
|
|
225
|
+
# Add label for value column
|
|
226
|
+
if include_value and value_col not in labels:
|
|
227
|
+
labels[value_col] = get_label(darray, "value")
|
|
228
|
+
|
|
229
|
+
return labels
|
xarray_plotly/config.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Configuration for xarray_plotly.
|
|
2
|
+
|
|
3
|
+
This module provides a global configuration system similar to xarray and pandas,
|
|
4
|
+
allowing users to customize label extraction and slot assignment behavior.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Generator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Default slot orders per plot type
|
|
18
|
+
DEFAULT_SLOT_ORDERS: dict[str, tuple[str, ...]] = {
|
|
19
|
+
"line": (
|
|
20
|
+
"x",
|
|
21
|
+
"color",
|
|
22
|
+
"line_dash",
|
|
23
|
+
"symbol",
|
|
24
|
+
"facet_col",
|
|
25
|
+
"facet_row",
|
|
26
|
+
"animation_frame",
|
|
27
|
+
),
|
|
28
|
+
"bar": ("x", "color", "pattern_shape", "facet_col", "facet_row", "animation_frame"),
|
|
29
|
+
"area": (
|
|
30
|
+
"x",
|
|
31
|
+
"color",
|
|
32
|
+
"pattern_shape",
|
|
33
|
+
"facet_col",
|
|
34
|
+
"facet_row",
|
|
35
|
+
"animation_frame",
|
|
36
|
+
),
|
|
37
|
+
"scatter": (
|
|
38
|
+
"x",
|
|
39
|
+
"color",
|
|
40
|
+
"symbol",
|
|
41
|
+
"facet_col",
|
|
42
|
+
"facet_row",
|
|
43
|
+
"animation_frame",
|
|
44
|
+
),
|
|
45
|
+
"imshow": ("y", "x", "facet_col", "animation_frame"),
|
|
46
|
+
"box": ("x", "color", "facet_col", "facet_row", "animation_frame"),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class Options:
|
|
52
|
+
"""Configuration options for xarray_plotly.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
label_use_long_name: Use `long_name` attribute for labels. Default True.
|
|
56
|
+
label_use_standard_name: Fall back to `standard_name` if `long_name` not available.
|
|
57
|
+
label_include_units: Append units to labels. Default True.
|
|
58
|
+
label_unit_format: Format string for units. Use `{units}` as placeholder.
|
|
59
|
+
slot_orders: Slot orders per plot type. Keys are plot types, values are tuples.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
label_use_long_name: bool = True
|
|
63
|
+
label_use_standard_name: bool = True
|
|
64
|
+
label_include_units: bool = True
|
|
65
|
+
label_unit_format: str = "[{units}]"
|
|
66
|
+
slot_orders: dict[str, tuple[str, ...]] = field(
|
|
67
|
+
default_factory=lambda: dict(DEFAULT_SLOT_ORDERS)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def to_dict(self) -> dict[str, Any]:
|
|
71
|
+
"""Return options as a dictionary."""
|
|
72
|
+
return {
|
|
73
|
+
"label_use_long_name": self.label_use_long_name,
|
|
74
|
+
"label_use_standard_name": self.label_use_standard_name,
|
|
75
|
+
"label_include_units": self.label_include_units,
|
|
76
|
+
"label_unit_format": self.label_unit_format,
|
|
77
|
+
"slot_orders": self.slot_orders,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Global options instance
|
|
82
|
+
_options = Options()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_options() -> dict[str, Any]:
|
|
86
|
+
"""Get the current xarray_plotly options.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Dictionary of current option values.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
```python
|
|
93
|
+
from xarray_plotly import config
|
|
94
|
+
config.get_options()
|
|
95
|
+
```
|
|
96
|
+
"""
|
|
97
|
+
return _options.to_dict()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@contextmanager
|
|
101
|
+
def set_options(
|
|
102
|
+
*,
|
|
103
|
+
label_use_long_name: bool | None = None,
|
|
104
|
+
label_use_standard_name: bool | None = None,
|
|
105
|
+
label_include_units: bool | None = None,
|
|
106
|
+
label_unit_format: str | None = None,
|
|
107
|
+
slot_orders: dict[str, tuple[str, ...]] | None = None,
|
|
108
|
+
) -> Generator[None, None, None]:
|
|
109
|
+
"""Set xarray_plotly options globally or as a context manager.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
label_use_long_name: Use `long_name` attribute for labels.
|
|
113
|
+
label_use_standard_name: Fall back to `standard_name` if `long_name` not available.
|
|
114
|
+
label_include_units: Append units to labels.
|
|
115
|
+
label_unit_format: Format string for units. Use `{units}` as placeholder.
|
|
116
|
+
slot_orders: Slot orders per plot type.
|
|
117
|
+
|
|
118
|
+
Yields:
|
|
119
|
+
None when used as a context manager.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
```python
|
|
123
|
+
from xarray_plotly import config, xpx
|
|
124
|
+
|
|
125
|
+
# Use as context manager
|
|
126
|
+
with config.set_options(label_include_units=False):
|
|
127
|
+
fig = xpx(da).line() # No units in labels
|
|
128
|
+
# Units are back after the context
|
|
129
|
+
```
|
|
130
|
+
"""
|
|
131
|
+
# Store old values
|
|
132
|
+
old_values = {
|
|
133
|
+
"label_use_long_name": _options.label_use_long_name,
|
|
134
|
+
"label_use_standard_name": _options.label_use_standard_name,
|
|
135
|
+
"label_include_units": _options.label_include_units,
|
|
136
|
+
"label_unit_format": _options.label_unit_format,
|
|
137
|
+
"slot_orders": dict(_options.slot_orders),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Apply new values (modify in place to keep reference)
|
|
141
|
+
if label_use_long_name is not None:
|
|
142
|
+
_options.label_use_long_name = label_use_long_name
|
|
143
|
+
if label_use_standard_name is not None:
|
|
144
|
+
_options.label_use_standard_name = label_use_standard_name
|
|
145
|
+
if label_include_units is not None:
|
|
146
|
+
_options.label_include_units = label_include_units
|
|
147
|
+
if label_unit_format is not None:
|
|
148
|
+
_options.label_unit_format = label_unit_format
|
|
149
|
+
if slot_orders is not None:
|
|
150
|
+
_options.slot_orders = dict(slot_orders)
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
yield
|
|
154
|
+
finally:
|
|
155
|
+
# Restore old values (modify in place)
|
|
156
|
+
_options.label_use_long_name = old_values["label_use_long_name"]
|
|
157
|
+
_options.label_use_standard_name = old_values["label_use_standard_name"]
|
|
158
|
+
_options.label_include_units = old_values["label_include_units"]
|
|
159
|
+
_options.label_unit_format = old_values["label_unit_format"]
|
|
160
|
+
_options.slot_orders = old_values["slot_orders"]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def notebook(renderer: str = "notebook") -> None:
|
|
164
|
+
"""Configure Plotly for Jupyter notebook rendering.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
renderer: The Plotly renderer to use. Default is "notebook".
|
|
168
|
+
Other options include "jupyterlab", "colab", "kaggle", etc.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
```python
|
|
172
|
+
from xarray_plotly import config
|
|
173
|
+
config.notebook() # Configure for Jupyter notebooks
|
|
174
|
+
```
|
|
175
|
+
"""
|
|
176
|
+
import plotly.io as pio
|
|
177
|
+
|
|
178
|
+
pio.renderers.default = renderer
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plotly Express plotting functions for DataArray objects.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
import plotly.express as px
|
|
10
|
+
|
|
11
|
+
from xarray_plotly.common import (
|
|
12
|
+
SlotValue,
|
|
13
|
+
assign_slots,
|
|
14
|
+
auto,
|
|
15
|
+
build_labels,
|
|
16
|
+
get_label,
|
|
17
|
+
get_value_col,
|
|
18
|
+
to_dataframe,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
import plotly.graph_objects as go
|
|
23
|
+
from xarray import DataArray
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def line(
|
|
27
|
+
darray: DataArray,
|
|
28
|
+
*,
|
|
29
|
+
x: SlotValue = auto,
|
|
30
|
+
color: SlotValue = auto,
|
|
31
|
+
line_dash: SlotValue = auto,
|
|
32
|
+
symbol: SlotValue = auto,
|
|
33
|
+
facet_col: SlotValue = auto,
|
|
34
|
+
facet_row: SlotValue = auto,
|
|
35
|
+
animation_frame: SlotValue = auto,
|
|
36
|
+
**px_kwargs: Any,
|
|
37
|
+
) -> go.Figure:
|
|
38
|
+
"""
|
|
39
|
+
Create an interactive line plot from a DataArray.
|
|
40
|
+
|
|
41
|
+
The y-axis shows DataArray values. Dimensions fill slots in order:
|
|
42
|
+
x -> color -> line_dash -> symbol -> facet_col -> facet_row -> animation_frame
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
darray
|
|
47
|
+
The DataArray to plot.
|
|
48
|
+
x
|
|
49
|
+
Dimension for x-axis. Default: first dimension.
|
|
50
|
+
color
|
|
51
|
+
Dimension for color grouping. Default: second dimension.
|
|
52
|
+
line_dash
|
|
53
|
+
Dimension for line dash style. Default: third dimension.
|
|
54
|
+
symbol
|
|
55
|
+
Dimension for marker symbol. Default: fourth dimension.
|
|
56
|
+
facet_col
|
|
57
|
+
Dimension for subplot columns. Default: fifth dimension.
|
|
58
|
+
facet_row
|
|
59
|
+
Dimension for subplot rows. Default: sixth dimension.
|
|
60
|
+
animation_frame
|
|
61
|
+
Dimension for animation. Default: seventh dimension.
|
|
62
|
+
**px_kwargs
|
|
63
|
+
Additional arguments passed to `plotly.express.line()`.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
plotly.graph_objects.Figure
|
|
68
|
+
"""
|
|
69
|
+
slots = assign_slots(
|
|
70
|
+
list(darray.dims),
|
|
71
|
+
"line",
|
|
72
|
+
x=x,
|
|
73
|
+
color=color,
|
|
74
|
+
line_dash=line_dash,
|
|
75
|
+
symbol=symbol,
|
|
76
|
+
facet_col=facet_col,
|
|
77
|
+
facet_row=facet_row,
|
|
78
|
+
animation_frame=animation_frame,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
df = to_dataframe(darray)
|
|
82
|
+
value_col = get_value_col(darray)
|
|
83
|
+
labels = {**build_labels(darray, slots, value_col), **px_kwargs.pop("labels", {})}
|
|
84
|
+
|
|
85
|
+
return px.line(
|
|
86
|
+
df,
|
|
87
|
+
x=slots.get("x"),
|
|
88
|
+
y=value_col,
|
|
89
|
+
color=slots.get("color"),
|
|
90
|
+
line_dash=slots.get("line_dash"),
|
|
91
|
+
symbol=slots.get("symbol"),
|
|
92
|
+
facet_col=slots.get("facet_col"),
|
|
93
|
+
facet_row=slots.get("facet_row"),
|
|
94
|
+
animation_frame=slots.get("animation_frame"),
|
|
95
|
+
labels=labels,
|
|
96
|
+
**px_kwargs,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def bar(
|
|
101
|
+
darray: DataArray,
|
|
102
|
+
*,
|
|
103
|
+
x: SlotValue = auto,
|
|
104
|
+
color: SlotValue = auto,
|
|
105
|
+
pattern_shape: SlotValue = auto,
|
|
106
|
+
facet_col: SlotValue = auto,
|
|
107
|
+
facet_row: SlotValue = auto,
|
|
108
|
+
animation_frame: SlotValue = auto,
|
|
109
|
+
**px_kwargs: Any,
|
|
110
|
+
) -> go.Figure:
|
|
111
|
+
"""
|
|
112
|
+
Create an interactive bar chart from a DataArray.
|
|
113
|
+
|
|
114
|
+
The y-axis shows DataArray values. Dimensions fill slots in order:
|
|
115
|
+
x -> color -> pattern_shape -> facet_col -> facet_row -> animation_frame
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
darray
|
|
120
|
+
The DataArray to plot.
|
|
121
|
+
x
|
|
122
|
+
Dimension for x-axis. Default: first dimension.
|
|
123
|
+
color
|
|
124
|
+
Dimension for color grouping. Default: second dimension.
|
|
125
|
+
pattern_shape
|
|
126
|
+
Dimension for bar fill pattern. Default: third dimension.
|
|
127
|
+
facet_col
|
|
128
|
+
Dimension for subplot columns. Default: fourth dimension.
|
|
129
|
+
facet_row
|
|
130
|
+
Dimension for subplot rows. Default: fifth dimension.
|
|
131
|
+
animation_frame
|
|
132
|
+
Dimension for animation. Default: sixth dimension.
|
|
133
|
+
**px_kwargs
|
|
134
|
+
Additional arguments passed to `plotly.express.bar()`.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
plotly.graph_objects.Figure
|
|
139
|
+
"""
|
|
140
|
+
slots = assign_slots(
|
|
141
|
+
list(darray.dims),
|
|
142
|
+
"bar",
|
|
143
|
+
x=x,
|
|
144
|
+
color=color,
|
|
145
|
+
pattern_shape=pattern_shape,
|
|
146
|
+
facet_col=facet_col,
|
|
147
|
+
facet_row=facet_row,
|
|
148
|
+
animation_frame=animation_frame,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
df = to_dataframe(darray)
|
|
152
|
+
value_col = get_value_col(darray)
|
|
153
|
+
labels = {**build_labels(darray, slots, value_col), **px_kwargs.pop("labels", {})}
|
|
154
|
+
|
|
155
|
+
return px.bar(
|
|
156
|
+
df,
|
|
157
|
+
x=slots.get("x"),
|
|
158
|
+
y=value_col,
|
|
159
|
+
color=slots.get("color"),
|
|
160
|
+
pattern_shape=slots.get("pattern_shape"),
|
|
161
|
+
facet_col=slots.get("facet_col"),
|
|
162
|
+
facet_row=slots.get("facet_row"),
|
|
163
|
+
animation_frame=slots.get("animation_frame"),
|
|
164
|
+
labels=labels,
|
|
165
|
+
**px_kwargs,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def area(
|
|
170
|
+
darray: DataArray,
|
|
171
|
+
*,
|
|
172
|
+
x: SlotValue = auto,
|
|
173
|
+
color: SlotValue = auto,
|
|
174
|
+
pattern_shape: SlotValue = auto,
|
|
175
|
+
facet_col: SlotValue = auto,
|
|
176
|
+
facet_row: SlotValue = auto,
|
|
177
|
+
animation_frame: SlotValue = auto,
|
|
178
|
+
**px_kwargs: Any,
|
|
179
|
+
) -> go.Figure:
|
|
180
|
+
"""
|
|
181
|
+
Create an interactive stacked area chart from a DataArray.
|
|
182
|
+
|
|
183
|
+
The y-axis shows DataArray values. Dimensions fill slots in order:
|
|
184
|
+
x -> color -> pattern_shape -> facet_col -> facet_row -> animation_frame
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
darray
|
|
189
|
+
The DataArray to plot.
|
|
190
|
+
x
|
|
191
|
+
Dimension for x-axis. Default: first dimension.
|
|
192
|
+
color
|
|
193
|
+
Dimension for color/stacking. Default: second dimension.
|
|
194
|
+
pattern_shape
|
|
195
|
+
Dimension for fill pattern. Default: third dimension.
|
|
196
|
+
facet_col
|
|
197
|
+
Dimension for subplot columns. Default: fourth dimension.
|
|
198
|
+
facet_row
|
|
199
|
+
Dimension for subplot rows. Default: fifth dimension.
|
|
200
|
+
animation_frame
|
|
201
|
+
Dimension for animation. Default: sixth dimension.
|
|
202
|
+
**px_kwargs
|
|
203
|
+
Additional arguments passed to `plotly.express.area()`.
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
plotly.graph_objects.Figure
|
|
208
|
+
"""
|
|
209
|
+
slots = assign_slots(
|
|
210
|
+
list(darray.dims),
|
|
211
|
+
"area",
|
|
212
|
+
x=x,
|
|
213
|
+
color=color,
|
|
214
|
+
pattern_shape=pattern_shape,
|
|
215
|
+
facet_col=facet_col,
|
|
216
|
+
facet_row=facet_row,
|
|
217
|
+
animation_frame=animation_frame,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
df = to_dataframe(darray)
|
|
221
|
+
value_col = get_value_col(darray)
|
|
222
|
+
labels = {**build_labels(darray, slots, value_col), **px_kwargs.pop("labels", {})}
|
|
223
|
+
|
|
224
|
+
return px.area(
|
|
225
|
+
df,
|
|
226
|
+
x=slots.get("x"),
|
|
227
|
+
y=value_col,
|
|
228
|
+
color=slots.get("color"),
|
|
229
|
+
pattern_shape=slots.get("pattern_shape"),
|
|
230
|
+
facet_col=slots.get("facet_col"),
|
|
231
|
+
facet_row=slots.get("facet_row"),
|
|
232
|
+
animation_frame=slots.get("animation_frame"),
|
|
233
|
+
labels=labels,
|
|
234
|
+
**px_kwargs,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def box(
|
|
239
|
+
darray: DataArray,
|
|
240
|
+
*,
|
|
241
|
+
x: SlotValue = auto,
|
|
242
|
+
color: SlotValue = None,
|
|
243
|
+
facet_col: SlotValue = None,
|
|
244
|
+
facet_row: SlotValue = None,
|
|
245
|
+
animation_frame: SlotValue = None,
|
|
246
|
+
**px_kwargs: Any,
|
|
247
|
+
) -> go.Figure:
|
|
248
|
+
"""
|
|
249
|
+
Create an interactive box plot from a DataArray.
|
|
250
|
+
|
|
251
|
+
The y-axis shows DataArray values. By default, only x is auto-assigned;
|
|
252
|
+
other dimensions are aggregated into the box statistics.
|
|
253
|
+
|
|
254
|
+
Dimensions fill slots in order: x -> color -> facet_col -> facet_row -> animation_frame
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
darray
|
|
259
|
+
The DataArray to plot.
|
|
260
|
+
x
|
|
261
|
+
Dimension for x-axis categories. Default: first dimension.
|
|
262
|
+
color
|
|
263
|
+
Dimension for color grouping. Default: None (aggregated).
|
|
264
|
+
facet_col
|
|
265
|
+
Dimension for subplot columns. Default: None (aggregated).
|
|
266
|
+
facet_row
|
|
267
|
+
Dimension for subplot rows. Default: None (aggregated).
|
|
268
|
+
animation_frame
|
|
269
|
+
Dimension for animation. Default: None (aggregated).
|
|
270
|
+
**px_kwargs
|
|
271
|
+
Additional arguments passed to `plotly.express.box()`.
|
|
272
|
+
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
plotly.graph_objects.Figure
|
|
276
|
+
"""
|
|
277
|
+
slots = assign_slots(
|
|
278
|
+
list(darray.dims),
|
|
279
|
+
"box",
|
|
280
|
+
allow_unassigned=True,
|
|
281
|
+
x=x,
|
|
282
|
+
color=color,
|
|
283
|
+
facet_col=facet_col,
|
|
284
|
+
facet_row=facet_row,
|
|
285
|
+
animation_frame=animation_frame,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
df = to_dataframe(darray)
|
|
289
|
+
value_col = get_value_col(darray)
|
|
290
|
+
labels = {**build_labels(darray, slots, value_col), **px_kwargs.pop("labels", {})}
|
|
291
|
+
|
|
292
|
+
return px.box(
|
|
293
|
+
df,
|
|
294
|
+
x=slots.get("x"),
|
|
295
|
+
y=value_col,
|
|
296
|
+
color=slots.get("color"),
|
|
297
|
+
facet_col=slots.get("facet_col"),
|
|
298
|
+
facet_row=slots.get("facet_row"),
|
|
299
|
+
animation_frame=slots.get("animation_frame"),
|
|
300
|
+
labels=labels,
|
|
301
|
+
**px_kwargs,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def scatter(
|
|
306
|
+
darray: DataArray,
|
|
307
|
+
*,
|
|
308
|
+
x: SlotValue = auto,
|
|
309
|
+
y: SlotValue | str = "value",
|
|
310
|
+
color: SlotValue = auto,
|
|
311
|
+
symbol: SlotValue = auto,
|
|
312
|
+
facet_col: SlotValue = auto,
|
|
313
|
+
facet_row: SlotValue = auto,
|
|
314
|
+
animation_frame: SlotValue = auto,
|
|
315
|
+
**px_kwargs: Any,
|
|
316
|
+
) -> go.Figure:
|
|
317
|
+
"""
|
|
318
|
+
Create an interactive scatter plot from a DataArray.
|
|
319
|
+
|
|
320
|
+
By default, y-axis shows DataArray values. Set y to a dimension name
|
|
321
|
+
for dimension-vs-dimension plots (e.g., lat vs lon colored by value).
|
|
322
|
+
|
|
323
|
+
Dimensions fill slots in order:
|
|
324
|
+
x -> color -> symbol -> facet_col -> facet_row -> animation_frame
|
|
325
|
+
|
|
326
|
+
Parameters
|
|
327
|
+
----------
|
|
328
|
+
darray
|
|
329
|
+
The DataArray to plot.
|
|
330
|
+
x
|
|
331
|
+
Dimension for x-axis. Default: first dimension.
|
|
332
|
+
y
|
|
333
|
+
What to plot on y-axis. Default "value" uses DataArray values.
|
|
334
|
+
Can be a dimension name for dimension vs dimension plots.
|
|
335
|
+
color
|
|
336
|
+
Dimension for color grouping. Default: second dimension.
|
|
337
|
+
Use "value" to color by DataArray values (useful with y=dimension).
|
|
338
|
+
symbol
|
|
339
|
+
Dimension for marker symbol. Default: third dimension.
|
|
340
|
+
facet_col
|
|
341
|
+
Dimension for subplot columns. Default: fourth dimension.
|
|
342
|
+
facet_row
|
|
343
|
+
Dimension for subplot rows. Default: fifth dimension.
|
|
344
|
+
animation_frame
|
|
345
|
+
Dimension for animation. Default: sixth dimension.
|
|
346
|
+
**px_kwargs
|
|
347
|
+
Additional arguments passed to `plotly.express.scatter()`.
|
|
348
|
+
|
|
349
|
+
Returns
|
|
350
|
+
-------
|
|
351
|
+
plotly.graph_objects.Figure
|
|
352
|
+
"""
|
|
353
|
+
# If y is a dimension, exclude it from slot assignment
|
|
354
|
+
y_is_dim = y != "value" and y in darray.dims
|
|
355
|
+
dims_for_slots = [d for d in darray.dims if d != y] if y_is_dim else list(darray.dims)
|
|
356
|
+
|
|
357
|
+
slots = assign_slots(
|
|
358
|
+
dims_for_slots,
|
|
359
|
+
"scatter",
|
|
360
|
+
x=x,
|
|
361
|
+
color=color,
|
|
362
|
+
symbol=symbol,
|
|
363
|
+
facet_col=facet_col,
|
|
364
|
+
facet_row=facet_row,
|
|
365
|
+
animation_frame=animation_frame,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
df = to_dataframe(darray)
|
|
369
|
+
value_col = get_value_col(darray)
|
|
370
|
+
|
|
371
|
+
# Resolve y and color columns (may be "value" -> actual column name)
|
|
372
|
+
y_col = value_col if y == "value" else y
|
|
373
|
+
color_col = value_col if slots.get("color") == "value" else slots.get("color")
|
|
374
|
+
|
|
375
|
+
# Build labels
|
|
376
|
+
labels = {**build_labels(darray, slots, value_col), **px_kwargs.pop("labels", {})}
|
|
377
|
+
if y_is_dim and str(y) not in labels:
|
|
378
|
+
labels[str(y)] = get_label(darray, y)
|
|
379
|
+
|
|
380
|
+
return px.scatter(
|
|
381
|
+
df,
|
|
382
|
+
x=slots.get("x"),
|
|
383
|
+
y=y_col,
|
|
384
|
+
color=color_col,
|
|
385
|
+
symbol=slots.get("symbol"),
|
|
386
|
+
facet_col=slots.get("facet_col"),
|
|
387
|
+
facet_row=slots.get("facet_row"),
|
|
388
|
+
animation_frame=slots.get("animation_frame"),
|
|
389
|
+
labels=labels,
|
|
390
|
+
**px_kwargs,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def imshow(
|
|
395
|
+
darray: DataArray,
|
|
396
|
+
*,
|
|
397
|
+
x: SlotValue = auto,
|
|
398
|
+
y: SlotValue = auto,
|
|
399
|
+
facet_col: SlotValue = auto,
|
|
400
|
+
animation_frame: SlotValue = auto,
|
|
401
|
+
**px_kwargs: Any,
|
|
402
|
+
) -> go.Figure:
|
|
403
|
+
"""
|
|
404
|
+
Create an interactive heatmap from a DataArray.
|
|
405
|
+
|
|
406
|
+
Both x and y are dimensions. Dimensions fill slots in order:
|
|
407
|
+
y (rows) -> x (columns) -> facet_col -> animation_frame
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
darray
|
|
412
|
+
The DataArray to plot.
|
|
413
|
+
x
|
|
414
|
+
Dimension for x-axis (columns). Default: second dimension.
|
|
415
|
+
y
|
|
416
|
+
Dimension for y-axis (rows). Default: first dimension.
|
|
417
|
+
facet_col
|
|
418
|
+
Dimension for subplot columns. Default: third dimension.
|
|
419
|
+
animation_frame
|
|
420
|
+
Dimension for animation. Default: fourth dimension.
|
|
421
|
+
**px_kwargs
|
|
422
|
+
Additional arguments passed to `plotly.express.imshow()`.
|
|
423
|
+
|
|
424
|
+
Returns
|
|
425
|
+
-------
|
|
426
|
+
plotly.graph_objects.Figure
|
|
427
|
+
"""
|
|
428
|
+
slots = assign_slots(
|
|
429
|
+
list(darray.dims),
|
|
430
|
+
"imshow",
|
|
431
|
+
y=y,
|
|
432
|
+
x=x,
|
|
433
|
+
facet_col=facet_col,
|
|
434
|
+
animation_frame=animation_frame,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# Transpose to: y (rows), x (cols), facet_col, animation_frame
|
|
438
|
+
transpose_order = [
|
|
439
|
+
slots[k] for k in ("y", "x", "facet_col", "animation_frame") if slots.get(k) is not None
|
|
440
|
+
]
|
|
441
|
+
plot_data = darray.transpose(*transpose_order) if transpose_order else darray
|
|
442
|
+
|
|
443
|
+
return px.imshow(
|
|
444
|
+
plot_data,
|
|
445
|
+
facet_col=slots.get("facet_col"),
|
|
446
|
+
animation_frame=slots.get("animation_frame"),
|
|
447
|
+
**px_kwargs,
|
|
448
|
+
)
|
xarray_plotly/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xarray_plotly
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: Interactive Plotly Express plotting accessor for xarray
|
|
5
|
+
Author: Felix
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FBumann/xarray_plotly
|
|
8
|
+
Project-URL: Documentation, https://fbumann.github.io/xarray_plotly
|
|
9
|
+
Project-URL: Repository, https://github.com/FBumann/xarray_plotly
|
|
10
|
+
Keywords: xarray,plotly,visualization,interactive,plotting
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: xarray>=2023.1.0
|
|
25
|
+
Requires-Dist: plotly>=5.0.0
|
|
26
|
+
Requires-Dist: pandas>=1.5.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest==8.3.5; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov==6.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy==1.14.1; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff==0.9.2; extra == "dev"
|
|
32
|
+
Requires-Dist: pre-commit==4.0.1; extra == "dev"
|
|
33
|
+
Requires-Dist: nbstripout==0.8.1; extra == "dev"
|
|
34
|
+
Provides-Extra: docs
|
|
35
|
+
Requires-Dist: mkdocs==1.6.1; extra == "docs"
|
|
36
|
+
Requires-Dist: mkdocs-material==9.5.49; extra == "docs"
|
|
37
|
+
Requires-Dist: mkdocstrings[python]==0.27.0; extra == "docs"
|
|
38
|
+
Requires-Dist: mkdocs-jupyter==0.25.1; extra == "docs"
|
|
39
|
+
Requires-Dist: mkdocs-plotly-plugin==0.1.3; extra == "docs"
|
|
40
|
+
Requires-Dist: jupyter==1.1.1; extra == "docs"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# xarray_plotly
|
|
44
|
+
|
|
45
|
+
**Interactive Plotly Express plotting for xarray**
|
|
46
|
+
|
|
47
|
+
[](https://badge.fury.io/py/xarray_plotly)
|
|
48
|
+
[](https://pypi.org/project/xarray_plotly/)
|
|
49
|
+
[](https://github.com/FBumann/xarray_plotly/actions)
|
|
50
|
+
[](https://fbumann.github.io/xarray_plotly/)
|
|
51
|
+
[](https://opensource.org/licenses/MIT)
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install xarray_plotly
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import xarray as xr
|
|
63
|
+
import numpy as np
|
|
64
|
+
import xarray_plotly # registers the accessor
|
|
65
|
+
|
|
66
|
+
da = xr.DataArray(
|
|
67
|
+
np.random.randn(100, 3).cumsum(axis=0),
|
|
68
|
+
dims=["time", "city"],
|
|
69
|
+
coords={"time": np.arange(100), "city": ["NYC", "LA", "Chicago"]},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Accessor style
|
|
73
|
+
fig = da.plotly.line()
|
|
74
|
+
fig.show()
|
|
75
|
+
|
|
76
|
+
# Or with xpx() for IDE code completion
|
|
77
|
+
from xarray_plotly import xpx
|
|
78
|
+
fig = xpx(da).line()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Why `xpx()`?** The accessor (`da.plotly`) works but IDEs can't provide code completion for it. This is because xarray accessors are registered dynamically at runtime, making them invisible to static type checkers. The `xpx()` function provides the same functionality with full IDE support. This limitation could only be solved by xarray itself, if at all — it may be a fundamental Python limitation.
|
|
82
|
+
|
|
83
|
+
## Documentation
|
|
84
|
+
|
|
85
|
+
Full documentation: [https://fbumann.github.io/xarray_plotly](https://fbumann.github.io/xarray_plotly)
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
xarray_plotly/__init__.py,sha256=lmRFXU6azK9Il9mWH5qFP2I6QeYxq2-XvLuiJK0spVU,2244
|
|
2
|
+
xarray_plotly/accessor.py,sha256=ekntB7TnOZEKEyXAt0F8pnFgdfmYcIbzPq0Nfyy3Xt0,9523
|
|
3
|
+
xarray_plotly/common.py,sha256=YTiaPLJ0Gh20mHV8-72J8DjWk-XaSYaT_ZiqXp6cecU,7192
|
|
4
|
+
xarray_plotly/config.py,sha256=oHHUd0vOOWJkAifyp7ShDJqklj1UXjeRwZijFAGdp1s,5597
|
|
5
|
+
xarray_plotly/plotting.py,sha256=A8J3TIOtUhTGpyp8wk680wo3I8MVe0z_ZWnI67rkkbw,12393
|
|
6
|
+
xarray_plotly/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
xarray_plotly-0.0.3.dist-info/licenses/LICENSE,sha256=AvVEfNqbhIm9jHvt0acJNjW1JUKa2a70Zb5rJdEXCJI,1064
|
|
8
|
+
xarray_plotly-0.0.3.dist-info/METADATA,sha256=Uw5AyuxuLp7SWnwnbRnVBTbt8NcgkyulBmdgPJ0xJ88,3415
|
|
9
|
+
xarray_plotly-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
xarray_plotly-0.0.3.dist-info/top_level.txt,sha256=GtMkvuZvLAYTjYXtwoNUa0ag42CJARZJK1CZemYD7pg,14
|
|
11
|
+
xarray_plotly-0.0.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FBumann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xarray_plotly
|