大型软件系统中离不开各种状态机的处理,平常工做中也涉及到各种事务处理流程;从表现力看文不如表,表不如图;所以平常工做中常常须要绘制各类状态机的状态转换图和流程图,以协助理解代码逻辑和各种事务处理流程等。node
绘制此类图形的经常使用工具备visio,liberoffice draw等,这些软件采用"所见即所得"的设计思想,彻底由手动放置形状、填充文本、绘制线条、拖动箭头指向关系、调整文本格式、调整布局等等。此类工具优势是绘图直观、布局可控;缺点一是须要安装专门的软件;二是过于繁琐,以状态机状态转换图为例,当状态和激励较多时,表示状态的矩形框和表示激励的线条会显得很凌乱,新添加状态或者激励时可能须要从新调整已有状态和激励的布局。python
为了提升绘图效率,同时让绘图在每台电脑上随时可用,以及随时能够访问已经绘制的图形协助代码分析,用python+graphviz开发了状态机在线绘图工具。web
工具如下列格式的文本做为输入:django
source:XXX; trigger:YYY; destination:ZZZ; color="red"
上述文本表示在XXX状态下收到YYY激励会跳转到新的ZZZ状态;color="red"表示该条边绘制为红色,能够设置其余颜色,不设置默认为黑色。网络
注意:上述输入文本格式中,source,tigger,destination后面必需要有英文冒号(:)和英文分号(;)。app
只要在输入框内输入多条上述语句,点击按钮便可一键自动绘图,只要有网络访问便可,不须要安装软件,不须要手动绘制各种形状和线条。工具
下图是工具绘图的简单示例:布局
访问http://www.tasksteper.com:8099/flow/home/;以用户名/密码:testuser1/ testuser1登陆;进入“集成工具”项目后;点击“建立条目”;spa
概要栏随便填写,输入栏输入如下文本框中的内容;点击“建立”按钮后;在刷新后的界面点击“Graphviz绘图”按钮,便可在页面右侧看到绘制的状态转换图;以下图所示:设计
输入内容:
source:吃饭; trigger:goto睡觉; destination:睡觉;
source:吃饭; trigger:goto打豆豆; destination:打豆豆;
source:睡觉; trigger:goto打豆豆; destination:打豆豆;
source:睡觉; trigger:goto吃饭; destination:吃饭;
source:打豆豆; trigger:goto吃饭; destination:吃饭;
source:打豆豆; trigger:goto睡觉; destination:睡觉;
输出:
绘图的实现步骤以下:
1.后台接收输入表单中的文本内容,并根据换行符,将一行内容做为一个字符串;
2.循环判断每一个字符串是否知足以下格式:
source:XXX; trigger:YYY; destination:ZZZ;
若知足,则在dot语言中生成XXX,ZZZ两个节点,以及一条XXX指向ZZZ的边;节点信息记录到node_database中,边信息记录到edge_string中;
3.全部字符串遍历完成后,根据node_database和edge_string中记录的信息生成用于graphviz绘图的临时dot语言脚本;
4.在后台调用步骤3生成的dot语言脚本进行绘图,生成图形后并将图形显示在web界面上,随后删除dot语言脚本;
接收输入表单数据,并生成dot语言进行绘图的python代码以下所示:
1 def tools_draw_pygraphviz(request, model_instance): 2 prefix = '''digraph graphviz { 3 graph [ 4 //rankdir = "LR" 5 //splines=polyline 6 overlap=false 7 bgcolor="#FFFFCE" 8 ]; 9 10 node [ 11 fontsize = "16" 12 shape = "ellipse" 13 ]; 14 15 edge [ 16 ]; 17 ''' 18 edge_string = '' 19 space4 = ' ' 20 space8 = space4 + space4 21 node_database = {} 22 node_database['created'] = [] 23 tmpline = "" 24 for tmpchar in model_instance.detail: 25 if tmpchar == '\n': 26 m = re.search(r'source: *([^\s].*[^\s]) *;.*trigger: *([^\s].*[^\s]) *;.*destination: *([^\s].*[^\s]) *;(.*)', tmpline) 27 if m: 28 if m.group(1) not in node_database['created']: 29 node_database['created'].append(m.group(1)) 30 if m.group(3) not in node_database['created']: 31 node_database['created'].append(m.group(3)) 32 n = re.search(r'(color *= *\"[^\"]*\")', m.group(4)) 33 if n: 34 color_string = ', '+n.group(1) 35 else: 36 color_string = '' 37 edge_string = edge_string + "\"" + m.group(1) + "\"" + "->" + "\"" + m.group(3) + "\"" + "[ label = \"" + m.group(2) + "\"" +color_string+ "]\n" + space4 38 tmpline = "" 39 else: 40 tmpline = tmpline + tmpchar 41 42 m = re.search(r'source: *([^\s].*[^\s]) *;.*trigger: *([^\s].*[^\s]) *;.*destination: *([^\s].*[^\s]) *;(.*)', tmpline) 43 if m: 44 if m.group(1) not in node_database['created']: 45 node_database['created'].append(m.group(1)) 46 if m.group(3) not in node_database['created']: 47 node_database['created'].append(m.group(3)) 48 n = re.search(r'(color *= *\"[^\"]*\")', m.group(4)) 49 if n: 50 color_string = ', '+n.group(1) 51 else: 52 color_string = '' 53 edge_string = edge_string + "\"" + m.group(1) + "\"" + "->" + "\"" + m.group(3) + "\"" + "[ label = \"" + m.group(2) + "\"" +color_string+ "]\n" + space4 54 for tmp_node in node_database['created']: 55 tmp_node_string = space4 + "\"" + tmp_node + "\" [\n" + space8 + "label = \"" + tmp_node + "\"\n" + space8 + "shape = \"record\"\n" + space4 + "];\n" 56 prefix = prefix + tmp_node_string 57 image_path = '/root/virenv_python3/django_for_study/mysite/polls/static/polls/images/' 58 output_file = image_path + 'tools_graphviz_' + str(model_instance.id) + model_instance.graphviz_format 59 dot_file = image_path + 'dot_' + str(model_instance.id) 60 with open(dot_file,'w+') as f_output: 61 f_output.write(prefix + space4 + edge_string + "\n}") 62 if os.path.exists(output_file): 63 os.remove(output_file) 64 dot_cmd = model_instance.graphviz_style+' -T'+ model_instance.graphviz_format[1:] + ' ' + dot_file +' -o ' + output_file 65 os.system(dot_cmd) 66 os.remove(dot_file)
由上述代码python解析表单输入自动生成的dot脚本以下所示:
digraph graphviz { graph [ //rankdir = "LR" //splines=polyline overlap=false bgcolor="#FFFFCE" ]; node [ fontsize = "16" shape = "ellipse" ]; edge [ ]; "吃饭" [ label = "吃饭" shape = "record" ]; "睡觉" [ label = "睡觉" shape = "record" ]; "打豆豆" [ label = "打豆豆" shape = "record" ]; "吃饭"->"睡觉"[ label = "goto睡觉"] "吃饭"->"打豆豆"[ label = "goto打豆豆"] "睡觉"->"打豆豆"[ label = "goto打豆豆"] "睡觉"->"吃饭"[ label = "goto吃饭"] "打豆豆"->"吃饭"[ label = "goto吃饭"] "打豆豆"->"睡觉"[ label = "goto睡觉"] }
下图是实际工做中所涉及FC协议的端口状态机跳转流程:
其中红色表示端口开工主流程,蓝色表示端口停工流程;比代码直观许多。
该绘图工具具备如下优点:
1.自动布局自动绘图,避免了手动放置形状、填充文本、绘制线条、拖动箭头指向关系、调整文本格式、调整布局等一系列繁琐的操做;
添加新的状态跳转描述时,只须要点击按钮一键从新绘图便可,不须要关心以前的布局怎样;
2. 代码中的状态转换描述能够轻易的经脚本进行格式化处理为以下格式:
source:XXX; trigger:YYY; destination:ZZZ;
随后将格式化处理后的文本贴入网页就能够一键绘图;对于一些复杂的状态机(好比20+个状态,20+个激励)手动绘制可能须要两天左右,利用脚本预处理并利用网页生成仅须要几分钟;
3.只要能访问网络就随时随地可用,不须要安装visio等绘图工具,节约绘图前等待软件启动的时间;
4.支持设置颜色,将主要流程以颜色区分显示,便于理解;如上图中的端口启动和中止流程分别以红色和蓝色显示。
5.纯文本的输入便于批量修改,好比LLL, MMM, NNN等多个状态下都收到YYY激励,咱们须要加上激励编号将YYY修改成YYY(05),在visio等绘图工具中须要手动修改多个状态下YYY激励对应的线条上的描述;使用web绘图工具只须要将输入中的YYY全文替换成YYY(05), 点击按钮从新绘图便可。