xarray-plotly 0.0.12__py3-none-any.whl → 0.0.14__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.
xarray_plotly/common.py CHANGED
@@ -13,7 +13,7 @@ from xarray_plotly.config import DEFAULT_SLOT_ORDERS, _options
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  import pandas as pd
16
- from xarray import DataArray
16
+ from xarray import DataArray, Dataset
17
17
 
18
18
 
19
19
  class _AUTO:
@@ -251,7 +251,21 @@ def _get_qualitative_scale_names() -> frozenset[str]:
251
251
  )
252
252
 
253
253
 
254
- def resolve_colors(colors: Colors, px_kwargs: dict[str, Any]) -> dict[str, Any]:
254
+ def _sample_colorscale(name: str, n: int) -> list[str]:
255
+ """Sample *n* evenly-spaced colors from a named Plotly colorscale."""
256
+ scale = px.colors.get_colorscale(name)
257
+ samplepoints = [i / max(n - 1, 1) for i in range(n)]
258
+ result: list[str] = px.colors.sample_colorscale(scale, samplepoints)
259
+ return result
260
+
261
+
262
+ def resolve_colors(
263
+ colors: Colors,
264
+ px_kwargs: dict[str, Any],
265
+ *,
266
+ color_dim: Hashable | None = None,
267
+ darray: DataArray | Dataset | None = None,
268
+ ) -> dict[str, Any]:
255
269
  """Map unified `colors` parameter to appropriate Plotly px_kwargs.
256
270
 
257
271
  Direct color_* kwargs take precedence and trigger a warning if
@@ -260,6 +274,14 @@ def resolve_colors(colors: Colors, px_kwargs: dict[str, Any]) -> dict[str, Any]:
260
274
  Args:
261
275
  colors: Unified color specification (str, list, dict, or None).
262
276
  px_kwargs: Existing kwargs to pass to Plotly Express.
277
+ color_dim: Dimension name used for discrete color grouping.
278
+ When provided together with *darray*, a continuous colorscale
279
+ string is sampled into a discrete sequence whose length
280
+ matches the number of coordinates along this dimension.
281
+ Use for chart types that only accept discrete color
282
+ parameters (line, area, box, pie).
283
+ darray: Source DataArray or Dataset; used with *color_dim* to
284
+ determine the number of discrete colors to sample.
263
285
 
264
286
  Returns:
265
287
  Updated px_kwargs with color parameters injected.
@@ -284,6 +306,10 @@ def resolve_colors(colors: Colors, px_kwargs: dict[str, Any]) -> dict[str, Any]:
284
306
  # Check if it's a qualitative (discrete) palette name
285
307
  if colors in _get_qualitative_scale_names():
286
308
  px_kwargs["color_discrete_sequence"] = getattr(px.colors.qualitative, colors)
309
+ elif color_dim is not None and darray is not None:
310
+ # Sample from continuous scale into a discrete sequence
311
+ n = darray.sizes[color_dim]
312
+ px_kwargs["color_discrete_sequence"] = _sample_colorscale(colors, n)
287
313
  else:
288
314
  # Assume continuous scale
289
315
  px_kwargs["color_continuous_scale"] = colors
xarray_plotly/figures.py CHANGED
@@ -308,6 +308,13 @@ def add_secondary_y(
308
308
  # Build mapping from primary y-axes to secondary y-axes
309
309
  y_mapping = _build_secondary_y_mapping(base_axes)
310
310
 
311
+ # Build x-y correspondence from base_axes (which x-axis pairs with which y-axis)
312
+ x_for_y = {yaxis: xaxis for xaxis, yaxis in base_axes}
313
+
314
+ # Find the rightmost x-axis (highest number) to determine which secondary axis shows ticks
315
+ rightmost_x = max(x_for_y.values(), key=lambda x: int(x[1:]) if x != "x" else 1)
316
+ rightmost_primary_y = next(y for y, x in x_for_y.items() if x == rightmost_x)
317
+
311
318
  # Create new figure with base's layout
312
319
  combined = go.Figure(layout=copy.deepcopy(base.layout))
313
320
 
@@ -322,24 +329,32 @@ def add_secondary_y(
322
329
  trace_copy.yaxis = y_mapping[original_yaxis]
323
330
  combined.add_trace(trace_copy)
324
331
 
332
+ # Get the rightmost secondary y-axis name for linking
333
+ rightmost_secondary_y = y_mapping[rightmost_primary_y]
334
+
325
335
  # Configure secondary y-axes
326
336
  for primary_yaxis, secondary_yaxis in y_mapping.items():
327
- # Get title - only set on first secondary axis or use provided title
337
+ is_rightmost = primary_yaxis == rightmost_primary_y
338
+
339
+ # Get title - only set on rightmost secondary axis
328
340
  title = None
329
- if secondary_y_title is not None:
330
- # Only set title on the first secondary axis to avoid repetition
331
- if primary_yaxis == "y":
341
+ if is_rightmost:
342
+ if secondary_y_title is not None:
332
343
  title = secondary_y_title
333
- elif primary_yaxis == "y" and secondary.layout.yaxis and secondary.layout.yaxis.title:
334
- # Try to get from secondary's layout
335
- title = secondary.layout.yaxis.title.text
344
+ elif secondary.layout.yaxis and secondary.layout.yaxis.title:
345
+ title = secondary.layout.yaxis.title.text
336
346
 
337
347
  # Configure the secondary axis
348
+ # Anchor to the corresponding x-axis so it appears on the right side of its subplot
338
349
  axis_config = {
339
350
  "title": title,
340
351
  "overlaying": primary_yaxis,
341
352
  "side": "right",
342
- "anchor": "free" if primary_yaxis != "y" else None,
353
+ "anchor": x_for_y[primary_yaxis],
354
+ # Only show ticks on the rightmost secondary axis
355
+ "showticklabels": is_rightmost,
356
+ # Link non-rightmost axes to the rightmost for consistent scaling
357
+ "matches": None if is_rightmost else rightmost_secondary_y,
343
358
  }
344
359
  # Remove None values
345
360
  axis_config = {k: v for k, v in axis_config.items() if v is not None}
xarray_plotly/plotting.py CHANGED
@@ -82,7 +82,6 @@ def line(
82
82
  -------
83
83
  plotly.graph_objects.Figure
84
84
  """
