最近很忙、也很懒,一堆烦心事,jtopo后面不准备再深究了,本身东西也不多,做出的新功能,新特效也都写到博客中来了,今天给大家分享最近研究的一个新技能——jtopo一键布局,写给大家、也写给自己。
因为jtopo天然不支持节点对其,所以很不友好,但甲方往往需要的是理想化的操作,所以一键布局显得尤为必要,还是老规矩,先看效果,再说实现。
这里完全不需要手动去拖动节点,只需要点击按钮,即可实现自动布局成树形结构,用到的思想就是递归函数,会的小伙伴们可以止步了,下面详细说说是怎么实现的。
想要实现自动布局,第一步当然是要先拿到所有的节点了,然后再找到根节点,放到指定的位置,再依次排布它的子节点,所有的节点都这样排列完成后,就能得到我们想要的树形布局,思路有了,下来就该动手实现了。
//一键布局(顺序布局)
function Auto_position(){let nodes = editor.utils.getAllNodes();let nodes_tree=[],childs=[];nodes.map(function(item,index){if(item.inLinks.length<1){nodes_tree.push(item)}else{childs.push(item)}});//递归填充树状数据结构for (let i = 0; i < nodes_tree.length; i++) {find_child(nodes_tree[i],childs)}let start_point={"x":0,"y":0};start_point.x = editor.scene.translateX//-(childs.length/2*editor.config.nodeDefaultWidth);start_point.y = editor.scene.translateY;//重新设置节点新坐标for (let i = 0; i < nodes_tree.length; i++) { set_location(Object.assign({},start_point),nodes_tree[i],nodes_tree.length);start_point.x +=editor.config.nodeDefaultWidth*5;}
}
这里自定义了两个递归函数,一个是find_child,另外一个是set_location。find_child用来查找每个节点的子节点,最终拼装成树形数据结构(这里给每个节点都增加了cnode属性,存储当前节点的所有子节点),具体实现如下:
//查找指定节点的父节点
function find_child(node,all)
{node.cnode=[];let outlines = node.outLinks;for (let i = 0; i < outlines.length; i++) {let cnode_id = outlines[i].nodeDst;let cnodes = []all.map(function(t,c_index){if (t.nodeId==cnode_id) {cnodes.push(t);}});if (cnodes.length>0) {node.cnode.push(cnodes[0]);find_child(cnodes[0],all);}}
}
代码很短很简洁,主要的思路是根据传进来的节点node,查找到它的所有子节点填装到cnodes数组中去,再去判断每个子节点有无孙子节点,如果有,继续套入当前方法,直到最终的末级节点。最终的结果是每个node结构底下都会多出一个cnode的树形,存储了当前节点的子节点的集合。
到这里我们基本上已经完了数据的建模,整个数据结构都已经出来了,接下来要做的是去重新布局。
假设,我们拿到了任意的一个节点,并且知道这个节点的坐标,那么我们就可以确定它的子节点的坐标,其实也简单,我们只需要给x轴一个偏移量,y轴增加一个固定值即可,想想,子节点就在父节点的下方,难道不是吗?这里我们分了两种情况,一种是有两个子节点,一种是有多个子节点,这种做的目的是为了只有两个子节点时,让节点的y轴位置偏移小一点,最终的效果是二次折线是直角连接,代码如下:
//设置当前节点的坐标到指定位置
function set_location(location,node,brothers_cout){let his,laster;//只有两个节点时直角连线if (brothers_cout == 2) {location.y -= 2.25*editor.config.nodeDefaultWidth;his = points.find(a=>a.x==location.x && a.y > (location.y/3));while (his) {laster = his; location.x =laster.x + 5*editor.config.nodeDefaultWidth; his = points.find(a=>a.x==location.x && a.y > (location.y/3));} }else{his = points.find(a=>a.y==location.y && a.x > (location.x-5*editor.config.nodeDefaultWidth));while (his) {laster = his; location.x =laster.x + 5*editor.config.nodeDefaultWidth; his = points.find(a=>a.x > (location.x-5*editor.config.nodeDefaultWidth) && a.y==location.y);}}node.x=location.x;node.y=location.y;//console.log(location);points.push(node);location.y +=6*editor.config.nodeDefaultWidth;let outlinks = editor.utils.getAllLinks();let x_h = (node.cnode.length-1)*5*editor.config.nodeDefaultWidth/2;location.x -= x_hfor (let i = 0; i < node.cnode.length; i++) {set_location(Object.assign({},location),node.cnode[i],node.cnode.length);//创建新的连线let lin = outlinks.find(a=>a.nodeSrc == node.nodeId && a.nodeDst == node.cnode[i].nodeId)[0];if (lin) {self.link = new JTopo.FlexionalLink(node, node.cnode[i])self.link.lineType = 'flexLine'self.link.lineWidth = editor.config.linkDefaultWidthself.link.strokeColor = editor.config.linkFillColorself.link.arrowsRadius = 10;self.link.linkAlpha = editor.config.linkAlphaself.link.linkStrokeColor = editor.config.linkStrokeColorself.link.linkFillColor = editor.config.linkFillColorself.link.linkShadow = editor.config.linkShadowself.link.linkShadowColor = editor.config.linkShadowColorself.link.linkFont = editor.config.linkFontself.link.fontColor = editor.config.linkFontColorself.link.bid = lin.bid;self.link.nodeDst = lin.nodeDst;self.link.nodeSrc = lin.nodeSrc;self.link.text = lin.text;self.link.textA = lin.textA;self.link.textZ = lin.textZ;self.link.linkArrowsRadius = editor.config.linkArrowsRadiusself.link.linkDefaultWidth = editor.config.linkDefaultWidthself.link.linkOffsetGap = editor.config.linkOffsetGapself.link.linkDirection = editor.config.linkDirectioneditor.scene.add(self.link)editor.scene.remove(lin)}location.x +=5*editor.config.nodeDefaultWidth;}//修正父节点的坐标resetParent_location(node);
}
这里需要注意的一点是节点间的连线,因为我们是自动布局,所以之前的连线不再适用,这里就重新绘制了(for循环里边的部分就是绘制连线,同时删除了之前老的连线),最后一行resetParent_location(node);是为了二次反馈修复父节点的坐标,因为存在如下的情形:
子节点的坐标没有问题,但父节点偏到了一边,所以需要二次反馈矫正。二次反馈矫正的思路是找到父节点的所有亲兄弟节点,算出他们的中心位置节点的坐标,然后赋值给当前的父节点:
//二次修复父节点坐标
function resetParent_location(node){let outlinks = editor.utils.getAllLinks();let lin = outlinks.find(a=>a.nodeDst == node.nodeId)[0];if (!lin) {return;}let nodes = editor.utils.getAllNodes();let parent = nodes.find(a=>a.nodeId == lin.nodeSrc);if (!parent) {return}else{parent = parent[0];}let x_s = [];parent.cnode.map(function(t,i){x_s.push(t.x);});let x = Math.min.apply(null,x_s) + (Math.max.apply(null,x_s) - Math.min.apply(null,x_s))/2;parent.x = x;
}
所有的这些可以封装到一个js模块中,当哪里需要的时候,可以直接调用Auto_position()方法,即可实现我们想要的效果,好了,有问题的小伙伴可以留言给我,看到会及时回复的,如果觉得博主的代码有用,别忘了点赞加关注哦~!