xarray-plotly 0.0.11__tar.gz → 0.0.13__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 (47) hide show
  1. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/.github/workflows/ci.yml +3 -0
  2. xarray_plotly-0.0.13/.github/workflows/pr-title.yml +33 -0
  3. xarray_plotly-0.0.13/.github/workflows/release-please.yml +19 -0
  4. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/.github/workflows/release.yml +0 -7
  5. xarray_plotly-0.0.13/.release-please-config.json +13 -0
  6. xarray_plotly-0.0.13/.release-please-manifest.json +3 -0
  7. xarray_plotly-0.0.13/CHANGELOG.md +8 -0
  8. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/PKG-INFO +20 -1
  9. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/README.md +19 -0
  10. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/examples/kwargs.ipynb +65 -0
  11. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/tests/test_accessor.py +125 -0
  12. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/tests/test_figures.py +2 -2
  13. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly/__init__.py +4 -3
  14. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly/accessor.py +46 -1
  15. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly/common.py +73 -4
  16. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly/config.py +7 -7
  17. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly/figures.py +30 -13
  18. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly/plotting.py +73 -1
  19. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly.egg-info/PKG-INFO +20 -1
  20. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly.egg-info/SOURCES.txt +5 -0
  21. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/.github/dependabot.yml +0 -0
  22. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/.github/workflows/dependabot-auto-merge.yml +0 -0
  23. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/.github/workflows/docs.yml +0 -0
  24. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/.gitignore +0 -0
  25. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/.pre-commit-config.yaml +0 -0
  26. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/CONTRIBUTING.md +0 -0
  27. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/LICENSE +0 -0
  28. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/api.md +0 -0
  29. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/examples/combining.ipynb +0 -0
  30. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/examples/datasets.ipynb +0 -0
  31. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/examples/dimensions.ipynb +0 -0
  32. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/examples/fast_bar.ipynb +0 -0
  33. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/examples/figure.ipynb +0 -0
  34. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/examples/manipulation.ipynb +0 -0
  35. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/examples/plot-types.ipynb +0 -0
  36. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/getting-started.ipynb +0 -0
  37. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/docs/index.md +0 -0
  38. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/mkdocs.yml +0 -0
  39. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/pyproject.toml +0 -0
  40. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/setup.cfg +0 -0
  41. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/tests/__init__.py +0 -0
  42. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/tests/test_common.py +0 -0
  43. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/tests/test_config.py +0 -0
  44. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly/py.typed +0 -0
  45. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly.egg-info/dependency_links.txt +0 -0
  46. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly.egg-info/requires.txt +0 -0
  47. {xarray_plotly-0.0.11 → xarray_plotly-0.0.13}/xarray_plotly.egg-info/top_level.txt +0 -0
@@ -31,6 +31,9 @@ jobs:
31
31
  - name: Format check
32
32
  run: uv run ruff format --check .
33
33
 
34
+ - name: Type check
35
+ run: uv run mypy xarray_plotly
36
+
34
37
  - name: Test
35
38
  run: uv run pytest --cov=xarray_plotly --cov-report=xml
36
39
 
@@ -0,0 +1,33 @@
1
+ name: PR Title Check
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, edited, synchronize, reopened]
6
+
7
+ jobs:
8
+ validate:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: amannn/action-semantic-pull-request@v5
12
+ env:
13
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14
+ with:
15
+ # Require conventional commit format: type(scope): description
16
+ # Types allowed (default set)
17
+ types: |
18
+ feat
19
+ fix
20
+ docs
21
+ style
22
+ refactor
23
+ perf
24
+ test
25
+ build
26
+ ci
27
+ chore
28
+ revert
29
+ # Scope is optional
30
+ requireScope: false
31
+ # Subject (description) requirements
32
+ subjectPattern: ^.+$
33
+ subjectPatternError: "PR title must have a description after the type"
@@ -0,0 +1,19 @@
1
+ name: Release Please
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: write
10
+ pull-requests: write
11
+
12
+ jobs:
13
+ release-please:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: googleapis/release-please-action@v4
17
+ with:
18
+ config-file: .release-please-config.json
19
+ manifest-file: .release-please-manifest.json
@@ -10,7 +10,6 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  permissions:
12
12
  id-token: write # for trusted publishing
