D3.js實(shí)現(xiàn)雷達(dá)圖的方法詳解
前言
再簡(jiǎn)單介紹下D3.js,D3.js 是一個(gè)基于數(shù)據(jù)操作文檔JavaScript庫(kù)。D3幫助你給數(shù)據(jù)帶來(lái)活力通過(guò)使用HTML、SVG和CSS。D3重視Web標(biāo)準(zhǔn)為你提供現(xiàn)代瀏覽器的全部功能,而不是給你一個(gè)專有的框架。結(jié)合強(qiáng)大的可視化組件和數(shù)據(jù)驅(qū)動(dòng)方式Dom操作。這里也可以看到它是用SVG來(lái)呈現(xiàn)圖表的,所以使用D3.js是需要一定的SVG基礎(chǔ)的。
本文依然是先把簡(jiǎn)單的畫圖框架搭起來(lái),添加SVG畫布。這里和餅圖有點(diǎn)類似,為了方便后面的繪制,我們把組合這些元素的g元素移動(dòng)到畫布的中心:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>雷達(dá)圖</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;
// 創(chuàng)建一個(gè)分組用來(lái)組合要畫的圖表元素
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>
這里為什么我會(huì)說(shuō)雷達(dá)圖和餅圖會(huì)有點(diǎn)類似呢?看一下下面這張圖。

可以看到,雷達(dá)圖的網(wǎng)軸(藍(lán)色部分)是由多個(gè)正多邊形所組成的,而正多邊形的繪制正好是可以利用圓半徑的特性來(lái)繪制的,所以從一開(kāi)始把繪制的原點(diǎn)移動(dòng)到畫布的中心是很方便后面的繪制工作的。
模擬數(shù)據(jù)
我們先模擬一些原始數(shù)據(jù)。
var data = {
fieldNames: ['語(yǔ)文','數(shù)學(xué)','外語(yǔ)','物理','化學(xué)','生物','政治','歷史'],
values: [
[10,20,30,40,50,60,70,80]
]
};
計(jì)算網(wǎng)軸坐標(biāo)并繪制
在前面的其他圖表的實(shí)現(xiàn)中,都有比例尺或者布局這樣的東西來(lái)為我們轉(zhuǎn)化數(shù)據(jù)提供便利,雷達(dá)圖是否也存在這樣的工具函數(shù)呢?答案是沒(méi)有!沒(méi)有!沒(méi)有!重要的事情說(shuō)三遍!(-_-) 所以,我們只能開(kāi)動(dòng)自己的小腦瓜自己算了。
// 設(shè)定一些方便計(jì)算的常量
var radius = 100,
// 指標(biāo)的個(gè)數(shù),即fieldNames的長(zhǎng)度
total = 8,
// 需要將網(wǎng)軸分成幾級(jí),即網(wǎng)軸上從小到大有多少個(gè)正多邊形
level = 4,
// 網(wǎng)軸的范圍,類似坐標(biāo)軸
rangeMin = 0,
rangeMax = 100,
arc = 2 * Math.PI;
// 每項(xiàng)指標(biāo)所在的角度
var onePiece = arc/total;
// 計(jì)算網(wǎng)軸的正多邊形的坐標(biāo)
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);
}
計(jì)算網(wǎng)軸的坐標(biāo)就是計(jì)算一個(gè)個(gè)多邊形的各點(diǎn)坐標(biāo),為了后面添加polygon元素時(shí)方便繪制(points屬性的賦值),我們需要在求點(diǎn)坐標(biāo)的時(shí)候順便把它們拼成字符串。上述代碼的for循環(huán)中,外層循環(huán)代表一個(gè)多邊形,內(nèi)層循環(huán)代表多邊形上的點(diǎn),多邊形與多邊形之間差異僅僅在于它們的外圓的半徑不同,而同一多邊形的點(diǎn)與點(diǎn)之間的差異在于它們的角度不同。點(diǎn)的坐標(biāo)由半徑乘以角度的正弦或者余弦來(lái)求得。
得到了計(jì)算好的坐標(biāo)以后,我們就開(kāi)始添加網(wǎng)軸。
// 繪制網(wǎng)軸
var webs = main.append('g')
.classed('webs', true);
webs.selectAll('polygon')
.data(polygons.webs)
.enter()
.append('polygon')
.attr('points', function(d) {
return d;
});
添加一個(gè)g元素用來(lái)組合所有代表網(wǎng)軸的元素,選擇其中的polygon元素并綁定polygons.webs數(shù)組,enter()搭配append()添加新的polygon元素,對(duì)points屬性進(jìn)行復(fù)制。完成這一系列在前面幾篇文章中已經(jīng)反復(fù)練習(xí)的操作以后,為了讓網(wǎng)軸更加的明顯,我們給它加一點(diǎn)樣式。
.webs polygon {
fill: white;
fill-opacity: 0.5;
stroke: gray;
stroke-dasharray: 10 5;
}
我們得到了如下圖所示的網(wǎng)軸。

