雷达图(蜘蛛网图)是一种常见的数据分析图表,本文采用canvas来绘制雷达图,并最终封装成一个小组件。首先来看一下最终的效果图:
以正五边形雷达图为例(其他任意正多边形也一样),如下图所示。Canvas
画图的原点在左上角,以r为半径,(r, r)
为圆心作圆,作为正五边形的外接圆,则正五边形每条边所对应的圆心角均为 rad = 2*Math.PI/5
。再根据正余弦可以求得每个定点所对应的坐标,这样一个正五边形就可以画出来了。
下面进行具体的代码实现。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Canvas绘制雷达图title>
<style>
body, html, div{
margin: 0;
padding: 0;
}
.container {
width: 300px;
height: 300px;
margin: 50px auto;
}
style>
head>
<body>
<div class="container" id="container">div>
body>
html>
var container = document.getElementById('container');
var cans = document.createElement("canvas");
container.appendChild(cans);
var ctx = cans.getContext("2d");
var data = [["HTML", 0.5], ["CSS", 0.6], ["JS", 0.4], ["jQuery", 0.8], ["React", 0.7]];
cans.width = 300;
cans.height = 300;
var step = data.length;
var r = 150;
将正五边形从内到外分成十份,并以蓝白相间的背景进行填充。
//绘制网格背景
var isBlue = false;
for(var s = 10; s > 0; s--) {
ctx.beginPath();
for(var i=0;ivar rad = 2*Math.PI/step * i;
var x = r + Math.sin(rad)*r*(s/10);
var y = r + Math.cos(rad)*r*(s/10);
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fillStyle = (isBlue = !isBlue)?'#99c0ff' : '#f1f9ff';
ctx.fill();
}
效果如下:
由于项目名称是定位在五边形的顶点处,因此,在绘制伞骨的同时可以把每个项目的名称同时绘制出来。
//绘制伞骨
ctx.beginPath();
for(var i=0;i2*Math.PI/step * i;
var x = r + Math.sin(rad)*r;
var y = r + Math.cos(rad)*r;
ctx.moveTo(r,r);
ctx.lineTo(x, y);
var text = document.createElement("div");
text.innerHTML = data[i][0];
text.style.position = "absolute";
//添加文本
if(x > r) {
text.style.left = ( x + 10) + 'px';
} else {
text.style.right = (300-x +5) + 'px';
}
if(y > r) {
text.style.top = y + 'px';
} else {
text.style.bottom = (300 - y) + 'px';
}
container.appendChild(text);
}
ctx.strokeStyle = "#e0e0e0"
ctx.stroke();
效果如下:
ctx.fillStyle = "#ff7676";
for(var i=0;ivar rad = 2*Math.PI/step * i;
var x = r + Math.sin(rad)*r*data[i][1];
var y = r + Math.cos(rad)*r*data[i][1];
ctx.beginPath();
ctx.arc(x,y,4,0,2*Math.PI);
ctx.fill();
ctx.closePath();
}
效果入下:
ctx.strokeStyle = "#f00";
ctx.beginPath();
for(var i=0;ivar rad = 2*Math.PI/step * i;
var x = r + Math.sin(rad)*r*data[i][1];
var y = r + Math.cos(rad)*r*data[i][1];
ctx.lineTo(x,y);
}
ctx.closePath();
ctx.stroke();
最终效果图:
完整插件代码:
//radar.js
(function() {
var Radar = function(cfg) {
var outContainer = document.querySelector(cfg.el);
var container = document.createElement("div");
var cans = document.createElement("canvas");
container.appendChild(cans);
outContainer.appendChild(container);
var ctx = cans.getContext("2d");
var data = cfg.data;
var w = cfg.width;
var h = cfg.height;
container.style.position = "relative";
container.style.width = w+"px";
container.style.height = h+"px";
cans.width = w;
cans.height = h;
var step = data.length;
var r = w/2;
//绘制网格背景
var isBlue = false;
for(var s = 10; s > 0; s--) {
ctx.beginPath();
for(var i=0;ivar rad = 2*Math.PI/step * i;
var x = r + Math.sin(rad)*r*(s/10);
var y = r + Math.cos(rad)*r*(s/10);
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fillStyle = (isBlue = !isBlue)?'#99c0ff' : '#f1f9ff';
ctx.fill();
}
//绘制伞骨
ctx.beginPath();
for(var i=0;ivar rad = 2*Math.PI/step * i;
var x = r + Math.sin(rad)*r;
var y = r + Math.cos(rad)*r;
ctx.moveTo(r,r);
ctx.lineTo(x, y);
var text = document.createElement("div");
text.innerHTML = data[i][0];
text.style.position = "absolute";
//添加文本
if(x > r) {
text.style.left = ( x + 10) + 'px';
} else {
text.style.right = (w-x +5) + 'px';
}
if(y > r) {
text.style.top = y + 'px';
} else {
text.style.bottom = (h - y) + 'px';
}
container.appendChild(text);
}
ctx.strokeStyle = "#e0e0e0"
ctx.stroke();
//绘制折线
ctx.strokeStyle = "#f00";
ctx.beginPath();
for(var i=0;ivar rad = 2*Math.PI/step * i;
var x = r + Math.sin(rad)*r*data[i][1];
var y = r + Math.cos(rad)*r*data[i][1];
ctx.lineTo(x,y);
}
ctx.closePath();
ctx.stroke();
//添加数据点
ctx.fillStyle = "#ff7676";
for(var i=0;ivar rad = 2*Math.PI/step * i;
var x = r + Math.sin(rad)*r*data[i][1];
var y = r + Math.cos(rad)*r*data[i][1];
ctx.beginPath();
ctx.arc(x,y,4,0,2*Math.PI);
ctx.fill();
ctx.closePath();
}
}
window["Radar"] = Radar;
})();
调用:
var radar = new Radar({
el: "#container",
width: 300,
height: 300,
data: [["HTML", 0.5], ["CSS", 0.6], ["JS", 0.9], ["jQuery", 0.8], ["React", 0.7]]
});
到现在为止,整个雷达图就绘制完毕。当然还可以添加更多的内容,比如文字颜色,动画等。文字颜色可以在data数组中添加,给每一项再增加一个颜色项。动画的话可以将数据点和折线部分提出来单独绘制一个图层,通过控制数据点的大小实现图形的生长动画。完整版参加github:https://github.com/materialcoder/Chart-Component