Skip to content

Convert matplotlib 3D line plots to scatter3d traces - #5852

Open
robertoffmoura wants to merge 18 commits into
plotly:mainfrom
robertoffmoura:rm/support-plot3d
Open

robertoffmoura wants to merge 18 commits into
plotly:mainfrom
robertoffmoura:rm/support-plot3d

Conversation

@robertoffmoura

Copy link
Copy Markdown
Contributor

mpl_to_plotly can't convert 3D plots. Matplotlib's Axes3D sets get_frame_on = None, so reading the axes properties raises TypeError: 'NoneType' object is not callable for any figure with a 3D axes, and nothing is converted.

Fix: 3D axes are now converted to plotly scenes:

  • each 3D axes becomes a scene (scene, scene2, ...) placed at the position of the axes
  • ax.plot lines become scatter3d traces with their color, width, dash style and markers; marker symbols scatter3d lacks (triangles, stars) export as circles, and unlabeled lines stay out of the legend
  • scene axes carry the axis limits, labels with their font size and color, log scales, line and tick label colors, and pane colors (blended over the background when translucent), and are hidden when the axis is turned off
  • the camera follows view_init (elevation, azimuth and roll), and the box aspect sets the scene aspect ratio
  • subplot titles are centered on their scenes
  • 3D artists without a scene conversion (scatter, surface, bar, text) warn and are skipped

Before: TypeError: 'NoneType' object is not callable.
After: 3D lines render as scatter3d traces in a plotly scene.

Snippet to reproduce:

import matplotlib

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

t = np.linspace(0, 10, 200)


def spiral(fig):
    ax = fig.add_subplot(projection="3d")
    ax.plot(t, np.sin(t), np.cos(t), "r--")
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")


def view_angles(fig):
    ax = fig.add_subplot(projection="3d")
    ax.plot(np.cos(t), np.sin(t), t)
    ax.view_init(elev=15, azim=40)


cases = {
    "spiral": spiral,
    "view_angles": view_angles,
}

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

    p = tls.mpl_to_plotly(fig)  # TypeError before this change
    p.write_image(f"{name}_plotly.png")
matplotlibplotly beforeplotly after
spiral_mpl
- TypeError: 'NoneType' object is not callable
spiral_plotly
view_angles_mpl
- TypeError: 'NoneType' object is not callable
view_angles_plotly

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

Copy link
Copy Markdown
Contributor

Thanks for putting this together! I've added it to my review list.

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