xarray-plotly 0.0.16__tar.gz → 0.0.17__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 (50) hide show
  1. xarray_plotly-0.0.17/.release-please-manifest.json +3 -0
  2. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/CHANGELOG.md +7 -0
  3. {xarray_plotly-0.0.16/xarray_plotly.egg-info → xarray_plotly-0.0.17}/PKG-INFO +1 -1
  4. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/tests/test_accessor.py +4 -4
  5. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/tests/test_figures.py +157 -0
  6. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly/__init__.py +2 -0
  7. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly/accessor.py +60 -0
  8. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly/figures.py +77 -0
  9. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly/plotting.py +49 -8
  10. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17/xarray_plotly.egg-info}/PKG-INFO +1 -1
  11. xarray_plotly-0.0.16/.release-please-manifest.json +0 -3
  12. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.github/dependabot.yml +0 -0
  13. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.github/workflows/ci.yml +0 -0
  14. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.github/workflows/dependabot-auto-merge.yml +0 -0
  15. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.github/workflows/docs.yml +0 -0
  16. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.github/workflows/latest-deps.yml +0 -0
  17. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.github/workflows/pr-title.yml +0 -0
  18. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.github/workflows/publish.yaml +0 -0
  19. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.github/workflows/release.yaml +0 -0
  20. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.gitignore +0 -0
  21. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.pre-commit-config.yaml +0 -0
  22. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.readthedocs.yaml +0 -0
  23. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/.release-please-config.json +0 -0
  24. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/CONTRIBUTING.md +0 -0
  25. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/LICENSE +0 -0
  26. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/README.md +0 -0
  27. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/api.md +0 -0
  28. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/examples/combining.ipynb +0 -0
  29. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/examples/datasets.ipynb +0 -0
  30. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/examples/dimensions.ipynb +0 -0
  31. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/examples/fast_bar.ipynb +0 -0
  32. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/examples/figure.ipynb +0 -0
  33. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/examples/kwargs.ipynb +0 -0
  34. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/examples/manipulation.ipynb +0 -0
  35. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/examples/plot-types.ipynb +0 -0
  36. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/getting-started.ipynb +0 -0
  37. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/docs/index.md +0 -0
  38. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/mkdocs.yml +0 -0
  39. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/pyproject.toml +0 -0
  40. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/setup.cfg +0 -0
  41. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/tests/__init__.py +0 -0
  42. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/tests/test_common.py +0 -0
  43. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/tests/test_config.py +0 -0
  44. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly/common.py +0 -0
  45. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly/config.py +0 -0
  46. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly/py.typed +0 -0
  47. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly.egg-info/SOURCES.txt +0 -0
  48. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly.egg-info/dependency_links.txt +0 -0
  49. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly.egg-info/requires.txt +0 -0
  50. {xarray_plotly-0.0.16 → xarray_plotly-0.0.17}/xarray_plotly.egg-info/top_level.txt +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.0.17"
3
+ }
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.17](https://github.com/FBumann/xarray_plotly/compare/v0.0.16...v0.0.17) (2026-06-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * **plotting:** shared axis labels for faceted plots ([#84](https://github.com/FBumann/xarray_plotly/issues/84)) ([5a3bef8](https://github.com/FBumann/xarray_plotly/commit/5a3bef863ce07dd1a1f69d79ece116c059782025))
9
+
3
10
  ## [0.0.16](https://github.com/FBumann/xarray_plotly/compare/v0.0.15...v0.0.16) (2026-06-03)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray_plotly
3
- Version: 0.0.16
3
+ Version: 0.0.17
4
4
  Summary: Interactive Plotly Express plotting accessor for xarray
5
5
  Author: Felix
6
6
  License: MIT
@@ -443,8 +443,8 @@ class TestImshowFaceting:
443
443
  assert len(fig.data) == 3
444
444
  yaxes = [k for k in fig.layout if k.startswith("yaxis")]
445
445
  assert len(yaxes) == 3
446
- annotations = {a.text for a in fig.layout.annotations}
447
- assert annotations == {"scenario=a", "scenario=b", "scenario=c"}
446
+ facet_titles = {a.text for a in fig.layout.annotations if "=" in (a.text or "")}
447
+ assert facet_titles == {"scenario=a", "scenario=b", "scenario=c"}
448
448
 
449
449
  @requires_imshow_facet_row
450
450
  def test_imshow_facet_row_auto_4d(self) -> None:
@@ -452,8 +452,8 @@ class TestImshowFaceting:
452
452
  fig = self.da_4d.plotly.imshow()
453
453
  # 2 facet columns (scenario) x 3 facet rows (year)
454
454
  assert len(fig.data) == 6
455
- annotations = {a.text for a in fig.layout.annotations}
456
- assert annotations == {
455
+ facet_titles = {a.text for a in fig.layout.annotations if "=" in (a.text or "")}
456
+ assert facet_titles == {
457
457
  "scenario=low",
458
458
  "scenario=high",
459
459
  "year=2020",
@@ -12,6 +12,7 @@ import xarray as xr
12
12
  from xarray_plotly import (
13
13
  add_secondary_y,
14
14
  overlay,
15
+ share_axis_labels,
15
16
  simplify_facet_titles,
16
17
  subplots,
17
18
  xpx,
@@ -989,3 +990,159 @@ class TestSimplifyFacetTitles:
989
990
  def test_kwarg_invalid_raises(self) -> None:
990
991
  with pytest.raises(ValueError, match="facet_titles must be"):
991
992
  xpx(self.da).line(facet_col="country", facet_titles="bogus") # type: ignore[arg-type]
993
+
994
+
995
+ def _axis_titles(fig: go.Figure, letter: str) -> list[str]:
996
+ """Collect non-empty axis title texts for one axis direction."""
997
+ return [
998
+ fig.layout[k].title.text
999
+ for k in fig.layout
1000
+ if k.startswith(f"{letter}axis") and fig.layout[k].title.text
1001
+ ]
1002
+
1003
+
1004
+ def _shared_label_annotations(fig: go.Figure) -> list[go.layout.Annotation]:
1005
+ """Collect annotations positioned like shared axis labels (centered bottom/left)."""
1006
+ return [
1007
+ a
1008
+ for a in fig.layout.annotations
1009
+ if a.xref == "paper" and a.yref == "paper" and (a.x, a.y) in {(0.5, 0), (0, 0.5)}
1010
+ ]
1011
+
1012
+
1013
+ class TestShareAxisLabels:
1014
+ """Tests for the share_axis_labels helper and the `shared_axis_labels` kwarg."""
1015
+
1016
+ @pytest.fixture(autouse=True)
1017
+ def setup(self) -> None:
1018
+ self.da = xr.DataArray(
1019
+ np.random.rand(10, 2, 3),
1020
+ dims=["time", "scenario", "country"],
1021
+ coords={"scenario": ["low", "high"], "country": ["US", "CN", "BR"]},
1022
+ name="value",
1023
+ )
1024
+
1025
+ def test_matches_plotly_builtin_spec(self) -> None:
1026
+ """Shared labels must be identical to make_subplots(x_title=, y_title=) output."""
1027
+ import json
1028
+
1029
+ from plotly.subplots import make_subplots
1030
+
1031
+ def canonical(annotations: list[go.layout.Annotation]) -> set[str]:
1032
+ return {json.dumps(a.to_plotly_json(), sort_keys=True) for a in annotations}
1033
+
1034
+ reference = make_subplots(rows=2, cols=2, x_title="value", y_title="value")
1035
+ ref_specs = canonical(list(reference.layout.annotations))
1036
+
1037
+ fig = xpx(self.da).line(color=None, facet_col="scenario", facet_row="country")
1038
+ # Rename both labels to "value" so the comparison covers all other fields
1039
+ for ann in _shared_label_annotations(fig):
1040
+ ann.text = "value"
1041
+ assert canonical(_shared_label_annotations(fig)) == ref_specs
1042
+
1043
+ def test_helper_facet_col_shares_x_label(self) -> None:
1044
+ fig = xpx(self.da.isel(scenario=0, drop=True)).line(
1045
+ color=None, facet_col="country", shared_axis_labels=False
1046
+ )
1047
+ assert len(_axis_titles(fig, "x")) == 3 # repeated per column
1048
+
1049
+ share_axis_labels(fig)
1050
+
1051
+ assert _axis_titles(fig, "x") == []
1052
+ labels = _shared_label_annotations(fig)
1053
+ assert [a.text for a in labels] == ["time"]
1054
+ # y title was never repeated, so it stays a regular axis title
1055
+ assert _axis_titles(fig, "y") == ["value"]
1056
+
1057
+ def test_helper_facet_row_shares_y_label(self) -> None:
1058
+ fig = xpx(self.da.isel(scenario=0, drop=True)).line(
1059
+ color=None, facet_row="country", shared_axis_labels=False
1060
+ )
1061
+ assert len(_axis_titles(fig, "y")) == 3 # repeated per row
1062
+
1063
+ share_axis_labels(fig)
1064
+
1065
+ assert _axis_titles(fig, "y") == []
1066
+ labels = _shared_label_annotations(fig)
1067
+ assert [a.text for a in labels] == ["value"]
1068
+ assert _axis_titles(fig, "x") == ["time"]
1069
+
1070
+ def test_helper_facet_grid_shares_both(self) -> None:
1071
+ fig = xpx(self.da).line(
1072
+ color=None, facet_col="scenario", facet_row="country", shared_axis_labels=False
1073
+ )
1074
+ share_axis_labels(fig)
1075
+
1076
+ assert _axis_titles(fig, "x") == []
1077
+ assert _axis_titles(fig, "y") == []
1078
+ labels = {a.text for a in _shared_label_annotations(fig)}
1079
+ assert labels == {"time", "value"}
1080
+
1081
+ def test_helper_no_facets_is_noop(self) -> None:
1082
+ fig = xpx(self.da.isel(scenario=0, country=0, drop=True)).line()
1083
+ before = fig.to_plotly_json()
1084
+ share_axis_labels(fig)
1085
+ assert fig.to_plotly_json() == before
1086
+
1087
+ def test_helper_idempotent(self) -> None:
1088
+ fig = xpx(self.da).line(color=None, facet_col="scenario", facet_row="country")
1089
+ share_axis_labels(fig)
1090
+ n_annotations = len(fig.layout.annotations)
1091
+ share_axis_labels(fig)
1092
+ assert len(fig.layout.annotations) == n_annotations
1093
+
1094
+ def test_helper_preserves_facet_titles(self) -> None:
1095
+ fig = xpx(self.da).line(color=None, facet_col="scenario", facet_row="country")
1096
+ share_axis_labels(fig)
1097
+ texts = [a.text for a in fig.layout.annotations]
1098
+ assert "scenario=low" in texts
1099
+ assert "scenario=high" in texts
1100
+
1101
+ def test_helper_different_titles_untouched(self) -> None:
1102
+ """Figures whose subplots have different axis titles are not collapsed."""
1103
+ da1 = xr.DataArray(np.random.rand(5), dims=["time"], name="temperature")
1104
+ da2 = xr.DataArray(np.random.rand(5), dims=["distance"], name="pressure")
1105
+ fig = subplots(xpx(da1).line(), xpx(da2).line(), cols=2)
1106
+ before_x = _axis_titles(fig, "x")
1107
+ before_y = _axis_titles(fig, "y")
1108
+
1109
+ share_axis_labels(fig)
1110
+
1111
+ assert _axis_titles(fig, "x") == before_x
1112
+ assert _axis_titles(fig, "y") == before_y
1113
+ assert _shared_label_annotations(fig) == []
1114
+
1115
+ def test_helper_secondary_y_untouched(self) -> None:
1116
+ """Identical titles on a primary and overlaying secondary y axis stay separate."""
1117
+ da = xr.DataArray(np.random.rand(5), dims=["time"], name="value")
1118
+ base = xpx(da).line()
1119
+ secondary = xpx(da).line()
1120
+ fig = add_secondary_y(base, secondary)
1121
+ before_y = _axis_titles(fig, "y")
1122
+
1123
+ share_axis_labels(fig)
1124
+
1125
+ assert _axis_titles(fig, "y") == before_y
1126
+
1127
+ def test_kwarg_shares_by_default(self) -> None:
1128
+ fig = xpx(self.da).line(color=None, facet_col="scenario", facet_row="country")
1129
+ assert _axis_titles(fig, "x") == []
1130
+ assert _axis_titles(fig, "y") == []
1131
+ assert {a.text for a in _shared_label_annotations(fig)} == {"time", "value"}
1132
+
1133
+ def test_kwarg_opt_out(self) -> None:
1134
+ fig = xpx(self.da).line(
1135
+ color=None, facet_col="scenario", facet_row="country", shared_axis_labels=False
1136
+ )
1137
+ assert len(_axis_titles(fig, "x")) > 1
1138
+ assert len(_axis_titles(fig, "y")) > 1
1139
+ assert _shared_label_annotations(fig) == []
1140
+
1141
+ def test_kwarg_on_imshow(self) -> None:
1142
+ da = xr.DataArray(
1143
+ np.random.rand(4, 5, 2, 3),
1144
+ dims=["lat", "lon", "scenario", "year"],
1145
+ name="temperature",
1146
+ )
1147
+ fig = da.plotly.imshow()
1148
+ assert {a.text for a in _shared_label_annotations(fig)} == {"lat", "lon"}
@@ -56,6 +56,7 @@ from xarray_plotly.common import SLOT_ORDERS, Colors, auto
56
56
  from xarray_plotly.figures import (
57
57
  add_secondary_y,
58
58
  overlay,
59
+ share_axis_labels,
59
60
  simplify_facet_titles,
60
61
  subplots,
61
62
  update_traces,
@@ -68,6 +69,7 @@ __all__ = [
68
69
  "auto",
69
70
  "config",
70
71
  "overlay",
72
+ "share_axis_labels",
71
73
  "simplify_facet_titles",
72
74
  "subplots",
73
75
  "update_traces",
@@ -55,6 +55,7 @@ class DataArrayPlotlyAccessor:
55
55
  animation_frame: SlotValue = auto,
56
56
  colors: Colors = None,
57
57
  facet_titles: FacetTitlesMode = "default",
58
+ shared_axis_labels: bool = True,
58
59
  **px_kwargs: Any,
59
60
  ) -> go.Figure:
60
61
  """Create an interactive line plot.
@@ -70,6 +71,8 @@ class DataArrayPlotlyAccessor:
70
71
  facet_row: Dimension for subplot rows. Default: sixth dimension.
71
72
  animation_frame: Dimension for animation. Default: seventh dimension.
72
73
  colors: Color specification (scale name, list, or dict). See module docs.
74
+ shared_axis_labels: If True (default), repeated axis titles on faceted
75
+ plots are replaced with a single shared, centered label per axis.
73
76
  **px_kwargs: Additional arguments passed to `plotly.express.line()`.
74
77
 
75
78
  Returns:
@@ -86,6 +89,7 @@ class DataArrayPlotlyAccessor:
86
89
  animation_frame=animation_frame,
87
90
  colors=colors,
88
91
  facet_titles=facet_titles,
92
+ shared_axis_labels=shared_axis_labels,
89
93
  **px_kwargs,
90
94
  )
91
95
 
@@ -100,6 +104,7 @@ class DataArrayPlotlyAccessor:
100
104
  animation_frame: SlotValue = auto,
101
105
  colors: Colors = None,
102
106
  facet_titles: FacetTitlesMode = "default",
107
+ shared_axis_labels: bool = True,
103
108
  **px_kwargs: Any,
104
109
  ) -> go.Figure:
105
110
  """Create an interactive bar chart.
@@ -114,6 +119,8 @@ class DataArrayPlotlyAccessor:
114
119
  facet_row: Dimension for subplot rows. Default: fifth dimension.
115
120
  animation_frame: Dimension for animation. Default: sixth dimension.
116
121
  colors: Color specification (scale name, list, or dict). See module docs.
122
+ shared_axis_labels: If True (default), repeated axis titles on faceted
123
+ plots are replaced with a single shared, centered label per axis.
117
124
  **px_kwargs: Additional arguments passed to `plotly.express.bar()`.
118
125
 
119
126
  Returns:
@@ -129,6 +136,7 @@ class DataArrayPlotlyAccessor:
129
136
  animation_frame=animation_frame,
130
137
  colors=colors,
131
138
  facet_titles=facet_titles,
139
+ shared_axis_labels=shared_axis_labels,
132
140
  **px_kwargs,
133
141
  )
134
142
 
@@ -143,6 +151,7 @@ class DataArrayPlotlyAccessor:
143
151
  animation_frame: SlotValue = auto,
144
152
  colors: Colors = None,
145
153
  facet_titles: FacetTitlesMode = "default",
154
+ shared_axis_labels: bool = True,
146
155
  **px_kwargs: Any,
147
156
  ) -> go.Figure:
148
157
  """Create an interactive stacked area chart.
@@ -157,6 +166,8 @@ class DataArrayPlotlyAccessor:
157
166
  facet_row: Dimension for subplot rows. Default: fifth dimension.
158
167
  animation_frame: Dimension for animation. Default: sixth dimension.
159
168
  colors: Color specification (scale name, list, or dict). See module docs.
169
+ shared_axis_labels: If True (default), repeated axis titles on faceted
170
+ plots are replaced with a single shared, centered label per axis.
160
171
  **px_kwargs: Additional arguments passed to `plotly.express.area()`.
161
172
 
162
173
  Returns:
@@ -172,6 +183,7 @@ class DataArrayPlotlyAccessor:
172
183
  animation_frame=animation_frame,
173
184
  colors=colors,
174
185
  facet_titles=facet_titles,
186
+ shared_axis_labels=shared_axis_labels,
175
187
  **px_kwargs,
176
188
  )
177
189
 
@@ -185,6 +197,7 @@ class DataArrayPlotlyAccessor:
185
197
  animation_frame: SlotValue = auto,
186
198
  colors: Colors = None,
187
199
  facet_titles: FacetTitlesMode = "default",
200
+ shared_axis_labels: bool = True,
188
201
  **px_kwargs: Any,
189
202
  ) -> go.Figure:
190
203
  """Create a bar-like chart using stacked areas for better performance.
@@ -198,6 +211,8 @@ class DataArrayPlotlyAccessor:
198
211
  facet_row: Dimension for subplot rows. Default: fourth dimension.
199
212
  animation_frame: Dimension for animation. Default: fifth dimension.
200
213
  colors: Color specification (scale name, list, or dict). See module docs.
214
+ shared_axis_labels: If True (default), repeated axis titles on faceted
215
+ plots are replaced with a single shared, centered label per axis.
201
216
  **px_kwargs: Additional arguments passed to `plotly.express.area()`.
202
217
 
203
218
  Returns:
@@ -212,6 +227,7 @@ class DataArrayPlotlyAccessor:
212
227
  animation_frame=animation_frame,
213
228
  colors=colors,
214
229
  facet_titles=facet_titles,
230
+ shared_axis_labels=shared_axis_labels,
215
231
  **px_kwargs,
216
232
  )
217
233
 
@@ -227,6 +243,7 @@ class DataArrayPlotlyAccessor:
227
243
  animation_frame: SlotValue = auto,
228
244
  colors: Colors = None,
229
245
  facet_titles: FacetTitlesMode = "default",
246
+ shared_axis_labels: bool = True,
230
247
  **px_kwargs: Any,
231
248
  ) -> go.Figure:
232
249
  """Create an interactive scatter plot.
@@ -246,6 +263,8 @@ class DataArrayPlotlyAccessor:
246
263
  facet_row: Dimension for subplot rows. Default: fifth dimension.
247
264
  animation_frame: Dimension for animation. Default: sixth dimension.
248
265
  colors: Color specification (scale name, list, or dict). See module docs.
266
+ shared_axis_labels: If True (default), repeated axis titles on faceted
267
+ plots are replaced with a single shared, centered label per axis.
249
268
  **px_kwargs: Additional arguments passed to `plotly.express.scatter()`.
250
269
 
251
270
  Returns:
@@ -262,6 +281,7 @@ class DataArrayPlotlyAccessor:
262
281
  animation_frame=animation_frame,
263
282
  colors=colors,
264
283
  facet_titles=facet_titles,
284
+ shared_axis_labels=shared_axis_labels,
265
285
  **px_kwargs,
266
286
  )
267
287
 
@@ -275,6 +295,7 @@ class DataArrayPlotlyAccessor:
275
295
  animation_frame: SlotValue = None,
276
296
  colors: Colors = None,
277
297
  facet_titles: FacetTitlesMode = "default",
298
+ shared_axis_labels: bool = True,
278
299
  **px_kwargs: Any,
279
300
  ) -> go.Figure:
280
301
  """Create an interactive box plot.
@@ -291,6 +312,8 @@ class DataArrayPlotlyAccessor:
291
312
  facet_row: Dimension for subplot rows. Default: None (aggregated).
292
313
  animation_frame: Dimension for animation. Default: None (aggregated).
293
314
  colors: Color specification (scale name, list, or dict). See module docs.
315
+ shared_axis_labels: If True (default), repeated axis titles on faceted
316
+ plots are replaced with a single shared, centered label per axis.
294
317
  **px_kwargs: Additional arguments passed to `plotly.express.box()`.
295
318
 
296
319
  Returns:
@@ -305,6 +328,7 @@ class DataArrayPlotlyAccessor:
305
328
  animation_frame=animation_frame,
306
329
  colors=colors,
307
330
  facet_titles=facet_titles,
331
+ shared_axis_labels=shared_axis_labels,
308
332
  **px_kwargs,
309
333
  )
