使用graphviz绘图

2015年11月10日更新 在实践中,我又发现了一些graphviz的有趣的特性,比如时序图,rank以及图片节点等。在这里一并更新。 ##前言 日常的开发工作中,为代码添加注释是代码可维护性的一个重要方面,但是仅仅提供注释是不够的,特别是当系统功能越来越复杂,涉及到的模块越来越多的时候,仅仅靠代码就很难从宏观的层次去理解。因此我们需要图例的支持,图例不仅仅包含功能之间的交互,也可以包含复杂的数据结构的示意图,数据流向等。 但是,常用的UML建模工具,如Visio等都略显复杂,且体积庞大。对于开发人员,特别是后台开发人员来说,命令行,脚本才是最友好的,而图形界面会很大程度的限制开发效率。相对于鼠标,键盘才是开发人员最好的朋友。 ###graphviz简介 本文介绍一个高效而简洁的绘图工具graphviz。graphviz是贝尔实验室开发的一个开源的工具包,它使用一个特定的DSL(领域特定语言): dot作为脚本语言,然后使用布局引擎来解析此脚本,并完成自动布局。graphviz提供丰富的导出格式,如常用的图片格式,SVG,PDF格式等。 graphviz中包含了众多的布局器: dot 默认布局方式,主要用于有向图 neato 基于spring-model(又称force-based)算法 twopi 径向布局 circo 圆环布局 fdp 用于无向图 graphviz的设计初衷是对有向图/无向图等进行自动布局,开发人员使用dot脚本定义图形元素,然后选择算法进行布局,最终导出结果。 首先,在dot脚本中定义图的顶点和边,顶点和边都具有各自的属性,比如形状,颜色,填充模式,字体,样式等。然后使用合适的布局算法进行布局。布局算法除了绘制各个顶点和边之外,需要尽可能的将顶点均匀的分布在画布上,并且尽可能的减少边的交叉(如果交叉过多,就很难看清楚顶点之间的关系了)。所以使用graphviz的一般流程为: 定义一个图,并向图中添加需要的顶点和边 为顶点和边添加样式 使用布局引擎进行绘制 一旦熟悉这种开发模式,就可以快速的将你的想法绘制出来。配合一个良好的编辑器(vim/emacs)等,可以极大的提高开发效率,与常见的GUI应用的所见即所得模式对应,此模式称为所思即所得。比如在我的机器上,使用Sublime Text 编辑dot脚本,然后将F7/Cmd-B映射为调用dot引擎去绘制当前脚本,并打开一个新的窗口来显示运行结果: 对于开发人员而言,经常会用到的图形绘制可能包括:函数调用关系,一个复杂的数据结构,系统的模块组成,抽象语法树等。 ###基础知识 graphviz包含3中元素,图,顶点和边。每个元素都可以具有各自的属性,用来定义字体,样式,颜色,形状等。下面是一些简单的示例,可以帮助我们快速的了解graphviz的基本用法。 ####第一个graphviz图 比如,要绘制一个有向图,包含4个节点a,b,c,d。其中a指向b,b和c指向d。可以定义下列脚本: digraph abc{ a; b; c; d; a -> b; b -> d; c -> d; } 使用dot布局方式,绘制出来的效果如下: 默认的顶点中的文字为定义顶点变量的名称,形状为椭圆。边的默认样式为黑色实线箭头,我们可以在脚本中做一下修改,将顶点改为方形,边改为虚线。 ####定义顶点和边的样式 在digraph的花括号内,添加顶点和边的新定义: node [shape="record"]; edge [style="dashed"]; 则绘制的效果如下: ####进一步修改顶点和边样式 进一步,我们将顶点a的颜色改为淡绿色,并将c到d的边改为红色,脚本如下: digraph abc{ node [shape="record"]; edge [style="dashed"]; a [style="filled", color="black", fillcolor="chartreuse"]; b; c; d; a -> b; b -> d; c -> d [color="red"]; } 绘制的结果如下:...

November 10, 2015 6 min

Linux命令行中的7个小技巧

Linux命令行中的7个小技巧 命令行是开发者的好朋友,*nix系统(包括Mac OS X和各种Linux)都自带了强大的Shell环境,作为一个专业的程序员,Shell是离不开的。这里总结了几个常用的小技巧,都是我自己平时经常用,而又不想每次都去Google的。 如何知道哪个进程在监听4000端口? 当启动服务时,经常会遇到想要Address already in use这样的异常,那么如何知道是哪个进程占用了该端口呢? $ lsof -i :4000 lsof会列出系统目前打开的文件(List open files,Linux世界中,一切都是文件),-i表示网络地址(Internet address),注意此处的冒号。如果不带参数,lsof会列出所有打开的网络地址: $ lsof -i :4000 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ruby 32303 jtqiu 7u IPv4 0x947b402a4bb8370b 0t0 TCP *:terabase (LISTEN) Shell中如何设置代理? 很多公司都会有一个代理服务器供员工上外网使用,命令行中设置代理非常容易: export http_proxy="http://username:password@hosename:port" 如果密码中有特设字符,比如$的话,需要转义一下。使用URLEncoding即可,比如$就是%24。最简单的就是在Chrome的Console中输入encodeURIComponent("$")来获得转义字符。 如果不想对某些地址使用代理,可以设置no_proxy环境变量: export no_proxy="127.0.0.1, localhost, *.cnn.com, 192.168.1.10, domain.com:8080" 如何为svn中的脚本设置可执行属性 你在Linux下创建了一个可执行脚本e2e_test.sh,但是团队里的其他人工作在Windows系统上,当你提交可执行脚本之后,他们checkout的是一个不能执行的文件!(其实也在情理之中,Windows这种垃圾货什么时候正常过呢?) 这时候可以通过给这个脚本设置一个svn的属性: svn propset svn:executable "*" e2e_test.sh 这样在Windows上才heckout之后就正常了。 在Bash脚本中如何判断一个文件是否可执行? 有时候我们在Bash中需要判断某个文件是否可以执行,这行脚本可以解救你: if [ -x $FILENAME ]; then # the file is executable fi 判断某个文件是否存在的脚本为:...

February 14, 2015 2 min