基于Jtopo的网络拓扑编辑器初探

来源:互联网 发布:手机淘宝4.0.1 编辑:程序博客网 时间:2024/05/22 01:51

为了方便演示,我已经把一个静态DEMO部署到github,传送门

基本的操作可以看下面的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。

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/

1 0
原创粉丝点击