310
334
 
@@ -319,6 +343,7 @@ class DataArrayPlotlyAccessor:
319
343
  robust: bool = False,
320
344
  colors: Colors = None,
321
345
  facet_titles: FacetTitlesMode = "default",
346
+ shared_axis_labels: bool = True,
322
347
  **px_kwargs: Any,
323
348
  ) -> go.Figure:
324
349
  """Create an interactive heatmap image.
@@ -340,6 +365,8 @@ class DataArrayPlotlyAccessor:
340
365
  animation_frame: Dimension for animation. Default: fifth dimension.
341
366
  robust: If True, use 2nd/98th percentiles for color bounds (handles outliers).
342
367
  colors: Color scale name (e.g., "Viridis", "RdBu"). See module docs.
368
+ shared_axis_labels: If True (default), repeated axis titles on faceted
369
+ plots are replaced with a single shared, centered label per axis.
343
370
  **px_kwargs: Additional arguments passed to `plotly.express.imshow()`.
344
371
  Use `zmin` and `zmax` to manually set color scale bounds.
345
372
 
@@ -356,6 +383,7 @@ class DataArrayPlotlyAccessor:
356
383
  robust=robust,
357
384
  colors=colors,
358
385
  facet_titles=facet_titles,
386
+ shared_axis_labels=shared_axis_labels,
359
387
  **px_kwargs,
360
388
  )
361
389
 
@@ -368,6 +396,7 @@ class DataArrayPlotlyAccessor:
368
396
  facet_row: SlotValue = auto,
369
397
  colors: Colors = None,
370
398
  facet_titles: FacetTitlesMode = "default",
399
+ shared_axis_labels: bool = True,
371
400
  **px_kwargs: Any,
372
401
  ) -> go.Figure:
373
402
  """Create an interactive pie chart.
