写论文的人都知道一个魔咒:正文排版折腾到半夜终于整齐了,结果一张三维曲线图插进来,字体、线宽、配色全变味,导师一句话“这张图风格不对,重画”,前功尽弃。LaTeX 环境下的 pgfplots 宏包,就是专门打破这个魔咒的工具。“Latex-pgfplots绘制3维曲线图”这件事,说穿了就是在 TeX 文档内部用 \addplot3 命令直接生成参数曲线、空间轨迹、函数曲面这类三维图形,不需要外挂任何画图软件,坐标轴字体、刻度密度、线条粗细天然和正文统一。这篇文章适合两类人:一类是论文里需要 3D 曲线或者空间轨迹图、但还在用外部工具导图再插入文档的苦逼研究生;另一类是刚接触 pgfplots、想直接系统上手三维绘图的新手。看完你应该能画出能直接放进论文的图,也知道编译报错时从哪里下手。
1. 为什么非要在 LaTeX 里画三维曲线图
1.1 图表与正文风格一致这件事,比想象中重要
写学术文档时最烦的往往不是画图本身,而是图放进正文之后的“违和感”。拿 matplotlib 举例,默认字体是 DejaVu Sans,坐标轴数字、图例、标签印出来之后和 LaTeX 正文的 Computer Modern 字体放在一起,总有一种“贴了张外来户口”的感觉。倒不是说 matplotlib 画不出高质量的图,而是你为了一张图要额外配置字体、刻度方向、标签位置、线宽体系,这是一整套琐碎的活。
pgfplots 最核心的价值在于所有文字元素默认继承文档字体,正文是什么字体,图里数字就是什么字体,正文用什么磅值,坐标轴标签就是什么磅值,不存在“图内字体和正文字体打架”的问题。
还有一个实际操作中特别舒服的点:数据更新之后,不需要重新去外部软件导出图片替换文件。整个文档编译一次,图就跟着变了。写论文期间数据反复修改是常态,用外部工具的话,工作目录里经常堆满finial_v2.png、final_ver3.png这种文件名,最后自己都不知道插的是哪版。用 pgfplots 之后,图表代码和数据都在 .tex 工程里,版本跟着编译走,基本不存在插错图的问题。
1.2 和其他绘图工具的取舍
很多初学者第一反应是:三维曲线图为什么不直接用 Python 画?对比一下就知道 pgfplots 的位置在哪:
| 方案 | 字体统一 | 数据修改后流程 | 学习成本 | 三维表达能力 |
|---|---|---|---|---|
| matplotlib 手动导图 | 差,需要额外配置 | 改数据 → 重新出图 → 插入文档 | 中 | 强,但参数繁多 |
| Origin / MATLAB | 差,导出后常需后期修图 | 同上,且软件较重 | 中高 | 强 |
| pgfplots | 好,天然继承 | 改数据 → 重新编译文档 | 初始有门槛 | 够用,曲线曲面都支持 |
pgfplots 的绘图方式更接近声明式:你告诉它“画一条从哪到哪、用什么颜色、多少采样点的曲线”,而不是一步步操作坐标系和画笔。这套思路有学习曲线,一旦适应了,习惯之后写起来比点鼠标快很多。
但这不代表 pgfplots 全能。它擅长的是学术出版风格下的二维曲线、三维曲线、曲面和部分数据图表。如果你要做交互式 3D 可视化、带复杂光照的渲染图,或者动辄几百万个数据点的云图,那还是老老实实回到专业可视化工具。单就论文里的三维曲线图而言,pgfplots 是非常够用的。
1.3 三维曲线图在论文里的典型应用场景
三维曲线图不是天天用,但一旦用上往往就是论文里的核心图。常见的有三类:
- 参数曲线,例如螺线、悬链线、空间运动轨迹。这类图最能体现三维曲线绘制的价值,因为外部工具导出的三维轨迹图经常因为坐标轴方向不明确而被读者误解。
- 动力学系统仿真结果的三维投影,比如相空间轨迹。这类图需要多条曲线对比,pgfplots 的多曲线绘制和图例支持很顺手。
- 传感器或实验设备采集的三维路径数据。这类图通常需要把散点连成线,或者散点与线同时展示。
这些场景的重合点是:图要精确、风格要克制、信息要干净。pgfplots 默认的坐标轴边框和刻度样式天生就贴近学术出版需求,不需要额外“美化”。
2. 环境准备与最短可用模板
2.1 需要准备哪些东西
画三维曲线图需要的最小环境其实就是一个带 pgfplots 宏包的 LaTeX 发行版。Windows 上一般装完整发行版,自带 pgfplots 及其依赖;macOS 或 Linux 的发行版通常也完整包含。
实际操作中我建议额外确认两件事:
- LaTeX 发行版版本不要太老,pgfplots 是一个持续更新的宏包,老版本在三维坐标轴处理和一些新特性上差异很大。建议至少使用近三到五年内更新的发行版,并显式设置
\pgfplotsset{compat=1.18}这类兼容版本号,让宏包按一致的规则渲染坐标轴。 - 如果只是画普通的三维曲线和曲面,不需要额外安装任何程序,pdflatex 直接编译即可。只有用到 gnuplot 这类外部计算引擎做隐式曲面或复杂依赖计算时,才需要额外安装 gnuplot,并且编译时加
-shell-escape参数。
2.2 一个能跑通的最短模板
先给你一个可以直接复制编译的最短模板,后面所有例子的基础结构都在这里面:
\documentclass{standalone} \usepackage{pgfplots} \pgfplotsset{compat=1.18} \begin{document} \begin{tikzpicture} \begin{axis}[ view={60}{20}, width=12cm, height=8cm, xlabel={$x$}, ylabel={$y$}, zlabel={$z$}, grid=major ] \addplot3[ domain=0:12.566, samples=150, line width=1.1pt, color=blue!70!black ] ({cos(deg(x))}, {sin(deg(x))}, {x/2}); \end{axis} \end{tikzpicture} \end{document}这篇文章里后续的实例都会基于documentclass为standalone的结构来写,单独编译时输出一个独立 PDF,然后再用\includegraphics插入主文档,或者直接把整段tikzpicture放进主文档正文。前者编译隔离性好,后者更符合“图随文走”的理念,看个人习惯。
2.3 \addplot3 的三种数据来源,必须分清
\addplot3是画三维图的入口,数据来源有三种方式,刚开始最容易混淆:
- 表达式形式:直接在命令后写带括号的三个坐标表达式,例如
({cos(deg(x))}, {sin(deg(x))}, {x/2})。这类方法适合绘制函数曲线和参数曲线,代码可读性最好。 coordinates形式:在命令后直接写coordinates {(0,0,0) (1,1,0) (2,1,1)},适合绘制少量关键点之间的连线。table形式:从外部文件读取数据,例如\addplot3 table {curve.dat};。实验数据、仿真数据这类量大且不易写成表达式的场景用这种方式。
三种方式都可以叠加颜色、线宽、标记、图例等样式。实际项目中经常混用:参数曲线用表达式,实验数据用 table,示意图用 coordinates。没有谁比谁高级,按数据来源选即可。
2.4 编译命令与常见环境坑
编译三维 pgfplots 图形和普通 LaTeX 文档基本一样,直接用 pdflatex 编译即可:
pdflatex main.tex不需要额外加参数。只有两种例外:
- 文档里用了 gnuplot 引擎计算图形,需要改成
pdflatex -shell-escape main.tex。注意这个参数会允许 LaTeX 调用外部程序,在线编译平台或者公司内部环境有时禁用,需要先确认。 - 文档包含中文标签时,用 XeLaTeX 或 LuaLaTeX 编译,并加载 ctex 宏包。pgfplots 的三维图对中文字体没有特殊要求,只要主文档能显示中文,图内标签就能跟着显示。
新手经常卡在路径上:table读取外部文件时,若文件不在 .tex 文件同级目录,会直接报错“Package pgfplots Error: Could not read table file”。解决办法一是把数据文件放在主文档同目录,二是写相对路径,例如table {data/run1.dat}。这也是三维曲线图最常见的起步坑之一。
3. 三维曲线图的四个关键参数,决定成图质量
3.1 view 视角:先决定读者看什么
三维图比二维图多一个自由度:你从哪个角度看它。这直接影响读者能不能一眼看明白曲线走势。view参数接收两个角度值,格式是view={azimuth}{elevation}。
第一个值是方位角,第二个是仰角。默认值是view={25}{30}——也就是相机从偏右上方约 25 度方位、30 度仰角的位置看图形。这个视角在大部分情况下是合理的,但不同曲线需要微调。螺旋线这类沿一个方向延伸的曲线,增大方位角到 60 度到 80 度之间,往往能更好展示螺距变化;而平面空间中的轨迹曲线,仰角压低到 15 度到 20 度能凸显曲线在平面上的投影关系。
一个实用技巧:把view当成最终审美参数来调,而不是一开始就纠结。先把曲线画出来,确认坐标范围没爆炸,然后花两分钟试几组视角,选最能展示曲线特征的。
3.2 坐标轴与网格:信息清晰比花哨重要
三维图的坐标轴默认是完整边框,axis lines=box。如果曲线本身跨度大、三个轴量级差距明显,建议用axis equal相关选项来强制三个轴的单位长度一致。三维环境里通常用unit vector ratio*=1 1 1,避免同一个圆因为坐标轴缩放被画成椭圆。这个细节在实际使用中最容易被忽略,但最容易被懂行的人一眼看出。
网格建议打开grid=major,并且把网格线调淡一点:
grid=major, grid style={gray!30, line width=0.4pt},三维图的网格是读者判断空间位置的重要参考,但网格线太醒目会抢掉曲线本身的风头。灰色和细线是比较安全的选择。xlabel、ylabel、zlabel建议全部显式写上,因为三轴缺失标签的三维图在论文里几乎等于废图,读者完全无法判断坐标含义。
3.3 曲线样式:线宽和采样点数量直接挂钩
三维曲线默认就是一条线,但这条线的质量受两个参数影响:samples和line width。
samples控制采样点数量。二维图 50 个采样点可能已经很平滑,三维参数曲线必须更多。原因是三维曲线在投影后曲率变化更复杂,采样点太少会看到明显的折线棱角。实践经验:
- 简单曲线,比如抛物线轨迹,
samples=60足够。 - 多圈螺旋线,建议
samples=160起步。 - 带急剧转折的复杂曲线,
samples=300都不算多。
line width直接对应出版风格的“线宽偏好”。期刊一般要求图的线条墨迹清晰但不过分粗壮,0.8pt到1.2pt是安全区间。如果要区分多条曲线,用不同颜色加不同线型(实线、虚线、点划线),同时也要考虑灰度打印场景,颜色区分之外最好辅助线型区分。
3.4 颜色映射:三维曲线的彩色表达不是越花越好
三维曲线经常需要表达第三维度的数值信息,例如高度或者速度。如果只画一条均匀颜色的线,这部分信息就丢失了。pgfplots 支持用颜色映射沿曲线表达数值变化:
\addplot3[ mesh, point meta=x, colormap/viridis, samples=150, line width=0.8pt, domain=0:12.566 ] ({cos(deg(x))}, {sin(deg(x))}, {x/2});核心是两条:一是加mesh选项让线条按顶点着色,二是用point meta=...指定颜色映射的依据,x、y、z都可以,甚至可以使用显式数值。
学术图最稳的颜色映射是viridis和hot,颜色区分度好且彩色打印时不刺眼。花哨的霓虹色系建议留给演示 PPT,论文场景里颜色越克制越专业。如果整条曲线不需要表达额外维度,就用单色,完全没有必要默认加一个彩虹色。
4. 四种典型三维曲线图的实战演示
4.1 参数曲线:空间螺旋线
三维曲线里最经典也最常用的是参数曲线,螺旋线就是最好的入门例子。xyz 三个坐标都写成参数 t 的函数,画出来就是空间中的一条立体轨迹:
\begin{tikzpicture} \begin{axis}[ view={60}{25}, width=12cm, height=8cm, xlabel={$x$}, ylabel={$y$}, zlabel={$z$}, grid=major, grid style={gray!30}, unit vector ratio*=1 1 1 ] \addplot3[ domain=0:12.566, samples=180, line width=1.1pt, color=blue!70!black ] ({cos(deg(x))}, {sin(deg(x))}, {x/2}); \end{axis} \end{tikzpicture}这些细节值得专门说明:domain设到 12.566,即 4π,对应螺旋线绕两圈。samples=180保证两圈曲线足够平滑。坐标表达式里的deg(x)把弧度转成度,因为 pgfplots 的三角函数默认按度数计算,这里不转换画出来的曲线是完全错误的形状。unit vector ratio*=1 1 1确保 x、y、z 三个方向单位长度一致,否则原本是正圆的螺线投影会被压成椭圆。
4.2 显式函数曲线:两个坐标作为另一个坐标的函数
有时候曲线的 x 坐标能直接作为参数,y 和 z 分别是 x 的函数。这种曲线在三维空间中往往展示了沿某轴传播时的高度和偏移变化。比如下面这条:沿 x 轴前进,y 按正弦波动,z 随 x 平方上升:
\addplot3[ domain=-2:2, samples=120, line width=1pt, color=teal!70!black ] (x, {sin(deg(x))}, {x^2});这里的坐标三元组第一个直接写x,说明 x 轴坐标就是自变量;第二个坐标是 x 的正弦函数;第三个是 x 的平方。pgfplots 的表达式三元组里可以使用当前参数变量,也可以用其他表达式组合。这条曲线在三维图里看起来像一条在竖直方向上抬升的波浪线,能很好的表达“轨迹在不同高度上的摆动”。
如果数据点本身来自实验而非公式,想对离散数据做平滑连线,可以配合smooth选项:
\addplot3[ smooth, tension=0.4, line width=1pt ] table {traj.dat};smooth选项在三维曲线里同样生效,帮助减弱数据点间的锯齿感。但要留意它只是基于样条插值的视觉平滑,不会修改底层数据,也不适合科研数据再做“虚假修饰”的用途。
4.3 外部数据文件:实验轨迹的正确打开方式
实际项目中三维曲线图的数据多数来自仿真导出或仪器采集,这时候用table数据来源最合适。数据文件 curvetrack.dat 的格式并不复杂,三列数据分别代表 x、y、z,列间可以用空格或制表符分隔:
0.0 0.0 0.0 0.1 0.35 0.2 0.2 0.68 0.4 0.3 0.95 0.7 0.4 1.10 1.1读取并画线:
\addplot3[ line width=1pt, color=orange!80!black ] table {curvetrack.dat};如果还需要强调每个数据点的原始位置,加only marks只显示标记点,或者用mark=*、mark size=1.2pt在连线的同时保留点标记。数据文件用逗号分隔时,记得告诉 pgfplots 分隔符:
\addplot3 table [col sep=comma] {curvetrack.csv};多段轨迹可以用空行分隔数据块,pgfplots 会自动在空行处断开曲线。
4.4 多曲线对比:一次看清三组轨迹差异
对比型三维图在实际论文中出场率很高,比如两种方案下的仿真轨迹对比。多曲线图的关键点在于颜色区分和图例。把三条轨迹放到同一坐标系中:
\begin{tikzpicture} \begin{axis}[ view={65}{25}, xlabel={$x$}, ylabel={$y$}, zlabel={$z$}, grid=major, width=13cm, height=8.5cm, legend pos=north east, legend style={font=\small, draw=gray!50} ] \addplot3[domain=0:12.566, samples=150, line width=1pt, color=red!70!black] ({cos(deg(x))}, {sin(deg(x))}, {x/2}); \addlegendentry{方案A} \addplot3[domain=0:12.566, samples=150, line width=1pt, color=blue!70!black] ({cos(deg(x))}, {sin(deg(x))}, {0.3 * x}); \addlegendentry{方案B} \addplot3[domain=0:12.566, samples=150, line width=1pt, dashed, color=black!80!green] ({1.2*cos(deg(x))}, {1.2*sin(deg(x))}, {x/2}); \addlegendentry{方案C} \end{axis} \end{tikzpicture}三条曲线在空间中的走向差异一目了然。这里推荐的组合是:红色、蓝色、墨绿色,配合一条虚线线型,在灰度打印时也比较容易区分。图例位置north east是三维图中最不遮挡曲线上半部分的选择,如果曲线在右上角区域密集,可以换成south east或者outer north east。
5. 常见问题与避坑指南
5.1 “Dimension too large” 错误是怎么回事
这是 pgfplots 三维绘图里出现频率最高的报错。原因很简单:TeX 的算术能力上限是固定的,当某个坐标数值过大时,底层计算直接溢出。实验数据常见的情况是时间戳数值上万、位移数值上千,pgfplots 在绘图时内部会把坐标转换成 pt,超出范围就炸。
解决办法不是硬调任何参数,而是把数据缩放到合理量级。最直接的方式是在读入数据时做归一化:
\addplot3[ line width=1pt ] table [ x expr=\thisrow{time}/1000, y expr=\thisrow{val1}/100, z expr=\thisrow{val2}/100 ] {data.dat};坐标轴标签里补上对应的单位说明,比如“(×10^3)”。这也是我在实际项目里最常用的处理方式,比把整个 data 文件乘以一个系数然后另存一个新文件要干净得多。
5.2 三角函数角度模式导致的“离谱曲线”
画出来的曲线和理论形状完全对不上,九十成是三角函数角度模式问题。LaTeX 的三角函数概念上默认度数是常见惯例,pgfplots 延续了这一点。画参数曲线时,自变量通常习惯用弧度,这时必须用deg(x)显式转换。
看这两行的区别:
% 错误:cos(2) 被理解为 2 度而不是 2 弧度 \addplot3[domain=0:2*pi] ({cos(x)}, {sin(x)}, {x}); % 正确:deg(x) 把弧度转成度,再交给三角函数 \addplot3[domain=0:2*pi] ({cos(deg(x))}, {sin(deg(x))}, {x});domain=0:2*pi中 pi 是数值常数,x 在 0 到 6.28 之间移动,这本身是弧度值。但如果不转成度,cos 函数接收到的是 0 到 6.28 度的小角度正弦变化,画出来的“螺旋”完全是歪的。这一点哪怕是有经验的使用者也偶尔会踩中,建议把它写进自己的常用代码模板里。
5.3 编译速度慢与 shell-escape 边界
三维图形的编译速度通常慢于二维图形,主要原因是采样点数量大。samples=300的三维曲线配合grid=major时,一个图编译十到二十秒是正常现象。如果文档里有多张三维图,总编译时间会明显拉长。
优化思路有三条:
- 降低 samples 到刚好平滑即可,不要盲目堆到很高。
- 网格改成
grid=major就已经够用,不必每个小刻度也画网格。 - 文档写作期间用低采样率版本快速预览,定稿前再恢复高采样率正式编译。
-shell-escape参数只在确定使用 gnuplot 依赖时才加,平时不要加。随随便便打开外部程序执行权限存在安全隐患,而且多数在线编辑器也不支持这个参数。
5.4 三维线条遮挡与渲染层次问题
三维图的一个先天限制是:曲线本质上是一堆线段投影到平面上,pgfplots 默认会大致按深度排序,但这种排序不总是完美的。当一条粗曲线和曲面相交、或两条曲线交叉角度极小时,视觉上的遮挡关系可能出现细微错误。
经验做法是尽量避免在同一 3D 图中同时绘制曲面和密集的粗曲线。如果确实要表现曲线与曲面的相交关系,把曲线分成两段绘制,一段在曲面前、一段在曲面后,人为控制绘制顺序,或者干脆适当调整视角让相交处不处于尴尬位置。对论文图表来说,让读者误解空间位置比牺牲一点点“真实感”更得不偿失。
最后整理一张速查表,碰到问题直接对照:
| 报错或异常现象 | 可能原因 | 处理办法 |
|---|---|---|
| Dimension too large | 坐标数值量级过大 | 用 x expr 等方式缩放数据 |
| 曲线形状完全不对 | 三角函数没做弧度转角度 | 给参数加 deg(x) |
| 读取数据失败 | 数据文件路径不对或分隔符不符 | 检查路径、检查 col sep 参数 |
| 编译速度极慢 | samples 过高、网格过密 | 降低 samples,网格只留 major |
| 三条曲线颜色难区分 | 只用颜色不换线型 | 增加 dashed/dotted 线型 |
| 圆变成椭圆 | 三维坐标轴单位长度不一致 | 设置 unit vector ratio*=1 1 1 |
这些坑每一个我都亲手踩过。尤其是 deg(x) 那个问题,我第一次画螺旋线时完全没意识到,检查了半小时代码才反应过来。
我个人在反复写三维曲线图时的最大体会是:pgfplots 这条路刚开始略陡,但一旦把几个基础参数吃透,后续写图会越来越顺手。最后再分享一个小技巧:把常用的三维绘图参数模板存成一个文件,例如pgfplots_3d_settings.tex,里头写好 view、网格、单位比例、坐标轴样式,每次画图直接调用,既保证文档内所有三维图风格统一,又省掉重复敲参数的体力活。写论文这件事,技术上的偷懒往往会产生更好的结果。