canvas+js实现动态饼图效果

Posted Katle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了canvas+js实现动态饼图效果相关的知识,希望对你有一定的参考价值。

canvas+js实现动态饼图效果

参考了一些网上的例子,利用canvas+js实现动态饼图效果demo,包括鼠标移入图块颜色变化以及带有tooltip提示。实现逻辑可分为三个步骤:

1、首先利用canvas画布原理画饼图。
2、添加鼠标移入监听事件,获取鼠标当前坐标。
3、判断当前鼠标坐标是否在所画饼图扇形区域,改变饼图颜色和显示tooltip提示。

1、画饼图

利用canvas实现一个饼图效果,我们首先需要知道canvas画布画图原理以及相关需要的API。
W3school这样介绍canvas,还不太了解的朋友可以先去熟悉熟悉。

什么是 Canvas?
html5 的 canvas 元素使用 javascript 在网页上绘制图像。
画布是一个矩形区域,您可以控制其每一像素。
canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

创建 Canvas 元素
向 HTML5 页面添加 canvas 元素。
规定元素的 id、宽度和高度:

  <canvas id="myCanvas" width="200" height="100"></canvas>

通过上面的介绍,即画图的html部分我们已经了解,只需要定义一个带有宽高及id的canvas即可,接下来便是通过js来绘制图形,首先我们需要利用id来寻找canvas元素。

let el = document.getElementById("myCanvas");

然后创建画布的绘制对象:

let cxt = el.getContext("2d"); 

接下来,即可利用canvas的相关API来绘制我们需要的图像,canvas的API可参考《HTML Canvas 参考手册》。本次画饼图设计需要的API整理如下:

ctx.beginPath(); // 起始一条路径,或重置当前路径
ctx.fillStyle; // 设置或返回用于填充绘画的颜色、渐变或模式 ctx.moveTo(x, y); // 把路径移动到画布中的指定点,不创建线条
ctx.arc(x, y, radius, sAngle, eAngle, false); // 创建弧/曲线(用于创建圆形或部分圆)
ctx.fill(); // 填充当前绘图(路径)

以上只列出了几个化饼图的关键API,其他包括饼图具体实现、小图标以及文字的相关API详细参见下面的代码。

2、添加鼠标监听

利用js的addEventListener,添加所绘画布鼠标监听,获取当前鼠标移入的坐标。

// 鼠标位置监听
getPosition(element) {
    let mouseTimer = null;
    element.addEventListener('mousemove', (e) => {
        e = e || window.event;
        if ( e.ofSfsetX || e.offsetX==0 ) {
            this.mousePosition.x = e.offsetX;
            this.mousePosition.y = e.offsetY;
        } else if ( e.layerX || e.layerX==0 ){
            this.mousePosition.x = e.layerX;
            this.mousePosition.y = e.layerY;
        } 

        // 监听tooltip显示
        let el =  document.getElementById('self-tooltip');
        el.style.visibility = 'hidden';
        
        clearTimeout(mouseTimer);
        mouseTimer = setTimeout(() => {
            this.drawPie(element, this.pieData);
        }, 50)
    });
}, 

3、鼠标移入,改变饼图样式

在绘制饼图方法中调用getPosition(element)方法,利用ctx.isPointInPath()API判断当前鼠标坐标是否在所绘制扇形中,以改变相关样式和显示tooltip提示。

// 监听鼠标是否移动到绘制扇形处
if (ctx.isPointInPath(this.mousePosition.x, this.mousePosition.y)) {
    // 鼠标移上改变颜色
    ctx.fillStyle = '#F56C6C';
    // tooltip显示
    let el =  document.getElementById('self-tooltip');
    el.innerHTML = data.labels[index] + ":" + (percent * 100).toFixed(2) + "%";
    el.style.left = `${this.mousePosition.x + 15}px`;
    el.style.top = `${this.mousePosition.y + 15}px`;
    el.style.visibility = 'visible';
}

4、详细代码如下

<template>
    <div class="box-style">
        <canvas :id="canvasId" :width="width"  :height="height" style="border: 1px solid #909399"></canvas>
        <span id="self-tooltip" class="tooltip-text"></span>
    </div>
</template>

<script>
export default {
    props: {
        pieData: {
            type: Object,
            default: function() {
                return {
                    colors: ['#AFB4DB', '#91CC75', '#FFC333', '#FFC0CB', '#73C0DE'], // 颜色
                    labels: ['周一', '周二', '周三', '周四', '周五'], //标签
                    values: [10, 20, 30 , 40, 50], //值
                    radius: 100 //圆半径
                };
            }
        },
        width: {
            type: Number,
            default: function() {
                return 400;
            }
        },
        height: {
            type: Number,
            default: function() {
                return 400;
            }
        },
        canvasId: {
            type: String,
            default: function() {
                return 'pie';
            }
        }
    },
    data() {
        return {
            // 鼠标移动是坐标
            mousePosition: {}
        }
    },
    mounted() {
        let pieElement = document.getElementById(this.canvasId);
        this.drawPie(pieElement, this.pieData);
        this.getPosition(pieElement);
    },
    methods: {
        // 画饼状图
        drawPie(element, data) {
            // 在画布上初始化绘图环境
            let ctx = element.getContext('2d');
            let drawData = data.values; // 画图数据
            let sum = this.getSum(drawData); //获取绘制数据的总和
            let sAngle = 0; // 扇形开始的角度
            let eAngle; // 结束的角度
            let x = element.width / 2;
            let y = element.height / 2; //圆心坐标
            let radius = data.radius;  // 圆半径
            let xMarker = 20; // 标记坐标
            let yMarker = 20; // 标记坐标
            drawData.forEach((value, index) => {
                // 绘制饼图
                let percent = value / sum; // 计算每个数据的占比,根据占比求扇形的弧度,即每个扇形结束的角度
                eAngle = sAngle + Math.PI * 2 * (percent);
                ctx.beginPath(); //新路径
                ctx.fillStyle = data.colors[index];
                ctx.moveTo(x, y);
                ctx.arc(x, y, radius, sAngle, eAngle, false);
                // 监听鼠标是否移动到绘制扇形处
                if (ctx.isPointInPath(this.mousePosition.x, this.mousePosition.y)) {
                    // 鼠标移上改变颜色
                    ctx.fillStyle = '#F56C6C';
                    // tooltip显示
                    let el =  document.getElementById('self-tooltip');
                    el.innerHTML = data.labels[index] + ":" + (percent * 100).toFixed(2) + "%";
                    el.style.left = `${this.mousePosition.x + 15}px`;
                    el.style.top = `${this.mousePosition.y + 15}px`;
                    el.style.visibility = 'visible';
                }

                // 绘制左侧标记
                ctx.moveTo(xMarker, yMarker);
                // 绘制矩形
                ctx.fillRect(xMarker, yMarker, 30, 10);
                // 绘制文字
                ctx.font = 'blod 14px';
                ctx.txtalgin = 'center';
                ctx.textBaseline = 'top';
                
                ctx.fillText(data.labels[index] + ":" + (percent * 100).toFixed(2) + "%", xMarker + 35, yMarker);

                // 填充以上绘制
                ctx.fill(); 
                // 绘制下一个扇形时初始值改变
                sAngle = eAngle;
                yMarker += 20;
            });
        },
        // 求和
        getSum(data) {
            let sum = 0;
            data.map(value => {
                sum += value;
            });
            return sum;
        },
        // 鼠标位置监听
        getPosition(element) {
            let mouseTimer = null;
            element.addEventListener('mousemove', (e) => {
                e = e || window.event;
                if ( e.ofSfsetX || e.offsetX==0 ) {
                    this.mousePosition.x = e.offsetX;
                    this.mousePosition.y = e.offsetY;
                } else if ( e.layerX || e.layerX==0 ){
                    this.mousePosition.x = e.layerX;
                    this.mousePosition.y = e.layerY;
                } 

                // 监听tooltip显示
                let el =  document.getElementById('self-tooltip');
                el.style.visibility = 'hidden';
                
                clearTimeout(mouseTimer);
                mouseTimer = setTimeout(() => {
                    this.drawPie(element, this.pieData);
                }, 50)
            });
        }, 
    }
}
</script>

<style lang="less" scoped>
.box-style {
    position: relative;
    display: inline-block;
    width: 100%;
    height: 100%;
    // 画布
    canvas {
        cursor: pointer;
    }
} 
// 提示工具
.tooltip-text {
    position: absolute;
    width: 150px;
    z-index: 10;
    background-color: #909399;
    color: #fff;
    border-radius: 5px;
    padding: 5px;
    left: 0;
    top: 0;
    visibility: hidden;
}
</style>>

5、效果

以上是关于canvas+js实现动态饼图效果的主要内容,如果未能解决你的问题,请参考以下文章

第五周可执行代码 以及 Canvas 制作个人PSP分类饼图

原生CANVAS语法实现的封装折线图和饼图

原生CANVAS语法实现的封装折线图和饼图

小程序里echarts画的饼图在安卓手机上效果不正常?

使用vue学习three.js之加载和使用纹理-使用canvas画布上的绘画作为纹理渲染到方块上,使用动态绘画纹理

canvas 模仿highcharts自制饼图插件