@@ -380,6 +409,8 @@ class DataArrayPlotlyAccessor:
380
409
  facet_col: Dimension for subplot columns. Default: second dimension.
381
410
  facet_row: Dimension for subplot rows. Default: third dimension.
382
411
  colors: Color specification (scale name, list, or dict). See module docs.
412
+ shared_axis_labels: If True (default), repeated axis titles on faceted
413
+ plots are replaced with a single shared, centered label per axis.
383
414
  **px_kwargs: Additional arguments passed to `plotly.express.pie()`.
384
415
 
385
416
  Returns:
@@ -393,6 +424,7 @@ class DataArrayPlotlyAccessor:
393
424
  facet_row=facet_row,
394
425
  colors=colors,
395
426
  facet_titles=facet_titles,
427
+ shared_axis_labels=shared_axis_labels,
396
428
  **px_kwargs,
397
429
  )
398
430
 
@@ -474,6 +506,7 @@ class DatasetPlotlyAccessor:
474
506
  animation_frame: SlotValue = auto,
475
507
  colors: Colors = None,
476
508
  facet_titles: FacetTitlesMode = "default",
509
+ shared_axis_labels: bool = True,
477
510
  **px_kwargs: Any,
478
511
  ) -> go.Figure:
479
512
  """Create an interactive line plot.
@@ -488,6 +521,8 @@ class DatasetPlotlyAccessor:
488
521
  facet_row: Dimension for subplot rows.
489
522
  animation_frame: Dimension for animation.
490
523
  colors: Color specification (scale name, list, or dict). See module docs.
524
+ shared_axis_labels: If True (default), repeated axis titles on faceted
525
+ plots are replaced with a single shared, centered label per axis.
491
526
  **px_kwargs: Additional arguments passed to `plotly.express.line()`.
492
527
 
493
528
  Returns:
@@ -505,6 +540,7 @@ class DatasetPlotlyAccessor:
505
540
  animation_frame=animation_frame,
506
541
  colors=colors,
507
542
  facet_titles=facet_titles,
543
+ shared_axis_labels=shared_axis_labels,
508
544
  **px_kwargs,
509
545
  )
510
546
 
@@ -520,6 +556,7 @@ class DatasetPlotlyAccessor:
520
556
  animation_frame: SlotValue = auto,
521
557
  colors: Colors = None,
522
558
  facet_titles: FacetTitlesMode = "default",
559
+ shared_axis_labels: bool = True,
523
560
  **px_kwargs: Any,
524
561
  ) -> go.Figure:
525
562
  """Create an interactive bar chart.
