今年由於項目上需要給客戶的流程管理系統進行升級,其中包含流程的可視化。於是在網上找一些可以用的輪子,考察了D3,js、GooFlow.js、G6-Editor等工具後,發現D3,js學習成本太高,G6-Editor功能基本夠用,但是不能放大縮小圖形和移動連接線是硬傷,而且GooFlow.js和G6- ...
今年由於項目上需要給客戶的流程管理系統進行升級,其中包含流程的可視化。於是在網上找一些可以用的輪子,考察了D3,js、GooFlow.js、G6-Editor等工具後,發現D3,js學習成本太高,G6-Editor功能基本夠用,但是不能放大縮小圖形和移動連接線是硬傷,而且GooFlow.js和G6-Editor目前已經閉源,於是就決定自己試著寫一個流程編輯器。
由於之前畫流程圖基本使用的都是Visio,所以編輯器的操作風格基本和Visio一致,也吸收了一些G6-Editor的操作風格。
HTML內容如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test Page</title> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="shortcut icon" href="assets/img/favicon.jpg"> <link rel="stylesheet" href="assets/css/bootstrap.min.css" type="text/css" /> <link rel="stylesheet" href="assets/css/font-awesome.min.css" type="text/css" /> <link rel="stylesheet" href="assets/css/editor.css" type="text/css" /> </head> <body> <div style="width:99%"> <div id="toolbar" class="toolbar"> <i data-command="undo" class="fa fa-mail-reply" title="撤銷"></i> <i data-command="redo" class="fa fa-mail-forward" title="重做"></i> <i data-command="delete" class="fa fa-trash-o" title="刪除"></i> <i data-command="save" class="fa fa-save" title="保存"></i> </div> <div class="row" style="width:99%"> <div class="col-md-2"> <div id="itempannel" class="itempannel"> <div class="node" data-type="node" data-shape="capsule" data-size="80*40" data-label="開始" data-color="rgba(200,255,200,1)" data-edgecolor="rgb(10,255,10,1)" > <img draggable="false" src="assets/img/start.svg" alt="" scrset=""> </div> <div class="node" data-type="node" data-shape="rect" data-size="100*50" data-label="常規節點" data-color="#E6F7FF" data-edgecolor="#1890FF" > <img draggable="false" src="assets/img/normal.svg" alt="" scrset=""> </div> <div class="node" data-type="node" data-shape="rhomb" data-size="100*80" data-label="判斷" data-color="#E6FFFB" data-edgecolor="#5CDBD3" > <img draggable="false" src="assets/img/judge.svg" alt="" scrset=""> </div> <div class="node" data-type="node" data-shape="capsule" data-size="80*40" data-label="結束" data-color="rgba(255,200,200,1)" data-edgecolor="rgb(255,50,50,1)" > <img draggable="false" src="assets/img/end.svg" alt="" scrset=""> </div> </div> </div> <div class="col-md-7"> <div id="canvas"></div> </div> <div class="col-md-3"> <div id="detailpannel" class="detailpannel"> <div><b>屬性詳情</b></div> <div id="nodedetail" style="display:none"> <div class="row"> <label class="col-sm-4 control-label"> 節點名稱 </label> <div class="col-sm-8"> <input id="nodename" type="text" class="form-control" /> </div> </div> <div class="row"> <label class="col-sm-4 control-label"> 字體大小 </label> <div class="col-sm-8"> <input id="fontsize_node" type="text" class="form-control" /> </div> </div> <div class="row"> <label class="col-sm-4 control-label"> 自定義屬性 </label> <div class="col-sm-8"> <input id="nodecustom" type="text" class="form-control" /> </div> </div> </div> <div id="linedetail" style="display:none"> <div class="row"> <label class="col-sm-4 control-label"> 線條名稱 </label> <div class="col-sm-8"> <input id="linename" type="text" class="form-control" /> </div> </div> <div class="row"> <label class="col-sm-4 control-label"> 字體大小 </label> <div class="col-sm-8"> <input id="fontsize_line" type="text" class="form-control" /> </div> </div> <div class="row"> <label class="col-sm-4 control-label"> 自定義屬性 </label> <div class="col-sm-8"> <input id="linecustom" type="text" class="form-control" /> </div> </div> </div> </div> </div> </div> </div> <script type="text/javascript" src="assets\js\popper.min.js"></script> <script type="text/javascript" src="assets\js\jquery.min.js"></script> <script type="text/javascript" src="assets\js\bootstrap.min.js"></script> <script type="text/javascript" src="assets\js\graphEditor.js"></script> <script type="text/javascript" src="assets\js\index.js"></script> </body> </html>
其中graphEditor.js就是編輯器的js文件,編輯器的畫布載入在id為canvas的容器中。
index.js文件內容如下:
const data = {}; //節點格式 data.nodes = [ { color:"rgba(200,200,255,1)", customProperties: { nodestyle: "11" }, edgeColor:"#1890FF", id: "9471a484-7a2e-4ca9-949d-6ec4b495a1a2", index: 1, label: "測試載入節點", shape: "rect", size: "100*50", type: "node", x: 282, y: 222 }, { color:"rgba(200,200,255,1)", customProperties: { nodestyle: "22" }, edgeColor:"#1890FF", id: "9471a484-7a2e-4ca9-949d-6ec4b495a1a3", index: 2, label: "測試載入判斷", shape: "rhomb", size: "100*80", type: "node", x: 282, y: 422, fontSize: "12px" } ] //線條格式 data.lines = [ { color:"rgba(0,0,0,1)", customProperties: { btnstyle: "test" }, index: 3, label: "測試線條1", fontSize: "12px", fromConOrder: 3, source: "9471a484-7a2e-4ca9-949d-6ec4b495a1a2", type: "line", endPoint: { x: 81, y: 210 }, inflexionPoint: [ { x: 282, y: 272 }, { x: 81, y: 272 } ] }, { color:"rgba(0,0,0,1)", customProperties: { btnstyle: "test" }, index: 4, label: "測試線條2", fontSize: "14px", toConOrder: 1, startPoint: { x: 555, y: 447 }, target: "9471a484-7a2e-4ca9-949d-6ec4b495a1a3", type: "line", inflexionPoint: [ { x: 555, y: 357 }, { x: 282, y: 357 } ] } ] //創建編輯器 const editor = new GraphEditor(); //編輯器載入數據 //editor.load(data); //綁定元素容器 editor.itemPannel = 'itempannel'; //綁定詳細屬性欄 editor.detailPannel = 'detailpannel'; //綁定工具欄 editor.toolbar = 'toolbar'; const nodeDetail = $('#nodedetail'); const lineDetail = $('#linedetail'); const nodeName = $('#nodename'); const lineName = $('#linename'); const nodeCustom = $('#nodecustom'); const lineCustom = $('#linecustom'); const nodeFontSize = $('#fontsize_node'); const lineFontSize = $('#fontsize_line'); //選擇元素事件 editor.on('selectedElement', function(e) { if(e.element.isNode){ lineDetail.hide(); nodeDetail.show(); nodeName.val(e.element.label); nodeFontSize.val(e.element.fontSize); nodeCustom.val(e.element.customProperties.nodestyle); } else if (e.element.isLine){ nodeDetail.hide(); lineDetail.show(); lineName.val(e.element.label); lineFontSize.val(e.element.fontSize); lineCustom.val(e.element.customProperties.btnstyle); } }); //點擊畫布事件 editor.on('click', function(e) { if(!e.element){ nodeDetail.hide(); lineDetail.hide(); } }); //撤銷事件 editor.on('undo', function(e) { nodeDetail.hide(); lineDetail.hide(); //console.log(e.data); }); //重做事件 editor.on('redo', function(e) { nodeDetail.hide(); lineDetail.hide(); //console.log(e.data); }); //保存事件 editor.on('save', function(e) {console.log(e.data)}); nodeName.change(function(e){ updateEditor(e, 'label'); }); lineName.change(function(e){ updateEditor(e, 'label'); }); nodeFontSize.change(function(e){ updateEditor(e, 'fontSize'); }); lineFontSize.change(function(e){ updateEditor(e, 'fontSize'); }); nodeCustom.change(function(e){ updateEditor(e, 'nodestyle'); }); lineCustom.change(function(e){ updateEditor(e, 'btnstyle'); }); function updateEditor(e, name){ const property = {}; property.name = name; property.value = $('#' +e.target.id).val(); if (editor.selectedElement){ editor.update(editor.selectedElement.id, property); }; }
GraphEditor方法列表:
- load(data):用於載入數據,data的結構參考index.js。
node節點的屬性如下:
color:節點填充顏色 edgeColor:節點邊框顏色 shape:“rect”為矩形,“rhomb”為菱形,“capsule”為膠囊 id:節點編號 index:節點層級 label:文字內容 fontSize:文字大小 size:節點的長和寬,例如“100*50” type:“node” x:節點x軸坐標 y:節點y軸坐標 customProperties:自定義屬性,用於保存業務相關數據 line線條的屬性如下: color:線條顏色 id:線條編號 index:線條層級 label:文字內容 fontSize:文字大小 type:“line” source:線條起始節點的編號 target:線條結束節點的編號 fromConOrder:起始節點對應的錨點編號 toConOrder:結束節點對應的錨點編號 startPoint:起始點坐標,用於無起始節點情況 endPoint:結束點坐標,用於無結束節點情況 inflexionPoint:線條拐點集合- update(id,property)
id:元素的編號
property:{ name: 要更新的屬性名稱, value:更新的值 }
GraphEditor事件列表:
- selectedElement:選中任何canvas元素觸發,事件對象的element屬性存放選中的元素
- click:點擊canvas容器觸發
- undo:撤銷操作觸發
- redo:重做操作觸發
- save:保存操作觸發,事件對象的data屬性存放畫布中所有的元素,編輯器對象的data屬性也包含畫布所有元素
GraphEditor快捷鍵列表:
- Delete:刪除畫布上的元素
- Ctrl+z:撤銷
- Ctrl+y:重做
- Ctrl+s:保存
Demo下載地址:https://files.cnblogs.com/files/lixincloud/GraphEditorDemo.rar