利用Canvas绘制雷达图

雷达图(蜘蛛网图)是一种常见的数据分析图表,本文采用canvas来绘制雷达图,并最终封装成一个小组件。首先来看一下最终的效果图:


利用Canvas绘制雷达图_第1张图片

如何画正多边形

以正五边形雷达图为例(其他任意正多边形也一样),如下图所示。Canvas画图的原点在左上角,以r为半径,(r, r)为圆心作圆,作为正五边形的外接圆,则正五边形每条边所对应的圆心角均为 rad = 2*Math.PI/5。再根据正余弦可以求得每个定点所对应的坐标,这样一个正五边形就可以画出来了。


利用Canvas绘制雷达图_第2张图片

下面进行具体的代码实现。

HTML


<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();
}

效果如下:


利用Canvas绘制雷达图_第3张图片

绘制伞骨

由于项目名称是定位在五边形的顶点处,因此,在绘制伞骨的同时可以把每个项目的名称同时绘制出来。

//绘制伞骨
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();

效果如下:


利用Canvas绘制雷达图_第4张图片

绘制数据点

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();
}

效果入下:


利用Canvas绘制雷达图_第5张图片

绘制折线

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();

最终效果图:


利用Canvas绘制雷达图_第6张图片

封装插件

完整插件代码:

//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

你可能感兴趣的:(demo)