13
- contents: write # for creating GitHub release
14
13
 
15
14
  steps:
16
15
  - uses: actions/checkout@v6
@@ -23,9 +22,3 @@ jobs:
23
22
 
24
23
  - name: Publish to PyPI
25
24
  uses: pypa/gh-action-pypi-publish@release/v1
26
-
27
- - name: Create GitHub Release
28
- uses: softprops/action-gh-release@v2
29
- with:
30
- generate_release_notes: true
31
- files: dist/*
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
+ "bootstrap-sha": "3c1b9d991a58c2369fe4817ad2f3d555aaab969e",
4
+ "include-component-in-tag": false,
5
+ "packages": {
6
+ ".": {
7
+ "release-type": "simple",
8
+ "package-name": "xarray_plotly",
9
+ "bump-patch-for-minor-pre-major": true,
10
+ "changelog-path": "CHANGELOG.md"
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.0.13"
3
+ }
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## [0.0.13](https://github.com/FBumann/xarray_plotly/compare/v0.0.12...v0.0.13) (2026-01-28)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * secondary y axis match and visibility when faceted ([#32](https://github.com/FBumann/xarray_plotly/issues/32)) ([10da229](https://github.com/FBumann/xarray_plotly/commit/10da229282377a229054321e8ff98e3fd6825939))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray_plotly
3
- Version: 0.0.11
3
+ Version: 0.0.13
4
4
  Summary: Interactive Plotly Express plotting accessor for xarray
5
5
  Author: Felix
6
6
  License: MIT
@@ -84,6 +84,25 @@ fig = xpx(da).line()
84
84
 
85
85
  Full documentation: [https://fbumann.github.io/xarray_plotly](https://fbumann.github.io/xarray_plotly)
86
86
 
87
+ ## Roadmap
88
+
89
+ Planned additions (contributions welcome):
90
+
91
+ **New plot types**
92
+ - `histogram()` — distribution of DataArray values
93
+ - `violin()` — richer distribution visualization than box plots
94
+ - `density_heatmap()` — 2D histograms (x and y as dimensions)
95
+ - `density_contour()` — 2D density contours (x and y as dimensions)
96
+
97
+ **Enhancements**
98
+ - WebGL rendering option for `line()` and `scatter()` (large datasets)
99
+
100
+ **Figure utilities** (facet/animation-aware)
101
+ - `fill_between()` — fill area between two traces (uncertainty bands)
102
+ - `sync_axes()` — consistent axis ranges across facets and animation frames
103
+ - `add_secondary_x()` — secondary x-axis (like `add_secondary_y()`)
104
+ - `stack()` — vertically stack separate figures into subplots
105
+
87
106
  ## License
88
107
 
89
108
  MIT
@@ -42,6 +42,25 @@ fig = xpx(da).line()
42
42
 
43
43
  Full documentation: [https://fbumann.github.io/xarray_plotly](https://fbumann.github.io/xarray_plotly)
44
44
 
45
+ ## Roadmap
46
+
47
+ Planned additions (contributions welcome):
48
+
49
+ **New plot types**
50
+ - `histogram()` — distribution of DataArray values
51
+ - `violin()` — richer distribution visualization than box plots
52
+ - `density_heatmap()` — 2D histograms (x and y as dimensions)
53
+ - `density_contour()` — 2D density contours (x and y as dimensions)
54
+
55
+ **Enhancements**
56
+ - WebGL rendering option for `line()` and `scatter()` (large datasets)
57
+
58
+ **Figure utilities** (facet/animation-aware)
59
+ - `fill_between()` — fill area between two traces (uncertainty bands)
60
+ - `sync_axes()` — consistent axis ranges across facets and animation frames
61
+ - `add_secondary_x()` — secondary x-axis (like `add_secondary_y()`)
62
+ - `stack()` — vertically stack separate figures into subplots
63
+
45
64
  ## License
46
65
 
47
66
  MIT
@@ -159,6 +159,71 @@
159
159
  "xpx(change).imshow(color_continuous_scale=\"RdBu_r\", color_continuous_midpoint=0)"
160
160
  ]
161
161
  },
162
+ {
163
+ "cell_type": "markdown",
164
+ "metadata": {},
165
+ "source": [
166
+ "## colors (unified parameter)\n",
167
+ "\n",
168
+ "The `colors` parameter provides a simpler way to set colors without remembering the exact Plotly parameter name. It automatically maps to the correct parameter based on the input type:\n",
169
+ "\n",
170
+ "| Input | Maps To |\n",
171
+ "|-------|---------|\n",
172
+ "| `\"Viridis\"` (continuous scale name) | `color_continuous_scale` |\n",
173
+ "| `\"D3\"` (qualitative palette name) | `color_discrete_sequence` |\n",
174
+ "| `[\"red\", \"blue\"]` (list) | `color_discrete_sequence` |\n",
175
+ "| `{\"A\": \"red\"}` (dict) | `color_discrete_map` |"
176
+ ]
177
+ },
178
+ {
179
+ "cell_type": "code",
180
+ "execution_count": null,
181
+ "metadata": {},
182
+ "outputs": [],
183
+ "source": [
184
+ "# Named qualitative palette\n",
185
+ "xpx(stocks).line(colors=\"D3\")"
186
+ ]
187
+ },
188
+ {
189
+ "cell_type": "code",
190
+ "execution_count": null,
191
+ "metadata": {},
192
+ "outputs": [],
193
+ "source": [
194
+ "# List of custom colors\n",
195
+ "xpx(stocks).line(colors=[\"#E63946\", \"#457B9D\", \"#2A9D8F\", \"#E9C46A\", \"#F4A261\"])"
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": null,
201
+ "metadata": {},
202
+ "outputs": [],
203
+ "source": [
204
+ "# Dict for explicit mapping\n",
205
+ "xpx(stocks).line(\n",
206
+ " colors={\n",
207
+ " \"GOOG\": \"red\",\n",
208
+ " \"AAPL\": \"blue\",\n",
209
+ " \"AMZN\": \"green\",\n",
210
+ " \"FB\": \"purple\",\n",
211
+ " \"NFLX\": \"orange\",\n",
212
+ " \"MSFT\": \"brown\",\n",
213
+ " }\n",
214
+ ")"
215
+ ]
216
+ },
217
+ {
218
+ "cell_type": "code",
219
+ "execution_count": null,
220
+ "metadata": {},
221
+ "outputs": [],
222
+ "source": [
223
+ "# Continuous scale for heatmaps\n",
224
+ "xpx(stocks).imshow(colors=\"Plasma\")"
225
+ ]
226
+ },
162
227
  {
163
228
  "cell_type": "markdown",
164
229
  "metadata": {},
@@ -401,3 +401,128 @@ class TestImshowBounds:
401
401
  coloraxis = fig.layout.coloraxis
402
402
  assert coloraxis.cmin == 0.0
403
403
  assert coloraxis.cmax == 70.0
404
+
405
+
406
+ class TestColorsParameter:
407
+ """Tests for the unified colors parameter."""
408
+
409
+ @pytest.fixture(autouse=True)
410
+ def setup(self) -> None:
411
+ """Create test DataArrays."""
412
+ self.da = xr.DataArray(
413
+ np.random.rand(10, 3),
414
+ dims=["time", "city"],
415
+ coords={"city": ["A", "B", "C"]},
416
+ )
417
+
418
+ def test_colors_list_sets_discrete_sequence(self) -> None:
419
+ """Test that a list of colors sets color_discrete_sequence."""
420
+ fig = self.da.plotly.line(colors=["red", "blue", "green"])
421
+ # Check that traces have the expected colors
422
+ assert len(fig.data) == 3
423
+ assert fig.data[0].line.color == "red"
424
+ assert fig.data[1].line.color == "blue"
425
+ assert fig.data[2].line.color == "green"
426
+
427
+ def test_colors_dict_sets_discrete_map(self) -> None:
428
+ """Test that a dict sets color_discrete_map."""
429
+ fig = self.da.plotly.line(colors={"A": "red", "B": "blue", "C": "green"})
430
+ # Traces should be colored according to the mapping
431
+ assert len(fig.data) == 3
432
+ # Find traces by name and check their color
433
+ colors_by_name = {trace.name: trace.line.color for trace in fig.data}
434
+ assert colors_by_name["A"] == "red"
435
+ assert colors_by_name["B"] == "blue"
436
+ assert colors_by_name["C"] == "green"
437
+
438
+ def test_colors_continuous_scale_string(self) -> None:
439
+ """Test that a continuous scale name sets color_continuous_scale."""
440
+ da = xr.DataArray(
441
+ np.random.rand(50, 2),
442
+ dims=["point", "coord"],
443
+ coords={"coord": ["x", "y"]},
444
+ )
445
+ fig = da.plotly.scatter(y="coord", x="point", color="value", colors="Viridis")
446
+ # Plotly Express uses coloraxis in the layout for continuous scales
447
+ # Check that the colorscale was applied to the coloraxis
448
+ assert fig.layout.coloraxis.colorscale is not None
449
+ colorscale = fig.layout.coloraxis.colorscale
450
+ # Viridis should be in the colorscale definition
451
+ assert any("viridis" in str(c).lower() for c in colorscale) or len(colorscale) > 0
452
+
453
+ def test_colors_qualitative_palette_string(self) -> None:
454
+ """Test that a qualitative palette name sets color_discrete_sequence."""
455
+ import plotly.express as px
456
+
457
+ fig = self.da.plotly.line(colors="D3")
458
+ # D3 palette should be applied - check first trace color is from D3
459
+ d3_colors = px.colors.qualitative.D3
460
+ assert fig.data[0].line.color in d3_colors
461
+
462
+ def test_colors_ignored_with_warning_when_px_kwargs_present(self) -> None:
463
+ """Test that colors is ignored with warning when color_* kwargs are present."""
464
+ import warnings
465
+
466
+ with warnings.catch_warnings(record=True) as w:
467
+ warnings.simplefilter("always")
468
+ fig = self.da.plotly.line(
469
+ colors="D3", color_discrete_sequence=["orange", "purple", "cyan"]
470
+ )
471
+ # Should have raised a warning about colors being ignored
472
+ assert any(
473
+ "colors" in str(m.message).lower() and "ignored" in str(m.message).lower()
474
+ for m in w
475
+ ), "Expected warning about 'colors' being 'ignored' not found"
476
+ # The explicit px_kwargs should take precedence
477
+ assert fig.data[0].line.color == "orange"
478
+
479
+ def test_colors_none_uses_defaults(self) -> None:
480
+ """Test that colors=None uses Plotly defaults."""
481
+ fig1 = self.da.plotly.line(colors=None)
482
+ fig2 = self.da.plotly.line()
483
+ # Both should produce the same result
484
+ assert fig1.data[0].line.color == fig2.data[0].line.color
485
+
486
+ def test_colors_works_with_bar(self) -> None:
487
+ """Test colors parameter with bar chart."""
488
+ fig = self.da.plotly.bar(colors=["#e41a1c", "#377eb8", "#4daf4a"])
489
+ assert fig.data[0].marker.color == "#e41a1c"
490
+
491
+ def test_colors_works_with_area(self) -> None:
492
+ """Test colors parameter with area chart."""
493
+ fig = self.da.plotly.area(colors=["red", "green", "blue"])
494
+ assert len(fig.data) == 3
495
+
496
+ def test_colors_works_with_scatter(self) -> None:
497
+ """Test colors parameter with scatter plot."""
498
+ fig = self.da.plotly.scatter(colors=["red", "green", "blue"])
499
+ assert len(fig.data) == 3
500
+
501
+ def test_colors_works_with_imshow(self) -> None:
502
+ """Test colors parameter with imshow (continuous scale)."""
503
+ da = xr.DataArray(np.random.rand(10, 10), dims=["y", "x"])
504
+ fig = da.plotly.imshow(colors="RdBu")
505
+ # Plotly Express uses coloraxis in the layout for continuous scales
506
+ assert fig.layout.coloraxis.colorscale is not None
507
+ colorscale = fig.layout.coloraxis.colorscale
508
+ # RdBu should be in the colorscale definition
509
+ assert any("rdbu" in str(c).lower() for c in colorscale) or len(colorscale) > 0
510
+
511
+ def test_colors_works_with_pie(self) -> None:
512
+ """Test colors parameter with pie chart."""
513
+ da = xr.DataArray([30, 40, 30], dims=["category"], coords={"category": ["A", "B", "C"]})
514
+ fig = da.plotly.pie(colors={"A": "red", "B": "blue", "C": "green"})
515
+ assert isinstance(fig, go.Figure)
516
+
517
+ def test_colors_works_with_dataset(self) -> None:
518
+ """Test colors parameter works with Dataset accessor."""
519
+ ds = xr.Dataset(
520
+ {
521
+ "temp": (["time"], np.random.rand(10)),
522
+ "precip": (["time"], np.random.rand(10)),
523
+ }
524
+ )
525
+ fig = ds.plotly.line(colors=["red", "blue"])
526
+ assert len(fig.data) == 2
527
+ assert fig.data[0].line.color == "red"
528
+ assert fig.data[1].line.color == "blue"
@@ -505,8 +505,8 @@ class TestAddSecondaryYFacets:
505
505
 
506
506
  combined = add_secondary_y(base, secondary, secondary_y_title="Custom Title")
507
507
 
508
- # Title should be on the first secondary axis
509
- assert combined.layout.yaxis4.title.text == "Custom Title"
508
+ # Title should be on the rightmost secondary axis (yaxis6 for 3 facets)
509
+ assert combined.layout.yaxis6.title.text == "Custom Title"
510
510
 
511
511
 
512
512
  class TestAddSecondaryYAnimation:
@@ -52,7 +52,7 @@ from xarray import DataArray, Dataset, register_dataarray_accessor, register_dat
52
52
 
53
53
  from xarray_plotly import config
54
54
  from xarray_plotly.accessor import DataArrayPlotlyAccessor, DatasetPlotlyAccessor
55
- from xarray_plotly.common import SLOT_ORDERS, auto
55
+ from xarray_plotly.common import SLOT_ORDERS, Colors, auto
56
56
  from xarray_plotly.figures import (
57
57
  add_secondary_y,
58
58
  overlay,
@@ -61,6 +61,7 @@ from xarray_plotly.figures import (
61
61
 
62
62
  __all__ = [
63
63
  "SLOT_ORDERS",
64
+ "Colors",
64
65
  "add_secondary_y",
65
66
  "auto",
66
67
  "config",
@@ -110,5 +111,5 @@ def xpx(data: DataArray | Dataset) -> DataArrayPlotlyAccessor | DatasetPlotlyAcc
110
111
  __version__ = version("xarray_plotly")
111
112
 
112
113
  # Register the accessors
113
- register_dataarray_accessor("plotly")(DataArrayPlotlyAccessor)
114
- register_dataset_accessor("plotly")(DatasetPlotlyAccessor)
114
+ register_dataarray_accessor("plotly")(DataArrayPlotlyAccessor) # type: ignore[no-untyped-call]
115
+ register_dataset_accessor("plotly")(DatasetPlotlyAccessor) # type: ignore[no-untyped-call]