@@ -533,6 +570,8 @@ class DatasetPlotlyAccessor:
533
570
  facet_row: Dimension for subplot rows.
534
571
  animation_frame: Dimension for animation.
535
572
  colors: Color specification (scale name, list, or dict). See module docs.
573
+ shared_axis_labels: If True (default), repeated axis titles on faceted
574
+ plots are replaced with a single shared, centered label per axis.
536
575
  **px_kwargs: Additional arguments passed to `plotly.express.bar()`.
537
576
 
538
577
  Returns:
@@ -549,6 +588,7 @@ class DatasetPlotlyAccessor:
549
588
  animation_frame=animation_frame,
550
589
  colors=colors,
551
590
  facet_titles=facet_titles,
591
+ shared_axis_labels=shared_axis_labels,
552
592
  **px_kwargs,
553
593
  )
554
594
 
@@ -564,6 +604,7 @@ class DatasetPlotlyAccessor:
564
604
  animation_frame: SlotValue = auto,
565
605
  colors: Colors = None,
566
606
  facet_titles: FacetTitlesMode = "default",
607
+ shared_axis_labels: bool = True,
567
608
  **px_kwargs: Any,
568
609
  ) -> go.Figure:
569
610
  """Create an interactive stacked area chart.
@@ -577,6 +618,8 @@ class DatasetPlotlyAccessor:
577
618
  facet_row: Dimension for subplot rows.
578
619
  animation_frame: Dimension for animation.
579
620
  colors: Color specification (scale name, list, or dict). See module docs.
621
+ shared_axis_labels: If True (default), repeated axis titles on faceted
622
+ plots are replaced with a single shared, centered label per axis.
580
623
  **px_kwargs: Additional arguments passed to `plotly.express.area()`.
581
624
 
582
625
  Returns:
@@ -593,6 +636,7 @@ class DatasetPlotlyAccessor:
593
636
  animation_frame=animation_frame,
594
637
  colors=colors,
595
638
  facet_titles=facet_titles,
639
+ shared_axis_labels=shared_axis_labels,
596
640
  **px_kwargs,
597
641
  )
598
642
 
@@ -607,6 +651,7 @@ class DatasetPlotlyAccessor:
607
651
  animation_frame: SlotValue = auto,
608
652
  colors: Colors = None,
609
653
  facet_titles: FacetTitlesMode = "default",
654
+ shared_axis_labels: bool = True,
610
655
  **px_kwargs: Any,
611
656
  ) -> go.Figure:
612
657
  """Create a bar-like chart using stacked areas for better performance.
@@ -619,6 +664,8 @@ class DatasetPlotlyAccessor:
619
664
  facet_row: Dimension for subplot rows.
620
665
  animation_frame: Dimension for animation.
621
666
  colors: Color specification (scale name, list, or dict). See module docs.
667
+ shared_axis_labels: If True (default), repeated axis titles on faceted
668
+ plots are replaced with a single shared, centered label per axis.
622
669
  **px_kwargs: Additional arguments passed to `plotly.express.area()`.
623
670
 
624
671
  Returns:
@@ -634,6 +681,7 @@ class DatasetPlotlyAccessor:
634
681
  animation_frame=animation_frame,
635
682
  colors=colors,
636
683
  facet_titles=facet_titles,
684
+ shared_axis_labels=shared_axis_labels,
637
685
  **px_kwargs,
638
686
  )
639
687
 
@@ -650,6 +698,7 @@ class DatasetPlotlyAccessor:
650
698
  animation_frame: SlotValue = auto,
651
699
  colors: Colors = None,
652
700
  facet_titles: FacetTitlesMode = "default",
701
+ shared_axis_labels: bool = True,
653
702
  **px_kwargs: Any,
654
703
  ) -> go.Figure:
