自动生成chart源码,js的canvas绘制chart

  • • 发表于 8年前
  • • 作者 leo
  • • 3938 人浏览
  • • 13 条评论
  • • 最后编辑时间 8年前
  • • 来自 [开发工具]

原创声明:本文为作者原创,未经允许不得转载,经授权转载需注明作者和出处

先看看效果图

通过可视化工具自动生成chart。
目前工具新增了2D曲线图、2D柱状图、2D饼状图组件。只要将组件拖拽到画布上即可。最好是将组件放到view中,方便布局。
以下是自动生成的代码截图:

<view style="display:block;padding:1px;">
    <canvas canvas-id="WColumnChart1" class="WColumnChart1" style="">
    </canvas>
</view>      
<view style="display:block;padding:1px;">
    <canvas canvas-id="WLineChart1" class="WLineChart1" style="">
    </canvas>
</view>  
<view style="display:block;padding:1px;">
    <canvas canvas-id="WPieChart1" class="WPieChart1" style="">
    </canvas>
</view>
.WColumnChart1{
 width:300px;  
 height:200px;  

}
.WLineChart1{
 width:300px;  
 height:200px;  

}
.WPieChart1{
 width:300px;  
 height:200px;  

}
Page({
    onReady:function(e){
        var context = wx.createContext();
        //柱状图
        var WColumnChart1Series = [
                [100,110,120,130,140,120,105],
                [90,100,110,120,130,120,150],
        ];
        drawColumn(context,"WColumnChart1",WColumnChart1Series,300,200);
        //曲线
        var WLineChart1Series = {values:[
            { value0:[
                {x:"1",y:5},
                {x:"2",y:5},
                {x:"3",y:80},
                {x:"4",y:10},
                {x:"5",y:30},
                {x:"6",y:30},
                {x:"7",y:60},
                {x:"8",y:10}
                ]},
            { value1:[
                    {x:"1",y:50},
                    {x:"2",y:40},
                    {x:"3",y:60},
                    {x:"4",y:10},
                    {x:"5",y:20},
                    {x:"6",y:10},
                    {x:"7",y:40},
                    {x:"8",y:10}
                ]}
        ]};
        drawLine(context,"WLineChart1",WLineChart1Series,300,200)
        //饼图
        var WPieChart1Series = [{ value:20, color:"#f4b300" },{ value:30, color:"#78ba00" },{ value:40, color:"#2673ec" }]  
        drawPie(context,"WPieChart1",WPieChart1Series,300,200);
    }
})
function drawColumn(context,canvasid,series,width,height){
    //初始化数据。二维数组,第一维:系列。第二维数值。每个系列数组长度必须一样
    var config = {  
        width :width,    //canvas宽度
        height: height,  //canvas高度
        cMargin : 0,//canvas的内边距
        series: series,  //数组
        canvas:   context,  //绘图上下文context
        };  
    ColumnChart.initSettings(config);  //初始化
    ColumnChart.render();  //绘制
        wx.drawCanvas({
        canvasId: canvasid,
        actions: context.getActions()
    });
}
function drawLine(context,canvasid,series,width,height){
    //必须按照这个格式定义数据,关键字values value0 value1 ...... 

    var config = {  
        width : width,
        height: height,
        isBg:true,//是否绘制背景线
        context:context,  //绘图上下文context
        series:series,
        colors:["#006ac1","#001e4e","#008287","#008287","#004d60","#199900"],
        dotColor:"#f4b300",
    };  
    LineChart.initSettings(config);  //初始化
    LineChart.render();  //绘制
    wx.drawCanvas({
        canvasId: canvasid,
        actions: context.getActions()
    });
}
function drawPie(context,canvasid,series,width,height){
    //通过程序替换seriesData为自己的数据

    var config = {  
            width :width,    //canvas宽度
            height: height,  //canvas高度
            series: series,  
            canvas:   context,  //绘图上下文context

    };  
    pieChart.initSettings(config);  //初始化
    pieChart.render();  //绘制
    wx.drawCanvas({
            canvasId: canvasid,
            actions: context.getActions()
        });
}
var ColumnChart = {  
    cwidth: 300,  //canvas宽度
    cheight: 200,  //canvas高度
    cMargin:0,//canvas的内边距
    cMarginSpace:0,
    cMarginHeight:0,
    bWidth:0,
    bCoverWidth:0,//计算得来的column的单位宽度。按照一个x节点宽度是间距2倍处理的。
    totalSeries:0,
    totalcolumns:0,//多个series的话,需要计算总共的column数量
    maxDataValue:0,
    bWidthMargin:0,
    ctr:0,
    numctr:0,
    speed:0,
    totLabelsOnYAxis:0,
    series: [],  //序列数组
    chartCanvas: null,  
    colors:[],
    edge : {  
        width: 0,  
        height: 0  
    },  
    initSettings: function (config) {  
        this.chartCanvas = config.canvas;  
        this.cwidth = config.width;  
        this.cheight = config.height;  
        this.series = config.series;
        this.colors=["#f4b300","#78ba00","#2673ec","#008287","#004d60","#56c5ff"];  //可以这里改为自己的色值
        // chart properties
        this.cMargin = config.cMargin;
        this.cheight = this.cheight - 2 * this.cMargin;
        this.cwidth = this.cwidth - 2 * this.cMargin;
        this.cMarginSpace = this.cMargin ;
        this.cMarginHeight = this.cMargin + this.cheight;
        // column properties
        this.totalSeries = this.series.length;
        this.totalcolumns=0;
        for (var i = 0; i < this.totalSeries; i++) {
            for (var j = 0; j < this.series[i].length; j++) {
                this.totalcolumns++;
            }
        }
        //设置一个x点column的width是间距的的2倍
        this.bCoverWidth = this.cwidth/3/(this.totalcolumns/this.totalSeries);
        //每个column的宽度
        this.bWidth = 2*this.bCoverWidth / this.totalSeries;
        // find maximum value to plot on chart
        this.maxDataValue = 0;
        for (var i = 0; i < this.totalSeries; i++) {
            for (var j = 0; j < this.series[i].length; j++) {
                var Val = this.series[i][j];
                var columnVal = parseInt(Val);
                if (parseInt(columnVal) > parseInt(this.maxDataValue))
                    this.maxDataValue = columnVal;
            }

        }
        this.totLabelsOnYAxis = 10;
    },  
    render : function() {  
        this.drawAxisLabelMarkers();
        this.drawChart();
    },  
    drawAxisLabelMarkers:function() {
        this.chartCanvas.lineWidth = "2.0";
        // draw y axis
        this.drawAxis(this.cMarginSpace, this.cMarginHeight, this.cMarginSpace, this.cMargin);
        // draw x axis
        this.drawAxis(this.cMarginSpace, this.cMarginHeight, this.cMarginSpace + this.cwidth, this.cMarginHeight);
        this.chartCanvas.lineWidth = "1.0";
        //this.drawMarkers();//此处屏蔽显示数值,因为显示的文字不好定位。
    },
    drawAxis:function(x, y, X, Y) {
        this.chartCanvas.beginPath();
        this.chartCanvas.moveTo(x, y);
        this.chartCanvas.lineTo(X, Y);
        this.chartCanvas.closePath();
        this.chartCanvas.setStrokeStyle("#e8e8e8");
        this.chartCanvas.stroke();
        this.chartCanvas.save();
    },
    drawMarkers:function() {//此方法暂不调用,因为显示的文字不好定位。
        var numMarkers = parseInt(this.maxDataValue / this.totLabelsOnYAxis);
        numMarkers= (numMarkers / 10) % 10;
        numMarkers = Math.ceil(numMarkers)*10;
        this.chartCanvas.textAlign = "right";
        this.chartCanvas.fillStyle = "#000";
        // Y Axis
        for (var i = 0; i <= this.totLabelsOnYAxis; i++) {
            var markerVal = i * numMarkers;
            var markerValHt = i * numMarkers * this.cheight;
            var xMarkers = this.cMarginSpace - 20;
            var yMarkers = this.cMarginHeight - (markerValHt / this.maxDataValue);
            this.chartCanvas.setFontSize(14);
            this.chartCanvas.fillText(markerVal, xMarkers, yMarkers);
        }
        // X Axis
        this.chartCanvas.textAlign = 'center';
        for (var i = 0; i < this.totalSeries; i++) {
                var arrval = this.series[i].split(",");
                name = arrval[0];
                var markerXPos = this.cMarginSpace + this.bMargin 
                            + (i * (this.bWidth + this.bMargin)) + (this.bWidth/2)-15;
                var markerYPos = this.cMarginHeight + 10;
                this.chartCanvas.setFontSize(14);
                this.chartCanvas.fillText(name, markerXPos, markerYPos, this.bWidth);

        }
        this.chartCanvas.save();
        // Add Y Axis title
        this.chartCanvas.translate(this.cMargin + 10, this.cheight / 2);
        this.chartCanvas.rotate(Math.PI * -90 / 180);
        this.chartCanvas.fillText('Visitors in Thousands', 0, 0);
        this.chartCanvas.restore();
        // Add X Axis Title
        this.chartCanvas.fillText('Year Wise', this.cMarginSpace + 
                    (this.cwidth / 2), this.cMarginHeight + 30 );
    },
    drawChart:function() {
        // Loop through the total columns and draw
        var prewidth=this.bCoverWidth;
        //绘制每个系列的每个数值对应的column
        for (var i = 0; i < this.totalSeries; i++) {
            for (var j = 0; j < this.series[i].length; j++) {
               var Val = this.series[i][j];
               var bVal = parseInt(Val);
               var bHt = (bVal * this.cheight / this.maxDataValue) ;
               var bX = i * this.bWidth +  this.bCoverWidth+j*3*this.bCoverWidth;
               var bY = this.cMarginHeight - bHt - 2;
               this.drawRectangle(i,bX, bY, this.bWidth, bHt, true);
            }
        }

    },
    drawRectangle:function(i,x, y, w, h, fill) {
        this.chartCanvas.beginPath();          
        this.chartCanvas.rect(x, y, w, h);          
        this.chartCanvas.closePath();
        this.chartCanvas.setStrokeStyle("#ffffff");
        this.chartCanvas.stroke();
        if (fill) {

            this.chartCanvas.setFillStyle(this.colors[i]);

            this.chartCanvas.fill();
        }
    }

};  

