首页 > 代码库 > 移动端图表插件jChart.js的修改

移动端图表插件jChart.js的修改

注:

1.我自己都忘了改哪里,反正是改了一些bug

2.原项目地址是https://github.com/shixy/JChart

window.JingleChart = JChart = {    version : ‘0.1‘,    animationOptions : {        linear : function (t){            return t;        },        easeInQuad: function (t) {            return t*t;        },        easeOutQuad: function (t) {            return -1 *t*(t-2);        },        easeInOutQuad: function (t) {            if ((t/=1/2) < 1) return 1/2*t*t;            return -1/2 * ((--t)*(t-2) - 1);        },        easeInCubic: function (t) {            return t*t*t;        },        easeOutCubic: function (t) {            return 1*((t=t/1-1)*t*t + 1);        },        easeInOutCubic: function (t) {            if ((t/=1/2) < 1) return 1/2*t*t*t;            return 1/2*((t-=2)*t*t + 2);        },        easeInQuart: function (t) {            return t*t*t*t;        },        easeOutQuart: function (t) {            return -1 * ((t=t/1-1)*t*t*t - 1);        },        easeInOutQuart: function (t) {            if ((t/=1/2) < 1) return 1/2*t*t*t*t;            return -1/2 * ((t-=2)*t*t*t - 2);        },        easeInQuint: function (t) {            return 1*(t/=1)*t*t*t*t;        },        easeOutQuint: function (t) {            return 1*((t=t/1-1)*t*t*t*t + 1);        },        easeInOutQuint: function (t) {            if ((t/=1/2) < 1) return 1/2*t*t*t*t*t;            return 1/2*((t-=2)*t*t*t*t + 2);        },        easeInSine: function (t) {            return -1 * Math.cos(t/1 * (Math.PI/2)) + 1;        },        easeOutSine: function (t) {            return 1 * Math.sin(t/1 * (Math.PI/2));        },        easeInOutSine: function (t) {            return -1/2 * (Math.cos(Math.PI*t/1) - 1);        },        easeInExpo: function (t) {            return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1));        },        easeOutExpo: function (t) {            return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1);        },        easeInOutExpo: function (t) {            if (t==0) return 0;            if (t==1) return 1;            if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1));            return 1/2 * (-Math.pow(2, -10 * --t) + 2);        },        easeInCirc: function (t) {            if (t>=1) return t;            return -1 * (Math.sqrt(1 - (t/=1)*t) - 1);        },        easeOutCirc: function (t) {            return 1 * Math.sqrt(1 - (t=t/1-1)*t);        },        easeInOutCirc: function (t) {            if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1);            return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1);        },        easeInElastic: function (t) {            var s=1.70158;var p=0;var a=1;            if (t==0) return 0;  if ((t/=1)==1) return 1;  if (!p) p=1*.3;            if (a < Math.abs(1)) { a=1; var s=p/4; }            else var s = p/(2*Math.PI) * Math.asin (1/a);            return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));        },        easeOutElastic: function (t) {            var s=1.70158;var p=0;var a=1;            if (t==0) return 0;  if ((t/=1)==1) return 1;  if (!p) p=1*.3;            if (a < Math.abs(1)) { a=1; var s=p/4; }            else var s = p/(2*Math.PI) * Math.asin (1/a);            return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1;        },        easeInOutElastic: function (t) {            var s=1.70158;var p=0;var a=1;            if (t==0) return 0;  if ((t/=1/2)==2) return 1;  if (!p) p=1*(.3*1.5);            if (a < Math.abs(1)) { a=1; var s=p/4; }            else var s = p/(2*Math.PI) * Math.asin (1/a);            if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));            return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1;        },        easeInBack: function (t) {            var s = 1.70158;            return 1*(t/=1)*t*((s+1)*t - s);        },        easeOutBack: function (t) {            var s = 1.70158;            return 1*((t=t/1-1)*t*((s+1)*t + s) + 1);        },        easeInOutBack: function (t) {            var s = 1.70158;            if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s));            return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2);        },        easeInBounce: function (t) {            return 1 - JChart.animationOptions.easeOutBounce (1-t);        },        easeOutBounce: function (t) {            if ((t/=1) < (1/2.75)) {                return 1*(7.5625*t*t);            } else if (t < (2/2.75)) {                return 1*(7.5625*(t-=(1.5/2.75))*t + .75);            } else if (t < (2.5/2.75)) {                return 1*(7.5625*(t-=(2.25/2.75))*t + .9375);            } else {                return 1*(7.5625*(t-=(2.625/2.75))*t + .984375);            }        },        easeInOutBounce: function (t) {            if (t < 1/2) return JChart.animationOptions.easeInBounce (t*2) * .5;            return JChart.animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5;        }    },    /**     * 通用的计时控制器     */    requestAnimFrame : (function(){        return window.requestAnimationFrame ||            window.webkitRequestAnimationFrame ||            window.mozRequestAnimationFrame ||            window.oRequestAnimationFrame ||            window.msRequestAnimationFrame ||            function(callback) {                window.setTimeout(callback, 1000 / 60);            };    })(),    isNumber : function(n){        return !isNaN(parseFloat(n)) && isFinite(n);    },    isEqual : function(number1, number2, digits){        digits = digits == undefined? 10: digits; // 默认精度为10        return number1.toFixed(digits) === number2.toFixed(digits);    },    /**     * 取有效区域内的值     * @param valueToCap     * @param maxValue     * @param minValue     * @return {*}     */    capValue : function(valueToCap, maxValue, minValue){        var value;        if(this.isNumber(maxValue) && valueToCap > maxValue) {            return maxValue;        }        if(this.isNumber(minValue) && valueToCap < minValue ){            return minValue;        }        return valueToCap;    },    getDecimalPlaces : function(num){        if (num%1!=0){            return num.toString().split(".")[1].length        }        else{            return 0;        }    },    extend : function(target){        var args = Array.prototype.slice.call(arguments,1);        this.each(args,function(v,i){            extend(target,v);        });        function extend(target,source){            for(var key in source){                var o = source[key];                if(o instanceof Array){                    target[key] = extend([], o);                }else if(o instanceof Object){                    target[key] = extend({},o);                }else{                    target[key] = o;                }            }            return target;        }        return target;    },    clone : function(obj){        var o;        if (typeof obj == "object") {            if (obj === null) {                o = null;            } else {                if (obj instanceof Array) {                    o = [];                    for (var i = 0, len = obj.length; i < len; i++) {                        o.push(this.clone(obj[i]));                    }                } else {                    o = {};                    for (var j in obj) {                        o[j] = this.clone(obj[j]);                    }                }            }        } else {            o = obj;        }        return o;    },    //只对array有效    each : function(array,fn,context){        for(var i = 0,len=array.length;i<len;i++){            var result = fn.call(context,array[i],i,array);            if(result === true){                continue;            }else if(result === false){                break;            }        }    },    getOffset : function(el){        var box = el.getBoundingClientRect(),         doc = el.ownerDocument,         body = doc.body,         html = doc.documentElement,         clientTop = html.clientTop || body.clientTop || 0,         clientLeft = html.clientLeft || body.clientLeft || 0,         top = box.top + (self.pageYOffset || html.scrollTop || body.scrollTop ) - clientTop,         left = box.left + (self.pageXOffset || html.scrollLeft || body.scrollLeft) - clientLeft         return { ‘top‘: top, ‘left‘: left };     },    /**     * 将颜色代码转换成RGB颜色     * @param color     * @return {*}     */    hex2Rgb : function(color,alpha){        var r, g, b;        // 参数为RGB模式时不做进制转换,直接截取字符串即可        if( /rgb/.test(color) ){            var arr = color.match( /\d+/g );            r = parseInt( arr[0] );            g = parseInt( arr[1] );            b = parseInt( arr[2] );        }else if( /#/.test(color) ){// 参数为十六进制时需要做进制转换            var len = color.length;            if( len === 7 ){// 非简写模式 #0066cc                r = parseInt( color.slice(1, 3), 16 );                g = parseInt( color.slice(3, 5), 16 );                b = parseInt( color.slice(5), 16 );            }else if( len === 4 ){ // 简写模式 #06c                r = parseInt( color.charAt(1) + val.charAt(1), 16 );                g = parseInt( color.charAt(2) + val.charAt(2), 16 );                b = parseInt( color.charAt(3) + val.charAt(3), 16 );            }        }        else{            return color;        }        return ‘rgba(‘+r+‘,‘+g+‘,‘+ b + ‘,‘+(alpha?alpha:1)+‘)‘;    },    tmpl : (function(){        //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/        var cache = {};        function tmpl(str, data){            // Figure out if we‘re getting a template, or if we need to            // load the template - and be sure to cache the result.            var fn = !/\W/.test(str) ?                cache[str] = cache[str] ||                    tmpl(document.getElementById(str).innerHTML) :                // Generate a reusable function that will serve as a template                // generator (and which will be cached).                new Function("obj",                    "var p=[],print=function(){p.push.apply(p,arguments);};" +                        // Introduce the data as local variables using with(){}                        "with(obj){p.push(‘" +                        // Convert the template into pure JavaScript                        str                            .replace(/[\r\t\n]/g, " ")                            .split("<%").join("\t")                            .replace(/((^|%>)[^\t]*)‘/g, "$1\r")                            .replace(/\t=(.*?)%>/g, "‘,$1,‘")                            .split("\t").join("‘);")                            .split("%>").join("p.push(‘")                            .split("\r").join("\\‘")                        + "‘);}return p.join(‘‘);");            // Provide some basic currying to the user            return data ? fn( data ) : fn;        };        return tmpl;    })()};;(function(_){    function Bar(data,cfg){        _.Scale.apply(this);        var barRanges = [];//记录柱状图的占据的位置        this._type_ = ‘bar‘;        var _this = this;        this.data = http://www.mamicode.com/data;//所有的数据        this.chartData = http://www.mamicode.com/null;//图表当前展示的数据        //配置项        _.extend(this.config,{            //是否显示bar的边框            showBarBorder : true,            //bar边框宽度            barBorderWidth : 2,            //每两个bar之间的间距            barSpacing : 1,            //每两组bar之间的间距            barSetSpacing : 5,            //是否可以对数据进行拖动            datasetGesture : false,            //每次显示的数据条数            datasetShowNumber : 12        });        /**         * 绑定canvas dom元素上的事件 如:click、touch         */        this.bindEvents = function(){            this.on(‘_tap‘,function(x,y){tapHandler(x,y,‘tap.bar‘)});            //this.on(‘_doubleTap‘,function(x,y){tapHandler(x,y,‘doubleTap.bar‘)});            this.on(‘_longTap‘,function(x,y){tapHandler(x,y,‘longTap.bar‘)});            if(this.config.datasetGesture){                this.bindDataGestureEvent();            }        }        /**         * 初始化部分元素值         */        this.draw = function(noAnim){            if(this.config.datasetGesture && this.data.labels.length > _this.config.datasetShowNumber){                this.chartData = http://www.mamicode.com/this.sliceData(this.data,0,this.data.labels.length,this.config.datasetShowNumber);            }else{                this.chartData = http://www.mamicode.com/this.data;            }            this.mergeFont([‘scaleFont‘,‘textFont‘]);            this.initScale(true);            if(noAnim){                this.drawScale();                this.drawBars(1);            }else{                this.doAnim(this.drawScale,this.drawBars);            }        }        this.redraw = function(data){            this.chartData =http://www.mamicode.com/ data;            this.clear();            this.initScale(true);            this.drawScale();            this.drawBars(1);        }        this.drawBars = function(animPc){            if(animPc >= 1)barRanges = [];            var ctx = _this.ctx,cfg = _this.config,scale = _this.scaleData;            _.each(_this.chartData.datasets,function(set,i){                if(!cfg.showBarBorder)borderColor = null;                _.each(set.data,function(d,j){                    var x = scale.x + cfg.barSetSpacing + scale.xHop*j + scale.barWidth*i + cfg.barSpacing*i + cfg.barBorderWidth* i,                        y = scale.y,width = scale.barWidth,height = animPc*_this.calcOffset(d,scale.yScaleValue,scale.yHop)+(cfg.barBorderWidth/2),                        color = set.color,borderColor,bgColor = _.hex2Rgb(color,0.6);                    if(cfg.showBarBorder){                        //边框颜色默认与设置颜色一致                        borderColor = set.borderColor || color;                    }                    ctx.rect(x,y,width,-height,bgColor,borderColor,cfg.barBorderWidth);                    if(animPc >= 1){                        barRanges.push([x,x+width,y,y-height,j,i]);                    }                    cfg.showText && _this.drawText(d,x+width/2,y-height-3,[j,i]);                });            })        }        function tapHandler(x,y,event){            var p = isInBarRange(x,y);            if(p){                _this.trigger(event,[_this.chartData.datasets[p[5]].data[p[4]],p[4],p[5]]);            }        }        function isInBarRange(x,y){            var range;            _.each(barRanges,function(r){                if(x >= r[0] && x <= r[1] && y >= r[3] && y <= r[2]){                    range = r;                    return false;                }            });            return range;        }        //初始化参数        if(cfg)this.initial(cfg);    }    _.Bar = Bar;})(JChart)/** * 简单的Canvas帮助类,使canvas支持类似于jquery的链式操作,支持CanvasRenderingContext2D所有的方法,并提供一些常用的工具方法 */;(function(_){    function Chain(el){        //需要返回结果的方法,这些方法将不能进行后续的链式调用        var needReturnValueFn = [‘isPointInPath‘,‘measureText‘,‘getImageData‘];        function Canvas(){            this.el = el = (typeof el === ‘string‘) ? document.getElementById(el) : el;            this.ctx = el.getContext(‘2d‘);            this.width = el.width;            this.height = el.height;            addProtoFunc(this.ctx);        }        //添加canvas原生方法到prototype中        function addProtoFunc(ctx){            for(var fn in CanvasRenderingContext2D.prototype){                if(Canvas.prototype[fn])continue;                Canvas.prototype[fn] = function(fn){                    return function(){                        var args = Array.prototype.slice.call(arguments);                        var result = ctx[fn].apply(ctx,args);                        if(needReturnValueFn.indexOf(fn)>-1){                            return result;                        }                        return this;                    }                }(fn);            }        }        Canvas.prototype = {            /**             *  设置context的属性值             * @param name 属性名             * @param value 属性值             * @return this             */            set : function(name,value){                if(typeof name == ‘object‘){                    for(var p in name){                        this.ctx[p] && (this.ctx[p] = name[p]);                    }                }else{                    this.ctx[name] && (this.ctx[name] = value);                }                return this;            },            /**             * 获取context的属性值             * @param name 属性名             * @return value 属性值             */            get : function(name){                return this.ctx[name];            },            /**             * context填充             * @param color 填充颜色             * @return this             */            fill : function (color) {                if (typeof color === ‘string‘) {                    this.set(‘fillStyle‘, color);                }                this.ctx.fill();                return this;            },            /**             * context描边             * @param color 描边颜色             * @return this             */            stroke : function (color,width) {                if (typeof color === ‘string‘) {                    this.set(‘strokeStyle‘, color);                    width && this.set(‘lineWidth‘,width);                }                this.ctx.stroke();                return this;            },            /**             * 填充文本,在文本中加入\n可实现换行             * @param text             * @param x             * @param y             * @param style             * @return {*}             */            fillText : function(text,x,y,style){                this.ctx.save();                if(style && typeof style == ‘object‘){                    for(var p in style){                        this.set(p,style[p]);                    }                }                var texts = (text+‘‘).split(‘\n‘);                if(texts.length > 1){                    var fontsize = this.getFontSize();                    for(var i=0;i<texts.length;i++){                        this.ctx.fillText(texts[i],x,y+i*fontsize);                    }                }else{                    this.ctx.fillText(text,x,y);                }                this.ctx.restore();                return this;            },            /**             * 清除矩形             * @param x             * @param y             * @param w             * @param h             * @return this             */            clear : function (x, y, w, h) {                x = x || 0;                y = y || 0;                w = w || this.width;                h = h || this.height;                this.ctx.clearRect(x, y, w, h);                return this;            },            /**             * 重新设置大小             * @param width 宽             * @param height 高             * @return this             */            resize : function (width, height) {                this.el.width = width;                this.el.height = height;                this.width = width;                this.height = height;                return this;            },            /**             * 画线             */            line : function(x,y,x1,y1,stroke,strokeWidth){                this.beginPath().moveTo(x,y).lineTo(x1,y1);                stroke && this.stroke(stroke,strokeWidth);                return this;            },            /**             * 画矩形             * @param x             * @param y             * @param w             * @param h             * @param fill 填充颜色             * @param stroke 描边颜色             * @param strokeWidth 描边宽度             * @return this             */            rect : function (x, y, w, h, fill, stroke,strokeWidth) {                this.ctx.beginPath();                this.ctx.rect(x, y, w, h);                this.ctx.closePath();                fill && this.fill(fill);                stroke && this.stroke(stroke,strokeWidth);                return this;            },            /**             * 画圆形             * @param x             * @param y             * @param r 半径             * @param fill 填充颜色             * @param stroke 描边颜色             * @param strokeWidth 描边宽度             * @return this             */            circle : function (x, y, r, fill, stroke,strokeWidth) {                this.beginPath();                this.ctx.arc(x, y, r, 0, 2 * Math.PI, false);                this.closePath();                fill && this.fill(fill);                stroke && this.stroke(stroke,strokeWidth);                return this;            },            /**             * 画扇形             * @param x             * @param y             * @param r 半径             * @param start 开始角度             * @param end 结束角度             * @param fill 填充颜色             * @param stroke 描边颜色             * @param strokeWidth 描边宽度             * @reutrn this             */            sector : function(x,y,r,start,end,fill,stroke,strokeWidth){                this.beginPath()                    .arc(x,y,r,start, end, false)                    .lineTo(x,y)                    .closePath();                fill && this.fill(fill);                stroke && this.stroke(stroke,strokeWidth);                return this;            },            /**             * 环形扇形             * @param x             * @param y             * @param ir 内半径             * @param or 外半径             * @param start 开始角度             * @param end 结束角度             * @param fill 填充颜色             * @param stroke 描边颜色             * @param strokeWidth 描边宽度             * @reutrn this             */            dountSector : function(x,y,ir,or,start,end,fill,stroke,strokeWidth){                this.beginPath()                    .arc(x,y,or,start, end, false)                    .arc(x,y,ir,end,start,true)                    .closePath();                fill && this.fill(fill);                stroke && this.stroke(stroke,strokeWidth);                return this;            },            /**             * 加载一张图片             * @param img             * @return this             */            image : function (img) {                var _self = this;                var args = Array.prototype.slice.call(arguments);                var cb = function () {                    _self.ctx.drawImage.apply(_self.ctx, args);                };                if (typeof img === ‘string‘) {                    args[0] = new Image();                    args[0].onload = cb;                    args[0].src =http://www.mamicode.com/ img;                } else {                    cb();                }                return this;            },            /**             * 获取canvas当前的字体大小             * @return {Boolean}             */            getFontSize : function(){                var size = this.ctx.font.match(/\d+(?=px)/i);                if(size){                    size = parseInt(size[0]);                }                return size;            }        }        return new Canvas();    }    _.Canvas = Chain;})(JChart || window);;(function(_){    var Chart = function(){        //当前动画的状态        this.isAnimating = false;        this.config = {            width : 0,            height : 0,            bgColor : ‘#fff‘,            //优先画刻度            drawScaleFirst : true,            //文本字体属性            showText : true,            textFont : {},            //是否开启动画            animation : true,            //动画帧数            animationSteps : 60,            //动画函数            animationEasing : "easeOutBounce"        }        this.defaultFont = {            family : ‘Arial‘,            size : 16,            style : ‘normal‘,            color : ‘#5b5b5b‘,            textAlign : ‘center‘,            textBaseline : ‘middle‘        }        this.events = {};        /**         * 初始化参数         */        this.initial = function(cfg){            //合并设置参数            if(typeof cfg == ‘string‘){                this.config.id = cfg;            }else{                _.extend(this.config,cfg);            }            this.ctx = _.Canvas(this.config.id);            var canvas = this.ctx.el;            this.config.width && (canvas.width = this.config.width);            this.config.height && (canvas.height = this.config.height);            if(this.config.fit){                //todo 自动计算高度宽度                //todo 检测 转屏 事件            }            this.width = canvas.width;            this.height = canvas.height;            //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.            //如果设备为视网膜屏,将canvas按照设备像素比放大像素,然后再等比缩小            if (window.devicePixelRatio) {                canvas.style.width = this.width + "px";                canvas.style.height = this.height + "px";                canvas.height = this.height * window.devicePixelRatio;                canvas.width = this.width * window.devicePixelRatio;                this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio);            }            this.bindTouchEvents();            this.bindEvents();            this.setBg();        };        this.setBg = function(){            this.ctx.set(‘fillStyle‘,this.config.bgColor);            this.ctx.fillRect(0,0,this.width,this.height);        }        this.resize = function(w,h){        },        /**         * 清空画布后重新设置画布的背景         */        this.clear = function(){            this.ctx.clear();            this.setBg();        };        /**         * 重新刷新图表         */        this.refresh = function(config){            this.update(null,config,true);        };        /**         * 加载数据         * @param data         * @param config         */        this.load = function(data){            this.update(data,null,false);        }        /**         * 更新图表         * @param data         * @param config         * @param animation         */        this.update = function(data,config,animation){            config && _.extend(this.config,config);            data && (this.data =http://www.mamicode.com/ data);            this.dataOffset = 0;            this.clear();            this.draw(animation);        }        this.mergeFont = function(key){            if(key instanceof Array){                _.each(key,function(v){                    this.mergeFont(v);                },this);            }else{                var of = this.config[key];                var f = _.extend({},this.defaultFont,of);                f.font = f.style + " " + f.size+"px " + f.family;                f.fillStyle = f.color;                this.config[key] = f;            }        }        /**         * 动画函数         * @param drawScale 缩放动画 函数         * @param drawData  增长式动画 函数         * @param callback  执行成功回调函数         */        this.doAnim = function(drawScale,drawData,callback){            this.isAnimating = true;            var config = this.config,_this = this;            // 1/动画帧数            var animFrameAmount = (config.animation)? 1/ _.capValue(config.animationSteps,1000,1) : 1,            //动画效果                easingFunction = _.animationOptions[config.animationEasing],            //动画完成率                percentAnimComplete =(config.animation)? 0 : 1,                _this = this;            if (typeof drawScale !== "function") drawScale = function(){};            _.requestAnimFrame.call(window,animLoop);            function animLoop(){                percentAnimComplete += animFrameAmount;                animateFrame();                if (percentAnimComplete <= 1){                    _.requestAnimFrame.call(window,animLoop);                }else{                    _this.isAnimating = false;                    callback && callback.call(_this);                    _this.trigger(‘animationComplete‘);                }            };            function animateFrame(){                _this.clear();                var animPercent =(config.animation)? _.capValue(easingFunction(percentAnimComplete),1,0) : 1;                drawData.call(_this,animPercent);                if(_this.config.drawScaleFirst){                    drawScale.call(_this);                    drawData.call(_this,animPercent);                }else{                    drawData.call(_this,animPercent);                    drawScale.call(_this);                }            };        }        /**         * 简易的事件绑定         */        this.on = function(event,callback){            this.events[event] = callback;        };        /**         * 调用事件函数         * @param event 事件名称         * @param data 参数(数组形式)         */        this.trigger = function(event,data){            var callback = this.events[event];            if(callback){                return callback.apply(this,data);            }else{                return null;            }        };        this.drawText = function(text,x,y,args,style){            this.ctx.set(this.config.textFont);            style && this.ctx.set(style);            args = args ? [text].concat(args) : [text];            var t = this.trigger(‘renderText‘,args);            t = (t == null)?text:t;            this.ctx.fillText(t,x,y);        };        //给chart添加tap longTap doubleTap事件        this.bindTouchEvents = function(){            var touch = {},touchTimeout,longTapDelay = 750, longTapTimeout,now, delta,offset,                hasTouch = ‘ontouchstart‘ in window,                START_EV = hasTouch ? ‘touchstart‘ : ‘mousedown‘,                MOVE_EV = hasTouch ? ‘touchmove‘ : ‘mousemove‘,                END_EV = hasTouch ? ‘touchend‘ : ‘mouseup‘,                CANCEL_EV = hasTouch ? ‘touchcancel‘ : ‘mouseup‘,                _this = this;            this.ctx.el.addEventListener(START_EV,touchstart);            this.ctx.el.addEventListener(MOVE_EV,touchmove);            this.ctx.el.addEventListener(END_EV,touchend);            this.ctx.el.addEventListener(CANCEL_EV,cancelAll);            function touchstart(e){                now = Date.now();                e = e.touches ? e.touches[0] : e;                delta = now - (touch.last || now);                touchTimeout && clearTimeout(touchTimeout);                offset = _.getOffset(_this.ctx.el);                touch.x1 = e.pageX - offset.left;                touch.y1 = e.pageY - offset.top;                if (delta > 0 && delta <= 250) touch.isDoubleTap = true;                touch.last = now;                longTapTimeout = setTimeout(longTap, longTapDelay);            }            function touchmove(e){                if(!touch.last)return;                var ev = e.touches ? e.touches[0] : e;                touch.x2 = ev.pageX - offset.left;                touch.y2 = ev.pageY - offset.top;                if (Math.abs(touch.x1 - touch.x2) > 15){                    e.preventDefault();                    cancelAll();                }            }            function touchend(e){                cancelLongTap();                if (‘last‘ in touch){                    //tap事件,单击/双击都会触发,0延迟,建议在不使用doubleTap的环境中使用,如果要同时使用tap和doubleTap,请使用singleTap                    _this.trigger(‘_tap‘,[touch.x1,touch.y1]);                    _this.trigger(‘tap‘,[touch.x1,touch.y1]);                    if (touch.isDoubleTap) {                        cancelAll();                        _this.trigger(‘_doubleTap‘,[touch.x1,touch.y1]);                        _this.trigger(‘doubleTap‘,[touch.x1,touch.y1]);                    }else {                        touchTimeout = setTimeout(function(){                            touchTimeout = null;                            _this.trigger(‘_singleTap‘,[touch.x1,touch.y1]);                            _this.trigger(‘singleTap‘,[touch.x1,touch.y1]);                            touch = {};                        }, 250)                    }                };            }            function longTap() {                longTapTimeout = null;                if (touch.last) {                    _this.trigger(‘_longTap‘,[touch.x1,touch.y1]);                    _this.trigger(‘longTap‘,[touch.x1,touch.y1]);                    touch = {};                }            }            function cancelLongTap() {                if (longTapTimeout) clearTimeout(longTapTimeout);                longTapTimeout = null;            }            function cancelAll() {                if (touchTimeout) clearTimeout(touchTimeout);                if (longTapTimeout) clearTimeout(longTapTimeout);                touchTimeout = longTapTimeout = null;                touch = {};            }        }    }    _.Chart = Chart;})(JChart);;(function(_){    function Line(data,cfg){          _.Scale.apply(this);        var pointRanges = [];//记录线的节点位置 (for click 事件)        this._type_ = ‘line‘;        this.data =http://www.mamicode.com/ data;        this.chartData = http://www.mamicode.com/null;        var _this = this;        _.extend(this.config,{            //平滑曲线            smooth : true,            //是否显示线的连接点            showPoint : true,            //连接圆点半径            pointRadius : 4,            //连接点的边框宽度            pointBorderWidth : 2,            //连接点的点击范围(方便手指触摸)            pointClickBounds : 20,            //连接线的宽度            lineWidth : 2,            //是否填充为面积图            fill : true,            //是否可以对数据进行拖动            datasetGesture : false,            //每次显示的数据条数            datasetShowNumber : 12        });        /**         * 绑定canvas dom元素上的事件 如:click、touch         */        this.bindEvents = function(){            //this.ctx.canvas.addEventListener(‘click‘,tapHandler);            this.on(‘_tap‘,tapHandler);            if(this.config.datasetGesture){                this.bindDataGestureEvent();            }        }        /**         * 初始化部分元素值         */        this.draw = function(noAnim){            this.mergeFont([‘textFont‘,‘scaleFont‘]);            if(this.config.datasetGesture && this.data.labels.length > _this.config.datasetShowNumber){                this.chartData = http://www.mamicode.com/this.sliceData(this.data,0,this.data.labels.length,this.config.datasetShowNumber);            }else{                this.chartData = http://www.mamicode.com/this.data;            }            _this.initScale(true);            if(noAnim){                this.drawScale();                this.drawLines(1);            }else{                this.doAnim(this.drawScale,this.drawLines);            }        }        this.redraw = function(data){            this.chartData =http://www.mamicode.com/ data;            this.clear();            this.initScale(true);            this.drawScale();            this.drawLines(1);        }        this.drawLines = function(animPc){            if(animPc >= 1)pointRanges = [];            var ctx = _this.ctx,cfg = _this.config,dataset = _this.chartData.datasets,scale = _this.scaleData;            _.each(dataset,function(set,i){                //画连接线                ctx.beginPath().moveTo(scale.x, yPos(i,0));                _.each(set.data,function(d,j){                    var pointX = xPos(j),pointY = yPos(i,j);                    if (cfg.smooth){//贝塞尔曲线                        ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),pointY,pointX,pointY);                    }else{                        ctx.lineTo(pointX,pointY);                    }                    if(animPc >= 1){                        pointRanges.push([pointX,pointY,j,i]);                    }                });                ctx.stroke(set.color,cfg.lineWidth);                //填充区域                cfg.fill ? ctx.lineTo(scale.x + (scale.xHop*(set.data.length-1)),scale.y).lineTo(scale.x,scale.y).closePath()                    .fill(set.fillColor?set.fillColor : _.hex2Rgb(set.color,0.6)) : ctx.closePath();                //画点以及点上文本                _.each(set.data,function(d,k){                    var x = xPos(k),y = yPos(i,k);                    cfg.showPoint && _this.drawPoint(x,y,set);                    cfg.showText && _this.drawText("¥"+d,x,y-6,[k,i]);                });            });            function yPos(i,j){                return scale.y - animPc*(_this.calcOffset(dataset[i].data[j],scale.yScaleValue,scale.yHop));            }            function xPos(i){                return scale.x + (scale.xHop * i);            }        }        function tapHandler(x,y){            var p = isInPointRange(x,y);            if(p){                _this.trigger(‘tap.point‘,[_this.chartData.datasets[p[3]].data[p[2]],p[2],p[3]]);            }        }        function isInPointRange(x,y){            var point,pb = _this.config.pointClickBounds;            _.each(pointRanges,function(p){                if(x >= p[0] - pb && x <= p[0] + pb && y >= p[1]-pb && y <= p[1] + pb){                    point = p;                    return false;                }            });            return point;        }        //初始化参数        if(cfg)this.initial(cfg);    }    _.Line = Line;})(JChart);(function(_){    function Pie(data,cfg){        _.Chart.apply(this);        var angleRanges;//记录每个扇形的起始角度(从0开始)        var _this = this;        this.data =http://www.mamicode.com/ data;        var radius,totalData = http://www.mamicode.com/0,startAngle = 0,rotateAngle = 0,currentOutIndex = -1,origin = {};        //覆盖配置项        _.extend(this.config,{            //border            showBorder : true,            //border color            borderColor : "#fff",            //border width            borderWidth : 2,            //开始角度,默认为12点钟方向            startAngle : -Math.PI/2,            //旋转扇形,使其中线对应的角度            rotateAngle : Math.PI/2,            //扇形弹出距离            pullOutDistance : 10,            //点击扇形默认触发的事件类型            clickType : ‘pullOut‘,// pullOut||rotate            //环形图            isDount : false,            dountRadiusPercent :0.4,            totalAngle : Math.PI*2,            dountText : ‘‘,            dountFont : {                size : 20,                style : 600,                color : ‘#3498DB‘            }        });        /**         * 计算各个扇形的起始角度         * @param data         */        function calcAngel(){            var angle = 0;            angleRanges = [];            _.each(_this.data,function(d,i){                var start = angle;                var percent = d.value/totalData;                angle = angle + percent * _this.config.totalAngle;                var end = angle;                angleRanges.push([start,end,d,i,percent]);            })        }        function animRotate(percent){            drawPie(percent,‘rotate‘);        }        /**         *  画饼图         * @param percent 动画比例         */        function drawPie (percent,type){            _this.clear();            percent = _this.config.animation ? percent : 1;            _.each(angleRanges,function(a){                drawSector(a,percent,type);            });            _this.config.isDount && _this.config.dountText && drawDountText();        }        /**         * 计算扇形真实的角度         */        function calcSectorAngle(r,p,t){            var start = r[0],end = r[1];            if(t == ‘rotate‘){                //旋转                start = start + startAngle + rotateAngle*p;                end = end + startAngle + rotateAngle*p;            }else{                //默认动画                start = start*p + startAngle;                end = end*p + startAngle            }            return {                start : start,                end : end            }        }        /**         * 画扇形         * @param i         * @param animPercent         */        function drawSector(r,p,t){            var x = origin.x,y = origin.y,cfg = _this.config,                index = r[3],angle = calcSectorAngle(r,p,t);            if(index == currentOutIndex){                var midAngle = (r[0] + r[1])/2+startAngle;                x += Math.cos(midAngle) * cfg.pullOutDistance;                y += Math.sin(midAngle) * cfg.pullOutDistance;            }            if(cfg.isDount){                _this.ctx.dountSector(x,y,radius*cfg.dountRadiusPercent,radius,angle.start,angle.end,_this.data[index].color);            }else{                _this.ctx.sector(x,y,radius,angle.start,angle.end,_this.data[index].color);            }            cfg.showBorder && _this.ctx.stroke(cfg.borderColor,cfg.borderWidth);            cfg.showText && drawText(x,y,radius,angle.start,angle.end,r);        }        function drawText(x,y,r,start,end,data){            //计算文本位置            var middAngle = (start+end)/ 2, dis = r/ 2,                percent = data[4],d = data[2];            if(_this.config.isDount){                dis = r/2 + r*_this.config.dountRadiusPercent/2;            }            percent = (percent * 100).toFixed(1)+‘%‘;            // sam修改,修改了pie图表默认带有百分号的文字            percent = "";            var xaxis = Math.cos(middAngle) * dis + x, yaxis = Math.sin(middAngle) * dis + y;            _this.drawText(percent,xaxis,yaxis,[d,data[3],data[4]]);        }        function drawDountText(){            _this.ctx.fillText(_this.config.dountText,origin.x,origin.y,_this.config.dountFont);        }        /**         * 绑定canvas dom元素上的事件 如:click、touch         */        this.bindEvents = function(){            this.on(‘_tap‘,function(x,y){tapHandler(x,y,‘tap.pie‘)});            //暂时关闭doubleTap事件            //this.on(‘_doubleTap‘,function(x,y){tapHandler(x,y,‘doubleTap.pie‘)});            this.on(‘_longTap‘,function(x,y){tapHandler(x,y,‘longTap.pie‘)});            //添加一个默认点击事件            this.on(‘tap.pie‘,function(){return true;})        }        function tapHandler(x,y,event){            var type = _this.config.clickType;            var angle = isInSegment(x,y);            if(angle){                if(event == ‘tap.pie‘){//处理一些默认行为                    if(!_this.trigger(event,[angle[2],angle[3]]))return;                    if(type == ‘rotate‘){                        _this.rotate(angle[3]);                    }else if(type == ‘pullOut‘){                        _this.toggleSegment(angle[3]);                    }                }else{                    _this.trigger(type,[angle[2],angle[3]]);                }            }        }        function isInSegment(offsetX,offsetY){            var angle;            var x = offsetX - origin.x;            var y = offsetY - origin.y;            //距离圆点的距离            var dfc = Math.sqrt( Math.pow( Math.abs(x), 2 ) + Math.pow( Math.abs(y), 2 ) );            var isInPie = (dfc <= radius);            if(isInPie && _this.config.isDount){//排除dount图中心区                isInPie = (dfc >= radius*_this.config.dountRadiusPercent);            }            if(!isInPie)return;            var clickAngle = Math.atan2(y, x)-startAngle;            if ( clickAngle < 0 ) clickAngle += 2 * Math.PI;            if(clickAngle > 2 * Math.PI) clickAngle -= 2 * Math.PI;            _.each(angleRanges,function(a){                if(clickAngle >= a[0] && clickAngle < a[1]){                    angle = a;                    return false;                }            });            return angle;        }        /**         * 弹出/收起扇形块         * @param i 扇形索引         */        this.toggleSegment = function(i){            if(i == currentOutIndex){                this.pushIn();            }else{                this.pullOut(i);            }        }        /**         * 收起所有弹出的扇形块         */        this.pushIn = function(){            currentOutIndex = -1;            drawPie(1);            this.trigger(‘pushIn‘);        }        /**         * 弹出指定的扇形块         * @param i 扇形索引         */        this.pullOut = function(i){            if ( currentOutIndex == i ) return;            currentOutIndex = i;            drawPie(1);            this.trigger(‘pullOut‘,[_this.data[i],i,angleRanges[i][4]]);        }        /**         * 旋转扇形块的中线指向6点钟方向         * @param i 扇形索引         */        this.rotate = function(i){            if(_this.isAnimating)return;            var middAngle = (angleRanges[i][0] + angleRanges[i][1]) / 2 + startAngle;            var newRotateAngle = _this.config.rotateAngle-middAngle;            if(_.isEqual(newRotateAngle,0))return;            this.pushIn();            rotateAngle = newRotateAngle;            this.doAnim(null,animRotate,function(){                startAngle += rotateAngle;                _this.trigger(‘rotate‘,[_this.data[i],i,angleRanges[i][4]]);            });        }        this.setDountText = function(text){            _this.config.dountText = text;            drawPie(1);        }        /**         * 画图         */        this.draw = function(noAnim){            this.mergeFont([‘textFont‘,‘dountFont‘]);            calcOrigin();            totalData = 0;            currentOutIndex = -1;            _.each(_this.data,function(d){                totalData += d.value;            });            calcAngel();            startAngle = _this.config.startAngle;            if(noAnim){                drawPie(1);            }else{                this.doAnim(null,drawPie);            }        }        //计算原点位置及半径        function calcOrigin(){            if(_this.config.totalAngle == Math.PI){                origin = {                    x : _this.width/2,                    y : _this.height - 20                }                radius = Math.min(origin.x,origin.y) - 10;            }else{                origin = {x:_this.width/2,y:_this.height/2};                radius = Math.min(origin.x,origin.y) - 10;            }        }        //初始化参数        if(cfg)this.initial(cfg);    }    _.Pie = Pie;}(JChart));  ;(function(_){    function Polar(data,cfg){        _.Scale.apply(this);        var _this = this;        this.data = http://www.mamicode.com/this.chartData =http://www.mamicode.com/ data;          //配置项        _.extend(this.config,{            drawScaleFirst : false,            //是否显示刻度文本背景            showScaleLabelBackdrop : true,            //刻度背景颜色            scaleBackdropColor : "rgba(255,255,255,0.75)",            //刻度padding-top bottom            scaleBackdropPaddingY : 2,            //刻度padding-left right            scaleBackdropPaddingX : 2,            //是否显示角度分割线            showAngleLine : true,            //分割线颜色            angleLineColor : "rgba(0,0,0,.1)",            showBorder : true,            borderColor : ‘#fff‘,            borderWidth : 1,            textFont : {                size : 16,                color : ‘#666‘,                textBaseline : ‘middle‘            },            //分割线宽度            angleLineWidth : 1,            //是否开启旋转动画            animateRotate : true,            //是否开启缩放动画            animateScale : false        });        /**         * 绑定canvas dom元素上的事件         */        this.bindEvents = function(){            this.on(‘_tap‘,tapHandler);        }                this.draw = function(noAnim){            this.mergeFont([‘scaleFont‘,‘textFont‘]);            this.initScale();            if(noAnim){                this.drawAllSegments(1);                this.drawScale();            }            this.doAnim(this.drawScale,this.drawAllSegments);        }        function tapHandler(x,y){            var i = isInSegment(x,y);            if(i>-1){                this.trigger(‘tap.pie‘,[this.data[i],i]);            }        }        this.calcDrawingSizes = function(){            var maxSize = Math.min(this.width,this.height)/2,                cfg = this.config,size = cfg.scaleFont.size,lh = size*2;            maxSize -= Math.max(size*0.5,cfg.scaleLineWidth*0.5);            if (cfg.showScaleLabelBackdrop){                lh += (2 * cfg.scaleBackdropPaddingY);                maxSize -= cfg.scaleBackdropPaddingY*1.5;            }            this.scaleData.yHeight = maxSize - 10;            this.scaleData.yLabelHeight = lh;        }                this.drawScale = function(){            var cfg = this.config,scale = this.scaleData,x = this.width/2, y = this.height/2                size = cfg.scaleFont.size,px = cfg.scaleBackdropPaddingX,py = cfg.scaleBackdropPaddingY;            this.ctx.save().translate(x,y);            //画圆圈            for (var i=0; i<scale.yScaleValue.step; i++){                var hop = scale.yHop * (i + 1);                cfg.showGridLine && this.ctx.circle(0, 0, hop,false,cfg.gridLineColor,cfg.gridLineWidth);                if (cfg.showScaleLabel){                    var label =  scale.yScaleValue.labels[i];                    if (cfg.showScaleLabelBackdrop){                        var textWidth = this.ctx.measureText(label).width;                        this.ctx.rect(                            Math.round(- textWidth/2 - px),     //X                            Math.round(- hop - size/2 - py),//Y                            Math.round(textWidth + px*2), //Width                            Math.round(size + py*2), //Height                            cfg.scaleBackdropColor                        );                    }                    this.ctx.fillText(label,0,-hop,cfg.scaleFont);                }            }            //画角度分割线            var len = this.data.labels.length,rotateAngle = (2*Math.PI)/len;            this.ctx.rotate(-Math.PI/2-rotateAngle);            _.each(this.data.labels,function(label,i){                this.ctx.rotate(rotateAngle);                if(cfg.showAngleLine){                this.ctx.line(0,0,scale.yHeight,0,cfg.angleLineColor,cfg.angleLineWidth);                }                if(cfg.showLabel){                    this.ctx.save().translate(scale.yHeight+10,0).rotate(Math.PI/2 - rotateAngle*i);                    this.ctx.fillText(label,0,0,cfg.textFont);                    this.ctx.restore();                }            },this);            this.ctx.restore();        }        this.drawAllSegments = function(animPc){            var startAngle = -Math.PI/2,angleStep = (Math.PI*2)/this.data.datasets.length,               scaleAnim = 1,rotateAnim = 1,                scale = this.scaleData,cfg = this.config,                borderColor,borderWidth;            if (cfg.animation) {                cfg.animateScale && (scaleAnim = animPc);                cfg.animateRotate && (rotateAnim = animPc);            }            if(cfg.showBorder){                borderColor = cfg.borderColor;                borderWidth = cfg.borderWidth;            }            _.each(this.data.datasets,function(d){                var r = scaleAnim * this.calcOffset(d.value,scale.yScaleValue,scale.yHop);                this.ctx.sector(this.width/2,this.height/2,r,startAngle, startAngle + rotateAnim*angleStep, _.hex2Rgb(d.color,.6),borderColor,borderWidth);                startAngle += rotateAnim*angleStep;            },this);        }        this.getValueBounds = function(data){            var upperValue =http://www.mamicode.com/ Number.MIN_VALUE;            var lowerValue =http://www.mamicode.com/ Number.MAX_VALUE;            for (var i=0; i<data.length; i++){                if (data[i].value > upperValue) {upperValue =http://www.mamicode.com/ data[i].value;}                if (data[i].value < lowerValue) {lowerValue =http://www.mamicode.com/ data[i].value;}            };            var yh = this.scaleData.yHeight;            var lh = this.scaleData.yLabelHeight;            var maxSteps = Math.floor((yh/(lh*0.66)));            var minSteps = Math.floor((yh/lh*0.5));            return {                maxValue : upperValue,                minValue : lowerValue,                maxSteps : maxSteps,                minSteps : minSteps            };        }        function isInSegment(x,y){            var startAngle = -Math.PI/2,                angleStep = (Math.PI*2)/this.data.length;            var x = x-_this.width/ 2,y = y-_this.height/2;            //距离圆点的距离            var dfc = Math.sqrt( Math.pow( Math.abs(x), 2 ) + Math.pow( Math.abs(y), 2 ) );            var isInPie = (dfc <= _this.scaleData.yHeight);            if(!isInPie)return -1;            var clickAngle = Math.atan2(y, x)-startAngle;            if ( clickAngle < 0 ) clickAngle = 2 * Math.PI + clickAngle;            if(clickAngle > 2 * Math.PI) clickAngle = clickAngle - 2 * Math.PI;            return Math.floor(clickAngle/angleStep);        }        //初始化参数        if(cfg)this.initial(cfg);      }    _.Polar = Polar;  })(JChart);;(function (_) {    function Radar(data, cfg) {        _.Scale.apply(this);        var pointRanges = [];//记录线的节点位置 (for click 事件)        var _this = this;        this.data = http://www.mamicode.com/this.chartData =http://www.mamicode.com/ data;        //配置项        _.extend(this.config, {            drawScaleFirst : false,            //是否显示刻度文本背景            scaleShowLabelBackdrop:true,            //刻度背景颜色            scaleBackdropColor:"rgba(255,255,255,0.75)",            //刻度padding-top bottom            scaleBackdropPaddingY:2,            //刻度padding-left right            scaleBackdropPaddingX:2,            //图形形状,菱形 diamond,圆形 circle            graphShape:‘circle‘,            //是否显示角度分割线            showAngleLine:true,            //角度分割线颜色            angleLineColor:"rgba(0,0,0,.1)",            //角度分割线宽度            angleLineWidth:1,            //是否显示线的连接点            showPoint:true,            //连接圆点半径            pointRadius:3,            //连接点的边框宽度            pointBorderWidth:1,            //连接点的点击范围(方便手指触摸)            pointClickBounds:20,            //连接线的宽度            lineWidth:2,            //是否填充为面积图            fill:true,            showScaleLabel:true,            showText : false,            gridLineColor:‘rgb(0,0,0,.5)‘        });        /**         * 绑定canvas dom元素上的事件 如:click、touch         */        this.bindEvents = function () {            this.on(‘_tap‘, tapHandler);        }        this.draw = function (noAnim) {            this.mergeFont([‘scaleFont‘,‘textFont‘]);            this.initScale();            if (noAnim) {                this.drawAllDataPoints(1);                this.drawScale();            } else {                this.doAnim(this.drawScale, this.drawAllDataPoints);            }        }        function tapHandler(x, y) {            var p = isInPointRange(x,y);            if(p){                _this.trigger(‘tap.point‘,[_this.data.datasets[p[3]].data[p[2]],p[2],p[3]]);            }        }        this.calcDrawingSizes = function () {            var maxSize = (Math.min(this.width, this.height) / 2),                cfg = this.config,                labelHeight = cfg.scaleFont.size * 2;            var labelLength = 0;            _.each(_this.data.labels, function (label) {                this.ctx.set(cfg.textFont);                var w = this.ctx.measureText(label).width;                if (w > labelLength) labelLength = w;            }, this);            maxSize -= Math.max(labelLength, ((cfg.textFont.size/2) * 1.5));            maxSize -= cfg.textFont.size;            maxSize = _.capValue(maxSize, null, 0);            this.scaleData.yHeight = maxSize;            this.scaleData.yLabelHeight = labelHeight;        }        this.drawScale = function () {            var ctx = this.ctx, cfg = this.config, scale = this.scaleData,scaleSize = cfg.scaleFont.size,textSize = cfg.textFont.size,                dataLen = this.data.labels.length,px = cfg.scaleBackdropPaddingX,py = cfg.scaleBackdropPaddingY;            //计算每条数据的角度            var rotationDegree = (2 * Math.PI) / dataLen;            ctx.save().translate(this.width / 2, this.height / 2);            //显示角度分割线            if (cfg.showAngleLine) {                var w = scale.yHeight - (scale.yHeight % scale.yHop);                //画每个角度的分割线                for (var h = 0; h < dataLen; h++) {                    ctx.rotate(rotationDegree).line(0,0,0,-w,cfg.angleLineColor,cfg.angleLineWidth);                }            }            //画刻度线            for (var i = 0; i < scale.yScaleValue.step; i++) {                var hop = scale.yHop * (i + 1);                ctx.beginPath();                if (cfg.showGridLine) {                    ctx.set({strokeStyle : cfg.gridLineColor,lineWidth : cfg.gridLineWidth})                    if (cfg.graphShape == ‘diamond‘) {                        ctx.moveTo(0, -hop);                        for (var j = 0; j < dataLen; j++) {                            ctx.rotate(rotationDegree).lineTo(0, -hop);                        }                    } else {                        ctx.circle(0, 0, hop);                    }                    ctx.closePath().stroke();                }                //画刻度值                if (cfg.showScaleLabel) {                    var label =  scale.yScaleValue.labels[i];                    //显示刻度值的背景                    if (cfg.showScaleLabelBackdrop){                        var textWidth = this.ctx.measureText(label).width;                        this.ctx.rect(                            Math.round(-textWidth/2 - px),     //X                            Math.round(-hop - scaleSize/2 - py),//Y                            Math.round(textWidth + px*2), //Width                            Math.round(scaleSize + py*2), //Height                            cfg.scaleBackdropColor                        );                    }                    this.ctx.fillText(label,0,-hop,cfg.scaleFont);                }            }            //设置文本样式            this.ctx.set(cfg.textFont);            //显示数据标签文本            for (var k = 0; k < dataLen; k++) {                var opposite = Math.sin(rotationDegree * k) * (scale.yHeight + textSize);                var adjacent = Math.cos(rotationDegree * k) * (scale.yHeight + textSize);                var align;                if (rotationDegree * k == Math.PI || rotationDegree * k == 0) {                    align = ‘center‘;                } else if (rotationDegree * k > Math.PI) {                    align = ‘right‘;                }else {                    align = ‘left‘;                }                this.ctx.fillText(this.data.labels[k], opposite, -adjacent,{textAlign:align});            }            ctx.restore();        }        this.drawAllDataPoints = function (animPc) {            if (animPc >= 1)pointRanges = [];            var dataLen = data.datasets[0].data.length,                rotationDegree = (2 * Math.PI) / dataLen,                scale = this.scaleData,                ctx = this.ctx, cfg = this.config;            ctx.save().translate(this.width / 2, this.height / 2);            _.each(this.data.datasets, function (set, i) {                ctx.beginPath().moveTo(0, getY(set.data[0]));                //画连接线                _.each(set.data, function (d, j) {                    if (j == 0)return true;                    ctx.rotate(rotationDegree).lineTo(0, getY(d));                });                ctx.closePath();                cfg.fill && ctx.fill(set.fillColor||_.hex2Rgb(set.color,0.6));                ctx.stroke(set.color,cfg.lineWidth);                //画连接点                _.each(set.data,function(d,j){                    var y = getY(d);                    if (cfg.showPoint) {                        ctx.rotate(rotationDegree).circle(0, y, cfg.pointRadius,set.pointColor,set.pointBorderColor,cfg.pointBorderWidth);                    }                    if(animPc >= 1){                        var p = getPosition(y,j);                        pointRanges.push([p[0],p[1],j,i]);                    }                });                ctx.rotate(rotationDegree);            }, this);            ctx.restore();            if(cfg.showText){                drawText();            }            function getY(d){                return -animPc * _this.calcOffset(d, scale.yScaleValue, scale.yHop);            }            function getPosition(radius,i){                radius = Math.abs(radius);                var x,y;                var angel = -Math.PI/2 + i * rotationDegree;                x = Math.cos(angel)*radius + _this.width/2;                y = Math.sin(angel)*radius + _this.height/2;                return [x,y];            }        }        function drawText(){            _.each(pointRanges,function(p){                var y = p[1];                if(y > _this.height/2){                    y += 6;                }                _this.drawText(_this.data.datasets[p[3]].data[p[2]],p[0],y,[p[2],p[3]]);            });        }        function isInPointRange(x,y){            var point,pb = _this.config.pointClickBounds;            _.each(pointRanges,function(p){                if(x >= p[0] - pb && x <= p[0] + pb && y >= p[1]-pb && y <= p[1] + pb){                    point = p;                    return false;                }            });            return point;        }        //初始化参数        if (cfg)this.initial(cfg);    }    _.Radar = Radar;})(JChart);;(function(_){    /**     * 抽象类-刻度值     * 用来初始化XY轴各项数据     * @constructor     */    function Scale(){        var P_T = 5,//图表顶部空白            P_R = 5,//图表右侧空白            P_Y = 10,//y轴左侧空白            P_X = 10;//x轴文本与x之间的间距        _.Chart.apply(this);        _.extend(this.config,{            /**             * @Object             * Y轴刻度值,默认为null,会自动生成,也可以自己指定             *   {             *      step : 10,//刻度个数,必选项             *      stepValue : 10//每两个刻度线之间的差值,必选项             *      start : 0//起始刻度值,默认为0             *   }             */            scale : null,            //xy轴刻度线的颜色            scaleLineColor : "rgba(0,0,0,.3)",            //刻度线宽度            scaleLineWidth:1,            //是否显示Y轴刻度值            showScaleLabel : true,            //是否显示X轴刻度值            showLabel : true,            //刻度值字体属性            scaleFont : {                size:12,                color : ‘#666‘            },            textFont : {                size : 14,                textBaseline : ‘bottom‘            },            //是否显示网格线            showGridLine : true,            //网格线颜色            gridLineColor : "rgba(0,0,0,.1)",            //网格线宽度            gridLineWidth : 1        });        //数据偏移量-已经偏移        this.dataOffset = 0;        this.scaleData =http://www.mamicode.com/ {            x : 0,//圆点坐标            y : 0,            xHop : 0,//x轴数据项宽度            yHop : 0,//y轴每个刻度的高度            xLength : 0,//x轴长度            yHeight : 0,//y轴高度            yLabelHeight : 0,//y轴刻度文本高度            yScaleValue : null,//y轴刻度指标            labelRotate : 0,//x轴label旋转角度            xLabelWidth : 0,//x轴label宽度            xLabelHeight : 0,//x轴label宽度            barWidth : 0//柱形图柱子宽度        }        /**         * 计算X轴文本宽度、旋转角度及Y轴高度         */        this.calcDrawingSizes = function(){            var maxSize = this.height,widestX = 0,scaleFontSize = this.config.scaleFont.size, xLabelWidth = 0,xLabelHeight = scaleFontSize,                labelRotate = 0,dataLen = this.chartData.labels.length;            //计算X轴,如果发现数据宽度超过总宽度,需要将label进行旋转            this.ctx.set(this.config.scaleFont);            //找出最宽的label            _.each(this.chartData.labels,function(o){                var w = this.ctx.measureText(o).width;                widestX = (w > widestX)? w : widestX;            },this);            xLabelWidth = widestX;            if (this.width/dataLen < widestX){                labelRotate = 45;                xLabelWidth = Math.cos(labelRotate*Math.PI/180) * widestX;                xLabelHeight = Math.sin(labelRotate*Math.PI/180) * widestX ;                if (this.width/dataLen < xLabelHeight){                    labelRotate = 90;                    xLabelWidth = scaleFontSize;                    xLabelHeight = widestX;                }            }            //减去x轴label的高度            maxSize -= xLabelHeight;            //减去x轴文本与x轴之间的间距            maxSize -= P_X;            //给Y轴顶部留一点空白            maxSize -= P_T;            maxSize -= this.config.showText?scaleFontSize:0;            //y轴高度            this.scaleData.yHeight = maxSize;            //y轴刻度高度            this.scaleData.yLabelHeight = scaleFontSize;            //x轴文本旋转角度            this.scaleData.labelRotate = labelRotate;            //x轴文本的宽度            this.scaleData.xLabelWidth = xLabelWidth;            //x轴文本的高度            this.scaleData.xLabelHeight = xLabelHeight;        }        /**         * 计算Y轴刻度的边界及刻度步数         * @return {Object}         */        this.getValueBounds = function(dataset) {            var upperValue =http://www.mamicode.com/ Number.MIN_VALUE;            var lowerValue =http://www.mamicode.com/ Number.MAX_VALUE;            _.each(dataset,function(o){                _.each(o.data,function(obj){                    if(obj > upperValue){upperValue =http://www.mamicode.com/ obj};                    if (obj < lowerValue) { lowerValue =http://www.mamicode.com/ obj};                });            })            var yh = this.scaleData.yHeight;            var lh = this.scaleData.yLabelHeight;            var maxSteps = Math.floor((yh/(lh*0.66)));            var minSteps = Math.floor((yh/lh*0.5));            return {                maxValue : upperValue,                minValue : lowerValue,                maxSteps : maxSteps,                minSteps : minSteps            };        }        /**         * 计算Y轴刻度的各项数据         */        this.calcYAxis = function(){            var scale = this.config.scale;            if (scale){                scale.start = scale.start || 0;                scale.labels = this.populateLabels(scale.step,scale.start,scale.stepValue);            }else {                var bounds = this.getValueBounds(this.chartData.datasets);                scale = this.calcScale(this.scaleData.yHeight,bounds.maxSteps,bounds.minSteps,bounds.maxValue,bounds.minValue);            }            this.scaleData.yScaleValue =http://www.mamicode.com/ scale;            this.scaleData.yHop = Math.floor(this.scaleData.yHeight/scale.step);        }        /**         * 计算X轴宽度,每个数据项宽度大小及坐标原点         */        this.calcXAxis = function(){            var config = this.config,scale = this.scaleData,yLabelWidth = 0,xAxisLength,valueHop, x,y;            if (config.showScaleLabel){                //找出Y轴刻度的最宽值                _.each(scale.yScaleValue.labels,function(o){                    var w = this.ctx.measureText(o).width;                    yLabelWidth = (w > yLabelWidth)? w : yLabelWidth;                },this);                yLabelWidth += P_Y;            }            // sam修改,主要是x轴宽度加10了,对于line图表,如果你隐藏了y轴的刻度值,连着隐藏x轴的起点一半            //x轴的宽度            xAxisLength = this.width - yLabelWidth-(P_R+10)-(this.config.showText?this.config.textFont.size:0);            if(this._type_ == ‘bar‘){//计算柱形图柱子宽度,柱形图x轴文本居中显示,需要重新计算数据项宽度                valueHop = Math.floor(xAxisLength/this.chartData.labels.length);                var len = this.chartData.datasets.length;                scale.barWidth = (valueHop - config.gridLineWidth*2 - (config.barSetSpacing*2) - (config.barSpacing*len-1) - ((config.barBorderWidth/2)*len-1))/len;            }else{                valueHop = Math.floor(xAxisLength/(this.chartData.labels.length-1));            }            // sam修改,主要是x轴宽度加10了,对于line图表,如果你隐藏了y轴的刻度值,连着隐藏x轴的起点一半            scale.x = yLabelWidth+10;            scale.y = this.height - scale.xLabelHeight - P_X;            scale.xWidth = xAxisLength+10;            scale.xHop = valueHop;        }        this.drawScale = function(){            var ctx = this.ctx,cfg = this.config,scale = this.scaleData,align;            ctx.set({                strokeStyle :  cfg.scaleLineColor,                lineWidth : cfg.scaleLineWidth            })            //画X轴            ctx.line(scale.x-3, scale.y, scale.x+scale.xWidth, scale.y, true);            //画Y轴            ctx.line(scale.x,scale.y+3, scale.x,scale.y-scale.yHeight, true);            //画X轴刻度文本            if (scale.labelRotate > 0){                ctx.save();                align = ‘right‘;            }else{                align = ‘center‘;            }            ctx.set({                fillStyle : cfg.scaleFont.color,                textAlign : align,                textBaseline : ‘hanging‘,                strokeStyle : cfg.gridLineColor,                lineWidth : cfg.gridLineWidth            });            _.each(this.chartData.labels,function(label,i){                ctx.save();                var cx = scale.x + i*scale.xHop,labelY = scale.y + P_X/ 2,                    labelX = this._type_ == ‘bar‘?cx + scale.xHop/2 : cx;                if (scale.labelRotate > 0){                    ctx.translate(labelX,labelY).rotate(-(scale.labelRotate * (Math.PI/180))).fillText(label,0,0).restore();                }else{                    ctx.fillText(label, labelX,labelY);                }                //画纵向的网格线                if(cfg.showGridLine){                    var x = (this._type_ == ‘bar‘)?cx + scale.xHop : cx;                    ctx.line(x, scale.y, x, scale.y-scale.yHeight,true);                }            },this);            //画横向网格线            ctx.set({textAlign:‘right‘,textBaseline:‘middle‘});            for (var j=0; j<scale.yScaleValue.step; j++){                var y = scale.y - ((j+1) * scale.yHop);                cfg.showGridLine && ctx.line(scale.x,y,scale.x + scale.xWidth,y, true);                cfg.showScaleLabel && ctx.fillText(scale.yScaleValue.labels[j],scale.x-P_Y/2,y);            }        }        this.initScale = function(showX){            this.calcDrawingSizes();            this.calcYAxis();            showX && this.calcXAxis();        }        this.drawPoint = function(x,y,d){            //填充色默认为白色,边框颜色默认与线条颜色一致            this.ctx.beginPath().circle(x,y,this.config.pointRadius,d.pointColor || ‘#fff‘,d.pointBorderColor || d.color,this.config.pointBorderWidth);        }        /**         * 计算坐标轴的刻度         * @param drawingHeight         * @param maxSteps         * @param minSteps         * @param maxValue         * @param minValue         */        this.calcScale = function(drawingHeight,maxSteps,minSteps,maxValue,minValue){            var min,max,range,stepValue,step,valueRange,rangeOrderOfMagnitude;            valueRange = maxValue - minValue;            rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);            //min = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);            min = 0;//固定起始点为0            max = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);            range = max - min;            stepValue = Math.pow(10, rangeOrderOfMagnitude);            step = Math.round(range / stepValue);            //Compare number of steps to the max and min for that size graph, and add in half steps if need be.            while(step < minSteps || step > maxSteps) {                if (step < minSteps){                    stepValue /= 2;                    step = Math.round(range/stepValue);                }                else{                    stepValue *=2;                    step = Math.round(range/stepValue);                }            };            var labels = this.populateLabels(step, min, stepValue);;            return {                step : step,                stepValue : stepValue,                start : min,                labels : labels            }            function calculateOrderOfMagnitude(val){                return Math.floor(Math.log(val) / Math.LN10);            }        }        /**         * 构造刻度值         * @param labels         * @param numberOfSteps         * @param graphMin         * @param stepValue         */        this.populateLabels = function (step, start, stepValue) {            var labels = [];            for (var i = 1; i < step + 1; i++) {                if(!this.config.showScaleLabel){                    labels.push(‘‘);                    continue;                }                //小数点位数与stepValue后的小数点一致                var value = http://www.mamicode.com/(start + (stepValue * i)).toFixed(_.getDecimalPlaces(stepValue));                var text = this.trigger(‘renderYLabel‘,[value]);                text = text ? text : value;                labels.push(text);            }            return labels;        },        this.calcOffset = function(val,scale,scaleHop){            var outerValue = http://www.mamicode.com/scale.step * scale.stepValue;            var adjustedValue = http://www.mamicode.com/val - scale.start;            var scalingFactor = _.capValue(adjustedValue/outerValue,1,0);            return (scaleHop*scale.step) * scalingFactor;        },                this.sliceData = http://www.mamicode.com/function(data,offset,len,num){            var newdata =http://www.mamicode.com/ _.clone(data);            var min = offset,max = offset + num;            if(max > len){                min = len - num;                max = len;            }            newdata.labels = newdata.labels.slice(min,max);            _.each(newdata.datasets,function(d){                d.data = d.data.slice(min,max)            });            return newdata;        }        this.bindDataGestureEvent = function(){            var _this = this,                touchDistanceX,//手指滑动偏移量                startPosition,//触摸初始位置记录                currentOffset = 0,//当前一次滑动的偏移量                dataNum = this.config.datasetShowNumber,//每屏显示的数据条数                gestureStarted,                hasTouch = ‘ontouchstart‘ in window,                START_EV = hasTouch ? ‘touchstart‘ : ‘mousedown‘,                MOVE_EV = hasTouch ? ‘touchmove‘ : ‘mousemove‘,                END_EV = hasTouch ? ‘touchend‘ : ‘mouseup‘;            this.ctx.el.addEventListener(START_EV,touchstart);            this.ctx.el.addEventListener(MOVE_EV,touchmove);            this.ctx.el.addEventListener(END_EV,touchend);            function touchstart(e){                e = e.touches ? e.touches[0] : e;                startPosition = {                    x : e.pageX,                    y : e.pageY                }                touchDistanceX = 0;                gestureStarted = true;            }            function touchmove(e){                if(!gestureStarted || !_this.config.datasetGesture)return;                e = e.touches ? e.touches[0] : e;                var x = e.pageX;                var y = e.pageY;                touchDistanceX = x - startPosition.x;                //每滑动xHop加载下一组数据                var totalLen = _this.data.labels.length;//数据总长度                var offset = _this.dataOffset - Math.floor(touchDistanceX/_this.scaleData.xHop);                if(offset < 0 || offset == currentOffset||(offset+dataNum > totalLen))return;                currentOffset = offset;                console.log(offset);                //将操作加入系统队列,解决android系统下touchmove的bug                setTimeout(function(){                    _this.redraw(_this.sliceData(_this.data,offset,totalLen,dataNum));                },0)            }            function touchend(event){                gestureStarted = false;                _this.dataOffset = currentOffset;            }        }    }    _.Scale = Scale;})(JChart);

 

移动端图表插件jChart.js的修改