zoukankan      html  css  js  c++  java
  • Echarts关系图-力引导布局

    需要做一个树形图,可以查看各个人员的关系。

    可伸缩的力引导图-失败

    刚开始,打算做一个可展开和伸缩的,搜索时候发现CSDN有一篇美美哒程序媛写的Echarts Force力导向图实现节点可折叠

    这里放上前辈的代码

    /**
        这段代码来自 http://blog.csdn.net/r4NqiAn/article/details/48320487  
        Echarts-Force
        力导向布局图树状结构实现节点可折叠效果
        作者:Reese   
        日期:2015-09-09
        版本:V0.1
        功能:点击一次节点,展开一级子节点;再次点击节点,折叠所有子孙节点;
              弹出最终子节点的标签
        备注:在使用该方法的时候,在nodes的属性里要自定义flag属性,并设置ignore
    */
    var ecConfig = require('echarts/config');
    function openOrFold(param){
        var linksNodes=[];//中间变量
        var data=param.data;//表示当前选择的某一节点
    
        var option = myChart.getOption();//获取已生成图形的Option
        var nodesOption=option.series[0].nodes;//获得所有节点的数组
        var linksOption=option.series[0].links;//获得所有连接的数组
        var categoryLength=option.series[0].categories.length;//获得类别数组的大小
    
        /**
        该段代码判断当前节点的category是否为最终子节点,
        如果是,则弹出该节点的label
        */  
        if(data.category==(categoryLength-1)){
            alert(data.label);
        }
    
        /**判断是否选择到了连接线上*/
        if(data != null && data != undefined){
            /**
            判断所选节点的flag
            如果为真,则表示要展开数据,
            如果为假,则表示要折叠数据
            */
            if (data.flag) {
                /**
                遍历连接关系数组
                最终获得所选择节点的一层子节点
                */
                for(var m in linksOption){
                    //引用的连接关系的目标,既父节点是当前节点
                    if(linksOption[m].target==data.id){
                        linksNodes.push(linksOption[m].source);//获得子节点数组
                    }
                }//for(var m in linksOption){...}
                /**
                遍历子节点数组
                设置对应的option属性
                */
                if(linksNodes != null && linksNodes != undefined){
                    for(var p in linksNodes){
                        nodesOption[linksNodes[p]].ignore = false;//设置展示该节点
                        nodesOption[linksNodes[p]].flag = true;
                    }
                }
                //设置该节点的flag为false,下次点击折叠子孙节点
                nodesOption[data.id].flag = false;
                //重绘
                myChart.setOption(option);
            }else{
                /**
                遍历连接关系数组
                最终获得所选择节点的所有子孙子节点
                */
                for(var m in linksOption){
                    //引用的连接关系的目标,既父节点是当前节点
                    if(linksOption[m].target==data.id){
                        linksNodes.push(linksOption[m].source);//找到当前节点的第一层子节点
                    }
                    if(linksNodes != null && linksNodes != undefined){
                        for(var n in linksNodes){
                            //第一层子节点作为父节点,找到所有子孙节点
                            if(linksOption[m].target==linksNodes[n]){
                                linksNodes.push(linksOption[m].source);
                            }
                        }
                    }
                }//for(var m in linksOption){...}
                /**
                遍历最终生成的连接关系数组
                */
                if(linksNodes != null && linksNodes != undefined){
                    for(var p in linksNodes){
                        nodesOption[linksNodes[p]].ignore = true;//设置折叠该节点
                        nodesOption[linksNodes[p]].flag = true;
                    }
                }
                //设置该节点的flag为true,下次点击展开子节点
                nodesOption[data.id].flag = true;
                //重绘
                myChart.setOption(option);
            }//if (data.flag) {...}
        }//if(data != null && data != undefined){...}
    }//function openOrFold(param){...}
    myChart.on(ecConfig.EVENT.CLICK, openOrFold);

    看了一下,思路很清晰。然后开始做,发现她的这代码有个问题就是折叠如果多层会有折叠不上的情况,也可能是我自己代码的原因。

    需注意

    1.在Echarts3中没有ignore属性,我发现data[].category如果对应值不存在的话,就会不显示节点,所以,再点击的时候设置子节点 x.category=x.category*-1;就可隐藏,显示时候同样反转就行。有需要特殊隐藏稍加一点判断就行。

    nodesOption[linksNodes[p]].category = nodesOption[linksNodes[p]].category*-1;

    2.不用多加自定义属性去折叠了,隐藏了就折叠了,但是得获取到递归获取到所有子id。这里还有些残留的代码片段

    //先判断是要展开还是闭合:如果有一个category为正,则闭合;否则扩展一层    。
    expend=true;
    
    for ( var p in linksNodes) { 
        if(nodesOption[linksNodes[p]].category>0 ){  //&& !is_exist(linksOption,nodesOption,nodesOption[linksNodes[p]].id)
            expend=false;
            nodesOption[linksNodes[p]].category=Math.abs(nodesOption[linksNodes[p]].category)*-1;
        }
        nodesOption[linksNodes[p]].category=Math.abs(nodesOption[linksNodes[p]].category)*-1; //顺便使所有的值一致,全置为负值。因为关闭全关闭,展开只展开一层(特例)下面做处理就好、
        //console.log(p+':'+nodesOption[linksNodes[p]].category)
    }
    
    if(expend){ //展开 一层
        linksNodeArrs=[];
        linksNodes_in=get_id(linksOption,data.id,0);
    //console.log(linksNodes_in)
        for ( var p in linksNodes_in) {
            nodesOption[linksNodes_in[p]].category = nodesOption[linksNodes_in[p]].category*-1;
        }
    }else{ //闭合不需要做处理
     
    }
    
    
    //递归取所有 id
    function get_id(arr,cId,f=1){
        for ( var m in arr) {
            if (arr[m].source == cId) {
                linksNodeArrs.push(arr[m].target);
                //linksNodeArrs.push(m);
                //console.log(arr[m].target);
                if(f)
                get_id(arr,arr[m].target);
            }
       }
     return linksNodeArrs;
    }

    图一为简单的扩展折叠,完美

    图二为有问题的折叠,点击[设计师B]时,[项目1]和[厂商D]应该存在,因为还有别的路径,逻辑错误,这里要弄折叠肯定得通过有向图比较好解决。不过这里暂时用不到,以后有机会填此坑。

    那么,不考虑折叠了,考虑单击出相关一级子节点,双击只显示此节点的第一级。

    可扩展,以及单独显示的力引导图

    为了以后需要查数据库动态获取数据,所以暂时把关系都存到数据库里。主要有两组数据,一组是每个节点(数据),一组是他们的关系即连接线 。有人可能会想,这想一种数据结构,对,这就是有向图 :

    扯回来,代码只是演示相关功能并不完善,这里将不详细介绍各代码,只是简单的取数据库而已,Echarts基础请看官方文档和技术文章。

    数据库简单设计三个,categories各分类,nodes数据节点,links边(可以看到保存了关系)

    TP代码:

        #查询最基本的显示
        public function index(){
            ####分类查询####
            $category=M('categories')->field('name')->where('name is not null')->order('id asc')->select();//注:需要从0排起
            //legend,页顶部的标签 -可空
            $legend_data=' ';
            foreach($category as $v){
                $legend_data.="'{$v['name']}',";
            }
            $legend_data=substr($legend_data,0,-1);
            $this->assign('legend_data',$legend_data);
    
            //分类,与legend同数据
            $json_cate=json_encode($category,JSON_NUMERIC_CHECK);
            $this->assign('json_cate',$json_cate);
            
    
            ####连接查询####  查询第一层
            $links_data=M('links')->field('source,target,value,id as id_')->order('id asc')->where('source=0')->select();//
            
          
            $id_str='0,';
            foreach($links_data as $v){
                $id_str.="{$v['target']},";
            }
            
            $id_str=substr($id_str,0,-1);
            $links_data2=M('links')->field('source,target,value,id as id_')->order('id asc')->where(" `source` in (%s) ",$id_str)->select();
     
            foreach($links_data2 as $k=>$v){
                $links_data[]=$v;
            }
    
            $json_links=json_encode($links_data);
            $this->assign('json_links',$json_links);
            ####数据查询####
            $where=array();
            $where['id']=array('in',$id_str);
            $nodes_data=M('nodes')->field('id,name,category')->order('id asc')->where($where)->select();
            
            $json_nodes=json_encode($nodes_data,JSON_NUMERIC_CHECK);
            //echo $json_nodes;
            $this->assign('json_nodes',$json_nodes);
           
            $this->display();
        }
      
        public function only_show(){ //only_show函数等下点击节点时候ajax用。
            $selfid=I('post.id');
            ####连接查询####  
            $map=array();
            $map['_query'] = "source=$selfid&target=$selfid&_logic=or";
            $links_data=M('links')->field('source,target,value')->order('id asc')->where($map)->select();
    
            $id_str=' ';
     
            foreach($links_data as $v){
                $id_str.="{$v['target']},";
                $id_str.="{$v['source']},";
            }
            $id_str=substr($id_str,0,-1);
    
            $links_data=M('links')->field('source,target,value,id as id_')->order('id asc')->where(" `source` in (%s) or `target` in  (%s) ",$id_str,$id_str)->select(); 
            
            $json_links=json_encode($links_data);
            $result['json_links']=$json_links;
    
            ####数据查询####
            $where['id']=array('in',$id_str);
            $nodes_data=M('nodes')->field('id as id,name,category')->order('id asc')->where($where)->select();
                    
            $json_nodes=json_encode($nodes_data,JSON_NUMERIC_CHECK);
            $result['json_nodes']=$json_nodes;
            $this->ajaxReturn($result);
        }

    视图代码:

      1 <!DOCTYPE html>
      2 <html>
      3     <head>
      4         <title>关系图试验</title>
      5         <meta charset="utf-8">
      6         <script type="text/javascript" src="__PUBLIC__/js/echarts.js"></script>
      7         <script type="text/javascript" src="__PUBLIC__/js/jquery.js"></script>
      8  
      9     </head>
     10     <body>
     11         <div id="main" style=" 1000px;height:600px;"></div>    
     12     <script type="text/javascript">
     13 var linksNodeArrs=[];
     14 var TimeFn=null; 
     15     $(function(){
     16 
     17 option = {
     18     title: {
     19         text: '测试关系图'
     20     },
     21     tooltip: {},
     22     //animationDurationUpdate: 1500,
     23     animationEasingUpdate: 'quinticInOut',
     24     label: {
     25         normal: {
     26             show: true,
     27             textStyle: {
     28                 fontSize: 12
     29             },
     30         }
     31     },
     32     legend: {
     33         x: "center",
     34         show: true,
     35         data: [{$legend_data}]
     36     },
     37     series: [
     38         {
     39             //name:'系列名',
     40             type: 'graph',
     41             layout: 'force',
     42             symbolSize: 45,
     43             focusNodeAdjacency: true,  //突出相关
     44             roam: true,  //鼠标缩放、平移
     45             force: {  //斥力因子
     46                 repulsion: 500,
     47                 edgeLength:[100,200],
     48             },
     49              draggable:true,
     50             tooltip:{
     51                 trigger:'item',
     52                 backgroundColor:  'rgba(245, 244, 237,0.7)' ,//提示框浮动背景色
     53                 borderColor:'black',
     54                 borderWidth:1,
     55                 textStyle:{
     56                     color:'black',
     57                     fontWeight:'bold',
     58 
     59                 }
     60             },
     61   
     62             categories: {$json_cate},
     63             label: {
     64                 normal: {
     65                     show: true,
     66                     textStyle: {
     67                         fontSize: 12
     68                     },
     69                 }
     70             },
     71             
     72             edgeSymbolSize: [0, 10],
     73             edgeSymbol:'arrow',
     74             
     75             edgeLabel: {
     76                 normal: {
     77                     show: true,
     78                     textStyle: {
     79                         fontSize: 10
     80                     },
     81                     formatter: "{b}"
     82                 }
     83             },
     84             nodes: {$json_nodes},
     85             links: {$json_links},
     86             lineStyle: {
     87                 normal: {
     88                     opacity: 0.9,
     89                      1,
     90                     curveness: 0,
     91                     shadowColor: 'rgba(0, 0, 0, 0.8)',
     92                     shadowBlur: 5,
     93                     shadowOffsetX:3,
     94                     shadowOffsetY:3,
     95                 },
     96                 emphasis:{
     97                     3 //hover时改变线宽
     98                 }
     99             },
    100 
    101             
    102         }
    103     ]
    104 };
    105 
    106     //console.log(option)
    107       var myChart = echarts.init(document.getElementById('main'));
    108       myChart.setOption(option);
    109 
    110       //↑上面为加载完后初次显示
    111 
    112     myChart.on('click', cevent);
    113     myChart.on('dblclick', dbcevent);
    114 
    115     function cevent(param) {
    116          clearTimeout(TimeFn);
    117         //执行延时
    118         TimeFn = setTimeout(function(){
    119             var option = myChart.getOption();
    120             var data = param.data;
    121             if (data != null && data != undefined) {
    122                 cid=data.id;
    123                 $.post("{:U('only_show')}",{id:cid},function(rs){
    124                      var json_nodes = eval('(' + rs.json_nodes + ')'); 
    125                      var json_links = eval('(' + rs.json_links + ')'); 
    126 
    127                     for(var v in json_nodes){
    128                         var exist=false;//是否存在
    129                         for(var i in option.series[0].nodes){
    130                             
    131                             if(json_nodes[v]['id'] == option.series[0].nodes[i]['id'] || json_nodes[v]['name'] == option.series[0].nodes[i]['name']){
    132                                 exist=true;
    133                                 break;
    134                             }
    135                         }
    136                         if(!exist){
    137                             option.series[0].nodes.push(json_nodes[v]);
    138                         }
    139                     }
    140 
    141                     for(var v in json_links){
    142                          var exist=false; //是否存在
    143                         for(var i in option.series[0].links){
    144                             if(json_links[v]['id_'] == option.series[0].links[i]['id_']){
    145                                 exist=true;
    146                                 break;
    147                             }
    148                         }
    149                         if(!exist){
    150                             option.series[0].links.push(json_links[v]);
    151                         }
    152                     }
    153 
    154                     option.series[0].nodes=quicksort(option.series[0].nodes)
    155                  
    156                     //option.series[0].nodes=json_nodes;
    157                     //option.series[0].links=json_links;
    158                     myChart.setOption(option);
    159                 })
    160             }
    161         },300);          
    162     }
    163 
    164 
    165 
    166     function dbcevent(param) {
    167         clearTimeout(TimeFn);
    168         var option = myChart.getOption();
    169         var data = param.data;
    170         if (data != null && data != undefined) {
    171             cid=data.id;
    172             $.post("{:U('only_show')}",{id:cid},function(rs){
    173                 //console.log(rs);
    174                  var json_nodes = eval('(' + rs.json_nodes + ')'); 
    175                  var json_links = eval('(' + rs.json_links + ')'); 
    176                 option.series[0].nodes=json_nodes;
    177                 option.series[0].links=json_links;
    178                 myChart.setOption(option);
    179             })
    180         }
    181     }
    182 
    183     //冒泡
    184     function bubbleSort(array){
    185         var i = 0,
    186         len = array.length,
    187         j,d;
    188         for(;i<len;i++){
    189             for(j=0;j<len;j++){
    190                 if(array[i]['id']<array[j]['id']){
    191                     d=array[j];
    192                     array[j]=array[i];
    193                     array[i]=d;
    194                 }
    195             }
    196         }
    197         return array;
    198     }
    199     //快速
    200     function quicksort(arr){
    201         if (arr.length == 0)
    202             return [];
    203      
    204         var left = new Array();
    205         var right = new Array();
    206         var pivot = arr[0];
    207      
    208         for (var i = 1; i < arr.length; i++) {
    209             if (arr[i]['id'] < pivot['id']) {
    210                left.push(arr[i]);
    211             } else {
    212                right.push(arr[i]);
    213             }
    214         }
    215  
    216         return quicksort(left).concat(pivot, quicksort(right));
    217     }
    218 
    219 });            
    220         </script>
    221 
    222     </body>
    223  
    224 </html>

    稍微说明一下:

    • cevent()和dbcevent()函数单击双击效果加了延迟是因为,在我测试的时候,双击也会触发到两次单击然后才执行双击,重复执行。加延迟为了解决这个问题。
    • 154行在设置重置前用了个排序去排nodes,是因为,在乱序模式中不排序会造成错误,links的source和target用不到nodes里面的id值,就会去用索引的值,放两个图看效果。
    • p.s.本来测试的时候随便找了个冒泡排序排了序测试了下,最后写博客才想用个快排,放个冒泡确实比较不提倡。

    最后放一个完结图:单击扩展一级节点,双击已节点扩展一级!

        

    参考资料

    0.Echarts官方文档,graph

    1.Echarts Force力导向图实现节点可折叠

    2.快速排序(Quicksort)的Javascript实现,阮一峰

    3.Friday Algorithms: Quicksort – Difference Between PHP and JavaScript 

    4.ECharts3.x中的点击事件与行为

    5.数据结构基础 C语言版,第二版,豆瓣

  • 相关阅读:
    使用ActivityGroup来切换Activity和Layout
    Fragment
    [Java]LeetCode297. 二叉树的序列化与反序列化 | Serialize and Deserialize Binary Tree
    [Swift]LeetCode298. 二叉树最长连续序列 $ Binary Tree Longest Consecutive Sequence
    [Swift]LeetCode296. 最佳开会地点 $ Best Meeting Point
    [Swift]LeetCode294. 翻转游戏之 II $ Flip Game II
    [Swift]LeetCode293. 翻转游戏 $ Flip Game
    [Swift]LeetCode291. 单词模式 II $ Word Pattern II
    [Postman]发出SOAP请求(18)
    [Postman]生成代码段(17)
  • 原文地址:https://www.cnblogs.com/warcraft/p/7251001.html
Copyright © 2011-2022 走看看