新年伊始,学习不止。
顺带恭喜下自己博客点击量突破 20w~啦~~ 不容易啊~~
--------------------------------------------------------------------------
王者荣耀的英雄介绍资料里,都有个雷达图,以直观的形式可以分析这个英雄的优缺点。如下图所示:图片来自网络。
为了避嫌,免得引发口水战,我隐去了具体的英雄,只谈雷达图的绘制。
先看效果。
准备一个 canvas。
一些基础工作:获取canvas,获取 ctx。
let mycan = document.getElementById("mycan");
let ctx = mycan.getContext("2d");
雷达图的拐角,其实就是三角函数得出来的坐标。
用 JavaScript 写出来就是:θ 是弧度。
X = Math.cos( θ )* R
Y = Math.sin( θ )* R
每个点的坐标,可以借用循环得出,如下所示。变量 bian,雷达图的边的个数。dotsArray 是专门用来存储坐标点的数组。
for(let i = 0 ; i <= bian-1 ; i++){
let angleHD = Math.PI*2 / bian * i - Math.PI/2 ;
let x = r * Math.cos( angleHD );
let y = r * Math.sin( angleHD );
dotsArray.push({x:x,y:y});
}
雷达图的关键就是要会绘制基本的多边形。由上面的推理,可以得知,一个基本的多边形的绘制如下所示:
let mycan = document.getElementById("mycan");
let ctx = mycan.getContext("2d");
let bian = 5 ; // 5 边形
let r = 150 ; // 坐标点与中心的距离
let dotsArray = []; // 坐标点数组
for(let i = 0 ; i <= bian-1 ; i++){
// 角度 - PI/2 ,是让起点坐标在 12点 位置。
let angleHD = Math.PI*2 / bian * i - Math.PI/2 ;
let x = r * Math.cos( angleHD );
let y = r * Math.sin( angleHD );
dotsArray.push({x:x,y:y});
}
console.info( dotsArray );
// 开始绘制路径
ctx.beginPath();
ctx.save();
// 位移原点到canvas中心。这样,绘制坐标的时候,就不用考虑位移情况。
ctx.translate( mycan.width/2 ,mycan.height/2);
ctx.moveTo( dotsArray[0].x, dotsArray[0].y );
for(let i=1; i<=dotsArray.length-1 ; i++){
ctx.lineTo( dotsArray[i].x, dotsArray[i].y);
}
ctx.closePath();
ctx.stroke();
ctx.restore();
以上代码为基础。现在改进下,用面向对象方式绘制多边形。
polygon.js
/*
* 多边形类:
* 参数:
* x,y 中心点坐标
* r 距离中心点的距离
* bian 边数,至少大于3
* dotsArray 存储各个点的坐标数组
* */
class Polygon{
constructor(props) {
this.x = 0 ;
this.y = 0 ;
this.r = 150 ;
this.bian = 3 ;
this.dotsArray = [];
this.fillStyle = "#000";
this.strokeStyle = "#f30";
Object.assign(this, props);
return this;
}
createPath(ctx){
let {x,y, bian,r} = this;
ctx.beginPath();
ctx.save();
for(let i = 0 ; i <= bian-1; i++ ){
let angleHD = Math.PI*2 / bian * i - Math.PI/2 ;
let dotX = r*Math.cos( angleHD );
let dotY = r*Math.sin( angleHD );
this.dotsArray.push({x:dotX, y:dotY});
}
ctx.moveTo( this.dotsArray[0].x , this.dotsArray[0].y);
for(let i=1 ; i <= this.dotsArray.length-1 ; i++){
ctx.lineTo( this.dotsArray[i].x , this.dotsArray[i].y);
}
ctx.closePath(); // 封闭路径
ctx.restore();
return this;
}
render(ctx){
let {x,y,strokeStyle, fillStyle } = this ;
ctx.save();
ctx.translate( x , y );
ctx.strokeStyle = strokeStyle;
this.createPath(ctx);
ctx.stroke();
ctx.restore();
return this ;
}
}
let mycan = document.getElementById("mycan");
let ctx = mycan.getContext("2d");
let myPolygon = new Polygon({
x : mycan.width/2,
y : mycan.height/2,
bian : 5,
r : 200
});
myPolygon.render(ctx);
把上面的类改进,拓展下,完成雷达图的绘制。
/*
* 对变形类:
* 参数:
* x,y : 中心点坐标
* r : 距离中心点的距离
* datas 各个点的文字内容
* 数据 v : 0-100 的分数
* name: 点上的数据名称
* false(默认) 或者
[
{ name:"智力", v:90},
{ name:"武力", v:80},
{ name:"策略", v:99},
{ name:"内政", v:90}
]
*
* bian 边数,至少大于3
* dotsArray 存储各个点的坐标数组
* fillStyle , strokeStyle 颜色,以深色为主
* subStrokeStyle 内部线条颜色,以浅色为主。
* font 文字大小,字体
* radarXXColor 雷达信息的相关色彩
*/
class Polygon{
constructor(props) {
this.x = 0 ;
this.y = 0 ;
this.r = 150 ;
this.datas = props.datas ;
this.bianNum = this.datas.length || 3 ;
this.dotsArray = [];
this.fillStyle = "#000";
this.strokeStyle = "#000";
this.subStrokeStyle = "#ccc";
this.font = "18px '微软雅黑'";
this.radarFillColor = "rgba(255,100,100, 0.3)"
this.radarStrokeColor = "rgba(255,100,100, 1)"
Object.assign(this, props);
return this;
}
/*
* createBgPath : 绘制雷达图的背景。
* */
createBgPath(ctx){
let {x,y,r,bianNum,subStrokeStyle,strokeStyle} = this;
let disR = r / 5 ;
let angle = Math.PI*2 / bianNum ;
ctx.save();
// 5 个同心多边形
for(let i=0 ; i<=4 ; i++ ){
let dotsX , dotsY ;
ctx.beginPath();
ctx.strokeStyle = subStrokeStyle;
for( let j=0 ; j <= bianNum-1 ; j++ ){
dotsX = Math.cos( angle*j-Math.PI/2 )*disR*(i+1);
dotsY = Math.sin( angle*j-Math.PI/2 )*disR*(i+1);
if( j===0 ){
ctx.moveTo( dotsX, dotsY );
}else{
ctx.lineTo( dotsX, dotsY );
}
// 如果是最外面的边。存储边的各个坐标。
if( i === 4 ){
this.dotsArray.push({x:dotsX,y:dotsY});
}
}
// 如果是最外面的边,更改描边色。
if( i===4 ){
ctx.strokeStyle = strokeStyle;
}
ctx.closePath();
ctx.stroke();
}
for(let i = 0 ; i<=this.dotsArray.length-1; i++){
ctx.save();
ctx.strokeStyle = subStrokeStyle ;
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(this.dotsArray[i].x, this.dotsArray[i].y);
ctx.stroke();
ctx.restore();
}
ctx.restore();
return this;
}
/*
* createRadarText : 绘制雷达图的文字
* */
createRadarText( ctx ){
let {x,y, datas ,font, dotsArray } = this;
ctx.save();
ctx.translate(x, y);
ctx.font = font ;
this.createBgPath(ctx); // 绘制雷达图背景
for(let i=0 ; i <= dotsArray.length-1 ; i++){
let txt = datas[i].name ;
let dx = dotsArray[i].x;
let dy = dotsArray[i].y;
// 根据坐标,把文本的位置做适当调整。
if(dx<0){
ctx.textAlign = "end" ;
dx = dx - 5 ;
}else if(dx>0.01){
ctx.textAlign = "start" ;
dx = dx + 5 ;
}else{
ctx.textAlign = "center" ;
}
if( dy < 0 ){
ctx.textBaseline = "bottom";
dy = dy-5 ;
}else if(dy>0.01){
ctx.textAlign = "top" ;
dy = dy + 20 ;
}else{
ctx.textBaseline = "middle";
}
// 绘制文本,坐标在最大的多边形的几个点上
ctx.fillText( txt ,dx, dy );
}
ctx.restore();
return this;
}
/*
* 绘制雷达的点
* */
drawRadarDots(ctx,dotsArray){
let {x,y,radarStrokeColor} = this;
ctx.save();
ctx.translate(x, y);
ctx.fillStyle = radarStrokeColor;
for(let i = 0 ; i <= dotsArray.length-1 ; i++){
ctx.beginPath();
ctx.arc( dotsArray[i].x , dotsArray[i].y , 5, 0,Math.PI*2);
ctx.fill();
}
ctx.restore();
return this;
}
/*
* 绘制雷达图
* */
drawRadar(ctx){
let {x,y,r, datas, bianNum, radarFillColor,radarStrokeColor} = this;
let radarDots = []; // 雷达点数组
let angle = Math.PI*2 / bianNum ;
// 绘制雷达文字,含背景
this.createRadarText(ctx);
// 绘制雷达部分
ctx.save();
ctx.strokeStyle = radarStrokeColor;
ctx.fillStyle = radarFillColor ;
ctx.translate(x,y);
ctx.beginPath();
for( let i = 0 ; i<=datas.length-1 ; i++){
let dx = datas[i].v/100*r*Math.cos( angle*i - Math.PI/2 );
let dy = datas[i].v/100*r*Math.sin( angle*i - Math.PI/2 );
radarDots.push( {x:dx,y:dy} );
if( i===0 ){
ctx.moveTo(dx,dy);
}else{
ctx.lineTo(dx,dy);
}
}
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
// 绘制雷达点
this.drawRadarDots(ctx,radarDots);
console.info( radarDots );
}
}
let mycan = document.getElementById("mycan");
let ctx = mycan.getContext("2d");
let myData = [
{ name:"战绩", v:98},
{ name:"团战", v:38},
{ name:"发育", v:100},
{ name:"输出", v:95},
{ name:"推进", v:72},
{ name:"生存", v:80}
];
let myPolygon = new Polygon({
datas:myData,
x:mycan.width/2 ,
y:mycan.height/2 ,
r:200
});
myPolygon.drawRadar(ctx);
完工~
可能有不完美的地方,后面再改进~