655
704
  """Create an interactive scatter plot.
@@ -664,6 +713,8 @@ class DatasetPlotlyAccessor:
664
713
  facet_row: Dimension for subplot rows.
665
714
  animation_frame: Dimension for animation.
666
715
  colors: Color specification (scale name, list, or dict). See module docs.
716
+ shared_axis_labels: If True (default), repeated axis titles on faceted
717
+ plots are replaced with a single shared, centered label per axis.
667
718
  **px_kwargs: Additional arguments passed to `plotly.express.scatter()`.
668
719
 
669
720
  Returns:
@@ -681,6 +732,7 @@ class DatasetPlotlyAccessor:
681
732
  animation_frame=animation_frame,
682
733
  colors=colors,
683
734
  facet_titles=facet_titles,
735
+ shared_axis_labels=shared_axis_labels,
684
736
  **px_kwargs,
685
737
  )
686
738
 
@@ -695,6 +747,7 @@ class DatasetPlotlyAccessor:
695
747
  animation_frame: SlotValue = None,
696
748
  colors: Colors = None,
697
749
  facet_titles: FacetTitlesMode = "default",
750
+ shared_axis_labels: bool = True,
698
751
  **px_kwargs: Any,
699
752
  ) -> go.Figure:
700
753
  """Create an interactive box plot.
@@ -707,6 +760,8 @@ class DatasetPlotlyAccessor:
707
760
  facet_row: Dimension for subplot rows.
708
761
  animation_frame: Dimension for animation.
709
762
  colors: Color specification (scale name, list, or dict). See module docs.
763
+ shared_axis_labels: If True (default), repeated axis titles on faceted
764
+ plots are replaced with a single shared, centered label per axis.
710
765
  **px_kwargs: Additional arguments passed to `plotly.express.box()`.
711
766
 
712
767
  Returns:
@@ -722,6 +777,7 @@ class DatasetPlotlyAccessor:
722
777
  animation_frame=animation_frame,
723
778
  colors=colors,
724
779
  facet_titles=facet_titles,
780
+ shared_axis_labels=shared_axis_labels,
725
781
  **px_kwargs,
726
782
  )
727
783
 
@@ -735,6 +791,7 @@ class DatasetPlotlyAccessor:
735
791
  facet_row: SlotValue = auto,
736
792
  colors: Colors = None,
737
793
  facet_titles: FacetTitlesMode = "default",
794
+ shared_axis_labels: bool = True,
738
795
  **px_kwargs: Any,
739
796
  ) -> go.Figure:
740
797
  """Create an interactive pie chart.
@@ -746,6 +803,8 @@ class DatasetPlotlyAccessor:
746
803
  facet_col: Dimension for subplot columns.
747
804
  facet_row: Dimension for subplot rows.
748
805
  colors: Color specification (scale name, list, or dict). See module docs.
806
+ shared_axis_labels: If True (default), repeated axis titles on faceted
807
+ plots are replaced with a single shared, centered label per axis.
749
808
  **px_kwargs: Additional arguments passed to `plotly.express.pie()`.
750
809
 
751
810
  Returns:
@@ -760,5 +819,6 @@ class DatasetPlotlyAccessor:
760
819
  facet_row=facet_row,
761
820
  colors=colors,
762
821
  facet_titles=facet_titles,
822
+ shared_axis_labels=shared_axis_labels,
763
823
  **px_kwargs,
764
824
  )
@@ -971,3 +971,80 @@ def simplify_facet_titles(
971
971
  if text and _FACET_TITLE_PREFIX_RE.match(text):
972
972
  ann.text = text.split("=", 1)[1]
973
973
  return fig
974
+
975
+
976
+ # Matches cartesian axis layout keys like "xaxis", "xaxis2", "yaxis12".
977
+ _AXIS_KEY_RE = re.compile(r"([xy])axis\d*$")
978
+
979
+ # Annotation specs identical to plotly's built-in shared subplot titles,
980
+ # i.e. what `make_subplots(x_title=..., y_title=...)` produces. Kept in
981
+ # sync with plotly via a test against make_subplots output.
982
+ _SHARED_LABEL_SPECS: dict[str, dict[str, Any]] = {
983
+ "x": {
984
+ "x": 0.5,
985
+ "y": 0,
986
+ "xref": "paper",
987
+ "yref": "paper",
988
+ "xanchor": "center",
989
+ "yanchor": "top",
990
+ "yshift": -30,
991
+ "showarrow": False,
992
+ "font": {"size": 16},
993
+ },
994
+ "y": {
995
+ "x": 0,
996
+ "y": 0.5,
997
+ "xref": "paper",
998
+ "yref": "paper",
999
+ "xanchor": "right",
1000
+ "yanchor": "middle",
1001
+ "xshift": -40,
1002
+ "textangle": -90,
1003
+ "showarrow": False,
1004
+ "font": {"size": 16},
1005
+ },
1006
+ }
1007
+
1008
+
1009
+ def share_axis_labels(fig: go.Figure) -> go.Figure:
1010
+ """Replace repeated facet axis titles with a single shared label per axis.
1011
+
1012
+ Plotly Express repeats the x-axis title under every facet column and the
1013
+ y-axis title beside every facet row. This helper removes the repeated
1014
+ titles and adds one centered label per axis instead, styled exactly like
1015
+ plotly's built-in shared titles (``make_subplots(x_title=..., y_title=...)``),
1016
+ which Plotly Express does not expose for faceted figures.
1017
+
1018
+ Titles are only collapsed when they are repeated and identical, so
1019
+ figures without facets, figures combined from differently-labeled
1020
+ subplots, and secondary-y figures are returned unchanged.
1021
+
1022
+ Args:
1023
+ fig: A Plotly figure (mutated in place).
1024
+
1025
+ Returns:
1026
+ The (possibly mutated) figure, for chaining.
1027
+
1028
+ Example:
1029
+ >>> import plotly.express as px
1030
+ >>> from xarray_plotly import share_axis_labels
1031
+ >>> fig = px.line(df, x="year", y="gdp", facet_col="country", facet_row="metric")
1032
+ >>> share_axis_labels(fig) # one "year" below, one "gdp" at the left
1033
+ """
1034
+ axes_by_letter: dict[str, list[Any]] = {"x": [], "y": []}
1035
+ for key in fig.layout:
1036
+ match = _AXIS_KEY_RE.match(key)
1037
+ # Overlaying axes (secondary y) share their domain with the axis
1038
+ # they overlay; their titles are independent, not facet repetition.
1039
+ if match and not fig.layout[key].overlaying:
1040
+ axes_by_letter[match.group(1)].append(fig.layout[key])
1041
+
1042
+ for letter, axes in axes_by_letter.items():
1043
+ titles = [axis.title.text for axis in axes if axis.title.text]
1044
+ # Only collapse titles that are actually repeated and identical
1045
+ if len(titles) < 2 or len(set(titles)) != 1:
1046
+ continue
1047
+ for axis in axes:
1048
+ axis.title.text = None
1049
+ fig.add_annotation(text=titles[0], **_SHARED_LABEL_SPECS[letter])
1050
+ return fig
@@ -26,6 +26,7 @@ from xarray_plotly.common import (
26
26
  )
27
27
  from xarray_plotly.figures import (
28
28
  _iter_all_traces,
29
+ share_axis_labels,
29
30
  simplify_facet_titles,
30
31
  )
31
32
 
@@ -34,6 +35,14 @@ if TYPE_CHECKING:
34
35
  from xarray import DataArray
35
36
 
36
37
 
38
+ def _finalize(fig: go.Figure, facet_titles: FacetTitlesMode, shared_axis_labels: bool) -> go.Figure:
39
+ """Apply facet post-processing shared by all plot functions."""
40
+ simplify_facet_titles(fig, facet_titles)
41
+ if shared_axis_labels:
42
+ share_axis_labels(fig)
43
+ return fig
44
+
45
+
37
46
  def line(
38
47
  darray: DataArray,
39
48
  *,
@@ -46,6 +55,7 @@ def line(
46
55
  animation_frame: SlotValue = auto,
47
56
  colors: Colors = None,
48
57
  facet_titles: FacetTitlesMode = "default",
58
+ shared_axis_labels: bool = True,
49
59
  **px_kwargs: Any,
50
60
  ) -> go.Figure:
51
61
  """
