核心实现细节分析
设计模式、关键算法、代码片段与实现技巧深度剖析
视觉模式库实现
模式 1:扇出(Fan-Out)- 一对多关系
使用场景: API Gateway→微服务、PRD→功能列表、根原因→多个影响
实现代码结构:
{
"elements": [
{
"id": "central_ellipse",
"type": "ellipse",
"x": 400, "y": 300,
"width": 120, "height": 80,
"strokeColor": "#c2410c",
"backgroundColor": "#fed7aa",
"text": "API Gateway"
},
{
"id": "arrow_fan_1",
"type": "arrow",
"x": 460, "y": 260,
"points": [[0, 0], [-80, -60]], // 左上
"startBinding": {"elementId": "central_ellipse", "focus": 0, "gap": 2},
"endBinding": {"elementId": "service_1", "focus": 0, "gap": 2}
},
{
"id": "arrow_fan_2",
"type": "arrow",
"x": 460, "y": 340,
"points": [[0, 0], [-80, 60]], // 左下
"startBinding": {"elementId": "central_ellipse", "focus": 0, "gap": 2},
"endBinding": {"elementId": "service_2", "focus": 0, "gap": 2}
},
{
"id": "arrow_fan_3",
"type": "arrow",
"x": 520, "y": 300,
"points": [[0, 0], [80, 0]], // 右侧
"startBinding": {"elementId": "central_ellipse", "focus": 0, "gap": 2},
"endBinding": {"elementId": "service_3", "focus": 0, "gap": 2}
}
]
}
设计要点:
- 中心元素使用椭圆(柔软、起源感)
- 箭头从中心放射状发出
- 角度均匀分布(如 120°、90° 间隔)
- 目标元素使用相同形状(矩形或椭圆)
模式 2:收敛(Convergence)- 多对一聚合
使用场景: 多数据源→聚合、多输入→单输出、funnel 模式
实现代码结构:
{
"elements": [
{
"id": "input_1",
"type": "ellipse",
"x": 100, "y": 200,
"strokeColor": "#1e3a5f",
"backgroundColor": "#3b82f6",
"text": "Data Source A"
},
{
"id": "input_2",
"type": "ellipse",
"x": 100, "y": 300,
"strokeColor": "#1e3a5f",
"backgroundColor": "#3b82f6",
"text": "Data Source B"
},
{
"id": "input_3",
"type": "ellipse",
"x": 100, "y": 400,
"strokeColor": "#1e3a5f",
"backgroundColor": "#3b82f6",
"text": "Data Source C"
},
{
"id": "aggregator",
"type": "diamond", // 菱形表示聚合/决策
"x": 400, "y": 300,
"width": 140, "height": 100,
"strokeColor": "#b45309",
"backgroundColor": "#fef3c7",
"text": "Aggregator"
},
{
"id": "arrow_merge_1",
"type": "arrow",
"points": [[0, 0], [150, 50]], // 从 input_1 到 aggregator
"startBinding": {"elementId": "input_1", "focus": 0, "gap": 2},
"endBinding": {"elementId": "aggregator", "focus": 0, "gap": 2}
}
// ... 类似添加 arrow_merge_2, arrow_merge_3
]
}
设计要点:
- 多个输入元素垂直/水平排列
- 使用菱形表示聚合点(经典 funnel 符号)
- 箭头从多个方向汇聚到单点
模式 3:时间线(Timeline)- 序列/事件流
使用场景: 协议事件流、生命周期、历史演变
实现代码结构:
{
"elements": [
{
"id": "timeline_spine",
"type": "line",
"x": 200, "y": 100,
"points": [[0, 0], [0, 500]], // 垂直主线
"strokeColor": "#1e3a5f",
"strokeWidth": 3 // 加粗主线
},
{
"id": "event_1_dot",
"type": "ellipse",
"x": 194, "y": 144, // 居中于主线
"width": 12, "height": 12,
"strokeColor": "#3b82f6",
"backgroundColor": "#3b82f6"
},
{
"id": "event_1_label",
"type": "text",
"x": 220, "y": 140, // 主线右侧
"width": 200, "height": 25,
"text": "RUN_STARTED",
"fontSize": 16,
"fontFamily": 3,
"textAlign": "left",
"strokeColor": "#1e40af" // 标题颜色
},
{
"id": "event_2_dot",
"type": "ellipse",
"x": 194, "y": 244,
"width": 12, "height": 12,
"strokeColor": "#3b82f6",
"backgroundColor": "#3b82f6"
},
{
"id": "event_2_label",
"type": "text",
"x": 220, "y": 240,
"text": "STATE_DELTA",
"strokeColor": "#3b82f6" // 副标题颜色
},
{
"id": "event_3_dot",
"type": "ellipse",
"x": 194, "y": 344,
"width": 12, "height": 12,
"strokeColor": "#3b82f6",
"backgroundColor": "#3b82f6"
},
{
"id": "event_3_label",
"type": "text",
"x": 220, "y": 340,
"text": "A2UI_UPDATE → createA2UIMessageRenderer()",
"strokeColor": "#64748b" // 正文颜色
}
]
}
设计要点:
- 使用
line类型作为主轴(非箭头) - 小椭圆点(12x12)标记事件位置
- 自由浮动文本(无容器)作为标签
- 用字号/颜色区分事件重要性
- 可添加代码片段作为证据工件
模式 4:树结构(Tree)- 层级/文件系统
使用场景: 文件目录、组织结构、分类体系
实现代码结构:
{
"elements": [
{
"id": "tree_trunk",
"type": "line",
"x": 100, "y": 50,
"points": [[0, 0], [0, 300]], // 垂直主干
"strokeColor": "#1e3a5f",
"strokeWidth": 2
},
{
"id": "branch_1",
"type": "line",
"x": 100, "y": 100,
"points": [[0, 0], [60, 0]], // 向右分支
"strokeColor": "#1e3a5f",
"strokeWidth": 1
},
{
"id": "node_1_label",
"type": "text",
"x": 170, "y": 95,
"text": "src/",
"fontSize": 16,
"fontFamily": 3,
"textAlign": "left",
"strokeColor": "#1e40af"
},
{
"id": "sub_branch_1a",
"type": "line",
"x": 160, "y": 100,
"points": [[0, 0], [0, 80]], // 向下的子分支
"strokeColor": "#1e3a5f",
"strokeWidth": 1
},
{
"id": "sub_branch_1b",
"type": "line",
"x": 160, "y": 140,
"points": [[0, 0], [40, 0]], // 向右的子分支
"strokeColor": "#1e3a5f",
"strokeWidth": 1
},
{
"id": "leaf_1_label",
"type": "text",
"x": 210, "y": 135,
"text": "components/",
"fontSize": 14,
"strokeColor": "#3b82f6"
}
]
}
设计要点:
- 完全使用
line+ 自由浮动文本,无矩形容器 - 主干线宽 > 分支线宽(视觉层级)
- 用字号/颜色区分层级深度
- 分支角度一致(90° 或 45°)
分区块 JSON 构建策略详解
问题背景
LLM 输出限制: Claude Code 单次响应约 32K token,而综合技术图表的 JSON 轻松超过此限制。
错误示例(试图一次性生成):
用户:创建展示整个微服务架构的图表
AI: 开始生成 JSON...
→ 输出到 25K token 时被截断
→ JSON 不完整,无法解析
→ 用户得到损坏的文件
分区块策略实现
Phase 1: 逐区构建
第一遍响应:
{
"type": "excalidraw",
"version": 2,
"elements": [
// Section 1: Entry point / trigger
{"id": "trigger_rect", "type": "rectangle", ...},
{"id": "trigger_text", "type": "text", ...},
{"id": "section1_arrow", "type": "arrow", ...}
],
"appState": {...},
"files": {}
}
第二遍响应:
[编辑现有文件,追加 Section 2]
{
...
"elements": [
// 保留 Section 1 元素
{"id": "trigger_rect", ...},
{"id": "trigger_text", ...},
{"id": "section1_arrow", ...},
// 新增 Section 2 元素
{"id": "decision_diamond", "type": "diamond", ...},
{"id": "section2_text", "type": "text", ...}
]
}
第三遍响应:
[追加 Section 3 - 主内容区]
{
...
"elements": [
// 保留 Section 1+2
...,
// 新增 Section 3
{"id": "hero_rect", ...},
{"id": "evidence_code_snippet", ...}
]
}
Phase 2: 审查与修复
最终遍:
1. 检查跨区箭头绑定是否正确
- arrow 的 startBinding 和 endBinding 是否都指向存在元素?
2. 检查整体间距
- Section 1 和 Section 2 之间是否有足够空白?
- 是否有区域过于拥挤?
3. 更新 boundElements
- 当新元素绑定到旧元素时,更新旧元素的 boundElements 数组
关键技巧
技巧 1: 描述性字符串 ID
// ❌ 不好的 ID(无意义,难以跨区引用)
{"id": "elem_1", ...}
{"id": "elem_2", ...}
// ✅ 好的 ID(语义清晰,便于调试)
{"id": "trigger_rect", ...}
{"id": "arrow_to_service", ...}
{"id": "evidence_json_snippet", ...}
优势:
- 跨区引用时可读性强
- 调试时可快速定位
- 审查时容易理解元素用途
技巧 2: Seed 值按区命名空间
// Section 1: 100xxx
{"id": "trigger_rect", "seed": 100001, ...}
{"id": "trigger_text", "seed": 100002, ...}
// Section 2: 200xxx
{"id": "decision_diamond", "seed": 200001, ...}
{"id": "decision_text", "seed": 200002, ...}
// Section 3: 300xxx
{"id": "hero_rect", "seed": 300001, ...}
优势:
- 避免 seed 碰撞
- 可通过 seed 值快速识别元素所属区
- 便于按需删除/修改整区
技巧 3: 跨区绑定同步更新
// 第三遍追加 Section 3 时,新增箭头指向 Section 1
{
"id": "arrow_cross_section",
"type": "arrow",
"startBinding": {"elementId": "trigger_rect", "focus": 0, "gap": 2}, // Section 1 元素
"endBinding": {"elementId": "hero_rect", "focus": 0, "gap": 2} // Section 3 元素
}
// 同时编辑 Section 1 的 trigger_rect,更新 boundElements
{
"id": "trigger_rect",
...
"boundElements": [
{"id": "trigger_text", "type": "text"},
{"id": "arrow_cross_section", "type": "arrow"} // ← 新增跨区绑定
]
}
关键点: 当新元素绑定到旧元素时,必须同步更新旧元素的 boundElements 数组,否则 Excalidraw 无法正确识别关系。
渲染验证循环实现
循环流程图
┌─────────────────┐
│ 生成/编辑 JSON │
└────────┬────────┘
│
↓
┌─────────────────────────────────┐
│ render_excalidraw.py │
│ cd references && │
│ uv run python │
│ render_excalidraw.py │
│ diagram.excalidraw │
└────────┬────────────────────────┘
│
↓
┌─────────────────┐
│ 输出 diagram.png │
└────────┬────────┘
│
↓
┌─────────────────────────────────┐
│ AI 使用 Read 工具读取 PNG │
│ 视觉检查以下内容: │
│ - 文本是否溢出容器? │
│ - 元素是否重叠? │
│ - 箭头路径是否交叉元素? │
│ - 标签是否明确锚定? │
│ - 间距是否均匀? │
│ - 构图是否平衡? │
└────────┬────────────────────────┘
│
↓
┌────┴────┐
│ 有问题? │
└────┬────┘
│
┌────┴────┐
│ 是 │ 否
↓ ↓
┌────────┐ ┌──────────┐
│ 修复 JSON│ │ 完成 ✓ │
│ 重新渲染│ └──────────┘
└───┬────┘
│
└──────→ (循环)
典型修复场景
场景 1: 文本溢出容器
问题诊断(从 PNG 观察):
"API Gateway" 文本右侧被矩形裁剪
修复方案:
{
"id": "trigger_rect",
"type": "rectangle",
"width": 180, // ← 原 150,增加 30px
...
}
场景 2: 箭头穿过元素
问题诊断:
从 trigger 到 service 的箭头穿过 decision 菱形
修复方案 1(添加路径点):
{
"id": "arrow_avoid",
"type": "arrow",
"points": [
[0, 0], // 起点
[50, 0], // 先向右
[50, -80], // 向上绕过
[150, -80], // 继续向右
[150, 0] // 向下到终点
]
}
修复方案 2(调整元素位置):
{
"id": "decision_diamond",
"y": 400 // ← 原 300,下移 100px 避开箭头路径
}
场景 3: 间距不均匀
问题诊断:
Section 1 和 Section 2 之间间距 50px,Section 2 和 Section 3 之间 200px
修复方案:
统一调整为 120px:
{
"id": "section2_group",
"y": 350 // ← 原 280,下移 70px
}
迭代次数统计
根据 SKILL.md 中的经验法则:
| 图表复杂度 | 典型迭代次数 | 主要修复类型 |
|---|---|---|
| 简单概念图 | 1-2 次 | 微调间距、文本对齐 |
| 综合技术图 | 3-5 次 | 文本溢出、箭头路径、跨区绑定 |
| 超大多区图 | 5-8 次 | 整体构图平衡、多区对齐 |
关键原则: 不要为了”完成任务”而提前结束循环。如果构图可以改进,就继续迭代。
证据工件实现
代码片段样式
JSON 结构:
{
"id": "evidence_code_bg",
"type": "rectangle",
"x": 600, "y": 200,
"width": 400, "height": 180,
"strokeColor": "#1e293b", // 深色背景
"backgroundColor": "#1e293b",
"fillStyle": "solid",
"strokeWidth": 1,
"roughness": 0,
"roundness": {"type": 3} // 圆角
}
代码文本(语法高亮通过颜色实现):
{
"id": "evidence_code_line1",
"type": "text",
"x": 620, "y": 220,
"text": "import { createA2UIMessageRenderer } from '@copilotkit/react-ui';",
"fontSize": 14,
"fontFamily": 3, // 等宽字体
"textAlign": "left",
"strokeColor": "#e2e8f0", // 浅色文本
"backgroundColor": "transparent"
}
设计要点:
- 深色背景(#1e293b)与浅色文本(#e2e8f0)高对比
- 等宽字体(fontFamily: 3)模拟代码编辑器
- 左对齐(textAlign: “left”)符合代码阅读习惯
- 圆角矩形(roundness)现代感
JSON 数据示例样式
JSON 结构:
{
"id": "evidence_json_bg",
"type": "rectangle",
"x": 600, "y": 420,
"width": 350, "height": 140,
"strokeColor": "#1e293b",
"backgroundColor": "#1e293b",
"fillStyle": "solid"
}
JSON 文本(绿色高亮):
{
"id": "evidence_json_content",
"type": "text",
"x": 620, "y": 440,
"text": "{\n \"type\": \"RUN_STARTED\",\n \"agentId\": \"orchestrator\",\n \"timestamp\": 1234567890\n}",
"fontSize": 14,
"fontFamily": 3,
"textAlign": "left",
"strokeColor": "#22c55e", // 绿色文本
"backgroundColor": "transparent"
}
设计要点:
- 绿色文本(#22c55e)是 JSON/数据的语义颜色
- 使用
\n换行符格式化 JSON - 保持真实数据结构(非”Input”、“Output”占位符)
事件/步骤序列样式
使用时间线模式 + 真实事件名:
{
"elements": [
{
"id": "event_1_label",
"type": "text",
"text": "RUN_STARTED", // ← 真实事件名(非"Event 1")
"strokeColor": "#1e40af" // 标题颜色
},
{
"id": "event_2_label",
"type": "text",
"text": "STATE_DELTA", // ← 真实事件名
"strokeColor": "#3b82f6" // 副标题颜色
},
{
"id": "event_3_label",
"type": "text",
"text": "A2UI_UPDATE → createA2UIMessageRenderer()", // ← 具体方法名
"strokeColor": "#64748b" // 正文颜色
}
]
}
研究强制要求: SKILL.md 明确要求在生成技术图表前,必须查阅真实文档,使用实际的事件名、API 方法名、数据格式。
颜色作为实现细节
语义颜色映射算法
伪代码:
function getColorForConcept(concept):
if concept in ["start", "trigger", "input", "entry"]:
return palette["Start/Trigger"] // fill: #fed7aa, stroke: #c2410c
if concept in ["end", "success", "output", "result", "complete"]:
return palette["End/Success"] // fill: #a7f3d0, stroke: #047857
if concept in ["decision", "condition", "if", "switch"]:
return palette["Decision"] // fill: #fef3c7, stroke: #b45309
if concept in ["ai", "llm", "agent", "model"]:
return palette["AI/LLM"] // fill: #ddd6fe, stroke: #6d28d9
if concept in ["error", "exception", "fail", "retry"]:
return palette["Error"] // fill: #fecaca, stroke: #b91c1c
// 默认:Primary/Neutral
return palette["Primary/Neutral"] // fill: #3b82f6, stroke: #1e3a5f
文本层级颜色实现
三级文本系统:
| 层级 | 字号 | 颜色 | 用途 |
|---|---|---|---|
| Title | 20-28px | #1e40af | 章节标题、主标签 |
| Subtitle | 16-20px | #3b82f6 | 副标题、次要标签 |
| Body/Detail | 14-16px | #64748b | 描述文本、注解、元数据 |
实现示例:
{
"id": "section_title",
"type": "text",
"text": "Backend Processing",
"fontSize": 24,
"strokeColor": "#1e40af" // Title color
}
{
"id": "section_subtitle",
"type": "text",
"text": "Event Streaming Pipeline",
"fontSize": 18,
"strokeColor": "#3b82f6" // Subtitle color
}
{
"id": "section_description",
"type": "text",
"text": "Events are streamed from the AI agent to the frontend via AG-UI protocol.",
"fontSize": 14,
"strokeColor": "#64748b" // Body color
}
优势:
- 无需容器即可创建视觉层级
- 颜色编码帮助快速扫描
- 减少 70% 以上的矩形使用
<30% 容器化原则实现
判断是否使用容器的决策树
文本是否需要容器?
│
├─ 是章节焦点/视觉锚点? → 使用矩形
│
├─ 需要与相邻元素视觉分组? → 使用框架或共享背景
│
├─ 箭头需要连接到此文本? → 使用小椭圆标记点(12x12)
│
├─ 文本本身是决策/条件? → 使用菱形
│
└─ 以上都不是 → 自由浮动文本(无容器)
实践统计
根据 SKILL.md 的 27 点质量检查清单第 20 条:
Container ratio: <30% of text elements should be inside containers
典型综合图表元素统计:
总文本元素:50 个
- 自由浮动文本:38 个(76%)
- 容器内文本:12 个(24%)✓ 符合<30% 要求
容器类型分布:
- 矩形:8 个(焦点元素、证据工件背景)
- 菱形:2 个(决策点)
- 椭圆:2 个(起始/结束点)
替代容器的技术
技术 1: 结构线
// 使用时间线而非带框的步骤列表
{
"id": "timeline_spine",
"type": "line",
"points": [[0, 0], [0, 500]]
}
// 自由浮动文本作为标签(无容器)
技术 2: 小标记点
// 使用 12x12 椭圆点而非大矩形框
{
"id": "bullet_dot",
"type": "ellipse",
"width": 12, "height": 12,
"strokeColor": "#3b82f6",
"backgroundColor": "#3b82f6"
}
// 自由浮动文本在点右侧
技术 3: 留白分隔
不使用分隔线或背景框,而是通过留白创建视觉分组:
Section 1 (y: 100-250)
← 120px 留白 →
Section 2 (y: 370-520)
← 120px 留白 →
Section 3 (y: 640-790)
本章小结
核心实现技巧:
- 分区块 JSON 构建 - 描述性 ID、seed 命名空间、跨区绑定同步
- 渲染验证循环 - 典型 3-5 次迭代,修复文本溢出/箭头路径/间距
- 证据工件 - 深色背景代码片段、绿色 JSON 数据、真实事件名
- 语义化颜色 - 概念→颜色映射算法,文本三级层级
- <30% 容器化 - 自由浮动文本优先,结构线/标记点替代
设计模式总结:
- 扇出/收敛模式用于关系可视化
- 时间线/树结构用于序列/层级
- 证据工件增强教学价值
- 颜色编码信息而非装饰
下一章将分析实际应用场景和用例。