用JavaScript來實現的超炫組織結構圖
到新公司實習第七天,Boos就讓我做個組織架構,用來展示人員關系圖...然后就開始了我的code,不經意間在Github上看到了一個 開源的javascript類庫可以生成非常酷炫的節點圖形,我選擇了其中一種spacetree類型做為我的組織結構圖基礎,這類庫種類很多,功能非常強大,非常適合復雜的圖形功能需求
spacetree
這種圖形可以支持一下特性:
- 支持向上下左右四個方向展開圖表
- 支持子節點擴展
- 支持圖表拖放
- 支持圖表縮放
html:
<!-- JIT Library File --> <script language="javascript" type="text/javascript" src="/public/javascripts/jit.js"></script> <!-- Example File --> <script language="javascript" type="text/javascript" src="/public/javascripts/example1.js"></script> <div> <div id="container"> <div id="center-container"> <div id="infovis"></div> </div> <div id="log"></div> </div> </div> <script type="text/javascript"> window.onload=init(); </script>
var labelType, useGradients, nativeTextSupport, animate; (function() { var ua = navigator.userAgent, iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i), typeOfCanvas = typeof HTMLCanvasElement, nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'), textSupport = nativeCanvasSupport && (typeof document.createElement('canvas').getContext('2d').fillText == 'function'); //I'm setting this based on the fact that ExCanvas provides text support for IE //and that as of today iPhone/iPad current text support is lame labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML'; nativeTextSupport = labelType == 'Native'; useGradients = nativeCanvasSupport; animate = !(iStuff || !nativeCanvasSupport); })(); var Log = { elem: false, write: function(text){ if (!this.elem) this.elem = document.getElementById('log'); this.elem.innerHTML = text; this.elem.style.left = (500 - this.elem.offsetWidth / 2) + 'px'; } }; function init(){ var json; $.ajax({ type: "GET", url: "/home/spacetreeJSON", data: {username:$("#username").val(), content:$("#content").val()}, dataType: "json", async: false, success: function(data){ if(data.result=="success"){ json=data.info.json; } console.log(data.info.json); } }); //init Spacetree //Create a new ST instance var st = new $jit.ST({ //id of viz container element injectInto: 'infovis', //set duration for the animation duration: 800, //set animation transition type transition: $jit.Trans.Quart.easeInOut, //set distance between node and its children levelDistance: 50, //enable panning Navigation: { enable:true, panning:true }, //set node and edge styles //set overridable=true for styling individual //nodes or edges Node: { height: 20, width: 100, type: 'rectangle', color: '#aaa', overridable: true }, Edge: { type: 'bezier', overridable: true }, onBeforeCompute: function(node){ Log.write("loading " + node.name); }, onAfterCompute: function(){ Log.write("done"); }, //This method is called on DOM label creation. //Use this method to add event handlers and styles to //your node. onCreateLabel: function(label, node){ label.id = node.id; label.innerHTML = node.name; label.onclick = function(){ // if(normal.checked) { st.onClick(node.id); // } else { // st.setRoot(node.id, 'animate'); // } }; //set label styles var style = label.style; style.width = 100 + 'px'; style.height = 17 + 'px'; style.cursor = 'pointer'; style.color = '#333'; style.fontSize = '0.8em'; style.textAlign= 'center'; style.paddingTop = '3px'; }, //This method is called right before plotting //a node. It's useful for changing an individual node //style properties before plotting it. //The data properties prefixed with a dollar //sign will override the global node style properties. onBeforePlotNode: function(node){ //add some color to the nodes in the path between the //root node and the selected node. if (node.selected) { node.data.$color = "#ff7"; } else { delete node.data.$color; //if the node belongs to the last plotted level if(!node.anySubnode("exist")) { //count children number var count = 0; node.eachSubnode(function(n) { count++; }); //assign a node color based on //how many children it has node.data.$color = ['#aaa', '#baa', '#caa', '#daa', '#eaa', '#faa'][count]; } } }, //This method is called right before plotting //an edge. It's useful for changing an individual edge //style properties before plotting it. //Edge data proprties prefixed with a dollar sign will //override the Edge global style properties. onBeforePlotLine: function(adj){ if (adj.nodeFrom.selected && adj.nodeTo.selected) { adj.data.$color = "#eed"; adj.data.$lineWidth = 3; } else { delete adj.data.$color; delete adj.data.$lineWidth; } } }); //load json data st.loadJSON(json); //compute node positions and layout st.compute(); //optional: make a translation of the tree st.geom.translate(new $jit.Complex(-200, 0), "current"); //emulate a click on the root node. st.onClick(st.root); }
如上js代碼 是用的spacetree方式,重寫的jit.ST方法,這里有一點需要注意,就是獲取json的時候 建議ajax異步獲取數據改為同步獲取,即 async: false。
在做以上工作的時候,非常簡單,在后台整理對數據庫中數據整理成json數組的時候,出現了一些問題,使用遞歸算法結合數據庫解析成JSON樹形結構,就這點小問題,讓我弄了老長時間┭┮﹏┭┮,我就說一下我解決的思路把
先上需要整理出來的json格式

{ "id": "node02", "name": "0.2", "data": { }, "children": [ { "id": "node13", "name": "1.3", "data": { }, "children": [ { "id": "node24", "name": "2.4", "data": { }, "children": [ { "id": "node35", "name": "3.5", "data": { }, "children": [ { "id": "node46", "name": "4.6", "data": { }, "children": [ ] } ] }, { "id": "node37", "name": "3.7", "data": { }, "children": [ { "id": "node48", "name": "4.8", "data": { }, "children": [ ] }, { "id": "node49", "name": "4.9", "data": { }, "children": [ ] }, { "id": "node410", "name": "4.10", "data": { }, "children": [ ] }, { "id": "node411", "name": "4.11", "data": { }, "children": [ ] } ] }, { "id": "node312", "name": "3.12", "data": { }, "children": [ { "id": "node413", "name": "4.13", "data": { }, "children": [ ] } ] }, { "id": "node314", "name": "3.14", "data": { }, "children": [ { "id": "node415", "name": "4.15", "data": { }, "children": [ ] }, { "id": "node416", "name": "4.16", "data": { }, "children": [ ] } ] } ] } ] } ] }
1.先獲取數據庫中層級最高的數據(本項目設計,最頂層級id為1) (User user)
select * from user where id='id'
2.在獲取id為'id'的所有子節點 (List <User>)
select * from user where learderid='id'
3.然后for循環 遍歷id='id'的子節點
注意:for循環中 要是同遞歸算法了, 因為 節點的字節點一直往里延伸 你不知道有多少層,所以用遞歸算法是正解!
以上是解法思路,還有一個難點(我認為~~)就是將數據轉換成json了
1.因為是鍵值對 所以用Map是王道, 運行框架 用的是play!框架,也都是封裝好的 ,直接調用方法就可以
2.要在獲取當前節點后,立刻將需要的值放到map中
3.在for循環中,要new map 用來傳遞給遞歸函數的map
3.要在for循環之前new List 用來存儲你for循環中 創建的map
直接上代碼吧
1 public static void spacetreeJSON(){ 2 ResultInfo result = new ResultInfo(); 3 String sql="select * from user where id=1"; 4 List<User> userlist = new ArrayList<User>(); 5 userlist=UserController.query(sql, User.class); 6 7 Map<String, Object> json = new HashMap<String, Object>(); 8 Map<String, Object> map = new HashMap<String, Object>(); 9 recursiveTree(Long.parseLong("1"), map); 10 json.put("json", map); 11 renderJSON(result.success(json, refreshExpire())); 12 } 13 public static User recursiveTree(Long id,Map map){ 14 User usernode = User.findById(id); 15 map.put("id",usernode.id+""); 16 map.put("name",usernode.name); 17 List<User> list = UserController.query("select * from user where leaderid='"+id+"'", User.class); 18 19 List childlist= new ArrayList(); 20 for(User u : list){ 21 Map<String, Object> map_u = new HashMap<String, Object>(); 22 User n = recursiveTree(u.id,map_u); //遞歸 23 childlist.add(map_u); 24 } 25 map.put("children",childlist); 26 return usernode; 27 }
小弟技術比較渣,寫的東西 含量也不是很高,就算是寫給自己看的吧...可以半年后 再回來看 ,這些全是笑話 哈哈哈~~