canvas 简易图表插件

function Chart_line(e) {
    this.ele = e;
    this.cav = document.createElement('canvas'); //创建canvas实例
    this.ctx = this.cav.getContext('2d'); //创建画笔
    this.axis = {}; //定义轴
    this.data = [];
    this.canStyle = {}; //定义画布样式
    this.config = { //定义内部元素样式
        axis: { //定义轴样式
            width : 2, //轴宽度
            color: '#00A6FF', //轴颜色
            textColor: '#fff',//轴文字颜色
            textSize: '12px', //轴文字大小
        },
        data: { //定义数据样式
            lineColor: '#00A6FF',// 数据曲线颜色
            lineWidth: 2, //曲线宽度
            lineCap: 'round', //定义线端样式
            lineDash: [10, 5], //自定义曲线线形
            arcColor: '#fff', //数据点颜色
            arcSize: 3,//数据点大小
            hoverArcColor: '#f00', //鼠标移入数据点样式
            hoverArcSize: 8, //鼠标移入数据点大小
        },
        tips: {
            tipsWidth: 100, //提示框宽度
            tipsHeight: 50,//提示框高度
            backgroundColor: 'rgba(0,0,255,.3)', // 信息提示框背景颜色
            tipsTitleBefore: '', //标题文字前缀
            tipsTitleAfter: '', //标题文字后缀
            tipsDataBefore: '', //数据文字前缀
            tipsDataAfter: '', //数据文字后缀
        }
    }
    this.readConfig = (obj)=>{
        for (key in obj) {
            if (this.config[key]) {
                for (j in obj[key]) {
                    if (this.config[key][j]==''|| this.config[key][j]) {
                        this.config[key][j] = obj[key][j];
                    }
                }
            }
        }
    }
    this.init = () => { //初始化样式函数
        this.ele.style.position = 'relative';
        // this.ele.style.cursor = 'none';
        if (!('width' in this.canStyle) || this.canStyle['width']!=this.ele.offsetWidth) {
            this.canStyle['width'] = this.ele.offsetWidth + 'px';
            this.cav.setAttribute('width',this.ele.offsetWidth + 'px');
        } else {
            this.cav.setAttribute('width',this.canStyle.width);
        }
        if (!('height' in this.canStyle)|| this.canStyle['height']!=this.ele.offsetHeight) {
            this.canStyle['height'] = this.ele.offsetHeight + 'px';
            this.cav.setAttribute('height',this.ele.offsetHeight + 'px');
        } else {
            this.cav.setAttribute('height',this.canStyle.height);
        }
        for (let key in this.canStyle) {
            this.cav.style[key] = this.canStyle[key];
        }
    }
    this.createAxis = (x1, y1, x2, y2) => { //绘制轴
        this.ctx.save();
        let lin = this.ctx.createLinearGradient(x1, y1, x2, y2);
        lin.addColorStop(0, this.config.axis.color);
        // lin.addColorStop(1, '#00A6FF');
        this.ctx.lineWidth = this.config.axis.width;
        this.ctx.strokeStyle = lin;
        this.ctx.beginPath();
        this.ctx.moveTo(x1, y1);
        this.ctx.lineTo(x2, y2);
        this.ctx.closePath();
        this.ctx.stroke();
        this.ctx.restore();
    }
    this.createText = (text, x, y) => {//绘制文字
        this.ctx.save();
        this.ctx.moveTo(x,y);
        this.ctx.fillStyle = this.config.axis.textColor;
        this.ctx.font = this.config.axis.textSize+'"微软雅黑"';
        this.ctx.textBaseline = 'bottom';
        this.ctx.textAlign = 'left';
        this.ctx.fillText(text, x, y);
        this.ctx.closePath();
        this.ctx.restore();
    }
    this.SerializationData = (data) => {//数据处理
        if (Object.prototype.toString.call(data[0]) == '[object Object]') {
            for (i = 0; i < this.data.length; i++){
                data[i] = data[i].num;
            }
        }
        for (i = 0; i < this.data.length; i++){
            let num = data[i];
            data[i] = {}
            data[i].x = ((parseInt(this.canStyle.width)-40) / this.axis.axisX.length * i) + 55;
            data[i].y = parseInt(this.canStyle.height) - 20 - ((parseInt(this.canStyle.height)-20) / (this.axis.axisY[0] * (this.axis.axisY[1]+1)) * num);
            data[i].num = num;
        }
        return data;
    }
    this.createArc = (x, y) => {//绘制数据点
        this.ctx.save();
        this.ctx.beginPath();
        this.ctx.fillStyle = this.config.data.arcColor;
        this.ctx.arc(x, y, this.config.data.arcSize, 0, Math.PI * 2, false);
        this.ctx.closePath();
        this.ctx.fill();
        this.ctx.restore();
    }
    this.gradient = (a, b) => (b.y - a.y) / (b.x - a.x);//计算两点间斜率
    this.createData = (data) => {//绘制数据曲线
        if (data.length < 3) {
            let f = 0.6;
            let t = 0.1;
            let g = 0;
            let dx1 = 0;
            let dy1 = 0;
            let dx2 = 0;
            let dy2 = 0;
            let predata = data[0];
            let nextdata = null;
            this.ctx.save()
            this.ctx.strokeStyle = this.config.data.lineColor;
            this.ctx.lineWidth = this.config.data.lineWidth;
            this.ctx.lineCap = this.config.data.lineCap;
            this.ctx.lineJoin = this.config.data.lineCap;
            this.ctx.setLineDash(this.config.data.lineDash);
            this.ctx.beginPath();
            this.ctx.moveTo(data[0].x, data[0].y);
            for (i = 1; i < data.length; i++){
                let curdata = data[i];
                nextdata = curdata;
                if (nextdata) {
                    g = this.gradient(predata, nextdata);
                    dx2 = (nextdata.x - curdata.x) * (-f);
                    dy2 = dx2 * g * t;
                } else {
                    dx2 = 0;
                    dy2 = 0;
                }
                this.ctx.bezierCurveTo(predata.x - dx1, predata.y - dy1, curdata.x + dx2, curdata.y + dy2, curdata.x, curdata.y)
                dx1 = dx2;
                dy1 = dy2;
                predata = curdata;
            }
            this.ctx.stroke();
            this.ctx.closePath();
            this.ctx.restore()
        } else {
            let f = 0.6;
            let t = 0.1;
            let g = 0;
            let dx1 = 0;
            let dy1 = 0;
            let dx2 = 0;
            let dy2 = 0;
            let predata = data[0];
            let nextdata = null;
            this.ctx.save()
            this.ctx.strokeStyle = this.config.data.lineColor;
            this.ctx.lineWidth = this.config.data.lineWidth;
            this.ctx.lineCap = this.config.data.lineCap;
            this.ctx.lineJoin = this.config.data.lineCap;
            this.ctx.setLineDash(this.config.data.lineDash);
            this.ctx.beginPath();
            this.ctx.moveTo(data[0].x, data[0].y);
            for (i = 1; i < data.length; i++){
                let curdata = data[i];
                nextdata = data[i + 1];
                if (nextdata) {
                    g = this.gradient(predata, nextdata);
                    dx2 = (nextdata.x - curdata.x) * (-f);
                    dy2 = dx2 * g * t;
                } else {
                    dx2 = 0;
                    dy2 = 0;
                }
                this.ctx.bezierCurveTo(predata.x - dx1, predata.y - dy1, curdata.x + dx2, curdata.y + dy2, curdata.x, curdata.y)
                dx1 = dx2;
                dy1 = dy2;
                predata = curdata;
            }
            this.ctx.stroke();
            this.ctx.closePath();
            this.ctx.restore()
        }
    }
    this.createOnArc = (x, y) => { //设置鼠标移入数据点
        this.ctx.save();
        this.ctx.beginPath();
        this.ctx.fillStyle = this.config.data.hoverArcColor;
        this.ctx.arc(x, y, this.config.data.hoverArcSize, 0, Math.PI * 2, false);
        this.ctx.closePath();
        this.ctx.fill();
        this.ctx.restore();
    }
    // 绘制圆角矩形
    this.Point = (x, y) => {
        return {x:x, y:y};
    };
    this.Rect = (x, y, w, h) => {
        return {x:x, y:y, width:w, height:h};
    }
    this.drawRoundedRect = (rect, r, ctx) => {
        var ptA = this.Point(rect.x + r, rect.y);
        var ptB = this.Point(rect.x + rect.width, rect.y);
        var ptC = this.Point(rect.x + rect.width, rect.y + rect.height);
        var ptD = this.Point(rect.x, rect.y + rect.height);
        var ptE = this.Point(rect.x, rect.y);
        ctx.beginPath();
        ctx.fillStyle = this.config.tips.backgroundColor;
        ctx.moveTo(ptA.x, ptA.y);
        ctx.arcTo(ptB.x, ptB.y, ptC.x, ptC.y, r);
        ctx.arcTo(ptC.x, ptC.y, ptD.x, ptD.y, r);
        ctx.arcTo(ptD.x, ptD.y, ptE.x, ptE.y, r);
        ctx.arcTo(ptE.x, ptE.y, ptA.x, ptA.y, r);
        ctx.closePath();
        ctx.fill();
        ctx.restore();
        // ctx.stroke();
    }
    this.createRect = (x,y) => {
        let rectwidth = this.config.tips.tipsWidth;
        let rectheight = this.config.tips.tipsHeight;
        let rectx = x - rectwidth / 2; // 使圆角矩形水平居中与数据点
        rectx < 40 ? rectx = 40 : rectx;
        let recty = y - rectheight - 10; // 使圆角矩形垂直位置居于数据点上方10像素;
        if (recty < 0) {
            recty = 20;
            rectx = x - rectwidth-10;
            rectx < 0 ? rectx = x + 10 : rectx;
        }
        let rect = this.Rect(rectx, recty, rectwidth, rectheight);
        this.drawRoundedRect(rect, 10, this.ctx);
        this.createText(this.config.tips.tipsTitleBefore+this.axis.axisX[i]+this.config.tips.tipsTitleAfter, rectx+10, recty + 20);//添加标题文字
        this.createText(this.config.tips.tipsDataBefore+this.data[i].num+this.config.tips.tipsDataAfter, rectx+10, recty + 45); //添加数据文字
    }
    this.mouseMove = (e) => { // 鼠标移动事件监听
        this.render();
        for (i = 0; i < this.data.length; i++){
            if (i == 0 || i == this.data.length - 1) {
                if (Math.abs(this.data[i].x - e.x) < 3 &&  Math.abs(this.data[i].y - e.y) < 3) {
                    this.createOnArc(this.data[i].x, this.data[i].y);
                    this.createRect(this.data[i].x, this.data[i].y,i);
                    break;
                }
            } else {
                if (Math.abs(this.data[i].x - e.x) < 3 &&  Math.abs(this.data[i].y - e.y) < 3) {
                    this.createOnArc(this.data[i].x, this.data[i].y);
                    this.createRect(this.data[i].x, this.data[i].y,i);
                    break;
                }
            }
        }
    }
    // 绘画
    this.render = () => {
        this.init(); //初始化样式
        window.addEventListener('resize', this.render, false); //监听窗口大小变化
        this.ele.appendChild(this.cav); //添加画布到窗口
        this.createAxis(40, parseInt(this.canStyle.height) - 20, parseInt(this.canStyle.width) - 20, parseInt(this.canStyle.height) - 20);  //绘制x轴
        this.createAxis(40, parseInt(this.canStyle.height) - 20, 40, 20);  //绘制y轴
        this.axis.axisX.forEach((v, i) => {//绘制x轴文字
            this.createText(v, ((parseInt(this.canStyle.width)-40) / this.axis.axisX.length * i) + 40, parseInt(this.canStyle.height) - 2);
        });
        for (let i = 0; i < this.axis.axisY[1]+1; i++){//绘制y轴文字
            this.createText(this.axis.axisY[0]*i, 5, parseInt(this.canStyle.height)-20-((parseInt(this.canStyle.height)-20)/(this.axis.axisY[1]+1))*i);
        }
        this.data = this.SerializationData(this.data);//数据处理
        this.createData(this.data); //绘制数据曲线 
        this.data.forEach((v,i) => { //绘制数据点
            // console.log(v, i);
            if (i == 0 || i == this.data.length-1) {
                this.createArc(v.x, v.y);
            } else {
                this.createArc(v.x, v.y);
            }
        })
        this.ele.addEventListener('mousemove', this.mouseMove, false);//监听鼠标移动事件对数据进行处理
    }
}

  

posted @ 2021-08-19 08:18  尘猿  阅读(137)  评论(0)    收藏  举报