数据可视化Graphviz程序化绘图—学习资料收藏整理笔记
Author:zhoulujun Date:
之前介绍过PlanUML,《程序猿专属UML画图工具:PlantUML简介及应用》以及在编辑器写UML,《UML开发语言plantuml如何应用到编辑工具—mac上类visio工具》,本篇学习Graphviz,http://www.graphviz.org/about/
一般的Graphviz 库都支持DOT, PlantUML, UMLGraph 甚至SVG
OmniGraffle 生成自动布局图形的基础是 Graphviz 引擎。Graphviz(Graph Visualization Software)是一个由AT&T实验室启动的开源工具包,能够支持基于 DOT 脚本,文件扩展名通常是 .gv 或 .dot 的描述绘制图形。DOT 是一种文本图形描述语言,将生成的图形转换成多种输出格式的命令行工具,其输出格式包括PostScript,PDF,SVG,PNG,含注解的文本等。DOT 本身非常原始,提供了一种非常简单的描述图形的方法,同时意味着可以在命令行终端使用,或者被其它编程语言调用(Graphviz 就可以作为一个库使用)。这一点非常关键,基于 Graphviz 应用开发者不必掌握布局的复杂算法,而是可以把精力放在业务方面,将最后的图对象交给绘图引擎来处理即可。
推荐阅读知乎回答:数据可视化(三)基于 Graphviz 实现程序化绘图 - RiboseYim的文章 - 知乎
对于前段同学,可以在nodejs生成,再在网页上显示
https://www.npmjs.com/package/graphviz
对于简单的关系图,可以直接调用http://www.gravizo.com/服务显示
前端库:https://www.npmjs.com/package/ts-graphviz
直接在前段网页,可以选择D3:https://www.npmjs.com/package/d3-graphviz
基于此库,在vue reat框架有:
https://www.npmjs.com/package/graphviz-react
https://www.npmjs.com/package/vue-graphviz
贴一下别人的总结:《Graphviz 画图的一些总结》
Graphviz 是一个自动排版的作图软件,可以生成 png pdf 等格式。
dot 语言
Graphviz 构建组件为 图,节点,边,用属性对其进行描述。
以下是定义DOT语言的抽象语法,约束的规则如下:
元素的终止以 粗体 显示
文字字符用单引号 '' 引起来
圆括号 () 的内容为必选项
方括号 [] 为可选项目
竖杠 | 为择一选择
声明 | 结构 |
---|---|
graph | [ strict ] (graph | digraph) [ ID ] '{' stmt_list '}' |
stmt_list | [ stmt [ ';' ] stmt_list ] |
stmt | node_stmt | edge_stmt | attr_stmt | ID '=' ID | subgraph |
attr_stmt | (graph | node | edge) attr_list |
attr_list | '['** [ a_list ] **']' [ attr_list ] |
a_list | ID '=' ID [ (';' | ',') ] [ a_list ] |
edge_stmt | (node_id | subgraph) edgeRHS [ attr_list ] |
edgeRHS | edgeop (node_id | subgraph) [ edgeRHS ] |
node_stmt | node_id [ attr_list ] |
node_id | ID [ port ] |
port | ':' ID [ ':' compass_pt ] | ':' compass_pt |
subgraph | [ subgraph [ ID ] ] '{' stmt_list '}' |
compass_pt | (n | ne | e | se | s | sw | w | nw | c | _) |
ID 其实就是一个字符串,为该组件的名称或者属性的名称,命名规则如下:
所有的字母
[a-zA-Z\200-\377]
下划线,数字[0-9]
,数字不能出现在起始位置纯数字
所有用双引号引用的字符串
"..."
HTML 格式的字符串
<>
dot 语法的关键字
strict, 严格的图限定,禁止创建多个相同的边
graph, 无向图. 在图的创建时必须声明为有向图还是无向图
digraph, 有向图
node, 节点
edge, 边
subgraph, 子图
通过 dot 的抽象语法可以看到
整个 graph 必须使用 graph 或 digraph {} 进行限定说明图的属性
图里面的声明列表可以为空,也可以为多个,每个声明后的 ; 为可选项
声明有几种类型
节点 node
边 edge
子图 subgraph
属性列表
ID = ID, 这个类型暂时还没有看到有什么作用
属性列表
必须使用中括号 [ ] 将列表项括起来
列表项为可选
属性列表项
以 key = value 的形式存在,列表项可选择 ',' 和 ';' 结尾
可存在多个列表项
边的声明
首端为 节点标识符或者子图,
右部分由边连接节点标识符或者子图构成,右部分可以存在多个
尾部可选属性列表
节点的声明
示例 节点的用法 node0 [label = "<postid1> string|<postid2> string|<postid3> string3", height=.5]` node0:head[color=lightblue] // 设置该部分的颜色
首部为节点标识符 节点部分(post) 方向 组成,其中后两项为可选项。
后半部分为可选的属性列表
方向 | 说明 |
---|---|
n | north 北 |
ne | north east |
e | east 东 |
se | south east 东南 |
s | south 南 |
sw | south west 西南 |
w | west 西 |
nw | north west 西北 |
c | center 中部 |
_ | 任意 |
一个方向的示例
digraph action { node [shape = record,height=.1]; node0 [label = "<head> head|<body> body|<foot> foot", height=.5] node2 [shape = box label="mind"] node0:head:n -> node2:n [label = "n"] node0:head:ne -> node2:ne [label = "ne"] node0:head:e -> node2:e [label = "e"] node0:head:se -> node2:se [label = "se"] node0:head:s -> node2:s [label = "s"] node0:head:sw -> node2:sw [label = "sw"] node0:head:w -> node2:w [label = "w"] node0:head:nw -> node2:nw [label = "nw"] node0:head:c -> node2:c [label = "c"] node0:head:_ -> node2:_ [label = "_"] node0:body[style=filled color=lightblue] }
效果如下 图-1
绘制属性
一个图中有非常多的 node 和 edge,如果每次都需要声明一个节点的属性会非常麻烦,有一个简单的方式为声明一个公共的属性如
digraph action { rankdir = LR // 设置方向 node [shape=box color=blue] edge [color=red] node1 // 默认节点属性 node2 [color=lightblue] // 属于该节点的颜色属性 node1 -> node2 // 默认边属性 node2 -> node1 [color=green] // 属于该变的属性}
在声明位置之后的节点都有一个 默认 的形状和颜色属性。
全部的属性见graphviz官网,这里列举部分常用的属性
charset 编码,一般设置 UTF-8
fontname 字体名称,这个在中文的情况需要设置,否则导出图片的时候会乱码,一般设置微软雅黑("Microsoft YaHei"), linux 下也是同样设置系统带的字体就好,其他字体设置见fontpath 属性
fontcolor 字体颜色
fontsize 字体大小,用于文本内容
fillcolor 用于填充节点或者集群(cluster)的背景颜色。
size 图形的最大宽度和高度
label 图形上的文本标记
margin 设置图形的边距
pad 指定将绘制区域扩展到绘制图形所需的最小区域的长度(以英寸为单位)
style 设置图形组件的样式信息。 对于聚类子图或者节点,如果style = "filled",则填充聚类框的背景
rankdir 设置图形布局的排列方向 (全局只有一个生效). "TB", "LR", "BT", "RL", 分别对应于从上到下,从左到右,从下到上和从右到左绘制的有向图。
ranksep 以英寸为单位提供所需的排列间隔
ratio 设置生成图片的纵横比
节点(node)
节点的默认属性为 shape = ellipse, width = .75, height = 0.5 并且用节点标识符作为节点的显示文字。
如图一中所示,声明两个节点 node0 和 node2,node0 或 node2 就表示这个节点的节点标识符,后面紧跟的是该节点的属性列表;另一种用法为 节点标识符:节点部分:方向[属性列表] node0:body[style=filled color=lightblue]
, 这个为单一节点声明的方式。
节点中最基本的属性为:
shape 形状,全部形状见graphviz官网,一些常用的图形有
width height, 图形的宽度和高度,如果设置了 fixedsize 为 true,则宽和高为最终的长度
fixedsize, 如果为false,节点的大小由其文本内容所需要的最小值决定
rank 子图中节点上的排列等级约束. 最小等级是最顶部或最左侧,最大等级是最底部或最右侧。
same. 所有节点都位于同一等级
min. 所有节点都位于最小等级上
source. 所有节点都位于最小等级上,并且最小等级上的唯一节点属于某个等级 source 或 min 的子图.
max sink. 和上类似
边 (edge)
有向图中的的边用 ->
表示,无向图用 --
表示。
可以同时连接多个节点或者子图,但是只能有一个属性列表,如下
digraph { rankdir = LR A -> B -> c[color=green] }
一些关于边的属性如下:
digraph { rankdir = LR splines = ortho A -> B -> C -> D -> F [color = green] E -> F -> B -> D [color = blue] B -> E -> H[color = red] }
len 首选边的长度
weight 边的权重, 权重越大越接近边的长度
lhead 逻辑边缘的头部(箭头那个位置),compound 设置为 true 时,边被裁减到子图的边界处
ltail 类似 lhead
headlabel 边上靠近箭头部分的标签
taillabel 边上靠近尾部部分的标签
设置 A->B->C->D->F的权重最大,修改绿色的分支的权重为 100,使其变成主要逻辑分支。
splines 控制如何以及是否表示边缘。其值如下
none 或者 "", 无边
true 或者 spline, 样条线(无规则,可为直或者曲线)
false 或者 line, 直线段
polyline, 折线
curved, 曲弧线,两条?
ortho, 正直的线(横竖)
dir 设置绘制箭头的边缘类型
子图
subgraph 必须配合 cluster 一起使用,用法为 subgraph cluster* {}
需要设置 compound 为 true,则在群集之间留出边缘,子图的边界关系在 边 的定义中有给出,这里直接给个示例。
digraph G { compound = true // 允许子图间存在边 ranksep = 1 node [shape = record] subgraph cluster_hardware { label = "hardware" color = lightblue CPU Memory } subgraph cluster_kernel { label = "kernel" color = green Init IPC } subgraph cluster_libc { label = "libc" color = yellow glibc } CPU -> Init [lhead = cluster_kernel ltail = cluster_hardware] IPC -> glibc [lhead = cluster_libc ltail = cluster_kernel] }
示例
TCP IP 状态流程图
展示了两个版本,怎么把这些图形节点稍微规范的显示出来
digraph { compound=true fontsize=10 margin="0,0" ranksep = .75 nodesep = .65 node [shape=Mrecord fontname="Inconsolata, Consolas", fontsize=12, penwidth=0.5] edge [fontname="Inconsolata, Consolas", fontsize=10, arrowhead=normal] "TCP/IP State Transition" [shape = "plaintext", fontsize = 16] // now start server state transition "CLOSED" -> "LISTEN" [style = blod, label = "应用:被动打开\n发送:<无>"]; "LISTEN" -> "SENT_REVD" [style = blod, label = "接收:SYN\n发送:SYN,ACK"] "SENT_REVD" -> "ESTABLISHED" [style = blod, label = "接收:ACK\n发送:<无>", weight = 20] "ESTABLISHED" -> "CLOSE_WAIT" [style = blod, label = "接收:FIN\n发送:ACK", weight = 20] subgraph cluster_passive_close { style = dotted margin = 10 passive_close [shape = plaintext, label = "被动关闭", fontsize = 14] "CLOSE_WAIT" -> "LAST_ACK" [style = blod, label = "应用:关闭\n发送:FIN", weight = 10] } "LAST_ACK" -> "CLOSED" [style = blod, label = "接收:ACK\n发送:<无>"] // now start client state transition "CLOSED" -> "SYN_SENT" [style = dashed, label = "应用:主动打开\n发送:SYN"]; "SYN_SENT" -> "ESTABLISHED" [style = dashed, label = "接收:SYN,ACK\n发送:ACK", weight = 25] "SYN_SENT" -> "SENT_REVD" [style = dotted, label = "接收:SYN\n发送:SYN,ACK\n同时打开"] "ESTABLISHED" -> "FIN_WAIT_1" [style = dashed, label = "应用:关闭\n发送:FIN", weight = 20] subgraph cluster_active_close { style = dotted margin = 10 active_open [shape = plaintext, label = "主动关闭", fontsize = 14] "FIN_WAIT_1" -> "FIN_WAIT_2" [style = dashed, label = "接收:ACK\n发送:<无>"] "FIN_WAIT_2" -> "TIME_WAIT" [style = dashed, label = "接收:FIN\n发送:ACK"] "FIN_WAIT_1" -> "CLOSING" [style = dotted, label = "接收:ACK\n发送:<无>"] "FIN_WAIT_1" -> "TIME_WAIT" [style = dotted, label = "接收:SYN,ACK\n发送:ACK"] "CLOSING" -> "TIME_WAIT" [style = dotted] } "TIME_WAIT" -> "CLOSED" [style = dashed, label = "2MSL超时"] }
这是一个很挫的版本,排版乱飞了。
digraph rankdot { compound=true margin="0,0" ranksep = .75 nodesep = 1 pad = .5 //splines = ortho node [shape=Mrecord, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=14] edge [charset = "UTF-8" fontname="Microsoft YaHei", fontsize=11, arrowhead = normal] CLOSED -> LISTEN [style = dashed, label = "应用:被动打开\n发送:<无>", weight = 100]; "TCP/IP State Transition" [shape = "plaintext", fontsize = 16] { rank = same SYN_RCVD SYN_SENT point_1 [shape = point, width = 0] SYN_SENT -> point_1 [style = dotted, label = "应用关闭或者超时"] // SYN_SENT -> SYN_RCVD 这个一行代码和上一行冲突了,syn_sent 会在syn_rcvd右边 SYN_RCVD -> SYN_SENT [style = dotted, dir = back, headlabel = "接收:SYN\n发送:SYN,ACK\n同时打开"] } LISTEN -> SYN_RCVD [style = dashed, headlabel = "接收:SYN\n发送:SYN,ACK"] SYN_RCVD -> LISTEN [style = dotted, headlabel = "接收:RST"] CLOSED:es -> SYN_SENT [style = blod, label = "应用:主动打开\n发送:SYN"] { rank = same ESTABLISHED CLOSE_WAIT ESTABLISHED -> CLOSE_WAIT [style = dashed, label = "接收:SYN,ACK\n发送:ACK"] } SYN_RCVD -> ESTABLISHED [style = dashed, label = "接收:ACK\n发送:<无>", weight = 9] SYN_SENT -> ESTABLISHED [style = blod, label = "接收:SYN,ACK\n发送:ACK", weight = 10] { rank = same FIN_WAIT_1 CLOSING LAST_ACK point_2 [shape = point, width = 0] FIN_WAIT_1 -> CLOSING [style = dotted, label = "接收:FIN\n发送:ACK"] LAST_ACK -> point_2 [style = dashed, label = "接收:ACK\n发送:<无>"] } CLOSE_WAIT -> LAST_ACK [style = dashed, label = "应用:关闭\n发送:FIN", weight = 10] { rank = same FIN_WAIT_2 TIME_WAIT point_3 [shape = point, width = 0] TIME_WAIT -> point_3 [style = blod, label = "2MSL超时"] } ESTABLISHED -> FIN_WAIT_1 [style = blod, label = "应用:关闭\n发送:FIN"] FIN_WAIT_1 -> FIN_WAIT_2 [style = blod, headlabel = "接收:ACK\n发送:<无>", weight = 15] FIN_WAIT_2 -> TIME_WAIT [style = blod, label = "接收:FIN\n发送:ACK", weight = 10] CLOSING -> TIME_WAIT [style = dotted, label = "接收:ACK\n发送:<无>", weight = 15] FIN_WAIT_1 -> TIME_WAIT [style = dotted, label = "接收:ACK\n发送:<无>"] point_3 -> point_2 [arrowhead = none, style = dotted, weight = 10] point_2 -> point_1 [arrowhead = none, style = dotted] point_1 -> CLOSED [style = dotted] }
这个版本看起来有内味了,最最最的主要的原因就是我使用 rank = same 属性,将一些图形固定在 同一行,一些需要横竖的直线的地方使用 weight 来调整权重,达到横竖的直接的效果,很多地方都是微调的结果。有一个很差的地方是 使用了rank限制若干图形后,就不能使用 subgraph 属性了,这样就不能在若干不同部分的节点周边画线(对比关闭的区域)了。
epoll 相关数据结构及关系
digraph rankdot { compound=true margin="0,0" ranksep = .75 nodesep = 1 pad = .5 rankdir = LR node [shape=record, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=14] edge [style = dashed, charset = "UTF-8" fontname="Microsoft YaHei", fontsize=11] epoll [shape = plaintext, label = "epoll 相关结构及部分关系"] eventpoll [ color = cornflowerblue, label = "<eventpoll> struct \n eventpoll | <lock> spinlock_t lock; | <mutex> struct mutex mtx; | <wq> wait_queue_head_t wq; | <poll_wait> wait_queue_head_t poll_wait; | <rdllist> struct list_head rdllist; | <ovflist> struct epitem *ovflist; | <rbr> struct rb_root_cached rbr; | <ws> struct wakeup_source *ws; | <user> struct user_struct *user; | <file> struct file *file; | <visited> int visited; | <visited_list_link> struct list_head visited_list_link;" ] epitem [ color = sienna, label = "<epitem> struct \n epitem | <rb>struct rb_node rbn;\nstruct rcu_head rcu; | <rdllink> struct list_head rdllink; | <next> struct epitem *next; | <ffd> struct epoll_filefd ffd; | <nwait> int nwait; | <pwqlist> struct list_head pwqlist; | <ep> struct eventpoll *ep; | <fllink> struct list_head fllink; | <ws> struct wakeup_source __rcu *ws; | <event> struct epoll_event event;" ] epitem2 [ color = sienna, label = "<epitem> struct \n epitem | <rb>struct rb_node rbn;\nstruct rcu_head rcu; | <rdllink> struct list_head rdllink; | <next> struct epitem *next; | <ep> struct eventpoll *ep; | ··· | ··· " ] eppoll_entry [ color = darkviolet, label = "<entry> struct \n eppoll_entry | <llink> struct list_head llink; | <base> struct epitem *base; | <wait> wait_queue_entry_t wait; | <whead> wait_queue_head_t *whead;" ] epitem:ep -> eventpoll:se [color = sienna] epitem2:ep -> eventpoll:se [color = sienna] eventpoll:ovflist -> epitem:next -> epitem2:next [color = cornflowerblue] eventpoll:rdllist -> epitem:rdllink -> epitem2:rdllink [dir = both] eppoll_entry:llink -> epitem:pwqlist [color = darkviolet] eppoll_entry:base -> epitem:nw [color = darkviolet] }
遗留问题
在以上TCP/IP 状态变迁图中,尝试增加主动关闭方的区域边框
尝试增加 TCP/IP 的时序图
使用 VSCode 进行预览生成
在官网下载graphviz安装包
安装 vscode 插件
Graphviz Preview
在 settings.json 中添加
"graphvizPreview.dotPath": "graphviz_path\graphviz-2.38\\release\\bin\\dot.exe"
, graphviz_path 为所在路径,这些修改一下既可新建一个 dot 文件,右上角就会有预览生成的按钮了
12/05 更新,用了一圈发现并没有那么好用,自动排版是优势,但有的时候也是劣势,需要固定位置的作图时还是手动控制比较好一些,ProcessOn 用了几次觉得很不错,推荐!
参考
转载本站文章《数据可视化Graphviz程序化绘图—学习资料收藏整理笔记》,
请注明出处:https://www.zhoulujun.cn/html/Operation/UML/2016_0713_8291.html