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