添加縱軸
接著我們把縱軸也添加上??v軸就是添加一根根的線條,連接中心點(diǎn)和最外層的多邊形上的點(diǎn),需要的數(shù)據(jù)可以從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;
});
雷達(dá)圖的坐標(biāo)軸部分就完成了。

計(jì)算雷達(dá)圖區(qū)域并添加
雷達(dá)圖區(qū)域也是一個(gè)多邊形,只不過(guò)是一個(gè)不規(guī)則的多邊形。但是他的幾個(gè)點(diǎn)始終處在縱軸上,并且點(diǎn)在縱軸上的位置可以通過(guò)點(diǎn)所代表的值在縱軸范圍內(nèi)的占比計(jì)算出來(lái)的。
// 計(jì)算雷達(dá)圖表的坐標(biāo)
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
});
}
計(jì)算完點(diǎn)的坐標(biāo)以后我們就可以添加雷達(dá)圖區(qū)域了。為了使雷達(dá)圖更可觀,我們除了添加多邊形表示雷達(dá)圖的區(qū)域以外,也把多邊形在各縱軸上的點(diǎn)標(biāo)記出來(lái)。
// 添加g分組包含所有雷達(dá)圖區(qū)域
var areas = main.append('g')
.classed('areas', true);
// 添加g分組用來(lái)包含一個(gè)雷達(dá)圖區(qū)域下的多邊形以及圓點(diǎn)
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++) {
// 依次循環(huán)每個(gè)雷達(dá)圖區(qū)域
var area = areas.select('.area' + (i+1)),
areaData = areasData[i];
// 繪制雷達(dá)圖區(qū)域下的多邊形
area.append('polygon')
.attr('points', areaData.polygon)
.attr('stroke', function(d, index) {
return getColor(i);
})
.attr('fill', function(d, index) {
return getColor(i);
});
// 繪制雷達(dá)圖區(qū)域下的點(diǎn)
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);
});
}
這里為了體驗(yàn)層次關(guān)系,我用areas包含住所有雷達(dá)圖區(qū)域,又在里面用一個(gè)g分組表示一個(gè)雷達(dá)圖區(qū)域,在雷達(dá)圖區(qū)域里包含組成該區(qū)域的多邊形和圓點(diǎn)。這里因?yàn)槲覀償?shù)據(jù)用一個(gè)雷達(dá)圖區(qū)域就表示了,所以這個(gè)for循環(huán)只會(huì)循環(huán)一次。給繪制好的區(qū)域加上樣式。
.areas polygon {
fill-opacity: 0.5;
stroke-width: 3;
}
.areas circle {
fill: white;
stroke-width: 3;
}
于是得到了下圖這個(gè)樣子的圖表。

