Skip to content

Render matplotlib path collections (violin plots, pcolor, stems, etc.) in plotly - #5702

Merged
camdecoster merged 9 commits into
plotly:mainfrom
robertoffmoura:rm/fix-violin-plot
Aug 21, 2026
Merged

Render matplotlib path collections (violin plots, pcolor, stems, etc.) in plotly#5702
camdecoster merged 9 commits into
plotly:mainfrom
robertoffmoura:rm/fix-violin-plot

Conversation

@robertoffmoura

Copy link
Copy Markdown
Contributor

mpl_to_plotly silently drops several common plot types. Violin plots, pcolor, event plots, stack plots, fill_between and stem plots all produce PolyCollection/LineCollection artists in data coordinates, which the renderer skipped with "Dang! That path collection is out of this world...". The converted figure ends up with zero traces (except for stem, which gets partially drawn).

Fix: data-coordinate path collections are now drawn:

  • collections with face colors (violin bodies, pcolor cells, stacked areas, fills) → filled polygons (fill="toself")
  • collections without face colors (contour lines, stem lines) → plain line traces
  • per-path colors/widths handled for collections that mix them; empty color arrays and degenerate paths skipped

Before: plots converts to 0 traces (empty figures).
After: plots render correctly.

Snippet to reproduce:

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

x = np.linspace(-3, 3, 30)
X, Y = np.meshgrid(x, x)
t = np.linspace(0, 2 * np.pi, 50)

cases = {
    "violinplot":   lambda: plt.violinplot(np.random.randn(100, 3)),
    "pcolor":       lambda: plt.pcolor(X, Y, np.sin(X) * np.cos(Y)),
    "eventplot":    lambda: plt.eventplot([np.random.randn(20) for _ in range(5)]),
    "stackplot":    lambda: plt.stackplot(np.arange(10), np.random.rand(10), np.random.rand(10), np.random.rand(10)),
    "fill_between": lambda: plt.fill_between(t, np.sin(t), np.cos(t)),
    "stem":         lambda: plt.stem(np.linspace(0, 2 * np.pi, 20), np.sin(np.linspace(0, 2 * np.pi, 20))),
}

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

    p = tls.mpl_to_plotly(fig)   # 0 traces for all of these before the fix (except stem, which had some traces)
    p.write_image(f"{name}_plotly.png")

    print(f"{name:13s} -> {len(p.data)} traces")
Plot type Converted plotly figure
violinplot (12 traces) violinplot_plotly
pcolor (900 traces) pcolor_plotly
eventplot (100 traces) eventplot_plotly
stackplot (3 traces) stackplot_plotly
fill_between (1 trace) fill_between_plotly
stem (22 traces) stem_plotly

@camdecoster camdecoster left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Overall this is in good shape but needs a couple of changes. Could you also please add a changelog entry?

Comment thread plotly/matplotlylib/renderer.py
Comment thread plotly/matplotlylib/renderer.py Outdated
Comment thread plotly/matplotlylib/renderer.py Outdated
Comment thread plotly/matplotlylib/renderer.py Outdated
Comment thread plotly/matplotlylib/renderer.py Outdated
@robertoffmoura

Copy link
Copy Markdown
Contributor Author

Thanks! I addressed your comments and added a changelog entry.

camdecoster
camdecoster previously approved these changes Aug 21, 2026

@camdecoster camdecoster left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix!

@camdecoster
camdecoster merged commit a5974ce into plotly:main Aug 21, 2026
24 checks passed
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