xarray-plotly 0.0.4__tar.gz → 0.0.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/.github/workflows/ci.yml +32 -0
  2. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/.github/workflows/docs.yml +2 -1
  3. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/PKG-INFO +3 -3
  4. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/README.md +2 -2
  5. xarray_plotly-0.0.6/docs/examples/datasets.ipynb +261 -0
  6. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/mkdocs.yml +1 -0
  7. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/tests/test_accessor.py +49 -0
  8. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly/accessor.py +24 -2
  9. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly/config.py +12 -0
  10. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly/plotting.py +20 -0
  11. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly.egg-info/PKG-INFO +3 -3
  12. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly.egg-info/SOURCES.txt +1 -0
  13. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/.github/dependabot.yml +0 -0
  14. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/.github/workflows/dependabot-auto-merge.yml +0 -0
  15. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/.github/workflows/release.yml +0 -0
  16. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/.gitignore +0 -0
  17. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/.pre-commit-config.yaml +0 -0
  18. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/CONTRIBUTING.md +0 -0
  19. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/LICENSE +0 -0
  20. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/docs/api.md +0 -0
  21. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/docs/examples/dimensions.ipynb +0 -0
  22. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/docs/examples/figure.ipynb +0 -0
  23. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/docs/examples/kwargs.ipynb +0 -0
  24. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/docs/examples/plot-types.ipynb +0 -0
  25. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/docs/getting-started.ipynb +0 -0
  26. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/docs/index.md +0 -0
  27. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/pyproject.toml +0 -0
  28. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/setup.cfg +0 -0
  29. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/tests/__init__.py +0 -0
  30. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/tests/test_common.py +0 -0
  31. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/tests/test_config.py +0 -0
  32. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly/__init__.py +0 -0
  33. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly/common.py +0 -0
  34. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly/py.typed +0 -0
  35. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly.egg-info/dependency_links.txt +0 -0
  36. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly.egg-info/requires.txt +0 -0
  37. {xarray_plotly-0.0.4 → xarray_plotly-0.0.6}/xarray_plotly.egg-info/top_level.txt +0 -0
@@ -40,3 +40,35 @@ jobs:
40
40
  with:
41
41
  token: ${{ secrets.CODECOV_TOKEN }}
42
42
  fail_ci_if_error: false
43
+
44
+ docs:
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v6
48
+
49
+ - name: Install uv
50
+ uses: astral-sh/setup-uv@v7
51
+
52
+ - name: Install dependencies
53
+ run: uv sync --extra docs
54
+
55
+ - name: Build docs
56
+ run: uv run mkdocs build
57
+
58
+ ci-success:
59
+ name: CI Success
60
+ needs: [test, docs]
61
+ if: always()
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - name: Check all jobs passed
65
+ run: |
66
+ if [[ "${{ needs.test.result }}" != "success" ]]; then
67
+ echo "Test job failed or was cancelled"
68
+ exit 1
69
+ fi
70
+ if [[ "${{ needs.docs.result }}" != "success" ]]; then
71
+ echo "Docs job failed or was cancelled"
72
+ exit 1
73
+ fi
74
+ echo "All CI checks passed!"
@@ -1,7 +1,7 @@
1
1
  name: Docs
2
2
 
3
3
  on:
4
- push:
4
+ pull_request:
5
5
  branches: [main]
6
6
  release:
7
7
  types: [published]
@@ -26,6 +26,7 @@ jobs:
26
26
  run: uv run mkdocs build
27
27
 
28
28
  - name: Deploy to GitHub Pages
29
+ if: github.event_name == 'release'
29
30
  uses: peaceiris/actions-gh-pages@v4
30
31
  with:
31
32
  github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray_plotly
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: Interactive Plotly Express plotting accessor for xarray
5
5
  Author: Felix
6
6
  License: MIT
@@ -44,8 +44,8 @@ Dynamic: license-file
44
44
 
45
45
  **Interactive Plotly Express plotting for xarray**
46
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/)
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
49
  [![CI](https://github.com/FBumann/xarray_plotly/actions/workflows/ci.yml/badge.svg)](https://github.com/FBumann/xarray_plotly/actions)
50
50
  [![Docs](https://img.shields.io/badge/docs-fbumann.github.io-blue)](https://fbumann.github.io/xarray_plotly/)
51
51
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -2,8 +2,8 @@
2
2
 
3
3
  **Interactive Plotly Express plotting for xarray**
4
4
 
5
- [![PyPI version](https://badge.fury.io/py/xarray_plotly.svg)](https://badge.fury.io/py/xarray_plotly)
6
- [![Python](https://img.shields.io/pypi/pyversions/xarray_plotly.svg)](https://pypi.org/project/xarray_plotly/)
5
+ [![PyPI version](https://badge.fury.io/py/xarray-plotly.svg)](https://badge.fury.io/py/xarray-plotly)
6
+ [![Python](https://img.shields.io/pypi/pyversions/xarray-plotly.svg)](https://pypi.org/project/xarray-plotly/)
7
7
  [![CI](https://github.com/FBumann/xarray_plotly/actions/workflows/ci.yml/badge.svg)](https://github.com/FBumann/xarray_plotly/actions)
8
8
  [![Docs](https://img.shields.io/badge/docs-fbumann.github.io-blue)](https://fbumann.github.io/xarray_plotly/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -0,0 +1,261 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Dataset Plotting\n",
8
+ "\n",
9
+ "Plot multiple variables from an xarray Dataset with automatic or custom slot assignment."
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": null,
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import numpy as np\n",
19
+ "import xarray as xr\n",
20
+ "\n",
21
+ "from xarray_plotly import config, xpx\n",
22
+ "\n",
23
+ "config.notebook()"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "code",
28
+ "execution_count": null,
29
+ "metadata": {},
30
+ "outputs": [],
31
+ "source": [
32
+ "# Create a Dataset with multiple variables\n",
33
+ "time = np.arange(50)\n",
34
+ "cities = [\"NYC\", \"LA\", \"Chicago\"]\n",
35
+ "\n",
36
+ "ds = xr.Dataset(\n",
37
+ " {\n",
38
+ " \"temperature\": ([\"time\", \"city\"], 20 + 5 * np.random.randn(50, 3).cumsum(axis=0) / 10),\n",
39
+ " \"humidity\": ([\"time\", \"city\"], 50 + 10 * np.random.randn(50, 3).cumsum(axis=0) / 10),\n",
40
+ " \"pressure\": ([\"time\", \"city\"], 1013 + np.random.randn(50, 3).cumsum(axis=0)),\n",
41
+ " },\n",
42
+ " coords={\"time\": time, \"city\": cities},\n",
43
+ ")\n",
44
+ "ds"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "markdown",
49
+ "metadata": {},
50
+ "source": [
51
+ "## Plot All Variables\n",
52
+ "\n",
53
+ "When you call a plot method on a Dataset without specifying `var`, all variables are combined into a single DataArray with a new `\"variable\"` dimension:"
54
+ ]
55
+ },
56
+ {
57
+ "cell_type": "code",
58
+ "execution_count": null,
59
+ "metadata": {},
60
+ "outputs": [],
61
+ "source": [
62
+ "# All variables: time -> x, variable -> color, city -> line_dash\n",
63
+ "xpx(ds).line()"
64
+ ]
65
+ },
66
+ {
67
+ "cell_type": "markdown",
68
+ "metadata": {},
69
+ "source": [
70
+ "## Control Where \"variable\" Goes\n",
71
+ "\n",
72
+ "The `\"variable\"` dimension can be assigned to any slot:"
73
+ ]
74
+ },
75
+ {
76
+ "cell_type": "code",
77
+ "execution_count": null,
78
+ "metadata": {},
79
+ "outputs": [],
80
+ "source": [
81
+ "# Variables as facet columns\n",
82
+ "xpx(ds).line(facet_col=\"variable\")"
83
+ ]
84
+ },
85
+ {
86
+ "cell_type": "code",
87
+ "execution_count": null,
88
+ "metadata": {},
89
+ "outputs": [],
90
+ "source": [
91
+ "# Variables as rows, cities as columns\n",
92
+ "xpx(ds).line(facet_row=\"variable\", facet_col=\"city\")"
93
+ ]
94
+ },
95
+ {
96
+ "cell_type": "markdown",
97
+ "metadata": {},
98
+ "source": [
99
+ "## Configure Default \"variable\" Position\n",
100
+ "\n",
101
+ "By default, `\"variable\"` is placed as the **second** dimension so it maps to `color`. This keeps your first dimension (e.g., time) on the x-axis.\n",
102
+ "\n",
103
+ "You can change this globally with `config.set_options()`:"
104
+ ]
105
+ },
106
+ {
107
+ "cell_type": "code",
108
+ "execution_count": null,
109
+ "metadata": {},
110
+ "outputs": [],
111
+ "source": [
112
+ "# Default: position=1 (second) -> variable goes to color\n",
113
+ "# Note: to_array() puts \"variable\" first, but xpx() reorders it to position 1\n",
114
+ "print(\"Raw to_array() dims:\", ds.to_array().dims) # (variable, time, city)\n",
115
+ "print(\"After xpx reorder: (time, variable, city)\") # time->x, variable->color\n",
116
+ "xpx(ds).line(title=\"Default: variable as color (position=1)\")"
117
+ ]
118
+ },
119
+ {
120
+ "cell_type": "code",
121
+ "execution_count": null,
122
+ "metadata": {},
123
+ "outputs": [],
124
+ "source": [
125
+ "# Position 0: variable goes first (x-axis) - usually not what you want!\n",
126
+ "with config.set_options(dataset_variable_position=0):\n",
127
+ " fig = xpx(ds).line(title=\"position=0: variable on x-axis (probably not desired)\")\n",
128
+ "fig"
129
+ ]
130
+ },
131
+ {
132
+ "cell_type": "code",
133
+ "execution_count": null,
134
+ "metadata": {},
135
+ "outputs": [],
136
+ "source": [
137
+ "# Position -1: variable goes last -> city gets color, variable gets line_dash\n",
138
+ "with config.set_options(dataset_variable_position=-1):\n",
139
+ " fig = xpx(ds).line(title=\"position=-1: variable as line_dash\")\n",
140
+ "fig"
141
+ ]
142
+ },
143
+ {
144
+ "cell_type": "markdown",
145
+ "metadata": {},
146
+ "source": [
147
+ "## Plot a Single Variable\n",
148
+ "\n",
149
+ "Use `var=\"name\"` to plot just one variable:"
150
+ ]
151
+ },
152
+ {
153
+ "cell_type": "code",
154
+ "execution_count": null,
155
+ "metadata": {},
156
+ "outputs": [],
157
+ "source": [
158
+ "xpx(ds).line(var=\"temperature\", title=\"Temperature Only\")"
159
+ ]
160
+ },
161
+ {
162
+ "cell_type": "markdown",
163
+ "metadata": {},
164
+ "source": [
165
+ "## Different Plot Types"
166
+ ]
167
+ },
168
+ {
169
+ "cell_type": "code",
170
+ "execution_count": null,
171
+ "metadata": {},
172
+ "outputs": [],
173
+ "source": [
174
+ "# Bar chart - latest values by city\n",
175
+ "xpx(ds.isel(time=-1)).bar(x=\"city\", color=\"variable\", barmode=\"group\")"
176
+ ]
177
+ },
178
+ {
179
+ "cell_type": "code",
180
+ "execution_count": null,
181
+ "metadata": {},
182
+ "outputs": [],
183
+ "source": [
184
+ "# Box plot - distribution by variable\n",
185
+ "xpx(ds).box(x=\"variable\", color=\"city\")"
186
+ ]
187
+ },
188
+ {
189
+ "cell_type": "code",
190
+ "execution_count": null,
191
+ "metadata": {},
192
+ "outputs": [],
193
+ "source": [
194
+ "# Area chart\n",
195
+ "xpx(ds).area(var=\"humidity\", title=\"Humidity Over Time\")"
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": null,
201
+ "metadata": {},
202
+ "outputs": [],
203
+ "source": [
204
+ "# Scatter\n",
205
+ "xpx(ds).scatter(var=\"temperature\", title=\"Temperature Scatter\")"
206
+ ]
207
+ },
208
+ {
209
+ "cell_type": "code",
210
+ "execution_count": null,
211
+ "metadata": {},
212
+ "outputs": [],
213
+ "source": [
214
+ "# Pie chart - snapshot at one time\n",
215
+ "xpx(ds.isel(time=-1)).pie(var=\"temperature\", names=\"city\", title=\"Temperature Distribution\")"
216
+ ]
217
+ },
218
+ {
219
+ "cell_type": "markdown",
220
+ "metadata": {},
221
+ "source": [
222
+ "## Combining Slot Assignments\n",
223
+ "\n",
224
+ "Mix explicit assignments with auto-assignment:"
225
+ ]
226
+ },
227
+ {
228
+ "cell_type": "code",
229
+ "execution_count": null,
230
+ "metadata": {},
231
+ "outputs": [],
232
+ "source": [
233
+ "# Explicit: variable -> facet_col, let city auto-assign to color\n",
234
+ "xpx(ds).line(facet_col=\"variable\", color=\"city\")"
235
+ ]
236
+ },
237
+ {
238
+ "cell_type": "code",
239
+ "execution_count": null,
240
+ "metadata": {},
241
+ "outputs": [],
242
+ "source": [
243
+ "# Skip color slot with None\n",
244
+ "xpx(ds).line(var=\"temperature\", color=None)"
245
+ ]
246
+ }
247
+ ],
248
+ "metadata": {
249
+ "kernelspec": {
250
+ "display_name": "Python 3",
251
+ "language": "python",
252
+ "name": "python3"
253
+ },
254
+ "language_info": {
255
+ "name": "python",
256
+ "version": "3.12.0"
257
+ }
258
+ },
259
+ "nbformat": 4,
260
+ "nbformat_minor": 4
261
+ }
@@ -69,6 +69,7 @@ nav:
69
69
  - Getting Started: getting-started.ipynb
70
70
  - Examples:
71
71
  - Plot Types: examples/plot-types.ipynb
72
+ - Dataset Plotting: examples/datasets.ipynb
72
73
  - Dimensions & Facets: examples/dimensions.ipynb
73
74
  - Plotly Express Options: examples/kwargs.ipynb
74
75
  - Figure Customization: examples/figure.ipynb
@@ -292,3 +292,52 @@ class TestDatasetPlotlyAccessor:
292
292
  """Test box plot with all variables."""
293
293
  fig = self.ds.plotly.box()
294
294
  assert isinstance(fig, go.Figure)
295
+
296
+
297
+ class TestImshowBounds:
298
+ """Tests for imshow global bounds and robust mode."""
299
+
300
+ def test_imshow_global_bounds(self) -> None:
301
+ """Test that imshow uses global min/max by default."""
302
+ da = xr.DataArray(
303
+ np.array([[[1, 2], [3, 4]], [[5, 6], [7, 100]]]),
304
+ dims=["time", "y", "x"],
305
+ )
306
+ fig = da.plotly.imshow(animation_frame="time")
307
+ # Check coloraxis for zmin/zmax (plotly stores them there)
308
+ coloraxis = fig.layout.coloraxis
309
+ assert coloraxis.cmin == 1.0
310
+ assert coloraxis.cmax == 100.0
311
+
312
+ def test_imshow_robust_bounds(self) -> None:
313
+ """Test that robust=True uses percentile-based bounds."""
314
+ # Create data with outlier
315
+ data = np.random.rand(10, 20) * 100
316
+ data[0, 0] = 10000 # extreme outlier
317
+ da = xr.DataArray(data, dims=["y", "x"])
318
+
319
+ fig = da.plotly.imshow(robust=True)
320
+ # With robust=True, cmax should be much less than the outlier
321
+ coloraxis = fig.layout.coloraxis
322
+ assert coloraxis.cmax < 10000
323
+ assert coloraxis.cmax < 200 # Should be around 98th percentile (~98)
324
+
325
+ def test_imshow_user_zmin_zmax_override(self) -> None:
326
+ """Test that user-provided zmin/zmax overrides auto bounds."""
327
+ da = xr.DataArray(np.random.rand(10, 20) * 100, dims=["y", "x"])
328
+ fig = da.plotly.imshow(zmin=0, zmax=50)
329
+ coloraxis = fig.layout.coloraxis
330
+ assert coloraxis.cmin == 0
331
+ assert coloraxis.cmax == 50
332
+
333
+ def test_imshow_animation_consistent_bounds(self) -> None:
334
+ """Test that animation frames have consistent color bounds."""
335
+ da = xr.DataArray(
336
+ np.array([[[0, 10], [20, 30]], [[40, 50], [60, 70]]]),
337
+ dims=["time", "y", "x"],
338
+ )
339
+ fig = da.plotly.imshow(animation_frame="time")
340
+ # All frames should use global min (0) and max (70)
341
+ coloraxis = fig.layout.coloraxis
342
+ assert coloraxis.cmin == 0.0
343
+ assert coloraxis.cmax == 70.0
@@ -7,6 +7,7 @@ from xarray import DataArray, Dataset
7
7
 
8
8
  from xarray_plotly import plotting
9
9
  from xarray_plotly.common import SlotValue, auto
10
+ from xarray_plotly.config import _options
10
11
 
11
12
 
12
13
  class DataArrayPlotlyAccessor:
@@ -249,6 +250,7 @@ class DataArrayPlotlyAccessor:
249
250
  y: SlotValue = auto,
250
251
  facet_col: SlotValue = auto,
251
252
  animation_frame: SlotValue = auto,
253
+ robust: bool = False,
252
254
  **px_kwargs: Any,
253
255
  ) -> go.Figure:
254
256
  """Create an interactive heatmap image.
@@ -260,7 +262,9 @@ class DataArrayPlotlyAccessor:
260
262
  y: Dimension for y-axis (rows). Default: first dimension.
261
263
  facet_col: Dimension for subplot columns. Default: third dimension.
262
264
  animation_frame: Dimension for animation. Default: fourth dimension.
265
+ robust: If True, use 2nd/98th percentiles for color bounds (handles outliers).
263
266
  **px_kwargs: Additional arguments passed to `plotly.express.imshow()`.
267
+ Use `zmin` and `zmax` to manually set color scale bounds.
264
268
 
265
269
  Returns:
266
270
  Interactive Plotly Figure.
@@ -271,6 +275,7 @@ class DataArrayPlotlyAccessor:
271
275
  y=y,
272
276
  facet_col=facet_col,
273
277
  animation_frame=animation_frame,
278
+ robust=robust,
274
279
  **px_kwargs,
275
280
  )
276
281
 
@@ -349,9 +354,26 @@ class DatasetPlotlyAccessor:
349
354
  return list(self.__all__) + list(super().__dir__())
350
355
 
351
356
  def _get_dataarray(self, var: str | None) -> DataArray:
352
- """Get DataArray from Dataset, either single var or all via to_array()."""
357
+ """Get DataArray from Dataset, either single var or all via to_array().
358
+
359
+ When combining all variables, "variable" is placed at the position
360
+ specified by config.dataset_variable_position (default 1, second position).
361
+ Supports Python-style negative indexing: -1 = last, -2 = second-to-last, etc.
362
+ """
353
363
  if var is None:
354
- return self._ds.to_array(dim="variable")
364
+ da = self._ds.to_array(dim="variable")
365
+ pos = _options.dataset_variable_position
366
+ # Move "variable" to configured position
367
+ if len(da.dims) > 1 and pos != 0:
368
+ dims = list(da.dims)
369
+ dims.remove("variable")
370
+ # Use Python-style indexing (handles negative indices correctly)
371
+ # Clamp to valid range: -1 -> last, -2 -> second-to-last, etc.
372
+ n = len(dims)
373
+ insert_pos = max(0, n + pos + 1) if pos < 0 else min(pos, n)
374
+ dims.insert(insert_pos, "variable")
375
+ da = da.transpose(*dims)
376
+ return da
355
377
  return self._ds[var]
356
378
 
357
379
  def line(
@@ -58,6 +58,9 @@ class Options:
58
58
  label_include_units: Append units to labels. Default True.
59
59
  label_unit_format: Format string for units. Use `{units}` as placeholder.
60
60
  slot_orders: Slot orders per plot type. Keys are plot types, values are tuples.
61
+ dataset_variable_position: Position of "variable" dim when plotting all Dataset
62
+ variables. Default 1 (second position, typically color). Set to 0 for first
63
+ position (x-axis), or -1 for last position.
61
64
  """
62
65
 
63
66
  label_use_long_name: bool = True
@@ -67,6 +70,7 @@ class Options:
67
70
  slot_orders: dict[str, tuple[str, ...]] = field(
68
71
  default_factory=lambda: dict(DEFAULT_SLOT_ORDERS)
69
72
  )
73
+ dataset_variable_position: int = 1
70
74
 
71
75
  def to_dict(self) -> dict[str, Any]:
72
76
  """Return options as a dictionary."""
@@ -76,6 +80,7 @@ class Options:
76
80
  "label_include_units": self.label_include_units,
77
81
  "label_unit_format": self.label_unit_format,
78
82
  "slot_orders": self.slot_orders,
83
+ "dataset_variable_position": self.dataset_variable_position,
79
84
  }
80
85
 
81
86
 
@@ -106,6 +111,7 @@ def set_options(
106
111
  label_include_units: bool | None = None,
107
112
  label_unit_format: str | None = None,
108
113
  slot_orders: dict[str, tuple[str, ...]] | None = None,
114
+ dataset_variable_position: int | None = None,
109
115
  ) -> Generator[None, None, None]:
110
116
  """Set xarray_plotly options globally or as a context manager.
111
117
 
@@ -115,6 +121,8 @@ def set_options(
115
121
  label_include_units: Append units to labels.
116
122
  label_unit_format: Format string for units. Use `{units}` as placeholder.
117
123
  slot_orders: Slot orders per plot type.
124
+ dataset_variable_position: Position of "variable" dim when plotting all Dataset
125
+ variables. Default 1 (second, typically color). Use 0 for first, -1 for last.
118
126
 
119
127
  Yields:
120
128
  None when used as a context manager.
@@ -136,6 +144,7 @@ def set_options(
136
144
  "label_include_units": _options.label_include_units,
137
145
  "label_unit_format": _options.label_unit_format,
138
146
  "slot_orders": dict(_options.slot_orders),
147
+ "dataset_variable_position": _options.dataset_variable_position,
139
148
  }
140
149
 
141
150
  # Apply new values (modify in place to keep reference)
@@ -149,6 +158,8 @@ def set_options(
149
158
  _options.label_unit_format = label_unit_format
150
159
  if slot_orders is not None:
151
160
  _options.slot_orders = dict(slot_orders)
161
+ if dataset_variable_position is not None:
162
+ _options.dataset_variable_position = dataset_variable_position
152
163
 
153
164
  try:
154
165
  yield
@@ -159,6 +170,7 @@ def set_options(
159
170
  _options.label_include_units = old_values["label_include_units"]
160
171
  _options.label_unit_format = old_values["label_unit_format"]
161
172
  _options.slot_orders = old_values["slot_orders"]
173
+ _options.dataset_variable_position = old_values["dataset_variable_position"]
162
174
 
163
175
 
164
176
  def notebook(renderer: str = "notebook") -> None:
@@ -6,6 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  from typing import TYPE_CHECKING, Any
8
8
 
9
+ import numpy as np
9
10
  import plotly.express as px
10
11
 
11
12
  from xarray_plotly.common import (
@@ -398,6 +399,7 @@ def imshow(
398
399
  y: SlotValue = auto,
399
400
  facet_col: SlotValue = auto,
400
401
  animation_frame: SlotValue = auto,
402
+ robust: bool = False,
401
403
  **px_kwargs: Any,
402
404
  ) -> go.Figure:
403
405
  """
@@ -418,8 +420,12 @@ def imshow(
418
420
  Dimension for subplot columns. Default: third dimension.
419
421
  animation_frame
420
422
  Dimension for animation. Default: fourth dimension.
423
+ robust
424
+ If True, compute color bounds using 2nd and 98th percentiles
425
+ for robustness against outliers. Default: False.
421
426
  **px_kwargs
422
427
  Additional arguments passed to `plotly.express.imshow()`.
428
+ Use `zmin` and `zmax` to manually set color scale bounds.
423
429
 
424
430
  Returns
425
431
  -------
@@ -440,6 +446,20 @@ def imshow(
440
446
  ]
441
447
  plot_data = darray.transpose(*transpose_order) if transpose_order else darray
442
448
 
449
+ # Compute global color bounds if not provided
450
+ if "zmin" not in px_kwargs or "zmax" not in px_kwargs:
451
+ values = plot_data.values
452
+ if robust:
453
+ # Use percentiles for outlier robustness
454
+ zmin = float(np.nanpercentile(values, 2))
455
+ zmax = float(np.nanpercentile(values, 98))
456
+ else:
457
+ # Use global min/max across all data
458
+ zmin = float(np.nanmin(values))
459
+ zmax = float(np.nanmax(values))
460
+ px_kwargs.setdefault("zmin", zmin)
461
+ px_kwargs.setdefault("zmax", zmax)
462
+
443
463
  return px.imshow(
444
464
  plot_data,
445
465
  facet_col=slots.get("facet_col"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray_plotly
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: Interactive Plotly Express plotting accessor for xarray
5
5
  Author: Felix
6
6
  License: MIT
@@ -44,8 +44,8 @@ Dynamic: license-file
44
44
 
45
45
  **Interactive Plotly Express plotting for xarray**
46
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/)
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
49
  [![CI](https://github.com/FBumann/xarray_plotly/actions/workflows/ci.yml/badge.svg)](https://github.com/FBumann/xarray_plotly/actions)
50
50
  [![Docs](https://img.shields.io/badge/docs-fbumann.github.io-blue)](https://fbumann.github.io/xarray_plotly/)
51
51
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -13,6 +13,7 @@ pyproject.toml
13
13
  docs/api.md
14
14
  docs/getting-started.ipynb
15
15
  docs/index.md
16
+ docs/examples/datasets.ipynb
16
17
  docs/examples/dimensions.ipynb
17
18
  docs/examples/figure.ipynb
18
19
  docs/examples/kwargs.ipynb
File without changes
File without changes
File without changes
File without changes