From 86e615628274841d7f1be206d6cec678948ce151 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 May 2016 14:49:20 +0200 Subject: [PATCH] =?UTF-8?q?#14;=20Prototype=20du=20graphique=20pour=20le?= =?UTF-8?q?=20'type=20de=20communication'=20(entrant,=20sortant,=20manqu?= =?UTF-8?q?=C3=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- automate.php | 11 +- config/dispatcher-tree.json | 2 +- config/modules.json | 14 +- js/lib/_charts-min.js | 304 + js/lib/_charts.js | 9214 +++++++++++++++++++++++++ manager/module/chart.php | 85 +- manager/module/chart/networkChart.php | 229 - test-charts/communication-type.php | 57 + view/js/charts.js | 1 - view/js/input-min.js | 46 +- view/js/input.js | 48 +- 11 files changed, 9688 insertions(+), 323 deletions(-) create mode 100644 js/lib/_charts-min.js create mode 100644 js/lib/_charts.js delete mode 100644 manager/module/chart/networkChart.php create mode 100644 test-charts/communication-type.php diff --git a/automate.php b/automate.php index 61a48aa..16b6e97 100755 --- a/automate.php +++ b/automate.php @@ -91,13 +91,12 @@ /* [4] Test download via AJAX =========================================================*/ - // $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; - $req = new ModuleRequest('download/multiple', array( - 'phone' => true, - 'all' => false - )); + $db = new lightdb('phone_db', __ROOT__.'/src/dynamic/'); + var_dump( array_keys($db->index())); + $db->close(); + $req = new ModuleRequest('chart/communication_type', array( 'subject' => 273 )); - $res = $req->download(); + $res = $req->dispatch(); if( $res->error != ManagerError::Success ) var_dump( ManagerError::explicit($res->error) ); diff --git a/config/dispatcher-tree.json b/config/dispatcher-tree.json index 8df3bcf..12b0ff0 100755 --- a/config/dispatcher-tree.json +++ b/config/dispatcher-tree.json @@ -9,7 +9,7 @@ "css" : "/css", "js" : "/js", - "highcharts": "/js/lib/highcharts", + "highcharts": "/js/lib/highcharts/js", "sigma": "/js/lib/sigma", "upload": "/src/upload" diff --git a/config/modules.json b/config/modules.json index 8f472c6..9af840c 100755 --- a/config/modules.json +++ b/config/modules.json @@ -106,17 +106,11 @@ "chart": { - "network_data": { - "description": "Renvoie un jeu de donnees fictif pour un graphique de type #network.", - "permissions": [], - "parameters": {} - }, - - "network_render": { - "description": "Renvoie le contenu du svg representant un graphique de type #network avec les donnees recues.", - "permissions": [], + "communication_type": { + "description": "Renvoie les données pour un graphique sur les types de communications", + "permissions": ["admin"], "parameters": { - "dataset": { "description": "Dataset au bon format pour un graphique de type #network." } + "subject": { "description": "Identifiant du sujet à étudier,", "type": "id" } } } }, diff --git a/js/lib/_charts-min.js b/js/lib/_charts-min.js new file mode 100644 index 0000000..b1ec7cb --- /dev/null +++ b/js/lib/_charts-min.js @@ -0,0 +1,304 @@ +(function e$$0(m,n,f){function e(a,h){if(!n[a]){if(!m[a]){var k="function"==typeof require&&require;if(!h&&k)return k(a,!0);if(c)return c(a,!0);k=Error("Cannot find module '"+a+"'");throw k.code="MODULE_NOT_FOUND",k;}k=n[a]={exports:{}};m[a][0].call(k.exports,function(c){var k=m[a][1][c];return e(k?k:c)},k,k.exports,e$$0,m,n,f)}return n[a].exports}for(var c="function"==typeof require&&require,a=0;al&&(l+=360);c=(a+h)/2;return[l,100*(h==a?0:.5>=c?e/(h+a):e/(2-h-a)),100*c]}function e(a){var c=a[0],d=a[1];a=a[2];var k=Math.min(c,d,a),h=Math.max(c,d,a),e=h-k,l;h==k?l=0:c==h?l=(d-a)/e:d==h?l=2+(a-c)/e:a==h&&(l=4+(c-d)/e);l=Math.min(60*l,360);0>l&&(l+=360);return[l,0==h?0:e/h*1E3/10,h/255*1E3/10]}function c(a){var c=a[0],d=a[1],k=a[2];a=f(a)[0]; +var h=1/255*Math.min(c,Math.min(d,k)),k=1-1/255*Math.max(c,Math.max(d,k));return[a,100*h,100*k]}function a(a){var c=a[0]/255,d=a[1]/255;a=a[2]/255;var k;k=Math.min(1-c,1-d,1-a);return[100*((1-c-k)/(1-k)||0),100*((1-d-k)/(1-k)||0),100*((1-a-k)/(1-k)||0),100*k]}function d(a){return I[JSON.stringify(a)]}function h(a){var c=a[0]/255,d=a[1]/255;a=a[2]/255;c=.04045a?a*(1+d):a+d-a*d;a=2*a-d;h=[0,0,0];for(var e=0;3>e;e++)k=c+1/3*-(e-1),0>k&& +k++,16*k?a+6*(d-a)*k:1>2*k?d:2>3*k?a+(d-a)*(2/3-k)*6:a,h[e]=255*k;return h}function q(a){var c=a[0]/60,d=a[1]/100;a=a[2]/100;var k=Math.floor(c)%6,h=c-Math.floor(c),c=255*a*(1-d),e=255*a*(1-d*h),d=255*a*(1-d*(1-h));a*=255;switch(k){case 0:return[a,d,c];case 1:return[e,a,c];case 2:return[c,a,d];case 3:return[c,e,a];case 4:return[d,c,a];case 5:return[a,c,e]}}function t(a){var c=a[0]/360,d=a[1]/100,k=a[2]/100;a=d+k;1=c?(c=100*c/903.3,e=c/100*7.787+16/116):(c=100*Math.pow((c+16)/116,3),e=Math.pow(c/100,1/3));k=.008856>=k/95.047?k=95.047*(d/500+e-16/116)/7.787:95.047*Math.pow(d/500+e,3);h=.008859>=h/108.883?h=108.883*(e-a/200-16/116)/7.787:108.883*Math.pow(e-a/200,3);return[k,c,h]}function v(a){var c=a[0],d=a[1];a=a[2];var k;k=360*Math.atan2(a,d)/2/Math.PI;0>k&&(k+=360);return[c,Math.sqrt(d*d+a*a),k]}function A(a){return x(E(a))}function F(a){var c=a[0],d=a[1];a=a[2]/360*2*Math.PI;return[c, +d*Math.cos(a),d*Math.sin(a)]}m.exports={rgb2hsl:f,rgb2hsv:e,rgb2hwb:c,rgb2cmyk:a,rgb2keyword:d,rgb2xyz:h,rgb2lab:k,rgb2lch:function(a){return v(k(a))},hsl2rgb:l,hsl2hsv:function(a){var c=a[0],d=a[1]/100;a=a[2]/100;if(0===a)return[0,0,0];a*=2;d*=1>=a?a:2-a;return[c,2*d/(a+d)*100,(a+d)/2*100]},hsl2hwb:function(a){return c(l(a))},hsl2cmyk:function(c){return a(l(c))},hsl2keyword:function(a){return d(l(a))},hsv2rgb:q,hsv2hsl:function(a){var c=a[0],d=a[1]/100,k=a[2]/100;a=(2-d)*k;d=(d=d*k/(1>=a?a:2-a))|| +0;return[c,100*d,a/2*100]},hsv2hwb:function(a){return c(q(a))},hsv2cmyk:function(c){return a(q(c))},hsv2keyword:function(a){return d(q(a))},hwb2rgb:t,hwb2hsl:function(a){return f(t(a))},hwb2hsv:function(a){return e(t(a))},hwb2cmyk:function(c){return a(t(c))},hwb2keyword:function(a){return d(t(a))},cmyk2rgb:w,cmyk2hsl:function(a){return f(w(a))},cmyk2hsv:function(a){return e(w(a))},cmyk2hwb:function(a){return c(w(a))},cmyk2keyword:function(a){return d(w(a))},keyword2rgb:function(a){return z[a]},keyword2hsl:function(a){return f(z[a])}, +keyword2hsv:function(a){return e(z[a])},keyword2hwb:function(a){return c(z[a])},keyword2cmyk:function(c){return a(z[c])},keyword2lab:function(a){return k(z[a])},keyword2xyz:function(a){return h(z[a])},xyz2rgb:x,xyz2lab:u,xyz2lch:function(a){return v(u(a))},lab2xyz:E,lab2rgb:A,lab2lch:v,lch2lab:F,lch2xyz:function(a){return E(F(a))},lch2rgb:function(a){return A(F(a))}};var z={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245, +245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139, +0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220, +220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211, +211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123, +104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255, +218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130, +180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},I={},y;for(y in z)I[JSON.stringify(z[y])]=y},{}],3:[function(p,m,n){var f=p("./conversions"),e=function(){return new a},c;for(c in f)e[c+"Raw"]=function(a){return function(c){"number"==typeof c&&(c=Array.prototype.slice.call(arguments));return f[a](c)}}(c),n=/(\w+)2(\w+)/.exec(c), +p=n[1],n=n[2],e[p]=e[p]||{},e[p][n]=e[c]=function(a){return function(c){"number"==typeof c&&(c=Array.prototype.slice.call(arguments));var k=f[a](c);if("string"==typeof k||void 0===k)return k;for(var e=0;ea.length?"0"+a:a}var q=p("color-name");m.exports={getRgba:f,getHsla:e,getRgb:function(a){return(a=f(a))&&a.slice(0,3)},getHsl:function(a){return(a=e(a))&&a.slice(0,3)},getHwb:c,getAlpha:function(a){var d=f(a);if(d||(d=e(a))||(d=c(a)))return d[3]},hexString:function(a){return"#"+l(a[0])+l(a[1])+l(a[2])},rgbString:function(c,d){return 1>d||c[3]&&1>c[3]?a(c,d):"rgb("+c[0]+", "+c[1]+", "+c[2]+")"},rgbaString:a, +percentString:function(a,c){return 1>c||a[3]&&1>a[3]?d(a,c):"rgb("+Math.round(a[0]/255*100)+"%, "+Math.round(a[1]/255*100)+"%, "+Math.round(a[2]/255*100)+"%)"},percentaString:d,hslString:function(a,c){return 1>c||a[3]&&1>a[3]?h(a,c):"hsl("+a[0]+", "+a[1]+"%, "+a[2]+"%)"},hslaString:h,hwbString:function(a,c){void 0===c&&(c=void 0!==a[3]?a[3]:1);return"hwb("+a[0]+", "+a[1]+"%, "+a[2]+"%"+(void 0!==c&&1!==c?", "+c:"")+")"},keyword:function(a){return t[a.slice(0,3)]}};var t={},w;for(w in q)t[q[w]]=w}, +{"color-name":5}],5:[function(p,m,n){m.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255], +darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105, +105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124, +252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255, +0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255, +69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45], +silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},{}],6:[function(p,m,n){var f=p("color-convert"),e=p("color-string"),c=function(a){if(a instanceof +c)return a;if(!(this instanceof c))return new c(a);this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1};if("string"==typeof a){var d=e.getRgba(a);if(d)this.setValues("rgb",d);else if(d=e.getHsla(a))this.setValues("hsl",d);else if(d=e.getHwb(a))this.setValues("hwb",d);else throw Error('Unable to parse color from string "'+a+'"');}else if("object"==typeof a)if(d=a,void 0!==d.r||void 0!==d.red)this.setValues("rgb",d);else if(void 0!==d.l||void 0!==d.lightness)this.setValues("hsl", +d);else if(void 0!==d.v||void 0!==d.value)this.setValues("hsv",d);else if(void 0!==d.w||void 0!==d.whiteness)this.setValues("hwb",d);else if(void 0!==d.c||void 0!==d.cyan)this.setValues("cmyk",d);else throw Error("Unable to parse color from object "+JSON.stringify(a));};c.prototype={rgb:function(a){return this.setSpace("rgb",arguments)},hsl:function(a){return this.setSpace("hsl",arguments)},hsv:function(a){return this.setSpace("hsv",arguments)},hwb:function(a){return this.setSpace("hwb",arguments)}, +cmyk:function(a){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){return 1!==this.values.alpha?this.values.hwb.concat([this.values.alpha]):this.values.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){return this.values.rgb.concat([this.values.alpha])},hslaArray:function(){return this.values.hsl.concat([this.values.alpha])},alpha:function(a){if(void 0=== +a)return this.values.alpha;this.setValues("alpha",a);return this},red:function(a){return this.setChannel("rgb",0,a)},green:function(a){return this.setChannel("rgb",1,a)},blue:function(a){return this.setChannel("rgb",2,a)},hue:function(a){return this.setChannel("hsl",0,a)},saturation:function(a){return this.setChannel("hsl",1,a)},lightness:function(a){return this.setChannel("hsl",2,a)},saturationv:function(a){return this.setChannel("hsv",1,a)},whiteness:function(a){return this.setChannel("hwb",1,a)}, +blackness:function(a){return this.setChannel("hwb",2,a)},value:function(a){return this.setChannel("hsv",2,a)},cyan:function(a){return this.setChannel("cmyk",0,a)},magenta:function(a){return this.setChannel("cmyk",1,a)},yellow:function(a){return this.setChannel("cmyk",2,a)},black:function(a){return this.setChannel("cmyk",3,a)},hexString:function(){return e.hexString(this.values.rgb)},rgbString:function(){return e.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return e.rgbaString(this.values.rgb, +this.values.alpha)},percentString:function(){return e.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return e.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return e.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return e.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return e.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){return this.values.rgb[0]<<16|this.values.rgb[1]<<8|this.values.rgb[2]},luminosity:function(){for(var a= +this.values.rgb,c=[],h=0;h=k?k/12.92:Math.pow((k+.055)/1.055,2.4)}return.2126*c[0]+.7152*c[1]+.0722*c[2]},contrast:function(a){var c=this.luminosity();a=a.luminosity();return c>a?(c+.05)/(a+.05):(a+.05)/(c+.05)},level:function(a){a=this.contrast(a);return 7.1<=a?"AAA":4.5<=a?"AA":""},dark:function(){var a=this.values.rgb;return 128>(299*a[0]+587*a[1]+114*a[2])/1E3},light:function(){return!this.dark()},negate:function(){for(var a=[],c=0;3>c;c++)a[c]=255-this.values.rgb[c]; +this.setValues("rgb",a);return this},lighten:function(a){this.values.hsl[2]+=this.values.hsl[2]*a;this.setValues("hsl",this.values.hsl);return this},darken:function(a){this.values.hsl[2]-=this.values.hsl[2]*a;this.setValues("hsl",this.values.hsl);return this},saturate:function(a){this.values.hsl[1]+=this.values.hsl[1]*a;this.setValues("hsl",this.values.hsl);return this},desaturate:function(a){this.values.hsl[1]-=this.values.hsl[1]*a;this.setValues("hsl",this.values.hsl);return this},whiten:function(a){this.values.hwb[1]+= +this.values.hwb[1]*a;this.setValues("hwb",this.values.hwb);return this},blacken:function(a){this.values.hwb[2]+=this.values.hwb[2]*a;this.setValues("hwb",this.values.hwb);return this},greyscale:function(){var a=this.values.rgb,a=.3*a[0]+.59*a[1]+.11*a[2];this.setValues("rgb",[a,a,a]);return this},clearer:function(a){this.setValues("alpha",this.values.alpha-this.values.alpha*a);return this},opaquer:function(a){this.setValues("alpha",this.values.alpha+this.values.alpha*a);return this},rotate:function(a){var c= +this.values.hsl[0],c=(c+a)%360;this.values.hsl[0]=0>c?360+c:c;this.setValues("hsl",this.values.hsl);return this},mix:function(a,c){c=1-(null==c?.5:c);for(var h=2*c-1,k=this.alpha()-a.alpha(),h=((-1==h*k?h:(h+k)/(1+h*k))+1)/2,k=1-h,e=this.rgbArray(),q=a.rgbArray(),f=0;fl.min&&0>l.max?l.getPixelForValue(l.max):0this.chart.data.datasets[c].data[a])for(var k=0;kl.data[a]?l.data[a]:0)}else for(k=0;k= +d.min&&0<=d.max||0<=d.min&&0>=d.max?h=d.getPixelForValue(0,0):0>d.min&&0>d.max&&(h=d.getPixelForValue(d.max));return h},getRuler:function(){var c=this.getScaleForId(this.getDataset().xAxisID);this.getScaleForId(this.getDataset().yAxisID);var a=this.getBarCount(),d;d=c.getPixelForTick(1)-c.getPixelForTick(0);for(var e=2;ef.data[c]?l+=f.data[c]||0:k+=f.data[c]||0)}return 0>h?d.getPixelForValue(l+h):d.getPixelForValue(k+h)}return d.getPixelForValue(h)},draw:function(c){var a=c||1;e.each(this.getDataset().metaData,function(c,e){var k=this.getDataset().data[e];null===k||void 0===k||isNaN(k)||c.transition(a).draw()},this)},setHoverStyle:function(c){var a=this.chart.data.datasets[c._datasetIndex],d=c._index;c._model.backgroundColor= +c.custom&&c.custom.hoverBackgroundColor?c.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(a.hoverBackgroundColor,d,e.color(c._model.backgroundColor).saturate(.5).darken(.1).rgbString());c._model.borderColor=c.custom&&c.custom.hoverBorderColor?c.custom.hoverBorderColor:e.getValueAtIndexOrDefault(a.hoverBorderColor,d,e.color(c._model.borderColor).saturate(.5).darken(.1).rgbString());c._model.borderWidth=c.custom&&c.custom.hoverBorderWidth?c.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(a.hoverBorderWidth, +d,c._model.borderWidth)},removeHoverStyle:function(c){var a=c._index;c._model.backgroundColor=c.custom&&c.custom.backgroundColor?c.custom.backgroundColor:e.getValueAtIndexOrDefault(this.getDataset().backgroundColor,a,this.chart.options.elements.rectangle.backgroundColor);c._model.borderColor=c.custom&&c.custom.borderColor?c.custom.borderColor:e.getValueAtIndexOrDefault(this.getDataset().borderColor,a,this.chart.options.elements.rectangle.borderColor);c._model.borderWidth=c.custom&&c.custom.borderWidth? +c.custom.borderWidth:e.getValueAtIndexOrDefault(this.getDataset().borderWidth,a,this.chart.options.elements.rectangle.borderWidth)}})}},{}],16:[function(p,m,n){m.exports=function(f){var e=f.helpers;f.defaults.bubble={hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(c,a){return""},label:function(c,a){var d=a.datasets[c.datasetIndex].data[c.index];return(a.datasets[c.datasetIndex].label|| +"")+": ("+d.x+", "+d.y+", "+d.r+")"}}}};f.controllers.bubble=f.DatasetController.extend({addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[];e.each(this.getDataset().data,function(c,a){this.getDataset().metaData[a]=this.getDataset().metaData[a]||new f.elements.Point({_chart:this.chart.chart,_datasetIndex:this.index,_index:a})},this)},addElementAndReset:function(c){this.getDataset().metaData=this.getDataset().metaData||[];var a=new f.elements.Point({_chart:this.chart.chart, +_datasetIndex:this.index,_index:c});this.updateElement(a,c,!0);this.getDataset().metaData.splice(c,0,a)},update:function(c){var a=this.getDataset().metaData,d=this.getScaleForId(this.getDataset().yAxisID);this.getScaleForId(this.getDataset().xAxisID);0>d.min&&0>d.max?d.getPixelForValue(d.max):0h.min&&0>h.max?h.getPixelForValue(h.max):0');if(c.data.datasets.length)for(var d=0;d'),c.data.labels[d]&&a.push(c.data.labels[d]),a.push("");a.push("");return a.join("")},legend:{labels:{generateLabels:function(c){return c.labels.length&& +c.datasets.length?c.labels.map(function(a,d){return{text:a,fillStyle:c.datasets[0].backgroundColor[d],hidden:isNaN(c.datasets[0].data[d]),index:d}}):[]}},onClick:function(c,a){e.each(this.chart.data.datasets,function(c){c.metaHiddenData=c.metaHiddenData||[];var e=a.index;isNaN(c.data[e])?isNaN(c.metaHiddenData[e])||(c.data[e]=c.metaHiddenData[e]):(c.metaHiddenData[e]=c.data[e],c.data[e]=NaN)});this.chart.update()}},cutoutPercentage:50,rotation:-.5*Math.PI,circumference:2*Math.PI,tooltips:{callbacks:{title:function(){return""}, +label:function(c,a){return a.labels[c.index]+": "+a.datasets[c.datasetIndex].data[c.index]}}}};f.defaults.pie=e.clone(f.defaults.doughnut);e.extend(f.defaults.pie,{cutoutPercentage:0});f.controllers.doughnut=f.controllers.pie=f.DatasetController.extend({linkScales:function(){},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[];e.each(this.getDataset().data,function(c,a){this.getDataset().metaData[a]=this.getDataset().metaData[a]||new f.elements.Arc({_chart:this.chart.chart, +_datasetIndex:this.index,_index:a})},this)},addElementAndReset:function(c,a){this.getDataset().metaData=this.getDataset().metaData||[];var d=new f.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:c});a&&e.isArray(this.getDataset().backgroundColor)&&this.getDataset().backgroundColor.splice(c,0,a);this.updateElement(d,c,!0);this.getDataset().metaData.splice(c,0,d)},getVisibleDatasetCount:function(){return e.where(this.chart.data.datasets,function(c){return e.isDatasetVisible(c)}).length}, +getRingIndex:function(c){for(var a=0,d=0;d= +Math.PI?-1:h<-Math.PI?1:0),q=h+this.chart.options.circumference,f=Math.cos(h),w=Math.sin(h),x=Math.cos(q),u=Math.sin(q),m=this.chart.options.cutoutPercentage/100,k=h<=-Math.PI&&-Math.PI<=q||h<=Math.PI&&Math.PI<=q?-1:Math.min(f*(0>f?1:m),x*(0>x?1:m)),l=h<=.5*-Math.PI&&.5*-Math.PI<=q||h<=1.5*Math.PI&&1.5*Math.PI<=q?-1:Math.min(w*(0>w?1:m),u*(0>u?1:m)),f=0>=h&&0<=q||h<=2*Math.PI&&2*Math.PI<=q?1:Math.max(f*(0h.min&&0>h.max?h.getPixelForValue(h.max):0h.min&&0>h.max?h.getPixelForValue(h.max):0f.data[a]?l+=f.data[a]||0:k+=f.data[a]||0)}return 0>c?h.getPixelForValue(l+c):h.getPixelForValue(k+c)}return h.getPixelForValue(c)},updateBezierControlPoints:function(){e.each(this.getDataset().metaData,function(c,a){var d=e.splineCurve(e.previousItem(this.getDataset().metaData,a)._model,c._model,e.nextItem(this.getDataset().metaData, +a)._model,c._model.tension);c._model.controlPointPreviousX=Math.max(Math.min(d.previous.x,this.chart.chartArea.right),this.chart.chartArea.left);c._model.controlPointPreviousY=Math.max(Math.min(d.previous.y,this.chart.chartArea.bottom),this.chart.chartArea.top);c._model.controlPointNextX=Math.max(Math.min(d.next.x,this.chart.chartArea.right),this.chart.chartArea.left);c._model.controlPointNextY=Math.max(Math.min(d.next.y,this.chart.chartArea.bottom),this.chart.chartArea.top);c.pivot()},this)},draw:function(c){var a= +c||1;e.each(this.getDataset().metaData,function(c){c.transition(a)});this.chart.options.showLines&&this.getDataset().metaDataset.transition(a).draw();e.each(this.getDataset().metaData,function(a){a.draw()})},setHoverStyle:function(c){var a=this.chart.data.datasets[c._datasetIndex],d=c._index;c._model.radius=c.custom&&c.custom.hoverRadius?c.custom.hoverRadius:e.getValueAtIndexOrDefault(a.pointHoverRadius,d,this.chart.options.elements.point.hoverRadius);c._model.backgroundColor=c.custom&&c.custom.hoverBackgroundColor? +c.custom.hoverBackgroundColor:e.getValueAtIndexOrDefault(a.pointHoverBackgroundColor,d,e.color(c._model.backgroundColor).saturate(.5).darken(.1).rgbString());c._model.borderColor=c.custom&&c.custom.hoverBorderColor?c.custom.hoverBorderColor:e.getValueAtIndexOrDefault(a.pointHoverBorderColor,d,e.color(c._model.borderColor).saturate(.5).darken(.1).rgbString());c._model.borderWidth=c.custom&&c.custom.hoverBorderWidth?c.custom.hoverBorderWidth:e.getValueAtIndexOrDefault(a.pointHoverBorderWidth,d,c._model.borderWidth)}, +removeHoverStyle:function(c){var a=c._index;c._model.radius=c.custom&&c.custom.radius?c.custom.radius:e.getValueAtIndexOrDefault(this.getDataset().radius,a,this.chart.options.elements.point.radius);c._model.backgroundColor=this.getPointBackgroundColor(c,a);c._model.borderColor=this.getPointBorderColor(c,a);c._model.borderWidth=this.getPointBorderWidth(c,a)}})}},{}],19:[function(p,m,n){m.exports=function(f){var e=f.helpers;f.defaults.polarArea={scale:{type:"radialLinear",lineArc:!0},animateRotate:!0, +animateScale:!0,aspectRatio:1,legendCallback:function(c){var a=[];a.push('
    ');if(c.data.datasets.length)for(var d=0;d'),c.data.labels[d]&&a.push(c.data.labels[d]),a.push("");a.push("
");return a.join("")},legend:{labels:{generateLabels:function(c){return c.labels.length&&c.datasets.length?c.labels.map(function(a,d){return{text:a,fillStyle:c.datasets[0].backgroundColor[d], +hidden:isNaN(c.datasets[0].data[d]),index:d}}):[]}},onClick:function(c,a){e.each(this.chart.data.datasets,function(c){c.metaHiddenData=c.metaHiddenData||[];var e=a.index;isNaN(c.data[e])?isNaN(c.metaHiddenData[e])||(c.data[e]=c.metaHiddenData[e]):(c.metaHiddenData[e]=c.data[e],c.data[e]=NaN)});this.chart.update()}},tooltips:{callbacks:{title:function(){return""},label:function(c,a){return a.labels[c.index]+": "+c.yLabel}}}};f.controllers.polarArea=f.DatasetController.extend({linkScales:function(){}, +addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[];e.each(this.getDataset().data,function(c,a){this.getDataset().metaData[a]=this.getDataset().metaData[a]||new f.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:a})},this)},addElementAndReset:function(c){this.getDataset().metaData=this.getDataset().metaData||[];var a=new f.elements.Arc({_chart:this.chart.chart,_datasetIndex:this.index,_index:c});this.updateElement(a,c,!0);this.getDataset().metaData.splice(c, +0,a)},getVisibleDatasetCount:function(){return e.where(this.chart.data.datasets,function(c){return e.isDatasetVisible(c)}).length},update:function(c){this.chart.outerRadius=Math.max((Math.min(this.chart.chartArea.right-this.chart.chartArea.left,this.chart.chartArea.bottom-this.chart.chartArea.top)-this.chart.options.elements.arc.borderWidth/2)/2,0);this.chart.innerRadius=Math.max(this.chart.options.cutoutPercentage?this.chart.outerRadius/100*this.chart.options.cutoutPercentage:1,0);this.chart.radiusLength= +(this.chart.outerRadius-this.chart.innerRadius)/this.getVisibleDatasetCount();this.getDataset().total=0;e.each(this.getDataset().data,function(a){this.getDataset().total+=Math.abs(a)},this);this.outerRadius=this.chart.outerRadius-this.chart.radiusLength*this.index;this.innerRadius=this.outerRadius-this.chart.radiusLength;e.each(this.getDataset().metaData,function(a,d){this.updateElement(a,d,c)},this)},updateElement:function(c,a,d){for(var h=this.calculateCircumference(this.getDataset().data[a]),k= +(this.chart.chartArea.left+this.chart.chartArea.right)/2,l=(this.chart.chartArea.top+this.chart.chartArea.bottom)/2,q=0,f=0;fh.min&&0>h.max?h.getPointPositionForValue(0,h.max):0this.animations[d].animationObject.numSteps&&(this.animations[d].animationObject.currentStep=this.animations[d].animationObject.numSteps),this.animations[d].animationObject.render(this.animations[d].chartInstance,this.animations[d].animationObject), +this.animations[d].animationObject.onAnimationProgress&&this.animations[d].animationObject.onAnimationProgress.call&&this.animations[d].animationObject.onAnimationProgress.call(this.animations[d].chartInstance,this.animations[d]),this.animations[d].animationObject.currentStep===this.animations[d].animationObject.numSteps?(this.animations[d].animationObject.onAnimationComplete&&this.animations[d].animationObject.onAnimationComplete.call&&this.animations[d].animationObject.onAnimationComplete.call(this.animations[d].chartInstance, +this.animations[d]),this.animations[d].chartInstance.animating=!1,this.animations.splice(d,1)):++d;c=(Date.now()-c)/this.frameDuration;this.dropFrames+=c;0a)for(;a=h[k].length||!h[k][c].type?h[k].push(d.configMerge(l,a)):h[k][c]=a.type&&a.type!==h[k][c].type?d.configMerge(h[k][c],l,a):d.configMerge(h[k][c],a)}):(h[k]=[],d.each(a,function(a){var c=d.getValueOrDefault(a.type,"xAxes"===k?"category":"linear");h[k].push(d.configMerge(e.scaleService.getScaleDefaults(c),a))})):h.hasOwnProperty(k)&&"object"===typeof h[k]&&null!==h[k]&&"object"===typeof a?h[k]=d.configMerge(h[k],a):h[k]=a)});return h}; +d.getValueAtIndexOrDefault=function(a,c,e){return void 0===a||null===a?e:d.isArray(a)?c=a.length-1?a[0]:a[c+1]:c>=a.length-1?a[a.length-1]:a[c+1]};d.previousItem=function(a,c,d){return d?0>=c?a[a.length-1]:a[c-1]:0>=c?a[0]: +a[c-1]};d.niceNum=function(a,c){var e=Math.floor(d.log10(a)),h=a/Math.pow(10,e);return(c?1.5>h?1:3>h?2:7>h?5:10:1>=h?1:2>=h?2:5>=h?5:10)*Math.pow(10,e)};var h=d.easingEffects={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=.5)?.5*a*a:-.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=.5)?.5*a*a*a:.5*((a-= +2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=.5)?.5*a*a*a*a:-.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=.5)?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-.5* +(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0===a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1===a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0===a?0:1===a?1:1>(a/=.5)?.5*Math.pow(2,10*(a-1)):.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=.5)?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c= +1.70158,d=0,e=1;if(0===a)return 0;if(1===(a/=1))return 1;d||(d=.3);ea?-.5*e*Math.pow(2,10*--a)*Math.sin(2*(1*a-c)*Math.PI/d):e*Math.pow(2,-10*--a)*Math.sin(2*(1*a-c)*Math.PI/d)*.5+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var c=1.70158;return 1>(a/=.5)?.5*a*a*(((c*=1.525)+1)*a-c):.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},easeInBounce:function(a){return 1-h.easeOutBounce(1-a)},easeOutBounce:function(a){return(a/= +1)<1/2.75?7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)*a+.75):a<2.5/2.75?1*(7.5625*(a-=2.25/2.75)*a+.9375):1*(7.5625*(a-=2.625/2.75)*a+.984375)},easeInOutBounce:function(a){return.5>a?.5*h.easeInBounce(2*a):.5*h.easeOutBounce(2*a-1)+.5}};d.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){return window.setTimeout(a,1E3/60)}}();d.cancelAnimFrame= +function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(a){return window.clearTimeout(a,1E3/60)}}();d.getRelativePosition=function(a,c){var e,h;h=a.originalEvent||a;var f=a.currentTarget||a.srcElement,m=f.getBoundingClientRect();h.touches&&0f&&(f=d)}});c=h.garbageCollect.length/2;if(c>e.length){for(e=0;e');for(var a=0;a'),e.data.datasets[a].label&&c.push(e.data.datasets[a].label),c.push("");c.push("");return c.join("")}}};return f}},{}],27:[function(p,m,n){m.exports=function(f){var e=f.helpers;f.layoutService={defaults:{},addBox:function(c,a){c.boxes||(c.boxes=[]);c.boxes.push(a)},removeBox:function(c,a){c.boxes&&c.boxes.splice(c.boxes.indexOf(a), +1)},update:function(c,a,d){function h(a){var c,d=a.isHorizontal();d?(c=a.update(a.options.fullWidth?v:y,I),D-=c.height):(c=a.update(z,F),y-=c.width);N.push({horizontal:d,minSize:c,box:a})}function k(a){var c=e.findNextWhere(N,function(c){return c.box===a});c&&(a.isHorizontal()?a.update(a.options.fullWidth?v:y,A/2,{left:B,right:G,top:0,bottom:0}):a.update(c.minSize.width,D))}function f(a){var c=e.findNextWhere(N,function(c){return c.box===a}),d={left:0,right:0,top:C,bottom:H};c&&a.update(c.minSize.width, +D,d)}function q(c){c.isHorizontal()?(c.left=c.options.fullWidth?0:B,c.right=c.options.fullWidth?a-0:B+y,c.top=J,c.bottom=J+c.height,J=c.bottom):(c.left=K,c.right=K+c.width,c.top=C,c.bottom=C+D,K=c.right)}if(c){var t=e.where(c.boxes,function(a){return"left"===a.options.position}),m=e.where(c.boxes,function(a){return"right"===a.options.position}),n=e.where(c.boxes,function(a){return"top"===a.options.position}),p=e.where(c.boxes,function(a){return"bottom"===a.options.position}),E=e.where(c.boxes,function(a){return"chartArea"=== +a.options.position});n.sort(function(a,c){return(c.options.fullWidth?1:0)-(a.options.fullWidth?1:0)});p.sort(function(a,c){return(a.options.fullWidth?1:0)-(c.options.fullWidth?1:0)});var v=a-0,A=d-0,F=A/2,z=(a-v/2)/(t.length+m.length),I=(d-F)/(n.length+p.length),y=v,D=A,N=[];e.each(t.concat(m,n,p),h);var B=0,G=0,C=0,H=0;e.each(t.concat(m),k);e.each(t,function(a){B+=a.width});e.each(m,function(a){G+=a.width});e.each(n.concat(p),k);e.each(n,function(a){C+=a.height});e.each(p,function(a){H+=a.height}); +e.each(t.concat(m),f);H=C=G=B=0;e.each(t,function(a){B+=a.width});e.each(m,function(a){G+=a.width});e.each(n,function(a){C+=a.height});e.each(p,function(a){H+=a.height});var L=d-C-H,M=a-B-G;if(M!==y||L!==D)e.each(t,function(a){a.height=L}),e.each(m,function(a){a.height=L}),e.each(n,function(a){a.width=M}),e.each(p,function(a){a.width=M}),D=L,y=M;var K=0,J=0;e.each(t.concat(n),q);K+=y;J+=D;e.each(m,q);e.each(p,q);c.chartArea={left:B,top:C,right:B+y,bottom:C+D};e.each(E,function(a){a.left=c.chartArea.left; +a.top=c.chartArea.top;a.right=c.chartArea.right;a.bottom=c.chartArea.bottom;a.update(y,D)})}}}}},{}],28:[function(p,m,n){m.exports=function(f){var e=f.helpers;f.defaults.global.legend={display:!0,position:"top",fullWidth:!0,reverse:!1,onClick:function(c,a){var d=this.chart.data.datasets[a.datasetIndex];d.hidden=!d.hidden;this.chart.update()},labels:{boxWidth:40,padding:10,generateLabels:function(c){return e.isArray(c.datasets)?c.datasets.map(function(a,c){return{text:a.label,fillStyle:a.backgroundColor, +hidden:a.hidden,lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,datasetIndex:c}},this):[]}}};f.Legend=f.Element.extend({initialize:function(c){e.extend(this,c);this.legendHitBoxes=[];this.doughnutMode=!1},beforeUpdate:e.noop,update:function(c,a,d){this.beforeUpdate();this.maxWidth=c;this.maxHeight=a;this.margins=d;this.beforeSetDimensions();this.setDimensions();this.afterSetDimensions();this.beforeBuildLabels(); +this.buildLabels();this.afterBuildLabels();this.beforeFit();this.fit();this.afterFit();this.afterUpdate();return this.minSize},afterUpdate:e.noop,beforeSetDimensions:e.noop,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height);this.paddingBottom=this.paddingRight=this.paddingTop=this.paddingLeft=0;this.minSize={width:0,height:0}},afterSetDimensions:e.noop,beforeBuildLabels:e.noop,buildLabels:function(){this.legendItems= +this.options.labels.generateLabels.call(this,this.chart.data);this.options.reverse&&this.legendItems.reverse()},afterBuildLabels:e.noop,beforeFit:e.noop,fit:function(){var c=this.ctx,a=e.getValueOrDefault(this.options.labels.fontSize,f.defaults.global.defaultFontSize),d=e.getValueOrDefault(this.options.labels.fontStyle,f.defaults.global.defaultFontStyle),h=e.getValueOrDefault(this.options.labels.fontFamily,f.defaults.global.defaultFontFamily),d=e.fontString(a,d,h);this.legendHitBoxes=[];this.isHorizontal()? +this.minSize.width=this.maxWidth:this.minSize.width=this.options.display?10:0;this.isHorizontal()?this.minSize.height=this.options.display?10:0:this.minSize.height=this.maxHeight;if(this.options.display&&this.isHorizontal()){this.lineWidths=[0];var k=this.legendItems.length?a+this.options.labels.padding:0;c.textAlign="left";c.textBaseline="top";c.font=d;e.each(this.legendItems,function(d,e){var h=this.options.labels.boxWidth+a/2+c.measureText(d.text).width;this.lineWidths[this.lineWidths.length-1]+ +h+this.options.labels.padding>=this.width&&(k+=a+this.options.labels.padding,this.lineWidths[this.lineWidths.length]=this.left);this.legendHitBoxes[e]={left:0,top:0,width:h,height:a};this.lineWidths[this.lineWidths.length-1]+=h+this.options.labels.padding},this);this.minSize.height+=k}this.width=this.minSize.width;this.height=this.minSize.height},afterFit:e.noop,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){if(this.options.display){var c= +this.ctx,a=this.left+(this.width-this.lineWidths[0])/2,d=this.top+this.options.labels.padding,h=0,k=e.getValueOrDefault(this.options.labels.fontColor,f.defaults.global.defaultFontColor),l=e.getValueOrDefault(this.options.labels.fontSize,f.defaults.global.defaultFontSize),q=e.getValueOrDefault(this.options.labels.fontStyle,f.defaults.global.defaultFontStyle),t=e.getValueOrDefault(this.options.labels.fontFamily,f.defaults.global.defaultFontFamily),q=e.fontString(l,q,t);this.isHorizontal()&&(c.textAlign= +"left",c.textBaseline="top",c.lineWidth=.5,c.strokeStyle=k,c.fillStyle=k,c.font=q,e.each(this.legendItems,function(e,k){var q=c.measureText(e.text).width,t=this.options.labels.boxWidth+l/2+q;a+t>=this.width&&(d+=l+this.options.labels.padding,h++,a=this.left+(this.width-this.lineWidths[h])/2);c.save();var m=function(a,c){return void 0!==a?a:c};c.fillStyle=m(e.fillStyle,f.defaults.global.defaultColor);c.lineCap=m(e.lineCap,f.defaults.global.elements.line.borderCapStyle);c.lineDashOffset=m(e.lineDashOffset, +f.defaults.global.elements.line.borderDashOffset);c.lineJoin=m(e.lineJoin,f.defaults.global.elements.line.borderJoinStyle);c.lineWidth=m(e.lineWidth,f.defaults.global.elements.line.borderWidth);c.strokeStyle=m(e.strokeStyle,f.defaults.global.defaultColor);c.setLineDash&&c.setLineDash(m(e.lineDash,f.defaults.global.elements.line.borderDash));c.strokeRect(a,d,this.options.labels.boxWidth,l);c.fillRect(a,d,this.options.labels.boxWidth,l);c.restore();this.legendHitBoxes[k].left=a;this.legendHitBoxes[k].top= +d;c.fillText(e.text,this.options.labels.boxWidth+l/2+a,d);e.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(this.options.labels.boxWidth+l/2+a,d+l/2),c.lineTo(this.options.labels.boxWidth+l/2+a+q,d+l/2),c.stroke());a+=t+this.options.labels.padding},this))}},handleEvent:function(c){var a=e.getRelativePosition(c,this.chart.chart);if(a.x>=this.left&&a.x<=this.right&&a.y>=this.top&&a.y<=this.bottom)for(var d=0;d=h.left&&a.x<=h.left+h.width&& +a.y>=h.top&&a.y<=h.top+h.height){this.options.onClick&&this.options.onClick.call(this,c,this.legendItems[d]);break}}}})}},{}],29:[function(p,m,n){m.exports=function(f){var e=f.helpers;f.defaults.scale={display:!0,gridLines:{display:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",offsetGridLines:!1},scaleLabel:{labelString:"",display:!1},ticks:{beginAtZero:!1,maxRotation:50,mirror:!1,padding:10,reverse:!1,display:!0,autoSkip:!0, +autoSkipPadding:0,callback:function(c){return""+c}}};f.Scale=f.Element.extend({beforeUpdate:function(){e.callCallback(this.options.beforeUpdate,[this])},update:function(c,a,d){this.beforeUpdate();this.maxWidth=c;this.maxHeight=a;this.margins=e.extend({left:0,right:0,top:0,bottom:0},d);this.beforeSetDimensions();this.setDimensions();this.afterSetDimensions();this.beforeDataLimits();this.determineDataLimits();this.afterDataLimits();this.beforeBuildTicks();this.buildTicks();this.afterBuildTicks();this.beforeTickToLabelConversion(); +this.convertTicksToLabels();this.afterTickToLabelConversion();this.beforeCalculateTickRotation();this.calculateTickRotation();this.afterCalculateTickRotation();this.beforeFit();this.fit();this.afterFit();this.afterUpdate();return this.minSize},afterUpdate:function(){e.callCallback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){e.callCallback(this.options.beforeSetDimensions,[this])},setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width): +(this.height=this.maxHeight,this.top=0,this.bottom=this.height);this.paddingBottom=this.paddingRight=this.paddingTop=this.paddingLeft=0},afterSetDimensions:function(){e.callCallback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){e.callCallback(this.options.beforeDataLimits,[this])},determineDataLimits:e.noop,afterDataLimits:function(){e.callCallback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){e.callCallback(this.options.beforeBuildTicks,[this])},buildTicks:e.noop, +afterBuildTicks:function(){e.callCallback(this.options.afterBuildTicks,[this])},beforeTickToLabelConversion:function(){e.callCallback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){this.ticks=this.ticks.map(function(c,a,d){return this.options.ticks.userCallback?this.options.ticks.userCallback(c,a,d):this.options.ticks.callback(c,a,d)},this)},afterTickToLabelConversion:function(){e.callCallback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){e.callCallback(this.options.beforeCalculateTickRotation, +[this])},calculateTickRotation:function(){var c=e.getValueOrDefault(this.options.ticks.fontSize,f.defaults.global.defaultFontSize),a=e.getValueOrDefault(this.options.ticks.fontStyle,f.defaults.global.defaultFontStyle),d=e.getValueOrDefault(this.options.ticks.fontFamily,f.defaults.global.defaultFontFamily),d=e.fontString(c,a,d);this.ctx.font=d;var a=this.ctx.measureText(this.ticks[0]).width,h=this.ctx.measureText(this.ticks[this.ticks.length-1]).width,k;this.paddingLeft=this.paddingRight=this.labelRotation= +0;if(this.options.display&&this.isHorizontal()){this.paddingRight=h/2+3;this.paddingLeft=a/2+3;this.longestTextCache||(this.longestTextCache={});k=d=e.longestText(this.ctx,d,this.ticks,this.longestTextCache);for(var l,q,h=this.getPixelForTick(1)-this.getPixelForTick(0)-6;k>h&&this.labelRotationthis.yLabelWidth&&(this.paddingLeft=k+c/2);this.paddingRight=c/2;if(q*d> +this.maxHeight){this.labelRotation--;break}this.labelRotation++;k=l*d}}this.margins&&(this.paddingLeft=Math.max(this.paddingLeft-this.margins.left,0),this.paddingRight=Math.max(this.paddingRight-this.margins.right,0))},afterCalculateTickRotation:function(){e.callCallback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){e.callCallback(this.options.beforeFit,[this])},fit:function(){this.minSize={width:0,height:0};var c=e.getValueOrDefault(this.options.ticks.fontSize,f.defaults.global.defaultFontSize), +a=e.getValueOrDefault(this.options.ticks.fontStyle,f.defaults.global.defaultFontStyle),d=e.getValueOrDefault(this.options.ticks.fontFamily,f.defaults.global.defaultFontFamily),a=e.fontString(c,a,d),d=e.getValueOrDefault(this.options.scaleLabel.fontSize,f.defaults.global.defaultFontSize),h=e.getValueOrDefault(this.options.scaleLabel.fontStyle,f.defaults.global.defaultFontStyle),k=e.getValueOrDefault(this.options.scaleLabel.fontFamily,f.defaults.global.defaultFontFamily);e.fontString(d,h,k);this.isHorizontal()? +this.minSize.width=this.isFullWidth()?this.maxWidth-this.margins.left-this.margins.right:this.maxWidth:this.minSize.width=this.options.gridLines.display&&this.options.display?10:0;this.isHorizontal()?this.minSize.height=this.options.gridLines.display&&this.options.display?10:0:this.minSize.height=this.maxHeight;this.options.scaleLabel.display&&(this.isHorizontal()?this.minSize.height+=1.5*d:this.minSize.width+=1.5*d);this.options.ticks.display&&this.options.display&&(this.longestTextCache||(this.longestTextCache= +{}),d=e.longestText(this.ctx,a,this.ticks,this.longestTextCache),this.isHorizontal()?(this.longestLabelWidth=d,d=Math.sin(e.toRadians(this.labelRotation))*this.longestLabelWidth+1.5*c,this.minSize.height=Math.min(this.maxHeight,this.minSize.height+d),this.ctx.font=a,a=this.ctx.measureText(this.ticks[0]).width,d=this.ctx.measureText(this.ticks[this.ticks.length-1]).width,h=Math.cos(e.toRadians(this.labelRotation)),k=Math.sin(e.toRadians(this.labelRotation)),this.paddingLeft=0!==this.labelRotation? +h*a+3:a/2+3,this.paddingRight=0!==this.labelRotation?c/2*k+3:d/2+3):(a=this.maxWidth-this.minSize.width,this.options.ticks.mirror||(d+=this.options.ticks.padding),this.minSize.width=dthis.width-(this.paddingLeft+this.paddingRight)&&(k=1+Math.floor((v/2+this.options.ticks.autoSkipPadding)*this.ticks.length/(this.width-(this.paddingLeft+ +this.paddingRight))));if(q&&this.ticks.length>q)for(;!k||this.ticks.length/(k||1)>q;)k||(k=1),k+=1;l||(k=!1);e.each(this.ticks,function(f,l){var q=this.ticks.length===l+1;if((!(1this.ticks.length)||q)&&void 0!==f&&null!==f){var q=this.getPixelForTick(l),m=this.getPixelForTick(l,this.options.gridLines.offsetGridLines);this.options.gridLines.display&&(l===("undefined"!==typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle= +this.options.gridLines.zeroLineColor,d=!0):d&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,d=!1),q+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(q,A),this.ctx.lineTo(q,F)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(q,a.top),this.ctx.lineTo(q,a.bottom)),this.ctx.stroke());this.options.ticks.display&&(this.ctx.save(),this.ctx.translate(m,h?this.top+12:"top"===this.options.position? +this.bottom-10:this.top+10),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=E,this.ctx.textAlign=h?"right":"center",this.ctx.textBaseline=h?"middle":"top"===this.options.position?"bottom":"top",this.ctx.fillText(f,0,0),this.ctx.restore())}},this);this.options.scaleLabel.display&&(this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillStyle=n,this.ctx.font=u,l=this.left+(this.right-this.left)/2,q="bottom"===this.options.position?this.bottom-p/2:this.top+p/2,this.ctx.fillText(this.options.scaleLabel.labelString, +l,q))}else{d=!0;var z="right"===this.options.position?this.left:this.right-5,I="right"===this.options.position?this.left+5:this.right;e.each(this.ticks,function(h,k){if(void 0!==h&&null!==h){var f=this.getPixelForTick(k);this.options.gridLines.display&&(k===("undefined"!==typeof this.zeroLineIndex?this.zeroLineIndex:0)?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,d=!0):d&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle= +this.options.gridLines.color,d=!1),f+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(z,f),this.ctx.lineTo(I,f)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(a.left,f),this.ctx.lineTo(a.right,f)),this.ctx.stroke());if(this.options.ticks.display){var l=this.getPixelForTick(k,this.options.gridLines.offsetGridLines);this.ctx.save();"left"===this.options.position?this.options.ticks.mirror?(f=this.right+this.options.ticks.padding,this.ctx.textAlign= +"left"):(f=this.right-this.options.ticks.padding,this.ctx.textAlign="right"):this.options.ticks.mirror?(f=this.left-this.options.ticks.padding,this.ctx.textAlign="right"):(f=this.left+this.options.ticks.padding,this.ctx.textAlign="left");this.ctx.translate(f,l);this.ctx.rotate(-1*e.toRadians(this.labelRotation));this.ctx.font=E;this.ctx.textBaseline="middle";this.ctx.fillText(h,0,0);this.ctx.restore()}}},this);this.options.scaleLabel.display&&(l="left"===this.options.position?this.left+p/2:this.right- +p/2,q=this.top+(this.bottom-this.top)/2,m="left"===this.options.position?-.5*Math.PI:.5*Math.PI,this.ctx.save(),this.ctx.translate(l,q),this.ctx.rotate(m),this.ctx.textAlign="center",this.ctx.fillStyle=n,this.ctx.font=u,this.ctx.textBaseline="middle",this.ctx.fillText(this.options.scaleLabel.labelString,0,0),this.ctx.restore())}this.ctx.lineWidth=this.options.gridLines.lineWidth;this.ctx.strokeStyle=this.options.gridLines.color;l=this.left;q=this.right;m=this.top;n=this.bottom;this.isHorizontal()? +(m=n="top"===this.options.position?this.bottom:this.top,m+=e.aliasPixel(this.ctx.lineWidth),n+=e.aliasPixel(this.ctx.lineWidth)):(l=q="left"===this.options.position?this.right:this.left,l+=e.aliasPixel(this.ctx.lineWidth),q+=e.aliasPixel(this.ctx.lineWidth));this.ctx.beginPath();this.ctx.moveTo(l,m);this.ctx.lineTo(q,n);this.ctx.stroke()}}})}},{}],30:[function(p,m,n){m.exports=function(f){var e=f.helpers;f.scaleService={constructors:{},defaults:{},registerScaleType:function(c,a,d){this.constructors[c]= +a;this.defaults[c]=e.clone(d)},getScaleConstructor:function(c){return this.constructors.hasOwnProperty(c)?this.constructors[c]:void 0},getScaleDefaults:function(c){return this.defaults.hasOwnProperty(c)?e.scaleMerge(f.defaults.scale,this.defaults[c]):{}},addScalesToLayout:function(c){e.each(c.scales,function(a){f.layoutService.addBox(c,a)})}}}},{}],31:[function(p,m,n){m.exports=function(f){var e=f.helpers;f.defaults.global.title={display:!1,position:"top",fullWidth:!0,fontStyle:"bold",padding:10, +text:""};f.Title=f.Element.extend({initialize:function(c){e.extend(this,c);this.options=e.configMerge(f.defaults.global.title,c.options);this.legendHitBoxes=[]},beforeUpdate:e.noop,update:function(c,a,d){this.beforeUpdate();this.maxWidth=c;this.maxHeight=a;this.margins=d;this.beforeSetDimensions();this.setDimensions();this.afterSetDimensions();this.beforeBuildLabels();this.buildLabels();this.afterBuildLabels();this.beforeFit();this.fit();this.afterFit();this.afterUpdate();return this.minSize},afterUpdate:e.noop, +beforeSetDimensions:e.noop,setDimensions:function(){this.isHorizontal()?(this.width=this.maxWidth,this.left=0,this.right=this.width):(this.height=this.maxHeight,this.top=0,this.bottom=this.height);this.paddingBottom=this.paddingRight=this.paddingTop=this.paddingLeft=0;this.minSize={width:0,height:0}},afterSetDimensions:e.noop,beforeBuildLabels:e.noop,buildLabels:e.noop,afterBuildLabels:e.noop,beforeFit:e.noop,fit:function(){var c=e.getValueOrDefault(this.options.fontSize,f.defaults.global.defaultFontSize), +a=e.getValueOrDefault(this.options.fontStyle,f.defaults.global.defaultFontStyle),d=e.getValueOrDefault(this.options.fontFamily,f.defaults.global.defaultFontFamily);e.fontString(c,a,d);this.isHorizontal()?this.minSize.width=this.maxWidth:this.minSize.width=0;this.isHorizontal()?this.minSize.height=0:this.minSize.height=this.maxHeight;this.isHorizontal()?this.options.display&&(this.minSize.height+=c+2*this.options.padding):this.options.display&&(this.minSize.width+=c+2*this.options.padding);this.width= +this.minSize.width;this.height=this.minSize.height},afterFit:e.noop,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){if(this.options.display){var c=this.ctx,a,d;a=e.getValueOrDefault(this.options.fontColor,f.defaults.global.defaultFontColor);d=e.getValueOrDefault(this.options.fontSize,f.defaults.global.defaultFontSize);var h=e.getValueOrDefault(this.options.fontStyle,f.defaults.global.defaultFontStyle),k=e.getValueOrDefault(this.options.fontFamily, +f.defaults.global.defaultFontFamily),h=e.fontString(d,h,k);c.fillStyle=a;c.font=h;this.isHorizontal()?(c.textAlign="center",c.textBaseline="middle",a=this.left+(this.right-this.left)/2,d=this.top+(this.bottom-this.top)/2,c.fillText(this.options.text,a,d)):(a="left"===this.options.position?this.left+d/2:this.right-d/2,d=this.top+(this.bottom-this.top)/2,h="left"===this.options.position?-.5*Math.PI:.5*Math.PI,c.save(),c.translate(a,d),c.rotate(h),c.textAlign="center",c.textBaseline="middle",c.fillText(this.options.text, +0,0),c.restore())}}})}},{}],32:[function(p,m,n){m.exports=function(f){function e(a,d){d&&(c.isArray(d)?a=a.concat(d):a.push(d));return a}var c=f.helpers;f.defaults.global.tooltips={enabled:!0,custom:null,mode:"single",backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleColor:"#fff",titleAlign:"left",bodySpacing:2,bodyColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6, +yAlign:"center",xAlign:"center",caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",callbacks:{beforeTitle:c.noop,title:function(a,c){var e="";0this._chart.height-a.height&&(this._model.yAlign="bottom");var c,e,k,f,q,m=this,n=(this._chartInstance.chartArea.left+this._chartInstance.chartArea.right)/2,p=(this._chartInstance.chartArea.top+this._chartInstance.chartArea.bottom)/2;"center"===this._model.yAlign?(c=function(a){return a<=n},e=function(a){return a>n}):(c=function(c){return c<= +a.width/2},e=function(c){return c>=m._chart.width-a.width/2});k=function(c){return c+a.width>m._chart.width};f=function(c){return 0>c-a.width};q=function(a){return a<=p?"top":"bottom"};c(this._model.x)?(this._model.xAlign="left",k(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=q(this._model.y))):e(this._model.x)&&(this._model.xAlign="right",f(this._model.x)&&(this._model.xAlign="center",this._model.yAlign=q(this._model.y)))},getBackgroundPoint:function(a,c){var e={x:a.x,y:a.y};"right"=== +a.xAlign?e.x-=c.width:"center"===a.xAlign&&(e.x-=c.width/2);e.y="top"===a.yAlign?e.y+(a.caretPadding+a.caretSize):"bottom"===a.yAlign?e.y-(c.height+a.caretPadding+a.caretSize):e.y-c.height/2;"center"===a.yAlign?"left"===a.xAlign?e.x+=a.caretPadding+a.caretSize:"right"===a.xAlign&&(e.x-=a.caretPadding+a.caretSize):"left"===a.xAlign?e.x-=a.cornerRadius+a.caretPadding:"right"===a.xAlign&&(e.x+=a.cornerRadius+a.caretPadding);return e},drawCaret:function(a,d,e,k){var f=this._view;k=this._chart.ctx;var q, +m,n,p;"center"===f.yAlign?("left"===f.xAlign?(q=a.x,m=q-f.caretSize):(q=a.x+d.width,m=q+f.caretSize),n=q,d=a.y+d.height/2,a=d-f.caretSize,p=d+f.caretSize):("left"===f.xAlign?(q=a.x+f.cornerRadius,m=q+f.caretSize,n=m+f.caretSize):"right"===f.xAlign?(q=a.x+d.width-f.cornerRadius,m=q-f.caretSize,n=m-f.caretSize):(m=a.x+d.width/2,q=m-f.caretSize,n=m+f.caretSize),"top"===f.yAlign?(a=a.y,d=a-f.caretSize):(a=a.y+d.height,d=a+f.caretSize),p=a);f=c.color(f.backgroundColor);k.fillStyle=f.alpha(e*f.alpha()).rgbString(); +k.beginPath();k.moveTo(q,a);k.lineTo(m,d);k.lineTo(n,p);k.closePath();k.fill()},drawTitle:function(a,d,e,k){if(d.title.length){e.textAlign=d._titleAlign;e.textBaseline="top";var f=c.color(d.titleColor);e.fillStyle=f.alpha(k*f.alpha()).rgbString();e.font=c.fontString(d.titleFontSize,d._titleFontStyle,d._titleFontFamily);c.each(d.title,function(c,k){e.fillText(c,a.x,a.y);a.y+=d.titleFontSize+d.titleSpacing;k+1===d.title.length&&(a.y+=d.titleMarginBottom-d.titleSpacing)})}},drawBody:function(a,d,e,k){e.textAlign= +d._bodyAlign;e.textBaseline="top";var f=c.color(d.bodyColor);e.fillStyle=f.alpha(k*f.alpha()).rgbString();e.font=c.fontString(d.bodyFontSize,d._bodyFontStyle,d._bodyFontFamily);c.each(d.beforeBody,function(c){e.fillText(c,a.x,a.y);a.y+=d.bodyFontSize+d.bodySpacing});c.each(d.body,function(f,l){"single"!==this._options.tooltips.mode&&(e.fillStyle=c.color(d.legendColorBackground).alpha(k).rgbaString(),e.fillRect(a.x,a.y,d.bodyFontSize,d.bodyFontSize),e.strokeStyle=c.color(d.labelColors[l].borderColor).alpha(k).rgbaString(), +e.strokeRect(a.x,a.y,d.bodyFontSize,d.bodyFontSize),e.fillStyle=c.color(d.labelColors[l].backgroundColor).alpha(k).rgbaString(),e.fillRect(a.x+1,a.y+1,d.bodyFontSize-2,d.bodyFontSize-2),e.fillStyle=c.color(d.bodyColor).alpha(k).rgbaString());e.fillText(f,a.x+("single"!==this._options.tooltips.mode?d.bodyFontSize+2:0),a.y);a.y+=d.bodyFontSize+d.bodySpacing},this);c.each(d.afterBody,function(c){e.fillText(c,a.x,a.y);a.y+=d.bodyFontSize});a.y-=d.bodySpacing},drawFooter:function(a,d,e,k){if(d.footer.length){a.y+= +d.footerMarginTop;e.textAlign=d._footerAlign;e.textBaseline="top";var f=c.color(d.footerColor);e.fillStyle=f.alpha(k*f.alpha()).rgbString();e.font=c.fontString(d.footerFontSize,d._footerFontStyle,d._footerFontFamily);c.each(d.footer,function(c){e.fillText(c,a.x,a.y);a.y+=d.footerFontSize+d.footerSpacing})}},draw:function(){var a=this._chart.ctx,d=this._view;if(0!==d.opacity){var e=d.caretPadding,k=this.getTooltipSize(d),f={x:d.x,y:d.y},q=Math.abs(.001>d.opacity)?0:d.opacity;if(this._options.tooltips.enabled){var m= +c.color(d.backgroundColor);a.fillStyle=m.alpha(q*m.alpha()).rgbString();c.drawRoundedRectangle(a,f.x,f.y,k.width,k.height,d.cornerRadius);a.fill();this.drawCaret(f,k,q,e);f.x+=d.xPadding;f.y+=d.yPadding;this.drawTitle(f,d,a,q);this.drawBody(f,d,a,q);this.drawFooter(f,d,a,q)}}}})}},{}],33:[function(p,m,n){m.exports=function(f,e){var c=f.helpers;f.defaults.global.elements.arc={backgroundColor:f.defaults.global.defaultColor,borderColor:"#fff",borderWidth:2};f.elements.Arc=f.Element.extend({inLabelRange:function(a){var c= +this._view;return c?Math.pow(a-c.x,2)q;)f.angle-=2*Math.PI;for(;f.angle=e.innerRadius&&f.distance<=e.outerRadius;return f.angle>=l&&f.angle<=q&&e}return!1},tooltipPosition:function(){var a=this._view,c=a.startAngle+(a.endAngle-a.startAngle)/2,e=(a.outerRadius-a.innerRadius)/2+a.innerRadius; +return{x:a.x+Math.cos(c)*e,y:a.y+Math.sin(c)*e}},draw:function(){var a=this._chart.ctx,c=this._view;a.beginPath();a.arc(c.x,c.y,c.outerRadius,c.startAngle,c.endAngle);a.arc(c.x,c.y,c.innerRadius,c.endAngle,c.startAngle,!0);a.closePath();a.strokeStyle=c.borderColor;a.lineWidth=c.borderWidth;a.fillStyle=c.backgroundColor;a.fill();a.lineJoin="bevel";c.borderWidth&&a.stroke()}})}},{}],34:[function(p,m,n){m.exports=function(f){var e=f.helpers;f.defaults.global.elements.line={tension:.4,backgroundColor:f.defaults.global.defaultColor, +borderWidth:3,borderColor:f.defaults.global.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",fill:!0};f.elements.Line=f.Element.extend({lineToNextPoint:function(c,a,d,e,f){var l=this._chart.ctx;a._view.skip?e.call(this,c,a,d):c._view.skip?f.call(this,c,a,d):0===a._view.tension?l.lineTo(a._view.x,a._view.y):l.bezierCurveTo(c._view.controlPointNextX,c._view.controlPointNextY,a._view.controlPointPreviousX,a._view.controlPointPreviousY,a._view.x,a._view.y)}, +draw:function(){function c(c){k._view.skip||l._view.skip?c&&h.lineTo(a._view.scaleZero.x,a._view.scaleZero.y):h.bezierCurveTo(l._view.controlPointNextX,l._view.controlPointNextY,k._view.controlPointPreviousX,k._view.controlPointPreviousY,k._view.x,k._view.y)}var a=this,d=this._view,h=this._chart.ctx,k=this._children[0],l=this._children[this._children.length-1];h.save();0h;h++)e.lineTo.apply(e,d[(a+h)%4]);e.fill();c.borderWidth&&e.stroke()},height:function(){var e=this._view;return e.base-e.y},inRange:function(e,c){var a=this._view,d=!1;a&&(d=a.y=a.x-a.width/2&&e<=a.x+a.width/2&&c>=a.y&&c<=a.base:e>=a.x-a.width/2&&e<=a.x+a.width/2&&c>=a.base&&c<=a.y);return d},inLabelRange:function(e){var c=this._view;return c? +e>=c.x-c.width/2&&e<=c.x+c.width/2:!1},tooltipPosition:function(){var e=this._view;return{x:e.x,y:e.y}}})}},{}],37:[function(p,m,n){m.exports=function(f){var e=f.helpers,c=f.Scale.extend({buildTicks:function(a){this.startIndex=0;this.endIndex=this.chart.data.labels.length;void 0!==this.options.ticks.min&&(a=e.indexOf(this.chart.data.labels,this.options.ticks.min),this.startIndex=-1!==a?a:this.startIndex);void 0!==this.options.ticks.max&&(a=e.indexOf(this.chart.data.labels,this.options.ticks.max), +this.endIndex=-1!==a?a:this.endIndex);this.ticks=0===this.startIndex&&this.endIndex===this.chart.data.labels.length?this.chart.data.labels:this.chart.data.labels.slice(this.startIndex,this.endIndex+1)},getLabelForIndex:function(a,c){return this.ticks[a]},getPixelForValue:function(a,c,e,f){a=Math.max(this.ticks.length-(this.options.gridLines.offsetGridLines?0:1),1);if(this.isHorizontal())return a=(this.width-(this.paddingLeft+this.paddingRight))/a,c=a*(c-this.startIndex)+this.paddingLeft,this.options.gridLines.offsetGridLines&& +f&&(c+=a/2),this.left+Math.round(c);a=(this.height-(this.paddingTop+this.paddingBottom))/a;c=a*(c-this.startIndex)+this.paddingTop;this.options.gridLines.offsetGridLines&&f&&(c+=a/2);return this.top+Math.round(c)},getPixelForTick:function(a,c){return this.getPixelForValue(this.ticks[a],a+this.startIndex,null,c)}});f.scaleService.registerScaleType("category",c,{position:"bottom"})}},{}],38:[function(p,m,n){m.exports=function(f){var e=f.helpers,c=f.Scale.extend({determineDataLimits:function(){this.max= +this.min=null;if(this.options.stacked){var a={};e.each(this.chart.data.datasets,function(c){void 0===a[c.type]&&(a[c.type]={positiveValues:[],negativeValues:[]});var d=a[c.type].positiveValues,h=a[c.type].negativeValues;e.isDatasetVisible(c)&&(this.isHorizontal()?c.xAxisID===this.id:c.yAxisID===this.id)&&e.each(c.data,function(a,c){var e=+this.getRightValue(a);isNaN(e)||(d[c]=d[c]||0,h[c]=h[c]||0,this.options.relativePoints?d[c]=100:0>e?h[c]+=e:d[c]+=e)},this)},this);e.each(a,function(a){var c=a.positiveValues.concat(a.negativeValues); +a=e.min(c);c=e.max(c);this.min=null===this.min?a:Math.min(this.min,a);this.max=null===this.max?c:Math.max(this.max,c)},this)}else e.each(this.chart.data.datasets,function(a){e.isDatasetVisible(a)&&(this.isHorizontal()?a.xAxisID===this.id:a.yAxisID===this.id)&&e.each(a.data,function(a,c){var d=+this.getRightValue(a);isNaN(d)||(null===this.min?this.min=d:dthis.max&&(this.max=d))},this)},this);if(this.options.ticks.beginAtZero){var c=e.sign(this.min), +h=e.sign(this.max);0>c&&0>h?this.max=0:0this.max&&(this.max=d))},this)},this);this.min=void 0!==this.options.ticks.min?this.options.ticks.min:this.min;this.max=void 0!==this.options.ticks.max?this.options.ticks.max:this.max;this.min===this.max&&(0!==this.min&&null!==this.min?(this.min=Math.pow(10,Math.floor(e.log10(this.min))-1),this.max=Math.pow(10,Math.floor(e.log10(this.max))+1)):(this.min=1,this.max=10))},buildTicks:function(){this.ticks=[];for(var a=void 0!==this.options.ticks.min? +this.options.ticks.min:Math.pow(10,Math.floor(e.log10(this.min)));athis.max&&(this.max=d))},this)},this);if(this.options.ticks.beginAtZero){var a=e.sign(this.min),c=e.sign(this.max);0>a&&0>c?this.max=0:0c&&(c=k.x+m,n=l),k.x-mc&&(c=k.x+m,n=l):l>this.getValueCount()/2&&k.x- +mm-n;m=c===n||c===m-n;a.textAlign=0===c?"center":c===k?"center":c=Math.ceil(this.scaleSizeInUnits/d)){this.unitScale=c.getValueOrDefault(this.options.time.unitStepSize,f.steps[l]);break}break}else if(!1===f.maxStep||Math.ceil(this.scaleSizeInUnits/d) 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y *100, z * 100]; +} + +function rgb2lab(rgb) { + var xyz = rgb2xyz(rgb), + x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +} + +function rgb2lch(args) { + return lab2lch(rgb2lab(args)); +} + +function hsl2rgb(hsl) { + var h = hsl[0] / 360, + s = hsl[1] / 100, + l = hsl[2] / 100, + t1, t2, t3, rgb, val; + + if (s == 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) + t2 = l * (1 + s); + else + t2 = l + s - l * s; + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * - (i - 1); + t3 < 0 && t3++; + t3 > 1 && t3--; + + if (6 * t3 < 1) + val = t1 + (t2 - t1) * 6 * t3; + else if (2 * t3 < 1) + val = t2; + else if (3 * t3 < 2) + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + else + val = t1; + + rgb[i] = val * 255; + } + + return rgb; +} + +function hsl2hsv(hsl) { + var h = hsl[0], + s = hsl[1] / 100, + l = hsl[2] / 100, + sv, v; + + if(l === 0) { + // no need to do calc on black + // also avoids divide by 0 error + return [0, 0, 0]; + } + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + v = (l + s) / 2; + sv = (2 * s) / (l + s); + return [h, sv * 100, v * 100]; +} + +function hsl2hwb(args) { + return rgb2hwb(hsl2rgb(args)); +} + +function hsl2cmyk(args) { + return rgb2cmyk(hsl2rgb(args)); +} + +function hsl2keyword(args) { + return rgb2keyword(hsl2rgb(args)); +} + + +function hsv2rgb(hsv) { + var h = hsv[0] / 60, + s = hsv[1] / 100, + v = hsv[2] / 100, + hi = Math.floor(h) % 6; + + var f = h - Math.floor(h), + p = 255 * v * (1 - s), + q = 255 * v * (1 - (s * f)), + t = 255 * v * (1 - (s * (1 - f))), + v = 255 * v; + + switch(hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +} + +function hsv2hsl(hsv) { + var h = hsv[0], + s = hsv[1] / 100, + v = hsv[2] / 100, + sl, l; + + l = (2 - s) * v; + sl = s * v; + sl /= (l <= 1) ? l : 2 - l; + sl = sl || 0; + l /= 2; + return [h, sl * 100, l * 100]; +} + +function hsv2hwb(args) { + return rgb2hwb(hsv2rgb(args)) +} + +function hsv2cmyk(args) { + return rgb2cmyk(hsv2rgb(args)); +} + +function hsv2keyword(args) { + return rgb2keyword(hsv2rgb(args)); +} + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +function hwb2rgb(hwb) { + var h = hwb[0] / 360, + wh = hwb[1] / 100, + bl = hwb[2] / 100, + ratio = wh + bl, + i, v, f, n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + if ((i & 0x01) != 0) { + f = 1 - f; + } + n = wh + f * (v - wh); // linear interpolation + + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +} + +function hwb2hsl(args) { + return rgb2hsl(hwb2rgb(args)); +} + +function hwb2hsv(args) { + return rgb2hsv(hwb2rgb(args)); +} + +function hwb2cmyk(args) { + return rgb2cmyk(hwb2rgb(args)); +} + +function hwb2keyword(args) { + return rgb2keyword(hwb2rgb(args)); +} + +function cmyk2rgb(cmyk) { + var c = cmyk[0] / 100, + m = cmyk[1] / 100, + y = cmyk[2] / 100, + k = cmyk[3] / 100, + r, g, b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + return [r * 255, g * 255, b * 255]; +} + +function cmyk2hsl(args) { + return rgb2hsl(cmyk2rgb(args)); +} + +function cmyk2hsv(args) { + return rgb2hsv(cmyk2rgb(args)); +} + +function cmyk2hwb(args) { + return rgb2hwb(cmyk2rgb(args)); +} + +function cmyk2keyword(args) { + return rgb2keyword(cmyk2rgb(args)); +} + + +function xyz2rgb(xyz) { + var x = xyz[0] / 100, + y = xyz[1] / 100, + z = xyz[2] / 100, + r, g, b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r = (r * 12.92); + + g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g = (g * 12.92); + + b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b = (b * 12.92); + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; +} + +function xyz2lab(xyz) { + var x = xyz[0], + y = xyz[1], + z = xyz[2], + l, a, b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +} + +function xyz2lch(args) { + return lab2lch(xyz2lab(args)); +} + +function lab2xyz(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + x, y, z, y2; + + if (l <= 8) { + y = (l * 100) / 903.3; + y2 = (7.787 * (y / 100)) + (16 / 116); + } else { + y = 100 * Math.pow((l + 16) / 116, 3); + y2 = Math.pow(y / 100, 1/3); + } + + x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3); + + z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3); + + return [x, y, z]; +} + +function lab2lch(lab) { + var l = lab[0], + a = lab[1], + b = lab[2], + hr, h, c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + if (h < 0) { + h += 360; + } + c = Math.sqrt(a * a + b * b); + return [l, c, h]; +} + +function lab2rgb(args) { + return xyz2rgb(lab2xyz(args)); +} + +function lch2lab(lch) { + var l = lch[0], + c = lch[1], + h = lch[2], + a, b, hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + return [l, a, b]; +} + +function lch2xyz(args) { + return lab2xyz(lch2lab(args)); +} + +function lch2rgb(args) { + return lab2rgb(lch2lab(args)); +} + +function keyword2rgb(keyword) { + return cssKeywords[keyword]; +} + +function keyword2hsl(args) { + return rgb2hsl(keyword2rgb(args)); +} + +function keyword2hsv(args) { + return rgb2hsv(keyword2rgb(args)); +} + +function keyword2hwb(args) { + return rgb2hwb(keyword2rgb(args)); +} + +function keyword2cmyk(args) { + return rgb2cmyk(keyword2rgb(args)); +} + +function keyword2lab(args) { + return rgb2lab(keyword2rgb(args)); +} + +function keyword2xyz(args) { + return rgb2xyz(keyword2rgb(args)); +} + +var cssKeywords = { + aliceblue: [240,248,255], + antiquewhite: [250,235,215], + aqua: [0,255,255], + aquamarine: [127,255,212], + azure: [240,255,255], + beige: [245,245,220], + bisque: [255,228,196], + black: [0,0,0], + blanchedalmond: [255,235,205], + blue: [0,0,255], + blueviolet: [138,43,226], + brown: [165,42,42], + burlywood: [222,184,135], + cadetblue: [95,158,160], + chartreuse: [127,255,0], + chocolate: [210,105,30], + coral: [255,127,80], + cornflowerblue: [100,149,237], + cornsilk: [255,248,220], + crimson: [220,20,60], + cyan: [0,255,255], + darkblue: [0,0,139], + darkcyan: [0,139,139], + darkgoldenrod: [184,134,11], + darkgray: [169,169,169], + darkgreen: [0,100,0], + darkgrey: [169,169,169], + darkkhaki: [189,183,107], + darkmagenta: [139,0,139], + darkolivegreen: [85,107,47], + darkorange: [255,140,0], + darkorchid: [153,50,204], + darkred: [139,0,0], + darksalmon: [233,150,122], + darkseagreen: [143,188,143], + darkslateblue: [72,61,139], + darkslategray: [47,79,79], + darkslategrey: [47,79,79], + darkturquoise: [0,206,209], + darkviolet: [148,0,211], + deeppink: [255,20,147], + deepskyblue: [0,191,255], + dimgray: [105,105,105], + dimgrey: [105,105,105], + dodgerblue: [30,144,255], + firebrick: [178,34,34], + floralwhite: [255,250,240], + forestgreen: [34,139,34], + fuchsia: [255,0,255], + gainsboro: [220,220,220], + ghostwhite: [248,248,255], + gold: [255,215,0], + goldenrod: [218,165,32], + gray: [128,128,128], + green: [0,128,0], + greenyellow: [173,255,47], + grey: [128,128,128], + honeydew: [240,255,240], + hotpink: [255,105,180], + indianred: [205,92,92], + indigo: [75,0,130], + ivory: [255,255,240], + khaki: [240,230,140], + lavender: [230,230,250], + lavenderblush: [255,240,245], + lawngreen: [124,252,0], + lemonchiffon: [255,250,205], + lightblue: [173,216,230], + lightcoral: [240,128,128], + lightcyan: [224,255,255], + lightgoldenrodyellow: [250,250,210], + lightgray: [211,211,211], + lightgreen: [144,238,144], + lightgrey: [211,211,211], + lightpink: [255,182,193], + lightsalmon: [255,160,122], + lightseagreen: [32,178,170], + lightskyblue: [135,206,250], + lightslategray: [119,136,153], + lightslategrey: [119,136,153], + lightsteelblue: [176,196,222], + lightyellow: [255,255,224], + lime: [0,255,0], + limegreen: [50,205,50], + linen: [250,240,230], + magenta: [255,0,255], + maroon: [128,0,0], + mediumaquamarine: [102,205,170], + mediumblue: [0,0,205], + mediumorchid: [186,85,211], + mediumpurple: [147,112,219], + mediumseagreen: [60,179,113], + mediumslateblue: [123,104,238], + mediumspringgreen: [0,250,154], + mediumturquoise: [72,209,204], + mediumvioletred: [199,21,133], + midnightblue: [25,25,112], + mintcream: [245,255,250], + mistyrose: [255,228,225], + moccasin: [255,228,181], + navajowhite: [255,222,173], + navy: [0,0,128], + oldlace: [253,245,230], + olive: [128,128,0], + olivedrab: [107,142,35], + orange: [255,165,0], + orangered: [255,69,0], + orchid: [218,112,214], + palegoldenrod: [238,232,170], + palegreen: [152,251,152], + paleturquoise: [175,238,238], + palevioletred: [219,112,147], + papayawhip: [255,239,213], + peachpuff: [255,218,185], + peru: [205,133,63], + pink: [255,192,203], + plum: [221,160,221], + powderblue: [176,224,230], + purple: [128,0,128], + rebeccapurple: [102, 51, 153], + red: [255,0,0], + rosybrown: [188,143,143], + royalblue: [65,105,225], + saddlebrown: [139,69,19], + salmon: [250,128,114], + sandybrown: [244,164,96], + seagreen: [46,139,87], + seashell: [255,245,238], + sienna: [160,82,45], + silver: [192,192,192], + skyblue: [135,206,235], + slateblue: [106,90,205], + slategray: [112,128,144], + slategrey: [112,128,144], + snow: [255,250,250], + springgreen: [0,255,127], + steelblue: [70,130,180], + tan: [210,180,140], + teal: [0,128,128], + thistle: [216,191,216], + tomato: [255,99,71], + turquoise: [64,224,208], + violet: [238,130,238], + wheat: [245,222,179], + white: [255,255,255], + whitesmoke: [245,245,245], + yellow: [255,255,0], + yellowgreen: [154,205,50] +}; + +var reverseKeywords = {}; +for (var key in cssKeywords) { + reverseKeywords[JSON.stringify(cssKeywords[key])] = key; +} + +},{}],3:[function(require,module,exports){ +var conversions = require("./conversions"); + +var convert = function() { + return new Converter(); +} + +for (var func in conversions) { + // export Raw versions + convert[func + "Raw"] = (function(func) { + // accept array or plain args + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + return conversions[func](arg); + } + })(func); + + var pair = /(\w+)2(\w+)/.exec(func), + from = pair[1], + to = pair[2]; + + // export rgb2hsl and ["rgb"]["hsl"] + convert[from] = convert[from] || {}; + + convert[from][to] = convert[func] = (function(func) { + return function(arg) { + if (typeof arg == "number") + arg = Array.prototype.slice.call(arguments); + + var val = conversions[func](arg); + if (typeof val == "string" || val === undefined) + return val; // keyword + + for (var i = 0; i < val.length; i++) + val[i] = Math.round(val[i]); + return val; + } + })(func); +} + + +/* Converter does lazy conversion and caching */ +var Converter = function() { + this.convs = {}; +}; + +/* Either get the values for a space or + set the values for a space, depending on args */ +Converter.prototype.routeSpace = function(space, args) { + var values = args[0]; + if (values === undefined) { + // color.rgb() + return this.getValues(space); + } + // color.rgb(10, 10, 10) + if (typeof values == "number") { + values = Array.prototype.slice.call(args); + } + + return this.setValues(space, values); +}; + +/* Set the values for a space, invalidating cache */ +Converter.prototype.setValues = function(space, values) { + this.space = space; + this.convs = {}; + this.convs[space] = values; + return this; +}; + +/* Get the values for a space. If there's already + a conversion for the space, fetch it, otherwise + compute it */ +Converter.prototype.getValues = function(space) { + var vals = this.convs[space]; + if (!vals) { + var fspace = this.space, + from = this.convs[fspace]; + vals = convert[fspace][space](from); + + this.convs[space] = vals; + } + return vals; +}; + +["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) { + Converter.prototype[space] = function(vals) { + return this.routeSpace(space, arguments); + } +}); + +module.exports = convert; +},{"./conversions":2}],4:[function(require,module,exports){ +/* MIT license */ +var colorNames = require('color-name'); + +module.exports = { + getRgba: getRgba, + getHsla: getHsla, + getRgb: getRgb, + getHsl: getHsl, + getHwb: getHwb, + getAlpha: getAlpha, + + hexString: hexString, + rgbString: rgbString, + rgbaString: rgbaString, + percentString: percentString, + percentaString: percentaString, + hslString: hslString, + hslaString: hslaString, + hwbString: hwbString, + keyword: keyword +} + +function getRgba(string) { + if (!string) { + return; + } + var abbr = /^#([a-fA-F0-9]{3})$/, + hex = /^#([a-fA-F0-9]{6})$/, + rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/, + per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/, + keyword = /(\w+)/; + + var rgb = [0, 0, 0], + a = 1, + match = string.match(abbr); + if (match) { + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i] + match[i], 16); + } + } + else if (match = string.match(hex)) { + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); + } + } + else if (match = string.match(rgba)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i + 1]); + } + a = parseFloat(match[4]); + } + else if (match = string.match(per)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); + } + a = parseFloat(match[4]); + } + else if (match = string.match(keyword)) { + if (match[1] == "transparent") { + return [0, 0, 0, 0]; + } + rgb = colorNames[match[1]]; + if (!rgb) { + return; + } + } + + for (var i = 0; i < rgb.length; i++) { + rgb[i] = scale(rgb[i], 0, 255); + } + if (!a && a != 0) { + a = 1; + } + else { + a = scale(a, 0, 1); + } + rgb[3] = a; + return rgb; +} + +function getHsla(string) { + if (!string) { + return; + } + var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hsl); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + s = scale(parseFloat(match[2]), 0, 100), + l = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, s, l, a]; + } +} + +function getHwb(string) { + if (!string) { + return; + } + var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hwb); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + w = scale(parseFloat(match[2]), 0, 100), + b = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, w, b, a]; + } +} + +function getRgb(string) { + var rgba = getRgba(string); + return rgba && rgba.slice(0, 3); +} + +function getHsl(string) { + var hsla = getHsla(string); + return hsla && hsla.slice(0, 3); +} + +function getAlpha(string) { + var vals = getRgba(string); + if (vals) { + return vals[3]; + } + else if (vals = getHsla(string)) { + return vals[3]; + } + else if (vals = getHwb(string)) { + return vals[3]; + } +} + +// generators +function hexString(rgb) { + return "#" + hexDouble(rgb[0]) + hexDouble(rgb[1]) + + hexDouble(rgb[2]); +} + +function rgbString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return rgbaString(rgba, alpha); + } + return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; +} + +function rgbaString(rgba, alpha) { + if (alpha === undefined) { + alpha = (rgba[3] !== undefined ? rgba[3] : 1); + } + return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + + ", " + alpha + ")"; +} + +function percentString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return percentaString(rgba, alpha); + } + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + + return "rgb(" + r + "%, " + g + "%, " + b + "%)"; +} + +function percentaString(rgba, alpha) { + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; +} + +function hslString(hsla, alpha) { + if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { + return hslaString(hsla, alpha); + } + return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; +} + +function hslaString(hsla, alpha) { + if (alpha === undefined) { + alpha = (hsla[3] !== undefined ? hsla[3] : 1); + } + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + + alpha + ")"; +} + +// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax +// (hwb have alpha optional & 1 is default value) +function hwbString(hwb, alpha) { + if (alpha === undefined) { + alpha = (hwb[3] !== undefined ? hwb[3] : 1); + } + return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" + + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; +} + +function keyword(rgb) { + return reverseNames[rgb.slice(0, 3)]; +} + +// helpers +function scale(num, min, max) { + return Math.min(Math.max(min, num), max); +} + +function hexDouble(num) { + var str = num.toString(16).toUpperCase(); + return (str.length < 2) ? "0" + str : str; +} + + +//create a list of reverse color names +var reverseNames = {}; +for (var name in colorNames) { + reverseNames[colorNames[name]] = name; +} + +},{"color-name":5}],5:[function(require,module,exports){ +module.exports = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; +},{}],6:[function(require,module,exports){ +/* MIT license */ + +var convert = require("color-convert"), + string = require("color-string"); + +var Color = function(obj) { + if (obj instanceof Color) return obj; + if (!(this instanceof Color)) return new Color(obj); + + this.values = { + rgb: [0, 0, 0], + hsl: [0, 0, 0], + hsv: [0, 0, 0], + hwb: [0, 0, 0], + cmyk: [0, 0, 0, 0], + alpha: 1 + } + + // parse Color() argument + if (typeof obj == "string") { + var vals = string.getRgba(obj); + if (vals) { + this.setValues("rgb", vals); + } else if (vals = string.getHsla(obj)) { + this.setValues("hsl", vals); + } else if (vals = string.getHwb(obj)) { + this.setValues("hwb", vals); + } else { + throw new Error("Unable to parse color from string \"" + obj + "\""); + } + } else if (typeof obj == "object") { + var vals = obj; + if (vals["r"] !== undefined || vals["red"] !== undefined) { + this.setValues("rgb", vals) + } else if (vals["l"] !== undefined || vals["lightness"] !== undefined) { + this.setValues("hsl", vals) + } else if (vals["v"] !== undefined || vals["value"] !== undefined) { + this.setValues("hsv", vals) + } else if (vals["w"] !== undefined || vals["whiteness"] !== undefined) { + this.setValues("hwb", vals) + } else if (vals["c"] !== undefined || vals["cyan"] !== undefined) { + this.setValues("cmyk", vals) + } else { + throw new Error("Unable to parse color from object " + JSON.stringify(obj)); + } + } +} + +Color.prototype = { + rgb: function(vals) { + return this.setSpace("rgb", arguments); + }, + hsl: function(vals) { + return this.setSpace("hsl", arguments); + }, + hsv: function(vals) { + return this.setSpace("hsv", arguments); + }, + hwb: function(vals) { + return this.setSpace("hwb", arguments); + }, + cmyk: function(vals) { + return this.setSpace("cmyk", arguments); + }, + + rgbArray: function() { + return this.values.rgb; + }, + hslArray: function() { + return this.values.hsl; + }, + hsvArray: function() { + return this.values.hsv; + }, + hwbArray: function() { + if (this.values.alpha !== 1) { + return this.values.hwb.concat([this.values.alpha]) + } + return this.values.hwb; + }, + cmykArray: function() { + return this.values.cmyk; + }, + rgbaArray: function() { + var rgb = this.values.rgb; + return rgb.concat([this.values.alpha]); + }, + hslaArray: function() { + var hsl = this.values.hsl; + return hsl.concat([this.values.alpha]); + }, + alpha: function(val) { + if (val === undefined) { + return this.values.alpha; + } + this.setValues("alpha", val); + return this; + }, + + red: function(val) { + return this.setChannel("rgb", 0, val); + }, + green: function(val) { + return this.setChannel("rgb", 1, val); + }, + blue: function(val) { + return this.setChannel("rgb", 2, val); + }, + hue: function(val) { + return this.setChannel("hsl", 0, val); + }, + saturation: function(val) { + return this.setChannel("hsl", 1, val); + }, + lightness: function(val) { + return this.setChannel("hsl", 2, val); + }, + saturationv: function(val) { + return this.setChannel("hsv", 1, val); + }, + whiteness: function(val) { + return this.setChannel("hwb", 1, val); + }, + blackness: function(val) { + return this.setChannel("hwb", 2, val); + }, + value: function(val) { + return this.setChannel("hsv", 2, val); + }, + cyan: function(val) { + return this.setChannel("cmyk", 0, val); + }, + magenta: function(val) { + return this.setChannel("cmyk", 1, val); + }, + yellow: function(val) { + return this.setChannel("cmyk", 2, val); + }, + black: function(val) { + return this.setChannel("cmyk", 3, val); + }, + + hexString: function() { + return string.hexString(this.values.rgb); + }, + rgbString: function() { + return string.rgbString(this.values.rgb, this.values.alpha); + }, + rgbaString: function() { + return string.rgbaString(this.values.rgb, this.values.alpha); + }, + percentString: function() { + return string.percentString(this.values.rgb, this.values.alpha); + }, + hslString: function() { + return string.hslString(this.values.hsl, this.values.alpha); + }, + hslaString: function() { + return string.hslaString(this.values.hsl, this.values.alpha); + }, + hwbString: function() { + return string.hwbString(this.values.hwb, this.values.alpha); + }, + keyword: function() { + return string.keyword(this.values.rgb, this.values.alpha); + }, + + rgbNumber: function() { + return (this.values.rgb[0] << 16) | (this.values.rgb[1] << 8) | this.values.rgb[2]; + }, + + luminosity: function() { + // http://www.w3.org/TR/WCAG20/#relativeluminancedef + var rgb = this.values.rgb; + var lum = []; + for (var i = 0; i < rgb.length; i++) { + var chan = rgb[i] / 255; + lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4) + } + return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; + }, + + contrast: function(color2) { + // http://www.w3.org/TR/WCAG20/#contrast-ratiodef + var lum1 = this.luminosity(); + var lum2 = color2.luminosity(); + if (lum1 > lum2) { + return (lum1 + 0.05) / (lum2 + 0.05) + }; + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function(color2) { + var contrastRatio = this.contrast(color2); + return (contrastRatio >= 7.1) ? 'AAA' : (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function() { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb, + yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function() { + return !this.dark(); + }, + + negate: function() { + var rgb = [] + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues("rgb", rgb); + return this; + }, + + lighten: function(ratio) { + this.values.hsl[2] += this.values.hsl[2] * ratio; + this.setValues("hsl", this.values.hsl); + return this; + }, + + darken: function(ratio) { + this.values.hsl[2] -= this.values.hsl[2] * ratio; + this.setValues("hsl", this.values.hsl); + return this; + }, + + saturate: function(ratio) { + this.values.hsl[1] += this.values.hsl[1] * ratio; + this.setValues("hsl", this.values.hsl); + return this; + }, + + desaturate: function(ratio) { + this.values.hsl[1] -= this.values.hsl[1] * ratio; + this.setValues("hsl", this.values.hsl); + return this; + }, + + whiten: function(ratio) { + this.values.hwb[1] += this.values.hwb[1] * ratio; + this.setValues("hwb", this.values.hwb); + return this; + }, + + blacken: function(ratio) { + this.values.hwb[2] += this.values.hwb[2] * ratio; + this.setValues("hwb", this.values.hwb); + return this; + }, + + greyscale: function() { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues("rgb", [val, val, val]); + return this; + }, + + clearer: function(ratio) { + this.setValues("alpha", this.values.alpha - (this.values.alpha * ratio)); + return this; + }, + + opaquer: function(ratio) { + this.setValues("alpha", this.values.alpha + (this.values.alpha * ratio)); + return this; + }, + + rotate: function(degrees) { + var hue = this.values.hsl[0]; + hue = (hue + degrees) % 360; + hue = hue < 0 ? 360 + hue : hue; + this.values.hsl[0] = hue; + this.setValues("hsl", this.values.hsl); + return this; + }, + + mix: function(color2, weight) { + weight = 1 - (weight == null ? 0.5 : weight); + + // algorithm from Sass's mix(). Ratio of first color in mix is + // determined by the alphas of both colors and the weight + var t1 = weight * 2 - 1, + d = this.alpha() - color2.alpha(); + + var weight1 = (((t1 * d == -1) ? t1 : (t1 + d) / (1 + t1 * d)) + 1) / 2; + var weight2 = 1 - weight1; + + var rgb = this.rgbArray(); + var rgb2 = color2.rgbArray(); + + for (var i = 0; i < rgb.length; i++) { + rgb[i] = rgb[i] * weight1 + rgb2[i] * weight2; + } + this.setValues("rgb", rgb); + + var alpha = this.alpha() * weight + color2.alpha() * (1 - weight); + this.setValues("alpha", alpha); + + return this; + }, + + toJSON: function() { + return this.rgb(); + }, + + clone: function() { + return new Color(this.rgb()); + } +} + + +Color.prototype.getValues = function(space) { + var vals = {}; + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = this.values[space][i]; + } + if (this.values.alpha != 1) { + vals["a"] = this.values.alpha; + } + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; +} + +Color.prototype.setValues = function(space, vals) { + var spaces = { + "rgb": ["red", "green", "blue"], + "hsl": ["hue", "saturation", "lightness"], + "hsv": ["hue", "saturation", "value"], + "hwb": ["hue", "whiteness", "blackness"], + "cmyk": ["cyan", "magenta", "yellow", "black"] + }; + + var maxes = { + "rgb": [255, 255, 255], + "hsl": [360, 100, 100], + "hsv": [360, 100, 100], + "hwb": [360, 100, 100], + "cmyk": [100, 100, 100, 100] + }; + + var alpha = 1; + if (space == "alpha") { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + this.values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (var i = 0; i < space.length; i++) { + this.values[space][i] = vals[space.charAt(i)]; + } + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + for (var i = 0; i < space.length; i++) { + this.values[space][i] = vals[chans[i]]; + } + alpha = vals.alpha; + } + this.values.alpha = Math.max(0, Math.min(1, (alpha !== undefined ? alpha : this.values.alpha))); + if (space == "alpha") { + return; + } + + // cap values of the space prior converting all values + for (var i = 0; i < space.length; i++) { + var capped = Math.max(0, Math.min(maxes[space][i], this.values[space][i])); + this.values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname != space) { + this.values[sname] = convert[space][sname](this.values[space]) + } + + // cap values + for (var i = 0; i < sname.length; i++) { + var capped = Math.max(0, Math.min(maxes[sname][i], this.values[sname][i])); + this.values[sname][i] = Math.round(capped); + } + } + return true; +} + +Color.prototype.setSpace = function(space, args) { + var vals = args[0]; + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + // color.rgb(10, 10, 10) + if (typeof vals == "number") { + vals = Array.prototype.slice.call(args); + } + this.setValues(space, vals); + return this; +} + +Color.prototype.setChannel = function(space, index, val) { + if (val === undefined) { + // color.red() + return this.values[space][index]; + } + // color.red(100) + this.values[space][index] = val; + this.setValues(space, this.values[space]); + return this; +} + +window.Color = module.exports = Color + +},{"color-convert":3,"color-string":4}],7:[function(require,module,exports){ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 2.0.2 + * + * Copyright 2015 Nick Downie + * Released under the MIT license + * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + */ + + +var Chart = require('./core/core.js')(); + +require('./core/core.helpers')(Chart); +require('./core/core.element')(Chart); +require('./core/core.animation')(Chart); +require('./core/core.controller')(Chart); +require('./core/core.datasetController')(Chart); +require('./core/core.layoutService')(Chart); +require('./core/core.legend')(Chart); +require('./core/core.scale')(Chart); +require('./core/core.scaleService')(Chart); +require('./core/core.title')(Chart); +require('./core/core.tooltip')(Chart); + +require('./controllers/controller.bar')(Chart); +require('./controllers/controller.bubble')(Chart); +require('./controllers/controller.doughnut')(Chart); +require('./controllers/controller.line')(Chart); +require('./controllers/controller.polarArea')(Chart); +require('./controllers/controller.radar')(Chart); + +require('./scales/scale.category')(Chart); +require('./scales/scale.linear')(Chart); +require('./scales/scale.logarithmic')(Chart); +require('./scales/scale.radialLinear')(Chart); +require('./scales/scale.time')(Chart); + +require('./elements/element.arc')(Chart); +require('./elements/element.line')(Chart); +require('./elements/element.point')(Chart); +require('./elements/element.rectangle')(Chart); + +require('./charts/Chart.Bar')(Chart); +require('./charts/Chart.Bubble')(Chart); +require('./charts/Chart.Doughnut')(Chart); +require('./charts/Chart.Line')(Chart); +require('./charts/Chart.PolarArea')(Chart); +require('./charts/Chart.Radar')(Chart); +require('./charts/Chart.Scatter')(Chart); + +window.Chart = module.exports = Chart; + +},{"./charts/Chart.Bar":8,"./charts/Chart.Bubble":9,"./charts/Chart.Doughnut":10,"./charts/Chart.Line":11,"./charts/Chart.PolarArea":12,"./charts/Chart.Radar":13,"./charts/Chart.Scatter":14,"./controllers/controller.bar":15,"./controllers/controller.bubble":16,"./controllers/controller.doughnut":17,"./controllers/controller.line":18,"./controllers/controller.polarArea":19,"./controllers/controller.radar":20,"./core/core.animation":21,"./core/core.controller":22,"./core/core.datasetController":23,"./core/core.element":24,"./core/core.helpers":25,"./core/core.js":26,"./core/core.layoutService":27,"./core/core.legend":28,"./core/core.scale":29,"./core/core.scaleService":30,"./core/core.title":31,"./core/core.tooltip":32,"./elements/element.arc":33,"./elements/element.line":34,"./elements/element.point":35,"./elements/element.rectangle":36,"./scales/scale.category":37,"./scales/scale.linear":38,"./scales/scale.logarithmic":39,"./scales/scale.radialLinear":40,"./scales/scale.time":41}],8:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + Chart.Bar = function(context, config) { + config.type = 'bar'; + + return new Chart(context, config); + }; + +}; +},{}],9:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + Chart.Bubble = function(context, config) { + config.type = 'bubble'; + return new Chart(context, config); + }; + +}; +},{}],10:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + Chart.Doughnut = function(context, config) { + config.type = 'doughnut'; + + return new Chart(context, config); + }; + +}; +},{}],11:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + Chart.Line = function(context, config) { + config.type = 'line'; + + return new Chart(context, config); + }; + +}; +},{}],12:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + Chart.PolarArea = function(context, config) { + config.type = 'polarArea'; + + return new Chart(context, config); + }; + +}; +},{}],13:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + var defaultConfig = { + aspectRatio: 1 + }; + + Chart.Radar = function(context, config) { + config.options = helpers.configMerge(defaultConfig, config.options); + config.type = 'radar'; + + return new Chart(context, config); + }; + +}; + +},{}],14:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var defaultConfig = { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: "linear", // scatter should not use a category axis + position: "bottom", + id: "x-axis-1" // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: "linear", + position: "left", + id: "y-axis-1" + }] + }, + + tooltips: { + callbacks: { + title: function(tooltipItems, data) { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(tooltipItem, data) { + return '(' + tooltipItem.xLabel + ', ' + tooltipItem.yLabel + ')'; + } + } + } + }; + + // Register the default config for this type + Chart.defaults.scatter = defaultConfig; + + // Scatter charts use line controllers + Chart.controllers.scatter = Chart.controllers.line; + + Chart.Scatter = function(context, config) { + config.type = 'scatter'; + return new Chart(context, config); + }; + +}; +},{}],15:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.bar = { + hover: { + mode: "label" + }, + + scales: { + xAxes: [{ + type: "category", + + // Specific to Bar Controller + categoryPercentage: 0.8, + barPercentage: 0.9, + + // grid line settings + gridLines: { + offsetGridLines: true + } + }], + yAxes: [{ + type: "linear" + }] + } + }; + + Chart.controllers.bar = Chart.DatasetController.extend({ + initialize: function(chart, datasetIndex) { + Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); + + // Use this to indicate that this is a bar dataset. + this.getDataset().bar = true; + }, + // Get the number of datasets that display bars. We use this to correctly calculate the bar width + getBarCount: function getBarCount() { + var barCount = 0; + helpers.each(this.chart.data.datasets, function(dataset) { + if (helpers.isDatasetVisible(dataset) && dataset.bar) { + ++barCount; + } + }); + return barCount; + }, + + addElements: function() { + this.getDataset().metaData = this.getDataset().metaData || []; + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Rectangle({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + }, this); + }, + addElementAndReset: function(index) { + this.getDataset().metaData = this.getDataset().metaData || []; + var rectangle = new Chart.elements.Rectangle({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + + var numBars = this.getBarCount(); + + this.updateElement(rectangle, index, true, numBars); + this.getDataset().metaData.splice(index, 0, rectangle); + }, + + update: function update(reset) { + var numBars = this.getBarCount(); + + helpers.each(this.getDataset().metaData, function(rectangle, index) { + this.updateElement(rectangle, index, reset, numBars); + }, this); + }, + + updateElement: function updateElement(rectangle, index, reset, numBars) { + + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var yScale = this.getScaleForId(this.getDataset().yAxisID); + + var yScalePoint; + + if (yScale.min < 0 && yScale.max < 0) { + // all less than 0. use the top + yScalePoint = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + yScalePoint = yScale.getPixelForValue(yScale.min); + } else { + yScalePoint = yScale.getPixelForValue(0); + } + + helpers.extend(rectangle, { + // Utility + _chart: this.chart.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: this.index, + _index: index, + + + // Desired view properties + _model: { + x: this.calculateBarX(index, this.index), + y: reset ? yScalePoint : this.calculateBarY(index, this.index), + + // Tooltip + label: this.chart.data.labels[index], + datasetLabel: this.getDataset().label, + + // Appearance + base: reset ? yScalePoint : this.calculateBarBase(this.index, index), + width: this.calculateBarWidth(numBars), + backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor), + borderSkipped: rectangle.custom && rectangle.custom.borderSkipped ? rectangle.custom.borderSkipped : this.chart.options.elements.rectangle.borderSkipped, + borderColor: rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor), + borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth) + } + }); + rectangle.pivot(); + }, + + calculateBarBase: function(datasetIndex, index) { + + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var yScale = this.getScaleForId(this.getDataset().yAxisID); + + var base = 0; + + if (yScale.options.stacked) { + + var value = this.chart.data.datasets[datasetIndex].data[index]; + + if (value < 0) { + for (var i = 0; i < datasetIndex; i++) { + var negDS = this.chart.data.datasets[i]; + if (helpers.isDatasetVisible(negDS) && negDS.yAxisID === yScale.id && negDS.bar) { + base += negDS.data[index] < 0 ? negDS.data[index] : 0; + } + } + } else { + for (var j = 0; j < datasetIndex; j++) { + var posDS = this.chart.data.datasets[j]; + if (helpers.isDatasetVisible(posDS) && posDS.yAxisID === yScale.id && posDS.bar) { + base += posDS.data[index] > 0 ? posDS.data[index] : 0; + } + } + } + + return yScale.getPixelForValue(base); + } + + base = yScale.getPixelForValue(yScale.min); + + if (yScale.beginAtZero || ((yScale.min <= 0 && yScale.max >= 0) || (yScale.min >= 0 && yScale.max <= 0))) { + base = yScale.getPixelForValue(0, 0); + //base += yScale.options.gridLines.lineWidth; + } else if (yScale.min < 0 && yScale.max < 0) { + // All values are negative. Use the top as the base + base = yScale.getPixelForValue(yScale.max); + } + + return base; + + }, + + getRuler: function() { + + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var yScale = this.getScaleForId(this.getDataset().yAxisID); + var datasetCount = this.getBarCount(); + + var tickWidth = (function() { + var min = xScale.getPixelForTick(1) - xScale.getPixelForTick(0); + for (var i = 2; i < this.getDataset().data.length; i++) { + min = Math.min(xScale.getPixelForTick(i) - xScale.getPixelForTick(i - 1), min); + } + return min; + }).call(this); + var categoryWidth = tickWidth * xScale.options.categoryPercentage; + var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; + var fullBarWidth = categoryWidth / datasetCount; + var barWidth = fullBarWidth * xScale.options.barPercentage; + var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); + + return { + datasetCount: datasetCount, + tickWidth: tickWidth, + categoryWidth: categoryWidth, + categorySpacing: categorySpacing, + fullBarWidth: fullBarWidth, + barWidth: barWidth, + barSpacing: barSpacing + }; + }, + + calculateBarWidth: function() { + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var ruler = this.getRuler(); + return xScale.options.stacked ? ruler.categoryWidth : ruler.barWidth; + }, + + // Get bar index from the given dataset index accounting for the fact that not all bars are visible + getBarIndex: function(datasetIndex) { + var barIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (helpers.isDatasetVisible(this.chart.data.datasets[j]) && this.chart.data.datasets[j].bar) { + ++barIndex; + } + } + + return barIndex; + }, + + calculateBarX: function(index, datasetIndex) { + + var yScale = this.getScaleForId(this.getDataset().yAxisID); + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var barIndex = this.getBarIndex(datasetIndex); + + var ruler = this.getRuler(); + var leftTick = xScale.getPixelForValue(null, index, datasetIndex, this.chart.isCombo); + leftTick -= this.chart.isCombo ? (ruler.tickWidth / 2) : 0; + + if (xScale.options.stacked) { + return leftTick + (ruler.categoryWidth / 2) + ruler.categorySpacing; + } + + return leftTick + + (ruler.barWidth / 2) + + ruler.categorySpacing + + (ruler.barWidth * barIndex) + + (ruler.barSpacing / 2) + + (ruler.barSpacing * barIndex); + }, + + calculateBarY: function(index, datasetIndex) { + + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var yScale = this.getScaleForId(this.getDataset().yAxisID); + + var value = this.getDataset().data[index]; + + if (yScale.options.stacked) { + + var sumPos = 0, + sumNeg = 0; + + for (var i = 0; i < datasetIndex; i++) { + var ds = this.chart.data.datasets[i]; + if (helpers.isDatasetVisible(ds) && ds.bar && ds.yAxisID === yScale.id) { + if (ds.data[index] < 0) { + sumNeg += ds.data[index] || 0; + } else { + sumPos += ds.data[index] || 0; + } + } + } + + if (value < 0) { + return yScale.getPixelForValue(sumNeg + value); + } else { + return yScale.getPixelForValue(sumPos + value); + } + + return yScale.getPixelForValue(value); + } + + return yScale.getPixelForValue(value); + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + helpers.each(this.getDataset().metaData, function(rectangle, index) { + var d = this.getDataset().data[index]; + if (d !== null && d !== undefined && !isNaN(d)) { + rectangle.transition(easingDecimal).draw(); + } + }, this); + }, + + setHoverStyle: function(rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; + + rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, rectangle._model.borderWidth); + }, + + removeHoverStyle: function(rectangle) { + var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + var index = rectangle._index; + + rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor); + rectangle._model.borderColor = rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor); + rectangle._model.borderWidth = rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth); + } + + }); +}; + +},{}],16:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.bubble = { + hover: { + mode: "single" + }, + + scales: { + xAxes: [{ + type: "linear", // bubble should probably use a linear scale by default + position: "bottom", + id: "x-axis-0" // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: "linear", + position: "left", + id: "y-axis-0" + }] + }, + + tooltips: { + callbacks: { + title: function(tooltipItems, data) { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; + var dataPoint = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + return datasetLabel + ': (' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.r + ')'; + } + } + } + }; + + + Chart.controllers.bubble = Chart.DatasetController.extend({ + addElements: function() { + + this.getDataset().metaData = this.getDataset().metaData || []; + + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + }, this); + }, + addElementAndReset: function(index) { + this.getDataset().metaData = this.getDataset().metaData || []; + var point = new Chart.elements.Point({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + + // Reset the point + this.updateElement(point, index, true); + + // Add to the points array + this.getDataset().metaData.splice(index, 0, point); + }, + + update: function update(reset) { + var points = this.getDataset().metaData; + + var yScale = this.getScaleForId(this.getDataset().yAxisID); + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var scaleBase; + + if (yScale.min < 0 && yScale.max < 0) { + scaleBase = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + scaleBase = yScale.getPixelForValue(yScale.min); + } else { + scaleBase = yScale.getPixelForValue(0); + } + + // Update Points + helpers.each(points, function(point, index) { + this.updateElement(point, index, reset); + }, this); + + }, + + updateElement: function(point, index, reset) { + var yScale = this.getScaleForId(this.getDataset().yAxisID); + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var scaleBase; + + if (yScale.min < 0 && yScale.max < 0) { + scaleBase = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + scaleBase = yScale.getPixelForValue(yScale.min); + } else { + scaleBase = yScale.getPixelForValue(0); + } + + helpers.extend(point, { + // Utility + _chart: this.chart.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: this.index, + _index: index, + + // Desired view properties + _model: { + x: reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo), + y: reset ? scaleBase : yScale.getPixelForValue(this.getDataset().data[index], index, this.index), + // Appearance + radius: reset ? 0 : point.custom && point.custom.radius ? point.custom.radius : this.getRadius(this.getDataset().data[index]), + backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.point.backgroundColor), + borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.point.borderColor), + borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth), + + // Tooltip + hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius) + } + }); + + point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + + point.pivot(); + }, + + getRadius: function(value) { + return value.r || this.chart.options.elements.point.radius; + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + + // Transition and Draw the Points + helpers.each(this.getDataset().metaData, function(point, index) { + point.transition(easingDecimal); + point.draw(); + }); + + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + + point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : (helpers.getValueAtIndexOrDefault(dataset.hoverRadius, index, this.chart.options.elements.point.hoverRadius)) + this.getRadius(this.getDataset().data[point._index]); + point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, point._model.borderWidth); + }, + + removeHoverStyle: function(point) { + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + + point._model.radius = point.custom && point.custom.radius ? point.custom.radius : this.getRadius(this.getDataset().data[point._index]); + point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.point.backgroundColor); + point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.point.borderColor); + point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.point.borderWidth); + } + }); +}; +},{}],17:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.doughnut = { + animation: { + //Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + //Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + aspectRatio: 1, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var text = []; + text.push('
    '); + + if (chart.data.datasets.length) { + for (var i = 0; i < chart.data.datasets[0].data.length; ++i) { + text.push('
  • '); + if (chart.data.labels[i]) { + text.push(chart.data.labels[i]); + } + text.push('
  • '); + } + } + + text.push('
'); + return text.join(""); + }, + legend: { + labels: { + generateLabels: function(data) { + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + return { + text: label, + fillStyle: data.datasets[0].backgroundColor[i], + hidden: isNaN(data.datasets[0].data[i]), + + // Extra data used for toggling the correct item + index: i + }; + }); + } else { + return []; + } + } + }, + onClick: function(e, legendItem) { + helpers.each(this.chart.data.datasets, function(dataset) { + dataset.metaHiddenData = dataset.metaHiddenData || []; + var idx = legendItem.index; + + if (!isNaN(dataset.data[idx])) { + dataset.metaHiddenData[idx] = dataset.data[idx]; + dataset.data[idx] = NaN; + } else if (!isNaN(dataset.metaHiddenData[idx])) { + dataset.data[idx] = dataset.metaHiddenData[idx]; + } + }); + + this.chart.update(); + } + }, + + //The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + //The rotation of the chart, where the first data arc begins. + rotation: Math.PI * -0.5, + + //The total circumference of the chart. + circumference: Math.PI * 2.0, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + return data.labels[tooltipItem.index] + ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + } + } + } + }; + + Chart.defaults.pie = helpers.clone(Chart.defaults.doughnut); + helpers.extend(Chart.defaults.pie, { + cutoutPercentage: 0 + }); + + + Chart.controllers.doughnut = Chart.controllers.pie = Chart.DatasetController.extend({ + linkScales: function() { + // no scales for doughnut + }, + + addElements: function() { + this.getDataset().metaData = this.getDataset().metaData || []; + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + }, this); + }, + addElementAndReset: function(index, colorForNewElement) { + this.getDataset().metaData = this.getDataset().metaData || []; + var arc = new Chart.elements.Arc({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + + if (colorForNewElement && helpers.isArray(this.getDataset().backgroundColor)) { + this.getDataset().backgroundColor.splice(index, 0, colorForNewElement); + } + + // Reset the point + this.updateElement(arc, index, true); + + // Add to the points array + this.getDataset().metaData.splice(index, 0, arc); + }, + + getVisibleDatasetCount: function getVisibleDatasetCount() { + return helpers.where(this.chart.data.datasets, function(ds) { + return helpers.isDatasetVisible(ds); + }).length; + }, + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function getRingIndex(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (helpers.isDatasetVisible(this.chart.data.datasets[j])) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function update(reset) { + var availableWidth = this.chart.chartArea.right - this.chart.chartArea.left - this.chart.options.elements.arc.borderWidth; + var availableHeight = this.chart.chartArea.bottom - this.chart.chartArea.top - this.chart.options.elements.arc.borderWidth; + var minSize = Math.min(availableWidth, availableHeight); + var offset = {x: 0, y: 0}; + + // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc + if (this.chart.options.circumference && this.chart.options.circumference < Math.PI * 2.0) { + var startAngle = this.chart.options.rotation % (Math.PI * 2.0); + startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0); + var endAngle = startAngle + this.chart.options.circumference; + var start = {x: Math.cos(startAngle), y: Math.sin(startAngle)}; + var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)}; + var contains0 = (startAngle <= 0 && 0 <= endAngle) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle); + var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle); + var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle); + var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle); + var cutout = this.chart.options.cutoutPercentage / 100.0; + var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))}; + var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))}; + var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5}; + minSize = Math.min(availableWidth / size.width, availableHeight / size.height); + offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5}; + } + + this.chart.outerRadius = Math.max(minSize / 2, 0); + this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0); + this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.getVisibleDatasetCount(); + this.chart.offsetX = offset.x * this.chart.outerRadius; + this.chart.offsetY = offset.y * this.chart.outerRadius; + + this.getDataset().total = 0; + helpers.each(this.getDataset().data, function(value) { + if (!isNaN(value)) { + this.getDataset().total += Math.abs(value); + } + }, this); + + this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.getRingIndex(this.index)); + this.innerRadius = this.outerRadius - this.chart.radiusLength; + + helpers.each(this.getDataset().metaData, function(arc, index) { + this.updateElement(arc, index, reset); + }, this); + }, + updateElement: function(arc, index, reset) { + var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2; + var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2; + var startAngle = this.chart.options.rotation || (Math.PI * -0.5); // non reset case handled later + var endAngle = this.chart.options.rotation || (Math.PI * -0.5); // non reset case handled later + var circumference = reset && this.chart.options.animation.animateRotate ? 0 : this.calculateCircumference(this.getDataset().data[index]) * ((this.chart.options.circumference || (2.0 * Math.PI)) / (2.0 * Math.PI)); + var innerRadius = reset && this.chart.options.animation.animateScale ? 0 : this.innerRadius; + var outerRadius = reset && this.chart.options.animation.animateScale ? 0 : this.outerRadius; + + helpers.extend(arc, { + // Utility + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index, + + // Desired view properties + _model: { + x: centerX + this.chart.offsetX, + y: centerY + this.chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + + backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor), + hoverBackgroundColor: arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().hoverBackgroundColor, index, this.chart.options.elements.arc.hoverBackgroundColor), + borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth), + borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor), + + label: helpers.getValueAtIndexOrDefault(this.getDataset().label, index, this.chart.data.labels[index]) + } + }); + + // Set correct angles if not resetting + if (!reset) { + + if (index === 0) { + arc._model.startAngle = this.chart.options.rotation || (Math.PI * -0.5); + } else { + arc._model.startAngle = this.getDataset().metaData[index - 1]._model.endAngle; + } + + arc._model.endAngle = arc._model.startAngle + arc._model.circumference; + } + + arc.pivot(); + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + helpers.each(this.getDataset().metaData, function(arc, index) { + arc.transition(easingDecimal).draw(); + }); + }, + + setHoverStyle: function(arc) { + var dataset = this.chart.data.datasets[arc._datasetIndex]; + var index = arc._index; + + arc._model.backgroundColor = arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(arc._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + arc._model.borderColor = arc.custom && arc.custom.hoverBorderColor ? arc.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(arc._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, arc._model.borderWidth); + }, + + removeHoverStyle: function(arc) { + var dataset = this.chart.data.datasets[arc._datasetIndex]; + var index = arc._index; + + arc._model.backgroundColor = arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor); + arc._model.borderColor = arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor); + arc._model.borderWidth = arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth); + }, + + calculateCircumference: function(value) { + if (this.getDataset().total > 0 && !isNaN(value)) { + return (Math.PI * 1.999999) * (value / this.getDataset().total); + } else { + return 0; + } + } + }); +}; +},{}],18:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.line = { + showLines: true, + + hover: { + mode: "label" + }, + + scales: { + xAxes: [{ + type: "category", + id: 'x-axis-0' + }], + yAxes: [{ + type: "linear", + id: 'y-axis-0' + }] + } + }; + + + Chart.controllers.line = Chart.DatasetController.extend({ + addElements: function() { + + this.getDataset().metaData = this.getDataset().metaData || []; + + this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _points: this.getDataset().metaData + }); + + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + }, this); + }, + addElementAndReset: function(index) { + this.getDataset().metaData = this.getDataset().metaData || []; + var point = new Chart.elements.Point({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + + // Reset the point + this.updateElement(point, index, true); + + // Add to the points array + this.getDataset().metaData.splice(index, 0, point); + + // Make sure bezier control points are updated + if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0) + this.updateBezierControlPoints(); + }, + + update: function update(reset) { + var line = this.getDataset().metaDataset; + var points = this.getDataset().metaData; + + var yScale = this.getScaleForId(this.getDataset().yAxisID); + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var scaleBase; + + if (yScale.min < 0 && yScale.max < 0) { + scaleBase = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + scaleBase = yScale.getPixelForValue(yScale.min); + } else { + scaleBase = yScale.getPixelForValue(0); + } + + // Update Line + if (this.chart.options.showLines) { + // Utility + line._scale = yScale; + line._datasetIndex = this.index; + // Data + line._children = points; + // Model + line._model = { + // Appearance + tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), + backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor), + borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth), + borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor), + borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle), + borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash), + borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset), + borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle), + fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill), + // Scale + scaleTop: yScale.top, + scaleBottom: yScale.bottom, + scaleZero: scaleBase + }; + line.pivot(); + } + + // Update Points + helpers.each(points, function(point, index) { + this.updateElement(point, index, reset); + }, this); + + if (this.chart.options.showLines && this.chart.options.elements.line.tension !== 0) + this.updateBezierControlPoints(); + }, + + getPointBackgroundColor: function(point, index) { + var backgroundColor = this.chart.options.elements.point.backgroundColor; + var dataset = this.getDataset(); + + if (point.custom && point.custom.backgroundColor) { + backgroundColor = point.custom.backgroundColor; + } else if (dataset.pointBackgroundColor) { + backgroundColor = helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, backgroundColor); + } else if (dataset.backgroundColor) { + backgroundColor = dataset.backgroundColor; + } + + return backgroundColor; + }, + getPointBorderColor: function(point, index) { + var borderColor = this.chart.options.elements.point.borderColor; + var dataset = this.getDataset(); + + if (point.custom && point.custom.borderColor) { + borderColor = point.custom.borderColor; + } else if (dataset.pointBorderColor) { + borderColor = helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, borderColor); + } else if (dataset.borderColor) { + borderColor = dataset.borderColor; + } + + return borderColor; + }, + getPointBorderWidth: function(point, index) { + var borderWidth = this.chart.options.elements.point.borderWidth; + var dataset = this.getDataset(); + + if (point.custom && point.custom.borderWidth !== undefined) { + borderWidth = point.custom.borderWidth; + } else if (dataset.pointBorderWidth !== undefined) { + borderWidth = helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, borderWidth); + } else if (dataset.borderWidth !== undefined) { + borderWidth = dataset.borderWidth; + } + + return borderWidth; + }, + + updateElement: function(point, index, reset) { + var yScale = this.getScaleForId(this.getDataset().yAxisID); + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var scaleBase; + + if (yScale.min < 0 && yScale.max < 0) { + scaleBase = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + scaleBase = yScale.getPixelForValue(yScale.min); + } else { + scaleBase = yScale.getPixelForValue(0); + } + + // Utility + point._chart = this.chart.chart; + point._xScale = xScale; + point._yScale = yScale; + point._datasetIndex = this.index; + point._index = index; + + // Desired view properties + point._model = { + x: xScale.getPixelForValue(this.getDataset().data[index], index, this.index, this.chart.isCombo), + y: reset ? scaleBase : this.calculatePointY(this.getDataset().data[index], index, this.index, this.chart.isCombo), + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), + radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius), + pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle), + backgroundColor: this.getPointBackgroundColor(point, index), + borderColor: this.getPointBorderColor(point, index), + borderWidth: this.getPointBorderWidth(point, index), + // Tooltip + hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius) + }; + + point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + }, + + calculatePointY: function(value, index, datasetIndex, isCombo) { + + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var yScale = this.getScaleForId(this.getDataset().yAxisID); + + if (yScale.options.stacked) { + + var sumPos = 0, + sumNeg = 0; + + for (var i = 0; i < datasetIndex; i++) { + var ds = this.chart.data.datasets[i]; + if (ds.type === 'line' && helpers.isDatasetVisible(ds)) { + if (ds.data[index] < 0) { + sumNeg += ds.data[index] || 0; + } else { + sumPos += ds.data[index] || 0; + } + } + } + + if (value < 0) { + return yScale.getPixelForValue(sumNeg + value); + } else { + return yScale.getPixelForValue(sumPos + value); + } + } + + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function() { + // Update bezier control points + helpers.each(this.getDataset().metaData, function(point, index) { + var controlPoints = helpers.splineCurve( + helpers.previousItem(this.getDataset().metaData, index)._model, + point._model, + helpers.nextItem(this.getDataset().metaData, index)._model, + point._model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left); + point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top); + + point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left); + point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top); + + // Now pivot the point for animation + point.pivot(); + }, this); + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + + // Transition Point Locations + helpers.each(this.getDataset().metaData, function(point) { + point.transition(easingDecimal); + }); + + // Transition and Draw the line + if (this.chart.options.showLines) + this.getDataset().metaDataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(this.getDataset().metaData, function(point) { + point.draw(); + }); + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + + point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth); + }, + + removeHoverStyle: function(point) { + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + + point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius); + point._model.backgroundColor = this.getPointBackgroundColor(point, index); + point._model.borderColor = this.getPointBorderColor(point, index); + point._model.borderWidth = this.getPointBorderWidth(point, index); + } + }); +}; + +},{}],19:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.polarArea = { + + scale: { + type: "radialLinear", + lineArc: true // so that lines are circular + }, + + //Boolean - Whether to animate the rotation of the chart + animateRotate: true, + animateScale: true, + + aspectRatio: 1, + legendCallback: function(chart) { + var text = []; + text.push('
    '); + + if (chart.data.datasets.length) { + for (var i = 0; i < chart.data.datasets[0].data.length; ++i) { + text.push('
  • '); + if (chart.data.labels[i]) { + text.push(chart.data.labels[i]); + } + text.push('
  • '); + } + } + + text.push('
'); + return text.join(""); + }, + legend: { + labels: { + generateLabels: function(data) { + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + return { + text: label, + fillStyle: data.datasets[0].backgroundColor[i], + hidden: isNaN(data.datasets[0].data[i]), + + // Extra data used for toggling the correct item + index: i + }; + }); + } else { + return []; + } + } + }, + onClick: function(e, legendItem) { + helpers.each(this.chart.data.datasets, function(dataset) { + dataset.metaHiddenData = dataset.metaHiddenData || []; + var idx = legendItem.index; + + if (!isNaN(dataset.data[idx])) { + dataset.metaHiddenData[idx] = dataset.data[idx]; + dataset.data[idx] = NaN; + } else if (!isNaN(dataset.metaHiddenData[idx])) { + dataset.data[idx] = dataset.metaHiddenData[idx]; + } + }); + + this.chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + return data.labels[tooltipItem.index] + ': ' + tooltipItem.yLabel; + } + } + } + }; + + Chart.controllers.polarArea = Chart.DatasetController.extend({ + linkScales: function() { + // no scales for doughnut + }, + addElements: function() { + this.getDataset().metaData = this.getDataset().metaData || []; + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Arc({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + }, this); + }, + addElementAndReset: function(index) { + this.getDataset().metaData = this.getDataset().metaData || []; + var arc = new Chart.elements.Arc({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + + // Reset the point + this.updateElement(arc, index, true); + + // Add to the points array + this.getDataset().metaData.splice(index, 0, arc); + }, + getVisibleDatasetCount: function getVisibleDatasetCount() { + return helpers.where(this.chart.data.datasets, function(ds) { + return helpers.isDatasetVisible(ds); + }).length; + }, + + update: function update(reset) { + var minSize = Math.min(this.chart.chartArea.right - this.chart.chartArea.left, this.chart.chartArea.bottom - this.chart.chartArea.top); + this.chart.outerRadius = Math.max((minSize - this.chart.options.elements.arc.borderWidth / 2) / 2, 0); + this.chart.innerRadius = Math.max(this.chart.options.cutoutPercentage ? (this.chart.outerRadius / 100) * (this.chart.options.cutoutPercentage) : 1, 0); + this.chart.radiusLength = (this.chart.outerRadius - this.chart.innerRadius) / this.getVisibleDatasetCount(); + + this.getDataset().total = 0; + helpers.each(this.getDataset().data, function(value) { + this.getDataset().total += Math.abs(value); + }, this); + + this.outerRadius = this.chart.outerRadius - (this.chart.radiusLength * this.index); + this.innerRadius = this.outerRadius - this.chart.radiusLength; + + helpers.each(this.getDataset().metaData, function(arc, index) { + this.updateElement(arc, index, reset); + }, this); + }, + + updateElement: function(arc, index, reset) { + var circumference = this.calculateCircumference(this.getDataset().data[index]); + var centerX = (this.chart.chartArea.left + this.chart.chartArea.right) / 2; + var centerY = (this.chart.chartArea.top + this.chart.chartArea.bottom) / 2; + + // If there is NaN data before us, we need to calculate the starting angle correctly. + // We could be way more efficient here, but its unlikely that the polar area chart will have a lot of data + var notNullIndex = 0; + for (var i = 0; i < index; ++i) { + if (!isNaN(this.getDataset().data[i])) { + ++notNullIndex; + } + } + + var startAngle = (-0.5 * Math.PI) + (circumference * notNullIndex); + var endAngle = startAngle + circumference; + + var resetModel = { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: this.chart.options.animateScale ? 0 : this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[index]), + startAngle: this.chart.options.animateRotate ? Math.PI * -0.5 : startAngle, + endAngle: this.chart.options.animateRotate ? Math.PI * -0.5 : endAngle, + + backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor), + borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth), + borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor), + + label: helpers.getValueAtIndexOrDefault(this.chart.data.labels, index, this.chart.data.labels[index]) + }; + + helpers.extend(arc, { + // Utility + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index, + _scale: this.chart.scale, + + // Desired view properties + _model: reset ? resetModel : { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: this.chart.scale.getDistanceFromCenterForValue(this.getDataset().data[index]), + startAngle: startAngle, + endAngle: endAngle, + + backgroundColor: arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor), + borderWidth: arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth), + borderColor: arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor), + + label: helpers.getValueAtIndexOrDefault(this.chart.data.labels, index, this.chart.data.labels[index]) + } + }); + + arc.pivot(); + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + helpers.each(this.getDataset().metaData, function(arc, index) { + arc.transition(easingDecimal).draw(); + }); + }, + + setHoverStyle: function(arc) { + var dataset = this.chart.data.datasets[arc._datasetIndex]; + var index = arc._index; + + arc._model.backgroundColor = arc.custom && arc.custom.hoverBackgroundColor ? arc.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(arc._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + arc._model.borderColor = arc.custom && arc.custom.hoverBorderColor ? arc.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(arc._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + arc._model.borderWidth = arc.custom && arc.custom.hoverBorderWidth ? arc.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, arc._model.borderWidth); + }, + + removeHoverStyle: function(arc) { + var dataset = this.chart.data.datasets[arc._datasetIndex]; + var index = arc._index; + + arc._model.backgroundColor = arc.custom && arc.custom.backgroundColor ? arc.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.arc.backgroundColor); + arc._model.borderColor = arc.custom && arc.custom.borderColor ? arc.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.arc.borderColor); + arc._model.borderWidth = arc.custom && arc.custom.borderWidth ? arc.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.arc.borderWidth); + }, + + calculateCircumference: function(value) { + if (isNaN(value)) { + return 0; + } else { + // Count the number of NaN values + var numNaN = helpers.where(this.getDataset().data, function(data) { + return isNaN(data); + }).length; + + return (2 * Math.PI) / (this.getDataset().data.length - numNaN); + } + } + }); + +}; +},{}],20:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + + Chart.defaults.radar = { + scale: { + type: "radialLinear" + }, + elements: { + line: { + tension: 0 // no bezier in radar + } + } + }; + + Chart.controllers.radar = Chart.DatasetController.extend({ + linkScales: function() { + // No need. Single scale only + }, + + addElements: function() { + + this.getDataset().metaData = this.getDataset().metaData || []; + + this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _points: this.getDataset().metaData, + _loop: true + }); + + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index, + _model: { + x: 0, //xScale.getPixelForValue(null, index, true), + y: 0 //this.chartArea.bottom, + } + }); + }, this); + }, + addElementAndReset: function(index) { + this.getDataset().metaData = this.getDataset().metaData || []; + var point = new Chart.elements.Point({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index + }); + + // Reset the point + this.updateElement(point, index, true); + + // Add to the points array + this.getDataset().metaData.splice(index, 0, point); + + // Make sure bezier control points are updated + this.updateBezierControlPoints(); + }, + + update: function update(reset) { + + var line = this.getDataset().metaDataset; + var points = this.getDataset().metaData; + + var scale = this.chart.scale; + var scaleBase; + + if (scale.min < 0 && scale.max < 0) { + scaleBase = scale.getPointPositionForValue(0, scale.max); + } else if (scale.min > 0 && scale.max > 0) { + scaleBase = scale.getPointPositionForValue(0, scale.min); + } else { + scaleBase = scale.getPointPositionForValue(0, 0); + } + + helpers.extend(this.getDataset().metaDataset, { + // Utility + _datasetIndex: this.index, + // Data + _children: this.getDataset().metaData, + // Model + _model: { + // Appearance + tension: line.custom && line.custom.tension ? line.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), + backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor), + borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth), + borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor), + fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill), + borderCapStyle: line.custom && line.custom.borderCapStyle ? line.custom.borderCapStyle : (this.getDataset().borderCapStyle || this.chart.options.elements.line.borderCapStyle), + borderDash: line.custom && line.custom.borderDash ? line.custom.borderDash : (this.getDataset().borderDash || this.chart.options.elements.line.borderDash), + borderDashOffset: line.custom && line.custom.borderDashOffset ? line.custom.borderDashOffset : (this.getDataset().borderDashOffset || this.chart.options.elements.line.borderDashOffset), + borderJoinStyle: line.custom && line.custom.borderJoinStyle ? line.custom.borderJoinStyle : (this.getDataset().borderJoinStyle || this.chart.options.elements.line.borderJoinStyle), + + // Scale + scaleTop: scale.top, + scaleBottom: scale.bottom, + scaleZero: scaleBase + } + }); + + this.getDataset().metaDataset.pivot(); + + // Update Points + helpers.each(points, function(point, index) { + this.updateElement(point, index, reset); + }, this); + + + // Update bezier control points + this.updateBezierControlPoints(); + }, + updateElement: function(point, index, reset) { + var pointPosition = this.chart.scale.getPointPositionForValue(index, this.getDataset().data[index]); + + helpers.extend(point, { + // Utility + _datasetIndex: this.index, + _index: index, + _scale: this.chart.scale, + + // Desired view properties + _model: { + x: reset ? this.chart.scale.xCenter : pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales + y: reset ? this.chart.scale.yCenter : pointPosition.y, + + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : helpers.getValueOrDefault(this.getDataset().tension, this.chart.options.elements.line.tension), + radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().pointRadius, index, this.chart.options.elements.point.radius), + backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor), + borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor), + borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth), + pointStyle: point.custom && point.custom.pointStyle ? point.custom.pointStyle : helpers.getValueAtIndexOrDefault(this.getDataset().pointStyle, index, this.chart.options.elements.point.pointStyle), + + // Tooltip + hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius) + } + }); + + point._model.skip = point.custom && point.custom.skip ? point.custom.skip : (isNaN(point._model.x) || isNaN(point._model.y)); + }, + updateBezierControlPoints: function() { + helpers.each(this.getDataset().metaData, function(point, index) { + var controlPoints = helpers.splineCurve( + helpers.previousItem(this.getDataset().metaData, index, true)._model, + point._model, + helpers.nextItem(this.getDataset().metaData, index, true)._model, + point._model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + point._model.controlPointPreviousX = Math.max(Math.min(controlPoints.previous.x, this.chart.chartArea.right), this.chart.chartArea.left); + point._model.controlPointPreviousY = Math.max(Math.min(controlPoints.previous.y, this.chart.chartArea.bottom), this.chart.chartArea.top); + + point._model.controlPointNextX = Math.max(Math.min(controlPoints.next.x, this.chart.chartArea.right), this.chart.chartArea.left); + point._model.controlPointNextY = Math.max(Math.min(controlPoints.next.y, this.chart.chartArea.bottom), this.chart.chartArea.top); + + // Now pivot the point for animation + point.pivot(); + }, this); + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + + // Transition Point Locations + helpers.each(this.getDataset().metaData, function(point, index) { + point.transition(easingDecimal); + }); + + // Transition and Draw the line + this.getDataset().metaDataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(this.getDataset().metaData, function(point) { + point.draw(); + }); + }, + + setHoverStyle: function(point) { + // Point + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + + point._model.radius = point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderWidth, index, point._model.borderWidth); + }, + + removeHoverStyle: function(point) { + var dataset = this.chart.data.datasets[point._datasetIndex]; + var index = point._index; + + point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius); + point._model.backgroundColor = point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor); + point._model.borderColor = point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor); + point._model.borderWidth = point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth); + } + }); +}; +},{}],21:[function(require,module,exports){ +/*global window: false */ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.animation = { + duration: 1000, + easing: "easeOutQuart", + onProgress: helpers.noop, + onComplete: helpers.noop + }; + + Chart.Animation = Chart.Element.extend({ + currentStep: null, // the current animation step + numSteps: 60, // default number of steps + easing: "", // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null // user specified callback to fire when the animation finishes + }); + + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + request: null, + addAnimation: function(chartInstance, animationObject, duration, lazy) { + + if (!lazy) { + chartInstance.animating = true; + } + + for (var index = 0; index < this.animations.length; ++index) { + if (this.animations[index].chartInstance === chartInstance) { + // replacing an in progress animation + this.animations[index].animationObject = animationObject; + return; + } + } + + this.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (this.animations.length === 1) { + this.requestAnimationFrame(); + } + }, + // Cancel the animation for a given chart instance + cancelAnimation: function(chartInstance) { + var index = helpers.findIndex(this.animations, function(animationWrapper) { + return animationWrapper.chartInstance === chartInstance; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chartInstance.animating = false; + } + }, + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers.requestAnimFrame.call(window, function() { + me.request = null; + me.startDigest(); + }); + } + }, + startDigest: function() { + + var startTime = Date.now(); + var framesToDrop = 0; + + if (this.dropFrames > 1) { + framesToDrop = Math.floor(this.dropFrames); + this.dropFrames = this.dropFrames % 1; + } + + var i = 0; + while (i < this.animations.length) { + if (this.animations[i].animationObject.currentStep === null) { + this.animations[i].animationObject.currentStep = 0; + } + + this.animations[i].animationObject.currentStep += 1 + framesToDrop; + + if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) { + this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; + } + + this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); + if (this.animations[i].animationObject.onAnimationProgress && this.animations[i].animationObject.onAnimationProgress.call) { + this.animations[i].animationObject.onAnimationProgress.call(this.animations[i].chartInstance, this.animations[i]); + } + + if (this.animations[i].animationObject.currentStep === this.animations[i].animationObject.numSteps) { + if (this.animations[i].animationObject.onAnimationComplete && this.animations[i].animationObject.onAnimationComplete.call) { + this.animations[i].animationObject.onAnimationComplete.call(this.animations[i].chartInstance, this.animations[i]); + } + + // executed the last frame. Remove the animation. + this.animations[i].chartInstance.animating = false; + + this.animations.splice(i, 1); + } else { + ++i; + } + } + + var endTime = Date.now(); + var dropFrames = (endTime - startTime) / this.frameDuration; + + this.dropFrames += dropFrames; + + // Do we have more stuff to animate? + if (this.animations.length > 0) { + this.requestAnimationFrame(); + } + } + }; +}; +},{}],22:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + //Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + //Store a reference to each instance - allowing us to globally resize chart instances on window resize. + //Destroy method on the chart will remove the instance of the chart from this reference. + Chart.instances = {}; + + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; + + // The main controller of a chart + Chart.Controller = function(instance) { + + this.chart = instance; + this.config = instance.config; + this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {}); + this.id = helpers.uid(); + + Object.defineProperty(this, 'data', { + get: function() { + return this.config.data; + } + }); + + //Add the chart instance to the global namespace + Chart.instances[this.id] = this; + + if (this.options.responsive) { + // Silent resize before chart draws + this.resize(true); + } + + this.initialize(); + + return this; + }; + + helpers.extend(Chart.Controller.prototype, { + + initialize: function initialize() { + + // TODO + // If BeforeInit(this) doesn't return false, proceed + + this.bindEvents(); + + // Make sure controllers are built first so that each dataset is bound to an axis before the scales + // are built + this.ensureScalesHaveIDs(); + this.buildOrUpdateControllers(); + this.buildScales(); + this.buildSurroundingItems(); + this.updateLayout(); + this.resetElements(); + this.initToolTip(); + this.update(); + + // TODO + // If AfterInit(this) doesn't return false, proceed + + return this; + }, + + clear: function clear() { + helpers.clear(this.chart); + return this; + }, + + stop: function stop() { + // Stops any current animation loop occuring + Chart.animationService.cancelAnimation(this); + return this; + }, + + resize: function resize(silent) { + var canvas = this.chart.canvas; + var newWidth = helpers.getMaximumWidth(this.chart.canvas); + var newHeight = (this.options.maintainAspectRatio && isNaN(this.chart.aspectRatio) === false && isFinite(this.chart.aspectRatio) && this.chart.aspectRatio !== 0) ? newWidth / this.chart.aspectRatio : helpers.getMaximumHeight(this.chart.canvas); + + var sizeChanged = this.chart.width !== newWidth || this.chart.height !== newHeight; + + if (!sizeChanged) + return this; + + canvas.width = this.chart.width = newWidth; + canvas.height = this.chart.height = newHeight; + + helpers.retinaScale(this.chart); + + if (!silent) { + this.stop(); + this.update(this.options.responsiveAnimationDuration); + } + + return this; + }, + ensureScalesHaveIDs: function ensureScalesHaveIDs() { + var defaultXAxisID = 'x-axis-'; + var defaultYAxisID = 'y-axis-'; + + if (this.options.scales) { + if (this.options.scales.xAxes && this.options.scales.xAxes.length) { + helpers.each(this.options.scales.xAxes, function(xAxisOptions, index) { + xAxisOptions.id = xAxisOptions.id || (defaultXAxisID + index); + }); + } + + if (this.options.scales.yAxes && this.options.scales.yAxes.length) { + // Build the y axes + helpers.each(this.options.scales.yAxes, function(yAxisOptions, index) { + yAxisOptions.id = yAxisOptions.id || (defaultYAxisID + index); + }); + } + } + }, + buildScales: function buildScales() { + // Map of scale ID to scale object so we can lookup later + this.scales = {}; + + // Build the x axes + if (this.options.scales) { + if (this.options.scales.xAxes && this.options.scales.xAxes.length) { + helpers.each(this.options.scales.xAxes, function(xAxisOptions, index) { + var xType = helpers.getValueOrDefault(xAxisOptions.type, 'category'); + var ScaleClass = Chart.scaleService.getScaleConstructor(xType); + if (ScaleClass) { + var scale = new ScaleClass({ + ctx: this.chart.ctx, + options: xAxisOptions, + chart: this, + id: xAxisOptions.id + }); + + this.scales[scale.id] = scale; + } + }, this); + } + + if (this.options.scales.yAxes && this.options.scales.yAxes.length) { + // Build the y axes + helpers.each(this.options.scales.yAxes, function(yAxisOptions, index) { + var yType = helpers.getValueOrDefault(yAxisOptions.type, 'linear'); + var ScaleClass = Chart.scaleService.getScaleConstructor(yType); + if (ScaleClass) { + var scale = new ScaleClass({ + ctx: this.chart.ctx, + options: yAxisOptions, + chart: this, + id: yAxisOptions.id + }); + + this.scales[scale.id] = scale; + } + }, this); + } + } + if (this.options.scale) { + // Build radial axes + var ScaleClass = Chart.scaleService.getScaleConstructor(this.options.scale.type); + if (ScaleClass) { + var scale = new ScaleClass({ + ctx: this.chart.ctx, + options: this.options.scale, + chart: this + }); + + this.scale = scale; + + this.scales.radialScale = scale; + } + } + + Chart.scaleService.addScalesToLayout(this); + }, + + buildSurroundingItems: function() { + if (this.options.title) { + this.titleBlock = new Chart.Title({ + ctx: this.chart.ctx, + options: this.options.title, + chart: this + }); + + Chart.layoutService.addBox(this, this.titleBlock); + } + + if (this.options.legend) { + this.legend = new Chart.Legend({ + ctx: this.chart.ctx, + options: this.options.legend, + chart: this + }); + + Chart.layoutService.addBox(this, this.legend); + } + }, + + updateLayout: function() { + Chart.layoutService.update(this, this.chart.width, this.chart.height); + }, + + buildOrUpdateControllers: function buildOrUpdateControllers() { + var types = []; + var newControllers = []; + + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + if (!dataset.type) { + dataset.type = this.config.type; + } + + var type = dataset.type; + types.push(type); + + if (dataset.controller) { + dataset.controller.updateIndex(datasetIndex); + } else { + dataset.controller = new Chart.controllers[type](this, datasetIndex); + newControllers.push(dataset.controller); + } + }, this); + + if (types.length > 1) { + for (var i = 1; i < types.length; i++) { + if (types[i] !== types[i - 1]) { + this.isCombo = true; + break; + } + } + } + + return newControllers; + }, + + resetElements: function resetElements() { + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.controller.reset(); + }); + }, + + update: function update(animationDuration, lazy) { + // In case the entire data object changed + this.tooltip._data = this.data; + + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = this.buildOrUpdateControllers(); + + Chart.layoutService.update(this, this.chart.width, this.chart.height); + + // Can only reset the new controllers after the scales have been updated + helpers.each(newControllers, function(controller) { + controller.reset(); + }); + + // Make sure all dataset controllers have correct meta data counts + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.controller.buildOrUpdateElements(); + }); + + // This will loop through any data and do the appropriate element update for the type + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.controller.update(); + }); + this.render(animationDuration, lazy); + }, + + render: function render(duration, lazy) { + + if (this.options.animation && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && this.options.animation.duration !== 0))) { + var animation = new Chart.Animation(); + animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps + animation.easing = this.options.animation.easing; + + // render function + animation.render = function(chartInstance, animationObject) { + var easingFunction = helpers.easingEffects[animationObject.easing]; + var stepDecimal = animationObject.currentStep / animationObject.numSteps; + var easeDecimal = easingFunction(stepDecimal); + + chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); + }; + + // user events + animation.onAnimationProgress = this.options.animation.onProgress; + animation.onAnimationComplete = this.options.animation.onComplete; + + Chart.animationService.addAnimation(this, animation, duration, lazy); + } else { + this.draw(); + if (this.options.animation && this.options.animation.onComplete && this.options.animation.onComplete.call) { + this.options.animation.onComplete.call(this); + } + } + return this; + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + this.clear(); + + // Draw all the scales + helpers.each(this.boxes, function(box) { + box.draw(this.chartArea); + }, this); + if (this.scale) { + this.scale.draw(); + } + + // Clip out the chart area so that anything outside does not draw. This is necessary for zoom and pan to function + this.chart.ctx.save(); + this.chart.ctx.beginPath(); + this.chart.ctx.rect(this.chartArea.left, this.chartArea.top, this.chartArea.right - this.chartArea.left, this.chartArea.bottom - this.chartArea.top); + this.chart.ctx.clip(); + + // Draw each dataset via its respective controller (reversed to support proper line stacking) + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + if (helpers.isDatasetVisible(dataset)) { + dataset.controller.draw(ease); + } + }, null, true); + + // Restore from the clipping operation + this.chart.ctx.restore(); + + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); + }, + + // Get the single element that was clicked on + // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw + getElementAtEvent: function(e) { + + var eventPosition = helpers.getRelativePosition(e, this.chart); + var elementsArray = []; + + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + if (helpers.isDatasetVisible(dataset)) { + helpers.each(dataset.metaData, function(element, index) { + if (element.inRange(eventPosition.x, eventPosition.y)) { + elementsArray.push(element); + return elementsArray; + } + }); + } + }); + + return elementsArray; + }, + + getElementsAtEvent: function(e) { + var eventPosition = helpers.getRelativePosition(e, this.chart); + var elementsArray = []; + + var found = (function() { + if (this.data.datasets) { + for (var i = 0; i < this.data.datasets.length; i++) { + if (helpers.isDatasetVisible(this.data.datasets[i])) { + for (var j = 0; j < this.data.datasets[i].metaData.length; j++) { + if (this.data.datasets[i].metaData[j].inRange(eventPosition.x, eventPosition.y)) { + return this.data.datasets[i].metaData[j]; + } + } + } + } + } + }).call(this); + + if (!found) { + return elementsArray; + } + + helpers.each(this.data.datasets, function(dataset, dsIndex) { + if (helpers.isDatasetVisible(dataset)) { + elementsArray.push(dataset.metaData[found._index]); + } + }); + + return elementsArray; + }, + + getDatasetAtEvent: function(e) { + var elementsArray = this.getElementAtEvent(e); + + if (elementsArray.length > 0) { + elementsArray = this.data.datasets[elementsArray[0]._datasetIndex].metaData; + } + + return elementsArray; + }, + + generateLegend: function generateLegend() { + return this.options.legendCallback(this); + }, + + destroy: function destroy() { + this.clear(); + helpers.unbindEvents(this, this.events); + helpers.removeResizeListener(this.chart.canvas.parentNode); + + // Reset canvas height/width attributes + var canvas = this.chart.canvas; + canvas.width = this.chart.width; + canvas.height = this.chart.height; + + // if we scaled the canvas in response to a devicePixelRatio !== 1, we need to undo that transform here + if (this.chart.originalDevicePixelRatio !== undefined) { + this.chart.ctx.scale(1 / this.chart.originalDevicePixelRatio, 1 / this.chart.originalDevicePixelRatio); + } + + // Reset to the old style since it may have been changed by the device pixel ratio changes + canvas.style.width = this.chart.originalCanvasStyleWidth; + canvas.style.height = this.chart.originalCanvasStyleHeight; + + delete Chart.instances[this.id]; + }, + + toBase64Image: function toBase64Image() { + return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); + }, + + initToolTip: function initToolTip() { + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _chartInstance: this, + _data: this.data, + _options: this.options + }, this); + }, + + bindEvents: function bindEvents() { + helpers.bindEvents(this, this.options.events, function(evt) { + this.eventHandler(evt); + }); + }, + eventHandler: function eventHandler(e) { + this.lastActive = this.lastActive || []; + this.lastTooltipActive = this.lastTooltipActive || []; + + // Find Active Elements for hover and tooltips + if (e.type === 'mouseout') { + this.active = []; + this.tooltipActive = []; + } else { + + var _this = this; + var getItemsForMode = function(mode) { + switch (mode) { + case 'single': + return _this.getElementAtEvent(e); + case 'label': + return _this.getElementsAtEvent(e); + case 'dataset': + return _this.getDatasetAtEvent(e); + default: + return e; + } + }; + + this.active = getItemsForMode(this.options.hover.mode); + this.tooltipActive = getItemsForMode(this.options.tooltips.mode); + } + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } + + if (e.type === 'mouseup' || e.type === 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + + if (this.legend && this.legend.handleEvent) { + this.legend.handleEvent(e); + } + } + + var dataset; + var index; + + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hover.mode) { + case 'single': + this.data.datasets[this.lastActive[0]._datasetIndex].controller.removeHoverStyle(this.lastActive[0], this.lastActive[0]._datasetIndex, this.lastActive[0]._index); + break; + case 'label': + case 'dataset': + for (var i = 0; i < this.lastActive.length; i++) { + if (this.lastActive[i]) + this.data.datasets[this.lastActive[i]._datasetIndex].controller.removeHoverStyle(this.lastActive[i], this.lastActive[i]._datasetIndex, this.lastActive[i]._index); + } + break; + default: + // Don't change anything + } + } + + // Built in hover styling + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { + case 'single': + this.data.datasets[this.active[0]._datasetIndex].controller.setHoverStyle(this.active[0]); + break; + case 'label': + case 'dataset': + for (var j = 0; j < this.active.length; j++) { + if (this.active[j]) + this.data.datasets[this.active[j]._datasetIndex].controller.setHoverStyle(this.active[j]); + } + break; + default: + // Don't change anything + } + } + + + // Built in Tooltips + if (this.options.tooltips.enabled || this.options.tooltips.custom) { + + // The usual updates + this.tooltip.initialize(); + this.tooltip._active = this.tooltipActive; + this.tooltip.update(); + } + + // Hover animations + this.tooltip.pivot(); + + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + helpers.each(this.tooltipActive, function(element, index) { + if (element !== this.lastTooltipActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((this.lastActive.length !== this.active.length) || + (this.lastTooltipActive.length !== this.tooltipActive.length) || + changed) { + + this.stop(); + + if (this.options.tooltips.enabled || this.options.tooltips.custom) { + this.tooltip.update(true); + } + + // We only need to render at this point. Updating will cause scales to be recomputed generating flicker & using more + // memory than necessary. + this.render(this.options.hover.animationDuration, true); + } + } + + // Remember Last Actives + this.lastActive = this.active; + this.lastTooltipActive = this.tooltipActive; + return this; + } + }); +}; + +},{}],23:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + // Base class for all dataset controllers (line, bar, etc) + Chart.DatasetController = function(chart, datasetIndex) { + this.initialize.call(this, chart, datasetIndex); + }; + + helpers.extend(Chart.DatasetController.prototype, { + initialize: function(chart, datasetIndex) { + this.chart = chart; + this.index = datasetIndex; + this.linkScales(); + this.addElements(); + }, + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + if (!this.getDataset().xAxisID) { + this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id; + } + + if (!this.getDataset().yAxisID) { + this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + reset: function() { + this.update(true); + }, + + buildOrUpdateElements: function buildOrUpdateElements() { + // Handle the number of data points changing + var numData = this.getDataset().data.length; + var numMetaData = this.getDataset().metaData.length; + + // Make sure that we handle number of datapoints changing + if (numData < numMetaData) { + // Remove excess bars for data points that have been removed + this.getDataset().metaData.splice(numData, numMetaData - numData); + } else if (numData > numMetaData) { + // Add new elements + for (var index = numMetaData; index < numData; ++index) { + this.addElementAndReset(index); + } + } + }, + + // Controllers should implement the following + addElements: helpers.noop, + addElementAndReset: helpers.noop, + draw: helpers.noop, + removeHoverStyle: helpers.noop, + setHoverStyle: helpers.noop, + update: helpers.noop + }); + + Chart.DatasetController.extend = helpers.inherits; + +}; +},{}],24:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.elements = {}; + + Chart.Element = function(configuration) { + helpers.extend(this, configuration); + this.initialize.apply(this, arguments); + }; + helpers.extend(Chart.Element.prototype, { + initialize: function() {}, + pivot: function() { + if (!this._view) { + this._view = helpers.clone(this._model); + } + this._start = helpers.clone(this._view); + return this; + }, + transition: function(ease) { + if (!this._view) { + this._view = helpers.clone(this._model); + } + + // No animation -> No Transition + if (ease === 1) { + this._view = this._model; + this._start = null; + return this; + } + + if (!this._start) { + this.pivot(); + } + + helpers.each(this._model, function(value, key) { + + if (key[0] === '_' || !this._model.hasOwnProperty(key)) { + // Only non-underscored properties + } + + // Init if doesn't exist + else if (!this._view.hasOwnProperty(key)) { + if (typeof value === 'number' && !isNaN(this._view[key])) { + this._view[key] = value * ease; + } else { + this._view[key] = value; + } + } + + // No unnecessary computations + else if (value === this._view[key]) { + // It's the same! Woohoo! + } + + // Color transitions if possible + else if (typeof value === 'string') { + try { + var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease); + this._view[key] = color.rgbString(); + } catch (err) { + this._view[key] = value; + } + } + // Number transitions + else if (typeof value === 'number') { + var startVal = this._start[key] !== undefined && isNaN(this._start[key]) === false ? this._start[key] : 0; + this._view[key] = ((this._model[key] - startVal) * ease) + startVal; + } + // Everything else + else { + this._view[key] = value; + } + }, this); + + return this; + }, + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + hasValue: function() { + return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); + } + }); + + Chart.Element.extend = helpers.inherits; + +}; + +},{}],25:[function(require,module,exports){ +/*global window: false */ +/*global document: false */ +"use strict"; + +var color = require('chartjs-color'); + +module.exports = function(Chart) { + + //Global Chart helpers object for utility methods and classes + var helpers = Chart.helpers = {}; + + //-- Basic js utility methods + helpers.each = function(loopable, callback, self, reverse) { + // Check to see if null or undefined firstly. + var i, len; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + callback.call(self, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + callback.call(self, loopable[i], i); + } + } + } else if (typeof loopable === 'object') { + var keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + callback.call(self, loopable[keys[i]], keys[i]); + } + } + }; + helpers.clone = function(obj) { + var objClone = {}; + helpers.each(obj, function(value, key) { + if (obj.hasOwnProperty(key)) { + if (helpers.isArray(value)) { + objClone[key] = value.slice(0); + } else if (typeof value === 'object' && value !== null) { + objClone[key] = helpers.clone(value); + } else { + objClone[key] = value; + } + } + }); + return objClone; + }; + helpers.extend = function(base) { + var len = arguments.length; + var additionalArgs = []; + for (var i = 1; i < len; i++) { + additionalArgs.push(arguments[i]); + } + helpers.each(additionalArgs, function(extensionObject) { + helpers.each(extensionObject, function(value, key) { + if (extensionObject.hasOwnProperty(key)) { + base[key] = value; + } + }); + }); + return base; + }; + // Need a special merge function to chart configs since they are now grouped + helpers.configMerge = function(_base) { + var base = helpers.clone(_base); + helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { + helpers.each(extension, function(value, key) { + if (extension.hasOwnProperty(key)) { + if (key === 'scales') { + // Scale config merging is complex. Add out own function here for that + base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value); + + } else if (key === 'scale') { + // Used in polar area & radar charts since there is only one scale + base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value); + } else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { + // In this case we have an array of objects replacing another array. Rather than doing a strict replace, + // merge. This allows easy scale option merging + var baseArray = base[key]; + + helpers.each(value, function(valueObj, index) { + + if (index < baseArray.length) { + if (typeof baseArray[index] === 'object' && baseArray[index] !== null && typeof valueObj === 'object' && valueObj !== null) { + // Two objects are coming together. Do a merge of them. + baseArray[index] = helpers.configMerge(baseArray[index], valueObj); + } else { + // Just overwrite in this case since there is nothing to merge + baseArray[index] = valueObj; + } + } else { + baseArray.push(valueObj); // nothing to merge + } + }); + + } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { + // If we are overwriting an object with an object, do a merge of the properties. + base[key] = helpers.configMerge(base[key], value); + + } else { + // can just overwrite the value in this case + base[key] = value; + } + } + }); + }); + + return base; + }; + helpers.extendDeep = function(_base) { + return _extendDeep.apply(this, arguments); + + function _extendDeep(dst) { + helpers.each(arguments, function(obj) { + if (obj !== dst) { + helpers.each(obj, function(value, key) { + if (dst[key] && dst[key].constructor && dst[key].constructor === Object) { + _extendDeep(dst[key], value); + } else { + dst[key] = value; + } + }); + } + }); + return dst; + } + }; + helpers.scaleMerge = function(_base, extension) { + var base = helpers.clone(_base); + + helpers.each(extension, function(value, key) { + if (extension.hasOwnProperty(key)) { + if (key === 'xAxes' || key === 'yAxes') { + // These properties are arrays of items + if (base.hasOwnProperty(key)) { + helpers.each(value, function(valueObj, index) { + var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); + var axisDefaults = Chart.scaleService.getScaleDefaults(axisType); + if (index >= base[key].length || !base[key][index].type) { + base[key].push(helpers.configMerge(axisDefaults, valueObj)); + } else if (valueObj.type && valueObj.type !== base[key][index].type) { + // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults + base[key][index] = helpers.configMerge(base[key][index], axisDefaults, valueObj); + } else { + // Type is the same + base[key][index] = helpers.configMerge(base[key][index], valueObj); + } + }); + } else { + base[key] = []; + helpers.each(value, function(valueObj) { + var axisType = helpers.getValueOrDefault(valueObj.type, key === 'xAxes' ? 'category' : 'linear'); + base[key].push(helpers.configMerge(Chart.scaleService.getScaleDefaults(axisType), valueObj)); + }); + } + } else if (base.hasOwnProperty(key) && typeof base[key] === "object" && base[key] !== null && typeof value === "object") { + // If we are overwriting an object with an object, do a merge of the properties. + base[key] = helpers.configMerge(base[key], value); + + } else { + // can just overwrite the value in this case + base[key] = value; + } + } + }); + + return base; + }; + helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { + if (value === undefined || value === null) { + return defaultValue; + } + + if (helpers.isArray(value)) { + return index < value.length ? value[index] : defaultValue; + } + + return value; + }; + helpers.getValueOrDefault = function(value, defaultValue) { + return value === undefined ? defaultValue : value; + }; + helpers.indexOf = function(arrayToSearch, item) { + if (Array.prototype.indexOf) { + return arrayToSearch.indexOf(item); + } else { + for (var i = 0; i < arrayToSearch.length; i++) { + if (arrayToSearch[i] === item) + return i; + } + return -1; + } + }; + helpers.where = function(collection, filterCallback) { + var filtered = []; + + helpers.each(collection, function(item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); + + return filtered; + }; + helpers.findIndex = function(arrayToSearch, callback, thisArg) { + var index = -1; + if (Array.prototype.findIndex) { + index = arrayToSearch.findIndex(callback, thisArg); + } else { + for (var i = 0; i < arrayToSearch.length; ++i) { + thisArg = thisArg !== undefined ? thisArg : arrayToSearch; + + if (callback.call(thisArg, arrayToSearch[i], i, arrayToSearch)) { + index = i; + break; + } + } + } + + return index; + }; + helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to start of the array + if (startIndex === undefined || startIndex === null) { + startIndex = -1; + } + for (var i = startIndex + 1; i < arrayToSearch.length; i++) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to end of the array + if (startIndex === undefined || startIndex === null) { + startIndex = arrayToSearch.length; + } + for (var i = startIndex - 1; i >= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers.inherits = function(extensions) { + //Basic javascript inheritance based on the model created in Backbone.js + var parent = this; + var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() { + return parent.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + Surrogate.prototype = parent.prototype; + ChartElement.prototype = new Surrogate(); + + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = parent.prototype; + + return ChartElement; + }; + helpers.noop = function() {}; + helpers.uid = (function() { + var id = 0; + return function() { + return "chart-" + id++; + }; + })(); + helpers.warn = function(str) { + //Method for warning of errors + if (console && typeof console.warn === "function") { + console.warn(str); + } + }; + //-- Math methods + helpers.isNumber = function(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + helpers.almostEquals = function(x, y, epsilon) { + return Math.abs(x - y) < epsilon; + }; + helpers.max = function(array) { + return array.reduce(function(max, value) { + if (!isNaN(value)) { + return Math.max(max, value); + } else { + return max; + } + }, Number.NEGATIVE_INFINITY); + }; + helpers.min = function(array) { + return array.reduce(function(min, value) { + if (!isNaN(value)) { + return Math.min(min, value); + } else { + return min; + } + }, Number.POSITIVE_INFINITY); + }; + helpers.sign = function(x) { + if (Math.sign) { + return Math.sign(x); + } else { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + } + }; + helpers.log10 = function(x) { + if (Math.log10) { + return Math.log10(x); + } else { + return Math.log(x) / Math.LN10; + } + }; + helpers.toRadians = function(degrees) { + return degrees * (Math.PI / 180); + }; + helpers.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }; + // Gets the angle from vertical upright to the point about a centre. + helpers.getAngleFromPoint = function(centrePoint, anglePoint) { + var distanceFromXCenter = anglePoint.x - centrePoint.x, + distanceFromYCenter = anglePoint.y - centrePoint.y, + radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + + var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } + + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }; + helpers.aliasPixel = function(pixelWidth) { + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }; + helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { + //Props to Rob Spencer at scaled innovation for his post on splining between points + //http://scaledinnovation.com/analytics/splines/aboutSplines.html + + // This function must also respect "skipped" points + + var previous = firstPoint.skip ? middlePoint : firstPoint, + current = middlePoint, + next = afterPoint.skip ? middlePoint : afterPoint; + + var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + + var s01 = d01 / (d01 + d12); + var s12 = d12 / (d01 + d12); + + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; + + var fa = t * s01; // scaling factor for triangle Ta + var fb = t * s12; + + return { + previous: { + x: current.x - fa * (next.x - previous.x), + y: current.y - fa * (next.y - previous.y) + }, + next: { + x: current.x + fb * (next.x - previous.x), + y: current.y + fb * (next.y - previous.y) + } + }; + }; + helpers.nextItem = function(collection, index, loop) { + if (loop) { + return index >= collection.length - 1 ? collection[0] : collection[index + 1]; + } + + return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; + }; + helpers.previousItem = function(collection, index, loop) { + if (loop) { + return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; + } + return index <= 0 ? collection[0] : collection[index - 1]; + }; + // Implementation of the nice number algorithm used in determining where axis labels will go + helpers.niceNum = function(range, round) { + var exponent = Math.floor(helpers.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else { + if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } + + return niceFraction * Math.pow(10, exponent); + }; + //Easing functions adapted from Robert Penner's easing equations + //http://www.robertpenner.com/easing/ + var easingEffects = helpers.easingEffects = { + 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 * 0.3; + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + 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 * 0.3; + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + 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 * (0.3 * 1.5); + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.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) * 0.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 - easingEffects.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 + 0.75); + } else if (t < (2.5 / 2.75)) { + return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); + } else { + return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); + } + }, + easeInOutBounce: function(t) { + if (t < 1 / 2) { + return easingEffects.easeInBounce(t * 2) * 0.5; + } + return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; + } + }; + //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + helpers.requestAnimFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + })(); + helpers.cancelAnimFrame = (function() { + return window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.mozCancelAnimationFrame || + window.oCancelAnimationFrame || + window.msCancelAnimationFrame || + function(callback) { + return window.clearTimeout(callback, 1000 / 60); + }; + })(); + //-- DOM methods + helpers.getRelativePosition = function(evt, chart) { + var mouseX, mouseY; + var e = evt.originalEvent || evt, + canvas = evt.currentTarget || evt.srcElement, + boundingRect = canvas.getBoundingClientRect(); + + if (e.touches && e.touches.length > 0) { + mouseX = e.touches[0].clientX; + mouseY = e.touches[0].clientY; + + } else { + mouseX = e.clientX; + mouseY = e.clientY; + } + + // Scale mouse coordinates into canvas coordinates + // by following the pattern laid out by 'jerryj' in the comments of + // http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ + var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left')); + var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top')); + var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right')); + var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom')); + var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; + var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; + + // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However + // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here + mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); + mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); + + return { + x: mouseX, + y: mouseY + }; + + }; + helpers.addEvent = function(node, eventType, method) { + if (node.addEventListener) { + node.addEventListener(eventType, method); + } else if (node.attachEvent) { + node.attachEvent("on" + eventType, method); + } else { + node["on" + eventType] = method; + } + }; + helpers.removeEvent = function(node, eventType, handler) { + if (node.removeEventListener) { + node.removeEventListener(eventType, handler, false); + } else if (node.detachEvent) { + node.detachEvent("on" + eventType, handler); + } else { + node["on" + eventType] = helpers.noop; + } + }; + helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) { + // Create the events object if it's not already present + if (!chartInstance.events) + chartInstance.events = {}; + + helpers.each(arrayOfEvents, function(eventName) { + chartInstance.events[eventName] = function() { + handler.apply(chartInstance, arguments); + }; + helpers.addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]); + }); + }; + helpers.unbindEvents = function(chartInstance, arrayOfEvents) { + helpers.each(arrayOfEvents, function(handler, eventName) { + helpers.removeEvent(chartInstance.chart.canvas, eventName, handler); + }); + }; + + // Private helper function to convert max-width/max-height values that may be percentages into a number + function parseMaxStyle(styleValue, node, parentProperty) { + var valueInPixels; + if (typeof(styleValue) === 'string') { + valueInPixels = parseInt(styleValue, 10); + + if (styleValue.indexOf('%') != -1) { + // percentage * size in dimension + valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + } + } else { + valueInPixels = styleValue; + } + + return valueInPixels; + } + + // Private helper to get a constraint dimension + // @param domNode : the node to check the constraint on + // @param maxStyle : the style that defines the maximum for the direction we are using (max-width / max-height) + // @param percentageProperty : property of parent to use when calculating width as a percentage + function getConstraintDimension(domNode, maxStyle, percentageProperty) { + var constrainedDimension; + var constrainedNode = document.defaultView.getComputedStyle(domNode)[maxStyle]; + var constrainedContainer = document.defaultView.getComputedStyle(domNode.parentNode)[maxStyle]; + var hasCNode = constrainedNode !== null && constrainedNode !== "none"; + var hasCContainer = constrainedContainer !== null && constrainedContainer !== "none"; + + if (hasCNode || hasCContainer) { + constrainedDimension = Math.min((hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : Number.POSITIVE_INFINITY), (hasCContainer ? parseMaxStyle(constrainedContainer, domNode.parentNode, percentageProperty) : Number.POSITIVE_INFINITY)); + } + return constrainedDimension; + } + // returns Number or undefined if no constraint + helpers.getConstraintWidth = function(domNode) { + return getConstraintDimension(domNode, 'max-width', 'clientWidth'); + }; + // returns Number or undefined if no constraint + helpers.getConstraintHeight = function(domNode) { + return getConstraintDimension(domNode, 'max-height', 'clientHeight'); + }; + helpers.getMaximumWidth = function(domNode) { + var container = domNode.parentNode; + var padding = parseInt(helpers.getStyle(container, 'padding-left')) + parseInt(helpers.getStyle(container, 'padding-right')); + + var w = container.clientWidth - padding; + var cw = helpers.getConstraintWidth(domNode); + if (cw !== undefined) { + w = Math.min(w, cw); + } + + return w; + }; + helpers.getMaximumHeight = function(domNode) { + var container = domNode.parentNode; + var padding = parseInt(helpers.getStyle(container, 'padding-top')) + parseInt(helpers.getStyle(container, 'padding-bottom')); + + var h = container.clientHeight - padding; + var ch = helpers.getConstraintHeight(domNode); + if (ch !== undefined) { + h = Math.min(h, ch); + } + + return h; + }; + helpers.getStyle = function(el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }; + helpers.retinaScale = function(chart) { + var ctx = chart.ctx; + var width = chart.canvas.width; + var height = chart.canvas.height; + var pixelRatio = chart.currentDevicePixelRatio = window.devicePixelRatio || 1; + + if (pixelRatio !== 1) { + ctx.canvas.height = height * pixelRatio; + ctx.canvas.width = width * pixelRatio; + ctx.scale(pixelRatio, pixelRatio); + + // Store the device pixel ratio so that we can go backwards in `destroy`. + // The devicePixelRatio changes with zoom, so there are no guarantees that it is the same + // when destroy is called + chart.originalDevicePixelRatio = chart.originalDevicePixelRatio || pixelRatio; + } + + ctx.canvas.style.width = width + 'px'; + ctx.canvas.style.height = height + 'px'; + }; + //-- Canvas methods + helpers.clear = function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }; + helpers.fontString = function(pixelSize, fontStyle, fontFamily) { + return fontStyle + " " + pixelSize + "px " + fontFamily; + }; + helpers.longestText = function(ctx, font, arrayOfStrings, cache) { + cache = cache || {}; + cache.data = cache.data || {}; + cache.garbageCollect = cache.garbageCollect || []; + + if (cache.font !== font) { + cache.data = {}; + cache.garbageCollect = []; + cache.font = font; + } + + ctx.font = font; + var longest = 0; + helpers.each(arrayOfStrings, function(string) { + // Undefined strings should not be measured + if (string !== undefined && string !== null) { + var textWidth = cache.data[string]; + if (!textWidth) { + textWidth = cache.data[string] = ctx.measureText(string).width; + cache.garbageCollect.push(string); + } + + if (textWidth > longest) { + longest = textWidth; + } + } + }); + + var gcLen = cache.garbageCollect.length / 2; + if (gcLen > arrayOfStrings.length) { + for (var i = 0; i < gcLen; i++) { + delete cache.data[cache.garbageCollect[i]]; + } + cache.garbageCollect.splice(0, gcLen); + } + + return longest; + }; + helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) { + ctx.beginPath(); + ctx.moveTo(x + radius, y); + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + }; + helpers.color = function(c) { + if (!color) { + console.log('Color.js not found!'); + return c; + } + + /* global CanvasGradient */ + if (c instanceof CanvasGradient) { + return color(Chart.defaults.global.defaultColor); + } + + return color(c); + }; + helpers.addResizeListener = function(node, callback) { + // Hide an iframe before the node + var hiddenIframe = document.createElement('iframe'); + var hiddenIframeClass = 'chartjs-hidden-iframe'; + + if (hiddenIframe.classlist) { + // can use classlist + hiddenIframe.classlist.add(hiddenIframeClass); + } else { + hiddenIframe.setAttribute('class', hiddenIframeClass); + } + + // Set the style + hiddenIframe.style.width = '100%'; + hiddenIframe.style.display = 'block'; + hiddenIframe.style.border = 0; + hiddenIframe.style.height = 0; + hiddenIframe.style.margin = 0; + hiddenIframe.style.position = 'absolute'; + hiddenIframe.style.left = 0; + hiddenIframe.style.right = 0; + hiddenIframe.style.top = 0; + hiddenIframe.style.bottom = 0; + + // Insert the iframe so that contentWindow is available + node.insertBefore(hiddenIframe, node.firstChild); + + (hiddenIframe.contentWindow || hiddenIframe).onresize = function() { + if (callback) { + callback(); + } + }; + }; + helpers.removeResizeListener = function(node) { + var hiddenIframe = node.querySelector('.chartjs-hidden-iframe'); + + // Remove the resize detect iframe + if (hiddenIframe) { + hiddenIframe.parentNode.removeChild(hiddenIframe); + } + }; + helpers.isArray = function(obj) { + if (!Array.isArray) { + return Object.prototype.toString.call(obj) === '[object Array]'; + } + return Array.isArray(obj); + }; + helpers.pushAllIfDefined = function(element, array) { + if (typeof element === "undefined") { + return; + } + + if (helpers.isArray(element)) { + array.push.apply(array, element); + } else { + array.push(element); + } + }; + helpers.isDatasetVisible = function(dataset) { + return !dataset.hidden; + }; + helpers.callCallback = function(fn, args, _tArg) { + if (fn && typeof fn.call === 'function') { + fn.apply(_tArg, args); + } + }; + +}; + +},{"chartjs-color":6}],26:[function(require,module,exports){ +"use strict"; + +module.exports = function() { + + //Occupy the global variable of Chart, and create a simple base class + var Chart = function(context, config) { + this.config = config; + + // Support a jQuery'd canvas element + if (context.length && context[0].getContext) { + context = context[0]; + } + + // Support a canvas domnode + if (context.getContext) { + context = context.getContext("2d"); + } + + this.ctx = context; + this.canvas = context.canvas; + + // Figure out what the size of the chart will be. + // If the canvas has a specified width and height, we use those else + // we look to see if the canvas node has a CSS width and height. + // If there is still no height, fill the parent container + this.width = context.canvas.width || parseInt(Chart.helpers.getStyle(context.canvas, 'width')) || Chart.helpers.getMaximumWidth(context.canvas); + this.height = context.canvas.height || parseInt(Chart.helpers.getStyle(context.canvas, 'height')) || Chart.helpers.getMaximumHeight(context.canvas); + + this.aspectRatio = this.width / this.height; + + if (isNaN(this.aspectRatio) || isFinite(this.aspectRatio) === false) { + // If the canvas has no size, try and figure out what the aspect ratio will be. + // Some charts prefer square canvases (pie, radar, etc). If that is specified, use that + // else use the canvas default ratio of 2 + this.aspectRatio = config.aspectRatio !== undefined ? config.aspectRatio : 2; + } + + // Store the original style of the element so we can set it back + this.originalCanvasStyleWidth = context.canvas.style.width; + this.originalCanvasStyleHeight = context.canvas.style.height; + + // High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. + Chart.helpers.retinaScale(this); + + if (config) { + this.controller = new Chart.Controller(this); + } + + // Always bind this so that if the responsive state changes we still work + var _this = this; + Chart.helpers.addResizeListener(context.canvas.parentNode, function() { + if (_this.controller && _this.controller.config.options.responsive) { + _this.controller.resize(); + } + }); + + return this.controller ? this.controller : this; + + }; + + //Globally expose the defaults to allow for user updating/changing + Chart.defaults = { + global: { + responsive: true, + responsiveAnimationDuration: 0, + maintainAspectRatio: true, + events: ["mousemove", "mouseout", "click", "touchstart", "touchmove"], + hover: { + onHover: null, + mode: 'single', + animationDuration: 400 + }, + onClick: null, + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + showLines: true, + + // Element defaults defined in element extensions + elements: {}, + + // Legend callback string + legendCallback: function(chart) { + var text = []; + text.push('
    '); + for (var i = 0; i < chart.data.datasets.length; i++) { + text.push('
  • '); + if (chart.data.datasets[i].label) { + text.push(chart.data.datasets[i].label); + } + text.push('
  • '); + } + text.push('
'); + + return text.join(""); + } + } + }; + + return Chart; + +}; + +},{}],27:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + // The layout service is very self explanatory. It's responsible for the layout within a chart. + // Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need + // It is this service's responsibility of carrying out that layout. + Chart.layoutService = { + defaults: {}, + + // Register a box to a chartInstance. A box is simply a reference to an object that requires layout. eg. Scales, Legend, Plugins. + addBox: function(chartInstance, box) { + if (!chartInstance.boxes) { + chartInstance.boxes = []; + } + chartInstance.boxes.push(box); + }, + + removeBox: function(chartInstance, box) { + if (!chartInstance.boxes) { + return; + } + chartInstance.boxes.splice(chartInstance.boxes.indexOf(box), 1); + }, + + // The most important function + update: function(chartInstance, width, height) { + + if (!chartInstance) { + return; + } + + var xPadding = 0; + var yPadding = 0; + + var leftBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === "left"; + }); + var rightBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === "right"; + }); + var topBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === "top"; + }); + var bottomBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === "bottom"; + }); + + // Boxes that overlay the chartarea such as the radialLinear scale + var chartAreaBoxes = helpers.where(chartInstance.boxes, function(box) { + return box.options.position === "chartArea"; + }); + + function fullWidthSorter(a, b) { + + } + + // Ensure that full width boxes are at the very top / bottom + topBoxes.sort(function(a, b) { + return (b.options.fullWidth ? 1 : 0) - (a.options.fullWidth ? 1 : 0); + }); + bottomBoxes.sort(function(a, b) { + return (a.options.fullWidth ? 1 : 0) - (b.options.fullWidth ? 1 : 0); + }); + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + // What we do to find the best sizing, we do the following + // 1. Determine the minimum size of the chart area. + // 2. Split the remaining width equally between each vertical axis + // 3. Split the remaining height equally between each horizontal axis + // 4. Give each layout the maximum size it can be. The layout will return it's minimum size + // 5. Adjust the sizes of each axis based on it's minimum reported size. + // 6. Refit each axis + // 7. Position each axis in the final location + // 8. Tell the chart the final location of the chart area + // 9. Tell any axes that overlay the chart area the positions of the chart area + + // Step 1 + var chartWidth = width - (2 * xPadding); + var chartHeight = height - (2 * yPadding); + var chartAreaWidth = chartWidth / 2; // min 50% + var chartAreaHeight = chartHeight / 2; // min 50% + + // Step 2 + var verticalBoxWidth = (width - chartAreaWidth) / (leftBoxes.length + rightBoxes.length); + + // Step 3 + var horizontalBoxHeight = (height - chartAreaHeight) / (topBoxes.length + bottomBoxes.length); + + // Step 4 + var maxChartAreaWidth = chartWidth; + var maxChartAreaHeight = chartHeight; + var minBoxSizes = []; + + helpers.each(leftBoxes.concat(rightBoxes, topBoxes, bottomBoxes), getMinimumBoxSize); + + function getMinimumBoxSize(box) { + var minSize; + var isHorizontal = box.isHorizontal(); + + if (isHorizontal) { + minSize = box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, horizontalBoxHeight); + maxChartAreaHeight -= minSize.height; + } else { + minSize = box.update(verticalBoxWidth, chartAreaHeight); + maxChartAreaWidth -= minSize.width; + } + + minBoxSizes.push({ + horizontal: isHorizontal, + minSize: minSize, + box: box + }); + } + + // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. + + // Steps 5 & 6 + var totalLeftBoxesWidth = xPadding; + var totalRightBoxesWidth = xPadding; + var totalTopBoxesHeight = yPadding; + var totalBottomBoxesHeight = yPadding; + + // Update, and calculate the left and right margins for the horizontal boxes + helpers.each(leftBoxes.concat(rightBoxes), fitBox); + + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); + + // Set the Left and Right margins for the horizontal boxes + helpers.each(topBoxes.concat(bottomBoxes), fitBox); + + // Function to fit a box + function fitBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { + return minBoxSize.box === box; + }); + + if (minBoxSize) { + if (box.isHorizontal()) { + var scaleMargin = { + left: totalLeftBoxesWidth, + right: totalRightBoxesWidth, + top: 0, + bottom: 0 + }; + + // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends + // on the margin. Sometimes they need to increase in size slightly + box.update(box.options.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin); + } else { + box.update(minBoxSize.minSize.width, maxChartAreaHeight); + } + } + } + + // Figure out how much margin is on the top and bottom of the vertical boxes + helpers.each(topBoxes, function(box) { + totalTopBoxesHeight += box.height; + }); + + helpers.each(bottomBoxes, function(box) { + totalBottomBoxesHeight += box.height; + }); + + // Let the left layout know the final margin + helpers.each(leftBoxes.concat(rightBoxes), finalFitVerticalBox); + + function finalFitVerticalBox(box) { + var minBoxSize = helpers.findNextWhere(minBoxSizes, function(minBoxSize) { + return minBoxSize.box === box; + }); + + var scaleMargin = { + left: 0, + right: 0, + top: totalTopBoxesHeight, + bottom: totalBottomBoxesHeight + }; + + if (minBoxSize) { + box.update(minBoxSize.minSize.width, maxChartAreaHeight, scaleMargin); + } + } + + // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance) + totalLeftBoxesWidth = xPadding; + totalRightBoxesWidth = xPadding; + totalTopBoxesHeight = yPadding; + totalBottomBoxesHeight = yPadding; + + helpers.each(leftBoxes, function(box) { + totalLeftBoxesWidth += box.width; + }); + + helpers.each(rightBoxes, function(box) { + totalRightBoxesWidth += box.width; + }); + + helpers.each(topBoxes, function(box) { + totalTopBoxesHeight += box.height; + }); + helpers.each(bottomBoxes, function(box) { + totalBottomBoxesHeight += box.height; + }); + + // Figure out if our chart area changed. This would occur if the dataset layout label rotation + // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do + // without calling `fit` again + var newMaxChartAreaHeight = height - totalTopBoxesHeight - totalBottomBoxesHeight; + var newMaxChartAreaWidth = width - totalLeftBoxesWidth - totalRightBoxesWidth; + + if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) { + helpers.each(leftBoxes, function(box) { + box.height = newMaxChartAreaHeight; + }); + + helpers.each(rightBoxes, function(box) { + box.height = newMaxChartAreaHeight; + }); + + helpers.each(topBoxes, function(box) { + box.width = newMaxChartAreaWidth; + }); + + helpers.each(bottomBoxes, function(box) { + box.width = newMaxChartAreaWidth; + }); + + maxChartAreaHeight = newMaxChartAreaHeight; + maxChartAreaWidth = newMaxChartAreaWidth; + } + + // Step 7 - Position the boxes + var left = xPadding; + var top = yPadding; + var right = 0; + var bottom = 0; + + helpers.each(leftBoxes.concat(topBoxes), placeBox); + + // Account for chart width and height + left += maxChartAreaWidth; + top += maxChartAreaHeight; + + helpers.each(rightBoxes, placeBox); + helpers.each(bottomBoxes, placeBox); + + function placeBox(box) { + if (box.isHorizontal()) { + box.left = box.options.fullWidth ? xPadding : totalLeftBoxesWidth; + box.right = box.options.fullWidth ? width - xPadding : totalLeftBoxesWidth + maxChartAreaWidth; + box.top = top; + box.bottom = top + box.height; + + // Move to next point + top = box.bottom; + + } else { + + box.left = left; + box.right = left + box.width; + box.top = totalTopBoxesHeight; + box.bottom = totalTopBoxesHeight + maxChartAreaHeight; + + // Move to next point + left = box.right; + } + } + + // Step 8 + chartInstance.chartArea = { + left: totalLeftBoxesWidth, + top: totalTopBoxesHeight, + right: totalLeftBoxesWidth + maxChartAreaWidth, + bottom: totalTopBoxesHeight + maxChartAreaHeight + }; + + // Step 9 + helpers.each(chartAreaBoxes, function(box) { + box.left = chartInstance.chartArea.left; + box.top = chartInstance.chartArea.top; + box.right = chartInstance.chartArea.right; + box.bottom = chartInstance.chartArea.bottom; + + box.update(maxChartAreaWidth, maxChartAreaHeight); + }); + } + }; +}; + +},{}],28:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.legend = { + + display: true, + position: 'top', + fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) + reverse: false, + + // a callback that will handle + onClick: function(e, legendItem) { + var dataset = this.chart.data.datasets[legendItem.datasetIndex]; + dataset.hidden = !dataset.hidden; + + // We hid a dataset ... rerender the chart + this.chart.update(); + }, + + labels: { + boxWidth: 40, + padding: 10, + // Generates labels shown in the legend + // Valid properties to return: + // text : text to display + // fillStyle : fill of coloured box + // strokeStyle: stroke of coloured box + // hidden : if this legend item refers to a hidden item + // lineCap : cap style for line + // lineDash + // lineDashOffset : + // lineJoin : + // lineWidth : + generateLabels: function(data) { + return helpers.isArray(data.datasets) ? data.datasets.map(function(dataset, i) { + return { + text: dataset.label, + fillStyle: dataset.backgroundColor, + hidden: dataset.hidden, + lineCap: dataset.borderCapStyle, + lineDash: dataset.borderDash, + lineDashOffset: dataset.borderDashOffset, + lineJoin: dataset.borderJoinStyle, + lineWidth: dataset.borderWidth, + strokeStyle: dataset.borderColor, + + // Below is extra data used for toggling the datasets + datasetIndex: i + }; + }, this) : []; + } + } + }; + + Chart.Legend = Chart.Element.extend({ + + initialize: function(config) { + helpers.extend(this, config); + + // Contains hit boxes for each dataset (in dataset order) + this.legendHitBoxes = []; + + // Are we in doughnut mode which has a different data type + this.doughnutMode = false; + }, + + // These methods are ordered by lifecyle. Utilities then follow. + // Any function defined here is inherited by all legend types. + // Any function can be extended by the legend type + + beforeUpdate: helpers.noop, + update: function(maxWidth, maxHeight, margins) { + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + this.beforeUpdate(); + + // Absorb the master measurements + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this.margins = margins; + + // Dimensions + this.beforeSetDimensions(); + this.setDimensions(); + this.afterSetDimensions(); + // Labels + this.beforeBuildLabels(); + this.buildLabels(); + this.afterBuildLabels(); + + // Fit + this.beforeFit(); + this.fit(); + this.afterFit(); + // + this.afterUpdate(); + + return this.minSize; + + }, + afterUpdate: helpers.noop, + + // + + beforeSetDimensions: helpers.noop, + setDimensions: function() { + // Set the unconstrained dimension before label rotation + if (this.isHorizontal()) { + // Reset position before calculating rotation + this.width = this.maxWidth; + this.left = 0; + this.right = this.width; + } else { + this.height = this.maxHeight; + + // Reset position before calculating rotation + this.top = 0; + this.bottom = this.height; + } + + // Reset padding + this.paddingLeft = 0; + this.paddingTop = 0; + this.paddingRight = 0; + this.paddingBottom = 0; + + // Reset minSize + this.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: helpers.noop, + + // + + beforeBuildLabels: helpers.noop, + buildLabels: function() { + this.legendItems = this.options.labels.generateLabels.call(this, this.chart.data); + if(this.options.reverse){ + this.legendItems.reverse(); + } + }, + afterBuildLabels: helpers.noop, + + // + + beforeFit: helpers.noop, + fit: function() { + + var ctx = this.ctx; + var fontSize = helpers.getValueOrDefault(this.options.labels.fontSize, Chart.defaults.global.defaultFontSize); + var fontStyle = helpers.getValueOrDefault(this.options.labels.fontStyle, Chart.defaults.global.defaultFontStyle); + var fontFamily = helpers.getValueOrDefault(this.options.labels.fontFamily, Chart.defaults.global.defaultFontFamily); + var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + + // Reset hit boxes + this.legendHitBoxes = []; + + // Width + if (this.isHorizontal()) { + this.minSize.width = this.maxWidth; // fill all the width + } else { + this.minSize.width = this.options.display ? 10 : 0; + } + + // height + if (this.isHorizontal()) { + this.minSize.height = this.options.display ? 10 : 0; + } else { + this.minSize.height = this.maxHeight; // fill all the height + } + + // Increase sizes here + if (this.options.display) { + if (this.isHorizontal()) { + // Labels + + // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one + this.lineWidths = [0]; + var totalHeight = this.legendItems.length ? fontSize + (this.options.labels.padding) : 0; + + ctx.textAlign = "left"; + ctx.textBaseline = 'top'; + ctx.font = labelFont; + + helpers.each(this.legendItems, function(legendItem, i) { + var width = this.options.labels.boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + if (this.lineWidths[this.lineWidths.length - 1] + width + this.options.labels.padding >= this.width) { + totalHeight += fontSize + (this.options.labels.padding); + this.lineWidths[this.lineWidths.length] = this.left; + } + + // Store the hitbox width and height here. Final position will be updated in `draw` + this.legendHitBoxes[i] = { + left: 0, + top: 0, + width: width, + height: fontSize + }; + + this.lineWidths[this.lineWidths.length - 1] += width + this.options.labels.padding; + }, this); + + this.minSize.height += totalHeight; + + } else { + // TODO vertical + } + } + + this.width = this.minSize.width; + this.height = this.minSize.height; + + }, + afterFit: helpers.noop, + + // Shared Methods + isHorizontal: function() { + return this.options.position === "top" || this.options.position === "bottom"; + }, + + // Actualy draw the legend on the canvas + draw: function() { + if (this.options.display) { + var ctx = this.ctx; + var cursor = { + x: this.left + ((this.width - this.lineWidths[0]) / 2), + y: this.top + this.options.labels.padding, + line: 0 + }; + + var fontColor = helpers.getValueOrDefault(this.options.labels.fontColor, Chart.defaults.global.defaultFontColor); + var fontSize = helpers.getValueOrDefault(this.options.labels.fontSize, Chart.defaults.global.defaultFontSize); + var fontStyle = helpers.getValueOrDefault(this.options.labels.fontStyle, Chart.defaults.global.defaultFontStyle); + var fontFamily = helpers.getValueOrDefault(this.options.labels.fontFamily, Chart.defaults.global.defaultFontFamily); + var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily); + + // Horizontal + if (this.isHorizontal()) { + // Labels + ctx.textAlign = "left"; + ctx.textBaseline = 'top'; + ctx.lineWidth = 0.5; + ctx.strokeStyle = fontColor; // for strikethrough effect + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = labelFont; + + helpers.each(this.legendItems, function(legendItem, i) { + var textWidth = ctx.measureText(legendItem.text).width; + var width = this.options.labels.boxWidth + (fontSize / 2) + textWidth; + + if (cursor.x + width >= this.width) { + cursor.y += fontSize + (this.options.labels.padding); + cursor.line++; + cursor.x = this.left + ((this.width - this.lineWidths[cursor.line]) / 2); + } + + // Set the ctx for the box + ctx.save(); + + var itemOrDefault = function(item, defaulVal) { + return item !== undefined ? item : defaulVal; + }; + + ctx.fillStyle = itemOrDefault(legendItem.fillStyle, Chart.defaults.global.defaultColor); + ctx.lineCap = itemOrDefault(legendItem.lineCap, Chart.defaults.global.elements.line.borderCapStyle); + ctx.lineDashOffset = itemOrDefault(legendItem.lineDashOffset, Chart.defaults.global.elements.line.borderDashOffset); + ctx.lineJoin = itemOrDefault(legendItem.lineJoin, Chart.defaults.global.elements.line.borderJoinStyle); + ctx.lineWidth = itemOrDefault(legendItem.lineWidth, Chart.defaults.global.elements.line.borderWidth); + ctx.strokeStyle = itemOrDefault(legendItem.strokeStyle, Chart.defaults.global.defaultColor); + + if (ctx.setLineDash) { + // IE 9 and 10 do not support line dash + ctx.setLineDash(itemOrDefault(legendItem.lineDash, Chart.defaults.global.elements.line.borderDash)); + } + + // Draw the box + ctx.strokeRect(cursor.x, cursor.y, this.options.labels.boxWidth, fontSize); + ctx.fillRect(cursor.x, cursor.y, this.options.labels.boxWidth, fontSize); + + ctx.restore(); + + this.legendHitBoxes[i].left = cursor.x; + this.legendHitBoxes[i].top = cursor.y; + + // Fill the actual label + ctx.fillText(legendItem.text, this.options.labels.boxWidth + (fontSize / 2) + cursor.x, cursor.y); + + if (legendItem.hidden) { + // Strikethrough the text if hidden + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(this.options.labels.boxWidth + (fontSize / 2) + cursor.x, cursor.y + (fontSize / 2)); + ctx.lineTo(this.options.labels.boxWidth + (fontSize / 2) + cursor.x + textWidth, cursor.y + (fontSize / 2)); + ctx.stroke(); + } + + cursor.x += width + (this.options.labels.padding); + }, this); + } else { + + } + } + }, + + // Handle an event + handleEvent: function(e) { + var position = helpers.getRelativePosition(e, this.chart.chart); + + if (position.x >= this.left && position.x <= this.right && position.y >= this.top && position.y <= this.bottom) { + // See if we are touching one of the dataset boxes + for (var i = 0; i < this.legendHitBoxes.length; ++i) { + var hitBox = this.legendHitBoxes[i]; + + if (position.x >= hitBox.left && position.x <= hitBox.left + hitBox.width && position.y >= hitBox.top && position.y <= hitBox.top + hitBox.height) { + // Touching an element + if (this.options.onClick) { + this.options.onClick.call(this, e, this.legendItems[i]); + } + break; + } + } + } + } + }); + +}; + +},{}],29:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.scale = { + display: true, + + // grid line settings + gridLines: { + display: true, + color: "rgba(0, 0, 0, 0.1)", + lineWidth: 1, + drawOnChartArea: true, + drawTicks: true, + zeroLineWidth: 1, + zeroLineColor: "rgba(0,0,0,0.25)", + offsetGridLines: false + }, + + // scale label + scaleLabel: { + // actual label + labelString: '', + + // display property + display: false + }, + + // label settings + ticks: { + beginAtZero: false, + maxRotation: 50, + mirror: false, + padding: 10, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + callback: function(value) { + return '' + value; + } + } + }; + + Chart.Scale = Chart.Element.extend({ + + // These methods are ordered by lifecyle. Utilities then follow. + // Any function defined here is inherited by all scale types. + // Any function can be extended by the scale type + + beforeUpdate: function() { + helpers.callCallback(this.options.beforeUpdate, [this]); + }, + update: function(maxWidth, maxHeight, margins) { + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + this.beforeUpdate(); + + // Absorb the master measurements + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this.margins = helpers.extend({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); + + // Dimensions + this.beforeSetDimensions(); + this.setDimensions(); + this.afterSetDimensions(); + + // Data min/max + this.beforeDataLimits(); + this.determineDataLimits(); + this.afterDataLimits(); + + // Ticks + this.beforeBuildTicks(); + this.buildTicks(); + this.afterBuildTicks(); + + this.beforeTickToLabelConversion(); + this.convertTicksToLabels(); + this.afterTickToLabelConversion(); + + // Tick Rotation + this.beforeCalculateTickRotation(); + this.calculateTickRotation(); + this.afterCalculateTickRotation(); + // Fit + this.beforeFit(); + this.fit(); + this.afterFit(); + // + this.afterUpdate(); + + return this.minSize; + + }, + afterUpdate: function() { + helpers.callCallback(this.options.afterUpdate, [this]); + }, + + // + + beforeSetDimensions: function() { + helpers.callCallback(this.options.beforeSetDimensions, [this]); + }, + setDimensions: function() { + // Set the unconstrained dimension before label rotation + if (this.isHorizontal()) { + // Reset position before calculating rotation + this.width = this.maxWidth; + this.left = 0; + this.right = this.width; + } else { + this.height = this.maxHeight; + + // Reset position before calculating rotation + this.top = 0; + this.bottom = this.height; + } + + // Reset padding + this.paddingLeft = 0; + this.paddingTop = 0; + this.paddingRight = 0; + this.paddingBottom = 0; + }, + afterSetDimensions: function() { + helpers.callCallback(this.options.afterSetDimensions, [this]); + }, + + // Data limits + beforeDataLimits: function() { + helpers.callCallback(this.options.beforeDataLimits, [this]); + }, + determineDataLimits: helpers.noop, + afterDataLimits: function() { + helpers.callCallback(this.options.afterDataLimits, [this]); + }, + + // + beforeBuildTicks: function() { + helpers.callCallback(this.options.beforeBuildTicks, [this]); + }, + buildTicks: helpers.noop, + afterBuildTicks: function() { + helpers.callCallback(this.options.afterBuildTicks, [this]); + }, + + beforeTickToLabelConversion: function() { + helpers.callCallback(this.options.beforeTickToLabelConversion, [this]); + }, + convertTicksToLabels: function() { + // Convert ticks to strings + this.ticks = this.ticks.map(function(numericalTick, index, ticks) { + if (this.options.ticks.userCallback) { + return this.options.ticks.userCallback(numericalTick, index, ticks); + } + return this.options.ticks.callback(numericalTick, index, ticks); + }, + this); + }, + afterTickToLabelConversion: function() { + helpers.callCallback(this.options.afterTickToLabelConversion, [this]); + }, + + // + + beforeCalculateTickRotation: function() { + helpers.callCallback(this.options.beforeCalculateTickRotation, [this]); + }, + calculateTickRotation: function() { + //Get the width of each grid by calculating the difference + //between x offsets between 0 and 1. + var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + this.ctx.font = tickLabelFont; + + var firstWidth = this.ctx.measureText(this.ticks[0]).width; + var lastWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width; + var firstRotated; + + this.labelRotation = 0; + this.paddingRight = 0; + this.paddingLeft = 0; + + if (this.options.display) { + if (this.isHorizontal()) { + this.paddingRight = lastWidth / 2 + 3; + this.paddingLeft = firstWidth / 2 + 3; + + if (!this.longestTextCache) { + this.longestTextCache = {}; + } + var originalLabelWidth = helpers.longestText(this.ctx, tickLabelFont, this.ticks, this.longestTextCache); + var labelWidth = originalLabelWidth; + var cosRotation; + var sinRotation; + + // Allow 3 pixels x2 padding either side for label readability + // only the index matters for a dataset scale, but we want a consistent interface between scales + var tickWidth = this.getPixelForTick(1) - this.getPixelForTick(0) - 6; + + //Max label rotation can be set or default to 90 - also act as a loop counter + while (labelWidth > tickWidth && this.labelRotation < this.options.ticks.maxRotation) { + cosRotation = Math.cos(helpers.toRadians(this.labelRotation)); + sinRotation = Math.sin(helpers.toRadians(this.labelRotation)); + + firstRotated = cosRotation * firstWidth; + + // We're right aligning the text now. + if (firstRotated + tickFontSize / 2 > this.yLabelWidth) { + this.paddingLeft = firstRotated + tickFontSize / 2; + } + + this.paddingRight = tickFontSize / 2; + + if (sinRotation * originalLabelWidth > this.maxHeight) { + // go back one step + this.labelRotation--; + break; + } + + this.labelRotation++; + labelWidth = cosRotation * originalLabelWidth; + } + } + } + + if (this.margins) { + this.paddingLeft = Math.max(this.paddingLeft - this.margins.left, 0); + this.paddingRight = Math.max(this.paddingRight - this.margins.right, 0); + } + }, + afterCalculateTickRotation: function() { + helpers.callCallback(this.options.afterCalculateTickRotation, [this]); + }, + + // + + beforeFit: function() { + helpers.callCallback(this.options.beforeFit, [this]); + }, + fit: function() { + + this.minSize = { + width: 0, + height: 0 + }; + + var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + + var scaleLabelFontSize = helpers.getValueOrDefault(this.options.scaleLabel.fontSize, Chart.defaults.global.defaultFontSize); + var scaleLabelFontStyle = helpers.getValueOrDefault(this.options.scaleLabel.fontStyle, Chart.defaults.global.defaultFontStyle); + var scaleLabelFontFamily = helpers.getValueOrDefault(this.options.scaleLabel.fontFamily, Chart.defaults.global.defaultFontFamily); + var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily); + + // Width + if (this.isHorizontal()) { + // subtract the margins to line up with the chartArea if we are a full width scale + this.minSize.width = this.isFullWidth() ? this.maxWidth - this.margins.left - this.margins.right : this.maxWidth; + } else { + this.minSize.width = this.options.gridLines.display && this.options.display ? 10 : 0; + } + + // height + if (this.isHorizontal()) { + this.minSize.height = this.options.gridLines.display && this.options.display ? 10 : 0; + } else { + this.minSize.height = this.maxHeight; // fill all the height + } + + // Are we showing a title for the scale? + if (this.options.scaleLabel.display) { + if (this.isHorizontal()) { + this.minSize.height += (scaleLabelFontSize * 1.5); + } else { + this.minSize.width += (scaleLabelFontSize * 1.5); + } + } + + if (this.options.ticks.display && this.options.display) { + // Don't bother fitting the ticks if we are not showing them + if (!this.longestTextCache) { + this.longestTextCache = {}; + } + + var largestTextWidth = helpers.longestText(this.ctx, tickLabelFont, this.ticks, this.longestTextCache); + + if (this.isHorizontal()) { + // A horizontal axis is more constrained by the height. + this.longestLabelWidth = largestTextWidth; + + // TODO - improve this calculation + var labelHeight = (Math.sin(helpers.toRadians(this.labelRotation)) * this.longestLabelWidth) + 1.5 * tickFontSize; + + this.minSize.height = Math.min(this.maxHeight, this.minSize.height + labelHeight); + this.ctx.font = tickLabelFont; + + var firstLabelWidth = this.ctx.measureText(this.ticks[0]).width; + var lastLabelWidth = this.ctx.measureText(this.ticks[this.ticks.length - 1]).width; + + // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned which means that the right padding is dominated + // by the font height + var cosRotation = Math.cos(helpers.toRadians(this.labelRotation)); + var sinRotation = Math.sin(helpers.toRadians(this.labelRotation)); + this.paddingLeft = this.labelRotation !== 0 ? (cosRotation * firstLabelWidth) + 3 : firstLabelWidth / 2 + 3; // add 3 px to move away from canvas edges + this.paddingRight = this.labelRotation !== 0 ? (sinRotation * (tickFontSize / 2)) + 3 : lastLabelWidth / 2 + 3; // when rotated + } else { + // A vertical axis is more constrained by the width. Labels are the dominant factor here, so get that length first + var maxLabelWidth = this.maxWidth - this.minSize.width; + + // Account for padding + if (!this.options.ticks.mirror) { + largestTextWidth += this.options.ticks.padding; + } + + if (largestTextWidth < maxLabelWidth) { + // We don't need all the room + this.minSize.width += largestTextWidth; + } else { + // Expand to max size + this.minSize.width = this.maxWidth; + } + + this.paddingTop = tickFontSize / 2; + this.paddingBottom = tickFontSize / 2; + } + } + + if (this.margins) { + this.paddingLeft = Math.max(this.paddingLeft - this.margins.left, 0); + this.paddingTop = Math.max(this.paddingTop - this.margins.top, 0); + this.paddingRight = Math.max(this.paddingRight - this.margins.right, 0); + this.paddingBottom = Math.max(this.paddingBottom - this.margins.bottom, 0); + } + + this.width = this.minSize.width; + this.height = this.minSize.height; + + }, + afterFit: function() { + helpers.callCallback(this.options.afterFit, [this]); + }, + + // Shared Methods + isHorizontal: function() { + return this.options.position === "top" || this.options.position === "bottom"; + }, + isFullWidth: function() { + return (this.options.fullWidth); + }, + + // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not + getRightValue: function getRightValue(rawValue) { + // Null and undefined values first + if (rawValue === null || typeof(rawValue) === 'undefined') { + return NaN; + } + // isNaN(object) returns true, so make sure NaN is checking for a number + if (typeof(rawValue) === 'number' && isNaN(rawValue)) { + return NaN; + } + // If it is in fact an object, dive in one more level + if (typeof(rawValue) === "object") { + if (rawValue instanceof Date) { + return rawValue; + } else { + return getRightValue(this.isHorizontal() ? rawValue.x : rawValue.y); + } + } + + // Value is good, return it + return rawValue; + }, + + // Used to get the value to display in the tooltip for the data at the given index + // function getLabelForIndex(index, datasetIndex) + getLabelForIndex: helpers.noop, + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: helpers.noop, + + // Used for tick location, should + getPixelForTick: function(index, includeOffset) { + if (this.isHorizontal()) { + var innerWidth = this.width - (this.paddingLeft + this.paddingRight); + var tickWidth = innerWidth / Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1); + var pixel = (tickWidth * index) + this.paddingLeft; + + if (includeOffset) { + pixel += tickWidth / 2; + } + + var finalVal = this.left + Math.round(pixel); + finalVal += this.isFullWidth() ? this.margins.left : 0; + return finalVal; + } else { + var innerHeight = this.height - (this.paddingTop + this.paddingBottom); + return this.top + (index * (innerHeight / (this.ticks.length - 1))); + } + }, + + // Utility for getting the pixel location of a percentage of scale + getPixelForDecimal: function(decimal /*, includeOffset*/ ) { + if (this.isHorizontal()) { + var innerWidth = this.width - (this.paddingLeft + this.paddingRight); + var valueOffset = (innerWidth * decimal) + this.paddingLeft; + + var finalVal = this.left + Math.round(valueOffset); + finalVal += this.isFullWidth() ? this.margins.left : 0; + return finalVal; + } else { + return this.top + (decimal * this.height); + } + }, + + // Actualy draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function(chartArea) { + if (this.options.display) { + + var setContextLineSettings; + var isRotated = this.labelRotation !== 0; + var skipRatio; + var scaleLabelX; + var scaleLabelY; + var useAutoskipper = this.options.ticks.autoSkip; + + + // figure out the maximum number of gridlines to show + var maxTicks; + + if (this.options.ticks.maxTicksLimit) { + maxTicks = this.options.ticks.maxTicksLimit; + } + + var tickFontColor = helpers.getValueOrDefault(this.options.ticks.fontColor, Chart.defaults.global.defaultFontColor); + var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + + var scaleLabelFontColor = helpers.getValueOrDefault(this.options.scaleLabel.fontColor, Chart.defaults.global.defaultFontColor); + var scaleLabelFontSize = helpers.getValueOrDefault(this.options.scaleLabel.fontSize, Chart.defaults.global.defaultFontSize); + var scaleLabelFontStyle = helpers.getValueOrDefault(this.options.scaleLabel.fontStyle, Chart.defaults.global.defaultFontStyle); + var scaleLabelFontFamily = helpers.getValueOrDefault(this.options.scaleLabel.fontFamily, Chart.defaults.global.defaultFontFamily); + var scaleLabelFont = helpers.fontString(scaleLabelFontSize, scaleLabelFontStyle, scaleLabelFontFamily); + + var cosRotation = Math.cos(helpers.toRadians(this.labelRotation)); + var sinRotation = Math.sin(helpers.toRadians(this.labelRotation)); + var longestRotatedLabel = this.longestLabelWidth * cosRotation; + var rotatedLabelHeight = tickFontSize * sinRotation; + + // Make sure we draw text in the correct color and font + this.ctx.fillStyle = tickFontColor; + + if (this.isHorizontal()) { + setContextLineSettings = true; + var yTickStart = this.options.position === "bottom" ? this.top : this.bottom - 10; + var yTickEnd = this.options.position === "bottom" ? this.top + 10 : this.bottom; + skipRatio = false; + + if (((longestRotatedLabel / 2) + this.options.ticks.autoSkipPadding) * this.ticks.length > (this.width - (this.paddingLeft + this.paddingRight))) { + skipRatio = 1 + Math.floor((((longestRotatedLabel / 2) + this.options.ticks.autoSkipPadding) * this.ticks.length) / (this.width - (this.paddingLeft + this.paddingRight))); + } + + // if they defined a max number of ticks, + // increase skipRatio until that number is met + if (maxTicks && this.ticks.length > maxTicks) { + while (!skipRatio || this.ticks.length / (skipRatio || 1) > maxTicks) { + if (!skipRatio) { + skipRatio = 1; + } + skipRatio += 1; + } + } + + if (!useAutoskipper) { + skipRatio = false; + } + + helpers.each(this.ticks, function(label, index) { + // Blank ticks + var isLastTick = this.ticks.length === index + 1; + + // Since we always show the last tick,we need may need to hide the last shown one before + var shouldSkip = (skipRatio > 1 && index % skipRatio > 0) || (index % skipRatio === 0 && index + skipRatio > this.ticks.length); + if (shouldSkip && !isLastTick || (label === undefined || label === null)) { + return; + } + var xLineValue = this.getPixelForTick(index); // xvalues for grid lines + var xLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option) + + if (this.options.gridLines.display) { + if (index === (typeof this.zeroLineIndex !== 'undefined' ? this.zeroLineIndex : 0)) { + // Draw the first index specially + this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; + this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; + setContextLineSettings = true; // reset next time + } else if (setContextLineSettings) { + this.ctx.lineWidth = this.options.gridLines.lineWidth; + this.ctx.strokeStyle = this.options.gridLines.color; + setContextLineSettings = false; + } + + xLineValue += helpers.aliasPixel(this.ctx.lineWidth); + + // Draw the label area + this.ctx.beginPath(); + + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xLineValue, yTickStart); + this.ctx.lineTo(xLineValue, yTickEnd); + } + + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(xLineValue, chartArea.top); + this.ctx.lineTo(xLineValue, chartArea.bottom); + } + + // Need to stroke in the loop because we are potentially changing line widths & colours + this.ctx.stroke(); + } + + if (this.options.ticks.display) { + this.ctx.save(); + this.ctx.translate(xLabelValue, (isRotated) ? this.top + 12 : this.options.position === "top" ? this.bottom - 10 : this.top + 10); + this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1); + this.ctx.font = tickLabelFont; + this.ctx.textAlign = (isRotated) ? "right" : "center"; + this.ctx.textBaseline = (isRotated) ? "middle" : this.options.position === "top" ? "bottom" : "top"; + this.ctx.fillText(label, 0, 0); + this.ctx.restore(); + } + }, this); + + if (this.options.scaleLabel.display) { + // Draw the scale label + this.ctx.textAlign = "center"; + this.ctx.textBaseline = 'middle'; + this.ctx.fillStyle = scaleLabelFontColor; // render in correct colour + this.ctx.font = scaleLabelFont; + + scaleLabelX = this.left + ((this.right - this.left) / 2); // midpoint of the width + scaleLabelY = this.options.position === 'bottom' ? this.bottom - (scaleLabelFontSize / 2) : this.top + (scaleLabelFontSize / 2); + + this.ctx.fillText(this.options.scaleLabel.labelString, scaleLabelX, scaleLabelY); + } + + } else { + setContextLineSettings = true; + var xTickStart = this.options.position === "right" ? this.left : this.right - 5; + var xTickEnd = this.options.position === "right" ? this.left + 5 : this.right; + + helpers.each(this.ticks, function(label, index) { + // If the callback returned a null or undefined value, do not draw this line + if (label === undefined || label === null) { + return; + } + + var yLineValue = this.getPixelForTick(index); // xvalues for grid lines + + if (this.options.gridLines.display) { + if (index === (typeof this.zeroLineIndex !== 'undefined' ? this.zeroLineIndex : 0)) { + // Draw the first index specially + this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; + this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; + setContextLineSettings = true; // reset next time + } else if (setContextLineSettings) { + this.ctx.lineWidth = this.options.gridLines.lineWidth; + this.ctx.strokeStyle = this.options.gridLines.color; + setContextLineSettings = false; + } + + yLineValue += helpers.aliasPixel(this.ctx.lineWidth); + + // Draw the label area + this.ctx.beginPath(); + + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xTickStart, yLineValue); + this.ctx.lineTo(xTickEnd, yLineValue); + } + + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(chartArea.left, yLineValue); + this.ctx.lineTo(chartArea.right, yLineValue); + } + + // Need to stroke in the loop because we are potentially changing line widths & colours + this.ctx.stroke(); + } + + if (this.options.ticks.display) { + var xLabelValue; + var yLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines); // x values for ticks (need to consider offsetLabel option) + + this.ctx.save(); + + if (this.options.position === "left") { + if (this.options.ticks.mirror) { + xLabelValue = this.right + this.options.ticks.padding; + this.ctx.textAlign = "left"; + } else { + xLabelValue = this.right - this.options.ticks.padding; + this.ctx.textAlign = "right"; + } + } else { + // right side + if (this.options.ticks.mirror) { + xLabelValue = this.left - this.options.ticks.padding; + this.ctx.textAlign = "right"; + } else { + xLabelValue = this.left + this.options.ticks.padding; + this.ctx.textAlign = "left"; + } + } + + this.ctx.translate(xLabelValue, yLabelValue); + this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1); + this.ctx.font = tickLabelFont; + this.ctx.textBaseline = "middle"; + this.ctx.fillText(label, 0, 0); + this.ctx.restore(); + } + }, this); + + if (this.options.scaleLabel.display) { + // Draw the scale label + scaleLabelX = this.options.position === 'left' ? this.left + (scaleLabelFontSize / 2) : this.right - (scaleLabelFontSize / 2); + scaleLabelY = this.top + ((this.bottom - this.top) / 2); + var rotation = this.options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI; + + this.ctx.save(); + this.ctx.translate(scaleLabelX, scaleLabelY); + this.ctx.rotate(rotation); + this.ctx.textAlign = "center"; + this.ctx.fillStyle =scaleLabelFontColor; // render in correct colour + this.ctx.font = scaleLabelFont; + this.ctx.textBaseline = 'middle'; + this.ctx.fillText(this.options.scaleLabel.labelString, 0, 0); + this.ctx.restore(); + } + } + + // Draw the line at the edge of the axis + this.ctx.lineWidth = this.options.gridLines.lineWidth; + this.ctx.strokeStyle = this.options.gridLines.color; + var x1 = this.left, + x2 = this.right, + y1 = this.top, + y2 = this.bottom; + + if (this.isHorizontal()) { + y1 = y2 = this.options.position === 'top' ? this.bottom : this.top; + y1 += helpers.aliasPixel(this.ctx.lineWidth); + y2 += helpers.aliasPixel(this.ctx.lineWidth); + } else { + x1 = x2 = this.options.position === 'left' ? this.right : this.left; + x1 += helpers.aliasPixel(this.ctx.lineWidth); + x2 += helpers.aliasPixel(this.ctx.lineWidth); + } + + this.ctx.beginPath(); + this.ctx.moveTo(x1, y1); + this.ctx.lineTo(x2, y2); + this.ctx.stroke(); + } + } + }); +}; + +},{}],30:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.scaleService = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers + + // Scale config defaults + defaults: {}, + registerScaleType: function(type, scaleConstructor, defaults) { + this.constructors[type] = scaleConstructor; + this.defaults[type] = helpers.clone(defaults); + }, + getScaleConstructor: function(type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, + getScaleDefaults: function(type) { + // Return the scale defaults merged with the global settings so that we always use the latest ones + return this.defaults.hasOwnProperty(type) ? helpers.scaleMerge(Chart.defaults.scale, this.defaults[type]) : {}; + }, + addScalesToLayout: function(chartInstance) { + // Adds each scale to the chart.boxes array to be sized accordingly + helpers.each(chartInstance.scales, function(scale) { + Chart.layoutService.addBox(chartInstance, scale); + }); + } + }; +}; +},{}],31:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.title = { + display: false, + position: 'top', + fullWidth: true, // marks that this box should take the full width of the canvas (pushing down other boxes) + + fontStyle: 'bold', + padding: 10, + + // actual title + text: '' + }; + + Chart.Title = Chart.Element.extend({ + + initialize: function(config) { + helpers.extend(this, config); + this.options = helpers.configMerge(Chart.defaults.global.title, config.options); + + // Contains hit boxes for each dataset (in dataset order) + this.legendHitBoxes = []; + }, + + // These methods are ordered by lifecyle. Utilities then follow. + + beforeUpdate: helpers.noop, + update: function(maxWidth, maxHeight, margins) { + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + this.beforeUpdate(); + + // Absorb the master measurements + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this.margins = margins; + + // Dimensions + this.beforeSetDimensions(); + this.setDimensions(); + this.afterSetDimensions(); + // Labels + this.beforeBuildLabels(); + this.buildLabels(); + this.afterBuildLabels(); + + // Fit + this.beforeFit(); + this.fit(); + this.afterFit(); + // + this.afterUpdate(); + + return this.minSize; + + }, + afterUpdate: helpers.noop, + + // + + beforeSetDimensions: helpers.noop, + setDimensions: function() { + // Set the unconstrained dimension before label rotation + if (this.isHorizontal()) { + // Reset position before calculating rotation + this.width = this.maxWidth; + this.left = 0; + this.right = this.width; + } else { + this.height = this.maxHeight; + + // Reset position before calculating rotation + this.top = 0; + this.bottom = this.height; + } + + // Reset padding + this.paddingLeft = 0; + this.paddingTop = 0; + this.paddingRight = 0; + this.paddingBottom = 0; + + // Reset minSize + this.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: helpers.noop, + + // + + beforeBuildLabels: helpers.noop, + buildLabels: helpers.noop, + afterBuildLabels: helpers.noop, + + // + + beforeFit: helpers.noop, + fit: function() { + + var ctx = this.ctx; + var fontSize = helpers.getValueOrDefault(this.options.fontSize, Chart.defaults.global.defaultFontSize); + var fontStyle = helpers.getValueOrDefault(this.options.fontStyle, Chart.defaults.global.defaultFontStyle); + var fontFamily = helpers.getValueOrDefault(this.options.fontFamily, Chart.defaults.global.defaultFontFamily); + var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); + + // Width + if (this.isHorizontal()) { + this.minSize.width = this.maxWidth; // fill all the width + } else { + this.minSize.width = 0; + } + + // height + if (this.isHorizontal()) { + this.minSize.height = 0; + } else { + this.minSize.height = this.maxHeight; // fill all the height + } + + // Increase sizes here + if (this.isHorizontal()) { + + // Title + if (this.options.display) { + this.minSize.height += fontSize + (this.options.padding * 2); + } + } else { + if (this.options.display) { + this.minSize.width += fontSize + (this.options.padding * 2); + } + } + + this.width = this.minSize.width; + this.height = this.minSize.height; + + }, + afterFit: helpers.noop, + + // Shared Methods + isHorizontal: function() { + return this.options.position === "top" || this.options.position === "bottom"; + }, + + // Actualy draw the title block on the canvas + draw: function() { + if (this.options.display) { + var ctx = this.ctx; + var titleX, titleY; + + var fontColor = helpers.getValueOrDefault(this.options.fontColor, Chart.defaults.global.defaultFontColor); + var fontSize = helpers.getValueOrDefault(this.options.fontSize, Chart.defaults.global.defaultFontSize); + var fontStyle = helpers.getValueOrDefault(this.options.fontStyle, Chart.defaults.global.defaultFontStyle); + var fontFamily = helpers.getValueOrDefault(this.options.fontFamily, Chart.defaults.global.defaultFontFamily); + var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily); + + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = titleFont; + + // Horizontal + if (this.isHorizontal()) { + // Title + ctx.textAlign = "center"; + ctx.textBaseline = 'middle'; + + titleX = this.left + ((this.right - this.left) / 2); // midpoint of the width + titleY = this.top + ((this.bottom - this.top) / 2); // midpoint of the height + + ctx.fillText(this.options.text, titleX, titleY); + } else { + + // Title + titleX = this.options.position === 'left' ? this.left + (fontSize / 2) : this.right - (fontSize / 2); + titleY = this.top + ((this.bottom - this.top) / 2); + var rotation = this.options.position === 'left' ? -0.5 * Math.PI : 0.5 * Math.PI; + + ctx.save(); + ctx.translate(titleX, titleY); + ctx.rotate(rotation); + ctx.textAlign = "center"; + ctx.textBaseline = 'middle'; + ctx.fillText(this.options.text, 0, 0); + ctx.restore(); + } + } + } + }); +}; +},{}],32:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.tooltips = { + enabled: true, + custom: null, + mode: 'single', + backgroundColor: "rgba(0,0,0,0.8)", + titleFontStyle: "bold", + titleSpacing: 2, + titleMarginBottom: 6, + titleColor: "#fff", + titleAlign: "left", + bodySpacing: 2, + bodyColor: "#fff", + bodyAlign: "left", + footerFontStyle: "bold", + footerSpacing: 2, + footerMarginTop: 6, + footerColor: "#fff", + footerAlign: "left", + yPadding: 6, + xPadding: 6, + yAlign : 'center', + xAlign : 'center', + caretSize: 5, + cornerRadius: 6, + multiKeyBackground: '#fff', + callbacks: { + // Args are: (tooltipItems, data) + beforeTitle: helpers.noop, + title: function(tooltipItems, data) { + // Pick first xLabel for now + var title = ''; + + if (tooltipItems.length > 0) { + if (tooltipItems[0].xLabel) { + title = tooltipItems[0].xLabel; + } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) { + title = data.labels[tooltipItems[0].index]; + } + } + + return title; + }, + afterTitle: helpers.noop, + + // Args are: (tooltipItems, data) + beforeBody: helpers.noop, + + // Args are: (tooltipItem, data) + beforeLabel: helpers.noop, + label: function(tooltipItem, data) { + var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || ''; + return datasetLabel + ': ' + tooltipItem.yLabel; + }, + afterLabel: helpers.noop, + + // Args are: (tooltipItems, data) + afterBody: helpers.noop, + + // Args are: (tooltipItems, data) + beforeFooter: helpers.noop, + footer: helpers.noop, + afterFooter: helpers.noop + } + }; + + // Helper to push or concat based on if the 2nd parameter is an array or not + function pushOrConcat(base, toPush) { + if (toPush) { + if (helpers.isArray(toPush)) { + base = base.concat(toPush); + } else { + base.push(toPush); + } + } + + return base; + } + + Chart.Tooltip = Chart.Element.extend({ + initialize: function() { + var options = this._options; + helpers.extend(this, { + _model: { + // Positioning + xPadding: options.tooltips.xPadding, + yPadding: options.tooltips.yPadding, + xAlign : options.tooltips.yAlign, + yAlign : options.tooltips.xAlign, + + // Body + bodyColor: options.tooltips.bodyColor, + _bodyFontFamily: helpers.getValueOrDefault(options.tooltips.bodyFontFamily, Chart.defaults.global.defaultFontFamily), + _bodyFontStyle: helpers.getValueOrDefault(options.tooltips.bodyFontStyle, Chart.defaults.global.defaultFontStyle), + _bodyAlign: options.tooltips.bodyAlign, + bodyFontSize: helpers.getValueOrDefault(options.tooltips.bodyFontSize, Chart.defaults.global.defaultFontSize), + bodySpacing: options.tooltips.bodySpacing, + + // Title + titleColor: options.tooltips.titleColor, + _titleFontFamily: helpers.getValueOrDefault(options.tooltips.titleFontFamily, Chart.defaults.global.defaultFontFamily), + _titleFontStyle: helpers.getValueOrDefault(options.tooltips.titleFontStyle, Chart.defaults.global.defaultFontStyle), + titleFontSize: helpers.getValueOrDefault(options.tooltips.titleFontSize, Chart.defaults.global.defaultFontSize), + _titleAlign: options.tooltips.titleAlign, + titleSpacing: options.tooltips.titleSpacing, + titleMarginBottom: options.tooltips.titleMarginBottom, + + // Footer + footerColor: options.tooltips.footerColor, + _footerFontFamily: helpers.getValueOrDefault(options.tooltips.footerFontFamily, Chart.defaults.global.defaultFontFamily), + _footerFontStyle: helpers.getValueOrDefault(options.tooltips.footerFontStyle, Chart.defaults.global.defaultFontStyle), + footerFontSize: helpers.getValueOrDefault(options.tooltips.footerFontSize, Chart.defaults.global.defaultFontSize), + _footerAlign: options.tooltips.footerAlign, + footerSpacing: options.tooltips.footerSpacing, + footerMarginTop: options.tooltips.footerMarginTop, + + // Appearance + caretSize: options.tooltips.caretSize, + cornerRadius: options.tooltips.cornerRadius, + backgroundColor: options.tooltips.backgroundColor, + opacity: 0, + legendColorBackground: options.tooltips.multiKeyBackground + } + }); + }, + + // Get the title + // Args are: (tooltipItem, data) + getTitle: function() { + var beforeTitle = this._options.tooltips.callbacks.beforeTitle.apply(this, arguments), + title = this._options.tooltips.callbacks.title.apply(this, arguments), + afterTitle = this._options.tooltips.callbacks.afterTitle.apply(this, arguments); + + var lines = []; + lines = pushOrConcat(lines, beforeTitle); + lines = pushOrConcat(lines, title); + lines = pushOrConcat(lines, afterTitle); + + return lines; + }, + + // Args are: (tooltipItem, data) + getBeforeBody: function() { + var lines = this._options.tooltips.callbacks.beforeBody.apply(this, arguments); + return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; + }, + + // Args are: (tooltipItem, data) + getBody: function(tooltipItems, data) { + var lines = []; + + helpers.each(tooltipItems, function(bodyItem) { + helpers.pushAllIfDefined(this._options.tooltips.callbacks.beforeLabel.call(this, bodyItem, data), lines); + helpers.pushAllIfDefined(this._options.tooltips.callbacks.label.call(this, bodyItem, data), lines); + helpers.pushAllIfDefined(this._options.tooltips.callbacks.afterLabel.call(this, bodyItem, data), lines); + }, this); + + return lines; + }, + + // Args are: (tooltipItem, data) + getAfterBody: function() { + var lines = this._options.tooltips.callbacks.afterBody.apply(this, arguments); + return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; + }, + + // Get the footer and beforeFooter and afterFooter lines + // Args are: (tooltipItem, data) + getFooter: function() { + var beforeFooter = this._options.tooltips.callbacks.beforeFooter.apply(this, arguments); + var footer = this._options.tooltips.callbacks.footer.apply(this, arguments); + var afterFooter = this._options.tooltips.callbacks.afterFooter.apply(this, arguments); + + var lines = []; + lines = pushOrConcat(lines, beforeFooter); + lines = pushOrConcat(lines, footer); + lines = pushOrConcat(lines, afterFooter); + + return lines; + }, + + getAveragePosition: function(elements) { + + if (!elements.length) { + return false; + } + + var xPositions = []; + var yPositions = []; + + helpers.each(elements, function(el) { + if (el) { + var pos = el.tooltipPosition(); + xPositions.push(pos.x); + yPositions.push(pos.y); + } + }); + + var x = 0, + y = 0; + for (var i = 0; i < xPositions.length; i++) { + x += xPositions[i]; + y += yPositions[i]; + } + + return { + x: Math.round(x / xPositions.length), + y: Math.round(y / xPositions.length) + }; + + }, + + update: function(changed) { + if (this._active.length) { + this._model.opacity = 1; + + var element = this._active[0], + labelColors = [], + tooltipPosition; + + var tooltipItems = []; + + if (this._options.tooltips.mode === 'single') { + var yScale = element._yScale || element._scale; // handle radar || polarArea charts + tooltipItems.push({ + xLabel: element._xScale ? element._xScale.getLabelForIndex(element._index, element._datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(element._index, element._datasetIndex) : '', + index: element._index, + datasetIndex: element._datasetIndex + }); + tooltipPosition = this.getAveragePosition(this._active); + } else { + helpers.each(this._data.datasets, function(dataset, datasetIndex) { + if (!helpers.isDatasetVisible(dataset)) { + return; + } + var currentElement = dataset.metaData[element._index]; + if (currentElement) { + var yScale = element._yScale || element._scale; // handle radar || polarArea charts + + tooltipItems.push({ + xLabel: currentElement._xScale ? currentElement._xScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '', + index: element._index, + datasetIndex: datasetIndex + }); + } + }, null, element._yScale.options.stacked); + + helpers.each(this._active, function(active) { + if (active) { + labelColors.push({ + borderColor: active._view.borderColor, + backgroundColor: active._view.backgroundColor + }); + } + }, null, element._yScale.options.stacked); + + tooltipPosition = this.getAveragePosition(this._active); + tooltipPosition.y = this._active[0]._yScale.getPixelForDecimal(0.5); + } + + // Build the Text Lines + helpers.extend(this._model, { + title: this.getTitle(tooltipItems, this._data), + beforeBody: this.getBeforeBody(tooltipItems, this._data), + body: this.getBody(tooltipItems, this._data), + afterBody: this.getAfterBody(tooltipItems, this._data), + footer: this.getFooter(tooltipItems, this._data) + }); + + helpers.extend(this._model, { + x: Math.round(tooltipPosition.x), + y: Math.round(tooltipPosition.y), + caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2), + labelColors: labelColors + }); + + // We need to determine alignment of + var tooltipSize = this.getTooltipSize(this._model); + this.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas + + helpers.extend(this._model, this.getBackgroundPoint(this._model, tooltipSize)); + } else { + this._model.opacity = 0; + } + + if (changed && this._options.tooltips.custom) { + this._options.tooltips.custom.call(this, this._model); + } + + return this; + }, + getTooltipSize: function getTooltipSize(vm) { + var ctx = this._chart.ctx; + + var size = { + height: vm.yPadding * 2, // Tooltip Padding + width: 0 + }; + var combinedBodyLength = vm.body.length + vm.beforeBody.length + vm.afterBody.length; + + size.height += vm.title.length * vm.titleFontSize; // Title Lines + size.height += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing + size.height += vm.title.length ? vm.titleMarginBottom : 0; // Title's bottom Margin + size.height += combinedBodyLength * vm.bodyFontSize; // Body Lines + size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing + size.height += vm.footer.length ? vm.footerMarginTop : 0; // Footer Margin + size.height += vm.footer.length * (vm.footerFontSize); // Footer Lines + size.height += vm.footer.length ? (vm.footer.length - 1) * vm.footerSpacing : 0; // Footer Line Spacing + + // Width + ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + helpers.each(vm.title, function(line) { + size.width = Math.max(size.width, ctx.measureText(line).width); + }); + + ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + helpers.each(vm.beforeBody.concat(vm.afterBody), function(line) { + size.width = Math.max(size.width, ctx.measureText(line).width); + }); + helpers.each(vm.body, function(line) { + size.width = Math.max(size.width, ctx.measureText(line).width + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0)); + }, this); + + ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + helpers.each(vm.footer, function(line) { + size.width = Math.max(size.width, ctx.measureText(line).width); + }); + size.width += 2 * vm.xPadding; + + return size; + }, + determineAlignment: function determineAlignment(size) { + if (this._model.y < size.height) { + this._model.yAlign = 'top'; + } else if (this._model.y > (this._chart.height - size.height)) { + this._model.yAlign = 'bottom'; + } + + var lf, rf; // functions to determine left, right alignment + var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart + var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges + var _this = this; + var midX = (this._chartInstance.chartArea.left + this._chartInstance.chartArea.right) / 2; + var midY = (this._chartInstance.chartArea.top + this._chartInstance.chartArea.bottom) / 2; + + if (this._model.yAlign === 'center') { + lf = function(x) { + return x <= midX; + }; + rf = function(x) { + return x > midX; + }; + } else { + lf = function(x) { + return x <= (size.width / 2); + }; + rf = function(x) { + return x >= (_this._chart.width - (size.width / 2)); + }; + } + + olf = function(x) { + return x + size.width > _this._chart.width; + }; + orf = function(x) { + return x - size.width < 0; + }; + yf = function(y) { + return y <= midY ? 'top' : 'bottom'; + }; + + if (lf(this._model.x)) { + this._model.xAlign = 'left'; + + // Is tooltip too wide and goes over the right side of the chart.? + if (olf(this._model.x)) { + this._model.xAlign = 'center'; + this._model.yAlign = yf(this._model.y); + } + } else if (rf(this._model.x)) { + this._model.xAlign = 'right'; + + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(this._model.x)) { + this._model.xAlign = 'center'; + this._model.yAlign = yf(this._model.y); + } + } + }, + getBackgroundPoint: function getBackgroundPoint(vm, size) { + // Background Position + var pt = { + x: vm.x, + y: vm.y + }; + + if (vm.xAlign === 'right') { + pt.x -= size.width; + } else if (vm.xAlign === 'center') { + pt.x -= (size.width / 2); + } + + if (vm.yAlign === 'top') { + pt.y += vm.caretPadding + vm.caretSize; + } else if (vm.yAlign === 'bottom') { + pt.y -= size.height + vm.caretPadding + vm.caretSize; + } else { + pt.y -= (size.height / 2); + } + + if (vm.yAlign === 'center') { + if (vm.xAlign === 'left') { + pt.x += vm.caretPadding + vm.caretSize; + } else if (vm.xAlign === 'right') { + pt.x -= vm.caretPadding + vm.caretSize; + } + } else { + if (vm.xAlign === 'left') { + pt.x -= vm.cornerRadius + vm.caretPadding; + } else if (vm.xAlign === 'right') { + pt.x += vm.cornerRadius + vm.caretPadding; + } + } + + return pt; + }, + drawCaret: function drawCaret(tooltipPoint, size, opacity, caretPadding) { + var vm = this._view; + var ctx = this._chart.ctx; + var x1, x2, x3; + var y1, y2, y3; + + if (vm.yAlign === 'center') { + // Left or right side + if (vm.xAlign === 'left') { + x1 = tooltipPoint.x; + x2 = x1 - vm.caretSize; + x3 = x1; + } else { + x1 = tooltipPoint.x + size.width; + x2 = x1 + vm.caretSize; + x3 = x1; + } + + y2 = tooltipPoint.y + (size.height / 2); + y1 = y2 - vm.caretSize; + y3 = y2 + vm.caretSize; + } else { + if (vm.xAlign === 'left') { + x1 = tooltipPoint.x + vm.cornerRadius; + x2 = x1 + vm.caretSize; + x3 = x2 + vm.caretSize; + } else if (vm.xAlign === 'right') { + x1 = tooltipPoint.x + size.width - vm.cornerRadius; + x2 = x1 - vm.caretSize; + x3 = x2 - vm.caretSize; + } else { + x2 = tooltipPoint.x + (size.width / 2); + x1 = x2 - vm.caretSize; + x3 = x2 + vm.caretSize; + } + + if (vm.yAlign === 'top') { + y1 = tooltipPoint.y; + y2 = y1 - vm.caretSize; + y3 = y1; + } else { + y1 = tooltipPoint.y + size.height; + y2 = y1 + vm.caretSize; + y3 = y1; + } + } + + var bgColor = helpers.color(vm.backgroundColor); + ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.lineTo(x3, y3); + ctx.closePath(); + ctx.fill(); + }, + drawTitle: function drawTitle(pt, vm, ctx, opacity) { + if (vm.title.length) { + ctx.textAlign = vm._titleAlign; + ctx.textBaseline = "top"; + + var titleColor = helpers.color(vm.titleColor); + ctx.fillStyle = titleColor.alpha(opacity * titleColor.alpha()).rgbString(); + ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + + helpers.each(vm.title, function(title, i) { + ctx.fillText(title, pt.x, pt.y); + pt.y += vm.titleFontSize + vm.titleSpacing; // Line Height and spacing + + if (i + 1 === vm.title.length) { + pt.y += vm.titleMarginBottom - vm.titleSpacing; // If Last, add margin, remove spacing + } + }); + } + }, + drawBody: function drawBody(pt, vm, ctx, opacity) { + ctx.textAlign = vm._bodyAlign; + ctx.textBaseline = "top"; + + var bodyColor = helpers.color(vm.bodyColor); + ctx.fillStyle = bodyColor.alpha(opacity * bodyColor.alpha()).rgbString(); + ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + + // Before Body + helpers.each(vm.beforeBody, function(beforeBody) { + ctx.fillText(beforeBody, pt.x, pt.y); + pt.y += vm.bodyFontSize + vm.bodySpacing; + }); + + helpers.each(vm.body, function(body, i) { + // Draw Legend-like boxes if needed + if (this._options.tooltips.mode !== 'single') { + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString(); + ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize); + + // Border + ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString(); + ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize); + + // Inner square + ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString(); + ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2); + + ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text + } + + // Body Line + ctx.fillText(body, pt.x + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y); + + pt.y += vm.bodyFontSize + vm.bodySpacing; + }, this); + + // After Body + helpers.each(vm.afterBody, function(afterBody) { + ctx.fillText(afterBody, pt.x, pt.y); + pt.y += vm.bodyFontSize; + }); + + pt.y -= vm.bodySpacing; // Remove last body spacing + }, + drawFooter: function drawFooter(pt, vm, ctx, opacity) { + if (vm.footer.length) { + pt.y += vm.footerMarginTop; + + ctx.textAlign = vm._footerAlign; + ctx.textBaseline = "top"; + + var footerColor = helpers.color(vm.footerColor); + ctx.fillStyle = footerColor.alpha(opacity * footerColor.alpha()).rgbString(); + ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + + helpers.each(vm.footer, function(footer) { + ctx.fillText(footer, pt.x, pt.y); + pt.y += vm.footerFontSize + vm.footerSpacing; + }); + } + }, + draw: function draw() { + var ctx = this._chart.ctx; + var vm = this._view; + + if (vm.opacity === 0) { + return; + } + + var caretPadding = vm.caretPadding; + var tooltipSize = this.getTooltipSize(vm); + var pt = { + x: vm.x, + y: vm.y + }; + + // IE11/Edge does not like very small opacities, so snap to 0 + var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; + + if (this._options.tooltips.enabled) { + // Draw Background + var bgColor = helpers.color(vm.backgroundColor); + ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString(); + helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius); + ctx.fill(); + + // Draw Caret + this.drawCaret(pt, tooltipSize, opacity, caretPadding); + + // Draw Title, Body, and Footer + pt.x += vm.xPadding; + pt.y += vm.yPadding; + + // Titles + this.drawTitle(pt, vm, ctx, opacity); + + // Body + this.drawBody(pt, vm, ctx, opacity); + + // Footer + this.drawFooter(pt, vm, ctx, opacity); + } + } + }); +}; + +},{}],33:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart, moment) { + + var helpers = Chart.helpers; + + Chart.defaults.global.elements.arc = { + backgroundColor: Chart.defaults.global.defaultColor, + borderColor: "#fff", + borderWidth: 2 + }; + + Chart.elements.Arc = Chart.Element.extend({ + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } else { + return false; + } + }, + inRange: function(chartX, chartY) { + + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers.getAngleFromPoint(vm, { + x: chartX, + y: chartY + }); + + //Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += 2.0 * Math.PI; + } + while (pointRelativePosition.angle > endAngle) { + pointRelativePosition.angle -= 2.0 * Math.PI; + } + while (pointRelativePosition.angle < startAngle) { + pointRelativePosition.angle += 2.0 * Math.PI; + } + + //Check if within the range of the open/close angle + var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle), + withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } else { + return false; + } + }, + tooltipPosition: function() { + var vm = this._view; + + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), + rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + draw: function() { + + var ctx = this._chart.ctx; + var vm = this._view; + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle); + + ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true); + + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + ctx.fillStyle = vm.backgroundColor; + + ctx.fill(); + ctx.lineJoin = 'bevel'; + + if (vm.borderWidth) { + ctx.stroke(); + } + } + }); +}; + +},{}],34:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.elements.line = { + tension: 0.4, + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 3, + borderColor: Chart.defaults.global.defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + fill: true // do we fill in the area between the line and its base axis + }; + + Chart.elements.Line = Chart.Element.extend({ + lineToNextPoint: function(previousPoint, point, nextPoint, skipHandler, previousSkipHandler) { + var ctx = this._chart.ctx; + + if (point._view.skip) { + skipHandler.call(this, previousPoint, point, nextPoint); + } else if (previousPoint._view.skip) { + previousSkipHandler.call(this, previousPoint, point, nextPoint); + } else if (point._view.tension === 0) { + ctx.lineTo(point._view.x, point._view.y); + } else { + // Line between points + ctx.bezierCurveTo( + previousPoint._view.controlPointNextX, + previousPoint._view.controlPointNextY, + point._view.controlPointPreviousX, + point._view.controlPointPreviousY, + point._view.x, + point._view.y + ); + } + }, + + draw: function() { + var _this = this; + + var vm = this._view; + var ctx = this._chart.ctx; + var first = this._children[0]; + var last = this._children[this._children.length - 1]; + + function loopBackToStart(drawLineToCenter) { + if (!first._view.skip && !last._view.skip) { + // Draw a bezier line from last to first + ctx.bezierCurveTo( + last._view.controlPointNextX, + last._view.controlPointNextY, + first._view.controlPointPreviousX, + first._view.controlPointPreviousY, + first._view.x, + first._view.y + ); + } else if (drawLineToCenter) { + // Go to center + ctx.lineTo(_this._view.scaleZero.x, _this._view.scaleZero.y); + } + } + + ctx.save(); + + // If we had points and want to fill this line, do so. + if (this._children.length > 0 && vm.fill) { + // Draw the background first (so the border is always on top) + ctx.beginPath(); + + helpers.each(this._children, function(point, index) { + var previous = helpers.previousItem(this._children, index); + var next = helpers.nextItem(this._children, index); + + // First point moves to it's starting position no matter what + if (index === 0) { + if (this._loop) { + ctx.moveTo(vm.scaleZero.x, vm.scaleZero.y); + } else { + ctx.moveTo(point._view.x, vm.scaleZero); + } + + if (point._view.skip) { + if (!this._loop) { + ctx.moveTo(next._view.x, this._view.scaleZero); + } + } else { + ctx.lineTo(point._view.x, point._view.y); + } + } else { + this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { + if (this._loop) { + // Go to center + ctx.lineTo(this._view.scaleZero.x, this._view.scaleZero.y); + } else { + ctx.lineTo(previousPoint._view.x, this._view.scaleZero); + ctx.moveTo(nextPoint._view.x, this._view.scaleZero); + } + }, function(previousPoint, point) { + // If we skipped the last point, draw a line to ourselves so that the fill is nice + ctx.lineTo(point._view.x, point._view.y); + }); + } + }, this); + + // For radial scales, loop back around to the first point + if (this._loop) { + loopBackToStart(true); + } else { + //Round off the line by going to the base of the chart, back to the start, then fill. + ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); + ctx.lineTo(this._children[0]._view.x, vm.scaleZero); + } + + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; + ctx.closePath(); + ctx.fill(); + } + + // Now draw the line between all the points with any borders + ctx.lineCap = vm.borderCapStyle || Chart.defaults.global.elements.line.borderCapStyle; + + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || Chart.defaults.global.elements.line.borderDash); + } + + ctx.lineDashOffset = vm.borderDashOffset || Chart.defaults.global.elements.line.borderDashOffset; + ctx.lineJoin = vm.borderJoinStyle || Chart.defaults.global.elements.line.borderJoinStyle; + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.line.borderWidth; + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; + ctx.beginPath(); + + helpers.each(this._children, function(point, index) { + var previous = helpers.previousItem(this._children, index); + var next = helpers.nextItem(this._children, index); + + if (index === 0) { + ctx.moveTo(point._view.x, point._view.y); + } else { + this.lineToNextPoint(previous, point, next, function(previousPoint, point, nextPoint) { + ctx.moveTo(nextPoint._view.x, nextPoint._view.y); + }, function(previousPoint, point) { + // If we skipped the last point, move up to our point preventing a line from being drawn + ctx.moveTo(point._view.x, point._view.y); + }); + } + }, this); + + if (this._loop && this._children.length > 0) { + loopBackToStart(); + } + + ctx.stroke(); + ctx.restore(); + } + }); +}; +},{}],35:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.elements.point = { + radius: 3, + pointStyle: 'circle', + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + }; + + + Chart.elements.Point = Chart.Element.extend({ + inRange: function(mouseX, mouseY) { + var vm = this._view; + + if (vm) { + var hoverRange = vm.hitRadius + vm.radius; + return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); + } else { + return false; + } + }, + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)); + } else { + return false; + } + }, + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + draw: function() { + + var vm = this._view; + var ctx = this._chart.ctx; + + + if (vm.skip) { + return; + } + + if (typeof vm.pointStyle === 'object' && ((vm.pointStyle.toString() === '[object HTMLImageElement]') || (vm.pointStyle.toString() === '[object HTMLCanvasElement]'))) { + ctx.drawImage(vm.pointStyle, vm.x - vm.pointStyle.width / 2, vm.y - vm.pointStyle.height / 2); + return; + } + + if (!isNaN(vm.radius) && vm.radius > 0) { + + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; + ctx.lineWidth = helpers.getValueOrDefault(vm.borderWidth, Chart.defaults.global.elements.point.borderWidth); + + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; + + var radius = vm.radius; + + var xOffset; + var yOffset; + + switch (vm.pointStyle) { + // Default includes circle + default: ctx.beginPath(); + ctx.arc(vm.x, vm.y, radius, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + break; + case 'triangle': + ctx.beginPath(); + var edgeLength = 3 * radius / Math.sqrt(3); + var height = edgeLength * Math.sqrt(3) / 2; + ctx.moveTo(vm.x - edgeLength / 2, vm.y + height / 3); + ctx.lineTo(vm.x + edgeLength / 2, vm.y + height / 3); + ctx.lineTo(vm.x, vm.y - 2 * height / 3); + ctx.closePath(); + ctx.fill(); + break; + case 'rect': + ctx.fillRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); + ctx.strokeRect(vm.x - 1 / Math.SQRT2 * radius, vm.y - 1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); + break; + case 'rectRot': + ctx.translate(vm.x, vm.y); + ctx.rotate(Math.PI / 4); + ctx.fillRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); + ctx.strokeRect(-1 / Math.SQRT2 * radius, -1 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius, 2 / Math.SQRT2 * radius); + ctx.setTransform(1, 0, 0, 1, 0, 0); + break; + case 'cross': + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y + radius); + ctx.lineTo(vm.x, vm.y - radius); + ctx.moveTo(vm.x - radius, vm.y); + ctx.lineTo(vm.x + radius, vm.y); + ctx.closePath(); + break; + case 'crossRot': + ctx.beginPath(); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(vm.x - xOffset, vm.y - yOffset); + ctx.lineTo(vm.x + xOffset, vm.y + yOffset); + ctx.moveTo(vm.x - xOffset, vm.y + yOffset); + ctx.lineTo(vm.x + xOffset, vm.y - yOffset); + ctx.closePath(); + break; + case 'star': + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y + radius); + ctx.lineTo(vm.x, vm.y - radius); + ctx.moveTo(vm.x - radius, vm.y); + ctx.lineTo(vm.x + radius, vm.y); + xOffset = Math.cos(Math.PI / 4) * radius; + yOffset = Math.sin(Math.PI / 4) * radius; + ctx.moveTo(vm.x - xOffset, vm.y - yOffset); + ctx.lineTo(vm.x + xOffset, vm.y + yOffset); + ctx.moveTo(vm.x - xOffset, vm.y + yOffset); + ctx.lineTo(vm.x + xOffset, vm.y - yOffset); + ctx.closePath(); + break; + case 'line': + ctx.beginPath(); + ctx.moveTo(vm.x - radius, vm.y); + ctx.lineTo(vm.x + radius, vm.y); + ctx.closePath(); + break; + case 'dash': + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y); + ctx.lineTo(vm.x + radius, vm.y); + ctx.closePath(); + break; + } + + ctx.stroke(); + } + } + }); +}; +},{}],36:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + Chart.defaults.global.elements.rectangle = { + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 0, + borderColor: Chart.defaults.global.defaultColor, + borderSkipped: 'bottom' + }; + + Chart.elements.Rectangle = Chart.Element.extend({ + draw: function() { + + var ctx = this._chart.ctx; + var vm = this._view; + + var halfWidth = vm.width / 2, + leftX = vm.x - halfWidth, + rightX = vm.x + halfWidth, + top = vm.base - (vm.base - vm.y), + halfStroke = vm.borderWidth / 2; + + // Canvas doesn't allow us to stroke inside the width so we can + // adjust the sizes to fit if we're setting a stroke on the line + if (vm.borderWidth) { + leftX += halfStroke; + rightX -= halfStroke; + top += halfStroke; + } + + ctx.beginPath(); + + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + var corners = [ + [leftX, vm.base], + [leftX, top], + [rightX, top], + [rightX, vm.base] + ]; + + // Find first (starting) corner with fallback to 'bottom' + var borders = ['bottom', 'left', 'top', 'right']; + var startCorner = borders.indexOf(vm.borderSkipped, 0); + if (startCorner === -1) + startCorner = 0; + + function cornerAt(index) { + return corners[(startCorner + index) % 4]; + } + + // Draw rectangle from 'startCorner' + ctx.moveTo.apply(ctx, cornerAt(0)); + for (var i = 1; i < 4; i++) + ctx.lineTo.apply(ctx, cornerAt(i)); + + ctx.fill(); + if (vm.borderWidth) { + ctx.stroke(); + } + }, + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + inRange: function(mouseX, mouseY) { + var vm = this._view; + var inRange = false; + + if (vm) { + if (vm.y < vm.base) { + inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); + } else { + inRange = (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y); + } + } + + return inRange; + }, + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); + } else { + return false; + } + }, + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } + }); + +}; +},{}],37:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + // Default config for a category scale + var defaultConfig = { + position: "bottom" + }; + + var DatasetScale = Chart.Scale.extend({ + buildTicks: function(index) { + this.startIndex = 0; + this.endIndex = this.chart.data.labels.length; + var findIndex; + + if (this.options.ticks.min !== undefined) { + // user specified min value + findIndex = helpers.indexOf(this.chart.data.labels, this.options.ticks.min); + this.startIndex = findIndex !== -1 ? findIndex : this.startIndex; + } + + if (this.options.ticks.max !== undefined) { + // user specified max value + findIndex = helpers.indexOf(this.chart.data.labels, this.options.ticks.max); + this.endIndex = findIndex !== -1 ? findIndex : this.endIndex; + } + + // If we are viewing some subset of labels, slice the original array + this.ticks = (this.startIndex === 0 && this.endIndex === this.chart.data.labels.length) ? this.chart.data.labels : this.chart.data.labels.slice(this.startIndex, this.endIndex + 1); + }, + + getLabelForIndex: function(index, datasetIndex) { + return this.ticks[index]; + }, + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function(value, index, datasetIndex, includeOffset) { + // 1 is added because we need the length but we have the indexes + var offsetAmt = Math.max((this.ticks.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1); + + if (this.isHorizontal()) { + var innerWidth = this.width - (this.paddingLeft + this.paddingRight); + var valueWidth = innerWidth / offsetAmt; + var widthOffset = (valueWidth * (index - this.startIndex)) + this.paddingLeft; + + if (this.options.gridLines.offsetGridLines && includeOffset) { + widthOffset += (valueWidth / 2); + } + + return this.left + Math.round(widthOffset); + } else { + var innerHeight = this.height - (this.paddingTop + this.paddingBottom); + var valueHeight = innerHeight / offsetAmt; + var heightOffset = (valueHeight * (index - this.startIndex)) + this.paddingTop; + + if (this.options.gridLines.offsetGridLines && includeOffset) { + heightOffset += (valueHeight / 2); + } + + return this.top + Math.round(heightOffset); + } + }, + getPixelForTick: function(index, includeOffset) { + return this.getPixelForValue(this.ticks[index], index + this.startIndex, null, includeOffset); + } + }); + + Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig); + +}; +},{}],38:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + var defaultConfig = { + position: "left", + ticks: { + callback: function(tickValue, index, ticks) { + var delta = ticks[1] - ticks[0]; + + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1) { + if (tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } + } + + var logDelta = helpers.log10(Math.abs(delta)); + var tickString = ''; + + if (tickValue !== 0) { + var numDecimal = -1 * Math.floor(logDelta); + numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places + tickString = tickValue.toFixed(numDecimal); + } else { + tickString = '0'; // never show decimal places for 0 + } + + return tickString; + } + } + }; + + var LinearScale = Chart.Scale.extend({ + determineDataLimits: function() { + // First Calculate the range + this.min = null; + this.max = null; + + if (this.options.stacked) { + var valuesPerType = {}; + var hasPositiveValues = false; + var hasNegativeValues = false; + + helpers.each(this.chart.data.datasets, function(dataset) { + if (valuesPerType[dataset.type] === undefined) { + valuesPerType[dataset.type] = { + positiveValues: [], + negativeValues: [] + }; + } + + // Store these per type + var positiveValues = valuesPerType[dataset.type].positiveValues; + var negativeValues = valuesPerType[dataset.type].negativeValues; + + if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) { + helpers.each(dataset.data, function(rawValue, index) { + + var value = +this.getRightValue(rawValue); + if (isNaN(value)) { + return; + } + + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; + + if (this.options.relativePoints) { + positiveValues[index] = 100; + } else { + if (value < 0) { + hasNegativeValues = true; + negativeValues[index] += value; + } else { + hasPositiveValues = true; + positiveValues[index] += value; + } + } + }, this); + } + }, this); + + helpers.each(valuesPerType, function(valuesForType) { + var values = valuesForType.positiveValues.concat(valuesForType.negativeValues); + var minVal = helpers.min(values); + var maxVal = helpers.max(values); + this.min = this.min === null ? minVal : Math.min(this.min, minVal); + this.max = this.max === null ? maxVal : Math.max(this.max, maxVal); + }, this); + + } else { + helpers.each(this.chart.data.datasets, function(dataset) { + if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +this.getRightValue(rawValue); + if (isNaN(value)) { + return; + } + + if (this.min === null) { + this.min = value; + } else if (value < this.min) { + this.min = value; + } + + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + } + }, this); + } + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (this.options.ticks.beginAtZero) { + var minSign = helpers.sign(this.min); + var maxSign = helpers.sign(this.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + this.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the botttom down to 0 + this.min = 0; + } + } + + if (this.options.ticks.min !== undefined) { + this.min = this.options.ticks.min; + } else if (this.options.ticks.suggestedMin !== undefined) { + this.min = Math.min(this.min, this.options.ticks.suggestedMin); + } + + if (this.options.ticks.max !== undefined) { + this.max = this.options.ticks.max; + } else if (this.options.ticks.suggestedMax !== undefined) { + this.max = Math.max(this.max, this.options.ticks.suggestedMax); + } + + if (this.min === this.max) { + this.min--; + this.max++; + } + }, + buildTicks: function() { + + // Then calulate the ticks + this.ticks = []; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + + var maxTicks; + + if (this.isHorizontal()) { + maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, Math.ceil(this.width / 50)); + } else { + // The factor of 2 used to scale the font size has been experimentally determined. + var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, Math.ceil(this.height / (2 * tickFontSize))); + } + + // Make sure we always have at least 2 ticks + maxTicks = Math.max(2, maxTicks); + + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + var spacing; + var fixedStepSizeSet = (this.options.ticks.fixedStepSize && this.options.ticks.fixedStepSize > 0) || (this.options.ticks.stepSize && this.options.ticks.stepSize > 0); + if (fixedStepSizeSet) { + spacing = helpers.getValueOrDefault(this.options.ticks.fixedStepSize, this.options.ticks.stepSize); + } else { + var niceRange = helpers.niceNum(this.max - this.min, false); + spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); + } + var niceMin = Math.floor(this.min / spacing) * spacing; + var niceMax = Math.ceil(this.max / spacing) * spacing; + var numSpaces = (niceMax - niceMin) / spacing; + + // If very close to our rounded value, use it. + if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } + + // Put the values into the ticks array + this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin); + for (var j = 1; j < numSpaces; ++j) { + this.ticks.push(niceMin + (j * spacing)); + } + this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax); + + if (this.options.position === "left" || this.options.position === "right") { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + this.max = helpers.max(this.ticks); + this.min = helpers.min(this.ticks); + + if (this.options.ticks.reverse) { + this.ticks.reverse(); + + this.start = this.max; + this.end = this.min; + } else { + this.start = this.min; + this.end = this.max; + } + }, + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + convertTicksToLabels: function() { + this.ticksAsNumbers = this.ticks.slice(); + this.zeroLineIndex = this.ticks.indexOf(0); + + Chart.Scale.prototype.convertTicksToLabels.call(this); + }, + // Utils + getPixelForValue: function(value, index, datasetIndex, includeOffset) { + // This must be called after fit has been run so that + // this.left, this.top, this.right, and this.bottom have been defined + var rightValue = +this.getRightValue(value); + var pixel; + var range = this.end - this.start; + + if (this.isHorizontal()) { + var innerWidth = this.width - (this.paddingLeft + this.paddingRight); + pixel = this.left + (innerWidth / range * (rightValue - this.start)); + return Math.round(pixel + this.paddingLeft); + } else { + var innerHeight = this.height - (this.paddingTop + this.paddingBottom); + pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (rightValue - this.start)); + return Math.round(pixel); + } + }, + getPixelForTick: function(index, includeOffset) { + return this.getPixelForValue(this.ticksAsNumbers[index], null, null, includeOffset); + } + }); + Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig); + +}; +},{}],39:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + var defaultConfig = { + position: "left", + + // label settings + ticks: { + callback: function(value, index, arr) { + var remain = value / (Math.pow(10, Math.floor(Chart.helpers.log10(value)))); + + if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === arr.length - 1) { + return value.toExponential(); + } else { + return ''; + } + } + } + }; + + var LogarithmicScale = Chart.Scale.extend({ + determineDataLimits: function() { + // Calculate Range + this.min = null; + this.max = null; + + if (this.options.stacked) { + var valuesPerType = {}; + + helpers.each(this.chart.data.datasets, function(dataset) { + if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) { + if (valuesPerType[dataset.type] === undefined) { + valuesPerType[dataset.type] = []; + } + + helpers.each(dataset.data, function(rawValue, index) { + var values = valuesPerType[dataset.type]; + var value = +this.getRightValue(rawValue); + if (isNaN(value)) { + return; + } + + values[index] = values[index] || 0; + + if (this.options.relativePoints) { + values[index] = 100; + } else { + // Don't need to split positive and negative since the log scale can't handle a 0 crossing + values[index] += value; + } + }, this); + } + }, this); + + helpers.each(valuesPerType, function(valuesForType) { + var minVal = helpers.min(valuesForType); + var maxVal = helpers.max(valuesForType); + this.min = this.min === null ? minVal : Math.min(this.min, minVal); + this.max = this.max === null ? maxVal : Math.max(this.max, maxVal); + }, this); + + } else { + helpers.each(this.chart.data.datasets, function(dataset) { + if (helpers.isDatasetVisible(dataset) && (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +this.getRightValue(rawValue); + if (isNaN(value)) { + return; + } + + if (this.min === null) { + this.min = value; + } else if (value < this.min) { + this.min = value; + } + + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + } + }, this); + } + + this.min = this.options.ticks.min !== undefined ? this.options.ticks.min : this.min; + this.max = this.options.ticks.max !== undefined ? this.options.ticks.max : this.max; + + if (this.min === this.max) { + if (this.min !== 0 && this.min !== null) { + this.min = Math.pow(10, Math.floor(helpers.log10(this.min)) - 1); + this.max = Math.pow(10, Math.floor(helpers.log10(this.max)) + 1); + } else { + this.min = 1; + this.max = 10; + } + } + }, + buildTicks: function() { + // Reset the ticks array. Later on, we will draw a grid line at these positions + // The array simply contains the numerical value of the spots where ticks will be + this.ticks = []; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + + var tickVal = this.options.ticks.min !== undefined ? this.options.ticks.min : Math.pow(10, Math.floor(helpers.log10(this.min))); + + while (tickVal < this.max) { + this.ticks.push(tickVal); + + var exp = Math.floor(helpers.log10(tickVal)); + var significand = Math.floor(tickVal / Math.pow(10, exp)) + 1; + + if (significand === 10) { + significand = 1; + ++exp; + } + + tickVal = significand * Math.pow(10, exp); + } + + var lastTick = this.options.ticks.max !== undefined ? this.options.ticks.max : tickVal; + this.ticks.push(lastTick); + + if (this.options.position === "left" || this.options.position === "right") { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + this.max = helpers.max(this.ticks); + this.min = helpers.min(this.ticks); + + if (this.options.ticks.reverse) { + this.ticks.reverse(); + + this.start = this.max; + this.end = this.min; + } else { + this.start = this.min; + this.end = this.max; + } + }, + convertTicksToLabels: function() { + this.tickValues = this.ticks.slice(); + + Chart.Scale.prototype.convertTicksToLabels.call(this); + }, + // Get the correct tooltip label + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + getPixelForTick: function(index, includeOffset) { + return this.getPixelForValue(this.tickValues[index], null, null, includeOffset); + }, + getPixelForValue: function(value, index, datasetIndex, includeOffset) { + var pixel; + + var newVal = +this.getRightValue(value); + var range = helpers.log10(this.end) - helpers.log10(this.start); + + if (this.isHorizontal()) { + + if (newVal === 0) { + pixel = this.left + this.paddingLeft; + } else { + var innerWidth = this.width - (this.paddingLeft + this.paddingRight); + pixel = this.left + (innerWidth / range * (helpers.log10(newVal) - helpers.log10(this.start))); + pixel += this.paddingLeft; + } + } else { + // Bottom - top since pixels increase downard on a screen + if (newVal === 0) { + pixel = this.top + this.paddingTop; + } else { + var innerHeight = this.height - (this.paddingTop + this.paddingBottom); + pixel = (this.bottom - this.paddingBottom) - (innerHeight / range * (helpers.log10(newVal) - helpers.log10(this.start))); + } + } + + return pixel; + } + + }); + Chart.scaleService.registerScaleType("logarithmic", LogarithmicScale, defaultConfig); + +}; +},{}],40:[function(require,module,exports){ +"use strict"; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + + var defaultConfig = { + display: true, + + //Boolean - Whether to animate scaling the chart from the centre + animate: true, + lineArc: false, + position: "chartArea", + + angleLines: { + display: true, + color: "rgba(0, 0, 0, 0.1)", + lineWidth: 1 + }, + + // label settings + ticks: { + //Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + //String - The colour of the label backdrop + backdropColor: "rgba(255,255,255,0.75)", + + //Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + //Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2 + }, + + pointLabels: { + //Number - Point label font size in pixels + fontSize: 10, + + //Function - Used to convert point labels + callback: function(label) { + return label; + } + } + }; + + var LinearRadialScale = Chart.Scale.extend({ + getValueCount: function() { + return this.chart.data.labels.length; + }, + setDimensions: function() { + // Set the unconstrained dimension before label rotation + this.width = this.maxWidth; + this.height = this.maxHeight; + this.xCenter = Math.round(this.width / 2); + this.yCenter = Math.round(this.height / 2); + + var minSize = helpers.min([this.height, this.width]); + var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + this.drawingArea = (this.options.display) ? (minSize / 2) - (tickFontSize / 2 + this.options.ticks.backdropPaddingY) : (minSize / 2); + }, + determineDataLimits: function() { + this.min = null; + this.max = null; + + helpers.each(this.chart.data.datasets, function(dataset) { + if (helpers.isDatasetVisible(dataset)) { + helpers.each(dataset.data, function(rawValue, index) { + var value = +this.getRightValue(rawValue); + if (isNaN(value)) { + return; + } + + if (this.min === null) { + this.min = value; + } else if (value < this.min) { + this.min = value; + } + + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + } + }, this); + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (this.options.ticks.beginAtZero) { + var minSign = helpers.sign(this.min); + var maxSign = helpers.sign(this.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + this.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the botttom down to 0 + this.min = 0; + } + } + + if (this.options.ticks.min !== undefined) { + this.min = this.options.ticks.min; + } else if (this.options.ticks.suggestedMin !== undefined) { + this.min = Math.min(this.min, this.options.ticks.suggestedMin); + } + + if (this.options.ticks.max !== undefined) { + this.max = this.options.ticks.max; + } else if (this.options.ticks.suggestedMax !== undefined) { + this.max = Math.max(this.max, this.options.ticks.suggestedMax); + } + + if (this.min === this.max) { + this.min--; + this.max++; + } + }, + buildTicks: function() { + + + this.ticks = []; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + var maxTicks = Math.min(this.options.ticks.maxTicksLimit ? this.options.ticks.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize))); + maxTicks = Math.max(2, maxTicks); // Make sure we always have at least 2 ticks + + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + var niceRange = helpers.niceNum(this.max - this.min, false); + var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); + var niceMin = Math.floor(this.min / spacing) * spacing; + var niceMax = Math.ceil(this.max / spacing) * spacing; + + var numSpaces = Math.ceil((niceMax - niceMin) / spacing); + + // Put the values into the ticks array + this.ticks.push(this.options.ticks.min !== undefined ? this.options.ticks.min : niceMin); + for (var j = 1; j < numSpaces; ++j) { + this.ticks.push(niceMin + (j * spacing)); + } + this.ticks.push(this.options.ticks.max !== undefined ? this.options.ticks.max : niceMax); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + this.max = helpers.max(this.ticks); + this.min = helpers.min(this.ticks); + + if (this.options.ticks.reverse) { + this.ticks.reverse(); + + this.start = this.max; + this.end = this.min; + } else { + this.start = this.min; + this.end = this.max; + } + + this.zeroLineIndex = this.ticks.indexOf(0); + }, + convertTicksToLabels: function() { + Chart.Scale.prototype.convertTicksToLabels.call(this); + + // Point labels + this.pointLabels = this.chart.data.labels.map(this.options.pointLabels.callback, this); + }, + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + fit: function() { + /* + * Right, this is really confusing and there is a lot of maths going on here + * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + * + * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + * + * Solution: + * + * We assume the radius of the polygon is half the size of the canvas at first + * at each index we check if the text overlaps. + * + * Where it does, we store that angle and that index. + * + * After finding the largest index and angle we calculate how much we need to remove + * from the shape radius to move the point inwards by that x. + * + * We average the left and right distances to get the maximum shape radius that can fit in the box + * along with labels. + * + * Once we have that, we can find the centre point for the chart, by taking the x text protrusion + * on each side, removing that from the size, halving it and adding the left x protrusion width. + * + * This will mean we have a shape fitted to the canvas, as large as it can be with the labels + * and position it in the most space efficient manner + * + * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + */ + + var pointLabelFontSize = helpers.getValueOrDefault(this.options.pointLabels.fontSize, Chart.defaults.global.defaultFontSize); + var pointLabeFontStyle = helpers.getValueOrDefault(this.options.pointLabels.fontStyle, Chart.defaults.global.defaultFontStyle); + var pointLabeFontFamily = helpers.getValueOrDefault(this.options.pointLabels.fontFamily, Chart.defaults.global.defaultFontFamily); + var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]), + pointPosition, + i, + textWidth, + halfTextWidth, + furthestRight = this.width, + furthestRightIndex, + furthestRightAngle, + furthestLeft = 0, + furthestLeftIndex, + furthestLeftAngle, + xProtrusionLeft, + xProtrusionRight, + radiusReductionRight, + radiusReductionLeft, + maxWidthRadius; + this.ctx.font = pointLabeFont; + + for (i = 0; i < this.getValueCount(); i++) { + // 5px to space the text slightly out - similar to what we do in the draw function. + pointPosition = this.getPointPosition(i, largestPossibleRadius); + textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5; + if (i === 0 || i === this.getValueCount() / 2) { + // If we're at index zero, or exactly the middle, we're at exactly the top/bottom + // of the radar chart, so text will be aligned centrally, so we'll half it and compare + // w/left and right text sizes + halfTextWidth = textWidth / 2; + if (pointPosition.x + halfTextWidth > furthestRight) { + furthestRight = pointPosition.x + halfTextWidth; + furthestRightIndex = i; + } + if (pointPosition.x - halfTextWidth < furthestLeft) { + furthestLeft = pointPosition.x - halfTextWidth; + furthestLeftIndex = i; + } + } else if (i < this.getValueCount() / 2) { + // Less than half the values means we'll left align the text + if (pointPosition.x + textWidth > furthestRight) { + furthestRight = pointPosition.x + textWidth; + furthestRightIndex = i; + } + } else if (i > this.getValueCount() / 2) { + // More than half the values means we'll right align the text + if (pointPosition.x - textWidth < furthestLeft) { + furthestLeft = pointPosition.x - textWidth; + furthestLeftIndex = i; + } + } + } + + xProtrusionLeft = furthestLeft; + xProtrusionRight = Math.ceil(furthestRight - this.width); + + furthestRightAngle = this.getIndexAngle(furthestRightIndex); + furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); + + radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); + radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); + + // Ensure we actually need to reduce the size of the chart + radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; + radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; + + this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2); + this.setCenterPoint(radiusReductionLeft, radiusReductionRight); + }, + setCenterPoint: function(leftMovement, rightMovement) { + + var maxRight = this.width - rightMovement - this.drawingArea, + maxLeft = leftMovement + this.drawingArea; + + this.xCenter = Math.round(((maxLeft + maxRight) / 2) + this.left); + // Always vertically in the centre as the text height doesn't change + this.yCenter = Math.round((this.height / 2) + this.top); + }, + + getIndexAngle: function(index) { + var angleMultiplier = (Math.PI * 2) / this.getValueCount(); + // Start from the top instead of right, so remove a quarter of the circle + + return index * angleMultiplier - (Math.PI / 2); + }, + getDistanceFromCenterForValue: function(value) { + if (value === null) { + return 0; // null always in center + } + + // Take into account half font size + the yPadding of the top value + var scalingFactor = this.drawingArea / (this.max - this.min); + if (this.options.reverse) { + return (this.max - value) * scalingFactor; + } else { + return (value - this.min) * scalingFactor; + } + }, + getPointPosition: function(index, distanceFromCenter) { + var thisAngle = this.getIndexAngle(index); + return { + x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, + y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + this.yCenter + }; + }, + getPointPositionForValue: function(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, + draw: function() { + if (this.options.display) { + var ctx = this.ctx; + helpers.each(this.ticks, function(label, index) { + // Don't draw a centre value (if it is minimum) + if (index > 0 || this.options.reverse) { + var yCenterOffset = this.getDistanceFromCenterForValue(this.ticks[index]); + var yHeight = this.yCenter - yCenterOffset; + + // Draw circular lines around the scale + if (this.options.gridLines.display) { + ctx.strokeStyle = this.options.gridLines.color; + ctx.lineWidth = this.options.gridLines.lineWidth; + + if (this.options.lineArc) { + // Draw circular arcs between the points + ctx.beginPath(); + ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2); + ctx.closePath(); + ctx.stroke(); + } else { + // Draw straight lines connecting each index + ctx.beginPath(); + for (var i = 0; i < this.getValueCount(); i++) { + var pointPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.ticks[index])); + if (i === 0) { + ctx.moveTo(pointPosition.x, pointPosition.y); + } else { + ctx.lineTo(pointPosition.x, pointPosition.y); + } + } + ctx.closePath(); + ctx.stroke(); + } + } + + if (this.options.ticks.display) { + var tickFontColor = helpers.getValueOrDefault(this.options.ticks.fontColor, Chart.defaults.global.defaultFontColor); + var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + ctx.font = tickLabelFont; + + if (this.options.ticks.showLabelBackdrop) { + var labelWidth = ctx.measureText(label).width; + ctx.fillStyle = this.options.ticks.backdropColor; + ctx.fillRect( + this.xCenter - labelWidth / 2 - this.options.ticks.backdropPaddingX, + yHeight - tickFontSize / 2 - this.options.ticks.backdropPaddingY, + labelWidth + this.options.ticks.backdropPaddingX * 2, + tickFontSize + this.options.ticks.backdropPaddingY * 2 + ); + } + + ctx.textAlign = 'center'; + ctx.textBaseline = "middle"; + ctx.fillStyle = tickFontColor; + ctx.fillText(label, this.xCenter, yHeight); + } + } + }, this); + + if (!this.options.lineArc) { + ctx.lineWidth = this.options.angleLines.lineWidth; + ctx.strokeStyle = this.options.angleLines.color; + + for (var i = this.getValueCount() - 1; i >= 0; i--) { + if (this.options.angleLines.display) { + var outerPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max)); + ctx.beginPath(); + ctx.moveTo(this.xCenter, this.yCenter); + ctx.lineTo(outerPosition.x, outerPosition.y); + ctx.stroke(); + ctx.closePath(); + } + // Extra 3px out for some label spacing + var pointLabelPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.options.reverse ? this.min : this.max) + 5); + + var pointLabelFontColor = helpers.getValueOrDefault(this.options.pointLabels.fontColor, Chart.defaults.global.defaultFontColor); + var pointLabelFontSize = helpers.getValueOrDefault(this.options.pointLabels.fontSize, Chart.defaults.global.defaultFontSize); + var pointLabeFontStyle = helpers.getValueOrDefault(this.options.pointLabels.fontStyle, Chart.defaults.global.defaultFontStyle); + var pointLabeFontFamily = helpers.getValueOrDefault(this.options.pointLabels.fontFamily, Chart.defaults.global.defaultFontFamily); + var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily); + + ctx.font = pointLabeFont; + ctx.fillStyle = pointLabelFontColor; + + var labelsCount = this.pointLabels.length, + halfLabelsCount = this.pointLabels.length / 2, + quarterLabelsCount = halfLabelsCount / 2, + upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), + exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); + if (i === 0) { + ctx.textAlign = 'center'; + } else if (i === halfLabelsCount) { + ctx.textAlign = 'center'; + } else if (i < halfLabelsCount) { + ctx.textAlign = 'left'; + } else { + ctx.textAlign = 'right'; + } + + // Set the correct text baseline based on outer positioning + if (exactQuarter) { + ctx.textBaseline = 'middle'; + } else if (upperHalf) { + ctx.textBaseline = 'bottom'; + } else { + ctx.textBaseline = 'top'; + } + + ctx.fillText(this.pointLabels[i] ? this.pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y); + } + } + } + } + }); + Chart.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig); + +}; +},{}],41:[function(require,module,exports){ +/*global window: false */ +"use strict"; + +var moment = require('moment'); +moment = typeof(moment) === 'function' ? moment : window.moment; + +module.exports = function(Chart) { + + var helpers = Chart.helpers; + var time = { + units: [{ + name: 'millisecond', + steps: [1, 2, 5, 10, 20, 50, 100, 250, 500] + }, { + name: 'second', + steps: [1, 2, 5, 10, 30] + }, { + name: 'minute', + steps: [1, 2, 5, 10, 30] + }, { + name: 'hour', + steps: [1, 2, 3, 6, 12] + }, { + name: 'day', + steps: [1, 2, 5] + }, { + name: 'week', + maxStep: 4 + }, { + name: 'month', + maxStep: 3 + }, { + name: 'quarter', + maxStep: 4 + }, { + name: 'year', + maxStep: false + }] + }; + + var defaultConfig = { + position: "bottom", + + time: { + parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment + format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/ + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + displayFormat: false, // DEPRECATED + + // defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/ + displayFormats: { + 'millisecond': 'h:mm:ss.SSS a', // 11:20:01.123 AM, + 'second': 'h:mm:ss a', // 11:20:01 AM + 'minute': 'h:mm:ss a', // 11:20:01 AM + 'hour': 'MMM D, hA', // Sept 4, 5PM + 'day': 'll', // Sep 4 2015 + 'week': 'll', // Week 46, or maybe "[W]WW - YYYY" ? + 'month': 'MMM YYYY', // Sept 2015 + 'quarter': '[Q]Q - YYYY', // Q3 + 'year': 'YYYY' // 2015 + } + }, + ticks: { + autoSkip: false + } + }; + + var TimeScale = Chart.Scale.extend({ + initialize: function() { + if (!moment) { + throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com'); + } + + Chart.Scale.prototype.initialize.call(this); + }, + getLabelMoment: function(datasetIndex, index) { + return this.labelMoments[datasetIndex][index]; + }, + determineDataLimits: function() { + this.labelMoments = []; + + // Only parse these once. If the dataset does not have data as x,y pairs, we will use + // these + var scaleLabelMoments = []; + if (this.chart.data.labels && this.chart.data.labels.length > 0) { + helpers.each(this.chart.data.labels, function(label, index) { + var labelMoment = this.parseTime(label); + if (this.options.time.round) { + labelMoment.startOf(this.options.time.round); + } + scaleLabelMoments.push(labelMoment); + }, this); + + this.firstTick = moment.min.call(this, scaleLabelMoments); + this.lastTick = moment.max.call(this, scaleLabelMoments); + } else { + this.firstTick = null; + this.lastTick = null; + } + + helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + var momentsForDataset = []; + + if (typeof dataset.data[0] === 'object') { + helpers.each(dataset.data, function(value, index) { + var labelMoment = this.parseTime(this.getRightValue(value)); + if (this.options.time.round) { + labelMoment.startOf(this.options.time.round); + } + momentsForDataset.push(labelMoment); + + // May have gone outside the scale ranges, make sure we keep the first and last ticks updated + this.firstTick = this.firstTick !== null ? moment.min(this.firstTick, labelMoment) : labelMoment; + this.lastTick = this.lastTick !== null ? moment.max(this.lastTick, labelMoment) : labelMoment; + }, this); + } else { + // We have no labels. Use the ones from the scale + momentsForDataset = scaleLabelMoments; + } + + this.labelMoments.push(momentsForDataset); + }, this); + + // Set these after we've done all the data + if (this.options.time.min) { + this.firstTick = this.parseTime(this.options.time.min); + } + + if (this.options.time.max) { + this.lastTick = this.parseTime(this.options.time.max); + } + + // We will modify these, so clone for later + this.firstTick = (this.firstTick || moment()).clone(); + this.lastTick = (this.lastTick || moment()).clone(); + }, + buildTicks: function(index) { + + this.ctx.save(); + var tickFontSize = helpers.getValueOrDefault(this.options.ticks.fontSize, Chart.defaults.global.defaultFontSize); + var tickFontStyle = helpers.getValueOrDefault(this.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle); + var tickFontFamily = helpers.getValueOrDefault(this.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily); + var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily); + this.ctx.font = tickLabelFont; + + this.ticks = []; + this.unitScale = 1; // How much we scale the unit by, ie 2 means 2x unit per step + this.scaleSizeInUnits = 0; // How large the scale is in the base unit (seconds, minutes, etc) + + // Set unit override if applicable + if (this.options.time.unit) { + this.tickUnit = this.options.time.unit || 'day'; + this.displayFormat = this.options.time.displayFormats[this.tickUnit]; + this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true); + this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, 1); + } else { + // Determine the smallest needed unit of the time + var innerWidth = this.isHorizontal() ? this.width - (this.paddingLeft + this.paddingRight) : this.height - (this.paddingTop + this.paddingBottom); + + // Crude approximation of what the label length might be + var tempFirstLabel = this.tickFormatFunction(this.firstTick, 0, []); + var tickLabelWidth = this.ctx.measureText(tempFirstLabel).width; + var cosRotation = Math.cos(helpers.toRadians(this.options.ticks.maxRotation)); + var sinRotation = Math.sin(helpers.toRadians(this.options.ticks.maxRotation)); + tickLabelWidth = (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation); + var labelCapacity = innerWidth / (tickLabelWidth); + + // Start as small as possible + this.tickUnit = 'millisecond'; + this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true); + this.displayFormat = this.options.time.displayFormats[this.tickUnit]; + + var unitDefinitionIndex = 0; + var unitDefinition = time.units[unitDefinitionIndex]; + + // While we aren't ideal and we don't have units left + while (unitDefinitionIndex < time.units.length) { + // Can we scale this unit. If `false` we can scale infinitely + this.unitScale = 1; + + if (helpers.isArray(unitDefinition.steps) && Math.ceil(this.scaleSizeInUnits / labelCapacity) < helpers.max(unitDefinition.steps)) { + // Use one of the prefedined steps + for (var idx = 0; idx < unitDefinition.steps.length; ++idx) { + if (unitDefinition.steps[idx] >= Math.ceil(this.scaleSizeInUnits / labelCapacity)) { + this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, unitDefinition.steps[idx]); + break; + } + } + + break; + } else if ((unitDefinition.maxStep === false) || (Math.ceil(this.scaleSizeInUnits / labelCapacity) < unitDefinition.maxStep)) { + // We have a max step. Scale this unit + this.unitScale = helpers.getValueOrDefault(this.options.time.unitStepSize, Math.ceil(this.scaleSizeInUnits / labelCapacity)); + break; + } else { + // Move to the next unit up + ++unitDefinitionIndex; + unitDefinition = time.units[unitDefinitionIndex]; + + this.tickUnit = unitDefinition.name; + this.scaleSizeInUnits = this.lastTick.diff(this.firstTick, this.tickUnit, true); + this.displayFormat = this.options.time.displayFormats[unitDefinition.name]; + } + } + } + + var roundedStart; + + // Only round the first tick if we have no hard minimum + if (!this.options.time.min) { + this.firstTick.startOf(this.tickUnit); + roundedStart = this.firstTick; + } else { + roundedStart = this.firstTick.clone().startOf(this.tickUnit); + } + + // Only round the last tick if we have no hard maximum + if (!this.options.time.max) { + this.lastTick.endOf(this.tickUnit); + } + + this.smallestLabelSeparation = this.width; + + helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + for (var i = 1; i < this.labelMoments[datasetIndex].length; i++) { + this.smallestLabelSeparation = Math.min(this.smallestLabelSeparation, this.labelMoments[datasetIndex][i].diff(this.labelMoments[datasetIndex][i - 1], this.tickUnit, true)); + } + }, this); + + // Tick displayFormat override + if (this.options.time.displayFormat) { + this.displayFormat = this.options.time.displayFormat; + } + + // first tick. will have been rounded correctly if options.time.min is not specified + this.ticks.push(this.firstTick.clone()); + + // For every unit in between the first and last moment, create a moment and add it to the ticks tick + for (var i = 1; i < this.scaleSizeInUnits; ++i) { + var newTick = roundedStart.clone().add(i, this.tickUnit); + + // Are we greater than the max time + if (this.options.time.max && newTick.diff(this.lastTick, this.tickUnit, true) >= 0) { + break; + } + + if (i % this.unitScale === 0) { + this.ticks.push(newTick); + } + } + + // Always show the right tick + if (this.ticks[this.ticks.length - 1].diff(this.lastTick, this.tickUnit) !== 0 || this.scaleSizeInUnits === 0) { + // this is a weird case. If the option is the same as the end option, we can't just diff the times because the tick was created from the roundedStart + // but the last tick was not rounded. + if (this.options.time.max) { + this.ticks.push(this.lastTick.clone()); + this.scaleSizeInUnits = this.lastTick.diff(this.ticks[0], this.tickUnit, true); + } else { + this.scaleSizeInUnits = Math.ceil(this.scaleSizeInUnits / this.unitScale) * this.unitScale; + this.ticks.push(this.firstTick.clone().add(this.scaleSizeInUnits, this.tickUnit)); + this.lastTick = this.ticks[this.ticks.length - 1].clone(); + } + } + this.ctx.restore(); + }, + // Get tooltip label + getLabelForIndex: function(index, datasetIndex) { + var label = this.chart.data.labels && index < this.chart.data.labels.length ? this.chart.data.labels[index] : ''; + + if (typeof this.chart.data.datasets[datasetIndex].data[0] === 'object') { + label = this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + } + + // Format nicely + if (this.options.time.tooltipFormat) { + label = this.parseTime(label).format(this.options.time.tooltipFormat); + } + + return label; + }, + // Function to format an individual tick mark + tickFormatFunction: function tickFormatFunction(tick, index, ticks) { + var formattedTick = tick.format(this.displayFormat); + + if (this.options.ticks.userCallback) { + return this.options.ticks.userCallback(formattedTick, index, ticks); + } else { + return formattedTick; + } + }, + convertTicksToLabels: function() { + this.ticks = this.ticks.map(this.tickFormatFunction, this); + }, + getPixelForValue: function(value, index, datasetIndex, includeOffset) { + var labelMoment = this.getLabelMoment(datasetIndex, index); + + if (labelMoment) { + var offset = labelMoment.diff(this.firstTick, this.tickUnit, true); + + var decimal = offset / this.scaleSizeInUnits; + + if (this.isHorizontal()) { + var innerWidth = this.width - (this.paddingLeft + this.paddingRight); + var valueWidth = innerWidth / Math.max(this.ticks.length - 1, 1); + var valueOffset = (innerWidth * decimal) + this.paddingLeft; + + return this.left + Math.round(valueOffset); + } else { + var innerHeight = this.height - (this.paddingTop + this.paddingBottom); + var valueHeight = innerHeight / Math.max(this.ticks.length - 1, 1); + var heightOffset = (innerHeight * decimal) + this.paddingTop; + + return this.top + Math.round(heightOffset); + } + } + }, + parseTime: function(label) { + if (typeof this.options.time.parser === 'string') { + return moment(label, this.options.time.parser); + } + if (typeof this.options.time.parser === 'function') { + return this.options.time.parser(label); + } + // Date objects + if (typeof label.getMonth === 'function' || typeof label === 'number') { + return moment(label); + } + // Moment support + if (label.isValid && label.isValid()) { + return label; + } + // Custom parsing (return an instance of moment) + if (typeof this.options.time.format !== 'string' && this.options.time.format.call) { + console.warn("options.time.format is deprecated and replaced by options.time.parser. See http://nnnick.github.io/Chart.js/docs-v2/#scales-time-scale"); + return this.options.time.format(label); + } + // Moment format parsing + return moment(label, this.options.time.format); + } + }); + Chart.scaleService.registerScaleType("time", TimeScale, defaultConfig); + +}; + +},{"moment":1}]},{},[7]); diff --git a/manager/module/chart.php b/manager/module/chart.php index 6998d48..0eade16 100755 --- a/manager/module/chart.php +++ b/manager/module/chart.php @@ -3,10 +3,7 @@ namespace manager\module; use \manager\sessionManager; use \manager\ManagerError; - - // On inclut les graphiques - use \manager\module\chart\networkChart; - + use \manager\lightdb; @@ -14,36 +11,68 @@ class chart{ - /* RETOURNE UN JEU DE DONNEES POUR GRAPHIQUE #NETWORK + /* RETOURNE UN JEU DE DONNEES POUR LE TYPE DE COMMUNICATION (MANQUE/ENTRANT/SORTANT) * */ - public static function network_data(){ - return networkChart::data(); - } - - /* RETOURNE UN SVG CORRESPONDANT AU GRAPHIQUE #NETWORK - * - * @data Jeu de donnees - * - * @return render Contenu du svg representation graphique des donnees - * - */ - public static function network_render($params){ + public static function communication_type($params){ extract($params); - return networkChart::render($data); + $subject = intval($subject); + + + /* [1] On récupère les données de ce sujet + =========================================================*/ + $db = new lightdb('phone_db', __ROOT__.'/src/dynamic/'); + $data = $db->fetch($subject); + $db->close(); + + // Si erreur + if( $data === false ) + return array( 'ModuleError' => ManagerError::ModuleError ); + + + /* [2] S'il a un journal d'appel, on renvoie les données + =========================================================*/ + if( isset($data['logs']) && is_array($data['logs']) ){ + + /* (1) On initialise les compteurs */ + $MISSED = 0; + $OUTGOING = 0; + $INCOMING = 0; + + /* (2) On incrémente les compteurs */ + foreach($data['logs'] as $log){ + + /* (3) Si ce n'est pas un appel, on passe au suivant */ + if( $log['type'] != 0 ) + continue; + + /* (4) On incrémente les types */ + $MISSED += ($log['direction']==2) ? 1 : 0; + $OUTGOING += ($log['direction']==1) ? 1 : 0; + $INCOMING += ($log['direction']==0) ? 1 : 0; + + } + + /* [3] Si aucun journal d'appel + =========================================================*/ + }else{ + + /* (1) On initialise les compteurs */ + $MISSED = 0; + $OUTGOING = 0; + $INCOMING = 0; + + } + + + return array( + 'ModuleError' => ManagerError::Success, + 'labels' => array('ENTRANT', 'SORTANT', 'MANQUÉ'), + 'data' => array($INCOMING, $OUTGOING, $MISSED) + ); } - - - - - - - - - - } diff --git a/manager/module/chart/networkChart.php b/manager/module/chart/networkChart.php deleted file mode 100644 index 320bbe0..0000000 --- a/manager/module/chart/networkChart.php +++ /dev/null @@ -1,229 +0,0 @@ - 17, - - /* (2) Liste des alters */ - 'alter' => array( - // Pour chaque alter, on a ID_ALTER, NOM_ALTER, AFFINITE_ALTER - array( 653, "Norris", 2*5/100 ), - array( 1743, "Melissa", 79*5/100 ), - array( 3739, "Sue", 38*5/100 ), - array( 36, "Rasmussen", 34*5/100 ), - array( 1372, "Brown", 35*5/100 ), - array( 3030, "Brock", 84*5/100 ), - array( 3821, "Savage", 21*5/100 ), - array( 1288, "Black", 17*5/100 ), - array( 1443, "Susie", 69*5/100 ), - array( 4129, "Rowe", 66*5/100 ), - array( 71, "Dillard", 87*5/100 ), - array( 1002, "Richmond", 5*5/100 ), - array( 640, "Morris", 24*5/100 ), - array( 2335, "Daugherty", 53*5/100 ), - array( 2541, "Hughes", 74*5/100 ), - array( 3963, "Roach", 13*5/100 ), - array( 4550, "Hazel", 30*5/100 ), - array( 3120, "Eugenia", 97*5/100 ), - array( 4302, "Nicholson", 25*5/100 ), - array( 859, "Marguerite", 70*5/100 ), - array( 2551, "Aurora", 36*5/100 ), - array( 3612, "Tracie", 55*5/100 ), - array( 3040, "Fulton", 62*5/100 ), - array( 1895, "Stuart", 37*5/100 ), - array( 4342, "Latasha", 21*5/100 ), - array( 2957, "Coleen", 1*5/100 ), - array( 2493, "Contreras", 100*5/100 ), - array( 4776, "Holmes", 97*5/100 ), - array( 3623, "Hallie", 88*5/100 ), - array( 3660, "Ginger", 26*5/100 ), - array( 471, "Slater", 23*5/100 ), - array( 4581, "Elnora", 23*5/100 ), - array( 286, "Patton", 71*5/100 ), - array( 3210, "Autumn", 23*5/100 ), - array( 1896, "Benton", 99*5/100 ), - array( 4875, "Fern", 17*5/100 ), - array( 2156, "Bond", 99*5/100 ), - array( 1556, "Ingrid", 34*5/100 ), - array( 2097, "Case", 23*5/100 ), - array( 3024, "Pat", 57*5/100 ), - array( 1121, "Reeves", 41*5/100 ), - array( 1832, "Francis", 75*5/100 ), - array( 3733, "Moore", 77*5/100 ), - array( 737, "Elva", 49*5/100 ), - array( 1137, "Stevenson", 10*5/100 ), - array( 988, "Lauri", 44*5/100 ), - array( 1182, "Boone", 19*5/100 ), - array( 909, "Morin", 68*5/100 ) - ), - - /* (1) Liste des relations */ - 'inter' => array( - array( 471, 3821 ), - array( 4129, 1137 ), - array( 4302, 2957 ), - array( 859, 3660 ), - array( 640, 71 ), - array( 4129, 1372 ), - array( 909, 4550 ), - array( 2156, 3210 ), - array( 2493, 3660 ), - array( 2541, 4550 ), - array( 1372, 3660 ), - array( 1896, 4875 ), - array( 988, 3120 ), - array( 1121, 1372 ), - array( 4550, 909 ), - array( 988, 3739 ), - array( 2156, 3030 ), - array( 4342, 4302 ), - array( 71, 286 ), - array( 3120, 1182 ), - array( 3821, 3660 ), - array( 2541, 1743 ), - array( 1895, 1182 ), - array( 3030, 1372 ), - array( 3040, 1556 ), - array( 2541, 1832 ), - array( 3660, 3210 ), - array( 1895, 3821 ) - ) - - ); - - /* [5] Gestion du retour - =========================================================*/ - return array( - 'ModuleError' => ManagerError::Success, - 'data' => $data - ); - } - - - - - - - - - - - - /* RETOURNE UN SVG CORRESPONDANT AU GRAPHIQUE #FIELD - * - * @data Jeu de donnees - * - * @return render Contenu du svg representation graphique des donnees - * - */ - public static function render($data){ - $render = ""; - $nb_circles = 4; - - - // Couleur Facebook - $facebook = 0x3b5998; - - /* [1] On genere le debut du svg - =========================================================*/ - $render .= ''; - $render .= ''; - - /* [2] On cree le contexte (cercles dont ego) - =========================================================*/ - $circles = array(); - - // Difference de rayon (40 = taille ego + espace) - $raddiff = (500-50) / ($nb_circles); - - // Pour chaque cercle dont ego - for( $i = 0 ; $i < $nb_circles ; $i++ ){ - - $circles[$i] = ""; - - } - - $circles[ $nb_circles ] = ""; - - - /* [3] On cree les elements dynamiques - =========================================================*/ - /* (1) On calcule l'angle de diff entre chaque ALTER */ - $ang = 2*pi() / count($data['alter']); - - /* (2) On ajoute tous les alters */ - $alters = array(); - $origins = array(); - foreach($data['alter'] as $i=>$alter){ - // On calcule l'origine (centre du cercle) - $origins[$alter[0]] = array( - 'x' => 500 + (5-$alter[2])*(400/5) * cos($ang*$i), - 'y' => 500 + (5-$alter[2])*(400/5) * sin($ang*$i) - ); - - // On ajoute le cercle associe a l'alter courant - $alters[$i] = ""; - - } - - - /* [4] On relie toutes les relations - =========================================================*/ - $inter = array(); - foreach($data['inter'] as $rel){ - $lefthand = isset($origins[$rel[0]]) && is_array($origins[$rel[0]]); - $righthand = isset($origins[$rel[1]]) && is_array($origins[$rel[1]]); - - // Si les deux existent, on les relie - if( $lefthand && $righthand ) - array_push($inter, - "" - ); - } - - - - /* [5] On ajoute les elements et on ferme le svg - =========================================================*/ - foreach($circles as $circle) $render .= $circle; // les cercles (dont ego) - foreach($inter as $rel) $render .= $rel; // les relations - foreach($alters as $alter) $render .= $alter; // les alters - - $render .= ""; - - - /* [6] Gestion du retour - =========================================================*/ - return array( - 'ModuleError' => ManagerError::Success, - 'render' => $render - ); - } - - - - - - - - - - - - } - - -?> \ No newline at end of file diff --git a/test-charts/communication-type.php b/test-charts/communication-type.php new file mode 100644 index 0000000..589ca6c --- /dev/null +++ b/test-charts/communication-type.php @@ -0,0 +1,57 @@ + + + + + Chart type de communication (MANQUE/ENTRANT/SORTANT) + + + + + + + + + + + diff --git a/view/js/charts.js b/view/js/charts.js index c6c67e7..9100a8d 100644 --- a/view/js/charts.js +++ b/view/js/charts.js @@ -14,7 +14,6 @@ var sociogram; chartManagers.sociogram.loaded = include(chartManagers.sociogram.link, null, true); /* (2) Si il est pas déja chargé, on initialise le sociogramme */ - include(chartManagers.sociogram.link, function(){ // On ajoute les méthodes si c'est la première fois qu'on charge if( !chartManagers.sociogram.loaded ) sociogramClass.prototype.overloadGraph(); diff --git a/view/js/input-min.js b/view/js/input-min.js index 2c89662..84497ba 100644 --- a/view/js/input-min.js +++ b/view/js/input-min.js @@ -1,24 +1,26 @@ var cSubjectManager,cContactManager,cMiniManager,cFicheManager,cMatriceManager; -function cDynamicUpdate(b){var a=b instanceof Element,d=a&&"INPUT"==b.tagName&&"submit"==b.type,e=a&&"SPAN"==b.tagName&&("p_nav-mini"==b.parentNode.id||"p_nav-fiche"==b.parentNode.id),a=a&&"SPAN"==b.tagName&&"p_nav-contact"==b.parentNode.id;if(!d&&!e&&!a&&!0!==b)return!1;console.log("> dynamic update");cMiniManager.fieldsToStorage();cFicheManager.fieldsToStorage();cContactManager.fieldsToStorage();cMatriceManager.fieldsToStorage();cFicheManager.sync();cMiniManager.sync();cMiniManager.storageToFields(); -cFicheManager.storageToFields();cMatriceManager.storageToFields();(d||a)&&cContactManager.storageToFields()} -include("/js/includes/input-phone-subject.js",function(){include("/js/includes/input-phone-contact.js",function(){include("/js/includes/input-phone-mini.js",function(){include("/js/includes/input-phone-fiche.js",function(){include("/js/includes/input-phone-matrice.js",function(){cSubjectManager=new inputPhoneSubject($('[data-sublink="phone"] article.subject-panel [data-name="tmp_id"]'),$('[data-sublink="phone"] article.subject-panel [data-name="number"]'),$('[data-sublink="phone"] article.subject-panel [data-name="username"]'), +function cDynamicUpdate(a){var b=a instanceof Element,c=b&&"INPUT"==a.tagName&&"submit"==a.type,e=b&&"SPAN"==a.tagName&&("p_nav-mini"==a.parentNode.id||"p_nav-fiche"==a.parentNode.id),b=b&&"SPAN"==a.tagName&&"p_nav-contact"==a.parentNode.id;if(!c&&!e&&!b&&!0!==a)return!1;console.log("> dynamic update");cMiniManager.fieldsToStorage();cFicheManager.fieldsToStorage();cContactManager.fieldsToStorage();cMatriceManager.fieldsToStorage();cFicheManager.sync();cMiniManager.sync();cMiniManager.storageToFields(); +cFicheManager.storageToFields();cMatriceManager.storageToFields();(c||b)&&cContactManager.storageToFields()} +include("/js/includes/input-phone-subject-min.js",function(){include("/js/includes/input-phone-contact-min.js",function(){include("/js/includes/input-phone-mini-min.js",function(){include("/js/includes/input-phone-fiche-min.js",function(){include("/js/includes/input-phone-matrice-min.js",function(){cSubjectManager=new inputPhoneSubject($('[data-sublink="phone"] article.subject-panel [data-name="tmp_id"]'),$('[data-sublink="phone"] article.subject-panel [data-name="number"]'),$('[data-sublink="phone"] article.subject-panel [data-name="username"]'), $('[data-sublink="phone"] article.subject-panel [data-name="submit"]'));cSubjectManager.attach();cContactManager=new inputPhoneContact($('[data-sublink="phone"] article.contact-panel'),$('[data-sublink="phone"] #p_nav-contact'));cContactManager.attach(cDynamicUpdate);cMiniManager=new inputPhoneMini($('[data-sublink="phone"] article.mini-relation-panel'),$('[data-sublink="phone"] #p_nav-mini'));cMiniManager.attach(cDynamicUpdate);cFicheManager=new inputPhoneFiche($('[data-sublink="phone"] article.relation-panel'), -$('[data-sublink="phone"] #p_nav-fiche'));cFicheManager.attach(cDynamicUpdate);cMatriceManager=new inputPhoneMatrice($('[data-sublink="phone"] article.matrice-panel'));cMatriceManager.attach(cDynamicUpdate);$('[data-sublink="phone"] input#p_call_log-import[type="file"]').addEventListener("click",function(b){b.target.value=null},!1);$('[data-sublink="phone"] input#p_call_log-import[type="file"]').addEventListener("change",function(b){b={path:"upload/call_log",phone_number:$('[data-sublink="phone"] #p_subject_phone_number').value, -file:b.target.files[0]};api.send(b,function(a){console.log(a);var b=null;if(0!=a.ModuleError)9==a.ModuleError?Notification.error("Erreur","Il est n\u00e9cessaire de saisir les informations du sujet avant d'exporter son journal d'appel"):Notification.error("Erreur",a.ModuleError);else{b=Notification.info("Info","Chargement du journal d'appel");cSubjectManager.tmp_id.value=a.tmp_id;cSubjectManager.fieldsToStorage();cSubjectManager.storageToFields();for(var e=0;e GATHERING ALL DATA");cSubjectManager.fieldsToStorage();cContactManager.fieldsToStorage();cMiniManager.fieldsToStorage();cFicheManager.fieldsToStorage();if(!cSubjectManager.check())return Notification.warning("Attention","Vous devez saisir les informations du sujet"),!1;b=lsi["export"]("p_mini-fiches");var a=0,d;for(d in b)if(!isNaN(d)&&(a++,!b[d].valid))return Notification.warning("Attention", -"La fiche rapide "+a+" est incompl\u00e8te et/ou incorrecte"),!1;d={path:"input/phone",subject:lsi["export"]("p_subject")[0],contacts:lsi["export"]("p_contacts"),mini:lsi["export"]("p_mini-fiches"),fiches:lsi["export"]("p_fiches"),matrice:lsi["export"]("p_matrice")[0]};api.send(d,function(a){console.log(a);if(0!=a.ModuleError)return Notification.error("ERREUR",a.ModuleError),!1;console.log(a)},!1)},!1)})})})})});var fSubjectManager,fContactManager,fMiniManager,fFicheManager,fMatriceManager; -function fDynamicUpdate(b){var a=b instanceof Element,d=a&&"INPUT"==b.tagName&&"submit"==b.type,e=a&&"SPAN"==b.tagName&&("f_nav-mini"==b.parentNode.id||"f_nav-fiche"==b.parentNode.id),a=a&&"SPAN"==b.tagName&&"f_nav-contact"==b.parentNode.id;if(!d&&!e&&!a&&!0!==b)return!1;console.log("> dynamic update");fMiniManager.fieldsToStorage();fFicheManager.fieldsToStorage();fContactManager.fieldsToStorage();fMatriceManager.fieldsToStorage();fFicheManager.sync();fMiniManager.sync();fMiniManager.storageToFields(); -fFicheManager.storageToFields();fMatriceManager.storageToFields();(d||a)&&fContactManager.storageToFields()} -include("/js/includes/input-facebook-subject.js",function(){include("/js/includes/input-facebook-contact.js",function(){include("/js/includes/input-facebook-mini.js",function(){include("/js/includes/input-facebook-fiche.js",function(){include("/js/includes/input-facebook-matrice.js",function(){fSubjectManager=new inputFacebookSubject($('[data-sublink="facebook"] article.subject-panel [data-name="username"]'),$('[data-sublink="facebook"] article.subject-panel [data-name="submit"]'));fSubjectManager.attach(); -fContactManager=new inputFacebookContact($('[data-sublink="facebook"] article.contact-panel'),$('[data-sublink="facebook"] #f_nav-contact'));fContactManager.attach(fDynamicUpdate);fMiniManager=new inputFacebookMini($('[data-sublink="facebook"] article.mini-relation-panel'),$('[data-sublink="facebook"] #f_nav-mini'));fMiniManager.attach(fDynamicUpdate);fFicheManager=new inputFacebookFiche($('[data-sublink="facebook"] article.relation-panel'),$('[data-sublink="facebook"] #f_nav-fiche'));fFicheManager.attach(fDynamicUpdate); -fMatriceManager=new inputFacebookMatrice($('[data-sublink="facebook"] article.matrice-panel'));fMatriceManager.attach(fDynamicUpdate);$('[data-sublink="facebook"] #f_clear-all').addEventListener("click",function(b){lsi.clear("f_subject");lsi.clear("f_contacts");lsi.clear("f_mini-fiches");lsi.clear("f_fiches");lsi.clear("f_matrice");fSubjectManager.storageToFields();fContactManager.storageToFields();fMiniManager.storageToFields();fFicheManager.storageToFields();fMatriceManager.storageToFields();Notification.success("OK", -"Les donn\u00e9es ont \u00e9t\u00e9 supprim\u00e9es")},!1);$('[data-sublink="facebook"] #f_export-all').addEventListener("click",function(b){Notification.info("INFORMATION","Lancement du t\u00e9l\u00e9chargement de la sauvegarde");b={subject:lsi["export"]("f_subject")[0],contacts:lsi["export"]("f_contacts"),mini:lsi["export"]("f_mini-fiches"),fiches:lsi["export"]("f_fiches"),matrice:lsi["export"]("f_matrice")[0]};var a=$('[data-sublink="facebook"] #f_download-target');a.download="local-facebook-data.json"; -a.href="data:application/octet-stream,"+encodeURIComponent(JSON.stringify(b));a.click()},!1);$('[data-sublink="facebook"] #f_import-all').addEventListener("click",function(b){$('[data-sublink="facebook"] #f_local-upload').click()},!1);$('[data-sublink="facebook"] #f_local-upload').addEventListener("click",function(b){b.target.value=null},!1);$('[data-sublink="facebook"] #f_local-upload').addEventListener("change",function(b){b={path:"upload/local_data",file:$('[data-sublink="facebook"] #f_local-upload').files[0]}; -api.send(b,function(a){console.log(a);if(0!=a.ModuleError)return Notification.error("Erreur",a.ModuleError),!1;lsi.set("f_subject",0,a.local_data.subject);lsi["import"]("f_contacts",a.local_data.contacts);lsi["import"]("f_mini-fiches",a.local_data.mini);lsi["import"]("f_fiches",a.local_data.fiches);lsi.set("f_matrice",0,a.local_data.matrice);fSubjectManager.storageToFields();fContactManager.storageToFields();fMatriceManager.storageToFields();fDynamicUpdate(!0)})},!1);$('[data-sublink="facebook"] #f_submit-all').addEventListener("click", -function(b){console.log("> GATHERING ALL DATA");fSubjectManager.fieldsToStorage();fContactManager.fieldsToStorage();fMiniManager.fieldsToStorage();fFicheManager.fieldsToStorage();if(!fSubjectManager.check())return Notification.warning("Attention","Vous devez saisir les informations du sujet"),!1;b=lsi["export"]("f_mini-fiches");var a=0,d;for(d in b)if(!isNaN(d)&&(a++,!b[d].valid))return Notification.warning("Attention","La fiche rapide "+a+" est incompl\u00e8te et/ou incorrecte"), -!1;d={path:"input/facebook",subject:lsi["export"]("f_subject")[0],contacts:lsi["export"]("f_contacts"),mini:lsi["export"]("f_mini-fiches"),fiches:lsi["export"]("f_fiches"),matrice:lsi["export"]("f_matrice")[0]};api.send(d,function(a){console.log(a);if(0!=a.ModuleError)return Notification.error("ERREUR",a.ModuleError),!1;console.log(a)},!1)},!1)})})})})}); +$('[data-sublink="phone"] #p_nav-fiche'));cFicheManager.attach(cDynamicUpdate);cMatriceManager=new inputPhoneMatrice($('[data-sublink="phone"] article.matrice-panel'));cMatriceManager.attach(cDynamicUpdate);$('[data-sublink="phone"] input#p_call_log-import[type="file"]').addEventListener("click",function(a){a.target.value=null},!1);$('[data-sublink="phone"] input#p_call_log-import[type="file"]').addEventListener("change",function(a){a={path:"upload/call_log",phone_number:$('[data-sublink="phone"] #p_subject_phone_number').value, +file:a.target.files[0]};api.send(a,function(b){console.log(b);var a=null;if(0!=b.ModuleError)9==b.ModuleError?Notification.error("Erreur","Il est n\u00e9cessaire de saisir les informations du sujet avant d'exporter son journal d'appel"):Notification.error("Erreur",b.ModuleError);else{a=Notification.info("Info","Chargement du journal d'appel");cSubjectManager.tmp_id.value=b.tmp_id;cSubjectManager.fieldsToStorage();cSubjectManager.storageToFields();for(var e=0;e GATHERING ALL DATA");cSubjectManager.fieldsToStorage();cContactManager.fieldsToStorage();cMiniManager.fieldsToStorage();cFicheManager.fieldsToStorage();if(!cSubjectManager.check())return Notification.warning("Attention","Vous devez saisir les informations du sujet"),!1;a=lsi["export"]("p_mini-fiches");var b=0,c;for(c in a)if(!isNaN(c)&&(b++,!a[c].valid))return Notification.warning("Attention", +"La fiche rapide "+b+" est incompl\u00e8te et/ou incorrecte"),!1;a=lsi["export"]("p_fiches");for(c in a)if(!a[c].valid)return Notification.warning("Attention","La fiche compl\u00e8te "+(parseInt(c)+1)+" est incompl\u00e8te et/ou incorrecte"),!1;c={path:"input/phone",subject:lsi["export"]("p_subject")[0],contacts:lsi["export"]("p_contacts"),mini:lsi["export"]("p_mini-fiches"),fiches:lsi["export"]("p_fiches"),matrice:lsi["export"]("p_matrice")[0]};api.send(c,function(a){console.log(a); +if(0!=a.ModuleError)return Notification.error("ERREUR",a.ModuleError),!1;console.log(a)},!1)},!1)})})})})});var fSubjectManager,fContactManager,fMiniManager,fFicheManager,fMatriceManager; +function fDynamicUpdate(a){var b=a instanceof Element,c=b&&"INPUT"==a.tagName&&"submit"==a.type,e=b&&"SPAN"==a.tagName&&("f_nav-mini"==a.parentNode.id||"f_nav-fiche"==a.parentNode.id),b=b&&"SPAN"==a.tagName&&"f_nav-contact"==a.parentNode.id;if(!c&&!e&&!b&&!0!==a)return!1;console.log("> dynamic update");fMiniManager.fieldsToStorage();fFicheManager.fieldsToStorage();fContactManager.fieldsToStorage();fMatriceManager.fieldsToStorage();fFicheManager.sync();fMiniManager.sync();fMiniManager.storageToFields(); +fFicheManager.storageToFields();fMatriceManager.storageToFields();(c||b)&&fContactManager.storageToFields()} +include("/js/includes/input-facebook-subject-min.js",function(){include("/js/includes/input-facebook-contact-min.js",function(){include("/js/includes/input-facebook-mini-min.js",function(){include("/js/includes/input-facebook-fiche-min.js",function(){include("/js/includes/input-facebook-matrice-min.js",function(){fSubjectManager=new inputFacebookSubject($('[data-sublink="facebook"] article.subject-panel [data-name="username"]'),$('[data-sublink="facebook"] article.subject-panel [data-name="submit"]')); +fSubjectManager.attach();fContactManager=new inputFacebookContact($('[data-sublink="facebook"] article.contact-panel'),$('[data-sublink="facebook"] #f_nav-contact'));fContactManager.attach(fDynamicUpdate);fMiniManager=new inputFacebookMini($('[data-sublink="facebook"] article.mini-relation-panel'),$('[data-sublink="facebook"] #f_nav-mini'));fMiniManager.attach(fDynamicUpdate);fFicheManager=new inputFacebookFiche($('[data-sublink="facebook"] article.relation-panel'),$('[data-sublink="facebook"] #f_nav-fiche')); +fFicheManager.attach(fDynamicUpdate);fMatriceManager=new inputFacebookMatrice($('[data-sublink="facebook"] article.matrice-panel'));fMatriceManager.attach(fDynamicUpdate);$('[data-sublink="facebook"] #f_clear-all').addEventListener("click",function(a){lsi.clear("f_subject");lsi.clear("f_contacts");lsi.clear("f_mini-fiches");lsi.clear("f_fiches");lsi.clear("f_matrice");fSubjectManager.storageToFields();fContactManager.storageToFields();fMiniManager.storageToFields();fFicheManager.storageToFields(); +fMatriceManager.storageToFields();Notification.success("OK","Les donn\u00e9es ont \u00e9t\u00e9 supprim\u00e9es")},!1);$('[data-sublink="facebook"] #f_export-all').addEventListener("click",function(a){Notification.info("INFORMATION","Lancement du t\u00e9l\u00e9chargement de la sauvegarde");a={subject:lsi["export"]("f_subject")[0],contacts:lsi["export"]("f_contacts"),mini:lsi["export"]("f_mini-fiches"),fiches:lsi["export"]("f_fiches"),matrice:lsi["export"]("f_matrice")[0]};var b=$('[data-sublink="facebook"] #f_download-target'); +b.download="local-facebook-data.json";b.href="data:application/octet-stream,"+encodeURIComponent(JSON.stringify(a));b.click()},!1);$('[data-sublink="facebook"] #f_import-all').addEventListener("click",function(a){$('[data-sublink="facebook"] #f_local-upload').click()},!1);$('[data-sublink="facebook"] #f_local-upload').addEventListener("click",function(a){a.target.value=null},!1);$('[data-sublink="facebook"] #f_local-upload').addEventListener("change",function(a){a={path:"upload/local_data",file:$('[data-sublink="facebook"] #f_local-upload').files[0]}; +api.send(a,function(a){console.log(a);if(0!=a.ModuleError)return Notification.error("Erreur",a.ModuleError),!1;lsi.set("f_subject",0,a.local_data.subject);lsi["import"]("f_contacts",a.local_data.contacts);lsi["import"]("f_mini-fiches",a.local_data.mini);lsi["import"]("f_fiches",a.local_data.fiches);lsi.set("f_matrice",0,a.local_data.matrice);fSubjectManager.storageToFields();fContactManager.storageToFields();fMatriceManager.storageToFields();fDynamicUpdate(!0)})},!1);$('[data-sublink="facebook"] #f_submit-all').addEventListener("click", +function(a){console.log("> GATHERING ALL DATA");fSubjectManager.fieldsToStorage();fContactManager.fieldsToStorage();fMiniManager.fieldsToStorage();fFicheManager.fieldsToStorage();if(!fSubjectManager.check())return Notification.warning("Attention","Vous devez saisir les informations du sujet"),!1;a=lsi["export"]("f_mini-fiches");var b=0,c;for(c in a)if(!isNaN(c)&&(b++,!a[c].valid))return Notification.warning("Attention","La fiche rapide "+b+" est incompl\u00e8te et/ou incorrecte"), +!1;a=lsi["export"]("f_fiches");for(c in a)if(!a[c].valid)return Notification.warning("Attention","La fiche compl\u00e8te "+(parseInt(c)+1)+" est incompl\u00e8te et/ou incorrecte"),!1;c={path:"input/facebook",subject:lsi["export"]("f_subject")[0],contacts:lsi["export"]("f_contacts"),mini:lsi["export"]("f_mini-fiches"),fiches:lsi["export"]("f_fiches"),matrice:lsi["export"]("f_matrice")[0]};api.send(c,function(a){console.log(a);if(0!=a.ModuleError)return Notification.error("ERREUR",a.ModuleError), +!1;console.log(a)},!1)},!1)})})})})}); diff --git a/view/js/input.js b/view/js/input.js index 149b0dd..a0eea57 100644 --- a/view/js/input.js +++ b/view/js/input.js @@ -74,12 +74,11 @@ function cDynamicUpdate(target){ /* [2] Inclusion des scripts externes et lancement =========================================================*/ -// TODO: Mettre les versions minifiées -include('/js/includes/input-phone-subject.js', function(){ -include('/js/includes/input-phone-contact.js', function(){ -include('/js/includes/input-phone-mini.js', function(){ -include('/js/includes/input-phone-fiche.js', function(){ -include('/js/includes/input-phone-matrice.js', function(){ +include('/js/includes/input-phone-subject-min.js', function(){ +include('/js/includes/input-phone-contact-min.js', function(){ +include('/js/includes/input-phone-mini-min.js', function(){ +include('/js/includes/input-phone-fiche-min.js', function(){ +include('/js/includes/input-phone-matrice-min.js', function(){ /* (1) Gestion du formulaire du sujet @@ -423,13 +422,12 @@ include('/js/includes/input-phone-matrice.js', function(){ }} // {3} Vérification des fiches // - //TODO: Remettre la vérification des fiches - // var fiches = lsi.export('p_fiches'); - // for( var id in fiches ) - // if( !fiches[id].valid ){ - // Notification.warning('Attention', 'La fiche complète '+(parseInt(id)+1)+' est incomplète et/ou incorrecte'); - // return false; - // } + var fiches = lsi.export('p_fiches'); + for( var id in fiches ) + if( !fiches[id].valid ){ + Notification.warning('Attention', 'La fiche complète '+(parseInt(id)+1)+' est incomplète et/ou incorrecte'); + return false; + } @@ -621,12 +619,11 @@ function fDynamicUpdate(target){ /* [2] Inclusion des scripts externes et lancement =========================================================*/ -// TODO: Mettre les versions minifiées -include('/js/includes/input-facebook-subject.js', function(){ -include('/js/includes/input-facebook-contact.js', function(){ -include('/js/includes/input-facebook-mini.js', function(){ -include('/js/includes/input-facebook-fiche.js', function(){ -include('/js/includes/input-facebook-matrice.js', function(){ +include('/js/includes/input-facebook-subject-min.js', function(){ +include('/js/includes/input-facebook-contact-min.js', function(){ +include('/js/includes/input-facebook-mini-min.js', function(){ +include('/js/includes/input-facebook-fiche-min.js', function(){ +include('/js/includes/input-facebook-matrice-min.js', function(){ /* (1) Gestion du formulaire du sujet @@ -827,13 +824,12 @@ include('/js/includes/input-facebook-matrice.js', function(){ }} // {3} Vérification des fiches // - //TODO: Remettre la vérification des fiches - // var fiches = lsi.export('f_fiches'); - // for( var id in fiches ) - // if( !fiches[id].valid ){ - // Notification.warning('Attention', 'La fiche complète '+(parseInt(id)+1)+' est incomplète et/ou incorrecte'); - // return false; - // } + var fiches = lsi.export('f_fiches'); + for( var id in fiches ) + if( !fiches[id].valid ){ + Notification.warning('Attention', 'La fiche complète '+(parseInt(id)+1)+' est incomplète et/ou incorrecte'); + return false; + }