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.
@@ -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
+ )
@@ -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
@@ -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
+ [![PyPI version](https://badge.fury.io/py/xarray_plotly.svg)](https://badge.fury.io/py/xarray_plotly)
48
+ [![Python](https://img.shields.io/pypi/pyversions/xarray_plotly.svg)](https://pypi.org/project/xarray_plotly/)
49
+ [![CI](https://github.com/FBumann/xarray_plotly/actions/workflows/ci.yml/badge.svg)](https://github.com/FBumann/xarray_plotly/actions)
50
+ [![Docs](https://img.shields.io/badge/docs-fbumann.github.io-blue)](https://fbumann.github.io/xarray_plotly/)
51
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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