@@ -79,6 +89,9 @@ def line(
79
89
  - A list of colors (e.g., ["red", "blue", "green"])
80
90
  - A dict mapping values to colors (e.g., {"A": "red", "B": "blue"})
81
91
  Explicit color_* kwargs in px_kwargs take precedence.
92
+ shared_axis_labels
93
+ If True (default), repeated axis titles on faceted plots are
94
+ replaced with a single shared, centered label per axis.
82
95
  **px_kwargs
83
96
  Additional arguments passed to `plotly.express.line()`.
84
97
 
@@ -116,7 +129,7 @@ def line(
116
129
  labels=labels,
117
130
  **px_kwargs,
118
131
  )
119
- return simplify_facet_titles(fig, facet_titles)
132
+ return _finalize(fig, facet_titles, shared_axis_labels)
120
133
 
121
134
 
122
135
  def bar(
@@ -130,6 +143,7 @@ def bar(
130
143
  animation_frame: SlotValue = auto,
131
144
  colors: Colors = None,
132
145
  facet_titles: FacetTitlesMode = "default",
146
+ shared_axis_labels: bool = True,
133
147
  **px_kwargs: Any,
134
148
  ) -> go.Figure:
135
149
  """
@@ -161,6 +175,9 @@ def bar(
161
175
  - A list of colors (e.g., ["red", "blue", "green"])
162
176
  - A dict mapping values to colors (e.g., {"A": "red", "B": "blue"})
163
177
  Explicit color_* kwargs in px_kwargs take precedence.
178
+ shared_axis_labels
179
+ If True (default), repeated axis titles on faceted plots are
180
+ replaced with a single shared, centered label per axis.
164
181
  **px_kwargs
165
182
  Additional arguments passed to `plotly.express.bar()`.
166
183
 
@@ -196,7 +213,7 @@ def bar(
196
213
  labels=labels,
197
214
  **px_kwargs,
198
215
  )
199
- return simplify_facet_titles(fig, facet_titles)
216
+ return _finalize(fig, facet_titles, shared_axis_labels)
200
217
 
201
218
 
202
219
  def _classify_trace_sign(y_values: npt.ArrayLike) -> str:
@@ -293,6 +310,7 @@ def fast_bar(
293
310
  animation_frame: SlotValue = auto,
294
311
  colors: Colors = None,
295
312
  facet_titles: FacetTitlesMode = "default",
313
+ shared_axis_labels: bool = True,
296
314
  **px_kwargs: Any,
297
315
  ) -> go.Figure:
298
316
  """
@@ -330,6 +348,9 @@ def fast_bar(
330
348
  - A list of colors (e.g., ["red", "blue", "green"])
331
349
  - A dict mapping values to colors (e.g., {"A": "red", "B": "blue"})
332
350
  Explicit color_* kwargs in px_kwargs take precedence.
351
+ shared_axis_labels
352
+ If True (default), repeated axis titles on faceted plots are
353
+ replaced with a single shared, centered label per axis.
333
354
  **px_kwargs
334
355
  Additional arguments passed to `plotly.express.area()`.
335
356
 
@@ -367,7 +388,7 @@ def fast_bar(
367
388
 
368
389
  _style_traces_as_bars(fig)
369
390
 
370
- return simplify_facet_titles(fig, facet_titles)
391
+ return _finalize(fig, facet_titles, shared_axis_labels)
371
392
 
372
393
 
373
394
  def area(
@@ -381,6 +402,7 @@ def area(
381
402
  animation_frame: SlotValue = auto,
382
403
  colors: Colors = None,
383
404
  facet_titles: FacetTitlesMode = "default",
405
+ shared_axis_labels: bool = True,
384
406
  **px_kwargs: Any,
385
407
  ) -> go.Figure:
386
408
  """
@@ -412,6 +434,9 @@ def area(
412
434
  - A list of colors (e.g., ["red", "blue", "green"])
413
435
  - A dict mapping values to colors (e.g., {"A": "red", "B": "blue"})
414
436
  Explicit color_* kwargs in px_kwargs take precedence.
437
+ shared_axis_labels
438
+ If True (default), repeated axis titles on faceted plots are
439
+ replaced with a single shared, centered label per axis.
415
440
  **px_kwargs
416
441
  Additional arguments passed to `plotly.express.area()`.
417
442
 
@@ -447,7 +472,7 @@ def area(
447
472
  labels=labels,
448
473
  **px_kwargs,
449
474
  )
450
- return simplify_facet_titles(fig, facet_titles)
475
+ return _finalize(fig, facet_titles, shared_axis_labels)
451
476
 
452
477
 
453
478
  def box(
@@ -460,6 +485,7 @@ def box(
460
485
  animation_frame: SlotValue = None,
461
486
  colors: Colors = None,
462
487
  facet_titles: FacetTitlesMode = "default",
488
+ shared_axis_labels: bool = True,
463
489
  **px_kwargs: Any,
464
490
  ) -> go.Figure:
465
491
  """
@@ -491,6 +517,9 @@ def box(
491
517
  - A list of colors (e.g., ["red", "blue", "green"])
492
518
  - A dict mapping values to colors (e.g., {"A": "red", "B": "blue"})
493
519
  Explicit color_* kwargs in px_kwargs take precedence.
520
+ shared_axis_labels
521
+ If True (default), repeated axis titles on faceted plots are
522
+ replaced with a single shared, centered label per axis.
494
523
  **px_kwargs
495
524
  Additional arguments passed to `plotly.express.box()`.
496
525
 
@@ -525,7 +554,7 @@ def box(
525
554
  labels=labels,
526
555
  **px_kwargs,
527
556
  )
528
- return simplify_facet_titles(fig, facet_titles)
557
+ return _finalize(fig, facet_titles, shared_axis_labels)
529
558
 
530
559
 
531
560
  def scatter(
@@ -540,6 +569,7 @@ def scatter(
540
569
  animation_frame: SlotValue = auto,
541
570
  colors: Colors = None,
542
571
  facet_titles: FacetTitlesMode = "default",
572
+ shared_axis_labels: bool = True,
543
573
  **px_kwargs: Any,
544
574
  ) -> go.Figure:
545
575
  """
@@ -578,6 +608,9 @@ def scatter(
578
608
  - A list of colors (e.g., ["red", "blue", "green"])
579
609
  - A dict mapping values to colors (e.g., {"A": "red", "B": "blue"})
580
610
  Explicit color_* kwargs in px_kwargs take precedence.
611
+ shared_axis_labels
612
+ If True (default), repeated axis titles on faceted plots are
613
+ replaced with a single shared, centered label per axis.
581
614
  **px_kwargs
582
615
  Additional arguments passed to `plotly.express.scatter()`.
583
616
 
@@ -625,7 +658,7 @@ def scatter(
625
658
  labels=labels,
626
659
  **px_kwargs,
627
660
  )
628
- return simplify_facet_titles(fig, facet_titles)
661
+ return _finalize(fig, facet_titles, shared_axis_labels)
629
662
 
630
663
 
631
664
  def _imshow_supports_facet_row() -> bool:
@@ -647,6 +680,7 @@ def imshow(
647
680
  robust: bool = False,
648
681
  colors: Colors = None,
649
682
  facet_titles: FacetTitlesMode = "default",
683
+ shared_axis_labels: bool = True,
650
684
  **px_kwargs: Any,
651
685
  ) -> go.Figure:
652
686
  """
@@ -689,6 +723,9 @@ def imshow(
689
723
  continuous scale (e.g., "Viridis", "RdBu"). Lists and dicts
690
724
  are not applicable for heatmaps.
691
725
  Explicit color_continuous_scale in px_kwargs takes precedence.
726
+ shared_axis_labels
727
+ If True (default), repeated axis titles on faceted plots are
728
+ replaced with a single shared, centered label per axis.
692
729
  **px_kwargs
693
730
  Additional arguments passed to `plotly.express.imshow()`.
694
731
  Use `zmin` and `zmax` to manually set color scale bounds.
@@ -752,7 +789,7 @@ def imshow(
752
789
  **facet_row_kwargs,
753
790
  **px_kwargs,
754
791
  )
755
- return simplify_facet_titles(fig, facet_titles)
792
+ return _finalize(fig, facet_titles, shared_axis_labels)
756
793
 
757
794
 
758
795
  def pie(
@@ -764,6 +801,7 @@ def pie(
764
801
  facet_row: SlotValue = auto,
765
802
  colors: Colors = None,
766
803
  facet_titles: FacetTitlesMode = "default",
804
+ shared_axis_labels: bool = True,
767
805
  **px_kwargs: Any,
768
806
  ) -> go.Figure:
769
807
  """
@@ -790,6 +828,9 @@ def pie(
790
828
  - A list of colors (e.g., ["red", "blue", "green"])
791
829
  - A dict mapping values to colors (e.g., {"A": "red", "B": "blue"})
792
830
  Explicit color_* kwargs in px_kwargs take precedence.
831
+ shared_axis_labels
832
+ If True (default), repeated axis titles on faceted plots are
833
+ replaced with a single shared, centered label per axis.
793
834
  **px_kwargs
794
835
  Additional arguments passed to `plotly.express.pie()`.
795
836
 
@@ -823,4 +864,4 @@ def pie(
823
864
  labels=labels,
824
865
  **px_kwargs,
825
866
  )
826
- return simplify_facet_titles(fig, facet_titles)
867
+ return _finalize(fig, facet_titles, shared_axis_labels)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray_plotly
3
- Version: 0.0.16
3
+ Version: 0.0.17
4
4
  Summary: Interactive Plotly Express plotting accessor for xarray
5
5
  Author: Felix
6
6
  License: MIT
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.0.16"
3
- }
File without changes
File without changes
File without changes