mcpField
S
svg-diagram

svg-diagram

github.com/bybit-exchange设计与 UI630467 次安装8 周前更新
代码仓库

关于 svg-diagram

svg-diagram 是一套生成 SVG 图表的约定,面向需要在文档中制作清晰、可维护示意图的用户。它规定布局、间距、连接线、字体和检查方式。

主要功能

  1. 01规定 SVG 文件存放与引用方式
  2. 02提供布局和间距标准
  3. 03定义连接线与箭头规则
  4. 04说明文字宽度与标签放置
  5. 05要求声明支持 CJK 的字体

适用场景

  1. 01为 Markdown 文档制作流程图
  2. 02绘制系统架构图
  3. 03检查 SVG 图表布局和可读性

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bybit-exchange/svg-diagram --skill svg-diagram

安装后在 Claude Code 中输入/svg-diagram确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.gitignore
LICENSE
package.json
README.md
tools/svg-lint/
README.md
tools/svg-lint/bin/
svg-lint.mjs
tools/svg-lint/lib/
color.mjs
format-text.mjs
registry.mjs
palette.mjs
lint.mjs
format-json.mjs
panels.mjs
geometry.mjs
parse-svg.mjs
report.mjs
text-metrics.mjs
document.mjs
tools/svg-lint/lib/checks/
baseline-offset.mjs
box-height.mjs
block-spacing.mjs
arrow-marker.mjs
connector-geometry.mjs
xml-escaping.mjs
viewbox-clipping.mjs
font-stack.mjs
text-overflow.mjs
light-bg-fallback.mjs
palette-conformance.mjs
overlap.mjs

详细介绍

SVG 绘图约定

输出位置与引用

图表应使用 SVG,绝不使用 ASCII 艺术图。这条规则决定了是否绘图;本规范介绍如何绘图,首先说明文件的存放位置。

  • 将文件放在与文档同级的 assets/ 目录中(例如 docs/foo.md → docs/assets/foo-arch.svg)
  • 在 Markdown 中使用相对路径引用:![title](assets/xxx.svg)。不要内嵌 SVG XML。
  • 文档中的数据发生变化时,也必须更新对应的 SVG。与旁边正文相矛盾的图表,还不如没有图表

核心原则

首次绘制就做到位

首次生成的 SVG 就必须满足本文末尾验证清单中的每一项。不要依赖用户反馈来修复间距、内边距、对齐、viewBox 裁切等基本布局问题。生成后,请自行逐项检查清单,并在展示结果前修复发现的问题。

元素不得重叠
  • 文字不得覆盖方框或线条
  • 线条不得穿过方框(以箭头终止于该方框的情况除外)
  • 文字与线条之间保持 10–20px 的间距
  • 同级/相邻方框之间的间距请参阅下文的“区块间距”部分(统一标准:≥25px,建议为 25–30px)
  • 颜色图例应放在图表主体之外(底部或侧边),不得与内容区域重叠

<path d="M35,200 C 35,120 ..." .../>
<text x="55" y="200">Label text</text>  


<text x="35" y="200">Label text</text>
视觉平衡

左右两部分应具有相近的视觉分量;可添加或移除注释框,使两侧保持均衡。

布局规则

居中
content width = right edge of rightmost element - left edge of leftmost element
left margin   = (viewBox width - content width) / 2

标题应相对于内容中心居中,而非 viewBox 中心(只要内容不对称,两者就会不同):


<text x="[content center x]" y="[top_padding + font_ascent]" text-anchor="middle">Title</text>

只有内容左右对称时,content center x = viewBox width / 2 才成立。内容不对称时,使用 <g transform="translate(...)"> 将整个分组居中,并相应地将标题的 x 更新为新的内容中心。

事后整体平移

如果 SVG 完成后才发现左右(或上下)内边距不对称,无需修改每个坐标。将内容包在一个 <g transform="translate(dx, dy)"> 中即可:

<svg viewBox="0 0 800 460" ...>
  <defs>...</defs>
  <g transform="translate(22, 0)">
    
  </g>
