diff --git a/README.md b/README.md index a9a480c..e60f70b 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ Planned additions (contributions welcome): - WebGL rendering option for `line()` and `scatter()` (large datasets) **Figure utilities** (facet/animation-aware) -- `add_trace()` — add a trace to base figure and all animation frames - `fill_between()` — fill area between two traces (uncertainty bands) - `sync_axes()` — consistent axis ranges across facets and animation frames - `add_secondary_x()` — secondary x-axis (like `add_secondary_y()`) diff --git a/tests/test_figures.py b/tests/test_figures.py index 4226725..dacf19d 100644 --- a/tests/test_figures.py +++ b/tests/test_figures.py @@ -505,8 +505,8 @@ def test_facets_with_custom_title(self) -> None: combined = add_secondary_y(base, secondary, secondary_y_title="Custom Title") - # Title should be on the first secondary axis - assert combined.layout.yaxis4.title.text == "Custom Title" + # Title should be on the rightmost secondary axis (yaxis6 for 3 facets) + assert combined.layout.yaxis6.title.text == "Custom Title" class TestAddSecondaryYAnimation: diff --git a/xarray_plotly/figures.py b/xarray_plotly/figures.py index 2bb4031..6ed743d 100644 --- a/xarray_plotly/figures.py +++ b/xarray_plotly/figures.py @@ -308,6 +308,13 @@ def add_secondary_y( # Build mapping from primary y-axes to secondary y-axes y_mapping = _build_secondary_y_mapping(base_axes) + # Build x-y correspondence from base_axes (which x-axis pairs with which y-axis) + x_for_y = {yaxis: xaxis for xaxis, yaxis in base_axes} + + # Find the rightmost x-axis (highest number) to determine which secondary axis shows ticks + rightmost_x = max(x_for_y.values(), key=lambda x: int(x[1:]) if x != "x" else 1) + rightmost_primary_y = next(y for y, x in x_for_y.items() if x == rightmost_x) + # Create new figure with base's layout combined = go.Figure(layout=copy.deepcopy(base.layout)) @@ -322,24 +329,32 @@ def add_secondary_y( trace_copy.yaxis = y_mapping[original_yaxis] combined.add_trace(trace_copy) + # Get the rightmost secondary y-axis name for linking + rightmost_secondary_y = y_mapping[rightmost_primary_y] + # Configure secondary y-axes for primary_yaxis, secondary_yaxis in y_mapping.items(): - # Get title - only set on first secondary axis or use provided title + is_rightmost = primary_yaxis == rightmost_primary_y + + # Get title - only set on rightmost secondary axis title = None - if secondary_y_title is not None: - # Only set title on the first secondary axis to avoid repetition - if primary_yaxis == "y": + if is_rightmost: + if secondary_y_title is not None: title = secondary_y_title - elif primary_yaxis == "y" and secondary.layout.yaxis and secondary.layout.yaxis.title: - # Try to get from secondary's layout - title = secondary.layout.yaxis.title.text + elif secondary.layout.yaxis and secondary.layout.yaxis.title: + title = secondary.layout.yaxis.title.text # Configure the secondary axis + # Anchor to the corresponding x-axis so it appears on the right side of its subplot axis_config = { "title": title, "overlaying": primary_yaxis, "side": "right", - "anchor": "free" if primary_yaxis != "y" else None, + "anchor": x_for_y[primary_yaxis], + # Only show ticks on the rightmost secondary axis + "showticklabels": is_rightmost, + # Link non-rightmost axes to the rightmost for consistent scaling + "matches": None if is_rightmost else rightmost_secondary_y, } # Remove None values axis_config = {k: v for k, v in axis_config.items() if v is not None}