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="width: 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                     width: 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                     width: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語言版,第二版,豆瓣


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM