highcharts不只能夠繪製官網上面指定的報表,同時,還能夠利用其封裝好的繪圖庫來繪製自定義圖形。css
有朋友在工做中恰好有這樣的需求,當時看了兩個相似的js繪圖庫:node
highcharts重在報表,可是同時提供了封裝好的繪圖庫,屏蔽了瀏覽器間差別(IE8及如下不支持SVG,使用VML繪圖)web
Raphaël僅僅提供了各式各樣的繪圖的API,也很是的好用。瀏覽器
僅僅實現當前功能的話,Raphaël應該是首選,可是,咱們作web應用的時候,項目中不免用到其餘的報表樣式,因此權衡之下,仍是選擇highcharts比較划算。數據結構
先把效果圖貼上,比較好描述問題:ide
1. 首先,咱們定義一組這樣的樹形結構,你們學過數據結構的,應該很容易使用相似鏈表的結構進行描述。函數
2. 關於怎麼添加節點之類的就很少說了,主要說一下,畫圖須要知道繪製圖形的位置,而這個位置須要根據節點來進行計算。this
3. 計算位置的時候,採用遞歸的方式來進行計算,當前節點的位置始終位於,其子節點的的中間偏上的位置,因此須要一個獲取子節點左邊位置和一個獲取右邊位置的函數,此函數會遞歸調用。和子節點之間的高度則由一個常數來進行定義。spa
4. 繪製圖形,經過第三步能夠計算出每一個節點對應的位置,那麼就能夠進行繪圖了,首先是節點圖形,這個相對簡單。其次是父子節點之間的連線。每條線均使用3次貝澤爾曲線來繪製。須要提供3個點,共6個參數。恰好這6個參數能夠由父節點的位置和子節點的位置經過組合獲得。.net
具體代碼以下所示:(也能夠經過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 });