85
- px_kwargs = resolve_colors(colors, px_kwargs)
86
85
  slots = assign_slots(
87
86
  list(darray.dims),
88
87
  "line",
@@ -94,6 +93,7 @@ def line(
94
93
  facet_row=facet_row,
95
94
  animation_frame=animation_frame,
96
95
  )
96
+ px_kwargs = resolve_colors(colors, px_kwargs, color_dim=slots.get("color"), darray=darray)
97
97
 
98
98
  df = to_dataframe(darray)
99
99
  value_col = get_value_col(darray)
@@ -329,7 +329,6 @@ def fast_bar(
329
329
  -------
330
330
  plotly.graph_objects.Figure
331
331
  """
332
- px_kwargs = resolve_colors(colors, px_kwargs)
333
332
  slots = assign_slots(
334
333
  list(darray.dims),
335
334
  "fast_bar",
@@ -339,6 +338,7 @@ def fast_bar(
339
338
  facet_row=facet_row,
340
339
  animation_frame=animation_frame,
341
340
  )
341
+ px_kwargs = resolve_colors(colors, px_kwargs, color_dim=slots.get("color"), darray=darray)
342
342
 
343
343
  df = to_dataframe(darray)
344
344
  value_col = get_value_col(darray)
@@ -410,7 +410,6 @@ def area(
410
410
  -------
411
411
  plotly.graph_objects.Figure
412
412
  """
413
- px_kwargs = resolve_colors(colors, px_kwargs)
414
413
  slots = assign_slots(
415
414
  list(darray.dims),
416
415
  "area",
@@ -421,6 +420,7 @@ def area(
421
420
  facet_row=facet_row,
422
421
  animation_frame=animation_frame,
423
422
  )
423
+ px_kwargs = resolve_colors(colors, px_kwargs, color_dim=slots.get("color"), darray=darray)
424
424
 
425
425
  df = to_dataframe(darray)
426
426
  value_col = get_value_col(darray)
@@ -487,7 +487,6 @@ def box(
487
487
  -------
488
488
  plotly.graph_objects.Figure
489
489
  """
490
- px_kwargs = resolve_colors(colors, px_kwargs)
491
490
  slots = assign_slots(
492
491
  list(darray.dims),
493
492
  "box",
@@ -498,6 +497,7 @@ def box(
498
497
  facet_row=facet_row,
499
498
  animation_frame=animation_frame,
500
499
  )
500
+ px_kwargs = resolve_colors(colors, px_kwargs, color_dim=slots.get("color"), darray=darray)
501
501
 
502
502
  df = to_dataframe(darray)
503
503
  value_col = get_value_col(darray)
@@ -746,7 +746,6 @@ def pie(
746
746
  -------
747
747
  plotly.graph_objects.Figure
748
748
  """
749
- px_kwargs = resolve_colors(colors, px_kwargs)
750
749
  slots = assign_slots(
751
750
  list(darray.dims),
752
751
  "pie",
@@ -754,6 +753,7 @@ def pie(
754
753
  facet_col=facet_col,
755
754
  facet_row=facet_row,
756
755
  )
756
+ px_kwargs = resolve_colors(colors, px_kwargs, color_dim=slots.get("names"), darray=darray)
757
757
 
758
758
  df = to_dataframe(darray)
759
759
  value_col = get_value_col(darray)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xarray_plotly
3
- Version: 0.0.12
3
+ Version: 0.0.14
4
4
  Summary: Interactive Plotly Express plotting accessor for xarray
5
5
  Author: Felix
6
6
  License: MIT
@@ -28,13 +28,13 @@ Provides-Extra: dev
28
28
  Requires-Dist: pytest==9.0.2; extra == "dev"
29
29
  Requires-Dist: pytest-cov==7.0.0; extra == "dev"
30
30
  Requires-Dist: mypy==1.19.1; extra == "dev"
31
- Requires-Dist: ruff==0.14.14; extra == "dev"
31
+ Requires-Dist: ruff==0.15.5; extra == "dev"
32
32
  Requires-Dist: pre-commit==4.5.1; extra == "dev"
33
- Requires-Dist: nbstripout==0.9.0; extra == "dev"
33
+ Requires-Dist: nbstripout==0.9.1; extra == "dev"
34
34
  Provides-Extra: docs
35
35
  Requires-Dist: mkdocs==1.6.1; extra == "docs"
36
- Requires-Dist: mkdocs-material==9.7.1; extra == "docs"
37
- Requires-Dist: mkdocstrings[python]==1.0.1; extra == "docs"
36
+ Requires-Dist: mkdocs-material==9.7.4; extra == "docs"
37
+ Requires-Dist: mkdocstrings[python]==1.0.3; extra == "docs"
38
38
  Requires-Dist: mkdocs-jupyter==0.25.1; extra == "docs"
39
39
  Requires-Dist: mkdocs-plotly-plugin==0.1.3; extra == "docs"
40
40
  Requires-Dist: jupyter==1.1.1; extra == "docs"
@@ -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
@@ -0,0 +1,12 @@
1
+ xarray_plotly/__init__.py,sha256=8YaSdbzAakzlRqkq9HkQlMxWOLGg2JfUN-gAkyCGf5U,3472
2
+ xarray_plotly/accessor.py,sha256=XsGREdfGGqzFzpM53AzTbMnCOEtZTv0Sau-dc15meHQ,25313
3
+ xarray_plotly/common.py,sha256=YdxqrK5LPTxDAALeMswxtTyEJuoHYUSess_28axJo2s,10782
4
+ xarray_plotly/config.py,sha256=fOL7cqPHVj_XNLqN1oT2KQZ0DitIjzeYnWyMKcJy0cc,6679
5
+ xarray_plotly/figures.py,sha256=uKC_8kcoFJYXsqMu-QRAsKfY8O_E3r8McBfRiHZxciE,16457
6
+ xarray_plotly/plotting.py,sha256=OTz9ScASHnRQAJ9NvQAABiYALIoTrmYSLMEIZa4Zd44,24331
7
+ xarray_plotly/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ xarray_plotly-0.0.14.dist-info/licenses/LICENSE,sha256=AvVEfNqbhIm9jHvt0acJNjW1JUKa2a70Zb5rJdEXCJI,1064
9
+ xarray_plotly-0.0.14.dist-info/METADATA,sha256=fKfz7Y0vuHRkhHfNGtObSxC9EWWKZr3eCYaCfzDyK_Y,4163
10
+ xarray_plotly-0.0.14.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ xarray_plotly-0.0.14.dist-info/top_level.txt,sha256=GtMkvuZvLAYTjYXtwoNUa0ag42CJARZJK1CZemYD7pg,14
12
+ xarray_plotly-0.0.14.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.2)
2
+ Generator: setuptools (82.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,12 +0,0 @@
1
- xarray_plotly/__init__.py,sha256=8YaSdbzAakzlRqkq9HkQlMxWOLGg2JfUN-gAkyCGf5U,3472
2
- xarray_plotly/accessor.py,sha256=XsGREdfGGqzFzpM53AzTbMnCOEtZTv0Sau-dc15meHQ,25313
3
- xarray_plotly/common.py,sha256=XN5qDYG1UOGuocNekckujJNyDbWA9Q7pvdx8hC6w7OA,9576
4
- xarray_plotly/config.py,sha256=fOL7cqPHVj_XNLqN1oT2KQZ0DitIjzeYnWyMKcJy0cc,6679
5
- xarray_plotly/figures.py,sha256=kfTPkIpT0683XHBH-RuuM5cvuA_IzChDRcCSjOC0QEc,15709
6
- xarray_plotly/plotting.py,sha256=qHoEU1NX-YWkI23G60xYg_gzR_fEBSDcdrg0B2NpoPs,24106
7
- xarray_plotly/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- xarray_plotly-0.0.12.dist-info/licenses/LICENSE,sha256=AvVEfNqbhIm9jHvt0acJNjW1JUKa2a70Zb5rJdEXCJI,1064
9
- xarray_plotly-0.0.12.dist-info/METADATA,sha256=3f-FDiVVKjJOt2GpbYC0mr0Q5F2tNPSNVPvWA7ejRRY,3416
10
- xarray_plotly-0.0.12.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
- xarray_plotly-0.0.12.dist-info/top_level.txt,sha256=GtMkvuZvLAYTjYXtwoNUa0ag42CJARZJK1CZemYD7pg,14
12
- xarray_plotly-0.0.12.dist-info/RECORD,,