var LineChart={
    keynames:[],//数据信息数组
    series: [],  //序列数组
    ctx:null,
    width:300,
    height:300,
    colors:[],

    dotColor:"#78ba00",
    isBg:false,//是否绘制背景线
    initSettings:function(config){
        this.width=config.width;
        this.height=config.height;
        this.colors=config.colors; 
        this.dotColor = config.dotColor;
        this.ctx = config.context;
        this.isBg = config.isBg;

        this.series = config.series;
        this.isMultiData();
    },
   isMultiData:function(){
      if(this.series.values.length>1){
        this.isMultiData = true;
       }
    },//是否是多条数据线
    render : function() {  
        this.drawXY(this.series,0,0);
    }, 
    drawXY:function(data,key,padding){

        this.ctx.beginPath();
        this.ctx.moveTo(padding,0)
        this.ctx.lineTo(padding,this.height-padding);
        this.ctx.lineTo(this.width,this.height-padding);
        this.ctx.stroke();
        var perwidth = this.getPixel(data,key,this.width,padding);//x 轴每一个数据占据的宽度
        var maxY =  this.getMax(data,0,this.isMultiData);//获得Y轴上的最大值
        var yPixel = this.getYPixel(maxY,this.height,padding).pixel;
        var ycount = this.getYPixel(maxY,this.height,padding).ycount;
        for( var i=0,ptindex;i< data.values[key]["value"+key].length;i++ ){
            ptindex = i+1;
            var x_x = this.getCoordX(padding,perwidth,ptindex);
            var x_y = this.height-padding+20;
            this.ctx.fillText(data.values[key]["value"+key][i].x,x_x,x_y,perwidth);
        }

        //for(var i=0;i< ycount/10;i++){
        //    this.ctx.fillText(i*10,padding-10,(ycount/10-i)*10*yPixel,perwidth);
        //}
        if(this.isBg){
            var x =  padding;
            this.ctx.lineWidth=1;
            this.ctx.setStrokeStyle("#e8e8e8");

            for( var i=0;i< ycount/10;i++ ){
                var y = (ycount/10-i)*10*yPixel;
                this.ctx.moveTo(x,y);
                this.ctx.lineTo(this.width,y);
                this.ctx.stroke();
            }
        }//选择绘制背景线
        this.ctx.closePath();
        this.drawData(data,0,padding,perwidth,yPixel,this.isMultiData);
    },//绘制XY坐标 线 以及点

    drawData:function(data,key,padding,perwidth,yPixel,isMultiData){
        if(!isMultiData){
            var keystr = "value"+key;
            this.ctx.beginPath();
            this.ctx.setLineWidth(2);
            this.ctx.setStrokeStyle(this.colors[key]);
            var startX = this.getCoordX(padding,perwidth,0);
            var startY = this.getCoordY(padding,yPixel,data.values[key][keystr][0].y);
            this.ctx.beginPath();
            this.ctx.setLineWidth(2);
            for( var i=0;i< data.values[key][keystr].length;i++ ){
                var x = this.getCoordX(padding,perwidth,i+1);
                var y = this.getCoordY(padding,yPixel,data.values[key][keystr][i].y);
                this.ctx.lineTo(x,y);
            }
            this.ctx.stroke();
            this.ctx.closePath();
            /*下面绘制数据线上的点*/
            this.ctx.beginPath();
            this.ctx.setFillStyle(this.dotColor);

            for( var i=0;i< data.values[key][keystr].length;i++ ){
                var x = this.getCoordX(padding,perwidth,i+1);
                var y = this.getCoordY(padding,yPixel,data.values[key][keystr][i].y);
                this.ctx.moveTo(x,y);
                this.ctx.arc(x,y,3,0,Math.PI*2,true);//绘制数据线上的点
                this.ctx.fill();
            }
            this.ctx.closePath();
        }else{//如果是多条数据线
            for( var i=0;i< data.values.length;i++ ){

                LineChart.drawData(data,i,padding,perwidth,yPixel,false);
                //LineChart.drawKey(color,this.keynames[i],padding,i);
            }
        }
    },//绘制数据线和数据点
    getPixel:function(data,key,width,padding){
        var count = data.values[key]["value"+key].length;
        return (width-20-padding)/(count+(count-1)*1.5);    
    },//宽度
    getCoordX:function(padding,perwidth,ptindex){//下标从1开始 不是从0开始
        return 2.5*perwidth*ptindex+padding+10-2*perwidth;
    },//横坐标X 随ptindex 获得
    getCoordY:function(padding,yPixel,value){
        var y = yPixel*value;
        return this.height-padding-y;
    },//纵坐标X 随ptindex 获得(注意 纵坐标的算法是倒着的因为原点在最上面)
        getYPixel:function(maxY,height,padding){
        var ycount = (parseInt(maxY/10)+1)*10+10;//y轴最大值
        return {pixel:(height-padding)/ycount,ycount:ycount};
    },//y轴的单位长度

    getMax:function(data,key,isMultiData){
        if(!isMultiData){
            var maxY = data.values[key]["value"+key][0].y;
            var length = data.values[key]["value"+key].length;
            var keystr = "value"+key;
            for( var i=1;i< length;i++ ){
                if(maxY< data.values[key][keystr][i].y)
                    maxY=data.values[key][keystr][i].y;
            }
            return maxY;//返回最大值 如果不是多数据
        }else{
            var maxarr=[];
            var count = data.values.length;//多条数据的数据长度
            for(var i=0;i< count;i++){
                maxarr.push(LineChart.getMax(data,i,false));
            }
            var maxvalue = maxarr[0];
            for(var i=1;i< maxarr.length;i++){
                maxvalue = (maxvalue< maxarr[i])?maxarr[i]:maxvalue; 
            }
            return maxvalue;
        }//如果是多数据
    },

    setKey:function(keynames){//keynames 是数组
        for(var i=0;i< keynames.length;i++){
            this.keynames.push(keynames[i]);//存入数组中
        }
    },

     drawKey:function(color,keyname,padding,lineindex){
        var x = padding+10;
        var y = this.height - padding+20+13*(lineindex+1);
        this.ctx.beginPath();
        this.ctx.strokeStyle = color;
        this.ctx.font="10px";
        this.ctx.moveTo(x,y);
        this.ctx.lineTo(x+50,y);
        this.ctx.fillText(":"+keyname,x+80,y,30);
        this.ctx.stroke();
        this.ctx.closePath();
    }    
}



