利用highcharts绘制树形结构图

时间:2024-02-23 09:20:34

highcharts不仅可以绘制官网上面指定的报表,同时,还可以利用其封装好的绘图库来绘制自定义图形。

有朋友在工作中刚好有这样的需求,当时看了两个类似的js绘图库:

highcharts重在报表,但是同时提供了封装好的绘图库,屏蔽了浏览器间差异(IE8及以下不支持SVG,使用VML绘图)

Raphaël仅仅提供了各式各样的绘图的API,也非常的好用。

仅仅实现当前功能的话,Raphaël应该是首选,但是,我们做web应用的时候,项目中难免用到其他的报表样式,所以权衡之下,还是选择highcharts比较划算。

先把效果图贴上,比较好描述问题:

1. 首先,我们定义一组这样的树形结构,大家学过数据结构的,应该很容易使用类似链表的结构进行描述。

2. 关于怎么添加节点之类的就不多说了,主要说一下,画图需要知道绘制图形的位置,而这个位置需要根据节点来进行计算。

3. 计算位置的时候,采用递归的方式来进行计算,当前节点的位置始终位于,其子节点的的中间偏上的位置,所以需要一个获取子节点左边位置和一个获取右边位置的函数,此函数会递归调用。和子节点之间的高度则由一个常数来进行定义。

4. 绘制图形,通过第三步可以计算出每个节点对应的位置,那么就可以进行绘图了,首先是节点图形,这个相对简单。其次是父子节点之间的连线。每条线均使用3次贝泽尔曲线来绘制。需要提供3个点,共6个参数。刚好这6个参数可以由父节点的位置和子节点的位置通过组合得到。

具体代码如下所示:(也可以通过http://jsfiddle.net/lloydzhou/JY59J/5/ 在线查看)

定义节点类
 1 var Node = {
 2     height: 20,
 3     width: 60,
 4     padding: 30,
 5     paddingTop: 50,
 6     paddingText: 5,
 7     paddingLeft: 20,
 8     arrowLength: 4,
 9     arrowWidth: 3,
10     create: function(textVal){
11         return {
12             text: textVal,
13             childNodes: [],
14             parentNode: null,
15             index: null,
16             x: null,
17             y: null,
18             add: function(node) {
19                 this.childNodes[node.index = this.childNodes.length] = node;
20                 return node.parentNode = this;
21             },
22             leftNode: function() {
23                 return this.parentNode ? (this.index > 0 ? this.parentNode.childNodes[this.index - 1] : this.parentNode.leftNode()) : null;
24             },
25             top: function() {
26                 if (this.y) return this.y;
27                 else return this.y = (this.parentNode ? this.parentNode.top() + Node.height + Node.padding : Node.paddingTop);
28             },
29             left: function() {
30                 if (this.x) {
31                     return this.x;
32                 }else{
33                     return this.x = this.leftNode() ? this.leftNode().left() + Node.padding + this.leftNode().childWidth() : Node.paddingLeft;
34                 }
35             },
36             childHeight: function() {
37                 var h = 0;if (this.childNodes.length > 0) {for(var i=0;i<this.childNodes.length;i++) h = Math.max(h,this.childNodes[i].childHeight() + Node.padding);}
38                 return (h === 0) ? Node.height : h ;
39             },
40             childWidth: function() {
41                 var w = 0;if (this.childNodes.length > 0) {for(var i=0;i<this.childNodes.length;i++) w = w + this.childNodes[i].childWidth() + Node.padding;}
42                 return (w === 0) ? Node.width : w - Node.padding;
43             },
44             toString: function() {
45                 return this.text + \'[\' + this.left() + \',\' + this.top() + \']\' + (this.childNodes.length > 0 ? \':{\' + this.childNodes + \'}\' : \'\');
46             },
47             arrowX: function () {
48                 return this.left() + this.childWidth() / 2 - (this.parentNode.left() + this.parentNode.childWidth() / 2 - Node.width / 2) - Node.width / 2;
49             },
50             arrowY: function () {
51                 return Node.padding - 2 * Node.paddingText;
52             },
53             arrow: function(renderer, colors) {
54                 var a = Node.arrowLength, b = Node.arrowWidth, x2 = this.arrowX(), y2 = this.arrowY(),
55                     x = this.parentNode.left() + this.parentNode.childWidth() / 2 - Node.width / 2+Node.width/2 + Node.paddingText,
56                     y = this.parentNode.top() + Node.height + 2 * Node.paddingText
57                 renderer.path([\'M\', 0, 0, \'C\', 0, y2/1.8, x2, 0, x2, y2]).attr({\'stroke-width\': 2, stroke: colors[1]}).translate(x,y).add()
58             },
59             drow: function(renderer, colors) {
60                 var x = this.left() + this.childWidth() / 2 - Node.width / 2, y = this.top();
61                 renderer.label(this.text, x, y).attr({fill: colors[0], stroke: \'white\',\'stroke-width\': 3,padding: Node.paddingText, r: 2 * Node.paddingText, width: Node.width, height: Node.height}).css({color: \'white\', textAlign:\'center\',fontWeight: \'bold\',fontSize: \'10px\'}).add().shadow(true);
62                 if (this.childNodes.length > 0) {
63                     for(var i=0;i<this.childNodes.length;i++) {
64                         this.childNodes[i].drow(renderer, colors);
65                         this.childNodes[i].arrow(renderer, colors);
66                     }
67                 }
68             }
69         };
70     }
71 }

1 var n = Node.create
2 
3 var datas = n(1)
4     .add(n(2))
5     .add(n(3).add(n(7)).add(n(8)))
6     .add(n(4))
7     .add(n(5).add(n(6)).add(n(9)))
生成节点
1     var chart = new Highcharts.Chart({
2         chart: {
3             renderTo: \'container\',
4             events: {load: function () { 
5                 datas.drow(this.renderer, Highcharts.getOptions().colors);
6             }}
7         },
8         title: {text: \'demo\'}
9     });
绘制图形