强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 库对比

特性graphvizpydotpygraphviz
安装难度简单简单需要 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。