計(jì)算文字標(biāo)簽坐標(biāo)并添加
為了讓上面的圖表更完整一些,我們給它加上文字標(biāo)簽。文字標(biāo)簽標(biāo)注在網(wǎng)軸的外圍,所以可以以計(jì)算網(wǎng)軸多邊形點(diǎn)坐標(biāo)的同樣的原理計(jì)算文字標(biāo)簽的坐標(biāo)。
// 計(jì)算文字標(biāo)簽坐標(biāo)
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
});
}
計(jì)算好坐標(biāo)以后再添加到畫布中。
// 繪制文字標(biāo)簽
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];
});
最后的樣子是這樣的。

總結(jié)
以上就是利用D3.js實(shí)現(xiàn)雷達(dá)的全部?jī)?nèi)容,希望這篇文章對(duì)大家的學(xué)習(xí)和工作能有所幫助。如果有疑問(wèn)大家可以留言交流,感興趣的朋友們請(qǐng)大家繼續(xù)關(guān)注腳本之家。
- d3.js實(shí)現(xiàn)簡(jiǎn)單的網(wǎng)絡(luò)拓?fù)鋱D實(shí)例代碼
- D3.js實(shí)現(xiàn)拓?fù)鋱D的示例代碼
- D3.js實(shí)現(xiàn)折線圖的方法詳解
- D3.js實(shí)現(xiàn)柱狀圖的方法詳解
- JavaScript可視化圖表庫(kù)D3.js API中文參考
- D3.js實(shí)現(xiàn)餅狀圖的方法詳解
- 基于d3.js實(shí)現(xiàn)實(shí)時(shí)刷新的折線圖
- D3.js實(shí)現(xiàn)散點(diǎn)圖和氣泡圖的方法詳解
- d3.js實(shí)現(xiàn)立體柱圖的方法詳解
- 利用d3.js力導(dǎo)布局繪制資源拓?fù)鋱D實(shí)例教程
相關(guān)文章
BootstrapValidator實(shí)現(xiàn)注冊(cè)校驗(yàn)和登錄錯(cuò)誤提示效果
這篇文章主要為大家詳細(xì)介紹了Bootstrap Validator實(shí)現(xiàn)注冊(cè)校驗(yàn)和登錄錯(cuò)誤提示效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
JS面向?qū)ο蟮某绦蛟O(shè)計(jì)相關(guān)知識(shí)小結(jié)
這篇文章主要介紹了JS面向?qū)ο蟮某绦蛟O(shè)計(jì),現(xiàn)在很多代碼都是基于面向?qū)ο髮?shí)現(xiàn),需要的朋友可以參考下2018-05-05
打字效果動(dòng)畫的4種實(shí)現(xiàn)方法(超簡(jiǎn)單)
下面小編就為大家?guī)?lái)一篇打字效果動(dòng)畫的4種實(shí)現(xiàn)方法(超簡(jiǎn)單)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
使用pdf-lib.js實(shí)現(xiàn)pdf添加自定義水印功能
pdf-lib是一個(gè)強(qiáng)大的JavaScript庫(kù),允許在任何JavaScript環(huán)境中創(chuàng)建和修改PDF文檔,下面就跟隨小編一起來(lái)學(xué)習(xí)一下如何使用pdf-lib實(shí)現(xiàn)pdf添加自定義水印功能吧2024-11-11
使用Vue3實(shí)現(xiàn)一個(gè)Upload組件的示例代碼
這篇文章主要介紹了使用Vue3實(shí)現(xiàn)一個(gè)Upload組件的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
JavaScript建立一個(gè)語(yǔ)法高亮輸入框?qū)崿F(xiàn)思路
通常網(wǎng)站自帶的textarea編輯器不能滿足我們的需求比如高亮顯示代碼等,在這篇文章中,我將使用JavaScript庫(kù)ACE來(lái)創(chuàng)建一個(gè)輸入框效果,該腳本允許開(kāi)發(fā)人員創(chuàng)建支持語(yǔ)法高亮的輸入框,感興趣的你可不要錯(cuò)過(guò)了哈2013-02-02
js下將字符串當(dāng)函數(shù)執(zhí)行的方法
js下將字符串當(dāng)函數(shù)執(zhí)行的方法,需要的朋友可以參考下。2011-07-07

