Canvas繪制帶標(biāo)簽導(dǎo)引線的環(huán)形圖

運(yùn)行效果

line-pie.png

實(shí)例代碼

<!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;
        //初始鼠標(biāo)位置
        this.pos = {
            x: 0,
            y: 0
        };
    }

    CirclePie.prototype.init = function (dom) {
        if (!dom) {
            console.log('canvas掛載節(jié)點(diǎn)不存在');
            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節(jié)點(diǎn)
        dom.appendChild(canvas);
        //定點(diǎn)圓心
        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";
        //默認(rèn)字體大小
        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 = {};
        //求總數(shù)
        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;
            //畫扇區(qū)
            this.ring(0, 0, this.r, startAngle, endAngle, options.colors[i]);
            
            //話導(dǎo)引線
            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;
        //初始化數(shù)據(jù)
        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: "扇區(qū)1",
                    value: 20//Math.floor(Math.random() * 100)
                },
                {
                    name: "扇區(qū)2",
                    value: 30//Math.floor(Math.random() * 100)
                },
                {
                    name: "扇區(qū)3",
                    value: 50//Math.floor(Math.random() * 100)
                }
            ]
        });
    }
</script>

</html>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捉貌,一起剝皮案震驚了整個(gè)濱河市僵芹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡晓勇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門灌旧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绑咱,“玉大人,你說我怎么就攤上這事枢泰∶枞冢” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵宗苍,是天一觀的道長。 經(jīng)常有香客問我薄榛,道長讳窟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任敞恋,我火速辦了婚禮丽啡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘硬猫。我一直安慰自己补箍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布啸蜜。 她就那樣靜靜地躺著坑雅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衬横。 梳的紋絲不亂的頭發(fā)上裹粤,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音蜂林,去河邊找鬼遥诉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛噪叙,可吹牛的內(nèi)容都是我干的矮锈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼睁蕾,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼苞笨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤猫缭,失蹤者是張志新(化名)和其女友劉穎葱弟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猜丹,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芝加,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了射窒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藏杖。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖脉顿,靈堂內(nèi)的尸體忽然破棺而出蝌麸,到底是詐尸還是另有隱情,我是刑警寧澤艾疟,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布来吩,位于F島的核電站,受9級(jí)特大地震影響蔽莱,放射性物質(zhì)發(fā)生泄漏弟疆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一盗冷、第九天 我趴在偏房一處隱蔽的房頂上張望怠苔。 院中可真熱鬧,春花似錦仪糖、人聲如沸柑司。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攒驰。三九已至,卻和暖如春故爵,著一層夾襖步出監(jiān)牢的瞬間讼育,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工稠集, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奶段,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓剥纷,卻偏偏與公主長得像痹籍,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晦鞋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容