前言
再簡單介紹下D3.js,D3.js 是一個基於數據操作文檔JavaScript庫。D3幫助你給數據帶來活力通過使用HTML、SVG和CSS。D3重視Web標准為你提供現代浏覽器的全部功能,而不是給你一個專有的框架。結合強大的可視化組件和數據驅動方式Dom操作。這裡也可以看到它是用SVG來呈現圖表的,所以使用D3.js是需要一定的SVG基礎的。
本文依然是先把簡單的畫圖框架搭起來,添加SVG畫布。這裡和餅圖有點類似,為了方便後面的繪制,我們把組合這些元素的g元素移動到畫布的中心:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>雷達圖</title> <style> .container { margin: 30px auto; width: 600px; height: 300px; border: 1px solid #000; } </style> </head> <body> <div class="container"> <svg width="100%" height="100%"></svg> </div> <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> <script> window.onload = function() { var width = 600, height = 300; // 創建一個分組用來組合要畫的圖表元素 var main = d3.select('.container svg').append('g') .classed('main', true) .attr('transform', "translate(" + width/2 + ',' + height/2 + ')'); }; function getColor(idx) { var palette = [ '#2ec7c9', '#b6a2de', '#5ab1ef', '#ffb980', '#d87a80', '#8d98b3', '#e5cf0d', '#97b552', '#95706d', '#dc69aa', '#07a2a4', '#9a7fd1', '#588dd5', '#f5994e', '#c05050', '#59678c', '#c9ab00', '#7eb00a', '#6f5553', '#c14089' ] return palette[idx % palette.length]; } </script> </body> </html>
這裡為什麼我會說雷達圖和餅圖會有點類似呢?看一下下面這張圖。
可以看到,雷達圖的網軸(藍色部分)是由多個正多邊形所組成的,而正多邊形的繪制正好是可以利用圓半徑的特性來繪制的,所以從一開始把繪制的原點移動到畫布的中心是很方便後面的繪制工作的。
模擬數據
我們先模擬一些原始數據。
var data = { fieldNames: ['語文','數學','外語','物理','化學','生物','政治','歷史'], values: [ [10,20,30,40,50,60,70,80] ] };
計算網軸坐標並繪制
在前面的其他圖表的實現中,都有比例尺或者布局這樣的東西來為我們轉化數據提供便利,雷達圖是否也存在這樣的工具函數呢?答案是沒有!沒有!沒有!重要的事情說三遍!(-_-) 所以,我們只能開動自己的小腦瓜自己算了。
// 設定一些方便計算的常量 var radius = 100, // 指標的個數,即fieldNames的長度 total = 8, // 需要將網軸分成幾級,即網軸上從小到大有多少個正多邊形 level = 4, // 網軸的范圍,類似坐標軸 rangeMin = 0, rangeMax = 100, arc = 2 * Math.PI; // 每項指標所在的角度 var onePiece = arc/total; // 計算網軸的正多邊形的坐標 var polygons = { webs: [], webPoints: [] }; for(var k=level;k>0;k--) { var webs = '', webPoints = []; var r = radius/level * k; for(var i=0;i<total;i++) { var x = r * Math.sin(i * onePiece), y = r * Math.cos(i * onePiece); webs += x + ',' + y + ' '; webPoints.push({ x: x, y: y }); } polygons.webs.push(webs); polygons.webPoints.push(webPoints); }
計算網軸的坐標就是計算一個個多邊形的各點坐標,為了後面添加polygon元素時方便繪制(points屬性的賦值),我們需要在求點坐標的時候順便把它們拼成字符串。上述代碼的for循環中,外層循環代表一個多邊形,內層循環代表多邊形上的點,多邊形與多邊形之間差異僅僅在於它們的外圓的半徑不同,而同一多邊形的點與點之間的差異在於它們的角度不同。點的坐標由半徑乘以角度的正弦或者余弦來求得。
得到了計算好的坐標以後,我們就開始添加網軸。
// 繪制網軸 var webs = main.append('g') .classed('webs', true); webs.selectAll('polygon') .data(polygons.webs) .enter() .append('polygon') .attr('points', function(d) { return d; });
添加一個g元素用來組合所有代表網軸的元素,選擇其中的polygon元素並綁定polygons.webs數組,enter()
搭配append()
添加新的polygon元素,對points屬性進行復制。完成這一系列在前面幾篇文章中已經反復練習的操作以後,為了讓網軸更加的明顯,我們給它加一點樣式。
.webs polygon { fill: white; fill-opacity: 0.5; stroke: gray; stroke-dasharray: 10 5; }
我們得到了如下圖所示的網軸。
添加縱軸
接著我們把縱軸也添加上。縱軸就是添加一根根的線條,連接中心點和最外層的多邊形上的點,需要的數據可以從polygons.webPoints[0]
中取。
// 添加縱軸 var lines = main.append('g') .classed('lines', true); lines.selectAll('line') .data(polygons.webPoints[0]) .enter() .append('line') .attr('x1', 0) .attr('y1', 0) .attr('x2', function(d) { return d.x; }) .attr('y2', function(d) { return d.y; });
雷達圖的坐標軸部分就完成了。
計算雷達圖區域並添加
雷達圖區域也是一個多邊形,只不過是一個不規則的多邊形。但是他的幾個點始終處在縱軸上,並且點在縱軸上的位置可以通過點所代表的值在縱軸范圍內的占比計算出來的。
// 計算雷達圖表的坐標 var areasData = []; var values = data.values; for(var i=0;i<values.length;i++) { var value = values[i], area = '', points = []; for(var k=0;k<total;k++) { var r = radius * (value[k] - rangeMin)/(rangeMax - rangeMin); var x = r * Math.sin(k * onePiece), y = r * Math.cos(k * onePiece); area += x + ',' + y + ' '; points.push({ x: x, y: y }) } areasData.push({ polygon: area, points: points }); }
計算完點的坐標以後我們就可以添加雷達圖區域了。為了使雷達圖更可觀,我們除了添加多邊形表示雷達圖的區域以外,也把多邊形在各縱軸上的點標記出來。
// 添加g分組包含所有雷達圖區域 var areas = main.append('g') .classed('areas', true); // 添加g分組用來包含一個雷達圖區域下的多邊形以及圓點 areas.selectAll('g') .data(areasData) .enter() .append('g') .attr('class',function(d, i) { return 'area' + (i+1); }); for(var i=0;i<areasData.length;i++) { // 依次循環每個雷達圖區域 var area = areas.select('.area' + (i+1)), areaData = areasData[i]; // 繪制雷達圖區域下的多邊形 area.append('polygon') .attr('points', areaData.polygon) .attr('stroke', function(d, index) { return getColor(i); }) .attr('fill', function(d, index) { return getColor(i); }); // 繪制雷達圖區域下的點 var circles = area.append('g') .classed('circles', true); circles.selectAll('circle') .data(areaData.points) .enter() .append('circle') .attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }) .attr('r', 3) .attr('stroke', function(d, index) { return getColor(i); }); }
這裡為了體驗層次關系,我用areas包含住所有雷達圖區域,又在裡面用一個g分組表示一個雷達圖區域,在雷達圖區域裡包含組成該區域的多邊形和圓點。這裡因為我們數據用一個雷達圖區域就表示了,所以這個for循環只會循環一次。給繪制好的區域加上樣式。
.areas polygon { fill-opacity: 0.5; stroke-width: 3; } .areas circle { fill: white; stroke-width: 3; }
於是得到了下圖這個樣子的圖表。
計算文字標簽坐標並添加
為了讓上面的圖表更完整一些,我們給它加上文字標簽。文字標簽標注在網軸的外圍,所以可以以計算網軸多邊形點坐標的同樣的原理計算文字標簽的坐標。
// 計算文字標簽坐標 var textPoints = []; var textRadius = radius + 20; for(var i=0;i<total;i++) { var x = textRadius * Math.sin(i * onePiece), y = textRadius * Math.cos(i * onePiece); textPoints.push({ x: x, y: y }); }
計算好坐標以後再添加到畫布中。
// 繪制文字標簽 var texts = main.append('g') .classed('texts', true); texts.selectAll('text') .data(textPoints) .enter() .append('text') .attr('x', function(d) { return d.x; }) .attr('y', function(d) { return d.y; }) .text(function(d,i) { return data.fieldNames[i]; });
最後的樣子是這樣的。
總結
以上就是利用D3.js實現雷達的全部內容,希望這篇文章對大家的學習和工作能有所幫助。如果有疑問大家可以留言交流,感興趣的朋友們請大家繼續關注。