Graphviz 图形可视化教程 / 10 - 编程语言绑定
第 10 章 · 编程语言绑定
10.1 为什么需要编程绑定
直接编写 DOT 文件适用于静态图形,但在以下场景中需要编程绑定:
| 场景 | 说明 |
|---|---|
| 动态数据 | 从数据库/API 获取数据生成图 |
| 批量生成 | 自动化生成大量图形 |
| 条件逻辑 | 根据条件改变图形结构 |
| 模板引擎 | 图形模板与数据分离 |
| 交互应用 | Web 应用中动态渲染 |
| 数据可视化 | 从分析结果自动生成图 |
10.2 Python — graphviz 库
安装
pip install graphviz
⚠️ 需要系统已安装 Graphviz(
dot命令可用)。
基本用法
from graphviz import Digraph
# 创建有向图
dot = Digraph(comment='示例图', format='png')
dot.attr(rankdir='LR', bgcolor='#FAFAFA')
dot.attr('node', shape='box', style='filled,rounded',
fillcolor='#E3F2FD', color='#1976D2',
fontname='Microsoft YaHei', fontsize='11')
dot.attr('edge', color='#666666', fontname='Microsoft YaHei')
# 添加节点
dot.node('A', '开始')
dot.node('B', '处理')
dot.node('C', '结束')
# 添加边
dot.edge('A', 'B', label='请求')
dot.edge('B', 'C', label='响应')
# 渲染输出
dot.render('output/demo', cleanup=True)
print("已生成 output/demo.png")
无向图
from graphviz import Graph
g = Graph(comment='无向图', format='svg')
g.node('A')
g.node('B')
g.edge('A', 'B')
g.render('output/undirected', cleanup=True)
子图与集群
from graphviz import Digraph
dot = Digraph()
dot.attr(rankdir='TB')
# 集群(cluster_ 前缀)
with dot.subgraph(name='cluster_frontend') as c:
c.attr(label='前端', style='filled', fillcolor='#E3F2FD', color='#1976D2')
c.node('React', 'React')
c.node('Vue', 'Vue')
with dot.subgraph(name='cluster_backend') as c:
c.attr(label='后端', style='filled', fillcolor='#E8F5E9', color='#388E3C')
c.node('Go', 'Go')
c.node('Python', 'Python')
dot.edge('React', 'Go', label='API')
dot.edge('Vue', 'Python', label='REST')
dot.render('output/clusters', cleanup=True)
高级特性
from graphviz import Digraph
dot = Digraph(format='svg')
# HTML 标签
dot.node('table', label='''<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="6">
<TR>
<TD COLSPAN="2" BGCOLOR="#1976D2">
<FONT COLOR="white"><B>用户信息</B></FONT>
</TD>
</TR>
<TR><TD>姓名</TD><TD>张三</TD></TR>
<TR><TD>年龄</TD><TD>28</TD></TR>
</TABLE>
>''', shape='plain')
# Record 形状
dot.node('struct', shape='record',
label='{<f0> 输入|<f1> 处理|<f2> 输出}')
# 端口连接
dot.edge('struct:f0', 'struct:f2', style='dashed')
dot.render('output/html_table', cleanup=True)
10.3 Python — pydot 库
安装
pip install pydot
基本用法
import pydot
# 创建图
graph = pydot.Dot(graph_type='digraph', rankdir='LR')
# 添加节点
node_a = pydot.Node('A', label='开始', shape='circle',
style='filled', fillcolor='#C8E6C9')
node_b = pydot.Node('B', label='处理', shape='box',
style='filled', fillcolor='#E3F2FD')
node_c = pydot.Node('C', label='结束', shape='doublecircle',
style='filled', fillcolor='#FFCDD2')
graph.add_node(node_a)
graph.add_node(node_b)
graph.add_node(node_c)
# 添加边
graph.add_edge(pydot.Edge(node_a, node_b, label='请求'))
graph.add_edge(pydot.Edge(node_b, node_c, label='响应'))
# 输出
graph.write_png('output/pydot_demo.png')
graph.write_svg('output/pydot_demo.svg')
从 DOT 字符串解析
import pydot
dot_string = '''
digraph G {
A -> B -> C
A -> C
}
'''
# 解析 DOT 字符串
graphs = pydot.graph_from_dot_data(dot_string)
graph = graphs[0]
graph.write_png('output/parsed.png')
10.4 Python — pygraphviz 库
安装
# 需要先安装 graphviz-dev
# Ubuntu: sudo apt-get install graphviz libgraphviz-dev
pip install pygraphviz
基本用法
import pygraphviz as pgv
# 创建图
G = pgv.AGraph(directed=True, rankdir='LR')
G.node_attr['shape'] = 'box'
G.node_attr['style'] = 'filled,rounded'
G.node_attr['fontname'] = 'Microsoft YaHei'
# 添加节点和边
G.add_node('A', label='开始', fillcolor='#C8E6C9')
G.add_node('B', label='处理', fillcolor='#E3F2FD')
G.add_node('C', label='结束', fillcolor='#FFCDD2')
G.add_edge('A', 'B', label='请求')
G.add_edge('B', 'C', label='响应')
# 布局和渲染
G.layout(prog='dot')
G.draw('output/pygraphviz.png')
G.draw('output/pygraphviz.svg')
10.5 Python 库对比
| 特性 | graphviz | pydot | pygraphviz |
|---|---|---|---|
| 安装难度 | 简单 | 简单 | 需要 dev 库 |
| API 风格 | 链式 | 命令式 | 对象式 |
| DOT 解析 | 不支持 | 支持 | 支持 |
| 布局控制 | 命令行 | 命令行 | 内置 |
| 性能 | 中等 | 中等 | 快 |
| 维护状态 | 活跃 | 维护中 | 活跃 |
| 推荐场景 | 新建项目 | 解析 DOT | 高性能需求 |
10.6 Go 语言绑定
使用 gonum 生态
package main
import (
"fmt"
"os"
"os/exec"
"strings"
)
func main() {
// 构建 DOT 字符串
dot := `digraph G {
rankdir=LR
node [shape=box style="filled,rounded" fillcolor="#E3F2FD" color="#1976D2" fontname="Microsoft YaHei"]
edge [color="#666666"]
A [label="开始"]
B [label="处理"]
C [label="结束"]
A -> B [label="请求"]
B -> C [label="响应"]
}`
// 写入临时文件
tmpFile := "/tmp/graph.dot"
os.WriteFile(tmpFile, []byte(dot), 0644)
// 调用 dot 命令
cmd := exec.Command("dot", "-Tpng", tmpFile, "-o", "output.png")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("已生成 output.png")
}
使用 gographviz
package main
import (
"fmt"
"os"
github.com/awalterschulze/gographviz
)
func main() {
g := gographviz.NewGraph()
g.SetName("G")
g.SetDir(true) // 有向图
g.AddAttr("G", "rankdir", "LR")
// 添加节点
g.AddNode("G", "A", map[string]string{
"label": `"开始"`,
"shape": "box",
})
g.AddNode("G", "B", map[string]string{
"label": `"处理"`,
"shape": "box",
})
g.AddNode("G", "C", map[string]string{
"label": `"结束"`,
"shape": "box",
})
// 添加边
g.AddEdge("A", "B", true, nil)
g.AddEdge("B", "C", true, nil)
// 输出 DOT
dot := g.String()
fmt.Println(dot)
os.WriteFile("output.dot", []byte(dot), 0644)
}
10.7 Node.js 绑定
使用 @viz-js/viz(推荐)
npm install @viz-js/viz
import { instance } from "@viz-js/viz";
async function main() {
const viz = await instance();
const dot = `
digraph G {
rankdir=LR
node [shape=box style="filled,rounded" fillcolor="#E3F2FD"]
A -> B -> C
A -> C
}
`;
// 渲染为 SVG
const svg = viz.renderSVGElement(dot);
console.log(svg.outerHTML);
// 渲染为 JSON
const json = viz.renderJSON(dot);
console.log(JSON.stringify(json, null, 2));
// 渲染为 PNG(返回 ArrayBuffer)
const png = viz.renderString(dot, { format: "png" });
console.log("PNG 数据长度:", png.length);
}
main().catch(console.error);
在浏览器中使用
<!DOCTYPE html>
<html>
<head>
<title>Graphviz in Browser</title>
<script type="module">
import { instance } from "https://cdn.jsdelivr.net/npm/@viz-js/viz/+esm";
document.addEventListener("DOMContentLoaded", async () => {
const viz = await instance();
const dot = document.getElementById("dot-input").value;
const svg = viz.renderSVGElement(dot);
document.getElementById("output").appendChild(svg);
});
</script>
</head>
<body>
<textarea id="dot-input" rows="10" cols="50">
digraph G {
A -> B -> C
A -> C
}
</textarea>
<div id="output"></div>
</body>
</html>
使用 graphviz npm 包
const graphviz = require('graphviz');
// 创建图
const g = graphviz.digraph("G");
g.set("rankdir", "LR");
// 添加节点
const a = g.addNode("A", { label: "开始", shape: "box" });
const b = g.addNode("B", { label: "处理", shape: "box" });
const c = g.addNode("C", { label: "结束", shape: "box" });
// 添加边
g.addEdge(a, b, { label: "请求" });
g.addEdge(b, c, { label: "响应" });
// 输出 DOT
console.log(g.to_dot());
// 渲染(需要本地安装 Graphviz)
g.output("png", "output/node_graph.png", (err) => {
if (err) console.error(err);
else console.log("已生成 output/node_graph.png");
});
10.8 Rust 绑定
使用 graphviz-rust
# Cargo.toml
[dependencies]
graphviz-rust = "0.9"
use graphviz_rust::dot_generator::*;
use graphviz_rust::dot_structures::*;
use graphviz_rust::print;
use graphviz_rust::cmd::{CommandArg, Format};
use graphviz_rust::exec;
fn main() {
// 构建图
let g = graph!(id!("G");
attr!("rankdir", "LR"),
node!("A"; attr!("label", esc "开始"), attr!("shape", "box")),
node!("B"; attr!("label", esc "处理"), attr!("shape", "box")),
node!("C"; attr!("label", esc "结束"), attr!("shape", "box")),
edge!(node_id!("A") => node_id!("B"; port!("e")),
attr!("label", esc "请求")),
edge!(node_id!("B") => node_id!("C"; port!("w")),
attr!("label", esc "响应"))
);
// 输出 DOT
let dot = print(g.clone());
println!("{}", dot);
// 渲染为 PNG(需要系统安装 Graphviz)
let svg = exec(
g,
&mut vec![Format::Svg.into()],
).expect("渲染失败");
println!("{}", svg);
}
10.9 动态生成模式
从 JSON 数据生成图
import json
from graphviz import Digraph
# 模拟数据
data = {
"services": [
{"name": "用户服务", "type": "api", "deps": ["数据库", "缓存"]},
{"name": "订单服务", "type": "api", "deps": ["用户服务", "消息队列"]},
{"name": "支付服务", "type": "api", "deps": ["订单服务"]},
{"name": "数据库", "type": "db", "deps": []},
{"name": "缓存", "type": "db", "deps": []},
{"name": "消息队列", "type": "infra", "deps": []},
]
}
def generate_architecture_diagram(data):
dot = Digraph(comment='服务架构', format='svg')
dot.attr(rankdir='TB')
dot.attr('node', fontname='Microsoft YaHei', fontsize='11')
dot.attr('edge', fontname='Microsoft YaHei', fontsize='9', color='#666666')
# 样式映射
styles = {
'api': {'shape': 'component', 'fillcolor': '#E3F2FD', 'color': '#1976D2'},
'db': {'shape': 'cylinder', 'fillcolor': '#F3E5F5', 'color': '#7B1FA2'},
'infra': {'shape': 'box3d', 'fillcolor': '#FFF3E0', 'color': '#FF9800'},
}
for svc in data['services']:
style = styles.get(svc['type'], {})
dot.node(svc['name'], label=svc['name'],
style='filled', **style)
for svc in data['services']:
for dep in svc['deps']:
dot.edge(svc['name'], dep)
return dot
dot = generate_architecture_diagram(data)
dot.render('output/architecture', cleanup=True)
从 CSV 生成关系图
import csv
from graphviz import Digraph
def generate_from_csv(csv_path, output_path):
dot = Digraph(format='svg')
dot.attr(rankdir='LR')
dot.attr('node', shape='box', style='filled,rounded',
fillcolor='#E3F2FD', fontname='Microsoft YaHei')
with open(csv_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
nodes = set()
for row in reader:
src = row['source']
dst = row['target']
label = row.get('label', '')
if src not in nodes:
dot.node(src, src)
nodes.add(src)
if dst not in nodes:
dot.node(dst, dst)
nodes.add(dst)
dot.edge(src, dst, label=label)
dot.render(output_path, cleanup=True)
return dot
# CSV 格式:source,target,label
# generate_from_csv('relations.csv', 'output/relations')
注意事项
⚠️ 系统依赖:所有编程绑定都需要系统安装 Graphviz(
dot命令)。
⚠️ 中文编码:确保 Python 文件使用 UTF-8 编码,标签中的中文才能正确渲染。
⚠️ HTML 标签转义:在 Python 字符串中嵌入 HTML 标签时注意转义
<>。
⚠️ 临时文件:某些库会生成临时文件,注意清理。
⚠️ viz.js 的 WASM:Node.js 中的
@viz-js/viz使用 WebAssembly,不需要系统安装 Graphviz。
⚠️ pygraphviz 编译:
pygraphviz需要 C 编译器和 graphviz-dev 包。
扩展阅读
下一章:11 - Docker 与自动化 — 在容器中使用 Graphviz。