用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 }
小弟技术比较渣,写的东西 含量也不是很高,就算是写给自己看的吧...可以半年后 再回来看 ,这些全是笑话 哈哈哈~~