</svg>

计算偏移量:dx = (right padding - left padding) / 2,其中内边距是从 viewBox 边缘到最近内容元素的距离。

  • dx > 0:向右平移内容(适用于内容过于靠左的情况)
  • dx < 0:向左平移内容(适用于内容过于靠右的情况)

适用情况:整体内容偏离中心,且你不想逐个修改每个 x 坐标。缺点:文件中的坐标不再等于渲染后的坐标,因此后续修改时需要在脑中减去偏移量——如果接下来要大幅调整布局,请将偏移量合并到各个坐标中,并移除 <g>。

viewBox 边距

标题属于内容的一部分,因此上下边距应从标题开始测量:

title y      = margin + font ascent (≈ font-size × 0.75)
viewBox height = bottom y of content + margin

建议边距:20–25px




<svg viewBox="0 0 680 295" ...>
  <text x="340" y="32" font-size="16">Title</text>
  ...
</svg>
方框布局
  • 同一行中的方框尺寸差异 ≤60px;文字居中(text-anchor="middle")
  • 同一列中的方框共用中心线
  • 外部方框应完全包住内部方框:outer size = content size + padding × 2
虚线分组框(容器)

用于将相关框分组在一起(例如“服务端”“客户端”“员工实例”)。

样式一致性:在同一张图中,每个虚线分组框都必须具有以下一致属性:

  • stroke-dasharray(推荐 6,4)
  • rx(圆角半径,推荐 8–12)
  • fill(推荐 #f8fafc 或 none)
  • 内边距(推荐 15–20px)
  • 标题字号(推荐 11px,以区别于 12px 的框内正文)

标题位置:将分组标题放在框的左上角内侧(x = box x + 10, y = box y + 14),并使用次要文本颜色 #64748b。

垂直居中的陷阱:应根据标题 + 内部内容的总高度计算分组框的垂直位置,而不是只根据框本身计算。添加标题后,框顶部大约有 20–25px 的空间被占用,内部框应从其下方开始:



<rect x="50" y="100" width="200" height="120" rx="10"
      stroke="#94a3b8" stroke-dasharray="6,4" fill="#f8fafc"/>
<text x="60" y="114" font-size="11" fill="#64748b">Server</text>

<rect x="65" y="125" width="170" height="36" rx="6" .../>
<rect x="65" y="170" width="170" height="36" rx="6" .../>

指向分组的连接线:当连接线在逻辑上指向一组元素而非其中某一个元素时,应让连接线终止于分组框边界,而不是某个单独成员。也可以将这些元素放入分组框,并让箭头指向该框。

框尺寸

根据字号确定框高,以确保有足够的内边距:

内容高度公式示例(12px 字号)
单行字号 × 336px
两行字号 × 3 + 行高36 + 18 = 54px
多行字号 × 3 + (行数 - 1) × 行高每增加一行 +18px

公式来源:font-size × 3 = 上内边距(≈字号)+ 字形高度(≈字号)+ 下内边距(≈字号)。非标准字号也按相同比例缩放。

建议行高:字号 × 1.5(例如,12px 字号对应 18px)

在框内垂直居中文本(基线定位)

在 SVG 中,<text y=...> 指的是基线,而非字形顶部。要使文本在框内视觉居中:

text y = box center y + font-size × 0.35
       = box y + box height/2 + font-size × 0.35

0.35 系数是“字形视觉中心到基线”的经验值(约为字号的三分之一)。

区分这两个系数:

  • font-size × 0.75(上行高度):用于从顶部定位的元素,例如标题(y = top_padding + ascent)
  • font-size × 0.35(基线偏移):用于在框内垂直居中

<rect x="30" y="50" width="110" height="36" rx="6" .../>

<text x="85" y="72" ...>Single line</text>


<rect x="30" y="50" width="110" height="54" rx="6" .../>
<text x="85" y="70" ...>First line</text>
<text x="85" y="88" ...>Second line</text>  
显示尺寸
<svg viewBox="0 0 750 400" width="750" xmlns="http://www.w3.org/2000/svg">
图表类型宽度
简单列表400px
流程图600–700px
架构图700–800px

连接线规则

选择连接线

同轴连接使用直线 L;路径转弯或绕行时使用平滑的 C/Q 曲线。切勿使用直角折线。


<path d="M190,77 L 320,77" .../>


<path d="M130,145 L 130,213" .../>


<path d="M100,100 L100,200 L200,200" .../>


<path d="M100,100 C 100,150 150,200 200,200" .../>
情况推荐语法
同轴(水平/垂直)LL endx,endy
简单转弯QQ ctrlx,ctrly endx,endy
S 形曲线 / 复杂路径CC ctrl1x,ctrl1y ctrl2x,ctrl2y endx,endy
箭头规则
  • 方向由路径的最后一段决定:向右表示 x 增大,向下表示 y 增大
  • 对称间距:箭头与源框之间为 5px,与目标框之间也保持相同的视觉间距
  • 标签居中:x = (source right edge + target left edge) / 2,并使用 text-anchor="middle"

修正曲线路径上的箭头方向:orient="auto" 会使箭头与路径末端的切线对齐。C/Q 曲线的末端切线可能会倾斜,导致箭头看起来歪斜。解决方法:放置第二个控制点(cp2),使 cp2 → 终点的方向恰好与箭头所需的朝向一致。


<path d="M100,100 C 100,200 250,200 300,250" marker-end="url(#arrow)"/>


<path d="M100,100 C 100,200 300,220 300,250" marker-end="url(#arrow)"/>


<path d="M100,100 C 100,200 270,250 300,250" marker-end="url(#arrow)"/>

控制箭头方向的规则:

所需方向cp2 约束示例
↓ 向下cp2.x = end.x, cp2.y < end.yC ...,... ex,ey-20 ex,ey
→ 向右cp2.y = end.y, cp2.x < end.xC ...,... ex-20,ey ex,ey
← 向左cp2.y = end.y, cp2.x > end.xC ...,... ex+20,ey ex,ey
↑ 向上cp2.x = end.x, cp2.y > end.yC ...,... ex,ey+20 ex,ey

计算路径端点(间距为 5px,箭头超出线条末端 6px):

核心规则:起点距离源框 5px,终点距离目标框 11px(5px 间距 + 6px 箭头延伸长度)。

由于我们使用 refX="2"(箭头尾部的凹口),箭头尖端会超出线条末端 6px(8-2=6),且线条与箭头在凹口中心处连接,因此连接处看起来连续。

四个方向的公式:

方向起点终点
→ 向右源框右边缘 + 5目标框左边缘 - 11
← 向左源框左边缘 - 5目标框右边缘 + 11
↓ 向下源框底边缘 + 5目标框顶边缘 - 11
↑ 向上源框顶边缘 - 5目标框底边缘 + 11


<path d="M185,70 L 209,70" marker-end="url(#arrow)"/>



<path d="M215,70 L 191,70" marker-end="url(#arrow)"/>



<path d="M130,145 L 130,159" marker-end="url(#arrow)"/>



<path d="M130,165 L 130,151" marker-end="url(#arrow)"/>

双向示例(两个框之间有向上和向下的箭头):



<path d="M320,380 L 320,409" marker-end="url(#arrow)"/>  

<path d="M480,415 L 480,386" marker-end="url(#arrow)"/>  
绕开障碍物布线

绕行路径与障碍物边界之间至少保持 20px 的距离:


<path d="M600,313 Q 650,313 650,400 Q 650,640 635,685"
      fill="none" stroke="#a855f7" stroke-dasharray="6,4" marker-end="url(#arrow)"/>
块间距(全局统一标准)

相邻框/块之间的间距应为 ≥25px,建议 25–30px。

细分:起点间距 5px + 终点间距和箭头延伸长度 11px + 可见线条长度 ≥6px + 安全余量 ≈ 25px。可见线条至少为 6px,否则箭头会退化成一个点。

保持紧凑:不要超过 30px。间距过大(>40px)会使整个图示显得松散,连接线也会过长。两端分别保持 5px / 11px,中间的可见线条长度保持在 6–12px。

整体密度:完成后退一步检查整体密度——如果内容区域相对于 viewBox 明显偏小(留有大片空白区域),请缩小间距或 viewBox。常见原因:框间距 >30px、viewBox 边距 >25px、分组框内边距过大。

这是本文档唯一的块间距标准;“不得出现元素重叠”和验证清单均参照此标准。

SVG 绘制顺序(z-order)

SVG 没有 z-index——后绘制的元素会显示在上层。按以下顺序排列:


<rect ... stroke-dasharray="6,4" fill="#f8fafc"/>


<path d="..." marker-end="url(#arrow)"/>


<rect ... fill="#dbeafe"/>
<text ...>Label</text>

常见错误:先绘制连接线,再绘制虚线背景框 → 连接线会被遮住。

例外:跨层回环线:必须跨越多个框的连接线——例如迭代循环、跨区域虚线——必须在框之后绘制(第 4 层),否则会被框遮住:





<path d="..." stroke-dasharray="6,4" marker-end="url(#arrow-purple)"/>

估算文本宽度(防止溢出)

放置文本前,估算其右边缘,并确认不会侵入相邻元素:

字体大小拉丁字符宽度(约)CJK 字符宽度(约)
8px4.5px8px
9px5.0px9px
10px5.5px10px
11px6.0px11px
12px7.0px12px

估算公式:

  • text-anchor="start":右边缘 ≈ x + 字符数 × 字符宽度
  • text-anchor="middle":左/右边缘 ≈ x ± (字符数 × 字符宽度) / 2
  • text-anchor="end":左边缘 ≈ x - 字符数 × 字符宽度

放置前检查:文本右边缘 + 10px < 其右侧相邻元素的左边缘。



<text x="274" y="115" font-size="9">4K tokens (= mini-batch)</text>


<text x="154" y="138" font-size="9" text-anchor="middle">4K tokens (= mini-batch)</text>

在曲线上放置标签

描述弧线或曲线的标签不能放在线条本身上(线条会穿过文本)——应放在曲线的凸侧或凹侧:


<text x="350" y="218">Skip Connection</text>
<path d="M82,165 C 82,225 620,225 646,225" .../>


<text x="350" y="222">Skip Connection</text>
<path d="M82,149 C 82,260 600,255 640,255" .../>

规则:标签与曲线上最近点之间至少保持 15px。对于向下弯曲的曲线,将标签放在曲线上方;对于向上弯曲的曲线,将标签放在下方。

坐标管理

在注释中记录边界

<rect x="280" y="55" width="340" height="435" .../>
移动元素时,以下内容必须同步更新
  1. viewBox 尺寸
  2. 所有指向该元素或从该元素出发的箭头(起点、终点、控制点)
  3. 相关标签的位置
  4. 包围该元素的虚线背景/分组框(泳道、容器)——这是最容易遗漏的一项
  5. 同一行/列中与其对齐的其他框
  6. 其他所有受影响的元素

样式规则

字体(必需)

每个 SVG 都必须在 <style> 中声明支持 CJK 的字体栈——这是硬性要求:

<style>
  text { font-family: 'PingFang SC', 'Microsoft YaHei', 'Noto Sans CJK SC', system-ui, sans-serif; }
</style>

回退顺序:macOS(PingFang SC)→ Windows(Microsoft YaHei)→ Linux(Noto Sans CJK SC)→ 系统默认字体。必须保留 Noto Sans CJK SC,否则 Linux 上的服务器端渲染会回退到不支持 CJK 的字体。

元素大小
标题16px
框内正文12px
次要文本/注释10px

竖排文本:<text writing-mode="tb">vertical text</text>

颜色

基础颜色:主要文本 #1e293b,次要文本 #64748b,弱化文本/箭头 #94a3b8

语义颜色:

含义填充描边文本
输入/主要#dbeafe#3b82f6#1e40af
处理中/进行中#fef3c7#f59e0b#b45309
数据/输出#d1fae5#22c55e#166534
AI/分析#f3e8ff#a855f7#6b21a8
敏感/警告#fce7f3#ec4899#9d174d

箭头定义

使用带凹口的箭头,而不是普通三角形——视觉效果更好:

<defs>
  
  <marker id="arrow" markerWidth="8" markerHeight="8" refX="2" refY="4" orient="auto" markerUnits="userSpaceOnUse">
    <path d="M0,0 L8,4 L0,8 L2,4 z" fill="#64748b"/>
  </marker>
  
  <marker id="arrow-blue" markerWidth="8" markerHeight="8" refX="2" refY="4" orient="auto" markerUnits="userSpaceOnUse">
    <path d="M0,0 L8,4 L0,8 L2,4 z" fill="#3b82f6"/>
  </marker>
  <marker id="arrow-orange" markerWidth="8" markerHeight="8" refX="2" refY="4" orient="auto" markerUnits="userSpaceOnUse">
    <path d="M0,0 L8,4 L0,8 L2,4 z" fill="#f59e0b"/>
  </marker>
  <marker id="arrow-green" markerWidth="8" markerHeight="8" refX="2" refY="4" orient="auto" markerUnits="userSpaceOnUse">
    <path d="M0,0 L8,4 L0,8 L2,4 z" fill="#22c55e"/>
  </marker>
  <marker id="arrow-purple" markerWidth="8" markerHeight="8" refX="2" refY="4" orient="auto" markerUnits="userSpaceOnUse">
    <path d="M0,0 L8,4 L0,8 L2,4 z" fill="#a855f7"/>
  </marker>
  <marker id="arrow-red" markerWidth="8" markerHeight="8" refX="2" refY="4" orient="auto" markerUnits="userSpaceOnUse">
    <path d="M0,0 L8,4 L0,8 L2,4 z" fill="#ef4444"/>
  </marker>
</defs>

箭头颜色参考:

ID颜色用途
arrow#64748b默认 / 中性连接
arrow-blue#3b82f6输入 / 主要流程
arrow-orange#f59e0b处理 / 进行中
arrow-green#22c55e数据 / 输出 / 成功
arrow-purple#a855f7AI / 分析 / 特殊
arrow-red#ef4444警告 / 危险操作

关键参数:

  • orient="auto" 会旋转箭头,使其跟随路径方向
  • 必须显式声明 markerUnits="userSpaceOnUse",否则默认的 strokeWidth 会根据线条粗细缩放箭头(当 stroke-width=2 时,箭头会放大一倍,超出预期的间距)
  • refX="2" 会将线条末端与箭头尾部凹口的中心对齐,使连接处看起来更自然
  • refY="4" 会使箭头垂直居中(箭头高度为 8,中点为 4)
  • 箭头路径 M0,0 L8,4 L0,8 L2,4 z 会形成带凹口的形状,尖端位于 x=8
  • 尖端会超出线条末端 6px(8-2=6),因此终点按 target edge - 11 计算

粗线上的箭头:当 stroke-width > 1.5(例如粗虚线)时,8×8 的箭头看起来太小。请定义按比例放大的标记:


<marker id="arrow-red-lg" markerWidth="12" markerHeight="12" refX="3" refY="6"
        orient="auto" markerUnits="userSpaceOnUse">
  <path d="M0,0 L12,6 L0,12 L3,6 z" fill="#ef4444"/>
</marker>
线条 stroke-width标记大小refX尖端延伸距离终点公式
≤ 1.58×826pxtarget edge - 11
1.5 ~ 2.512×1239pxtarget edge - 14
> 2.516×16412pxtarget edge - 17

XML 特殊字符(高风险,务必检查)

SVG 是 XML,因此文本中的特殊字符必须转义,否则整个 SVG 都无法渲染:

字符转义形式示例
&&amp;Validate &amp; Sanitize
<&lt;x &lt; 10
>&gt;x &gt; 0
"&quot;属性值中
'&apos;属性值中

<text>Load & Save</text>


<text>Load &amp; Save</text>
以编程方式生成 SVG 时进行转义

使用 Python/JS 生成 SVG 时,来自外部来源(CSV、数据库、API)的文本必须先转义,然后才能拼接到 SVG 中:

def svg_escape(text):
    return str(text).replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')

# ❌ Splicing external data directly ("Research&Development" → XML parse failure, image won't render)
svg += f'<text>{team_name}</text>'

# ✅ Escape first
svg += f'<text>{svg_escape(team_name)}</text>'

高风险数据来源:团队名称(包含 &)、需求描述(包含 <>)、用户输入、文件名

故障排查

问题检查内容
元素重叠文本与线条的间距是否 ≥10px;绕行路径是否穿过边界?
箭头不可见块间距是否小于 25px——请增大间距
标签未居中确认 text-anchor="middle",且 x = 间隙中心
箭头方向错误检查最后一段路径的方向(x/y 是递增还是递减)
XML 解析错误检查文本中的 & < > 是否已转义
底部内容被裁切viewBox 高度是否过小;其高度必须等于最低元素的底边位置 + 25px
添加标题后虚线分组框未居中垂直居中必须按“标题 + 内容”的总高度计算——参见“虚线分组框”
整体留白过多检查框间距是否 >30px,或 viewBox 边距是否 >25px

验证清单

  • 没有元素重叠(文本、框、线条)
  • 文本右边缘没有侵入相邻元素(根据字符宽度估算)
  • 曲线标签与曲线上最近点之间的距离 ≥15px
  • 两端的箭头留白对称(5px),可见线段 ≥6px
  • 标记声明 markerUnits="userSpaceOnUse"
  • 粗线(stroke-width > 1.5)使用放大的标记
  • 连接线使用 C/Q 曲线,不使用直角
  • 标签居中(text-anchor="middle")
  • 绕行路径避开障碍物
  • 区块间距 ≥25px(建议 25–30px,参见“区块间距”)
  • 标题居中,各框排列成行且大小相近
  • 存在 width 属性,viewBox 与内容相符
  • 框的高度足够(一行文字 ≥ 字号 × 3)
  • viewBox 的顶部和底部边距相当(20–25px,从标题顶部测量)
  • viewBox 左右内边距对称(如果不对称,使用 <g transform="translate(dx,0)"> 平移)
  • 特殊字符已转义(& → &amp;,< → &lt;)
  • 每个元素都位于可见的 viewBox 内(底部 = 最低元素的底边 + 25px)
  • 每条连接线都有明确含义(带有标签,或可根据上下文推断其起点和终点)
  • 虚线分组框的样式保持一致(dasharray、圆角半径、填充、内边距、标题字号)
  • 没有大块空白区域(间距控制在 30px 以内,viewBox 紧贴内容)

编辑工作流

1. 收集:动手修改前先收集全部需求

收到修改 SVG 的请求时,不要立即开始编辑。先判断:

  • 用户只提出了一项修改 → 回复“还有其他需要调整的地方吗?”,以便一次性收集完整需求
  • 用户提出了多项修改 → 进入执行阶段,一次性全部应用

目标:减少往返次数,避免每次小调整都重新输出整个 SVG。

2. 执行:一次性应用所有修改,并附上修改清单

完成所有修改后,先打印修改清单,再展示最终 SVG:

Change list:
1. Title spacing 20px → 30px
2. Box A fill #dbeafe → #f3e8ff
3. Connector L1 path adjusted (routes around the new box)

不要在每项修改后展示图表——用户只需要最终结果。

3. 调整布局:理解意图并同步调整相关元素

当用户说“将框 A 向右移动 20px”时,不要机械地只修改框 A 的 x。还要处理相关元素:

  • 所有连接到框 A 或从框 A 出发的箭头路径
  • 框 A 所在行与其他框的对齐
  • 框 A 的文本标签位置
  • 包含框 A 的外部/背景框大小

间距/对齐请求(“缩紧一点”“左对齐”“居中”)→ 理解整体意图,并一次性调整所有相关元素,避免用户逐像素反复迭代。

评论

支持 Markdown