
在数据分析和软件开发中,我们经常需要绘制各种关系图:系统架构图、数据流程图、依赖关系图、决策树等。传统的手动拖拽方式不仅耗时,而且修改起来极其麻烦。今天,我将介绍如何通过Python结合Graphviz,实现“代码即图形”的高效绘图方式。
Graphviz不是一个普通的绘图库,而是一个声明式图形可视化工具。你只需要告诉它:“我有这些节点,它们之间这样连接”,至于“怎么画得好看、不交叉、布局合理”这个最头疼的问题,完全交给Graphviz的自动布局引擎。
想象一下:你设计电路图(逻辑),它生成PCB布线图(布局)。这就是Graphviz的哲学。
# 安装Graphviz系统工具(需要先下载安装)
# 访问 https://graphviz.org/download/ 下载对应系统版本
# 安装Python库
pipinstallgraphviz让我们从一个简单的项目依赖关系图开始:
fromgraphvizimportDigraph
# 创建一个有向图
project_graph = Digraph(
name='项目依赖关系',
format='png',
encoding='utf-8'
)
# 设置全局参数
project_graph.graph_attr.update({
'rankdir': 'LR', # 布局方向:从左到右
'splines': 'ortho', # 使用直角连线,避免交叉
'fontname': 'Microsoft YaHei, SimSun, sans-serif',
'bgcolor': '#f8f9fa'
})
# 设置节点默认样式
project_graph.node_attr.update({
'shape': 'box',
'style': 'filled,rounded',
'fillcolor': '#e3f2fd',
'fontname': 'Microsoft YaHei',
'fontsize': '11'
})
# 设置边默认样式
project_graph.edge_attr.update({
'color': '#455a64',
'penwidth': '1.2',
'arrowhead': 'vee',
'fontname': 'Microsoft YaHei',
'fontsize': '10'
})
# 添加项目节点
project_graph.node('web_app', 'Web应用')
project_graph.node('api_server', 'API服务')
project_graph.node('database', '数据库')
project_graph.node('cache', 'Redis缓存')
project_graph.node('auth_service', '认证服务')
# 添加依赖关系
project_graph.edge('web_app', 'api_server', label='HTTP请求')
project_graph.edge('api_server', 'database', label='SQL查询')
project_graph.edge('api_server', 'cache', label='缓存读写')
project_graph.edge('web_app', 'auth_service', label='用户认证')
project_graph.edge('api_server', 'auth_service', label='权限验证')
# 保存并生成图形
project_graph.render('project_dependency', view=False, cleanup=True)
print("项目依赖关系图已生成:project_dependency.png")对于数据科学项目,我们可以用Graphviz清晰地展示数据处理流程:
fromgraphvizimportDigraph
importpandasaspd
defcreate_data_pipeline_viz():
"""创建数据处理流水线可视化"""
pipeline = Digraph('数据处理流水线', format='svg')
# 全局样式
pipeline.graph_attr.update({
'rankdir': 'TB', # 从上到下布局
'splines': 'ortho',
'fontname': 'Microsoft YaHei',
'nodesep': '0.5',
'ranksep': '0.8'
})
# 定义节点样式映射
node_styles = {
'source': {'shape': 'cylinder', 'fillcolor': '#ffccbc'},
'process': {'shape': 'box', 'fillcolor': '#c8e6c9'},
'analysis': {'shape': 'ellipse', 'fillcolor': '#bbdefb'},
'output': {'shape': 'folder', 'fillcolor': '#fff9c4'}
}
# 添加数据处理节点
nodes = [
('raw_data', '原始数据\nCSV/JSON文件', 'source'),
('clean', '数据清洗\n去重、填充缺失值', 'process'),
('transform', '特征工程\n标准化、编码', 'process'),
('train', '模型训练\nXGBoost/LSTM', 'analysis'),
('evaluate', '模型评估\n准确率、召回率', 'analysis'),
('deploy', '部署上线\nAPI服务', 'output')
]
fornode_id, label, node_typeinnodes:
style = node_styles.get(node_type, {})
pipeline.node(
node_id,
label,
shape=style.get('shape', 'box'),
style='filled,rounded',
fillcolor=style.get('fillcolor', 'lightgray'),
fontname='Microsoft YaHei'
)
# 连接处理流程
pipeline.edges([
('raw_data', 'clean'),
('clean', 'transform'),
('transform', 'train'),
('train', 'evaluate'),
('evaluate', 'deploy')
])
# 添加反馈循环
pipeline.edge('evaluate', 'train',
label='参数调整',
style='dashed',
color='#f44336')
returnpipeline
# 生成并保存
pipeline_viz = create_data_pipeline_viz()
pipeline_viz.render('data_pipeline', view=False, cleanup=True)# 确保中文正常显示的三要素
viz = Digraph(
encoding='utf-8', # 1. Python编码
graph_attr={'charset': 'UTF-8'}, # 2. DOT文件编码
node_attr={'fontname': 'Microsoft YaHei, SimSun, sans-serif'} # 3. 字体回退链
)# 创建包含表格的复杂节点
viz.node('user_profile', label='''<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR>
<TD COLSPAN="2" BGCOLOR="#2196f3">
<FONT COLOR="white"><B>用户画像</B></FONT>
</TD>
</TR>
<TR>
<TD>ID</TD>
<TD>U1001</TD>
</TR>
<TR>
<TD>注册时间</TD>
<TD>2024-03-15</TD>
</TR>
<TR>
<TD>用户等级</TD>
<TD>VIP3</TD>
</TR>
</TABLE>>''', shape='none')defgenerate_dependency_graph(packages):
"""根据包依赖数据动态生成图形"""
dep_viz = Digraph('包依赖图', format='png')
dep_viz.graph_attr.update({
'rankdir': 'LR',
'splines': 'ortho',
'fontname': 'Microsoft YaHei'
})
# 添加所有包节点
forpkginpackages:
dep_viz.node(pkg, pkg)
# 添加依赖关系
forpkg, depsinpackages.items():
fordepindeps:
dep_viz.edge(dep, pkg) # 依赖指向被依赖方
returndep_viz
# 示例数据
package_deps = {
'my_project': ['requests', 'pandas', 'numpy'],
'data_processor': ['pandas', 'numpy'],
'api_client': ['requests'],
'requests': [],
'pandas': ['numpy'],
'numpy': []
}
# 生成图形
dependency_viz = generate_dependency_graph(package_deps)
dependency_viz.render('package_dependencies', view=False)defexport_multiple_formats(viz, base_name):
"""导出多种格式的图形"""
formats = ['png', 'svg', 'pdf']
forfmtinformats:
viz.format = fmt
viz.render(f'{base_name}_{fmt}', view=False, cleanup=True)
print(f"已生成: {base_name}.{fmt}")
# 同时保存DOT源文件(便于修改)
withopen(f'{base_name}.dot', 'w', encoding='utf-8') asf:
f.write(viz.source)
# 使用示例
export_multiple_formats(pipeline_viz, 'data_workflow')当节点数量超过100个时,需要特别注意性能:
defoptimize_large_graph(viz):
"""优化大型图形的性能"""
viz.graph_attr.update({
'overlap': 'vpsc', # 防止节点重叠
'maxiter': '1000', # 增加布局迭代次数
'epsilon': '0.0001', # 收敛阈值
'splines': 'true', # 大型图可关闭ortho
'K': '0.6', # 布局弹簧常数
})
viz.node_attr.update({
'fixedsize': 'true',
'width': '0.8',
'height': '0.5'
})
returnvizfromgraphvizimportDigraph
fromIPython.displayimportImage, display
defshow_in_notebook():
viz = Digraph()
viz.node('A', '开始')
viz.node('B', '处理')
viz.node('C', '结束')
viz.edges(['AB', 'BC'])
# 直接显示在Notebook中
viz.format = 'png'
display(Image(viz.pipe(format='png')))
returnvizimportos
fromdatetimeimportdatetime
defauto_generate_docs(viz, description):
"""自动生成带时间戳的文档"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f'docs/diagram_{timestamp}'
# 生成图形
viz.render(filename, view=False, cleanup=True)
# 生成Markdown文档
md_content = f"""# 关系图文档
生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
## 描述
{description}
## 预览

## DOT源码
```dot
{viz.source}“””
with open(f'{filename}.md', 'w', encoding='utf-8') as f:
f.write(md_content)
print(f"文档已生成: {filename}.md")
return filename通过Python结合Graphviz,可以实现:
1. 代码驱动绘图:将图形定义转化为可维护的代码
2. 自动布局:告别手动调整节点位置的烦恼
3. 版本控制友好:DOT文件可以像代码一样进行版本管理
4. 批量生成:轻松生成多个变体或不同风格的图形
5. 集成自动化:将图形生成嵌入到CI/CD流程中
这种"声明式"的绘图方式特别适合:
- 需要频繁修改的架构图
- 自动生成的数据流程文档
- 动态变化的系统依赖关系
- 需要版本控制的图形资产
“无他,惟手熟尔”!有需要的用起来!
本文分享自 Nicholas与Pypi 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!