如何将 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 导出限制无关。
备注:内容仅供参考。
