Canvas绘制带标签导引线的环形图

2019-07-25  本文已影响0人  Odeng

运行效果

line-pie.png

实例代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>CirclePie</title>
    <style>
        html {
            background-color: black;
        }

        .pie {
            width: 300px;
            height: 300px;
            border: 1px solid red;
            margin: 100px auto;
        }
    </style>
</head>

<body>
    <div class="pie"></div>
</body>
<script>
    function CirclePie(options) {
        this.x = options.x || 0;
        this.y = options.y || 0;
        this.r = options.r || 50;
        this.width = 100;
        this.height = 100;
        this.ctx = null;
        //初始鼠标位置
        this.pos = {
            x: 0,
            y: 0
        };
    }

    CirclePie.prototype.init = function (dom) {
        if (!dom) {
            console.log('canvas挂载节点不存在');
            return;
        }
        var canvas = document.createElement("canvas");
        this.width = dom.offsetWidth;
        this.height = dom.offsetHeight;
        canvas.width = this.width;
        canvas.height = this.height;
        this.ctx = canvas.getContext("2d");
        //添加canvas节点
        dom.appendChild(canvas);
        //定点圆心
        this.ctx.translate(this.width / 2, this.height / 2);
        var _this = this;

        //绑定事件
        canvas.addEventListener('mousemove', function (event) {
            _this.pos = _this.getEventPosition(event);
            _this.draw(_this.options);
        }); 
    };

    CirclePie.prototype.getEventPosition = function (event) {
        var x, y;
        if (event.layerX || event.layerX == 0) {
            x = event.layerX;
            y = event.layerY;
        } else if (event.offsetX || event.offsetX == 0) { // Opera
            x = event.offsetX;
            y = event.offsetY;
        }
        return {
            x: x,
            y: y
        };
    }

    CirclePie.prototype.ring = function (x, y, r, start, end, color) {
        this.ctx.save();
        this.ctx.beginPath();
        this.ctx.strokeStyle = color;
        this.ctx.lineWidth = 10;
        this.ctx.arc(x, y, r, start, end);
        
        // this.ctx.closePath();
        if (this.ctx.isPointInPath(this.pos.x, this.pos.y)) {
            this.ctx.stroke();
            this.ctx.beginPath();
            if (this.pos.x > this.width / 2) {
                x = x + 10 * Math.sin(end - start);
            } else {
                x = x - 10 * Math.sin(end - start);
            }
            if (this.pos.y > this.height / 2) {
                y = y + 10 * Math.cos(end - start);
            } else {
                y = y - 10 * Math.cos(end - start);
            }
            this.ctx.arc(x, y, r, start, end);
            this.ctx.stroke();
        } else {
            this.ctx.stroke();
        }
         this.ctx.closePath();
        this.ctx.restore();
    };
    CirclePie.prototype.drawGuidLine = function (
        x0,
        y0,
        radius,
        startAngle,
        angle,
        color,
        text
    ) {
        this.ctx.beginPath();
        this.ctx.save();
        let out = 10;
        let cAngle = (startAngle + angle) / 2;
        let x = x0 + (radius + 2) * Math.cos(cAngle);
        let y = y0 + (radius + 2) * Math.sin(cAngle);
        let x2 = x0 + (radius + 15) * Math.cos(cAngle);
        let y2 = y0 + (radius + 15) * Math.sin(cAngle);
        this.ctx.moveTo(x, y);
        this.ctx.strokeStyle = color;
        this.ctx.lineTo(x2, y2);
        this.ctx.textBaseline = "middle";
        this.ctx.textAlign = "center";
        //默认字体大小
        this.ctx.font = "14px Microsoft Yahei";
        this.ctx.fillStyle = color;
        if (x2 > x) {
            x2 = x2 + 20;
            this.ctx.lineTo(x2, y2);
            this.ctx.fillText(text, x2 + 20, y2);
        } else {
            x2 = x2 - 20;
            this.ctx.lineTo(x2, y2);
            this.ctx.fillText(text, x2 - 20, y2);
        }
        if (this.ctx.isPointInPath(this.pos.x, this.pos.y)) {
            alert('line in path');
        } 
        this.ctx.stroke();
        // this.ctx.beginPath();
        this.ctx.restore();
    };

    //绘制文本
    CirclePie.prototype.drawText = function (x, y, color, fontSize, text) {
        this.ctx.save();
        this.ctx.fillStyle = color;
        this.ctx.textBaseline = "middle";
        this.ctx.textAlign = "center";
        this.ctx.font = `${fontSize}px Microsoft Yahei`;
        if (this.ctx.isPointInPath(this.pos.x, this.pos.y)) {
            alert('text in path');
        } 
        this.ctx.fillText(text, x, y);
        this.ctx.restore();
    };
    CirclePie.prototype.clearLable = function () {
        this.ctx.clearRect(-50, -25, 100, 100);
    };

    CirclePie.prototype.clear = function () {
        this.ctx.clearRect(
            -this.width / 2,
            -this.height / 2,
            this.width,
            this.height
        );
    };

    CirclePie.prototype.draw = function (options, offsetX, offsetY, index) {
        //清空上一次绘制
        this.clear();
        var data = options.data;
        var sum = 0;
        var map = {};
        //求总数
        data.forEach((element, index) => {
            sum += element.value;
            map[element.name] = element.value;
        });

        let i = 0;
        let startAngle = (Math.PI / 180) * 0;
        let endAngle = 0;
        let offset = 0;

        // this.ctx.beginPath();


        for (let key in map) {
            offset = (((map[key] / sum) * Math.PI) / 180) * 360;
            endAngle = startAngle + offset;
            //画扇区
            this.ring(0, 0, this.r, startAngle, endAngle, options.colors[i]);
            
            //话导引线
            this.drawGuidLine(0, 0, this.r, startAngle, endAngle, options.colors[i], key);

            startAngle = endAngle;
            i++;
        }
        this.drawText(0, -15, "rgba(255, 255, 255, 1)", 12, options.name);
        this.drawText(0, 15, "rgba(255, 255, 255, 1)", 30, sum);
    };

    CirclePie.prototype.render = function (options) {

        this.options = options;

        //中间名称
        this.name = options.name;
        //颜色值
        this.colors = options.colors;
        //初始化数据
        if (!this.data) {
            this.data = JSON.parse(JSON.stringify(options.data));
            this.data.forEach((item, index) => {
                item.value = 0;
            });
        }

        let _this = this;

        this.timer = window.requestAnimationFrame(function () {
            let data = [];
            let j = 0;
            for (let i = 0; i < _this.data.length; i++) {
                if (_this.data[i].value > options.data[i].value) {
                    _this.data[i].value -= 1;
                } else if (_this.data[i].value == options.data[i].value) {
                    j++;
                    if (j >= _this.data.length) {
                        window.cancelAnimationFrame(_this.timer);
                        return;
                    }
                } else {
                    _this.data[i].value += 1;
                }
            }
            _this.draw({
                name: options.name,
                colors: options.colors,
                data: _this.data
            });
            _this.render(options);
        });
    };

    window.onload = function () {

        var colors = ["rgba(239, 58, 113, 1)", "rgba(95, 222, 145, 1)"];
        var circlePie = new CirclePie({
            x: 0,
            y: 0,
            r: 50
        });
        circlePie.init(document.getElementsByClassName('pie')[0]);
        circlePie.render({
            name: "名称",
            colors: [
                "red",
                "green",
                "skyblue"
            ],
            data: [
                {
                    name: "扇区1",
                    value: 20//Math.floor(Math.random() * 100)
                },
                {
                    name: "扇区2",
                    value: 30//Math.floor(Math.random() * 100)
                },
                {
                    name: "扇区3",
                    value: 50//Math.floor(Math.random() * 100)
                }
            ]
        });
    }
</script>

</html>
上一篇下一篇

猜你喜欢

热点阅读