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


    bug1:

    折线图,设置datasetGesture : true时,Y轴的刻度值居然会变。会变也就算了,居然没地方设置不能变。

    bug2:

    折线图,设置tap.point事件,和datasetGesture : true,拖动时,返回的(data,i,j)这个i居然是以折线表的展现的0为基准。

    注:

    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(/[
    	
    ]/g, " ")
                                .split("<%").join("	")
                                .replace(/((^|%>)[^	]*)'/g, "$1
    ")
                                .replace(/	=(.*?)%>/g, "',$1,'")
                                .split("	").join("');")
                                .split("%>").join("p.push('")
                                .split("
    ").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 = data;//所有的数据
            this.chartData = 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 = this.sliceData(this.data,0,this.data.labels.length,this.config.datasetShowNumber);
                }else{
                    this.chartData = 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 = 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;
                },
                /**
                 * 填充文本,在文本中加入
    可实现换行
                 * @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('
    ');
                    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 = 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 = 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 = data;
            this.chartData = 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 = this.sliceData(this.data,0,this.data.labels.length,this.config.datasetShowNumber);
                }else{
                    this.chartData = 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 = 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 = data;
            var radius,totalData = 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 = this.chartData = 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 = Number.MIN_VALUE;
                var lowerValue = Number.MAX_VALUE;
                for (var i=0; i<data.length; i++){
                    if (data[i].value > upperValue) {upperValue = data[i].value;}
                    if (data[i].value < lowerValue) {lowerValue = 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 = this.chartData = 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 = {
                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 = Number.MIN_VALUE;
                var lowerValue = Number.MAX_VALUE;
                _.each(dataset,function(o){
                    _.each(o.data,function(obj){
                        if(obj > upperValue){upperValue = obj};
                        if (obj < lowerValue) { lowerValue = 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 = 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 = (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 = scale.step * scale.stepValue;
                var adjustedValue = val - scale.start;
                var scalingFactor = _.capValue(adjustedValue/outerValue,1,0);
                return (scaleHop*scale.step) * scalingFactor;
            },
            
            this.sliceData = function(data,offset,len,num){
                var newdata = _.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);
  • 相关阅读:
    go语言入门(3)运算符及流程控制
    go语言入门(2)数据类型
    go语言入门(1)
    ubuntu上软件下载慢,github下载慢
    密码基础知识(2)以RSA为例说明加密、解密、签名、验签
    让你减少焦虑的一首英文小诗
    使用脚本启动fabric时出错
    Hyperledger Fabric(5)ChainCode的编写步骤
    Hyperledger Fabric(4)链码ChainCode
    设计题专题总结
  • 原文地址:https://www.cnblogs.com/samwu/p/4208597.html
Copyright © 2020-2023  润新知