论文图表
三条硬规则
- 流程类图先问语法,再动笔。GB 1526 流程图(跑道形起止 + 直角矩形)和 UML 活动图(实心圆起、带环圆止 + 圆角矩形)是同一件事的两套符号,结构一样、长相不同;选哪套由用户定,不归你猜。图种一判定为流程/活动类,先问一句再画,问法见下文。一篇只能选一套,中途要换就整批换。评审未必因"没按国标"扣分,但一定会因前后不一致质疑。
- 图元超过 7 个就不许手写坐标。8 个以上还靠肉眼算 x/y,几乎必然出现连线穿框、层距忽宽忽窄、分支左右不对称——而且图渲染出来之前你自己看不出来。交给
scripts/layout.py(只写结构)或 PlantUML(只写语法)。 - 校验不通过不算画完。
check_diagram.py退出码不为 0 就继续改,最多 3 轮;3 轮仍不过说明规模或通道选错了,降规模重来,别在错的路线上磨。
手写坐标只留给"单个符号"和"极简示意"(≤7 个图元,一屏能看全)。这是唯一能靠眼睛保证正确的规模。
流程类图:先问,再画
判断完"该画什么图"之后,如果图种是流程 / 活动类,先停下来问用户用哪套符号,不要自己替他定。
- 要问的:流程图、业务流程、处理流程、操作流程、活动图、泳道图。
- 不用问的:用例图、类图、时序图、状态图、E-R 图、部署图、数据流图——这些只有一套符号,问了是废话。
照抄这段,别自己发挥:
> 这张图有两种合规画法,结构一样、符号不同,你选一个:
> 1. UML 活动图——实心圆起、带环圆止、圆角动作框;软件工程教材和 UML 作业的默认画法。
> 2. GB 1526 流程图——跑道形起止(框内写"开始/结束")、直角矩形处理、菱形判断;国内学位论文与答辩评审更认这套。
>
> 题目里写"活动图"就选 1、写"流程图"就选 2;你定了之后,全篇的流程类图都按这一套。
几条配套约定:
- 问一次就记住。同一篇里后面的流程类图直接沿用,不要每张图问一遍;交付说明里写一句"本篇流程图采用 ×× 符号"。
- 用户说"你定"/"随便":按题目用语走(题目写"活动图"→ UML,写"流程图"→ GB 1526);题目也没提 → GB 1526,并用一句话说明理由(国内评审看这套)。
- 画到一半才发现:不要偷偷换。停下来问,换就整批一起换,别留两张 UML 两张国标。
- 问不出口的场合(比如用户只要一张图、明确说别问):按上面"你定"的规则选,并在交付说明里写明选了什么、为什么。
生成通道选路
先定语法,再选通道。 流程类图只有两套合法写法,一篇论文里只能挑一套:
| 起止 | 处理框 | 判断 | |
|---|---|---|---|
| GB 1526 流程图 | 跑道形(框内写"开始/结束") | 直角矩形 | 四边菱形 |
| UML 活动图 | 实心圆 / 带环圆(不写文字) | 圆角矩形 | 四边菱形 |
差的是符号,不是结构;两套混用比用错更致命。先按上一节问用户;问不到时按题目用语走,题目也没提就按 GB 1526。
layout.py 的 linear 两套都能出——写 "style": "gb1526" 是国标,不写是 UML——两边的判定都是四边菱形。
注意 UML 样式下别用 shape: "terminator" 去写开始/结束,那是国标的跑道形,混进来就成了两套符号(脚本会打 ⚠)。
| 图种 / 场景 | 通道 | 坐标由谁定 |
|---|---|---|
| 流程图 · GB 1526 符号 | scripts/layout.py(linear + "style": "gb1526") |
脚本 |
| 活动图 · UML 符号(实心圆起、带环圆止、圆角动作框) | scripts/layout.py(linear,不写 style) |
脚本 |
| 流程图 / 活动图 · 要有泳道、并发分叉条、嵌套判定 | PlantUML activity |
引擎(判定框会是六边形,见下) |
| 状态图 | PlantUML | 引擎 |
| 时序图 · 要统一线宽字号、激活条位置可控 | scripts/layout.py(sequence) |
脚本 |
时序图 · 需要 alt / opt / loop 组合片段 |
PlantUML | 引擎 |
| 用例图 · 标准版 | PlantUML | 引擎 |
| 用例图 · 主用例一列 + 被包含用例右移一层 | scripts/layout.py(usecase) |
脚本 |
| E-R 图 | PlantUML entity(IE 鸦脚) |
引擎 |
| 单个符号 / 极简示意(≤7 图元) | 手写 SVG | 你,且只有这时可以 |
判定框:PlantUML 画不出菱形。 ConditionStyle Inside 出来的是六边形(上下边平),实测任何字数都是 6 个顶点;ConditionStyle Diamond 虽是四边菱形,但条件文字每次都压在流入箭头上。要菱形就走 layout.py 的 linear,它的判定本来就是四边菱形、文字在内。
PlantUML 出图发虚或错位时,先调 skinparam(monochrome true、shadowing false、linetype ortho),不要急着换工具。
结构交给脚本,坐标不要自己算
scripts/layout.py 吃一份结构 JSON,吐出画好的 SVG。它负责分层、分支左右、汇合、回溯绕行、viewBox——这些全是"看一眼好、看二十眼看不过来"的活。
python scripts/layout.py 流程.json -o 图4-2.svg
python scripts/preview.py 图4-2.svg -o 图4-2.png # 自查用,别交付
{
"layout": "linear",
"style": "gb1526",
"nodes": [
{"id": "s", "text": "开始", "shape": "terminator"},
{"id": "a", "text": "采集数据", "shape": "process"},
{"id": "d", "text": "是否完整?", "shape": "decision"},
{"id": "x", "text": "标记异常", "shape": "process", "col": 1},
{"id": "b", "text": "特征提取", "shape": "process"},
{"id": "e", "text": "结束", "shape": "terminator"}
],
"edges": [
["s", "a"], ["a", "d"],
{"from": "d", "to": "b", "label": "是"},
{"from": "d", "to": "x", "label": "否"},
["x", "b"], ["b", "e"]
]
}
要点:shape 取 start / end / process / terminator / decision / io;判定节点的分支目标写 col: -1(左)或 1(右),只写一个时另一个自动落到中线;两条都不写照出边顺序摊成左右;多层汇合到中线会自动画汇合点和单根主干;同层同列两个节点直接报错,不会画出一张"看起来排好了"的重叠图。
两套符号体系,靠 style 切:不写 style 是 UML 活动图(圆角动作框、start 实心圆、end 带环圆);写 "style": "gb1526" 是国标流程图(process 直角矩形、起止用 terminator 跑道形且要自己写"开始/结束"、判定本来就是菱形)。一篇论文里只能选一套。
两条排布规矩,违反了就出怪图:
- 分支节点要排在汇合点的上一层。两条支路长度不等时,短的那条用
"rank"显式钉到"汇合点 rank − 1",否则它会和汇合点同层,线绕右侧通道兜回来,看着不像汇合。 - 判定往某一侧引出的横线,走的是判定自己那一层,所以目标列在这一层必须是空的。两个判定共用右列时,把第二个的目标换到左列(左右交替),或者把右列上方的节点错开一层。
用例图同理:"layout": "usecase",节点 col: 0 是主用例列、1 是被包含列,actors 列参与者,边的 kind 取 assoc / include / extend,连线和边界矩形全自动。尺寸、字号、线宽与 assets/examples 里的样板一致。
时序图用 "layout": "sequence":participants 按出现顺序列参与者,messages 按发生顺序写,kind 取 call(实线实心箭头)或 return(虚线开放箭头),from 与 to 相同就是自调用。激活条自动加在被调用过的一端,从头到尾只发消息的参与者保持裸生命线。
起步别从空白写,改 assets/templates/ 里的样板:activity-linear.json(14 图元、两处分支汇合、一条回溯线)和 usecase-three-column.json。加节点只是加两行 JSON。
脚本报错就改 JSON,不要回头去手改 SVG——手改一次,后面所有重排都作废。
它能画什么、不能画什么
layout.py 不是通用图描述语言,是两种论文版式的生成器。它的价值恰恰在于不通用:层级、分支左右、间距由 JSON 显式指定,所以出来的图横平竖直、样式一致,模型也没有机会把坐标算歪。换成真正的通用布局引擎(DOT、自动排布),排得下任何图,但排不出这套模板的样子。
| 版式 | 覆盖 | 硬限制 |
|---|---|---|
linear |
流程图、UML 活动图:单列主链、判定左/右分支、多路汇合到中线、回溯绕行;style 可切 GB 1526(直角矩形 + 跑道形起止)或 UML 活动图 |
只有三列(-1 / 0 / 1),四路分支排不下;不支持泳道、并行分叉条、注释框、嵌套判定(内层 if 排不了,要拆成两个判定串行) |
usecase |
用例图:左参与者 + 主用例列 + 被包含列 | 参与者一律排左侧,不支持右侧次要参与者;两人的关联用例纵向交叠时直接报错(拆图或换通道)。参与者→用例一律直线:主用例一多,直线会从侧面扎进中间的椭圆,脚本会自动把用例列右推(画布随之变宽)直到不碰任何邻居;推满 420px 仍避不开就直接报错要求拆图(每张不超过 5 个),不会产出穿框的图 |
sequence |
时序图:参与者列、生命线、激活条、调用/返回、自调用 | 参与者横向一排,每人占 118px,六个就到 700px(A4 正文栏约 600);不支持 alt/opt/loop/par 组合片段 |
下面这些一律走 PlantUML,别拿 layout.py 硬凑:类图、状态图、部署图、E-R 图、数据流图、架构图、泳道图、带组合片段的时序图。
判断口径就一句话:图的结构能不能用"一条主链 + 左右分支"或"一列主用例 + 一列被包含"讲完。能,走脚本;不能,换通道。硬凑的结果是脚本报一串冲突,或者出来一张结构对、读不懂的图。
要加版式就加一个 Layout 类(照 LinearLayout 的结构,一百多行),不要把现有 DSL 改成通用 DSL——一旦允许任意 x/y 写进 JSON,"坐标不归模型管"这条就破了,图的规模上限会跟着塌回手写水平。
线要直、粗细要匀
PlantUML 默认样式有两个天然缺陷,评审一眼就能看出来:
- 曲线绕行:默认样条布线,长距离 include 画出 S 弯,跨边界关联绕大弧线。加
skinparam linetype ortho强制横平竖直。 - 线宽不匀:默认椭圆边框 0.5、连线 1.0、actor 小人 0.5,同一张图三种粗细。所有
<图种>BorderThickness拉齐到 1.0(完整清单见references/snippets.md样板),三个易漏的:usecaseBorderThickness、actorBorderThickness、sequenceParticipantBorderThickness。
例外:时序图生命线 0.5 细虚线是 UML 惯例(辅助线细于消息线),actor 胶囊和数据库圆柱的 1.5 是 PlantUML 硬编码的装饰描边,看到不用管。这两类描边是 #181818,被"只统计纯黑"的口径整体豁免——代价是线宽检查对 PlantUML 产出实际不生效,线宽是否统一要靠 skinparam 那套模板保证,不是靠事后检查。
用例图的箭头
参与者与用例之间写 -->,不要写 --:
@startuml
left to right direction
actor 专家
rectangle 农事决策系统 {
usecase "查看任务中心" as UC1
usecase "识别结果复核" as UC2
}
专家 --> UC1
专家 --> UC2
@enduml
-- 画出来是一条没有箭头的直线,导出后与普通连线无异;评审最常见的读法是"关系没画清、方向不明"。论文场景一律带箭头,箭头指向用例,不指向参与者。include / extend 用 ..>,方向指向被依赖的那一个。
手写 SVG 时同理:<line> 必须带 marker-end,且终点落在椭圆边界上(见下文交点公式)。
工作流
- 定图种,再定语法。先判断这张图属于哪一类(用例 / 类 / 时序 / 活动 / 状态 / E-R / 部署 / 数据流)。
落到流程·活动类就按上一节先问用户要 UML 活动图还是 GB 1526 流程图,问完记住,全篇沿用;其它图种跳过这一步。 - 定图号:按章编号
<章>-<序>,如图 4-2。批量改号必须从大到小倒序替换,正序会互相覆盖。 - 数图元:> 7 个 → 走
layout.py或 PlantUML;≤ 7 个且要精细版式,才允许手写。 - 生成并落到文件(
.json/.puml/.svg),不要只在对话里贴。 - 过校验:
check_diagram.py退出码 0 才继续。有问题改源文件再重跑,最多 3 轮。 - 交付三样:
- 图文件
- 题注:图 4-2 系统部署图(全角空格分隔,置于图下方居中)
- 正文引用句:如图 4-2 所示,……(必须先引后出)
- 另加一句说明本篇流程类图用的是哪套符号(UML 还是 GB 1526),方便日后返工
PlantUML 产出也要过校验:导出 SVG(-charset UTF-8 别省,否则中文直接报语法错)后跑一遍。图元越界、文字、重叠、连线穿框对它照样有效;但箭头贴合和连线交叉对它不生效——PlantUML 用独立的小多边形画箭头,没有 marker-end 可认。它的图还得自己看一眼走向。
校验脚本
python scripts/check_diagram.py 图.svg
python scripts/check_diagram.py 页面.html --grayscale
支持 .svg 与 .html(HTML 会逐个检查内嵌 SVG)。发现问题时退出码为 1。
| 项 | 判据 | 典型成因 |
|---|---|---|
| 箭头未贴合 | 箭尖离最近图元 > 1.5px | 终点写成"框边 − 2~4px" |
| 箭头悬空 | 箭尖 9px 内没有任何图元 | 目标图元被改名/挪走,线还指着老位置 |
| 图元越界 | bbox 超出 viewBox(含 line / path) | 加内容后忘放大画布 |
| 文字溢出 | 估算宽度超出 viewBox | 换了个更长的词 |
| 文字挤爆所在框 | 估算宽 > 框宽 − 8px | 文案变长没改框 |
| 连线横穿图元 | 连线(去掉两端)穿过非端点图元 | 上下层之间有节点,线直接穿过去了 |
| 连线交叉 | 两条带箭头的线相交,交点不在端点 | 只留了一条通道 |
| 图元重叠 | 两个图元 bbox 相交且非包含关系 | 层距/列距不够 |
| 终止符形状 | 写着"开始/结束"的框 rx ≠ 高/2 |
用了工具默认圆角矩形 |
| 用例间流程箭头 | 两个椭圆之间连了实线箭头 | 把用例图当流程图用 |
| 线宽不统一 | 同一图出现 ≥0.5px 差的多档 stroke-width(虚线辅助线豁免) | 用工具默认值直接出图 |
| 非灰度颜色 | RGB 极差 > 8(--grayscale) |
用颜色承载语义 |
--tolerance 放宽贴合容差,--json 供程序解析。
哪些项对 PlantUML 产出不生效:箭头未贴合、箭头悬空、连线交叉、线宽统一——它不用 marker-end 标箭头,描边也不是纯黑。跑过 check 不代表它的连线走向没问题。改检查器后跑 scripts/tests/ 里的四份夹具(两份该报、两份该过),别让它悄悄失效。
含故意画错的对照图(教学用反面教材)会被逐条报出,属预期;检查对象应是成品图。
交点必须解出来
手写 SVG 时(≤7 图元那种),箭头终点要取目标图形的边界坐标。椭圆上的点不能目测:
((x0 + dx·t − cx)/rx)² + ((y0 + dy·t − cy)/ry)² = 1
取较小的 t 代回得交点。矩形则取与连线方向相交的那条边。参与者引出多条关联线时,统一从手臂末端出发形成扇形。
图元一多,这些交点就不是"算几道题"而是"算几十道题",漏一个箭尖就悬空一个——layout.py 里这段是函数,不是心算。
三层参考,按顺序用
assets/examples/gallery.png— 20 个图例的正误对照总览,一张图看完所有图种。开工前先看这张,建立"什么是对的"的直观印象。assets/examples/NN-<图种>-<ok|bad>.svg— 单个图例的完整源码,坐标原样保留。要画某类图时打开对应的ok文件,照着结构改内容,不要从零画。索引见同目录INDEX.md。references/snippets.md— 符号级片段:marker、终止符、UML 起止节点、类图关系端点、分叉粗线、椭圆交点公式、用例图布局尺寸。只画单个符号时抄这里。
references/symbols.md 是规则表,用来最后核对"有没有漏元素",不是用来起步的。
顺序不能反:先看样例,再抄结构,最后对规则。直接从规则起步,就会把"两端半圆"脑补成 rx="6"。
> 样例只教形状,不教规模。 每个 ok 样例都是 4–6 个图元的单符号示范,画布 300~400 宽。它们的坐标是手工配平过的,只对那个图元数成立。别拿 300 宽的画布去塞 20 个节点,也别指望"照着改内容"就能改出 20 个来——改到第 8 个,层距和分支偏移就已经不在同一个坐标系里了。
> 图例由 scripts/extract_examples.py 从规范手册自动抽取。手册更新后重跑:
> python scripts/extract_examples.py 手册.html --out assets/examples
输出禁忌
- 图内不写标题,标题归题注
- 参与者—用例的关联线必须带箭头(
-->或marker-end),光杆线一律返工 - 处理框不写数据库字段值、方法签名、代码变量名
- 不用界面截图充当系统图
- 不用颜色区分语义(论文默认黑白打印)
- 同一流程不重复画成多种图种