Skip to content

Convert axhspan and axvspan to layout shapes - #5847

Open
robertoffmoura wants to merge 6 commits into
plotly:mainfrom
robertoffmoura:rm/support-axhspan
Open

robertoffmoura wants to merge 6 commits into
plotly:mainfrom
robertoffmoura:rm/support-axhspan

Conversation

@robertoffmoura

Copy link
Copy Markdown
Contributor

mpl_to_plotly silently drops reference spans. axhspan and axvspan add a Rectangle patch with a blended axes/data transform, which the renderer skipped with "I found a path object that I don't think is part of a bar chart. Ignoring.". The converted figure ends up with the spans missing completely.

Fix: reference spans are now drawn as layout rectangle shapes:

  • a span is recognized as an axis-aligned rectangle in a blended axes/data transform (matplotlib gives spans no other marker); other shapes in such transforms keep the warning
  • sides in data coordinates map onto the data axis, and sides in axes fractions (the full width/height, or xmin/xmax, ymin/ymax) map onto the axis domain (x domain/y domain), so they stay fixed to the axes on pan and zoom
  • x-values are converted to date strings on date axes, and spans work on log axes
  • fill color, edge color, alpha, linewidth and dash styles are preserved
  • labeled spans get a legend entry

Before: 0 shapes (spans dropped with a warning).
After: spans render as layout shapes.

Snippet to reproduce:

import datetime

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import plotly.tools as tls


def spans(ax):
    ax.plot([0, 10], [0, 10])
    ax.axhspan(2, 4, color="orange", alpha=0.5)
    ax.axvspan(6, 8, color="green", alpha=0.5)


def partial_spans(ax):
    ax.plot([0, 10], [0, 10], label="line")
    ax.axhspan(2, 4, xmin=0.2, xmax=0.6, color="orange", label="axhspan")
    ax.axvspan(6, 8, ymin=0.1, ymax=0.5, color="green")
    ax.legend()


def spans_date_axis(ax):
    dates = [datetime.datetime(2023, 1, day) for day in range(1, 10)]
    ax.plot(dates, range(9))
    ax.axvspan(dates[2], dates[5], color="purple", alpha=0.3)
    ax.axhspan(2, 4, facecolor="red", alpha=0.3, edgecolor="black", linestyle="--")


cases = {
    "spans": spans,
    "partial_spans": partial_spans,
    "spans_date_axis": spans_date_axis,
}

for name, build in cases.items():
    fig, ax = plt.subplots()
    build(ax)
    fig.savefig(f"{name}_mpl.png")

    p = tls.mpl_to_plotly(fig)  # 0 shapes before this fix
    print(f"{name:16s} -> {len(p.layout.shapes)} shape(s)")
    p.write_image(f"{name}_plotly.png")
matplotlib plotly before plotly after
spans_mpl spans_plotly_before spans_plotly_after
partial_spans_mpl partial_spans_plotly_before partial_spans_plotly_after
spans_date_axis_mpl spans_date_axis_plotly_before spans_date_axis_plotly_after

@camdecoster camdecoster self-assigned this Oct 27, 2026
@camdecoster

Copy link
Copy Markdown
Contributor

Thanks for the PR! I'll review and follow up.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants