大型軟件系統中離不開各種狀態機的處理,平常工做中也涉及到各種事務處理流程;從表現力看文不如表,表不如圖;所以平常工做中常常須要繪製各類狀態機的狀態轉換圖和流程圖,以協助理解代碼邏輯和各種事務處理流程等。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), 點擊按鈕從新繪圖便可。