PAYATHON 2026

如何将 Plotly Scatter3d 导出为真正的矢量 SVG?

数据阿明

现有的 go.Scatter3d 不能通过 Plotly 自带的导出配置,直接生成线条和标记全部为矢量元素的 SVG。即使图中只有几条线和几个圆形标记,也有同样的限制。

为什么 SVG 里会嵌入位图?

Scatter3d 使用 WebGL 渲染。Plotly 导出 SVG 或 PDF 时,会将这部分渲染结果以位图形式嵌入文件。Plotly 静态图像导出文档中说明了这一行为。

导出的文件仍是有效的 SVG,但其中的 3D 图形不能作为独立的矢量路径编辑。无论是否使用曲面和光照,也无论图形有多简单,Scatter3d 的渲染方式都不会改变。

在 Python 中使用下面的调用也一样:

fig.write_image("figure.svg")

改用 PDF 同样不能让 WebGL 内容变成矢量路径,render_mode="svg" 也不能作为 go.Scatter3d 的矢量后端开关。

只需要固定视角时,可以转成二维图形

如果可以接受重新构造固定视角的图形,可以先将三维坐标投影到二维,再使用 go.Scatter 导出。普通二维 go.Scatter 支持 SVG 矢量输出,参见官方说明。

下面的示例针对题目中的两条 trace,假设原始 fig 已创建。它以 (-x, z) 为二维坐标,得到从正 y 方向观察的简化正交投影:

import plotly.graph_objects as go

flat_fig = go.Figure()

for trace in fig.data:
    flat_fig.add_trace(go.Scatter(
        x=[None if value is None else -value for value in trace.x],
        y=list(trace.z),
        mode="lines+markers",
        line=dict(
            color=trace.line.color,
            width=trace.line.width,
        ),
        marker=dict(
            color=trace.marker.color,
            size=trace.marker.size,
            symbol="circle",
        ),
        showlegend=False,
    ))

flat_fig.update_layout(
    template="none",
    width=800,
    height=800,
    margin=dict(l=60, r=60, t=60, b=60),
    xaxis=dict(visible=False),
    yaxis=dict(visible=False, scaleanchor="x", scaleratio=1),
)

flat_fig.write_image("figure_vector.svg")

这段代码输出二维矢量图,但不会精确复现原来的 3D 画面。它忽略了 y 方向的深度,也没有处理透视和深度遮挡;两条 trace 按添加顺序绘制。线宽和标记尺寸沿用了原参数,视觉效果仍可能不同。

要严格复现原图,还需要处理坐标归一化、相机变换、投影和遮挡,相当于实现另一套渲染流程。Plotly 没有导出参数可以代替这些工作。题目中已经采用的自定义渲染方案,是解决这类需求的一种可行方式。

必须保留原始外观时怎么办?

如果主要需求是保留当前 3D 视图,可以导出更高分辨率的 PNG:

fig.write_image(
    "figure.png",
    width=1200,
    height=1200,
    scale=3,
)

width、height 和 scale 可以控制导出尺寸与分辨率,具体含义见 Plotly 导出尺寸说明。提高分辨率能改善放大或打印效果,但不会生成可编辑的矢量路径。

上述 write_image 示例需要配置可用的静态导出环境,依赖安装可参考 Kaleido 安装说明。另外,题目代码中 for 循环下的三条赋值语句需要缩进后才能在 Python 中执行;这个语法问题与 SVG 导出限制无关。

备注:内容仅供参考。