最近很忙、也很懒,一堆烦心事,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_h
for (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.linkDefaultWidth
self.link.strokeColor = editor.config.linkFillColor
self.link.arrowsRadius = 10;
self.link.linkAlpha = editor.config.linkAlpha
self.link.linkStrokeColor = editor.config.linkStrokeColor
self.link.linkFillColor = editor.config.linkFillColor
self.link.linkShadow = editor.config.linkShadow
self.link.linkShadowColor = editor.config.linkShadowColor
self.link.linkFont = editor.config.linkFont
self.link.fontColor = editor.config.linkFontColor
self.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.linkArrowsRadius
self.link.linkDefaultWidth = editor.config.linkDefaultWidth
self.link.linkOffsetGap = editor.config.linkOffsetGap
self.link.linkDirection = editor.config.linkDirection
editor.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()方法,即可实现我们想要的效果,好了,有问题的小伙伴可以留言给我,看到会及时回复的,如果觉得博主的代码有用,别忘了点赞加关注哦~!