jtopo 实现一键布局

           最近很忙、也很懒,一堆烦心事,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()方法,即可实现我们想要的效果,好了,有问题的小伙伴可以留言给我,看到会及时回复的,如果觉得博主的代码有用,别忘了点赞加关注哦~!

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不脱发的牧码人

你的鼓励将是我前进的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值