JavaScript 练手小技巧:我用canvas画出了王者荣耀英雄属性的雷达图

新年伊始,学习不止。

顺带恭喜下自己博客点击量突破 20w~啦~~ 不容易啊~~

--------------------------------------------------------------------------

王者荣耀的英雄介绍资料里,都有个雷达图,以直观的形式可以分析这个英雄的优缺点。如下图所示:图片来自网络。

为了避嫌,免得引发口水战,我隐去了具体的英雄,只谈雷达图的绘制。

先看效果。

HTML

准备一个 canvas。

<canvas id="mycan" width="800" height="600"></canvas>

JavaScript

一些基础工作:获取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);

完工~

可能有不完美的地方,后面再改进~

猜你喜欢

转载自blog.csdn.net/weixin_42703239/article/details/113791517