需要做一個樹形圖,可以查看各個人員的關系。
可伸縮的力引導圖-失敗
剛開始,打算做一個可展開和伸縮的,搜索時候發現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.本來測試的時候隨便找了個冒泡排序排了序測試了下,最后寫博客才想用個快排,放個冒泡確實比較不提倡。
最后放一個完結圖:單擊擴展一級節點,雙擊已節點擴展一級!
參考資料
2.快速排序(Quicksort)的Javascript實現,阮一峰
3.Friday Algorithms: Quicksort – Difference Between PHP and JavaScript