為了方便演示,我已經把一個靜態DEMO部署到github,傳送門 關於項目訪問,因為讀取遠程json數據,直接用瀏覽器打開有安全限制.請將項目放到tomcat啟動訪問或者直接用idea/webstorm打開項目直接右鍵打開,如下圖所示: 完整代碼單獨放到了github:https://github. ...
為了方便演示,我已經把一個靜態DEMO部署到github,傳送門
關於項目訪問,因為讀取遠程json數據,直接用瀏覽器打開有安全限制.請將項目放到tomcat啟動訪問或者直接用idea/webstorm打開項目直接右鍵打開,如下圖所示:
完整代碼單獨放到了github:https://github.com/gongxufan/web-topology
基本的操作可以看下麵的GIF圖,節點從左邊圖標欄拖拽至編輯區,通過單擊節點圖標,然後鬆開滑鼠按鍵則可拖動一條連線。在目標節點單擊,怎完成一次連線操作。左欄可以選擇多種連線方式。
這個拓撲編輯器UI是easyUI做的佈局,節點的編輯和連線則是基於jTopo二次開發的。jTopo提供了Stage、Scene、Node、Container以及對動畫的支持,API使用相對簡單易用。缺點是文檔缺乏,但是我們可以通過作者提供的DEMO進行熟悉。一旦熟悉其源碼,則十分便於二次開發。用來實現一些動畫也是輕而易舉的。由於是基於canvas的繪畫,所以每次更改和操作其實都會重繪整個畫布。當中還是有不少可以優化的地方。其實想D3.js也可以走到這樣的效果,只是近期工作都在後端就沒去探究了。
由於只是摘取個人開發項目的前端部分,由於公司許可原因只能開發這一部分了,有興趣的可以去我的github上clone下來參考。核心部分代碼都在editor.js,提供了節點拖拽、節點連線、佈局等方法支持。這個是一個初始版本。代碼粗糙,僅供參考。
下麵摘取幾個重要的地方稍微講解:
1、編輯器初始化代碼
//創建JTOP舞臺屏幕對象 var canvas = document.getElementById('drawCanvas'); canvas.width = $("#contextBody").width(); canvas.height = $("#contextBody").height(); //載入空白的編輯器 if(stageJson == "-1"){ this.stage = new JTopo.Stage(canvas); this.stage.topoLevel = 1; this.stage.parentLevel = 0; this.modeIdIndex = 1; this.scene= new JTopo.Scene(this.stage); this.scene.totalLevel = 1; }else{ this.stage = JTopo.createStageFromJson(stageJson, canvas); this.scene = this.stage.childs[0]; }
我們可以調用JTopo.Stage(canvas)來初始化一個畫圖區域,接下來就可以使用API進行節點和動畫的操作了。整個畫圖對象的JSON層次結構如下所示:
{ "version": "0.4.8", "deviceNum": "19", "wheelZoom": 0.95, "width": 864, "height": 569, "id": "ST172.19.105.52015100809430700001", "topoLevel": "1", "parentLevel": "0", "nextLevel": "0", "childs": [ { "elementType": "scene", "id": "S172.19.105.52015100809430700002", "topoLevel": "1", "parentLevel": "0", "nextLevel": "0", "translateX": 106.5, "translateY": 20, "scaleX": 1, "scaleY": 1, "totalLevel": "1", "childs": [ { "elementType": "node", "id": "", "topoLevel": 1, "parentLevel": "0", "nextLevel": "0", "x": 211.5, "y": 135, "width": 32, "height": 32, "visible": true, "rotate": 0, "scaleX": 1, "scaleY": 1, "zIndex": 3, "deviceId": "1404683827351.4666", "dataType": "VR", "nodeImage": "tpIcon_9.png", "text": "CS路由器", "textPosition": "Bottom_Center", "templateId": undefined } ] } ] }
其結構為:
通常我們只需要一個 Scene對象即可管理所有的對象,當然如果要實現更複雜的分組對象管理則可以創建多個Scene對象進行單獨管理。同時我們可以調用JTopo.createStageFromJson(stageJson, canvas)方法來講一個保存好的拓撲結構重新渲染。
2、節點的拖拽
節點的拖拽使用的原生H5的drag&drop來實現
/** * 圖元拖放功能實現 * @param modeDiv * @param drawArea */ networkTopologyEditor.prototype.drag = function (modeDiv, drawArea, text) { if (!text) text = ""; var self = this; //拖拽開始,攜帶必要的參數 modeDiv.ondragstart = function (e) { e = e || window.event; var dragSrc = this; var backImg = $(dragSrc).find("img").eq(0).attr("src"); backImg = backImg.substring(backImg.lastIndexOf('/') + 1); var datatype = $(this).attr("datatype"); try { //IE只允許KEY為text和URL e.dataTransfer.setData('text', backImg + ";" + text + ";" + datatype); } catch (ex) { console.log(ex); } }; //阻止預設事件 drawArea.ondragover = function (e) { e.preventDefault(); return false; }; //創建節點 drawArea.ondrop = function (e) { e = e || window.event; var data = e.dataTransfer.getData("text"); var img, text,datatype; if (data) { var datas = data.split(";"); if (datas && datas.length == 3) { img = datas[0]; text = datas[1]; datatype = datas[2]; var node = new JTopo.Node(); node.fontColor = self.config.nodeFontColor; node.setBound((e.layerX ? e.layerX : e.offsetX) - self.scene.translateX - self.config.defaultWidth / 2, (e.layerY ? e.layerY : e.offsetY) - self.scene.translateY - self.config.defaultHeight / 2,self.config.defaultWidth,self.config.defaultHeight); //設備圖片 node.setImage(context + 'post/web-topology/icon/' + img); //var cuurId = "device" + (++self.modeIdIndex); var cuurId = "" + new Date().getTime() * Math.random(); node.deviceId = cuurId; node.dataType = datatype; node.nodeImage = img; ++self.modeIdIndex; node.text = text; node.layout = self.layout; //節點所屬層次 node.topoLevel = parseInt($("#selectLevel").find("option:selected").val()); //節點所屬父層次 node.parentLevel = $("#parentLevel").val(); //子網連接點的下一個層,預設為0 node.nextLevel = "0"; self.scene.add(node); //載入屬性面板 /* if(self.currDataType) self.clearOldPanels(self.currDataType) self.currDeviceId = cuurId; self.createNewPanels(datatype,self.templateId,self.currentModeId);*/ //self.currDataType = datatype; self.currentNode = node; } } if (e.preventDefault()) { e.preventDefault(); } if (e.stopPropagation()) { e.stopPropagation(); } } }
在ondragstart回調方法中傳遞底圖以及必要參數,然後在ondrop進行節點的創建
新建節點使用JTopo.Node()構造,設置好相關屬性然後通過scene.add(node)加入到Stage。為何執行add操作在界面上就可以看到新的節點了呢?原因是Stage有一個frames屬性,它定義了畫布重繪頻率1000/frames。
設置當前舞臺播放的幀數/秒
預設為:24
frames可以為0,表示:不自動繪製,由用戶手工調用Stage對象的paint()方法來觸發。
如果小於0意味著:只有鍵盤、滑鼠有動作時才會重繪,例如:stage.frames = -24。
預設畫面幀數為24幀,也就是每1000/24ms就會重繪屏幕。後臺刷新的代碼如下:
function() { 0 == stage.frames ? setTimeout(arguments.callee, 100) : stage.frames < 0 ? (stage.repaint(), setTimeout(arguments.callee, 1e3 / -stage.frames)) : (stage.repaint(), setTimeout(arguments.callee, 1e3 / stage.frames)) } ()setTimeout會調用下麵的重繪函數,
this.paint = function() { null != this.canvas && (this.graphics.save(), this.graphics.clearRect(0, 0, this.width, this.height), this.childs.forEach(function(a) { 1 == a.visible && a.repaint(stage.graphics) } ), 1 == this.eagleEye.visible && this.eagleEye.paint(this), this.graphics.restore()) } , this.repaint = function() { 0 != this.frames && (this.frames < 0 && 0 == this.needRepaint || (this.paint(), this.frames < 0 && (this.needRepaint = !1))) }paint對遍歷所有可見對象 ,依次調用repaint方法。
3、節點連線
這裡採用的連線方法是在節點按下滑鼠左鍵,然後鬆開滑鼠則創建一個連線,起點是被點擊的節點,終點則隨滑鼠移動而動態更新。因此單機一個節點鬆開滑鼠則可以看到隨滑鼠移動的一條連線。然後在某個節點點擊左鍵鬆開怎完成了兩個節點的連線。效果如下:部分代碼實現如下:
jTopo支持支線、折線、曲線等的常見,但是折線的拐角處長度現在只能在創建的時候指定。如需動態的秒點創建需要二次開發。代碼如下:
if(self.lineType == "line"){ self.link = new JTopo.Link(self.tempNodeA, self.tempNodeZ); self.link.lineType = "line"; }else if(self.lineType == "foldLine"){ self.link = new JTopo.FoldLink(self.tempNodeA, self.tempNodeZ); self.link.lineType = "foldLine"; self.link.direction = self.config.direction; }else if(self.lineType == "flexLine"){ self.link = new JTopo.FlexionalLink(self.tempNodeA, self.tempNodeZ); self.link.direction = self.config.direction; self.link.lineType = "flexLine"; }else if(self.lineType == "curveLine"){ self.link = new JTopo.CurveLink(self.tempNodeA, self.tempNodeZ); self.link.lineType = "curveLine"; }其餘細節在此不做贅述,這個項目需要讀者具備:H5、easyUI、jTopo、canvas、JSON等基本知識。至於jTopo只需看看其作者提供的DEMO和很少的API就可以很快上手。最好的學習方法是打斷點不同的調試跟蹤,查看整個工作機制。這裡演示的拓撲編輯也是一個很簡單不完整的例子,其實還是有很多可以定製化的東西,比如連線以及連線方式都可以進一步定製。 參考資料: http://www.jtopo.com/
http://www.jeasyui.com/documentation/
http://www.w3school.com.cn/html5/