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.
@@ -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
+ )
@@ -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