var pieChart = {  
    width: 200,  
    height: 200,  
    series: [],  
    chartCanvas: null,  
    edge : {  
        width: 0,  
        height: 0  
    },  
    circle : {  
        cx: 0,  
        cy: 0,  
        radius: 0  
    },  
    initSettings: function (config) {  
        this.chartCanvas = config.canvas;  
        this.width = config.width;  
        this.height = config.height;  
        this.series = config.series;  

    },  
    render : function() {  
        // initialization circle  
        this.circle.cx = this.width/2;  
        this.circle.cy = this.height/2;  
        this.circle.radius = Math.min(this.width/2, this.height/2) - Math.max(this.edge.width, this.edge.height);  
        var ctx = null;  
        ctx = this.chartCanvas;  
        if(this.circle.radius <= 0) {  
            return;  
        }  
        // draw each arc according to data series   
        var sum = 0;  
        var nums = this.series.length;  
        for(var i=0; i<nums; i++) {  
            sum += this.series[i].value;  
        }  
        var deltaArc = 0;  
        for(var i=0; i<nums; i++) {  
            var precent = this.series[i].value/sum;  
            this.renderPie(ctx, i, precent, deltaArc);  
            deltaArc += 2*Math.PI * precent;  
        }  
        ctx.save();  
    },  
    renderPie : function(ctx, index, precent, deltaArc) {  
        var endAngle = 2*Math.PI*precent;  
        ctx.beginPath();  
        ctx.arc(this.circle.cx+0.5, this.circle.cy+0.5, this.circle.radius, deltaArc, endAngle, false);  
        ctx.setStrokeStyle("#ffffff");
        ctx.setLineWidth(2);
        ctx.lineTo(this.circle.cx, this.circle.cy);  
        ctx.setFillStyle(this.series[index].color);
        ctx.fill();  
        ctx.stroke()
        ctx.closePath();  
        ctx.moveTo(this.circle.cx, this.circle.cy);  
        ctx.lineTo(this.circle.cx + this.circle.radius * Math.cos(deltaArc), this.circle.cy + this.circle.radius * Math.sin(deltaArc));
        ctx.stroke()  
    },  
};
分享到:
13条评论
Ctrl+Enter
作者

leo

leo

APP:0 帖子:16 回复:8 积分:516

已加入社区[2922]天

工欲善其事必先利其器-小程序UI工具

作者详情》
Top