From 86e615628274841d7f1be206d6cec678948ce151 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 May 2016 14:49:20 +0200 Subject: [PATCH 01/12] =?UTF-8?q?#14;=20Prototype=20du=20graphique=20pour?= =?UTF-8?q?=20le=20'type=20de=20communication'=20(entrant,=20sortant,=20ma?= =?UTF-8?q?nqu=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; + } From d5d6887a58a503bbb19234d16ea30c0f3fce752e Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 May 2016 14:54:57 +0200 Subject: [PATCH 02/12] #14; Prototype du graphique pour le 'type de communication' (appel, sms) --- config/modules.json | 8 ++++ manager/module/chart.php | 64 ++++++++++++++++++++++++- test-charts/communication-direction.php | 57 ++++++++++++++++++++++ test-charts/communication-type.php | 6 +-- 4 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 test-charts/communication-direction.php diff --git a/config/modules.json b/config/modules.json index 9af840c..0c9e22d 100755 --- a/config/modules.json +++ b/config/modules.json @@ -106,6 +106,14 @@ "chart": { + "communication_direction": { + "description": "Renvoie les données pour un graphique sur les sens de communications", + "permissions": ["admin"], + "parameters": { + "subject": { "description": "Identifiant du sujet à étudier,", "type": "id" } + } + }, + "communication_type": { "description": "Renvoie les données pour un graphique sur les types de communications", "permissions": ["admin"], diff --git a/manager/module/chart.php b/manager/module/chart.php index 0eade16..580df15 100755 --- a/manager/module/chart.php +++ b/manager/module/chart.php @@ -11,10 +11,10 @@ class chart{ - /* RETOURNE UN JEU DE DONNEES POUR LE TYPE DE COMMUNICATION (MANQUE/ENTRANT/SORTANT) + /* RETOURNE UN JEU DE DONNEES POUR LE SENS DE COMMUNICATION (MANQUE/ENTRANT/SORTANT) * */ - public static function communication_type($params){ + public static function communication_direction($params){ extract($params); $subject = intval($subject); @@ -73,6 +73,66 @@ ); } + + + + + /* RETOURNE UN JEU DE DONNEES POUR LE TYPE DE COMMUNICATION (APPEL/SMS) + * + */ + public static function communication_type($params){ + extract($params); + + $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 */ + $PHONE = 0; + $SMS = 0; + + /* (2) On incrémente les compteurs */ + foreach($data['logs'] as $log){ + + /* (3) On incrémente les compteurs */ + $PHONE += ($log['type']==0) ? 1 : 0; + $SMS += ($log['type']==1) ? 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('APPELS', 'SMS'), + 'data' => array($PHONE, $SMS) + ); + } + } diff --git a/test-charts/communication-direction.php b/test-charts/communication-direction.php new file mode 100644 index 0000000..e38b2c3 --- /dev/null +++ b/test-charts/communication-direction.php @@ -0,0 +1,57 @@ + + + + + Chart sens de communication (MANQUE/ENTRANT/SORTANT) + + + + + + + + + + + diff --git a/test-charts/communication-type.php b/test-charts/communication-type.php index 589ca6c..89abc92 100644 --- a/test-charts/communication-type.php +++ b/test-charts/communication-type.php @@ -2,7 +2,7 @@ - Chart type de communication (MANQUE/ENTRANT/SORTANT) + Chart type de communication (APPEL/SMS) @@ -37,8 +37,8 @@ labels: response.labels, datasets: [{ data: response.data, - backgroundColor: [ "#FF6384", "#36A2EB", "#FFCE56" ], - hoverBackgroundColor: [ "#E65977", "#3090D1", "#E6B94D" ] + backgroundColor: [ "#FF6384", "#36A2EB" ], + hoverBackgroundColor: [ "#E65977", "#3090D1" ] }] } From 1975587df38180af3b11ac0f7499624e8b089268 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 May 2016 15:20:49 +0200 Subject: [PATCH 03/12] #14; Prototype du graphique du sexe (homme/femme) pour chaque communication --- automate.php | 2 +- config/modules.json | 8 + doc/phone_log.json | 149 ++++++++++--------- manager/module/chart.php | 72 ++++++++- src/dynamic/phone/dictionary.json | 236 +++++++++++++++--------------- test-charts/all.php | 137 +++++++++++++++++ test-charts/sexe.php | 57 ++++++++ 7 files changed, 464 insertions(+), 197 deletions(-) create mode 100644 test-charts/all.php create mode 100644 test-charts/sexe.php diff --git a/automate.php b/automate.php index 16b6e97..d506505 100755 --- a/automate.php +++ b/automate.php @@ -94,7 +94,7 @@ $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 )); + $req = new ModuleRequest('chart/sexe', array( 'subject' => 273 )); $res = $req->dispatch(); diff --git a/config/modules.json b/config/modules.json index 0c9e22d..edbc85a 100755 --- a/config/modules.json +++ b/config/modules.json @@ -120,6 +120,14 @@ "parameters": { "subject": { "description": "Identifiant du sujet à étudier,", "type": "id" } } + }, + + "sexe": { + "description": "Renvoie les données pour un graphique sur le sexe des contacts", + "permissions": ["admin"], + "parameters": { + "subject": { "description": "Identifiant du sujet à étudier,", "type": "id" } + } } }, diff --git a/doc/phone_log.json b/doc/phone_log.json index 990c742..86313a3 100644 --- a/doc/phone_log.json +++ b/doc/phone_log.json @@ -1,83 +1,82 @@ { - "logs": [ - { "id":"1", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"2", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"3", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"4", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"5", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"6", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"7", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"8", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"9", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"10", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"11", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, - { "id":"12", "date":"2014-02-28T15:00:10", "duration":"125", "direction":"1", "type":"1" }, - { "id":"13", "date":"2014-02-15T23:06:46", "duration":"0", "direction":"0", "type":"1" }, - { "id":"14", "date":"2014-02-15T23:06:18", "duration":"0", "direction":"0", "type":"1" }, - { "id":"15", "date":"2014-02-15T21:08:34", "duration":"349", "direction":"1", "type":"1" }, - { "id":"16", "date":"2014-02-15T20:51:38", "duration":"0", "direction":"1", "type":"1" }, - { "id":"17", "date":"2014-02-15T19:06:33", "duration":"657", "direction":"1", "type":"1" }, - { "id":"18", "date":"2014-02-15T19:06:33", "duration":"657", "direction":"2", "type":"1" }, - { "id":"19", "date":"2014-02-15T19:06:33", "duration":"657", "direction":"1", "type":"1" }, - { "id":"20", "date":"2014-02-15T19:06:33", "duration":"657", "direction":"1", "type":"1" }, - { "id":"21", "date":"2014-02-15T17:24:18", "duration":"276", "direction":"1", "type":"1" }, - { "id":"22", "date":"2014-02-15T16:34:29", "duration":"36", "direction":"1", "type":"1" } - ], + "logs": [ + { "id":"1", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"2", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"3", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"4", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"5", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"6", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"7", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"8", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"9", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"10", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"11", "date":"2014-05-28T14:43:25", "duration":"0", "direction":"0", "type":"0" }, + { "id":"12", "date":"2014-02-28T15:00:10", "duration":"125", "direction":"1", "type":"1" }, + { "id":"13", "date":"2014-02-15T23:06:46", "duration":"0", "direction":"0", "type":"1" }, + { "id":"14", "date":"2014-02-15T23:06:18", "duration":"0", "direction":"0", "type":"1" }, + { "id":"15", "date":"2014-02-15T21:08:34", "duration":"349", "direction":"1", "type":"1" }, + { "id":"16", "date":"2014-02-15T20:51:38", "duration":"0", "direction":"1", "type":"1" }, + { "id":"17", "date":"2014-02-15T19:06:33", "duration":"657", "direction":"1", "type":"1" }, + { "id":"18", "date":"2014-02-15T19:06:33", "duration":"657", "direction":"2", "type":"1" }, + { "id":"19", "date":"2014-02-15T19:06:33", "duration":"657", "direction":"1", "type":"1" }, + { "id":"20", "date":"2014-02-15T19:06:33", "duration":"657", "direction":"1", "type":"1" }, + { "id":"21", "date":"2014-02-15T17:24:18", "duration":"276", "direction":"1", "type":"1" }, + { "id":"22", "date":"2014-02-15T16:34:29", "duration":"36", "direction":"1", "type":"1" } + ], - "contacts": [ - { "id":"0", "number":"0102030405", "name":"", "sexe":"0", "age":"1", "studies1":"1", "reltype":"1", "dist":"0" }, - { "id":"1", "number":"0502030405", "name":"", "sexe":"0", "age":"1", "studies1":"1", "reltype":"1", "dist":"0" }, - { "id":"2", "number":"0502030561", "name":"", "sexe":"0", "age":"2", "studies1":"2", "reltype":"2", "dist":"1" }, - { "id":"3", "number":"0502030717", "name":"", "sexe":"0", "age":"3", "studies1":"3", "reltype":"3", "dist":"2" }, - { "id":"4", "number":"0502030873", "name":"", "sexe":"0", "age":"4", "studies1":"4", "reltype":"4", "dist":"3" }, - { "id":"5", "number":"0502031029", "name":"", "sexe":"0", "age":"5", "studies1":"5", "reltype":"5", "dist":"4" }, - { "id":"6", "number":"0502031185", "name":"", "sexe":"0", "age":"6", "studies1":"6", "reltype":"6", "dist":"5" }, - { "id":"7", "number":"0502031341", "name":"", "sexe":"0", "age":"7", "studies1":"7", "reltype":"7", "dist":"6" }, - { "id":"8", "number":"0502031497", "name":"", "sexe":"0", "age":"8", "studies1":"8", "reltype":"8", "dist":"7" }, - { "id":"9", "number":"0502031653", "name":"", "sexe":"0", "age":"9", "studies1":"9", "reltype":"9", "dist":"8" }, - { "id":"10", "number":"0502031809", "name":"", "sexe":"0", "age":"10", "studies1":"10", "reltype":"autretext", "dist":"9" }, - { "id":"11", "number":"0502031965", "name":"", "sexe":"0", "age":"11", "studies1":"11", "reltype":"11", "dist":"10" }, - { "id":"12", "number":"0502032121", "name":"", "sexe":"1", "age":"12", "studies1":"12", "reltype":"12", "dist":"11" }, - { "id":"13", "number":"0502032277", "name":"", "sexe":"1", "age":"13", "studies1":"13", "reltype":"13", "dist":"12" }, - { "id":"14", "number":"0502032433", "name":"", "sexe":"1", "age":"14", "studies1":"14", "reltype":"14", "dist":"13" }, - { "id":"15", "number":"0502032589", "name":"", "sexe":"1", "age":"15", "studies1":"15", "reltype":"15", "dist":"14" }, - { "id":"16", "number":"0502032745", "name":"", "sexe":"1", "age":"16", "studies1":"16", "reltype":"16", "dist":"15" }, - { "id":"17", "number":"0502032901", "name":"", "sexe":"1", "age":"17", "studies1":"17", "reltype":"17", "dist":"16" }, - { "id":"18", "number":"0502033057", "name":"", "sexe":"1", "age":"18", "studies1":"18", "reltype":"18", "dist":"17" }, - { "id":"19", "number":"0502033213", "name":"", "sexe":"1", "age":"19", "studies1":"19", "reltype":"19", "dist":"18" }, - { "id":"20", "number":"0502033369", "name":"", "sexe":"1", "age":"20", "studies1":"20", "reltype":"20", "dist":"19" }, - { "id":"21", "number":"0502033525", "name":"", "sexe":"1", "age":"21", "studies1":"21", "reltype":"21", "dist":"20" }, - { "id":"22", "number":"0502033681", "name":"", "sexe":"1", "age":"22", "studies1":"22", "reltype":"22", "dist":"21" }, + "contacts": [ + { "id":"1", "number":"0502030405", "name":"", "sexe":"0", "age":"1", "studies1":"1", "reltype":"1", "dist":"0" }, + { "id":"2", "number":"0502030561", "name":"", "sexe":"0", "age":"2", "studies1":"2", "reltype":"2", "dist":"1" }, + { "id":"3", "number":"0502030717", "name":"", "sexe":"0", "age":"3", "studies1":"3", "reltype":"3", "dist":"2" }, + { "id":"4", "number":"0502030873", "name":"", "sexe":"0", "age":"4", "studies1":"4", "reltype":"4", "dist":"3" }, + { "id":"5", "number":"0502031029", "name":"", "sexe":"0", "age":"5", "studies1":"5", "reltype":"5", "dist":"4" }, + { "id":"6", "number":"0502031185", "name":"", "sexe":"0", "age":"6", "studies1":"6", "reltype":"6", "dist":"5" }, + { "id":"7", "number":"0502031341", "name":"", "sexe":"0", "age":"7", "studies1":"7", "reltype":"7", "dist":"6" }, + { "id":"8", "number":"0502031497", "name":"", "sexe":"0", "age":"8", "studies1":"8", "reltype":"8", "dist":"7" }, + { "id":"9", "number":"0502031653", "name":"", "sexe":"0", "age":"9", "studies1":"9", "reltype":"9", "dist":"8" }, + { "id":"10", "number":"0502031809", "name":"", "sexe":"0", "age":"10", "studies1":"10", "reltype":"autretext", "dist":"9" }, + { "id":"11", "number":"0502031965", "name":"", "sexe":"0", "age":"11", "studies1":"11", "reltype":"11", "dist":"10" }, + { "id":"12", "number":"0502032121", "name":"", "sexe":"1", "age":"12", "studies1":"12", "reltype":"12", "dist":"11" }, + { "id":"13", "number":"0502032277", "name":"", "sexe":"1", "age":"13", "studies1":"13", "reltype":"13", "dist":"12" }, + { "id":"14", "number":"0502032433", "name":"", "sexe":"1", "age":"14", "studies1":"14", "reltype":"14", "dist":"13" }, + { "id":"15", "number":"0502032589", "name":"", "sexe":"1", "age":"15", "studies1":"15", "reltype":"15", "dist":"14" }, + { "id":"16", "number":"0502032745", "name":"", "sexe":"1", "age":"16", "studies1":"16", "reltype":"16", "dist":"15" }, + { "id":"17", "number":"0502032901", "name":"", "sexe":"1", "age":"17", "studies1":"17", "reltype":"17", "dist":"16" }, + { "id":"18", "number":"0502033057", "name":"", "sexe":"1", "age":"18", "studies1":"18", "reltype":"18", "dist":"17" }, + { "id":"19", "number":"0502033213", "name":"", "sexe":"1", "age":"19", "studies1":"19", "reltype":"19", "dist":"18" }, + { "id":"20", "number":"0502033369", "name":"", "sexe":"1", "age":"20", "studies1":"20", "reltype":"20", "dist":"19" }, + { "id":"21", "number":"0502033525", "name":"", "sexe":"1", "age":"21", "studies1":"21", "reltype":"21", "dist":"20" }, + { "id":"22", "number":"0502033681", "name":"", "sexe":"1", "age":"22", "studies1":"22", "reltype":"22", "dist":"21" }, - { "id":"23", "number":"0606060606", "name":"", "sexe":"0", "age":".", "studies2":".", "reltype":"0", "dist":"0", - "job":"82", - "famsit":"1", - "city":"Toulouse", - "cp":"31000", - "duration":["2","5"], - "context":"11", - "contextExtra": ["facebook", "", ""], - "freq":["4","9","14","19","24"], - "connect":["1","3","5","7","9","11"], - "connectExtra":["",""]}, + { "id":"23", "number":"0606060606", "name":"", "sexe":"0", "age":".", "studies2":".", "reltype":"0", "dist":"0", + "job":"82", + "famsit":"1", + "city":"Toulouse", + "cp":"31000", + "duration":["2","5"], + "context":"11", + "contextExtra": ["facebook", "", ""], + "freq":["4","9","14","19","24"], + "connect":["1","3","5","7","9","11"], + "connectExtra":["",""]}, - { "id":"23", "number":"0606060606", "name":"", "sexe":"0", "age":".", "studies2":".", "reltype":"autretext", "dist":"0", - "job":"82", - "famsit":"1", - "city":"Toulouse", - "cp":"31000", - "duration":["2","5"], - "context":"10", - "contextExtra": ["", "", ""], - "freq":["4","9","14","19","24"], - "connect":["1","3","5","7","9","11"], - "connectExtra":["",""]} - ], + { "id":"23", "number":"0606060606", "name":"", "sexe":"0", "age":".", "studies2":".", "reltype":"autretext", "dist":"0", + "job":"82", + "famsit":"1", + "city":"Toulouse", + "cp":"31000", + "duration":["2","5"], + "context":"10", + "contextExtra": ["", "", ""], + "freq":["4","9","14","19","24"], + "connect":["1","3","5","7","9","11"], + "connectExtra":["",""]} + ], - "relations": { - "0": ["1", "4", "6", "8"], - "5": ["cd 4", "5", "6"] - } + "relations": { + "0": ["1", "4", "6", "8"], + "5": ["cd 4", "5", "6"] + } diff --git a/manager/module/chart.php b/manager/module/chart.php index 580df15..8e18421 100755 --- a/manager/module/chart.php +++ b/manager/module/chart.php @@ -119,9 +119,8 @@ }else{ /* (1) On initialise les compteurs */ - $MISSED = 0; - $OUTGOING = 0; - $INCOMING = 0; + $H = 0; + $F = 0; } @@ -133,6 +132,73 @@ ); } + + + + /* RETOURNE UN JEU DE DONNEES POUR LE SEXE DES CONTACTS + * + */ + public static function sexe($params){ + extract($params); + + $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 */ + $H = 0; + $F = 0; + + /* (2) On incrémente les compteurs */ + foreach($data['logs'] as $log){ + + /* (3) On récupère le contact associé */ + $associatedContact = null; + foreach($data['contacts'] as $contact) + if( $log['id'] == $contact['id'] ) + $associatedContact = $contact; + + // Si on ne trouve pas, on passe au suivant + if( is_null($associatedContact) ) + continue; + + /* (4) On incrémente les compteurs */ + $H += ($associatedContact['sexe']==0) ? 1 : 0; + $F += ($associatedContact['sexe']==1) ? 1 : 0; + + } + + /* [3] Si aucun journal d'appel + =========================================================*/ + }else{ + + /* (1) On initialise les compteurs */ + $H = 0; + $F = 0; + + }/* (1) On initialise les compteurs */ + + + return array( + 'ModuleError' => ManagerError::Success, + 'labels' => array('HOMME', 'FEMME'), + 'data' => array($H, $F) + ); + } } diff --git a/src/dynamic/phone/dictionary.json b/src/dynamic/phone/dictionary.json index 9e97d40..812fa62 100755 --- a/src/dynamic/phone/dictionary.json +++ b/src/dynamic/phone/dictionary.json @@ -1,147 +1,147 @@ { - "logs": { - "direction": { "0": "INCOMING", "1": "OUTGOING", "2": "MISSED" }, - "type": { "0": "PHONE", "1": "SMS" } + "logs": { + "direction": { "0": "INCOMING", "1": "OUTGOING", "2": "MISSED" }, + "type": { "0": "PHONE", "1": "SMS" } - }, + }, - "contacts": { - "sexe": { "0":"Homme", "1":"Femme" }, - "age": { - ".": "NA", - "0": "5 à 10", "1": "10 à 15", "2": "15 à 20", "3": "20 à 25", "4": "25 à 30", - "5": "30 à 35", "6": "35 à 40", "7": "40 à 45", "8": "45 à 50", "9": "50 à 55", - "10": "55 à 60", "11": "60 à 65", "12": "65 à 70", "13": "70 à 75", "14": "75 à 80", - "15": "80 à 85", "16": "85 à 90", "17": "90 à 95", "18": "95 à 100" - }, + "contacts": { + "sexe": { "0":"Homme", "1":"Femme" }, + "age": { + ".": "NA", + "0": "5 à 10", "1": "10 à 15", "2": "15 à 20", "3": "20 à 25", "4": "25 à 30", + "5": "30 à 35", "6": "35 à 40", "7": "40 à 45", "8": "45 à 50", "9": "50 à 55", + "10": "55 à 60", "11": "60 à 65", "12": "65 à 70", "13": "70 à 75", "14": "75 à 80", + "15": "80 à 85", "16": "85 à 90", "17": "90 à 95", "18": "95 à 100" + }, - "studies1": { - ".": "NA", - "0": "Inconnu", - "1": "< BAC", - "2": "BAC", - "3": "BAC+2", - "4": "BAC+3", - "5": "BAC+4 et plus" - }, + "studies1": { + ".": "NA", + "0": "Inconnu", + "1": "< BAC", + "2": "BAC", + "3": "BAC+2", + "4": "BAC+3", + "5": "BAC+4 et plus" + }, - "studies2": { - ".": "NA", - "0": "Inconnue", - "01": "Pas de scolarité", - "02": "Aucun diplôme mais scolarité jusqu'en école primaire ou au collège", - "03": "Aucun diplôme mais scolarité au-delà du collège", - "04": "Certificat d'études primaires (CEP)", - "05": "BEPC, brevet élémentaire, brevet des collèges", - "06": "Certificat d'aptitudes professionnelles (CAP), brevet de compagnon, y compris CAPA", - "07": "Brevet d'études professionnelles (BEP), y compris BEPA et diplômes agricoles (BAA, BPA) Diplômes sociaux (aide-soignante, auxiliaire de puériculture, travailleuse familiale)", - "08": "Baccalauréat général, brevet supérieur BAC ( L, ES, S ou A, B , C, D, D’, E )", - "09": "Bac technologique ou professionnel, brevet professionnel ou de technicien BAC (STI, STL, SMS, STT ou F, G, H) BEC, BEI, BEH, capacité en droit Brevet de technicien agricole (BTA)", - "10": "Diplôme universitaire de 1er cycle BTS, DUT, DEUG Diplôme des professions sociales ou de santé, d'infirmier(ère) Diplôme universitaire propédeutique, DUEL, DUES, DEUG, PCEM", - "11": "Diplôme universitaire de 2ème ou 3ème cycle MASTER, DOCTORAT (y compris médecine, pharmacie, dentaire), Maîtrise, DEA, DESS, CAPES, CAPET, agrégation Diplôme d'ingénieur, d'une grande école, etc." - }, + "studies2": { + ".": "NA", + "0": "Inconnue", + "01": "Pas de scolarité", + "02": "Aucun diplôme mais scolarité jusqu'en école primaire ou au collège", + "03": "Aucun diplôme mais scolarité au-delà du collège", + "04": "Certificat d'études primaires (CEP)", + "05": "BEPC, brevet élémentaire, brevet des collèges", + "06": "Certificat d'aptitudes professionnelles (CAP), brevet de compagnon, y compris CAPA", + "07": "Brevet d'études professionnelles (BEP), y compris BEPA et diplômes agricoles (BAA, BPA) Diplômes sociaux (aide-soignante, auxiliaire de puériculture, travailleuse familiale)", + "08": "Baccalauréat général, brevet supérieur BAC ( L, ES, S ou A, B , C, D, D’, E )", + "09": "Bac technologique ou professionnel, brevet professionnel ou de technicien BAC (STI, STL, SMS, STT ou F, G, H) BEC, BEI, BEH, capacité en droit Brevet de technicien agricole (BTA)", + "10": "Diplôme universitaire de 1er cycle BTS, DUT, DEUG Diplôme des professions sociales ou de santé, d'infirmier(ère) Diplôme universitaire propédeutique, DUEL, DUES, DEUG, PCEM", + "11": "Diplôme universitaire de 2ème ou 3ème cycle MASTER, DOCTORAT (y compris médecine, pharmacie, dentaire), Maîtrise, DEA, DESS, CAPES, CAPET, agrégation Diplôme d'ingénieur, d'une grande école, etc." + }, - "job": { - ".": "NA", - "10": "Agriculteurs exploitants (Agriculteurs, maraîchers, viticulteurs…)", - "21": "Artisans (Maçons, électriciens, couturiers, boulangers, garagistes, coiffeurs…)", - "22": "Commerçants et assimilés (Commerçants en détail ou en gros, buralistes, cafetiers, agents immobiliers…)", - "23": "Chefs d'entreprise de 10 salariés ou plus", - "31": "Professions libérales et assimilés (Médecins, avocats, notaires, architectes libéraux…)", - "32": "Cadres de la fonction publique, professions intellectuelles et artistiques (Personnels administratifs de catégorie A, professeurs de l’enseignement secondaire ou supérieur, médecins hospitaliers, artistes…)", - "36": "Cadres d'entreprise (Cadres administratifs, cadres commerciaux, ingénieurs…)", - "41": "Professions intermédiaires de l'enseignement, de la santé, de la fonction publique et assimilés (Personnels administratifs de catégorie B, instituteurs, infirmiers, travailleurs sociaux…)", - "46": "Professions intermédiaires administratives et commerciales des entreprises (Comptables, chargés de clientèles…)", - "47": "Techniciens (Techniciens d’études, techniciens de contrôle, dessinateurs en bâtiment, géomètres…)", - "48": "Contremaîtres, agents de maîtrise (Conducteurs de travaux, chefs d’équipe…)", - "51": "Employés de la fonction publique (Personnels de catégorie C, agents administratifs, agents de service…)", - "54": "Employés administratifs d'entreprise (Secrétaires, agents d’accueil…)", - "55": "Employés de commerce (Vendeurs, caissiers…)", - "56": "Personnels des services directs aux particuliers (Serveurs, coiffeurs, assistantes maternelles…)", - "61": "Ouvriers qualifiés de type industriel ou artisanal (Mécaniciens, chauffeurs, cuisiniers…)", - "66": "Ouvriers non qualifiés de type industriel ou artisanal (Ouvrier de production, manœuvres…)", - "69": "Ouvriers agricoles (Bergers, ouvriers de la viticulture, bûcherons, ouvrier forestiers…)", - "71": "Anciens agriculteurs exploitants", - "72": "Anciens artisans, commerçants, chefs d'entreprise", - "73": "Anciens cadres", - "74": "Anciennes professions intermédiaires", - "75": "Anciens employés", - "76": "Anciens ouvriers", - "81": "Chômeurs n'ayant jamais travaillé", - "82": "Inactifs divers (autres que retraités)" - }, + "job": { + ".": "NA", + "10": "Agriculteurs exploitants (Agriculteurs, maraîchers, viticulteurs…)", + "21": "Artisans (Maçons, électriciens, couturiers, boulangers, garagistes, coiffeurs…)", + "22": "Commerçants et assimilés (Commerçants en détail ou en gros, buralistes, cafetiers, agents immobiliers…)", + "23": "Chefs d'entreprise de 10 salariés ou plus", + "31": "Professions libérales et assimilés (Médecins, avocats, notaires, architectes libéraux…)", + "32": "Cadres de la fonction publique, professions intellectuelles et artistiques (Personnels administratifs de catégorie A, professeurs de l’enseignement secondaire ou supérieur, médecins hospitaliers, artistes…)", + "36": "Cadres d'entreprise (Cadres administratifs, cadres commerciaux, ingénieurs…)", + "41": "Professions intermédiaires de l'enseignement, de la santé, de la fonction publique et assimilés (Personnels administratifs de catégorie B, instituteurs, infirmiers, travailleurs sociaux…)", + "46": "Professions intermédiaires administratives et commerciales des entreprises (Comptables, chargés de clientèles…)", + "47": "Techniciens (Techniciens d’études, techniciens de contrôle, dessinateurs en bâtiment, géomètres…)", + "48": "Contremaîtres, agents de maîtrise (Conducteurs de travaux, chefs d’équipe…)", + "51": "Employés de la fonction publique (Personnels de catégorie C, agents administratifs, agents de service…)", + "54": "Employés administratifs d'entreprise (Secrétaires, agents d’accueil…)", + "55": "Employés de commerce (Vendeurs, caissiers…)", + "56": "Personnels des services directs aux particuliers (Serveurs, coiffeurs, assistantes maternelles…)", + "61": "Ouvriers qualifiés de type industriel ou artisanal (Mécaniciens, chauffeurs, cuisiniers…)", + "66": "Ouvriers non qualifiés de type industriel ou artisanal (Ouvrier de production, manœuvres…)", + "69": "Ouvriers agricoles (Bergers, ouvriers de la viticulture, bûcherons, ouvrier forestiers…)", + "71": "Anciens agriculteurs exploitants", + "72": "Anciens artisans, commerçants, chefs d'entreprise", + "73": "Anciens cadres", + "74": "Anciennes professions intermédiaires", + "75": "Anciens employés", + "76": "Anciens ouvriers", + "81": "Chômeurs n'ayant jamais travaillé", + "82": "Inactifs divers (autres que retraités)" + }, - "context": { - "0": "De la même famille", - "1": "Grandi ensemble", - "2": "Par mon mari/ma femme/relation amoureuse", - "3": "Par mes parents", - "4": "Par mes enfants", - "5": "Par un ami", - "6": "Comme voisin", - "7": "Par d’autres membres de la famille", - "8": "Etudes", - "9": "Etudes supérieures", - "10": "Au travail", - "11": "Internet", - "12": "Association", - "13": "Autre" - }, + "context": { + "0": "De la même famille", + "1": "Grandi ensemble", + "2": "Par mon mari/ma femme/relation amoureuse", + "3": "Par mes parents", + "4": "Par mes enfants", + "5": "Par un ami", + "6": "Comme voisin", + "7": "Par d’autres membres de la famille", + "8": "Etudes", + "9": "Etudes supérieures", + "10": "Au travail", + "11": "Internet", + "12": "Association", + "13": "Autre" + }, - "famsit": { - "0": "Seul", - "1": "Seul avec enfant(s)", - "2": "En couple sans enfants", - "3": "En couple avec enfants" - }, + "famsit": { + "0": "Seul", + "1": "Seul avec enfant(s)", + "2": "En couple sans enfants", + "3": "En couple avec enfants" + }, - "reltype": { - "0": "Père, mère ou équivalent", - "1": "Frère ou soeur", - "2": "Autre membre de la famille", - "3": "Relation amoureuse", - "4": "Collègue", - "5": "Voisin", - "6": "Ami proche", - "7": "Ami", - "8": "Relation de service (médecin, ...)", - "9": "Inconnu" - }, + "reltype": { + "0": "Père, mère ou équivalent", + "1": "Frère ou soeur", + "2": "Autre membre de la famille", + "3": "Relation amoureuse", + "4": "Collègue", + "5": "Voisin", + "6": "Ami proche", + "7": "Ami", + "8": "Relation de service (médecin, ...)", + "9": "Inconnu" + }, - "dist": { - "0": "- de 5 minutes", - "1": "de 5 à 15 minutes", - "2": "de 15 à 60 minutes", - "3": "+ d'une heure" - }, + "dist": { + "0": "- de 5 minutes", + "1": "de 5 à 15 minutes", + "2": "de 15 à 60 minutes", + "3": "+ d'une heure" + }, - "freq": { - "0": "plusieurs fois par semaine", - "1": "1 fois par semaine", - "2": "1 fois par mois", - "3": "1 fois par an ou moins", - "4": "Jamais" - }, + "freq": { + "0": "plusieurs fois par semaine", + "1": "1 fois par semaine", + "2": "1 fois par mois", + "3": "1 fois par an ou moins", + "4": "Jamais" + }, - "connect": { - "0": "Oui", - "1": "Non" - } + "connect": { + "0": "Oui", + "1": "Non" + } - } + } } diff --git a/test-charts/all.php b/test-charts/all.php new file mode 100644 index 0000000..8256769 --- /dev/null +++ b/test-charts/all.php @@ -0,0 +1,137 @@ + + + + + Chart du sexe (HOMME/FEMME) + + + + + + + + + + + + + + diff --git a/test-charts/sexe.php b/test-charts/sexe.php new file mode 100644 index 0000000..87cd493 --- /dev/null +++ b/test-charts/sexe.php @@ -0,0 +1,57 @@ + + + + + Chart du sexe (HOMME/FEMME) + + + + + + + + + + + From 912bb8933f546176a567153fa5b0a2cefb42bf6c Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 May 2016 16:41:12 +0200 Subject: [PATCH 04/12] #14; Simplification des noms des modules --- config/modules.json | 4 ++-- manager/module/chart.php | 4 ++-- test-charts/all.php | 4 ++-- test-charts/{communication-direction.php => direction.php} | 2 +- test-charts/{communication-type.php => type.php} | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename test-charts/{communication-direction.php => direction.php} (97%) rename test-charts/{communication-type.php => type.php} (97%) diff --git a/config/modules.json b/config/modules.json index edbc85a..0a39688 100755 --- a/config/modules.json +++ b/config/modules.json @@ -106,7 +106,7 @@ "chart": { - "communication_direction": { + "direction": { "description": "Renvoie les données pour un graphique sur les sens de communications", "permissions": ["admin"], "parameters": { @@ -114,7 +114,7 @@ } }, - "communication_type": { + "type": { "description": "Renvoie les données pour un graphique sur les types de communications", "permissions": ["admin"], "parameters": { diff --git a/manager/module/chart.php b/manager/module/chart.php index 8e18421..324c97a 100755 --- a/manager/module/chart.php +++ b/manager/module/chart.php @@ -14,7 +14,7 @@ /* RETOURNE UN JEU DE DONNEES POUR LE SENS DE COMMUNICATION (MANQUE/ENTRANT/SORTANT) * */ - public static function communication_direction($params){ + public static function direction($params){ extract($params); $subject = intval($subject); @@ -80,7 +80,7 @@ /* RETOURNE UN JEU DE DONNEES POUR LE TYPE DE COMMUNICATION (APPEL/SMS) * */ - public static function communication_type($params){ + public static function type($params){ extract($params); $subject = intval($subject); diff --git a/test-charts/all.php b/test-charts/all.php index 8256769..62c1b8c 100644 --- a/test-charts/all.php +++ b/test-charts/all.php @@ -66,7 +66,7 @@ =========================================================*/ /* (1) On rédige la requête */ var request = { - path: 'chart/communication_direction', + path: 'chart/direction', subject: 273 } @@ -102,7 +102,7 @@ =========================================================*/ /* (1) On rédige la requête */ var request = { - path: 'chart/communication_type', + path: 'chart/type', subject: 273 } diff --git a/test-charts/communication-direction.php b/test-charts/direction.php similarity index 97% rename from test-charts/communication-direction.php rename to test-charts/direction.php index e38b2c3..e4beee0 100644 --- a/test-charts/communication-direction.php +++ b/test-charts/direction.php @@ -21,7 +21,7 @@ =========================================================*/ /* (1) On rédige la requête */ var request = { - path: 'chart/communication_direction', + path: 'chart/direction', subject: 273 } diff --git a/test-charts/communication-type.php b/test-charts/type.php similarity index 97% rename from test-charts/communication-type.php rename to test-charts/type.php index 89abc92..efca11e 100644 --- a/test-charts/communication-type.php +++ b/test-charts/type.php @@ -21,7 +21,7 @@ =========================================================*/ /* (1) On rédige la requête */ var request = { - path: 'chart/communication_type', + path: 'chart/type', subject: 273 } From ee3d18261ad7d37c64879e4aba25bc9027963ccb Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 May 2016 17:36:49 +0200 Subject: [PATCH 05/12] #14; Prototype (iface+module) du graphique des classes d'ages --- automate.php | 2 +- config/modules.json | 8 + manager/module/chart.php | 255 ++++++++++++++++++++++++------ src/dynamic/phone/dictionary.json | 3 +- test-charts/all.php | 64 ++++++-- test-charts/direction.php | 57 ------- test-charts/sexe.php | 57 ------- test-charts/type.php | 57 ------- 8 files changed, 267 insertions(+), 236 deletions(-) delete mode 100644 test-charts/direction.php delete mode 100644 test-charts/sexe.php delete mode 100644 test-charts/type.php diff --git a/automate.php b/automate.php index d506505..56263a3 100755 --- a/automate.php +++ b/automate.php @@ -94,7 +94,7 @@ $db = new lightdb('phone_db', __ROOT__.'/src/dynamic/'); var_dump( array_keys($db->index())); $db->close(); - $req = new ModuleRequest('chart/sexe', array( 'subject' => 273 )); + $req = new ModuleRequest('chart/direction', array( 'subject' => 273 )); $res = $req->dispatch(); diff --git a/config/modules.json b/config/modules.json index 0a39688..9d4e7c5 100755 --- a/config/modules.json +++ b/config/modules.json @@ -128,6 +128,14 @@ "parameters": { "subject": { "description": "Identifiant du sujet à étudier,", "type": "id" } } + }, + + "ages": { + "description": "Renvoie les données pour un graphique sur les ages des contacts", + "permissions": ["admin"], + "parameters": { + "subject": { "description": "Identifiant du sujet à étudier,", "type": "id" } + } } }, diff --git a/manager/module/chart.php b/manager/module/chart.php index 324c97a..a53bb81 100755 --- a/manager/module/chart.php +++ b/manager/module/chart.php @@ -3,6 +3,7 @@ namespace manager\module; use \manager\sessionManager; use \manager\ManagerError; + use \manager\ResourceDispatcher; use \manager\lightdb; @@ -11,6 +12,56 @@ class chart{ + /* CHARGE LE CONTENU DU DICTIONNAIRE + * + * @return dictionary Contenu du dictionnaire, ou FALSE si erreur + * + */ + private static function loadDictionary(){ + $dict = ResourceDispatcher::getResource('f/json/dictionary/dy/phone'); + + $dict = json_decode( $dict, true ); + + // Si erreur, on retourne false + if( is_null($dict) ) + return false; + + return $dict; + } + + + /* MODIFIE LES COULEURS DONNEES + * + * @colors Tableau de couleurs au format hsl + * @tint Valeur a ajouter à tint + * @constract Valeur a ajouter à constract + * @lightness Valeur a ajouter à lightness + * + * @return darken Copie du tableau d'entrée, mais en plus foncé + * + */ + private static function cFilter($colors, $tint=0, $contrast=0, $lightness=0){ + $darken = $colors; + + /* [1] Pour chaque couleur + =========================================================*/ + foreach($colors as $i=>$color){ + + /* (1) On vérifie que c'est bien au format hsl(tint, saturation%, lightness%) */ + if( !preg_match('/^hsl\((\d+) ?, ?(\d+)% ?, ?(\d+)%\)$/i', $color, $matches) ) + continue; + + $newTint = intval($matches[1]) + $tint; + $newContrast = intval($matches[2]) + $contrast; + $newLightness = intval($matches[3]) + $lightness; + + $darken[$i] = "hsl($newTint,$newContrast%, $newLightness%)"; + } + + return $darken; + } + + /* RETOURNE UN JEU DE DONNEES POUR LE SENS DE COMMUNICATION (MANQUE/ENTRANT/SORTANT) * */ @@ -31,45 +82,48 @@ return array( 'ModuleError' => ManagerError::ModuleError ); - /* [2] S'il a un journal d'appel, on renvoie les données + /* [2] On initialise les compteurs + =========================================================*/ + $labels = array('ENTRANT', 'SORTANT', 'MANQUÉ'); + + $MISSED = 0; + $OUTGOING = 0; + $INCOMING = 0; + + /* [3] 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 */ + /* (1) On incrémente les compteurs */ foreach($data['logs'] as $log){ - /* (3) Si ce n'est pas un appel, on passe au suivant */ + /* (2) Si ce n'est pas un appel, on passe au suivant */ if( $log['type'] != 0 ) continue; - /* (4) On incrémente les types */ + /* (3) 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; - } + /* [4] Gestion des couleurs + =========================================================*/ + $colors = array(); + + $colors['default'] = array( 'hsl(347,100%,69%)', 'hsl(204,82%,57%)', 'hsl(43,100%,67%)' ); + $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); + + return array( 'ModuleError' => ManagerError::Success, - 'labels' => array('ENTRANT', 'SORTANT', 'MANQUÉ'), - 'data' => array($INCOMING, $OUTGOING, $MISSED) + 'labels' => $labels, + 'colors' => $colors, + 'data' => array($INCOMING, $OUTGOING, $MISSED) ); } @@ -97,45 +151,48 @@ return array( 'ModuleError' => ManagerError::ModuleError ); + /* [2] On initialise les compteurs + =========================================================*/ + $PHONE = 0; + $SMS = 0; + + /* [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 */ - $PHONE = 0; - $SMS = 0; - - /* (2) On incrémente les compteurs */ + /* (1) On incrémente les compteurs */ foreach($data['logs'] as $log){ - /* (3) On incrémente les compteurs */ + /* (2) On incrémente les compteurs */ $PHONE += ($log['type']==0) ? 1 : 0; $SMS += ($log['type']==1) ? 1 : 0; } - /* [3] Si aucun journal d'appel - =========================================================*/ - }else{ - - /* (1) On initialise les compteurs */ - $H = 0; - $F = 0; - } + /* [4] Gestion des couleurs + =========================================================*/ + $colors = array(); + + $colors['default'] = array( 'hsl(347,100%,69%)', 'hsl(204,82%,57%)' ); + $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); + + return array( 'ModuleError' => ManagerError::Success, - 'labels' => array('APPELS', 'SMS'), - 'data' => array($PHONE, $SMS) + 'colors' => $colors, + 'labels' => array('APPELS', 'SMS'), + 'data' => array($PHONE, $SMS) ); } - /* RETOURNE UN JEU DE DONNEES POUR LE SEXE DES CONTACTS + /* RETOURNE UN JEU DE DONNEES POUR LE SEXE DES COMMUNICATIONS (HOMME/FEMME) * */ public static function sexe($params){ @@ -154,14 +211,99 @@ if( $data === false ) return array( 'ModuleError' => ManagerError::ModuleError ); + /* [2] On initialise les compteurs + =========================================================*/ + $H = 0; + $F = 0; - /* [2] S'il a un journal d'appel, on renvoie les données + /* [3] S'il a un journal d'appel, on renvoie les données + =========================================================*/ + if( isset($data['logs']) && is_array($data['logs']) ){ + + /* (1) On incrémente les compteurs */ + foreach($data['logs'] as $log){ + + /* (2) On récupère le contact associé */ + $associatedContact = null; + foreach($data['contacts'] as $contact) + if( $log['id'] == $contact['id'] ) + $associatedContact = $contact; + + // Si on ne trouve pas, on passe au suivant + if( is_null($associatedContact) ) + continue; + + /* (3) On incrémente les compteurs */ + $H += ($associatedContact['sexe']==0) ? 1 : 0; + $F += ($associatedContact['sexe']==1) ? 1 : 0; + + } + + } + + + /* [4] Gestion des couleurs + =========================================================*/ + $colors = array(); + + $colors['default'] = array( 'hsl(347,100%,69%)', 'hsl(204,82%,57%)' ); + $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); + + + return array( + 'ModuleError' => ManagerError::Success, + 'colors' => $colors, + 'labels' => array('HOMME', 'FEMME'), + 'data' => array($H, $F) + ); + } + + + + + + /* RETOURNE UN JEU DE DONNEES POUR LES AGES DES COMMUNICATIONS + * + */ + public static function ages($params){ + extract($params); + + $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] On initialise les valeurs + =========================================================*/ + /* (1) On initialise les compteurs */ + $age_classes = array(); + for( $i = 0 ; $i < 18 ; $i++ ) + $age_classes[$i] = 0; + + /* (2) On charge le dictionnaire */ + $dict = self::loadDictionary(); + if( $dict === false ) + return array( 'ModuleError' => ManagerError::ParsingFailed ); + + /* (2) On initialise les labels */ + $labels = array(); + foreach($dict['contacts']['age'] as $i=>$label) + array_push($labels, $label); + + + /* [3] 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 */ - $H = 0; - $F = 0; /* (2) On incrémente les compteurs */ foreach($data['logs'] as $log){ @@ -176,29 +318,38 @@ if( is_null($associatedContact) ) continue; - /* (4) On incrémente les compteurs */ - $H += ($associatedContact['sexe']==0) ? 1 : 0; - $F += ($associatedContact['sexe']==1) ? 1 : 0; + /* (4) On incrémente le compteur de la classe d'age en question */ + if( isset($age_classes[ $associatedContact['age'] ]) ) + $age_classes[ $associatedContact['age'] ]++; } - /* [3] Si aucun journal d'appel + } + + + /* [4] Gestion des couleurs =========================================================*/ - }else{ + $colors = array(); - /* (1) On initialise les compteurs */ - $H = 0; - $F = 0; + $colors['default'] = array(); - }/* (1) On initialise les compteurs */ + $step = (int) (50/count($age_classes)); + + // Pour chaque classe, on ajoute une couleur + for( $i = 0 ; $i < count($age_classes) ; $i++ ) + array_push($colors['default'], 'hsl(216,100%,'.(25+(50-$step*$i)).'%)'); + + $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); return array( 'ModuleError' => ManagerError::Success, - 'labels' => array('HOMME', 'FEMME'), - 'data' => array($H, $F) + 'colors' => $colors, + 'labels' => $labels, + 'data' => $age_classes ); } + } diff --git a/src/dynamic/phone/dictionary.json b/src/dynamic/phone/dictionary.json index 812fa62..4b45dbd 100755 --- a/src/dynamic/phone/dictionary.json +++ b/src/dynamic/phone/dictionary.json @@ -15,8 +15,7 @@ "contacts": { "sexe": { "0":"Homme", "1":"Femme" }, "age": { - ".": "NA", - "0": "5 à 10", "1": "10 à 15", "2": "15 à 20", "3": "20 à 25", "4": "25 à 30", + "0": "5 à 10", "1": "10 à 15", "2": "15 à 20", "3": "20 à 25", "4": "25 à 30", "5": "30 à 35", "6": "35 à 40", "7": "40 à 45", "8": "45 à 50", "9": "50 à 55", "10": "55 à 60", "11": "60 à 65", "12": "65 à 70", "13": "70 à 75", "14": "75 à 80", "15": "80 à 85", "16": "85 à 90", "17": "90 à 95", "18": "95 à 100" diff --git a/test-charts/all.php b/test-charts/all.php index 62c1b8c..3683605 100644 --- a/test-charts/all.php +++ b/test-charts/all.php @@ -11,6 +11,7 @@ + @@ -25,13 +26,15 @@ =========================================================*/ Chart.defaults.global.responsive = false; + subject = 273; + /* [1] On récupére les données SEXE =========================================================*/ /* (1) On rédige la requête */ var request = { path: 'chart/sexe', - subject: 273 + subject: subject } /* (2) On lance la requête */ @@ -46,8 +49,8 @@ labels: response.labels, datasets: [{ data: response.data, - backgroundColor: [ "#FF6384", "#36A2EB" ], - hoverBackgroundColor: [ "#E65977", "#3090D1" ] + backgroundColor: response.colors.default, + hoverBackgroundColor: response.colors.hover }] } @@ -67,7 +70,7 @@ /* (1) On rédige la requête */ var request = { path: 'chart/direction', - subject: 273 + subject: subject } /* (2) On lance la requête */ @@ -82,8 +85,8 @@ labels: response.labels, datasets: [{ data: response.data, - backgroundColor: [ "#FF6384", "#36A2EB", "#FFCE56" ], - hoverBackgroundColor: [ "#E65977", "#3090D1", "#E6B94D" ] + backgroundColor: response.colors.default, + hoverBackgroundColor: response.colors.hover }] } @@ -98,12 +101,12 @@ }); - /* [1] On récupére les données TYPE + /* [3] On récupére les données TYPE =========================================================*/ /* (1) On rédige la requête */ var request = { path: 'chart/type', - subject: 273 + subject: subject } /* (2) On lance la requête */ @@ -118,8 +121,8 @@ labels: response.labels, datasets: [{ data: response.data, - backgroundColor: [ "#FF6384", "#36A2EB" ], - hoverBackgroundColor: [ "#E65977", "#3090D1" ] + backgroundColor: response.colors.default, + hoverBackgroundColor: response.colors.hover }] } @@ -132,6 +135,47 @@ }); }); + + + + + /* [4] On récupére les données AGES + =========================================================*/ + /* (1) On rédige la requête */ + var request = { + path: 'chart/ages', + subject: subject + } + + /* (2) On lance la requête */ + api.send(request, function(response){ + /* (3) Si erreur, on quitte */ + if( response.ModuleError != 0 ) + return false; + + /* [2] On construit les données + =========================================================*/ + var data = { + labels: response.labels, + datasets: [{ + data: response.data, + backgroundColor: response.colors.default, + hoverBackgroundColor: response.colors.hover + }] + } + + /* [3] On construit notre graphique + =========================================================*/ + var myDoughnutChart = new Chart(age, { + type: 'doughnut', + animation: { animateScale: true }, + data: data + }); + + }); + + + diff --git a/test-charts/direction.php b/test-charts/direction.php deleted file mode 100644 index e4beee0..0000000 --- a/test-charts/direction.php +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Chart sens de communication (MANQUE/ENTRANT/SORTANT) - - - - - - - - - - - diff --git a/test-charts/sexe.php b/test-charts/sexe.php deleted file mode 100644 index 87cd493..0000000 --- a/test-charts/sexe.php +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Chart du sexe (HOMME/FEMME) - - - - - - - - - - - diff --git a/test-charts/type.php b/test-charts/type.php deleted file mode 100644 index efca11e..0000000 --- a/test-charts/type.php +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Chart type de communication (APPEL/SMS) - - - - - - - - - - - From a5e80cf5968601a68211f0a31b57cf5f45e90581 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Sun, 22 May 2016 19:48:44 +0200 Subject: [PATCH 06/12] #14; Prototype (iface+module) du graphique des types de relations --- automate.php | 2 - config/modules.json | 8 +++ manager/module/chart.php | 117 +++++++++++++++++++++++++++++++++++---- test-charts/all.php | 53 ++++++++++++++++-- 4 files changed, 163 insertions(+), 17 deletions(-) diff --git a/automate.php b/automate.php index 56263a3..7162a89 100755 --- a/automate.php +++ b/automate.php @@ -87,8 +87,6 @@ // } - - /* [4] Test download via AJAX =========================================================*/ $db = new lightdb('phone_db', __ROOT__.'/src/dynamic/'); diff --git a/config/modules.json b/config/modules.json index 9d4e7c5..91b0dc2 100755 --- a/config/modules.json +++ b/config/modules.json @@ -136,6 +136,14 @@ "parameters": { "subject": { "description": "Identifiant du sujet à étudier,", "type": "id" } } + }, + + "relations": { + "description": "Renvoie les données pour un graphique sur les types de relations des contacts", + "permissions": ["admin"], + "parameters": { + "subject": { "description": "Identifiant du sujet à étudier,", "type": "id" } + } } }, diff --git a/manager/module/chart.php b/manager/module/chart.php index a53bb81..6b581c0 100755 --- a/manager/module/chart.php +++ b/manager/module/chart.php @@ -32,30 +32,32 @@ /* MODIFIE LES COULEURS DONNEES * - * @colors Tableau de couleurs au format hsl + * @colors Tableau de couleurs au format hsla * @tint Valeur a ajouter à tint * @constract Valeur a ajouter à constract * @lightness Valeur a ajouter à lightness + * @opacity Valeur a ajouter à opacity * * @return darken Copie du tableau d'entrée, mais en plus foncé * */ - private static function cFilter($colors, $tint=0, $contrast=0, $lightness=0){ + private static function cFilter($colors, $tint=0, $contrast=0, $lightness=0, $opacity=0){ $darken = $colors; /* [1] Pour chaque couleur =========================================================*/ foreach($colors as $i=>$color){ - /* (1) On vérifie que c'est bien au format hsl(tint, saturation%, lightness%) */ - if( !preg_match('/^hsl\((\d+) ?, ?(\d+)% ?, ?(\d+)%\)$/i', $color, $matches) ) + /* (1) On vérifie que c'est bien au format hsl(tint, saturation%, lightness%, ) */ + if( !preg_match('/^hsl\((\d+) ?, ?(\d+)% ?, ?(\d+)%\ ?, ?(\d+?.\d*)\)$/i', $color, $matches) ) continue; - $newTint = intval($matches[1]) + $tint; - $newContrast = intval($matches[2]) + $contrast; - $newLightness = intval($matches[3]) + $lightness; + $newTint = intval($matches[1]) + $tint; + $newContrast = intval($matches[2]) + $contrast; + $newLightness = intval($matches[3]) + $lightness; + $newOpacity = floatval($matches[4]) + $opacity; - $darken[$i] = "hsl($newTint,$newContrast%, $newLightness%)"; + $darken[$i] = "hsl($newTint,$newContrast%, $newLightness%, $newOpacity)"; } return $darken; @@ -115,7 +117,7 @@ =========================================================*/ $colors = array(); - $colors['default'] = array( 'hsl(347,100%,69%)', 'hsl(204,82%,57%)', 'hsl(43,100%,67%)' ); + $colors['default'] = array( 'hsla(347,100%,69%,1)', 'hsla(204,82%,57%,1)', 'hsla(43,100%,67%,1)' ); $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); @@ -177,7 +179,7 @@ =========================================================*/ $colors = array(); - $colors['default'] = array( 'hsl(347,100%,69%)', 'hsl(204,82%,57%)' ); + $colors['default'] = array( 'hsla(347,100%,69%,1)', 'hsla(204,82%,57%,1)' ); $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); @@ -246,7 +248,7 @@ =========================================================*/ $colors = array(); - $colors['default'] = array( 'hsl(347,100%,69%)', 'hsl(204,82%,57%)' ); + $colors['default'] = array( 'hsla(347,100%,69%,1)', 'hsla(204,82%,57%,1)' ); $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); @@ -350,6 +352,99 @@ ); } + + + + + + /* RETOURNE UN JEU DE DONNEES POUR LES TYPES DE RELATION DES COMMUNICATIONS + * + */ + public static function relations($params){ + extract($params); + + $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] On initialise les valeurs + =========================================================*/ + /* (1) On initialise les compteurs */ + $relations = array(); + for( $i = 0 ; $i < 18 ; $i++ ) + $relations[$i] = 0; + + /* (2) On charge le dictionnaire */ + $dict = self::loadDictionary(); + if( $dict === false ) + return array( 'ModuleError' => ManagerError::ParsingFailed ); + + /* (2) On initialise les labels */ + $labels = array(); + foreach($dict['contacts']['reltype'] as $i=>$label) + array_push($labels, $label); + + + /* [3] S'il a un journal d'appel, on renvoie les données + =========================================================*/ + if( isset($data['logs']) && is_array($data['logs']) ){ + + + /* (2) On incrémente les compteurs */ + foreach($data['logs'] as $log){ + + /* (3) On récupère le contact associé */ + $associatedContact = null; + foreach($data['contacts'] as $contact) + if( $log['id'] == $contact['id'] ) + $associatedContact = $contact; + + // Si on ne trouve pas, on passe au suivant + if( is_null($associatedContact) ) + continue; + + /* (4) On incrémente le compteur de la classe d'age en question */ + if( isset($relations[ $associatedContact['reltype'] ]) ) + $relations[ $associatedContact['reltype'] ]++; + + } + + } + + + /* [4] Gestion des couleurs + =========================================================*/ + $colors = array(); + + $colors['default'] = array(); + + $step = (int) (50/count($relations)); + + // Pour chaque classe, on ajoute une couleur + for( $i = 0 ; $i < count($relations) ; $i++ ) + array_push($colors['default'], 'hsla(216,100%,'.(25+(50-$step*$i)).'%,.5)'); + + $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); + + + return array( + 'ModuleError' => ManagerError::Success, + 'colors' => $colors, + 'labels' => $labels, + 'data' => $relations + ); + } + } diff --git a/test-charts/all.php b/test-charts/all.php index 3683605..dbbf121 100644 --- a/test-charts/all.php +++ b/test-charts/all.php @@ -8,10 +8,11 @@ - - - - + + + + + @@ -21,6 +22,8 @@ var direction = document.getElementById('direction'); var type = document.getElementById('type'); var sexe = document.getElementById('sexe'); + var age = document.getElementById('age'); + var relation = document.getElementById('relation'); /* [0] Paramètres globaux =========================================================*/ @@ -176,6 +179,48 @@ + + /* [5] On récupére les données RELATIONS (types) + =========================================================*/ + /* (1) On rédige la requête */ + var request = { + path: 'chart/relations', + subject: subject + } + + /* (2) On lance la requête */ + api.send(request, function(response){ + /* (3) Si erreur, on quitte */ + if( response.ModuleError != 0 ) + return false; + + /* [2] On construit les données + =========================================================*/ + console.log(response.labels); + var data = { + labels: response.labels, + datasets: [{ + label: "Types de relation", + data: response.data, + borderWidth: 1, + borderColor: 'hsla(216,100%,80%,1)', + backgroundColor: response.colors.default, + hoverBackgroundColor: response.colors.hover + }] + } + + /* [3] On construit notre graphique + =========================================================*/ + var myBarChart = new Chart(relation, { + type: 'bar', + animation: { animateScale: true }, + data: data + }); + + }); + + + From 94d91d479e124a7bc32ffe7cbe369be7ac626fe4 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 23 May 2016 11:09:52 +0200 Subject: [PATCH 07/12] Opti+revision 'api.js' --- js/lib/api-min.js | 7 ++++--- js/lib/api.js | 29 +++++++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/js/lib/api-min.js b/js/lib/api-min.js index e6d4e7a..86eeb6d 100644 --- a/js/lib/api-min.js +++ b/js/lib/api-min.js @@ -1,3 +1,4 @@ -function APIClass(b){this.target=b} -APIClass.prototype={xhr:[],buffer:null,send:function(b,e,g){b.hasOwnProperty("path")||e({ModuleError:4});for(var a=0;a l'objet passé en POST (attribut->postfield) à http://host/api/ * @param pHandler fonction qui s'éxécutera lors de la réponse (1 argument -> réponse) - * @param pToken si donne, token d'auth pour l'api + * @param pToken Optionnel, token d'auth pour l'api + * @param pParams Optionnels, liste d'arguments à passer au scope de @pHandler * * @return answer l'objet retourné par http://host/api/ via pHandler (1er argument) * @@ -34,23 +35,29 @@ APIClass.prototype = { pHandler({ModuleError:4}); // on efface les requêtes qui sont terminées (toutes celles de this.xhr) - for( var i = 0 ; i < this.xhr.length ; i++ ){ + for( var i = 0 ; i < this.xhr.length ; i++ ) if( this.xhr[i].readyState == 4 ) // si terminée - this.xhr = this.xhr.slice(0,i-1).concat(this.xhr.slice(i,this.xhr.length-1)); // suppression entrée - } + this.xhr.splice(i, 1); + // on créé une nouvelle entrée this.xhr.push(null); i = this.xhr.length-1; + + // Gestion des paramètres optionnels à passer au scope de @pHandler + this.optionalParams[i] = []; + if( arguments.length > 3 ) + for( var arg = 3 ; arg < arguments.length ; arg++ ) + this.optionalParams[i].push( arguments[arg] ); + + // création de l'objet AJAX if(window.XMLHttpRequest) // IE7+, Firefox, Chrome, Opera, Safari this.xhr[i] = new XMLHttpRequest(); else // IE5, IE6 this.xhr[i] = new ActiveXObject('Microsoft.XMLHttpRequest'); - console.log(pRequest); - var ptrAPI = this; this.xhr[i].onreadystatechange = function(){ if( ptrAPI.xhr[i].readyState == 4 ){ // si la requête est terminée @@ -60,10 +67,12 @@ APIClass.prototype = { // console.log('http://host/api/ => '+ptrAPI.xhr[i].responseText); // console.log( JSON.parse(ptrAPI.xhr[i].responseText) ); + console.log('api request', pRequest); + /* si success de requête */ if( [0,200].indexOf(ptrAPI.xhr[i].status) > -1 ){ // si fichier existe et reçu - try{ pHandler( JSON.parse(ptrAPI.xhr[i].responseText) ); } // si on peut parser, on envoie - catch(e){ pHandler({ModuleError:-1, ErrorDescription:'Erreur au niveau de api.js'}); console.warn(e); } // sinon on envoie obj.request = 'corrupted' + try{ pHandler( JSON.parse(ptrAPI.xhr[i].responseText), ptrAPI.optionalParams[i]); } // si on peut parser, on envoie + catch(e){ pHandler({ModuleError:-1, ErrorDescription:'Erreur au niveau de api.js'}, ptrAPI.optionalParams[i]); console.warn(e); } // sinon on envoie obj.request = 'corrupted' } /* sinon retourne obj.request = 'unreachable' */ else From 3b4a0565a1fe97b3070dfaa45cc15ef81b688f38 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Mon, 23 May 2016 11:10:24 +0200 Subject: [PATCH 08/12] =?UTF-8?q?#14;=20Int=C3=A9gration=20des=20graphique?= =?UTF-8?q?s=20dans=20le=20site=20'charts/phone'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- automate.php | 4 + doc/permissions.json~ | 39 - js/includes/charts-sociogram-min.js | 10 - js/includes/charts-sociogram.js | 558 - js/lib/sigma/README.md | 35 - .../plugins/sigma-layout-forceAtlas2-min.js | 1 - .../sigma/plugins/sigma-parsers-json-min.js | 1 - .../plugins/sigma-plugins-animate-min.js | 1 - .../sigma-renderers-customShapes-min.js | 1 - .../sigma/plugins/sigma.exporters.svg.min.js | 1 - .../sigma/plugins/sigma.neo4j.cypher.min.js | 1 - .../sigma/plugins/sigma.parsers.cypher.min.js | 1 - .../sigma/plugins/sigma.parsers.gexf.min.js | 1 - .../plugins/sigma.pathfinding.astar.min.js | 1 - .../plugins/sigma.plugins.dragNodes.min.js | 1 - .../sigma/plugins/sigma.plugins.filter.min.js | 1 - .../sigma.plugins.neighborhoods.min.js | 1 - .../plugins/sigma.plugins.relativeSize.min.js | 1 - .../sigma.renderers.customEdgeShapes.min.js | 1 - .../plugins/sigma.renderers.edgeLabels.min.js | 1 - .../sigma.renderers.parallelEdges.min.js | 1 - .../plugins/sigma.renderers.snapshot.min.js | 1 - .../plugins/sigma.statistics.HITS.min.js | 1 - js/lib/sigma/sigma-min.js | 5 - js/lib/sigma/sigma.js | 12097 --------------- js/lib/sigma/sigma.require.js | 12224 ---------------- manager/module/chart.php | 37 +- test-charts/all.php | 51 +- view.php | 4 +- view/charts.php | 48 +- view/js/charts-min.js | 3 +- view/js/charts.js | 80 +- 32 files changed, 123 insertions(+), 25090 deletions(-) delete mode 100644 doc/permissions.json~ delete mode 100644 js/includes/charts-sociogram-min.js delete mode 100644 js/includes/charts-sociogram.js delete mode 100755 js/lib/sigma/README.md delete mode 100755 js/lib/sigma/plugins/sigma-layout-forceAtlas2-min.js delete mode 100755 js/lib/sigma/plugins/sigma-parsers-json-min.js delete mode 100755 js/lib/sigma/plugins/sigma-plugins-animate-min.js delete mode 100755 js/lib/sigma/plugins/sigma-renderers-customShapes-min.js delete mode 100755 js/lib/sigma/plugins/sigma.exporters.svg.min.js delete mode 100755 js/lib/sigma/plugins/sigma.neo4j.cypher.min.js delete mode 100755 js/lib/sigma/plugins/sigma.parsers.cypher.min.js delete mode 100755 js/lib/sigma/plugins/sigma.parsers.gexf.min.js delete mode 100755 js/lib/sigma/plugins/sigma.pathfinding.astar.min.js delete mode 100755 js/lib/sigma/plugins/sigma.plugins.dragNodes.min.js delete mode 100755 js/lib/sigma/plugins/sigma.plugins.filter.min.js delete mode 100755 js/lib/sigma/plugins/sigma.plugins.neighborhoods.min.js delete mode 100755 js/lib/sigma/plugins/sigma.plugins.relativeSize.min.js delete mode 100755 js/lib/sigma/plugins/sigma.renderers.customEdgeShapes.min.js delete mode 100755 js/lib/sigma/plugins/sigma.renderers.edgeLabels.min.js delete mode 100755 js/lib/sigma/plugins/sigma.renderers.parallelEdges.min.js delete mode 100755 js/lib/sigma/plugins/sigma.renderers.snapshot.min.js delete mode 100755 js/lib/sigma/plugins/sigma.statistics.HITS.min.js delete mode 100755 js/lib/sigma/sigma-min.js delete mode 100755 js/lib/sigma/sigma.js delete mode 100755 js/lib/sigma/sigma.require.js diff --git a/automate.php b/automate.php index 7162a89..0b81975 100755 --- a/automate.php +++ b/automate.php @@ -92,6 +92,10 @@ $db = new lightdb('phone_db', __ROOT__.'/src/dynamic/'); var_dump( array_keys($db->index())); $db->close(); + + $db = new lightdb('facebook_db', __ROOT__.'/src/dynamic/'); + var_dump( array_keys($db->index())); + $db->close(); $req = new ModuleRequest('chart/direction', array( 'subject' => 273 )); $res = $req->dispatch(); diff --git a/doc/permissions.json~ b/doc/permissions.json~ deleted file mode 100644 index 13b71d2..0000000 --- a/doc/permissions.json~ +++ /dev/null @@ -1,39 +0,0 @@ -{ - "root": "/var/www/socioview", - - - - "permissions": { - "public": { "chown": "xdrm:www-data", "chmod": 750 }, - "private": { "chown": "xdrm:xdrm", "chmod": 700, ".htaccess": "Order deny,allow\nDeny from all" }, - "phponly": { "chown": "xdrm:www-data", "chmod": 770, ".htaccess": "Order deny,allow\nDeny from all" }, - "php": { "chown": "xdrm:www-data", "chmod": 770 } - }, - - - - - "files": { - "permissions": "public", - - ".git": { "permissions": "private" }, - - "config": { "permissions": "private" }, - "manager": { "permissions": "private" }, - - "src": { - "permissions": "phponly", - "static": "public" - }, - - - "tmp": { - "permissions": "php" - }, - - - ".htaccess": { "permissions": "private" } - } - - -} diff --git a/js/includes/charts-sociogram-min.js b/js/includes/charts-sociogram-min.js deleted file mode 100644 index 5422cc7..0000000 --- a/js/includes/charts-sociogram-min.js +++ /dev/null @@ -1,10 +0,0 @@ -function sociogramClass(a){this.container=a;this.log("sociogram created")} -sociogramClass.prototype={container:this.container,sigma:null,request:{path:"chart/network_data"},response:null,nodes:[],edges:[],rad:500,nodeDistance:100,overloadGraph:function(){},load:function(){},nodeAt:function(a,b){},arrange:function(a,b,d){},extractNodesFromResponse:function(){},extractEdgesFromResponse:function(){},renderNodes:function(){},renderEdges:function(){},overload:{nodeNeighbors:function(a){},nodeDirectNeighbors:function(a){}},bindings:{clickNode:function(a,b){},clickStage:function(a, -b){}},log:function(a){console.log("--[SOCIOGRAM]--");console.warn(a);console.log("--[/SOCIOGRAM]--")}};sociogramClass.prototype.overloadGraph=function(){sigma.classes.graph.addMethod("nodeNeighbors",this.overload.nodeNeighbors);sigma.classes.graph.addMethod("nodeDirectNeighbors",this.overload.nodeDirectNeighbors);this.log("graph overloaded")}; -sociogramClass.prototype.load=function(){this.sigma=new sigma({renderer:{container:this.container,type:"canvas"}});var a=this;api.send(this.request,function(b){a.log(b);0==b.ModuleError&&(a.response=b,a.sigma.settings({defaultNodeColor:"#348ed8",defaultLabelSize:14,defaultLabelBGColor:"#ddd",defaultHoverLabelBGColor:"#002147",defaultLabelHoverColor:"#fff",labelThreshold:10,defaultEdgeType:"line"}),a.extractNodesFromResponse(),a.extractEdgesFromResponse(),a.addNodes(),a.addEdges(),a.sigma.bind("clickNode", -function(b){a.bindings.clickNode(a,b)}),a.sigma.bind("clickStage",function(b){a.bindings.clickStage(a,b)}),a.sigma.graph.nodes().forEach(function(b){a.arrange(b.id,null,!0)}),a.sigma.camera.ratio=1.2,a.sigma.refresh())})};sociogramClass.prototype.nodeAt=function(a,b){var d=this.sigma.graph.nodes(),c=null,e;for(e in d){var f=Math.sqrt(Math.pow(a-d[e].x,2)+Math.pow(b-d[e].y,2));if(null==c||fMath.PI/10){e=!0;break}}while(e);a.x=b.x+this.nodeDistance*Math.cos(d);a.y=b.y+this.nodeDistance*Math.sin(d);this.arrange(a.id)}this.sigma.refresh()}};sociogramClass.prototype.overload.nodeNeighbors=function(a){a=this.allNeighborsIndex[a];var b=[],d;for(d in a)b.push(d);for(;0 Ajout de méthodes -* -*/ -sociogramClass.prototype.overloadGraph = function(){ - sigma.classes.graph.addMethod('nodeNeighbors', this.overload.nodeNeighbors); - sigma.classes.graph.addMethod('nodeDirectNeighbors', this.overload.nodeDirectNeighbors); - this.log('graph overloaded'); -}; - - - - - - - - - - - - - - - -/* (1) Point d'amorçage */ -sociogramClass.prototype.load = function(){ - // {1} On instancie SIGMA // - this.sigma = new sigma({ renderer: { container: this.container, 'type': 'canvas' } }); - - var thisPtr = this; - - // {2} On récupère les données via l'API // - api.send(this.request, function(response){ - thisPtr.log(response); - - // Si erreur, on quitte - if( response.ModuleError != 0 ) return; - - // On enregistre la réponse - thisPtr.response = response; - - - // {3} On paramètre SIGMA // - thisPtr.sigma.settings({ - defaultNodeColor: '#348ed8', - defaultLabelSize: 14, - defaultLabelBGColor: "#ddd", - defaultHoverLabelBGColor: "#002147", - defaultLabelHoverColor: "#fff", - labelThreshold: 10, - defaultEdgeType: "line" - }); - - // {4} On recupere les noeuds et les liens // - thisPtr.extractNodesFromResponse(); - thisPtr.extractEdgesFromResponse(); - - // {5} On ajoute les noeuds et les liens au rendu // - thisPtr.addNodes(); - thisPtr.addEdges(); - - - // {6} On définit les évènements // - // On affiche que les voisins d'un noeud quand on clique sur lui - thisPtr.sigma.bind('clickNode', function(e){ - thisPtr.bindings.clickNode(thisPtr, e); - }); - - // On affiche tous les noeuds quand on clique dans le vide - thisPtr.sigma.bind('clickStage', function(e){ - thisPtr.bindings.clickStage(thisPtr, e); - }); - - // {7} On positionne spatialement les noeuds // - thisPtr.sigma.graph.nodes().forEach(function(n){ thisPtr.arrange(n.id, null, true); }); - - // {8} On affiche le rendu // - thisPtr.sigma.camera.ratio = 1.2; - thisPtr.sigma.refresh(); - }); -}; - - - - - - - - - - - - - - - - - - - - - - - - - - -/* RETOURNE LA DISTANCE AVEC LE NOEUD LE PLUS PRES -* -* @x Abscisse du point -* @y Ordonnees du point -* -* @return distance Retourne la distance du noeud le plus proche -* -*/ -sociogramClass.prototype.nodeAt = function(x, y){ - var nodes = this.sigma.graph.nodes(); - var minDistance = null; - for( var nodeId in nodes ){ - var distance = Math.sqrt( Math.pow(x-nodes[nodeId].x, 2) + Math.pow(y-nodes[nodeId].y, 2) ); - if( minDistance == null || distance < minDistance ) - minDistance = distance; - } - - return minDistance; -}; - - - - - - - - - -/* POSITIONNE LES VOISINS AUTOUR DU NOEUD COURANT -* -* @nodeId Id du noeud courant -* @pos Contient {x, y} position initiale, sinon la position actuelle du noeud -* -*/ -sociogramClass.prototype.arrange = function(nodeId, pos, alone){ - var node = this.sigma.graph.nodes(nodeId); - - // Si le noeud est deja place, on ne fais rien - if( node.x != 0 || node.y != 0 ) return; - - pos = (pos==null) ? {x: node.x, y: node.y} : pos; // On recupere la position - - // Tant que le noeud est trop proche d'un autre, on l'eloigne - // UNIQUEMENT si alone n'est pas NULL - if( alone ){ - while( this.nodeAt(pos.x, pos.y) < 2*this.nodeDistance ){ - pos = { - x: pos.x + 2*this.nodeDistance*Math.cos(Math.random()*2*Math.PI), - y: pos.y + 2*this.nodeDistance*Math.sin(Math.random()*2*Math.PI) - }; - - - - - - - } - } - - // On recupere les voisins directs - var neighborsId = this.sigma.graph.nodeDirectNeighbors(nodeId); - var neighbors = {}; - - - - - - - var neighborsCount = 0; - for( var neighborId in neighborsId ){ - neighbors[neighborId] = this.sigma.graph.nodes(neighborId); - neighborsCount++; - } - - - // On positionne le noeud - node.x = pos.x; - node.y = pos.y; - - var angles = []; - - // On positionne chaque voisin si c'est pas deja fait - for( neighborId in neighbors ){ - var current = this.sigma.graph.nodes(neighborId); - // Si n'est pas deja positionne - if( current.x == 0 && current.y == 0 ){ - // On cherche un angle tant qu'il est pas trop pres d'un deja pris - var angle, alreadyUsed = false; - do{ - angle = Math.random()*2*Math.PI; - for( var i = 0 ; i < angles.length ; i++ ) - if( Math.abs(angle-angles[i]) > Math.PI/10 ){ - alreadyUsed = true; - break; - } - - }while( alreadyUsed ); - - current.x = pos.x + this.nodeDistance*Math.cos(angle); - current.y = pos.y + this.nodeDistance*Math.sin(angle); - this.arrange(current.id); - } - } - - this.sigma.refresh(); -}; - - - - - - - - - - -/* RENVOIE LA LISTE DES VOISINS (DIRECTS & INDIRECTS) D'UN NOEUD DONNÉ -* -* @nodeId Id du noeud en question -* -* @return neighbors Liste des voisins directs+indirects -* -*/ -sociogramClass.prototype.overload.nodeNeighbors = function(nodeId){ - var neighbors = this.allNeighborsIndex[nodeId]; - - // Pile des voisins pour lesquels il faut chercher leurs voisins - var stack = []; - for( var neighborId in neighbors ) stack.push(neighborId); - - // Tant qu'il reste des voisins a trouver - while( stack.length > 0 ){ - var subneighbors = this.allNeighborsIndex[stack[0]]; - for( var subId in subneighbors ) - // Si le voisin est pas deja dans la liste/pile, on l'ajoute a la liste des voisins - if( neighbors[subId] == null ){ - stack.push(subId); // On ajoute a la pile - neighbors[subId] = subneighbors[subId]; // On ajoute a la liste complete - } - - stack.shift(); - } - - - // On retourne le resultat - return neighbors; -}; - - - - - - - - - -/* RENVOIE LA LISTE DES VOISINS DIRECTS D'UN NOEUD DONNÉ -* -* @nodeId Id du noeud en question -* -* @return neighbors Liste des voisins directs -* -*/ -sociogramClass.prototype.overload.nodeDirectNeighbors = function(nodeId){ - return this.allNeighborsIndex[nodeId]; -}; - - - - - - - - - -/* EXTRAIT LES NOEUDS DE LA RÉPONSE DE L'API -* -*/ -sociogramClass.prototype.extractNodesFromResponse = function(){ - this.nodes = []; - - // Pour chaque alter - for( var i = 0 ; i < this.response.data.alter.length ; i++ ) - this.nodes.push({ - 'id': 'n-'+this.response.data.alter[i][0], - 'label': this.response.data.alter[i][1], - 'x': 0, - 'y': 0, - 'size': this.response.data.alter[i][2] - }); - -}; - - - - - - - - - -/* EXTRAIT LES NOEUDS DE LA RÉPONSE DE L'API -* -*/ -sociogramClass.prototype.extractEdgesFromResponse = function(){ - this.edges = []; - - // Pour chaque inter - for( var i = 0 ; i < this.response.data.inter.length ; i++ ) - this.edges.push({ - 'id': 'e-'+this.response.data.inter[i][0]+'-'+this.response.data.inter[i][1], - 'source': 'n-'+this.response.data.inter[i][0], - 'target': 'n-'+this.response.data.inter[i][1] - }); -}; - - - - - - - - - -/* AJOUTE LES NOEUDS AU RENDU (SIGMA GRAPH) -* -*/ -sociogramClass.prototype.addNodes = function(){ - /* (5) On ajoute nos noeuds */ - for( var i = 0 ; i < this.nodes.length ; i++) - this.sigma.graph.addNode(this.nodes[i]); -}; - - - - - - - - -/* AJOUTE LES LIENS AU RENDU (SIGMA GRAPH) -* -*/ -sociogramClass.prototype.addEdges = function(){ - /* (6) On ajoute nos liens */ - for( var i = 0 ; i < this.edges.length ; i++) - this.sigma.graph.addEdge(this.edges[i]); -}; - - - -/* FONCTION ACTIVÉE LORS DU CLIC SUR UN NOEUD -* -*/ -sociogramClass.prototype.bindings.clickNode = function(thisPtr, e){ - console.log( thisPtr ); - var nodeId = e.data.node.id; - // On recupere les voisins - var neighborNodes = thisPtr.sigma.graph.nodeNeighbors(nodeId); - neighborNodes[nodeId] = e.data.node; // on ajoute le noeud clique - - thisPtr.sigma.graph.nodes().forEach(function(n) { - if( neighborNodes[n.id] != null ) n.color = n.originalColor; - else n.color = '#888'; - }); - - thisPtr.sigma.refresh(); -}; - - - - -/* FONCTION ACTIVÉE LORS DU CLIC DANS LE VIDE -* -*/ -sociogramClass.prototype.bindings.clickStage = function(thisPtr, e){ - thisPtr.sigma.graph.nodes().forEach(function(n){ n.color = n.originalColor; }); - thisPtr.sigma.refresh(); -}; - - - - - - - - - - - - - - - - - -// var i, -// s, -// o, -// L = 10, -// N = 100, -// E = 500, -// g = { -// nodes: [], -// edges: [] -// }, -// step = 0; -// // Generate a random graph: -// for (i = 0; i < N; i++) { -// o = { -// id: 'n' + i, -// label: 'Node ' + i, -// circular_x: L * Math.cos(Math.PI * 2 * i / N - Math.PI / 2), -// circular_y: L * Math.sin(Math.PI * 2 * i / N - Math.PI / 2), -// circular_size: Math.random(), -// circular_color: '#' + ( -// Math.floor(Math.random() * 16777215).toString(16) + '000000' -// ).substr(0, 6), -// grid_x: i % L, -// grid_y: Math.floor(i / L), -// grid_size: 1, -// grid_color: '#ccc' -// }; - - - - - - - - -// ['x', 'y', 'size', 'color'].forEach(function(val) { -// o[val] = o['grid_' + val]; -// }); -// g.nodes.push(o); -// } -// for (i = 0; i < E; i++) -// g.edges.push({ -// id: 'e' + i, -// source: 'n' + (Math.random() * N | 0), -// target: 'n' + (Math.random() * N | 0), -// }); -// // Instantiate sigma: -// s = new sigma({ -// graph: g, -// renderer: { container: document.getElementById('graph-container'), 'type': 'canvas' }, -// settings: { -// animationsTime: 1000 -// } -// }); - -// setInterval(function(){ -// var prefix = ['grid_', 'circular_'][step = +!step]; -// sigma.plugins.animate( -// s, -// { -// x: prefix + 'x', -// y: prefix + 'y', -// size: prefix + 'size', -// color: prefix + 'color' -// } -// ); -// }, 10000); diff --git a/js/lib/sigma/README.md b/js/lib/sigma/README.md deleted file mode 100755 index c116782..0000000 --- a/js/lib/sigma/README.md +++ /dev/null @@ -1,35 +0,0 @@ -[![Build Status](https://travis-ci.org/jacomyal/sigma.js.svg)](https://travis-ci.org/jacomyal/sigma.js) - -sigma.js - v1.1.0 -================= - -Sigma is a JavaScript library dedicated to graph drawing, mainly developed by [@jacomyal](https://github.com/jacomyal) and [@Yomguithereal](https://github.com/Yomguithereal). - -### Resources - -[The website](http://sigmajs.org) provides a global overview of the project, and the documentation is available in the [Github Wiki](https://github.com/jacomyal/sigma.js/wiki). - -Also, the `plugins` and `examples` directories contain various use-cases that might help you understand how to use sigma. - -### How to use it - -To use it, clone the repository: - -``` -git clone git@github.com:jacomyal/sigma.js.git -``` - -To build the code: - - - Install [Node.js](http://nodejs.org/). - - Install [gjslint](https://developers.google.com/closure/utilities/docs/linter_howto?hl=en). - - Use `npm install` to install sigma development dependencies. - - Use `npm run build` to minify the code with [Uglify](https://github.com/mishoo/UglifyJS). The minified file `sigma.min.js` will then be accessible in the `build/` folder. - -Also, you can customize the build by adding or removing files from the `coreJsFiles` array in `Gruntfile.js` before applying the grunt task. - -### Contributing - -You can contribute by submitting [issues tickets](http://github.com/jacomyal/sigma.js/issues) and proposing [pull requests](http://github.com/jacomyal/sigma.js/pulls). Make sure that tests and linting pass before submitting any pull request by running the command `grunt`. - -The whole source code is validated by the [Google Closure Linter](https://developers.google.com/closure/utilities/) and [JSHint](http://www.jshint.com/), and the comments are written in [JSDoc](http://en.wikipedia.org/wiki/JSDoc) (tags description is available [here](https://developers.google.com/closure/compiler/docs/js-for-compiler)). diff --git a/js/lib/sigma/plugins/sigma-layout-forceAtlas2-min.js b/js/lib/sigma/plugins/sigma-layout-forceAtlas2-min.js deleted file mode 100755 index ae8bcee..0000000 --- a/js/lib/sigma/plugins/sigma-layout-forceAtlas2-min.js +++ /dev/null @@ -1 +0,0 @@ -(function(undefined){"use strict";function Supervisor(sigInst,options){var _this=this,workerFn=sigInst.getForceAtlas2Worker&&sigInst.getForceAtlas2Worker();if(options=options||{},_root.URL=_root.URL||_root.webkitURL,this.sigInst=sigInst,this.graph=this.sigInst.graph,this.ppn=10,this.ppe=3,this.config={},this.shouldUseWorker=options.worker===!1?!1:!0&&webWorkers,this.workerUrl=options.workerUrl,this.started=!1,this.running=!1,this.shouldUseWorker){if(this.workerUrl)this.worker=new Worker(this.workerUrl);else{var blob=this.makeBlob(workerFn);this.worker=new Worker(URL.createObjectURL(blob))}this.worker.postMessage=this.worker.webkitPostMessage||this.worker.postMessage}else eval(workerFn);this.msgName=this.worker?"message":"newCoords",this.listener=function(t){_this.nodesByteArray=new Float32Array(t.data.nodes),_this.running&&(_this.applyLayoutChanges(),_this.sendByteArrayToWorker(),_this.sigInst.refresh())},(this.worker||document).addEventListener(this.msgName,this.listener),this.graphToByteArrays(),sigInst.bind("kill",function(){sigInst.killForceAtlas2()})}if("undefined"==typeof sigma)throw"sigma is not declared";var _root=this,webWorkers="Worker"in _root;Supervisor.prototype.makeBlob=function(t){var e;try{e=new Blob([t],{type:"application/javascript"})}catch(s){_root.BlobBuilder=_root.BlobBuilder||_root.WebKitBlobBuilder||_root.MozBlobBuilder,e=new BlobBuilder,e.append(t),e=e.getBlob()}return e},Supervisor.prototype.graphToByteArrays=function(){var t,e,s,r=this.graph.nodes(),i=this.graph.edges(),o=r.length*this.ppn,n=i.length*this.ppe,a={};for(this.nodesByteArray=new Float32Array(o),this.edgesByteArray=new Float32Array(n),t=e=0,s=r.length;s>t;t++)a[r[t].id]=e,this.nodesByteArray[e]=r[t].x,this.nodesByteArray[e+1]=r[t].y,this.nodesByteArray[e+2]=0,this.nodesByteArray[e+3]=0,this.nodesByteArray[e+4]=0,this.nodesByteArray[e+5]=0,this.nodesByteArray[e+6]=1+this.graph.degree(r[t].id),this.nodesByteArray[e+7]=1,this.nodesByteArray[e+8]=r[t].size,this.nodesByteArray[e+9]=0,e+=this.ppn;for(t=e=0,s=i.length;s>t;t++)this.edgesByteArray[e]=a[i[t].source],this.edgesByteArray[e+1]=a[i[t].target],this.edgesByteArray[e+2]=i[t].weight||0,e+=this.ppe},Supervisor.prototype.applyLayoutChanges=function(){for(var t=this.graph.nodes(),e=0,s=0,r=this.nodesByteArray.length;r>s;s+=this.ppn)t[e].x=this.nodesByteArray[s],t[e].y=this.nodesByteArray[s+1],e++},Supervisor.prototype.sendByteArrayToWorker=function(t){var e={action:t||"loop",nodes:this.nodesByteArray.buffer},s=[this.nodesByteArray.buffer];"start"===t&&(e.config=this.config||{},e.edges=this.edgesByteArray.buffer,s.push(this.edgesByteArray.buffer)),this.shouldUseWorker?this.worker.postMessage(e,s):_root.postMessage(e,"*")},Supervisor.prototype.start=function(){if(!this.running){this.running=!0;var t,e;for(t in this.sigInst.cameras)e=this.sigInst.cameras[t],e.edgequadtree._enabled=!1;this.started?this.sendByteArrayToWorker():(this.sendByteArrayToWorker("start"),this.started=!0)}},Supervisor.prototype.stop=function(){if(this.running){var t,e,s;for(t in this.sigInst.cameras)e=this.sigInst.cameras[t],e.edgequadtree._enabled=!0,s=sigma.utils.getBoundaries(this.graph,e.readPrefix),e.settings("drawEdges")&&e.settings("enableEdgeHovering")&&e.edgequadtree.index(this.sigInst.graph,{prefix:e.readPrefix,bounds:{x:s.minX,y:s.minY,width:s.maxX-s.minX,height:s.maxY-s.minY}});this.running=!1}},Supervisor.prototype.killWorker=function(){this.worker?this.worker.terminate():(_root.postMessage({action:"kill"},"*"),document.removeEventListener(this.msgName,this.listener))},Supervisor.prototype.configure=function(t){if(this.config=t,this.started){var e={action:"config",config:this.config};this.shouldUseWorker?this.worker.postMessage(e):_root.postMessage(e,"*")}},sigma.prototype.startForceAtlas2=function(t){return this.supervisor||(this.supervisor=new Supervisor(this,t)),t&&this.supervisor.configure(t),this.supervisor.start(),this},sigma.prototype.stopForceAtlas2=function(){return this.supervisor?(this.supervisor.stop(),this):this},sigma.prototype.killForceAtlas2=function(){return this.supervisor?(this.supervisor.stop(),this.supervisor.killWorker(),this.supervisor=null,this):this},sigma.prototype.configForceAtlas2=function(t){return this.supervisor||(this.supervisor=new Supervisor(this,t)),this.supervisor.configure(t),this},sigma.prototype.isForceAtlas2Running=function(){return!!this.supervisor&&this.supervisor.running}}).call(this),function(undefined){"use strict";function no_crush(t){var e,s,r,i=["x","y","dx","dy","old_dx","old_dy","mass","convergence","size","fixed"],o=["source","target","weight"],n=["node","centerX","centerY","size","nextSibling","firstChild","mass","massCenterX","massCenterY"];for(s=0,r=n.length;r>s;s++)e=new RegExp("rp\\(([^,]*), '"+n[s]+"'\\)","g"),t=t.replace(e,0===s?"$1":"$1 + "+s);for(s=0,r=i.length;r>s;s++)e=new RegExp("np\\(([^,]*), '"+i[s]+"'\\)","g"),t=t.replace(e,0===s?"$1":"$1 + "+s);for(s=0,r=o.length;r>s;s++)e=new RegExp("ep\\(([^,]*), '"+o[s]+"'\\)","g"),t=t.replace(e,0===s?"$1":"$1 + "+s);return t}function getWorkerFn(){var t=crush?crush(Worker.toString()):Worker.toString();return";("+t+").call(this);"}var _root=this,inWebWorker=!("document"in _root),Worker=function(){function t(){var t,e,s={},r=arguments.length;for(t=r-1;t>=0;t--)for(e in arguments[t])s[e]=arguments[t][e];return s}function e(t){var e;for(e in t)"hasOwnProperty"in t&&!t.hasOwnProperty(e)||delete t[e];return t}function s(t,e,s){s=s||{};n=t,a=e,p.nodesLength=n.length,p.edgesLength=a.length,r(s)}function r(e){p.settings=t(e,p.settings)}function i(){var t,e,s,r,i,o,g,u,d,l,c,f,y,w,v;for(s=0;s=0)m=n[s]=0){if(w=Math.sqrt(Math.pow(n[s]-h[e+7],2)+Math.pow(n[s+1]-h[e+8],2)),2*h[e+3]/w0?(v=l*n[s+6]*h[e+6]/w/w,n[s+2]+=c*v,n[s+3]+=f*v):0>w&&(v=-l*n[s+6]*h[e+6]/w,n[s+2]+=c*v,n[s+3]+=f*v):w>0&&(v=l*n[s+6]*h[e+6]/w/w,n[s+2]+=c*v,n[s+3]+=f*v),h[e+4]<0)break;e=h[e+4];continue}e=h[e+5]}else{if(h[e]>=0&&h[e]!==s&&(c=n[s]-n[h[e]],f=n[s+1]-n[h[e]+1],w=Math.sqrt(c*c+f*f),p.settings.adjustSizes?w>0?(v=l*n[s+6]*n[h[e]+6]/w/w,n[s+2]+=c*v,n[s+3]+=f*v):0>w&&(v=-l*n[s+6]*n[h[e]+6]/w,n[s+2]+=c*v,n[s+3]+=f*v):w>0&&(v=l*n[s+6]*n[h[e]+6]/w/w,n[s+2]+=c*v,n[s+3]+=f*v)),h[e+4]<0)break;e=h[e+4]}else for(l=p.settings.scalingRatio,r=0;ri;i+=p.ppn)c=n[r]-n[i],f=n[r+1]-n[i+1],p.settings.adjustSizes?(w=Math.sqrt(c*c+f*f)-n[r+8]-n[i+8],w>0?(v=l*n[r+6]*n[i+6]/w/w,n[r+2]+=c*v,n[r+3]+=f*v,n[i+2]+=c*v,n[i+3]+=f*v):0>w&&(v=100*l*n[r+6]*n[i+6],n[r+2]+=c*v,n[r+3]+=f*v,n[i+2]-=c*v,n[i+3]-=f*v)):(w=Math.sqrt(c*c+f*f),w>0&&(v=l*n[r+6]*n[i+6]/w/w,n[r+2]+=c*v,n[r+3]+=f*v,n[i+2]-=c*v,n[i+3]-=f*v));for(u=p.settings.gravity/p.settings.scalingRatio,l=p.settings.scalingRatio,s=0;s0&&(v=l*n[s+6]*u):w>0&&(v=l*n[s+6]*u/w),n[s+2]-=c*v,n[s+3]-=f*v;for(l=1*(p.settings.outboundAttractionDistribution?d:1),o=0;o0&&(v=-l*y*Math.log(1+w)/w/n[r+6]):w>0&&(v=-l*y*Math.log(1+w)/w):p.settings.outboundAttractionDistribution?w>0&&(v=-l*y/n[r+6]):w>0&&(v=-l*y)):(w=Math.sqrt(Math.pow(c,2)+Math.pow(f,2)),p.settings.linLogMode?p.settings.outboundAttractionDistribution?w>0&&(v=-l*y*Math.log(1+w)/w/n[r+6]):w>0&&(v=-l*y*Math.log(1+w)/w):p.settings.outboundAttractionDistribution?(w=1,v=-l*y/n[r+6]):(w=1,v=-l*y)),w>0&&(n[r+2]+=c*v,n[r+3]+=f*v,n[i+2]-=c*v,n[i+3]-=f*v);var W,L,F,_;if(p.settings.adjustSizes)for(s=0;sp.maxForce&&(n[s+2]=n[s+2]*p.maxForce/W,n[s+3]=n[s+3]*p.maxForce/W),L=n[s+6]*Math.sqrt((n[s+4]-n[s+2])*(n[s+4]-n[s+2])+(n[s+5]-n[s+3])*(n[s+5]-n[s+3])),F=Math.sqrt((n[s+4]+n[s+2])*(n[s+4]+n[s+2])+(n[s+5]+n[s+3])*(n[s+5]+n[s+3]))/2,_=.1*Math.log(1+F)/(1+Math.sqrt(L)),n[s]=n[s]+n[s+2]*(_/p.settings.slowDown),n[s+1]=n[s+1]+n[s+3]*(_/p.settings.slowDown));else for(s=0;se;e++)i();g()}var n,a,h,g,p={ppn:10,ppe:3,ppr:9,maxForce:10,iterations:0,converged:!1,settings:{linLogMode:!1,outboundAttractionDistribution:!1,adjustSizes:!1,edgeWeightInfluence:0,scalingRatio:1,strongGravityMode:!1,gravity:1,slowDown:1,barnesHutOptimize:!1,barnesHutTheta:.5,startingIterations:1,iterationsPerRender:1}};g="undefined"!=typeof window&&window.document?function(){var t;document.createEvent?(t=document.createEvent("Event"),t.initEvent("newCoords",!0,!1)):(t=document.createEventObject(),t.eventType="newCoords"),t.eventName="newCoords",t.data={nodes:n.buffer},requestAnimationFrame(function(){document.dispatchEvent(t)})}:function(){self.postMessage({nodes:n.buffer},[n.buffer])};var u=function(t){switch(t.data.action){case"start":s(new Float32Array(t.data.nodes),new Float32Array(t.data.edges),t.data.config),o(p.settings.startingIterations);break;case"loop":n=new Float32Array(t.data.nodes),o(p.settings.iterationsPerRender);break;case"config":r(t.data.config);break;case"kill":e(p),n=null,a=null,h=null,self.removeEventListener("message",u)}};self.addEventListener("message",u)},crush=null;if(inWebWorker)eval(getWorkerFn());else{if("undefined"==typeof sigma)throw"sigma is not declared";sigma.prototype.getForceAtlas2Worker=getWorkerFn}}.call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma-parsers-json-min.js b/js/lib/sigma/plugins/sigma-parsers-json-min.js deleted file mode 100755 index f3de165..0000000 --- a/js/lib/sigma/plugins/sigma-parsers-json-min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.parsers"),sigma.utils.pkg("sigma.utils"),sigma.utils.xhr=function(){if(window.XMLHttpRequest)return new XMLHttpRequest;var a,b;if(window.ActiveXObject){a=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];for(b in a)try{return new ActiveXObject(a[b])}catch(c){}}return null},sigma.parsers.json=function(a,b,c){var d,e=sigma.utils.xhr();if(!e)throw"XMLHttpRequest not supported, cannot load the file.";e.open("GET",a,!0),e.onreadystatechange=function(){4===e.readyState&&(d=JSON.parse(e.responseText),b instanceof sigma?(b.graph.clear(),b.graph.read(d)):"object"==typeof b?(b.graph=d,b=new sigma(b)):"function"==typeof b&&(c=b,b=null),c&&c(b||d))},e.send()}}).call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma-plugins-animate-min.js b/js/lib/sigma/plugins/sigma-plugins-animate-min.js deleted file mode 100755 index 7e85d5d..0000000 --- a/js/lib/sigma/plugins/sigma-plugins-animate-min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";function a(a){if(d[a])return d[a];var b=[0,0,0];return a.match(/^#/)?(a=(a||"").replace(/^#/,""),b=3===a.length?[parseInt(a.charAt(0)+a.charAt(0),16),parseInt(a.charAt(1)+a.charAt(1),16),parseInt(a.charAt(2)+a.charAt(2),16)]:[parseInt(a.charAt(0)+a.charAt(1),16),parseInt(a.charAt(2)+a.charAt(3),16),parseInt(a.charAt(4)+a.charAt(5),16)]):a.match(/^ *rgba? *\(/)&&(a=a.match(/^ *rgba? *\( *([0-9]*) *, *([0-9]*) *, *([0-9]*) *(,.*)?\) *$/),b=[+a[1],+a[2],+a[3]]),d[a]={r:b[0],g:b[1],b:b[2]},d[a]}function b(b,c,d){b=a(b),c=a(c);var e={r:b.r*(1-d)+c.r*d,g:b.g*(1-d)+c.g*d,b:b.b*(1-d)+c.b*d};return"rgb("+[0|e.r,0|e.g,0|e.b].join(",")+")"}if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.plugins");var c=0,d={};sigma.plugins.animate=function(a,d,e){function f(){var c=(sigma.utils.dateNow()-m)/k;if(c>=1){g.forEach(function(a){for(var b in d)b in d&&(a[b]=a[d[b]])});var e,n;for(e in a.cameras)n=a.cameras[e],n.edgequadtree._enabled=!0;a.refresh(),"function"==typeof i.onComplete&&i.onComplete()}else c=l(c),g.forEach(function(a){for(var e in d)e in d&&(a[e]=e.match(/color$/)?b(h[a.id][e],a[d[e]],c):a[d[e]]*c+h[a.id][e]*(1-c))}),a.refresh(),a.animations[j]=requestAnimationFrame(f)}var g,h,i=e||{},j=++c,k=i.duration||a.settings("animationsTime"),l="string"==typeof i.easing?sigma.utils.easings[i.easing]:"function"==typeof i.easing?i.easing:sigma.utils.easings.quadraticInOut,m=sigma.utils.dateNow();g=i.nodes&&i.nodes.length?"object"==typeof i.nodes[0]?i.nodes:a.graph.nodes(i.nodes):a.graph.nodes(),h=g.reduce(function(a,b){var c;a[b.id]={};for(c in d)c in b&&(a[b.id][c]=b[c]);return a},{}),a.animations=a.animations||Object.create({}),sigma.plugins.kill(a);var n,o;for(n in a.cameras)o=a.cameras[n],o.edgequadtree._enabled=!1;f()},sigma.plugins.kill=function(a){for(var b in a.animations||{})cancelAnimationFrame(a.animations[b]);var b,c;for(b in a.cameras)c=a.cameras[b],c.edgequadtree._enabled=!0}}).call(window); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma-renderers-customShapes-min.js b/js/lib/sigma/plugins/sigma-renderers-customShapes-min.js deleted file mode 100755 index 3637688..0000000 --- a/js/lib/sigma/plugins/sigma-renderers-customShapes-min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";var a=[],b=function(b,c,d){a.push({name:b,drawShape:c,drawBorder:d})},c=function(){return a},d=function(a){return function(b,c,d,e,f,g){g.fillStyle=f,g.beginPath(),a(b,c,d,e,g),g.closePath(),g.fill()}},e=function(a){return function(b,c,d,e,f,g){g.strokeStyle=f,g.lineWidth=e/5,g.beginPath(),a(b,c,d,e,g),g.closePath(),g.stroke()}},f=function(a,b,c,d,e){var f=45*Math.PI/180;e.moveTo(b+d*Math.sin(f),c-d*Math.cos(f));for(var g=1;4>g;g++)e.lineTo(b+Math.sin(f+2*Math.PI*g/4)*d,c-Math.cos(f+2*Math.PI*g/4)*d)};b("square",d(f),e(f));var g=function(a,b,c,d,e){e.arc(b,c,d,0,2*Math.PI,!0)};b("circle",d(g),e(g));var h=function(a,b,c,d,e){e.moveTo(b-d,c),e.lineTo(b,c-d),e.lineTo(b+d,c),e.lineTo(b,c+d)};b("diamond",d(h),e(h));var i=function(a,b,c,d,e){var f=a.cross&&a.cross.lineWeight||5;e.moveTo(b-d,c-f),e.lineTo(b-d,c+f),e.lineTo(b-f,c+f),e.lineTo(b-f,c+d),e.lineTo(b+f,c+d),e.lineTo(b+f,c+f),e.lineTo(b+d,c+f),e.lineTo(b+d,c-f),e.lineTo(b+f,c-f),e.lineTo(b+f,c-d),e.lineTo(b-f,c-d),e.lineTo(b-f,c-f)};b("cross",d(i),e(i));var j=function(a,b,c,d,e){var f=a.equilateral&&a.equilateral.numPoints||5,g=(a.equilateral&&a.equilateral.rotate||0)*Math.PI/180,h=d;e.moveTo(b+h*Math.sin(g),c-h*Math.cos(g));for(var i=1;f>i;i++)e.lineTo(b+Math.sin(g+2*Math.PI*i/f)*h,c-Math.cos(g+2*Math.PI*i/f)*h)};b("equilateral",d(j),e(j));var k=function(a,b,c,d,e){var f=a.star&&a.star.numPoints||5,g=a.star&&a.star.innerRatio||.5,h=d,i=d*g,j=Math.PI/f;e.moveTo(b,c-d);for(var k=0;f>k;k++)e.lineTo(b+Math.sin(j+2*Math.PI*k/f)*i,c-Math.cos(j+2*Math.PI*k/f)*i),e.lineTo(b+Math.sin(2*Math.PI*(k+1)/f)*h,c-Math.cos(2*Math.PI*(k+1)/f)*h)};b("star",d(k),e(k));var l=function(a,b,c,d,e,f){f.fillStyle="yellow",f.beginPath(),f.arc(b,c,d,1.25*Math.PI,0,!1),f.arc(b,c,d,0,.75*Math.PI,!1),f.lineTo(b,c),f.closePath(),f.fill(),f.fillStyle="white",f.strokeStyle="black",f.beginPath(),f.arc(b+d/3,c-d/3,d/4,0,2*Math.PI,!1),f.closePath(),f.fill(),f.stroke(),f.fillStyle="black",f.beginPath(),f.arc(b+4*d/9,c-d/3,d/8,0,2*Math.PI,!1),f.closePath(),f.fill()};b("pacman",l,null),this.ShapeLibrary={enumerate:c,version:"0.1"}}).call(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";if("undefined"==typeof ShapeLibrary)throw"ShapeLibrary is not declared";sigma.utils.pkg("sigma.canvas.nodes"),sigma.utils.pkg("sigma.svg.nodes");var b=a,c={},d=function(a){b=a},e=function(a,d,e,f,g){if(b&&a.image&&a.image.url){var h=a.image.url,i=a.image.h||1,j=a.image.w||1,k=a.image.scale||1,l=a.image.clip||1,m=c[h];m||(m=document.createElement("IMG"),m.src=h,m.status="loading",m.onerror=function(){console.log("error loading",h),m.status="error"},m.onload=function(){console.log("redraw on image load",h),m.status="ok",b.refresh()},c[h]=m);var n=i>j?j/i:1,o=j>i?i/j:1,p=f*k;g.save(),g.beginPath(),g.arc(d,e,f*l,0,2*Math.PI,!0),g.closePath(),g.clip(),"ok"===m.status&&g.drawImage(m,d+Math.sin(-0.7855)*p*n,e-Math.cos(-0.7855)*p*o,p*n*2*Math.sin(-0.7855)*-1,p*o*2*Math.cos(-0.7855)),g.restore()}},f=function(a,c,d){if(b&&a.image&&a.image.url){{var e=document.createElementNS(d("xmlns"),"circle"),f=document.createElementNS(d("xmlns"),"clipPath"),g=d("classPrefix")+"-clip-path-"+a.id,h=document.createElementNS(d("xmlns"),"defs"),i=document.createElementNS(d("xmlns"),"image");a.image.url}f.setAttributeNS(null,"id",g),f.appendChild(e),h.appendChild(f);var j=/MSIE [5-9]/.test(navigator.userAgent)?"":document.location.href;j=j.split("#")[0],i.setAttributeNS(null,"class",d("classPrefix")+"-node-image"),i.setAttributeNS(null,"clip-path","url("+j+"#"+g+")"),i.setAttributeNS(null,"pointer-events","none"),i.setAttributeNS("http://www.w3.org/1999/xlink","href",a.image.url),c.appendChild(h),c.appendChild(i)}},g=function(a,b,c){sigma.canvas.nodes[a]=function(a,d,f){var g=f("prefix")||"",h=a[g+"size"],i=a.color||f("defaultNodeColor"),j=a.borderColor||i,k=a[g+"x"],l=a[g+"y"];d.save(),b&&b(a,k,l,h,i,d),c&&c(a,k,l,h,j,d),e(a,k,l,h,d),d.restore()},sigma.svg.nodes[a]={create:function(a,b){var c=document.createElementNS(b("xmlns"),"g"),d=document.createElementNS(b("xmlns"),"circle");return c.setAttributeNS(null,"class",b("classPrefix")+"-node-group"),c.setAttributeNS(null,"data-node-id",a.id),d.setAttributeNS(null,"data-node-id",a.id),d.setAttributeNS(null,"class",b("classPrefix")+"-node"),d.setAttributeNS(null,"fill",a.color||b("defaultNodeColor")),c.appendChild(d),f(a,c,b),c},update:function(a,b,c){for(var d=c("classPrefix"),e=a.image.clip||1,f=a.image.h||1,g=a.image.w||1,h=c("prefix")||"",i=a.image.scale||1,j=a[h+"size"],k=a[h+"x"],l=a[h+"y"],m=i*j,n=f>g?g/f:1,o=g>f?f/g:1,p=0,q=b.childNodes;p [class="'+b+'-node"]');for(h=0,i=n.length,g=!0;i>h;h++)d=n[h].getAttribute("fill"),c.data||n[h].removeAttribute("data-node-id"),c.classes&&(d in j||(j[d]=g?b+"-node":"c-"+l++,m+="."+j[d]+"{fill: "+d+"}"),j[d]!==b+"-node"&&n[h].setAttribute("class",n[h].getAttribute("class")+" "+j[d]),n[h].removeAttribute("fill")),g=!1;var o=a.querySelectorAll('[id="'+b+'-group-edges"] > [class="'+b+'-edge"]');for(h=0,i=o.length,g=!0;i>h;h++)d=o[h].getAttribute("stroke"),c.data||o[h].removeAttribute("data-edge-id"),c.classes&&(d in k||(k[d]=g?b+"-edge":"c-"+l++,m+="."+k[d]+"{stroke: "+d+"}"),k[d]!==b+"-edge"&&o[h].setAttribute("class",o[h].getAttribute("class")+" "+k[d]),o[h].removeAttribute("stroke")),g=!1;c.classes&&e.appendChild(document.createTextNode(m))}if("undefined"==typeof sigma)throw"sigma.renderers.snapshot: sigma not in scope.";var d=this.URL||this.webkitURL||this,e={size:"1000",width:"1000",height:"1000",classes:!0,labels:!0,data:!1,download:!1,filename:"graph.svg"},f="http://www.w3.org/2000/svg";sigma.prototype.toSVG=function(a){a=a||{};var d=this.settings("classPrefix"),f=a.size||a.width||e.size,g=a.size||a.height||e.size,h=document.createElement("div");h.setAttribute("width",f),h.setAttribute("height",g),h.setAttribute("style","position:absolute; top: 0px; left:0px; width: "+f+"px; height: "+g+"px;");var i=this.addCamera(),j=this.addRenderer({camera:i,container:h,type:"svg",forceLabels:!!a.labels});j.resize(f,g),this.refresh(),this.killRenderer(j),this.killCamera(i);var k=h.querySelector("svg");if(k.removeAttribute("style"),k.setAttribute("width",f+"px"),k.setAttribute("height",g+"px"),k.setAttribute("x","0px"),k.setAttribute("y","0px"),!a.labels){var l=k.querySelector('[id="'+d+'-group-labels"]');k.removeChild(l)}var m=k.querySelector('[id="'+d+'-group-hovers"]');k.removeChild(m),a.classes=a.classes!==!1,(!a.data||a.classes)&&c(k,d,a);var n=k.outerHTML;h=null;var o='\n';return o+='\n',o+=n,a.download&&b(o,a.filename||e.filename),o}}).call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.neo4j.cypher.min.js b/js/lib/sigma/plugins/sigma.neo4j.cypher.min.js deleted file mode 100755 index cfd6ef3..0000000 --- a/js/lib/sigma/plugins/sigma.neo4j.cypher.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.neo4j"),sigma.utils.pkg("sigma.utils"),sigma.neo4j.send=function(a,b,c,d,e){var f,g,h,i=sigma.utils.xhr();if(f=a,"object"==typeof a&&(f=a.url,g=a.user,h=a.password),!i)throw"XMLHttpRequest not supported, cannot load the file.";f+=b,i.open(c,f,!0),g&&h&&i.setRequestHeader("Authorization","Basic "+btoa(g+":"+h)),i.setRequestHeader("Accept","application/json"),i.setRequestHeader("Content-type","application/json; charset=utf-8"),i.onreadystatechange=function(){4===i.readyState&&e(JSON.parse(i.responseText))},i.send(d)},sigma.neo4j.cypher_parse=function(a){var b,c={nodes:[],edges:[]},d={},e={};a.results[0].data.forEach(function(a){a.graph.nodes.forEach(function(a){var b={id:a.id,label:a.id,x:Math.random(),y:Math.random(),size:1,color:"#000000",neo4j_labels:a.labels,neo4j_data:a.properties};b.id in d||(d[b.id]=b)}),a.graph.relationships.forEach(function(a){var b={id:a.id,label:a.type,source:a.startNode,target:a.endNode,color:"#7D7C8E",neo4j_type:a.type,neo4j_data:a.properties};b.id in e||(e[b.id]=b)})});for(b in d)c.nodes.push(d[b]);for(b in e)c.edges.push(e[b]);return c},sigma.neo4j.cypher=function(a,b,c,d){var e,f,g="/db/data/transaction/commit";e=JSON.stringify({statements:[{statement:b,resultDataContents:["graph"],includeStats:!1}]}),f=function(a){return function(b){var d={nodes:[],edges:[]};d=sigma.neo4j.cypher_parse(b),c instanceof sigma?(c.graph.clear(),c.graph.read(d)):"object"==typeof c?(c=new sigma(c),c.graph.read(d),c.refresh()):"function"==typeof c&&(a=c,c=null),a&&a(c||d)}},sigma.neo4j.send(a,g,"POST",e,f(d))},sigma.neo4j.getLabels=function(a,b){sigma.neo4j.send(a,"/db/data/labels","GET",null,b)},sigma.neo4j.getTypes=function(a,b){sigma.neo4j.send(a,"/db/data/relationship/types","GET",null,b)}}).call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.parsers.cypher.min.js b/js/lib/sigma/plugins/sigma.parsers.cypher.min.js deleted file mode 100755 index 5162aae..0000000 --- a/js/lib/sigma/plugins/sigma.parsers.cypher.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.neo4j"),sigma.utils.pkg("sigma.utils"),sigma.utils.pkg("sigma.parsers"),sigma.neo4j.send=function(a,b,c,d,e){var f,g,h,i=sigma.utils.xhr();if(f=a,"object"==typeof a&&(f=a.url,g=a.user,h=a.password),!i)throw"XMLHttpRequest not supported, cannot load the file.";f+=b,i.open(c,f,!0),g&&h&&i.setRequestHeader("Authorization","Basic "+btoa(g+":"+h)),i.setRequestHeader("Accept","application/json"),i.setRequestHeader("Content-type","application/json; charset=utf-8"),i.onreadystatechange=function(){4===i.readyState&&e(JSON.parse(i.responseText))},i.send(d)},sigma.neo4j.cypher_parse=function(a){var b,c={nodes:[],edges:[]},d={},e={};a.results[0].data.forEach(function(a){a.graph.nodes.forEach(function(a){var b={id:a.id,label:a.id,x:Math.random(),y:Math.random(),size:1,color:"#000000",neo4j_labels:a.labels,neo4j_data:a.properties};b.id in d||(d[b.id]=b)}),a.graph.relationships.forEach(function(a){var b={id:a.id,label:a.type,source:a.startNode,target:a.endNode,color:"#7D7C8E",neo4j_type:a.type,neo4j_data:a.properties};b.id in e||(e[b.id]=b)})});for(b in d)c.nodes.push(d[b]);for(b in e)c.edges.push(e[b]);return c},sigma.neo4j.cypher=function(a,b,c,d){var e,f,g="/db/data/transaction/commit";e=JSON.stringify({statements:[{statement:b,resultDataContents:["graph"],includeStats:!1}]}),f=function(a){return function(b){var d={nodes:[],edges:[]};d=sigma.neo4j.cypher_parse(b),c instanceof sigma?(c.graph.clear(),c.graph.read(d)):"object"==typeof c?(c=new sigma(c),c.graph.read(d),c.refresh()):"function"==typeof c&&(a=c,c=null),a&&a(c||d)}},sigma.neo4j.send(a,g,"POST",e,f(d))},sigma.neo4j.getLabels=function(a,b){sigma.neo4j.send(a,"/db/data/labels","GET",null,b)},sigma.neo4j.getTypes=function(a,b){sigma.neo4j.send(a,"/db/data/relationship/types","GET",null,b)}}).call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.parsers.gexf.min.js b/js/lib/sigma/plugins/sigma.parsers.gexf.min.js deleted file mode 100755 index 4136124..0000000 --- a/js/lib/sigma/plugins/sigma.parsers.gexf.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(a){"use strict";function b(a){var b={id:a.id,label:a.label};return a.viz&&(b.viz=a.viz),a.attributes&&(b.attributes=a.attributes),b}function c(a){var b={id:a.id,type:a.type||"undirected",label:a.label||"",source:a.source,target:a.target,weight:+a.weight||1};return a.viz&&(b.viz=a.viz),a.attributes&&(b.attributes=a.attributes),b}function d(a){function d(){var a={};return l.els.meta?(a.lastmodifieddate=l.els.meta.getAttribute("lastmodifieddate"),h.nodeListEach(l.els.meta.childNodes,function(b){a[b.tagName.toLowerCase()]=b.textContent}),a):a}function e(a){var b=[];return l.els.model[a]&&h.nodeListEach(l.els.model[a],function(a){var c={id:a.getAttribute("id")||a.getAttribute("for"),type:a.getAttribute("type")||"string",title:a.getAttribute("title")||""},d=h.nodeListToArray(a.childNodes);d.length>0&&(c.defaultValue=d[0].textContent),b.push(c)}),b.length>0?b:!1}function f(a,b){var c={},d=b.getElementsByTagName("attvalue"),e=h.nodeListToHash(d,function(a){var b=h.namedNodeMapToObject(a.attributes),c=b.id||b["for"];return{key:c,value:b.value}});return a.map(function(a){c[a.id]=!(a.id in e)&&"defaultValue"in a?h.enforceType(a.type,a.defaultValue):h.enforceType(a.type,e[a.id])}),c}function g(a){var c=[];return h.nodeListEach(l.els.nodes,function(d){var e={id:d.getAttribute("id"),label:d.getAttribute("label")||""};a&&(e.attributes=f(a,d)),l.hasViz&&(e.viz=i(d)),c.push(b(e))}),c}function i(a){var b={},c=h.getFirstElementByTagNS(a,"viz","color");if(c){var d=["r","g","b","a"].map(function(a){return c.getAttribute(a)});b.color=h.getRGB(d)}var e=h.getFirstElementByTagNS(a,"viz","position");e&&(b.position={},["x","y","z"].map(function(a){b.position[a]=+e.getAttribute(a)}));var f=h.getFirstElementByTagNS(a,"viz","size");f&&(b.size=+f.getAttribute("value"));var g=h.getFirstElementByTagNS(a,"viz","shape");return g&&(b.shape=g.getAttribute("value")),b}function j(a,b){var d=[];return h.nodeListEach(l.els.edges,function(e){var g=h.namedNodeMapToObject(e.attributes);"type"in g||(g.type=b),a&&(g.attributes=f(a,e)),l.hasViz&&(g.viz=k(e)),d.push(c(g))}),d}function k(a){var b={},c=h.getFirstElementByTagNS(a,"viz","color");if(c){var d=["r","g","b","a"].map(function(a){return c.getAttribute(a)});b.color=h.getRGB(d)}var e=h.getFirstElementByTagNS(a,"viz","shape");e&&(b.shape=e.getAttribute("value"));var f=h.getFirstElementByTagNS(a,"viz","thickness");return f&&(b.thickness=+f.getAttribute("value")),b}var l={};l.els={root:a.getElementsByTagName("gexf")[0],graph:a.getElementsByTagName("graph")[0],meta:a.getElementsByTagName("meta")[0],nodes:a.getElementsByTagName("node"),edges:a.getElementsByTagName("edge"),model:h.getModelTags(a)},l.hasViz=!!h.getAttributeNS(l.els.root,"xmlns","viz"),l.version=l.els.root.getAttribute("version")||"1.0",l.mode=l.els.graph.getAttribute("mode")||"static";var m=l.els.graph.getAttribute("defaultedgetype");l.defaultEdgetype=m||"undirected";var n=e("node"),o=e("edge"),p={version:l.version,mode:l.mode,defaultEdgeType:l.defaultEdgetype,meta:d(),model:{},nodes:g(n),edges:j(o,l.defaultEdgetype)};return n&&(p.model.node=n),o&&(p.model.edge=o),p}function e(a,b){var c=function(){if(window.XMLHttpRequest)return new XMLHttpRequest;var a,b;if(window.ActiveXObject){a=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];for(b in a)try{return new ActiveXObject(a[b])}catch(c){}}return null}();if(!c)throw"XMLHttpRequest not supported, cannot load the file.";var d,e="function"==typeof b;return c.overrideMimeType?(c.overrideMimeType("text/xml"),d=function(a){return a.responseXML}):d=function(a){var b=new DOMParser;return b.parseFromString(a.responseText,"application/xml")},c.open("GET",a,e),e&&(c.onreadystatechange=function(){4===c.readyState&&b(d(c))}),c.send(),e?c:d(c)}function f(a){return d(a)}function g(a,b){return"function"==typeof b?e(a,function(a){b(d(a))}):d(e(a))}var h={getModelTags:function(a){var b,c=a.getElementsByTagName("attributes"),d={},e=c.length;for(b=0;e>b;b++)d[c[b].getAttribute("class")]=c[b].childNodes;return d},nodeListToArray:function(a){for(var b=[],c=0,d=a.length;d>c;++c)"#text"!==a[c].nodeName&&b.push(a[c]);return b},nodeListEach:function(a,b){for(var c=0,d=a.length;d>c;++c)"#text"!==a[c].nodeName&&b(a[c])},nodeListToHash:function(a,b){for(var c={},d=0;df;f++)i=h[f],i.id=i.id,i.viz&&"object"==typeof i.viz&&(i.viz.position&&"object"==typeof i.viz.position&&(i.x=i.viz.position.x,i.y=-i.viz.position.y),i.size=i.viz.size,i.color=i.viz.color);for(h=b.edges,f=0,g=h.length;g>f;f++)i=h[f],i.id="string"==typeof i.id?i.id:a(),i.source=""+i.source,i.target=""+i.target,i.viz&&"object"==typeof i.viz&&(i.color=i.viz.color,i.size=i.viz.thickness),i.size=i.weight,i.direction=i.type,delete i.type;if(c instanceof sigma){for(c.graph.clear(),h=b.nodes,f=0,g=h.length;g>f;f++)c.graph.addNode(h[f]);for(h=b.edges,f=0,g=h.length;g>f;f++)c.graph.addEdge(h[f])}else"object"==typeof c?(c.graph=b,c=new sigma(c)):"function"==typeof c&&(d=c,c=null);return d?void d(c||b):b}var f,g,h,i;if("string"==typeof b)gexf.fetch(b,e);else if("object"==typeof b)return e(gexf.parse(b))}}.call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.pathfinding.astar.min.js b/js/lib/sigma/plugins/sigma.pathfinding.astar.min.js deleted file mode 100755 index a0de15e..0000000 --- a/js/lib/sigma/plugins/sigma.pathfinding.astar.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";var a=function(a,b,c){var d=void 0!=a&&void 0!=b&&void 0!=a.x&&void 0!=a.y&&void 0!=b.x&&void 0!=b.y;return d?(c||0)+Math.sqrt(Math.pow(b.y-a.y,2)+Math.pow(b.x-a.x,2)):void 0};sigma.classes.graph.addMethod("astar",function(b,c,d){var e=new sigma.classes.configurable({undirected:!1,pathLengthFunction:a,heuristicLengthFunction:void 0},d||{}),f=e("pathLengthFunction"),g=e("heuristicLengthFunction")||f,h=this.nodes(b),i=this.nodes(c),j={},k=[],l=function(a,b,c,d){var e=a.id,f={pathLength:c,heuristicLength:d,node:a,nodeId:e,previousNode:b};if(void 0==j[e]||j[e].pathLenth>c){j[e]=f;var g,h;for(h=0;hd));h++);k.splice(h,0,f)}};l(h,null,0,0);var m,n=!1;m=e("undirected")?this.allNeighborsIndex:this.outNeighborsIndex;for(var o,p,q,r,s,t;k.length>0;){if(o=k.shift(),o.nodeId==c){n=!0;break}for(p=Object.keys(m[o.nodeId]),t=0;t1){n.removeEventListener("mousedown",g),l.addEventListener("mousemove",i),l.addEventListener("mouseup",h);var c,d;for(c in k.cameras)d=k.cameras[c],void 0!==d.edgequadtree&&(d.edgequadtree._enabled=!1);m.settings({mouseEnabled:!1,enableHovering:!1}),k.refresh(),j.dispatchEvent("startdrag",{node:p,captor:a,renderer:m})}}function h(a){t=!1,n.addEventListener("mousedown",g),l.removeEventListener("mousemove",i),l.removeEventListener("mouseup",h);var b,c;for(b in k.cameras)c=k.cameras[b],void 0!==c.edgequadtree&&(c.edgequadtree._enabled=!0);m.settings({mouseEnabled:!0,enableHovering:!0}),k.refresh(),u&&j.dispatchEvent("drop",{node:p,captor:a,renderer:m}),j.dispatchEvent("dragend",{node:p,captor:a,renderer:m}),u=!1,p=null}function i(a){function b(){for(var b=c(m.container),d=a.clientX-b.left,e=a.clientY-b.top,f=Math.cos(o.angle),g=Math.sin(o.angle),h=k.graph.nodes(),i=[],l=0;2>l;l++){var n=h[l],r={x:n.x*f+n.y*g,y:n.y*f-n.x*g,renX:n[q+"x"],renY:n[q+"y"]};i.push(r)}if(i[0].x===i[1].x&&i[0].y===i[1].y){var s=0===i[0].renX?1:i[0].renX,t=0===i[0].renY?1:i[0].renY;d=i[0].x/s*(d-i[0].renX)+i[0].x,e=i[0].y/t*(e-i[0].renY)+i[0].y}else{var s=(i[1].renX-i[0].renX)/(i[1].x-i[0].x),t=(i[1].renY-i[0].renY)/(i[1].y-i[0].y);i[1].x===i[0].x&&(s=t),i[1].y===i[0].y&&(t=s),d=(d-i[0].renX)/s+i[0].x,e=(e-i[0].renY)/t+i[0].y}p.x=d*f-e*g,p.y=e*f+d*g,k.refresh(),u=!0,j.dispatchEvent("drag",{node:p,captor:a,renderer:m})}if(navigator.userAgent.toLowerCase().indexOf("firefox")>-1){clearTimeout(d);var d=setTimeout(b,0)}else b()}sigma.classes.dispatcher.extend(this);var j=this,k=a,l=document.body,m=b,n=b.container.lastChild,o=b.camera,p=null,q="",r=[],s={},t=!1,u=!1;b instanceof sigma.renderers.svg&&(n=b.container.firstChild),q=b instanceof sigma.renderers.webgl?b.options.prefix.substr(5):b.options.prefix,b.bind("overNode",e),b.bind("outNode",f),b.bind("click",d),k.bind("kill",function(){j.unbindAll()}),this.unbindAll=function(){n.removeEventListener("mousedown",g),l.removeEventListener("mousemove",i),l.removeEventListener("mouseup",h),m.unbind("overNode",e),m.unbind("outNode",f)}}if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.plugins");var b={};sigma.plugins.dragNodes=function(c,d){return b[c.id]||(b[c.id]=new a(c,d)),c.bind("kill",function(){sigma.plugins.killDragNodes(c)}),b[c.id]},sigma.plugins.killDragNodes=function(c){b[c.id]instanceof a&&(b[c.id].unbindAll(),delete b[c.id])}}).call(window); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.plugins.filter.min.js b/js/lib/sigma/plugins/sigma.plugins.filter.min.js deleted file mode 100755 index 9de5db5..0000000 --- a/js/lib/sigma/plugins/sigma.plugins.filter.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(undefined){"use strict";function register(a,b,c){if(c!=undefined&&"string"!=typeof c)throw'The filter key "'+c.toString()+'" must be a string.';if(c!=undefined&&!c.length)throw"The filter key must be a non-empty string.";if("function"!=typeof a)throw'The predicate of key "'+c+'" must be a function.';if("undo"===c)throw'"undo" is a reserved key.';if(_keysIndex[c])throw'The filter "'+c+'" already exists.';c&&(_keysIndex[c]=!0),_chain.push({key:c,processor:a,predicate:b})}function unregister(a){_chain=_chain.filter(function(b){return!(b.key in a)});for(var b in a)delete _keysIndex[b]}function Filter(a){_s=a,_g=a.graph}function deepCopy(o){var copy=Object.create(null);for(var i in o)"object"==typeof o[i]&&null!==o[i]?copy[i]=deepCopy(o[i]):"function"==typeof o[i]&&null!==o[i]?eval(" copy[i] = "+o[i].toString()):copy[i]=o[i];return copy}function cloneChain(a){for(var b=a.slice(0),c=0,d=b.length;d>c;c++)b[c]=deepCopy(b[c]),"function"==typeof b[c].processor&&(b[c].processor="filter.processors."+b[c].processor.name);return b}if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.plugins"),sigma.classes.graph.hasMethod("adjacentNodes")||sigma.classes.graph.addMethod("adjacentNodes",function(a){if("string"!=typeof a)throw"adjacentNodes: the node id must be a string.";var b,c=[];for(b in this.allNeighborsIndex[a])c.push(this.nodesIndex[b]);return c}),sigma.classes.graph.hasMethod("adjacentEdges")||sigma.classes.graph.addMethod("adjacentEdges",function(a){if("string"!=typeof a)throw"adjacentEdges: the node id must be a string.";var b,c,d=this.allNeighborsIndex[a],e=[];for(c in d)for(b in d[c])e.push(d[c][b]);return e});var _g=undefined,_s=undefined,_chain=[],_keysIndex=Object.create(null),Processors={};Processors.nodes=function(a){for(var b=_g.nodes(),c=b.length,d=_g.edges(),e=d.length;c--;)b[c].hidden=!a.call(_g,b[c])||b[c].hidden;for(;e--;)(_g.nodes(d[e].source).hidden||_g.nodes(d[e].target).hidden)&&(d[e].hidden=!0)},Processors.edges=function(a){for(var b=_g.edges(),c=b.length;c--;)b[c].hidden=!a.call(_g,b[c])||b[c].hidden},Processors.neighbors=function a(b){for(var c=_g.nodes(),d=c.length,e=_g.edges(),f=e.length,a=_g.adjacentNodes(b),g=a.length,h={};g--;)h[a[g].id]=!0;for(;d--;)c[d].id===b||c[d].id in h||(c[d].hidden=!0);for(;f--;)(_g.nodes(e[f].source).hidden||_g.nodes(e[f].target).hidden)&&(e[f].hidden=!0)},Filter.prototype.nodesBy=function(a,b){return register(Processors.nodes,a,b),this},Filter.prototype.edgesBy=function(a,b){return register(Processors.edges,a,b),this},Filter.prototype.neighborsOf=function(a,b){if("string"!=typeof a)throw'The node id "'+a.toString()+'" must be a string.';if(!a.length)throw"The node id must be a non-empty string.";return register(Processors.neighbors,a,b),this},Filter.prototype.apply=function(){for(var a=0,b=_chain.length;b>a;++a)_chain[a].processor(_chain[a].predicate);return _chain[0]&&"undo"===_chain[0].key&&_chain.shift(),_s.refresh(),this},Filter.prototype.undo=function(a){function b(){for(var a=_g.nodes(),b=a.length,c=_g.edges(),d=c.length;b--;)a[b].hidden=!1;for(;d--;)c[d].hidden=!1}var c=Object.create(null),d=arguments.length;if(1===d)if("[object Array]"===Object.prototype.toString.call(a))for(var e=0,f=a.length;f>e;e++)c[a[e]]=!0;else c[a]=!0;else if(d>1)for(var e=0;d>e;e++)c[arguments[e]]=!0;else this.clear();return unregister(c),_chain.unshift({key:"undo",processor:b}),this},Filter.prototype.clear=function(){return _chain.length=0,_keysIndex=Object.create(null),this},Filter.prototype.export=function(){var a=cloneChain(_chain);return a},Filter.prototype.import=function(a){if(a===undefined)throw"Wrong arguments.";if("[object Array]"!==Object.prototype.toString.call(a))throw'The chain" must be an array.';for(var b=cloneChain(a),c=0,d=b.length;d>c;c++){if(b[c].predicate===undefined||b[c].processor===undefined)throw"Wrong arguments.";if(b[c].key!=undefined&&"string"!=typeof b[c].key)throw'The filter key "'+b[c].key.toString()+'" must be a string.';if("function"!=typeof b[c].predicate)throw'The predicate of key "'+b[c].key+'" must be a function.';if("string"!=typeof b[c].processor)throw'The processor of key "'+b[c].key+'" must be a string.';switch(b[c].processor){case"filter.processors.nodes":b[c].processor=Processors.nodes;break;case"filter.processors.edges":b[c].processor=Processors.edges;break;case"filter.processors.neighbors":b[c].processor=Processors.neighbors;break;default:throw"Unknown processor "+b[c].processor}}return _chain=b,this};var filter=null;sigma.plugins.filter=function(a){return filter||(filter=new Filter(a)),filter}}).call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.plugins.neighborhoods.min.js b/js/lib/sigma/plugins/sigma.plugins.neighborhoods.min.js deleted file mode 100755 index aa6ddc7..0000000 --- a/js/lib/sigma/plugins/sigma.plugins.neighborhoods.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.classes.graph.addMethod("neighborhood",function(a){var b,c,d,e,f,g={},h={},i={nodes:[],edges:[]};if(!this.nodes(a))return i;e=this.nodes(a),f={},f.center=!0;for(b in e)f[b]=e[b];g[a]=!0,i.nodes.push(f);for(b in this.allNeighborsIndex[a]){g[b]||(g[b]=!0,i.nodes.push(this.nodesIndex[b]));for(c in this.allNeighborsIndex[a][b])h[c]||(h[c]=!0,i.edges.push(this.edgesIndex[c]))}for(b in g)if(b!==a)for(c in g)if(c!==a&&b!==c&&this.allNeighborsIndex[b][c])for(d in this.allNeighborsIndex[b][c])h[d]||(h[d]=!0,i.edges.push(this.edgesIndex[d]));return i}),sigma.utils.pkg("sigma.plugins"),sigma.plugins.neighborhoods=function(){var a=new sigma.classes.graph;this.neighborhood=function(b){return a.neighborhood(b)},this.load=function(b,c){var d=function(){if(window.XMLHttpRequest)return new XMLHttpRequest;var a,b;if(window.ActiveXObject){a=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];for(b in a)try{return new ActiveXObject(a[b])}catch(c){}}return null}();if(!d)throw"XMLHttpRequest not supported, cannot load the data.";return d.open("GET",b,!0),d.onreadystatechange=function(){4===d.readyState&&(a.clear().read(JSON.parse(d.responseText)),c&&c())},d.send(),this},this.read=function(b){a.clear().read(b)}}}).call(window); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.plugins.relativeSize.min.js b/js/lib/sigma/plugins/sigma.plugins.relativeSize.min.js deleted file mode 100755 index c58b068..0000000 --- a/js/lib/sigma/plugins/sigma.plugins.relativeSize.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.plugins");sigma.plugins.relativeSize=function(a,b){for(var c=a.graph.nodes(),d=0;dl?1:-1,s={},t=.5;b.id===c.id?(s=sigma.utils.getSelfLoopControlPoints(l,m,k),i=sigma.utils.getPointOnBezierCurve(t,l,m,n,o,s.x1,s.y1,s.x2,s.y2),j=Math.atan2(1,1)):(s=sigma.utils.getQuadraticControlPoint(l,m,n,o),i=sigma.utils.getPointOnQuadraticCurve(t,l,m,n,o,s.x,s.y),j=Math.atan2(q*r,p*r)),h="fixed"===e("edgeLabelSize")?e("defaultEdgeLabelSize"):e("defaultEdgeLabelSize")*g*Math.pow(g,-1/e("edgeLabelSizePowRatio")),d.save(),a.active?(d.font=[e("activeFontStyle"),h+"px",e("activeFont")||e("font")].join(" "),d.fillStyle="edge"===e("edgeActiveColor")?a.active_color||e("defaultEdgeActiveColor"):e("defaultEdgeLabelActiveColor")):(d.font=[e("fontStyle"),h+"px",e("font")].join(" "),d.fillStyle="edge"===e("edgeLabelColor")?a.color||e("defaultEdgeColor"):e("defaultEdgeLabelColor")),d.textAlign="center",d.textBaseline="alphabetic",d.translate(i.x,i.y),d.rotate(j),d.fillText(a.label,0,-g/2-3),d.restore()}}}}.call(this),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.canvas.edges.labels"),sigma.canvas.edges.labels.curvedArrow=function(a,b,c,d,e){sigma.canvas.edges.labels.curve(a,b,c,d,e)}}.call(this),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.canvas.edges.labels"),sigma.canvas.edges.labels.def=function(a,b,c,d,e){if("string"==typeof a.label&&b!=c){var f=e("prefix")||"",g=a[f+"size"]||1;if(!(gm?1:-1,t={},u=.5;b.id===c.id?(t=sigma.utils.getSelfLoopControlPoints(m,n,k,l),i=sigma.utils.getPointOnBezierCurve(u,m,n,o,p,t.x1,t.y1,t.x2,t.y2),j=Math.atan2(1,1)):(t=sigma.utils.getQuadraticControlPoint(m,n,o,p,l),i=sigma.utils.getPointOnQuadraticCurve(u,m,n,o,p,t.x,t.y),j=Math.atan2(r*s,q*s)),h="fixed"===e("edgeLabelSize")?e("defaultEdgeLabelSize"):e("defaultEdgeLabelSize")*g*Math.pow(g,-1/e("edgeLabelSizePowRatio")),d.save(),a.active?(d.font=[e("activeFontStyle"),h+"px",e("activeFont")||e("font")].join(" "),d.fillStyle="edge"===e("edgeActiveColor")?a.active_color||e("defaultEdgeActiveColor"):e("defaultEdgeLabelActiveColor")):(d.font=[e("fontStyle"),h+"px",e("font")].join(" "),d.fillStyle="edge"===e("edgeLabelColor")?a.color||e("defaultEdgeColor"):e("defaultEdgeLabelColor")),d.textAlign="center",d.textBaseline="alphabetic",d.translate(i.x,i.y),d.rotate(j),d.fillText(a.label,0,-g/2-3),d.restore()}}}}.call(this),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils=sigma.utils||{},sigma.utils.getQuadraticControlPoint=function(a,b,c,d,e){return e=e||0,{x:(a+c)/2+(d-b)/(60/(15+e)),y:(b+d)/2+(a-c)/(60/(15+e))}},sigma.utils.getSelfLoopControlPoints=function(a,b,c,d){return d=d||0,{x1:a-7*(c+d),y1:b,x2:a,y2:b+7*(c+d)}}}.call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.renderers.snapshot.min.js b/js/lib/sigma/plugins/sigma.renderers.snapshot.min.js deleted file mode 100755 index 3990165..0000000 --- a/js/lib/sigma/plugins/sigma.renderers.snapshot.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){function a(a,b,c){var d=document.createElement("a");d.setAttribute("href",a),d.setAttribute("download",c||"graph."+b);var e=document.createEvent("MouseEvent");e.initMouseEvent("click",!0,!1,window,0,0,0,0,0,!1,!1,!1,!1,0,null),d.dispatchEvent(e),delete d}function b(b){if(b=b||{},b.format&&!(b.format in d))throw Error('sigma.renderers.snaphot: unsupported format "'+b.format+'".');var e=this,f=this instanceof sigma.renderers.webgl,g=[],h=document.createElement("canvas"),i=h.getContext("2d"),j=!1;c.forEach(function(a){if(e.contexts[a]&&(b.labels!==!1||"labels"!==a)){var c=e.domElements[a]||e.domElements.scene,d=e.contexts[a];~g.indexOf(d)||(j||(h.width=f&&d instanceof WebGLRenderingContext?c.width/2:c.width,h.height=f&&d instanceof WebGLRenderingContext?c.height/2:c.height,j=!0,b.background&&(i.rect(0,0,h.width,h.height),i.fillStyle=b.background,i.fill())),d instanceof WebGLRenderingContext?i.drawImage(c,0,0,c.width/2,c.height/2):i.drawImage(c,0,0),g.push(d))}});var k=h.toDataURL(d[b.format||"png"]);return b.download&&a(k,b.format||"png",b.filename),delete i,delete h,delete g,k}if("undefined"==typeof sigma)throw"sigma.renderers.snapshot: sigma not in scope.";var c=["scene","edges","nodes","labels"],d={png:"image/png",jpg:"image/jpeg",gif:"image/gif",tiff:"image/tiff"};sigma.renderers.canvas.prototype.snapshot=b,sigma.renderers.webgl.prototype.snapshot=b}).call(this); \ No newline at end of file diff --git a/js/lib/sigma/plugins/sigma.statistics.HITS.min.js b/js/lib/sigma/plugins/sigma.statistics.HITS.min.js deleted file mode 100755 index a08c2b4..0000000 --- a/js/lib/sigma/plugins/sigma.statistics.HITS.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.classes.graph.addMethod("HITS",function(a){var b={},c=1e-4,d=[],e=[],f=this.nodes(),g=(f.length,{});a||(a=!1);for(var h in f)a?(d.push(f[h]),e.push(f[h])):(this.degree(f[h].id,"out")>0&&d.push(f[h]),this.degree(f[h].id,"in")>0&&e.push(f[h])),b[f[h].id]={authority:1,hub:1};for(var i;;){i=!0;var j=0,k=0;for(var h in e){g[e[h].id]={authority:1,hub:0};var l=[];l=a?this.allNeighborsIndex[e[h].id]:this.inNeighborsIndex[e[h].id];for(var m in l)m!=e[h].id&&(g[e[h].id].authority+=b[m].hub);j+=g[e[h].id].authority}for(var h in d){g[d[h].id]?g[d[h].id].hub=1:g[d[h].id]={authority:0,hub:1};var l=[];l=a?this.allNeighborsIndex[d[h].id]:this.outNeighborsIndex[d[h].id];for(var m in l)m!=d[h].id&&(g[d[h].id].hub+=b[m].authority);k+=g[d[h].id].hub}for(var h in e)g[e[h].id].authority/=j,Math.abs((g[e[h].id].authority-b[e[h].id].authority)/b[e[h].id].authority)>=c&&(i=!1);for(var h in d)g[d[h].id].hub/=k,Math.abs((g[d[h].id].hub-b[d[h].id].hub)/b[d[h].id].hub)>=c&&(i=!1);if(b=g,g={},i)break}return b})}).call(window); \ No newline at end of file diff --git a/js/lib/sigma/sigma-min.js b/js/lib/sigma/sigma-min.js deleted file mode 100755 index a34250e..0000000 --- a/js/lib/sigma/sigma-min.js +++ /dev/null @@ -1,5 +0,0 @@ -/* sigma.js - A JavaScript library dedicated to graph drawing. - Version: 1.1.0 - Author: Alexis Jacomy, Sciences-Po Médialab - License: MIT */ -(function(a){"use strict";var b={},c=function(a){var d,e,f,g,h;c.classes.dispatcher.extend(this);var i=this,j=a||{};if("string"==typeof j||j instanceof HTMLElement?j={renderers:[j]}:"[object Array]"===Object.prototype.toString.call(j)&&(j={renderers:j}),g=j.renderers||j.renderer||j.container,j.renderers&&0!==j.renderers.length||("string"==typeof g||g instanceof HTMLElement||"object"==typeof g&&"container"in g)&&(j.renderers=[g]),j.id){if(b[j.id])throw'sigma: Instance "'+j.id+'" already exists.';Object.defineProperty(this,"id",{value:j.id})}else{for(h=0;b[h];)h++;Object.defineProperty(this,"id",{value:""+h})}for(b[this.id]=this,this.settings=new c.classes.configurable(c.settings,j.settings||{}),Object.defineProperty(this,"graph",{value:new c.classes.graph(this.settings),configurable:!0}),Object.defineProperty(this,"middlewares",{value:[],configurable:!0}),Object.defineProperty(this,"cameras",{value:{},configurable:!0}),Object.defineProperty(this,"renderers",{value:{},configurable:!0}),Object.defineProperty(this,"renderersPerCamera",{value:{},configurable:!0}),Object.defineProperty(this,"cameraFrames",{value:{},configurable:!0}),Object.defineProperty(this,"camera",{get:function(){return this.cameras[0]}}),Object.defineProperty(this,"events",{value:["click","rightClick","clickStage","doubleClickStage","rightClickStage","clickNode","clickNodes","doubleClickNode","doubleClickNodes","rightClickNode","rightClickNodes","overNode","overNodes","outNode","outNodes","downNode","downNodes","upNode","upNodes"],configurable:!0}),this._handler=function(a){var b,c={};for(b in a.data)c[b]=a.data[b];c.renderer=a.target,this.dispatchEvent(a.type,c)}.bind(this),f=j.renderers||[],d=0,e=f.length;e>d;d++)this.addRenderer(f[d]);for(f=j.middlewares||[],d=0,e=f.length;e>d;d++)this.middlewares.push("string"==typeof f[d]?c.middlewares[f[d]]:f[d]);"object"==typeof j.graph&&j.graph&&(this.graph.read(j.graph),this.refresh()),window.addEventListener("resize",function(){i.settings&&i.refresh()})};if(c.prototype.addCamera=function(b){var d,e=this;if(!arguments.length){for(b=0;this.cameras[""+b];)b++;b=""+b}if(this.cameras[b])throw'sigma.addCamera: The camera "'+b+'" already exists.';return d=new c.classes.camera(b,this.graph,this.settings),this.cameras[b]=d,d.quadtree=new c.classes.quad,c.classes.edgequad!==a&&(d.edgequadtree=new c.classes.edgequad),d.bind("coordinatesUpdated",function(){e.renderCamera(d,d.isAnimated)}),this.renderersPerCamera[b]=[],d},c.prototype.killCamera=function(a){if(a="string"==typeof a?this.cameras[a]:a,!a)throw"sigma.killCamera: The camera is undefined.";var b,c,d=this.renderersPerCamera[a.id];for(c=d.length,b=c-1;b>=0;b--)this.killRenderer(d[b]);return delete this.renderersPerCamera[a.id],delete this.cameraFrames[a.id],delete this.cameras[a.id],a.kill&&a.kill(),this},c.prototype.addRenderer=function(a){var b,d,e,f,g=a||{};if("string"==typeof g?g={container:document.getElementById(g)}:g instanceof HTMLElement&&(g={container:g}),"string"==typeof g.container&&(g.container=document.getElementById(g.container)),"id"in g)b=g.id;else{for(b=0;this.renderers[""+b];)b++;b=""+b}if(this.renderers[b])throw'sigma.addRenderer: The renderer "'+b+'" already exists.';if(d="function"==typeof g.type?g.type:c.renderers[g.type],d=d||c.renderers.def,e="camera"in g?g.camera instanceof c.classes.camera?g.camera:this.cameras[g.camera]||this.addCamera(g.camera):this.addCamera(),this.cameras[e.id]!==e)throw"sigma.addRenderer: The camera is not properly referenced.";return f=new d(this.graph,e,this.settings,g),this.renderers[b]=f,Object.defineProperty(f,"id",{value:b}),f.bind&&f.bind(["click","rightClick","clickStage","doubleClickStage","rightClickStage","clickNode","clickNodes","clickEdge","clickEdges","doubleClickNode","doubleClickNodes","doubleClickEdge","doubleClickEdges","rightClickNode","rightClickNodes","rightClickEdge","rightClickEdges","overNode","overNodes","overEdge","overEdges","outNode","outNodes","outEdge","outEdges","downNode","downNodes","downEdge","downEdges","upNode","upNodes","upEdge","upEdges"],this._handler),this.renderersPerCamera[e.id].push(f),f},c.prototype.killRenderer=function(a){if(a="string"==typeof a?this.renderers[a]:a,!a)throw"sigma.killRenderer: The renderer is undefined.";var b=this.renderersPerCamera[a.camera.id],c=b.indexOf(a);return c>=0&&b.splice(c,1),a.kill&&a.kill(),delete this.renderers[a.id],this},c.prototype.refresh=function(b){var d,e,f,g,h,i,j=0;for(b=b||{},g=this.middlewares||[],d=0,e=g.length;e>d;d++)g[d].call(this,0===d?"":"tmp"+j+":",d===e-1?"ready:":"tmp"+ ++j+":");for(f in this.cameras)h=this.cameras[f],h.settings("autoRescale")&&this.renderersPerCamera[h.id]&&this.renderersPerCamera[h.id].length?c.middlewares.rescale.call(this,g.length?"ready:":"",h.readPrefix,{width:this.renderersPerCamera[h.id][0].width,height:this.renderersPerCamera[h.id][0].height}):c.middlewares.copy.call(this,g.length?"ready:":"",h.readPrefix),b.skipIndexation||(i=c.utils.getBoundaries(this.graph,h.readPrefix),h.quadtree.index(this.graph.nodes(),{prefix:h.readPrefix,bounds:{x:i.minX,y:i.minY,width:i.maxX-i.minX,height:i.maxY-i.minY}}),h.edgequadtree!==a&&h.settings("drawEdges")&&h.settings("enableEdgeHovering")&&h.edgequadtree.index(this.graph,{prefix:h.readPrefix,bounds:{x:i.minX,y:i.minY,width:i.maxX-i.minX,height:i.maxY-i.minY}}));for(g=Object.keys(this.renderers),d=0,e=g.length;e>d;d++)if(this.renderers[g[d]].process)if(this.settings("skipErrors"))try{this.renderers[g[d]].process()}catch(k){console.log('Warning: The renderer "'+g[d]+'" crashed on ".process()"')}else this.renderers[g[d]].process();return this.render(),this},c.prototype.render=function(){var a,b,c;for(c=Object.keys(this.renderers),a=0,b=c.length;b>a;a++)if(this.settings("skipErrors"))try{this.renderers[c[a]].render()}catch(d){this.settings("verbose")&&console.log('Warning: The renderer "'+c[a]+'" crashed on ".render()"')}else this.renderers[c[a]].render();return this},c.prototype.renderCamera=function(a,b){var c,d,e,f=this;if(b)for(e=this.renderersPerCamera[a.id],c=0,d=e.length;d>c;c++)if(this.settings("skipErrors"))try{e[c].render()}catch(g){this.settings("verbose")&&console.log('Warning: The renderer "'+e[c].id+'" crashed on ".render()"')}else e[c].render();else if(!this.cameraFrames[a.id]){for(e=this.renderersPerCamera[a.id],c=0,d=e.length;d>c;c++)if(this.settings("skipErrors"))try{e[c].render()}catch(g){this.settings("verbose")&&console.log('Warning: The renderer "'+e[c].id+'" crashed on ".render()"')}else e[c].render();this.cameraFrames[a.id]=requestAnimationFrame(function(){delete f.cameraFrames[a.id]})}return this},c.prototype.kill=function(){var a;this.dispatchEvent("kill"),this.graph.kill(),delete this.middlewares;for(a in this.renderers)this.killRenderer(this.renderers[a]);for(a in this.cameras)this.killCamera(this.cameras[a]);delete this.renderers,delete this.cameras;for(a in this)this.hasOwnProperty(a)&&delete this[a];delete b[this.id]},c.instances=function(a){return arguments.length?b[a]:c.utils.extend({},b)},c.version="1.1.0","undefined"!=typeof this.sigma)throw"An object called sigma is already in the global scope.";this.sigma=c}).call(this),function(a){"use strict";function b(a,c){var d,e,f,g;if(arguments.length)if(1===arguments.length&&Object(arguments[0])===arguments[0])for(a in arguments[0])b(a,arguments[0][a]);else if(arguments.length>1)for(g=Array.isArray(a)?a:a.split(/ /),d=0,e=g.length;d!==e;d+=1)f=g[d],C[f]||(C[f]=[]),C[f].push({handler:c})}function c(a,b){var c,d,e,f,g,h,i=Array.isArray(a)?a:a.split(/ /);if(arguments.length)if(b)for(c=0,d=i.length;c!==d;c+=1){if(h=i[c],C[h]){for(g=[],e=0,f=C[h].length;e!==f;e+=1)C[h][e].handler!==b&&g.push(C[h][e]);C[h]=g}C[h]&&0===C[h].length&&delete C[h]}else for(c=0,d=i.length;c!==d;c+=1)delete C[i[c]];else C=Object.create(null)}function d(a,b){var c,d,e,f,g,h,i=Array.isArray(a)?a:a.split(/ /);for(b=void 0===b?{}:b,c=0,e=i.length;c!==e;c+=1)if(h=i[c],C[h])for(g={type:h,data:b||{}},d=0,f=C[h].length;d!==f;d+=1)try{C[h][d].handler(g)}catch(j){}}function e(){var a,b,c,d,e=!1,f=s(),g=x.shift();if(c=g.job(),f=s()-f,g.done++,g.time+=f,g.currentTime+=f,g.weightTime=g.currentTime/(g.weight||1),g.averageTime=g.time/g.done,d=g.count?g.count<=g.done:!c,!d){for(a=0,b=x.length;b>a;a++)if(x[a].weightTime>g.weightTime){x.splice(a,0,g),e=!0;break}e||x.push(g)}return d?g:null}function f(a){var b=x.length;w[a.id]=a,a.status="running",b&&(a.weightTime=x[b-1].weightTime,a.currentTime=a.weightTime*(a.weight||1)),a.startTime=s(),d("jobStarted",q(a)),x.push(a)}function g(){var a,b,c;for(a in v)b=v[a],b.after?y[a]=b:f(b),delete v[a];for(u=!!x.length;x.length&&s()-tc;c++)h(a[c].id,p(a[c],b));A=!1,u||(t=s(),d("start"),g())}else if("object"==typeof a)if("string"==typeof a.id)h(a.id,a);else{A=!0;for(c in a)"function"==typeof a[c]?h(c,p({job:a[c]},b)):h(c,p(a[c],b));A=!1,u||(t=s(),d("start"),g())}else{if("string"!=typeof a)throw new Error("[conrad.addJob] Wrong arguments.");if(k(a))throw new Error('[conrad.addJob] Job with id "'+a+'" already exists.');if("function"==typeof b)f={id:a,done:0,time:0,status:"waiting",currentTime:0,averageTime:0,weightTime:0,job:b};else{if("object"!=typeof b)throw new Error("[conrad.addJob] Wrong arguments.");f=p({id:a,done:0,time:0,status:"waiting",currentTime:0,averageTime:0,weightTime:0},b)}v[a]=f,d("jobAdded",q(f)),u||A||(t=s(),d("start"),g())}return this}function i(a){var b,c,e,f,g=!1;if(Array.isArray(a))for(b=0,c=a.length;c>b;b++)i(a[b]);else{if("string"!=typeof a)throw new Error("[conrad.killJob] Wrong arguments.");for(e=[w,y,v],b=0,c=e.length;c>b;b++)a in e[b]&&(f=e[b][a],B.history&&(f.status="done",z.push(f)),d("jobEnded",q(f)),delete e[b][a],"function"==typeof f.end&&f.end(),g=!0);for(e=x,b=0,c=e.length;c>b;b++)if(e[b].id===a){e.splice(b,1);break}if(!g)throw new Error('[conrad.killJob] Job "'+a+'" not found.')}return this}function j(){var a,b=p(v,w,y);if(B.history)for(a in b)b[a].status="done",z.push(b[a]),"function"==typeof b[a].end&&b[a].end();return v={},y={},w={},x=[],u=!1,this}function k(a){var b=v[a]||w[a]||y[a];return b?p(b):null}function l(){var a;if("string"==typeof a1&&1===arguments.length)return B[a1];a="object"==typeof a1&&1===arguments.length?a1||{}:{},"string"==typeof a1&&(a[a1]=a2);for(var b in a)void 0!==a[b]?B[b]=a[b]:delete B[b];return this}function m(){return u}function n(){return z=[],this}function o(a,b){var c,d,e,f,g,h,i;if(!arguments.length){g=[];for(d in v)g.push(v[d]);for(d in y)g.push(y[d]);for(d in w)g.push(w[d]);g=g.concat(z)}if("string"==typeof a)switch(a){case"waiting":g=r(y);break;case"running":g=r(w);break;case"done":g=z;break;default:h=a}if(a instanceof RegExp&&(h=a),!h&&("string"==typeof b||b instanceof RegExp)&&(h=b),h){if(i="string"==typeof h,g instanceof Array)c=g;else if("object"==typeof g){c=[];for(d in g)c=c.concat(g[d])}else{c=[];for(d in v)c.push(v[d]);for(d in y)c.push(y[d]);for(d in w)c.push(w[d]);c=c.concat(z)}for(g=[],e=0,f=c.length;f>e;e++)(i?c[e].id===h:c[e].id.match(h))&&g.push(c[e])}return q(g)}function p(){var a,b,c={},d=arguments.length;for(a=d-1;a>=0;a--)for(b in arguments[a])c[b]=arguments[a][b];return c}function q(a){var b,c,d;if(!a)return a;if(Array.isArray(a))for(b=[],c=0,d=a.length;d>c;c++)b.push(q(a[c]));else if("object"==typeof a){b={};for(c in a)b[c]=q(a[c])}else b=a;return b}function r(a){var b,c=[];for(b in a)c.push(a[b]);return c}function s(){return Date.now?Date.now():(new Date).getTime()}if(a.conrad)throw new Error("conrad already exists");var t,u=!1,v={},w={},x=[],y={},z=[],A=!1,B={frameDuration:20,history:!0},C=Object.create(null);Array.isArray||(Array.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)});var D={hasJob:k,addJob:h,killJob:i,killAll:j,settings:l,getStats:o,isRunning:m,clearHistory:n,bind:b,unbind:c,version:"0.1.0"};"undefined"!=typeof exports&&("undefined"!=typeof module&&module.exports&&(exports=module.exports=D),exports.conrad=D),a.conrad=D}(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";var b=this;sigma.utils=sigma.utils||{},sigma.utils.extend=function(){var a,b,c={},d=arguments.length;for(a=d-1;a>=0;a--)for(b in arguments[a])c[b]=arguments[a][b];return c},sigma.utils.dateNow=function(){return Date.now?Date.now():(new Date).getTime()},sigma.utils.pkg=function(a){return(a||"").split(".").reduce(function(a,b){return b in a?a[b]:a[b]={}},b)},sigma.utils.id=function(){var a=0;return function(){return++a}}();var c={};sigma.utils.floatColor=function(a){if(c[a])return c[a];var b=a,d=0,e=0,f=0;"#"===a[0]?(a=a.slice(1),3===a.length?(d=parseInt(a.charAt(0)+a.charAt(0),16),e=parseInt(a.charAt(1)+a.charAt(1),16),f=parseInt(a.charAt(2)+a.charAt(2),16)):(d=parseInt(a.charAt(0)+a.charAt(1),16),e=parseInt(a.charAt(2)+a.charAt(3),16),f=parseInt(a.charAt(4)+a.charAt(5),16))):a.match(/^ *rgba? *\(/)&&(a=a.match(/^ *rgba? *\( *([0-9]*) *, *([0-9]*) *, *([0-9]*) *(,.*)?\) *$/),d=+a[1],e=+a[2],f=+a[3]);var g=256*d*256+256*e+f;return c[b]=g,g},sigma.utils.zoomTo=function(a,b,c,d,e){var f,g,h,i=a.settings;g=Math.max(i("zoomMin"),Math.min(i("zoomMax"),a.ratio*d)),g!==a.ratio&&(d=g/a.ratio,h={x:b*(1-d)+a.x,y:c*(1-d)+a.y,ratio:g},e&&e.duration?(f=sigma.misc.animation.killAll(a),e=sigma.utils.extend(e,{easing:f?"quadraticOut":"quadraticInOut"}),sigma.misc.animation.camera(a,h,e)):(a.goTo(h),e&&e.onComplete&&e.onComplete()))},sigma.utils.getQuadraticControlPoint=function(a,b,c,d){return{x:(a+c)/2+(d-b)/4,y:(b+d)/2+(a-c)/4}},sigma.utils.getPointOnQuadraticCurve=function(a,b,c,d,e,f,g){return{x:Math.pow(1-a,2)*b+2*(1-a)*a*f+Math.pow(a,2)*d,y:Math.pow(1-a,2)*c+2*(1-a)*a*g+Math.pow(a,2)*e}},sigma.utils.getPointOnBezierCurve=function(a,b,c,d,e,f,g,h,i){var j=Math.pow(1-a,3),k=3*a*Math.pow(1-a,2),l=3*Math.pow(a,2)*(1-a),m=Math.pow(a,3);return{x:j*b+k*f+l*h+m*d,y:j*c+k*g+l*i+m*e}},sigma.utils.getSelfLoopControlPoints=function(a,b,c){return{x1:a-7*c,y1:b,x2:a,y2:b+7*c}},sigma.utils.getDistance=function(a,b,c,d){return Math.sqrt(Math.pow(c-a,2)+Math.pow(d-b,2))},sigma.utils.getCircleIntersection=function(a,b,c,d,e,f){var g,h,i,j,k,l,m,n,o;if(h=d-a,i=e-b,j=Math.sqrt(i*i+h*h),j>c+f)return!1;if(jj&&Math.min(c,e)<=a&&a<=Math.max(c,e)&&Math.min(d,f)<=b&&b<=Math.max(d,f)},sigma.utils.isPointOnQuadraticCurve=function(a,b,c,d,e,f,g,h,i){var j=sigma.utils.getDistance(c,d,e,f);if(Math.abs(a-c)>j||Math.abs(b-d)>j)return!1;for(var k,l=sigma.utils.getDistance(a,b,c,d),m=sigma.utils.getDistance(a,b,e,f),n=.5,o=m>l?-.01:.01,p=.001,q=100,r=sigma.utils.getPointOnQuadraticCurve(n,c,d,e,f,g,h),s=sigma.utils.getDistance(a,b,r.x,r.y);q-->0&&n>=0&&1>=n&&s>i&&(o>p||-p>o);)k=s,r=sigma.utils.getPointOnQuadraticCurve(n,c,d,e,f,g,h),s=sigma.utils.getDistance(a,b,r.x,r.y),s>k?(o=-o/2,n+=o):0>n+o||n+o>1?(o/=2,s=k):n+=o;return i>s},sigma.utils.isPointOnBezierCurve=function(a,b,c,d,e,f,g,h,i,j,k){var l=sigma.utils.getDistance(c,d,g,h);if(Math.abs(a-c)>l||Math.abs(b-d)>l)return!1;for(var m,n=sigma.utils.getDistance(a,b,c,d),o=sigma.utils.getDistance(a,b,e,f),p=.5,q=o>n?-.01:.01,r=.001,s=100,t=sigma.utils.getPointOnBezierCurve(p,c,d,e,f,g,h,i,j),u=sigma.utils.getDistance(a,b,t.x,t.y);s-->0&&p>=0&&1>=p&&u>k&&(q>r||-r>q);)m=u,t=sigma.utils.getPointOnBezierCurve(p,c,d,e,f,g,h,i,j),u=sigma.utils.getDistance(a,b,t.x,t.y),u>m?(q=-q/2,p+=q):0>p+q||p+q>1?(q/=2,u=m):p+=q;return k>u},sigma.utils.getX=function(b){return b.offsetX!==a&&b.offsetX||b.layerX!==a&&b.layerX||b.clientX!==a&&b.clientX},sigma.utils.getY=function(b){return b.offsetY!==a&&b.offsetY||b.layerY!==a&&b.layerY||b.clientY!==a&&b.clientY},sigma.utils.getPixelRatio=function(){var b=1;return window.screen.deviceXDPI!==a&&window.screen.logicalXDPI!==a&&window.screen.deviceXDPI>window.screen.logicalXDPI?b=window.screen.systemXDPI/window.screen.logicalXDPI:window.devicePixelRatio!==a&&(b=window.devicePixelRatio),b},sigma.utils.getWidth=function(b){var c=b.target.ownerSVGElement?b.target.ownerSVGElement.width:b.target.width;return"number"==typeof c&&c||c!==a&&c.baseVal!==a&&c.baseVal.value},sigma.utils.getCenter=function(a){var b=-1!==a.target.namespaceURI.indexOf("svg")?1:sigma.utils.getPixelRatio();return{x:sigma.utils.getWidth(a)/(2*b),y:sigma.utils.getHeight(a)/(2*b)}},sigma.utils.mouseCoords=function(a,b,c){return b=b||sigma.utils.getX(a),c=c||sigma.utils.getY(a),{x:b-sigma.utils.getCenter(a).x,y:c-sigma.utils.getCenter(a).y,clientX:a.clientX,clientY:a.clientY,ctrlKey:a.ctrlKey,metaKey:a.metaKey,altKey:a.altKey,shiftKey:a.shiftKey}},sigma.utils.getHeight=function(b){var c=b.target.ownerSVGElement?b.target.ownerSVGElement.height:b.target.height;return"number"==typeof c&&c||c!==a&&c.baseVal!==a&&c.baseVal.value},sigma.utils.getDelta=function(b){return b.wheelDelta!==a&&b.wheelDelta||b.detail!==a&&-b.detail},sigma.utils.getOffset=function(a){for(var b=0,c=0;a;)c+=parseInt(a.offsetTop),b+=parseInt(a.offsetLeft),a=a.offsetParent;return{top:c,left:b}},sigma.utils.doubleClick=function(a,b,c){var d,e=0;a._doubleClickHandler=a._doubleClickHandler||{},a._doubleClickHandler[b]=a._doubleClickHandler[b]||[],d=a._doubleClickHandler[b],d.push(function(a){return e++,2===e?(e=0,c(a)):void(1===e&&setTimeout(function(){e=0},sigma.settings.doubleClickTimeout))}),a.addEventListener(b,d[d.length-1],!1)},sigma.utils.unbindDoubleClick=function(a,b){for(var c,d=(a._doubleClickHandler||{})[b]||[];c=d.pop();)a.removeEventListener(b,c);delete(a._doubleClickHandler||{})[b]},sigma.utils.easings=sigma.utils.easings||{},sigma.utils.easings.linearNone=function(a){return a},sigma.utils.easings.quadraticIn=function(a){return a*a},sigma.utils.easings.quadraticOut=function(a){return a*(2-a)},sigma.utils.easings.quadraticInOut=function(a){return(a*=2)<1?.5*a*a:-.5*(--a*(a-2)-1)},sigma.utils.easings.cubicIn=function(a){return a*a*a},sigma.utils.easings.cubicOut=function(a){return--a*a*a+1},sigma.utils.easings.cubicInOut=function(a){return(a*=2)<1?.5*a*a*a:.5*((a-=2)*a*a+2)},sigma.utils.loadShader=function(a,b,c,d){var e,f=a.createShader(c);return a.shaderSource(f,b),a.compileShader(f),e=a.getShaderParameter(f,a.COMPILE_STATUS),e?f:(d&&d('Error compiling shader "'+f+'":'+a.getShaderInfoLog(f)),a.deleteShader(f),null)},sigma.utils.loadProgram=function(a,b,c,d,e){var f,g,h=a.createProgram();for(f=0;fg;g++)if(void 0!==e[g][a])return e[g][a];return void 0}if("object"==typeof a&&"string"==typeof b)return void 0!==(a||{})[b]?a[b]:f(b);for(c="object"==typeof a&&void 0===b?a:{},"string"==typeof a&&(c[a]=b),g=0,i=Object.keys(c),h=i.length;h>g;g++)d[i[g]]=c[i[g]];return this};for(f.embedObjects=function(){var b=e.concat(d).concat(Array.prototype.splice.call(arguments,0));return a.apply({},b)},b=0,c=arguments.length;c>b;b++)f(arguments[b]);return f};"undefined"!=typeof this.sigma?(this.sigma.classes=this.sigma.classes||{},this.sigma.classes.configurable=a):"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=a),exports.configurable=a):this.configurable=a}.call(this),function(){"use strict";function a(a,b,c){var d=function(){var d,e;for(d in g[a])g[a][d].apply(b,arguments);e=c.apply(b,arguments);for(d in f[a])f[a][d].apply(b,arguments);return e};return d}function b(a){var b;for(b in a)"hasOwnProperty"in a&&!a.hasOwnProperty(b)||delete a[b];return a}var c=Object.create(null),d=Object.create(null),e=Object.create(null),f=Object.create(null),g=Object.create(null),h={immutable:!0,clone:!0},i=function(a){return h[a]},j=function(b){var d,f,g;g={settings:b||i,nodesArray:[],edgesArray:[],nodesIndex:Object.create(null),edgesIndex:Object.create(null),inNeighborsIndex:Object.create(null),outNeighborsIndex:Object.create(null),allNeighborsIndex:Object.create(null),inNeighborsCount:Object.create(null),outNeighborsCount:Object.create(null),allNeighborsCount:Object.create(null)};for(d in e)e[d].call(g);for(d in c)f=a(d,g,c[d]),this[d]=f,g[d]=f};j.addMethod=function(a,b){if("string"!=typeof a||"function"!=typeof b||2!==arguments.length)throw"addMethod: Wrong arguments.";if(c[a]||j[a])throw'The method "'+a+'" already exists.';return c[a]=b,f[a]=Object.create(null),g[a]=Object.create(null),this},j.hasMethod=function(a){return!(!c[a]&&!j[a])},j.attach=function(a,b,c,d){if("string"!=typeof a||"string"!=typeof b||"function"!=typeof c||arguments.length<3||arguments.length>4)throw"attach: Wrong arguments.";var h;if("constructor"===a)h=e;else if(d){if(!g[a])throw'The method "'+a+'" does not exist.';h=g[a]}else{if(!f[a])throw'The method "'+a+'" does not exist.';h=f[a]}if(h[b])throw'A function "'+b+'" is already attached to the method "'+a+'".';return h[b]=c,this},j.attachBefore=function(a,b,c){return this.attach(a,b,c,!0)},j.addIndex=function(a,b){if("string"!=typeof a||Object(b)!==b||2!==arguments.length)throw"addIndex: Wrong arguments.";if(d[a])throw'The index "'+a+'" already exists.';var c;d[a]=b;for(c in b){if("function"!=typeof b[c])throw"The bindings must be functions.";j.attach(c,a,b[c])}return this},j.addMethod("addNode",function(a){if(Object(a)!==a||1!==arguments.length)throw"addNode: Wrong arguments.";if("string"!=typeof a.id&&"number"!=typeof a.id)throw"The node must have a string or number id.";if(this.nodesIndex[a.id])throw'The node "'+a.id+'" already exists.';var b,c=a.id,d=Object.create(null);if(this.settings("clone"))for(b in a)"id"!==b&&(d[b]=a[b]);else d=a;return this.settings("immutable")?Object.defineProperty(d,"id",{value:c,enumerable:!0}):d.id=c,this.inNeighborsIndex[c]=Object.create(null),this.outNeighborsIndex[c]=Object.create(null),this.allNeighborsIndex[c]=Object.create(null),this.inNeighborsCount[c]=0,this.outNeighborsCount[c]=0,this.allNeighborsCount[c]=0,this.nodesArray.push(d),this.nodesIndex[d.id]=d,this}),j.addMethod("addEdge",function(a){if(Object(a)!==a||1!==arguments.length)throw"addEdge: Wrong arguments.";if("string"!=typeof a.id&&"number"!=typeof a.id)throw"The edge must have a string or number id.";if("string"!=typeof a.source&&"number"!=typeof a.source||!this.nodesIndex[a.source])throw"The edge source must have an existing node id.";if("string"!=typeof a.target&&"number"!=typeof a.target||!this.nodesIndex[a.target])throw"The edge target must have an existing node id.";if(this.edgesIndex[a.id])throw'The edge "'+a.id+'" already exists.';var b,c=Object.create(null);if(this.settings("clone"))for(b in a)"id"!==b&&"source"!==b&&"target"!==b&&(c[b]=a[b]);else c=a;return this.settings("immutable")?(Object.defineProperty(c,"id",{value:a.id,enumerable:!0}),Object.defineProperty(c,"source",{value:a.source,enumerable:!0}),Object.defineProperty(c,"target",{value:a.target,enumerable:!0})):(c.id=a.id,c.source=a.source,c.target=a.target),this.edgesArray.push(c),this.edgesIndex[c.id]=c,this.inNeighborsIndex[c.target][c.source]||(this.inNeighborsIndex[c.target][c.source]=Object.create(null)),this.inNeighborsIndex[c.target][c.source][c.id]=c,this.outNeighborsIndex[c.source][c.target]||(this.outNeighborsIndex[c.source][c.target]=Object.create(null)),this.outNeighborsIndex[c.source][c.target][c.id]=c,this.allNeighborsIndex[c.source][c.target]||(this.allNeighborsIndex[c.source][c.target]=Object.create(null)),this.allNeighborsIndex[c.source][c.target][c.id]=c,c.target!==c.source&&(this.allNeighborsIndex[c.target][c.source]||(this.allNeighborsIndex[c.target][c.source]=Object.create(null)),this.allNeighborsIndex[c.target][c.source][c.id]=c),this.inNeighborsCount[c.target]++,this.outNeighborsCount[c.source]++,this.allNeighborsCount[c.target]++,this.allNeighborsCount[c.source]++,this}),j.addMethod("dropNode",function(a){if("string"!=typeof a&&"number"!=typeof a||1!==arguments.length)throw"dropNode: Wrong arguments.";if(!this.nodesIndex[a])throw'The node "'+a+'" does not exist.';var b,c,d;for(delete this.nodesIndex[a],b=0,d=this.nodesArray.length;d>b;b++)if(this.nodesArray[b].id===a){this.nodesArray.splice(b,1);break}for(b=this.edgesArray.length-1;b>=0;b--)(this.edgesArray[b].source===a||this.edgesArray[b].target===a)&&this.dropEdge(this.edgesArray[b].id);delete this.inNeighborsIndex[a],delete this.outNeighborsIndex[a],delete this.allNeighborsIndex[a],delete this.inNeighborsCount[a],delete this.outNeighborsCount[a],delete this.allNeighborsCount[a];for(c in this.nodesIndex)delete this.inNeighborsIndex[c][a],delete this.outNeighborsIndex[c][a],delete this.allNeighborsIndex[c][a];return this}),j.addMethod("dropEdge",function(a){if("string"!=typeof a&&"number"!=typeof a||1!==arguments.length)throw"dropEdge: Wrong arguments.";if(!this.edgesIndex[a])throw'The edge "'+a+'" does not exist.';var b,c,d;for(d=this.edgesIndex[a],delete this.edgesIndex[a],b=0,c=this.edgesArray.length;c>b;b++)if(this.edgesArray[b].id===a){this.edgesArray.splice(b,1);break}return delete this.inNeighborsIndex[d.target][d.source][d.id],Object.keys(this.inNeighborsIndex[d.target][d.source]).length||delete this.inNeighborsIndex[d.target][d.source],delete this.outNeighborsIndex[d.source][d.target][d.id],Object.keys(this.outNeighborsIndex[d.source][d.target]).length||delete this.outNeighborsIndex[d.source][d.target],delete this.allNeighborsIndex[d.source][d.target][d.id],Object.keys(this.allNeighborsIndex[d.source][d.target]).length||delete this.allNeighborsIndex[d.source][d.target],d.target!==d.source&&(delete this.allNeighborsIndex[d.target][d.source][d.id],Object.keys(this.allNeighborsIndex[d.target][d.source]).length||delete this.allNeighborsIndex[d.target][d.source]),this.inNeighborsCount[d.target]--,this.outNeighborsCount[d.source]--,this.allNeighborsCount[d.source]--,this.allNeighborsCount[d.target]--,this}),j.addMethod("kill",function(){this.nodesArray.length=0,this.edgesArray.length=0,delete this.nodesArray,delete this.edgesArray,delete this.nodesIndex,delete this.edgesIndex,delete this.inNeighborsIndex,delete this.outNeighborsIndex,delete this.allNeighborsIndex,delete this.inNeighborsCount,delete this.outNeighborsCount,delete this.allNeighborsCount -}),j.addMethod("clear",function(){return this.nodesArray.length=0,this.edgesArray.length=0,b(this.nodesIndex),b(this.edgesIndex),b(this.nodesIndex),b(this.inNeighborsIndex),b(this.outNeighborsIndex),b(this.allNeighborsIndex),b(this.inNeighborsCount),b(this.outNeighborsCount),b(this.allNeighborsCount),this}),j.addMethod("read",function(a){var b,c,d;for(c=a.nodes||[],b=0,d=c.length;d>b;b++)this.addNode(c[b]);for(c=a.edges||[],b=0,d=c.length;d>b;b++)this.addEdge(c[b]);return this}),j.addMethod("nodes",function(a){if(!arguments.length)return this.nodesArray.slice(0);if(1===arguments.length&&("string"==typeof a||"number"==typeof a))return this.nodesIndex[a];if(1===arguments.length&&"[object Array]"===Object.prototype.toString.call(a)){var b,c,d=[];for(b=0,c=a.length;c>b;b++){if("string"!=typeof a[b]&&"number"!=typeof a[b])throw"nodes: Wrong arguments.";d.push(this.nodesIndex[a[b]])}return d}throw"nodes: Wrong arguments."}),j.addMethod("degree",function(a,b){if(b={"in":this.inNeighborsCount,out:this.outNeighborsCount}[b||""]||this.allNeighborsCount,"string"==typeof a||"number"==typeof a)return b[a];if("[object Array]"===Object.prototype.toString.call(a)){var c,d,e=[];for(c=0,d=a.length;d>c;c++){if("string"!=typeof a[c]&&"number"!=typeof a[c])throw"degree: Wrong arguments.";e.push(b[a[c]])}return e}throw"degree: Wrong arguments."}),j.addMethod("edges",function(a){if(!arguments.length)return this.edgesArray.slice(0);if(1===arguments.length&&("string"==typeof a||"number"==typeof a))return this.edgesIndex[a];if(1===arguments.length&&"[object Array]"===Object.prototype.toString.call(a)){var b,c,d=[];for(b=0,c=a.length;c>b;b++){if("string"!=typeof a[b]&&"number"!=typeof a[b])throw"edges: Wrong arguments.";d.push(this.edgesIndex[a[b]])}return d}throw"edges: Wrong arguments."}),"undefined"!=typeof sigma?(sigma.classes=sigma.classes||Object.create(null),sigma.classes.graph=j):"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports.graph=j):this.graph=j}.call(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.classes"),sigma.classes.camera=function(a,b,c,d){sigma.classes.dispatcher.extend(this),Object.defineProperty(this,"graph",{value:b}),Object.defineProperty(this,"id",{value:a}),Object.defineProperty(this,"readPrefix",{value:"read_cam"+a+":"}),Object.defineProperty(this,"prefix",{value:"cam"+a+":"}),this.x=0,this.y=0,this.ratio=1,this.angle=0,this.isAnimated=!1,this.settings="object"==typeof d&&d?c.embedObject(d):c},sigma.classes.camera.prototype.goTo=function(b){if(!this.settings("enableCamera"))return this;var c,d,e=b||{},f=["x","y","ratio","angle"];for(c=0,d=f.length;d>c;c++)if(e[f[c]]!==a){if("number"!=typeof e[f[c]]||isNaN(e[f[c]]))throw'Value for "'+f[c]+'" is not a number.';this[f[c]]=e[f[c]]}return this.dispatchEvent("coordinatesUpdated"),this},sigma.classes.camera.prototype.applyView=function(b,c,d){d=d||{},c=c!==a?c:this.prefix,b=b!==a?b:this.readPrefix;var e,f,g,h=d.nodes||this.graph.nodes(),i=d.edges||this.graph.edges(),j=Math.cos(this.angle),k=Math.sin(this.angle),l=Math.pow(this.ratio,this.settings("nodesPowRatio")),m=Math.pow(this.ratio,this.settings("edgesPowRatio")),n=(d.width||0)/2,o=(d.height||0)/2;for(e=0,f=h.length;f>e;e++)g=h[e],g[c+"x"]=(((g[b+"x"]||0)-this.x)*j+((g[b+"y"]||0)-this.y)*k)/this.ratio+n,g[c+"y"]=(((g[b+"y"]||0)-this.y)*j-((g[b+"x"]||0)-this.x)*k)/this.ratio+o,g[c+"size"]=(g[b+"size"]||0)/l;for(e=0,f=i.length;f>e;e++)i[e][c+"size"]=(i[e][b+"size"]||0)/m;return this},sigma.classes.camera.prototype.graphPosition=function(a,b,c){var d=0,e=0,f=Math.cos(this.angle),g=Math.sin(this.angle);return c||(d=-(this.x*f+this.y*g)/this.ratio,e=-(this.y*f-this.x*g)/this.ratio),{x:(a*f+b*g)/this.ratio+d,y:(b*f-a*g)/this.ratio+e}},sigma.classes.camera.prototype.cameraPosition=function(a,b,c){var d=0,e=0,f=Math.cos(this.angle),g=Math.sin(this.angle);return c||(d=-(this.x*f+this.y*g)/this.ratio,e=-(this.y*f-this.x*g)/this.ratio),{x:((a-d)*f-(b-e)*g)*this.ratio,y:((b-e)*f+(a-d)*g)*this.ratio}},sigma.classes.camera.prototype.getMatrix=function(){var a=sigma.utils.matrices.scale(1/this.ratio),b=sigma.utils.matrices.rotation(this.angle),c=sigma.utils.matrices.translation(-this.x,-this.y),d=sigma.utils.matrices.multiply(c,sigma.utils.matrices.multiply(b,a));return d},sigma.classes.camera.prototype.getRectangle=function(a,b){var c=this.cameraPosition(a,0,!0),d=this.cameraPosition(0,b,!0),e=this.cameraPosition(a/2,b/2,!0),f=this.cameraPosition(a/4,0,!0).x,g=this.cameraPosition(0,b/4,!0).y;return{x1:this.x-e.x-f,y1:this.y-e.y-g,x2:this.x-e.x+f+c.x,y2:this.y-e.y-g+c.y,height:Math.sqrt(Math.pow(d.x,2)+Math.pow(d.y+2*g,2))}}}.call(this),function(a){"use strict";function b(a,b){var c=b.x+b.width/2,d=b.y+b.height/2,e=a.yd;d++)a.x2>=b[d][0].x&&a.x1<=b[d][1].x&&a.y1+a.height>=b[d][0].y&&a.y1<=b[d][2].y&&c.push(d);return c}function d(a,b){for(var c=[],d=0;4>d;d++)j.collision(a,b[d])&&c.push(d);return c}function e(a,b){var c,d,e=b.level+1,f=Math.round(b.bounds.width/2),g=Math.round(b.bounds.height/2),h=Math.round(b.bounds.x),j=Math.round(b.bounds.y);switch(a){case 0:c=h,d=j;break;case 1:c=h+f,d=j;break;case 2:c=h,d=j+g;break;case 3:c=h+f,d=j+g}return i({x:c,y:d,width:f,height:g},e,b.maxElements,b.maxLevel)}function f(b,d,g){if(g.leveli;i++)g.nodes[h[i]]===a&&(g.nodes[h[i]]=e(h[i],g)),f(b,d,g.nodes[h[i]]);else g.elements.push(b)}function g(c,d){if(d.levelg;g++)c.nodes[f[g]]!==a&&h(b,c.nodes[f[g]],d,e);else for(var j=0,k=c.elements.length;k>j;j++)e[c.elements[j].id]===a&&(e[c.elements[j].id]=c.elements[j]);return e}function i(a,b,c,d){return{level:b||0,bounds:a,corners:j.splitSquare(a),maxElements:c||20,maxLevel:d||4,elements:[],nodes:[]}}var j={pointToSquare:function(a){return{x1:a.x-a.size,y1:a.y-a.size,x2:a.x+a.size,y2:a.y-a.size,height:2*a.size}},isAxisAligned:function(a){return a.x1===a.x2||a.y1===a.y2},axisAlignedTopPoints:function(a){return a.y1===a.y2&&a.x1a.y1?{x1:a.x1-a.height,y1:a.y1,x2:a.x1,y2:a.y1,height:a.height}:a.x1===a.x2&&a.y2f;f++){var g=this.projection(b[f],a),h=this.projection(c[f],a);d.push(g.x*a.x+g.y*a.y),e.push(h.x*a.x+h.y*a.y)}var i=Math.max.apply(Math,d),j=Math.max.apply(Math,e),k=Math.min.apply(Math,d),l=Math.min.apply(Math,e);return i>=l&&j>=k},collision:function(a,b){for(var c=this.axis(a,b),d=!0,e=0;4>e;e++)d=d&&this.axisCollision(c[e],a,b);return d}},k=function(){this._geom=j,this._tree=null,this._cache={query:!1,result:!1}};k.prototype.index=function(a,b){if(!b.bounds)throw"sigma.classes.quad.index: bounds information not given.";var c=b.prefix||"";this._tree=i(b.bounds,0,b.maxElements,b.maxLevel);for(var d=0,e=a.length;e>d;d++)f(a[d],j.pointToSquare({x:a[d][c+"x"],y:a[d][c+"y"],size:a[d][c+"size"]}),this._tree);return this._cache={query:!1,result:!1},this._tree},k.prototype.point=function(a,b){return this._tree?g({x:a,y:b},this._tree)||[]:[]},k.prototype.area=function(a){var b,e,f=JSON.stringify(a);if(this._cache.query===f)return this._cache.result;j.isAxisAligned(a)?(b=c,e=j.axisAlignedTopPoints(a)):(b=d,e=j.rectangleCorners(a));var g=this._tree?h(e,this._tree,b):[],i=[];for(var k in g)i.push(g[k]);return this._cache.query=f,this._cache.result=i,i},"undefined"!=typeof this.sigma?(this.sigma.classes=this.sigma.classes||{},this.sigma.classes.quad=k):"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=k),exports.quad=k):this.quad=k}.call(this),function(a){"use strict";function b(a,b){var c=b.x+b.width/2,d=b.y+b.height/2,e=a.yd;d++)a.x2>=b[d][0].x&&a.x1<=b[d][1].x&&a.y1+a.height>=b[d][0].y&&a.y1<=b[d][2].y&&c.push(d);return c}function d(a,b){for(var c=[],d=0;4>d;d++)j.collision(a,b[d])&&c.push(d);return c}function e(a,b){var c,d,e=b.level+1,f=Math.round(b.bounds.width/2),g=Math.round(b.bounds.height/2),h=Math.round(b.bounds.x),j=Math.round(b.bounds.y);switch(a){case 0:c=h,d=j;break;case 1:c=h+f,d=j;break;case 2:c=h,d=j+g;break;case 3:c=h+f,d=j+g}return i({x:c,y:d,width:f,height:g},e,b.maxElements,b.maxLevel)}function f(b,d,g){if(g.leveli;i++)g.nodes[h[i]]===a&&(g.nodes[h[i]]=e(h[i],g)),f(b,d,g.nodes[h[i]]);else g.elements.push(b)}function g(c,d){if(d.levelg;g++)c.nodes[f[g]]!==a&&h(b,c.nodes[f[g]],d,e);else for(var j=0,k=c.elements.length;k>j;j++)e[c.elements[j].id]===a&&(e[c.elements[j].id]=c.elements[j]);return e}function i(a,b,c,d){return{level:b||0,bounds:a,corners:j.splitSquare(a),maxElements:c||40,maxLevel:d||8,elements:[],nodes:[]}}var j={pointToSquare:function(a){return{x1:a.x-a.size,y1:a.y-a.size,x2:a.x+a.size,y2:a.y-a.size,height:2*a.size}},lineToSquare:function(a){return a.y1a.y1?{x1:a.x1-a.height,y1:a.y1,x2:a.x1,y2:a.y1,height:a.height}:a.x1===a.x2&&a.y2f;f++){var g=this.projection(b[f],a),h=this.projection(c[f],a);d.push(g.x*a.x+g.y*a.y),e.push(h.x*a.x+h.y*a.y)}var i=Math.max.apply(Math,d),j=Math.max.apply(Math,e),k=Math.min.apply(Math,d),l=Math.min.apply(Math,e);return i>=l&&j>=k},collision:function(a,b){for(var c=this.axis(a,b),d=!0,e=0;4>e;e++)d=d&&this.axisCollision(c[e],a,b);return d}},k=function(){this._geom=j,this._tree=null,this._cache={query:!1,result:!1},this._enabled=!0};k.prototype.index=function(a,b){if(!this._enabled)return this._tree;if(!b.bounds)throw"sigma.classes.edgequad.index: bounds information not given.";var c,d,e,g,h,k=b.prefix||"";this._tree=i(b.bounds,0,b.maxElements,b.maxLevel);for(var l=a.edges(),m=0,n=l.length;n>m;m++)d=a.nodes(l[m].source),e=a.nodes(l[m].target),h={x1:d[k+"x"],y1:d[k+"y"],x2:e[k+"x"],y2:e[k+"y"],size:l[m][k+"size"]||0},"curve"===l[m].type||"curvedArrow"===l[m].type?d.id===e.id?(g={x:d[k+"x"],y:d[k+"y"],size:d[k+"size"]||0},f(l[m],j.selfLoopToSquare(g),this._tree)):(c=sigma.utils.getQuadraticControlPoint(h.x1,h.y1,h.x2,h.y2),f(l[m],j.quadraticCurveToSquare(h,c),this._tree)):f(l[m],j.lineToSquare(h),this._tree);return this._cache={query:!1,result:!1},this._tree},k.prototype.point=function(a,b){return this._enabled&&this._tree?g({x:a,y:b},this._tree)||[]:[]},k.prototype.area=function(a){if(!this._enabled)return[];var b,e,f=JSON.stringify(a);if(this._cache.query===f)return this._cache.result;j.isAxisAligned(a)?(b=c,e=j.axisAlignedTopPoints(a)):(b=d,e=j.rectangleCorners(a));var g=this._tree?h(e,this._tree,b):[],i=[];for(var k in g)i.push(g[k]);return this._cache.query=f,this._cache.result=i,i},"undefined"!=typeof this.sigma?(this.sigma.classes=this.sigma.classes||{},this.sigma.classes.edgequad=k):"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=k),exports.edgequad=k):this.edgequad=k}.call(this),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.captors"),sigma.captors.mouse=function(a,b,c){function d(a){var b,c,d;return y("mouseEnabled")&&(v.dispatchEvent("mousemove",sigma.utils.mouseCoords(a)),q)?(r=!0,s=!0,u&&clearTimeout(u),u=setTimeout(function(){r=!1},y("dragTimeout")),sigma.misc.animation.killAll(x),x.isMoving=!0,d=x.cameraPosition(sigma.utils.getX(a)-o,sigma.utils.getY(a)-p,!0),b=k-d.x,c=l-d.y,(b!==x.x||c!==x.y)&&(m=x.x,n=x.y,x.goTo({x:b,y:c})),a.preventDefault?a.preventDefault():a.returnValue=!1,a.stopPropagation(),!1):void 0}function e(a){if(y("mouseEnabled")&&q){q=!1,u&&clearTimeout(u),x.isMoving=!1;var b=sigma.utils.getX(a),c=sigma.utils.getY(a);r?(sigma.misc.animation.killAll(x),sigma.misc.animation.camera(x,{x:x.x+y("mouseInertiaRatio")*(x.x-m),y:x.y+y("mouseInertiaRatio")*(x.y-n)},{easing:"quadraticOut",duration:y("mouseInertiaDuration")})):(o!==b||p!==c)&&x.goTo({x:x.x,y:x.y}),v.dispatchEvent("mouseup",sigma.utils.mouseCoords(a)),r=!1}}function f(a){if(y("mouseEnabled"))switch(k=x.x,l=x.y,m=x.x,n=x.y,o=sigma.utils.getX(a),p=sigma.utils.getY(a),s=!1,t=(new Date).getTime(),a.which){case 2:break;case 3:v.dispatchEvent("rightclick",sigma.utils.mouseCoords(a,o,p));break;default:q=!0,v.dispatchEvent("mousedown",sigma.utils.mouseCoords(a,o,p))}}function g(){y("mouseEnabled")&&v.dispatchEvent("mouseout")}function h(a){if(y("mouseEnabled")){var b=sigma.utils.mouseCoords(a);b.isDragging=(new Date).getTime()-t>100&&s,v.dispatchEvent("click",b)}return a.preventDefault?a.preventDefault():a.returnValue=!1,a.stopPropagation(),!1}function i(a){var b,c,d;return y("mouseEnabled")?(c=1/y("doubleClickZoomingRatio"),v.dispatchEvent("doubleclick",sigma.utils.mouseCoords(a,o,p)),y("doubleClickEnabled")&&(b=x.cameraPosition(sigma.utils.getX(a)-sigma.utils.getCenter(a).x,sigma.utils.getY(a)-sigma.utils.getCenter(a).y,!0),d={duration:y("doubleClickZoomDuration")},sigma.utils.zoomTo(x,b.x,b.y,c,d)),a.preventDefault?a.preventDefault():a.returnValue=!1,a.stopPropagation(),!1):void 0}function j(a){var b,c,d;return y("mouseEnabled")&&y("mouseWheelEnabled")?(c=sigma.utils.getDelta(a)>0?1/y("zoomingRatio"):y("zoomingRatio"),b=x.cameraPosition(sigma.utils.getX(a)-sigma.utils.getCenter(a).x,sigma.utils.getY(a)-sigma.utils.getCenter(a).y,!0),d={duration:y("mouseZoomDuration")},sigma.utils.zoomTo(x,b.x,b.y,c,d),a.preventDefault?a.preventDefault():a.returnValue=!1,a.stopPropagation(),!1):void 0}var k,l,m,n,o,p,q,r,s,t,u,v=this,w=a,x=b,y=c;sigma.classes.dispatcher.extend(this),sigma.utils.doubleClick(w,"click",i),w.addEventListener("DOMMouseScroll",j,!1),w.addEventListener("mousewheel",j,!1),w.addEventListener("mousemove",d,!1),w.addEventListener("mousedown",f,!1),w.addEventListener("click",h,!1),w.addEventListener("mouseout",g,!1),document.addEventListener("mouseup",e,!1),this.kill=function(){sigma.utils.unbindDoubleClick(w,"click"),w.removeEventListener("DOMMouseScroll",j),w.removeEventListener("mousewheel",j),w.removeEventListener("mousemove",d),w.removeEventListener("mousedown",f),w.removeEventListener("click",h),w.removeEventListener("mouseout",g),document.removeEventListener("mouseup",e)}}}.call(this),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.captors"),sigma.captors.touch=function(a,b,c){function d(a){var b=sigma.utils.getOffset(B);return{x:a.pageX-b.left,y:a.pageY-b.top}}function e(a){if(D("touchEnabled")){var b,c,e,f,g,h;switch(E=a.touches,E.length){case 1:C.isMoving=!0,w=1,i=C.x,j=C.y,m=C.x,n=C.y,g=d(E[0]),q=g.x,r=g.y;break;case 2:return C.isMoving=!0,w=2,g=d(E[0]),h=d(E[1]),b=g.x,e=g.y,c=h.x,f=h.y,m=C.x,n=C.y,k=C.angle,l=C.ratio,i=C.x,j=C.y,q=b,r=e,s=c,t=f,u=Math.atan2(t-r,s-q),v=Math.sqrt((t-r)*(t-r)+(s-q)*(s-q)),a.preventDefault(),!1}}}function f(a){if(D("touchEnabled")){E=a.touches;var b=D("touchInertiaRatio");switch(z&&(x=!1,clearTimeout(z)),w){case 2:if(1===a.touches.length){e(a),a.preventDefault();break}case 1:C.isMoving=!1,A.dispatchEvent("stopDrag"),x&&(y=!1,sigma.misc.animation.camera(C,{x:C.x+b*(C.x-m),y:C.y+b*(C.y-n)},{easing:"quadraticOut",duration:D("touchInertiaDuration")})),x=!1,w=0}}}function g(a){if(!y&&D("touchEnabled")){var b,c,e,f,g,h,B,F,G,H,I,J,K,L,M,N,O;switch(E=a.touches,x=!0,z&&clearTimeout(z),z=setTimeout(function(){x=!1},D("dragTimeout")),w){case 1:F=d(E[0]),b=F.x,e=F.y,H=C.cameraPosition(b-q,e-r,!0),L=i-H.x,M=j-H.y,(L!==C.x||M!==C.y)&&(m=C.x,n=C.y,C.goTo({x:L,y:M}),A.dispatchEvent("mousemove",sigma.utils.mouseCoords(a,F.x,F.y)),A.dispatchEvent("drag"));break;case 2:F=d(E[0]),G=d(E[1]),b=F.x,e=F.y,c=G.x,f=G.y,I=C.cameraPosition((q+s)/2-sigma.utils.getCenter(a).x,(r+t)/2-sigma.utils.getCenter(a).y,!0),B=C.cameraPosition((b+c)/2-sigma.utils.getCenter(a).x,(e+f)/2-sigma.utils.getCenter(a).y,!0),J=Math.atan2(f-e,c-b)-u,K=Math.sqrt((f-e)*(f-e)+(c-b)*(c-b))/v,b=I.x,e=I.y,N=l/K,b*=K,e*=K,O=k-J,g=Math.cos(-J),h=Math.sin(-J),c=b*g+e*h,f=e*g-b*h,b=c,e=f,L=b-B.x+i,M=e-B.y+j,(N!==C.ratio||O!==C.angle||L!==C.x||M!==C.y)&&(m=C.x,n=C.y,o=C.angle,p=C.ratio,C.goTo({x:L,y:M,angle:O,ratio:N}),A.dispatchEvent("drag"))}return a.preventDefault(),!1}}function h(a){var b,c,e;return a.touches&&1===a.touches.length&&D("touchEnabled")?(y=!0,c=1/D("doubleClickZoomingRatio"),b=d(a.touches[0]),A.dispatchEvent("doubleclick",sigma.utils.mouseCoords(a,b.x,b.y)),D("doubleClickEnabled")&&(b=C.cameraPosition(b.x-sigma.utils.getCenter(a).x,b.y-sigma.utils.getCenter(a).y,!0),e={duration:D("doubleClickZoomDuration"),onComplete:function(){y=!1}},sigma.utils.zoomTo(C,b.x,b.y,c,e)),a.preventDefault?a.preventDefault():a.returnValue=!1,a.stopPropagation(),!1):void 0}var i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A=this,B=a,C=b,D=c,E=[];sigma.classes.dispatcher.extend(this),sigma.utils.doubleClick(B,"touchstart",h),B.addEventListener("touchstart",e,!1),B.addEventListener("touchend",f,!1),B.addEventListener("touchcancel",f,!1),B.addEventListener("touchleave",f,!1),B.addEventListener("touchmove",g,!1),this.kill=function(){sigma.utils.unbindDoubleClick(B,"touchstart"),B.addEventListener("touchstart",e),B.addEventListener("touchend",f),B.addEventListener("touchcancel",f),B.addEventListener("touchleave",f),B.addEventListener("touchmove",g)}}}.call(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";if("undefined"==typeof conrad)throw"conrad is not declared";sigma.utils.pkg("sigma.renderers"),sigma.renderers.canvas=function(a,b,c,d){if("object"!=typeof d)throw"sigma.renderers.canvas: Wrong arguments.";if(!(d.container instanceof HTMLElement))throw"Container not found.";var e,f,g,h;for(sigma.classes.dispatcher.extend(this),Object.defineProperty(this,"conradId",{value:sigma.utils.id()}),this.graph=a,this.camera=b,this.contexts={},this.domElements={},this.options=d,this.container=this.options.container,this.settings="object"==typeof d.settings&&d.settings?c.embedObjects(d.settings):c,this.nodesOnScreen=[],this.edgesOnScreen=[],this.jobs={},this.options.prefix="renderer"+this.conradId+":",this.settings("batchEdgesDrawing")?(this.initDOM("canvas","edges"),this.initDOM("canvas","scene"),this.contexts.nodes=this.contexts.scene,this.contexts.labels=this.contexts.scene):(this.initDOM("canvas","scene"),this.contexts.edges=this.contexts.scene,this.contexts.nodes=this.contexts.scene,this.contexts.labels=this.contexts.scene),this.initDOM("canvas","mouse"),this.contexts.hover=this.contexts.mouse,this.captors=[],g=this.options.captors||[sigma.captors.mouse,sigma.captors.touch],e=0,f=g.length;f>e;e++)h="function"==typeof g[e]?g[e]:sigma.captors[g[e]],this.captors.push(new h(this.domElements.mouse,this.camera,this.settings));sigma.misc.bindEvents.call(this,this.options.prefix),sigma.misc.drawHovers.call(this,this.options.prefix),this.resize(!1)},sigma.renderers.canvas.prototype.render=function(b){b=b||{};var c,d,e,f,g,h,i,j,k,l,m,n,o,p={},q=this.graph,r=this.graph.nodes,s=(this.options.prefix||"",this.settings(b,"drawEdges")),t=this.settings(b,"drawNodes"),u=this.settings(b,"drawLabels"),v=this.settings(b,"drawEdgeLabels"),w=this.settings.embedObjects(b,{prefix:this.options.prefix});this.resize(!1),this.settings(b,"hideEdgesOnMove")&&(this.camera.isAnimated||this.camera.isMoving)&&(s=!1),this.camera.applyView(a,this.options.prefix,{width:this.width,height:this.height}),this.clear();for(e in this.jobs)conrad.hasJob(e)&&conrad.killJob(e);for(this.edgesOnScreen=[],this.nodesOnScreen=this.camera.quadtree.area(this.camera.getRectangle(this.width,this.height)),c=this.nodesOnScreen,d=0,f=c.length;f>d;d++)p[c[d].id]=c[d];if(s){for(c=q.edges(),d=0,f=c.length;f>d;d++)g=c[d],!p[g.source]&&!p[g.target]||g.hidden||r(g.source).hidden||r(g.target).hidden||this.edgesOnScreen.push(g);if(this.settings(b,"batchEdgesDrawing"))h="edges_"+this.conradId,n=w("canvasEdgesBatchSize"),l=this.edgesOnScreen,f=l.length,k=0,i=Math.min(l.length,k+n),j=function(){for(o=this.contexts.edges.globalCompositeOperation,this.contexts.edges.globalCompositeOperation="destination-over",m=sigma.canvas.edges,d=k;i>d;d++)g=l[d],(m[g.type||this.settings(b,"defaultEdgeType")]||m.def)(g,q.nodes(g.source),q.nodes(g.target),this.contexts.edges,w);if(v)for(m=sigma.canvas.edges.labels,d=k;i>d;d++)g=l[d],g.hidden||(m[g.type||this.settings(b,"defaultEdgeType")]||m.def)(g,q.nodes(g.source),q.nodes(g.target),this.contexts.labels,w);return this.contexts.edges.globalCompositeOperation=o,i===l.length?(delete this.jobs[h],!1):(k=i+1,i=Math.min(l.length,k+n),!0)},this.jobs[h]=j,conrad.addJob(h,j.bind(this));else{for(m=sigma.canvas.edges,c=this.edgesOnScreen,d=0,f=c.length;f>d;d++)g=c[d],(m[g.type||this.settings(b,"defaultEdgeType")]||m.def)(g,q.nodes(g.source),q.nodes(g.target),this.contexts.edges,w);if(v)for(m=sigma.canvas.edges.labels,c=this.edgesOnScreen,d=0,f=c.length;f>d;d++)c[d].hidden||(m[c[d].type||this.settings(b,"defaultEdgeType")]||m.def)(c[d],q.nodes(c[d].source),q.nodes(c[d].target),this.contexts.labels,w)}}if(t)for(m=sigma.canvas.nodes,c=this.nodesOnScreen,d=0,f=c.length;f>d;d++)c[d].hidden||(m[c[d].type||this.settings(b,"defaultNodeType")]||m.def)(c[d],this.contexts.nodes,w);if(u)for(m=sigma.canvas.labels,c=this.nodesOnScreen,d=0,f=c.length;f>d;d++)c[d].hidden||(m[c[d].type||this.settings(b,"defaultNodeType")]||m.def)(c[d],this.contexts.labels,w);return this.dispatchEvent("render"),this},sigma.renderers.canvas.prototype.initDOM=function(a,b){var c=document.createElement(a);c.style.position="absolute",c.setAttribute("class","sigma-"+b),this.domElements[b]=c,this.container.appendChild(c),"canvas"===a.toLowerCase()&&(this.contexts[b]=c.getContext("2d"))},sigma.renderers.canvas.prototype.resize=function(b,c){var d,e=this.width,f=this.height,g=sigma.utils.getPixelRatio();if(b!==a&&c!==a?(this.width=b,this.height=c):(this.width=this.container.offsetWidth,this.height=this.container.offsetHeight,b=this.width,c=this.height),e!==this.width||f!==this.height)for(d in this.domElements)this.domElements[d].style.width=b+"px",this.domElements[d].style.height=c+"px","canvas"===this.domElements[d].tagName.toLowerCase()&&(this.domElements[d].setAttribute("width",b*g+"px"),this.domElements[d].setAttribute("height",c*g+"px"),1!==g&&this.contexts[d].scale(g,g));return this},sigma.renderers.canvas.prototype.clear=function(){for(var a in this.contexts)this.contexts[a].clearRect(0,0,this.width,this.height);return this},sigma.renderers.canvas.prototype.kill=function(){for(var a,b;b=this.captors.pop();)b.kill();delete this.captors;for(a in this.domElements)this.domElements[a].parentNode.removeChild(this.domElements[a]),delete this.domElements[a],delete this.contexts[a];delete this.domElements,delete this.contexts},sigma.utils.pkg("sigma.canvas.nodes"),sigma.utils.pkg("sigma.canvas.edges"),sigma.utils.pkg("sigma.canvas.labels")}.call(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.renderers"),sigma.renderers.webgl=function(a,b,c,d){if("object"!=typeof d)throw"sigma.renderers.webgl: Wrong arguments.";if(!(d.container instanceof HTMLElement))throw"Container not found.";var e,f,g,h;for(sigma.classes.dispatcher.extend(this),this.jobs={},Object.defineProperty(this,"conradId",{value:sigma.utils.id()}),this.graph=a,this.camera=b,this.contexts={},this.domElements={},this.options=d,this.container=this.options.container,this.settings="object"==typeof d.settings&&d.settings?c.embedObjects(d.settings):c,this.options.prefix=this.camera.readPrefix,Object.defineProperty(this,"nodePrograms",{value:{}}),Object.defineProperty(this,"edgePrograms",{value:{}}),Object.defineProperty(this,"nodeFloatArrays",{value:{}}),Object.defineProperty(this,"edgeFloatArrays",{value:{}}),Object.defineProperty(this,"edgeIndicesArrays",{value:{}}),this.settings(d,"batchEdgesDrawing")?(this.initDOM("canvas","edges",!0),this.initDOM("canvas","nodes",!0)):(this.initDOM("canvas","scene",!0),this.contexts.nodes=this.contexts.scene,this.contexts.edges=this.contexts.scene),this.initDOM("canvas","labels"),this.initDOM("canvas","mouse"),this.contexts.hover=this.contexts.mouse,this.captors=[],g=this.options.captors||[sigma.captors.mouse,sigma.captors.touch],e=0,f=g.length;f>e;e++)h="function"==typeof g[e]?g[e]:sigma.captors[g[e]],this.captors.push(new h(this.domElements.mouse,this.camera,this.settings));sigma.misc.bindEvents.call(this,this.camera.prefix),sigma.misc.drawHovers.call(this,this.camera.prefix),this.resize()},sigma.renderers.webgl.prototype.process=function(){var a,b,c,d,e,f,g=this.graph,h=sigma.utils.extend(h,this.options),i=this.settings(h,"defaultEdgeType"),j=this.settings(h,"defaultNodeType");for(d in this.nodeFloatArrays)delete this.nodeFloatArrays[d];for(d in this.edgeFloatArrays)delete this.edgeFloatArrays[d];for(d in this.edgeIndicesArrays)delete this.edgeIndicesArrays[d];for(a=g.edges(),b=0,c=a.length;c>b;b++)e=a[b].type||i,d=e&&sigma.webgl.edges[e]?e:"def",this.edgeFloatArrays[d]||(this.edgeFloatArrays[d]={edges:[]}),this.edgeFloatArrays[d].edges.push(a[b]);for(a=g.nodes(),b=0,c=a.length;c>b;b++)e=a[b].type||j,d=e&&sigma.webgl.nodes[e]?e:"def",this.nodeFloatArrays[d]||(this.nodeFloatArrays[d]={nodes:[]}),this.nodeFloatArrays[d].nodes.push(a[b]);for(d in this.edgeFloatArrays){for(f=sigma.webgl.edges[d],a=this.edgeFloatArrays[d].edges,this.edgeFloatArrays[d].array=new Float32Array(a.length*f.POINTS*f.ATTRIBUTES),b=0,c=a.length;c>b;b++)a[b].hidden||g.nodes(a[b].source).hidden||g.nodes(a[b].target).hidden||f.addEdge(a[b],g.nodes(a[b].source),g.nodes(a[b].target),this.edgeFloatArrays[d].array,b*f.POINTS*f.ATTRIBUTES,h.prefix,this.settings);"function"==typeof f.computeIndices&&(this.edgeIndicesArrays[d]=f.computeIndices(this.edgeFloatArrays[d].array))}for(d in this.nodeFloatArrays)for(f=sigma.webgl.nodes[d],a=this.nodeFloatArrays[d].nodes,this.nodeFloatArrays[d].array=new Float32Array(a.length*f.POINTS*f.ATTRIBUTES),b=0,c=a.length;c>b;b++)this.nodeFloatArrays[d].array||(this.nodeFloatArrays[d].array=new Float32Array(a.length*f.POINTS*f.ATTRIBUTES)),a[b].hidden||f.addNode(a[b],this.nodeFloatArrays[d].array,b*f.POINTS*f.ATTRIBUTES,h.prefix,this.settings);return this},sigma.renderers.webgl.prototype.render=function(b){var c,d,e,f,g,h,i=this,j=(this.graph,this.contexts.nodes),k=this.contexts.edges,l=this.camera.getMatrix(),m=sigma.utils.extend(b,this.options),n=this.settings(m,"drawLabels"),o=this.settings(m,"drawEdges"),p=this.settings(m,"drawNodes");this.resize(!1),this.settings(m,"hideEdgesOnMove")&&(this.camera.isAnimated||this.camera.isMoving)&&(o=!1),this.clear(),l=sigma.utils.matrices.multiply(l,sigma.utils.matrices.translation(this.width/2,this.height/2));for(f in this.jobs)conrad.hasJob(f)&&conrad.killJob(f);if(o)if(this.settings(m,"batchEdgesDrawing"))(function(){var a,b,c,d,e,f,g,h,i,j;c="edges_"+this.conradId,j=this.settings(m,"webglEdgesBatchSize"),a=Object.keys(this.edgeFloatArrays),a.length&&(b=0,i=sigma.webgl.edges[a[b]],e=this.edgeFloatArrays[a[b]].array,h=this.edgeIndicesArrays[a[b]],g=0,f=Math.min(g+j*i.POINTS,e.length/i.ATTRIBUTES),d=function(){return this.edgePrograms[a[b]]||(this.edgePrograms[a[b]]=i.initProgram(k)),f>g&&(k.useProgram(this.edgePrograms[a[b]]),i.render(k,this.edgePrograms[a[b]],e,{settings:this.settings,matrix:l,width:this.width,height:this.height,ratio:this.camera.ratio,scalingRatio:this.settings(m,"webglOversamplingRatio"),start:g,count:f-g,indicesData:h})),f>=e.length/i.ATTRIBUTES&&b===a.length-1?(delete this.jobs[c],!1):(f>=e.length/i.ATTRIBUTES?(b++,e=this.edgeFloatArrays[a[b]].array,i=sigma.webgl.edges[a[b]],g=0,f=Math.min(g+j*i.POINTS,e.length/i.ATTRIBUTES)):(g=f,f=Math.min(g+j*i.POINTS,e.length/i.ATTRIBUTES)),!0)},this.jobs[c]=d,conrad.addJob(c,d.bind(this)))}).call(this);else for(f in this.edgeFloatArrays)h=sigma.webgl.edges[f],this.edgePrograms[f]||(this.edgePrograms[f]=h.initProgram(k)),this.edgeFloatArrays[f]&&(k.useProgram(this.edgePrograms[f]),h.render(k,this.edgePrograms[f],this.edgeFloatArrays[f].array,{settings:this.settings,matrix:l,width:this.width,height:this.height,ratio:this.camera.ratio,scalingRatio:this.settings(m,"webglOversamplingRatio"),indicesData:this.edgeIndicesArrays[f]})); -if(p){j.blendFunc(j.SRC_ALPHA,j.ONE_MINUS_SRC_ALPHA),j.enable(j.BLEND);for(f in this.nodeFloatArrays)h=sigma.webgl.nodes[f],this.nodePrograms[f]||(this.nodePrograms[f]=h.initProgram(j)),this.nodeFloatArrays[f]&&(j.useProgram(this.nodePrograms[f]),h.render(j,this.nodePrograms[f],this.nodeFloatArrays[f].array,{settings:this.settings,matrix:l,width:this.width,height:this.height,ratio:this.camera.ratio,scalingRatio:this.settings(m,"webglOversamplingRatio")}))}if(n)for(c=this.camera.quadtree.area(this.camera.getRectangle(this.width,this.height)),this.camera.applyView(a,a,{nodes:c,edges:[],width:this.width,height:this.height}),g=function(a){return i.settings({prefix:i.camera.prefix},a)},d=0,e=c.length;e>d;d++)c[d].hidden||(sigma.canvas.labels[c[d].type||this.settings(m,"defaultNodeType")]||sigma.canvas.labels.def)(c[d],this.contexts.labels,g);return this.dispatchEvent("render"),this},sigma.renderers.webgl.prototype.initDOM=function(a,b,c){var d=document.createElement(a),e=this;d.style.position="absolute",d.setAttribute("class","sigma-"+b),this.domElements[b]=d,this.container.appendChild(d),"canvas"===a.toLowerCase()&&(this.contexts[b]=d.getContext(c?"experimental-webgl":"2d",{preserveDrawingBuffer:!0}),c&&(d.addEventListener("webglcontextlost",function(a){a.preventDefault()},!1),d.addEventListener("webglcontextrestored",function(){e.render()},!1)))},sigma.renderers.webgl.prototype.resize=function(b,c){var d,e=this.width,f=this.height,g=sigma.utils.getPixelRatio();if(b!==a&&c!==a?(this.width=b,this.height=c):(this.width=this.container.offsetWidth,this.height=this.container.offsetHeight,b=this.width,c=this.height),e!==this.width||f!==this.height)for(d in this.domElements)this.domElements[d].style.width=b+"px",this.domElements[d].style.height=c+"px","canvas"===this.domElements[d].tagName.toLowerCase()&&(this.contexts[d]&&this.contexts[d].scale?(this.domElements[d].setAttribute("width",b*g+"px"),this.domElements[d].setAttribute("height",c*g+"px"),1!==g&&this.contexts[d].scale(g,g)):(this.domElements[d].setAttribute("width",b*this.settings("webglOversamplingRatio")+"px"),this.domElements[d].setAttribute("height",c*this.settings("webglOversamplingRatio")+"px")));for(d in this.contexts)this.contexts[d]&&this.contexts[d].viewport&&this.contexts[d].viewport(0,0,this.width*this.settings("webglOversamplingRatio"),this.height*this.settings("webglOversamplingRatio"));return this},sigma.renderers.webgl.prototype.clear=function(){return this.contexts.labels.clearRect(0,0,this.width,this.height),this.contexts.nodes.clear(this.contexts.nodes.COLOR_BUFFER_BIT),this.contexts.edges.clear(this.contexts.edges.COLOR_BUFFER_BIT),this},sigma.renderers.webgl.prototype.kill=function(){for(var a,b;b=this.captors.pop();)b.kill();delete this.captors;for(a in this.domElements)this.domElements[a].parentNode.removeChild(this.domElements[a]),delete this.domElements[a],delete this.contexts[a];delete this.domElements,delete this.contexts},sigma.utils.pkg("sigma.webgl.nodes"),sigma.utils.pkg("sigma.webgl.edges"),sigma.utils.pkg("sigma.canvas.labels")}.call(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";if("undefined"==typeof conrad)throw"conrad is not declared";sigma.utils.pkg("sigma.renderers"),sigma.renderers.svg=function(a,b,c,d){if("object"!=typeof d)throw"sigma.renderers.svg: Wrong arguments.";if(!(d.container instanceof HTMLElement))throw"Container not found.";var e,f,g,h,i=this;for(sigma.classes.dispatcher.extend(this),this.graph=a,this.camera=b,this.domElements={graph:null,groups:{},nodes:{},edges:{},labels:{},hovers:{}},this.measurementCanvas=null,this.options=d,this.container=this.options.container,this.settings="object"==typeof d.settings&&d.settings?c.embedObjects(d.settings):c,this.settings("freeStyle",!!this.options.freeStyle),this.settings("xmlns","http://www.w3.org/2000/svg"),this.nodesOnScreen=[],this.edgesOnScreen=[],this.options.prefix="renderer"+sigma.utils.id()+":",this.initDOM("svg"),this.captors=[],g=this.options.captors||[sigma.captors.mouse,sigma.captors.touch],e=0,f=g.length;f>e;e++)h="function"==typeof g[e]?g[e]:sigma.captors[g[e]],this.captors.push(new h(this.domElements.graph,this.camera,this.settings));window.addEventListener("resize",function(){i.resize()}),sigma.misc.bindDOMEvents.call(this,this.domElements.graph),this.bindHovers(this.options.prefix),this.resize(!1)},sigma.renderers.svg.prototype.render=function(b){b=b||{};var c,d,e,f,g,h,i,j,k,l={},m=this.graph,n=this.graph.nodes,o=(this.options.prefix||"",this.settings(b,"drawEdges")),p=this.settings(b,"drawNodes"),q=(this.settings(b,"drawLabels"),this.settings.embedObjects(b,{prefix:this.options.prefix,forceLabels:this.options.forceLabels}));for(this.settings(b,"hideEdgesOnMove")&&(this.camera.isAnimated||this.camera.isMoving)&&(o=!1),this.camera.applyView(a,this.options.prefix,{width:this.width,height:this.height}),this.hideDOMElements(this.domElements.nodes),this.hideDOMElements(this.domElements.edges),this.hideDOMElements(this.domElements.labels),this.edgesOnScreen=[],this.nodesOnScreen=this.camera.quadtree.area(this.camera.getRectangle(this.width,this.height)),c=this.nodesOnScreen,d=0,f=c.length;f>d;d++)l[c[d].id]=c[d];for(c=m.edges(),d=0,f=c.length;f>d;d++)g=c[d],!l[g.source]&&!l[g.target]||g.hidden||n(g.source).hidden||n(g.target).hidden||this.edgesOnScreen.push(g);if(j=sigma.svg.nodes,k=sigma.svg.labels,p)for(c=this.nodesOnScreen,d=0,f=c.length;f>d;d++)c[d].hidden||this.domElements.nodes[c[d].id]||(e=(j[c[d].type]||j.def).create(c[d],q),this.domElements.nodes[c[d].id]=e,this.domElements.groups.nodes.appendChild(e),e=(k[c[d].type]||k.def).create(c[d],q),this.domElements.labels[c[d].id]=e,this.domElements.groups.labels.appendChild(e));if(p)for(c=this.nodesOnScreen,d=0,f=c.length;f>d;d++)c[d].hidden||((j[c[d].type]||j.def).update(c[d],this.domElements.nodes[c[d].id],q),(k[c[d].type]||k.def).update(c[d],this.domElements.labels[c[d].id],q));if(j=sigma.svg.edges,o)for(c=this.edgesOnScreen,d=0,f=c.length;f>d;d++)this.domElements.edges[c[d].id]||(h=n(c[d].source),i=n(c[d].target),e=(j[c[d].type]||j.def).create(c[d],h,i,q),this.domElements.edges[c[d].id]=e,this.domElements.groups.edges.appendChild(e));if(o)for(c=this.edgesOnScreen,d=0,f=c.length;f>d;d++)h=n(c[d].source),i=n(c[d].target),(j[c[d].type]||j.def).update(c[d],this.domElements.edges[c[d].id],h,i,q);return this.dispatchEvent("render"),this},sigma.renderers.svg.prototype.initDOM=function(a){var b,c,d,e=document.createElementNS(this.settings("xmlns"),a),f=this.settings("classPrefix");e.style.position="absolute",e.setAttribute("class",f+"-svg"),e.setAttribute("xmlns",this.settings("xmlns")),e.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink"),e.setAttribute("version","1.1");var g=document.createElement("canvas");g.setAttribute("class",f+"-measurement-canvas"),this.domElements.graph=this.container.appendChild(e);var h=["edges","nodes","labels","hovers"];for(d=0,c=h.length;c>d;d++)b=document.createElementNS(this.settings("xmlns"),"g"),b.setAttributeNS(null,"id",f+"-group-"+h[d]),b.setAttributeNS(null,"class",f+"-group"),this.domElements.groups[h[d]]=this.domElements.graph.appendChild(b);this.container.appendChild(g),this.measurementCanvas=g.getContext("2d")},sigma.renderers.svg.prototype.hideDOMElements=function(a){var b,c;for(c in a)b=a[c],sigma.svg.utils.hide(b);return this},sigma.renderers.svg.prototype.bindHovers=function(a){function b(b){var c=b.data.node,d=g.settings.embedObjects({prefix:a});if(d("enableHovering")){var h=(f[c.type]||f.def).create(c,g.domElements.nodes[c.id],g.measurementCanvas,d);g.domElements.hovers[c.id]=h,g.domElements.groups.hovers.appendChild(h),e=c}}function c(b){var c=b.data.node,d=g.settings.embedObjects({prefix:a});d("enableHovering")&&(g.domElements.groups.hovers.removeChild(g.domElements.hovers[c.id]),e=null,delete g.domElements.hovers[c.id],g.domElements.groups.nodes.appendChild(g.domElements.nodes[c.id]))}function d(){if(e){var b=g.settings.embedObjects({prefix:a});g.domElements.groups.hovers.removeChild(g.domElements.hovers[e.id]),delete g.domElements.hovers[e.id];var c=(f[e.type]||f.def).create(e,g.domElements.nodes[e.id],g.measurementCanvas,b);g.domElements.hovers[e.id]=c,g.domElements.groups.hovers.appendChild(c)}}var e,f=sigma.svg.hovers,g=this;this.bind("overNode",b),this.bind("outNode",c),this.bind("render",d)},sigma.renderers.svg.prototype.resize=function(b,c){var d=this.width,e=this.height,f=1;return b!==a&&c!==a?(this.width=b,this.height=c):(this.width=this.container.offsetWidth,this.height=this.container.offsetHeight,b=this.width,c=this.height),(d!==this.width||e!==this.height)&&(this.domElements.graph.style.width=b+"px",this.domElements.graph.style.height=c+"px","svg"===this.domElements.graph.tagName.toLowerCase()&&(this.domElements.graph.setAttribute("width",b*f),this.domElements.graph.setAttribute("height",c*f))),this},sigma.utils.pkg("sigma.svg.nodes"),sigma.utils.pkg("sigma.svg.edges"),sigma.utils.pkg("sigma.svg.labels")}.call(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.renderers");var b,c=!!a.WebGLRenderingContext;if(c){b=document.createElement("canvas");try{c=!(!b.getContext("webgl")&&!b.getContext("experimental-webgl"))}catch(d){c=!1}}sigma.renderers.def=c?sigma.renderers.webgl:sigma.renderers.canvas}(this),function(){"use strict";sigma.utils.pkg("sigma.webgl.nodes"),sigma.webgl.nodes.def={POINTS:3,ATTRIBUTES:5,addNode:function(a,b,c,d,e){var f=sigma.utils.floatColor(a.color||e("defaultNodeColor"));b[c++]=a[d+"x"],b[c++]=a[d+"y"],b[c++]=a[d+"size"],b[c++]=f,b[c++]=0,b[c++]=a[d+"x"],b[c++]=a[d+"y"],b[c++]=a[d+"size"],b[c++]=f,b[c++]=2*Math.PI/3,b[c++]=a[d+"x"],b[c++]=a[d+"y"],b[c++]=a[d+"size"],b[c++]=f,b[c++]=4*Math.PI/3},render:function(a,b,c,d){var e,f=a.getAttribLocation(b,"a_position"),g=a.getAttribLocation(b,"a_size"),h=a.getAttribLocation(b,"a_color"),i=a.getAttribLocation(b,"a_angle"),j=a.getUniformLocation(b,"u_resolution"),k=a.getUniformLocation(b,"u_matrix"),l=a.getUniformLocation(b,"u_ratio"),m=a.getUniformLocation(b,"u_scale");e=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,e),a.bufferData(a.ARRAY_BUFFER,c,a.DYNAMIC_DRAW),a.uniform2f(j,d.width,d.height),a.uniform1f(l,1/Math.pow(d.ratio,d.settings("nodesPowRatio"))),a.uniform1f(m,d.scalingRatio),a.uniformMatrix3fv(k,!1,d.matrix),a.enableVertexAttribArray(f),a.enableVertexAttribArray(g),a.enableVertexAttribArray(h),a.enableVertexAttribArray(i),a.vertexAttribPointer(f,2,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,0),a.vertexAttribPointer(g,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,8),a.vertexAttribPointer(h,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,12),a.vertexAttribPointer(i,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,16),a.drawArrays(a.TRIANGLES,d.start||0,d.count||c.length/this.ATTRIBUTES)},initProgram:function(a){var b,c,d;return b=sigma.utils.loadShader(a,["attribute vec2 a_position;","attribute float a_size;","attribute float a_color;","attribute float a_angle;","uniform vec2 u_resolution;","uniform float u_ratio;","uniform float u_scale;","uniform mat3 u_matrix;","varying vec4 color;","varying vec2 center;","varying float radius;","void main() {","radius = a_size * u_ratio;","vec2 position = (u_matrix * vec3(a_position, 1)).xy;","center = position * u_scale;","center = vec2(center.x, u_scale * u_resolution.y - center.y);","position = position +","2.0 * radius * vec2(cos(a_angle), sin(a_angle));","position = (position / u_resolution * 2.0 - 1.0) * vec2(1, -1);","radius = radius * u_scale;","gl_Position = vec4(position, 0, 1);","float c = a_color;","color.b = mod(c, 256.0); c = floor(c / 256.0);","color.g = mod(c, 256.0); c = floor(c / 256.0);","color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;","color.a = 1.0;","}"].join("\n"),a.VERTEX_SHADER),c=sigma.utils.loadShader(a,["precision mediump float;","varying vec4 color;","varying vec2 center;","varying float radius;","void main(void) {","vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);","vec2 m = gl_FragCoord.xy - center;","float diff = radius - sqrt(m.x * m.x + m.y * m.y);","if (diff > 0.0)","gl_FragColor = color;","else","gl_FragColor = color0;","}"].join("\n"),a.FRAGMENT_SHADER),d=sigma.utils.loadProgram(a,[b,c])}}}(),function(){"use strict";sigma.utils.pkg("sigma.webgl.nodes"),sigma.webgl.nodes.fast={POINTS:1,ATTRIBUTES:4,addNode:function(a,b,c,d,e){b[c++]=a[d+"x"],b[c++]=a[d+"y"],b[c++]=a[d+"size"],b[c++]=sigma.utils.floatColor(a.color||e("defaultNodeColor"))},render:function(a,b,c,d){var e,f=a.getAttribLocation(b,"a_position"),g=a.getAttribLocation(b,"a_size"),h=a.getAttribLocation(b,"a_color"),i=a.getUniformLocation(b,"u_resolution"),j=a.getUniformLocation(b,"u_matrix"),k=a.getUniformLocation(b,"u_ratio"),l=a.getUniformLocation(b,"u_scale");e=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,e),a.bufferData(a.ARRAY_BUFFER,c,a.DYNAMIC_DRAW),a.uniform2f(i,d.width,d.height),a.uniform1f(k,1/Math.pow(d.ratio,d.settings("nodesPowRatio"))),a.uniform1f(l,d.scalingRatio),a.uniformMatrix3fv(j,!1,d.matrix),a.enableVertexAttribArray(f),a.enableVertexAttribArray(g),a.enableVertexAttribArray(h),a.vertexAttribPointer(f,2,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,0),a.vertexAttribPointer(g,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,8),a.vertexAttribPointer(h,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,12),a.drawArrays(a.POINTS,d.start||0,d.count||c.length/this.ATTRIBUTES)},initProgram:function(a){var b,c,d;return b=sigma.utils.loadShader(a,["attribute vec2 a_position;","attribute float a_size;","attribute float a_color;","uniform vec2 u_resolution;","uniform float u_ratio;","uniform float u_scale;","uniform mat3 u_matrix;","varying vec4 color;","void main() {","gl_Position = vec4(","((u_matrix * vec3(a_position, 1)).xy /","u_resolution * 2.0 - 1.0) * vec2(1, -1),","0,","1",");","gl_PointSize = a_size * u_ratio * u_scale * 2.0;","float c = a_color;","color.b = mod(c, 256.0); c = floor(c / 256.0);","color.g = mod(c, 256.0); c = floor(c / 256.0);","color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;","color.a = 1.0;","}"].join("\n"),a.VERTEX_SHADER),c=sigma.utils.loadShader(a,["precision mediump float;","varying vec4 color;","void main(void) {","float border = 0.01;","float radius = 0.5;","vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);","vec2 m = gl_PointCoord - vec2(0.5, 0.5);","float dist = radius - sqrt(m.x * m.x + m.y * m.y);","float t = 0.0;","if (dist > border)","t = 1.0;","else if (dist > 0.0)","t = dist / border;","gl_FragColor = mix(color0, color, t);","}"].join("\n"),a.FRAGMENT_SHADER),d=sigma.utils.loadProgram(a,[b,c])}}}(),function(){"use strict";sigma.utils.pkg("sigma.webgl.edges"),sigma.webgl.edges.def={POINTS:6,ATTRIBUTES:7,addEdge:function(a,b,c,d,e,f,g){var h=(a[f+"size"]||1)/2,i=b[f+"x"],j=b[f+"y"],k=c[f+"x"],l=c[f+"y"],m=a.color;if(!m)switch(g("edgeColor")){case"source":m=b.color||g("defaultNodeColor");break;case"target":m=c.color||g("defaultNodeColor");break;default:m=g("defaultEdgeColor")}m=sigma.utils.floatColor(m),d[e++]=i,d[e++]=j,d[e++]=k,d[e++]=l,d[e++]=h,d[e++]=0,d[e++]=m,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=1,d[e++]=m,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=0,d[e++]=m,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=0,d[e++]=m,d[e++]=i,d[e++]=j,d[e++]=k,d[e++]=l,d[e++]=h,d[e++]=1,d[e++]=m,d[e++]=i,d[e++]=j,d[e++]=k,d[e++]=l,d[e++]=h,d[e++]=0,d[e++]=m},render:function(a,b,c,d){var e,f=a.getAttribLocation(b,"a_color"),g=a.getAttribLocation(b,"a_position1"),h=a.getAttribLocation(b,"a_position2"),i=a.getAttribLocation(b,"a_thickness"),j=a.getAttribLocation(b,"a_minus"),k=a.getUniformLocation(b,"u_resolution"),l=a.getUniformLocation(b,"u_matrix"),m=a.getUniformLocation(b,"u_matrixHalfPi"),n=a.getUniformLocation(b,"u_matrixHalfPiMinus"),o=a.getUniformLocation(b,"u_ratio"),p=a.getUniformLocation(b,"u_scale");e=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,e),a.bufferData(a.ARRAY_BUFFER,c,a.STATIC_DRAW),a.uniform2f(k,d.width,d.height),a.uniform1f(o,d.ratio/Math.pow(d.ratio,d.settings("edgesPowRatio"))),a.uniform1f(p,d.scalingRatio),a.uniformMatrix3fv(l,!1,d.matrix),a.uniformMatrix2fv(m,!1,sigma.utils.matrices.rotation(Math.PI/2,!0)),a.uniformMatrix2fv(n,!1,sigma.utils.matrices.rotation(-Math.PI/2,!0)),a.enableVertexAttribArray(f),a.enableVertexAttribArray(g),a.enableVertexAttribArray(h),a.enableVertexAttribArray(i),a.enableVertexAttribArray(j),a.vertexAttribPointer(g,2,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,0),a.vertexAttribPointer(h,2,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,8),a.vertexAttribPointer(i,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,16),a.vertexAttribPointer(j,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,20),a.vertexAttribPointer(f,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,24),a.drawArrays(a.TRIANGLES,d.start||0,d.count||c.length/this.ATTRIBUTES)},initProgram:function(a){var b,c,d;return b=sigma.utils.loadShader(a,["attribute vec2 a_position1;","attribute vec2 a_position2;","attribute float a_thickness;","attribute float a_minus;","attribute float a_color;","uniform vec2 u_resolution;","uniform float u_ratio;","uniform float u_scale;","uniform mat3 u_matrix;","uniform mat2 u_matrixHalfPi;","uniform mat2 u_matrixHalfPiMinus;","varying vec4 color;","void main() {","vec2 position = a_thickness * u_ratio *","normalize(a_position2 - a_position1);","mat2 matrix = a_minus * u_matrixHalfPiMinus +","(1.0 - a_minus) * u_matrixHalfPi;","position = matrix * position + a_position1;","gl_Position = vec4(","((u_matrix * vec3(position, 1)).xy /","u_resolution * 2.0 - 1.0) * vec2(1, -1),","0,","1",");","float c = a_color;","color.b = mod(c, 256.0); c = floor(c / 256.0);","color.g = mod(c, 256.0); c = floor(c / 256.0);","color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;","color.a = 1.0;","}"].join("\n"),a.VERTEX_SHADER),c=sigma.utils.loadShader(a,["precision mediump float;","varying vec4 color;","void main(void) {","gl_FragColor = color;","}"].join("\n"),a.FRAGMENT_SHADER),d=sigma.utils.loadProgram(a,[b,c])}}}(),function(){"use strict";sigma.utils.pkg("sigma.webgl.edges"),sigma.webgl.edges.fast={POINTS:2,ATTRIBUTES:3,addEdge:function(a,b,c,d,e,f,g){var h=((a[f+"size"]||1)/2,b[f+"x"]),i=b[f+"y"],j=c[f+"x"],k=c[f+"y"],l=a.color;if(!l)switch(g("edgeColor")){case"source":l=b.color||g("defaultNodeColor");break;case"target":l=c.color||g("defaultNodeColor");break;default:l=g("defaultEdgeColor")}l=sigma.utils.floatColor(l),d[e++]=h,d[e++]=i,d[e++]=l,d[e++]=j,d[e++]=k,d[e++]=l},render:function(a,b,c,d){var e,f=a.getAttribLocation(b,"a_color"),g=a.getAttribLocation(b,"a_position"),h=a.getUniformLocation(b,"u_resolution"),i=a.getUniformLocation(b,"u_matrix");e=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,e),a.bufferData(a.ARRAY_BUFFER,c,a.DYNAMIC_DRAW),a.uniform2f(h,d.width,d.height),a.uniformMatrix3fv(i,!1,d.matrix),a.enableVertexAttribArray(g),a.enableVertexAttribArray(f),a.vertexAttribPointer(g,2,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,0),a.vertexAttribPointer(f,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,8),a.lineWidth(3),a.drawArrays(a.LINES,d.start||0,d.count||c.length/this.ATTRIBUTES)},initProgram:function(a){var b,c,d;return b=sigma.utils.loadShader(a,["attribute vec2 a_position;","attribute float a_color;","uniform vec2 u_resolution;","uniform mat3 u_matrix;","varying vec4 color;","void main() {","gl_Position = vec4(","((u_matrix * vec3(a_position, 1)).xy /","u_resolution * 2.0 - 1.0) * vec2(1, -1),","0,","1",");","float c = a_color;","color.b = mod(c, 256.0); c = floor(c / 256.0);","color.g = mod(c, 256.0); c = floor(c / 256.0);","color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;","color.a = 1.0;","}"].join("\n"),a.VERTEX_SHADER),c=sigma.utils.loadShader(a,["precision mediump float;","varying vec4 color;","void main(void) {","gl_FragColor = color;","}"].join("\n"),a.FRAGMENT_SHADER),d=sigma.utils.loadProgram(a,[b,c])}}}(),function(){"use strict";sigma.utils.pkg("sigma.webgl.edges"),sigma.webgl.edges.arrow={POINTS:9,ATTRIBUTES:11,addEdge:function(a,b,c,d,e,f,g){var h=(a[f+"size"]||1)/2,i=b[f+"x"],j=b[f+"y"],k=c[f+"x"],l=c[f+"y"],m=c[f+"size"],n=a.color;if(!n)switch(g("edgeColor")){case"source":n=b.color||g("defaultNodeColor");break;case"target":n=c.color||g("defaultNodeColor");break;default:n=g("defaultEdgeColor")}n=sigma.utils.floatColor(n),d[e++]=i,d[e++]=j,d[e++]=k,d[e++]=l,d[e++]=h,d[e++]=m,d[e++]=0,d[e++]=0,d[e++]=0,d[e++]=0,d[e++]=n,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=m,d[e++]=1,d[e++]=1,d[e++]=0,d[e++]=0,d[e++]=n,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=m,d[e++]=1,d[e++]=0,d[e++]=0,d[e++]=0,d[e++]=n,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=m,d[e++]=1,d[e++]=0,d[e++]=0,d[e++]=0,d[e++]=n,d[e++]=i,d[e++]=j,d[e++]=k,d[e++]=l,d[e++]=h,d[e++]=m,d[e++]=0,d[e++]=1,d[e++]=0,d[e++]=0,d[e++]=n,d[e++]=i,d[e++]=j,d[e++]=k,d[e++]=l,d[e++]=h,d[e++]=m,d[e++]=0,d[e++]=0,d[e++]=0,d[e++]=0,d[e++]=n,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=m,d[e++]=1,d[e++]=0,d[e++]=1,d[e++]=-1,d[e++]=n,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=m,d[e++]=1,d[e++]=0,d[e++]=1,d[e++]=0,d[e++]=n,d[e++]=k,d[e++]=l,d[e++]=i,d[e++]=j,d[e++]=h,d[e++]=m,d[e++]=1,d[e++]=0,d[e++]=1,d[e++]=1,d[e++]=n},render:function(a,b,c,d){var e,f=a.getAttribLocation(b,"a_pos1"),g=a.getAttribLocation(b,"a_pos2"),h=a.getAttribLocation(b,"a_thickness"),i=a.getAttribLocation(b,"a_tSize"),j=a.getAttribLocation(b,"a_delay"),k=a.getAttribLocation(b,"a_minus"),l=a.getAttribLocation(b,"a_head"),m=a.getAttribLocation(b,"a_headPosition"),n=a.getAttribLocation(b,"a_color"),o=a.getUniformLocation(b,"u_resolution"),p=a.getUniformLocation(b,"u_matrix"),q=a.getUniformLocation(b,"u_matrixHalfPi"),r=a.getUniformLocation(b,"u_matrixHalfPiMinus"),s=a.getUniformLocation(b,"u_ratio"),t=a.getUniformLocation(b,"u_nodeRatio"),u=a.getUniformLocation(b,"u_arrowHead"),v=a.getUniformLocation(b,"u_scale");e=a.createBuffer(),a.bindBuffer(a.ARRAY_BUFFER,e),a.bufferData(a.ARRAY_BUFFER,c,a.STATIC_DRAW),a.uniform2f(o,d.width,d.height),a.uniform1f(s,d.ratio/Math.pow(d.ratio,d.settings("edgesPowRatio"))),a.uniform1f(t,Math.pow(d.ratio,d.settings("nodesPowRatio"))/d.ratio),a.uniform1f(u,5),a.uniform1f(v,d.scalingRatio),a.uniformMatrix3fv(p,!1,d.matrix),a.uniformMatrix2fv(q,!1,sigma.utils.matrices.rotation(Math.PI/2,!0)),a.uniformMatrix2fv(r,!1,sigma.utils.matrices.rotation(-Math.PI/2,!0)),a.enableVertexAttribArray(f),a.enableVertexAttribArray(g),a.enableVertexAttribArray(h),a.enableVertexAttribArray(i),a.enableVertexAttribArray(j),a.enableVertexAttribArray(k),a.enableVertexAttribArray(l),a.enableVertexAttribArray(m),a.enableVertexAttribArray(n),a.vertexAttribPointer(f,2,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,0),a.vertexAttribPointer(g,2,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,8),a.vertexAttribPointer(h,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,16),a.vertexAttribPointer(i,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,20),a.vertexAttribPointer(j,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,24),a.vertexAttribPointer(k,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,28),a.vertexAttribPointer(l,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,32),a.vertexAttribPointer(m,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,36),a.vertexAttribPointer(n,1,a.FLOAT,!1,this.ATTRIBUTES*Float32Array.BYTES_PER_ELEMENT,40),a.drawArrays(a.TRIANGLES,d.start||0,d.count||c.length/this.ATTRIBUTES)},initProgram:function(a){var b,c,d;return b=sigma.utils.loadShader(a,["attribute vec2 a_pos1;","attribute vec2 a_pos2;","attribute float a_thickness;","attribute float a_tSize;","attribute float a_delay;","attribute float a_minus;","attribute float a_head;","attribute float a_headPosition;","attribute float a_color;","uniform vec2 u_resolution;","uniform float u_ratio;","uniform float u_nodeRatio;","uniform float u_arrowHead;","uniform float u_scale;","uniform mat3 u_matrix;","uniform mat2 u_matrixHalfPi;","uniform mat2 u_matrixHalfPiMinus;","varying vec4 color;","void main() {","vec2 pos = normalize(a_pos2 - a_pos1);","mat2 matrix = (1.0 - a_head) *","(","a_minus * u_matrixHalfPiMinus +","(1.0 - a_minus) * u_matrixHalfPi",") + a_head * (","a_headPosition * u_matrixHalfPiMinus * 0.6 +","(a_headPosition * a_headPosition - 1.0) * mat2(1.0)",");","pos = a_pos1 + (","(1.0 - a_head) * a_thickness * u_ratio * matrix * pos +","a_head * u_arrowHead * a_thickness * u_ratio * matrix * pos +","a_delay * pos * (","a_tSize / u_nodeRatio +","u_arrowHead * a_thickness * u_ratio",")",");","gl_Position = vec4(","((u_matrix * vec3(pos, 1)).xy /","u_resolution * 2.0 - 1.0) * vec2(1, -1),","0,","1",");","float c = a_color;","color.b = mod(c, 256.0); c = floor(c / 256.0);","color.g = mod(c, 256.0); c = floor(c / 256.0);","color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;","color.a = 1.0;","}"].join("\n"),a.VERTEX_SHADER),c=sigma.utils.loadShader(a,["precision mediump float;","varying vec4 color;","void main(void) {","gl_FragColor = color;","}"].join("\n"),a.FRAGMENT_SHADER),d=sigma.utils.loadProgram(a,[b,c])}}}(),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.canvas.labels"),sigma.canvas.labels.def=function(a,b,c){var d,e=c("prefix")||"",f=a[e+"size"];f0&&(b.beginPath(),b.fillStyle="node"===c("nodeBorderColor")?a.color||c("defaultNodeColor"):c("defaultNodeBorderColor"),b.arc(a[j+"x"],a[j+"y"],k+c("borderSize"),0,2*Math.PI,!0),b.closePath(),b.fill());var m=sigma.canvas.nodes[a.type]||sigma.canvas.nodes.def;m(a,b,c),a.label&&"string"==typeof a.label&&(b.fillStyle="node"===c("labelHoverColor")?a.color||c("defaultNodeColor"):c("defaultLabelHoverColor"),b.fillText(a.label,Math.round(a[j+"x"]+k+3),Math.round(a[j+"y"]+l/3)))}}.call(this),function(){"use strict";sigma.utils.pkg("sigma.canvas.nodes"),sigma.canvas.nodes.def=function(a,b,c){var d=c("prefix")||"";b.fillStyle=a.color||c("defaultNodeColor"),b.beginPath(),b.arc(a[d+"x"],a[d+"y"],a[d+"size"],0,2*Math.PI,!0),b.closePath(),b.fill()}}(),function(){"use strict";sigma.utils.pkg("sigma.canvas.edges"),sigma.canvas.edges.def=function(a,b,c,d,e){var f=a.color,g=e("prefix")||"",h=a[g+"size"]||1,i=e("edgeColor"),j=e("defaultNodeColor"),k=e("defaultEdgeColor");if(!f)switch(i){case"source":f=b.color||j;break;case"target":f=c.color||j;break;default:f=k}d.strokeStyle=f,d.lineWidth=h,d.beginPath(),d.moveTo(b[g+"x"],b[g+"y"]),d.lineTo(c[g+"x"],c[g+"y"]),d.stroke()}}(),function(){"use strict";sigma.utils.pkg("sigma.canvas.edges"),sigma.canvas.edges.curve=function(a,b,c,d,e){var f=a.color,g=e("prefix")||"",h=a[g+"size"]||1,i=e("edgeColor"),j=e("defaultNodeColor"),k=e("defaultEdgeColor"),l={},m=b[g+"size"],n=b[g+"x"],o=b[g+"y"],p=c[g+"x"],q=c[g+"y"];if(l=b.id===c.id?sigma.utils.getSelfLoopControlPoints(n,o,m):sigma.utils.getQuadraticControlPoint(n,o,p,q),!f)switch(i){case"source":f=b.color||j;break;case"target":f=c.color||j;break;default:f=k}d.strokeStyle=f,d.lineWidth=h,d.beginPath(),d.moveTo(n,o),b.id===c.id?d.bezierCurveTo(l.x1,l.y1,l.x2,l.y2,p,q):d.quadraticCurveTo(l.x,l.y,p,q),d.stroke()}}(),function(){"use strict";sigma.utils.pkg("sigma.canvas.edges"),sigma.canvas.edges.arrow=function(a,b,c,d,e){var f=a.color,g=e("prefix")||"",h=e("edgeColor"),i=e("defaultNodeColor"),j=e("defaultEdgeColor"),k=a[g+"size"]||1,l=c[g+"size"],m=b[g+"x"],n=b[g+"y"],o=c[g+"x"],p=c[g+"y"],q=Math.max(2.5*k,e("minArrowSize")),r=Math.sqrt(Math.pow(o-m,2)+Math.pow(p-n,2)),s=m+(o-m)*(r-q-l)/r,t=n+(p-n)*(r-q-l)/r,u=(o-m)*q/r,v=(p-n)*q/r;if(!f)switch(h){case"source":f=b.color||i;break;case"target":f=c.color||i;break;default:f=j}d.strokeStyle=f,d.lineWidth=k,d.beginPath(),d.moveTo(m,n),d.lineTo(s,t),d.stroke(),d.fillStyle=f,d.beginPath(),d.moveTo(s+u,t+v),d.lineTo(s+.6*v,t-.6*u),d.lineTo(s-.6*v,t+.6*u),d.lineTo(s+u,t+v),d.closePath(),d.fill()}}(),function(){"use strict";sigma.utils.pkg("sigma.canvas.edges"),sigma.canvas.edges.curvedArrow=function(a,b,c,d,e){var f,g,h,i,j,k=a.color,l=e("prefix")||"",m=e("edgeColor"),n=e("defaultNodeColor"),o=e("defaultEdgeColor"),p={},q=a[l+"size"]||1,r=c[l+"size"],s=b[l+"x"],t=b[l+"y"],u=c[l+"x"],v=c[l+"y"],w=Math.max(2.5*q,e("minArrowSize"));if(p=b.id===c.id?sigma.utils.getSelfLoopControlPoints(s,t,r):sigma.utils.getQuadraticControlPoint(s,t,u,v),b.id===c.id?(f=Math.sqrt(Math.pow(u-p.x1,2)+Math.pow(v-p.y1,2)),g=p.x1+(u-p.x1)*(f-w-r)/f,h=p.y1+(v-p.y1)*(f-w-r)/f,i=(u-p.x1)*w/f,j=(v-p.y1)*w/f):(f=Math.sqrt(Math.pow(u-p.x,2)+Math.pow(v-p.y,2)),g=p.x+(u-p.x)*(f-w-r)/f,h=p.y+(v-p.y)*(f-w-r)/f,i=(u-p.x)*w/f,j=(v-p.y)*w/f),!k)switch(m){case"source":k=b.color||n;break;case"target":k=c.color||n;break;default:k=o}d.strokeStyle=k,d.lineWidth=q,d.beginPath(),d.moveTo(s,t),b.id===c.id?d.bezierCurveTo(p.x2,p.y2,p.x1,p.y1,g,h):d.quadraticCurveTo(p.x,p.y,g,h),d.stroke(),d.fillStyle=k,d.beginPath(),d.moveTo(g+i,h+j),d.lineTo(g+.6*j,h-.6*i),d.lineTo(g-.6*j,h+.6*i),d.lineTo(g+i,h+j),d.closePath(),d.fill()}}(),function(){"use strict";sigma.utils.pkg("sigma.canvas.edgehovers"),sigma.canvas.edgehovers.def=function(a,b,c,d,e){var f=a.color,g=e("prefix")||"",h=a[g+"size"]||1,i=e("edgeColor"),j=e("defaultNodeColor"),k=e("defaultEdgeColor");if(!f)switch(i){case"source":f=b.color||j;break;case"target":f=c.color||j;break;default:f=k}f="edge"===e("edgeHoverColor")?a.hover_color||f:a.hover_color||e("defaultEdgeHoverColor")||f,h*=e("edgeHoverSizeRatio"),d.strokeStyle=f,d.lineWidth=h,d.beginPath(),d.moveTo(b[g+"x"],b[g+"y"]),d.lineTo(c[g+"x"],c[g+"y"]),d.stroke()}}(),function(){"use strict";sigma.utils.pkg("sigma.canvas.edgehovers"),sigma.canvas.edgehovers.curve=function(a,b,c,d,e){var f=a.color,g=e("prefix")||"",h=e("edgeHoverSizeRatio")*(a[g+"size"]||1),i=e("edgeColor"),j=e("defaultNodeColor"),k=e("defaultEdgeColor"),l={},m=b[g+"size"],n=b[g+"x"],o=b[g+"y"],p=c[g+"x"],q=c[g+"y"];if(l=b.id===c.id?sigma.utils.getSelfLoopControlPoints(n,o,m):sigma.utils.getQuadraticControlPoint(n,o,p,q),!f)switch(i){case"source":f=b.color||j;break;case"target":f=c.color||j;break;default:f=k}f="edge"===e("edgeHoverColor")?a.hover_color||f:a.hover_color||e("defaultEdgeHoverColor")||f,d.strokeStyle=f,d.lineWidth=h,d.beginPath(),d.moveTo(n,o),b.id===c.id?d.bezierCurveTo(l.x1,l.y1,l.x2,l.y2,p,q):d.quadraticCurveTo(l.x,l.y,p,q),d.stroke()}}(),function(){"use strict";sigma.utils.pkg("sigma.canvas.edgehovers"),sigma.canvas.edgehovers.arrow=function(a,b,c,d,e){var f=a.color,g=e("prefix")||"",h=e("edgeColor"),i=e("defaultNodeColor"),j=e("defaultEdgeColor"),k=a[g+"size"]||1,l=c[g+"size"],m=b[g+"x"],n=b[g+"y"],o=c[g+"x"],p=c[g+"y"]; -k=a.hover?e("edgeHoverSizeRatio")*k:k;var q=2.5*k,r=Math.sqrt(Math.pow(o-m,2)+Math.pow(p-n,2)),s=m+(o-m)*(r-q-l)/r,t=n+(p-n)*(r-q-l)/r,u=(o-m)*q/r,v=(p-n)*q/r;if(!f)switch(h){case"source":f=b.color||i;break;case"target":f=c.color||i;break;default:f=j}f="edge"===e("edgeHoverColor")?a.hover_color||f:a.hover_color||e("defaultEdgeHoverColor")||f,d.strokeStyle=f,d.lineWidth=k,d.beginPath(),d.moveTo(m,n),d.lineTo(s,t),d.stroke(),d.fillStyle=f,d.beginPath(),d.moveTo(s+u,t+v),d.lineTo(s+.6*v,t-.6*u),d.lineTo(s-.6*v,t+.6*u),d.lineTo(s+u,t+v),d.closePath(),d.fill()}}(),function(){"use strict";sigma.utils.pkg("sigma.canvas.edgehovers"),sigma.canvas.edgehovers.curvedArrow=function(a,b,c,d,e){var f,g,h,i,j,k,l=a.color,m=e("prefix")||"",n=e("edgeColor"),o=e("defaultNodeColor"),p=e("defaultEdgeColor"),q={},r=e("edgeHoverSizeRatio")*(a[m+"size"]||1),s=c[m+"size"],t=b[m+"x"],u=b[m+"y"],v=c[m+"x"],w=c[m+"y"];if(q=b.id===c.id?sigma.utils.getSelfLoopControlPoints(t,u,s):sigma.utils.getQuadraticControlPoint(t,u,v,w),b.id===c.id?(f=Math.sqrt(Math.pow(v-q.x1,2)+Math.pow(w-q.y1,2)),g=2.5*r,h=q.x1+(v-q.x1)*(f-g-s)/f,i=q.y1+(w-q.y1)*(f-g-s)/f,j=(v-q.x1)*g/f,k=(w-q.y1)*g/f):(f=Math.sqrt(Math.pow(v-q.x,2)+Math.pow(w-q.y,2)),g=2.5*r,h=q.x+(v-q.x)*(f-g-s)/f,i=q.y+(w-q.y)*(f-g-s)/f,j=(v-q.x)*g/f,k=(w-q.y)*g/f),!l)switch(n){case"source":l=b.color||o;break;case"target":l=c.color||o;break;default:l=p}l="edge"===e("edgeHoverColor")?a.hover_color||l:a.hover_color||e("defaultEdgeHoverColor")||l,d.strokeStyle=l,d.lineWidth=r,d.beginPath(),d.moveTo(t,u),b.id===c.id?d.bezierCurveTo(q.x2,q.y2,q.x1,q.y1,h,i):d.quadraticCurveTo(q.x,q.y,h,i),d.stroke(),d.fillStyle=l,d.beginPath(),d.moveTo(h+j,i+k),d.lineTo(h+.6*k,i-.6*j),d.lineTo(h-.6*k,i+.6*j),d.lineTo(h+j,i+k),d.closePath(),d.fill()}}(),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.canvas.extremities"),sigma.canvas.extremities.def=function(a,b,c,d,e){(sigma.canvas.hovers[b.type]||sigma.canvas.hovers.def)(b,d,e),(sigma.canvas.hovers[c.type]||sigma.canvas.hovers.def)(c,d,e)}}.call(this),function(){"use strict";sigma.utils.pkg("sigma.svg.utils"),sigma.svg.utils={show:function(a){return a.style.display="",this},hide:function(a){return a.style.display="none",this}}}(),function(){"use strict";sigma.utils.pkg("sigma.svg.nodes"),sigma.svg.nodes.def={create:function(a,b){var c=(b("prefix")||"",document.createElementNS(b("xmlns"),"circle"));return c.setAttributeNS(null,"data-node-id",a.id),c.setAttributeNS(null,"class",b("classPrefix")+"-node"),c.setAttributeNS(null,"fill",a.color||b("defaultNodeColor")),c},update:function(a,b,c){var d=c("prefix")||"";return b.setAttributeNS(null,"cx",a[d+"x"]),b.setAttributeNS(null,"cy",a[d+"y"]),b.setAttributeNS(null,"r",a[d+"size"]),c("freeStyle")||b.setAttributeNS(null,"fill",a.color||c("defaultNodeColor")),b.style.display="",this}}}(),function(){"use strict";sigma.utils.pkg("sigma.svg.edges"),sigma.svg.edges.def={create:function(a,b,c,d){var e=a.color,f=(d("prefix")||"",d("edgeColor")),g=d("defaultNodeColor"),h=d("defaultEdgeColor");if(!e)switch(f){case"source":e=b.color||g;break;case"target":e=c.color||g;break;default:e=h}var i=document.createElementNS(d("xmlns"),"line");return i.setAttributeNS(null,"data-edge-id",a.id),i.setAttributeNS(null,"class",d("classPrefix")+"-edge"),i.setAttributeNS(null,"stroke",e),i},update:function(a,b,c,d,e){var f=e("prefix")||"";return b.setAttributeNS(null,"stroke-width",a[f+"size"]||1),b.setAttributeNS(null,"x1",c[f+"x"]),b.setAttributeNS(null,"y1",c[f+"y"]),b.setAttributeNS(null,"x2",d[f+"x"]),b.setAttributeNS(null,"y2",d[f+"y"]),b.style.display="",this}}}(),function(){"use strict";sigma.utils.pkg("sigma.svg.edges"),sigma.svg.edges.curve={create:function(a,b,c,d){var e=a.color,f=(d("prefix")||"",d("edgeColor")),g=d("defaultNodeColor"),h=d("defaultEdgeColor");if(!e)switch(f){case"source":e=b.color||g;break;case"target":e=c.color||g;break;default:e=h}var i=document.createElementNS(d("xmlns"),"path");return i.setAttributeNS(null,"data-edge-id",a.id),i.setAttributeNS(null,"class",d("classPrefix")+"-edge"),i.setAttributeNS(null,"stroke",e),i},update:function(a,b,c,d,e){var f=e("prefix")||"";b.setAttributeNS(null,"stroke-width",a[f+"size"]||1);var g=(c[f+"x"]+d[f+"x"])/2+(d[f+"y"]-c[f+"y"])/4,h=(c[f+"y"]+d[f+"y"])/2+(c[f+"x"]-d[f+"x"])/4,i="M"+c[f+"x"]+","+c[f+"y"]+" Q"+g+","+h+" "+d[f+"x"]+","+d[f+"y"];return b.setAttributeNS(null,"d",i),b.setAttributeNS(null,"fill","none"),b.style.display="",this}}}(),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.svg.labels"),sigma.svg.labels.def={create:function(a,b){var c=b("prefix")||"",d=a[c+"size"],e=document.createElementNS(b("xmlns"),"text"),f="fixed"===b("labelSize")?b("defaultLabelSize"):b("labelSizeRatio")*d,g="node"===b("labelColor")?a.color||b("defaultNodeColor"):b("defaultLabelColor");return e.setAttributeNS(null,"data-label-target",a.id),e.setAttributeNS(null,"class",b("classPrefix")+"-label"),e.setAttributeNS(null,"font-size",f),e.setAttributeNS(null,"font-family",b("font")),e.setAttributeNS(null,"fill",g),e.innerHTML=a.label,e.textContent=a.label,e},update:function(a,b,c){var d=c("prefix")||"",e=a[d+"size"],f="fixed"===c("labelSize")?c("defaultLabelSize"):c("labelSizeRatio")*e;return!c("forceLabels")&&ed;d++)if(!y[x[d]])throw new Error('The rescale setting "'+x[d]+'" is not recognized.');var z=~x.indexOf("nodePosition"),A=~x.indexOf("nodeSize"),B=~x.indexOf("edgeSize");for(j="outside"===n("scalingMode")?Math.max(v/Math.max(r-p,1),w/Math.max(s-q,1)):Math.min(v/Math.max(r-p,1),w/Math.max(s-q,1)),k=(n("rescaleIgnoreSize")?0:(n("maxNodeSize")||t)/j)+(n("sideMargin")||0),r+=k,p-=k,s+=k,q-=k,j="outside"===n("scalingMode")?Math.max(v/Math.max(r-p,1),w/Math.max(s-q,1)):Math.min(v/Math.max(r-p,1),w/Math.max(s-q,1)),n("maxNodeSize")||n("minNodeSize")?n("maxNodeSize")===n("minNodeSize")?(f=0,g=+n("maxNodeSize")):(f=(n("maxNodeSize")-n("minNodeSize"))/t,g=+n("minNodeSize")):(f=1,g=0),n("maxEdgeSize")||n("minEdgeSize")?n("maxEdgeSize")===n("minEdgeSize")?(h=0,i=+n("minEdgeSize")):(h=(n("maxEdgeSize")-n("minEdgeSize"))/u,i=+n("minEdgeSize")):(h=1,i=0),d=0,e=m.length;e>d;d++)m[d][b+"size"]=m[d][a+"size"]*(B?h:1)+(B?i:0);for(d=0,e=l.length;e>d;d++)l[d][b+"size"]=l[d][a+"size"]*(A?f:1)+(A?g:0),l[d][b+"x"]=(l[d][a+"x"]-(r+p)/2)*(z?j:1),l[d][b+"y"]=(l[d][a+"y"]-(s+q)/2)*(z?j:1)},sigma.utils.getBoundaries=function(a,b,c){var d,e,f=a.edges(),g=a.nodes(),h=-1/0,i=-1/0,j=1/0,k=1/0,l=-1/0,m=-1/0;if(c)for(d=0,e=f.length;e>d;d++)h=Math.max(f[d][b+"size"],h);for(d=0,e=g.length;e>d;d++)i=Math.max(g[d][b+"size"],i),l=Math.max(g[d][b+"x"],l),j=Math.min(g[d][b+"x"],j),m=Math.max(g[d][b+"y"],m),k=Math.min(g[d][b+"y"],k);return h=h||1,i=i||1,{weightMax:h,sizeMax:i,minX:j,minY:k,maxX:l,maxY:m}}}.call(this),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.middlewares"),sigma.middlewares.copy=function(a,b){var c,d,e;if(b+""!=a+""){for(e=this.graph.nodes(),c=0,d=e.length;d>c;c++)e[c][b+"x"]=e[c][a+"x"],e[c][b+"y"]=e[c][a+"y"],e[c][b+"size"]=e[c][a+"size"];for(e=this.graph.edges(),c=0,d=e.length;d>c;c++)e[c][b+"size"]=e[c][a+"size"]}}}.call(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.misc.animation.running");var b=function(){var a=0;return function(){return""+ ++a}}();sigma.misc.animation.camera=function(c,d,e){if(!(c instanceof sigma.classes.camera&&"object"==typeof d&&d))throw"animation.camera: Wrong arguments.";if("number"!=typeof d.x&&"number"!=typeof d.y&&"number"!=typeof d.ratio&&"number"!=typeof d.angle)throw"There must be at least one valid coordinate in the given val.";var f,g,h,i,j,k,l=e||{},m=sigma.utils.dateNow();return k={x:c.x,y:c.y,ratio:c.ratio,angle:c.angle},j=l.duration,i="function"!=typeof l.easing?sigma.utils.easings[l.easing||"quadraticInOut"]:l.easing,f=function(){var b,e=l.duration?(sigma.utils.dateNow()-m)/l.duration:1;e>=1?(c.isAnimated=!1,c.goTo({x:d.x!==a?d.x:k.x,y:d.y!==a?d.y:k.y,ratio:d.ratio!==a?d.ratio:k.ratio,angle:d.angle!==a?d.angle:k.angle}),cancelAnimationFrame(g),delete sigma.misc.animation.running[g],"function"==typeof l.onComplete&&l.onComplete()):(b=i(e),c.isAnimated=!0,c.goTo({x:d.x!==a?k.x+(d.x-k.x)*b:k.x,y:d.y!==a?k.y+(d.y-k.y)*b:k.y,ratio:d.ratio!==a?k.ratio+(d.ratio-k.ratio)*b:k.ratio,angle:d.angle!==a?k.angle+(d.angle-k.angle)*b:k.angle}),"function"==typeof l.onNewFrame&&l.onNewFrame(),h.frameId=requestAnimationFrame(f))},g=b(),h={frameId:requestAnimationFrame(f),target:c,type:"camera",options:l,fn:f},sigma.misc.animation.running[g]=h,g},sigma.misc.animation.kill=function(a){if(1!==arguments.length||"number"!=typeof a)throw"animation.kill: Wrong arguments.";var b=sigma.misc.animation.running[a];return b&&(cancelAnimationFrame(a),delete sigma.misc.animation.running[b.frameId],"camera"===b.type&&(b.target.isAnimated=!1),"function"==typeof(b.options||{}).onComplete&&b.options.onComplete()),this},sigma.misc.animation.killAll=function(a){var b,c,d=0,e="string"==typeof a?a:null,f="object"==typeof a?a:null,g=sigma.misc.animation.running;for(c in g)e&&g[c].type!==e||f&&g[c].target!==f||(b=sigma.misc.animation.running[c],cancelAnimationFrame(b.frameId),delete sigma.misc.animation.running[c],"camera"===b.type&&(b.target.isAnimated=!1),d++,"function"==typeof(b.options||{}).onComplete&&b.options.onComplete());return d},sigma.misc.animation.has=function(a){var b,c="string"==typeof a?a:null,d="object"==typeof a?a:null,e=sigma.misc.animation.running;for(b in e)if(!(c&&e[b].type!==c||d&&e[b].target!==d))return!0;return!1}}.call(this),function(a){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.misc"),sigma.misc.bindEvents=function(b){function c(a){a&&(h="x"in a.data?a.data.x:h,i="y"in a.data?a.data.y:i);var c,d,e,f,g,k,l,m,n=[],o=h+j.width/2,p=i+j.height/2,q=j.camera.cameraPosition(h,i),r=j.camera.quadtree.point(q.x,q.y);if(r.length)for(c=0,e=r.length;e>c;c++)if(f=r[c],g=f[b+"x"],k=f[b+"y"],l=f[b+"size"],!f.hidden&&o>g-l&&g+l>o&&p>k-l&&k+l>p&&Math.sqrt(Math.pow(o-g,2)+Math.pow(p-k,2))n[d].size){n.splice(d,0,f),m=!0;break}m||n.push(f)}return n}function d(c){function d(a,b){for(r=!1,g=0;ga[g].size){a.splice(g,0,b),r=!0;break}r||a.push(b)}if(!j.settings("enableEdgeHovering"))return[];var e=sigma.renderers.canvas&&j instanceof sigma.renderers.canvas;if(!e)throw new Error("The edge events feature is not compatible with the WebGL renderer");c&&(h="x"in c.data?c.data.x:h,i="y"in c.data?c.data.y:i);var f,g,k,l,m,n,o,p,q,r,s=j.settings("edgeHoverPrecision"),t={},u=[],v=h+j.width/2,w=i+j.height/2,x=j.camera.cameraPosition(h,i),y=[];if(e){var z=j.camera.quadtree.area(j.camera.getRectangle(j.width,j.height));for(l=z,f=0,k=l.length;k>f;f++)t[l[f].id]=l[f]}if(j.camera.edgequadtree!==a&&(y=j.camera.edgequadtree.point(x.x,x.y)),y.length)for(f=0,k=y.length;k>f;f++)m=y[f],o=j.graph.nodes(m.source),p=j.graph.nodes(m.target),n=m[b+"size"]||m["read_"+b+"size"],!m.hidden&&!o.hidden&&!p.hidden&&(!e||t[m.source]||t[m.target])&&sigma.utils.getDistance(o[b+"x"],o[b+"y"],v,w)>o[b+"size"]&&sigma.utils.getDistance(p[b+"x"],p[b+"y"],v,w)>p[b+"size"]&&("curve"==m.type||"curvedArrow"==m.type?o.id===p.id?(q=sigma.utils.getSelfLoopControlPoints(o[b+"x"],o[b+"y"],o[b+"size"]),sigma.utils.isPointOnBezierCurve(v,w,o[b+"x"],o[b+"y"],p[b+"x"],p[b+"y"],q.x1,q.y1,q.x2,q.y2,Math.max(n,s))&&d(u,m)):(q=sigma.utils.getQuadraticControlPoint(o[b+"x"],o[b+"y"],p[b+"x"],p[b+"y"]),sigma.utils.isPointOnQuadraticCurve(v,w,o[b+"x"],o[b+"y"],p[b+"x"],p[b+"y"],q.x,q.y,Math.max(n,s))&&d(u,m)):sigma.utils.isPointOnSegment(v,w,o[b+"x"],o[b+"y"],p[b+"x"],p[b+"y"],Math.max(n,s))&&d(u,m));return u}function e(a){function b(a){j.settings("eventsEnabled")&&(j.dispatchEvent("click",a.data),i=c(a),k=d(a),i.length?(j.dispatchEvent("clickNode",{node:i[0],captor:a.data}),j.dispatchEvent("clickNodes",{node:i,captor:a.data})):k.length?(j.dispatchEvent("clickEdge",{edge:k[0],captor:a.data}),j.dispatchEvent("clickEdges",{edge:k,captor:a.data})):j.dispatchEvent("clickStage",{captor:a.data}))}function e(a){j.settings("eventsEnabled")&&(j.dispatchEvent("doubleClick",a.data),i=c(a),k=d(a),i.length?(j.dispatchEvent("doubleClickNode",{node:i[0],captor:a.data}),j.dispatchEvent("doubleClickNodes",{node:i,captor:a.data})):k.length?(j.dispatchEvent("doubleClickEdge",{edge:k[0],captor:a.data}),j.dispatchEvent("doubleClickEdges",{edge:k,captor:a.data})):j.dispatchEvent("doubleClickStage",{captor:a.data}))}function f(a){j.settings("eventsEnabled")&&(j.dispatchEvent("rightClick",a.data),i=c(a),k=d(a),i.length?(j.dispatchEvent("rightClickNode",{node:i[0],captor:a.data}),j.dispatchEvent("rightClickNodes",{node:i,captor:a.data})):k.length?(j.dispatchEvent("rightClickEdge",{edge:k[0],captor:a.data}),j.dispatchEvent("rightClickEdges",{edge:k,captor:a.data})):j.dispatchEvent("rightClickStage",{captor:a.data}))}function g(a){if(j.settings("eventsEnabled")){var b,c,d,e,f=[],g=[];for(b in l)f.push(l[b]);for(l={},c=0,d=f.length;d>c;c++)j.dispatchEvent("outNode",{node:f[c],captor:a.data});for(f.length&&j.dispatchEvent("outNodes",{nodes:f,captor:a.data}),m={},c=0,e=g.length;e>c;c++)j.dispatchEvent("outEdge",{edge:g[c],captor:a.data});g.length&&j.dispatchEvent("outEdges",{edges:g,captor:a.data})}}function h(a){if(j.settings("eventsEnabled")){i=c(a),k=d(a);var b,e,f,g,h=[],n=[],o={},p=i.length,q=[],r=[],s={},t=k.length;for(b=0;p>b;b++)f=i[b],o[f.id]=f,l[f.id]||(n.push(f),l[f.id]=f);for(e in l)o[e]||(h.push(l[e]),delete l[e]);for(b=0,p=n.length;p>b;b++)j.dispatchEvent("overNode",{node:n[b],captor:a.data});for(b=0,p=h.length;p>b;b++)j.dispatchEvent("outNode",{node:h[b],captor:a.data});for(n.length&&j.dispatchEvent("overNodes",{nodes:n,captor:a.data}),h.length&&j.dispatchEvent("outNodes",{nodes:h,captor:a.data}),b=0;t>b;b++)g=k[b],s[g.id]=g,m[g.id]||(r.push(g),m[g.id]=g);for(e in m)s[e]||(q.push(m[e]),delete m[e]);for(b=0,t=r.length;t>b;b++)j.dispatchEvent("overEdge",{edge:r[b],captor:a.data});for(b=0,t=q.length;t>b;b++)j.dispatchEvent("outEdge",{edge:q[b],captor:a.data});r.length&&j.dispatchEvent("overEdges",{edges:r,captor:a.data}),q.length&&j.dispatchEvent("outEdges",{edges:q,captor:a.data})}}var i,k,l={},m={};a.bind("click",b),a.bind("mousedown",h),a.bind("mouseup",h),a.bind("mousemove",h),a.bind("mouseout",g),a.bind("doubleclick",e),a.bind("rightclick",f),j.bind("render",h)}var f,g,h,i,j=this;for(f=0,g=this.captors.length;g>f;f++)e(this.captors[f])}}.call(this),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.misc"),sigma.misc.bindDOMEvents=function(a){function b(a){this.attr=function(b){return a.getAttributeNS(null,b)},this.tag=a.tagName,this.class=this.attr("class"),this.id=this.attr("id"),this.isNode=function(){return!!~this.class.indexOf(g.settings("classPrefix")+"-node")},this.isEdge=function(){return!!~this.class.indexOf(g.settings("classPrefix")+"-edge")},this.isHover=function(){return!!~this.class.indexOf(g.settings("classPrefix")+"-hover")}}function c(a){if(g.settings("eventsEnabled")){g.dispatchEvent("click",a);var c=new b(a.target);c.isNode()?g.dispatchEvent("clickNode",{node:h.nodes(c.attr("data-node-id"))}):g.dispatchEvent("clickStage"),a.preventDefault(),a.stopPropagation()}}function d(a){if(g.settings("eventsEnabled")){g.dispatchEvent("doubleClick",a);var c=new b(a.target);c.isNode()?g.dispatchEvent("doubleClickNode",{node:h.nodes(c.attr("data-node-id"))}):g.dispatchEvent("doubleClickStage"),a.preventDefault(),a.stopPropagation()}}function e(a){var c=a.toElement||a.target;if(g.settings("eventsEnabled")&&c){var d=new b(c);if(d.isNode())g.dispatchEvent("overNode",{node:h.nodes(d.attr("data-node-id"))});else if(d.isEdge()){var e=h.edges(d.attr("data-edge-id"));g.dispatchEvent("overEdge",{edge:e,source:h.nodes(e.source),target:h.nodes(e.target)})}}}function f(a){var c=a.fromElement||a.originalTarget;if(g.settings("eventsEnabled")){var d=new b(c);if(d.isNode())g.dispatchEvent("outNode",{node:h.nodes(d.attr("data-node-id"))});else if(d.isEdge()){var e=h.edges(d.attr("data-edge-id"));g.dispatchEvent("outEdge",{edge:e,source:h.nodes(e.source),target:h.nodes(e.target)})}}}var g=this,h=this.graph;a.addEventListener("click",c,!1),sigma.utils.doubleClick(a,"click",d),a.addEventListener("touchstart",c,!1),sigma.utils.doubleClick(a,"touchstart",d),a.addEventListener("mouseover",e,!0),a.addEventListener("mouseout",f,!0)}}.call(this),function(){"use strict";if("undefined"==typeof sigma)throw"sigma is not declared";sigma.utils.pkg("sigma.misc"),sigma.misc.drawHovers=function(a){function b(){var b,f,g,h,i,j=c.contexts.hover.canvas,k=c.settings("defaultNodeType"),l=c.settings("defaultEdgeType"),m=sigma.canvas.hovers,n=sigma.canvas.edgehovers,o=sigma.canvas.extremities,p=c.settings.embedObjects({prefix:a});if(c.contexts.hover.clearRect(0,0,j.width,j.height),p("enableHovering")&&p("singleHover")&&Object.keys(d).length&&(h=d[Object.keys(d)[0]],(m[h.type]||m[k]||m.def)(h,c.contexts.hover,p)),p("enableHovering")&&!p("singleHover"))for(b in d)(m[d[b].type]||m[k]||m.def)(d[b],c.contexts.hover,p);if(p("enableEdgeHovering")&&p("singleHover")&&Object.keys(e).length&&(i=e[Object.keys(e)[0]],f=c.graph.nodes(i.source),g=c.graph.nodes(i.target),i.hidden||((n[i.type]||n[l]||n.def)(i,f,g,c.contexts.hover,p),p("edgeHoverExtremities")?(o[i.type]||o.def)(i,f,g,c.contexts.hover,p):((sigma.canvas.nodes[f.type]||sigma.canvas.nodes.def)(f,c.contexts.hover,p),(sigma.canvas.nodes[g.type]||sigma.canvas.nodes.def)(g,c.contexts.hover,p)))),p("enableEdgeHovering")&&!p("singleHover"))for(b in e)i=e[b],f=c.graph.nodes(i.source),g=c.graph.nodes(i.target),i.hidden||((n[i.type]||n[l]||n.def)(i,f,g,c.contexts.hover,p),p("edgeHoverExtremities")?(o[i.type]||o.def)(i,f,g,c.contexts.hover,p):((sigma.canvas.nodes[f.type]||sigma.canvas.nodes.def)(f,c.contexts.hover,p),(sigma.canvas.nodes[g.type]||sigma.canvas.nodes.def)(g,c.contexts.hover,p)))}var c=this,d={},e={};this.bind("overNode",function(a){var c=a.data.node;c.hidden||(d[c.id]=c,b())}),this.bind("outNode",function(a){delete d[a.data.node.id],b()}),this.bind("overEdge",function(a){var c=a.data.edge;c.hidden||(e[c.id]=c,b())}),this.bind("outEdge",function(a){delete e[a.data.edge.id],b()}),this.bind("render",function(){b()})}}.call(this); \ No newline at end of file diff --git a/js/lib/sigma/sigma.js b/js/lib/sigma/sigma.js deleted file mode 100755 index 6f5915e..0000000 --- a/js/lib/sigma/sigma.js +++ /dev/null @@ -1,12097 +0,0 @@ -;(function(undefined) { - 'use strict'; - - var __instances = {}; - - /** - * This is the sigma instances constructor. One instance of sigma represent - * one graph. It is possible to represent this grapĥ with several renderers - * at the same time. By default, the default renderer (WebGL + Canvas - * polyfill) will be used as the only renderer, with the container specified - * in the configuration. - * - * @param {?*} conf The configuration of the instance. There are a lot of - * different recognized forms to instantiate sigma, check - * example files, documentation in this file and unit - * tests to know more. - * @return {sigma} The fresh new sigma instance. - * - * Instanciating sigma: - * ******************** - * If no parameter is given to the constructor, the instance will be created - * without any renderer or camera. It will just instantiate the graph, and - * other modules will have to be instantiated through the public methods, - * like "addRenderer" etc: - * - * > s0 = new sigma(); - * > s0.addRenderer({ - * > type: 'canvas', - * > container: 'my-container-id' - * > }); - * - * In most of the cases, sigma will simply be used with the default renderer. - * Then, since the only required parameter is the DOM container, there are - * some simpler way to call the constructor. The four following calls do the - * exact same things: - * - * > s1 = new sigma('my-container-id'); - * > s2 = new sigma(document.getElementById('my-container-id')); - * > s3 = new sigma({ - * > container: document.getElementById('my-container-id') - * > }); - * > s4 = new sigma({ - * > renderers: [{ - * > container: document.getElementById('my-container-id') - * > }] - * > }); - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters, when calling the - * constructor with to top level configuration object (fourth case in the - * previous examples): - * - * {?string} id The id of the instance. It will be generated - * automatically if not specified. - * {?array} renderers An array containing objects describing renderers. - * {?object} graph An object containing an array of nodes and an array - * of edges, to avoid having to add them by hand later. - * {?object} settings An object containing instance specific settings that - * will override the default ones defined in the object - * sigma.settings. - */ - var sigma = function(conf) { - // Local variables: - // **************** - var i, - l, - a, - c, - o, - id; - - sigma.classes.dispatcher.extend(this); - - // Private attributes: - // ******************* - var _self = this, - _conf = conf || {}; - - // Little shortcut: - // **************** - // The configuration is supposed to have a list of the configuration - // objects for each renderer. - // - If there are no configuration at all, then nothing is done. - // - If there are no renderer list, the given configuration object will be - // considered as describing the first and only renderer. - // - If there are no renderer list nor "container" object, it will be - // considered as the container itself (a DOM element). - // - If the argument passed to sigma() is a string, it will be considered - // as the ID of the DOM container. - if ( - typeof _conf === 'string' || - _conf instanceof HTMLElement - ) - _conf = { - renderers: [_conf] - }; - else if (Object.prototype.toString.call(_conf) === '[object Array]') - _conf = { - renderers: _conf - }; - - // Also check "renderer" and "container" keys: - o = _conf.renderers || _conf.renderer || _conf.container; - if (!_conf.renderers || _conf.renderers.length === 0) - if ( - typeof o === 'string' || - o instanceof HTMLElement || - (typeof o === 'object' && 'container' in o) - ) - _conf.renderers = [o]; - - // Recense the instance: - if (_conf.id) { - if (__instances[_conf.id]) - throw 'sigma: Instance "' + _conf.id + '" already exists.'; - Object.defineProperty(this, 'id', { - value: _conf.id - }); - } else { - id = 0; - while (__instances[id]) - id++; - Object.defineProperty(this, 'id', { - value: '' + id - }); - } - __instances[this.id] = this; - - // Initialize settings function: - this.settings = new sigma.classes.configurable( - sigma.settings, - _conf.settings || {} - ); - - // Initialize locked attributes: - Object.defineProperty(this, 'graph', { - value: new sigma.classes.graph(this.settings), - configurable: true - }); - Object.defineProperty(this, 'middlewares', { - value: [], - configurable: true - }); - Object.defineProperty(this, 'cameras', { - value: {}, - configurable: true - }); - Object.defineProperty(this, 'renderers', { - value: {}, - configurable: true - }); - Object.defineProperty(this, 'renderersPerCamera', { - value: {}, - configurable: true - }); - Object.defineProperty(this, 'cameraFrames', { - value: {}, - configurable: true - }); - Object.defineProperty(this, 'camera', { - get: function() { - return this.cameras[0]; - } - }); - Object.defineProperty(this, 'events', { - value: [ - 'click', - 'rightClick', - 'clickStage', - 'doubleClickStage', - 'rightClickStage', - 'clickNode', - 'clickNodes', - 'doubleClickNode', - 'doubleClickNodes', - 'rightClickNode', - 'rightClickNodes', - 'overNode', - 'overNodes', - 'outNode', - 'outNodes', - 'downNode', - 'downNodes', - 'upNode', - 'upNodes' - ], - configurable: true - }); - - // Add a custom handler, to redispatch events from renderers: - this._handler = (function(e) { - var k, - data = {}; - - for (k in e.data) - data[k] = e.data[k]; - - data.renderer = e.target; - this.dispatchEvent(e.type, data); - }).bind(this); - - // Initialize renderers: - a = _conf.renderers || []; - for (i = 0, l = a.length; i < l; i++) - this.addRenderer(a[i]); - - // Initialize middlewares: - a = _conf.middlewares || []; - for (i = 0, l = a.length; i < l; i++) - this.middlewares.push( - typeof a[i] === 'string' ? - sigma.middlewares[a[i]] : - a[i] - ); - - // Check if there is already a graph to fill in: - if (typeof _conf.graph === 'object' && _conf.graph) { - this.graph.read(_conf.graph); - - // If a graph is given to the to the instance, the "refresh" method is - // directly called: - this.refresh(); - } - - // Deal with resize: - window.addEventListener('resize', function() { - if (_self.settings) - _self.refresh(); - }); - }; - - - - - /** - * This methods will instantiate and reference a new camera. If no id is - * specified, then an automatic id will be generated. - * - * @param {?string} id Eventually the camera id. - * @return {sigma.classes.camera} The fresh new camera instance. - */ - sigma.prototype.addCamera = function(id) { - var self = this, - camera; - - if (!arguments.length) { - id = 0; - while (this.cameras['' + id]) - id++; - id = '' + id; - } - - if (this.cameras[id]) - throw 'sigma.addCamera: The camera "' + id + '" already exists.'; - - camera = new sigma.classes.camera(id, this.graph, this.settings); - this.cameras[id] = camera; - - // Add a quadtree to the camera: - camera.quadtree = new sigma.classes.quad(); - - // Add an edgequadtree to the camera: - if (sigma.classes.edgequad !== undefined) { - camera.edgequadtree = new sigma.classes.edgequad(); - } - - camera.bind('coordinatesUpdated', function(e) { - self.renderCamera(camera, camera.isAnimated); - }); - - this.renderersPerCamera[id] = []; - - return camera; - }; - - /** - * This method kills a camera, and every renderer attached to it. - * - * @param {string|camera} v The camera to kill or its ID. - * @return {sigma} Returns the instance. - */ - sigma.prototype.killCamera = function(v) { - v = typeof v === 'string' ? this.cameras[v] : v; - - if (!v) - throw 'sigma.killCamera: The camera is undefined.'; - - var i, - l, - a = this.renderersPerCamera[v.id]; - - for (l = a.length, i = l - 1; i >= 0; i--) - this.killRenderer(a[i]); - - delete this.renderersPerCamera[v.id]; - delete this.cameraFrames[v.id]; - delete this.cameras[v.id]; - - if (v.kill) - v.kill(); - - return this; - }; - - /** - * This methods will instantiate and reference a new renderer. The "type" - * argument can be the constructor or its name in the "sigma.renderers" - * package. If no type is specified, then "sigma.renderers.def" will be used. - * If no id is specified, then an automatic id will be generated. - * - * @param {?object} options Eventually some options to give to the renderer - * constructor. - * @return {renderer} The fresh new renderer instance. - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters in the "options" - * object: - * - * {?string} id Eventually the renderer id. - * {?(function|string)} type Eventually the renderer constructor or its - * name in the "sigma.renderers" package. - * {?(camera|string)} camera Eventually the renderer camera or its - * id. - */ - sigma.prototype.addRenderer = function(options) { - var id, - fn, - camera, - renderer, - o = options || {}; - - // Polymorphism: - if (typeof o === 'string') - o = { - container: document.getElementById(o) - }; - else if (o instanceof HTMLElement) - o = { - container: o - }; - - // If the container still is a string, we get it by id - if (typeof o.container === 'string') - o.container = document.getElementById(o.container); - - // Reference the new renderer: - if (!('id' in o)) { - id = 0; - while (this.renderers['' + id]) - id++; - id = '' + id; - } else - id = o.id; - - if (this.renderers[id]) - throw 'sigma.addRenderer: The renderer "' + id + '" already exists.'; - - // Find the good constructor: - fn = typeof o.type === 'function' ? o.type : sigma.renderers[o.type]; - fn = fn || sigma.renderers.def; - - // Find the good camera: - camera = 'camera' in o ? - ( - o.camera instanceof sigma.classes.camera ? - o.camera : - this.cameras[o.camera] || this.addCamera(o.camera) - ) : - this.addCamera(); - - if (this.cameras[camera.id] !== camera) - throw 'sigma.addRenderer: The camera is not properly referenced.'; - - // Instantiate: - renderer = new fn(this.graph, camera, this.settings, o); - this.renderers[id] = renderer; - Object.defineProperty(renderer, 'id', { - value: id - }); - - // Bind events: - if (renderer.bind) - renderer.bind( - [ - 'click', - 'rightClick', - 'clickStage', - 'doubleClickStage', - 'rightClickStage', - 'clickNode', - 'clickNodes', - 'clickEdge', - 'clickEdges', - 'doubleClickNode', - 'doubleClickNodes', - 'doubleClickEdge', - 'doubleClickEdges', - 'rightClickNode', - 'rightClickNodes', - 'rightClickEdge', - 'rightClickEdges', - 'overNode', - 'overNodes', - 'overEdge', - 'overEdges', - 'outNode', - 'outNodes', - 'outEdge', - 'outEdges', - 'downNode', - 'downNodes', - 'downEdge', - 'downEdges', - 'upNode', - 'upNodes', - 'upEdge', - 'upEdges' - ], - this._handler - ); - - // Reference the renderer by its camera: - this.renderersPerCamera[camera.id].push(renderer); - - return renderer; - }; - - /** - * This method kills a renderer. - * - * @param {string|renderer} v The renderer to kill or its ID. - * @return {sigma} Returns the instance. - */ - sigma.prototype.killRenderer = function(v) { - v = typeof v === 'string' ? this.renderers[v] : v; - - if (!v) - throw 'sigma.killRenderer: The renderer is undefined.'; - - var a = this.renderersPerCamera[v.camera.id], - i = a.indexOf(v); - - if (i >= 0) - a.splice(i, 1); - - if (v.kill) - v.kill(); - - delete this.renderers[v.id]; - - return this; - }; - - - - - /** - * This method calls the "render" method of each renderer, with the same - * arguments than the "render" method, but will also check if the renderer - * has a "process" method, and call it if it exists. - * - * It is useful for quadtrees or WebGL processing, for instance. - * - * @param {?object} options Eventually some options to give to the refresh - * method. - * @return {sigma} Returns the instance itself. - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters in the "options" - * object: - * - * {?boolean} skipIndexation A flag specifying wether or not the refresh - * function should reindex the graph in the - * quadtrees or not (default: false). - */ - sigma.prototype.refresh = function(options) { - var i, - l, - k, - a, - c, - bounds, - prefix = 0; - - options = options || {}; - - // Call each middleware: - a = this.middlewares || []; - for (i = 0, l = a.length; i < l; i++) - a[i].call( - this, - (i === 0) ? '' : 'tmp' + prefix + ':', - (i === l - 1) ? 'ready:' : ('tmp' + (++prefix) + ':') - ); - - // Then, for each camera, call the "rescale" middleware, unless the - // settings specify not to: - for (k in this.cameras) { - c = this.cameras[k]; - if ( - c.settings('autoRescale') && - this.renderersPerCamera[c.id] && - this.renderersPerCamera[c.id].length - ) - sigma.middlewares.rescale.call( - this, - a.length ? 'ready:' : '', - c.readPrefix, - { - width: this.renderersPerCamera[c.id][0].width, - height: this.renderersPerCamera[c.id][0].height - } - ); - else - sigma.middlewares.copy.call( - this, - a.length ? 'ready:' : '', - c.readPrefix - ); - - if (!options.skipIndexation) { - // Find graph boundaries: - bounds = sigma.utils.getBoundaries( - this.graph, - c.readPrefix - ); - - // Refresh quadtree: - c.quadtree.index(this.graph.nodes(), { - prefix: c.readPrefix, - bounds: { - x: bounds.minX, - y: bounds.minY, - width: bounds.maxX - bounds.minX, - height: bounds.maxY - bounds.minY - } - }); - - // Refresh edgequadtree: - if ( - c.edgequadtree !== undefined && - c.settings('drawEdges') && - c.settings('enableEdgeHovering') - ) { - c.edgequadtree.index(this.graph, { - prefix: c.readPrefix, - bounds: { - x: bounds.minX, - y: bounds.minY, - width: bounds.maxX - bounds.minX, - height: bounds.maxY - bounds.minY - } - }); - } - } - } - - // Call each renderer: - a = Object.keys(this.renderers); - for (i = 0, l = a.length; i < l; i++) - if (this.renderers[a[i]].process) { - if (this.settings('skipErrors')) - try { - this.renderers[a[i]].process(); - } catch (e) { - console.log( - 'Warning: The renderer "' + a[i] + '" crashed on ".process()"' - ); - } - else - this.renderers[a[i]].process(); - } - - this.render(); - - return this; - }; - - /** - * This method calls the "render" method of each renderer. - * - * @return {sigma} Returns the instance itself. - */ - sigma.prototype.render = function() { - var i, - l, - a, - prefix = 0; - - // Call each renderer: - a = Object.keys(this.renderers); - for (i = 0, l = a.length; i < l; i++) - if (this.settings('skipErrors')) - try { - this.renderers[a[i]].render(); - } catch (e) { - if (this.settings('verbose')) - console.log( - 'Warning: The renderer "' + a[i] + '" crashed on ".render()"' - ); - } - else - this.renderers[a[i]].render(); - - return this; - }; - - /** - * This method calls the "render" method of each renderer that is bound to - * the specified camera. To improve the performances, if this method is - * called too often, the number of effective renderings is limitated to one - * per frame, unless you are using the "force" flag. - * - * @param {sigma.classes.camera} camera The camera to render. - * @param {?boolean} force If true, will render the camera - * directly. - * @return {sigma} Returns the instance itself. - */ - sigma.prototype.renderCamera = function(camera, force) { - var i, - l, - a, - self = this; - - if (force) { - a = this.renderersPerCamera[camera.id]; - for (i = 0, l = a.length; i < l; i++) - if (this.settings('skipErrors')) - try { - a[i].render(); - } catch (e) { - if (this.settings('verbose')) - console.log( - 'Warning: The renderer "' + a[i].id + '" crashed on ".render()"' - ); - } - else - a[i].render(); - } else { - if (!this.cameraFrames[camera.id]) { - a = this.renderersPerCamera[camera.id]; - for (i = 0, l = a.length; i < l; i++) - if (this.settings('skipErrors')) - try { - a[i].render(); - } catch (e) { - if (this.settings('verbose')) - console.log( - 'Warning: The renderer "' + - a[i].id + - '" crashed on ".render()"' - ); - } - else - a[i].render(); - - this.cameraFrames[camera.id] = requestAnimationFrame(function() { - delete self.cameraFrames[camera.id]; - }); - } - } - - return this; - }; - - /** - * This method calls the "kill" method of each module and destroys any - * reference from the instance. - */ - sigma.prototype.kill = function() { - var k; - - // Dispatching event - this.dispatchEvent('kill'); - - // Kill graph: - this.graph.kill(); - - // Kill middlewares: - delete this.middlewares; - - // Kill each renderer: - for (k in this.renderers) - this.killRenderer(this.renderers[k]); - - // Kill each camera: - for (k in this.cameras) - this.killCamera(this.cameras[k]); - - delete this.renderers; - delete this.cameras; - - // Kill everything else: - for (k in this) - if (this.hasOwnProperty(k)) - delete this[k]; - - delete __instances[this.id]; - }; - - - - - /** - * Returns a clone of the instances object or a specific running instance. - * - * @param {?string} id Eventually an instance ID. - * @return {object} The related instance or a clone of the instances - * object. - */ - sigma.instances = function(id) { - return arguments.length ? - __instances[id] : - sigma.utils.extend({}, __instances); - }; - - - - /** - * The current version of sigma: - */ - sigma.version = '1.0.3'; - - - - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') - throw 'An object called sigma is already in the global scope.'; - - this.sigma = sigma; - -}).call(this); - -/** - * conrad.js is a tiny JavaScript jobs scheduler, - * - * Version: 0.1.0 - * Sources: http://github.com/jacomyal/conrad.js - * Doc: http://github.com/jacomyal/conrad.js#readme - * - * License: - * -------- - * Copyright © 2013 Alexis Jacomy, Sciences-Po médialab - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising - * from, out of or in connection with the software or the use or other dealings - * in the Software. - */ -(function(global) { - 'use strict'; - - // Check that conrad.js has not been loaded yet: - if (global.conrad) - throw new Error('conrad already exists'); - - - /** - * PRIVATE VARIABLES: - * ****************** - */ - - /** - * A flag indicating whether conrad is running or not. - * - * @type {Number} - */ - var _lastFrameTime; - - /** - * A flag indicating whether conrad is running or not. - * - * @type {Boolean} - */ - var _isRunning = false; - - /** - * The hash of registered jobs. Each job must at least have a unique ID - * under the key "id" and a function under the key "job". This hash - * contains each running job and each waiting job. - * - * @type {Object} - */ - var _jobs = {}; - - /** - * The hash of currently running jobs. - * - * @type {Object} - */ - var _runningJobs = {}; - - /** - * The array of currently running jobs, sorted by priority. - * - * @type {Array} - */ - var _sortedByPriorityJobs = []; - - /** - * The array of currently waiting jobs. - * - * @type {Object} - */ - var _waitingJobs = {}; - - /** - * The array of finished jobs. They are stored in an array, since two jobs - * with the same "id" can happen at two different times. - * - * @type {Array} - */ - var _doneJobs = []; - - /** - * A dirty flag to keep conrad from starting: Indeed, when addJob() is called - * with several jobs, conrad must be started only at the end. This flag keeps - * me from duplicating the code that effectively adds a job. - * - * @type {Boolean} - */ - var _noStart = false; - - /** - * An hash containing some global settings about how conrad.js should - * behave. - * - * @type {Object} - */ - var _parameters = { - frameDuration: 20, - history: true - }; - - /** - * This object contains every handlers bound to conrad events. It does not - * requirea any DOM implementation, since the events are all JavaScript. - * - * @type {Object} - */ - var _handlers = Object.create(null); - - - /** - * PRIVATE FUNCTIONS: - * ****************** - */ - - /** - * Will execute the handler everytime that the indicated event (or the - * indicated events) will be triggered. - * - * @param {string|array|object} events The name of the event (or the events - * separated by spaces). - * @param {function(Object)} handler The handler to bind. - * @return {Object} Returns conrad. - */ - function _bind(events, handler) { - var i, - i_end, - event, - eArray; - - if (!arguments.length) - return; - else if ( - arguments.length === 1 && - Object(arguments[0]) === arguments[0] - ) - for (events in arguments[0]) - _bind(events, arguments[0][events]); - else if (arguments.length > 1) { - eArray = - Array.isArray(events) ? - events : - events.split(/ /); - - for (i = 0, i_end = eArray.length; i !== i_end; i += 1) { - event = eArray[i]; - - if (!_handlers[event]) - _handlers[event] = []; - - // Using an object instead of directly the handler will make possible - // later to add flags - _handlers[event].push({ - handler: handler - }); - } - } - } - - /** - * Removes the handler from a specified event (or specified events). - * - * @param {?string} events The name of the event (or the events - * separated by spaces). If undefined, - * then all handlers are removed. - * @param {?function(Object)} handler The handler to unbind. If undefined, - * each handler bound to the event or the - * events will be removed. - * @return {Object} Returns conrad. - */ - function _unbind(events, handler) { - var i, - i_end, - j, - j_end, - a, - event, - eArray = Array.isArray(events) ? - events : - events.split(/ /); - - if (!arguments.length) - _handlers = Object.create(null); - else if (handler) { - for (i = 0, i_end = eArray.length; i !== i_end; i += 1) { - event = eArray[i]; - if (_handlers[event]) { - a = []; - for (j = 0, j_end = _handlers[event].length; j !== j_end; j += 1) - if (_handlers[event][j].handler !== handler) - a.push(_handlers[event][j]); - - _handlers[event] = a; - } - - if (_handlers[event] && _handlers[event].length === 0) - delete _handlers[event]; - } - } else - for (i = 0, i_end = eArray.length; i !== i_end; i += 1) - delete _handlers[eArray[i]]; - } - - /** - * Executes each handler bound to the event. - * - * @param {string} events The name of the event (or the events separated - * by spaces). - * @param {?Object} data The content of the event (optional). - * @return {Object} Returns conrad. - */ - function _dispatch(events, data) { - var i, - j, - i_end, - j_end, - event, - eventName, - eArray = Array.isArray(events) ? - events : - events.split(/ /); - - data = data === undefined ? {} : data; - - for (i = 0, i_end = eArray.length; i !== i_end; i += 1) { - eventName = eArray[i]; - - if (_handlers[eventName]) { - event = { - type: eventName, - data: data || {} - }; - - for (j = 0, j_end = _handlers[eventName].length; j !== j_end; j += 1) - try { - _handlers[eventName][j].handler(event); - } catch (e) {} - } - } - } - - /** - * Executes the most prioritary job once, and deals with filling the stats - * (done, time, averageTime, currentTime, etc...). - * - * @return {?Object} Returns the job object if it has to be killed, null else. - */ - function _executeFirstJob() { - var i, - l, - test, - kill, - pushed = false, - time = __dateNow(), - job = _sortedByPriorityJobs.shift(); - - // Execute the job and look at the result: - test = job.job(); - - // Deal with stats: - time = __dateNow() - time; - job.done++; - job.time += time; - job.currentTime += time; - job.weightTime = job.currentTime / (job.weight || 1); - job.averageTime = job.time / job.done; - - // Check if the job has to be killed: - kill = job.count ? (job.count <= job.done) : !test; - - // Reset priorities: - if (!kill) { - for (i = 0, l = _sortedByPriorityJobs.length; i < l; i++) - if (_sortedByPriorityJobs[i].weightTime > job.weightTime) { - _sortedByPriorityJobs.splice(i, 0, job); - pushed = true; - break; - } - - if (!pushed) - _sortedByPriorityJobs.push(job); - } - - return kill ? job : null; - } - - /** - * Activates a job, by adding it to the _runningJobs object and the - * _sortedByPriorityJobs array. It also initializes its currentTime value. - * - * @param {Object} job The job to activate. - */ - function _activateJob(job) { - var l = _sortedByPriorityJobs.length; - - // Add the job to the running jobs: - _runningJobs[job.id] = job; - job.status = 'running'; - - // Add the job to the priorities: - if (l) { - job.weightTime = _sortedByPriorityJobs[l - 1].weightTime; - job.currentTime = job.weightTime * (job.weight || 1); - } - - // Initialize the job and dispatch: - job.startTime = __dateNow(); - _dispatch('jobStarted', __clone(job)); - - _sortedByPriorityJobs.push(job); - } - - /** - * The main loop of conrad.js: - * . It executes job such that they all occupate the same processing time. - * . It stops jobs that do not need to be executed anymore. - * . It triggers callbacks when it is relevant. - * . It starts waiting jobs when they need to be started. - * . It injects frames to keep a constant frapes per second ratio. - * . It stops itself when there are no more jobs to execute. - */ - function _loop() { - var k, - o, - l, - job, - time, - deadJob; - - // Deal with the newly added jobs (the _jobs object): - for (k in _jobs) { - job = _jobs[k]; - - if (job.after) - _waitingJobs[k] = job; - else - _activateJob(job); - - delete _jobs[k]; - } - - // Set the _isRunning flag to false if there are no running job: - _isRunning = !!_sortedByPriorityJobs.length; - - // Deal with the running jobs (the _runningJobs object): - while ( - _sortedByPriorityJobs.length && - __dateNow() - _lastFrameTime < _parameters.frameDuration - ) { - deadJob = _executeFirstJob(); - - // Deal with the case where the job has ended: - if (deadJob) { - _killJob(deadJob.id); - - // Check for waiting jobs: - for (k in _waitingJobs) - if (_waitingJobs[k].after === deadJob.id) { - _activateJob(_waitingJobs[k]); - delete _waitingJobs[k]; - } - } - } - - // Check if conrad still has jobs to deal with, and kill it if not: - if (_isRunning) { - // Update the _lastFrameTime: - _lastFrameTime = __dateNow(); - - _dispatch('enterFrame'); - setTimeout(_loop, 0); - } else - _dispatch('stop'); - } - - /** - * Adds one or more jobs, and starts the loop if no job was running before. A - * job is at least a unique string "id" and a function, and there are some - * parameters that you can specify for each job to modify the way conrad will - * execute it. If a job is added with the "id" of another job that is waiting - * or still running, an error will be thrown. - * - * When a job is added, it is referenced in the _jobs object, by its id. - * Then, if it has to be executed right now, it will be also referenced in - * the _runningJobs object. If it has to wait, then it will be added into the - * _waitingJobs object, until it can start. - * - * Keep reading this documentation to see how to call this method. - * - * @return {Object} Returns conrad. - * - * Adding one job: - * *************** - * Basically, a job is defined by its string id and a function (the job). It - * is also possible to add some parameters: - * - * > conrad.addJob('myJobId', myJobFunction); - * > conrad.addJob('myJobId', { - * > job: myJobFunction, - * > someParameter: someValue - * > }); - * > conrad.addJob({ - * > id: 'myJobId', - * > job: myJobFunction, - * > someParameter: someValue - * > }); - * - * Adding several jobs: - * ******************** - * When adding several jobs at the same time, it is possible to specify - * parameters for each one individually or for all: - * - * > conrad.addJob([ - * > { - * > id: 'myJobId1', - * > job: myJobFunction1, - * > someParameter1: someValue1 - * > }, - * > { - * > id: 'myJobId2', - * > job: myJobFunction2, - * > someParameter2: someValue2 - * > } - * > ], { - * > someCommonParameter: someCommonValue - * > }); - * > conrad.addJob({ - * > myJobId1: {, - * > job: myJobFunction1, - * > someParameter1: someValue1 - * > }, - * > myJobId2: {, - * > job: myJobFunction2, - * > someParameter2: someValue2 - * > } - * > }, { - * > someCommonParameter: someCommonValue - * > }); - * > conrad.addJob({ - * > myJobId1: myJobFunction1, - * > myJobId2: myJobFunction2 - * > }, { - * > someCommonParameter: someCommonValue - * > }); - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters: - * - * {?Function} end A callback to execute when the job is ended. It is - * not executed if the job is killed instead of ended - * "naturally". - * {?Integer} count The number of time the job has to be executed. - * {?Number} weight If specified, the job will be executed as it was - * added "weight" times. - * {?String} after The id of another job (eventually not added yet). - * If specified, this job will start only when the - * specified "after" job is ended. - */ - function _addJob(v1, v2) { - var i, - l, - o; - - // Array of jobs: - if (Array.isArray(v1)) { - // Keep conrad to start until the last job is added: - _noStart = true; - - for (i = 0, l = v1.length; i < l; i++) - _addJob(v1[i].id, __extend(v1[i], v2)); - - _noStart = false; - if (!_isRunning) { - // Update the _lastFrameTime: - _lastFrameTime = __dateNow(); - - _dispatch('start'); - _loop(); - } - } else if (typeof v1 === 'object') { - // One job (object): - if (typeof v1.id === 'string') - _addJob(v1.id, v1); - - // Hash of jobs: - else { - // Keep conrad to start until the last job is added: - _noStart = true; - - for (i in v1) - if (typeof v1[i] === 'function') - _addJob(i, __extend({ - job: v1[i] - }, v2)); - else - _addJob(i, __extend(v1[i], v2)); - - _noStart = false; - if (!_isRunning) { - // Update the _lastFrameTime: - _lastFrameTime = __dateNow(); - - _dispatch('start'); - _loop(); - } - } - - // One job (string, *): - } else if (typeof v1 === 'string') { - if (_hasJob(v1)) - throw new Error( - '[conrad.addJob] Job with id "' + v1 + '" already exists.' - ); - - // One job (string, function): - if (typeof v2 === 'function') { - o = { - id: v1, - done: 0, - time: 0, - status: 'waiting', - currentTime: 0, - averageTime: 0, - weightTime: 0, - job: v2 - }; - - // One job (string, object): - } else if (typeof v2 === 'object') { - o = __extend( - { - id: v1, - done: 0, - time: 0, - status: 'waiting', - currentTime: 0, - averageTime: 0, - weightTime: 0 - }, - v2 - ); - - // If none of those cases, throw an error: - } else - throw new Error('[conrad.addJob] Wrong arguments.'); - - // Effectively add the job: - _jobs[v1] = o; - _dispatch('jobAdded', __clone(o)); - - // Check if the loop has to be started: - if (!_isRunning && !_noStart) { - // Update the _lastFrameTime: - _lastFrameTime = __dateNow(); - - _dispatch('start'); - _loop(); - } - - // If none of those cases, throw an error: - } else - throw new Error('[conrad.addJob] Wrong arguments.'); - - return this; - } - - /** - * Kills one or more jobs, indicated by their ids. It is only possible to - * kill running jobs or waiting jobs. If you try to kill a job that does not - * exists or that is already killed, a warning will be thrown. - * - * @param {Array|String} v1 A string job id or an array of job ids. - * @return {Object} Returns conrad. - */ - function _killJob(v1) { - var i, - l, - k, - a, - job, - found = false; - - // Array of job ids: - if (Array.isArray(v1)) - for (i = 0, l = v1.length; i < l; i++) - _killJob(v1[i]); - - // One job's id: - else if (typeof v1 === 'string') { - a = [_runningJobs, _waitingJobs, _jobs]; - - // Remove the job from the hashes: - for (i = 0, l = a.length; i < l; i++) - if (v1 in a[i]) { - job = a[i][v1]; - - if (_parameters.history) { - job.status = 'done'; - _doneJobs.push(job); - } - - _dispatch('jobEnded', __clone(job)); - delete a[i][v1]; - - if (typeof job.end === 'function') - job.end(); - - found = true; - } - - // Remove the priorities array: - a = _sortedByPriorityJobs; - for (i = 0, l = a.length; i < l; i++) - if (a[i].id === v1) { - a.splice(i, 1); - break; - } - - if (!found) - throw new Error('[conrad.killJob] Job "' + v1 + '" not found.'); - - // If none of those cases, throw an error: - } else - throw new Error('[conrad.killJob] Wrong arguments.'); - - return this; - } - - /** - * Kills every running, waiting, and just added jobs. - * - * @return {Object} Returns conrad. - */ - function _killAll() { - var k, - jobs = __extend(_jobs, _runningJobs, _waitingJobs); - - // Take every jobs and push them into the _doneJobs object: - if (_parameters.history) - for (k in jobs) { - jobs[k].status = 'done'; - _doneJobs.push(jobs[k]); - - if (typeof jobs[k].end === 'function') - jobs[k].end(); - } - - // Reinitialize the different jobs lists: - _jobs = {}; - _waitingJobs = {}; - _runningJobs = {}; - _sortedByPriorityJobs = []; - - // In case some jobs are added right after the kill: - _isRunning = false; - - return this; - } - - /** - * Returns true if a job with the specified id is currently running or - * waiting, and false else. - * - * @param {String} id The id of the job. - * @return {?Object} Returns the job object if it exists. - */ - function _hasJob(id) { - var job = _jobs[id] || _runningJobs[id] || _waitingJobs[id]; - return job ? __extend(job) : null; - } - - /** - * This method will set the setting specified by "v1" to the value specified - * by "v2" if both are given, and else return the current value of the - * settings "v1". - * - * @param {String} v1 The name of the property. - * @param {?*} v2 Eventually, a value to set to the specified - * property. - * @return {Object|*} Returns the specified settings value if "v2" is not - * given, and conrad else. - */ - function _settings(v1, v2) { - var o; - - if (typeof a1 === 'string' && arguments.length === 1) - return _parameters[a1]; - else { - o = (typeof a1 === 'object' && arguments.length === 1) ? - a1 || {} : - {}; - if (typeof a1 === 'string') - o[a1] = a2; - - for (var k in o) - if (o[k] !== undefined) - _parameters[k] = o[k]; - else - delete _parameters[k]; - - return this; - } - } - - /** - * Returns true if conrad is currently running, and false else. - * - * @return {Boolean} Returns _isRunning. - */ - function _getIsRunning() { - return _isRunning; - } - - /** - * Unreference every jobs that are stored in the _doneJobs object. It will - * not be possible anymore to get stats about these jobs, but it will release - * the memory. - * - * @return {Object} Returns conrad. - */ - function _clearHistory() { - _doneJobs = []; - return this; - } - - /** - * Returns a snapshot of every data about jobs that wait to be started, are - * currently running or are done. - * - * It is possible to get only running, waiting or done jobs by giving - * "running", "waiting" or "done" as fist argument. - * - * It is also possible to get every job with a specified id by giving it as - * first argument. Also, using a RegExp instead of an id will return every - * jobs whose ids match the RegExp. And these two last use cases work as well - * by giving before "running", "waiting" or "done". - * - * @return {Array} The array of the matching jobs. - * - * Some call examples: - * ******************* - * > conrad.getStats('running') - * > conrad.getStats('waiting') - * > conrad.getStats('done') - * > conrad.getStats('myJob') - * > conrad.getStats(/test/) - * > conrad.getStats('running', 'myRunningJob') - * > conrad.getStats('running', /test/) - */ - function _getStats(v1, v2) { - var a, - k, - i, - l, - stats, - pattern, - isPatternString; - - if (!arguments.length) { - stats = []; - - for (k in _jobs) - stats.push(_jobs[k]); - - for (k in _waitingJobs) - stats.push(_waitingJobs[k]); - - for (k in _runningJobs) - stats.push(_runningJobs[k]); - - stats = stats.concat(_doneJobs); - } - - if (typeof v1 === 'string') - switch (v1) { - case 'waiting': - stats = __objectValues(_waitingJobs); - break; - case 'running': - stats = __objectValues(_runningJobs); - break; - case 'done': - stats = _doneJobs; - break; - default: - pattern = v1; - } - - if (v1 instanceof RegExp) - pattern = v1; - - if (!pattern && (typeof v2 === 'string' || v2 instanceof RegExp)) - pattern = v2; - - // Filter jobs if a pattern is given: - if (pattern) { - isPatternString = typeof pattern === 'string'; - - if (stats instanceof Array) { - a = stats; - } else if (typeof stats === 'object') { - a = []; - - for (k in stats) - a = a.concat(stats[k]); - } else { - a = []; - - for (k in _jobs) - a.push(_jobs[k]); - - for (k in _waitingJobs) - a.push(_waitingJobs[k]); - - for (k in _runningJobs) - a.push(_runningJobs[k]); - - a = a.concat(_doneJobs); - } - - stats = []; - for (i = 0, l = a.length; i < l; i++) - if (isPatternString ? a[i].id === pattern : a[i].id.match(pattern)) - stats.push(a[i]); - } - - return __clone(stats); - } - - - /** - * TOOLS FUNCTIONS: - * **************** - */ - - /** - * This function takes any number of objects as arguments, copies from each - * of these objects each pair key/value into a new object, and finally - * returns this object. - * - * The arguments are parsed from the last one to the first one, such that - * when two objects have keys in common, the "earliest" object wins. - * - * Example: - * ******** - * > var o1 = { - * > a: 1, - * > b: 2, - * > c: '3' - * > }, - * > o2 = { - * > c: '4', - * > d: [ 5 ] - * > }; - * > __extend(o1, o2); - * > // Returns: { - * > // a: 1, - * > // b: 2, - * > // c: '3', - * > // d: [ 5 ] - * > // }; - * - * @param {Object+} Any number of objects. - * @return {Object} The merged object. - */ - function __extend() { - var i, - k, - res = {}, - l = arguments.length; - - for (i = l - 1; i >= 0; i--) - for (k in arguments[i]) - res[k] = arguments[i][k]; - - return res; - } - - /** - * This function simply clones an object. This object must contain only - * objects, arrays and immutable values. Since it is not public, it does not - * deal with cyclic references, DOM elements and instantiated objects - so - * use it carefully. - * - * @param {Object} The object to clone. - * @return {Object} The clone. - */ - function __clone(item) { - var result, i, k, l; - - if (!item) - return item; - - if (Array.isArray(item)) { - result = []; - for (i = 0, l = item.length; i < l; i++) - result.push(__clone(item[i])); - } else if (typeof item === 'object') { - result = {}; - for (i in item) - result[i] = __clone(item[i]); - } else - result = item; - - return result; - } - - /** - * Returns an array containing the values of an object. - * - * @param {Object} The object. - * @return {Array} The array of values. - */ - function __objectValues(o) { - var k, - a = []; - - for (k in o) - a.push(o[k]); - - return a; - } - - /** - * A short "Date.now()" polyfill. - * - * @return {Number} The current time (in ms). - */ - function __dateNow() { - return Date.now ? Date.now() : new Date().getTime(); - } - - /** - * Polyfill for the Array.isArray function: - */ - if (!Array.isArray) - Array.isArray = function(v) { - return Object.prototype.toString.call(v) === '[object Array]'; - }; - - - /** - * EXPORT PUBLIC API: - * ****************** - */ - var conrad = { - hasJob: _hasJob, - addJob: _addJob, - killJob: _killJob, - killAll: _killAll, - settings: _settings, - getStats: _getStats, - isRunning: _getIsRunning, - clearHistory: _clearHistory, - - // Events management: - bind: _bind, - unbind: _unbind, - - // Version: - version: '0.1.0' - }; - - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = conrad; - exports.conrad = conrad; - } - global.conrad = conrad; -})(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - var _root = this; - - // Initialize packages: - sigma.utils = sigma.utils || {}; - - /** - * MISC UTILS: - */ - /** - * This function takes any number of objects as arguments, copies from each - * of these objects each pair key/value into a new object, and finally - * returns this object. - * - * The arguments are parsed from the last one to the first one, such that - * when several objects have keys in common, the "earliest" object wins. - * - * Example: - * ******** - * > var o1 = { - * > a: 1, - * > b: 2, - * > c: '3' - * > }, - * > o2 = { - * > c: '4', - * > d: [ 5 ] - * > }; - * > sigma.utils.extend(o1, o2); - * > // Returns: { - * > // a: 1, - * > // b: 2, - * > // c: '3', - * > // d: [ 5 ] - * > // }; - * - * @param {object+} Any number of objects. - * @return {object} The merged object. - */ - sigma.utils.extend = function() { - var i, - k, - res = {}, - l = arguments.length; - - for (i = l - 1; i >= 0; i--) - for (k in arguments[i]) - res[k] = arguments[i][k]; - - return res; - }; - - /** - * A short "Date.now()" polyfill. - * - * @return {Number} The current time (in ms). - */ - sigma.utils.dateNow = function() { - return Date.now ? Date.now() : new Date().getTime(); - }; - - /** - * Takes a package name as parameter and checks at each lebel if it exists, - * and if it does not, creates it. - * - * Example: - * ******** - * > sigma.utils.pkg('a.b.c'); - * > a.b.c; - * > // Object {}; - * > - * > sigma.utils.pkg('a.b.d'); - * > a.b; - * > // Object { c: {}, d: {} }; - * - * @param {string} pkgName The name of the package to create/find. - * @return {object} The related package. - */ - sigma.utils.pkg = function(pkgName) { - return (pkgName || '').split('.').reduce(function(context, objName) { - return (objName in context) ? - context[objName] : - (context[objName] = {}); - }, _root); - }; - - /** - * Returns a unique incremental number ID. - * - * Example: - * ******** - * > sigma.utils.id(); - * > // 1; - * > - * > sigma.utils.id(); - * > // 2; - * > - * > sigma.utils.id(); - * > // 3; - * - * @param {string} pkgName The name of the package to create/find. - * @return {object} The related package. - */ - sigma.utils.id = (function() { - var i = 0; - return function() { - return ++i; - }; - })(); - - /** - * This function takes an hexa color (for instance "#ffcc00" or "#fc0") or a - * rgb / rgba color (like "rgb(255,255,12)" or "rgba(255,255,12,1)") and - * returns an integer equal to "r * 255 * 255 + g * 255 + b", to gain some - * memory in the data given to WebGL shaders. - * - * @param {string} val The hexa or rgba color. - * @return {number} The number value. - */ - sigma.utils.floatColor = function(val) { - var result = [0, 0, 0]; - - if (val.match(/^#/)) { - val = (val || '').replace(/^#/, ''); - result = (val.length === 3) ? - [ - parseInt(val.charAt(0) + val.charAt(0), 16), - parseInt(val.charAt(1) + val.charAt(1), 16), - parseInt(val.charAt(2) + val.charAt(2), 16) - ] : - [ - parseInt(val.charAt(0) + val.charAt(1), 16), - parseInt(val.charAt(2) + val.charAt(3), 16), - parseInt(val.charAt(4) + val.charAt(5), 16) - ]; - } else if (val.match(/^ *rgba? *\(/)) { - val = val.match( - /^ *rgba? *\( *([0-9]*) *, *([0-9]*) *, *([0-9]*) *(,.*)?\) *$/ - ); - result = [ - +val[1], - +val[2], - +val[3] - ]; - } - - return ( - result[0] * 256 * 256 + - result[1] * 256 + - result[2] - ); - }; - - /** - * Perform a zoom into a camera, with or without animation, to the - * coordinates indicated using a specified ratio. - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters in the animation - * object: - * - * {?number} duration An amount of time that means the duration of the - * animation. If this parameter doesn't exist the - * zoom will be performed without animation. - * {?function} onComplete A function to perform it after the animation. It - * will be performed even if there is no duration. - * - * @param {camera} The camera where perform the zoom. - * @param {x} The X coordiantion where the zoom goes. - * @param {y} The Y coordiantion where the zoom goes. - * @param {ratio} The ratio to apply it to the current camera ratio. - * @param {?animation} A dictionary with options for a possible animation. - */ - sigma.utils.zoomTo = function(camera, x, y, ratio, animation) { - var settings = camera.settings, - count, - newRatio, - animationSettings, - coordinates; - - // Create the newRatio dealing with min / max: - newRatio = Math.max( - settings('zoomMin'), - Math.min( - settings('zoomMax'), - camera.ratio * ratio - ) - ); - - // Check that the new ratio is different from the initial one: - if (newRatio !== camera.ratio) { - // Create the coordinates variable: - ratio = newRatio / camera.ratio; - coordinates = { - x: x * (1 - ratio) + camera.x, - y: y * (1 - ratio) + camera.y, - ratio: newRatio - }; - - if (animation && animation.duration) { - // Complete the animation setings: - count = sigma.misc.animation.killAll(camera); - animation = sigma.utils.extend( - animation, - { - easing: count ? 'quadraticOut' : 'quadraticInOut' - } - ); - - sigma.misc.animation.camera(camera, coordinates, animation); - } else { - camera.goTo(coordinates); - if (animation && animation.onComplete) - animation.onComplete(); - } - } - }; - - /** - * Return the control point coordinates for a quadratic bezier curve. - * - * @param {number} x1 The X coordinate of the start point. - * @param {number} y1 The Y coordinate of the start point. - * @param {number} x2 The X coordinate of the end point. - * @param {number} y2 The Y coordinate of the end point. - * @return {x,y} The control point coordinates. - */ - sigma.utils.getQuadraticControlPoint = function(x1, y1, x2, y2) { - return { - x: (x1 + x2) / 2 + (y2 - y1) / 4, - y: (y1 + y2) / 2 + (x1 - x2) / 4 - }; - }; - - /** - * Compute the coordinates of the point positioned - * at length t in the quadratic bezier curve. - * - * @param {number} t In [0,1] the step percentage to reach - * the point in the curve from the context point. - * @param {number} x1 The X coordinate of the context point. - * @param {number} y1 The Y coordinate of the context point. - * @param {number} x2 The X coordinate of the ending point. - * @param {number} y2 The Y coordinate of the ending point. - * @param {number} xi The X coordinate of the control point. - * @param {number} yi The Y coordinate of the control point. - * @return {object} {x,y}. - */ - sigma.utils.getPointOnQuadraticCurve = function(t, x1, y1, x2, y2, xi, yi) { - // http://stackoverflow.com/a/5634528 - return { - x: Math.pow(1 - t, 2) * x1 + 2 * (1 - t) * t * xi + Math.pow(t, 2) * x2, - y: Math.pow(1 - t, 2) * y1 + 2 * (1 - t) * t * yi + Math.pow(t, 2) * y2 - }; - }; - - /** - * Compute the coordinates of the point positioned - * at length t in the cubic bezier curve. - * - * @param {number} t In [0,1] the step percentage to reach - * the point in the curve from the context point. - * @param {number} x1 The X coordinate of the context point. - * @param {number} y1 The Y coordinate of the context point. - * @param {number} x2 The X coordinate of the end point. - * @param {number} y2 The Y coordinate of the end point. - * @param {number} cx The X coordinate of the first control point. - * @param {number} cy The Y coordinate of the first control point. - * @param {number} dx The X coordinate of the second control point. - * @param {number} dy The Y coordinate of the second control point. - * @return {object} {x,y} The point at t. - */ - sigma.utils.getPointOnBezierCurve = - function(t, x1, y1, x2, y2, cx, cy, dx, dy) { - // http://stackoverflow.com/a/15397596 - // Blending functions: - var B0_t = Math.pow(1 - t, 3), - B1_t = 3 * t * Math.pow(1 - t, 2), - B2_t = 3 * Math.pow(t, 2) * (1 - t), - B3_t = Math.pow(t, 3); - - return { - x: (B0_t * x1) + (B1_t * cx) + (B2_t * dx) + (B3_t * x2), - y: (B0_t * y1) + (B1_t * cy) + (B2_t * dy) + (B3_t * y2) - }; - }; - - /** - * Return the coordinates of the two control points for a self loop (i.e. - * where the start point is also the end point) computed as a cubic bezier - * curve. - * - * @param {number} x The X coordinate of the node. - * @param {number} y The Y coordinate of the node. - * @param {number} size The node size. - * @return {x1,y1,x2,y2} The coordinates of the two control points. - */ - sigma.utils.getSelfLoopControlPoints = function(x , y, size) { - return { - x1: x - size * 7, - y1: y, - x2: x, - y2: y + size * 7 - }; - }; - - /** - * Return the euclidian distance between two points of a plane - * with an orthonormal basis. - * - * @param {number} x1 The X coordinate of the first point. - * @param {number} y1 The Y coordinate of the first point. - * @param {number} x2 The X coordinate of the second point. - * @param {number} y2 The Y coordinate of the second point. - * @return {number} The euclidian distance. - */ - sigma.utils.getDistance = function(x0, y0, x1, y1) { - return Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)); - }; - - /** - * Return the coordinates of the intersection points of two circles. - * - * @param {number} x0 The X coordinate of center location of the first - * circle. - * @param {number} y0 The Y coordinate of center location of the first - * circle. - * @param {number} r0 The radius of the first circle. - * @param {number} x1 The X coordinate of center location of the second - * circle. - * @param {number} y1 The Y coordinate of center location of the second - * circle. - * @param {number} r1 The radius of the second circle. - * @return {xi,yi} The coordinates of the intersection points. - */ - sigma.utils.getCircleIntersection = function(x0, y0, r0, x1, y1, r1) { - // http://stackoverflow.com/a/12219802 - var a, dx, dy, d, h, rx, ry, x2, y2; - - // dx and dy are the vertical and horizontal distances between the circle - // centers: - dx = x1 - x0; - dy = y1 - y0; - - // Determine the straight-line distance between the centers: - d = Math.sqrt((dy * dy) + (dx * dx)); - - // Check for solvability: - if (d > (r0 + r1)) { - // No solution. circles do not intersect. - return false; - } - if (d < Math.abs(r0 - r1)) { - // No solution. one circle is contained in the other. - return false; - } - - //'point 2' is the point where the line through the circle intersection - // points crosses the line between the circle centers. - - // Determine the distance from point 0 to point 2: - a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d); - - // Determine the coordinates of point 2: - x2 = x0 + (dx * a / d); - y2 = y0 + (dy * a / d); - - // Determine the distance from point 2 to either of the intersection - // points: - h = Math.sqrt((r0 * r0) - (a * a)); - - // Determine the offsets of the intersection points from point 2: - rx = -dy * (h / d); - ry = dx * (h / d); - - // Determine the absolute intersection points: - var xi = x2 + rx; - var xi_prime = x2 - rx; - var yi = y2 + ry; - var yi_prime = y2 - ry; - - return {xi: xi, xi_prime: xi_prime, yi: yi, yi_prime: yi_prime}; - }; - - /** - * Check if a point is on a line segment. - * - * @param {number} x The X coordinate of the point to check. - * @param {number} y The Y coordinate of the point to check. - * @param {number} x1 The X coordinate of the line start point. - * @param {number} y1 The Y coordinate of the line start point. - * @param {number} x2 The X coordinate of the line end point. - * @param {number} y2 The Y coordinate of the line end point. - * @param {number} epsilon The precision (consider the line thickness). - * @return {boolean} True if point is "close to" the line - * segment, false otherwise. - */ - sigma.utils.isPointOnSegment = function(x, y, x1, y1, x2, y2, epsilon) { - // http://stackoverflow.com/a/328122 - var crossProduct = Math.abs((y - y1) * (x2 - x1) - (x - x1) * (y2 - y1)), - d = sigma.utils.getDistance(x1, y1, x2, y2), - nCrossProduct = crossProduct / d; // normalized cross product - - return (nCrossProduct < epsilon && - Math.min(x1, x2) <= x && x <= Math.max(x1, x2) && - Math.min(y1, y2) <= y && y <= Math.max(y1, y2)); - }; - - /** - * Check if a point is on a quadratic bezier curve segment with a thickness. - * - * @param {number} x The X coordinate of the point to check. - * @param {number} y The Y coordinate of the point to check. - * @param {number} x1 The X coordinate of the curve start point. - * @param {number} y1 The Y coordinate of the curve start point. - * @param {number} x2 The X coordinate of the curve end point. - * @param {number} y2 The Y coordinate of the curve end point. - * @param {number} cpx The X coordinate of the curve control point. - * @param {number} cpy The Y coordinate of the curve control point. - * @param {number} epsilon The precision (consider the line thickness). - * @return {boolean} True if (x,y) is on the curve segment, - * false otherwise. - */ - sigma.utils.isPointOnQuadraticCurve = - function(x, y, x1, y1, x2, y2, cpx, cpy, epsilon) { - // Fails if the point is too far from the extremities of the segment, - // preventing for more costly computation: - var dP1P2 = sigma.utils.getDistance(x1, y1, x2, y2); - if (Math.abs(x - x1) > dP1P2 || Math.abs(y - y1) > dP1P2) { - return false; - } - - var dP1 = sigma.utils.getDistance(x, y, x1, y1), - dP2 = sigma.utils.getDistance(x, y, x2, y2), - t = 0.5, - r = (dP1 < dP2) ? -0.01 : 0.01, - rThreshold = 0.001, - i = 100, - pt = sigma.utils.getPointOnQuadraticCurve(t, x1, y1, x2, y2, cpx, cpy), - dt = sigma.utils.getDistance(x, y, pt.x, pt.y), - old_dt; - - // This algorithm minimizes the distance from the point to the curve. It - // find the optimal t value where t=0 is the start point and t=1 is the end - // point of the curve, starting from t=0.5. - // It terminates because it runs a maximum of i interations. - while (i-- > 0 && - t >= 0 && t <= 1 && - (dt > epsilon) && - (r > rThreshold || r < -rThreshold)) { - old_dt = dt; - pt = sigma.utils.getPointOnQuadraticCurve(t, x1, y1, x2, y2, cpx, cpy); - dt = sigma.utils.getDistance(x, y, pt.x, pt.y); - - if (dt > old_dt) { - // not the right direction: - // halfstep in the opposite direction - r = -r / 2; - t += r; - } - else if (t + r < 0 || t + r > 1) { - // oops, we've gone too far: - // revert with a halfstep - r = r / 2; - dt = old_dt; - } - else { - // progress: - t += r; - } - } - - return dt < epsilon; - }; - - - /** - * Check if a point is on a cubic bezier curve segment with a thickness. - * - * @param {number} x The X coordinate of the point to check. - * @param {number} y The Y coordinate of the point to check. - * @param {number} x1 The X coordinate of the curve start point. - * @param {number} y1 The Y coordinate of the curve start point. - * @param {number} x2 The X coordinate of the curve end point. - * @param {number} y2 The Y coordinate of the curve end point. - * @param {number} cpx1 The X coordinate of the 1st curve control point. - * @param {number} cpy1 The Y coordinate of the 1st curve control point. - * @param {number} cpx2 The X coordinate of the 2nd curve control point. - * @param {number} cpy2 The Y coordinate of the 2nd curve control point. - * @param {number} epsilon The precision (consider the line thickness). - * @return {boolean} True if (x,y) is on the curve segment, - * false otherwise. - */ - sigma.utils.isPointOnBezierCurve = - function(x, y, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2, epsilon) { - // Fails if the point is too far from the extremities of the segment, - // preventing for more costly computation: - var dP1CP1 = sigma.utils.getDistance(x1, y1, cpx1, cpy1); - if (Math.abs(x - x1) > dP1CP1 || Math.abs(y - y1) > dP1CP1) { - return false; - } - - var dP1 = sigma.utils.getDistance(x, y, x1, y1), - dP2 = sigma.utils.getDistance(x, y, x2, y2), - t = 0.5, - r = (dP1 < dP2) ? -0.01 : 0.01, - rThreshold = 0.001, - i = 100, - pt = sigma.utils.getPointOnBezierCurve( - t, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2), - dt = sigma.utils.getDistance(x, y, pt.x, pt.y), - old_dt; - - // This algorithm minimizes the distance from the point to the curve. It - // find the optimal t value where t=0 is the start point and t=1 is the end - // point of the curve, starting from t=0.5. - // It terminates because it runs a maximum of i interations. - while (i-- > 0 && - t >= 0 && t <= 1 && - (dt > epsilon) && - (r > rThreshold || r < -rThreshold)) { - old_dt = dt; - pt = sigma.utils.getPointOnBezierCurve( - t, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2); - dt = sigma.utils.getDistance(x, y, pt.x, pt.y); - - if (dt > old_dt) { - // not the right direction: - // halfstep in the opposite direction - r = -r / 2; - t += r; - } - else if (t + r < 0 || t + r > 1) { - // oops, we've gone too far: - // revert with a halfstep - r = r / 2; - dt = old_dt; - } - else { - // progress: - t += r; - } - } - - return dt < epsilon; - }; - - - /** - * ************ - * EVENTS UTILS: - * ************ - */ - /** - * Here are some useful functions to unify extraction of the information we - * need with mouse events and touch events, from different browsers: - */ - - /** - * Extract the local X position from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The local X value of the mouse. - */ - sigma.utils.getX = function(e) { - return ( - (e.offsetX !== undefined && e.offsetX) || - (e.layerX !== undefined && e.layerX) || - (e.clientX !== undefined && e.clientX) - ); - }; - - /** - * Extract the local Y position from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The local Y value of the mouse. - */ - sigma.utils.getY = function(e) { - return ( - (e.offsetY !== undefined && e.offsetY) || - (e.layerY !== undefined && e.layerY) || - (e.clientY !== undefined && e.clientY) - ); - }; - - /** - * Extract the width from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The width of the event's target. - */ - sigma.utils.getWidth = function(e) { - var w = (!e.target.ownerSVGElement) ? - e.target.width : - e.target.ownerSVGElement.width; - - return ( - (typeof w === 'number' && w) || - (w !== undefined && w.baseVal !== undefined && w.baseVal.value) - ); - }; - - /** - * Extract the height from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The height of the event's target. - */ - sigma.utils.getHeight = function(e) { - var h = (!e.target.ownerSVGElement) ? - e.target.height : - e.target.ownerSVGElement.height; - - return ( - (typeof h === 'number' && h) || - (h !== undefined && h.baseVal !== undefined && h.baseVal.value) - ); - }; - - /** - * Extract the wheel delta from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The wheel delta of the mouse. - */ - sigma.utils.getDelta = function(e) { - return ( - (e.wheelDelta !== undefined && e.wheelDelta) || - (e.detail !== undefined && -e.detail) - ); - }; - - /** - * Returns the offset of a DOM element. - * - * @param {DOMElement} dom The element to retrieve the position. - * @return {object} The offset of the DOM element (top, left). - */ - sigma.utils.getOffset = function(dom) { - var left = 0, - top = 0; - - while (dom) { - top = top + parseInt(dom.offsetTop); - left = left + parseInt(dom.offsetLeft); - dom = dom.offsetParent; - } - - return { - top: top, - left: left - }; - }; - - /** - * Simulates a "double click" event. - * - * @param {HTMLElement} target The event target. - * @param {string} type The event type. - * @param {function} callback The callback to execute. - */ - sigma.utils.doubleClick = function(target, type, callback) { - var clicks = 0, - self = this, - handlers; - - target._doubleClickHandler = target._doubleClickHandler || {}; - target._doubleClickHandler[type] = target._doubleClickHandler[type] || []; - handlers = target._doubleClickHandler[type]; - - handlers.push(function(e) { - clicks++; - - if (clicks === 2) { - clicks = 0; - return callback(e); - } else if (clicks === 1) { - setTimeout(function() { - clicks = 0; - }, sigma.settings.doubleClickTimeout); - } - }); - - target.addEventListener(type, handlers[handlers.length - 1], false); - }; - - /** - * Unbind simulated "double click" events. - * - * @param {HTMLElement} target The event target. - * @param {string} type The event type. - */ - sigma.utils.unbindDoubleClick = function(target, type) { - var handler, - handlers = (target._doubleClickHandler || {})[type] || []; - - while ((handler = handlers.pop())) { - target.removeEventListener(type, handler); - } - - delete (target._doubleClickHandler || {})[type]; - }; - - - - - /** - * Here are just some of the most basic easing functions, used for the - * animated camera "goTo" calls. - * - * If you need some more easings functions, don't hesitate to add them to - * sigma.utils.easings. But I will not add some more here or merge PRs - * containing, because I do not want sigma sources full of overkill and never - * used stuff... - */ - sigma.utils.easings = sigma.utils.easings || {}; - sigma.utils.easings.linearNone = function(k) { - return k; - }; - sigma.utils.easings.quadraticIn = function(k) { - return k * k; - }; - sigma.utils.easings.quadraticOut = function(k) { - return k * (2 - k); - }; - sigma.utils.easings.quadraticInOut = function(k) { - if ((k *= 2) < 1) - return 0.5 * k * k; - return - 0.5 * (--k * (k - 2) - 1); - }; - sigma.utils.easings.cubicIn = function(k) { - return k * k * k; - }; - sigma.utils.easings.cubicOut = function(k) { - return --k * k * k + 1; - }; - sigma.utils.easings.cubicInOut = function(k) { - if ((k *= 2) < 1) - return 0.5 * k * k * k; - return 0.5 * ((k -= 2) * k * k + 2); - }; - - - - - /** - * ************ - * WEBGL UTILS: - * ************ - */ - /** - * Loads a WebGL shader and returns it. - * - * @param {WebGLContext} gl The WebGLContext to use. - * @param {string} shaderSource The shader source. - * @param {number} shaderType The type of shader. - * @param {function(string): void} error Callback for errors. - * @return {WebGLShader} The created shader. - */ - sigma.utils.loadShader = function(gl, shaderSource, shaderType, error) { - var compiled, - shader = gl.createShader(shaderType); - - // Load the shader source - gl.shaderSource(shader, shaderSource); - - // Compile the shader - gl.compileShader(shader); - - // Check the compile status - compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); - - // If something went wrong: - if (!compiled) { - if (error) { - error( - 'Error compiling shader "' + shader + '":' + - gl.getShaderInfoLog(shader) - ); - } - - gl.deleteShader(shader); - return null; - } - - return shader; - }; - - /** - * Creates a program, attaches shaders, binds attrib locations, links the - * program and calls useProgram. - * - * @param {Array.} shaders The shaders to attach. - * @param {Array.} attribs The attribs names. - * @param {Array.} locations The locations for the attribs. - * @param {function(string): void} error Callback for errors. - * @return {WebGLProgram} The created program. - */ - sigma.utils.loadProgram = function(gl, shaders, attribs, loc, error) { - var i, - linked, - program = gl.createProgram(); - - for (i = 0; i < shaders.length; ++i) - gl.attachShader(program, shaders[i]); - - if (attribs) - for (i = 0; i < attribs.length; ++i) - gl.bindAttribLocation( - program, - locations ? locations[i] : i, - opt_attribs[i] - ); - - gl.linkProgram(program); - - // Check the link status - linked = gl.getProgramParameter(program, gl.LINK_STATUS); - if (!linked) { - if (error) - error('Error in program linking: ' + gl.getProgramInfoLog(program)); - - gl.deleteProgram(program); - return null; - } - - return program; - }; - - - - - /** - * ********* - * MATRICES: - * ********* - * The following utils are just here to help generating the transformation - * matrices for the WebGL renderers. - */ - sigma.utils.pkg('sigma.utils.matrices'); - - /** - * The returns a 3x3 translation matrix. - * - * @param {number} dx The X translation. - * @param {number} dy The Y translation. - * @return {array} Returns the matrix. - */ - sigma.utils.matrices.translation = function(dx, dy) { - return [ - 1, 0, 0, - 0, 1, 0, - dx, dy, 1 - ]; - }; - - /** - * The returns a 3x3 or 2x2 rotation matrix. - * - * @param {number} angle The rotation angle. - * @param {boolean} m2 If true, the function will return a 2x2 matrix. - * @return {array} Returns the matrix. - */ - sigma.utils.matrices.rotation = function(angle, m2) { - var cos = Math.cos(angle), - sin = Math.sin(angle); - - return m2 ? [ - cos, -sin, - sin, cos - ] : [ - cos, -sin, 0, - sin, cos, 0, - 0, 0, 1 - ]; - }; - - /** - * The returns a 3x3 or 2x2 homothetic transformation matrix. - * - * @param {number} ratio The scaling ratio. - * @param {boolean} m2 If true, the function will return a 2x2 matrix. - * @return {array} Returns the matrix. - */ - sigma.utils.matrices.scale = function(ratio, m2) { - return m2 ? [ - ratio, 0, - 0, ratio - ] : [ - ratio, 0, 0, - 0, ratio, 0, - 0, 0, 1 - ]; - }; - - /** - * The returns a 3x3 or 2x2 homothetic transformation matrix. - * - * @param {array} a The first matrix. - * @param {array} b The second matrix. - * @param {boolean} m2 If true, the function will assume both matrices are - * 2x2. - * @return {array} Returns the matrix. - */ - sigma.utils.matrices.multiply = function(a, b, m2) { - var l = m2 ? 2 : 3, - a00 = a[0 * l + 0], - a01 = a[0 * l + 1], - a02 = a[0 * l + 2], - a10 = a[1 * l + 0], - a11 = a[1 * l + 1], - a12 = a[1 * l + 2], - a20 = a[2 * l + 0], - a21 = a[2 * l + 1], - a22 = a[2 * l + 2], - b00 = b[0 * l + 0], - b01 = b[0 * l + 1], - b02 = b[0 * l + 2], - b10 = b[1 * l + 0], - b11 = b[1 * l + 1], - b12 = b[1 * l + 2], - b20 = b[2 * l + 0], - b21 = b[2 * l + 1], - b22 = b[2 * l + 2]; - - return m2 ? [ - a00 * b00 + a01 * b10, - a00 * b01 + a01 * b11, - a10 * b00 + a11 * b10, - a10 * b01 + a11 * b11 - ] : [ - a00 * b00 + a01 * b10 + a02 * b20, - a00 * b01 + a01 * b11 + a02 * b21, - a00 * b02 + a01 * b12 + a02 * b22, - a10 * b00 + a11 * b10 + a12 * b20, - a10 * b01 + a11 * b11 + a12 * b21, - a10 * b02 + a11 * b12 + a12 * b22, - a20 * b00 + a21 * b10 + a22 * b20, - a20 * b01 + a21 * b11 + a22 * b21, - a20 * b02 + a21 * b12 + a22 * b22 - ]; - }; -}).call(this); - -;(function(global) { - 'use strict'; - - /** - * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - * http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating - * requestAnimationFrame polyfill by Erik Möller. - * fixes from Paul Irish and Tino Zijdel - * MIT license - */ - var x, - lastTime = 0, - vendors = ['ms', 'moz', 'webkit', 'o']; - - for (x = 0; x < vendors.length && !global.requestAnimationFrame; x++) { - global.requestAnimationFrame = - global[vendors[x] + 'RequestAnimationFrame']; - global.cancelAnimationFrame = - global[vendors[x] + 'CancelAnimationFrame'] || - global[vendors[x] + 'CancelRequestAnimationFrame']; - } - - if (!global.requestAnimationFrame) - global.requestAnimationFrame = function(callback, element) { - var currTime = new Date().getTime(), - timeToCall = Math.max(0, 16 - (currTime - lastTime)), - id = global.setTimeout( - function() { - callback(currTime + timeToCall); - }, - timeToCall - ); - - lastTime = currTime + timeToCall; - return id; - }; - - if (!global.cancelAnimationFrame) - global.cancelAnimationFrame = function(id) { - clearTimeout(id); - }; - - /** - * Function.prototype.bind polyfill found on MDN. - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility - * Public domain - */ - if (!Function.prototype.bind) - Function.prototype.bind = function(oThis) { - if (typeof this !== 'function') - // Closest thing possible to the ECMAScript 5 internal IsCallable - // function: - throw new TypeError( - 'Function.prototype.bind - what is trying to be bound is not callable' - ); - - var aArgs = Array.prototype.slice.call(arguments, 1), - fToBind = this, - fNOP, - fBound; - - fNOP = function() {}; - fBound = function() { - return fToBind.apply( - this instanceof fNOP && oThis ? - this : - oThis, - aArgs.concat(Array.prototype.slice.call(arguments)) - ); - }; - - fNOP.prototype = this.prototype; - fBound.prototype = new fNOP(); - - return fBound; - }; -})(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Packages initialization: - sigma.utils.pkg('sigma.settings'); - - var settings = { - /** - * GRAPH SETTINGS: - * *************** - */ - // {boolean} Indicates if the data have to be cloned in methods to add - // nodes or edges. - clone: true, - // {boolean} Indicates if nodes "id" values and edges "id", "source" and - // "target" values must be set as immutable. - immutable: true, - // {boolean} Indicates if sigma can log its errors and warnings. - verbose: false, - - - /** - * RENDERERS SETTINGS: - * ******************* - */ - // {string} - classPrefix: 'sigma', - // {string} - defaultNodeType: 'def', - // {string} - defaultEdgeType: 'def', - // {string} - defaultLabelColor: '#000', - // {string} - defaultEdgeColor: '#000', - // {string} - defaultNodeColor: '#000', - // {string} - defaultLabelSize: 14, - // {string} Indicates how to choose the edges color. Available values: - // "source", "target", "default" - edgeColor: 'source', - // {number} Defines the minimal edge's arrow display size. - minArrowSize: 0, - // {string} - font: 'arial', - // {string} Example: 'bold' - fontStyle: '', - // {string} Indicates how to choose the labels color. Available values: - // "node", "default" - labelColor: 'default', - // {string} Indicates how to choose the labels size. Available values: - // "fixed", "proportional" - labelSize: 'fixed', - // {string} The ratio between the font size of the label and the node size. - labelSizeRatio: 1, - // {number} The minimum size a node must have to see its label displayed. - labelThreshold: 8, - // {number} The oversampling factor used in WebGL renderer. - webglOversamplingRatio: 2, - // {number} The size of the border of hovered nodes. - borderSize: 0, - // {number} The default hovered node border's color. - defaultNodeBorderColor: '#000', - // {number} The hovered node's label font. If not specified, will heritate - // the "font" value. - hoverFont: '', - // {boolean} If true, then only one node can be hovered at a time. - singleHover: false, - // {string} Example: 'bold' - hoverFontStyle: '', - // {string} Indicates how to choose the hovered nodes shadow color. - // Available values: "node", "default" - labelHoverShadow: 'default', - // {string} - labelHoverShadowColor: '#000', - // {string} Indicates how to choose the hovered nodes color. - // Available values: "node", "default" - nodeHoverColor: 'node', - // {string} - defaultNodeHoverColor: '#000', - // {string} Indicates how to choose the hovered nodes background color. - // Available values: "node", "default" - labelHoverBGColor: 'default', - // {string} - defaultHoverLabelBGColor: '#fff', - // {string} Indicates how to choose the hovered labels color. - // Available values: "node", "default" - labelHoverColor: 'default', - // {string} - defaultLabelHoverColor: '#000', - // {string} Indicates how to choose the edges hover color. Available values: - // "edge", "default" - edgeHoverColor: 'edge', - // {number} The size multiplicator of hovered edges. - edgeHoverSizeRatio: 1, - // {string} - defaultEdgeHoverColor: '#000', - // {boolean} Indicates if the edge extremities must be hovered when the - // edge is hovered. - edgeHoverExtremities: false, - // {booleans} The different drawing modes: - // false: Layered not displayed. - // true: Layered displayed. - drawEdges: true, - drawNodes: true, - drawLabels: true, - drawEdgeLabels: false, - // {boolean} Indicates if the edges must be drawn in several frames or in - // one frame, as the nodes and labels are drawn. - batchEdgesDrawing: false, - // {boolean} Indicates if the edges must be hidden during dragging and - // animations. - hideEdgesOnMove: false, - // {numbers} The different batch sizes, when elements are displayed in - // several frames. - canvasEdgesBatchSize: 500, - webglEdgesBatchSize: 1000, - - - - - /** - * RESCALE SETTINGS: - * ***************** - */ - // {string} Indicates of to scale the graph relatively to its container. - // Available values: "inside", "outside" - scalingMode: 'inside', - // {number} The margin to keep around the graph. - sideMargin: 0, - // {number} Determine the size of the smallest and the biggest node / edges - // on the screen. This mapping makes easier to display the graph, - // avoiding too big nodes that take half of the screen, or too - // small ones that are not readable. If the two parameters are - // equals, then the minimal display size will be 0. And if they - // are both equal to 0, then there is no mapping, and the radius - // of the nodes will be their size. - minEdgeSize: 0.5, - maxEdgeSize: 1, - minNodeSize: 1, - maxNodeSize: 8, - - - - - /** - * CAPTORS SETTINGS: - * ***************** - */ - // {boolean} - touchEnabled: true, - // {boolean} - mouseEnabled: true, - // {boolean} - mouseWheelEnabled: true, - // {boolean} - doubleClickEnabled: true, - // {boolean} Defines whether the custom events such as "clickNode" can be - // used. - eventsEnabled: true, - // {number} Defines by how much multiplicating the zooming level when the - // user zooms with the mouse-wheel. - zoomingRatio: 1.7, - // {number} Defines by how much multiplicating the zooming level when the - // user zooms by double clicking. - doubleClickZoomingRatio: 2.2, - // {number} The minimum zooming level. - zoomMin: 0.0625, - // {number} The maximum zooming level. - zoomMax: 2, - // {number} The duration of animations following a mouse scrolling. - mouseZoomDuration: 200, - // {number} The duration of animations following a mouse double click. - doubleClickZoomDuration: 200, - // {number} The duration of animations following a mouse dropping. - mouseInertiaDuration: 200, - // {number} The inertia power (mouse captor). - mouseInertiaRatio: 3, - // {number} The duration of animations following a touch dropping. - touchInertiaDuration: 200, - // {number} The inertia power (touch captor). - touchInertiaRatio: 3, - // {number} The maximum time between two clicks to make it a double click. - doubleClickTimeout: 300, - // {number} The maximum time between two taps to make it a double tap. - doubleTapTimeout: 300, - // {number} The maximum time of dragging to trigger intertia. - dragTimeout: 200, - - - - - /** - * GLOBAL SETTINGS: - * **************** - */ - // {boolean} Determines whether the instance has to refresh itself - // automatically when a "resize" event is dispatched from the - // window object. - autoResize: true, - // {boolean} Determines whether the "rescale" middleware has to be called - // automatically for each camera on refresh. - autoRescale: true, - // {boolean} If set to false, the camera method "goTo" will basically do - // nothing. - enableCamera: true, - // {boolean} If set to false, the nodes cannot be hovered. - enableHovering: true, - // {boolean} If set to true, the edges can be hovered. - enableEdgeHovering: false, - // {number} The size of the area around the edges to activate hovering. - edgeHoverPrecision: 5, - // {boolean} If set to true, the rescale middleware will ignore node sizes - // to determine the graphs boundings. - rescaleIgnoreSize: false, - // {boolean} Determines if the core has to try to catch errors on - // rendering. - skipErrors: false, - - - - - /** - * CAMERA SETTINGS: - * **************** - */ - // {number} The power degrees applied to the nodes/edges size relatively to - // the zooming level. Basically: - // > onScreenR = Math.pow(zoom, nodesPowRatio) * R - // > onScreenT = Math.pow(zoom, edgesPowRatio) * T - nodesPowRatio: 0.5, - edgesPowRatio: 0.5, - - - - - /** - * ANIMATIONS SETTINGS: - * ******************** - */ - // {number} The default animation time. - animationsTime: 200 - }; - - // Export the previously designed settings: - sigma.settings = sigma.utils.extend(sigma.settings || {}, settings); -}).call(this); - -;(function() { - 'use strict'; - - /** - * Dispatcher constructor. - * - * @return {dispatcher} The new dispatcher instance. - */ - var dispatcher = function() { - Object.defineProperty(this, '_handlers', { - value: {} - }); - }; - - - - - /** - * Will execute the handler everytime that the indicated event (or the - * indicated events) will be triggered. - * - * @param {string} events The name of the event (or the events - * separated by spaces). - * @param {function(Object)} handler The handler to bind. - * @return {dispatcher} Returns the instance itself. - */ - dispatcher.prototype.bind = function(events, handler) { - var i, - l, - event, - eArray; - - if ( - arguments.length === 1 && - typeof arguments[0] === 'object' - ) - for (events in arguments[0]) - this.bind(events, arguments[0][events]); - else if ( - arguments.length === 2 && - typeof arguments[1] === 'function' - ) { - eArray = typeof events === 'string' ? events.split(' ') : events; - - for (i = 0, l = eArray.length; i !== l; i += 1) { - event = eArray[i]; - - // Check that event is not '': - if (!event) - continue; - - if (!this._handlers[event]) - this._handlers[event] = []; - - // Using an object instead of directly the handler will make possible - // later to add flags - this._handlers[event].push({ - handler: handler - }); - } - } else - throw 'bind: Wrong arguments.'; - - return this; - }; - - /** - * Removes the handler from a specified event (or specified events). - * - * @param {?string} events The name of the event (or the events - * separated by spaces). If undefined, - * then all handlers are removed. - * @param {?function(object)} handler The handler to unbind. If undefined, - * each handler bound to the event or the - * events will be removed. - * @return {dispatcher} Returns the instance itself. - */ - dispatcher.prototype.unbind = function(events, handler) { - var i, - n, - j, - m, - k, - a, - event, - eArray = typeof events === 'string' ? events.split(' ') : events; - - if (!arguments.length) { - for (k in this._handlers) - delete this._handlers[k]; - return this; - } - - if (handler) { - for (i = 0, n = eArray.length; i !== n; i += 1) { - event = eArray[i]; - if (this._handlers[event]) { - a = []; - for (j = 0, m = this._handlers[event].length; j !== m; j += 1) - if (this._handlers[event][j].handler !== handler) - a.push(this._handlers[event][j]); - - this._handlers[event] = a; - } - - if (this._handlers[event] && this._handlers[event].length === 0) - delete this._handlers[event]; - } - } else - for (i = 0, n = eArray.length; i !== n; i += 1) - delete this._handlers[eArray[i]]; - - return this; - }; - - /** - * Executes each handler bound to the event - * - * @param {string} events The name of the event (or the events separated - * by spaces). - * @param {?object} data The content of the event (optional). - * @return {dispatcher} Returns the instance itself. - */ - dispatcher.prototype.dispatchEvent = function(events, data) { - var i, - n, - j, - m, - a, - event, - eventName, - self = this, - eArray = typeof events === 'string' ? events.split(' ') : events; - - data = data === undefined ? {} : data; - - for (i = 0, n = eArray.length; i !== n; i += 1) { - eventName = eArray[i]; - - if (this._handlers[eventName]) { - event = self.getEvent(eventName, data); - a = []; - - for (j = 0, m = this._handlers[eventName].length; j !== m; j += 1) { - this._handlers[eventName][j].handler(event); - if (!this._handlers[eventName][j].one) - a.push(this._handlers[eventName][j]); - } - - this._handlers[eventName] = a; - } - } - - return this; - }; - - /** - * Return an event object. - * - * @param {string} events The name of the event. - * @param {?object} data The content of the event (optional). - * @return {object} Returns the instance itself. - */ - dispatcher.prototype.getEvent = function(event, data) { - return { - type: event, - data: data || {}, - target: this - }; - }; - - /** - * A useful function to deal with inheritance. It will make the target - * inherit the prototype of the class dispatcher as well as its constructor. - * - * @param {object} target The target. - */ - dispatcher.extend = function(target, args) { - var k; - - for (k in dispatcher.prototype) - if (dispatcher.prototype.hasOwnProperty(k)) - target[k] = dispatcher.prototype[k]; - - dispatcher.apply(target, args); - }; - - - - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') { - this.sigma.classes = this.sigma.classes || {}; - this.sigma.classes.dispatcher = dispatcher; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = dispatcher; - exports.dispatcher = dispatcher; - } else - this.dispatcher = dispatcher; -}).call(this); - -;(function() { - 'use strict'; - - /** - * This utils aims to facilitate the manipulation of each instance setting. - * Using a function instead of an object brings two main advantages: First, - * it will be easier in the future to catch settings updates through a - * function than an object. Second, giving it a full object will "merge" it - * to the settings object properly, keeping us to have to always add a loop. - * - * @return {configurable} The "settings" function. - */ - var configurable = function() { - var i, - l, - data = {}, - datas = Array.prototype.slice.call(arguments, 0); - - /** - * The method to use to set or get any property of this instance. - * - * @param {string|object} a1 If it is a string and if a2 is undefined, - * then it will return the corresponding - * property. If it is a string and if a2 is - * set, then it will set a2 as the property - * corresponding to a1, and return this. If - * it is an object, then each pair string + - * object(or any other type) will be set as a - * property. - * @param {*?} a2 The new property corresponding to a1 if a1 - * is a string. - * @return {*|configurable} Returns itself or the corresponding - * property. - * - * Polymorphism: - * ************* - * Here are some basic use examples: - * - * > settings = new configurable(); - * > settings('mySetting', 42); - * > settings('mySetting'); // Logs: 42 - * > settings('mySetting', 123); - * > settings('mySetting'); // Logs: 123 - * > settings({mySetting: 456}); - * > settings('mySetting'); // Logs: 456 - * - * Also, it is possible to use the function as a fallback: - * > settings({mySetting: 'abc'}, 'mySetting'); // Logs: 'abc' - * > settings({hisSetting: 'abc'}, 'mySetting'); // Logs: 456 - */ - var settings = function(a1, a2) { - var o, - k; - - if (arguments.length === 1 && typeof a1 === 'string') { - if ((a1 in data) && data[a1] !== undefined) - return data[a1]; - for (i = 0, l = datas.length; i < l; i++) - if ((a1 in datas[i]) && datas[i][a1] !== undefined) - return datas[i][a1]; - return undefined; - } else if (typeof a1 === 'object' && typeof a2 === 'string') { - return a2 in (a1 || {}) ? a1[a2] : settings(a2); - } else { - o = (typeof a1 === 'object' && a2 === undefined) ? a1 : {}; - - if (typeof a1 === 'string') - o[a1] = a2; - - for (k in o) - data[k] = o[k]; - - return this; - } - }; - - /** - * This method returns a new configurable function, with new objects - * - * @param {object*} Any number of objects to search in. - * @return {function} Returns the function. Check its documentation to know - * more about how it works. - */ - settings.embedObjects = function() { - var args = datas.concat( - data - ).concat( - Array.prototype.splice.call(arguments, 0) - ); - - return configurable.apply({}, args); - }; - - // Initialize - for (i = 0, l = arguments.length; i < l; i++) - settings(arguments[i]); - - return settings; - }; - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') { - this.sigma.classes = this.sigma.classes || {}; - this.sigma.classes.configurable = configurable; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = configurable; - exports.configurable = configurable; - } else - this.configurable = configurable; -}).call(this); - -;(function(undefined) { - 'use strict'; - - var _methods = Object.create(null), - _indexes = Object.create(null), - _initBindings = Object.create(null), - _methodBindings = Object.create(null), - _methodBeforeBindings = Object.create(null), - _defaultSettings = { - immutable: true, - clone: true - }, - _defaultSettingsFunction = function(key) { - return _defaultSettings[key]; - }; - - /** - * The graph constructor. It initializes the data and the indexes, and binds - * the custom indexes and methods to its own scope. - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters in the settings - * object: - * - * {boolean} clone Indicates if the data have to be cloned in methods - * to add nodes or edges. - * {boolean} immutable Indicates if nodes "id" values and edges "id", - * "source" and "target" values must be set as - * immutable. - * - * @param {?configurable} settings Eventually a settings function. - * @return {graph} The new graph instance. - */ - var graph = function(settings) { - var k, - fn, - data; - - /** - * DATA: - * ***** - * Every data that is callable from graph methods are stored in this "data" - * object. This object will be served as context for all these methods, - * and it is possible to add other type of data in it. - */ - data = { - /** - * SETTINGS FUNCTION: - * ****************** - */ - settings: settings || _defaultSettingsFunction, - - /** - * MAIN DATA: - * ********** - */ - nodesArray: [], - edgesArray: [], - - /** - * GLOBAL INDEXES: - * *************** - * These indexes just index data by ids. - */ - nodesIndex: Object.create(null), - edgesIndex: Object.create(null), - - /** - * LOCAL INDEXES: - * ************** - * These indexes refer from node to nodes. Each key is an id, and each - * value is the array of the ids of related nodes. - */ - inNeighborsCount: Object.create(null), - outNeighborsCount: Object.create(null), - allNeighborsCount: Object.create(null) - }; - - // Execute bindings: - for (k in _initBindings) - _initBindings[k].call(data); - - // Add methods to both the scope and the data objects: - for (k in _methods) { - fn = __bindGraphMethod(k, data, _methods[k]); - this[k] = fn; - data[k] = fn; - } - }; - - - - - /** - * A custom tool to bind methods such that function that are bound to it will - * be executed anytime the method is called. - * - * @param {string} methodName The name of the method to bind. - * @param {object} scope The scope where the method must be executed. - * @param {function} fn The method itself. - * @return {function} The new method. - */ - function __bindGraphMethod(methodName, scope, fn) { - var result = function() { - var k, - res; - - // Execute "before" bound functions: - for (k in _methodBeforeBindings[methodName]) - _methodBeforeBindings[methodName][k].apply(scope, arguments); - - // Apply the method: - res = fn.apply(scope, arguments); - - // Execute bound functions: - for (k in _methodBindings[methodName]) - _methodBindings[methodName][k].apply(scope, arguments); - - // Return res: - return res; - }; - - return result; - } - - /** - * This custom tool function removes every pair key/value from an hash. The - * goal is to avoid creating a new object while some other references are - * still hanging in some scopes... - * - * @param {object} obj The object to empty. - * @return {object} The empty object. - */ - sigma.utils.emptyObject = function(obj) { - var k; - - for (k in obj) - if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k)) - delete obj[k]; - - return obj; - }; - - - - - /** - * This global method adds a method that will be bound to the futurly created - * graph instances. - * - * Since these methods will be bound to their scope when the instances are - * created, it does not use the prototype. Because of that, methods have to - * be added before instances are created to make them available. - * - * Here is an example: - * - * > graph.addMethod('getNodesCount', function() { - * > return this.nodesArray.length; - * > }); - * > - * > var myGraph = new graph(); - * > console.log(myGraph.getNodesCount()); // outputs 0 - * - * @param {string} methodName The name of the method. - * @param {function} fn The method itself. - * @return {object} The global graph constructor. - */ - graph.addMethod = function(methodName, fn) { - if ( - typeof methodName !== 'string' || - typeof fn !== 'function' || - arguments.length !== 2 - ) - throw 'addMethod: Wrong arguments.'; - - if (_methods[methodName] || graph[methodName]) - throw 'The method "' + methodName + '" already exists.'; - - _methods[methodName] = fn; - _methodBindings[methodName] = Object.create(null); - _methodBeforeBindings[methodName] = Object.create(null); - - return this; - }; - - /** - * This global method returns true if the method has already been added, and - * false else. - * - * Here are some examples: - * - * > graph.hasMethod('addNode'); // returns true - * > graph.hasMethod('hasMethod'); // returns true - * > graph.hasMethod('unexistingMethod'); // returns false - * - * @param {string} methodName The name of the method. - * @return {boolean} The result. - */ - graph.hasMethod = function(methodName) { - return !!(_methods[methodName] || graph[methodName]); - }; - - /** - * This global methods attaches a function to a method. Anytime the specified - * method is called, the attached function is called right after, with the - * same arguments and in the same scope. The attached function is called - * right before if the last argument is true, unless the method is the graph - * constructor. - * - * To attach a function to the graph constructor, use 'constructor' as the - * method name (first argument). - * - * The main idea is to have a clean way to keep custom indexes up to date, - * for instance: - * - * > var timesAddNodeCalled = 0; - * > graph.attach('addNode', 'timesAddNodeCalledInc', function() { - * > timesAddNodeCalled++; - * > }); - * > - * > var myGraph = new graph(); - * > console.log(timesAddNodeCalled); // outputs 0 - * > - * > myGraph.addNode({ id: '1' }).addNode({ id: '2' }); - * > console.log(timesAddNodeCalled); // outputs 2 - * - * The idea for calling a function before is to provide pre-processors, for - * instance: - * - * > var colorPalette = { Person: '#C3CBE1', Place: '#9BDEBD' }; - * > graph.attach('addNode', 'applyNodeColorPalette', function(n) { - * > n.color = colorPalette[n.category]; - * > }, true); - * > - * > var myGraph = new graph(); - * > myGraph.addNode({ id: 'n0', category: 'Person' }); - * > console.log(myGraph.nodes('n0').color); // outputs '#C3CBE1' - * - * @param {string} methodName The name of the related method or - * "constructor". - * @param {string} key The key to identify the function to attach. - * @param {function} fn The function to bind. - * @param {boolean} before If true the function is called right before. - * @return {object} The global graph constructor. - */ - graph.attach = function(methodName, key, fn, before) { - if ( - typeof methodName !== 'string' || - typeof key !== 'string' || - typeof fn !== 'function' || - arguments.length < 3 || - arguments.length > 4 - ) - throw 'attach: Wrong arguments.'; - - var bindings; - - if (methodName === 'constructor') - bindings = _initBindings; - else { - if (before) { - if (!_methodBeforeBindings[methodName]) - throw 'The method "' + methodName + '" does not exist.'; - - bindings = _methodBeforeBindings[methodName]; - } - else { - if (!_methodBindings[methodName]) - throw 'The method "' + methodName + '" does not exist.'; - - bindings = _methodBindings[methodName]; - } - } - - if (bindings[key]) - throw 'A function "' + key + '" is already attached ' + - 'to the method "' + methodName + '".'; - - bindings[key] = fn; - - return this; - }; - - /** - * Alias of attach(methodName, key, fn, true). - */ - graph.attachBefore = function(methodName, key, fn) { - return this.attach(methodName, key, fn, true); - }; - - /** - * This methods is just an helper to deal with custom indexes. It takes as - * arguments the name of the index and an object containing all the different - * functions to bind to the methods. - * - * Here is a basic example, that creates an index to keep the number of nodes - * in the current graph. It also adds a method to provide a getter on that - * new index: - * - * > sigma.classes.graph.addIndex('nodesCount', { - * > constructor: function() { - * > this.nodesCount = 0; - * > }, - * > addNode: function() { - * > this.nodesCount++; - * > }, - * > dropNode: function() { - * > this.nodesCount--; - * > } - * > }); - * > - * > sigma.classes.graph.addMethod('getNodesCount', function() { - * > return this.nodesCount; - * > }); - * > - * > var myGraph = new sigma.classes.graph(); - * > console.log(myGraph.getNodesCount()); // outputs 0 - * > - * > myGraph.addNode({ id: '1' }).addNode({ id: '2' }); - * > console.log(myGraph.getNodesCount()); // outputs 2 - * - * @param {string} name The name of the index. - * @param {object} bindings The object containing the functions to bind. - * @return {object} The global graph constructor. - */ - graph.addIndex = function(name, bindings) { - if ( - typeof name !== 'string' || - Object(bindings) !== bindings || - arguments.length !== 2 - ) - throw 'addIndex: Wrong arguments.'; - - if (_indexes[name]) - throw 'The index "' + name + '" already exists.'; - - var k; - - // Store the bindings: - _indexes[name] = bindings; - - // Attach the bindings: - for (k in bindings) - if (typeof bindings[k] === 'object') - graph.attach(k, name, bindings[k].fn, bindings[k].before); - else if (typeof bindings[k] === 'function') - graph.attach(k, name, bindings[k]); - else - throw 'The bindings must be functions.'; - - return this; - }; - - - - - /** - * This method adds a node to the graph. The node must be an object, with a - * string under the key "id". Except for this, it is possible to add any - * other attribute, that will be preserved all along the manipulations. - * - * If the graph option "clone" has a truthy value, the node will be cloned - * when added to the graph. Also, if the graph option "immutable" has a - * truthy value, its id will be defined as immutable. - * - * @param {object} node The node to add. - * @return {object} The graph instance. - */ - graph.addMethod('addNode', function(node) { - // Check that the node is an object and has an id: - if (Object(node) !== node || arguments.length !== 1) - throw 'addNode: Wrong arguments.'; - - if (typeof node.id !== 'string' && typeof node.id !== 'number') - throw 'The node must have a string or number id.'; - - if (this.nodesIndex[node.id]) - throw 'The node "' + node.id + '" already exists.'; - - var k, - id = node.id, - validNode = Object.create(null); - - // Check the "clone" option: - if (this.settings('clone')) { - for (k in node) - if (k !== 'id') - validNode[k] = node[k]; - } else - validNode = node; - - // Check the "immutable" option: - if (this.settings('immutable')) - Object.defineProperty(validNode, 'id', { - value: id, - enumerable: true - }); - else - validNode.id = id; - - // Reinitialize counts: - this.inNeighborsCount[id] = 0; - this.outNeighborsCount[id] = 0; - this.allNeighborsCount[id] = 0; - - // Add the node to indexes: - this.nodesArray.push(validNode); - this.nodesIndex[validNode.id] = validNode; - - // Return the current instance: - return this; - }); - - /** - * This method adds an edge to the graph. The edge must be an object, with a - * string under the key "id", and strings under the keys "source" and - * "target" that design existing nodes. Except for this, it is possible to - * add any other attribute, that will be preserved all along the - * manipulations. - * - * If the graph option "clone" has a truthy value, the edge will be cloned - * when added to the graph. Also, if the graph option "immutable" has a - * truthy value, its id, source and target will be defined as immutable. - * - * @param {object} edge The edge to add. - * @return {object} The graph instance. - */ - graph.addMethod('addEdge', function(edge) { - // Check that the edge is an object and has an id: - if (Object(edge) !== edge || arguments.length !== 1) - throw 'addEdge: Wrong arguments.'; - - if (typeof edge.id !== 'string' && typeof edge.id !== 'number') - throw 'The edge must have a string or number id.'; - - if ((typeof edge.source !== 'string' && typeof edge.source !== 'number') || - !this.nodesIndex[edge.source]) - throw 'The edge source must have an existing node id.'; - - if ((typeof edge.target !== 'string' && typeof edge.target !== 'number') || - !this.nodesIndex[edge.target]) - throw 'The edge target must have an existing node id.'; - - if (this.edgesIndex[edge.id]) - throw 'The edge "' + edge.id + '" already exists.'; - - var k, - validEdge = Object.create(null); - - // Check the "clone" option: - if (this.settings('clone')) { - for (k in edge) - if (k !== 'id' && k !== 'source' && k !== 'target') - validEdge[k] = edge[k]; - } else - validEdge = edge; - - // Check the "immutable" option: - if (this.settings('immutable')) { - Object.defineProperty(validEdge, 'id', { - value: edge.id, - enumerable: true - }); - - Object.defineProperty(validEdge, 'source', { - value: edge.source, - enumerable: true - }); - - Object.defineProperty(validEdge, 'target', { - value: edge.target, - enumerable: true - }); - } else { - validEdge.id = edge.id; - validEdge.source = edge.source; - validEdge.target = edge.target; - } - - // Add the edge to indexes: - this.edgesArray.push(validEdge); - this.edgesIndex[validEdge.id] = validEdge; - - // Keep counts up to date: - this.inNeighborsCount[validEdge.target]++; - this.outNeighborsCount[validEdge.source]++; - this.allNeighborsCount[validEdge.target]++; - this.allNeighborsCount[validEdge.source]++; - - return this; - }); - - /** - * This method drops a node from the graph. It also removes each edge that is - * bound to it, through the dropEdge method. An error is thrown if the node - * does not exist. - * - * @param {string} id The node id. - * @return {object} The graph instance. - */ - graph.addMethod('dropNode', function(id) { - // Check that the arguments are valid: - if ((typeof id !== 'string' && typeof id !== 'number') || - arguments.length !== 1) - throw 'dropNode: Wrong arguments.'; - - if (!this.nodesIndex[id]) - throw 'The node "' + id + '" does not exist.'; - - var i, k, l; - - // Remove the node from indexes: - delete this.nodesIndex[id]; - for (i = 0, l = this.nodesArray.length; i < l; i++) - if (this.nodesArray[i].id === id) { - this.nodesArray.splice(i, 1); - break; - } - - // Remove related edges: - for (i = this.edgesArray.length - 1; i >= 0; i--) - if (this.edgesArray[i].source === id || this.edgesArray[i].target === id) - this.dropEdge(this.edgesArray[i].id); - - // Remove related edge indexes: - delete this.inNeighborsCount[id]; - delete this.outNeighborsCount[id]; - delete this.allNeighborsCount[id]; - - return this; - }); - - /** - * This method drops an edge from the graph. An error is thrown if the edge - * does not exist. - * - * @param {string} id The edge id. - * @return {object} The graph instance. - */ - graph.addMethod('dropEdge', function(id) { - // Check that the arguments are valid: - if ((typeof id !== 'string' && typeof id !== 'number') || - arguments.length !== 1) - throw 'dropEdge: Wrong arguments.'; - - if (!this.edgesIndex[id]) - throw 'The edge "' + id + '" does not exist.'; - - var i, l, edge; - - // Remove the edge from indexes: - edge = this.edgesIndex[id]; - delete this.edgesIndex[id]; - for (i = 0, l = this.edgesArray.length; i < l; i++) - if (this.edgesArray[i].id === id) { - this.edgesArray.splice(i, 1); - break; - } - - this.inNeighborsCount[edge.target]--; - this.outNeighborsCount[edge.source]--; - this.allNeighborsCount[edge.source]--; - this.allNeighborsCount[edge.target]--; - - return this; - }); - - /** - * This method destroys the current instance. It basically empties each index - * and methods attached to the graph. - */ - graph.addMethod('kill', function() { - // Delete arrays: - this.nodesArray.length = 0; - this.edgesArray.length = 0; - delete this.nodesArray; - delete this.edgesArray; - - // Delete indexes: - delete this.nodesIndex; - delete this.edgesIndex; - delete this.inNeighborsCount; - delete this.outNeighborsCount; - delete this.allNeighborsCount; - }); - - /** - * This method empties the nodes and edges arrays, as well as the different - * indexes. - * - * @return {object} The graph instance. - */ - graph.addMethod('clear', function() { - this.nodesArray.length = 0; - this.edgesArray.length = 0; - - // Due to GC issues, I prefer not to create new object. These objects are - // only available from the methods and attached functions, but still, it is - // better to prevent ghost references to unrelevant data... - sigma.utils.emptyObject(this.nodesIndex); - sigma.utils.emptyObject(this.edgesIndex); - sigma.utils.emptyObject(this.nodesIndex); - sigma.utils.emptyObject(this.inNeighborsCount); - sigma.utils.emptyObject(this.outNeighborsCount); - sigma.utils.emptyObject(this.allNeighborsCount); - - return this; - }); - - /** - * This method reads an object and adds the nodes and edges, through the - * proper methods "addNode" and "addEdge". - * - * Here is an example: - * - * > var myGraph = new graph(); - * > myGraph.read({ - * > nodes: [ - * > { id: 'n0' }, - * > { id: 'n1' } - * > ], - * > edges: [ - * > { - * > id: 'e0', - * > source: 'n0', - * > target: 'n1' - * > } - * > ] - * > }); - * > - * > console.log( - * > myGraph.nodes().length, - * > myGraph.edges().length - * > ); // outputs 2 1 - * - * @param {object} g The graph object. - * @return {object} The graph instance. - */ - graph.addMethod('read', function(g) { - var i, - a, - l; - - a = g.nodes || []; - for (i = 0, l = a.length; i < l; i++) - this.addNode(a[i]); - - a = g.edges || []; - for (i = 0, l = a.length; i < l; i++) - this.addEdge(a[i]); - - return this; - }); - - /** - * This methods returns one or several nodes, depending on how it is called. - * - * To get the array of nodes, call "nodes" without argument. To get a - * specific node, call it with the id of the node. The get multiple node, - * call it with an array of ids, and it will return the array of nodes, in - * the same order. - * - * @param {?(string|array)} v Eventually one id, an array of ids. - * @return {object|array} The related node or array of nodes. - */ - graph.addMethod('nodes', function(v) { - // Clone the array of nodes and return it: - if (!arguments.length) - return this.nodesArray.slice(0); - - // Return the related node: - if (arguments.length === 1 && - (typeof v === 'string' || typeof v === 'number')) - return this.nodesIndex[v]; - - // Return an array of the related node: - if ( - arguments.length === 1 && - Object.prototype.toString.call(v) === '[object Array]' - ) { - var i, - l, - a = []; - - for (i = 0, l = v.length; i < l; i++) - if (typeof v[i] === 'string' || typeof v[i] === 'number') - a.push(this.nodesIndex[v[i]]); - else - throw 'nodes: Wrong arguments.'; - - return a; - } - - throw 'nodes: Wrong arguments.'; - }); - - /** - * This methods returns the degree of one or several nodes, depending on how - * it is called. It is also possible to get incoming or outcoming degrees - * instead by specifying 'in' or 'out' as a second argument. - * - * @param {string|array} v One id, an array of ids. - * @param {?string} which Which degree is required. Values are 'in', - * 'out', and by default the normal degree. - * @return {number|array} The related degree or array of degrees. - */ - graph.addMethod('degree', function(v, which) { - // Check which degree is required: - which = { - 'in': this.inNeighborsCount, - 'out': this.outNeighborsCount - }[which || ''] || this.allNeighborsCount; - - // Return the related node: - if (typeof v === 'string' || typeof v === 'number') - return which[v]; - - // Return an array of the related node: - if (Object.prototype.toString.call(v) === '[object Array]') { - var i, - l, - a = []; - - for (i = 0, l = v.length; i < l; i++) - if (typeof v[i] === 'string' || typeof v[i] === 'number') - a.push(which[v[i]]); - else - throw 'degree: Wrong arguments.'; - - return a; - } - - throw 'degree: Wrong arguments.'; - }); - - /** - * This methods returns one or several edges, depending on how it is called. - * - * To get the array of edges, call "edges" without argument. To get a - * specific edge, call it with the id of the edge. The get multiple edge, - * call it with an array of ids, and it will return the array of edges, in - * the same order. - * - * @param {?(string|array)} v Eventually one id, an array of ids. - * @return {object|array} The related edge or array of edges. - */ - graph.addMethod('edges', function(v) { - // Clone the array of edges and return it: - if (!arguments.length) - return this.edgesArray.slice(0); - - // Return the related edge: - if (arguments.length === 1 && - (typeof v === 'string' || typeof v === 'number')) - return this.edgesIndex[v]; - - // Return an array of the related edge: - if ( - arguments.length === 1 && - Object.prototype.toString.call(v) === '[object Array]' - ) { - var i, - l, - a = []; - - for (i = 0, l = v.length; i < l; i++) - if (typeof v[i] === 'string' || typeof v[i] === 'number') - a.push(this.edgesIndex[v[i]]); - else - throw 'edges: Wrong arguments.'; - - return a; - } - - throw 'edges: Wrong arguments.'; - }); - - - /** - * EXPORT: - * ******* - */ - if (typeof sigma !== 'undefined') { - sigma.classes = sigma.classes || Object.create(null); - sigma.classes.graph = graph; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = graph; - exports.graph = graph; - } else - this.graph = graph; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - sigma.utils.pkg('sigma.classes'); - - /** - * The camera constructor. It just initializes its attributes and methods. - * - * @param {string} id The id. - * @param {sigma.classes.graph} graph The graph. - * @param {configurable} settings The settings function. - * @param {?object} options Eventually some overriding options. - * @return {camera} Returns the fresh new camera instance. - */ - sigma.classes.camera = function(id, graph, settings, options) { - sigma.classes.dispatcher.extend(this); - - Object.defineProperty(this, 'graph', { - value: graph - }); - Object.defineProperty(this, 'id', { - value: id - }); - Object.defineProperty(this, 'readPrefix', { - value: 'read_cam' + id + ':' - }); - Object.defineProperty(this, 'prefix', { - value: 'cam' + id + ':' - }); - - this.x = 0; - this.y = 0; - this.ratio = 1; - this.angle = 0; - this.isAnimated = false; - this.settings = (typeof options === 'object' && options) ? - settings.embedObject(options) : - settings; - }; - - /** - * Updates the camera position. - * - * @param {object} coordinates The new coordinates object. - * @return {camera} Returns the camera. - */ - sigma.classes.camera.prototype.goTo = function(coordinates) { - if (!this.settings('enableCamera')) - return this; - - var i, - l, - c = coordinates || {}, - keys = ['x', 'y', 'ratio', 'angle']; - - for (i = 0, l = keys.length; i < l; i++) - if (c[keys[i]] !== undefined) { - if (typeof c[keys[i]] === 'number' && !isNaN(c[keys[i]])) - this[keys[i]] = c[keys[i]]; - else - throw 'Value for "' + keys[i] + '" is not a number.'; - } - - this.dispatchEvent('coordinatesUpdated'); - return this; - }; - - /** - * This method takes a graph and computes for each node and edges its - * coordinates relatively to the center of the camera. Basically, it will - * compute the coordinates that will be used by the graphic renderers. - * - * Since it should be possible to use different cameras and different - * renderers, it is possible to specify a prefix to put before the new - * coordinates (to get something like "node.camera1_x") - * - * @param {?string} read The prefix of the coordinates to read. - * @param {?string} write The prefix of the coordinates to write. - * @param {?object} options Eventually an object of options. Those can be: - * - A restricted nodes array. - * - A restricted edges array. - * - A width. - * - A height. - * @return {camera} Returns the camera. - */ - sigma.classes.camera.prototype.applyView = function(read, write, options) { - options = options || {}; - write = write !== undefined ? write : this.prefix; - read = read !== undefined ? read : this.readPrefix; - - var nodes = options.nodes || this.graph.nodes(), - edges = options.edges || this.graph.edges(); - - var i, - l, - node, - cos = Math.cos(this.angle), - sin = Math.sin(this.angle); - - for (i = 0, l = nodes.length; i < l; i++) { - node = nodes[i]; - node[write + 'x'] = - ( - ((node[read + 'x'] || 0) - this.x) * cos + - ((node[read + 'y'] || 0) - this.y) * sin - ) / this.ratio + (options.width || 0) / 2; - node[write + 'y'] = - ( - ((node[read + 'y'] || 0) - this.y) * cos - - ((node[read + 'x'] || 0) - this.x) * sin - ) / this.ratio + (options.height || 0) / 2; - node[write + 'size'] = - (node[read + 'size'] || 0) / - Math.pow(this.ratio, this.settings('nodesPowRatio')); - } - - for (i = 0, l = edges.length; i < l; i++) { - edges[i][write + 'size'] = - (edges[i][read + 'size'] || 0) / - Math.pow(this.ratio, this.settings('edgesPowRatio')); - } - - return this; - }; - - /** - * This function converts the coordinates of a point from the frame of the - * camera to the frame of the graph. - * - * @param {number} x The X coordinate of the point in the frame of the - * camera. - * @param {number} y The Y coordinate of the point in the frame of the - * camera. - * @return {object} The point coordinates in the frame of the graph. - */ - sigma.classes.camera.prototype.graphPosition = function(x, y, vector) { - var X = 0, - Y = 0, - cos = Math.cos(this.angle), - sin = Math.sin(this.angle); - - // Revert the origin differential vector: - if (!vector) { - X = - (this.x * cos + this.y * sin) / this.ratio; - Y = - (this.y * cos - this.x * sin) / this.ratio; - } - - return { - x: (x * cos + y * sin) / this.ratio + X, - y: (y * cos - x * sin) / this.ratio + Y - }; - }; - - /** - * This function converts the coordinates of a point from the frame of the - * graph to the frame of the camera. - * - * @param {number} x The X coordinate of the point in the frame of the - * graph. - * @param {number} y The Y coordinate of the point in the frame of the - * graph. - * @return {object} The point coordinates in the frame of the camera. - */ - sigma.classes.camera.prototype.cameraPosition = function(x, y, vector) { - var X = 0, - Y = 0, - cos = Math.cos(this.angle), - sin = Math.sin(this.angle); - - // Revert the origin differential vector: - if (!vector) { - X = - (this.x * cos + this.y * sin) / this.ratio; - Y = - (this.y * cos - this.x * sin) / this.ratio; - } - - return { - x: ((x - X) * cos - (y - Y) * sin) * this.ratio, - y: ((y - Y) * cos + (x - X) * sin) * this.ratio - }; - }; - - /** - * This method returns the transformation matrix of the camera. This is - * especially useful to apply the camera view directly in shaders, in case of - * WebGL rendering. - * - * @return {array} The transformation matrix. - */ - sigma.classes.camera.prototype.getMatrix = function() { - var scale = sigma.utils.matrices.scale(1 / this.ratio), - rotation = sigma.utils.matrices.rotation(this.angle), - translation = sigma.utils.matrices.translation(-this.x, -this.y), - matrix = sigma.utils.matrices.multiply( - translation, - sigma.utils.matrices.multiply( - rotation, - scale - ) - ); - - return matrix; - }; - - /** - * Taking a width and a height as parameters, this method returns the - * coordinates of the rectangle representing the camera on screen, in the - * graph's referentiel. - * - * To keep displaying labels of nodes going out of the screen, the method - * keeps a margin around the screen in the returned rectangle. - * - * @param {number} width The width of the screen. - * @param {number} height The height of the screen. - * @return {object} The rectangle as x1, y1, x2 and y2, representing - * two opposite points. - */ - sigma.classes.camera.prototype.getRectangle = function(width, height) { - var widthVect = this.cameraPosition(width, 0, true), - heightVect = this.cameraPosition(0, height, true), - centerVect = this.cameraPosition(width / 2, height / 2, true), - marginX = this.cameraPosition(width / 4, 0, true).x, - marginY = this.cameraPosition(0, height / 4, true).y; - - return { - x1: this.x - centerVect.x - marginX, - y1: this.y - centerVect.y - marginY, - x2: this.x - centerVect.x + marginX + widthVect.x, - y2: this.y - centerVect.y - marginY + widthVect.y, - height: Math.sqrt( - Math.pow(heightVect.x, 2) + - Math.pow(heightVect.y + 2 * marginY, 2) - ) - }; - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - /** - * Sigma Quadtree Module - * ===================== - * - * Author: Guillaume Plique (Yomguithereal) - * Version: 0.2 - */ - - - - /** - * Quad Geometric Operations - * ------------------------- - * - * A useful batch of geometric operations used by the quadtree. - */ - - var _geom = { - - /** - * Transforms a graph node with x, y and size into an - * axis-aligned square. - * - * @param {object} A graph node with at least a point (x, y) and a size. - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - pointToSquare: function(n) { - return { - x1: n.x - n.size, - y1: n.y - n.size, - x2: n.x + n.size, - y2: n.y - n.size, - height: n.size * 2 - }; - }, - - /** - * Checks whether a rectangle is axis-aligned. - * - * @param {object} A rectangle defined by two points - * (x1, y1) and (x2, y2). - * @return {boolean} True if the rectangle is axis-aligned. - */ - isAxisAligned: function(r) { - return r.x1 === r.x2 || r.y1 === r.y2; - }, - - /** - * Compute top points of an axis-aligned rectangle. This is useful in - * cases when the rectangle has been rotated (left, right or bottom up) and - * later operations need to know the top points. - * - * @param {object} An axis-aligned rectangle defined by two points - * (x1, y1), (x2, y2) and height. - * @return {object} A rectangle: two points (x1, y1), (x2, y2) and height. - */ - axisAlignedTopPoints: function(r) { - - // Basic - if (r.y1 === r.y2 && r.x1 < r.x2) - return r; - - // Rotated to right - if (r.x1 === r.x2 && r.y2 > r.y1) - return { - x1: r.x1 - r.height, y1: r.y1, - x2: r.x1, y2: r.y1, - height: r.height - }; - - // Rotated to left - if (r.x1 === r.x2 && r.y2 < r.y1) - return { - x1: r.x1, y1: r.y2, - x2: r.x2 + r.height, y2: r.y2, - height: r.height - }; - - // Bottom's up - return { - x1: r.x2, y1: r.y1 - r.height, - x2: r.x1, y2: r.y1 - r.height, - height: r.height - }; - }, - - /** - * Get coordinates of a rectangle's lower left corner from its top points. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @return {object} Coordinates of the corner (x, y). - */ - lowerLeftCoor: function(r) { - var width = ( - Math.sqrt( - Math.pow(r.x2 - r.x1, 2) + - Math.pow(r.y2 - r.y1, 2) - ) - ); - - return { - x: r.x1 - (r.y2 - r.y1) * r.height / width, - y: r.y1 + (r.x2 - r.x1) * r.height / width - }; - }, - - /** - * Get coordinates of a rectangle's lower right corner from its top points - * and its lower left corner. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @param {object} A corner's coordinates (x, y). - * @return {object} Coordinates of the corner (x, y). - */ - lowerRightCoor: function(r, llc) { - return { - x: llc.x - r.x1 + r.x2, - y: llc.y - r.y1 + r.y2 - }; - }, - - /** - * Get the coordinates of all the corners of a rectangle from its top point. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @return {array} An array of the four corners' coordinates (x, y). - */ - rectangleCorners: function(r) { - var llc = this.lowerLeftCoor(r), - lrc = this.lowerRightCoor(r, llc); - - return [ - {x: r.x1, y: r.y1}, - {x: r.x2, y: r.y2}, - {x: llc.x, y: llc.y}, - {x: lrc.x, y: lrc.y} - ]; - }, - - /** - * Split a square defined by its boundaries into four. - * - * @param {object} Boundaries of the square (x, y, width, height). - * @return {array} An array containing the four new squares, themselves - * defined by an array of their four corners (x, y). - */ - splitSquare: function(b) { - return [ - [ - {x: b.x, y: b.y}, - {x: b.x + b.width / 2, y: b.y}, - {x: b.x, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height / 2} - ], - [ - {x: b.x + b.width / 2, y: b.y}, - {x: b.x + b.width, y: b.y}, - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x + b.width, y: b.y + b.height / 2} - ], - [ - {x: b.x, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x, y: b.y + b.height}, - {x: b.x + b.width / 2, y: b.y + b.height} - ], - [ - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x + b.width, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height}, - {x: b.x + b.width, y: b.y + b.height} - ] - ]; - }, - - /** - * Compute the four axis between corners of rectangle A and corners of - * rectangle B. This is needed later to check an eventual collision. - * - * @param {array} An array of rectangle A's four corners (x, y). - * @param {array} An array of rectangle B's four corners (x, y). - * @return {array} An array of four axis defined by their coordinates (x,y). - */ - axis: function(c1, c2) { - return [ - {x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y}, - {x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y}, - {x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y}, - {x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y} - ]; - }, - - /** - * Project a rectangle's corner on an axis. - * - * @param {object} Coordinates of a corner (x, y). - * @param {object} Coordinates of an axis (x, y). - * @return {object} The projection defined by coordinates (x, y). - */ - projection: function(c, a) { - var l = ( - (c.x * a.x + c.y * a.y) / - (Math.pow(a.x, 2) + Math.pow(a.y, 2)) - ); - - return { - x: l * a.x, - y: l * a.y - }; - }, - - /** - * Check whether two rectangles collide on one particular axis. - * - * @param {object} An axis' coordinates (x, y). - * @param {array} Rectangle A's corners. - * @param {array} Rectangle B's corners. - * @return {boolean} True if the rectangles collide on the axis. - */ - axisCollision: function(a, c1, c2) { - var sc1 = [], - sc2 = []; - - for (var ci = 0; ci < 4; ci++) { - var p1 = this.projection(c1[ci], a), - p2 = this.projection(c2[ci], a); - - sc1.push(p1.x * a.x + p1.y * a.y); - sc2.push(p2.x * a.x + p2.y * a.y); - } - - var maxc1 = Math.max.apply(Math, sc1), - maxc2 = Math.max.apply(Math, sc2), - minc1 = Math.min.apply(Math, sc1), - minc2 = Math.min.apply(Math, sc2); - - return (minc2 <= maxc1 && maxc2 >= minc1); - }, - - /** - * Check whether two rectangles collide on each one of their four axis. If - * all axis collide, then the two rectangles do collide on the plane. - * - * @param {array} Rectangle A's corners. - * @param {array} Rectangle B's corners. - * @return {boolean} True if the rectangles collide. - */ - collision: function(c1, c2) { - var axis = this.axis(c1, c2), - col = true; - - for (var i = 0; i < 4; i++) { - col *= this.axisCollision(axis[i], c1, c2); - } - - return !!col; - } - }; - - - /** - * Quad Functions - * ------------ - * - * The Quadtree functions themselves. - * For each of those functions, we consider that in a splitted quad, the - * index of each node is the following: - * 0: top left - * 1: top right - * 2: bottom left - * 3: bottom right - * - * Moreover, the hereafter quad's philosophy is to consider that if an element - * collides with more than one nodes, this element belongs to each of the - * nodes it collides with where other would let it lie on a higher node. - */ - - /** - * Get the index of the node containing the point in the quad - * - * @param {object} point A point defined by coordinates (x, y). - * @param {object} quadBounds Boundaries of the quad (x, y, width, heigth). - * @return {integer} The index of the node containing the point. - */ - function _quadIndex(point, quadBounds) { - var xmp = quadBounds.x + quadBounds.width / 2, - ymp = quadBounds.y + quadBounds.height / 2, - top = (point.y < ymp), - left = (point.x < xmp); - - if (top) { - if (left) - return 0; - else - return 1; - } - else { - if (left) - return 2; - else - return 3; - } - } - - /** - * Get a list of indexes of nodes containing an axis-aligned rectangle - * - * @param {object} rectangle A rectangle defined by two points (x1, y1), - * (x2, y2) and height. - * @param {array} quadCorners An array of the quad nodes' corners. - * @return {array} An array of indexes containing one to - * four integers. - */ - function _quadIndexes(rectangle, quadCorners) { - var indexes = []; - - // Iterating through quads - for (var i = 0; i < 4; i++) - if ((rectangle.x2 >= quadCorners[i][0].x) && - (rectangle.x1 <= quadCorners[i][1].x) && - (rectangle.y1 + rectangle.height >= quadCorners[i][0].y) && - (rectangle.y1 <= quadCorners[i][2].y)) - indexes.push(i); - - return indexes; - } - - /** - * Get a list of indexes of nodes containing a non-axis-aligned rectangle - * - * @param {array} corners An array containing each corner of the - * rectangle defined by its coordinates (x, y). - * @param {array} quadCorners An array of the quad nodes' corners. - * @return {array} An array of indexes containing one to - * four integers. - */ - function _quadCollision(corners, quadCorners) { - var indexes = []; - - // Iterating through quads - for (var i = 0; i < 4; i++) - if (_geom.collision(corners, quadCorners[i])) - indexes.push(i); - - return indexes; - } - - /** - * Subdivide a quad by creating a node at a precise index. The function does - * not generate all four nodes not to potentially create unused nodes. - * - * @param {integer} index The index of the node to create. - * @param {object} quad The quad object to subdivide. - * @return {object} A new quad representing the node created. - */ - function _quadSubdivide(index, quad) { - var next = quad.level + 1, - subw = Math.round(quad.bounds.width / 2), - subh = Math.round(quad.bounds.height / 2), - qx = Math.round(quad.bounds.x), - qy = Math.round(quad.bounds.y), - x, - y; - - switch (index) { - case 0: - x = qx; - y = qy; - break; - case 1: - x = qx + subw; - y = qy; - break; - case 2: - x = qx; - y = qy + subh; - break; - case 3: - x = qx + subw; - y = qy + subh; - break; - } - - return _quadTree( - {x: x, y: y, width: subw, height: subh}, - next, - quad.maxElements, - quad.maxLevel - ); - } - - /** - * Recursively insert an element into the quadtree. Only points - * with size, i.e. axis-aligned squares, may be inserted with this - * method. - * - * @param {object} el The element to insert in the quadtree. - * @param {object} sizedPoint A sized point defined by two top points - * (x1, y1), (x2, y2) and height. - * @param {object} quad The quad in which to insert the element. - * @return {undefined} The function does not return anything. - */ - function _quadInsert(el, sizedPoint, quad) { - if (quad.level < quad.maxLevel) { - - // Searching appropriate quads - var indexes = _quadIndexes(sizedPoint, quad.corners); - - // Iterating - for (var i = 0, l = indexes.length; i < l; i++) { - - // Subdividing if necessary - if (quad.nodes[indexes[i]] === undefined) - quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad); - - // Recursion - _quadInsert(el, sizedPoint, quad.nodes[indexes[i]]); - } - } - else { - - // Pushing the element in a leaf node - quad.elements.push(el); - } - } - - /** - * Recursively retrieve every elements held by the node containing the - * searched point. - * - * @param {object} point The searched point (x, y). - * @param {object} quad The searched quad. - * @return {array} An array of elements contained in the relevant - * node. - */ - function _quadRetrievePoint(point, quad) { - if (quad.level < quad.maxLevel) { - var index = _quadIndex(point, quad.bounds); - - // If node does not exist we return an empty list - if (quad.nodes[index] !== undefined) { - return _quadRetrievePoint(point, quad.nodes[index]); - } - else { - return []; - } - } - else { - return quad.elements; - } - } - - /** - * Recursively retrieve every elements contained within an rectangular area - * that may or may not be axis-aligned. - * - * @param {object|array} rectData The searched area defined either by - * an array of four corners (x, y) in - * the case of a non-axis-aligned - * rectangle or an object with two top - * points (x1, y1), (x2, y2) and height. - * @param {object} quad The searched quad. - * @param {function} collisionFunc The collision function used to search - * for node indexes. - * @param {array?} els The retrieved elements. - * @return {array} An array of elements contained in the - * area. - */ - function _quadRetrieveArea(rectData, quad, collisionFunc, els) { - els = els || {}; - - if (quad.level < quad.maxLevel) { - var indexes = collisionFunc(rectData, quad.corners); - - for (var i = 0, l = indexes.length; i < l; i++) - if (quad.nodes[indexes[i]] !== undefined) - _quadRetrieveArea( - rectData, - quad.nodes[indexes[i]], - collisionFunc, - els - ); - } else - for (var j = 0, m = quad.elements.length; j < m; j++) - if (els[quad.elements[j].id] === undefined) - els[quad.elements[j].id] = quad.elements[j]; - - return els; - } - - /** - * Creates the quadtree object itself. - * - * @param {object} bounds The boundaries of the quad defined by an - * origin (x, y), width and heigth. - * @param {integer} level The level of the quad in the tree. - * @param {integer} maxElements The max number of element in a leaf node. - * @param {integer} maxLevel The max recursion level of the tree. - * @return {object} The quadtree object. - */ - function _quadTree(bounds, level, maxElements, maxLevel) { - return { - level: level || 0, - bounds: bounds, - corners: _geom.splitSquare(bounds), - maxElements: maxElements || 20, - maxLevel: maxLevel || 4, - elements: [], - nodes: [] - }; - } - - - /** - * Sigma Quad Constructor - * ---------------------- - * - * The quad API as exposed to sigma. - */ - - /** - * The quad core that will become the sigma interface with the quadtree. - * - * property {object} _tree Property holding the quadtree object. - * property {object} _geom Exposition of the _geom namespace for testing. - * property {object} _cache Cache for the area method. - */ - var quad = function() { - this._geom = _geom; - this._tree = null; - this._cache = { - query: false, - result: false - }; - }; - - /** - * Index a graph by inserting its nodes into the quadtree. - * - * @param {array} nodes An array of nodes to index. - * @param {object} params An object of parameters with at least the quad - * bounds. - * @return {object} The quadtree object. - * - * Parameters: - * ---------- - * bounds: {object} boundaries of the quad defined by its origin (x, y) - * width and heigth. - * prefix: {string?} a prefix for node geometric attributes. - * maxElements: {integer?} the max number of elements in a leaf node. - * maxLevel: {integer?} the max recursion level of the tree. - */ - quad.prototype.index = function(nodes, params) { - - // Enforcing presence of boundaries - if (!params.bounds) - throw 'sigma.classes.quad.index: bounds information not given.'; - - // Prefix - var prefix = params.prefix || ''; - - // Building the tree - this._tree = _quadTree( - params.bounds, - 0, - params.maxElements, - params.maxLevel - ); - - // Inserting graph nodes into the tree - for (var i = 0, l = nodes.length; i < l; i++) { - - // Inserting node - _quadInsert( - nodes[i], - _geom.pointToSquare({ - x: nodes[i][prefix + 'x'], - y: nodes[i][prefix + 'y'], - size: nodes[i][prefix + 'size'] - }), - this._tree - ); - } - - // Reset cache: - this._cache = { - query: false, - result: false - }; - - // remove? - return this._tree; - }; - - /** - * Retrieve every graph nodes held by the quadtree node containing the - * searched point. - * - * @param {number} x of the point. - * @param {number} y of the point. - * @return {array} An array of nodes retrieved. - */ - quad.prototype.point = function(x, y) { - return this._tree ? - _quadRetrievePoint({x: x, y: y}, this._tree) || [] : - []; - }; - - /** - * Retrieve every graph nodes within a rectangular area. The methods keep the - * last area queried in cache for optimization reason and will act differently - * for the same reason if the area is axis-aligned or not. - * - * @param {object} A rectangle defined by two top points (x1, y1), (x2, y2) - * and height. - * @return {array} An array of nodes retrieved. - */ - quad.prototype.area = function(rect) { - var serialized = JSON.stringify(rect), - collisionFunc, - rectData; - - // Returning cache? - if (this._cache.query === serialized) - return this._cache.result; - - // Axis aligned ? - if (_geom.isAxisAligned(rect)) { - collisionFunc = _quadIndexes; - rectData = _geom.axisAlignedTopPoints(rect); - } - else { - collisionFunc = _quadCollision; - rectData = _geom.rectangleCorners(rect); - } - - // Retrieving nodes - var nodes = this._tree ? - _quadRetrieveArea( - rectData, - this._tree, - collisionFunc - ) : - []; - - // Object to array - var nodesArray = []; - for (var i in nodes) - nodesArray.push(nodes[i]); - - // Caching - this._cache.query = serialized; - this._cache.result = nodesArray; - - return nodesArray; - }; - - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') { - this.sigma.classes = this.sigma.classes || {}; - this.sigma.classes.quad = quad; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = quad; - exports.quad = quad; - } else - this.quad = quad; -}).call(this); - -;(function(undefined) { - 'use strict'; - - /** - * Sigma Quadtree Module for edges - * =============================== - * - * Author: Sébastien Heymann, - * from the quad of Guillaume Plique (Yomguithereal) - * Version: 0.2 - */ - - - - /** - * Quad Geometric Operations - * ------------------------- - * - * A useful batch of geometric operations used by the quadtree. - */ - - var _geom = { - - /** - * Transforms a graph node with x, y and size into an - * axis-aligned square. - * - * @param {object} A graph node with at least a point (x, y) and a size. - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - pointToSquare: function(n) { - return { - x1: n.x - n.size, - y1: n.y - n.size, - x2: n.x + n.size, - y2: n.y - n.size, - height: n.size * 2 - }; - }, - - /** - * Transforms a graph edge with x1, y1, x2, y2 and size into an - * axis-aligned square. - * - * @param {object} A graph edge with at least two points - * (x1, y1), (x2, y2) and a size. - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - lineToSquare: function(e) { - if (e.y1 < e.y2) { - // (e.x1, e.y1) on top - if (e.x1 < e.x2) { - // (e.x1, e.y1) on left - return { - x1: e.x1 - e.size, - y1: e.y1 - e.size, - x2: e.x2 + e.size, - y2: e.y1 - e.size, - height: e.y2 - e.y1 + e.size * 2 - }; - } - // (e.x1, e.y1) on right - return { - x1: e.x2 - e.size, - y1: e.y1 - e.size, - x2: e.x1 + e.size, - y2: e.y1 - e.size, - height: e.y2 - e.y1 + e.size * 2 - }; - } - - // (e.x2, e.y2) on top - if (e.x1 < e.x2) { - // (e.x1, e.y1) on left - return { - x1: e.x1 - e.size, - y1: e.y2 - e.size, - x2: e.x2 + e.size, - y2: e.y2 - e.size, - height: e.y1 - e.y2 + e.size * 2 - }; - } - // (e.x2, e.y2) on right - return { - x1: e.x2 - e.size, - y1: e.y2 - e.size, - x2: e.x1 + e.size, - y2: e.y2 - e.size, - height: e.y1 - e.y2 + e.size * 2 - }; - }, - - /** - * Transforms a graph edge of type 'curve' with x1, y1, x2, y2, - * control point and size into an axis-aligned square. - * - * @param {object} e A graph edge with at least two points - * (x1, y1), (x2, y2) and a size. - * @param {object} cp A control point (x,y). - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - quadraticCurveToSquare: function(e, cp) { - var pt = sigma.utils.getPointOnQuadraticCurve( - 0.5, - e.x1, - e.y1, - e.x2, - e.y2, - cp.x, - cp.y - ); - - // Bounding box of the two points and the point at the middle of the - // curve: - var minX = Math.min(e.x1, e.x2, pt.x), - maxX = Math.max(e.x1, e.x2, pt.x), - minY = Math.min(e.y1, e.y2, pt.y), - maxY = Math.max(e.y1, e.y2, pt.y); - - return { - x1: minX - e.size, - y1: minY - e.size, - x2: maxX + e.size, - y2: minY - e.size, - height: maxY - minY + e.size * 2 - }; - }, - - /** - * Transforms a graph self loop into an axis-aligned square. - * - * @param {object} n A graph node with a point (x, y) and a size. - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - selfLoopToSquare: function(n) { - // Fitting to the curve is too costly, we compute a larger bounding box - // using the control points: - var cp = sigma.utils.getSelfLoopControlPoints(n.x, n.y, n.size); - - // Bounding box of the point and the two control points: - var minX = Math.min(n.x, cp.x1, cp.x2), - maxX = Math.max(n.x, cp.x1, cp.x2), - minY = Math.min(n.y, cp.y1, cp.y2), - maxY = Math.max(n.y, cp.y1, cp.y2); - - return { - x1: minX - n.size, - y1: minY - n.size, - x2: maxX + n.size, - y2: minY - n.size, - height: maxY - minY + n.size * 2 - }; - }, - - /** - * Checks whether a rectangle is axis-aligned. - * - * @param {object} A rectangle defined by two points - * (x1, y1) and (x2, y2). - * @return {boolean} True if the rectangle is axis-aligned. - */ - isAxisAligned: function(r) { - return r.x1 === r.x2 || r.y1 === r.y2; - }, - - /** - * Compute top points of an axis-aligned rectangle. This is useful in - * cases when the rectangle has been rotated (left, right or bottom up) and - * later operations need to know the top points. - * - * @param {object} An axis-aligned rectangle defined by two points - * (x1, y1), (x2, y2) and height. - * @return {object} A rectangle: two points (x1, y1), (x2, y2) and height. - */ - axisAlignedTopPoints: function(r) { - - // Basic - if (r.y1 === r.y2 && r.x1 < r.x2) - return r; - - // Rotated to right - if (r.x1 === r.x2 && r.y2 > r.y1) - return { - x1: r.x1 - r.height, y1: r.y1, - x2: r.x1, y2: r.y1, - height: r.height - }; - - // Rotated to left - if (r.x1 === r.x2 && r.y2 < r.y1) - return { - x1: r.x1, y1: r.y2, - x2: r.x2 + r.height, y2: r.y2, - height: r.height - }; - - // Bottom's up - return { - x1: r.x2, y1: r.y1 - r.height, - x2: r.x1, y2: r.y1 - r.height, - height: r.height - }; - }, - - /** - * Get coordinates of a rectangle's lower left corner from its top points. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @return {object} Coordinates of the corner (x, y). - */ - lowerLeftCoor: function(r) { - var width = ( - Math.sqrt( - Math.pow(r.x2 - r.x1, 2) + - Math.pow(r.y2 - r.y1, 2) - ) - ); - - return { - x: r.x1 - (r.y2 - r.y1) * r.height / width, - y: r.y1 + (r.x2 - r.x1) * r.height / width - }; - }, - - /** - * Get coordinates of a rectangle's lower right corner from its top points - * and its lower left corner. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @param {object} A corner's coordinates (x, y). - * @return {object} Coordinates of the corner (x, y). - */ - lowerRightCoor: function(r, llc) { - return { - x: llc.x - r.x1 + r.x2, - y: llc.y - r.y1 + r.y2 - }; - }, - - /** - * Get the coordinates of all the corners of a rectangle from its top point. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @return {array} An array of the four corners' coordinates (x, y). - */ - rectangleCorners: function(r) { - var llc = this.lowerLeftCoor(r), - lrc = this.lowerRightCoor(r, llc); - - return [ - {x: r.x1, y: r.y1}, - {x: r.x2, y: r.y2}, - {x: llc.x, y: llc.y}, - {x: lrc.x, y: lrc.y} - ]; - }, - - /** - * Split a square defined by its boundaries into four. - * - * @param {object} Boundaries of the square (x, y, width, height). - * @return {array} An array containing the four new squares, themselves - * defined by an array of their four corners (x, y). - */ - splitSquare: function(b) { - return [ - [ - {x: b.x, y: b.y}, - {x: b.x + b.width / 2, y: b.y}, - {x: b.x, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height / 2} - ], - [ - {x: b.x + b.width / 2, y: b.y}, - {x: b.x + b.width, y: b.y}, - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x + b.width, y: b.y + b.height / 2} - ], - [ - {x: b.x, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x, y: b.y + b.height}, - {x: b.x + b.width / 2, y: b.y + b.height} - ], - [ - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x + b.width, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height}, - {x: b.x + b.width, y: b.y + b.height} - ] - ]; - }, - - /** - * Compute the four axis between corners of rectangle A and corners of - * rectangle B. This is needed later to check an eventual collision. - * - * @param {array} An array of rectangle A's four corners (x, y). - * @param {array} An array of rectangle B's four corners (x, y). - * @return {array} An array of four axis defined by their coordinates (x,y). - */ - axis: function(c1, c2) { - return [ - {x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y}, - {x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y}, - {x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y}, - {x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y} - ]; - }, - - /** - * Project a rectangle's corner on an axis. - * - * @param {object} Coordinates of a corner (x, y). - * @param {object} Coordinates of an axis (x, y). - * @return {object} The projection defined by coordinates (x, y). - */ - projection: function(c, a) { - var l = ( - (c.x * a.x + c.y * a.y) / - (Math.pow(a.x, 2) + Math.pow(a.y, 2)) - ); - - return { - x: l * a.x, - y: l * a.y - }; - }, - - /** - * Check whether two rectangles collide on one particular axis. - * - * @param {object} An axis' coordinates (x, y). - * @param {array} Rectangle A's corners. - * @param {array} Rectangle B's corners. - * @return {boolean} True if the rectangles collide on the axis. - */ - axisCollision: function(a, c1, c2) { - var sc1 = [], - sc2 = []; - - for (var ci = 0; ci < 4; ci++) { - var p1 = this.projection(c1[ci], a), - p2 = this.projection(c2[ci], a); - - sc1.push(p1.x * a.x + p1.y * a.y); - sc2.push(p2.x * a.x + p2.y * a.y); - } - - var maxc1 = Math.max.apply(Math, sc1), - maxc2 = Math.max.apply(Math, sc2), - minc1 = Math.min.apply(Math, sc1), - minc2 = Math.min.apply(Math, sc2); - - return (minc2 <= maxc1 && maxc2 >= minc1); - }, - - /** - * Check whether two rectangles collide on each one of their four axis. If - * all axis collide, then the two rectangles do collide on the plane. - * - * @param {array} Rectangle A's corners. - * @param {array} Rectangle B's corners. - * @return {boolean} True if the rectangles collide. - */ - collision: function(c1, c2) { - var axis = this.axis(c1, c2), - col = true; - - for (var i = 0; i < 4; i++) { - col *= this.axisCollision(axis[i], c1, c2); - } - - return !!col; - } - }; - - - /** - * Quad Functions - * ------------ - * - * The Quadtree functions themselves. - * For each of those functions, we consider that in a splitted quad, the - * index of each node is the following: - * 0: top left - * 1: top right - * 2: bottom left - * 3: bottom right - * - * Moreover, the hereafter quad's philosophy is to consider that if an element - * collides with more than one nodes, this element belongs to each of the - * nodes it collides with where other would let it lie on a higher node. - */ - - /** - * Get the index of the node containing the point in the quad - * - * @param {object} point A point defined by coordinates (x, y). - * @param {object} quadBounds Boundaries of the quad (x, y, width, heigth). - * @return {integer} The index of the node containing the point. - */ - function _quadIndex(point, quadBounds) { - var xmp = quadBounds.x + quadBounds.width / 2, - ymp = quadBounds.y + quadBounds.height / 2, - top = (point.y < ymp), - left = (point.x < xmp); - - if (top) { - if (left) - return 0; - else - return 1; - } - else { - if (left) - return 2; - else - return 3; - } - } - - /** - * Get a list of indexes of nodes containing an axis-aligned rectangle - * - * @param {object} rectangle A rectangle defined by two points (x1, y1), - * (x2, y2) and height. - * @param {array} quadCorners An array of the quad nodes' corners. - * @return {array} An array of indexes containing one to - * four integers. - */ - function _quadIndexes(rectangle, quadCorners) { - var indexes = []; - - // Iterating through quads - for (var i = 0; i < 4; i++) - if ((rectangle.x2 >= quadCorners[i][0].x) && - (rectangle.x1 <= quadCorners[i][1].x) && - (rectangle.y1 + rectangle.height >= quadCorners[i][0].y) && - (rectangle.y1 <= quadCorners[i][2].y)) - indexes.push(i); - - return indexes; - } - - /** - * Get a list of indexes of nodes containing a non-axis-aligned rectangle - * - * @param {array} corners An array containing each corner of the - * rectangle defined by its coordinates (x, y). - * @param {array} quadCorners An array of the quad nodes' corners. - * @return {array} An array of indexes containing one to - * four integers. - */ - function _quadCollision(corners, quadCorners) { - var indexes = []; - - // Iterating through quads - for (var i = 0; i < 4; i++) - if (_geom.collision(corners, quadCorners[i])) - indexes.push(i); - - return indexes; - } - - /** - * Subdivide a quad by creating a node at a precise index. The function does - * not generate all four nodes not to potentially create unused nodes. - * - * @param {integer} index The index of the node to create. - * @param {object} quad The quad object to subdivide. - * @return {object} A new quad representing the node created. - */ - function _quadSubdivide(index, quad) { - var next = quad.level + 1, - subw = Math.round(quad.bounds.width / 2), - subh = Math.round(quad.bounds.height / 2), - qx = Math.round(quad.bounds.x), - qy = Math.round(quad.bounds.y), - x, - y; - - switch (index) { - case 0: - x = qx; - y = qy; - break; - case 1: - x = qx + subw; - y = qy; - break; - case 2: - x = qx; - y = qy + subh; - break; - case 3: - x = qx + subw; - y = qy + subh; - break; - } - - return _quadTree( - {x: x, y: y, width: subw, height: subh}, - next, - quad.maxElements, - quad.maxLevel - ); - } - - /** - * Recursively insert an element into the quadtree. Only points - * with size, i.e. axis-aligned squares, may be inserted with this - * method. - * - * @param {object} el The element to insert in the quadtree. - * @param {object} sizedPoint A sized point defined by two top points - * (x1, y1), (x2, y2) and height. - * @param {object} quad The quad in which to insert the element. - * @return {undefined} The function does not return anything. - */ - function _quadInsert(el, sizedPoint, quad) { - if (quad.level < quad.maxLevel) { - - // Searching appropriate quads - var indexes = _quadIndexes(sizedPoint, quad.corners); - - // Iterating - for (var i = 0, l = indexes.length; i < l; i++) { - - // Subdividing if necessary - if (quad.nodes[indexes[i]] === undefined) - quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad); - - // Recursion - _quadInsert(el, sizedPoint, quad.nodes[indexes[i]]); - } - } - else { - - // Pushing the element in a leaf node - quad.elements.push(el); - } - } - - /** - * Recursively retrieve every elements held by the node containing the - * searched point. - * - * @param {object} point The searched point (x, y). - * @param {object} quad The searched quad. - * @return {array} An array of elements contained in the relevant - * node. - */ - function _quadRetrievePoint(point, quad) { - if (quad.level < quad.maxLevel) { - var index = _quadIndex(point, quad.bounds); - - // If node does not exist we return an empty list - if (quad.nodes[index] !== undefined) { - return _quadRetrievePoint(point, quad.nodes[index]); - } - else { - return []; - } - } - else { - return quad.elements; - } - } - - /** - * Recursively retrieve every elements contained within an rectangular area - * that may or may not be axis-aligned. - * - * @param {object|array} rectData The searched area defined either by - * an array of four corners (x, y) in - * the case of a non-axis-aligned - * rectangle or an object with two top - * points (x1, y1), (x2, y2) and height. - * @param {object} quad The searched quad. - * @param {function} collisionFunc The collision function used to search - * for node indexes. - * @param {array?} els The retrieved elements. - * @return {array} An array of elements contained in the - * area. - */ - function _quadRetrieveArea(rectData, quad, collisionFunc, els) { - els = els || {}; - - if (quad.level < quad.maxLevel) { - var indexes = collisionFunc(rectData, quad.corners); - - for (var i = 0, l = indexes.length; i < l; i++) - if (quad.nodes[indexes[i]] !== undefined) - _quadRetrieveArea( - rectData, - quad.nodes[indexes[i]], - collisionFunc, - els - ); - } else - for (var j = 0, m = quad.elements.length; j < m; j++) - if (els[quad.elements[j].id] === undefined) - els[quad.elements[j].id] = quad.elements[j]; - - return els; - } - - /** - * Creates the quadtree object itself. - * - * @param {object} bounds The boundaries of the quad defined by an - * origin (x, y), width and heigth. - * @param {integer} level The level of the quad in the tree. - * @param {integer} maxElements The max number of element in a leaf node. - * @param {integer} maxLevel The max recursion level of the tree. - * @return {object} The quadtree object. - */ - function _quadTree(bounds, level, maxElements, maxLevel) { - return { - level: level || 0, - bounds: bounds, - corners: _geom.splitSquare(bounds), - maxElements: maxElements || 40, - maxLevel: maxLevel || 8, - elements: [], - nodes: [] - }; - } - - - /** - * Sigma Quad Constructor - * ---------------------- - * - * The edgequad API as exposed to sigma. - */ - - /** - * The edgequad core that will become the sigma interface with the quadtree. - * - * property {object} _tree Property holding the quadtree object. - * property {object} _geom Exposition of the _geom namespace for testing. - * property {object} _cache Cache for the area method. - * property {boolean} _enabled Can index and retreive elements. - */ - var edgequad = function() { - this._geom = _geom; - this._tree = null; - this._cache = { - query: false, - result: false - }; - this._enabled = true; - }; - - /** - * Index a graph by inserting its edges into the quadtree. - * - * @param {object} graph A graph instance. - * @param {object} params An object of parameters with at least the quad - * bounds. - * @return {object} The quadtree object. - * - * Parameters: - * ---------- - * bounds: {object} boundaries of the quad defined by its origin (x, y) - * width and heigth. - * prefix: {string?} a prefix for edge geometric attributes. - * maxElements: {integer?} the max number of elements in a leaf node. - * maxLevel: {integer?} the max recursion level of the tree. - */ - edgequad.prototype.index = function(graph, params) { - if (!this._enabled) - return this._tree; - - // Enforcing presence of boundaries - if (!params.bounds) - throw 'sigma.classes.edgequad.index: bounds information not given.'; - - // Prefix - var prefix = params.prefix || '', - cp, - source, - target, - n, - e; - - // Building the tree - this._tree = _quadTree( - params.bounds, - 0, - params.maxElements, - params.maxLevel - ); - - var edges = graph.edges(); - - // Inserting graph edges into the tree - for (var i = 0, l = edges.length; i < l; i++) { - source = graph.nodes(edges[i].source); - target = graph.nodes(edges[i].target); - e = { - x1: source[prefix + 'x'], - y1: source[prefix + 'y'], - x2: target[prefix + 'x'], - y2: target[prefix + 'y'], - size: edges[i][prefix + 'size'] || 0 - }; - - // Inserting edge - if (edges[i].type === 'curve' || edges[i].type === 'curvedArrow') { - if (source.id === target.id) { - n = { - x: source[prefix + 'x'], - y: source[prefix + 'y'], - size: source[prefix + 'size'] || 0 - }; - _quadInsert( - edges[i], - _geom.selfLoopToSquare(n), - this._tree); - } - else { - cp = sigma.utils.getQuadraticControlPoint(e.x1, e.y1, e.x2, e.y2); - _quadInsert( - edges[i], - _geom.quadraticCurveToSquare(e, cp), - this._tree); - } - } - else { - _quadInsert( - edges[i], - _geom.lineToSquare(e), - this._tree); - } - } - - // Reset cache: - this._cache = { - query: false, - result: false - }; - - // remove? - return this._tree; - }; - - /** - * Retrieve every graph edges held by the quadtree node containing the - * searched point. - * - * @param {number} x of the point. - * @param {number} y of the point. - * @return {array} An array of edges retrieved. - */ - edgequad.prototype.point = function(x, y) { - if (!this._enabled) - return []; - - return this._tree ? - _quadRetrievePoint({x: x, y: y}, this._tree) || [] : - []; - }; - - /** - * Retrieve every graph edges within a rectangular area. The methods keep the - * last area queried in cache for optimization reason and will act differently - * for the same reason if the area is axis-aligned or not. - * - * @param {object} A rectangle defined by two top points (x1, y1), (x2, y2) - * and height. - * @return {array} An array of edges retrieved. - */ - edgequad.prototype.area = function(rect) { - if (!this._enabled) - return []; - - var serialized = JSON.stringify(rect), - collisionFunc, - rectData; - - // Returning cache? - if (this._cache.query === serialized) - return this._cache.result; - - // Axis aligned ? - if (_geom.isAxisAligned(rect)) { - collisionFunc = _quadIndexes; - rectData = _geom.axisAlignedTopPoints(rect); - } - else { - collisionFunc = _quadCollision; - rectData = _geom.rectangleCorners(rect); - } - - // Retrieving edges - var edges = this._tree ? - _quadRetrieveArea( - rectData, - this._tree, - collisionFunc - ) : - []; - - // Object to array - var edgesArray = []; - for (var i in edges) - edgesArray.push(edges[i]); - - // Caching - this._cache.query = serialized; - this._cache.result = edgesArray; - - return edgesArray; - }; - - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') { - this.sigma.classes = this.sigma.classes || {}; - this.sigma.classes.edgequad = edgequad; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = edgequad; - exports.edgequad = edgequad; - } else - this.edgequad = edgequad; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.captors'); - - /** - * The user inputs default captor. It deals with mouse events, keyboards - * events and touch events. - * - * @param {DOMElement} target The DOM element where the listeners will be - * bound. - * @param {camera} camera The camera related to the target. - * @param {configurable} settings The settings function. - * @return {sigma.captor} The fresh new captor instance. - */ - sigma.captors.mouse = function(target, camera, settings) { - var _self = this, - _target = target, - _camera = camera, - _settings = settings, - - // CAMERA MANAGEMENT: - // ****************** - // The camera position when the user starts dragging: - _startCameraX, - _startCameraY, - _startCameraAngle, - - // The latest stage position: - _lastCameraX, - _lastCameraY, - _lastCameraAngle, - _lastCameraRatio, - - // MOUSE MANAGEMENT: - // ***************** - // The mouse position when the user starts dragging: - _startMouseX, - _startMouseY, - - _isMouseDown, - _isMoving, - _hasDragged, - _downStartTime, - _movingTimeoutId; - - sigma.classes.dispatcher.extend(this); - - sigma.utils.doubleClick(_target, 'click', _doubleClickHandler); - _target.addEventListener('DOMMouseScroll', _wheelHandler, false); - _target.addEventListener('mousewheel', _wheelHandler, false); - _target.addEventListener('mousemove', _moveHandler, false); - _target.addEventListener('mousedown', _downHandler, false); - _target.addEventListener('click', _clickHandler, false); - _target.addEventListener('mouseout', _outHandler, false); - document.addEventListener('mouseup', _upHandler, false); - - - - - /** - * This method unbinds every handlers that makes the captor work. - */ - this.kill = function() { - sigma.utils.unbindDoubleClick(_target, 'click'); - _target.removeEventListener('DOMMouseScroll', _wheelHandler); - _target.removeEventListener('mousewheel', _wheelHandler); - _target.removeEventListener('mousemove', _moveHandler); - _target.removeEventListener('mousedown', _downHandler); - _target.removeEventListener('click', _clickHandler); - _target.removeEventListener('mouseout', _outHandler); - document.removeEventListener('mouseup', _upHandler); - }; - - - - - // MOUSE EVENTS: - // ************* - - /** - * The handler listening to the 'move' mouse event. It will effectively - * drag the graph. - * - * @param {event} e A mouse event. - */ - function _moveHandler(e) { - var x, - y, - pos; - - // Dispatch event: - if (_settings('mouseEnabled')) - _self.dispatchEvent('mousemove', { - x: sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2, - y: sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey - }); - - if (_settings('mouseEnabled') && _isMouseDown) { - _isMoving = true; - _hasDragged = true; - - if (_movingTimeoutId) - clearTimeout(_movingTimeoutId); - - _movingTimeoutId = setTimeout(function() { - _isMoving = false; - }, _settings('dragTimeout')); - - sigma.misc.animation.killAll(_camera); - - _camera.isMoving = true; - pos = _camera.cameraPosition( - sigma.utils.getX(e) - _startMouseX, - sigma.utils.getY(e) - _startMouseY, - true - ); - - x = _startCameraX - pos.x; - y = _startCameraY - pos.y; - - if (x !== _camera.x || y !== _camera.y) { - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - _camera.goTo({ - x: x, - y: y - }); - } - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - } - - /** - * The handler listening to the 'up' mouse event. It will stop dragging the - * graph. - * - * @param {event} e A mouse event. - */ - function _upHandler(e) { - if (_settings('mouseEnabled') && _isMouseDown) { - _isMouseDown = false; - if (_movingTimeoutId) - clearTimeout(_movingTimeoutId); - - _camera.isMoving = false; - - var x = sigma.utils.getX(e), - y = sigma.utils.getY(e); - - if (_isMoving) { - sigma.misc.animation.killAll(_camera); - sigma.misc.animation.camera( - _camera, - { - x: _camera.x + - _settings('mouseInertiaRatio') * (_camera.x - _lastCameraX), - y: _camera.y + - _settings('mouseInertiaRatio') * (_camera.y - _lastCameraY) - }, - { - easing: 'quadraticOut', - duration: _settings('mouseInertiaDuration') - } - ); - } else if ( - _startMouseX !== x || - _startMouseY !== y - ) - _camera.goTo({ - x: _camera.x, - y: _camera.y - }); - - _self.dispatchEvent('mouseup', { - x: x - sigma.utils.getWidth(e) / 2, - y: y - sigma.utils.getHeight(e) / 2, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey - }); - - // Update _isMoving flag: - _isMoving = false; - } - } - - /** - * The handler listening to the 'down' mouse event. It will start observing - * the mouse position for dragging the graph. - * - * @param {event} e A mouse event. - */ - function _downHandler(e) { - if (_settings('mouseEnabled')) { - _startCameraX = _camera.x; - _startCameraY = _camera.y; - - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - _startMouseX = sigma.utils.getX(e); - _startMouseY = sigma.utils.getY(e); - - _hasDragged = false; - _downStartTime = (new Date()).getTime(); - - switch (e.which) { - case 2: - // Middle mouse button pressed - // Do nothing. - break; - case 3: - // Right mouse button pressed - _self.dispatchEvent('rightclick', { - x: _startMouseX - sigma.utils.getWidth(e) / 2, - y: _startMouseY - sigma.utils.getHeight(e) / 2, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey - }); - break; - // case 1: - default: - // Left mouse button pressed - _isMouseDown = true; - - _self.dispatchEvent('mousedown', { - x: _startMouseX - sigma.utils.getWidth(e) / 2, - y: _startMouseY - sigma.utils.getHeight(e) / 2, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey - }); - } - } - } - - /** - * The handler listening to the 'out' mouse event. It will just redispatch - * the event. - * - * @param {event} e A mouse event. - */ - function _outHandler(e) { - if (_settings('mouseEnabled')) - _self.dispatchEvent('mouseout'); - } - - /** - * The handler listening to the 'click' mouse event. It will redispatch the - * click event, but with normalized X and Y coordinates. - * - * @param {event} e A mouse event. - */ - function _clickHandler(e) { - if (_settings('mouseEnabled')) - _self.dispatchEvent('click', { - x: sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2, - y: sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey, - isDragging: - (((new Date()).getTime() - _downStartTime) > 100) && - _hasDragged - }); - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - - /** - * The handler listening to the double click custom event. It will - * basically zoom into the graph. - * - * @param {event} e A mouse event. - */ - function _doubleClickHandler(e) { - var pos, - ratio, - animation; - - if (_settings('mouseEnabled')) { - ratio = 1 / _settings('doubleClickZoomingRatio'); - - _self.dispatchEvent('doubleclick', { - x: _startMouseX - sigma.utils.getWidth(e) / 2, - y: _startMouseY - sigma.utils.getHeight(e) / 2, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey - }); - - if (_settings('doubleClickEnabled')) { - pos = _camera.cameraPosition( - sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2, - sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2, - true - ); - - animation = { - duration: _settings('doubleClickZoomDuration') - }; - - sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation); - } - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - } - - /** - * The handler listening to the 'wheel' mouse event. It will basically zoom - * in or not into the graph. - * - * @param {event} e A mouse event. - */ - function _wheelHandler(e) { - var pos, - ratio, - animation; - - if (_settings('mouseEnabled') && _settings('mouseWheelEnabled')) { - ratio = sigma.utils.getDelta(e) > 0 ? - 1 / _settings('zoomingRatio') : - _settings('zoomingRatio'); - - pos = _camera.cameraPosition( - sigma.utils.getX(e) - sigma.utils.getWidth(e) / 2, - sigma.utils.getY(e) - sigma.utils.getHeight(e) / 2, - true - ); - - animation = { - duration: _settings('mouseZoomDuration') - }; - - sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation); - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - } - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.captors'); - - /** - * The user inputs default captor. It deals with mouse events, keyboards - * events and touch events. - * - * @param {DOMElement} target The DOM element where the listeners will be - * bound. - * @param {camera} camera The camera related to the target. - * @param {configurable} settings The settings function. - * @return {sigma.captor} The fresh new captor instance. - */ - sigma.captors.touch = function(target, camera, settings) { - var _self = this, - _target = target, - _camera = camera, - _settings = settings, - - // CAMERA MANAGEMENT: - // ****************** - // The camera position when the user starts dragging: - _startCameraX, - _startCameraY, - _startCameraAngle, - _startCameraRatio, - - // The latest stage position: - _lastCameraX, - _lastCameraY, - _lastCameraAngle, - _lastCameraRatio, - - // TOUCH MANAGEMENT: - // ***************** - // Touches that are down: - _downTouches = [], - - _startTouchX0, - _startTouchY0, - _startTouchX1, - _startTouchY1, - _startTouchAngle, - _startTouchDistance, - - _touchMode, - - _isMoving, - _doubleTap, - _movingTimeoutId; - - sigma.classes.dispatcher.extend(this); - - sigma.utils.doubleClick(_target, 'touchstart', _doubleTapHandler); - _target.addEventListener('touchstart', _handleStart, false); - _target.addEventListener('touchend', _handleLeave, false); - _target.addEventListener('touchcancel', _handleLeave, false); - _target.addEventListener('touchleave', _handleLeave, false); - _target.addEventListener('touchmove', _handleMove, false); - - function position(e) { - var offset = sigma.utils.getOffset(_target); - - return { - x: e.pageX - offset.left, - y: e.pageY - offset.top - }; - } - - - - - /** - * This method unbinds every handlers that makes the captor work. - */ - this.kill = function() { - sigma.utils.unbindDoubleClick(_target, 'touchstart'); - _target.addEventListener('touchstart', _handleStart); - _target.addEventListener('touchend', _handleLeave); - _target.addEventListener('touchcancel', _handleLeave); - _target.addEventListener('touchleave', _handleLeave); - _target.addEventListener('touchmove', _handleMove); - }; - - - - - // TOUCH EVENTS: - // ************* - /** - * The handler listening to the 'touchstart' event. It will set the touch - * mode ("_touchMode") and start observing the user touch moves. - * - * @param {event} e A touch event. - */ - function _handleStart(e) { - if (_settings('touchEnabled')) { - var x0, - x1, - y0, - y1, - pos0, - pos1; - - _downTouches = e.touches; - - switch (_downTouches.length) { - case 1: - _camera.isMoving = true; - _touchMode = 1; - - _startCameraX = _camera.x; - _startCameraY = _camera.y; - - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - pos0 = position(_downTouches[0]); - _startTouchX0 = pos0.x; - _startTouchY0 = pos0.y; - - break; - case 2: - _camera.isMoving = true; - _touchMode = 2; - - pos0 = position(_downTouches[0]); - pos1 = position(_downTouches[1]); - x0 = pos0.x; - y0 = pos0.y; - x1 = pos1.x; - y1 = pos1.y; - - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - _startCameraAngle = _camera.angle; - _startCameraRatio = _camera.ratio; - - _startCameraX = _camera.x; - _startCameraY = _camera.y; - - _startTouchX0 = x0; - _startTouchY0 = y0; - _startTouchX1 = x1; - _startTouchY1 = y1; - - _startTouchAngle = Math.atan2( - _startTouchY1 - _startTouchY0, - _startTouchX1 - _startTouchX0 - ); - _startTouchDistance = Math.sqrt( - Math.pow(_startTouchY1 - _startTouchY0, 2) + - Math.pow(_startTouchX1 - _startTouchX0, 2) - ); - - e.preventDefault(); - return false; - } - } - } - - /** - * The handler listening to the 'touchend', 'touchcancel' and 'touchleave' - * event. It will update the touch mode if there are still at least one - * finger, and stop dragging else. - * - * @param {event} e A touch event. - */ - function _handleLeave(e) { - if (_settings('touchEnabled')) { - _downTouches = e.touches; - var inertiaRatio = _settings('touchInertiaRatio'); - - if (_movingTimeoutId) { - _isMoving = false; - clearTimeout(_movingTimeoutId); - } - - switch (_touchMode) { - case 2: - if (e.touches.length === 1) { - _handleStart(e); - - e.preventDefault(); - break; - } - /* falls through */ - case 1: - _camera.isMoving = false; - _self.dispatchEvent('stopDrag'); - - if (_isMoving) { - _doubleTap = false; - sigma.misc.animation.camera( - _camera, - { - x: _camera.x + - inertiaRatio * (_camera.x - _lastCameraX), - y: _camera.y + - inertiaRatio * (_camera.y - _lastCameraY) - }, - { - easing: 'quadraticOut', - duration: _settings('touchInertiaDuration') - } - ); - } - - _isMoving = false; - _touchMode = 0; - break; - } - } - } - - /** - * The handler listening to the 'touchmove' event. It will effectively drag - * the graph, and eventually zooms and turn it if the user is using two - * fingers. - * - * @param {event} e A touch event. - */ - function _handleMove(e) { - if (!_doubleTap && _settings('touchEnabled')) { - var x0, - x1, - y0, - y1, - cos, - sin, - end, - pos0, - pos1, - diff, - start, - dAngle, - dRatio, - newStageX, - newStageY, - newStageRatio, - newStageAngle; - - _downTouches = e.touches; - _isMoving = true; - - if (_movingTimeoutId) - clearTimeout(_movingTimeoutId); - - _movingTimeoutId = setTimeout(function() { - _isMoving = false; - }, _settings('dragTimeout')); - - switch (_touchMode) { - case 1: - pos0 = position(_downTouches[0]); - x0 = pos0.x; - y0 = pos0.y; - - diff = _camera.cameraPosition( - x0 - _startTouchX0, - y0 - _startTouchY0, - true - ); - - newStageX = _startCameraX - diff.x; - newStageY = _startCameraY - diff.y; - - if (newStageX !== _camera.x || newStageY !== _camera.y) { - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - _camera.goTo({ - x: newStageX, - y: newStageY - }); - - _self.dispatchEvent('mousemove', { - x: pos0.x - sigma.utils.getWidth(e) / 2, - y: pos0.y - sigma.utils.getHeight(e) / 2, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey - }); - - _self.dispatchEvent('drag'); - } - break; - case 2: - pos0 = position(_downTouches[0]); - pos1 = position(_downTouches[1]); - x0 = pos0.x; - y0 = pos0.y; - x1 = pos1.x; - y1 = pos1.y; - - start = _camera.cameraPosition( - (_startTouchX0 + _startTouchX1) / 2 - - sigma.utils.getWidth(e) / 2, - (_startTouchY0 + _startTouchY1) / 2 - - sigma.utils.getHeight(e) / 2, - true - ); - end = _camera.cameraPosition( - (x0 + x1) / 2 - sigma.utils.getWidth(e) / 2, - (y0 + y1) / 2 - sigma.utils.getHeight(e) / 2, - true - ); - - dAngle = Math.atan2(y1 - y0, x1 - x0) - _startTouchAngle; - dRatio = Math.sqrt( - Math.pow(y1 - y0, 2) + Math.pow(x1 - x0, 2) - ) / _startTouchDistance; - - // Translation: - x0 = start.x; - y0 = start.y; - - // Homothetic transformation: - newStageRatio = _startCameraRatio / dRatio; - x0 = x0 * dRatio; - y0 = y0 * dRatio; - - // Rotation: - newStageAngle = _startCameraAngle - dAngle; - cos = Math.cos(-dAngle); - sin = Math.sin(-dAngle); - x1 = x0 * cos + y0 * sin; - y1 = y0 * cos - x0 * sin; - x0 = x1; - y0 = y1; - - // Finalize: - newStageX = x0 - end.x + _startCameraX; - newStageY = y0 - end.y + _startCameraY; - - if ( - newStageRatio !== _camera.ratio || - newStageAngle !== _camera.angle || - newStageX !== _camera.x || - newStageY !== _camera.y - ) { - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - _lastCameraAngle = _camera.angle; - _lastCameraRatio = _camera.ratio; - - _camera.goTo({ - x: newStageX, - y: newStageY, - angle: newStageAngle, - ratio: newStageRatio - }); - - _self.dispatchEvent('drag'); - } - - break; - } - - e.preventDefault(); - return false; - } - } - - /** - * The handler listening to the double tap custom event. It will - * basically zoom into the graph. - * - * @param {event} e A touch event. - */ - function _doubleTapHandler(e) { - var pos, - ratio, - animation; - - if (e.touches && e.touches.length === 1 && _settings('touchEnabled')) { - _doubleTap = true; - - ratio = 1 / _settings('doubleClickZoomingRatio'); - - pos = position(e.touches[0]); - _self.dispatchEvent('doubleclick', { - x: pos.x - sigma.utils.getWidth(e) / 2, - y: pos.y - sigma.utils.getHeight(e) / 2, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey - }); - - if (_settings('doubleClickEnabled')) { - pos = _camera.cameraPosition( - pos.x - sigma.utils.getWidth(e) / 2, - pos.y - sigma.utils.getHeight(e) / 2, - true - ); - - animation = { - duration: _settings('doubleClickZoomDuration'), - onComplete: function() { - _doubleTap = false; - } - }; - - sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation); - } - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - } - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - if (typeof conrad === 'undefined') - throw 'conrad is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.renderers'); - - /** - * This function is the constructor of the canvas sigma's renderer. - * - * @param {sigma.classes.graph} graph The graph to render. - * @param {sigma.classes.camera} camera The camera. - * @param {configurable} settings The sigma instance settings - * function. - * @param {object} object The options object. - * @return {sigma.renderers.canvas} The renderer instance. - */ - sigma.renderers.canvas = function(graph, camera, settings, options) { - if (typeof options !== 'object') - throw 'sigma.renderers.canvas: Wrong arguments.'; - - if (!(options.container instanceof HTMLElement)) - throw 'Container not found.'; - - var k, - i, - l, - a, - fn, - self = this; - - sigma.classes.dispatcher.extend(this); - - // Initialize main attributes: - Object.defineProperty(this, 'conradId', { - value: sigma.utils.id() - }); - this.graph = graph; - this.camera = camera; - this.contexts = {}; - this.domElements = {}; - this.options = options; - this.container = this.options.container; - this.settings = ( - typeof options.settings === 'object' && - options.settings - ) ? - settings.embedObjects(options.settings) : - settings; - - // Node indexes: - this.nodesOnScreen = []; - this.edgesOnScreen = []; - - // Conrad related attributes: - this.jobs = {}; - - // Find the prefix: - this.options.prefix = 'renderer' + this.conradId + ':'; - - // Initialize the DOM elements: - if ( - !this.settings('batchEdgesDrawing') - ) { - this.initDOM('canvas', 'scene'); - this.contexts.edges = this.contexts.scene; - this.contexts.nodes = this.contexts.scene; - this.contexts.labels = this.contexts.scene; - } else { - this.initDOM('canvas', 'edges'); - this.initDOM('canvas', 'scene'); - this.contexts.nodes = this.contexts.scene; - this.contexts.labels = this.contexts.scene; - } - - this.initDOM('canvas', 'mouse'); - this.contexts.hover = this.contexts.mouse; - - // Initialize captors: - this.captors = []; - a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch]; - for (i = 0, l = a.length; i < l; i++) { - fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]]; - this.captors.push( - new fn( - this.domElements.mouse, - this.camera, - this.settings - ) - ); - } - - // Deal with sigma events: - sigma.misc.bindEvents.call(this, this.options.prefix); - sigma.misc.drawHovers.call(this, this.options.prefix); - - this.resize(false); - }; - - - - - /** - * This method renders the graph on the canvases. - * - * @param {?object} options Eventually an object of options. - * @return {sigma.renderers.canvas} Returns the instance itself. - */ - sigma.renderers.canvas.prototype.render = function(options) { - options = options || {}; - - var a, - i, - k, - l, - o, - id, - end, - job, - start, - edges, - renderers, - rendererType, - batchSize, - tempGCO, - index = {}, - graph = this.graph, - nodes = this.graph.nodes, - prefix = this.options.prefix || '', - drawEdges = this.settings(options, 'drawEdges'), - drawNodes = this.settings(options, 'drawNodes'), - drawLabels = this.settings(options, 'drawLabels'), - drawEdgeLabels = this.settings(options, 'drawEdgeLabels'), - embedSettings = this.settings.embedObjects(options, { - prefix: this.options.prefix - }); - - // Call the resize function: - this.resize(false); - - // Check the 'hideEdgesOnMove' setting: - if (this.settings(options, 'hideEdgesOnMove')) - if (this.camera.isAnimated || this.camera.isMoving) - drawEdges = false; - - // Apply the camera's view: - this.camera.applyView( - undefined, - this.options.prefix, - { - width: this.width, - height: this.height - } - ); - - // Clear canvases: - this.clear(); - - // Kill running jobs: - for (k in this.jobs) - if (conrad.hasJob(k)) - conrad.killJob(k); - - // Find which nodes are on screen: - this.edgesOnScreen = []; - this.nodesOnScreen = this.camera.quadtree.area( - this.camera.getRectangle(this.width, this.height) - ); - - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) - index[a[i].id] = a[i]; - - // Draw edges: - // - If settings('batchEdgesDrawing') is true, the edges are displayed per - // batches. If not, they are drawn in one frame. - if (drawEdges) { - // First, let's identify which edges to draw. To do this, we just keep - // every edges that have at least one extremity displayed according to - // the quadtree and the "hidden" attribute. We also do not keep hidden - // edges. - for (a = graph.edges(), i = 0, l = a.length; i < l; i++) { - o = a[i]; - if ( - (index[o.source] || index[o.target]) && - (!o.hidden && !nodes(o.source).hidden && !nodes(o.target).hidden) - ) - this.edgesOnScreen.push(o); - } - - // If the "batchEdgesDrawing" settings is true, edges are batched: - if (this.settings(options, 'batchEdgesDrawing')) { - id = 'edges_' + this.conradId; - batchSize = embedSettings('canvasEdgesBatchSize'); - - edges = this.edgesOnScreen; - l = edges.length; - - start = 0; - end = Math.min(edges.length, start + batchSize); - - job = function() { - tempGCO = this.contexts.edges.globalCompositeOperation; - this.contexts.edges.globalCompositeOperation = 'destination-over'; - - renderers = sigma.canvas.edges; - for (i = start; i < end; i++) { - o = edges[i]; - (renderers[ - o.type || this.settings(options, 'defaultEdgeType') - ] || renderers.def)( - o, - graph.nodes(o.source), - graph.nodes(o.target), - this.contexts.edges, - embedSettings - ); - } - - // Draw edge labels: - if (drawEdgeLabels) { - renderers = sigma.canvas.edges.labels; - for (i = start; i < end; i++) { - o = edges[i]; - if (!o.hidden) - (renderers[ - o.type || this.settings(options, 'defaultEdgeType') - ] || renderers.def)( - o, - graph.nodes(o.source), - graph.nodes(o.target), - this.contexts.labels, - embedSettings - ); - } - } - - // Restore original globalCompositeOperation: - this.contexts.edges.globalCompositeOperation = tempGCO; - - // Catch job's end: - if (end === edges.length) { - delete this.jobs[id]; - return false; - } - - start = end + 1; - end = Math.min(edges.length, start + batchSize); - return true; - }; - - this.jobs[id] = job; - conrad.addJob(id, job.bind(this)); - - // If not, they are drawn in one frame: - } else { - renderers = sigma.canvas.edges; - for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) { - o = a[i]; - (renderers[ - o.type || this.settings(options, 'defaultEdgeType') - ] || renderers.def)( - o, - graph.nodes(o.source), - graph.nodes(o.target), - this.contexts.edges, - embedSettings - ); - } - - // Draw edge labels: - // - No batching - if (drawEdgeLabels) { - renderers = sigma.canvas.edges.labels; - for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) - if (!a[i].hidden) - (renderers[ - a[i].type || this.settings(options, 'defaultEdgeType') - ] || renderers.def)( - a[i], - graph.nodes(a[i].source), - graph.nodes(a[i].target), - this.contexts.labels, - embedSettings - ); - } - } - } - - // Draw nodes: - // - No batching - if (drawNodes) { - renderers = sigma.canvas.nodes; - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) - if (!a[i].hidden) - (renderers[ - a[i].type || this.settings(options, 'defaultNodeType') - ] || renderers.def)( - a[i], - this.contexts.nodes, - embedSettings - ); - } - - // Draw labels: - // - No batching - if (drawLabels) { - renderers = sigma.canvas.labels; - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) - if (!a[i].hidden) - (renderers[ - a[i].type || this.settings(options, 'defaultNodeType') - ] || renderers.def)( - a[i], - this.contexts.labels, - embedSettings - ); - } - - this.dispatchEvent('render'); - - return this; - }; - - /** - * This method creates a DOM element of the specified type, switches its - * position to "absolute", references it to the domElements attribute, and - * finally appends it to the container. - * - * @param {string} tag The label tag. - * @param {string} id The id of the element (to store it in "domElements"). - */ - sigma.renderers.canvas.prototype.initDOM = function(tag, id) { - var dom = document.createElement(tag); - - dom.style.position = 'absolute'; - dom.setAttribute('class', 'sigma-' + id); - - this.domElements[id] = dom; - this.container.appendChild(dom); - - if (tag.toLowerCase() === 'canvas') - this.contexts[id] = dom.getContext('2d'); - }; - - /** - * This method resizes each DOM elements in the container and stores the new - * dimensions. Then, it renders the graph. - * - * @param {?number} width The new width of the container. - * @param {?number} height The new height of the container. - * @return {sigma.renderers.canvas} Returns the instance itself. - */ - sigma.renderers.canvas.prototype.resize = function(w, h) { - var k, - oldWidth = this.width, - oldHeight = this.height, - pixelRatio = 1; - // TODO: - // ***** - // This pixelRatio is the solution to display with the good definition - // on canvases on Retina displays (ie oversampling). Unfortunately, it - // has a huge performance cost... - // > pixelRatio = window.devicePixelRatio || 1; - - if (w !== undefined && h !== undefined) { - this.width = w; - this.height = h; - } else { - this.width = this.container.offsetWidth; - this.height = this.container.offsetHeight; - - w = this.width; - h = this.height; - } - - if (oldWidth !== this.width || oldHeight !== this.height) { - for (k in this.domElements) { - this.domElements[k].style.width = w + 'px'; - this.domElements[k].style.height = h + 'px'; - - if (this.domElements[k].tagName.toLowerCase() === 'canvas') { - this.domElements[k].setAttribute('width', (w * pixelRatio) + 'px'); - this.domElements[k].setAttribute('height', (h * pixelRatio) + 'px'); - - if (pixelRatio !== 1) - this.contexts[k].scale(pixelRatio, pixelRatio); - } - } - } - - return this; - }; - - /** - * This method clears each canvas. - * - * @return {sigma.renderers.canvas} Returns the instance itself. - */ - sigma.renderers.canvas.prototype.clear = function() { - var k; - - for (k in this.domElements) - if (this.domElements[k].tagName === 'CANVAS') - this.domElements[k].width = this.domElements[k].width; - - return this; - }; - - /** - * This method kills contexts and other attributes. - */ - sigma.renderers.canvas.prototype.kill = function() { - var k, - captor; - - // Kill captors: - while ((captor = this.captors.pop())) - captor.kill(); - delete this.captors; - - // Kill contexts: - for (k in this.domElements) { - this.domElements[k].parentNode.removeChild(this.domElements[k]); - delete this.domElements[k]; - delete this.contexts[k]; - } - delete this.domElements; - delete this.contexts; - }; - - - - - /** - * The labels, nodes and edges renderers are stored in the three following - * objects. When an element is drawn, its type will be checked and if a - * renderer with the same name exists, it will be used. If not found, the - * default renderer will be used instead. - * - * They are stored in different files, in the "./canvas" folder. - */ - sigma.utils.pkg('sigma.canvas.nodes'); - sigma.utils.pkg('sigma.canvas.edges'); - sigma.utils.pkg('sigma.canvas.labels'); -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.renderers'); - - /** - * This function is the constructor of the canvas sigma's renderer. - * - * @param {sigma.classes.graph} graph The graph to render. - * @param {sigma.classes.camera} camera The camera. - * @param {configurable} settings The sigma instance settings - * function. - * @param {object} object The options object. - * @return {sigma.renderers.canvas} The renderer instance. - */ - sigma.renderers.webgl = function(graph, camera, settings, options) { - if (typeof options !== 'object') - throw 'sigma.renderers.webgl: Wrong arguments.'; - - if (!(options.container instanceof HTMLElement)) - throw 'Container not found.'; - - var k, - i, - l, - a, - fn, - _self = this; - - sigma.classes.dispatcher.extend(this); - - // Conrad related attributes: - this.jobs = {}; - - Object.defineProperty(this, 'conradId', { - value: sigma.utils.id() - }); - - // Initialize main attributes: - this.graph = graph; - this.camera = camera; - this.contexts = {}; - this.domElements = {}; - this.options = options; - this.container = this.options.container; - this.settings = ( - typeof options.settings === 'object' && - options.settings - ) ? - settings.embedObjects(options.settings) : - settings; - - // Find the prefix: - this.options.prefix = this.camera.readPrefix; - - // Initialize programs hash - Object.defineProperty(this, 'nodePrograms', { - value: {} - }); - Object.defineProperty(this, 'edgePrograms', { - value: {} - }); - Object.defineProperty(this, 'nodeFloatArrays', { - value: {} - }); - Object.defineProperty(this, 'edgeFloatArrays', { - value: {} - }); - - // Initialize the DOM elements: - if (this.settings(options, 'batchEdgesDrawing')) { - this.initDOM('canvas', 'edges', true); - this.initDOM('canvas', 'nodes', true); - } else { - this.initDOM('canvas', 'scene', true); - this.contexts.nodes = this.contexts.scene; - this.contexts.edges = this.contexts.scene; - } - - this.initDOM('canvas', 'labels'); - this.initDOM('canvas', 'mouse'); - this.contexts.hover = this.contexts.mouse; - - // Initialize captors: - this.captors = []; - a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch]; - for (i = 0, l = a.length; i < l; i++) { - fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]]; - this.captors.push( - new fn( - this.domElements.mouse, - this.camera, - this.settings - ) - ); - } - - // Deal with sigma events: - sigma.misc.bindEvents.call(this, this.camera.prefix); - sigma.misc.drawHovers.call(this, this.camera.prefix); - - this.resize(); - }; - - - - - /** - * This method will generate the nodes and edges float arrays. This step is - * separated from the "render" method, because to keep WebGL efficient, since - * all the camera and middlewares are modelised as matrices and they do not - * require the float arrays to be regenerated. - * - * Basically, when the user moves the camera or applies some specific linear - * transformations, this process step will be skipped, and the "render" - * method will efficiently refresh the rendering. - * - * And when the user modifies the graph colors or positions (applying a new - * layout or filtering the colors, for instance), this "process" step will be - * required to regenerate the float arrays. - * - * @return {sigma.renderers.webgl} Returns the instance itself. - */ - sigma.renderers.webgl.prototype.process = function() { - var a, - i, - l, - k, - type, - renderer, - graph = this.graph, - options = sigma.utils.extend(options, this.options); - - // Empty float arrays: - for (k in this.nodeFloatArrays) - delete this.nodeFloatArrays[k]; - - for (k in this.edgeFloatArrays) - delete this.edgeFloatArrays[k]; - - // Sort edges and nodes per types: - for (a = graph.edges(), i = 0, l = a.length; i < l; i++) { - type = a[i].type || this.settings(options, 'defaultEdgeType'); - k = (type && sigma.webgl.edges[type]) ? type : 'def'; - - if (!this.edgeFloatArrays[k]) - this.edgeFloatArrays[k] = { - edges: [] - }; - - this.edgeFloatArrays[k].edges.push(a[i]); - } - - for (a = graph.nodes(), i = 0, l = a.length; i < l; i++) { - type = a[i].type || this.settings(options, 'defaultNodeType'); - k = (type && sigma.webgl.nodes[type]) ? type : 'def'; - - if (!this.nodeFloatArrays[k]) - this.nodeFloatArrays[k] = { - nodes: [] - }; - - this.nodeFloatArrays[k].nodes.push(a[i]); - } - - // Push edges: - for (k in this.edgeFloatArrays) { - renderer = sigma.webgl.edges[k]; - - for (a = this.edgeFloatArrays[k].edges, i = 0, l = a.length; i < l; i++) { - if (!this.edgeFloatArrays[k].array) - this.edgeFloatArrays[k].array = new Float32Array( - a.length * renderer.POINTS * renderer.ATTRIBUTES - ); - - // Just check that the edge and both its extremities are visible: - if ( - !a[i].hidden && - !graph.nodes(a[i].source).hidden && - !graph.nodes(a[i].target).hidden - ) - renderer.addEdge( - a[i], - graph.nodes(a[i].source), - graph.nodes(a[i].target), - this.edgeFloatArrays[k].array, - i * renderer.POINTS * renderer.ATTRIBUTES, - options.prefix, - this.settings - ); - } - } - - // Push nodes: - for (k in this.nodeFloatArrays) { - renderer = sigma.webgl.nodes[k]; - - for (a = this.nodeFloatArrays[k].nodes, i = 0, l = a.length; i < l; i++) { - if (!this.nodeFloatArrays[k].array) - this.nodeFloatArrays[k].array = new Float32Array( - a.length * renderer.POINTS * renderer.ATTRIBUTES - ); - - // Just check that the edge and both its extremities are visible: - if ( - !a[i].hidden - ) - renderer.addNode( - a[i], - this.nodeFloatArrays[k].array, - i * renderer.POINTS * renderer.ATTRIBUTES, - options.prefix, - this.settings - ); - } - } - - return this; - }; - - - - - /** - * This method renders the graph. It basically calls each program (and - * generate them if they do not exist yet) to render nodes and edges, batched - * per renderer. - * - * As in the canvas renderer, it is possible to display edges, nodes and / or - * labels in batches, to make the whole thing way more scalable. - * - * @param {?object} params Eventually an object of options. - * @return {sigma.renderers.webgl} Returns the instance itself. - */ - sigma.renderers.webgl.prototype.render = function(params) { - var a, - i, - l, - k, - o, - program, - renderer, - self = this, - graph = this.graph, - nodesGl = this.contexts.nodes, - edgesGl = this.contexts.edges, - matrix = this.camera.getMatrix(), - options = sigma.utils.extend(params, this.options), - drawLabels = this.settings(options, 'drawLabels'), - drawEdges = this.settings(options, 'drawEdges'), - drawNodes = this.settings(options, 'drawNodes'); - - // Call the resize function: - this.resize(false); - - // Check the 'hideEdgesOnMove' setting: - if (this.settings(options, 'hideEdgesOnMove')) - if (this.camera.isAnimated || this.camera.isMoving) - drawEdges = false; - - // Clear canvases: - this.clear(); - - // Translate matrix to [width/2, height/2]: - matrix = sigma.utils.matrices.multiply( - matrix, - sigma.utils.matrices.translation(this.width / 2, this.height / 2) - ); - - // Kill running jobs: - for (k in this.jobs) - if (conrad.hasJob(k)) - conrad.killJob(k); - - if (drawEdges) { - if (this.settings(options, 'batchEdgesDrawing')) - (function() { - var a, - k, - i, - id, - job, - arr, - end, - start, - renderer, - batchSize, - currentProgram; - - id = 'edges_' + this.conradId; - batchSize = this.settings(options, 'webglEdgesBatchSize'); - a = Object.keys(this.edgeFloatArrays); - - if (!a.length) - return; - i = 0; - renderer = sigma.webgl.edges[a[i]]; - arr = this.edgeFloatArrays[a[i]].array; - start = 0; - end = Math.min( - start + batchSize * renderer.POINTS, - arr.length / renderer.ATTRIBUTES - ); - - job = function() { - // Check program: - if (!this.edgePrograms[a[i]]) - this.edgePrograms[a[i]] = renderer.initProgram(edgesGl); - - if (start < end) { - edgesGl.useProgram(this.edgePrograms[a[i]]); - renderer.render( - edgesGl, - this.edgePrograms[a[i]], - arr, - { - settings: this.settings, - matrix: matrix, - width: this.width, - height: this.height, - ratio: this.camera.ratio, - scalingRatio: this.settings( - options, - 'webglOversamplingRatio' - ), - start: start, - count: end - start - } - ); - } - - // Catch job's end: - if ( - end >= arr.length / renderer.ATTRIBUTES && - i === a.length - 1 - ) { - delete this.jobs[id]; - return false; - } - - if (end >= arr.length / renderer.ATTRIBUTES) { - i++; - arr = this.edgeFloatArrays[a[i]].array; - renderer = sigma.webgl.edges[a[i]]; - start = 0; - end = Math.min( - start + batchSize * renderer.POINTS, - arr.length / renderer.ATTRIBUTES - ); - } else { - start = end; - end = Math.min( - start + batchSize * renderer.POINTS, - arr.length / renderer.ATTRIBUTES - ); - } - - return true; - }; - - this.jobs[id] = job; - conrad.addJob(id, job.bind(this)); - }).call(this); - else { - for (k in this.edgeFloatArrays) { - renderer = sigma.webgl.edges[k]; - - // Check program: - if (!this.edgePrograms[k]) - this.edgePrograms[k] = renderer.initProgram(edgesGl); - - // Render - if (this.edgeFloatArrays[k]) { - edgesGl.useProgram(this.edgePrograms[k]); - renderer.render( - edgesGl, - this.edgePrograms[k], - this.edgeFloatArrays[k].array, - { - settings: this.settings, - matrix: matrix, - width: this.width, - height: this.height, - ratio: this.camera.ratio, - scalingRatio: this.settings(options, 'webglOversamplingRatio') - } - ); - } - } - } - } - - if (drawNodes) { - // Enable blending: - nodesGl.blendFunc(nodesGl.SRC_ALPHA, nodesGl.ONE_MINUS_SRC_ALPHA); - nodesGl.enable(nodesGl.BLEND); - - for (k in this.nodeFloatArrays) { - renderer = sigma.webgl.nodes[k]; - - // Check program: - if (!this.nodePrograms[k]) - this.nodePrograms[k] = renderer.initProgram(nodesGl); - - // Render - if (this.nodeFloatArrays[k]) { - nodesGl.useProgram(this.nodePrograms[k]); - renderer.render( - nodesGl, - this.nodePrograms[k], - this.nodeFloatArrays[k].array, - { - settings: this.settings, - matrix: matrix, - width: this.width, - height: this.height, - ratio: this.camera.ratio, - scalingRatio: this.settings(options, 'webglOversamplingRatio') - } - ); - } - } - } - - if (drawLabels) { - a = this.camera.quadtree.area( - this.camera.getRectangle(this.width, this.height) - ); - - // Apply camera view to these nodes: - this.camera.applyView( - undefined, - undefined, - { - nodes: a, - edges: [], - width: this.width, - height: this.height - } - ); - - o = function(key) { - return self.settings({ - prefix: self.camera.prefix - }, key); - }; - - for (i = 0, l = a.length; i < l; i++) - if (!a[i].hidden) - ( - sigma.canvas.labels[ - a[i].type || - this.settings(options, 'defaultNodeType') - ] || sigma.canvas.labels.def - )(a[i], this.contexts.labels, o); - } - - this.dispatchEvent('render'); - - return this; - }; - - - - - /** - * This method creates a DOM element of the specified type, switches its - * position to "absolute", references it to the domElements attribute, and - * finally appends it to the container. - * - * @param {string} tag The label tag. - * @param {string} id The id of the element (to store it in - * "domElements"). - * @param {?boolean} webgl Will init the WebGL context if true. - */ - sigma.renderers.webgl.prototype.initDOM = function(tag, id, webgl) { - var gl, - dom = document.createElement(tag), - self = this; - - dom.style.position = 'absolute'; - dom.setAttribute('class', 'sigma-' + id); - - this.domElements[id] = dom; - this.container.appendChild(dom); - - if (tag.toLowerCase() === 'canvas') { - this.contexts[id] = dom.getContext(webgl ? 'experimental-webgl' : '2d', { - preserveDrawingBuffer: true - }); - - // Adding webgl context loss listeners - if (webgl) { - dom.addEventListener('webglcontextlost', function(e) { - e.preventDefault(); - }, false); - - dom.addEventListener('webglcontextrestored', function(e) { - self.render(); - }, false); - } - } - }; - - /** - * This method resizes each DOM elements in the container and stores the new - * dimensions. Then, it renders the graph. - * - * @param {?number} width The new width of the container. - * @param {?number} height The new height of the container. - * @return {sigma.renderers.webgl} Returns the instance itself. - */ - sigma.renderers.webgl.prototype.resize = function(w, h) { - var k, - oldWidth = this.width, - oldHeight = this.height; - - if (w !== undefined && h !== undefined) { - this.width = w; - this.height = h; - } else { - this.width = this.container.offsetWidth; - this.height = this.container.offsetHeight; - - w = this.width; - h = this.height; - } - - if (oldWidth !== this.width || oldHeight !== this.height) { - for (k in this.domElements) { - this.domElements[k].style.width = w + 'px'; - this.domElements[k].style.height = h + 'px'; - - if (this.domElements[k].tagName.toLowerCase() === 'canvas') { - // If simple 2D canvas: - if (this.contexts[k] && this.contexts[k].scale) { - this.domElements[k].setAttribute('width', w + 'px'); - this.domElements[k].setAttribute('height', h + 'px'); - } else { - this.domElements[k].setAttribute( - 'width', - (w * this.settings('webglOversamplingRatio')) + 'px' - ); - this.domElements[k].setAttribute( - 'height', - (h * this.settings('webglOversamplingRatio')) + 'px' - ); - } - } - } - } - - // Scale: - for (k in this.contexts) - if (this.contexts[k] && this.contexts[k].viewport) - this.contexts[k].viewport( - 0, - 0, - this.width * this.settings('webglOversamplingRatio'), - this.height * this.settings('webglOversamplingRatio') - ); - - return this; - }; - - /** - * This method clears each canvas. - * - * @return {sigma.renderers.webgl} Returns the instance itself. - */ - sigma.renderers.webgl.prototype.clear = function() { - var k; - - for (k in this.domElements) - if (this.domElements[k].tagName === 'CANVAS') - this.domElements[k].width = this.domElements[k].width; - - this.contexts.nodes.clear(this.contexts.nodes.COLOR_BUFFER_BIT); - this.contexts.edges.clear(this.contexts.edges.COLOR_BUFFER_BIT); - - return this; - }; - - /** - * This method kills contexts and other attributes. - */ - sigma.renderers.webgl.prototype.kill = function() { - var k, - captor; - - // Kill captors: - while ((captor = this.captors.pop())) - captor.kill(); - delete this.captors; - - // Kill contexts: - for (k in this.domElements) { - this.domElements[k].parentNode.removeChild(this.domElements[k]); - delete this.domElements[k]; - delete this.contexts[k]; - } - delete this.domElements; - delete this.contexts; - }; - - - - - /** - * The object "sigma.webgl.nodes" contains the different WebGL node - * renderers. The default one draw nodes as discs. Here are the attributes - * any node renderer must have: - * - * {number} POINTS The number of points required to draw a node. - * {number} ATTRIBUTES The number of attributes needed to draw one point. - * {function} addNode A function that adds a node to the data stack that - * will be given to the buffer. Here is the arguments: - * > {object} node - * > {number} index The node index in the - * nodes array. - * > {Float32Array} data The stack. - * > {object} options Some options. - * {function} render The function that will effectively render the nodes - * into the buffer. - * > {WebGLRenderingContext} gl - * > {WebGLProgram} program - * > {Float32Array} data The stack to give to the - * buffer. - * > {object} params An object containing some - * options, like width, - * height, the camera ratio. - * {function} initProgram The function that will initiate the program, with - * the relevant shaders and parameters. It must return - * the newly created program. - * - * Check sigma.webgl.nodes.def or sigma.webgl.nodes.fast to see how it - * works more precisely. - */ - sigma.utils.pkg('sigma.webgl.nodes'); - - - - - /** - * The object "sigma.webgl.edges" contains the different WebGL edge - * renderers. The default one draw edges as direct lines. Here are the - * attributes any edge renderer must have: - * - * {number} POINTS The number of points required to draw an edge. - * {number} ATTRIBUTES The number of attributes needed to draw one point. - * {function} addEdge A function that adds an edge to the data stack that - * will be given to the buffer. Here is the arguments: - * > {object} edge - * > {object} source - * > {object} target - * > {Float32Array} data The stack. - * > {object} options Some options. - * {function} render The function that will effectively render the edges - * into the buffer. - * > {WebGLRenderingContext} gl - * > {WebGLProgram} program - * > {Float32Array} data The stack to give to the - * buffer. - * > {object} params An object containing some - * options, like width, - * height, the camera ratio. - * {function} initProgram The function that will initiate the program, with - * the relevant shaders and parameters. It must return - * the newly created program. - * - * Check sigma.webgl.edges.def or sigma.webgl.edges.fast to see how it - * works more precisely. - */ - sigma.utils.pkg('sigma.webgl.edges'); - - - - - /** - * The object "sigma.canvas.labels" contains the different - * label renderers for the WebGL renderer. Since displaying texts in WebGL is - * definitely painful and since there a way less labels to display than nodes - * or edges, the default renderer simply renders them in a canvas. - * - * A labels renderer is a simple function, taking as arguments the related - * node, the renderer and a settings function. - */ - sigma.utils.pkg('sigma.canvas.labels'); -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - if (typeof conrad === 'undefined') - throw 'conrad is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.renderers'); - - /** - * This function is the constructor of the svg sigma's renderer. - * - * @param {sigma.classes.graph} graph The graph to render. - * @param {sigma.classes.camera} camera The camera. - * @param {configurable} settings The sigma instance settings - * function. - * @param {object} object The options object. - * @return {sigma.renderers.svg} The renderer instance. - */ - sigma.renderers.svg = function(graph, camera, settings, options) { - if (typeof options !== 'object') - throw 'sigma.renderers.svg: Wrong arguments.'; - - if (!(options.container instanceof HTMLElement)) - throw 'Container not found.'; - - var i, - l, - a, - fn, - self = this; - - sigma.classes.dispatcher.extend(this); - - // Initialize main attributes: - this.graph = graph; - this.camera = camera; - this.domElements = { - graph: null, - groups: {}, - nodes: {}, - edges: {}, - labels: {}, - hovers: {} - }; - this.measurementCanvas = null; - this.options = options; - this.container = this.options.container; - this.settings = ( - typeof options.settings === 'object' && - options.settings - ) ? - settings.embedObjects(options.settings) : - settings; - - // Is the renderer meant to be freestyle? - this.settings('freeStyle', !!this.options.freeStyle); - - // SVG xmlns - this.settings('xmlns', 'http://www.w3.org/2000/svg'); - - // Indexes: - this.nodesOnScreen = []; - this.edgesOnScreen = []; - - // Find the prefix: - this.options.prefix = 'renderer' + sigma.utils.id() + ':'; - - // Initialize the DOM elements - this.initDOM('svg'); - - // Initialize captors: - this.captors = []; - a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch]; - for (i = 0, l = a.length; i < l; i++) { - fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]]; - this.captors.push( - new fn( - this.domElements.graph, - this.camera, - this.settings - ) - ); - } - - // Bind resize: - window.addEventListener('resize', function() { - self.resize(); - }); - - // Deal with sigma events: - // TODO: keep an option to override the DOM events? - sigma.misc.bindDOMEvents.call(this, this.domElements.graph); - this.bindHovers(this.options.prefix); - - // Resize - this.resize(false); - }; - - /** - * This method renders the graph on the svg scene. - * - * @param {?object} options Eventually an object of options. - * @return {sigma.renderers.svg} Returns the instance itself. - */ - sigma.renderers.svg.prototype.render = function(options) { - options = options || {}; - - var a, - i, - k, - e, - l, - o, - source, - target, - start, - edges, - renderers, - subrenderers, - index = {}, - graph = this.graph, - nodes = this.graph.nodes, - prefix = this.options.prefix || '', - drawEdges = this.settings(options, 'drawEdges'), - drawNodes = this.settings(options, 'drawNodes'), - drawLabels = this.settings(options, 'drawLabels'), - embedSettings = this.settings.embedObjects(options, { - prefix: this.options.prefix, - forceLabels: this.options.forceLabels - }); - - // Check the 'hideEdgesOnMove' setting: - if (this.settings(options, 'hideEdgesOnMove')) - if (this.camera.isAnimated || this.camera.isMoving) - drawEdges = false; - - // Apply the camera's view: - this.camera.applyView( - undefined, - this.options.prefix, - { - width: this.width, - height: this.height - } - ); - - // Hiding everything - // TODO: find a more sensible way to perform this operation - this.hideDOMElements(this.domElements.nodes); - this.hideDOMElements(this.domElements.edges); - this.hideDOMElements(this.domElements.labels); - - // Find which nodes are on screen - this.edgesOnScreen = []; - this.nodesOnScreen = this.camera.quadtree.area( - this.camera.getRectangle(this.width, this.height) - ); - - // Node index - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) - index[a[i].id] = a[i]; - - // Find which edges are on screen - for (a = graph.edges(), i = 0, l = a.length; i < l; i++) { - o = a[i]; - if ( - (index[o.source] || index[o.target]) && - (!o.hidden && !nodes(o.source).hidden && !nodes(o.target).hidden) - ) - this.edgesOnScreen.push(o); - } - - // Display nodes - //--------------- - renderers = sigma.svg.nodes; - subrenderers = sigma.svg.labels; - - //-- First we create the nodes which are not already created - if (drawNodes) - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) { - if (!a[i].hidden && !this.domElements.nodes[a[i].id]) { - - // Node - e = (renderers[a[i].type] || renderers.def).create( - a[i], - embedSettings - ); - - this.domElements.nodes[a[i].id] = e; - this.domElements.groups.nodes.appendChild(e); - - // Label - e = (subrenderers[a[i].type] || subrenderers.def).create( - a[i], - embedSettings - ); - - this.domElements.labels[a[i].id] = e; - this.domElements.groups.labels.appendChild(e); - } - } - - //-- Second we update the nodes - if (drawNodes) - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) { - - if (a[i].hidden) - continue; - - // Node - (renderers[a[i].type] || renderers.def).update( - a[i], - this.domElements.nodes[a[i].id], - embedSettings - ); - - // Label - (subrenderers[a[i].type] || subrenderers.def).update( - a[i], - this.domElements.labels[a[i].id], - embedSettings - ); - } - - // Display edges - //--------------- - renderers = sigma.svg.edges; - - //-- First we create the edges which are not already created - if (drawEdges) - for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) { - if (!this.domElements.edges[a[i].id]) { - source = nodes(a[i].source); - target = nodes(a[i].target); - - e = (renderers[a[i].type] || renderers.def).create( - a[i], - source, - target, - embedSettings - ); - - this.domElements.edges[a[i].id] = e; - this.domElements.groups.edges.appendChild(e); - } - } - - //-- Second we update the edges - if (drawEdges) - for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) { - source = nodes(a[i].source); - target = nodes(a[i].target); - - (renderers[a[i].type] || renderers.def).update( - a[i], - this.domElements.edges[a[i].id], - source, - target, - embedSettings - ); - } - - this.dispatchEvent('render'); - - return this; - }; - - /** - * This method creates a DOM element of the specified type, switches its - * position to "absolute", references it to the domElements attribute, and - * finally appends it to the container. - * - * @param {string} tag The label tag. - * @param {string} id The id of the element (to store it in "domElements"). - */ - sigma.renderers.svg.prototype.initDOM = function(tag) { - var dom = document.createElementNS(this.settings('xmlns'), tag), - c = this.settings('classPrefix'), - g, - l, - i; - - dom.style.position = 'absolute'; - dom.setAttribute('class', c + '-svg'); - - // Setting SVG namespace - dom.setAttribute('xmlns', this.settings('xmlns')); - dom.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); - dom.setAttribute('version', '1.1'); - - // Creating the measurement canvas - var canvas = document.createElement('canvas'); - canvas.setAttribute('class', c + '-measurement-canvas'); - - // Appending elements - this.domElements.graph = this.container.appendChild(dom); - - // Creating groups - var groups = ['edges', 'nodes', 'labels', 'hovers']; - for (i = 0, l = groups.length; i < l; i++) { - g = document.createElementNS(this.settings('xmlns'), 'g'); - - g.setAttributeNS(null, 'id', c + '-group-' + groups[i]); - g.setAttributeNS(null, 'class', c + '-group'); - - this.domElements.groups[groups[i]] = - this.domElements.graph.appendChild(g); - } - - // Appending measurement canvas - this.container.appendChild(canvas); - this.measurementCanvas = canvas.getContext('2d'); - }; - - /** - * This method hides a batch of SVG DOM elements. - * - * @param {array} elements An array of elements to hide. - * @param {object} renderer The renderer to use. - * @return {sigma.renderers.svg} Returns the instance itself. - */ - sigma.renderers.svg.prototype.hideDOMElements = function(elements) { - var o, - i; - - for (i in elements) { - o = elements[i]; - sigma.svg.utils.hide(o); - } - - return this; - }; - - /** - * This method binds the hover events to the renderer. - * - * @param {string} prefix The renderer prefix. - */ - // TODO: add option about whether to display hovers or not - sigma.renderers.svg.prototype.bindHovers = function(prefix) { - var renderers = sigma.svg.hovers, - self = this, - hoveredNode; - - function overNode(e) { - var node = e.data.node, - embedSettings = self.settings.embedObjects({ - prefix: prefix - }); - - if (!embedSettings('enableHovering')) - return; - - var hover = (renderers[node.type] || renderers.def).create( - node, - self.domElements.nodes[node.id], - self.measurementCanvas, - embedSettings - ); - - self.domElements.hovers[node.id] = hover; - - // Inserting the hover in the dom - self.domElements.groups.hovers.appendChild(hover); - hoveredNode = node; - } - - function outNode(e) { - var node = e.data.node, - embedSettings = self.settings.embedObjects({ - prefix: prefix - }); - - if (!embedSettings('enableHovering')) - return; - - // Deleting element - self.domElements.groups.hovers.removeChild( - self.domElements.hovers[node.id] - ); - hoveredNode = null; - delete self.domElements.hovers[node.id]; - - // Reinstate - self.domElements.groups.nodes.appendChild( - self.domElements.nodes[node.id] - ); - } - - // OPTIMIZE: perform a real update rather than a deletion - function update() { - if (!hoveredNode) - return; - - var embedSettings = self.settings.embedObjects({ - prefix: prefix - }); - - // Deleting element before update - self.domElements.groups.hovers.removeChild( - self.domElements.hovers[hoveredNode.id] - ); - delete self.domElements.hovers[hoveredNode.id]; - - var hover = (renderers[hoveredNode.type] || renderers.def).create( - hoveredNode, - self.domElements.nodes[hoveredNode.id], - self.measurementCanvas, - embedSettings - ); - - self.domElements.hovers[hoveredNode.id] = hover; - - // Inserting the hover in the dom - self.domElements.groups.hovers.appendChild(hover); - } - - // Binding events - this.bind('overNode', overNode); - this.bind('outNode', outNode); - - // Update on render - this.bind('render', update); - }; - - /** - * This method resizes each DOM elements in the container and stores the new - * dimensions. Then, it renders the graph. - * - * @param {?number} width The new width of the container. - * @param {?number} height The new height of the container. - * @return {sigma.renderers.svg} Returns the instance itself. - */ - sigma.renderers.svg.prototype.resize = function(w, h) { - var oldWidth = this.width, - oldHeight = this.height, - pixelRatio = 1; - - if (w !== undefined && h !== undefined) { - this.width = w; - this.height = h; - } else { - this.width = this.container.offsetWidth; - this.height = this.container.offsetHeight; - - w = this.width; - h = this.height; - } - - if (oldWidth !== this.width || oldHeight !== this.height) { - this.domElements.graph.style.width = w + 'px'; - this.domElements.graph.style.height = h + 'px'; - - if (this.domElements.graph.tagName.toLowerCase() === 'svg') { - this.domElements.graph.setAttribute('width', (w * pixelRatio)); - this.domElements.graph.setAttribute('height', (h * pixelRatio)); - } - } - - return this; - }; - - - /** - * The labels, nodes and edges renderers are stored in the three following - * objects. When an element is drawn, its type will be checked and if a - * renderer with the same name exists, it will be used. If not found, the - * default renderer will be used instead. - * - * They are stored in different files, in the "./svg" folder. - */ - sigma.utils.pkg('sigma.svg.nodes'); - sigma.utils.pkg('sigma.svg.edges'); - sigma.utils.pkg('sigma.svg.labels'); -}).call(this); - -;(function(global) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.renderers'); - - // Check if WebGL is enabled: - var canvas, - webgl = !!global.WebGLRenderingContext; - if (webgl) { - canvas = document.createElement('canvas'); - try { - webgl = !!( - canvas.getContext('webgl') || - canvas.getContext('experimental-webgl') - ); - } catch (e) { - webgl = false; - } - } - - // Copy the good renderer: - sigma.renderers.def = webgl ? - sigma.renderers.webgl : - sigma.renderers.canvas; -})(this); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.nodes'); - - /** - * This node renderer will display nodes as discs, shaped in triangles with - * the gl.TRIANGLES display mode. So, to be more precise, to draw one node, - * it will store three times the center of node, with the color and the size, - * and an angle indicating which "corner" of the triangle to draw. - * - * The fragment shader does not deal with anti-aliasing, so make sure that - * you deal with it somewhere else in the code (by default, the WebGL - * renderer will oversample the rendering through the webglOversamplingRatio - * value). - */ - sigma.webgl.nodes.def = { - POINTS: 3, - ATTRIBUTES: 5, - addNode: function(node, data, i, prefix, settings) { - var color = sigma.utils.floatColor( - node.color || settings('defaultNodeColor') - ); - - data[i++] = node[prefix + 'x']; - data[i++] = node[prefix + 'y']; - data[i++] = node[prefix + 'size']; - data[i++] = color; - data[i++] = 0; - - data[i++] = node[prefix + 'x']; - data[i++] = node[prefix + 'y']; - data[i++] = node[prefix + 'size']; - data[i++] = color; - data[i++] = 2 * Math.PI / 3; - - data[i++] = node[prefix + 'x']; - data[i++] = node[prefix + 'y']; - data[i++] = node[prefix + 'size']; - data[i++] = color; - data[i++] = 4 * Math.PI / 3; - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var positionLocation = - gl.getAttribLocation(program, 'a_position'), - sizeLocation = - gl.getAttribLocation(program, 'a_size'), - colorLocation = - gl.getAttribLocation(program, 'a_color'), - angleLocation = - gl.getAttribLocation(program, 'a_angle'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'), - ratioLocation = - gl.getUniformLocation(program, 'u_ratio'), - scaleLocation = - gl.getUniformLocation(program, 'u_scale'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniform1f( - ratioLocation, - 1 / Math.pow(params.ratio, params.settings('nodesPowRatio')) - ); - gl.uniform1f(scaleLocation, params.scalingRatio); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - - gl.enableVertexAttribArray(positionLocation); - gl.enableVertexAttribArray(sizeLocation); - gl.enableVertexAttribArray(colorLocation); - gl.enableVertexAttribArray(angleLocation); - - gl.vertexAttribPointer( - positionLocation, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer( - sizeLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - gl.vertexAttribPointer( - colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 12 - ); - gl.vertexAttribPointer( - angleLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 16 - ); - - gl.drawArrays( - gl.TRIANGLES, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_position;', - 'attribute float a_size;', - 'attribute float a_color;', - 'attribute float a_angle;', - - 'uniform vec2 u_resolution;', - 'uniform float u_ratio;', - 'uniform float u_scale;', - 'uniform mat3 u_matrix;', - - 'varying vec4 color;', - 'varying vec2 center;', - 'varying float radius;', - - 'void main() {', - // Multiply the point size twice: - 'radius = a_size * u_ratio;', - - // Scale from [[-1 1] [-1 1]] to the container: - 'vec2 position = (u_matrix * vec3(a_position, 1)).xy;', - // 'center = (position / u_resolution * 2.0 - 1.0) * vec2(1, -1);', - 'center = position * u_scale;', - 'center = vec2(center.x, u_scale * u_resolution.y - center.y);', - - 'position = position +', - '2.0 * radius * vec2(cos(a_angle), sin(a_angle));', - 'position = (position / u_resolution * 2.0 - 1.0) * vec2(1, -1);', - - 'radius = radius * u_scale;', - - 'gl_Position = vec4(position, 0, 1);', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - 'varying vec2 center;', - 'varying float radius;', - - 'void main(void) {', - 'vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);', - - 'vec2 m = gl_FragCoord.xy - center;', - 'float diff = radius - sqrt(m.x * m.x + m.y * m.y);', - - // Here is how we draw a disc instead of a square: - 'if (diff > 0.0)', - 'gl_FragColor = color;', - 'else', - 'gl_FragColor = color0;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.nodes'); - - /** - * This node renderer will display nodes in the fastest way: Nodes are basic - * squares, drawn through the gl.POINTS drawing method. The size of the nodes - * are represented with the "gl_PointSize" value in the vertex shader. - * - * It is the fastest node renderer here since the buffer just takes one line - * to draw each node (with attributes "x", "y", "size" and "color"). - * - * Nevertheless, this method has some problems, especially due to some issues - * with the gl.POINTS: - * - First, if the center of a node is outside the scene, the point will not - * be drawn, even if it should be partly on screen. - * - I tried applying a fragment shader similar to the one in the default - * node renderer to display them as discs, but it did not work fine on - * some computers settings, filling the discs with weird gradients not - * depending on the actual color. - */ - sigma.webgl.nodes.fast = { - POINTS: 1, - ATTRIBUTES: 4, - addNode: function(node, data, i, prefix, settings) { - data[i++] = node[prefix + 'x']; - data[i++] = node[prefix + 'y']; - data[i++] = node[prefix + 'size']; - data[i++] = sigma.utils.floatColor( - node.color || settings('defaultNodeColor') - ); - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var positionLocation = - gl.getAttribLocation(program, 'a_position'), - sizeLocation = - gl.getAttribLocation(program, 'a_size'), - colorLocation = - gl.getAttribLocation(program, 'a_color'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'), - ratioLocation = - gl.getUniformLocation(program, 'u_ratio'), - scaleLocation = - gl.getUniformLocation(program, 'u_scale'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniform1f( - ratioLocation, - 1 / Math.pow(params.ratio, params.settings('nodesPowRatio')) - ); - gl.uniform1f(scaleLocation, params.scalingRatio); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - - gl.enableVertexAttribArray(positionLocation); - gl.enableVertexAttribArray(sizeLocation); - gl.enableVertexAttribArray(colorLocation); - - gl.vertexAttribPointer( - positionLocation, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer( - sizeLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - gl.vertexAttribPointer( - colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 12 - ); - - gl.drawArrays( - gl.POINTS, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_position;', - 'attribute float a_size;', - 'attribute float a_color;', - - 'uniform vec2 u_resolution;', - 'uniform float u_ratio;', - 'uniform float u_scale;', - 'uniform mat3 u_matrix;', - - 'varying vec4 color;', - - 'void main() {', - // Scale from [[-1 1] [-1 1]] to the container: - 'gl_Position = vec4(', - '((u_matrix * vec3(a_position, 1)).xy /', - 'u_resolution * 2.0 - 1.0) * vec2(1, -1),', - '0,', - '1', - ');', - - // Multiply the point size twice: - // - x SCALING_RATIO to correct the canvas scaling - // - x 2 to correct the formulae - 'gl_PointSize = a_size * u_ratio * u_scale * 2.0;', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - - 'void main(void) {', - 'gl_FragColor = color;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.edges'); - - /** - * This edge renderer will display edges as lines going from the source node - * to the target node. To deal with edge thicknesses, the lines are made of - * two triangles forming rectangles, with the gl.TRIANGLES drawing mode. - * - * It is expensive, since drawing a single edge requires 6 points, each - * having 7 attributes (source position, target position, thickness, color - * and a flag indicating which vertice of the rectangle it is). - */ - sigma.webgl.edges.def = { - POINTS: 6, - ATTRIBUTES: 7, - addEdge: function(edge, source, target, data, i, prefix, settings) { - var w = (edge[prefix + 'size'] || 1) / 2, - x1 = source[prefix + 'x'], - y1 = source[prefix + 'y'], - x2 = target[prefix + 'x'], - y2 = target[prefix + 'y'], - color = edge.color; - - if (!color) - switch (settings('edgeColor')) { - case 'source': - color = source.color || settings('defaultNodeColor'); - break; - case 'target': - color = target.color || settings('defaultNodeColor'); - break; - default: - color = settings('defaultEdgeColor'); - break; - } - - // Normalize color: - color = sigma.utils.floatColor(color); - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = 1.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = 1.0; - data[i++] = color; - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = 0.0; - data[i++] = color; - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var colorLocation = - gl.getAttribLocation(program, 'a_color'), - positionLocation1 = - gl.getAttribLocation(program, 'a_position1'), - positionLocation2 = - gl.getAttribLocation(program, 'a_position2'), - thicknessLocation = - gl.getAttribLocation(program, 'a_thickness'), - minusLocation = - gl.getAttribLocation(program, 'a_minus'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'), - matrixHalfPiLocation = - gl.getUniformLocation(program, 'u_matrixHalfPi'), - matrixHalfPiMinusLocation = - gl.getUniformLocation(program, 'u_matrixHalfPiMinus'), - ratioLocation = - gl.getUniformLocation(program, 'u_ratio'), - scaleLocation = - gl.getUniformLocation(program, 'u_scale'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniform1f( - ratioLocation, - params.ratio / Math.pow(params.ratio, params.settings('edgesPowRatio')) - ); - gl.uniform1f(scaleLocation, params.scalingRatio); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - gl.uniformMatrix2fv( - matrixHalfPiLocation, - false, - sigma.utils.matrices.rotation(Math.PI / 2, true) - ); - gl.uniformMatrix2fv( - matrixHalfPiMinusLocation, - false, - sigma.utils.matrices.rotation(-Math.PI / 2, true) - ); - - gl.enableVertexAttribArray(colorLocation); - gl.enableVertexAttribArray(positionLocation1); - gl.enableVertexAttribArray(positionLocation2); - gl.enableVertexAttribArray(thicknessLocation); - gl.enableVertexAttribArray(minusLocation); - - gl.vertexAttribPointer(positionLocation1, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer(positionLocation2, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - gl.vertexAttribPointer(thicknessLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 16 - ); - gl.vertexAttribPointer(minusLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 20 - ); - gl.vertexAttribPointer(colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 24 - ); - - gl.drawArrays( - gl.TRIANGLES, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_position1;', - 'attribute vec2 a_position2;', - 'attribute float a_thickness;', - 'attribute float a_minus;', - 'attribute float a_color;', - - 'uniform vec2 u_resolution;', - 'uniform float u_ratio;', - 'uniform float u_scale;', - 'uniform mat3 u_matrix;', - 'uniform mat2 u_matrixHalfPi;', - 'uniform mat2 u_matrixHalfPiMinus;', - - 'varying vec4 color;', - - 'void main() {', - // Find the good point: - 'vec2 position = a_thickness * u_ratio *', - 'normalize(a_position2 - a_position1);', - - 'mat2 matrix = a_minus * u_matrixHalfPiMinus +', - '(1.0 - a_minus) * u_matrixHalfPi;', - - 'position = matrix * position + a_position1;', - - // Scale from [[-1 1] [-1 1]] to the container: - 'gl_Position = vec4(', - '((u_matrix * vec3(position, 1)).xy /', - 'u_resolution * 2.0 - 1.0) * vec2(1, -1),', - '0,', - '1', - ');', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - - 'void main(void) {', - 'gl_FragColor = color;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.edges'); - - /** - * This edge renderer will display edges as lines with the gl.LINES display - * mode. Since this mode does not support well thickness, edges are all drawn - * with the same thickness (3px), independantly of the edge attributes or the - * zooming ratio. - */ - sigma.webgl.edges.fast = { - POINTS: 2, - ATTRIBUTES: 3, - addEdge: function(edge, source, target, data, i, prefix, settings) { - var w = (edge[prefix + 'size'] || 1) / 2, - x1 = source[prefix + 'x'], - y1 = source[prefix + 'y'], - x2 = target[prefix + 'x'], - y2 = target[prefix + 'y'], - color = edge.color; - - if (!color) - switch (settings('edgeColor')) { - case 'source': - color = source.color || settings('defaultNodeColor'); - break; - case 'target': - color = target.color || settings('defaultNodeColor'); - break; - default: - color = settings('defaultEdgeColor'); - break; - } - - // Normalize color: - color = sigma.utils.floatColor(color); - - data[i++] = x1; - data[i++] = y1; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = color; - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var colorLocation = - gl.getAttribLocation(program, 'a_color'), - positionLocation = - gl.getAttribLocation(program, 'a_position'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - - gl.enableVertexAttribArray(positionLocation); - gl.enableVertexAttribArray(colorLocation); - - gl.vertexAttribPointer(positionLocation, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer(colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - - gl.lineWidth(3); - gl.drawArrays( - gl.LINES, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_position;', - 'attribute float a_color;', - - 'uniform vec2 u_resolution;', - 'uniform mat3 u_matrix;', - - 'varying vec4 color;', - - 'void main() {', - // Scale from [[-1 1] [-1 1]] to the container: - 'gl_Position = vec4(', - '((u_matrix * vec3(a_position, 1)).xy /', - 'u_resolution * 2.0 - 1.0) * vec2(1, -1),', - '0,', - '1', - ');', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - - 'void main(void) {', - 'gl_FragColor = color;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.edges'); - - /** - * This edge renderer will display edges as arrows going from the source node - * to the target node. To deal with edge thicknesses, the lines are made of - * three triangles: two forming rectangles, with the gl.TRIANGLES drawing - * mode. - * - * It is expensive, since drawing a single edge requires 9 points, each - * having a lot of attributes. - */ - sigma.webgl.edges.arrow = { - POINTS: 9, - ATTRIBUTES: 11, - addEdge: function(edge, source, target, data, i, prefix, settings) { - var w = (edge[prefix + 'size'] || 1) / 2, - x1 = source[prefix + 'x'], - y1 = source[prefix + 'y'], - x2 = target[prefix + 'x'], - y2 = target[prefix + 'y'], - targetSize = target[prefix + 'size'], - color = edge.color; - - if (!color) - switch (settings('edgeColor')) { - case 'source': - color = source.color || settings('defaultNodeColor'); - break; - case 'target': - color = target.color || settings('defaultNodeColor'); - break; - default: - color = settings('defaultEdgeColor'); - break; - } - - // Normalize color: - color = sigma.utils.floatColor(color); - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 0.0; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - // Arrow head: - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 1.0; - data[i++] = -1.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 1.0; - data[i++] = 1.0; - data[i++] = color; - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var positionLocation1 = - gl.getAttribLocation(program, 'a_pos1'), - positionLocation2 = - gl.getAttribLocation(program, 'a_pos2'), - thicknessLocation = - gl.getAttribLocation(program, 'a_thickness'), - targetSizeLocation = - gl.getAttribLocation(program, 'a_tSize'), - delayLocation = - gl.getAttribLocation(program, 'a_delay'), - minusLocation = - gl.getAttribLocation(program, 'a_minus'), - headLocation = - gl.getAttribLocation(program, 'a_head'), - headPositionLocation = - gl.getAttribLocation(program, 'a_headPosition'), - colorLocation = - gl.getAttribLocation(program, 'a_color'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'), - matrixHalfPiLocation = - gl.getUniformLocation(program, 'u_matrixHalfPi'), - matrixHalfPiMinusLocation = - gl.getUniformLocation(program, 'u_matrixHalfPiMinus'), - ratioLocation = - gl.getUniformLocation(program, 'u_ratio'), - nodeRatioLocation = - gl.getUniformLocation(program, 'u_nodeRatio'), - arrowHeadLocation = - gl.getUniformLocation(program, 'u_arrowHead'), - scaleLocation = - gl.getUniformLocation(program, 'u_scale'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniform1f( - ratioLocation, - params.ratio / Math.pow(params.ratio, params.settings('edgesPowRatio')) - ); - gl.uniform1f( - nodeRatioLocation, - Math.pow(params.ratio, params.settings('nodesPowRatio')) / - params.ratio - ); - gl.uniform1f(arrowHeadLocation, 5.0); - gl.uniform1f(scaleLocation, params.scalingRatio); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - gl.uniformMatrix2fv( - matrixHalfPiLocation, - false, - sigma.utils.matrices.rotation(Math.PI / 2, true) - ); - gl.uniformMatrix2fv( - matrixHalfPiMinusLocation, - false, - sigma.utils.matrices.rotation(-Math.PI / 2, true) - ); - - gl.enableVertexAttribArray(positionLocation1); - gl.enableVertexAttribArray(positionLocation2); - gl.enableVertexAttribArray(thicknessLocation); - gl.enableVertexAttribArray(targetSizeLocation); - gl.enableVertexAttribArray(delayLocation); - gl.enableVertexAttribArray(minusLocation); - gl.enableVertexAttribArray(headLocation); - gl.enableVertexAttribArray(headPositionLocation); - gl.enableVertexAttribArray(colorLocation); - - gl.vertexAttribPointer(positionLocation1, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer(positionLocation2, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - gl.vertexAttribPointer(thicknessLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 16 - ); - gl.vertexAttribPointer(targetSizeLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 20 - ); - gl.vertexAttribPointer(delayLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 24 - ); - gl.vertexAttribPointer(minusLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 28 - ); - gl.vertexAttribPointer(headLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 32 - ); - gl.vertexAttribPointer(headPositionLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 36 - ); - gl.vertexAttribPointer(colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 40 - ); - - gl.drawArrays( - gl.TRIANGLES, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_pos1;', - 'attribute vec2 a_pos2;', - 'attribute float a_thickness;', - 'attribute float a_tSize;', - 'attribute float a_delay;', - 'attribute float a_minus;', - 'attribute float a_head;', - 'attribute float a_headPosition;', - 'attribute float a_color;', - - 'uniform vec2 u_resolution;', - 'uniform float u_ratio;', - 'uniform float u_nodeRatio;', - 'uniform float u_arrowHead;', - 'uniform float u_scale;', - 'uniform mat3 u_matrix;', - 'uniform mat2 u_matrixHalfPi;', - 'uniform mat2 u_matrixHalfPiMinus;', - - 'varying vec4 color;', - - 'void main() {', - // Find the good point: - 'vec2 pos = normalize(a_pos2 - a_pos1);', - - 'mat2 matrix = (1.0 - a_head) *', - '(', - 'a_minus * u_matrixHalfPiMinus +', - '(1.0 - a_minus) * u_matrixHalfPi', - ') + a_head * (', - 'a_headPosition * u_matrixHalfPiMinus * 0.6 +', - '(a_headPosition * a_headPosition - 1.0) * mat2(1.0)', - ');', - - 'pos = a_pos1 + (', - // Deal with body: - '(1.0 - a_head) * a_thickness * u_ratio * matrix * pos +', - // Deal with head: - 'a_head * u_arrowHead * a_thickness * u_ratio * matrix * pos +', - // Deal with delay: - 'a_delay * pos * (', - 'a_tSize / u_nodeRatio +', - 'u_arrowHead * a_thickness * u_ratio', - ')', - ');', - - // Scale from [[-1 1] [-1 1]] to the container: - 'gl_Position = vec4(', - '((u_matrix * vec3(pos, 1)).xy /', - 'u_resolution * 2.0 - 1.0) * vec2(1, -1),', - '0,', - '1', - ');', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - - 'void main(void) {', - 'gl_FragColor = color;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.canvas.labels'); - - /** - * This label renderer will just display the label on the right of the node. - * - * @param {object} node The node object. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.labels.def = function(node, context, settings) { - var fontSize, - prefix = settings('prefix') || '', - size = node[prefix + 'size']; - - if (size < settings('labelThreshold')) - return; - - if (!node.label || typeof node.label !== 'string') - return; - - fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size; - - context.font = (settings('fontStyle') ? settings('fontStyle') + ' ' : '') + - fontSize + 'px ' + settings('font'); - context.fillStyle = (settings('labelColor') === 'node') ? - (node.color || settings('defaultNodeColor')) : - settings('defaultLabelColor'); - - context.fillText( - node.label, - Math.round(node[prefix + 'x'] + size + 3), - Math.round(node[prefix + 'y'] + fontSize / 3) - ); - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.canvas.hovers'); - - /** - * This hover renderer will basically display the label with a background. - * - * @param {object} node The node object. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.hovers.def = function(node, context, settings) { - var x, - y, - w, - h, - e, - fontStyle = settings('hoverFontStyle') || settings('fontStyle'), - prefix = settings('prefix') || '', - size = node[prefix + 'size'], - fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size; - - // Label background: - context.font = (fontStyle ? fontStyle + ' ' : '') + - fontSize + 'px ' + (settings('hoverFont') || settings('font')); - - context.beginPath(); - context.fillStyle = settings('labelHoverBGColor') === 'node' ? - (node.color || settings('defaultNodeColor')) : - settings('defaultHoverLabelBGColor'); - - if (node.label && settings('labelHoverShadow')) { - context.shadowOffsetX = 0; - context.shadowOffsetY = 0; - context.shadowBlur = 8; - context.shadowColor = settings('labelHoverShadowColor'); - } - - if (node.label && typeof node.label === 'string') { - x = Math.round(node[prefix + 'x'] - fontSize / 2 - 2); - y = Math.round(node[prefix + 'y'] - fontSize / 2 - 2); - w = Math.round( - context.measureText(node.label).width + fontSize / 2 + size + 7 - ); - h = Math.round(fontSize + 4); - e = Math.round(fontSize / 2 + 2); - - context.moveTo(x, y + e); - context.arcTo(x, y, x + e, y, e); - context.lineTo(x + w, y); - context.lineTo(x + w, y + h); - context.lineTo(x + e, y + h); - context.arcTo(x, y + h, x, y + h - e, e); - context.lineTo(x, y + e); - - context.closePath(); - context.fill(); - - context.shadowOffsetX = 0; - context.shadowOffsetY = 0; - context.shadowBlur = 0; - } - - // Node border: - if (settings('borderSize') > 0) { - context.beginPath(); - context.fillStyle = settings('nodeBorderColor') === 'node' ? - (node.color || settings('defaultNodeColor')) : - settings('defaultNodeBorderColor'); - context.arc( - node[prefix + 'x'], - node[prefix + 'y'], - size + settings('borderSize'), - 0, - Math.PI * 2, - true - ); - context.closePath(); - context.fill(); - } - - // Node: - var nodeRenderer = sigma.canvas.nodes[node.type] || sigma.canvas.nodes.def; - nodeRenderer(node, context, settings); - - // Display the label: - if (node.label && typeof node.label === 'string') { - context.fillStyle = (settings('labelHoverColor') === 'node') ? - (node.color || settings('defaultNodeColor')) : - settings('defaultLabelHoverColor'); - - context.fillText( - node.label, - Math.round(node[prefix + 'x'] + size + 3), - Math.round(node[prefix + 'y'] + fontSize / 3) - ); - } - }; -}).call(this); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.nodes'); - - /** - * The default node renderer. It renders the node as a simple disc. - * - * @param {object} node The node object. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.nodes.def = function(node, context, settings) { - var prefix = settings('prefix') || ''; - - context.fillStyle = node.color || settings('defaultNodeColor'); - context.beginPath(); - context.arc( - node[prefix + 'x'], - node[prefix + 'y'], - node[prefix + 'size'], - 0, - Math.PI * 2, - true - ); - - context.closePath(); - context.fill(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edges'); - - /** - * The default edge renderer. It renders the edge as a simple line. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edges.def = function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - size = edge[prefix + 'size'] || 1, - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo( - source[prefix + 'x'], - source[prefix + 'y'] - ); - context.lineTo( - target[prefix + 'x'], - target[prefix + 'y'] - ); - context.stroke(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edges'); - - /** - * This edge renderer will display edges as curves. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edges.curve = function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - size = edge[prefix + 'size'] || 1, - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - cp = {}, - sSize = source[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y']; - - cp = (source.id === target.id) ? - sigma.utils.getSelfLoopControlPoints(sX, sY, sSize) : - sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - if (source.id === target.id) { - context.bezierCurveTo(cp.x1, cp.y1, cp.x2, cp.y2, tX, tY); - } else { - context.quadraticCurveTo(cp.x, cp.y, tX, tY); - } - context.stroke(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edges'); - - /** - * This edge renderer will display edges as arrows going from the source node - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edges.arrow = function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - size = edge[prefix + 'size'] || 1, - tSize = target[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y'], - aSize = Math.max(size * 2.5, settings('minArrowSize')), - d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)), - aX = sX + (tX - sX) * (d - aSize - tSize) / d, - aY = sY + (tY - sY) * (d - aSize - tSize) / d, - vX = (tX - sX) * aSize / d, - vY = (tY - sY) * aSize / d; - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - context.lineTo( - aX, - aY - ); - context.stroke(); - - context.fillStyle = color; - context.beginPath(); - context.moveTo(aX + vX, aY + vY); - context.lineTo(aX + vY * 0.6, aY - vX * 0.6); - context.lineTo(aX - vY * 0.6, aY + vX * 0.6); - context.lineTo(aX + vX, aY + vY); - context.closePath(); - context.fill(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edges'); - - /** - * This edge renderer will display edges as curves with arrow heading. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edges.curvedArrow = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - cp = {}, - size = edge[prefix + 'size'] || 1, - tSize = target[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y'], - aSize = Math.max(size * 2.5, settings('minArrowSize')), - d, - aX, - aY, - vX, - vY; - - cp = (source.id === target.id) ? - sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) : - sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY); - - if (source.id === target.id) { - d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2)); - aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d; - aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d; - vX = (tX - cp.x1) * aSize / d; - vY = (tY - cp.y1) * aSize / d; - } - else { - d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2)); - aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d; - aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d; - vX = (tX - cp.x) * aSize / d; - vY = (tY - cp.y) * aSize / d; - } - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - if (source.id === target.id) { - context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY); - } else { - context.quadraticCurveTo(cp.x, cp.y, aX, aY); - } - context.stroke(); - - context.fillStyle = color; - context.beginPath(); - context.moveTo(aX + vX, aY + vY); - context.lineTo(aX + vY * 0.6, aY - vX * 0.6); - context.lineTo(aX - vY * 0.6, aY + vX * 0.6); - context.lineTo(aX + vX, aY + vY); - context.closePath(); - context.fill(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edgehovers'); - - /** - * This hover renderer will display the edge with a different color or size. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edgehovers.def = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - size = edge[prefix + 'size'] || 1, - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - if (settings('edgeHoverColor') === 'edge') { - color = edge.hover_color || color; - } else { - color = edge.hover_color || settings('defaultEdgeHoverColor') || color; - } - size *= settings('edgeHoverSizeRatio'); - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo( - source[prefix + 'x'], - source[prefix + 'y'] - ); - context.lineTo( - target[prefix + 'x'], - target[prefix + 'y'] - ); - context.stroke(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edgehovers'); - - /** - * This hover renderer will display the edge with a different color or size. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edgehovers.curve = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - size = settings('edgeHoverSizeRatio') * (edge[prefix + 'size'] || 1), - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - cp = {}, - sSize = source[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y']; - - cp = (source.id === target.id) ? - sigma.utils.getSelfLoopControlPoints(sX, sY, sSize) : - sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - if (settings('edgeHoverColor') === 'edge') { - color = edge.hover_color || color; - } else { - color = edge.hover_color || settings('defaultEdgeHoverColor') || color; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - if (source.id === target.id) { - context.bezierCurveTo(cp.x1, cp.y1, cp.x2, cp.y2, tX, tY); - } else { - context.quadraticCurveTo(cp.x, cp.y, tX, tY); - } - context.stroke(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edgehovers'); - - /** - * This hover renderer will display the edge with a different color or size. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edgehovers.arrow = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - size = edge[prefix + 'size'] || 1, - tSize = target[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y']; - - size = (edge.hover) ? - settings('edgeHoverSizeRatio') * size : size; - var aSize = size * 2.5, - d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)), - aX = sX + (tX - sX) * (d - aSize - tSize) / d, - aY = sY + (tY - sY) * (d - aSize - tSize) / d, - vX = (tX - sX) * aSize / d, - vY = (tY - sY) * aSize / d; - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - if (settings('edgeHoverColor') === 'edge') { - color = edge.hover_color || color; - } else { - color = edge.hover_color || settings('defaultEdgeHoverColor') || color; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - context.lineTo( - aX, - aY - ); - context.stroke(); - - context.fillStyle = color; - context.beginPath(); - context.moveTo(aX + vX, aY + vY); - context.lineTo(aX + vY * 0.6, aY - vX * 0.6); - context.lineTo(aX - vY * 0.6, aY + vX * 0.6); - context.lineTo(aX + vX, aY + vY); - context.closePath(); - context.fill(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edgehovers'); - - /** - * This hover renderer will display the edge with a different color or size. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edgehovers.curvedArrow = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - cp = {}, - size = settings('edgeHoverSizeRatio') * (edge[prefix + 'size'] || 1), - tSize = target[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y'], - d, - aSize, - aX, - aY, - vX, - vY; - - cp = (source.id === target.id) ? - sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) : - sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY); - - if (source.id === target.id) { - d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2)); - aSize = size * 2.5; - aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d; - aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d; - vX = (tX - cp.x1) * aSize / d; - vY = (tY - cp.y1) * aSize / d; - } - else { - d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2)); - aSize = size * 2.5; - aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d; - aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d; - vX = (tX - cp.x) * aSize / d; - vY = (tY - cp.y) * aSize / d; - } - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - if (settings('edgeHoverColor') === 'edge') { - color = edge.hover_color || color; - } else { - color = edge.hover_color || settings('defaultEdgeHoverColor') || color; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - if (source.id === target.id) { - context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY); - } else { - context.quadraticCurveTo(cp.x, cp.y, aX, aY); - } - context.stroke(); - - context.fillStyle = color; - context.beginPath(); - context.moveTo(aX + vX, aY + vY); - context.lineTo(aX + vY * 0.6, aY - vX * 0.6); - context.lineTo(aX - vY * 0.6, aY + vX * 0.6); - context.lineTo(aX + vX, aY + vY); - context.closePath(); - context.fill(); - }; -})(); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.canvas.extremities'); - - /** - * The default renderer for hovered edge extremities. It renders the edge - * extremities as hovered. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.extremities.def = - function(edge, source, target, context, settings) { - // Source Node: - ( - sigma.canvas.hovers[source.type] || - sigma.canvas.hovers.def - ) ( - source, context, settings - ); - - // Target Node: - ( - sigma.canvas.hovers[target.type] || - sigma.canvas.hovers.def - ) ( - target, context, settings - ); - }; -}).call(this); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.svg.utils'); - - /** - * Some useful functions used by sigma's SVG renderer. - */ - sigma.svg.utils = { - - /** - * SVG Element show. - * - * @param {DOMElement} element The DOM element to show. - */ - show: function(element) { - element.style.display = ''; - return this; - }, - - /** - * SVG Element hide. - * - * @param {DOMElement} element The DOM element to hide. - */ - hide: function(element) { - element.style.display = 'none'; - return this; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.svg.nodes'); - - /** - * The default node renderer. It renders the node as a simple disc. - */ - sigma.svg.nodes.def = { - - /** - * SVG Element creation. - * - * @param {object} node The node object. - * @param {configurable} settings The settings function. - */ - create: function(node, settings) { - var prefix = settings('prefix') || '', - circle = document.createElementNS(settings('xmlns'), 'circle'); - - // Defining the node's circle - circle.setAttributeNS(null, 'data-node-id', node.id); - circle.setAttributeNS(null, 'class', settings('classPrefix') + '-node'); - circle.setAttributeNS( - null, 'fill', node.color || settings('defaultNodeColor')); - - // Returning the DOM Element - return circle; - }, - - /** - * SVG Element update. - * - * @param {object} node The node object. - * @param {DOMElement} circle The node DOM element. - * @param {configurable} settings The settings function. - */ - update: function(node, circle, settings) { - var prefix = settings('prefix') || ''; - - // Applying changes - // TODO: optimize - check if necessary - circle.setAttributeNS(null, 'cx', node[prefix + 'x']); - circle.setAttributeNS(null, 'cy', node[prefix + 'y']); - circle.setAttributeNS(null, 'r', node[prefix + 'size']); - - // Updating only if not freestyle - if (!settings('freeStyle')) - circle.setAttributeNS( - null, 'fill', node.color || settings('defaultNodeColor')); - - // Showing - circle.style.display = ''; - - return this; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.svg.edges'); - - /** - * The default edge renderer. It renders the node as a simple line. - */ - sigma.svg.edges.def = { - - /** - * SVG Element creation. - * - * @param {object} edge The edge object. - * @param {object} source The source node object. - * @param {object} target The target node object. - * @param {configurable} settings The settings function. - */ - create: function(edge, source, target, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - var line = document.createElementNS(settings('xmlns'), 'line'); - - // Attributes - line.setAttributeNS(null, 'data-edge-id', edge.id); - line.setAttributeNS(null, 'class', settings('classPrefix') + '-edge'); - line.setAttributeNS(null, 'stroke', color); - - return line; - }, - - /** - * SVG Element update. - * - * @param {object} edge The edge object. - * @param {DOMElement} line The line DOM Element. - * @param {object} source The source node object. - * @param {object} target The target node object. - * @param {configurable} settings The settings function. - */ - update: function(edge, line, source, target, settings) { - var prefix = settings('prefix') || ''; - - line.setAttributeNS(null, 'stroke-width', edge[prefix + 'size'] || 1); - line.setAttributeNS(null, 'x1', source[prefix + 'x']); - line.setAttributeNS(null, 'y1', source[prefix + 'y']); - line.setAttributeNS(null, 'x2', target[prefix + 'x']); - line.setAttributeNS(null, 'y2', target[prefix + 'y']); - - // Showing - line.style.display = ''; - - return this; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.svg.edges'); - - /** - * The curve edge renderer. It renders the node as a bezier curve. - */ - sigma.svg.edges.curve = { - - /** - * SVG Element creation. - * - * @param {object} edge The edge object. - * @param {object} source The source node object. - * @param {object} target The target node object. - * @param {configurable} settings The settings function. - */ - create: function(edge, source, target, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - var path = document.createElementNS(settings('xmlns'), 'path'); - - // Attributes - path.setAttributeNS(null, 'data-edge-id', edge.id); - path.setAttributeNS(null, 'class', settings('classPrefix') + '-edge'); - path.setAttributeNS(null, 'stroke', color); - - return path; - }, - - /** - * SVG Element update. - * - * @param {object} edge The edge object. - * @param {DOMElement} line The line DOM Element. - * @param {object} source The source node object. - * @param {object} target The target node object. - * @param {configurable} settings The settings function. - */ - update: function(edge, path, source, target, settings) { - var prefix = settings('prefix') || ''; - - path.setAttributeNS(null, 'stroke-width', edge[prefix + 'size'] || 1); - - // Control point - var cx = (source[prefix + 'x'] + target[prefix + 'x']) / 2 + - (target[prefix + 'y'] - source[prefix + 'y']) / 4, - cy = (source[prefix + 'y'] + target[prefix + 'y']) / 2 + - (source[prefix + 'x'] - target[prefix + 'x']) / 4; - - // Path - var p = 'M' + source[prefix + 'x'] + ',' + source[prefix + 'y'] + ' ' + - 'Q' + cx + ',' + cy + ' ' + - target[prefix + 'x'] + ',' + target[prefix + 'y']; - - // Updating attributes - path.setAttributeNS(null, 'd', p); - path.setAttributeNS(null, 'fill', 'none'); - - // Showing - path.style.display = ''; - - return this; - } - }; -})(); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.svg.labels'); - - /** - * The default label renderer. It renders the label as a simple text. - */ - sigma.svg.labels.def = { - - /** - * SVG Element creation. - * - * @param {object} node The node object. - * @param {configurable} settings The settings function. - */ - create: function(node, settings) { - var prefix = settings('prefix') || '', - size = node[prefix + 'size'], - text = document.createElementNS(settings('xmlns'), 'text'); - - var fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size; - - var fontColor = (settings('labelColor') === 'node') ? - (node.color || settings('defaultNodeColor')) : - settings('defaultLabelColor'); - - text.setAttributeNS(null, 'data-label-target', node.id); - text.setAttributeNS(null, 'class', settings('classPrefix') + '-label'); - text.setAttributeNS(null, 'font-size', fontSize); - text.setAttributeNS(null, 'font-family', settings('font')); - text.setAttributeNS(null, 'fill', fontColor); - - text.innerHTML = node.label; - text.textContent = node.label; - - return text; - }, - - /** - * SVG Element update. - * - * @param {object} node The node object. - * @param {DOMElement} text The label DOM element. - * @param {configurable} settings The settings function. - */ - update: function(node, text, settings) { - var prefix = settings('prefix') || '', - size = node[prefix + 'size']; - - var fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size; - - // Case when we don't want to display the label - if (!settings('forceLabels') && size < settings('labelThreshold')) - return; - - if (typeof node.label !== 'string') - return; - - // Updating - text.setAttributeNS(null, 'x', - Math.round(node[prefix + 'x'] + size + 3)); - text.setAttributeNS(null, 'y', - Math.round(node[prefix + 'y'] + fontSize / 3)); - - // Showing - text.style.display = ''; - - return this; - } - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.svg.hovers'); - - /** - * The default hover renderer. - */ - sigma.svg.hovers.def = { - - /** - * SVG Element creation. - * - * @param {object} node The node object. - * @param {CanvasElement} measurementCanvas A fake canvas handled by - * the svg to perform some measurements and - * passed by the renderer. - * @param {DOMElement} nodeCircle The node DOM Element. - * @param {configurable} settings The settings function. - */ - create: function(node, nodeCircle, measurementCanvas, settings) { - - // Defining visual properties - var x, - y, - w, - h, - e, - d, - fontStyle = settings('hoverFontStyle') || settings('fontStyle'), - prefix = settings('prefix') || '', - size = node[prefix + 'size'], - fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size, - fontColor = (settings('labelHoverColor') === 'node') ? - (node.color || settings('defaultNodeColor')) : - settings('defaultLabelHoverColor'); - - // Creating elements - var group = document.createElementNS(settings('xmlns'), 'g'), - rectangle = document.createElementNS(settings('xmlns'), 'rect'), - circle = document.createElementNS(settings('xmlns'), 'circle'), - text = document.createElementNS(settings('xmlns'), 'text'); - - // Defining properties - group.setAttributeNS(null, 'class', settings('classPrefix') + '-hover'); - group.setAttributeNS(null, 'data-node-id', node.id); - - if (typeof node.label === 'string') { - - // Text - text.innerHTML = node.label; - text.textContent = node.label; - text.setAttributeNS( - null, - 'class', - settings('classPrefix') + '-hover-label'); - text.setAttributeNS(null, 'font-size', fontSize); - text.setAttributeNS(null, 'font-family', settings('font')); - text.setAttributeNS(null, 'fill', fontColor); - text.setAttributeNS(null, 'x', - Math.round(node[prefix + 'x'] + size + 3)); - text.setAttributeNS(null, 'y', - Math.round(node[prefix + 'y'] + fontSize / 3)); - - // Measures - // OPTIMIZE: Find a better way than a measurement canvas - x = Math.round(node[prefix + 'x'] - fontSize / 2 - 2); - y = Math.round(node[prefix + 'y'] - fontSize / 2 - 2); - w = Math.round( - measurementCanvas.measureText(node.label).width + - fontSize / 2 + size + 9 - ); - h = Math.round(fontSize + 4); - e = Math.round(fontSize / 2 + 2); - - // Circle - circle.setAttributeNS( - null, - 'class', - settings('classPrefix') + '-hover-area'); - circle.setAttributeNS(null, 'fill', '#fff'); - circle.setAttributeNS(null, 'cx', node[prefix + 'x']); - circle.setAttributeNS(null, 'cy', node[prefix + 'y']); - circle.setAttributeNS(null, 'r', e); - - // Rectangle - rectangle.setAttributeNS( - null, - 'class', - settings('classPrefix') + '-hover-area'); - rectangle.setAttributeNS(null, 'fill', '#fff'); - rectangle.setAttributeNS(null, 'x', node[prefix + 'x'] + e / 4); - rectangle.setAttributeNS(null, 'y', node[prefix + 'y'] - e); - rectangle.setAttributeNS(null, 'width', w); - rectangle.setAttributeNS(null, 'height', h); - } - - // Appending childs - group.appendChild(circle); - group.appendChild(rectangle); - group.appendChild(text); - group.appendChild(nodeCircle); - - return group; - } - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.middlewares'); - sigma.utils.pkg('sigma.utils'); - - /** - * This middleware will rescale the graph such that it takes an optimal space - * on the renderer. - * - * As each middleware, this function is executed in the scope of the sigma - * instance. - * - * @param {?string} readPrefix The read prefix. - * @param {?string} writePrefix The write prefix. - * @param {object} options The parameters. - */ - sigma.middlewares.rescale = function(readPrefix, writePrefix, options) { - var i, - l, - a, - b, - c, - d, - scale, - margin, - n = this.graph.nodes(), - e = this.graph.edges(), - settings = this.settings.embedObjects(options || {}), - bounds = settings('bounds') || sigma.utils.getBoundaries( - this.graph, - readPrefix, - true - ), - minX = bounds.minX, - minY = bounds.minY, - maxX = bounds.maxX, - maxY = bounds.maxY, - sizeMax = bounds.sizeMax, - weightMax = bounds.weightMax, - w = settings('width') || 1, - h = settings('height') || 1, - rescaleSettings = settings('autoRescale'), - validSettings = { - nodePosition: 1, - nodeSize: 1, - edgeSize: 1 - }; - - /** - * What elements should we rescale? - */ - if (!(rescaleSettings instanceof Array)) - rescaleSettings = ['nodePosition', 'nodeSize', 'edgeSize']; - - for (i = 0, l = rescaleSettings.length; i < l; i++) - if (!validSettings[rescaleSettings[i]]) - throw new Error( - 'The rescale setting "' + rescaleSettings[i] + '" is not recognized.' - ); - - var np = ~rescaleSettings.indexOf('nodePosition'), - ns = ~rescaleSettings.indexOf('nodeSize'), - es = ~rescaleSettings.indexOf('edgeSize'); - - /** - * First, we compute the scaling ratio, without considering the sizes - * of the nodes : Each node will have its center in the canvas, but might - * be partially out of it. - */ - scale = settings('scalingMode') === 'outside' ? - Math.max( - w / Math.max(maxX - minX, 1), - h / Math.max(maxY - minY, 1) - ) : - Math.min( - w / Math.max(maxX - minX, 1), - h / Math.max(maxY - minY, 1) - ); - - /** - * Then, we correct that scaling ratio considering a margin, which is - * basically the size of the biggest node. - * This has to be done as a correction since to compare the size of the - * biggest node to the X and Y values, we have to first get an - * approximation of the scaling ratio. - **/ - margin = - ( - settings('rescaleIgnoreSize') ? - 0 : - (settings('maxNodeSize') || sizeMax) / scale - ) + - (settings('sideMargin') || 0); - maxX += margin; - minX -= margin; - maxY += margin; - minY -= margin; - - // Fix the scaling with the new extrema: - scale = settings('scalingMode') === 'outside' ? - Math.max( - w / Math.max(maxX - minX, 1), - h / Math.max(maxY - minY, 1) - ) : - Math.min( - w / Math.max(maxX - minX, 1), - h / Math.max(maxY - minY, 1) - ); - - // Size homothetic parameters: - if (!settings('maxNodeSize') && !settings('minNodeSize')) { - a = 1; - b = 0; - } else if (settings('maxNodeSize') === settings('minNodeSize')) { - a = 0; - b = +settings('maxNodeSize'); - } else { - a = (settings('maxNodeSize') - settings('minNodeSize')) / sizeMax; - b = +settings('minNodeSize'); - } - - if (!settings('maxEdgeSize') && !settings('minEdgeSize')) { - c = 1; - d = 0; - } else if (settings('maxEdgeSize') === settings('minEdgeSize')) { - c = 0; - d = +settings('minEdgeSize'); - } else { - c = (settings('maxEdgeSize') - settings('minEdgeSize')) / weightMax; - d = +settings('minEdgeSize'); - } - - // Rescale the nodes and edges: - for (i = 0, l = e.length; i < l; i++) - e[i][writePrefix + 'size'] = - e[i][readPrefix + 'size'] * (es ? c : 1) + (es ? d : 0); - - for (i = 0, l = n.length; i < l; i++) { - n[i][writePrefix + 'size'] = - n[i][readPrefix + 'size'] * (ns ? a : 1) + (ns ? b : 0); - n[i][writePrefix + 'x'] = - (n[i][readPrefix + 'x'] - (maxX + minX) / 2) * (np ? scale : 1); - n[i][writePrefix + 'y'] = - (n[i][readPrefix + 'y'] - (maxY + minY) / 2) * (np ? scale : 1); - } - }; - - sigma.utils.getBoundaries = function(graph, prefix, doEdges) { - var i, - l, - e = graph.edges(), - n = graph.nodes(), - weightMax = -Infinity, - sizeMax = -Infinity, - minX = Infinity, - minY = Infinity, - maxX = -Infinity, - maxY = -Infinity; - - if (doEdges) - for (i = 0, l = e.length; i < l; i++) - weightMax = Math.max(e[i][prefix + 'size'], weightMax); - - for (i = 0, l = n.length; i < l; i++) { - sizeMax = Math.max(n[i][prefix + 'size'], sizeMax); - maxX = Math.max(n[i][prefix + 'x'], maxX); - minX = Math.min(n[i][prefix + 'x'], minX); - maxY = Math.max(n[i][prefix + 'y'], maxY); - minY = Math.min(n[i][prefix + 'y'], minY); - } - - weightMax = weightMax || 1; - sizeMax = sizeMax || 1; - - return { - weightMax: weightMax, - sizeMax: sizeMax, - minX: minX, - minY: minY, - maxX: maxX, - maxY: maxY - }; - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.middlewares'); - - /** - * This middleware will just copy the graphic properties. - * - * @param {?string} readPrefix The read prefix. - * @param {?string} writePrefix The write prefix. - */ - sigma.middlewares.copy = function(readPrefix, writePrefix) { - var i, - l, - a; - - if (writePrefix + '' === readPrefix + '') - return; - - a = this.graph.nodes(); - for (i = 0, l = a.length; i < l; i++) { - a[i][writePrefix + 'x'] = a[i][readPrefix + 'x']; - a[i][writePrefix + 'y'] = a[i][readPrefix + 'y']; - a[i][writePrefix + 'size'] = a[i][readPrefix + 'size']; - } - - a = this.graph.edges(); - for (i = 0, l = a.length; i < l; i++) - a[i][writePrefix + 'size'] = a[i][readPrefix + 'size']; - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.misc.animation.running'); - - /** - * Generates a unique ID for the animation. - * - * @return {string} Returns the new ID. - */ - var _getID = (function() { - var id = 0; - return function() { - return '' + (++id); - }; - })(); - - /** - * This function animates a camera. It has to be called with the camera to - * animate, the values of the coordinates to reach and eventually some - * options. It returns a number id, that you can use to kill the animation, - * with the method sigma.misc.animation.kill(id). - * - * The available options are: - * - * {?number} duration The duration of the animation. - * {?function} onNewFrame A callback to execute when the animation - * enter a new frame. - * {?function} onComplete A callback to execute when the animation - * is completed or killed. - * {?(string|function)} easing The name of a function from the package - * sigma.utils.easings, or a custom easing - * function. - * - * @param {camera} camera The camera to animate. - * @param {object} target The coordinates to reach. - * @param {?object} options Eventually an object to specify some options to - * the function. The available options are - * presented in the description of the function. - * @return {number} The animation id, to make it easy to kill - * through the method "sigma.misc.animation.kill". - */ - sigma.misc.animation.camera = function(camera, val, options) { - if ( - !(camera instanceof sigma.classes.camera) || - typeof val !== 'object' || - !val - ) - throw 'animation.camera: Wrong arguments.'; - - if ( - typeof val.x !== 'number' && - typeof val.y !== 'number' && - typeof val.ratio !== 'number' && - typeof val.angle !== 'number' - ) - throw 'There must be at least one valid coordinate in the given val.'; - - var fn, - id, - anim, - easing, - duration, - initialVal, - o = options || {}, - start = sigma.utils.dateNow(); - - // Store initial values: - initialVal = { - x: camera.x, - y: camera.y, - ratio: camera.ratio, - angle: camera.angle - }; - - duration = o.duration; - easing = typeof o.easing !== 'function' ? - sigma.utils.easings[o.easing || 'quadraticInOut'] : - o.easing; - - fn = function() { - var coef, - t = o.duration ? (sigma.utils.dateNow() - start) / o.duration : 1; - - // If the animation is over: - if (t >= 1) { - camera.isAnimated = false; - camera.goTo({ - x: val.x !== undefined ? val.x : initialVal.x, - y: val.y !== undefined ? val.y : initialVal.y, - ratio: val.ratio !== undefined ? val.ratio : initialVal.ratio, - angle: val.angle !== undefined ? val.angle : initialVal.angle - }); - - cancelAnimationFrame(id); - delete sigma.misc.animation.running[id]; - - // Check callbacks: - if (typeof o.onComplete === 'function') - o.onComplete(); - - // Else, let's keep going: - } else { - coef = easing(t); - camera.isAnimated = true; - camera.goTo({ - x: val.x !== undefined ? - initialVal.x + (val.x - initialVal.x) * coef : - initialVal.x, - y: val.y !== undefined ? - initialVal.y + (val.y - initialVal.y) * coef : - initialVal.y, - ratio: val.ratio !== undefined ? - initialVal.ratio + (val.ratio - initialVal.ratio) * coef : - initialVal.ratio, - angle: val.angle !== undefined ? - initialVal.angle + (val.angle - initialVal.angle) * coef : - initialVal.angle - }); - - // Check callbacks: - if (typeof o.onNewFrame === 'function') - o.onNewFrame(); - - anim.frameId = requestAnimationFrame(fn); - } - }; - - id = _getID(); - anim = { - frameId: requestAnimationFrame(fn), - target: camera, - type: 'camera', - options: o, - fn: fn - }; - sigma.misc.animation.running[id] = anim; - - return id; - }; - - /** - * Kills a running animation. It triggers the eventual onComplete callback. - * - * @param {number} id The id of the animation to kill. - * @return {object} Returns the sigma.misc.animation package. - */ - sigma.misc.animation.kill = function(id) { - if (arguments.length !== 1 || typeof id !== 'number') - throw 'animation.kill: Wrong arguments.'; - - var o = sigma.misc.animation.running[id]; - - if (o) { - cancelAnimationFrame(id); - delete sigma.misc.animation.running[o.frameId]; - - if (o.type === 'camera') - o.target.isAnimated = false; - - // Check callbacks: - if (typeof (o.options || {}).onComplete === 'function') - o.options.onComplete(); - } - - return this; - }; - - /** - * Kills every running animations, or only the one with the specified type, - * if a string parameter is given. - * - * @param {?(string|object)} filter A string to filter the animations to kill - * on their type (example: "camera"), or an - * object to filter on their target. - * @return {number} Returns the number of animations killed - * that way. - */ - sigma.misc.animation.killAll = function(filter) { - var o, - id, - count = 0, - type = typeof filter === 'string' ? filter : null, - target = typeof filter === 'object' ? filter : null, - running = sigma.misc.animation.running; - - for (id in running) - if ( - (!type || running[id].type === type) && - (!target || running[id].target === target) - ) { - o = sigma.misc.animation.running[id]; - cancelAnimationFrame(o.frameId); - delete sigma.misc.animation.running[id]; - - if (o.type === 'camera') - o.target.isAnimated = false; - - // Increment counter: - count++; - - // Check callbacks: - if (typeof (o.options || {}).onComplete === 'function') - o.options.onComplete(); - } - - return count; - }; - - /** - * Returns "true" if any animation that is currently still running matches - * the filter given to the function. - * - * @param {string|object} filter A string to filter the animations to kill - * on their type (example: "camera"), or an - * object to filter on their target. - * @return {boolean} Returns true if any running animation - * matches. - */ - sigma.misc.animation.has = function(filter) { - var id, - type = typeof filter === 'string' ? filter : null, - target = typeof filter === 'object' ? filter : null, - running = sigma.misc.animation.running; - - for (id in running) - if ( - (!type || running[id].type === type) && - (!target || running[id].target === target) - ) - return true; - - return false; - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.misc'); - - /** - * This helper will bind any no-DOM renderer (for instance canvas or WebGL) - * to its captors, to properly dispatch the good events to the sigma instance - * to manage clicking, hovering etc... - * - * It has to be called in the scope of the related renderer. - */ - sigma.misc.bindEvents = function(prefix) { - var i, - l, - mX, - mY, - captor, - self = this; - - function getNodes(e) { - if (e) { - mX = 'x' in e.data ? e.data.x : mX; - mY = 'y' in e.data ? e.data.y : mY; - } - - var i, - j, - l, - n, - x, - y, - s, - inserted, - selected = [], - modifiedX = mX + self.width / 2, - modifiedY = mY + self.height / 2, - point = self.camera.cameraPosition( - mX, - mY - ), - nodes = self.camera.quadtree.point( - point.x, - point.y - ); - - if (nodes.length) - for (i = 0, l = nodes.length; i < l; i++) { - n = nodes[i]; - x = n[prefix + 'x']; - y = n[prefix + 'y']; - s = n[prefix + 'size']; - - if ( - !n.hidden && - modifiedX > x - s && - modifiedX < x + s && - modifiedY > y - s && - modifiedY < y + s && - Math.sqrt( - Math.pow(modifiedX - x, 2) + - Math.pow(modifiedY - y, 2) - ) < s - ) { - // Insert the node: - inserted = false; - - for (j = 0; j < selected.length; j++) - if (n.size > selected[j].size) { - selected.splice(j, 0, n); - inserted = true; - break; - } - - if (!inserted) - selected.push(n); - } - } - - return selected; - } - - - function getEdges(e) { - if (!self.settings('enableEdgeHovering')) { - // No event if the setting is off: - return []; - } - - var isCanvas = ( - sigma.renderers.canvas && self instanceof sigma.renderers.canvas); - - if (!isCanvas) { - // A quick hardcoded rule to prevent people from using this feature - // with the WebGL renderer (which is not good enough at the moment): - throw new Error( - 'The edge events feature is not compatible with the WebGL renderer' - ); - } - - if (e) { - mX = 'x' in e.data ? e.data.x : mX; - mY = 'y' in e.data ? e.data.y : mY; - } - - var i, - j, - l, - a, - edge, - s, - maxEpsilon = self.settings('edgeHoverPrecision'), - source, - target, - cp, - nodeIndex = {}, - inserted, - selected = [], - modifiedX = mX + self.width / 2, - modifiedY = mY + self.height / 2, - point = self.camera.cameraPosition( - mX, - mY - ), - edges = []; - - if (isCanvas) { - var nodesOnScreen = self.camera.quadtree.area( - self.camera.getRectangle(self.width, self.height) - ); - for (a = nodesOnScreen, i = 0, l = a.length; i < l; i++) - nodeIndex[a[i].id] = a[i]; - } - - if (self.camera.edgequadtree !== undefined) { - edges = self.camera.edgequadtree.point( - point.x, - point.y - ); - } - - function insertEdge(selected, edge) { - inserted = false; - - for (j = 0; j < selected.length; j++) - if (edge.size > selected[j].size) { - selected.splice(j, 0, edge); - inserted = true; - break; - } - - if (!inserted) - selected.push(edge); - } - - if (edges.length) - for (i = 0, l = edges.length; i < l; i++) { - edge = edges[i]; - source = self.graph.nodes(edge.source); - target = self.graph.nodes(edge.target); - // (HACK) we can't get edge[prefix + 'size'] on WebGL renderer: - s = edge[prefix + 'size'] || - edge['read_' + prefix + 'size']; - - // First, let's identify which edges are drawn. To do this, we keep - // every edges that have at least one extremity displayed according to - // the quadtree and the "hidden" attribute. We also do not keep hidden - // edges. - // Then, let's check if the mouse is on the edge (we suppose that it - // is a line segment). - - if ( - !edge.hidden && - !source.hidden && !target.hidden && - (!isCanvas || - (nodeIndex[edge.source] || nodeIndex[edge.target])) && - sigma.utils.getDistance( - source[prefix + 'x'], - source[prefix + 'y'], - modifiedX, - modifiedY) > source[prefix + 'size'] && - sigma.utils.getDistance( - target[prefix + 'x'], - target[prefix + 'y'], - modifiedX, - modifiedY) > target[prefix + 'size'] - ) { - if (edge.type == 'curve' || edge.type == 'curvedArrow') { - if (source.id === target.id) { - cp = sigma.utils.getSelfLoopControlPoints( - source[prefix + 'x'], - source[prefix + 'y'], - source[prefix + 'size'] - ); - if ( - sigma.utils.isPointOnBezierCurve( - modifiedX, - modifiedY, - source[prefix + 'x'], - source[prefix + 'y'], - target[prefix + 'x'], - target[prefix + 'y'], - cp.x1, - cp.y1, - cp.x2, - cp.y2, - Math.max(s, maxEpsilon) - )) { - insertEdge(selected, edge); - } - } - else { - cp = sigma.utils.getQuadraticControlPoint( - source[prefix + 'x'], - source[prefix + 'y'], - target[prefix + 'x'], - target[prefix + 'y']); - if ( - sigma.utils.isPointOnQuadraticCurve( - modifiedX, - modifiedY, - source[prefix + 'x'], - source[prefix + 'y'], - target[prefix + 'x'], - target[prefix + 'y'], - cp.x, - cp.y, - Math.max(s, maxEpsilon) - )) { - insertEdge(selected, edge); - } - } - } else if ( - sigma.utils.isPointOnSegment( - modifiedX, - modifiedY, - source[prefix + 'x'], - source[prefix + 'y'], - target[prefix + 'x'], - target[prefix + 'y'], - Math.max(s, maxEpsilon) - )) { - insertEdge(selected, edge); - } - } - } - - return selected; - } - - - function bindCaptor(captor) { - var nodes, - edges, - overNodes = {}, - overEdges = {}; - - function onClick(e) { - if (!self.settings('eventsEnabled')) - return; - - self.dispatchEvent('click', e.data); - - nodes = getNodes(e); - edges = getEdges(e); - - if (nodes.length) { - self.dispatchEvent('clickNode', { - node: nodes[0], - captor: e.data - }); - self.dispatchEvent('clickNodes', { - node: nodes, - captor: e.data - }); - } else if (edges.length) { - self.dispatchEvent('clickEdge', { - edge: edges[0], - captor: e.data - }); - self.dispatchEvent('clickEdges', { - edge: edges, - captor: e.data - }); - } else - self.dispatchEvent('clickStage', {captor: e.data}); - } - - function onDoubleClick(e) { - if (!self.settings('eventsEnabled')) - return; - - self.dispatchEvent('doubleClick', e.data); - - nodes = getNodes(e); - edges = getEdges(e); - - if (nodes.length) { - self.dispatchEvent('doubleClickNode', { - node: nodes[0], - captor: e.data - }); - self.dispatchEvent('doubleClickNodes', { - node: nodes, - captor: e.data - }); - } else if (edges.length) { - self.dispatchEvent('doubleClickEdge', { - edge: edges[0], - captor: e.data - }); - self.dispatchEvent('doubleClickEdges', { - edge: edges, - captor: e.data - }); - } else - self.dispatchEvent('doubleClickStage', {captor: e.data}); - } - - function onRightClick(e) { - if (!self.settings('eventsEnabled')) - return; - - self.dispatchEvent('rightClick', e.data); - - nodes = getNodes(e); - edges = getEdges(e); - - if (nodes.length) { - self.dispatchEvent('rightClickNode', { - node: nodes[0], - captor: e.data - }); - self.dispatchEvent('rightClickNodes', { - node: nodes, - captor: e.data - }); - } else if (edges.length) { - self.dispatchEvent('rightClickEdge', { - edge: edges[0], - captor: e.data - }); - self.dispatchEvent('rightClickEdges', { - edge: edges, - captor: e.data - }); - } else - self.dispatchEvent('rightClickStage', {captor: e.data}); - } - - function onOut(e) { - if (!self.settings('eventsEnabled')) - return; - - var k, - i, - l, - le, - outNodes = [], - outEdges = []; - - for (k in overNodes) - outNodes.push(overNodes[k]); - - overNodes = {}; - // Dispatch both single and multi events: - for (i = 0, l = outNodes.length; i < l; i++) - self.dispatchEvent('outNode', { - node: outNodes[i], - captor: e.data - }); - if (outNodes.length) - self.dispatchEvent('outNodes', { - nodes: outNodes, - captor: e.data - }); - - overEdges = {}; - // Dispatch both single and multi events: - for (i = 0, le = outEdges.length; i < le; i++) - self.dispatchEvent('outEdge', { - edge: outEdges[i], - captor: e.data - }); - if (outEdges.length) - self.dispatchEvent('outEdges', { - edges: outEdges, - captor: e.data - }); - } - - function onMove(e) { - if (!self.settings('eventsEnabled')) - return; - - nodes = getNodes(e); - edges = getEdges(e); - - var i, - k, - node, - edge, - newOutNodes = [], - newOverNodes = [], - currentOverNodes = {}, - l = nodes.length, - newOutEdges = [], - newOverEdges = [], - currentOverEdges = {}, - le = edges.length; - - // Check newly overred nodes: - for (i = 0; i < l; i++) { - node = nodes[i]; - currentOverNodes[node.id] = node; - if (!overNodes[node.id]) { - newOverNodes.push(node); - overNodes[node.id] = node; - } - } - - // Check no more overred nodes: - for (k in overNodes) - if (!currentOverNodes[k]) { - newOutNodes.push(overNodes[k]); - delete overNodes[k]; - } - - // Dispatch both single and multi events: - for (i = 0, l = newOverNodes.length; i < l; i++) - self.dispatchEvent('overNode', { - node: newOverNodes[i], - captor: e.data - }); - for (i = 0, l = newOutNodes.length; i < l; i++) - self.dispatchEvent('outNode', { - node: newOutNodes[i], - captor: e.data - }); - if (newOverNodes.length) - self.dispatchEvent('overNodes', { - nodes: newOverNodes, - captor: e.data - }); - if (newOutNodes.length) - self.dispatchEvent('outNodes', { - nodes: newOutNodes, - captor: e.data - }); - - // Check newly overred edges: - for (i = 0; i < le; i++) { - edge = edges[i]; - currentOverEdges[edge.id] = edge; - if (!overEdges[edge.id]) { - newOverEdges.push(edge); - overEdges[edge.id] = edge; - } - } - - // Check no more overred edges: - for (k in overEdges) - if (!currentOverEdges[k]) { - newOutEdges.push(overEdges[k]); - delete overEdges[k]; - } - - // Dispatch both single and multi events: - for (i = 0, le = newOverEdges.length; i < le; i++) - self.dispatchEvent('overEdge', { - edge: newOverEdges[i], - captor: e.data - }); - for (i = 0, le = newOutEdges.length; i < le; i++) - self.dispatchEvent('outEdge', { - edge: newOutEdges[i], - captor: e.data - }); - if (newOverEdges.length) - self.dispatchEvent('overEdges', { - edges: newOverEdges, - captor: e.data - }); - if (newOutEdges.length) - self.dispatchEvent('outEdges', { - edges: newOutEdges, - captor: e.data - }); - } - - // Bind events: - captor.bind('click', onClick); - captor.bind('mousedown', onMove); - captor.bind('mouseup', onMove); - captor.bind('mousemove', onMove); - captor.bind('mouseout', onOut); - captor.bind('doubleclick', onDoubleClick); - captor.bind('rightclick', onRightClick); - self.bind('render', onMove); - } - - for (i = 0, l = this.captors.length; i < l; i++) - bindCaptor(this.captors[i]); - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.misc'); - - /** - * This helper will bind any DOM renderer (for instance svg) - * to its captors, to properly dispatch the good events to the sigma instance - * to manage clicking, hovering etc... - * - * It has to be called in the scope of the related renderer. - */ - sigma.misc.bindDOMEvents = function(container) { - var self = this, - graph = this.graph; - - // DOMElement abstraction - function Element(domElement) { - - // Helpers - this.attr = function(attrName) { - return domElement.getAttributeNS(null, attrName); - }; - - // Properties - this.tag = domElement.tagName; - this.class = this.attr('class'); - this.id = this.attr('id'); - - // Methods - this.isNode = function() { - return !!~this.class.indexOf(self.settings('classPrefix') + '-node'); - }; - - this.isEdge = function() { - return !!~this.class.indexOf(self.settings('classPrefix') + '-edge'); - }; - - this.isHover = function() { - return !!~this.class.indexOf(self.settings('classPrefix') + '-hover'); - }; - } - - // Click - function click(e) { - if (!self.settings('eventsEnabled')) - return; - - // Generic event - self.dispatchEvent('click', e); - - // Are we on a node? - var element = new Element(e.target); - - if (element.isNode()) - self.dispatchEvent('clickNode', { - node: graph.nodes(element.attr('data-node-id')) - }); - else - self.dispatchEvent('clickStage'); - - e.preventDefault(); - e.stopPropagation(); - } - - // Double click - function doubleClick(e) { - if (!self.settings('eventsEnabled')) - return; - - // Generic event - self.dispatchEvent('doubleClick', e); - - // Are we on a node? - var element = new Element(e.target); - - if (element.isNode()) - self.dispatchEvent('doubleClickNode', { - node: graph.nodes(element.attr('data-node-id')) - }); - else - self.dispatchEvent('doubleClickStage'); - - e.preventDefault(); - e.stopPropagation(); - } - - // On over - function onOver(e) { - var target = e.toElement || e.target; - - if (!self.settings('eventsEnabled') || !target) - return; - - var el = new Element(target); - - if (el.isNode()) { - self.dispatchEvent('overNode', { - node: graph.nodes(el.attr('data-node-id')) - }); - } - else if (el.isEdge()) { - var edge = graph.edges(el.attr('data-edge-id')); - self.dispatchEvent('overEdge', { - edge: edge, - source: graph.nodes(edge.source), - target: graph.nodes(edge.target) - }); - } - } - - // On out - function onOut(e) { - var target = e.fromElement || e.originalTarget; - - if (!self.settings('eventsEnabled')) - return; - - var el = new Element(target); - - if (el.isNode()) { - self.dispatchEvent('outNode', { - node: graph.nodes(el.attr('data-node-id')) - }); - } - else if (el.isEdge()) { - var edge = graph.edges(el.attr('data-edge-id')); - self.dispatchEvent('outEdge', { - edge: edge, - source: graph.nodes(edge.source), - target: graph.nodes(edge.target) - }); - } - } - - // Registering Events: - - // Click - container.addEventListener('click', click, false); - sigma.utils.doubleClick(container, 'click', doubleClick); - - // Touch counterparts - container.addEventListener('touchstart', click, false); - sigma.utils.doubleClick(container, 'touchstart', doubleClick); - - // Mouseover - container.addEventListener('mouseover', onOver, true); - - // Mouseout - container.addEventListener('mouseout', onOut, true); - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.misc'); - - /** - * This method listens to "overNode", "outNode", "overEdge" and "outEdge" - * events from a renderer and renders the nodes differently on the top layer. - * The goal is to make any node label readable with the mouse, and to - * highlight hovered nodes and edges. - * - * It has to be called in the scope of the related renderer. - */ - sigma.misc.drawHovers = function(prefix) { - var self = this, - hoveredNodes = {}, - hoveredEdges = {}; - - this.bind('overNode', function(event) { - var node = event.data.node; - if (!node.hidden) { - hoveredNodes[node.id] = node; - draw(); - } - }); - - this.bind('outNode', function(event) { - delete hoveredNodes[event.data.node.id]; - draw(); - }); - - this.bind('overEdge', function(event) { - var edge = event.data.edge; - if (!edge.hidden) { - hoveredEdges[edge.id] = edge; - draw(); - } - }); - - this.bind('outEdge', function(event) { - delete hoveredEdges[event.data.edge.id]; - draw(); - }); - - this.bind('render', function(event) { - draw(); - }); - - function draw() { - // Clear self.contexts.hover: - self.contexts.hover.canvas.width = self.contexts.hover.canvas.width; - - var k, - source, - target, - hoveredNode, - hoveredEdge, - defaultNodeType = self.settings('defaultNodeType'), - defaultEdgeType = self.settings('defaultEdgeType'), - nodeRenderers = sigma.canvas.hovers, - edgeRenderers = sigma.canvas.edgehovers, - extremitiesRenderers = sigma.canvas.extremities, - embedSettings = self.settings.embedObjects({ - prefix: prefix - }); - - // Node render: single hover - if ( - embedSettings('enableHovering') && - embedSettings('singleHover') && - Object.keys(hoveredNodes).length - ) { - hoveredNode = hoveredNodes[Object.keys(hoveredNodes)[0]]; - ( - nodeRenderers[hoveredNode.type] || - nodeRenderers[defaultNodeType] || - nodeRenderers.def - )( - hoveredNode, - self.contexts.hover, - embedSettings - ); - } - - // Node render: multiple hover - if ( - embedSettings('enableHovering') && - !embedSettings('singleHover') - ) - for (k in hoveredNodes) - ( - nodeRenderers[hoveredNodes[k].type] || - nodeRenderers[defaultNodeType] || - nodeRenderers.def - )( - hoveredNodes[k], - self.contexts.hover, - embedSettings - ); - - // Edge render: single hover - if ( - embedSettings('enableEdgeHovering') && - embedSettings('singleHover') && - Object.keys(hoveredEdges).length - ) { - hoveredEdge = hoveredEdges[Object.keys(hoveredEdges)[0]]; - source = self.graph.nodes(hoveredEdge.source); - target = self.graph.nodes(hoveredEdge.target); - - if (! hoveredEdge.hidden) { - ( - edgeRenderers[hoveredEdge.type] || - edgeRenderers[defaultEdgeType] || - edgeRenderers.def - ) ( - hoveredEdge, - source, - target, - self.contexts.hover, - embedSettings - ); - - if (embedSettings('edgeHoverExtremities')) { - ( - extremitiesRenderers[hoveredEdge.type] || - extremitiesRenderers.def - )( - hoveredEdge, - source, - target, - self.contexts.hover, - embedSettings - ); - - } else { - // Avoid edges rendered over nodes: - ( - sigma.canvas.nodes[source.type] || - sigma.canvas.nodes.def - ) ( - source, - self.contexts.hover, - embedSettings - ); - ( - sigma.canvas.nodes[target.type] || - sigma.canvas.nodes.def - ) ( - target, - self.contexts.hover, - embedSettings - ); - } - } - } - - // Edge render: multiple hover - if ( - embedSettings('enableEdgeHovering') && - !embedSettings('singleHover') - ) { - for (k in hoveredEdges) { - hoveredEdge = hoveredEdges[k]; - source = self.graph.nodes(hoveredEdge.source); - target = self.graph.nodes(hoveredEdge.target); - - if (!hoveredEdge.hidden) { - ( - edgeRenderers[hoveredEdge.type] || - edgeRenderers[defaultEdgeType] || - edgeRenderers.def - ) ( - hoveredEdge, - source, - target, - self.contexts.hover, - embedSettings - ); - - if (embedSettings('edgeHoverExtremities')) { - ( - extremitiesRenderers[hoveredEdge.type] || - extremitiesRenderers.def - )( - hoveredEdge, - source, - target, - self.contexts.hover, - embedSettings - ); - } else { - // Avoid edges rendered over nodes: - ( - sigma.canvas.nodes[source.type] || - sigma.canvas.nodes.def - ) ( - source, - self.contexts.hover, - embedSettings - ); - ( - sigma.canvas.nodes[target.type] || - sigma.canvas.nodes.def - ) ( - target, - self.contexts.hover, - embedSettings - ); - } - } - } - } - } - }; -}).call(this); diff --git a/js/lib/sigma/sigma.require.js b/js/lib/sigma/sigma.require.js deleted file mode 100755 index 08381e6..0000000 --- a/js/lib/sigma/sigma.require.js +++ /dev/null @@ -1,12224 +0,0 @@ -;(function(undefined) { - 'use strict'; - - var __instances = {}; - - /** - * This is the sigma instances constructor. One instance of sigma represent - * one graph. It is possible to represent this grapĥ with several renderers - * at the same time. By default, the default renderer (WebGL + Canvas - * polyfill) will be used as the only renderer, with the container specified - * in the configuration. - * - * @param {?*} conf The configuration of the instance. There are a lot of - * different recognized forms to instantiate sigma, check - * example files, documentation in this file and unit - * tests to know more. - * @return {sigma} The fresh new sigma instance. - * - * Instanciating sigma: - * ******************** - * If no parameter is given to the constructor, the instance will be created - * without any renderer or camera. It will just instantiate the graph, and - * other modules will have to be instantiated through the public methods, - * like "addRenderer" etc: - * - * > s0 = new sigma(); - * > s0.addRenderer({ - * > type: 'canvas', - * > container: 'my-container-id' - * > }); - * - * In most of the cases, sigma will simply be used with the default renderer. - * Then, since the only required parameter is the DOM container, there are - * some simpler way to call the constructor. The four following calls do the - * exact same things: - * - * > s1 = new sigma('my-container-id'); - * > s2 = new sigma(document.getElementById('my-container-id')); - * > s3 = new sigma({ - * > container: document.getElementById('my-container-id') - * > }); - * > s4 = new sigma({ - * > renderers: [{ - * > container: document.getElementById('my-container-id') - * > }] - * > }); - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters, when calling the - * constructor with to top level configuration object (fourth case in the - * previous examples): - * - * {?string} id The id of the instance. It will be generated - * automatically if not specified. - * {?array} renderers An array containing objects describing renderers. - * {?object} graph An object containing an array of nodes and an array - * of edges, to avoid having to add them by hand later. - * {?object} settings An object containing instance specific settings that - * will override the default ones defined in the object - * sigma.settings. - */ - var sigma = function(conf) { - // Local variables: - // **************** - var i, - l, - a, - c, - o, - id; - - sigma.classes.dispatcher.extend(this); - - // Private attributes: - // ******************* - var _self = this, - _conf = conf || {}; - - // Little shortcut: - // **************** - // The configuration is supposed to have a list of the configuration - // objects for each renderer. - // - If there are no configuration at all, then nothing is done. - // - If there are no renderer list, the given configuration object will be - // considered as describing the first and only renderer. - // - If there are no renderer list nor "container" object, it will be - // considered as the container itself (a DOM element). - // - If the argument passed to sigma() is a string, it will be considered - // as the ID of the DOM container. - if ( - typeof _conf === 'string' || - _conf instanceof HTMLElement - ) - _conf = { - renderers: [_conf] - }; - else if (Object.prototype.toString.call(_conf) === '[object Array]') - _conf = { - renderers: _conf - }; - - // Also check "renderer" and "container" keys: - o = _conf.renderers || _conf.renderer || _conf.container; - if (!_conf.renderers || _conf.renderers.length === 0) - if ( - typeof o === 'string' || - o instanceof HTMLElement || - (typeof o === 'object' && 'container' in o) - ) - _conf.renderers = [o]; - - // Recense the instance: - if (_conf.id) { - if (__instances[_conf.id]) - throw 'sigma: Instance "' + _conf.id + '" already exists.'; - Object.defineProperty(this, 'id', { - value: _conf.id - }); - } else { - id = 0; - while (__instances[id]) - id++; - Object.defineProperty(this, 'id', { - value: '' + id - }); - } - __instances[this.id] = this; - - // Initialize settings function: - this.settings = new sigma.classes.configurable( - sigma.settings, - _conf.settings || {} - ); - - // Initialize locked attributes: - Object.defineProperty(this, 'graph', { - value: new sigma.classes.graph(this.settings), - configurable: true - }); - Object.defineProperty(this, 'middlewares', { - value: [], - configurable: true - }); - Object.defineProperty(this, 'cameras', { - value: {}, - configurable: true - }); - Object.defineProperty(this, 'renderers', { - value: {}, - configurable: true - }); - Object.defineProperty(this, 'renderersPerCamera', { - value: {}, - configurable: true - }); - Object.defineProperty(this, 'cameraFrames', { - value: {}, - configurable: true - }); - Object.defineProperty(this, 'camera', { - get: function() { - return this.cameras[0]; - } - }); - Object.defineProperty(this, 'events', { - value: [ - 'click', - 'rightClick', - 'clickStage', - 'doubleClickStage', - 'rightClickStage', - 'clickNode', - 'clickNodes', - 'doubleClickNode', - 'doubleClickNodes', - 'rightClickNode', - 'rightClickNodes', - 'overNode', - 'overNodes', - 'outNode', - 'outNodes', - 'downNode', - 'downNodes', - 'upNode', - 'upNodes' - ], - configurable: true - }); - - // Add a custom handler, to redispatch events from renderers: - this._handler = (function(e) { - var k, - data = {}; - - for (k in e.data) - data[k] = e.data[k]; - - data.renderer = e.target; - this.dispatchEvent(e.type, data); - }).bind(this); - - // Initialize renderers: - a = _conf.renderers || []; - for (i = 0, l = a.length; i < l; i++) - this.addRenderer(a[i]); - - // Initialize middlewares: - a = _conf.middlewares || []; - for (i = 0, l = a.length; i < l; i++) - this.middlewares.push( - typeof a[i] === 'string' ? - sigma.middlewares[a[i]] : - a[i] - ); - - // Check if there is already a graph to fill in: - if (typeof _conf.graph === 'object' && _conf.graph) { - this.graph.read(_conf.graph); - - // If a graph is given to the to the instance, the "refresh" method is - // directly called: - this.refresh(); - } - - // Deal with resize: - window.addEventListener('resize', function() { - if (_self.settings) - _self.refresh(); - }); - }; - - - - - /** - * This methods will instantiate and reference a new camera. If no id is - * specified, then an automatic id will be generated. - * - * @param {?string} id Eventually the camera id. - * @return {sigma.classes.camera} The fresh new camera instance. - */ - sigma.prototype.addCamera = function(id) { - var self = this, - camera; - - if (!arguments.length) { - id = 0; - while (this.cameras['' + id]) - id++; - id = '' + id; - } - - if (this.cameras[id]) - throw 'sigma.addCamera: The camera "' + id + '" already exists.'; - - camera = new sigma.classes.camera(id, this.graph, this.settings); - this.cameras[id] = camera; - - // Add a quadtree to the camera: - camera.quadtree = new sigma.classes.quad(); - - // Add an edgequadtree to the camera: - if (sigma.classes.edgequad !== undefined) { - camera.edgequadtree = new sigma.classes.edgequad(); - } - - camera.bind('coordinatesUpdated', function(e) { - self.renderCamera(camera, camera.isAnimated); - }); - - this.renderersPerCamera[id] = []; - - return camera; - }; - - /** - * This method kills a camera, and every renderer attached to it. - * - * @param {string|camera} v The camera to kill or its ID. - * @return {sigma} Returns the instance. - */ - sigma.prototype.killCamera = function(v) { - v = typeof v === 'string' ? this.cameras[v] : v; - - if (!v) - throw 'sigma.killCamera: The camera is undefined.'; - - var i, - l, - a = this.renderersPerCamera[v.id]; - - for (l = a.length, i = l - 1; i >= 0; i--) - this.killRenderer(a[i]); - - delete this.renderersPerCamera[v.id]; - delete this.cameraFrames[v.id]; - delete this.cameras[v.id]; - - if (v.kill) - v.kill(); - - return this; - }; - - /** - * This methods will instantiate and reference a new renderer. The "type" - * argument can be the constructor or its name in the "sigma.renderers" - * package. If no type is specified, then "sigma.renderers.def" will be used. - * If no id is specified, then an automatic id will be generated. - * - * @param {?object} options Eventually some options to give to the renderer - * constructor. - * @return {renderer} The fresh new renderer instance. - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters in the "options" - * object: - * - * {?string} id Eventually the renderer id. - * {?(function|string)} type Eventually the renderer constructor or its - * name in the "sigma.renderers" package. - * {?(camera|string)} camera Eventually the renderer camera or its - * id. - */ - sigma.prototype.addRenderer = function(options) { - var id, - fn, - camera, - renderer, - o = options || {}; - - // Polymorphism: - if (typeof o === 'string') - o = { - container: document.getElementById(o) - }; - else if (o instanceof HTMLElement) - o = { - container: o - }; - - // If the container still is a string, we get it by id - if (typeof o.container === 'string') - o.container = document.getElementById(o.container); - - // Reference the new renderer: - if (!('id' in o)) { - id = 0; - while (this.renderers['' + id]) - id++; - id = '' + id; - } else - id = o.id; - - if (this.renderers[id]) - throw 'sigma.addRenderer: The renderer "' + id + '" already exists.'; - - // Find the good constructor: - fn = typeof o.type === 'function' ? o.type : sigma.renderers[o.type]; - fn = fn || sigma.renderers.def; - - // Find the good camera: - camera = 'camera' in o ? - ( - o.camera instanceof sigma.classes.camera ? - o.camera : - this.cameras[o.camera] || this.addCamera(o.camera) - ) : - this.addCamera(); - - if (this.cameras[camera.id] !== camera) - throw 'sigma.addRenderer: The camera is not properly referenced.'; - - // Instantiate: - renderer = new fn(this.graph, camera, this.settings, o); - this.renderers[id] = renderer; - Object.defineProperty(renderer, 'id', { - value: id - }); - - // Bind events: - if (renderer.bind) - renderer.bind( - [ - 'click', - 'rightClick', - 'clickStage', - 'doubleClickStage', - 'rightClickStage', - 'clickNode', - 'clickNodes', - 'clickEdge', - 'clickEdges', - 'doubleClickNode', - 'doubleClickNodes', - 'doubleClickEdge', - 'doubleClickEdges', - 'rightClickNode', - 'rightClickNodes', - 'rightClickEdge', - 'rightClickEdges', - 'overNode', - 'overNodes', - 'overEdge', - 'overEdges', - 'outNode', - 'outNodes', - 'outEdge', - 'outEdges', - 'downNode', - 'downNodes', - 'downEdge', - 'downEdges', - 'upNode', - 'upNodes', - 'upEdge', - 'upEdges' - ], - this._handler - ); - - // Reference the renderer by its camera: - this.renderersPerCamera[camera.id].push(renderer); - - return renderer; - }; - - /** - * This method kills a renderer. - * - * @param {string|renderer} v The renderer to kill or its ID. - * @return {sigma} Returns the instance. - */ - sigma.prototype.killRenderer = function(v) { - v = typeof v === 'string' ? this.renderers[v] : v; - - if (!v) - throw 'sigma.killRenderer: The renderer is undefined.'; - - var a = this.renderersPerCamera[v.camera.id], - i = a.indexOf(v); - - if (i >= 0) - a.splice(i, 1); - - if (v.kill) - v.kill(); - - delete this.renderers[v.id]; - - return this; - }; - - - - - /** - * This method calls the "render" method of each renderer, with the same - * arguments than the "render" method, but will also check if the renderer - * has a "process" method, and call it if it exists. - * - * It is useful for quadtrees or WebGL processing, for instance. - * - * @param {?object} options Eventually some options to give to the refresh - * method. - * @return {sigma} Returns the instance itself. - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters in the "options" - * object: - * - * {?boolean} skipIndexation A flag specifying wether or not the refresh - * function should reindex the graph in the - * quadtrees or not (default: false). - */ - sigma.prototype.refresh = function(options) { - var i, - l, - k, - a, - c, - bounds, - prefix = 0; - - options = options || {}; - - // Call each middleware: - a = this.middlewares || []; - for (i = 0, l = a.length; i < l; i++) - a[i].call( - this, - (i === 0) ? '' : 'tmp' + prefix + ':', - (i === l - 1) ? 'ready:' : ('tmp' + (++prefix) + ':') - ); - - // Then, for each camera, call the "rescale" middleware, unless the - // settings specify not to: - for (k in this.cameras) { - c = this.cameras[k]; - if ( - c.settings('autoRescale') && - this.renderersPerCamera[c.id] && - this.renderersPerCamera[c.id].length - ) - sigma.middlewares.rescale.call( - this, - a.length ? 'ready:' : '', - c.readPrefix, - { - width: this.renderersPerCamera[c.id][0].width, - height: this.renderersPerCamera[c.id][0].height - } - ); - else - sigma.middlewares.copy.call( - this, - a.length ? 'ready:' : '', - c.readPrefix - ); - - if (!options.skipIndexation) { - // Find graph boundaries: - bounds = sigma.utils.getBoundaries( - this.graph, - c.readPrefix - ); - - // Refresh quadtree: - c.quadtree.index(this.graph.nodes(), { - prefix: c.readPrefix, - bounds: { - x: bounds.minX, - y: bounds.minY, - width: bounds.maxX - bounds.minX, - height: bounds.maxY - bounds.minY - } - }); - - // Refresh edgequadtree: - if ( - c.edgequadtree !== undefined && - c.settings('drawEdges') && - c.settings('enableEdgeHovering') - ) { - c.edgequadtree.index(this.graph, { - prefix: c.readPrefix, - bounds: { - x: bounds.minX, - y: bounds.minY, - width: bounds.maxX - bounds.minX, - height: bounds.maxY - bounds.minY - } - }); - } - } - } - - // Call each renderer: - a = Object.keys(this.renderers); - for (i = 0, l = a.length; i < l; i++) - if (this.renderers[a[i]].process) { - if (this.settings('skipErrors')) - try { - this.renderers[a[i]].process(); - } catch (e) { - console.log( - 'Warning: The renderer "' + a[i] + '" crashed on ".process()"' - ); - } - else - this.renderers[a[i]].process(); - } - - this.render(); - - return this; - }; - - /** - * This method calls the "render" method of each renderer. - * - * @return {sigma} Returns the instance itself. - */ - sigma.prototype.render = function() { - var i, - l, - a, - prefix = 0; - - // Call each renderer: - a = Object.keys(this.renderers); - for (i = 0, l = a.length; i < l; i++) - if (this.settings('skipErrors')) - try { - this.renderers[a[i]].render(); - } catch (e) { - if (this.settings('verbose')) - console.log( - 'Warning: The renderer "' + a[i] + '" crashed on ".render()"' - ); - } - else - this.renderers[a[i]].render(); - - return this; - }; - - /** - * This method calls the "render" method of each renderer that is bound to - * the specified camera. To improve the performances, if this method is - * called too often, the number of effective renderings is limitated to one - * per frame, unless you are using the "force" flag. - * - * @param {sigma.classes.camera} camera The camera to render. - * @param {?boolean} force If true, will render the camera - * directly. - * @return {sigma} Returns the instance itself. - */ - sigma.prototype.renderCamera = function(camera, force) { - var i, - l, - a, - self = this; - - if (force) { - a = this.renderersPerCamera[camera.id]; - for (i = 0, l = a.length; i < l; i++) - if (this.settings('skipErrors')) - try { - a[i].render(); - } catch (e) { - if (this.settings('verbose')) - console.log( - 'Warning: The renderer "' + a[i].id + '" crashed on ".render()"' - ); - } - else - a[i].render(); - } else { - if (!this.cameraFrames[camera.id]) { - a = this.renderersPerCamera[camera.id]; - for (i = 0, l = a.length; i < l; i++) - if (this.settings('skipErrors')) - try { - a[i].render(); - } catch (e) { - if (this.settings('verbose')) - console.log( - 'Warning: The renderer "' + - a[i].id + - '" crashed on ".render()"' - ); - } - else - a[i].render(); - - this.cameraFrames[camera.id] = requestAnimationFrame(function() { - delete self.cameraFrames[camera.id]; - }); - } - } - - return this; - }; - - /** - * This method calls the "kill" method of each module and destroys any - * reference from the instance. - */ - sigma.prototype.kill = function() { - var k; - - // Dispatching event - this.dispatchEvent('kill'); - - // Kill graph: - this.graph.kill(); - - // Kill middlewares: - delete this.middlewares; - - // Kill each renderer: - for (k in this.renderers) - this.killRenderer(this.renderers[k]); - - // Kill each camera: - for (k in this.cameras) - this.killCamera(this.cameras[k]); - - delete this.renderers; - delete this.cameras; - - // Kill everything else: - for (k in this) - if (this.hasOwnProperty(k)) - delete this[k]; - - delete __instances[this.id]; - }; - - - - - /** - * Returns a clone of the instances object or a specific running instance. - * - * @param {?string} id Eventually an instance ID. - * @return {object} The related instance or a clone of the instances - * object. - */ - sigma.instances = function(id) { - return arguments.length ? - __instances[id] : - sigma.utils.extend({}, __instances); - }; - - - - /** - * The current version of sigma: - */ - sigma.version = '1.1.0'; - - - - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') - throw 'An object called sigma is already in the global scope.'; - - this.sigma = sigma; - -}).call(this); - -/** - * conrad.js is a tiny JavaScript jobs scheduler, - * - * Version: 0.1.0 - * Sources: http://github.com/jacomyal/conrad.js - * Doc: http://github.com/jacomyal/conrad.js#readme - * - * License: - * -------- - * Copyright © 2013 Alexis Jacomy, Sciences-Po médialab - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * The Software is provided "as is", without warranty of any kind, express or - * implied, including but not limited to the warranties of merchantability, - * fitness for a particular purpose and noninfringement. In no event shall the - * authors or copyright holders be liable for any claim, damages or other - * liability, whether in an action of contract, tort or otherwise, arising - * from, out of or in connection with the software or the use or other dealings - * in the Software. - */ -(function(global) { - 'use strict'; - - // Check that conrad.js has not been loaded yet: - if (global.conrad) - throw new Error('conrad already exists'); - - - /** - * PRIVATE VARIABLES: - * ****************** - */ - - /** - * A flag indicating whether conrad is running or not. - * - * @type {Number} - */ - var _lastFrameTime; - - /** - * A flag indicating whether conrad is running or not. - * - * @type {Boolean} - */ - var _isRunning = false; - - /** - * The hash of registered jobs. Each job must at least have a unique ID - * under the key "id" and a function under the key "job". This hash - * contains each running job and each waiting job. - * - * @type {Object} - */ - var _jobs = {}; - - /** - * The hash of currently running jobs. - * - * @type {Object} - */ - var _runningJobs = {}; - - /** - * The array of currently running jobs, sorted by priority. - * - * @type {Array} - */ - var _sortedByPriorityJobs = []; - - /** - * The array of currently waiting jobs. - * - * @type {Object} - */ - var _waitingJobs = {}; - - /** - * The array of finished jobs. They are stored in an array, since two jobs - * with the same "id" can happen at two different times. - * - * @type {Array} - */ - var _doneJobs = []; - - /** - * A dirty flag to keep conrad from starting: Indeed, when addJob() is called - * with several jobs, conrad must be started only at the end. This flag keeps - * me from duplicating the code that effectively adds a job. - * - * @type {Boolean} - */ - var _noStart = false; - - /** - * An hash containing some global settings about how conrad.js should - * behave. - * - * @type {Object} - */ - var _parameters = { - frameDuration: 20, - history: true - }; - - /** - * This object contains every handlers bound to conrad events. It does not - * requirea any DOM implementation, since the events are all JavaScript. - * - * @type {Object} - */ - var _handlers = Object.create(null); - - - /** - * PRIVATE FUNCTIONS: - * ****************** - */ - - /** - * Will execute the handler everytime that the indicated event (or the - * indicated events) will be triggered. - * - * @param {string|array|object} events The name of the event (or the events - * separated by spaces). - * @param {function(Object)} handler The handler to bind. - * @return {Object} Returns conrad. - */ - function _bind(events, handler) { - var i, - i_end, - event, - eArray; - - if (!arguments.length) - return; - else if ( - arguments.length === 1 && - Object(arguments[0]) === arguments[0] - ) - for (events in arguments[0]) - _bind(events, arguments[0][events]); - else if (arguments.length > 1) { - eArray = - Array.isArray(events) ? - events : - events.split(/ /); - - for (i = 0, i_end = eArray.length; i !== i_end; i += 1) { - event = eArray[i]; - - if (!_handlers[event]) - _handlers[event] = []; - - // Using an object instead of directly the handler will make possible - // later to add flags - _handlers[event].push({ - handler: handler - }); - } - } - } - - /** - * Removes the handler from a specified event (or specified events). - * - * @param {?string} events The name of the event (or the events - * separated by spaces). If undefined, - * then all handlers are removed. - * @param {?function(Object)} handler The handler to unbind. If undefined, - * each handler bound to the event or the - * events will be removed. - * @return {Object} Returns conrad. - */ - function _unbind(events, handler) { - var i, - i_end, - j, - j_end, - a, - event, - eArray = Array.isArray(events) ? - events : - events.split(/ /); - - if (!arguments.length) - _handlers = Object.create(null); - else if (handler) { - for (i = 0, i_end = eArray.length; i !== i_end; i += 1) { - event = eArray[i]; - if (_handlers[event]) { - a = []; - for (j = 0, j_end = _handlers[event].length; j !== j_end; j += 1) - if (_handlers[event][j].handler !== handler) - a.push(_handlers[event][j]); - - _handlers[event] = a; - } - - if (_handlers[event] && _handlers[event].length === 0) - delete _handlers[event]; - } - } else - for (i = 0, i_end = eArray.length; i !== i_end; i += 1) - delete _handlers[eArray[i]]; - } - - /** - * Executes each handler bound to the event. - * - * @param {string} events The name of the event (or the events separated - * by spaces). - * @param {?Object} data The content of the event (optional). - * @return {Object} Returns conrad. - */ - function _dispatch(events, data) { - var i, - j, - i_end, - j_end, - event, - eventName, - eArray = Array.isArray(events) ? - events : - events.split(/ /); - - data = data === undefined ? {} : data; - - for (i = 0, i_end = eArray.length; i !== i_end; i += 1) { - eventName = eArray[i]; - - if (_handlers[eventName]) { - event = { - type: eventName, - data: data || {} - }; - - for (j = 0, j_end = _handlers[eventName].length; j !== j_end; j += 1) - try { - _handlers[eventName][j].handler(event); - } catch (e) {} - } - } - } - - /** - * Executes the most prioritary job once, and deals with filling the stats - * (done, time, averageTime, currentTime, etc...). - * - * @return {?Object} Returns the job object if it has to be killed, null else. - */ - function _executeFirstJob() { - var i, - l, - test, - kill, - pushed = false, - time = __dateNow(), - job = _sortedByPriorityJobs.shift(); - - // Execute the job and look at the result: - test = job.job(); - - // Deal with stats: - time = __dateNow() - time; - job.done++; - job.time += time; - job.currentTime += time; - job.weightTime = job.currentTime / (job.weight || 1); - job.averageTime = job.time / job.done; - - // Check if the job has to be killed: - kill = job.count ? (job.count <= job.done) : !test; - - // Reset priorities: - if (!kill) { - for (i = 0, l = _sortedByPriorityJobs.length; i < l; i++) - if (_sortedByPriorityJobs[i].weightTime > job.weightTime) { - _sortedByPriorityJobs.splice(i, 0, job); - pushed = true; - break; - } - - if (!pushed) - _sortedByPriorityJobs.push(job); - } - - return kill ? job : null; - } - - /** - * Activates a job, by adding it to the _runningJobs object and the - * _sortedByPriorityJobs array. It also initializes its currentTime value. - * - * @param {Object} job The job to activate. - */ - function _activateJob(job) { - var l = _sortedByPriorityJobs.length; - - // Add the job to the running jobs: - _runningJobs[job.id] = job; - job.status = 'running'; - - // Add the job to the priorities: - if (l) { - job.weightTime = _sortedByPriorityJobs[l - 1].weightTime; - job.currentTime = job.weightTime * (job.weight || 1); - } - - // Initialize the job and dispatch: - job.startTime = __dateNow(); - _dispatch('jobStarted', __clone(job)); - - _sortedByPriorityJobs.push(job); - } - - /** - * The main loop of conrad.js: - * . It executes job such that they all occupate the same processing time. - * . It stops jobs that do not need to be executed anymore. - * . It triggers callbacks when it is relevant. - * . It starts waiting jobs when they need to be started. - * . It injects frames to keep a constant frapes per second ratio. - * . It stops itself when there are no more jobs to execute. - */ - function _loop() { - var k, - o, - l, - job, - time, - deadJob; - - // Deal with the newly added jobs (the _jobs object): - for (k in _jobs) { - job = _jobs[k]; - - if (job.after) - _waitingJobs[k] = job; - else - _activateJob(job); - - delete _jobs[k]; - } - - // Set the _isRunning flag to false if there are no running job: - _isRunning = !!_sortedByPriorityJobs.length; - - // Deal with the running jobs (the _runningJobs object): - while ( - _sortedByPriorityJobs.length && - __dateNow() - _lastFrameTime < _parameters.frameDuration - ) { - deadJob = _executeFirstJob(); - - // Deal with the case where the job has ended: - if (deadJob) { - _killJob(deadJob.id); - - // Check for waiting jobs: - for (k in _waitingJobs) - if (_waitingJobs[k].after === deadJob.id) { - _activateJob(_waitingJobs[k]); - delete _waitingJobs[k]; - } - } - } - - // Check if conrad still has jobs to deal with, and kill it if not: - if (_isRunning) { - // Update the _lastFrameTime: - _lastFrameTime = __dateNow(); - - _dispatch('enterFrame'); - setTimeout(_loop, 0); - } else - _dispatch('stop'); - } - - /** - * Adds one or more jobs, and starts the loop if no job was running before. A - * job is at least a unique string "id" and a function, and there are some - * parameters that you can specify for each job to modify the way conrad will - * execute it. If a job is added with the "id" of another job that is waiting - * or still running, an error will be thrown. - * - * When a job is added, it is referenced in the _jobs object, by its id. - * Then, if it has to be executed right now, it will be also referenced in - * the _runningJobs object. If it has to wait, then it will be added into the - * _waitingJobs object, until it can start. - * - * Keep reading this documentation to see how to call this method. - * - * @return {Object} Returns conrad. - * - * Adding one job: - * *************** - * Basically, a job is defined by its string id and a function (the job). It - * is also possible to add some parameters: - * - * > conrad.addJob('myJobId', myJobFunction); - * > conrad.addJob('myJobId', { - * > job: myJobFunction, - * > someParameter: someValue - * > }); - * > conrad.addJob({ - * > id: 'myJobId', - * > job: myJobFunction, - * > someParameter: someValue - * > }); - * - * Adding several jobs: - * ******************** - * When adding several jobs at the same time, it is possible to specify - * parameters for each one individually or for all: - * - * > conrad.addJob([ - * > { - * > id: 'myJobId1', - * > job: myJobFunction1, - * > someParameter1: someValue1 - * > }, - * > { - * > id: 'myJobId2', - * > job: myJobFunction2, - * > someParameter2: someValue2 - * > } - * > ], { - * > someCommonParameter: someCommonValue - * > }); - * > conrad.addJob({ - * > myJobId1: {, - * > job: myJobFunction1, - * > someParameter1: someValue1 - * > }, - * > myJobId2: {, - * > job: myJobFunction2, - * > someParameter2: someValue2 - * > } - * > }, { - * > someCommonParameter: someCommonValue - * > }); - * > conrad.addJob({ - * > myJobId1: myJobFunction1, - * > myJobId2: myJobFunction2 - * > }, { - * > someCommonParameter: someCommonValue - * > }); - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters: - * - * {?Function} end A callback to execute when the job is ended. It is - * not executed if the job is killed instead of ended - * "naturally". - * {?Integer} count The number of time the job has to be executed. - * {?Number} weight If specified, the job will be executed as it was - * added "weight" times. - * {?String} after The id of another job (eventually not added yet). - * If specified, this job will start only when the - * specified "after" job is ended. - */ - function _addJob(v1, v2) { - var i, - l, - o; - - // Array of jobs: - if (Array.isArray(v1)) { - // Keep conrad to start until the last job is added: - _noStart = true; - - for (i = 0, l = v1.length; i < l; i++) - _addJob(v1[i].id, __extend(v1[i], v2)); - - _noStart = false; - if (!_isRunning) { - // Update the _lastFrameTime: - _lastFrameTime = __dateNow(); - - _dispatch('start'); - _loop(); - } - } else if (typeof v1 === 'object') { - // One job (object): - if (typeof v1.id === 'string') - _addJob(v1.id, v1); - - // Hash of jobs: - else { - // Keep conrad to start until the last job is added: - _noStart = true; - - for (i in v1) - if (typeof v1[i] === 'function') - _addJob(i, __extend({ - job: v1[i] - }, v2)); - else - _addJob(i, __extend(v1[i], v2)); - - _noStart = false; - if (!_isRunning) { - // Update the _lastFrameTime: - _lastFrameTime = __dateNow(); - - _dispatch('start'); - _loop(); - } - } - - // One job (string, *): - } else if (typeof v1 === 'string') { - if (_hasJob(v1)) - throw new Error( - '[conrad.addJob] Job with id "' + v1 + '" already exists.' - ); - - // One job (string, function): - if (typeof v2 === 'function') { - o = { - id: v1, - done: 0, - time: 0, - status: 'waiting', - currentTime: 0, - averageTime: 0, - weightTime: 0, - job: v2 - }; - - // One job (string, object): - } else if (typeof v2 === 'object') { - o = __extend( - { - id: v1, - done: 0, - time: 0, - status: 'waiting', - currentTime: 0, - averageTime: 0, - weightTime: 0 - }, - v2 - ); - - // If none of those cases, throw an error: - } else - throw new Error('[conrad.addJob] Wrong arguments.'); - - // Effectively add the job: - _jobs[v1] = o; - _dispatch('jobAdded', __clone(o)); - - // Check if the loop has to be started: - if (!_isRunning && !_noStart) { - // Update the _lastFrameTime: - _lastFrameTime = __dateNow(); - - _dispatch('start'); - _loop(); - } - - // If none of those cases, throw an error: - } else - throw new Error('[conrad.addJob] Wrong arguments.'); - - return this; - } - - /** - * Kills one or more jobs, indicated by their ids. It is only possible to - * kill running jobs or waiting jobs. If you try to kill a job that does not - * exist or that is already killed, a warning will be thrown. - * - * @param {Array|String} v1 A string job id or an array of job ids. - * @return {Object} Returns conrad. - */ - function _killJob(v1) { - var i, - l, - k, - a, - job, - found = false; - - // Array of job ids: - if (Array.isArray(v1)) - for (i = 0, l = v1.length; i < l; i++) - _killJob(v1[i]); - - // One job's id: - else if (typeof v1 === 'string') { - a = [_runningJobs, _waitingJobs, _jobs]; - - // Remove the job from the hashes: - for (i = 0, l = a.length; i < l; i++) - if (v1 in a[i]) { - job = a[i][v1]; - - if (_parameters.history) { - job.status = 'done'; - _doneJobs.push(job); - } - - _dispatch('jobEnded', __clone(job)); - delete a[i][v1]; - - if (typeof job.end === 'function') - job.end(); - - found = true; - } - - // Remove the priorities array: - a = _sortedByPriorityJobs; - for (i = 0, l = a.length; i < l; i++) - if (a[i].id === v1) { - a.splice(i, 1); - break; - } - - if (!found) - throw new Error('[conrad.killJob] Job "' + v1 + '" not found.'); - - // If none of those cases, throw an error: - } else - throw new Error('[conrad.killJob] Wrong arguments.'); - - return this; - } - - /** - * Kills every running, waiting, and just added jobs. - * - * @return {Object} Returns conrad. - */ - function _killAll() { - var k, - jobs = __extend(_jobs, _runningJobs, _waitingJobs); - - // Take every jobs and push them into the _doneJobs object: - if (_parameters.history) - for (k in jobs) { - jobs[k].status = 'done'; - _doneJobs.push(jobs[k]); - - if (typeof jobs[k].end === 'function') - jobs[k].end(); - } - - // Reinitialize the different jobs lists: - _jobs = {}; - _waitingJobs = {}; - _runningJobs = {}; - _sortedByPriorityJobs = []; - - // In case some jobs are added right after the kill: - _isRunning = false; - - return this; - } - - /** - * Returns true if a job with the specified id is currently running or - * waiting, and false else. - * - * @param {String} id The id of the job. - * @return {?Object} Returns the job object if it exists. - */ - function _hasJob(id) { - var job = _jobs[id] || _runningJobs[id] || _waitingJobs[id]; - return job ? __extend(job) : null; - } - - /** - * This method will set the setting specified by "v1" to the value specified - * by "v2" if both are given, and else return the current value of the - * settings "v1". - * - * @param {String} v1 The name of the property. - * @param {?*} v2 Eventually, a value to set to the specified - * property. - * @return {Object|*} Returns the specified settings value if "v2" is not - * given, and conrad else. - */ - function _settings(v1, v2) { - var o; - - if (typeof a1 === 'string' && arguments.length === 1) - return _parameters[a1]; - else { - o = (typeof a1 === 'object' && arguments.length === 1) ? - a1 || {} : - {}; - if (typeof a1 === 'string') - o[a1] = a2; - - for (var k in o) - if (o[k] !== undefined) - _parameters[k] = o[k]; - else - delete _parameters[k]; - - return this; - } - } - - /** - * Returns true if conrad is currently running, and false else. - * - * @return {Boolean} Returns _isRunning. - */ - function _getIsRunning() { - return _isRunning; - } - - /** - * Unreference every job that is stored in the _doneJobs object. It will - * not be possible anymore to get stats about these jobs, but it will release - * the memory. - * - * @return {Object} Returns conrad. - */ - function _clearHistory() { - _doneJobs = []; - return this; - } - - /** - * Returns a snapshot of every data about jobs that wait to be started, are - * currently running or are done. - * - * It is possible to get only running, waiting or done jobs by giving - * "running", "waiting" or "done" as fist argument. - * - * It is also possible to get every job with a specified id by giving it as - * first argument. Also, using a RegExp instead of an id will return every - * jobs whose ids match the RegExp. And these two last use cases work as well - * by giving before "running", "waiting" or "done". - * - * @return {Array} The array of the matching jobs. - * - * Some call examples: - * ******************* - * > conrad.getStats('running') - * > conrad.getStats('waiting') - * > conrad.getStats('done') - * > conrad.getStats('myJob') - * > conrad.getStats(/test/) - * > conrad.getStats('running', 'myRunningJob') - * > conrad.getStats('running', /test/) - */ - function _getStats(v1, v2) { - var a, - k, - i, - l, - stats, - pattern, - isPatternString; - - if (!arguments.length) { - stats = []; - - for (k in _jobs) - stats.push(_jobs[k]); - - for (k in _waitingJobs) - stats.push(_waitingJobs[k]); - - for (k in _runningJobs) - stats.push(_runningJobs[k]); - - stats = stats.concat(_doneJobs); - } - - if (typeof v1 === 'string') - switch (v1) { - case 'waiting': - stats = __objectValues(_waitingJobs); - break; - case 'running': - stats = __objectValues(_runningJobs); - break; - case 'done': - stats = _doneJobs; - break; - default: - pattern = v1; - } - - if (v1 instanceof RegExp) - pattern = v1; - - if (!pattern && (typeof v2 === 'string' || v2 instanceof RegExp)) - pattern = v2; - - // Filter jobs if a pattern is given: - if (pattern) { - isPatternString = typeof pattern === 'string'; - - if (stats instanceof Array) { - a = stats; - } else if (typeof stats === 'object') { - a = []; - - for (k in stats) - a = a.concat(stats[k]); - } else { - a = []; - - for (k in _jobs) - a.push(_jobs[k]); - - for (k in _waitingJobs) - a.push(_waitingJobs[k]); - - for (k in _runningJobs) - a.push(_runningJobs[k]); - - a = a.concat(_doneJobs); - } - - stats = []; - for (i = 0, l = a.length; i < l; i++) - if (isPatternString ? a[i].id === pattern : a[i].id.match(pattern)) - stats.push(a[i]); - } - - return __clone(stats); - } - - - /** - * TOOLS FUNCTIONS: - * **************** - */ - - /** - * This function takes any number of objects as arguments, copies from each - * of these objects each pair key/value into a new object, and finally - * returns this object. - * - * The arguments are parsed from the last one to the first one, such that - * when two objects have keys in common, the "earliest" object wins. - * - * Example: - * ******** - * > var o1 = { - * > a: 1, - * > b: 2, - * > c: '3' - * > }, - * > o2 = { - * > c: '4', - * > d: [ 5 ] - * > }; - * > __extend(o1, o2); - * > // Returns: { - * > // a: 1, - * > // b: 2, - * > // c: '3', - * > // d: [ 5 ] - * > // }; - * - * @param {Object+} Any number of objects. - * @return {Object} The merged object. - */ - function __extend() { - var i, - k, - res = {}, - l = arguments.length; - - for (i = l - 1; i >= 0; i--) - for (k in arguments[i]) - res[k] = arguments[i][k]; - - return res; - } - - /** - * This function simply clones an object. This object must contain only - * objects, arrays and immutable values. Since it is not public, it does not - * deal with cyclic references, DOM elements and instantiated objects - so - * use it carefully. - * - * @param {Object} The object to clone. - * @return {Object} The clone. - */ - function __clone(item) { - var result, i, k, l; - - if (!item) - return item; - - if (Array.isArray(item)) { - result = []; - for (i = 0, l = item.length; i < l; i++) - result.push(__clone(item[i])); - } else if (typeof item === 'object') { - result = {}; - for (i in item) - result[i] = __clone(item[i]); - } else - result = item; - - return result; - } - - /** - * Returns an array containing the values of an object. - * - * @param {Object} The object. - * @return {Array} The array of values. - */ - function __objectValues(o) { - var k, - a = []; - - for (k in o) - a.push(o[k]); - - return a; - } - - /** - * A short "Date.now()" polyfill. - * - * @return {Number} The current time (in ms). - */ - function __dateNow() { - return Date.now ? Date.now() : new Date().getTime(); - } - - /** - * Polyfill for the Array.isArray function: - */ - if (!Array.isArray) - Array.isArray = function(v) { - return Object.prototype.toString.call(v) === '[object Array]'; - }; - - - /** - * EXPORT PUBLIC API: - * ****************** - */ - var conrad = { - hasJob: _hasJob, - addJob: _addJob, - killJob: _killJob, - killAll: _killAll, - settings: _settings, - getStats: _getStats, - isRunning: _getIsRunning, - clearHistory: _clearHistory, - - // Events management: - bind: _bind, - unbind: _unbind, - - // Version: - version: '0.1.0' - }; - - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = conrad; - exports.conrad = conrad; - } - global.conrad = conrad; -})(this); - -// Hardcoded export for the node.js version: -var sigma = this.sigma, - conrad = this.conrad; - -sigma.conrad = conrad; - -// Dirty polyfills to permit sigma usage in node -if (HTMLElement === undefined) - var HTMLElement = function() {}; - -if (window === undefined) - var window = { - addEventListener: function() {} - }; - -if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = sigma; - exports.sigma = sigma; -} - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - var _root = this; - - // Initialize packages: - sigma.utils = sigma.utils || {}; - - /** - * MISC UTILS: - */ - /** - * This function takes any number of objects as arguments, copies from each - * of these objects each pair key/value into a new object, and finally - * returns this object. - * - * The arguments are parsed from the last one to the first one, such that - * when several objects have keys in common, the "earliest" object wins. - * - * Example: - * ******** - * > var o1 = { - * > a: 1, - * > b: 2, - * > c: '3' - * > }, - * > o2 = { - * > c: '4', - * > d: [ 5 ] - * > }; - * > sigma.utils.extend(o1, o2); - * > // Returns: { - * > // a: 1, - * > // b: 2, - * > // c: '3', - * > // d: [ 5 ] - * > // }; - * - * @param {object+} Any number of objects. - * @return {object} The merged object. - */ - sigma.utils.extend = function() { - var i, - k, - res = {}, - l = arguments.length; - - for (i = l - 1; i >= 0; i--) - for (k in arguments[i]) - res[k] = arguments[i][k]; - - return res; - }; - - /** - * A short "Date.now()" polyfill. - * - * @return {Number} The current time (in ms). - */ - sigma.utils.dateNow = function() { - return Date.now ? Date.now() : new Date().getTime(); - }; - - /** - * Takes a package name as parameter and checks at each lebel if it exists, - * and if it does not, creates it. - * - * Example: - * ******** - * > sigma.utils.pkg('a.b.c'); - * > a.b.c; - * > // Object {}; - * > - * > sigma.utils.pkg('a.b.d'); - * > a.b; - * > // Object { c: {}, d: {} }; - * - * @param {string} pkgName The name of the package to create/find. - * @return {object} The related package. - */ - sigma.utils.pkg = function(pkgName) { - return (pkgName || '').split('.').reduce(function(context, objName) { - return (objName in context) ? - context[objName] : - (context[objName] = {}); - }, _root); - }; - - /** - * Returns a unique incremental number ID. - * - * Example: - * ******** - * > sigma.utils.id(); - * > // 1; - * > - * > sigma.utils.id(); - * > // 2; - * > - * > sigma.utils.id(); - * > // 3; - * - * @param {string} pkgName The name of the package to create/find. - * @return {object} The related package. - */ - sigma.utils.id = (function() { - var i = 0; - return function() { - return ++i; - }; - })(); - - /** - * This function takes an hexa color (for instance "#ffcc00" or "#fc0") or a - * rgb / rgba color (like "rgb(255,255,12)" or "rgba(255,255,12,1)") and - * returns an integer equal to "r * 255 * 255 + g * 255 + b", to gain some - * memory in the data given to WebGL shaders. - * - * Note that the function actually caches its results for better performance. - * - * @param {string} val The hexa or rgba color. - * @return {number} The number value. - */ - var floatColorCache = {}; - - sigma.utils.floatColor = function(val) { - - // Is the color already computed? - if (floatColorCache[val]) - return floatColorCache[val]; - - var original = val, - r = 0, - g = 0, - b = 0; - - if (val[0] === '#') { - val = val.slice(1); - - if (val.length === 3) { - r = parseInt(val.charAt(0) + val.charAt(0), 16); - g = parseInt(val.charAt(1) + val.charAt(1), 16); - b = parseInt(val.charAt(2) + val.charAt(2), 16); - } - else { - r = parseInt(val.charAt(0) + val.charAt(1), 16); - g = parseInt(val.charAt(2) + val.charAt(3), 16); - b = parseInt(val.charAt(4) + val.charAt(5), 16); - } - } else if (val.match(/^ *rgba? *\(/)) { - val = val.match( - /^ *rgba? *\( *([0-9]*) *, *([0-9]*) *, *([0-9]*) *(,.*)?\) *$/ - ); - r = +val[1]; - g = +val[2]; - b = +val[3]; - } - - var color = ( - r * 256 * 256 + - g * 256 + - b - ); - - // Caching the color - floatColorCache[original] = color; - - return color; - }; - - /** - * Perform a zoom into a camera, with or without animation, to the - * coordinates indicated using a specified ratio. - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters in the animation - * object: - * - * {?number} duration An amount of time that means the duration of the - * animation. If this parameter doesn't exist the - * zoom will be performed without animation. - * {?function} onComplete A function to perform it after the animation. It - * will be performed even if there is no duration. - * - * @param {camera} The camera where perform the zoom. - * @param {x} The X coordiantion where the zoom goes. - * @param {y} The Y coordiantion where the zoom goes. - * @param {ratio} The ratio to apply it to the current camera ratio. - * @param {?animation} A dictionary with options for a possible animation. - */ - sigma.utils.zoomTo = function(camera, x, y, ratio, animation) { - var settings = camera.settings, - count, - newRatio, - animationSettings, - coordinates; - - // Create the newRatio dealing with min / max: - newRatio = Math.max( - settings('zoomMin'), - Math.min( - settings('zoomMax'), - camera.ratio * ratio - ) - ); - - // Check that the new ratio is different from the initial one: - if (newRatio !== camera.ratio) { - // Create the coordinates variable: - ratio = newRatio / camera.ratio; - coordinates = { - x: x * (1 - ratio) + camera.x, - y: y * (1 - ratio) + camera.y, - ratio: newRatio - }; - - if (animation && animation.duration) { - // Complete the animation setings: - count = sigma.misc.animation.killAll(camera); - animation = sigma.utils.extend( - animation, - { - easing: count ? 'quadraticOut' : 'quadraticInOut' - } - ); - - sigma.misc.animation.camera(camera, coordinates, animation); - } else { - camera.goTo(coordinates); - if (animation && animation.onComplete) - animation.onComplete(); - } - } - }; - - /** - * Return the control point coordinates for a quadratic bezier curve. - * - * @param {number} x1 The X coordinate of the start point. - * @param {number} y1 The Y coordinate of the start point. - * @param {number} x2 The X coordinate of the end point. - * @param {number} y2 The Y coordinate of the end point. - * @return {x,y} The control point coordinates. - */ - sigma.utils.getQuadraticControlPoint = function(x1, y1, x2, y2) { - return { - x: (x1 + x2) / 2 + (y2 - y1) / 4, - y: (y1 + y2) / 2 + (x1 - x2) / 4 - }; - }; - - /** - * Compute the coordinates of the point positioned - * at length t in the quadratic bezier curve. - * - * @param {number} t In [0,1] the step percentage to reach - * the point in the curve from the context point. - * @param {number} x1 The X coordinate of the context point. - * @param {number} y1 The Y coordinate of the context point. - * @param {number} x2 The X coordinate of the ending point. - * @param {number} y2 The Y coordinate of the ending point. - * @param {number} xi The X coordinate of the control point. - * @param {number} yi The Y coordinate of the control point. - * @return {object} {x,y}. - */ - sigma.utils.getPointOnQuadraticCurve = function(t, x1, y1, x2, y2, xi, yi) { - // http://stackoverflow.com/a/5634528 - return { - x: Math.pow(1 - t, 2) * x1 + 2 * (1 - t) * t * xi + Math.pow(t, 2) * x2, - y: Math.pow(1 - t, 2) * y1 + 2 * (1 - t) * t * yi + Math.pow(t, 2) * y2 - }; - }; - - /** - * Compute the coordinates of the point positioned - * at length t in the cubic bezier curve. - * - * @param {number} t In [0,1] the step percentage to reach - * the point in the curve from the context point. - * @param {number} x1 The X coordinate of the context point. - * @param {number} y1 The Y coordinate of the context point. - * @param {number} x2 The X coordinate of the end point. - * @param {number} y2 The Y coordinate of the end point. - * @param {number} cx The X coordinate of the first control point. - * @param {number} cy The Y coordinate of the first control point. - * @param {number} dx The X coordinate of the second control point. - * @param {number} dy The Y coordinate of the second control point. - * @return {object} {x,y} The point at t. - */ - sigma.utils.getPointOnBezierCurve = - function(t, x1, y1, x2, y2, cx, cy, dx, dy) { - // http://stackoverflow.com/a/15397596 - // Blending functions: - var B0_t = Math.pow(1 - t, 3), - B1_t = 3 * t * Math.pow(1 - t, 2), - B2_t = 3 * Math.pow(t, 2) * (1 - t), - B3_t = Math.pow(t, 3); - - return { - x: (B0_t * x1) + (B1_t * cx) + (B2_t * dx) + (B3_t * x2), - y: (B0_t * y1) + (B1_t * cy) + (B2_t * dy) + (B3_t * y2) - }; - }; - - /** - * Return the coordinates of the two control points for a self loop (i.e. - * where the start point is also the end point) computed as a cubic bezier - * curve. - * - * @param {number} x The X coordinate of the node. - * @param {number} y The Y coordinate of the node. - * @param {number} size The node size. - * @return {x1,y1,x2,y2} The coordinates of the two control points. - */ - sigma.utils.getSelfLoopControlPoints = function(x , y, size) { - return { - x1: x - size * 7, - y1: y, - x2: x, - y2: y + size * 7 - }; - }; - - /** - * Return the euclidian distance between two points of a plane - * with an orthonormal basis. - * - * @param {number} x1 The X coordinate of the first point. - * @param {number} y1 The Y coordinate of the first point. - * @param {number} x2 The X coordinate of the second point. - * @param {number} y2 The Y coordinate of the second point. - * @return {number} The euclidian distance. - */ - sigma.utils.getDistance = function(x0, y0, x1, y1) { - return Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)); - }; - - /** - * Return the coordinates of the intersection points of two circles. - * - * @param {number} x0 The X coordinate of center location of the first - * circle. - * @param {number} y0 The Y coordinate of center location of the first - * circle. - * @param {number} r0 The radius of the first circle. - * @param {number} x1 The X coordinate of center location of the second - * circle. - * @param {number} y1 The Y coordinate of center location of the second - * circle. - * @param {number} r1 The radius of the second circle. - * @return {xi,yi} The coordinates of the intersection points. - */ - sigma.utils.getCircleIntersection = function(x0, y0, r0, x1, y1, r1) { - // http://stackoverflow.com/a/12219802 - var a, dx, dy, d, h, rx, ry, x2, y2; - - // dx and dy are the vertical and horizontal distances between the circle - // centers: - dx = x1 - x0; - dy = y1 - y0; - - // Determine the straight-line distance between the centers: - d = Math.sqrt((dy * dy) + (dx * dx)); - - // Check for solvability: - if (d > (r0 + r1)) { - // No solution. circles do not intersect. - return false; - } - if (d < Math.abs(r0 - r1)) { - // No solution. one circle is contained in the other. - return false; - } - - //'point 2' is the point where the line through the circle intersection - // points crosses the line between the circle centers. - - // Determine the distance from point 0 to point 2: - a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d); - - // Determine the coordinates of point 2: - x2 = x0 + (dx * a / d); - y2 = y0 + (dy * a / d); - - // Determine the distance from point 2 to either of the intersection - // points: - h = Math.sqrt((r0 * r0) - (a * a)); - - // Determine the offsets of the intersection points from point 2: - rx = -dy * (h / d); - ry = dx * (h / d); - - // Determine the absolute intersection points: - var xi = x2 + rx; - var xi_prime = x2 - rx; - var yi = y2 + ry; - var yi_prime = y2 - ry; - - return {xi: xi, xi_prime: xi_prime, yi: yi, yi_prime: yi_prime}; - }; - - /** - * Check if a point is on a line segment. - * - * @param {number} x The X coordinate of the point to check. - * @param {number} y The Y coordinate of the point to check. - * @param {number} x1 The X coordinate of the line start point. - * @param {number} y1 The Y coordinate of the line start point. - * @param {number} x2 The X coordinate of the line end point. - * @param {number} y2 The Y coordinate of the line end point. - * @param {number} epsilon The precision (consider the line thickness). - * @return {boolean} True if point is "close to" the line - * segment, false otherwise. - */ - sigma.utils.isPointOnSegment = function(x, y, x1, y1, x2, y2, epsilon) { - // http://stackoverflow.com/a/328122 - var crossProduct = Math.abs((y - y1) * (x2 - x1) - (x - x1) * (y2 - y1)), - d = sigma.utils.getDistance(x1, y1, x2, y2), - nCrossProduct = crossProduct / d; // normalized cross product - - return (nCrossProduct < epsilon && - Math.min(x1, x2) <= x && x <= Math.max(x1, x2) && - Math.min(y1, y2) <= y && y <= Math.max(y1, y2)); - }; - - /** - * Check if a point is on a quadratic bezier curve segment with a thickness. - * - * @param {number} x The X coordinate of the point to check. - * @param {number} y The Y coordinate of the point to check. - * @param {number} x1 The X coordinate of the curve start point. - * @param {number} y1 The Y coordinate of the curve start point. - * @param {number} x2 The X coordinate of the curve end point. - * @param {number} y2 The Y coordinate of the curve end point. - * @param {number} cpx The X coordinate of the curve control point. - * @param {number} cpy The Y coordinate of the curve control point. - * @param {number} epsilon The precision (consider the line thickness). - * @return {boolean} True if (x,y) is on the curve segment, - * false otherwise. - */ - sigma.utils.isPointOnQuadraticCurve = - function(x, y, x1, y1, x2, y2, cpx, cpy, epsilon) { - // Fails if the point is too far from the extremities of the segment, - // preventing for more costly computation: - var dP1P2 = sigma.utils.getDistance(x1, y1, x2, y2); - if (Math.abs(x - x1) > dP1P2 || Math.abs(y - y1) > dP1P2) { - return false; - } - - var dP1 = sigma.utils.getDistance(x, y, x1, y1), - dP2 = sigma.utils.getDistance(x, y, x2, y2), - t = 0.5, - r = (dP1 < dP2) ? -0.01 : 0.01, - rThreshold = 0.001, - i = 100, - pt = sigma.utils.getPointOnQuadraticCurve(t, x1, y1, x2, y2, cpx, cpy), - dt = sigma.utils.getDistance(x, y, pt.x, pt.y), - old_dt; - - // This algorithm minimizes the distance from the point to the curve. It - // find the optimal t value where t=0 is the start point and t=1 is the end - // point of the curve, starting from t=0.5. - // It terminates because it runs a maximum of i interations. - while (i-- > 0 && - t >= 0 && t <= 1 && - (dt > epsilon) && - (r > rThreshold || r < -rThreshold)) { - old_dt = dt; - pt = sigma.utils.getPointOnQuadraticCurve(t, x1, y1, x2, y2, cpx, cpy); - dt = sigma.utils.getDistance(x, y, pt.x, pt.y); - - if (dt > old_dt) { - // not the right direction: - // halfstep in the opposite direction - r = -r / 2; - t += r; - } - else if (t + r < 0 || t + r > 1) { - // oops, we've gone too far: - // revert with a halfstep - r = r / 2; - dt = old_dt; - } - else { - // progress: - t += r; - } - } - - return dt < epsilon; - }; - - - /** - * Check if a point is on a cubic bezier curve segment with a thickness. - * - * @param {number} x The X coordinate of the point to check. - * @param {number} y The Y coordinate of the point to check. - * @param {number} x1 The X coordinate of the curve start point. - * @param {number} y1 The Y coordinate of the curve start point. - * @param {number} x2 The X coordinate of the curve end point. - * @param {number} y2 The Y coordinate of the curve end point. - * @param {number} cpx1 The X coordinate of the 1st curve control point. - * @param {number} cpy1 The Y coordinate of the 1st curve control point. - * @param {number} cpx2 The X coordinate of the 2nd curve control point. - * @param {number} cpy2 The Y coordinate of the 2nd curve control point. - * @param {number} epsilon The precision (consider the line thickness). - * @return {boolean} True if (x,y) is on the curve segment, - * false otherwise. - */ - sigma.utils.isPointOnBezierCurve = - function(x, y, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2, epsilon) { - // Fails if the point is too far from the extremities of the segment, - // preventing for more costly computation: - var dP1CP1 = sigma.utils.getDistance(x1, y1, cpx1, cpy1); - if (Math.abs(x - x1) > dP1CP1 || Math.abs(y - y1) > dP1CP1) { - return false; - } - - var dP1 = sigma.utils.getDistance(x, y, x1, y1), - dP2 = sigma.utils.getDistance(x, y, x2, y2), - t = 0.5, - r = (dP1 < dP2) ? -0.01 : 0.01, - rThreshold = 0.001, - i = 100, - pt = sigma.utils.getPointOnBezierCurve( - t, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2), - dt = sigma.utils.getDistance(x, y, pt.x, pt.y), - old_dt; - - // This algorithm minimizes the distance from the point to the curve. It - // find the optimal t value where t=0 is the start point and t=1 is the end - // point of the curve, starting from t=0.5. - // It terminates because it runs a maximum of i interations. - while (i-- > 0 && - t >= 0 && t <= 1 && - (dt > epsilon) && - (r > rThreshold || r < -rThreshold)) { - old_dt = dt; - pt = sigma.utils.getPointOnBezierCurve( - t, x1, y1, x2, y2, cpx1, cpy1, cpx2, cpy2); - dt = sigma.utils.getDistance(x, y, pt.x, pt.y); - - if (dt > old_dt) { - // not the right direction: - // halfstep in the opposite direction - r = -r / 2; - t += r; - } - else if (t + r < 0 || t + r > 1) { - // oops, we've gone too far: - // revert with a halfstep - r = r / 2; - dt = old_dt; - } - else { - // progress: - t += r; - } - } - - return dt < epsilon; - }; - - - /** - * ************ - * EVENTS UTILS: - * ************ - */ - /** - * Here are some useful functions to unify extraction of the information we - * need with mouse events and touch events, from different browsers: - */ - - /** - * Extract the local X position from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The local X value of the mouse. - */ - sigma.utils.getX = function(e) { - return ( - (e.offsetX !== undefined && e.offsetX) || - (e.layerX !== undefined && e.layerX) || - (e.clientX !== undefined && e.clientX) - ); - }; - - /** - * Extract the local Y position from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The local Y value of the mouse. - */ - sigma.utils.getY = function(e) { - return ( - (e.offsetY !== undefined && e.offsetY) || - (e.layerY !== undefined && e.layerY) || - (e.clientY !== undefined && e.clientY) - ); - }; - - /** - * The pixel ratio of the screen. Taking zoom into account - * - * @return {number} Pixel ratio of the screen - */ - sigma.utils.getPixelRatio = function() { - var ratio = 1; - if (window.screen.deviceXDPI !== undefined && - window.screen.logicalXDPI !== undefined && - window.screen.deviceXDPI > window.screen.logicalXDPI) { - ratio = window.screen.systemXDPI / window.screen.logicalXDPI; - } - else if (window.devicePixelRatio !== undefined) { - ratio = window.devicePixelRatio; - } - return ratio; - }; - - /** - * Extract the width from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The width of the event's target. - */ - sigma.utils.getWidth = function(e) { - var w = (!e.target.ownerSVGElement) ? - e.target.width : - e.target.ownerSVGElement.width; - - return ( - (typeof w === 'number' && w) || - (w !== undefined && w.baseVal !== undefined && w.baseVal.value) - ); - }; - - /** - * Extract the center from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {object} The center of the event's target. - */ - sigma.utils.getCenter = function(e) { - var ratio = e.target.namespaceURI.indexOf('svg') !== -1 ? 1 : - sigma.utils.getPixelRatio(); - return { - x: sigma.utils.getWidth(e) / (2 * ratio), - y: sigma.utils.getHeight(e) / (2 * ratio), - }; - }; - - /** - * Convert mouse coords to sigma coords - * - * @param {event} e A mouse or touch event. - * @param {number?} x The x coord to convert - * @param {number?} x The y coord to convert - * - * @return {object} The standardized event - */ - sigma.utils.mouseCoords = function(e, x, y) { - x = x || sigma.utils.getX(e); - y = y || sigma.utils.getY(e); - return { - x: x - sigma.utils.getCenter(e).x, - y: y - sigma.utils.getCenter(e).y, - clientX: e.clientX, - clientY: e.clientY, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - altKey: e.altKey, - shiftKey: e.shiftKey - }; - }; - - /** - * Extract the height from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The height of the event's target. - */ - sigma.utils.getHeight = function(e) { - var h = (!e.target.ownerSVGElement) ? - e.target.height : - e.target.ownerSVGElement.height; - - return ( - (typeof h === 'number' && h) || - (h !== undefined && h.baseVal !== undefined && h.baseVal.value) - ); - }; - - /** - * Extract the wheel delta from a mouse or touch event. - * - * @param {event} e A mouse or touch event. - * @return {number} The wheel delta of the mouse. - */ - sigma.utils.getDelta = function(e) { - return ( - (e.wheelDelta !== undefined && e.wheelDelta) || - (e.detail !== undefined && -e.detail) - ); - }; - - /** - * Returns the offset of a DOM element. - * - * @param {DOMElement} dom The element to retrieve the position. - * @return {object} The offset of the DOM element (top, left). - */ - sigma.utils.getOffset = function(dom) { - var left = 0, - top = 0; - - while (dom) { - top = top + parseInt(dom.offsetTop); - left = left + parseInt(dom.offsetLeft); - dom = dom.offsetParent; - } - - return { - top: top, - left: left - }; - }; - - /** - * Simulates a "double click" event. - * - * @param {HTMLElement} target The event target. - * @param {string} type The event type. - * @param {function} callback The callback to execute. - */ - sigma.utils.doubleClick = function(target, type, callback) { - var clicks = 0, - self = this, - handlers; - - target._doubleClickHandler = target._doubleClickHandler || {}; - target._doubleClickHandler[type] = target._doubleClickHandler[type] || []; - handlers = target._doubleClickHandler[type]; - - handlers.push(function(e) { - clicks++; - - if (clicks === 2) { - clicks = 0; - return callback(e); - } else if (clicks === 1) { - setTimeout(function() { - clicks = 0; - }, sigma.settings.doubleClickTimeout); - } - }); - - target.addEventListener(type, handlers[handlers.length - 1], false); - }; - - /** - * Unbind simulated "double click" events. - * - * @param {HTMLElement} target The event target. - * @param {string} type The event type. - */ - sigma.utils.unbindDoubleClick = function(target, type) { - var handler, - handlers = (target._doubleClickHandler || {})[type] || []; - - while ((handler = handlers.pop())) { - target.removeEventListener(type, handler); - } - - delete (target._doubleClickHandler || {})[type]; - }; - - - - - /** - * Here are just some of the most basic easing functions, used for the - * animated camera "goTo" calls. - * - * If you need some more easings functions, don't hesitate to add them to - * sigma.utils.easings. But I will not add some more here or merge PRs - * containing, because I do not want sigma sources full of overkill and never - * used stuff... - */ - sigma.utils.easings = sigma.utils.easings || {}; - sigma.utils.easings.linearNone = function(k) { - return k; - }; - sigma.utils.easings.quadraticIn = function(k) { - return k * k; - }; - sigma.utils.easings.quadraticOut = function(k) { - return k * (2 - k); - }; - sigma.utils.easings.quadraticInOut = function(k) { - if ((k *= 2) < 1) - return 0.5 * k * k; - return - 0.5 * (--k * (k - 2) - 1); - }; - sigma.utils.easings.cubicIn = function(k) { - return k * k * k; - }; - sigma.utils.easings.cubicOut = function(k) { - return --k * k * k + 1; - }; - sigma.utils.easings.cubicInOut = function(k) { - if ((k *= 2) < 1) - return 0.5 * k * k * k; - return 0.5 * ((k -= 2) * k * k + 2); - }; - - - - - /** - * ************ - * WEBGL UTILS: - * ************ - */ - /** - * Loads a WebGL shader and returns it. - * - * @param {WebGLContext} gl The WebGLContext to use. - * @param {string} shaderSource The shader source. - * @param {number} shaderType The type of shader. - * @param {function(string): void} error Callback for errors. - * @return {WebGLShader} The created shader. - */ - sigma.utils.loadShader = function(gl, shaderSource, shaderType, error) { - var compiled, - shader = gl.createShader(shaderType); - - // Load the shader source - gl.shaderSource(shader, shaderSource); - - // Compile the shader - gl.compileShader(shader); - - // Check the compile status - compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); - - // If something went wrong: - if (!compiled) { - if (error) { - error( - 'Error compiling shader "' + shader + '":' + - gl.getShaderInfoLog(shader) - ); - } - - gl.deleteShader(shader); - return null; - } - - return shader; - }; - - /** - * Creates a program, attaches shaders, binds attrib locations, links the - * program and calls useProgram. - * - * @param {Array.} shaders The shaders to attach. - * @param {Array.} attribs The attribs names. - * @param {Array.} locations The locations for the attribs. - * @param {function(string): void} error Callback for errors. - * @return {WebGLProgram} The created program. - */ - sigma.utils.loadProgram = function(gl, shaders, attribs, loc, error) { - var i, - linked, - program = gl.createProgram(); - - for (i = 0; i < shaders.length; ++i) - gl.attachShader(program, shaders[i]); - - if (attribs) - for (i = 0; i < attribs.length; ++i) - gl.bindAttribLocation( - program, - locations ? locations[i] : i, - opt_attribs[i] - ); - - gl.linkProgram(program); - - // Check the link status - linked = gl.getProgramParameter(program, gl.LINK_STATUS); - if (!linked) { - if (error) - error('Error in program linking: ' + gl.getProgramInfoLog(program)); - - gl.deleteProgram(program); - return null; - } - - return program; - }; - - - - - /** - * ********* - * MATRICES: - * ********* - * The following utils are just here to help generating the transformation - * matrices for the WebGL renderers. - */ - sigma.utils.pkg('sigma.utils.matrices'); - - /** - * The returns a 3x3 translation matrix. - * - * @param {number} dx The X translation. - * @param {number} dy The Y translation. - * @return {array} Returns the matrix. - */ - sigma.utils.matrices.translation = function(dx, dy) { - return [ - 1, 0, 0, - 0, 1, 0, - dx, dy, 1 - ]; - }; - - /** - * The returns a 3x3 or 2x2 rotation matrix. - * - * @param {number} angle The rotation angle. - * @param {boolean} m2 If true, the function will return a 2x2 matrix. - * @return {array} Returns the matrix. - */ - sigma.utils.matrices.rotation = function(angle, m2) { - var cos = Math.cos(angle), - sin = Math.sin(angle); - - return m2 ? [ - cos, -sin, - sin, cos - ] : [ - cos, -sin, 0, - sin, cos, 0, - 0, 0, 1 - ]; - }; - - /** - * The returns a 3x3 or 2x2 homothetic transformation matrix. - * - * @param {number} ratio The scaling ratio. - * @param {boolean} m2 If true, the function will return a 2x2 matrix. - * @return {array} Returns the matrix. - */ - sigma.utils.matrices.scale = function(ratio, m2) { - return m2 ? [ - ratio, 0, - 0, ratio - ] : [ - ratio, 0, 0, - 0, ratio, 0, - 0, 0, 1 - ]; - }; - - /** - * The returns a 3x3 or 2x2 homothetic transformation matrix. - * - * @param {array} a The first matrix. - * @param {array} b The second matrix. - * @param {boolean} m2 If true, the function will assume both matrices are - * 2x2. - * @return {array} Returns the matrix. - */ - sigma.utils.matrices.multiply = function(a, b, m2) { - var l = m2 ? 2 : 3, - a00 = a[0 * l + 0], - a01 = a[0 * l + 1], - a02 = a[0 * l + 2], - a10 = a[1 * l + 0], - a11 = a[1 * l + 1], - a12 = a[1 * l + 2], - a20 = a[2 * l + 0], - a21 = a[2 * l + 1], - a22 = a[2 * l + 2], - b00 = b[0 * l + 0], - b01 = b[0 * l + 1], - b02 = b[0 * l + 2], - b10 = b[1 * l + 0], - b11 = b[1 * l + 1], - b12 = b[1 * l + 2], - b20 = b[2 * l + 0], - b21 = b[2 * l + 1], - b22 = b[2 * l + 2]; - - return m2 ? [ - a00 * b00 + a01 * b10, - a00 * b01 + a01 * b11, - a10 * b00 + a11 * b10, - a10 * b01 + a11 * b11 - ] : [ - a00 * b00 + a01 * b10 + a02 * b20, - a00 * b01 + a01 * b11 + a02 * b21, - a00 * b02 + a01 * b12 + a02 * b22, - a10 * b00 + a11 * b10 + a12 * b20, - a10 * b01 + a11 * b11 + a12 * b21, - a10 * b02 + a11 * b12 + a12 * b22, - a20 * b00 + a21 * b10 + a22 * b20, - a20 * b01 + a21 * b11 + a22 * b21, - a20 * b02 + a21 * b12 + a22 * b22 - ]; - }; -}).call(this); - -;(function(global) { - 'use strict'; - - /** - * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - * http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating - * requestAnimationFrame polyfill by Erik Möller. - * fixes from Paul Irish and Tino Zijdel - * MIT license - */ - var x, - lastTime = 0, - vendors = ['ms', 'moz', 'webkit', 'o']; - - for (x = 0; x < vendors.length && !global.requestAnimationFrame; x++) { - global.requestAnimationFrame = - global[vendors[x] + 'RequestAnimationFrame']; - global.cancelAnimationFrame = - global[vendors[x] + 'CancelAnimationFrame'] || - global[vendors[x] + 'CancelRequestAnimationFrame']; - } - - if (!global.requestAnimationFrame) - global.requestAnimationFrame = function(callback, element) { - var currTime = new Date().getTime(), - timeToCall = Math.max(0, 16 - (currTime - lastTime)), - id = global.setTimeout( - function() { - callback(currTime + timeToCall); - }, - timeToCall - ); - - lastTime = currTime + timeToCall; - return id; - }; - - if (!global.cancelAnimationFrame) - global.cancelAnimationFrame = function(id) { - clearTimeout(id); - }; - - /** - * Function.prototype.bind polyfill found on MDN. - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility - * Public domain - */ - if (!Function.prototype.bind) - Function.prototype.bind = function(oThis) { - if (typeof this !== 'function') - // Closest thing possible to the ECMAScript 5 internal IsCallable - // function: - throw new TypeError( - 'Function.prototype.bind - what is trying to be bound is not callable' - ); - - var aArgs = Array.prototype.slice.call(arguments, 1), - fToBind = this, - fNOP, - fBound; - - fNOP = function() {}; - fBound = function() { - return fToBind.apply( - this instanceof fNOP && oThis ? - this : - oThis, - aArgs.concat(Array.prototype.slice.call(arguments)) - ); - }; - - fNOP.prototype = this.prototype; - fBound.prototype = new fNOP(); - - return fBound; - }; -})(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Packages initialization: - sigma.utils.pkg('sigma.settings'); - - var settings = { - /** - * GRAPH SETTINGS: - * *************** - */ - // {boolean} Indicates if the data have to be cloned in methods to add - // nodes or edges. - clone: true, - // {boolean} Indicates if nodes "id" values and edges "id", "source" and - // "target" values must be set as immutable. - immutable: true, - // {boolean} Indicates if sigma can log its errors and warnings. - verbose: false, - - - /** - * RENDERERS SETTINGS: - * ******************* - */ - // {string} - classPrefix: 'sigma', - // {string} - defaultNodeType: 'def', - // {string} - defaultEdgeType: 'def', - // {string} - defaultLabelColor: '#000', - // {string} - defaultEdgeColor: '#000', - // {string} - defaultNodeColor: '#000', - // {string} - defaultLabelSize: 14, - // {string} Indicates how to choose the edges color. Available values: - // "source", "target", "default" - edgeColor: 'source', - // {number} Defines the minimal edge's arrow display size. - minArrowSize: 0, - // {string} - font: 'arial', - // {string} Example: 'bold' - fontStyle: '', - // {string} Indicates how to choose the labels color. Available values: - // "node", "default" - labelColor: 'default', - // {string} Indicates how to choose the labels size. Available values: - // "fixed", "proportional" - labelSize: 'fixed', - // {string} The ratio between the font size of the label and the node size. - labelSizeRatio: 1, - // {number} The minimum size a node must have to see its label displayed. - labelThreshold: 8, - // {number} The oversampling factor used in WebGL renderer. - webglOversamplingRatio: 2, - // {number} The size of the border of hovered nodes. - borderSize: 0, - // {number} The default hovered node border's color. - defaultNodeBorderColor: '#000', - // {number} The hovered node's label font. If not specified, will heritate - // the "font" value. - hoverFont: '', - // {boolean} If true, then only one node can be hovered at a time. - singleHover: true, - // {string} Example: 'bold' - hoverFontStyle: '', - // {string} Indicates how to choose the hovered nodes shadow color. - // Available values: "node", "default" - labelHoverShadow: 'default', - // {string} - labelHoverShadowColor: '#000', - // {string} Indicates how to choose the hovered nodes color. - // Available values: "node", "default" - nodeHoverColor: 'node', - // {string} - defaultNodeHoverColor: '#000', - // {string} Indicates how to choose the hovered nodes background color. - // Available values: "node", "default" - labelHoverBGColor: 'default', - // {string} - defaultHoverLabelBGColor: '#fff', - // {string} Indicates how to choose the hovered labels color. - // Available values: "node", "default" - labelHoverColor: 'default', - // {string} - defaultLabelHoverColor: '#000', - // {string} Indicates how to choose the edges hover color. Available values: - // "edge", "default" - edgeHoverColor: 'edge', - // {number} The size multiplicator of hovered edges. - edgeHoverSizeRatio: 1, - // {string} - defaultEdgeHoverColor: '#000', - // {boolean} Indicates if the edge extremities must be hovered when the - // edge is hovered. - edgeHoverExtremities: false, - // {booleans} The different drawing modes: - // false: Layered not displayed. - // true: Layered displayed. - drawEdges: true, - drawNodes: true, - drawLabels: true, - drawEdgeLabels: false, - // {boolean} Indicates if the edges must be drawn in several frames or in - // one frame, as the nodes and labels are drawn. - batchEdgesDrawing: false, - // {boolean} Indicates if the edges must be hidden during dragging and - // animations. - hideEdgesOnMove: false, - // {numbers} The different batch sizes, when elements are displayed in - // several frames. - canvasEdgesBatchSize: 500, - webglEdgesBatchSize: 1000, - - - - - /** - * RESCALE SETTINGS: - * ***************** - */ - // {string} Indicates of to scale the graph relatively to its container. - // Available values: "inside", "outside" - scalingMode: 'inside', - // {number} The margin to keep around the graph. - sideMargin: 0, - // {number} Determine the size of the smallest and the biggest node / edges - // on the screen. This mapping makes easier to display the graph, - // avoiding too big nodes that take half of the screen, or too - // small ones that are not readable. If the two parameters are - // equals, then the minimal display size will be 0. And if they - // are both equal to 0, then there is no mapping, and the radius - // of the nodes will be their size. - minEdgeSize: 0.5, - maxEdgeSize: 1, - minNodeSize: 1, - maxNodeSize: 8, - - - - - /** - * CAPTORS SETTINGS: - * ***************** - */ - // {boolean} - touchEnabled: true, - // {boolean} - mouseEnabled: true, - // {boolean} - mouseWheelEnabled: true, - // {boolean} - doubleClickEnabled: true, - // {boolean} Defines whether the custom events such as "clickNode" can be - // used. - eventsEnabled: true, - // {number} Defines by how much multiplicating the zooming level when the - // user zooms with the mouse-wheel. - zoomingRatio: 1.7, - // {number} Defines by how much multiplicating the zooming level when the - // user zooms by double clicking. - doubleClickZoomingRatio: 2.2, - // {number} The minimum zooming level. - zoomMin: 0.0625, - // {number} The maximum zooming level. - zoomMax: 2, - // {number} The duration of animations following a mouse scrolling. - mouseZoomDuration: 200, - // {number} The duration of animations following a mouse double click. - doubleClickZoomDuration: 200, - // {number} The duration of animations following a mouse dropping. - mouseInertiaDuration: 200, - // {number} The inertia power (mouse captor). - mouseInertiaRatio: 3, - // {number} The duration of animations following a touch dropping. - touchInertiaDuration: 200, - // {number} The inertia power (touch captor). - touchInertiaRatio: 3, - // {number} The maximum time between two clicks to make it a double click. - doubleClickTimeout: 300, - // {number} The maximum time between two taps to make it a double tap. - doubleTapTimeout: 300, - // {number} The maximum time of dragging to trigger intertia. - dragTimeout: 200, - - - - - /** - * GLOBAL SETTINGS: - * **************** - */ - // {boolean} Determines whether the instance has to refresh itself - // automatically when a "resize" event is dispatched from the - // window object. - autoResize: true, - // {boolean} Determines whether the "rescale" middleware has to be called - // automatically for each camera on refresh. - autoRescale: true, - // {boolean} If set to false, the camera method "goTo" will basically do - // nothing. - enableCamera: true, - // {boolean} If set to false, the nodes cannot be hovered. - enableHovering: true, - // {boolean} If set to true, the edges can be hovered. - enableEdgeHovering: false, - // {number} The size of the area around the edges to activate hovering. - edgeHoverPrecision: 5, - // {boolean} If set to true, the rescale middleware will ignore node sizes - // to determine the graphs boundings. - rescaleIgnoreSize: false, - // {boolean} Determines if the core has to try to catch errors on - // rendering. - skipErrors: false, - - - - - /** - * CAMERA SETTINGS: - * **************** - */ - // {number} The power degrees applied to the nodes/edges size relatively to - // the zooming level. Basically: - // > onScreenR = Math.pow(zoom, nodesPowRatio) * R - // > onScreenT = Math.pow(zoom, edgesPowRatio) * T - nodesPowRatio: 0.5, - edgesPowRatio: 0.5, - - - - - /** - * ANIMATIONS SETTINGS: - * ******************** - */ - // {number} The default animation time. - animationsTime: 200 - }; - - // Export the previously designed settings: - sigma.settings = sigma.utils.extend(sigma.settings || {}, settings); -}).call(this); - -;(function() { - 'use strict'; - - /** - * Dispatcher constructor. - * - * @return {dispatcher} The new dispatcher instance. - */ - var dispatcher = function() { - Object.defineProperty(this, '_handlers', { - value: {} - }); - }; - - - - - /** - * Will execute the handler everytime that the indicated event (or the - * indicated events) will be triggered. - * - * @param {string} events The name of the event (or the events - * separated by spaces). - * @param {function(Object)} handler The handler to bind. - * @return {dispatcher} Returns the instance itself. - */ - dispatcher.prototype.bind = function(events, handler) { - var i, - l, - event, - eArray; - - if ( - arguments.length === 1 && - typeof arguments[0] === 'object' - ) - for (events in arguments[0]) - this.bind(events, arguments[0][events]); - else if ( - arguments.length === 2 && - typeof arguments[1] === 'function' - ) { - eArray = typeof events === 'string' ? events.split(' ') : events; - - for (i = 0, l = eArray.length; i !== l; i += 1) { - event = eArray[i]; - - // Check that event is not '': - if (!event) - continue; - - if (!this._handlers[event]) - this._handlers[event] = []; - - // Using an object instead of directly the handler will make possible - // later to add flags - this._handlers[event].push({ - handler: handler - }); - } - } else - throw 'bind: Wrong arguments.'; - - return this; - }; - - /** - * Removes the handler from a specified event (or specified events). - * - * @param {?string} events The name of the event (or the events - * separated by spaces). If undefined, - * then all handlers are removed. - * @param {?function(object)} handler The handler to unbind. If undefined, - * each handler bound to the event or the - * events will be removed. - * @return {dispatcher} Returns the instance itself. - */ - dispatcher.prototype.unbind = function(events, handler) { - var i, - n, - j, - m, - k, - a, - event, - eArray = typeof events === 'string' ? events.split(' ') : events; - - if (!arguments.length) { - for (k in this._handlers) - delete this._handlers[k]; - return this; - } - - if (handler) { - for (i = 0, n = eArray.length; i !== n; i += 1) { - event = eArray[i]; - if (this._handlers[event]) { - a = []; - for (j = 0, m = this._handlers[event].length; j !== m; j += 1) - if (this._handlers[event][j].handler !== handler) - a.push(this._handlers[event][j]); - - this._handlers[event] = a; - } - - if (this._handlers[event] && this._handlers[event].length === 0) - delete this._handlers[event]; - } - } else - for (i = 0, n = eArray.length; i !== n; i += 1) - delete this._handlers[eArray[i]]; - - return this; - }; - - /** - * Executes each handler bound to the event - * - * @param {string} events The name of the event (or the events separated - * by spaces). - * @param {?object} data The content of the event (optional). - * @return {dispatcher} Returns the instance itself. - */ - dispatcher.prototype.dispatchEvent = function(events, data) { - var i, - n, - j, - m, - a, - event, - eventName, - self = this, - eArray = typeof events === 'string' ? events.split(' ') : events; - - data = data === undefined ? {} : data; - - for (i = 0, n = eArray.length; i !== n; i += 1) { - eventName = eArray[i]; - - if (this._handlers[eventName]) { - event = self.getEvent(eventName, data); - a = []; - - for (j = 0, m = this._handlers[eventName].length; j !== m; j += 1) { - this._handlers[eventName][j].handler(event); - if (!this._handlers[eventName][j].one) - a.push(this._handlers[eventName][j]); - } - - this._handlers[eventName] = a; - } - } - - return this; - }; - - /** - * Return an event object. - * - * @param {string} events The name of the event. - * @param {?object} data The content of the event (optional). - * @return {object} Returns the instance itself. - */ - dispatcher.prototype.getEvent = function(event, data) { - return { - type: event, - data: data || {}, - target: this - }; - }; - - /** - * A useful function to deal with inheritance. It will make the target - * inherit the prototype of the class dispatcher as well as its constructor. - * - * @param {object} target The target. - */ - dispatcher.extend = function(target, args) { - var k; - - for (k in dispatcher.prototype) - if (dispatcher.prototype.hasOwnProperty(k)) - target[k] = dispatcher.prototype[k]; - - dispatcher.apply(target, args); - }; - - - - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') { - this.sigma.classes = this.sigma.classes || {}; - this.sigma.classes.dispatcher = dispatcher; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = dispatcher; - exports.dispatcher = dispatcher; - } else - this.dispatcher = dispatcher; -}).call(this); - -;(function() { - 'use strict'; - - /** - * This utils aims to facilitate the manipulation of each instance setting. - * Using a function instead of an object brings two main advantages: First, - * it will be easier in the future to catch settings updates through a - * function than an object. Second, giving it a full object will "merge" it - * to the settings object properly, keeping us to have to always add a loop. - * - * @return {configurable} The "settings" function. - */ - var configurable = function() { - var i, - l, - data = {}, - datas = Array.prototype.slice.call(arguments, 0); - - /** - * The method to use to set or get any property of this instance. - * - * @param {string|object} a1 If it is a string and if a2 is undefined, - * then it will return the corresponding - * property. If it is a string and if a2 is - * set, then it will set a2 as the property - * corresponding to a1, and return this. If - * it is an object, then each pair string + - * object(or any other type) will be set as a - * property. - * @param {*?} a2 The new property corresponding to a1 if a1 - * is a string. - * @return {*|configurable} Returns itself or the corresponding - * property. - * - * Polymorphism: - * ************* - * Here are some basic use examples: - * - * > settings = new configurable(); - * > settings('mySetting', 42); - * > settings('mySetting'); // Logs: 42 - * > settings('mySetting', 123); - * > settings('mySetting'); // Logs: 123 - * > settings({mySetting: 456}); - * > settings('mySetting'); // Logs: 456 - * - * Also, it is possible to use the function as a fallback: - * > settings({mySetting: 'abc'}, 'mySetting'); // Logs: 'abc' - * > settings({hisSetting: 'abc'}, 'mySetting'); // Logs: 456 - */ - var settings = function(a1, a2) { - var o, - i, - l, - k; - - if (arguments.length === 1 && typeof a1 === 'string') { - if (data[a1] !== undefined) - return data[a1]; - for (i = 0, l = datas.length; i < l; i++) - if (datas[i][a1] !== undefined) - return datas[i][a1]; - return undefined; - } else if (typeof a1 === 'object' && typeof a2 === 'string') { - return (a1 || {})[a2] !== undefined ? a1[a2] : settings(a2); - } else { - o = (typeof a1 === 'object' && a2 === undefined) ? a1 : {}; - - if (typeof a1 === 'string') - o[a1] = a2; - - for (i = 0, k = Object.keys(o), l = k.length; i < l; i++) - data[k[i]] = o[k[i]]; - - return this; - } - }; - - /** - * This method returns a new configurable function, with new objects - * - * @param {object*} Any number of objects to search in. - * @return {function} Returns the function. Check its documentation to know - * more about how it works. - */ - settings.embedObjects = function() { - var args = datas.concat( - data - ).concat( - Array.prototype.splice.call(arguments, 0) - ); - - return configurable.apply({}, args); - }; - - // Initialize - for (i = 0, l = arguments.length; i < l; i++) - settings(arguments[i]); - - return settings; - }; - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') { - this.sigma.classes = this.sigma.classes || {}; - this.sigma.classes.configurable = configurable; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = configurable; - exports.configurable = configurable; - } else - this.configurable = configurable; -}).call(this); - -;(function(undefined) { - 'use strict'; - - var _methods = Object.create(null), - _indexes = Object.create(null), - _initBindings = Object.create(null), - _methodBindings = Object.create(null), - _methodBeforeBindings = Object.create(null), - _defaultSettings = { - immutable: true, - clone: true - }, - _defaultSettingsFunction = function(key) { - return _defaultSettings[key]; - }; - - /** - * The graph constructor. It initializes the data and the indexes, and binds - * the custom indexes and methods to its own scope. - * - * Recognized parameters: - * ********************** - * Here is the exhaustive list of every accepted parameters in the settings - * object: - * - * {boolean} clone Indicates if the data have to be cloned in methods - * to add nodes or edges. - * {boolean} immutable Indicates if nodes "id" values and edges "id", - * "source" and "target" values must be set as - * immutable. - * - * @param {?configurable} settings Eventually a settings function. - * @return {graph} The new graph instance. - */ - var graph = function(settings) { - var k, - fn, - data; - - /** - * DATA: - * ***** - * Every data that is callable from graph methods are stored in this "data" - * object. This object will be served as context for all these methods, - * and it is possible to add other type of data in it. - */ - data = { - /** - * SETTINGS FUNCTION: - * ****************** - */ - settings: settings || _defaultSettingsFunction, - - /** - * MAIN DATA: - * ********** - */ - nodesArray: [], - edgesArray: [], - - /** - * GLOBAL INDEXES: - * *************** - * These indexes just index data by ids. - */ - nodesIndex: Object.create(null), - edgesIndex: Object.create(null), - - /** - * LOCAL INDEXES: - * ************** - * These indexes refer from node to nodes. Each key is an id, and each - * value is the array of the ids of related nodes. - */ - inNeighborsIndex: Object.create(null), - outNeighborsIndex: Object.create(null), - allNeighborsIndex: Object.create(null), - - inNeighborsCount: Object.create(null), - outNeighborsCount: Object.create(null), - allNeighborsCount: Object.create(null) - }; - - // Execute bindings: - for (k in _initBindings) - _initBindings[k].call(data); - - // Add methods to both the scope and the data objects: - for (k in _methods) { - fn = __bindGraphMethod(k, data, _methods[k]); - this[k] = fn; - data[k] = fn; - } - }; - - - - - /** - * A custom tool to bind methods such that function that are bound to it will - * be executed anytime the method is called. - * - * @param {string} methodName The name of the method to bind. - * @param {object} scope The scope where the method must be executed. - * @param {function} fn The method itself. - * @return {function} The new method. - */ - function __bindGraphMethod(methodName, scope, fn) { - var result = function() { - var k, - res; - - // Execute "before" bound functions: - for (k in _methodBeforeBindings[methodName]) - _methodBeforeBindings[methodName][k].apply(scope, arguments); - - // Apply the method: - res = fn.apply(scope, arguments); - - // Execute bound functions: - for (k in _methodBindings[methodName]) - _methodBindings[methodName][k].apply(scope, arguments); - - // Return res: - return res; - }; - - return result; - } - - /** - * This custom tool function removes every pair key/value from an hash. The - * goal is to avoid creating a new object while some other references are - * still hanging in some scopes... - * - * @param {object} obj The object to empty. - * @return {object} The empty object. - */ - function __emptyObject(obj) { - var k; - - for (k in obj) - if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k)) - delete obj[k]; - - return obj; - } - - - - - /** - * This global method adds a method that will be bound to the futurly created - * graph instances. - * - * Since these methods will be bound to their scope when the instances are - * created, it does not use the prototype. Because of that, methods have to - * be added before instances are created to make them available. - * - * Here is an example: - * - * > graph.addMethod('getNodesCount', function() { - * > return this.nodesArray.length; - * > }); - * > - * > var myGraph = new graph(); - * > console.log(myGraph.getNodesCount()); // outputs 0 - * - * @param {string} methodName The name of the method. - * @param {function} fn The method itself. - * @return {object} The global graph constructor. - */ - graph.addMethod = function(methodName, fn) { - if ( - typeof methodName !== 'string' || - typeof fn !== 'function' || - arguments.length !== 2 - ) - throw 'addMethod: Wrong arguments.'; - - if (_methods[methodName] || graph[methodName]) - throw 'The method "' + methodName + '" already exists.'; - - _methods[methodName] = fn; - _methodBindings[methodName] = Object.create(null); - _methodBeforeBindings[methodName] = Object.create(null); - - return this; - }; - - /** - * This global method returns true if the method has already been added, and - * false else. - * - * Here are some examples: - * - * > graph.hasMethod('addNode'); // returns true - * > graph.hasMethod('hasMethod'); // returns true - * > graph.hasMethod('unexistingMethod'); // returns false - * - * @param {string} methodName The name of the method. - * @return {boolean} The result. - */ - graph.hasMethod = function(methodName) { - return !!(_methods[methodName] || graph[methodName]); - }; - - /** - * This global methods attaches a function to a method. Anytime the specified - * method is called, the attached function is called right after, with the - * same arguments and in the same scope. The attached function is called - * right before if the last argument is true, unless the method is the graph - * constructor. - * - * To attach a function to the graph constructor, use 'constructor' as the - * method name (first argument). - * - * The main idea is to have a clean way to keep custom indexes up to date, - * for instance: - * - * > var timesAddNodeCalled = 0; - * > graph.attach('addNode', 'timesAddNodeCalledInc', function() { - * > timesAddNodeCalled++; - * > }); - * > - * > var myGraph = new graph(); - * > console.log(timesAddNodeCalled); // outputs 0 - * > - * > myGraph.addNode({ id: '1' }).addNode({ id: '2' }); - * > console.log(timesAddNodeCalled); // outputs 2 - * - * The idea for calling a function before is to provide pre-processors, for - * instance: - * - * > var colorPalette = { Person: '#C3CBE1', Place: '#9BDEBD' }; - * > graph.attach('addNode', 'applyNodeColorPalette', function(n) { - * > n.color = colorPalette[n.category]; - * > }, true); - * > - * > var myGraph = new graph(); - * > myGraph.addNode({ id: 'n0', category: 'Person' }); - * > console.log(myGraph.nodes('n0').color); // outputs '#C3CBE1' - * - * @param {string} methodName The name of the related method or - * "constructor". - * @param {string} key The key to identify the function to attach. - * @param {function} fn The function to bind. - * @param {boolean} before If true the function is called right before. - * @return {object} The global graph constructor. - */ - graph.attach = function(methodName, key, fn, before) { - if ( - typeof methodName !== 'string' || - typeof key !== 'string' || - typeof fn !== 'function' || - arguments.length < 3 || - arguments.length > 4 - ) - throw 'attach: Wrong arguments.'; - - var bindings; - - if (methodName === 'constructor') - bindings = _initBindings; - else { - if (before) { - if (!_methodBeforeBindings[methodName]) - throw 'The method "' + methodName + '" does not exist.'; - - bindings = _methodBeforeBindings[methodName]; - } - else { - if (!_methodBindings[methodName]) - throw 'The method "' + methodName + '" does not exist.'; - - bindings = _methodBindings[methodName]; - } - } - - if (bindings[key]) - throw 'A function "' + key + '" is already attached ' + - 'to the method "' + methodName + '".'; - - bindings[key] = fn; - - return this; - }; - - /** - * Alias of attach(methodName, key, fn, true). - */ - graph.attachBefore = function(methodName, key, fn) { - return this.attach(methodName, key, fn, true); - }; - - /** - * This methods is just an helper to deal with custom indexes. It takes as - * arguments the name of the index and an object containing all the different - * functions to bind to the methods. - * - * Here is a basic example, that creates an index to keep the number of nodes - * in the current graph. It also adds a method to provide a getter on that - * new index: - * - * > sigma.classes.graph.addIndex('nodesCount', { - * > constructor: function() { - * > this.nodesCount = 0; - * > }, - * > addNode: function() { - * > this.nodesCount++; - * > }, - * > dropNode: function() { - * > this.nodesCount--; - * > } - * > }); - * > - * > sigma.classes.graph.addMethod('getNodesCount', function() { - * > return this.nodesCount; - * > }); - * > - * > var myGraph = new sigma.classes.graph(); - * > console.log(myGraph.getNodesCount()); // outputs 0 - * > - * > myGraph.addNode({ id: '1' }).addNode({ id: '2' }); - * > console.log(myGraph.getNodesCount()); // outputs 2 - * - * @param {string} name The name of the index. - * @param {object} bindings The object containing the functions to bind. - * @return {object} The global graph constructor. - */ - graph.addIndex = function(name, bindings) { - if ( - typeof name !== 'string' || - Object(bindings) !== bindings || - arguments.length !== 2 - ) - throw 'addIndex: Wrong arguments.'; - - if (_indexes[name]) - throw 'The index "' + name + '" already exists.'; - - var k; - - // Store the bindings: - _indexes[name] = bindings; - - // Attach the bindings: - for (k in bindings) - if (typeof bindings[k] !== 'function') - throw 'The bindings must be functions.'; - else - graph.attach(k, name, bindings[k]); - - return this; - }; - - - - - /** - * This method adds a node to the graph. The node must be an object, with a - * string under the key "id". Except for this, it is possible to add any - * other attribute, that will be preserved all along the manipulations. - * - * If the graph option "clone" has a truthy value, the node will be cloned - * when added to the graph. Also, if the graph option "immutable" has a - * truthy value, its id will be defined as immutable. - * - * @param {object} node The node to add. - * @return {object} The graph instance. - */ - graph.addMethod('addNode', function(node) { - // Check that the node is an object and has an id: - if (Object(node) !== node || arguments.length !== 1) - throw 'addNode: Wrong arguments.'; - - if (typeof node.id !== 'string' && typeof node.id !== 'number') - throw 'The node must have a string or number id.'; - - if (this.nodesIndex[node.id]) - throw 'The node "' + node.id + '" already exists.'; - - var k, - id = node.id, - validNode = Object.create(null); - - // Check the "clone" option: - if (this.settings('clone')) { - for (k in node) - if (k !== 'id') - validNode[k] = node[k]; - } else - validNode = node; - - // Check the "immutable" option: - if (this.settings('immutable')) - Object.defineProperty(validNode, 'id', { - value: id, - enumerable: true - }); - else - validNode.id = id; - - // Add empty containers for edges indexes: - this.inNeighborsIndex[id] = Object.create(null); - this.outNeighborsIndex[id] = Object.create(null); - this.allNeighborsIndex[id] = Object.create(null); - - this.inNeighborsCount[id] = 0; - this.outNeighborsCount[id] = 0; - this.allNeighborsCount[id] = 0; - - // Add the node to indexes: - this.nodesArray.push(validNode); - this.nodesIndex[validNode.id] = validNode; - - // Return the current instance: - return this; - }); - - /** - * This method adds an edge to the graph. The edge must be an object, with a - * string under the key "id", and strings under the keys "source" and - * "target" that design existing nodes. Except for this, it is possible to - * add any other attribute, that will be preserved all along the - * manipulations. - * - * If the graph option "clone" has a truthy value, the edge will be cloned - * when added to the graph. Also, if the graph option "immutable" has a - * truthy value, its id, source and target will be defined as immutable. - * - * @param {object} edge The edge to add. - * @return {object} The graph instance. - */ - graph.addMethod('addEdge', function(edge) { - // Check that the edge is an object and has an id: - if (Object(edge) !== edge || arguments.length !== 1) - throw 'addEdge: Wrong arguments.'; - - if (typeof edge.id !== 'string' && typeof edge.id !== 'number') - throw 'The edge must have a string or number id.'; - - if ((typeof edge.source !== 'string' && typeof edge.source !== 'number') || - !this.nodesIndex[edge.source]) - throw 'The edge source must have an existing node id.'; - - if ((typeof edge.target !== 'string' && typeof edge.target !== 'number') || - !this.nodesIndex[edge.target]) - throw 'The edge target must have an existing node id.'; - - if (this.edgesIndex[edge.id]) - throw 'The edge "' + edge.id + '" already exists.'; - - var k, - validEdge = Object.create(null); - - // Check the "clone" option: - if (this.settings('clone')) { - for (k in edge) - if (k !== 'id' && k !== 'source' && k !== 'target') - validEdge[k] = edge[k]; - } else - validEdge = edge; - - // Check the "immutable" option: - if (this.settings('immutable')) { - Object.defineProperty(validEdge, 'id', { - value: edge.id, - enumerable: true - }); - - Object.defineProperty(validEdge, 'source', { - value: edge.source, - enumerable: true - }); - - Object.defineProperty(validEdge, 'target', { - value: edge.target, - enumerable: true - }); - } else { - validEdge.id = edge.id; - validEdge.source = edge.source; - validEdge.target = edge.target; - } - - // Add the edge to indexes: - this.edgesArray.push(validEdge); - this.edgesIndex[validEdge.id] = validEdge; - - if (!this.inNeighborsIndex[validEdge.target][validEdge.source]) - this.inNeighborsIndex[validEdge.target][validEdge.source] = - Object.create(null); - this.inNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] = - validEdge; - - if (!this.outNeighborsIndex[validEdge.source][validEdge.target]) - this.outNeighborsIndex[validEdge.source][validEdge.target] = - Object.create(null); - this.outNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] = - validEdge; - - if (!this.allNeighborsIndex[validEdge.source][validEdge.target]) - this.allNeighborsIndex[validEdge.source][validEdge.target] = - Object.create(null); - this.allNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] = - validEdge; - - if (validEdge.target !== validEdge.source) { - if (!this.allNeighborsIndex[validEdge.target][validEdge.source]) - this.allNeighborsIndex[validEdge.target][validEdge.source] = - Object.create(null); - this.allNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] = - validEdge; - } - - // Keep counts up to date: - this.inNeighborsCount[validEdge.target]++; - this.outNeighborsCount[validEdge.source]++; - this.allNeighborsCount[validEdge.target]++; - this.allNeighborsCount[validEdge.source]++; - - return this; - }); - - /** - * This method drops a node from the graph. It also removes each edge that is - * bound to it, through the dropEdge method. An error is thrown if the node - * does not exist. - * - * @param {string} id The node id. - * @return {object} The graph instance. - */ - graph.addMethod('dropNode', function(id) { - // Check that the arguments are valid: - if ((typeof id !== 'string' && typeof id !== 'number') || - arguments.length !== 1) - throw 'dropNode: Wrong arguments.'; - - if (!this.nodesIndex[id]) - throw 'The node "' + id + '" does not exist.'; - - var i, k, l; - - // Remove the node from indexes: - delete this.nodesIndex[id]; - for (i = 0, l = this.nodesArray.length; i < l; i++) - if (this.nodesArray[i].id === id) { - this.nodesArray.splice(i, 1); - break; - } - - // Remove related edges: - for (i = this.edgesArray.length - 1; i >= 0; i--) - if (this.edgesArray[i].source === id || this.edgesArray[i].target === id) - this.dropEdge(this.edgesArray[i].id); - - // Remove related edge indexes: - delete this.inNeighborsIndex[id]; - delete this.outNeighborsIndex[id]; - delete this.allNeighborsIndex[id]; - - delete this.inNeighborsCount[id]; - delete this.outNeighborsCount[id]; - delete this.allNeighborsCount[id]; - - for (k in this.nodesIndex) { - delete this.inNeighborsIndex[k][id]; - delete this.outNeighborsIndex[k][id]; - delete this.allNeighborsIndex[k][id]; - } - - return this; - }); - - /** - * This method drops an edge from the graph. An error is thrown if the edge - * does not exist. - * - * @param {string} id The edge id. - * @return {object} The graph instance. - */ - graph.addMethod('dropEdge', function(id) { - // Check that the arguments are valid: - if ((typeof id !== 'string' && typeof id !== 'number') || - arguments.length !== 1) - throw 'dropEdge: Wrong arguments.'; - - if (!this.edgesIndex[id]) - throw 'The edge "' + id + '" does not exist.'; - - var i, l, edge; - - // Remove the edge from indexes: - edge = this.edgesIndex[id]; - delete this.edgesIndex[id]; - for (i = 0, l = this.edgesArray.length; i < l; i++) - if (this.edgesArray[i].id === id) { - this.edgesArray.splice(i, 1); - break; - } - - delete this.inNeighborsIndex[edge.target][edge.source][edge.id]; - if (!Object.keys(this.inNeighborsIndex[edge.target][edge.source]).length) - delete this.inNeighborsIndex[edge.target][edge.source]; - - delete this.outNeighborsIndex[edge.source][edge.target][edge.id]; - if (!Object.keys(this.outNeighborsIndex[edge.source][edge.target]).length) - delete this.outNeighborsIndex[edge.source][edge.target]; - - delete this.allNeighborsIndex[edge.source][edge.target][edge.id]; - if (!Object.keys(this.allNeighborsIndex[edge.source][edge.target]).length) - delete this.allNeighborsIndex[edge.source][edge.target]; - - if (edge.target !== edge.source) { - delete this.allNeighborsIndex[edge.target][edge.source][edge.id]; - if (!Object.keys(this.allNeighborsIndex[edge.target][edge.source]).length) - delete this.allNeighborsIndex[edge.target][edge.source]; - } - - this.inNeighborsCount[edge.target]--; - this.outNeighborsCount[edge.source]--; - this.allNeighborsCount[edge.source]--; - this.allNeighborsCount[edge.target]--; - - return this; - }); - - /** - * This method destroys the current instance. It basically empties each index - * and methods attached to the graph. - */ - graph.addMethod('kill', function() { - // Delete arrays: - this.nodesArray.length = 0; - this.edgesArray.length = 0; - delete this.nodesArray; - delete this.edgesArray; - - // Delete indexes: - delete this.nodesIndex; - delete this.edgesIndex; - delete this.inNeighborsIndex; - delete this.outNeighborsIndex; - delete this.allNeighborsIndex; - delete this.inNeighborsCount; - delete this.outNeighborsCount; - delete this.allNeighborsCount; - }); - - /** - * This method empties the nodes and edges arrays, as well as the different - * indexes. - * - * @return {object} The graph instance. - */ - graph.addMethod('clear', function() { - this.nodesArray.length = 0; - this.edgesArray.length = 0; - - // Due to GC issues, I prefer not to create new object. These objects are - // only available from the methods and attached functions, but still, it is - // better to prevent ghost references to unrelevant data... - __emptyObject(this.nodesIndex); - __emptyObject(this.edgesIndex); - __emptyObject(this.nodesIndex); - __emptyObject(this.inNeighborsIndex); - __emptyObject(this.outNeighborsIndex); - __emptyObject(this.allNeighborsIndex); - __emptyObject(this.inNeighborsCount); - __emptyObject(this.outNeighborsCount); - __emptyObject(this.allNeighborsCount); - - return this; - }); - - /** - * This method reads an object and adds the nodes and edges, through the - * proper methods "addNode" and "addEdge". - * - * Here is an example: - * - * > var myGraph = new graph(); - * > myGraph.read({ - * > nodes: [ - * > { id: 'n0' }, - * > { id: 'n1' } - * > ], - * > edges: [ - * > { - * > id: 'e0', - * > source: 'n0', - * > target: 'n1' - * > } - * > ] - * > }); - * > - * > console.log( - * > myGraph.nodes().length, - * > myGraph.edges().length - * > ); // outputs 2 1 - * - * @param {object} g The graph object. - * @return {object} The graph instance. - */ - graph.addMethod('read', function(g) { - var i, - a, - l; - - a = g.nodes || []; - for (i = 0, l = a.length; i < l; i++) - this.addNode(a[i]); - - a = g.edges || []; - for (i = 0, l = a.length; i < l; i++) - this.addEdge(a[i]); - - return this; - }); - - /** - * This methods returns one or several nodes, depending on how it is called. - * - * To get the array of nodes, call "nodes" without argument. To get a - * specific node, call it with the id of the node. The get multiple node, - * call it with an array of ids, and it will return the array of nodes, in - * the same order. - * - * @param {?(string|array)} v Eventually one id, an array of ids. - * @return {object|array} The related node or array of nodes. - */ - graph.addMethod('nodes', function(v) { - // Clone the array of nodes and return it: - if (!arguments.length) - return this.nodesArray.slice(0); - - // Return the related node: - if (arguments.length === 1 && - (typeof v === 'string' || typeof v === 'number')) - return this.nodesIndex[v]; - - // Return an array of the related node: - if ( - arguments.length === 1 && - Object.prototype.toString.call(v) === '[object Array]' - ) { - var i, - l, - a = []; - - for (i = 0, l = v.length; i < l; i++) - if (typeof v[i] === 'string' || typeof v[i] === 'number') - a.push(this.nodesIndex[v[i]]); - else - throw 'nodes: Wrong arguments.'; - - return a; - } - - throw 'nodes: Wrong arguments.'; - }); - - /** - * This methods returns the degree of one or several nodes, depending on how - * it is called. It is also possible to get incoming or outcoming degrees - * instead by specifying 'in' or 'out' as a second argument. - * - * @param {string|array} v One id, an array of ids. - * @param {?string} which Which degree is required. Values are 'in', - * 'out', and by default the normal degree. - * @return {number|array} The related degree or array of degrees. - */ - graph.addMethod('degree', function(v, which) { - // Check which degree is required: - which = { - 'in': this.inNeighborsCount, - 'out': this.outNeighborsCount - }[which || ''] || this.allNeighborsCount; - - // Return the related node: - if (typeof v === 'string' || typeof v === 'number') - return which[v]; - - // Return an array of the related node: - if (Object.prototype.toString.call(v) === '[object Array]') { - var i, - l, - a = []; - - for (i = 0, l = v.length; i < l; i++) - if (typeof v[i] === 'string' || typeof v[i] === 'number') - a.push(which[v[i]]); - else - throw 'degree: Wrong arguments.'; - - return a; - } - - throw 'degree: Wrong arguments.'; - }); - - /** - * This methods returns one or several edges, depending on how it is called. - * - * To get the array of edges, call "edges" without argument. To get a - * specific edge, call it with the id of the edge. The get multiple edge, - * call it with an array of ids, and it will return the array of edges, in - * the same order. - * - * @param {?(string|array)} v Eventually one id, an array of ids. - * @return {object|array} The related edge or array of edges. - */ - graph.addMethod('edges', function(v) { - // Clone the array of edges and return it: - if (!arguments.length) - return this.edgesArray.slice(0); - - // Return the related edge: - if (arguments.length === 1 && - (typeof v === 'string' || typeof v === 'number')) - return this.edgesIndex[v]; - - // Return an array of the related edge: - if ( - arguments.length === 1 && - Object.prototype.toString.call(v) === '[object Array]' - ) { - var i, - l, - a = []; - - for (i = 0, l = v.length; i < l; i++) - if (typeof v[i] === 'string' || typeof v[i] === 'number') - a.push(this.edgesIndex[v[i]]); - else - throw 'edges: Wrong arguments.'; - - return a; - } - - throw 'edges: Wrong arguments.'; - }); - - - /** - * EXPORT: - * ******* - */ - if (typeof sigma !== 'undefined') { - sigma.classes = sigma.classes || Object.create(null); - sigma.classes.graph = graph; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = graph; - exports.graph = graph; - } else - this.graph = graph; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - sigma.utils.pkg('sigma.classes'); - - /** - * The camera constructor. It just initializes its attributes and methods. - * - * @param {string} id The id. - * @param {sigma.classes.graph} graph The graph. - * @param {configurable} settings The settings function. - * @param {?object} options Eventually some overriding options. - * @return {camera} Returns the fresh new camera instance. - */ - sigma.classes.camera = function(id, graph, settings, options) { - sigma.classes.dispatcher.extend(this); - - Object.defineProperty(this, 'graph', { - value: graph - }); - Object.defineProperty(this, 'id', { - value: id - }); - Object.defineProperty(this, 'readPrefix', { - value: 'read_cam' + id + ':' - }); - Object.defineProperty(this, 'prefix', { - value: 'cam' + id + ':' - }); - - this.x = 0; - this.y = 0; - this.ratio = 1; - this.angle = 0; - this.isAnimated = false; - this.settings = (typeof options === 'object' && options) ? - settings.embedObject(options) : - settings; - }; - - /** - * Updates the camera position. - * - * @param {object} coordinates The new coordinates object. - * @return {camera} Returns the camera. - */ - sigma.classes.camera.prototype.goTo = function(coordinates) { - if (!this.settings('enableCamera')) - return this; - - var i, - l, - c = coordinates || {}, - keys = ['x', 'y', 'ratio', 'angle']; - - for (i = 0, l = keys.length; i < l; i++) - if (c[keys[i]] !== undefined) { - if (typeof c[keys[i]] === 'number' && !isNaN(c[keys[i]])) - this[keys[i]] = c[keys[i]]; - else - throw 'Value for "' + keys[i] + '" is not a number.'; - } - - this.dispatchEvent('coordinatesUpdated'); - return this; - }; - - /** - * This method takes a graph and computes for each node and edges its - * coordinates relatively to the center of the camera. Basically, it will - * compute the coordinates that will be used by the graphic renderers. - * - * Since it should be possible to use different cameras and different - * renderers, it is possible to specify a prefix to put before the new - * coordinates (to get something like "node.camera1_x") - * - * @param {?string} read The prefix of the coordinates to read. - * @param {?string} write The prefix of the coordinates to write. - * @param {?object} options Eventually an object of options. Those can be: - * - A restricted nodes array. - * - A restricted edges array. - * - A width. - * - A height. - * @return {camera} Returns the camera. - */ - sigma.classes.camera.prototype.applyView = function(read, write, options) { - options = options || {}; - write = write !== undefined ? write : this.prefix; - read = read !== undefined ? read : this.readPrefix; - - var nodes = options.nodes || this.graph.nodes(), - edges = options.edges || this.graph.edges(); - - var i, - l, - node, - cos = Math.cos(this.angle), - sin = Math.sin(this.angle), - nodeRatio = Math.pow(this.ratio, this.settings('nodesPowRatio')), - edgeRatio = Math.pow(this.ratio, this.settings('edgesPowRatio')), - xOffset = (options.width || 0) / 2, - yOffset = (options.height || 0) / 2; - - for (i = 0, l = nodes.length; i < l; i++) { - node = nodes[i]; - node[write + 'x'] = - ( - ((node[read + 'x'] || 0) - this.x) * cos + - ((node[read + 'y'] || 0) - this.y) * sin - ) / this.ratio + xOffset; - node[write + 'y'] = - ( - ((node[read + 'y'] || 0) - this.y) * cos - - ((node[read + 'x'] || 0) - this.x) * sin - ) / this.ratio + yOffset; - node[write + 'size'] = - (node[read + 'size'] || 0) / - nodeRatio; - } - - for (i = 0, l = edges.length; i < l; i++) { - edges[i][write + 'size'] = - (edges[i][read + 'size'] || 0) / - edgeRatio; - } - - return this; - }; - - /** - * This function converts the coordinates of a point from the frame of the - * camera to the frame of the graph. - * - * @param {number} x The X coordinate of the point in the frame of the - * camera. - * @param {number} y The Y coordinate of the point in the frame of the - * camera. - * @return {object} The point coordinates in the frame of the graph. - */ - sigma.classes.camera.prototype.graphPosition = function(x, y, vector) { - var X = 0, - Y = 0, - cos = Math.cos(this.angle), - sin = Math.sin(this.angle); - - // Revert the origin differential vector: - if (!vector) { - X = - (this.x * cos + this.y * sin) / this.ratio; - Y = - (this.y * cos - this.x * sin) / this.ratio; - } - - return { - x: (x * cos + y * sin) / this.ratio + X, - y: (y * cos - x * sin) / this.ratio + Y - }; - }; - - /** - * This function converts the coordinates of a point from the frame of the - * graph to the frame of the camera. - * - * @param {number} x The X coordinate of the point in the frame of the - * graph. - * @param {number} y The Y coordinate of the point in the frame of the - * graph. - * @return {object} The point coordinates in the frame of the camera. - */ - sigma.classes.camera.prototype.cameraPosition = function(x, y, vector) { - var X = 0, - Y = 0, - cos = Math.cos(this.angle), - sin = Math.sin(this.angle); - - // Revert the origin differential vector: - if (!vector) { - X = - (this.x * cos + this.y * sin) / this.ratio; - Y = - (this.y * cos - this.x * sin) / this.ratio; - } - - return { - x: ((x - X) * cos - (y - Y) * sin) * this.ratio, - y: ((y - Y) * cos + (x - X) * sin) * this.ratio - }; - }; - - /** - * This method returns the transformation matrix of the camera. This is - * especially useful to apply the camera view directly in shaders, in case of - * WebGL rendering. - * - * @return {array} The transformation matrix. - */ - sigma.classes.camera.prototype.getMatrix = function() { - var scale = sigma.utils.matrices.scale(1 / this.ratio), - rotation = sigma.utils.matrices.rotation(this.angle), - translation = sigma.utils.matrices.translation(-this.x, -this.y), - matrix = sigma.utils.matrices.multiply( - translation, - sigma.utils.matrices.multiply( - rotation, - scale - ) - ); - - return matrix; - }; - - /** - * Taking a width and a height as parameters, this method returns the - * coordinates of the rectangle representing the camera on screen, in the - * graph's referentiel. - * - * To keep displaying labels of nodes going out of the screen, the method - * keeps a margin around the screen in the returned rectangle. - * - * @param {number} width The width of the screen. - * @param {number} height The height of the screen. - * @return {object} The rectangle as x1, y1, x2 and y2, representing - * two opposite points. - */ - sigma.classes.camera.prototype.getRectangle = function(width, height) { - var widthVect = this.cameraPosition(width, 0, true), - heightVect = this.cameraPosition(0, height, true), - centerVect = this.cameraPosition(width / 2, height / 2, true), - marginX = this.cameraPosition(width / 4, 0, true).x, - marginY = this.cameraPosition(0, height / 4, true).y; - - return { - x1: this.x - centerVect.x - marginX, - y1: this.y - centerVect.y - marginY, - x2: this.x - centerVect.x + marginX + widthVect.x, - y2: this.y - centerVect.y - marginY + widthVect.y, - height: Math.sqrt( - Math.pow(heightVect.x, 2) + - Math.pow(heightVect.y + 2 * marginY, 2) - ) - }; - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - /** - * Sigma Quadtree Module - * ===================== - * - * Author: Guillaume Plique (Yomguithereal) - * Version: 0.2 - */ - - - - /** - * Quad Geometric Operations - * ------------------------- - * - * A useful batch of geometric operations used by the quadtree. - */ - - var _geom = { - - /** - * Transforms a graph node with x, y and size into an - * axis-aligned square. - * - * @param {object} A graph node with at least a point (x, y) and a size. - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - pointToSquare: function(n) { - return { - x1: n.x - n.size, - y1: n.y - n.size, - x2: n.x + n.size, - y2: n.y - n.size, - height: n.size * 2 - }; - }, - - /** - * Checks whether a rectangle is axis-aligned. - * - * @param {object} A rectangle defined by two points - * (x1, y1) and (x2, y2). - * @return {boolean} True if the rectangle is axis-aligned. - */ - isAxisAligned: function(r) { - return r.x1 === r.x2 || r.y1 === r.y2; - }, - - /** - * Compute top points of an axis-aligned rectangle. This is useful in - * cases when the rectangle has been rotated (left, right or bottom up) and - * later operations need to know the top points. - * - * @param {object} An axis-aligned rectangle defined by two points - * (x1, y1), (x2, y2) and height. - * @return {object} A rectangle: two points (x1, y1), (x2, y2) and height. - */ - axisAlignedTopPoints: function(r) { - - // Basic - if (r.y1 === r.y2 && r.x1 < r.x2) - return r; - - // Rotated to right - if (r.x1 === r.x2 && r.y2 > r.y1) - return { - x1: r.x1 - r.height, y1: r.y1, - x2: r.x1, y2: r.y1, - height: r.height - }; - - // Rotated to left - if (r.x1 === r.x2 && r.y2 < r.y1) - return { - x1: r.x1, y1: r.y2, - x2: r.x2 + r.height, y2: r.y2, - height: r.height - }; - - // Bottom's up - return { - x1: r.x2, y1: r.y1 - r.height, - x2: r.x1, y2: r.y1 - r.height, - height: r.height - }; - }, - - /** - * Get coordinates of a rectangle's lower left corner from its top points. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @return {object} Coordinates of the corner (x, y). - */ - lowerLeftCoor: function(r) { - var width = ( - Math.sqrt( - Math.pow(r.x2 - r.x1, 2) + - Math.pow(r.y2 - r.y1, 2) - ) - ); - - return { - x: r.x1 - (r.y2 - r.y1) * r.height / width, - y: r.y1 + (r.x2 - r.x1) * r.height / width - }; - }, - - /** - * Get coordinates of a rectangle's lower right corner from its top points - * and its lower left corner. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @param {object} A corner's coordinates (x, y). - * @return {object} Coordinates of the corner (x, y). - */ - lowerRightCoor: function(r, llc) { - return { - x: llc.x - r.x1 + r.x2, - y: llc.y - r.y1 + r.y2 - }; - }, - - /** - * Get the coordinates of all the corners of a rectangle from its top point. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @return {array} An array of the four corners' coordinates (x, y). - */ - rectangleCorners: function(r) { - var llc = this.lowerLeftCoor(r), - lrc = this.lowerRightCoor(r, llc); - - return [ - {x: r.x1, y: r.y1}, - {x: r.x2, y: r.y2}, - {x: llc.x, y: llc.y}, - {x: lrc.x, y: lrc.y} - ]; - }, - - /** - * Split a square defined by its boundaries into four. - * - * @param {object} Boundaries of the square (x, y, width, height). - * @return {array} An array containing the four new squares, themselves - * defined by an array of their four corners (x, y). - */ - splitSquare: function(b) { - return [ - [ - {x: b.x, y: b.y}, - {x: b.x + b.width / 2, y: b.y}, - {x: b.x, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height / 2} - ], - [ - {x: b.x + b.width / 2, y: b.y}, - {x: b.x + b.width, y: b.y}, - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x + b.width, y: b.y + b.height / 2} - ], - [ - {x: b.x, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x, y: b.y + b.height}, - {x: b.x + b.width / 2, y: b.y + b.height} - ], - [ - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x + b.width, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height}, - {x: b.x + b.width, y: b.y + b.height} - ] - ]; - }, - - /** - * Compute the four axis between corners of rectangle A and corners of - * rectangle B. This is needed later to check an eventual collision. - * - * @param {array} An array of rectangle A's four corners (x, y). - * @param {array} An array of rectangle B's four corners (x, y). - * @return {array} An array of four axis defined by their coordinates (x,y). - */ - axis: function(c1, c2) { - return [ - {x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y}, - {x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y}, - {x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y}, - {x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y} - ]; - }, - - /** - * Project a rectangle's corner on an axis. - * - * @param {object} Coordinates of a corner (x, y). - * @param {object} Coordinates of an axis (x, y). - * @return {object} The projection defined by coordinates (x, y). - */ - projection: function(c, a) { - var l = ( - (c.x * a.x + c.y * a.y) / - (Math.pow(a.x, 2) + Math.pow(a.y, 2)) - ); - - return { - x: l * a.x, - y: l * a.y - }; - }, - - /** - * Check whether two rectangles collide on one particular axis. - * - * @param {object} An axis' coordinates (x, y). - * @param {array} Rectangle A's corners. - * @param {array} Rectangle B's corners. - * @return {boolean} True if the rectangles collide on the axis. - */ - axisCollision: function(a, c1, c2) { - var sc1 = [], - sc2 = []; - - for (var ci = 0; ci < 4; ci++) { - var p1 = this.projection(c1[ci], a), - p2 = this.projection(c2[ci], a); - - sc1.push(p1.x * a.x + p1.y * a.y); - sc2.push(p2.x * a.x + p2.y * a.y); - } - - var maxc1 = Math.max.apply(Math, sc1), - maxc2 = Math.max.apply(Math, sc2), - minc1 = Math.min.apply(Math, sc1), - minc2 = Math.min.apply(Math, sc2); - - return (minc2 <= maxc1 && maxc2 >= minc1); - }, - - /** - * Check whether two rectangles collide on each one of their four axis. If - * all axis collide, then the two rectangles do collide on the plane. - * - * @param {array} Rectangle A's corners. - * @param {array} Rectangle B's corners. - * @return {boolean} True if the rectangles collide. - */ - collision: function(c1, c2) { - var axis = this.axis(c1, c2), - col = true; - - for (var i = 0; i < 4; i++) - col = col && this.axisCollision(axis[i], c1, c2); - - return col; - } - }; - - - /** - * Quad Functions - * ------------ - * - * The Quadtree functions themselves. - * For each of those functions, we consider that in a splitted quad, the - * index of each node is the following: - * 0: top left - * 1: top right - * 2: bottom left - * 3: bottom right - * - * Moreover, the hereafter quad's philosophy is to consider that if an element - * collides with more than one nodes, this element belongs to each of the - * nodes it collides with where other would let it lie on a higher node. - */ - - /** - * Get the index of the node containing the point in the quad - * - * @param {object} point A point defined by coordinates (x, y). - * @param {object} quadBounds Boundaries of the quad (x, y, width, heigth). - * @return {integer} The index of the node containing the point. - */ - function _quadIndex(point, quadBounds) { - var xmp = quadBounds.x + quadBounds.width / 2, - ymp = quadBounds.y + quadBounds.height / 2, - top = (point.y < ymp), - left = (point.x < xmp); - - if (top) { - if (left) - return 0; - else - return 1; - } - else { - if (left) - return 2; - else - return 3; - } - } - - /** - * Get a list of indexes of nodes containing an axis-aligned rectangle - * - * @param {object} rectangle A rectangle defined by two points (x1, y1), - * (x2, y2) and height. - * @param {array} quadCorners An array of the quad nodes' corners. - * @return {array} An array of indexes containing one to - * four integers. - */ - function _quadIndexes(rectangle, quadCorners) { - var indexes = []; - - // Iterating through quads - for (var i = 0; i < 4; i++) - if ((rectangle.x2 >= quadCorners[i][0].x) && - (rectangle.x1 <= quadCorners[i][1].x) && - (rectangle.y1 + rectangle.height >= quadCorners[i][0].y) && - (rectangle.y1 <= quadCorners[i][2].y)) - indexes.push(i); - - return indexes; - } - - /** - * Get a list of indexes of nodes containing a non-axis-aligned rectangle - * - * @param {array} corners An array containing each corner of the - * rectangle defined by its coordinates (x, y). - * @param {array} quadCorners An array of the quad nodes' corners. - * @return {array} An array of indexes containing one to - * four integers. - */ - function _quadCollision(corners, quadCorners) { - var indexes = []; - - // Iterating through quads - for (var i = 0; i < 4; i++) - if (_geom.collision(corners, quadCorners[i])) - indexes.push(i); - - return indexes; - } - - /** - * Subdivide a quad by creating a node at a precise index. The function does - * not generate all four nodes not to potentially create unused nodes. - * - * @param {integer} index The index of the node to create. - * @param {object} quad The quad object to subdivide. - * @return {object} A new quad representing the node created. - */ - function _quadSubdivide(index, quad) { - var next = quad.level + 1, - subw = Math.round(quad.bounds.width / 2), - subh = Math.round(quad.bounds.height / 2), - qx = Math.round(quad.bounds.x), - qy = Math.round(quad.bounds.y), - x, - y; - - switch (index) { - case 0: - x = qx; - y = qy; - break; - case 1: - x = qx + subw; - y = qy; - break; - case 2: - x = qx; - y = qy + subh; - break; - case 3: - x = qx + subw; - y = qy + subh; - break; - } - - return _quadTree( - {x: x, y: y, width: subw, height: subh}, - next, - quad.maxElements, - quad.maxLevel - ); - } - - /** - * Recursively insert an element into the quadtree. Only points - * with size, i.e. axis-aligned squares, may be inserted with this - * method. - * - * @param {object} el The element to insert in the quadtree. - * @param {object} sizedPoint A sized point defined by two top points - * (x1, y1), (x2, y2) and height. - * @param {object} quad The quad in which to insert the element. - * @return {undefined} The function does not return anything. - */ - function _quadInsert(el, sizedPoint, quad) { - if (quad.level < quad.maxLevel) { - - // Searching appropriate quads - var indexes = _quadIndexes(sizedPoint, quad.corners); - - // Iterating - for (var i = 0, l = indexes.length; i < l; i++) { - - // Subdividing if necessary - if (quad.nodes[indexes[i]] === undefined) - quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad); - - // Recursion - _quadInsert(el, sizedPoint, quad.nodes[indexes[i]]); - } - } - else { - - // Pushing the element in a leaf node - quad.elements.push(el); - } - } - - /** - * Recursively retrieve every elements held by the node containing the - * searched point. - * - * @param {object} point The searched point (x, y). - * @param {object} quad The searched quad. - * @return {array} An array of elements contained in the relevant - * node. - */ - function _quadRetrievePoint(point, quad) { - if (quad.level < quad.maxLevel) { - var index = _quadIndex(point, quad.bounds); - - // If node does not exist we return an empty list - if (quad.nodes[index] !== undefined) { - return _quadRetrievePoint(point, quad.nodes[index]); - } - else { - return []; - } - } - else { - return quad.elements; - } - } - - /** - * Recursively retrieve every elements contained within an rectangular area - * that may or may not be axis-aligned. - * - * @param {object|array} rectData The searched area defined either by - * an array of four corners (x, y) in - * the case of a non-axis-aligned - * rectangle or an object with two top - * points (x1, y1), (x2, y2) and height. - * @param {object} quad The searched quad. - * @param {function} collisionFunc The collision function used to search - * for node indexes. - * @param {array?} els The retrieved elements. - * @return {array} An array of elements contained in the - * area. - */ - function _quadRetrieveArea(rectData, quad, collisionFunc, els) { - els = els || {}; - - if (quad.level < quad.maxLevel) { - var indexes = collisionFunc(rectData, quad.corners); - - for (var i = 0, l = indexes.length; i < l; i++) - if (quad.nodes[indexes[i]] !== undefined) - _quadRetrieveArea( - rectData, - quad.nodes[indexes[i]], - collisionFunc, - els - ); - } else - for (var j = 0, m = quad.elements.length; j < m; j++) - if (els[quad.elements[j].id] === undefined) - els[quad.elements[j].id] = quad.elements[j]; - - return els; - } - - /** - * Creates the quadtree object itself. - * - * @param {object} bounds The boundaries of the quad defined by an - * origin (x, y), width and heigth. - * @param {integer} level The level of the quad in the tree. - * @param {integer} maxElements The max number of element in a leaf node. - * @param {integer} maxLevel The max recursion level of the tree. - * @return {object} The quadtree object. - */ - function _quadTree(bounds, level, maxElements, maxLevel) { - return { - level: level || 0, - bounds: bounds, - corners: _geom.splitSquare(bounds), - maxElements: maxElements || 20, - maxLevel: maxLevel || 4, - elements: [], - nodes: [] - }; - } - - - /** - * Sigma Quad Constructor - * ---------------------- - * - * The quad API as exposed to sigma. - */ - - /** - * The quad core that will become the sigma interface with the quadtree. - * - * property {object} _tree Property holding the quadtree object. - * property {object} _geom Exposition of the _geom namespace for testing. - * property {object} _cache Cache for the area method. - */ - var quad = function() { - this._geom = _geom; - this._tree = null; - this._cache = { - query: false, - result: false - }; - }; - - /** - * Index a graph by inserting its nodes into the quadtree. - * - * @param {array} nodes An array of nodes to index. - * @param {object} params An object of parameters with at least the quad - * bounds. - * @return {object} The quadtree object. - * - * Parameters: - * ---------- - * bounds: {object} boundaries of the quad defined by its origin (x, y) - * width and heigth. - * prefix: {string?} a prefix for node geometric attributes. - * maxElements: {integer?} the max number of elements in a leaf node. - * maxLevel: {integer?} the max recursion level of the tree. - */ - quad.prototype.index = function(nodes, params) { - - // Enforcing presence of boundaries - if (!params.bounds) - throw 'sigma.classes.quad.index: bounds information not given.'; - - // Prefix - var prefix = params.prefix || ''; - - // Building the tree - this._tree = _quadTree( - params.bounds, - 0, - params.maxElements, - params.maxLevel - ); - - // Inserting graph nodes into the tree - for (var i = 0, l = nodes.length; i < l; i++) { - - // Inserting node - _quadInsert( - nodes[i], - _geom.pointToSquare({ - x: nodes[i][prefix + 'x'], - y: nodes[i][prefix + 'y'], - size: nodes[i][prefix + 'size'] - }), - this._tree - ); - } - - // Reset cache: - this._cache = { - query: false, - result: false - }; - - // remove? - return this._tree; - }; - - /** - * Retrieve every graph nodes held by the quadtree node containing the - * searched point. - * - * @param {number} x of the point. - * @param {number} y of the point. - * @return {array} An array of nodes retrieved. - */ - quad.prototype.point = function(x, y) { - return this._tree ? - _quadRetrievePoint({x: x, y: y}, this._tree) || [] : - []; - }; - - /** - * Retrieve every graph nodes within a rectangular area. The methods keep the - * last area queried in cache for optimization reason and will act differently - * for the same reason if the area is axis-aligned or not. - * - * @param {object} A rectangle defined by two top points (x1, y1), (x2, y2) - * and height. - * @return {array} An array of nodes retrieved. - */ - quad.prototype.area = function(rect) { - var serialized = JSON.stringify(rect), - collisionFunc, - rectData; - - // Returning cache? - if (this._cache.query === serialized) - return this._cache.result; - - // Axis aligned ? - if (_geom.isAxisAligned(rect)) { - collisionFunc = _quadIndexes; - rectData = _geom.axisAlignedTopPoints(rect); - } - else { - collisionFunc = _quadCollision; - rectData = _geom.rectangleCorners(rect); - } - - // Retrieving nodes - var nodes = this._tree ? - _quadRetrieveArea( - rectData, - this._tree, - collisionFunc - ) : - []; - - // Object to array - var nodesArray = []; - for (var i in nodes) - nodesArray.push(nodes[i]); - - // Caching - this._cache.query = serialized; - this._cache.result = nodesArray; - - return nodesArray; - }; - - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') { - this.sigma.classes = this.sigma.classes || {}; - this.sigma.classes.quad = quad; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = quad; - exports.quad = quad; - } else - this.quad = quad; -}).call(this); - -;(function(undefined) { - 'use strict'; - - /** - * Sigma Quadtree Module for edges - * =============================== - * - * Author: Sébastien Heymann, - * from the quad of Guillaume Plique (Yomguithereal) - * Version: 0.2 - */ - - - - /** - * Quad Geometric Operations - * ------------------------- - * - * A useful batch of geometric operations used by the quadtree. - */ - - var _geom = { - - /** - * Transforms a graph node with x, y and size into an - * axis-aligned square. - * - * @param {object} A graph node with at least a point (x, y) and a size. - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - pointToSquare: function(n) { - return { - x1: n.x - n.size, - y1: n.y - n.size, - x2: n.x + n.size, - y2: n.y - n.size, - height: n.size * 2 - }; - }, - - /** - * Transforms a graph edge with x1, y1, x2, y2 and size into an - * axis-aligned square. - * - * @param {object} A graph edge with at least two points - * (x1, y1), (x2, y2) and a size. - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - lineToSquare: function(e) { - if (e.y1 < e.y2) { - // (e.x1, e.y1) on top - if (e.x1 < e.x2) { - // (e.x1, e.y1) on left - return { - x1: e.x1 - e.size, - y1: e.y1 - e.size, - x2: e.x2 + e.size, - y2: e.y1 - e.size, - height: e.y2 - e.y1 + e.size * 2 - }; - } - // (e.x1, e.y1) on right - return { - x1: e.x2 - e.size, - y1: e.y1 - e.size, - x2: e.x1 + e.size, - y2: e.y1 - e.size, - height: e.y2 - e.y1 + e.size * 2 - }; - } - - // (e.x2, e.y2) on top - if (e.x1 < e.x2) { - // (e.x1, e.y1) on left - return { - x1: e.x1 - e.size, - y1: e.y2 - e.size, - x2: e.x2 + e.size, - y2: e.y2 - e.size, - height: e.y1 - e.y2 + e.size * 2 - }; - } - // (e.x2, e.y2) on right - return { - x1: e.x2 - e.size, - y1: e.y2 - e.size, - x2: e.x1 + e.size, - y2: e.y2 - e.size, - height: e.y1 - e.y2 + e.size * 2 - }; - }, - - /** - * Transforms a graph edge of type 'curve' with x1, y1, x2, y2, - * control point and size into an axis-aligned square. - * - * @param {object} e A graph edge with at least two points - * (x1, y1), (x2, y2) and a size. - * @param {object} cp A control point (x,y). - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - quadraticCurveToSquare: function(e, cp) { - var pt = sigma.utils.getPointOnQuadraticCurve( - 0.5, - e.x1, - e.y1, - e.x2, - e.y2, - cp.x, - cp.y - ); - - // Bounding box of the two points and the point at the middle of the - // curve: - var minX = Math.min(e.x1, e.x2, pt.x), - maxX = Math.max(e.x1, e.x2, pt.x), - minY = Math.min(e.y1, e.y2, pt.y), - maxY = Math.max(e.y1, e.y2, pt.y); - - return { - x1: minX - e.size, - y1: minY - e.size, - x2: maxX + e.size, - y2: minY - e.size, - height: maxY - minY + e.size * 2 - }; - }, - - /** - * Transforms a graph self loop into an axis-aligned square. - * - * @param {object} n A graph node with a point (x, y) and a size. - * @return {object} A square: two points (x1, y1), (x2, y2) and height. - */ - selfLoopToSquare: function(n) { - // Fitting to the curve is too costly, we compute a larger bounding box - // using the control points: - var cp = sigma.utils.getSelfLoopControlPoints(n.x, n.y, n.size); - - // Bounding box of the point and the two control points: - var minX = Math.min(n.x, cp.x1, cp.x2), - maxX = Math.max(n.x, cp.x1, cp.x2), - minY = Math.min(n.y, cp.y1, cp.y2), - maxY = Math.max(n.y, cp.y1, cp.y2); - - return { - x1: minX - n.size, - y1: minY - n.size, - x2: maxX + n.size, - y2: minY - n.size, - height: maxY - minY + n.size * 2 - }; - }, - - /** - * Checks whether a rectangle is axis-aligned. - * - * @param {object} A rectangle defined by two points - * (x1, y1) and (x2, y2). - * @return {boolean} True if the rectangle is axis-aligned. - */ - isAxisAligned: function(r) { - return r.x1 === r.x2 || r.y1 === r.y2; - }, - - /** - * Compute top points of an axis-aligned rectangle. This is useful in - * cases when the rectangle has been rotated (left, right or bottom up) and - * later operations need to know the top points. - * - * @param {object} An axis-aligned rectangle defined by two points - * (x1, y1), (x2, y2) and height. - * @return {object} A rectangle: two points (x1, y1), (x2, y2) and height. - */ - axisAlignedTopPoints: function(r) { - - // Basic - if (r.y1 === r.y2 && r.x1 < r.x2) - return r; - - // Rotated to right - if (r.x1 === r.x2 && r.y2 > r.y1) - return { - x1: r.x1 - r.height, y1: r.y1, - x2: r.x1, y2: r.y1, - height: r.height - }; - - // Rotated to left - if (r.x1 === r.x2 && r.y2 < r.y1) - return { - x1: r.x1, y1: r.y2, - x2: r.x2 + r.height, y2: r.y2, - height: r.height - }; - - // Bottom's up - return { - x1: r.x2, y1: r.y1 - r.height, - x2: r.x1, y2: r.y1 - r.height, - height: r.height - }; - }, - - /** - * Get coordinates of a rectangle's lower left corner from its top points. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @return {object} Coordinates of the corner (x, y). - */ - lowerLeftCoor: function(r) { - var width = ( - Math.sqrt( - Math.pow(r.x2 - r.x1, 2) + - Math.pow(r.y2 - r.y1, 2) - ) - ); - - return { - x: r.x1 - (r.y2 - r.y1) * r.height / width, - y: r.y1 + (r.x2 - r.x1) * r.height / width - }; - }, - - /** - * Get coordinates of a rectangle's lower right corner from its top points - * and its lower left corner. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @param {object} A corner's coordinates (x, y). - * @return {object} Coordinates of the corner (x, y). - */ - lowerRightCoor: function(r, llc) { - return { - x: llc.x - r.x1 + r.x2, - y: llc.y - r.y1 + r.y2 - }; - }, - - /** - * Get the coordinates of all the corners of a rectangle from its top point. - * - * @param {object} A rectangle defined by two points (x1, y1) and (x2, y2). - * @return {array} An array of the four corners' coordinates (x, y). - */ - rectangleCorners: function(r) { - var llc = this.lowerLeftCoor(r), - lrc = this.lowerRightCoor(r, llc); - - return [ - {x: r.x1, y: r.y1}, - {x: r.x2, y: r.y2}, - {x: llc.x, y: llc.y}, - {x: lrc.x, y: lrc.y} - ]; - }, - - /** - * Split a square defined by its boundaries into four. - * - * @param {object} Boundaries of the square (x, y, width, height). - * @return {array} An array containing the four new squares, themselves - * defined by an array of their four corners (x, y). - */ - splitSquare: function(b) { - return [ - [ - {x: b.x, y: b.y}, - {x: b.x + b.width / 2, y: b.y}, - {x: b.x, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height / 2} - ], - [ - {x: b.x + b.width / 2, y: b.y}, - {x: b.x + b.width, y: b.y}, - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x + b.width, y: b.y + b.height / 2} - ], - [ - {x: b.x, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x, y: b.y + b.height}, - {x: b.x + b.width / 2, y: b.y + b.height} - ], - [ - {x: b.x + b.width / 2, y: b.y + b.height / 2}, - {x: b.x + b.width, y: b.y + b.height / 2}, - {x: b.x + b.width / 2, y: b.y + b.height}, - {x: b.x + b.width, y: b.y + b.height} - ] - ]; - }, - - /** - * Compute the four axis between corners of rectangle A and corners of - * rectangle B. This is needed later to check an eventual collision. - * - * @param {array} An array of rectangle A's four corners (x, y). - * @param {array} An array of rectangle B's four corners (x, y). - * @return {array} An array of four axis defined by their coordinates (x,y). - */ - axis: function(c1, c2) { - return [ - {x: c1[1].x - c1[0].x, y: c1[1].y - c1[0].y}, - {x: c1[1].x - c1[3].x, y: c1[1].y - c1[3].y}, - {x: c2[0].x - c2[2].x, y: c2[0].y - c2[2].y}, - {x: c2[0].x - c2[1].x, y: c2[0].y - c2[1].y} - ]; - }, - - /** - * Project a rectangle's corner on an axis. - * - * @param {object} Coordinates of a corner (x, y). - * @param {object} Coordinates of an axis (x, y). - * @return {object} The projection defined by coordinates (x, y). - */ - projection: function(c, a) { - var l = ( - (c.x * a.x + c.y * a.y) / - (Math.pow(a.x, 2) + Math.pow(a.y, 2)) - ); - - return { - x: l * a.x, - y: l * a.y - }; - }, - - /** - * Check whether two rectangles collide on one particular axis. - * - * @param {object} An axis' coordinates (x, y). - * @param {array} Rectangle A's corners. - * @param {array} Rectangle B's corners. - * @return {boolean} True if the rectangles collide on the axis. - */ - axisCollision: function(a, c1, c2) { - var sc1 = [], - sc2 = []; - - for (var ci = 0; ci < 4; ci++) { - var p1 = this.projection(c1[ci], a), - p2 = this.projection(c2[ci], a); - - sc1.push(p1.x * a.x + p1.y * a.y); - sc2.push(p2.x * a.x + p2.y * a.y); - } - - var maxc1 = Math.max.apply(Math, sc1), - maxc2 = Math.max.apply(Math, sc2), - minc1 = Math.min.apply(Math, sc1), - minc2 = Math.min.apply(Math, sc2); - - return (minc2 <= maxc1 && maxc2 >= minc1); - }, - - /** - * Check whether two rectangles collide on each one of their four axis. If - * all axis collide, then the two rectangles do collide on the plane. - * - * @param {array} Rectangle A's corners. - * @param {array} Rectangle B's corners. - * @return {boolean} True if the rectangles collide. - */ - collision: function(c1, c2) { - var axis = this.axis(c1, c2), - col = true; - - for (var i = 0; i < 4; i++) - col = col && this.axisCollision(axis[i], c1, c2); - - return col; - } - }; - - - /** - * Quad Functions - * ------------ - * - * The Quadtree functions themselves. - * For each of those functions, we consider that in a splitted quad, the - * index of each node is the following: - * 0: top left - * 1: top right - * 2: bottom left - * 3: bottom right - * - * Moreover, the hereafter quad's philosophy is to consider that if an element - * collides with more than one nodes, this element belongs to each of the - * nodes it collides with where other would let it lie on a higher node. - */ - - /** - * Get the index of the node containing the point in the quad - * - * @param {object} point A point defined by coordinates (x, y). - * @param {object} quadBounds Boundaries of the quad (x, y, width, heigth). - * @return {integer} The index of the node containing the point. - */ - function _quadIndex(point, quadBounds) { - var xmp = quadBounds.x + quadBounds.width / 2, - ymp = quadBounds.y + quadBounds.height / 2, - top = (point.y < ymp), - left = (point.x < xmp); - - if (top) { - if (left) - return 0; - else - return 1; - } - else { - if (left) - return 2; - else - return 3; - } - } - - /** - * Get a list of indexes of nodes containing an axis-aligned rectangle - * - * @param {object} rectangle A rectangle defined by two points (x1, y1), - * (x2, y2) and height. - * @param {array} quadCorners An array of the quad nodes' corners. - * @return {array} An array of indexes containing one to - * four integers. - */ - function _quadIndexes(rectangle, quadCorners) { - var indexes = []; - - // Iterating through quads - for (var i = 0; i < 4; i++) - if ((rectangle.x2 >= quadCorners[i][0].x) && - (rectangle.x1 <= quadCorners[i][1].x) && - (rectangle.y1 + rectangle.height >= quadCorners[i][0].y) && - (rectangle.y1 <= quadCorners[i][2].y)) - indexes.push(i); - - return indexes; - } - - /** - * Get a list of indexes of nodes containing a non-axis-aligned rectangle - * - * @param {array} corners An array containing each corner of the - * rectangle defined by its coordinates (x, y). - * @param {array} quadCorners An array of the quad nodes' corners. - * @return {array} An array of indexes containing one to - * four integers. - */ - function _quadCollision(corners, quadCorners) { - var indexes = []; - - // Iterating through quads - for (var i = 0; i < 4; i++) - if (_geom.collision(corners, quadCorners[i])) - indexes.push(i); - - return indexes; - } - - /** - * Subdivide a quad by creating a node at a precise index. The function does - * not generate all four nodes not to potentially create unused nodes. - * - * @param {integer} index The index of the node to create. - * @param {object} quad The quad object to subdivide. - * @return {object} A new quad representing the node created. - */ - function _quadSubdivide(index, quad) { - var next = quad.level + 1, - subw = Math.round(quad.bounds.width / 2), - subh = Math.round(quad.bounds.height / 2), - qx = Math.round(quad.bounds.x), - qy = Math.round(quad.bounds.y), - x, - y; - - switch (index) { - case 0: - x = qx; - y = qy; - break; - case 1: - x = qx + subw; - y = qy; - break; - case 2: - x = qx; - y = qy + subh; - break; - case 3: - x = qx + subw; - y = qy + subh; - break; - } - - return _quadTree( - {x: x, y: y, width: subw, height: subh}, - next, - quad.maxElements, - quad.maxLevel - ); - } - - /** - * Recursively insert an element into the quadtree. Only points - * with size, i.e. axis-aligned squares, may be inserted with this - * method. - * - * @param {object} el The element to insert in the quadtree. - * @param {object} sizedPoint A sized point defined by two top points - * (x1, y1), (x2, y2) and height. - * @param {object} quad The quad in which to insert the element. - * @return {undefined} The function does not return anything. - */ - function _quadInsert(el, sizedPoint, quad) { - if (quad.level < quad.maxLevel) { - - // Searching appropriate quads - var indexes = _quadIndexes(sizedPoint, quad.corners); - - // Iterating - for (var i = 0, l = indexes.length; i < l; i++) { - - // Subdividing if necessary - if (quad.nodes[indexes[i]] === undefined) - quad.nodes[indexes[i]] = _quadSubdivide(indexes[i], quad); - - // Recursion - _quadInsert(el, sizedPoint, quad.nodes[indexes[i]]); - } - } - else { - - // Pushing the element in a leaf node - quad.elements.push(el); - } - } - - /** - * Recursively retrieve every elements held by the node containing the - * searched point. - * - * @param {object} point The searched point (x, y). - * @param {object} quad The searched quad. - * @return {array} An array of elements contained in the relevant - * node. - */ - function _quadRetrievePoint(point, quad) { - if (quad.level < quad.maxLevel) { - var index = _quadIndex(point, quad.bounds); - - // If node does not exist we return an empty list - if (quad.nodes[index] !== undefined) { - return _quadRetrievePoint(point, quad.nodes[index]); - } - else { - return []; - } - } - else { - return quad.elements; - } - } - - /** - * Recursively retrieve every elements contained within an rectangular area - * that may or may not be axis-aligned. - * - * @param {object|array} rectData The searched area defined either by - * an array of four corners (x, y) in - * the case of a non-axis-aligned - * rectangle or an object with two top - * points (x1, y1), (x2, y2) and height. - * @param {object} quad The searched quad. - * @param {function} collisionFunc The collision function used to search - * for node indexes. - * @param {array?} els The retrieved elements. - * @return {array} An array of elements contained in the - * area. - */ - function _quadRetrieveArea(rectData, quad, collisionFunc, els) { - els = els || {}; - - if (quad.level < quad.maxLevel) { - var indexes = collisionFunc(rectData, quad.corners); - - for (var i = 0, l = indexes.length; i < l; i++) - if (quad.nodes[indexes[i]] !== undefined) - _quadRetrieveArea( - rectData, - quad.nodes[indexes[i]], - collisionFunc, - els - ); - } else - for (var j = 0, m = quad.elements.length; j < m; j++) - if (els[quad.elements[j].id] === undefined) - els[quad.elements[j].id] = quad.elements[j]; - - return els; - } - - /** - * Creates the quadtree object itself. - * - * @param {object} bounds The boundaries of the quad defined by an - * origin (x, y), width and heigth. - * @param {integer} level The level of the quad in the tree. - * @param {integer} maxElements The max number of element in a leaf node. - * @param {integer} maxLevel The max recursion level of the tree. - * @return {object} The quadtree object. - */ - function _quadTree(bounds, level, maxElements, maxLevel) { - return { - level: level || 0, - bounds: bounds, - corners: _geom.splitSquare(bounds), - maxElements: maxElements || 40, - maxLevel: maxLevel || 8, - elements: [], - nodes: [] - }; - } - - - /** - * Sigma Quad Constructor - * ---------------------- - * - * The edgequad API as exposed to sigma. - */ - - /** - * The edgequad core that will become the sigma interface with the quadtree. - * - * property {object} _tree Property holding the quadtree object. - * property {object} _geom Exposition of the _geom namespace for testing. - * property {object} _cache Cache for the area method. - * property {boolean} _enabled Can index and retreive elements. - */ - var edgequad = function() { - this._geom = _geom; - this._tree = null; - this._cache = { - query: false, - result: false - }; - this._enabled = true; - }; - - /** - * Index a graph by inserting its edges into the quadtree. - * - * @param {object} graph A graph instance. - * @param {object} params An object of parameters with at least the quad - * bounds. - * @return {object} The quadtree object. - * - * Parameters: - * ---------- - * bounds: {object} boundaries of the quad defined by its origin (x, y) - * width and heigth. - * prefix: {string?} a prefix for edge geometric attributes. - * maxElements: {integer?} the max number of elements in a leaf node. - * maxLevel: {integer?} the max recursion level of the tree. - */ - edgequad.prototype.index = function(graph, params) { - if (!this._enabled) - return this._tree; - - // Enforcing presence of boundaries - if (!params.bounds) - throw 'sigma.classes.edgequad.index: bounds information not given.'; - - // Prefix - var prefix = params.prefix || '', - cp, - source, - target, - n, - e; - - // Building the tree - this._tree = _quadTree( - params.bounds, - 0, - params.maxElements, - params.maxLevel - ); - - var edges = graph.edges(); - - // Inserting graph edges into the tree - for (var i = 0, l = edges.length; i < l; i++) { - source = graph.nodes(edges[i].source); - target = graph.nodes(edges[i].target); - e = { - x1: source[prefix + 'x'], - y1: source[prefix + 'y'], - x2: target[prefix + 'x'], - y2: target[prefix + 'y'], - size: edges[i][prefix + 'size'] || 0 - }; - - // Inserting edge - if (edges[i].type === 'curve' || edges[i].type === 'curvedArrow') { - if (source.id === target.id) { - n = { - x: source[prefix + 'x'], - y: source[prefix + 'y'], - size: source[prefix + 'size'] || 0 - }; - _quadInsert( - edges[i], - _geom.selfLoopToSquare(n), - this._tree); - } - else { - cp = sigma.utils.getQuadraticControlPoint(e.x1, e.y1, e.x2, e.y2); - _quadInsert( - edges[i], - _geom.quadraticCurveToSquare(e, cp), - this._tree); - } - } - else { - _quadInsert( - edges[i], - _geom.lineToSquare(e), - this._tree); - } - } - - // Reset cache: - this._cache = { - query: false, - result: false - }; - - // remove? - return this._tree; - }; - - /** - * Retrieve every graph edges held by the quadtree node containing the - * searched point. - * - * @param {number} x of the point. - * @param {number} y of the point. - * @return {array} An array of edges retrieved. - */ - edgequad.prototype.point = function(x, y) { - if (!this._enabled) - return []; - - return this._tree ? - _quadRetrievePoint({x: x, y: y}, this._tree) || [] : - []; - }; - - /** - * Retrieve every graph edges within a rectangular area. The methods keep the - * last area queried in cache for optimization reason and will act differently - * for the same reason if the area is axis-aligned or not. - * - * @param {object} A rectangle defined by two top points (x1, y1), (x2, y2) - * and height. - * @return {array} An array of edges retrieved. - */ - edgequad.prototype.area = function(rect) { - if (!this._enabled) - return []; - - var serialized = JSON.stringify(rect), - collisionFunc, - rectData; - - // Returning cache? - if (this._cache.query === serialized) - return this._cache.result; - - // Axis aligned ? - if (_geom.isAxisAligned(rect)) { - collisionFunc = _quadIndexes; - rectData = _geom.axisAlignedTopPoints(rect); - } - else { - collisionFunc = _quadCollision; - rectData = _geom.rectangleCorners(rect); - } - - // Retrieving edges - var edges = this._tree ? - _quadRetrieveArea( - rectData, - this._tree, - collisionFunc - ) : - []; - - // Object to array - var edgesArray = []; - for (var i in edges) - edgesArray.push(edges[i]); - - // Caching - this._cache.query = serialized; - this._cache.result = edgesArray; - - return edgesArray; - }; - - - /** - * EXPORT: - * ******* - */ - if (typeof this.sigma !== 'undefined') { - this.sigma.classes = this.sigma.classes || {}; - this.sigma.classes.edgequad = edgequad; - } else if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) - exports = module.exports = edgequad; - exports.edgequad = edgequad; - } else - this.edgequad = edgequad; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.captors'); - - /** - * The user inputs default captor. It deals with mouse events, keyboards - * events and touch events. - * - * @param {DOMElement} target The DOM element where the listeners will be - * bound. - * @param {camera} camera The camera related to the target. - * @param {configurable} settings The settings function. - * @return {sigma.captor} The fresh new captor instance. - */ - sigma.captors.mouse = function(target, camera, settings) { - var _self = this, - _target = target, - _camera = camera, - _settings = settings, - - // CAMERA MANAGEMENT: - // ****************** - // The camera position when the user starts dragging: - _startCameraX, - _startCameraY, - _startCameraAngle, - - // The latest stage position: - _lastCameraX, - _lastCameraY, - _lastCameraAngle, - _lastCameraRatio, - - // MOUSE MANAGEMENT: - // ***************** - // The mouse position when the user starts dragging: - _startMouseX, - _startMouseY, - - _isMouseDown, - _isMoving, - _hasDragged, - _downStartTime, - _movingTimeoutId; - - sigma.classes.dispatcher.extend(this); - - sigma.utils.doubleClick(_target, 'click', _doubleClickHandler); - _target.addEventListener('DOMMouseScroll', _wheelHandler, false); - _target.addEventListener('mousewheel', _wheelHandler, false); - _target.addEventListener('mousemove', _moveHandler, false); - _target.addEventListener('mousedown', _downHandler, false); - _target.addEventListener('click', _clickHandler, false); - _target.addEventListener('mouseout', _outHandler, false); - document.addEventListener('mouseup', _upHandler, false); - - - - - /** - * This method unbinds every handlers that makes the captor work. - */ - this.kill = function() { - sigma.utils.unbindDoubleClick(_target, 'click'); - _target.removeEventListener('DOMMouseScroll', _wheelHandler); - _target.removeEventListener('mousewheel', _wheelHandler); - _target.removeEventListener('mousemove', _moveHandler); - _target.removeEventListener('mousedown', _downHandler); - _target.removeEventListener('click', _clickHandler); - _target.removeEventListener('mouseout', _outHandler); - document.removeEventListener('mouseup', _upHandler); - }; - - - - - // MOUSE EVENTS: - // ************* - - /** - * The handler listening to the 'move' mouse event. It will effectively - * drag the graph. - * - * @param {event} e A mouse event. - */ - function _moveHandler(e) { - var x, - y, - pos; - - // Dispatch event: - if (_settings('mouseEnabled')) { - _self.dispatchEvent('mousemove', - sigma.utils.mouseCoords(e)); - - if (_isMouseDown) { - _isMoving = true; - _hasDragged = true; - - if (_movingTimeoutId) - clearTimeout(_movingTimeoutId); - - _movingTimeoutId = setTimeout(function() { - _isMoving = false; - }, _settings('dragTimeout')); - - sigma.misc.animation.killAll(_camera); - - _camera.isMoving = true; - pos = _camera.cameraPosition( - sigma.utils.getX(e) - _startMouseX, - sigma.utils.getY(e) - _startMouseY, - true - ); - - x = _startCameraX - pos.x; - y = _startCameraY - pos.y; - - if (x !== _camera.x || y !== _camera.y) { - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - _camera.goTo({ - x: x, - y: y - }); - } - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - } - } - - /** - * The handler listening to the 'up' mouse event. It will stop dragging the - * graph. - * - * @param {event} e A mouse event. - */ - function _upHandler(e) { - if (_settings('mouseEnabled') && _isMouseDown) { - _isMouseDown = false; - if (_movingTimeoutId) - clearTimeout(_movingTimeoutId); - - _camera.isMoving = false; - - var x = sigma.utils.getX(e), - y = sigma.utils.getY(e); - - if (_isMoving) { - sigma.misc.animation.killAll(_camera); - sigma.misc.animation.camera( - _camera, - { - x: _camera.x + - _settings('mouseInertiaRatio') * (_camera.x - _lastCameraX), - y: _camera.y + - _settings('mouseInertiaRatio') * (_camera.y - _lastCameraY) - }, - { - easing: 'quadraticOut', - duration: _settings('mouseInertiaDuration') - } - ); - } else if ( - _startMouseX !== x || - _startMouseY !== y - ) - _camera.goTo({ - x: _camera.x, - y: _camera.y - }); - - _self.dispatchEvent('mouseup', - sigma.utils.mouseCoords(e)); - - // Update _isMoving flag: - _isMoving = false; - } - } - - /** - * The handler listening to the 'down' mouse event. It will start observing - * the mouse position for dragging the graph. - * - * @param {event} e A mouse event. - */ - function _downHandler(e) { - if (_settings('mouseEnabled')) { - _startCameraX = _camera.x; - _startCameraY = _camera.y; - - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - _startMouseX = sigma.utils.getX(e); - _startMouseY = sigma.utils.getY(e); - - _hasDragged = false; - _downStartTime = (new Date()).getTime(); - - switch (e.which) { - case 2: - // Middle mouse button pressed - // Do nothing. - break; - case 3: - // Right mouse button pressed - _self.dispatchEvent('rightclick', - sigma.utils.mouseCoords(e, _startMouseX, _startMouseY)); - break; - // case 1: - default: - // Left mouse button pressed - _isMouseDown = true; - - _self.dispatchEvent('mousedown', - sigma.utils.mouseCoords(e, _startMouseX, _startMouseY)); - } - } - } - - /** - * The handler listening to the 'out' mouse event. It will just redispatch - * the event. - * - * @param {event} e A mouse event. - */ - function _outHandler(e) { - if (_settings('mouseEnabled')) - _self.dispatchEvent('mouseout'); - } - - /** - * The handler listening to the 'click' mouse event. It will redispatch the - * click event, but with normalized X and Y coordinates. - * - * @param {event} e A mouse event. - */ - function _clickHandler(e) { - if (_settings('mouseEnabled')) { - var event = sigma.utils.mouseCoords(e); - event.isDragging = - (((new Date()).getTime() - _downStartTime) > 100) && _hasDragged; - _self.dispatchEvent('click', event); - } - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - - /** - * The handler listening to the double click custom event. It will - * basically zoom into the graph. - * - * @param {event} e A mouse event. - */ - function _doubleClickHandler(e) { - var pos, - ratio, - animation; - - if (_settings('mouseEnabled')) { - ratio = 1 / _settings('doubleClickZoomingRatio'); - - _self.dispatchEvent('doubleclick', - sigma.utils.mouseCoords(e, _startMouseX, _startMouseY)); - - if (_settings('doubleClickEnabled')) { - pos = _camera.cameraPosition( - sigma.utils.getX(e) - sigma.utils.getCenter(e).x, - sigma.utils.getY(e) - sigma.utils.getCenter(e).y, - true - ); - - animation = { - duration: _settings('doubleClickZoomDuration') - }; - - sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation); - } - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - } - - /** - * The handler listening to the 'wheel' mouse event. It will basically zoom - * in or not into the graph. - * - * @param {event} e A mouse event. - */ - function _wheelHandler(e) { - var pos, - ratio, - animation; - - if (_settings('mouseEnabled') && _settings('mouseWheelEnabled')) { - ratio = sigma.utils.getDelta(e) > 0 ? - 1 / _settings('zoomingRatio') : - _settings('zoomingRatio'); - - pos = _camera.cameraPosition( - sigma.utils.getX(e) - sigma.utils.getCenter(e).x, - sigma.utils.getY(e) - sigma.utils.getCenter(e).y, - true - ); - - animation = { - duration: _settings('mouseZoomDuration') - }; - - sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation); - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - } - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.captors'); - - /** - * The user inputs default captor. It deals with mouse events, keyboards - * events and touch events. - * - * @param {DOMElement} target The DOM element where the listeners will be - * bound. - * @param {camera} camera The camera related to the target. - * @param {configurable} settings The settings function. - * @return {sigma.captor} The fresh new captor instance. - */ - sigma.captors.touch = function(target, camera, settings) { - var _self = this, - _target = target, - _camera = camera, - _settings = settings, - - // CAMERA MANAGEMENT: - // ****************** - // The camera position when the user starts dragging: - _startCameraX, - _startCameraY, - _startCameraAngle, - _startCameraRatio, - - // The latest stage position: - _lastCameraX, - _lastCameraY, - _lastCameraAngle, - _lastCameraRatio, - - // TOUCH MANAGEMENT: - // ***************** - // Touches that are down: - _downTouches = [], - - _startTouchX0, - _startTouchY0, - _startTouchX1, - _startTouchY1, - _startTouchAngle, - _startTouchDistance, - - _touchMode, - - _isMoving, - _doubleTap, - _movingTimeoutId; - - sigma.classes.dispatcher.extend(this); - - sigma.utils.doubleClick(_target, 'touchstart', _doubleTapHandler); - _target.addEventListener('touchstart', _handleStart, false); - _target.addEventListener('touchend', _handleLeave, false); - _target.addEventListener('touchcancel', _handleLeave, false); - _target.addEventListener('touchleave', _handleLeave, false); - _target.addEventListener('touchmove', _handleMove, false); - - function position(e) { - var offset = sigma.utils.getOffset(_target); - - return { - x: e.pageX - offset.left, - y: e.pageY - offset.top - }; - } - - /** - * This method unbinds every handlers that makes the captor work. - */ - this.kill = function() { - sigma.utils.unbindDoubleClick(_target, 'touchstart'); - _target.addEventListener('touchstart', _handleStart); - _target.addEventListener('touchend', _handleLeave); - _target.addEventListener('touchcancel', _handleLeave); - _target.addEventListener('touchleave', _handleLeave); - _target.addEventListener('touchmove', _handleMove); - }; - - // TOUCH EVENTS: - // ************* - /** - * The handler listening to the 'touchstart' event. It will set the touch - * mode ("_touchMode") and start observing the user touch moves. - * - * @param {event} e A touch event. - */ - function _handleStart(e) { - if (_settings('touchEnabled')) { - var x0, - x1, - y0, - y1, - pos0, - pos1; - - _downTouches = e.touches; - - switch (_downTouches.length) { - case 1: - _camera.isMoving = true; - _touchMode = 1; - - _startCameraX = _camera.x; - _startCameraY = _camera.y; - - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - pos0 = position(_downTouches[0]); - _startTouchX0 = pos0.x; - _startTouchY0 = pos0.y; - - break; - case 2: - _camera.isMoving = true; - _touchMode = 2; - - pos0 = position(_downTouches[0]); - pos1 = position(_downTouches[1]); - x0 = pos0.x; - y0 = pos0.y; - x1 = pos1.x; - y1 = pos1.y; - - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - _startCameraAngle = _camera.angle; - _startCameraRatio = _camera.ratio; - - _startCameraX = _camera.x; - _startCameraY = _camera.y; - - _startTouchX0 = x0; - _startTouchY0 = y0; - _startTouchX1 = x1; - _startTouchY1 = y1; - - _startTouchAngle = Math.atan2( - _startTouchY1 - _startTouchY0, - _startTouchX1 - _startTouchX0 - ); - _startTouchDistance = Math.sqrt( - (_startTouchY1 - _startTouchY0) * - (_startTouchY1 - _startTouchY0) + - (_startTouchX1 - _startTouchX0) * - (_startTouchX1 - _startTouchX0) - ); - - e.preventDefault(); - return false; - } - } - } - - /** - * The handler listening to the 'touchend', 'touchcancel' and 'touchleave' - * event. It will update the touch mode if there are still at least one - * finger, and stop dragging else. - * - * @param {event} e A touch event. - */ - function _handleLeave(e) { - if (_settings('touchEnabled')) { - _downTouches = e.touches; - var inertiaRatio = _settings('touchInertiaRatio'); - - if (_movingTimeoutId) { - _isMoving = false; - clearTimeout(_movingTimeoutId); - } - - switch (_touchMode) { - case 2: - if (e.touches.length === 1) { - _handleStart(e); - - e.preventDefault(); - break; - } - /* falls through */ - case 1: - _camera.isMoving = false; - _self.dispatchEvent('stopDrag'); - - if (_isMoving) { - _doubleTap = false; - sigma.misc.animation.camera( - _camera, - { - x: _camera.x + - inertiaRatio * (_camera.x - _lastCameraX), - y: _camera.y + - inertiaRatio * (_camera.y - _lastCameraY) - }, - { - easing: 'quadraticOut', - duration: _settings('touchInertiaDuration') - } - ); - } - - _isMoving = false; - _touchMode = 0; - break; - } - } - } - - /** - * The handler listening to the 'touchmove' event. It will effectively drag - * the graph, and eventually zooms and turn it if the user is using two - * fingers. - * - * @param {event} e A touch event. - */ - function _handleMove(e) { - if (!_doubleTap && _settings('touchEnabled')) { - var x0, - x1, - y0, - y1, - cos, - sin, - end, - pos0, - pos1, - diff, - start, - dAngle, - dRatio, - newStageX, - newStageY, - newStageRatio, - newStageAngle; - - _downTouches = e.touches; - _isMoving = true; - - if (_movingTimeoutId) - clearTimeout(_movingTimeoutId); - - _movingTimeoutId = setTimeout(function() { - _isMoving = false; - }, _settings('dragTimeout')); - - switch (_touchMode) { - case 1: - pos0 = position(_downTouches[0]); - x0 = pos0.x; - y0 = pos0.y; - - diff = _camera.cameraPosition( - x0 - _startTouchX0, - y0 - _startTouchY0, - true - ); - - newStageX = _startCameraX - diff.x; - newStageY = _startCameraY - diff.y; - - if (newStageX !== _camera.x || newStageY !== _camera.y) { - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - - _camera.goTo({ - x: newStageX, - y: newStageY - }); - - _self.dispatchEvent('mousemove', - sigma.utils.mouseCoords(e, pos0.x, pos0.y)); - - _self.dispatchEvent('drag'); - } - break; - case 2: - pos0 = position(_downTouches[0]); - pos1 = position(_downTouches[1]); - x0 = pos0.x; - y0 = pos0.y; - x1 = pos1.x; - y1 = pos1.y; - - start = _camera.cameraPosition( - (_startTouchX0 + _startTouchX1) / 2 - - sigma.utils.getCenter(e).x, - (_startTouchY0 + _startTouchY1) / 2 - - sigma.utils.getCenter(e).y, - true - ); - end = _camera.cameraPosition( - (x0 + x1) / 2 - sigma.utils.getCenter(e).x, - (y0 + y1) / 2 - sigma.utils.getCenter(e).y, - true - ); - - dAngle = Math.atan2(y1 - y0, x1 - x0) - _startTouchAngle; - dRatio = Math.sqrt( - (y1 - y0) * (y1 - y0) + (x1 - x0) * (x1 - x0) - ) / _startTouchDistance; - - // Translation: - x0 = start.x; - y0 = start.y; - - // Homothetic transformation: - newStageRatio = _startCameraRatio / dRatio; - x0 = x0 * dRatio; - y0 = y0 * dRatio; - - // Rotation: - newStageAngle = _startCameraAngle - dAngle; - cos = Math.cos(-dAngle); - sin = Math.sin(-dAngle); - x1 = x0 * cos + y0 * sin; - y1 = y0 * cos - x0 * sin; - x0 = x1; - y0 = y1; - - // Finalize: - newStageX = x0 - end.x + _startCameraX; - newStageY = y0 - end.y + _startCameraY; - - if ( - newStageRatio !== _camera.ratio || - newStageAngle !== _camera.angle || - newStageX !== _camera.x || - newStageY !== _camera.y - ) { - _lastCameraX = _camera.x; - _lastCameraY = _camera.y; - _lastCameraAngle = _camera.angle; - _lastCameraRatio = _camera.ratio; - - _camera.goTo({ - x: newStageX, - y: newStageY, - angle: newStageAngle, - ratio: newStageRatio - }); - - _self.dispatchEvent('drag'); - } - - break; - } - - e.preventDefault(); - return false; - } - } - - /** - * The handler listening to the double tap custom event. It will - * basically zoom into the graph. - * - * @param {event} e A touch event. - */ - function _doubleTapHandler(e) { - var pos, - ratio, - animation; - - if (e.touches && e.touches.length === 1 && _settings('touchEnabled')) { - _doubleTap = true; - - ratio = 1 / _settings('doubleClickZoomingRatio'); - - pos = position(e.touches[0]); - _self.dispatchEvent('doubleclick', - sigma.utils.mouseCoords(e, pos.x, pos.y)); - - if (_settings('doubleClickEnabled')) { - pos = _camera.cameraPosition( - pos.x - sigma.utils.getCenter(e).x, - pos.y - sigma.utils.getCenter(e).y, - true - ); - - animation = { - duration: _settings('doubleClickZoomDuration'), - onComplete: function() { - _doubleTap = false; - } - }; - - sigma.utils.zoomTo(_camera, pos.x, pos.y, ratio, animation); - } - - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - e.stopPropagation(); - return false; - } - } - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - if (typeof conrad === 'undefined') - throw 'conrad is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.renderers'); - - /** - * This function is the constructor of the canvas sigma's renderer. - * - * @param {sigma.classes.graph} graph The graph to render. - * @param {sigma.classes.camera} camera The camera. - * @param {configurable} settings The sigma instance settings - * function. - * @param {object} object The options object. - * @return {sigma.renderers.canvas} The renderer instance. - */ - sigma.renderers.canvas = function(graph, camera, settings, options) { - if (typeof options !== 'object') - throw 'sigma.renderers.canvas: Wrong arguments.'; - - if (!(options.container instanceof HTMLElement)) - throw 'Container not found.'; - - var k, - i, - l, - a, - fn, - self = this; - - sigma.classes.dispatcher.extend(this); - - // Initialize main attributes: - Object.defineProperty(this, 'conradId', { - value: sigma.utils.id() - }); - this.graph = graph; - this.camera = camera; - this.contexts = {}; - this.domElements = {}; - this.options = options; - this.container = this.options.container; - this.settings = ( - typeof options.settings === 'object' && - options.settings - ) ? - settings.embedObjects(options.settings) : - settings; - - // Node indexes: - this.nodesOnScreen = []; - this.edgesOnScreen = []; - - // Conrad related attributes: - this.jobs = {}; - - // Find the prefix: - this.options.prefix = 'renderer' + this.conradId + ':'; - - // Initialize the DOM elements: - if ( - !this.settings('batchEdgesDrawing') - ) { - this.initDOM('canvas', 'scene'); - this.contexts.edges = this.contexts.scene; - this.contexts.nodes = this.contexts.scene; - this.contexts.labels = this.contexts.scene; - } else { - this.initDOM('canvas', 'edges'); - this.initDOM('canvas', 'scene'); - this.contexts.nodes = this.contexts.scene; - this.contexts.labels = this.contexts.scene; - } - - this.initDOM('canvas', 'mouse'); - this.contexts.hover = this.contexts.mouse; - - // Initialize captors: - this.captors = []; - a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch]; - for (i = 0, l = a.length; i < l; i++) { - fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]]; - this.captors.push( - new fn( - this.domElements.mouse, - this.camera, - this.settings - ) - ); - } - - // Deal with sigma events: - sigma.misc.bindEvents.call(this, this.options.prefix); - sigma.misc.drawHovers.call(this, this.options.prefix); - - this.resize(false); - }; - - - - - /** - * This method renders the graph on the canvases. - * - * @param {?object} options Eventually an object of options. - * @return {sigma.renderers.canvas} Returns the instance itself. - */ - sigma.renderers.canvas.prototype.render = function(options) { - options = options || {}; - - var a, - i, - k, - l, - o, - id, - end, - job, - start, - edges, - renderers, - rendererType, - batchSize, - tempGCO, - index = {}, - graph = this.graph, - nodes = this.graph.nodes, - prefix = this.options.prefix || '', - drawEdges = this.settings(options, 'drawEdges'), - drawNodes = this.settings(options, 'drawNodes'), - drawLabels = this.settings(options, 'drawLabels'), - drawEdgeLabels = this.settings(options, 'drawEdgeLabels'), - embedSettings = this.settings.embedObjects(options, { - prefix: this.options.prefix - }); - - // Call the resize function: - this.resize(false); - - // Check the 'hideEdgesOnMove' setting: - if (this.settings(options, 'hideEdgesOnMove')) - if (this.camera.isAnimated || this.camera.isMoving) - drawEdges = false; - - // Apply the camera's view: - this.camera.applyView( - undefined, - this.options.prefix, - { - width: this.width, - height: this.height - } - ); - - // Clear canvases: - this.clear(); - - // Kill running jobs: - for (k in this.jobs) - if (conrad.hasJob(k)) - conrad.killJob(k); - - // Find which nodes are on screen: - this.edgesOnScreen = []; - this.nodesOnScreen = this.camera.quadtree.area( - this.camera.getRectangle(this.width, this.height) - ); - - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) - index[a[i].id] = a[i]; - - // Draw edges: - // - If settings('batchEdgesDrawing') is true, the edges are displayed per - // batches. If not, they are drawn in one frame. - if (drawEdges) { - // First, let's identify which edges to draw. To do this, we just keep - // every edges that have at least one extremity displayed according to - // the quadtree and the "hidden" attribute. We also do not keep hidden - // edges. - for (a = graph.edges(), i = 0, l = a.length; i < l; i++) { - o = a[i]; - if ( - (index[o.source] || index[o.target]) && - (!o.hidden && !nodes(o.source).hidden && !nodes(o.target).hidden) - ) - this.edgesOnScreen.push(o); - } - - // If the "batchEdgesDrawing" settings is true, edges are batched: - if (this.settings(options, 'batchEdgesDrawing')) { - id = 'edges_' + this.conradId; - batchSize = embedSettings('canvasEdgesBatchSize'); - - edges = this.edgesOnScreen; - l = edges.length; - - start = 0; - end = Math.min(edges.length, start + batchSize); - - job = function() { - tempGCO = this.contexts.edges.globalCompositeOperation; - this.contexts.edges.globalCompositeOperation = 'destination-over'; - - renderers = sigma.canvas.edges; - for (i = start; i < end; i++) { - o = edges[i]; - (renderers[ - o.type || this.settings(options, 'defaultEdgeType') - ] || renderers.def)( - o, - graph.nodes(o.source), - graph.nodes(o.target), - this.contexts.edges, - embedSettings - ); - } - - // Draw edge labels: - if (drawEdgeLabels) { - renderers = sigma.canvas.edges.labels; - for (i = start; i < end; i++) { - o = edges[i]; - if (!o.hidden) - (renderers[ - o.type || this.settings(options, 'defaultEdgeType') - ] || renderers.def)( - o, - graph.nodes(o.source), - graph.nodes(o.target), - this.contexts.labels, - embedSettings - ); - } - } - - // Restore original globalCompositeOperation: - this.contexts.edges.globalCompositeOperation = tempGCO; - - // Catch job's end: - if (end === edges.length) { - delete this.jobs[id]; - return false; - } - - start = end + 1; - end = Math.min(edges.length, start + batchSize); - return true; - }; - - this.jobs[id] = job; - conrad.addJob(id, job.bind(this)); - - // If not, they are drawn in one frame: - } else { - renderers = sigma.canvas.edges; - for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) { - o = a[i]; - (renderers[ - o.type || this.settings(options, 'defaultEdgeType') - ] || renderers.def)( - o, - graph.nodes(o.source), - graph.nodes(o.target), - this.contexts.edges, - embedSettings - ); - } - - // Draw edge labels: - // - No batching - if (drawEdgeLabels) { - renderers = sigma.canvas.edges.labels; - for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) - if (!a[i].hidden) - (renderers[ - a[i].type || this.settings(options, 'defaultEdgeType') - ] || renderers.def)( - a[i], - graph.nodes(a[i].source), - graph.nodes(a[i].target), - this.contexts.labels, - embedSettings - ); - } - } - } - - // Draw nodes: - // - No batching - if (drawNodes) { - renderers = sigma.canvas.nodes; - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) - if (!a[i].hidden) - (renderers[ - a[i].type || this.settings(options, 'defaultNodeType') - ] || renderers.def)( - a[i], - this.contexts.nodes, - embedSettings - ); - } - - // Draw labels: - // - No batching - if (drawLabels) { - renderers = sigma.canvas.labels; - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) - if (!a[i].hidden) - (renderers[ - a[i].type || this.settings(options, 'defaultNodeType') - ] || renderers.def)( - a[i], - this.contexts.labels, - embedSettings - ); - } - - this.dispatchEvent('render'); - - return this; - }; - - /** - * This method creates a DOM element of the specified type, switches its - * position to "absolute", references it to the domElements attribute, and - * finally appends it to the container. - * - * @param {string} tag The label tag. - * @param {string} id The id of the element (to store it in "domElements"). - */ - sigma.renderers.canvas.prototype.initDOM = function(tag, id) { - var dom = document.createElement(tag); - - dom.style.position = 'absolute'; - dom.setAttribute('class', 'sigma-' + id); - - this.domElements[id] = dom; - this.container.appendChild(dom); - - if (tag.toLowerCase() === 'canvas') - this.contexts[id] = dom.getContext('2d'); - }; - - /** - * This method resizes each DOM elements in the container and stores the new - * dimensions. Then, it renders the graph. - * - * @param {?number} width The new width of the container. - * @param {?number} height The new height of the container. - * @return {sigma.renderers.canvas} Returns the instance itself. - */ - sigma.renderers.canvas.prototype.resize = function(w, h) { - var k, - oldWidth = this.width, - oldHeight = this.height, - pixelRatio = sigma.utils.getPixelRatio(); - - if (w !== undefined && h !== undefined) { - this.width = w; - this.height = h; - } else { - this.width = this.container.offsetWidth; - this.height = this.container.offsetHeight; - - w = this.width; - h = this.height; - } - - if (oldWidth !== this.width || oldHeight !== this.height) { - for (k in this.domElements) { - this.domElements[k].style.width = w + 'px'; - this.domElements[k].style.height = h + 'px'; - - if (this.domElements[k].tagName.toLowerCase() === 'canvas') { - this.domElements[k].setAttribute('width', (w * pixelRatio) + 'px'); - this.domElements[k].setAttribute('height', (h * pixelRatio) + 'px'); - - if (pixelRatio !== 1) - this.contexts[k].scale(pixelRatio, pixelRatio); - } - } - } - - return this; - }; - - /** - * This method clears each canvas. - * - * @return {sigma.renderers.canvas} Returns the instance itself. - */ - sigma.renderers.canvas.prototype.clear = function() { - for (var k in this.contexts) { - this.contexts[k].clearRect(0, 0, this.width, this.height); - } - - return this; - }; - - /** - * This method kills contexts and other attributes. - */ - sigma.renderers.canvas.prototype.kill = function() { - var k, - captor; - - // Kill captors: - while ((captor = this.captors.pop())) - captor.kill(); - delete this.captors; - - // Kill contexts: - for (k in this.domElements) { - this.domElements[k].parentNode.removeChild(this.domElements[k]); - delete this.domElements[k]; - delete this.contexts[k]; - } - delete this.domElements; - delete this.contexts; - }; - - - - - /** - * The labels, nodes and edges renderers are stored in the three following - * objects. When an element is drawn, its type will be checked and if a - * renderer with the same name exists, it will be used. If not found, the - * default renderer will be used instead. - * - * They are stored in different files, in the "./canvas" folder. - */ - sigma.utils.pkg('sigma.canvas.nodes'); - sigma.utils.pkg('sigma.canvas.edges'); - sigma.utils.pkg('sigma.canvas.labels'); -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.renderers'); - - /** - * This function is the constructor of the canvas sigma's renderer. - * - * @param {sigma.classes.graph} graph The graph to render. - * @param {sigma.classes.camera} camera The camera. - * @param {configurable} settings The sigma instance settings - * function. - * @param {object} object The options object. - * @return {sigma.renderers.canvas} The renderer instance. - */ - sigma.renderers.webgl = function(graph, camera, settings, options) { - if (typeof options !== 'object') - throw 'sigma.renderers.webgl: Wrong arguments.'; - - if (!(options.container instanceof HTMLElement)) - throw 'Container not found.'; - - var k, - i, - l, - a, - fn, - _self = this; - - sigma.classes.dispatcher.extend(this); - - // Conrad related attributes: - this.jobs = {}; - - Object.defineProperty(this, 'conradId', { - value: sigma.utils.id() - }); - - // Initialize main attributes: - this.graph = graph; - this.camera = camera; - this.contexts = {}; - this.domElements = {}; - this.options = options; - this.container = this.options.container; - this.settings = ( - typeof options.settings === 'object' && - options.settings - ) ? - settings.embedObjects(options.settings) : - settings; - - // Find the prefix: - this.options.prefix = this.camera.readPrefix; - - // Initialize programs hash - Object.defineProperty(this, 'nodePrograms', { - value: {} - }); - Object.defineProperty(this, 'edgePrograms', { - value: {} - }); - Object.defineProperty(this, 'nodeFloatArrays', { - value: {} - }); - Object.defineProperty(this, 'edgeFloatArrays', { - value: {} - }); - Object.defineProperty(this, 'edgeIndicesArrays', { - value: {} - }); - - // Initialize the DOM elements: - if (this.settings(options, 'batchEdgesDrawing')) { - this.initDOM('canvas', 'edges', true); - this.initDOM('canvas', 'nodes', true); - } else { - this.initDOM('canvas', 'scene', true); - this.contexts.nodes = this.contexts.scene; - this.contexts.edges = this.contexts.scene; - } - - this.initDOM('canvas', 'labels'); - this.initDOM('canvas', 'mouse'); - this.contexts.hover = this.contexts.mouse; - - // Initialize captors: - this.captors = []; - a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch]; - for (i = 0, l = a.length; i < l; i++) { - fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]]; - this.captors.push( - new fn( - this.domElements.mouse, - this.camera, - this.settings - ) - ); - } - - // Deal with sigma events: - sigma.misc.bindEvents.call(this, this.camera.prefix); - sigma.misc.drawHovers.call(this, this.camera.prefix); - - this.resize(); - }; - - - - - /** - * This method will generate the nodes and edges float arrays. This step is - * separated from the "render" method, because to keep WebGL efficient, since - * all the camera and middlewares are modelised as matrices and they do not - * require the float arrays to be regenerated. - * - * Basically, when the user moves the camera or applies some specific linear - * transformations, this process step will be skipped, and the "render" - * method will efficiently refresh the rendering. - * - * And when the user modifies the graph colors or positions (applying a new - * layout or filtering the colors, for instance), this "process" step will be - * required to regenerate the float arrays. - * - * @return {sigma.renderers.webgl} Returns the instance itself. - */ - sigma.renderers.webgl.prototype.process = function() { - var a, - i, - l, - k, - type, - renderer, - graph = this.graph, - options = sigma.utils.extend(options, this.options), - defaultEdgeType = this.settings(options, 'defaultEdgeType'), - defaultNodeType = this.settings(options, 'defaultNodeType'); - - // Empty float arrays: - for (k in this.nodeFloatArrays) - delete this.nodeFloatArrays[k]; - - for (k in this.edgeFloatArrays) - delete this.edgeFloatArrays[k]; - - for (k in this.edgeIndicesArrays) - delete this.edgeIndicesArrays[k]; - - // Sort edges and nodes per types: - for (a = graph.edges(), i = 0, l = a.length; i < l; i++) { - type = a[i].type || defaultEdgeType; - k = (type && sigma.webgl.edges[type]) ? type : 'def'; - - if (!this.edgeFloatArrays[k]) - this.edgeFloatArrays[k] = { - edges: [] - }; - - this.edgeFloatArrays[k].edges.push(a[i]); - } - - for (a = graph.nodes(), i = 0, l = a.length; i < l; i++) { - type = a[i].type || defaultNodeType; - k = (type && sigma.webgl.nodes[type]) ? type : 'def'; - - if (!this.nodeFloatArrays[k]) - this.nodeFloatArrays[k] = { - nodes: [] - }; - - this.nodeFloatArrays[k].nodes.push(a[i]); - } - - // Push edges: - for (k in this.edgeFloatArrays) { - renderer = sigma.webgl.edges[k]; - a = this.edgeFloatArrays[k].edges; - - // Creating the necessary arrays - this.edgeFloatArrays[k].array = new Float32Array( - a.length * renderer.POINTS * renderer.ATTRIBUTES - ); - - for (i = 0, l = a.length; i < l; i++) { - - // Just check that the edge and both its extremities are visible: - if ( - !a[i].hidden && - !graph.nodes(a[i].source).hidden && - !graph.nodes(a[i].target).hidden - ) - renderer.addEdge( - a[i], - graph.nodes(a[i].source), - graph.nodes(a[i].target), - this.edgeFloatArrays[k].array, - i * renderer.POINTS * renderer.ATTRIBUTES, - options.prefix, - this.settings - ); - } - - if (typeof renderer.computeIndices === 'function') - this.edgeIndicesArrays[k] = renderer.computeIndices( - this.edgeFloatArrays[k].array - ); - } - - // Push nodes: - for (k in this.nodeFloatArrays) { - renderer = sigma.webgl.nodes[k]; - a = this.nodeFloatArrays[k].nodes; - - // Creating the necessary arrays - this.nodeFloatArrays[k].array = new Float32Array( - a.length * renderer.POINTS * renderer.ATTRIBUTES - ); - - for (i = 0, l = a.length; i < l; i++) { - if (!this.nodeFloatArrays[k].array) - this.nodeFloatArrays[k].array = new Float32Array( - a.length * renderer.POINTS * renderer.ATTRIBUTES - ); - - // Just check that the edge and both its extremities are visible: - if ( - !a[i].hidden - ) - renderer.addNode( - a[i], - this.nodeFloatArrays[k].array, - i * renderer.POINTS * renderer.ATTRIBUTES, - options.prefix, - this.settings - ); - } - } - - return this; - }; - - - - - /** - * This method renders the graph. It basically calls each program (and - * generate them if they do not exist yet) to render nodes and edges, batched - * per renderer. - * - * As in the canvas renderer, it is possible to display edges, nodes and / or - * labels in batches, to make the whole thing way more scalable. - * - * @param {?object} params Eventually an object of options. - * @return {sigma.renderers.webgl} Returns the instance itself. - */ - sigma.renderers.webgl.prototype.render = function(params) { - var a, - i, - l, - k, - o, - program, - renderer, - self = this, - graph = this.graph, - nodesGl = this.contexts.nodes, - edgesGl = this.contexts.edges, - matrix = this.camera.getMatrix(), - options = sigma.utils.extend(params, this.options), - drawLabels = this.settings(options, 'drawLabels'), - drawEdges = this.settings(options, 'drawEdges'), - drawNodes = this.settings(options, 'drawNodes'); - - // Call the resize function: - this.resize(false); - - // Check the 'hideEdgesOnMove' setting: - if (this.settings(options, 'hideEdgesOnMove')) - if (this.camera.isAnimated || this.camera.isMoving) - drawEdges = false; - - // Clear canvases: - this.clear(); - - // Translate matrix to [width/2, height/2]: - matrix = sigma.utils.matrices.multiply( - matrix, - sigma.utils.matrices.translation(this.width / 2, this.height / 2) - ); - - // Kill running jobs: - for (k in this.jobs) - if (conrad.hasJob(k)) - conrad.killJob(k); - - if (drawEdges) { - if (this.settings(options, 'batchEdgesDrawing')) - (function() { - var a, - k, - i, - id, - job, - arr, - end, - start, - indices, - renderer, - batchSize, - currentProgram; - - id = 'edges_' + this.conradId; - batchSize = this.settings(options, 'webglEdgesBatchSize'); - a = Object.keys(this.edgeFloatArrays); - - if (!a.length) - return; - i = 0; - renderer = sigma.webgl.edges[a[i]]; - arr = this.edgeFloatArrays[a[i]].array; - indices = this.edgeIndicesArrays[a[i]]; - start = 0; - end = Math.min( - start + batchSize * renderer.POINTS, - arr.length / renderer.ATTRIBUTES - ); - - job = function() { - // Check program: - if (!this.edgePrograms[a[i]]) - this.edgePrograms[a[i]] = renderer.initProgram(edgesGl); - - if (start < end) { - edgesGl.useProgram(this.edgePrograms[a[i]]); - renderer.render( - edgesGl, - this.edgePrograms[a[i]], - arr, - { - settings: this.settings, - matrix: matrix, - width: this.width, - height: this.height, - ratio: this.camera.ratio, - scalingRatio: this.settings( - options, - 'webglOversamplingRatio' - ), - start: start, - count: end - start, - indicesData: indices - } - ); - } - - // Catch job's end: - if ( - end >= arr.length / renderer.ATTRIBUTES && - i === a.length - 1 - ) { - delete this.jobs[id]; - return false; - } - - if (end >= arr.length / renderer.ATTRIBUTES) { - i++; - arr = this.edgeFloatArrays[a[i]].array; - renderer = sigma.webgl.edges[a[i]]; - start = 0; - end = Math.min( - start + batchSize * renderer.POINTS, - arr.length / renderer.ATTRIBUTES - ); - } else { - start = end; - end = Math.min( - start + batchSize * renderer.POINTS, - arr.length / renderer.ATTRIBUTES - ); - } - - return true; - }; - - this.jobs[id] = job; - conrad.addJob(id, job.bind(this)); - }).call(this); - else { - for (k in this.edgeFloatArrays) { - renderer = sigma.webgl.edges[k]; - - // Check program: - if (!this.edgePrograms[k]) - this.edgePrograms[k] = renderer.initProgram(edgesGl); - - // Render - if (this.edgeFloatArrays[k]) { - edgesGl.useProgram(this.edgePrograms[k]); - renderer.render( - edgesGl, - this.edgePrograms[k], - this.edgeFloatArrays[k].array, - { - settings: this.settings, - matrix: matrix, - width: this.width, - height: this.height, - ratio: this.camera.ratio, - scalingRatio: this.settings(options, 'webglOversamplingRatio'), - indicesData: this.edgeIndicesArrays[k] - } - ); - } - } - } - } - - if (drawNodes) { - // Enable blending: - nodesGl.blendFunc(nodesGl.SRC_ALPHA, nodesGl.ONE_MINUS_SRC_ALPHA); - nodesGl.enable(nodesGl.BLEND); - - for (k in this.nodeFloatArrays) { - renderer = sigma.webgl.nodes[k]; - - // Check program: - if (!this.nodePrograms[k]) - this.nodePrograms[k] = renderer.initProgram(nodesGl); - - // Render - if (this.nodeFloatArrays[k]) { - nodesGl.useProgram(this.nodePrograms[k]); - renderer.render( - nodesGl, - this.nodePrograms[k], - this.nodeFloatArrays[k].array, - { - settings: this.settings, - matrix: matrix, - width: this.width, - height: this.height, - ratio: this.camera.ratio, - scalingRatio: this.settings(options, 'webglOversamplingRatio') - } - ); - } - } - } - - if (drawLabels) { - a = this.camera.quadtree.area( - this.camera.getRectangle(this.width, this.height) - ); - - // Apply camera view to these nodes: - this.camera.applyView( - undefined, - undefined, - { - nodes: a, - edges: [], - width: this.width, - height: this.height - } - ); - - o = function(key) { - return self.settings({ - prefix: self.camera.prefix - }, key); - }; - - for (i = 0, l = a.length; i < l; i++) - if (!a[i].hidden) - ( - sigma.canvas.labels[ - a[i].type || - this.settings(options, 'defaultNodeType') - ] || sigma.canvas.labels.def - )(a[i], this.contexts.labels, o); - } - - this.dispatchEvent('render'); - - return this; - }; - - - - - /** - * This method creates a DOM element of the specified type, switches its - * position to "absolute", references it to the domElements attribute, and - * finally appends it to the container. - * - * @param {string} tag The label tag. - * @param {string} id The id of the element (to store it in - * "domElements"). - * @param {?boolean} webgl Will init the WebGL context if true. - */ - sigma.renderers.webgl.prototype.initDOM = function(tag, id, webgl) { - var gl, - dom = document.createElement(tag), - self = this; - - dom.style.position = 'absolute'; - dom.setAttribute('class', 'sigma-' + id); - - this.domElements[id] = dom; - this.container.appendChild(dom); - - if (tag.toLowerCase() === 'canvas') { - this.contexts[id] = dom.getContext(webgl ? 'experimental-webgl' : '2d', { - preserveDrawingBuffer: true - }); - - // Adding webgl context loss listeners - if (webgl) { - dom.addEventListener('webglcontextlost', function(e) { - e.preventDefault(); - }, false); - - dom.addEventListener('webglcontextrestored', function(e) { - self.render(); - }, false); - } - } - }; - - /** - * This method resizes each DOM elements in the container and stores the new - * dimensions. Then, it renders the graph. - * - * @param {?number} width The new width of the container. - * @param {?number} height The new height of the container. - * @return {sigma.renderers.webgl} Returns the instance itself. - */ - sigma.renderers.webgl.prototype.resize = function(w, h) { - var k, - oldWidth = this.width, - oldHeight = this.height, - pixelRatio = sigma.utils.getPixelRatio(); - - if (w !== undefined && h !== undefined) { - this.width = w; - this.height = h; - } else { - this.width = this.container.offsetWidth; - this.height = this.container.offsetHeight; - - w = this.width; - h = this.height; - } - - if (oldWidth !== this.width || oldHeight !== this.height) { - for (k in this.domElements) { - this.domElements[k].style.width = w + 'px'; - this.domElements[k].style.height = h + 'px'; - - if (this.domElements[k].tagName.toLowerCase() === 'canvas') { - // If simple 2D canvas: - if (this.contexts[k] && this.contexts[k].scale) { - this.domElements[k].setAttribute('width', (w * pixelRatio) + 'px'); - this.domElements[k].setAttribute('height', (h * pixelRatio) + 'px'); - - if (pixelRatio !== 1) - this.contexts[k].scale(pixelRatio, pixelRatio); - } else { - this.domElements[k].setAttribute( - 'width', - (w * this.settings('webglOversamplingRatio')) + 'px' - ); - this.domElements[k].setAttribute( - 'height', - (h * this.settings('webglOversamplingRatio')) + 'px' - ); - } - } - } - } - - // Scale: - for (k in this.contexts) - if (this.contexts[k] && this.contexts[k].viewport) - this.contexts[k].viewport( - 0, - 0, - this.width * this.settings('webglOversamplingRatio'), - this.height * this.settings('webglOversamplingRatio') - ); - - return this; - }; - - /** - * This method clears each canvas. - * - * @return {sigma.renderers.webgl} Returns the instance itself. - */ - sigma.renderers.webgl.prototype.clear = function() { - this.contexts.labels.clearRect(0, 0, this.width, this.height); - this.contexts.nodes.clear(this.contexts.nodes.COLOR_BUFFER_BIT); - this.contexts.edges.clear(this.contexts.edges.COLOR_BUFFER_BIT); - - return this; - }; - - /** - * This method kills contexts and other attributes. - */ - sigma.renderers.webgl.prototype.kill = function() { - var k, - captor; - - // Kill captors: - while ((captor = this.captors.pop())) - captor.kill(); - delete this.captors; - - // Kill contexts: - for (k in this.domElements) { - this.domElements[k].parentNode.removeChild(this.domElements[k]); - delete this.domElements[k]; - delete this.contexts[k]; - } - delete this.domElements; - delete this.contexts; - }; - - - - - /** - * The object "sigma.webgl.nodes" contains the different WebGL node - * renderers. The default one draw nodes as discs. Here are the attributes - * any node renderer must have: - * - * {number} POINTS The number of points required to draw a node. - * {number} ATTRIBUTES The number of attributes needed to draw one point. - * {function} addNode A function that adds a node to the data stack that - * will be given to the buffer. Here is the arguments: - * > {object} node - * > {number} index The node index in the - * nodes array. - * > {Float32Array} data The stack. - * > {object} options Some options. - * {function} render The function that will effectively render the nodes - * into the buffer. - * > {WebGLRenderingContext} gl - * > {WebGLProgram} program - * > {Float32Array} data The stack to give to the - * buffer. - * > {object} params An object containing some - * options, like width, - * height, the camera ratio. - * {function} initProgram The function that will initiate the program, with - * the relevant shaders and parameters. It must return - * the newly created program. - * - * Check sigma.webgl.nodes.def or sigma.webgl.nodes.fast to see how it - * works more precisely. - */ - sigma.utils.pkg('sigma.webgl.nodes'); - - - - - /** - * The object "sigma.webgl.edges" contains the different WebGL edge - * renderers. The default one draw edges as direct lines. Here are the - * attributes any edge renderer must have: - * - * {number} POINTS The number of points required to draw an edge. - * {number} ATTRIBUTES The number of attributes needed to draw one point. - * {function} addEdge A function that adds an edge to the data stack that - * will be given to the buffer. Here is the arguments: - * > {object} edge - * > {object} source - * > {object} target - * > {Float32Array} data The stack. - * > {object} options Some options. - * {function} render The function that will effectively render the edges - * into the buffer. - * > {WebGLRenderingContext} gl - * > {WebGLProgram} program - * > {Float32Array} data The stack to give to the - * buffer. - * > {object} params An object containing some - * options, like width, - * height, the camera ratio. - * {function} initProgram The function that will initiate the program, with - * the relevant shaders and parameters. It must return - * the newly created program. - * - * Check sigma.webgl.edges.def or sigma.webgl.edges.fast to see how it - * works more precisely. - */ - sigma.utils.pkg('sigma.webgl.edges'); - - - - - /** - * The object "sigma.canvas.labels" contains the different - * label renderers for the WebGL renderer. Since displaying texts in WebGL is - * definitely painful and since there a way less labels to display than nodes - * or edges, the default renderer simply renders them in a canvas. - * - * A labels renderer is a simple function, taking as arguments the related - * node, the renderer and a settings function. - */ - sigma.utils.pkg('sigma.canvas.labels'); -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - if (typeof conrad === 'undefined') - throw 'conrad is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.renderers'); - - /** - * This function is the constructor of the svg sigma's renderer. - * - * @param {sigma.classes.graph} graph The graph to render. - * @param {sigma.classes.camera} camera The camera. - * @param {configurable} settings The sigma instance settings - * function. - * @param {object} object The options object. - * @return {sigma.renderers.svg} The renderer instance. - */ - sigma.renderers.svg = function(graph, camera, settings, options) { - if (typeof options !== 'object') - throw 'sigma.renderers.svg: Wrong arguments.'; - - if (!(options.container instanceof HTMLElement)) - throw 'Container not found.'; - - var i, - l, - a, - fn, - self = this; - - sigma.classes.dispatcher.extend(this); - - // Initialize main attributes: - this.graph = graph; - this.camera = camera; - this.domElements = { - graph: null, - groups: {}, - nodes: {}, - edges: {}, - labels: {}, - hovers: {} - }; - this.measurementCanvas = null; - this.options = options; - this.container = this.options.container; - this.settings = ( - typeof options.settings === 'object' && - options.settings - ) ? - settings.embedObjects(options.settings) : - settings; - - // Is the renderer meant to be freestyle? - this.settings('freeStyle', !!this.options.freeStyle); - - // SVG xmlns - this.settings('xmlns', 'http://www.w3.org/2000/svg'); - - // Indexes: - this.nodesOnScreen = []; - this.edgesOnScreen = []; - - // Find the prefix: - this.options.prefix = 'renderer' + sigma.utils.id() + ':'; - - // Initialize the DOM elements - this.initDOM('svg'); - - // Initialize captors: - this.captors = []; - a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch]; - for (i = 0, l = a.length; i < l; i++) { - fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]]; - this.captors.push( - new fn( - this.domElements.graph, - this.camera, - this.settings - ) - ); - } - - // Bind resize: - window.addEventListener('resize', function() { - self.resize(); - }); - - // Deal with sigma events: - // TODO: keep an option to override the DOM events? - sigma.misc.bindDOMEvents.call(this, this.domElements.graph); - this.bindHovers(this.options.prefix); - - // Resize - this.resize(false); - }; - - /** - * This method renders the graph on the svg scene. - * - * @param {?object} options Eventually an object of options. - * @return {sigma.renderers.svg} Returns the instance itself. - */ - sigma.renderers.svg.prototype.render = function(options) { - options = options || {}; - - var a, - i, - k, - e, - l, - o, - source, - target, - start, - edges, - renderers, - subrenderers, - index = {}, - graph = this.graph, - nodes = this.graph.nodes, - prefix = this.options.prefix || '', - drawEdges = this.settings(options, 'drawEdges'), - drawNodes = this.settings(options, 'drawNodes'), - drawLabels = this.settings(options, 'drawLabels'), - embedSettings = this.settings.embedObjects(options, { - prefix: this.options.prefix, - forceLabels: this.options.forceLabels - }); - - // Check the 'hideEdgesOnMove' setting: - if (this.settings(options, 'hideEdgesOnMove')) - if (this.camera.isAnimated || this.camera.isMoving) - drawEdges = false; - - // Apply the camera's view: - this.camera.applyView( - undefined, - this.options.prefix, - { - width: this.width, - height: this.height - } - ); - - // Hiding everything - // TODO: find a more sensible way to perform this operation - this.hideDOMElements(this.domElements.nodes); - this.hideDOMElements(this.domElements.edges); - this.hideDOMElements(this.domElements.labels); - - // Find which nodes are on screen - this.edgesOnScreen = []; - this.nodesOnScreen = this.camera.quadtree.area( - this.camera.getRectangle(this.width, this.height) - ); - - // Node index - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) - index[a[i].id] = a[i]; - - // Find which edges are on screen - for (a = graph.edges(), i = 0, l = a.length; i < l; i++) { - o = a[i]; - if ( - (index[o.source] || index[o.target]) && - (!o.hidden && !nodes(o.source).hidden && !nodes(o.target).hidden) - ) - this.edgesOnScreen.push(o); - } - - // Display nodes - //--------------- - renderers = sigma.svg.nodes; - subrenderers = sigma.svg.labels; - - //-- First we create the nodes which are not already created - if (drawNodes) - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) { - if (!a[i].hidden && !this.domElements.nodes[a[i].id]) { - - // Node - e = (renderers[a[i].type] || renderers.def).create( - a[i], - embedSettings - ); - - this.domElements.nodes[a[i].id] = e; - this.domElements.groups.nodes.appendChild(e); - - // Label - e = (subrenderers[a[i].type] || subrenderers.def).create( - a[i], - embedSettings - ); - - this.domElements.labels[a[i].id] = e; - this.domElements.groups.labels.appendChild(e); - } - } - - //-- Second we update the nodes - if (drawNodes) - for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++) { - - if (a[i].hidden) - continue; - - // Node - (renderers[a[i].type] || renderers.def).update( - a[i], - this.domElements.nodes[a[i].id], - embedSettings - ); - - // Label - (subrenderers[a[i].type] || subrenderers.def).update( - a[i], - this.domElements.labels[a[i].id], - embedSettings - ); - } - - // Display edges - //--------------- - renderers = sigma.svg.edges; - - //-- First we create the edges which are not already created - if (drawEdges) - for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) { - if (!this.domElements.edges[a[i].id]) { - source = nodes(a[i].source); - target = nodes(a[i].target); - - e = (renderers[a[i].type] || renderers.def).create( - a[i], - source, - target, - embedSettings - ); - - this.domElements.edges[a[i].id] = e; - this.domElements.groups.edges.appendChild(e); - } - } - - //-- Second we update the edges - if (drawEdges) - for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) { - source = nodes(a[i].source); - target = nodes(a[i].target); - - (renderers[a[i].type] || renderers.def).update( - a[i], - this.domElements.edges[a[i].id], - source, - target, - embedSettings - ); - } - - this.dispatchEvent('render'); - - return this; - }; - - /** - * This method creates a DOM element of the specified type, switches its - * position to "absolute", references it to the domElements attribute, and - * finally appends it to the container. - * - * @param {string} tag The label tag. - * @param {string} id The id of the element (to store it in "domElements"). - */ - sigma.renderers.svg.prototype.initDOM = function(tag) { - var dom = document.createElementNS(this.settings('xmlns'), tag), - c = this.settings('classPrefix'), - g, - l, - i; - - dom.style.position = 'absolute'; - dom.setAttribute('class', c + '-svg'); - - // Setting SVG namespace - dom.setAttribute('xmlns', this.settings('xmlns')); - dom.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); - dom.setAttribute('version', '1.1'); - - // Creating the measurement canvas - var canvas = document.createElement('canvas'); - canvas.setAttribute('class', c + '-measurement-canvas'); - - // Appending elements - this.domElements.graph = this.container.appendChild(dom); - - // Creating groups - var groups = ['edges', 'nodes', 'labels', 'hovers']; - for (i = 0, l = groups.length; i < l; i++) { - g = document.createElementNS(this.settings('xmlns'), 'g'); - - g.setAttributeNS(null, 'id', c + '-group-' + groups[i]); - g.setAttributeNS(null, 'class', c + '-group'); - - this.domElements.groups[groups[i]] = - this.domElements.graph.appendChild(g); - } - - // Appending measurement canvas - this.container.appendChild(canvas); - this.measurementCanvas = canvas.getContext('2d'); - }; - - /** - * This method hides a batch of SVG DOM elements. - * - * @param {array} elements An array of elements to hide. - * @param {object} renderer The renderer to use. - * @return {sigma.renderers.svg} Returns the instance itself. - */ - sigma.renderers.svg.prototype.hideDOMElements = function(elements) { - var o, - i; - - for (i in elements) { - o = elements[i]; - sigma.svg.utils.hide(o); - } - - return this; - }; - - /** - * This method binds the hover events to the renderer. - * - * @param {string} prefix The renderer prefix. - */ - // TODO: add option about whether to display hovers or not - sigma.renderers.svg.prototype.bindHovers = function(prefix) { - var renderers = sigma.svg.hovers, - self = this, - hoveredNode; - - function overNode(e) { - var node = e.data.node, - embedSettings = self.settings.embedObjects({ - prefix: prefix - }); - - if (!embedSettings('enableHovering')) - return; - - var hover = (renderers[node.type] || renderers.def).create( - node, - self.domElements.nodes[node.id], - self.measurementCanvas, - embedSettings - ); - - self.domElements.hovers[node.id] = hover; - - // Inserting the hover in the dom - self.domElements.groups.hovers.appendChild(hover); - hoveredNode = node; - } - - function outNode(e) { - var node = e.data.node, - embedSettings = self.settings.embedObjects({ - prefix: prefix - }); - - if (!embedSettings('enableHovering')) - return; - - // Deleting element - self.domElements.groups.hovers.removeChild( - self.domElements.hovers[node.id] - ); - hoveredNode = null; - delete self.domElements.hovers[node.id]; - - // Reinstate - self.domElements.groups.nodes.appendChild( - self.domElements.nodes[node.id] - ); - } - - // OPTIMIZE: perform a real update rather than a deletion - function update() { - if (!hoveredNode) - return; - - var embedSettings = self.settings.embedObjects({ - prefix: prefix - }); - - // Deleting element before update - self.domElements.groups.hovers.removeChild( - self.domElements.hovers[hoveredNode.id] - ); - delete self.domElements.hovers[hoveredNode.id]; - - var hover = (renderers[hoveredNode.type] || renderers.def).create( - hoveredNode, - self.domElements.nodes[hoveredNode.id], - self.measurementCanvas, - embedSettings - ); - - self.domElements.hovers[hoveredNode.id] = hover; - - // Inserting the hover in the dom - self.domElements.groups.hovers.appendChild(hover); - } - - // Binding events - this.bind('overNode', overNode); - this.bind('outNode', outNode); - - // Update on render - this.bind('render', update); - }; - - /** - * This method resizes each DOM elements in the container and stores the new - * dimensions. Then, it renders the graph. - * - * @param {?number} width The new width of the container. - * @param {?number} height The new height of the container. - * @return {sigma.renderers.svg} Returns the instance itself. - */ - sigma.renderers.svg.prototype.resize = function(w, h) { - var oldWidth = this.width, - oldHeight = this.height, - pixelRatio = 1; - - if (w !== undefined && h !== undefined) { - this.width = w; - this.height = h; - } else { - this.width = this.container.offsetWidth; - this.height = this.container.offsetHeight; - - w = this.width; - h = this.height; - } - - if (oldWidth !== this.width || oldHeight !== this.height) { - this.domElements.graph.style.width = w + 'px'; - this.domElements.graph.style.height = h + 'px'; - - if (this.domElements.graph.tagName.toLowerCase() === 'svg') { - this.domElements.graph.setAttribute('width', (w * pixelRatio)); - this.domElements.graph.setAttribute('height', (h * pixelRatio)); - } - } - - return this; - }; - - - /** - * The labels, nodes and edges renderers are stored in the three following - * objects. When an element is drawn, its type will be checked and if a - * renderer with the same name exists, it will be used. If not found, the - * default renderer will be used instead. - * - * They are stored in different files, in the "./svg" folder. - */ - sigma.utils.pkg('sigma.svg.nodes'); - sigma.utils.pkg('sigma.svg.edges'); - sigma.utils.pkg('sigma.svg.labels'); -}).call(this); - -;(function(global) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.renderers'); - - // Check if WebGL is enabled: - var canvas, - webgl = !!global.WebGLRenderingContext; - if (webgl) { - canvas = document.createElement('canvas'); - try { - webgl = !!( - canvas.getContext('webgl') || - canvas.getContext('experimental-webgl') - ); - } catch (e) { - webgl = false; - } - } - - // Copy the good renderer: - sigma.renderers.def = webgl ? - sigma.renderers.webgl : - sigma.renderers.canvas; -})(this); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.nodes'); - - /** - * This node renderer will display nodes as discs, shaped in triangles with - * the gl.TRIANGLES display mode. So, to be more precise, to draw one node, - * it will store three times the center of node, with the color and the size, - * and an angle indicating which "corner" of the triangle to draw. - * - * The fragment shader does not deal with anti-aliasing, so make sure that - * you deal with it somewhere else in the code (by default, the WebGL - * renderer will oversample the rendering through the webglOversamplingRatio - * value). - */ - sigma.webgl.nodes.def = { - POINTS: 3, - ATTRIBUTES: 5, - addNode: function(node, data, i, prefix, settings) { - var color = sigma.utils.floatColor( - node.color || settings('defaultNodeColor') - ); - - data[i++] = node[prefix + 'x']; - data[i++] = node[prefix + 'y']; - data[i++] = node[prefix + 'size']; - data[i++] = color; - data[i++] = 0; - - data[i++] = node[prefix + 'x']; - data[i++] = node[prefix + 'y']; - data[i++] = node[prefix + 'size']; - data[i++] = color; - data[i++] = 2 * Math.PI / 3; - - data[i++] = node[prefix + 'x']; - data[i++] = node[prefix + 'y']; - data[i++] = node[prefix + 'size']; - data[i++] = color; - data[i++] = 4 * Math.PI / 3; - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var positionLocation = - gl.getAttribLocation(program, 'a_position'), - sizeLocation = - gl.getAttribLocation(program, 'a_size'), - colorLocation = - gl.getAttribLocation(program, 'a_color'), - angleLocation = - gl.getAttribLocation(program, 'a_angle'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'), - ratioLocation = - gl.getUniformLocation(program, 'u_ratio'), - scaleLocation = - gl.getUniformLocation(program, 'u_scale'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniform1f( - ratioLocation, - 1 / Math.pow(params.ratio, params.settings('nodesPowRatio')) - ); - gl.uniform1f(scaleLocation, params.scalingRatio); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - - gl.enableVertexAttribArray(positionLocation); - gl.enableVertexAttribArray(sizeLocation); - gl.enableVertexAttribArray(colorLocation); - gl.enableVertexAttribArray(angleLocation); - - gl.vertexAttribPointer( - positionLocation, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer( - sizeLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - gl.vertexAttribPointer( - colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 12 - ); - gl.vertexAttribPointer( - angleLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 16 - ); - - gl.drawArrays( - gl.TRIANGLES, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_position;', - 'attribute float a_size;', - 'attribute float a_color;', - 'attribute float a_angle;', - - 'uniform vec2 u_resolution;', - 'uniform float u_ratio;', - 'uniform float u_scale;', - 'uniform mat3 u_matrix;', - - 'varying vec4 color;', - 'varying vec2 center;', - 'varying float radius;', - - 'void main() {', - // Multiply the point size twice: - 'radius = a_size * u_ratio;', - - // Scale from [[-1 1] [-1 1]] to the container: - 'vec2 position = (u_matrix * vec3(a_position, 1)).xy;', - // 'center = (position / u_resolution * 2.0 - 1.0) * vec2(1, -1);', - 'center = position * u_scale;', - 'center = vec2(center.x, u_scale * u_resolution.y - center.y);', - - 'position = position +', - '2.0 * radius * vec2(cos(a_angle), sin(a_angle));', - 'position = (position / u_resolution * 2.0 - 1.0) * vec2(1, -1);', - - 'radius = radius * u_scale;', - - 'gl_Position = vec4(position, 0, 1);', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - 'varying vec2 center;', - 'varying float radius;', - - 'void main(void) {', - 'vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);', - - 'vec2 m = gl_FragCoord.xy - center;', - 'float diff = radius - sqrt(m.x * m.x + m.y * m.y);', - - // Here is how we draw a disc instead of a square: - 'if (diff > 0.0)', - 'gl_FragColor = color;', - 'else', - 'gl_FragColor = color0;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.nodes'); - - /** - * This node renderer will display nodes in the fastest way: Nodes are basic - * squares, drawn through the gl.POINTS drawing method. The size of the nodes - * are represented with the "gl_PointSize" value in the vertex shader. - * - * It is the fastest node renderer here since the buffer just takes one line - * to draw each node (with attributes "x", "y", "size" and "color"). - * - * Nevertheless, this method has some problems, especially due to some issues - * with the gl.POINTS: - * - First, if the center of a node is outside the scene, the point will not - * be drawn, even if it should be partly on screen. - * - I tried applying a fragment shader similar to the one in the default - * node renderer to display them as discs, but it did not work fine on - * some computers settings, filling the discs with weird gradients not - * depending on the actual color. - */ - sigma.webgl.nodes.fast = { - POINTS: 1, - ATTRIBUTES: 4, - addNode: function(node, data, i, prefix, settings) { - data[i++] = node[prefix + 'x']; - data[i++] = node[prefix + 'y']; - data[i++] = node[prefix + 'size']; - data[i++] = sigma.utils.floatColor( - node.color || settings('defaultNodeColor') - ); - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var positionLocation = - gl.getAttribLocation(program, 'a_position'), - sizeLocation = - gl.getAttribLocation(program, 'a_size'), - colorLocation = - gl.getAttribLocation(program, 'a_color'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'), - ratioLocation = - gl.getUniformLocation(program, 'u_ratio'), - scaleLocation = - gl.getUniformLocation(program, 'u_scale'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniform1f( - ratioLocation, - 1 / Math.pow(params.ratio, params.settings('nodesPowRatio')) - ); - gl.uniform1f(scaleLocation, params.scalingRatio); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - - gl.enableVertexAttribArray(positionLocation); - gl.enableVertexAttribArray(sizeLocation); - gl.enableVertexAttribArray(colorLocation); - - gl.vertexAttribPointer( - positionLocation, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer( - sizeLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - gl.vertexAttribPointer( - colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 12 - ); - - gl.drawArrays( - gl.POINTS, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_position;', - 'attribute float a_size;', - 'attribute float a_color;', - - 'uniform vec2 u_resolution;', - 'uniform float u_ratio;', - 'uniform float u_scale;', - 'uniform mat3 u_matrix;', - - 'varying vec4 color;', - - 'void main() {', - // Scale from [[-1 1] [-1 1]] to the container: - 'gl_Position = vec4(', - '((u_matrix * vec3(a_position, 1)).xy /', - 'u_resolution * 2.0 - 1.0) * vec2(1, -1),', - '0,', - '1', - ');', - - // Multiply the point size twice: - // - x SCALING_RATIO to correct the canvas scaling - // - x 2 to correct the formulae - 'gl_PointSize = a_size * u_ratio * u_scale * 2.0;', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - - 'void main(void) {', - 'float border = 0.01;', - 'float radius = 0.5;', - - 'vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);', - 'vec2 m = gl_PointCoord - vec2(0.5, 0.5);', - 'float dist = radius - sqrt(m.x * m.x + m.y * m.y);', - - 'float t = 0.0;', - 'if (dist > border)', - 't = 1.0;', - 'else if (dist > 0.0)', - 't = dist / border;', - - 'gl_FragColor = mix(color0, color, t);', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.edges'); - - /** - * This edge renderer will display edges as lines going from the source node - * to the target node. To deal with edge thicknesses, the lines are made of - * two triangles forming rectangles, with the gl.TRIANGLES drawing mode. - * - * It is expensive, since drawing a single edge requires 6 points, each - * having 7 attributes (source position, target position, thickness, color - * and a flag indicating which vertice of the rectangle it is). - */ - sigma.webgl.edges.def = { - POINTS: 6, - ATTRIBUTES: 7, - addEdge: function(edge, source, target, data, i, prefix, settings) { - var w = (edge[prefix + 'size'] || 1) / 2, - x1 = source[prefix + 'x'], - y1 = source[prefix + 'y'], - x2 = target[prefix + 'x'], - y2 = target[prefix + 'y'], - color = edge.color; - - if (!color) - switch (settings('edgeColor')) { - case 'source': - color = source.color || settings('defaultNodeColor'); - break; - case 'target': - color = target.color || settings('defaultNodeColor'); - break; - default: - color = settings('defaultEdgeColor'); - break; - } - - // Normalize color: - color = sigma.utils.floatColor(color); - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = 1.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = 1.0; - data[i++] = color; - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = 0.0; - data[i++] = color; - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var colorLocation = - gl.getAttribLocation(program, 'a_color'), - positionLocation1 = - gl.getAttribLocation(program, 'a_position1'), - positionLocation2 = - gl.getAttribLocation(program, 'a_position2'), - thicknessLocation = - gl.getAttribLocation(program, 'a_thickness'), - minusLocation = - gl.getAttribLocation(program, 'a_minus'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'), - matrixHalfPiLocation = - gl.getUniformLocation(program, 'u_matrixHalfPi'), - matrixHalfPiMinusLocation = - gl.getUniformLocation(program, 'u_matrixHalfPiMinus'), - ratioLocation = - gl.getUniformLocation(program, 'u_ratio'), - scaleLocation = - gl.getUniformLocation(program, 'u_scale'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniform1f( - ratioLocation, - params.ratio / Math.pow(params.ratio, params.settings('edgesPowRatio')) - ); - gl.uniform1f(scaleLocation, params.scalingRatio); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - gl.uniformMatrix2fv( - matrixHalfPiLocation, - false, - sigma.utils.matrices.rotation(Math.PI / 2, true) - ); - gl.uniformMatrix2fv( - matrixHalfPiMinusLocation, - false, - sigma.utils.matrices.rotation(-Math.PI / 2, true) - ); - - gl.enableVertexAttribArray(colorLocation); - gl.enableVertexAttribArray(positionLocation1); - gl.enableVertexAttribArray(positionLocation2); - gl.enableVertexAttribArray(thicknessLocation); - gl.enableVertexAttribArray(minusLocation); - - gl.vertexAttribPointer(positionLocation1, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer(positionLocation2, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - gl.vertexAttribPointer(thicknessLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 16 - ); - gl.vertexAttribPointer(minusLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 20 - ); - gl.vertexAttribPointer(colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 24 - ); - - gl.drawArrays( - gl.TRIANGLES, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_position1;', - 'attribute vec2 a_position2;', - 'attribute float a_thickness;', - 'attribute float a_minus;', - 'attribute float a_color;', - - 'uniform vec2 u_resolution;', - 'uniform float u_ratio;', - 'uniform float u_scale;', - 'uniform mat3 u_matrix;', - 'uniform mat2 u_matrixHalfPi;', - 'uniform mat2 u_matrixHalfPiMinus;', - - 'varying vec4 color;', - - 'void main() {', - // Find the good point: - 'vec2 position = a_thickness * u_ratio *', - 'normalize(a_position2 - a_position1);', - - 'mat2 matrix = a_minus * u_matrixHalfPiMinus +', - '(1.0 - a_minus) * u_matrixHalfPi;', - - 'position = matrix * position + a_position1;', - - // Scale from [[-1 1] [-1 1]] to the container: - 'gl_Position = vec4(', - '((u_matrix * vec3(position, 1)).xy /', - 'u_resolution * 2.0 - 1.0) * vec2(1, -1),', - '0,', - '1', - ');', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - - 'void main(void) {', - 'gl_FragColor = color;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.edges'); - - /** - * This edge renderer will display edges as lines with the gl.LINES display - * mode. Since this mode does not support well thickness, edges are all drawn - * with the same thickness (3px), independantly of the edge attributes or the - * zooming ratio. - */ - sigma.webgl.edges.fast = { - POINTS: 2, - ATTRIBUTES: 3, - addEdge: function(edge, source, target, data, i, prefix, settings) { - var w = (edge[prefix + 'size'] || 1) / 2, - x1 = source[prefix + 'x'], - y1 = source[prefix + 'y'], - x2 = target[prefix + 'x'], - y2 = target[prefix + 'y'], - color = edge.color; - - if (!color) - switch (settings('edgeColor')) { - case 'source': - color = source.color || settings('defaultNodeColor'); - break; - case 'target': - color = target.color || settings('defaultNodeColor'); - break; - default: - color = settings('defaultEdgeColor'); - break; - } - - // Normalize color: - color = sigma.utils.floatColor(color); - - data[i++] = x1; - data[i++] = y1; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = color; - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var colorLocation = - gl.getAttribLocation(program, 'a_color'), - positionLocation = - gl.getAttribLocation(program, 'a_position'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - - gl.enableVertexAttribArray(positionLocation); - gl.enableVertexAttribArray(colorLocation); - - gl.vertexAttribPointer(positionLocation, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer(colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - - gl.lineWidth(3); - gl.drawArrays( - gl.LINES, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_position;', - 'attribute float a_color;', - - 'uniform vec2 u_resolution;', - 'uniform mat3 u_matrix;', - - 'varying vec4 color;', - - 'void main() {', - // Scale from [[-1 1] [-1 1]] to the container: - 'gl_Position = vec4(', - '((u_matrix * vec3(a_position, 1)).xy /', - 'u_resolution * 2.0 - 1.0) * vec2(1, -1),', - '0,', - '1', - ');', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - - 'void main(void) {', - 'gl_FragColor = color;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.webgl.edges'); - - /** - * This edge renderer will display edges as arrows going from the source node - * to the target node. To deal with edge thicknesses, the lines are made of - * three triangles: two forming rectangles, with the gl.TRIANGLES drawing - * mode. - * - * It is expensive, since drawing a single edge requires 9 points, each - * having a lot of attributes. - */ - sigma.webgl.edges.arrow = { - POINTS: 9, - ATTRIBUTES: 11, - addEdge: function(edge, source, target, data, i, prefix, settings) { - var w = (edge[prefix + 'size'] || 1) / 2, - x1 = source[prefix + 'x'], - y1 = source[prefix + 'y'], - x2 = target[prefix + 'x'], - y2 = target[prefix + 'y'], - targetSize = target[prefix + 'size'], - color = edge.color; - - if (!color) - switch (settings('edgeColor')) { - case 'source': - color = source.color || settings('defaultNodeColor'); - break; - case 'target': - color = target.color || settings('defaultNodeColor'); - break; - default: - color = settings('defaultEdgeColor'); - break; - } - - // Normalize color: - color = sigma.utils.floatColor(color); - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 0.0; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x1; - data[i++] = y1; - data[i++] = x2; - data[i++] = y2; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = 0.0; - data[i++] = color; - - // Arrow head: - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 1.0; - data[i++] = -1.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = color; - - data[i++] = x2; - data[i++] = y2; - data[i++] = x1; - data[i++] = y1; - data[i++] = w; - data[i++] = targetSize; - data[i++] = 1.0; - data[i++] = 0.0; - data[i++] = 1.0; - data[i++] = 1.0; - data[i++] = color; - }, - render: function(gl, program, data, params) { - var buffer; - - // Define attributes: - var positionLocation1 = - gl.getAttribLocation(program, 'a_pos1'), - positionLocation2 = - gl.getAttribLocation(program, 'a_pos2'), - thicknessLocation = - gl.getAttribLocation(program, 'a_thickness'), - targetSizeLocation = - gl.getAttribLocation(program, 'a_tSize'), - delayLocation = - gl.getAttribLocation(program, 'a_delay'), - minusLocation = - gl.getAttribLocation(program, 'a_minus'), - headLocation = - gl.getAttribLocation(program, 'a_head'), - headPositionLocation = - gl.getAttribLocation(program, 'a_headPosition'), - colorLocation = - gl.getAttribLocation(program, 'a_color'), - resolutionLocation = - gl.getUniformLocation(program, 'u_resolution'), - matrixLocation = - gl.getUniformLocation(program, 'u_matrix'), - matrixHalfPiLocation = - gl.getUniformLocation(program, 'u_matrixHalfPi'), - matrixHalfPiMinusLocation = - gl.getUniformLocation(program, 'u_matrixHalfPiMinus'), - ratioLocation = - gl.getUniformLocation(program, 'u_ratio'), - nodeRatioLocation = - gl.getUniformLocation(program, 'u_nodeRatio'), - arrowHeadLocation = - gl.getUniformLocation(program, 'u_arrowHead'), - scaleLocation = - gl.getUniformLocation(program, 'u_scale'); - - buffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); - - gl.uniform2f(resolutionLocation, params.width, params.height); - gl.uniform1f( - ratioLocation, - params.ratio / Math.pow(params.ratio, params.settings('edgesPowRatio')) - ); - gl.uniform1f( - nodeRatioLocation, - Math.pow(params.ratio, params.settings('nodesPowRatio')) / - params.ratio - ); - gl.uniform1f(arrowHeadLocation, 5.0); - gl.uniform1f(scaleLocation, params.scalingRatio); - gl.uniformMatrix3fv(matrixLocation, false, params.matrix); - gl.uniformMatrix2fv( - matrixHalfPiLocation, - false, - sigma.utils.matrices.rotation(Math.PI / 2, true) - ); - gl.uniformMatrix2fv( - matrixHalfPiMinusLocation, - false, - sigma.utils.matrices.rotation(-Math.PI / 2, true) - ); - - gl.enableVertexAttribArray(positionLocation1); - gl.enableVertexAttribArray(positionLocation2); - gl.enableVertexAttribArray(thicknessLocation); - gl.enableVertexAttribArray(targetSizeLocation); - gl.enableVertexAttribArray(delayLocation); - gl.enableVertexAttribArray(minusLocation); - gl.enableVertexAttribArray(headLocation); - gl.enableVertexAttribArray(headPositionLocation); - gl.enableVertexAttribArray(colorLocation); - - gl.vertexAttribPointer(positionLocation1, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 0 - ); - gl.vertexAttribPointer(positionLocation2, - 2, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 8 - ); - gl.vertexAttribPointer(thicknessLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 16 - ); - gl.vertexAttribPointer(targetSizeLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 20 - ); - gl.vertexAttribPointer(delayLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 24 - ); - gl.vertexAttribPointer(minusLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 28 - ); - gl.vertexAttribPointer(headLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 32 - ); - gl.vertexAttribPointer(headPositionLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 36 - ); - gl.vertexAttribPointer(colorLocation, - 1, - gl.FLOAT, - false, - this.ATTRIBUTES * Float32Array.BYTES_PER_ELEMENT, - 40 - ); - - gl.drawArrays( - gl.TRIANGLES, - params.start || 0, - params.count || (data.length / this.ATTRIBUTES) - ); - }, - initProgram: function(gl) { - var vertexShader, - fragmentShader, - program; - - vertexShader = sigma.utils.loadShader( - gl, - [ - 'attribute vec2 a_pos1;', - 'attribute vec2 a_pos2;', - 'attribute float a_thickness;', - 'attribute float a_tSize;', - 'attribute float a_delay;', - 'attribute float a_minus;', - 'attribute float a_head;', - 'attribute float a_headPosition;', - 'attribute float a_color;', - - 'uniform vec2 u_resolution;', - 'uniform float u_ratio;', - 'uniform float u_nodeRatio;', - 'uniform float u_arrowHead;', - 'uniform float u_scale;', - 'uniform mat3 u_matrix;', - 'uniform mat2 u_matrixHalfPi;', - 'uniform mat2 u_matrixHalfPiMinus;', - - 'varying vec4 color;', - - 'void main() {', - // Find the good point: - 'vec2 pos = normalize(a_pos2 - a_pos1);', - - 'mat2 matrix = (1.0 - a_head) *', - '(', - 'a_minus * u_matrixHalfPiMinus +', - '(1.0 - a_minus) * u_matrixHalfPi', - ') + a_head * (', - 'a_headPosition * u_matrixHalfPiMinus * 0.6 +', - '(a_headPosition * a_headPosition - 1.0) * mat2(1.0)', - ');', - - 'pos = a_pos1 + (', - // Deal with body: - '(1.0 - a_head) * a_thickness * u_ratio * matrix * pos +', - // Deal with head: - 'a_head * u_arrowHead * a_thickness * u_ratio * matrix * pos +', - // Deal with delay: - 'a_delay * pos * (', - 'a_tSize / u_nodeRatio +', - 'u_arrowHead * a_thickness * u_ratio', - ')', - ');', - - // Scale from [[-1 1] [-1 1]] to the container: - 'gl_Position = vec4(', - '((u_matrix * vec3(pos, 1)).xy /', - 'u_resolution * 2.0 - 1.0) * vec2(1, -1),', - '0,', - '1', - ');', - - // Extract the color: - 'float c = a_color;', - 'color.b = mod(c, 256.0); c = floor(c / 256.0);', - 'color.g = mod(c, 256.0); c = floor(c / 256.0);', - 'color.r = mod(c, 256.0); c = floor(c / 256.0); color /= 255.0;', - 'color.a = 1.0;', - '}' - ].join('\n'), - gl.VERTEX_SHADER - ); - - fragmentShader = sigma.utils.loadShader( - gl, - [ - 'precision mediump float;', - - 'varying vec4 color;', - - 'void main(void) {', - 'gl_FragColor = color;', - '}' - ].join('\n'), - gl.FRAGMENT_SHADER - ); - - program = sigma.utils.loadProgram(gl, [vertexShader, fragmentShader]); - - return program; - } - }; -})(); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.canvas.labels'); - - /** - * This label renderer will just display the label on the right of the node. - * - * @param {object} node The node object. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.labels.def = function(node, context, settings) { - var fontSize, - prefix = settings('prefix') || '', - size = node[prefix + 'size']; - - if (size < settings('labelThreshold')) - return; - - if (!node.label || typeof node.label !== 'string') - return; - - fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size; - - context.font = (settings('fontStyle') ? settings('fontStyle') + ' ' : '') + - fontSize + 'px ' + settings('font'); - context.fillStyle = (settings('labelColor') === 'node') ? - (node.color || settings('defaultNodeColor')) : - settings('defaultLabelColor'); - - context.fillText( - node.label, - Math.round(node[prefix + 'x'] + size + 3), - Math.round(node[prefix + 'y'] + fontSize / 3) - ); - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.canvas.hovers'); - - /** - * This hover renderer will basically display the label with a background. - * - * @param {object} node The node object. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.hovers.def = function(node, context, settings) { - var x, - y, - w, - h, - e, - fontStyle = settings('hoverFontStyle') || settings('fontStyle'), - prefix = settings('prefix') || '', - size = node[prefix + 'size'], - fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size; - - // Label background: - context.font = (fontStyle ? fontStyle + ' ' : '') + - fontSize + 'px ' + (settings('hoverFont') || settings('font')); - - context.beginPath(); - context.fillStyle = settings('labelHoverBGColor') === 'node' ? - (node.color || settings('defaultNodeColor')) : - settings('defaultHoverLabelBGColor'); - - if (node.label && settings('labelHoverShadow')) { - context.shadowOffsetX = 0; - context.shadowOffsetY = 0; - context.shadowBlur = 8; - context.shadowColor = settings('labelHoverShadowColor'); - } - - if (node.label && typeof node.label === 'string') { - x = Math.round(node[prefix + 'x'] - fontSize / 2 - 2); - y = Math.round(node[prefix + 'y'] - fontSize / 2 - 2); - w = Math.round( - context.measureText(node.label).width + fontSize / 2 + size + 7 - ); - h = Math.round(fontSize + 4); - e = Math.round(fontSize / 2 + 2); - - context.moveTo(x, y + e); - context.arcTo(x, y, x + e, y, e); - context.lineTo(x + w, y); - context.lineTo(x + w, y + h); - context.lineTo(x + e, y + h); - context.arcTo(x, y + h, x, y + h - e, e); - context.lineTo(x, y + e); - - context.closePath(); - context.fill(); - - context.shadowOffsetX = 0; - context.shadowOffsetY = 0; - context.shadowBlur = 0; - } - - // Node border: - if (settings('borderSize') > 0) { - context.beginPath(); - context.fillStyle = settings('nodeBorderColor') === 'node' ? - (node.color || settings('defaultNodeColor')) : - settings('defaultNodeBorderColor'); - context.arc( - node[prefix + 'x'], - node[prefix + 'y'], - size + settings('borderSize'), - 0, - Math.PI * 2, - true - ); - context.closePath(); - context.fill(); - } - - // Node: - var nodeRenderer = sigma.canvas.nodes[node.type] || sigma.canvas.nodes.def; - nodeRenderer(node, context, settings); - - // Display the label: - if (node.label && typeof node.label === 'string') { - context.fillStyle = (settings('labelHoverColor') === 'node') ? - (node.color || settings('defaultNodeColor')) : - settings('defaultLabelHoverColor'); - - context.fillText( - node.label, - Math.round(node[prefix + 'x'] + size + 3), - Math.round(node[prefix + 'y'] + fontSize / 3) - ); - } - }; -}).call(this); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.nodes'); - - /** - * The default node renderer. It renders the node as a simple disc. - * - * @param {object} node The node object. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.nodes.def = function(node, context, settings) { - var prefix = settings('prefix') || ''; - - context.fillStyle = node.color || settings('defaultNodeColor'); - context.beginPath(); - context.arc( - node[prefix + 'x'], - node[prefix + 'y'], - node[prefix + 'size'], - 0, - Math.PI * 2, - true - ); - - context.closePath(); - context.fill(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edges'); - - /** - * The default edge renderer. It renders the edge as a simple line. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edges.def = function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - size = edge[prefix + 'size'] || 1, - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo( - source[prefix + 'x'], - source[prefix + 'y'] - ); - context.lineTo( - target[prefix + 'x'], - target[prefix + 'y'] - ); - context.stroke(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edges'); - - /** - * This edge renderer will display edges as curves. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edges.curve = function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - size = edge[prefix + 'size'] || 1, - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - cp = {}, - sSize = source[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y']; - - cp = (source.id === target.id) ? - sigma.utils.getSelfLoopControlPoints(sX, sY, sSize) : - sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - if (source.id === target.id) { - context.bezierCurveTo(cp.x1, cp.y1, cp.x2, cp.y2, tX, tY); - } else { - context.quadraticCurveTo(cp.x, cp.y, tX, tY); - } - context.stroke(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edges'); - - /** - * This edge renderer will display edges as arrows going from the source node - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edges.arrow = function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - size = edge[prefix + 'size'] || 1, - tSize = target[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y'], - aSize = Math.max(size * 2.5, settings('minArrowSize')), - d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)), - aX = sX + (tX - sX) * (d - aSize - tSize) / d, - aY = sY + (tY - sY) * (d - aSize - tSize) / d, - vX = (tX - sX) * aSize / d, - vY = (tY - sY) * aSize / d; - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - context.lineTo( - aX, - aY - ); - context.stroke(); - - context.fillStyle = color; - context.beginPath(); - context.moveTo(aX + vX, aY + vY); - context.lineTo(aX + vY * 0.6, aY - vX * 0.6); - context.lineTo(aX - vY * 0.6, aY + vX * 0.6); - context.lineTo(aX + vX, aY + vY); - context.closePath(); - context.fill(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edges'); - - /** - * This edge renderer will display edges as curves with arrow heading. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edges.curvedArrow = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - cp = {}, - size = edge[prefix + 'size'] || 1, - tSize = target[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y'], - aSize = Math.max(size * 2.5, settings('minArrowSize')), - d, - aX, - aY, - vX, - vY; - - cp = (source.id === target.id) ? - sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) : - sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY); - - if (source.id === target.id) { - d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2)); - aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d; - aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d; - vX = (tX - cp.x1) * aSize / d; - vY = (tY - cp.y1) * aSize / d; - } - else { - d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2)); - aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d; - aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d; - vX = (tX - cp.x) * aSize / d; - vY = (tY - cp.y) * aSize / d; - } - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - if (source.id === target.id) { - context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY); - } else { - context.quadraticCurveTo(cp.x, cp.y, aX, aY); - } - context.stroke(); - - context.fillStyle = color; - context.beginPath(); - context.moveTo(aX + vX, aY + vY); - context.lineTo(aX + vY * 0.6, aY - vX * 0.6); - context.lineTo(aX - vY * 0.6, aY + vX * 0.6); - context.lineTo(aX + vX, aY + vY); - context.closePath(); - context.fill(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edgehovers'); - - /** - * This hover renderer will display the edge with a different color or size. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edgehovers.def = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - size = edge[prefix + 'size'] || 1, - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - if (settings('edgeHoverColor') === 'edge') { - color = edge.hover_color || color; - } else { - color = edge.hover_color || settings('defaultEdgeHoverColor') || color; - } - size *= settings('edgeHoverSizeRatio'); - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo( - source[prefix + 'x'], - source[prefix + 'y'] - ); - context.lineTo( - target[prefix + 'x'], - target[prefix + 'y'] - ); - context.stroke(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edgehovers'); - - /** - * This hover renderer will display the edge with a different color or size. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edgehovers.curve = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - size = settings('edgeHoverSizeRatio') * (edge[prefix + 'size'] || 1), - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - cp = {}, - sSize = source[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y']; - - cp = (source.id === target.id) ? - sigma.utils.getSelfLoopControlPoints(sX, sY, sSize) : - sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - if (settings('edgeHoverColor') === 'edge') { - color = edge.hover_color || color; - } else { - color = edge.hover_color || settings('defaultEdgeHoverColor') || color; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - if (source.id === target.id) { - context.bezierCurveTo(cp.x1, cp.y1, cp.x2, cp.y2, tX, tY); - } else { - context.quadraticCurveTo(cp.x, cp.y, tX, tY); - } - context.stroke(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edgehovers'); - - /** - * This hover renderer will display the edge with a different color or size. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edgehovers.arrow = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - size = edge[prefix + 'size'] || 1, - tSize = target[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y']; - - size = (edge.hover) ? - settings('edgeHoverSizeRatio') * size : size; - var aSize = size * 2.5, - d = Math.sqrt(Math.pow(tX - sX, 2) + Math.pow(tY - sY, 2)), - aX = sX + (tX - sX) * (d - aSize - tSize) / d, - aY = sY + (tY - sY) * (d - aSize - tSize) / d, - vX = (tX - sX) * aSize / d, - vY = (tY - sY) * aSize / d; - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - if (settings('edgeHoverColor') === 'edge') { - color = edge.hover_color || color; - } else { - color = edge.hover_color || settings('defaultEdgeHoverColor') || color; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - context.lineTo( - aX, - aY - ); - context.stroke(); - - context.fillStyle = color; - context.beginPath(); - context.moveTo(aX + vX, aY + vY); - context.lineTo(aX + vY * 0.6, aY - vX * 0.6); - context.lineTo(aX - vY * 0.6, aY + vX * 0.6); - context.lineTo(aX + vX, aY + vY); - context.closePath(); - context.fill(); - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.canvas.edgehovers'); - - /** - * This hover renderer will display the edge with a different color or size. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.edgehovers.curvedArrow = - function(edge, source, target, context, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'), - cp = {}, - size = settings('edgeHoverSizeRatio') * (edge[prefix + 'size'] || 1), - tSize = target[prefix + 'size'], - sX = source[prefix + 'x'], - sY = source[prefix + 'y'], - tX = target[prefix + 'x'], - tY = target[prefix + 'y'], - d, - aSize, - aX, - aY, - vX, - vY; - - cp = (source.id === target.id) ? - sigma.utils.getSelfLoopControlPoints(sX, sY, tSize) : - sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY); - - if (source.id === target.id) { - d = Math.sqrt(Math.pow(tX - cp.x1, 2) + Math.pow(tY - cp.y1, 2)); - aSize = size * 2.5; - aX = cp.x1 + (tX - cp.x1) * (d - aSize - tSize) / d; - aY = cp.y1 + (tY - cp.y1) * (d - aSize - tSize) / d; - vX = (tX - cp.x1) * aSize / d; - vY = (tY - cp.y1) * aSize / d; - } - else { - d = Math.sqrt(Math.pow(tX - cp.x, 2) + Math.pow(tY - cp.y, 2)); - aSize = size * 2.5; - aX = cp.x + (tX - cp.x) * (d - aSize - tSize) / d; - aY = cp.y + (tY - cp.y) * (d - aSize - tSize) / d; - vX = (tX - cp.x) * aSize / d; - vY = (tY - cp.y) * aSize / d; - } - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - if (settings('edgeHoverColor') === 'edge') { - color = edge.hover_color || color; - } else { - color = edge.hover_color || settings('defaultEdgeHoverColor') || color; - } - - context.strokeStyle = color; - context.lineWidth = size; - context.beginPath(); - context.moveTo(sX, sY); - if (source.id === target.id) { - context.bezierCurveTo(cp.x2, cp.y2, cp.x1, cp.y1, aX, aY); - } else { - context.quadraticCurveTo(cp.x, cp.y, aX, aY); - } - context.stroke(); - - context.fillStyle = color; - context.beginPath(); - context.moveTo(aX + vX, aY + vY); - context.lineTo(aX + vY * 0.6, aY - vX * 0.6); - context.lineTo(aX - vY * 0.6, aY + vX * 0.6); - context.lineTo(aX + vX, aY + vY); - context.closePath(); - context.fill(); - }; -})(); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.canvas.extremities'); - - /** - * The default renderer for hovered edge extremities. It renders the edge - * extremities as hovered. - * - * @param {object} edge The edge object. - * @param {object} source node The edge source node. - * @param {object} target node The edge target node. - * @param {CanvasRenderingContext2D} context The canvas context. - * @param {configurable} settings The settings function. - */ - sigma.canvas.extremities.def = - function(edge, source, target, context, settings) { - // Source Node: - ( - sigma.canvas.hovers[source.type] || - sigma.canvas.hovers.def - ) ( - source, context, settings - ); - - // Target Node: - ( - sigma.canvas.hovers[target.type] || - sigma.canvas.hovers.def - ) ( - target, context, settings - ); - }; -}).call(this); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.svg.utils'); - - /** - * Some useful functions used by sigma's SVG renderer. - */ - sigma.svg.utils = { - - /** - * SVG Element show. - * - * @param {DOMElement} element The DOM element to show. - */ - show: function(element) { - element.style.display = ''; - return this; - }, - - /** - * SVG Element hide. - * - * @param {DOMElement} element The DOM element to hide. - */ - hide: function(element) { - element.style.display = 'none'; - return this; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.svg.nodes'); - - /** - * The default node renderer. It renders the node as a simple disc. - */ - sigma.svg.nodes.def = { - - /** - * SVG Element creation. - * - * @param {object} node The node object. - * @param {configurable} settings The settings function. - */ - create: function(node, settings) { - var prefix = settings('prefix') || '', - circle = document.createElementNS(settings('xmlns'), 'circle'); - - // Defining the node's circle - circle.setAttributeNS(null, 'data-node-id', node.id); - circle.setAttributeNS(null, 'class', settings('classPrefix') + '-node'); - circle.setAttributeNS( - null, 'fill', node.color || settings('defaultNodeColor')); - - // Returning the DOM Element - return circle; - }, - - /** - * SVG Element update. - * - * @param {object} node The node object. - * @param {DOMElement} circle The node DOM element. - * @param {configurable} settings The settings function. - */ - update: function(node, circle, settings) { - var prefix = settings('prefix') || ''; - - // Applying changes - // TODO: optimize - check if necessary - circle.setAttributeNS(null, 'cx', node[prefix + 'x']); - circle.setAttributeNS(null, 'cy', node[prefix + 'y']); - circle.setAttributeNS(null, 'r', node[prefix + 'size']); - - // Updating only if not freestyle - if (!settings('freeStyle')) - circle.setAttributeNS( - null, 'fill', node.color || settings('defaultNodeColor')); - - // Showing - circle.style.display = ''; - - return this; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.svg.edges'); - - /** - * The default edge renderer. It renders the node as a simple line. - */ - sigma.svg.edges.def = { - - /** - * SVG Element creation. - * - * @param {object} edge The edge object. - * @param {object} source The source node object. - * @param {object} target The target node object. - * @param {configurable} settings The settings function. - */ - create: function(edge, source, target, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - var line = document.createElementNS(settings('xmlns'), 'line'); - - // Attributes - line.setAttributeNS(null, 'data-edge-id', edge.id); - line.setAttributeNS(null, 'class', settings('classPrefix') + '-edge'); - line.setAttributeNS(null, 'stroke', color); - - return line; - }, - - /** - * SVG Element update. - * - * @param {object} edge The edge object. - * @param {DOMElement} line The line DOM Element. - * @param {object} source The source node object. - * @param {object} target The target node object. - * @param {configurable} settings The settings function. - */ - update: function(edge, line, source, target, settings) { - var prefix = settings('prefix') || ''; - - line.setAttributeNS(null, 'stroke-width', edge[prefix + 'size'] || 1); - line.setAttributeNS(null, 'x1', source[prefix + 'x']); - line.setAttributeNS(null, 'y1', source[prefix + 'y']); - line.setAttributeNS(null, 'x2', target[prefix + 'x']); - line.setAttributeNS(null, 'y2', target[prefix + 'y']); - - // Showing - line.style.display = ''; - - return this; - } - }; -})(); - -;(function() { - 'use strict'; - - sigma.utils.pkg('sigma.svg.edges'); - - /** - * The curve edge renderer. It renders the node as a bezier curve. - */ - sigma.svg.edges.curve = { - - /** - * SVG Element creation. - * - * @param {object} edge The edge object. - * @param {object} source The source node object. - * @param {object} target The target node object. - * @param {configurable} settings The settings function. - */ - create: function(edge, source, target, settings) { - var color = edge.color, - prefix = settings('prefix') || '', - edgeColor = settings('edgeColor'), - defaultNodeColor = settings('defaultNodeColor'), - defaultEdgeColor = settings('defaultEdgeColor'); - - if (!color) - switch (edgeColor) { - case 'source': - color = source.color || defaultNodeColor; - break; - case 'target': - color = target.color || defaultNodeColor; - break; - default: - color = defaultEdgeColor; - break; - } - - var path = document.createElementNS(settings('xmlns'), 'path'); - - // Attributes - path.setAttributeNS(null, 'data-edge-id', edge.id); - path.setAttributeNS(null, 'class', settings('classPrefix') + '-edge'); - path.setAttributeNS(null, 'stroke', color); - - return path; - }, - - /** - * SVG Element update. - * - * @param {object} edge The edge object. - * @param {DOMElement} line The line DOM Element. - * @param {object} source The source node object. - * @param {object} target The target node object. - * @param {configurable} settings The settings function. - */ - update: function(edge, path, source, target, settings) { - var prefix = settings('prefix') || ''; - - path.setAttributeNS(null, 'stroke-width', edge[prefix + 'size'] || 1); - - // Control point - var cx = (source[prefix + 'x'] + target[prefix + 'x']) / 2 + - (target[prefix + 'y'] - source[prefix + 'y']) / 4, - cy = (source[prefix + 'y'] + target[prefix + 'y']) / 2 + - (source[prefix + 'x'] - target[prefix + 'x']) / 4; - - // Path - var p = 'M' + source[prefix + 'x'] + ',' + source[prefix + 'y'] + ' ' + - 'Q' + cx + ',' + cy + ' ' + - target[prefix + 'x'] + ',' + target[prefix + 'y']; - - // Updating attributes - path.setAttributeNS(null, 'd', p); - path.setAttributeNS(null, 'fill', 'none'); - - // Showing - path.style.display = ''; - - return this; - } - }; -})(); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.svg.labels'); - - /** - * The default label renderer. It renders the label as a simple text. - */ - sigma.svg.labels.def = { - - /** - * SVG Element creation. - * - * @param {object} node The node object. - * @param {configurable} settings The settings function. - */ - create: function(node, settings) { - var prefix = settings('prefix') || '', - size = node[prefix + 'size'], - text = document.createElementNS(settings('xmlns'), 'text'); - - var fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size; - - var fontColor = (settings('labelColor') === 'node') ? - (node.color || settings('defaultNodeColor')) : - settings('defaultLabelColor'); - - text.setAttributeNS(null, 'data-label-target', node.id); - text.setAttributeNS(null, 'class', settings('classPrefix') + '-label'); - text.setAttributeNS(null, 'font-size', fontSize); - text.setAttributeNS(null, 'font-family', settings('font')); - text.setAttributeNS(null, 'fill', fontColor); - - text.innerHTML = node.label; - text.textContent = node.label; - - return text; - }, - - /** - * SVG Element update. - * - * @param {object} node The node object. - * @param {DOMElement} text The label DOM element. - * @param {configurable} settings The settings function. - */ - update: function(node, text, settings) { - var prefix = settings('prefix') || '', - size = node[prefix + 'size']; - - var fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size; - - // Case when we don't want to display the label - if (!settings('forceLabels') && size < settings('labelThreshold')) - return; - - if (typeof node.label !== 'string') - return; - - // Updating - text.setAttributeNS(null, 'x', - Math.round(node[prefix + 'x'] + size + 3)); - text.setAttributeNS(null, 'y', - Math.round(node[prefix + 'y'] + fontSize / 3)); - - // Showing - text.style.display = ''; - - return this; - } - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.svg.hovers'); - - /** - * The default hover renderer. - */ - sigma.svg.hovers.def = { - - /** - * SVG Element creation. - * - * @param {object} node The node object. - * @param {CanvasElement} measurementCanvas A fake canvas handled by - * the svg to perform some measurements and - * passed by the renderer. - * @param {DOMElement} nodeCircle The node DOM Element. - * @param {configurable} settings The settings function. - */ - create: function(node, nodeCircle, measurementCanvas, settings) { - - // Defining visual properties - var x, - y, - w, - h, - e, - d, - fontStyle = settings('hoverFontStyle') || settings('fontStyle'), - prefix = settings('prefix') || '', - size = node[prefix + 'size'], - fontSize = (settings('labelSize') === 'fixed') ? - settings('defaultLabelSize') : - settings('labelSizeRatio') * size, - fontColor = (settings('labelHoverColor') === 'node') ? - (node.color || settings('defaultNodeColor')) : - settings('defaultLabelHoverColor'); - - // Creating elements - var group = document.createElementNS(settings('xmlns'), 'g'), - rectangle = document.createElementNS(settings('xmlns'), 'rect'), - circle = document.createElementNS(settings('xmlns'), 'circle'), - text = document.createElementNS(settings('xmlns'), 'text'); - - // Defining properties - group.setAttributeNS(null, 'class', settings('classPrefix') + '-hover'); - group.setAttributeNS(null, 'data-node-id', node.id); - - if (typeof node.label === 'string') { - - // Text - text.innerHTML = node.label; - text.textContent = node.label; - text.setAttributeNS( - null, - 'class', - settings('classPrefix') + '-hover-label'); - text.setAttributeNS(null, 'font-size', fontSize); - text.setAttributeNS(null, 'font-family', settings('font')); - text.setAttributeNS(null, 'fill', fontColor); - text.setAttributeNS(null, 'x', - Math.round(node[prefix + 'x'] + size + 3)); - text.setAttributeNS(null, 'y', - Math.round(node[prefix + 'y'] + fontSize / 3)); - - // Measures - // OPTIMIZE: Find a better way than a measurement canvas - x = Math.round(node[prefix + 'x'] - fontSize / 2 - 2); - y = Math.round(node[prefix + 'y'] - fontSize / 2 - 2); - w = Math.round( - measurementCanvas.measureText(node.label).width + - fontSize / 2 + size + 9 - ); - h = Math.round(fontSize + 4); - e = Math.round(fontSize / 2 + 2); - - // Circle - circle.setAttributeNS( - null, - 'class', - settings('classPrefix') + '-hover-area'); - circle.setAttributeNS(null, 'fill', '#fff'); - circle.setAttributeNS(null, 'cx', node[prefix + 'x']); - circle.setAttributeNS(null, 'cy', node[prefix + 'y']); - circle.setAttributeNS(null, 'r', e); - - // Rectangle - rectangle.setAttributeNS( - null, - 'class', - settings('classPrefix') + '-hover-area'); - rectangle.setAttributeNS(null, 'fill', '#fff'); - rectangle.setAttributeNS(null, 'x', node[prefix + 'x'] + e / 4); - rectangle.setAttributeNS(null, 'y', node[prefix + 'y'] - e); - rectangle.setAttributeNS(null, 'width', w); - rectangle.setAttributeNS(null, 'height', h); - } - - // Appending childs - group.appendChild(circle); - group.appendChild(rectangle); - group.appendChild(text); - group.appendChild(nodeCircle); - - return group; - } - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.middlewares'); - sigma.utils.pkg('sigma.utils'); - - /** - * This middleware will rescale the graph such that it takes an optimal space - * on the renderer. - * - * As each middleware, this function is executed in the scope of the sigma - * instance. - * - * @param {?string} readPrefix The read prefix. - * @param {?string} writePrefix The write prefix. - * @param {object} options The parameters. - */ - sigma.middlewares.rescale = function(readPrefix, writePrefix, options) { - var i, - l, - a, - b, - c, - d, - scale, - margin, - n = this.graph.nodes(), - e = this.graph.edges(), - settings = this.settings.embedObjects(options || {}), - bounds = settings('bounds') || sigma.utils.getBoundaries( - this.graph, - readPrefix, - true - ), - minX = bounds.minX, - minY = bounds.minY, - maxX = bounds.maxX, - maxY = bounds.maxY, - sizeMax = bounds.sizeMax, - weightMax = bounds.weightMax, - w = settings('width') || 1, - h = settings('height') || 1, - rescaleSettings = settings('autoRescale'), - validSettings = { - nodePosition: 1, - nodeSize: 1, - edgeSize: 1 - }; - - /** - * What elements should we rescale? - */ - if (!(rescaleSettings instanceof Array)) - rescaleSettings = ['nodePosition', 'nodeSize', 'edgeSize']; - - for (i = 0, l = rescaleSettings.length; i < l; i++) - if (!validSettings[rescaleSettings[i]]) - throw new Error( - 'The rescale setting "' + rescaleSettings[i] + '" is not recognized.' - ); - - var np = ~rescaleSettings.indexOf('nodePosition'), - ns = ~rescaleSettings.indexOf('nodeSize'), - es = ~rescaleSettings.indexOf('edgeSize'); - - /** - * First, we compute the scaling ratio, without considering the sizes - * of the nodes : Each node will have its center in the canvas, but might - * be partially out of it. - */ - scale = settings('scalingMode') === 'outside' ? - Math.max( - w / Math.max(maxX - minX, 1), - h / Math.max(maxY - minY, 1) - ) : - Math.min( - w / Math.max(maxX - minX, 1), - h / Math.max(maxY - minY, 1) - ); - - /** - * Then, we correct that scaling ratio considering a margin, which is - * basically the size of the biggest node. - * This has to be done as a correction since to compare the size of the - * biggest node to the X and Y values, we have to first get an - * approximation of the scaling ratio. - **/ - margin = - ( - settings('rescaleIgnoreSize') ? - 0 : - (settings('maxNodeSize') || sizeMax) / scale - ) + - (settings('sideMargin') || 0); - maxX += margin; - minX -= margin; - maxY += margin; - minY -= margin; - - // Fix the scaling with the new extrema: - scale = settings('scalingMode') === 'outside' ? - Math.max( - w / Math.max(maxX - minX, 1), - h / Math.max(maxY - minY, 1) - ) : - Math.min( - w / Math.max(maxX - minX, 1), - h / Math.max(maxY - minY, 1) - ); - - // Size homothetic parameters: - if (!settings('maxNodeSize') && !settings('minNodeSize')) { - a = 1; - b = 0; - } else if (settings('maxNodeSize') === settings('minNodeSize')) { - a = 0; - b = +settings('maxNodeSize'); - } else { - a = (settings('maxNodeSize') - settings('minNodeSize')) / sizeMax; - b = +settings('minNodeSize'); - } - - if (!settings('maxEdgeSize') && !settings('minEdgeSize')) { - c = 1; - d = 0; - } else if (settings('maxEdgeSize') === settings('minEdgeSize')) { - c = 0; - d = +settings('minEdgeSize'); - } else { - c = (settings('maxEdgeSize') - settings('minEdgeSize')) / weightMax; - d = +settings('minEdgeSize'); - } - - // Rescale the nodes and edges: - for (i = 0, l = e.length; i < l; i++) - e[i][writePrefix + 'size'] = - e[i][readPrefix + 'size'] * (es ? c : 1) + (es ? d : 0); - - for (i = 0, l = n.length; i < l; i++) { - n[i][writePrefix + 'size'] = - n[i][readPrefix + 'size'] * (ns ? a : 1) + (ns ? b : 0); - n[i][writePrefix + 'x'] = - (n[i][readPrefix + 'x'] - (maxX + minX) / 2) * (np ? scale : 1); - n[i][writePrefix + 'y'] = - (n[i][readPrefix + 'y'] - (maxY + minY) / 2) * (np ? scale : 1); - } - }; - - sigma.utils.getBoundaries = function(graph, prefix, doEdges) { - var i, - l, - e = graph.edges(), - n = graph.nodes(), - weightMax = -Infinity, - sizeMax = -Infinity, - minX = Infinity, - minY = Infinity, - maxX = -Infinity, - maxY = -Infinity; - - if (doEdges) - for (i = 0, l = e.length; i < l; i++) - weightMax = Math.max(e[i][prefix + 'size'], weightMax); - - for (i = 0, l = n.length; i < l; i++) { - sizeMax = Math.max(n[i][prefix + 'size'], sizeMax); - maxX = Math.max(n[i][prefix + 'x'], maxX); - minX = Math.min(n[i][prefix + 'x'], minX); - maxY = Math.max(n[i][prefix + 'y'], maxY); - minY = Math.min(n[i][prefix + 'y'], minY); - } - - weightMax = weightMax || 1; - sizeMax = sizeMax || 1; - - return { - weightMax: weightMax, - sizeMax: sizeMax, - minX: minX, - minY: minY, - maxX: maxX, - maxY: maxY - }; - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.middlewares'); - - /** - * This middleware will just copy the graphic properties. - * - * @param {?string} readPrefix The read prefix. - * @param {?string} writePrefix The write prefix. - */ - sigma.middlewares.copy = function(readPrefix, writePrefix) { - var i, - l, - a; - - if (writePrefix + '' === readPrefix + '') - return; - - a = this.graph.nodes(); - for (i = 0, l = a.length; i < l; i++) { - a[i][writePrefix + 'x'] = a[i][readPrefix + 'x']; - a[i][writePrefix + 'y'] = a[i][readPrefix + 'y']; - a[i][writePrefix + 'size'] = a[i][readPrefix + 'size']; - } - - a = this.graph.edges(); - for (i = 0, l = a.length; i < l; i++) - a[i][writePrefix + 'size'] = a[i][readPrefix + 'size']; - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.misc.animation.running'); - - /** - * Generates a unique ID for the animation. - * - * @return {string} Returns the new ID. - */ - var _getID = (function() { - var id = 0; - return function() { - return '' + (++id); - }; - })(); - - /** - * This function animates a camera. It has to be called with the camera to - * animate, the values of the coordinates to reach and eventually some - * options. It returns a number id, that you can use to kill the animation, - * with the method sigma.misc.animation.kill(id). - * - * The available options are: - * - * {?number} duration The duration of the animation. - * {?function} onNewFrame A callback to execute when the animation - * enter a new frame. - * {?function} onComplete A callback to execute when the animation - * is completed or killed. - * {?(string|function)} easing The name of a function from the package - * sigma.utils.easings, or a custom easing - * function. - * - * @param {camera} camera The camera to animate. - * @param {object} target The coordinates to reach. - * @param {?object} options Eventually an object to specify some options to - * the function. The available options are - * presented in the description of the function. - * @return {number} The animation id, to make it easy to kill - * through the method "sigma.misc.animation.kill". - */ - sigma.misc.animation.camera = function(camera, val, options) { - if ( - !(camera instanceof sigma.classes.camera) || - typeof val !== 'object' || - !val - ) - throw 'animation.camera: Wrong arguments.'; - - if ( - typeof val.x !== 'number' && - typeof val.y !== 'number' && - typeof val.ratio !== 'number' && - typeof val.angle !== 'number' - ) - throw 'There must be at least one valid coordinate in the given val.'; - - var fn, - id, - anim, - easing, - duration, - initialVal, - o = options || {}, - start = sigma.utils.dateNow(); - - // Store initial values: - initialVal = { - x: camera.x, - y: camera.y, - ratio: camera.ratio, - angle: camera.angle - }; - - duration = o.duration; - easing = typeof o.easing !== 'function' ? - sigma.utils.easings[o.easing || 'quadraticInOut'] : - o.easing; - - fn = function() { - var coef, - t = o.duration ? (sigma.utils.dateNow() - start) / o.duration : 1; - - // If the animation is over: - if (t >= 1) { - camera.isAnimated = false; - camera.goTo({ - x: val.x !== undefined ? val.x : initialVal.x, - y: val.y !== undefined ? val.y : initialVal.y, - ratio: val.ratio !== undefined ? val.ratio : initialVal.ratio, - angle: val.angle !== undefined ? val.angle : initialVal.angle - }); - - cancelAnimationFrame(id); - delete sigma.misc.animation.running[id]; - - // Check callbacks: - if (typeof o.onComplete === 'function') - o.onComplete(); - - // Else, let's keep going: - } else { - coef = easing(t); - camera.isAnimated = true; - camera.goTo({ - x: val.x !== undefined ? - initialVal.x + (val.x - initialVal.x) * coef : - initialVal.x, - y: val.y !== undefined ? - initialVal.y + (val.y - initialVal.y) * coef : - initialVal.y, - ratio: val.ratio !== undefined ? - initialVal.ratio + (val.ratio - initialVal.ratio) * coef : - initialVal.ratio, - angle: val.angle !== undefined ? - initialVal.angle + (val.angle - initialVal.angle) * coef : - initialVal.angle - }); - - // Check callbacks: - if (typeof o.onNewFrame === 'function') - o.onNewFrame(); - - anim.frameId = requestAnimationFrame(fn); - } - }; - - id = _getID(); - anim = { - frameId: requestAnimationFrame(fn), - target: camera, - type: 'camera', - options: o, - fn: fn - }; - sigma.misc.animation.running[id] = anim; - - return id; - }; - - /** - * Kills a running animation. It triggers the eventual onComplete callback. - * - * @param {number} id The id of the animation to kill. - * @return {object} Returns the sigma.misc.animation package. - */ - sigma.misc.animation.kill = function(id) { - if (arguments.length !== 1 || typeof id !== 'number') - throw 'animation.kill: Wrong arguments.'; - - var o = sigma.misc.animation.running[id]; - - if (o) { - cancelAnimationFrame(id); - delete sigma.misc.animation.running[o.frameId]; - - if (o.type === 'camera') - o.target.isAnimated = false; - - // Check callbacks: - if (typeof (o.options || {}).onComplete === 'function') - o.options.onComplete(); - } - - return this; - }; - - /** - * Kills every running animations, or only the one with the specified type, - * if a string parameter is given. - * - * @param {?(string|object)} filter A string to filter the animations to kill - * on their type (example: "camera"), or an - * object to filter on their target. - * @return {number} Returns the number of animations killed - * that way. - */ - sigma.misc.animation.killAll = function(filter) { - var o, - id, - count = 0, - type = typeof filter === 'string' ? filter : null, - target = typeof filter === 'object' ? filter : null, - running = sigma.misc.animation.running; - - for (id in running) - if ( - (!type || running[id].type === type) && - (!target || running[id].target === target) - ) { - o = sigma.misc.animation.running[id]; - cancelAnimationFrame(o.frameId); - delete sigma.misc.animation.running[id]; - - if (o.type === 'camera') - o.target.isAnimated = false; - - // Increment counter: - count++; - - // Check callbacks: - if (typeof (o.options || {}).onComplete === 'function') - o.options.onComplete(); - } - - return count; - }; - - /** - * Returns "true" if any animation that is currently still running matches - * the filter given to the function. - * - * @param {string|object} filter A string to filter the animations to kill - * on their type (example: "camera"), or an - * object to filter on their target. - * @return {boolean} Returns true if any running animation - * matches. - */ - sigma.misc.animation.has = function(filter) { - var id, - type = typeof filter === 'string' ? filter : null, - target = typeof filter === 'object' ? filter : null, - running = sigma.misc.animation.running; - - for (id in running) - if ( - (!type || running[id].type === type) && - (!target || running[id].target === target) - ) - return true; - - return false; - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.misc'); - - /** - * This helper will bind any no-DOM renderer (for instance canvas or WebGL) - * to its captors, to properly dispatch the good events to the sigma instance - * to manage clicking, hovering etc... - * - * It has to be called in the scope of the related renderer. - */ - sigma.misc.bindEvents = function(prefix) { - var i, - l, - mX, - mY, - captor, - self = this; - - function getNodes(e) { - if (e) { - mX = 'x' in e.data ? e.data.x : mX; - mY = 'y' in e.data ? e.data.y : mY; - } - - var i, - j, - l, - n, - x, - y, - s, - inserted, - selected = [], - modifiedX = mX + self.width / 2, - modifiedY = mY + self.height / 2, - point = self.camera.cameraPosition( - mX, - mY - ), - nodes = self.camera.quadtree.point( - point.x, - point.y - ); - - if (nodes.length) - for (i = 0, l = nodes.length; i < l; i++) { - n = nodes[i]; - x = n[prefix + 'x']; - y = n[prefix + 'y']; - s = n[prefix + 'size']; - - if ( - !n.hidden && - modifiedX > x - s && - modifiedX < x + s && - modifiedY > y - s && - modifiedY < y + s && - Math.sqrt( - Math.pow(modifiedX - x, 2) + - Math.pow(modifiedY - y, 2) - ) < s - ) { - // Insert the node: - inserted = false; - - for (j = 0; j < selected.length; j++) - if (n.size > selected[j].size) { - selected.splice(j, 0, n); - inserted = true; - break; - } - - if (!inserted) - selected.push(n); - } - } - - return selected; - } - - - function getEdges(e) { - if (!self.settings('enableEdgeHovering')) { - // No event if the setting is off: - return []; - } - - var isCanvas = ( - sigma.renderers.canvas && self instanceof sigma.renderers.canvas); - - if (!isCanvas) { - // A quick hardcoded rule to prevent people from using this feature - // with the WebGL renderer (which is not good enough at the moment): - throw new Error( - 'The edge events feature is not compatible with the WebGL renderer' - ); - } - - if (e) { - mX = 'x' in e.data ? e.data.x : mX; - mY = 'y' in e.data ? e.data.y : mY; - } - - var i, - j, - l, - a, - edge, - s, - maxEpsilon = self.settings('edgeHoverPrecision'), - source, - target, - cp, - nodeIndex = {}, - inserted, - selected = [], - modifiedX = mX + self.width / 2, - modifiedY = mY + self.height / 2, - point = self.camera.cameraPosition( - mX, - mY - ), - edges = []; - - if (isCanvas) { - var nodesOnScreen = self.camera.quadtree.area( - self.camera.getRectangle(self.width, self.height) - ); - for (a = nodesOnScreen, i = 0, l = a.length; i < l; i++) - nodeIndex[a[i].id] = a[i]; - } - - if (self.camera.edgequadtree !== undefined) { - edges = self.camera.edgequadtree.point( - point.x, - point.y - ); - } - - function insertEdge(selected, edge) { - inserted = false; - - for (j = 0; j < selected.length; j++) - if (edge.size > selected[j].size) { - selected.splice(j, 0, edge); - inserted = true; - break; - } - - if (!inserted) - selected.push(edge); - } - - if (edges.length) - for (i = 0, l = edges.length; i < l; i++) { - edge = edges[i]; - source = self.graph.nodes(edge.source); - target = self.graph.nodes(edge.target); - // (HACK) we can't get edge[prefix + 'size'] on WebGL renderer: - s = edge[prefix + 'size'] || - edge['read_' + prefix + 'size']; - - // First, let's identify which edges are drawn. To do this, we keep - // every edges that have at least one extremity displayed according to - // the quadtree and the "hidden" attribute. We also do not keep hidden - // edges. - // Then, let's check if the mouse is on the edge (we suppose that it - // is a line segment). - - if ( - !edge.hidden && - !source.hidden && !target.hidden && - (!isCanvas || - (nodeIndex[edge.source] || nodeIndex[edge.target])) && - sigma.utils.getDistance( - source[prefix + 'x'], - source[prefix + 'y'], - modifiedX, - modifiedY) > source[prefix + 'size'] && - sigma.utils.getDistance( - target[prefix + 'x'], - target[prefix + 'y'], - modifiedX, - modifiedY) > target[prefix + 'size'] - ) { - if (edge.type == 'curve' || edge.type == 'curvedArrow') { - if (source.id === target.id) { - cp = sigma.utils.getSelfLoopControlPoints( - source[prefix + 'x'], - source[prefix + 'y'], - source[prefix + 'size'] - ); - if ( - sigma.utils.isPointOnBezierCurve( - modifiedX, - modifiedY, - source[prefix + 'x'], - source[prefix + 'y'], - target[prefix + 'x'], - target[prefix + 'y'], - cp.x1, - cp.y1, - cp.x2, - cp.y2, - Math.max(s, maxEpsilon) - )) { - insertEdge(selected, edge); - } - } - else { - cp = sigma.utils.getQuadraticControlPoint( - source[prefix + 'x'], - source[prefix + 'y'], - target[prefix + 'x'], - target[prefix + 'y']); - if ( - sigma.utils.isPointOnQuadraticCurve( - modifiedX, - modifiedY, - source[prefix + 'x'], - source[prefix + 'y'], - target[prefix + 'x'], - target[prefix + 'y'], - cp.x, - cp.y, - Math.max(s, maxEpsilon) - )) { - insertEdge(selected, edge); - } - } - } else if ( - sigma.utils.isPointOnSegment( - modifiedX, - modifiedY, - source[prefix + 'x'], - source[prefix + 'y'], - target[prefix + 'x'], - target[prefix + 'y'], - Math.max(s, maxEpsilon) - )) { - insertEdge(selected, edge); - } - } - } - - return selected; - } - - - function bindCaptor(captor) { - var nodes, - edges, - overNodes = {}, - overEdges = {}; - - function onClick(e) { - if (!self.settings('eventsEnabled')) - return; - - self.dispatchEvent('click', e.data); - - nodes = getNodes(e); - edges = getEdges(e); - - if (nodes.length) { - self.dispatchEvent('clickNode', { - node: nodes[0], - captor: e.data - }); - self.dispatchEvent('clickNodes', { - node: nodes, - captor: e.data - }); - } else if (edges.length) { - self.dispatchEvent('clickEdge', { - edge: edges[0], - captor: e.data - }); - self.dispatchEvent('clickEdges', { - edge: edges, - captor: e.data - }); - } else - self.dispatchEvent('clickStage', {captor: e.data}); - } - - function onDoubleClick(e) { - if (!self.settings('eventsEnabled')) - return; - - self.dispatchEvent('doubleClick', e.data); - - nodes = getNodes(e); - edges = getEdges(e); - - if (nodes.length) { - self.dispatchEvent('doubleClickNode', { - node: nodes[0], - captor: e.data - }); - self.dispatchEvent('doubleClickNodes', { - node: nodes, - captor: e.data - }); - } else if (edges.length) { - self.dispatchEvent('doubleClickEdge', { - edge: edges[0], - captor: e.data - }); - self.dispatchEvent('doubleClickEdges', { - edge: edges, - captor: e.data - }); - } else - self.dispatchEvent('doubleClickStage', {captor: e.data}); - } - - function onRightClick(e) { - if (!self.settings('eventsEnabled')) - return; - - self.dispatchEvent('rightClick', e.data); - - nodes = getNodes(e); - edges = getEdges(e); - - if (nodes.length) { - self.dispatchEvent('rightClickNode', { - node: nodes[0], - captor: e.data - }); - self.dispatchEvent('rightClickNodes', { - node: nodes, - captor: e.data - }); - } else if (edges.length) { - self.dispatchEvent('rightClickEdge', { - edge: edges[0], - captor: e.data - }); - self.dispatchEvent('rightClickEdges', { - edge: edges, - captor: e.data - }); - } else - self.dispatchEvent('rightClickStage', {captor: e.data}); - } - - function onOut(e) { - if (!self.settings('eventsEnabled')) - return; - - var k, - i, - l, - le, - outNodes = [], - outEdges = []; - - for (k in overNodes) - outNodes.push(overNodes[k]); - - overNodes = {}; - // Dispatch both single and multi events: - for (i = 0, l = outNodes.length; i < l; i++) - self.dispatchEvent('outNode', { - node: outNodes[i], - captor: e.data - }); - if (outNodes.length) - self.dispatchEvent('outNodes', { - nodes: outNodes, - captor: e.data - }); - - overEdges = {}; - // Dispatch both single and multi events: - for (i = 0, le = outEdges.length; i < le; i++) - self.dispatchEvent('outEdge', { - edge: outEdges[i], - captor: e.data - }); - if (outEdges.length) - self.dispatchEvent('outEdges', { - edges: outEdges, - captor: e.data - }); - } - - function onMove(e) { - if (!self.settings('eventsEnabled')) - return; - - nodes = getNodes(e); - edges = getEdges(e); - - var i, - k, - node, - edge, - newOutNodes = [], - newOverNodes = [], - currentOverNodes = {}, - l = nodes.length, - newOutEdges = [], - newOverEdges = [], - currentOverEdges = {}, - le = edges.length; - - // Check newly overred nodes: - for (i = 0; i < l; i++) { - node = nodes[i]; - currentOverNodes[node.id] = node; - if (!overNodes[node.id]) { - newOverNodes.push(node); - overNodes[node.id] = node; - } - } - - // Check no more overred nodes: - for (k in overNodes) - if (!currentOverNodes[k]) { - newOutNodes.push(overNodes[k]); - delete overNodes[k]; - } - - // Dispatch both single and multi events: - for (i = 0, l = newOverNodes.length; i < l; i++) - self.dispatchEvent('overNode', { - node: newOverNodes[i], - captor: e.data - }); - for (i = 0, l = newOutNodes.length; i < l; i++) - self.dispatchEvent('outNode', { - node: newOutNodes[i], - captor: e.data - }); - if (newOverNodes.length) - self.dispatchEvent('overNodes', { - nodes: newOverNodes, - captor: e.data - }); - if (newOutNodes.length) - self.dispatchEvent('outNodes', { - nodes: newOutNodes, - captor: e.data - }); - - // Check newly overred edges: - for (i = 0; i < le; i++) { - edge = edges[i]; - currentOverEdges[edge.id] = edge; - if (!overEdges[edge.id]) { - newOverEdges.push(edge); - overEdges[edge.id] = edge; - } - } - - // Check no more overred edges: - for (k in overEdges) - if (!currentOverEdges[k]) { - newOutEdges.push(overEdges[k]); - delete overEdges[k]; - } - - // Dispatch both single and multi events: - for (i = 0, le = newOverEdges.length; i < le; i++) - self.dispatchEvent('overEdge', { - edge: newOverEdges[i], - captor: e.data - }); - for (i = 0, le = newOutEdges.length; i < le; i++) - self.dispatchEvent('outEdge', { - edge: newOutEdges[i], - captor: e.data - }); - if (newOverEdges.length) - self.dispatchEvent('overEdges', { - edges: newOverEdges, - captor: e.data - }); - if (newOutEdges.length) - self.dispatchEvent('outEdges', { - edges: newOutEdges, - captor: e.data - }); - } - - // Bind events: - captor.bind('click', onClick); - captor.bind('mousedown', onMove); - captor.bind('mouseup', onMove); - captor.bind('mousemove', onMove); - captor.bind('mouseout', onOut); - captor.bind('doubleclick', onDoubleClick); - captor.bind('rightclick', onRightClick); - self.bind('render', onMove); - } - - for (i = 0, l = this.captors.length; i < l; i++) - bindCaptor(this.captors[i]); - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.misc'); - - /** - * This helper will bind any DOM renderer (for instance svg) - * to its captors, to properly dispatch the good events to the sigma instance - * to manage clicking, hovering etc... - * - * It has to be called in the scope of the related renderer. - */ - sigma.misc.bindDOMEvents = function(container) { - var self = this, - graph = this.graph; - - // DOMElement abstraction - function Element(domElement) { - - // Helpers - this.attr = function(attrName) { - return domElement.getAttributeNS(null, attrName); - }; - - // Properties - this.tag = domElement.tagName; - this.class = this.attr('class'); - this.id = this.attr('id'); - - // Methods - this.isNode = function() { - return !!~this.class.indexOf(self.settings('classPrefix') + '-node'); - }; - - this.isEdge = function() { - return !!~this.class.indexOf(self.settings('classPrefix') + '-edge'); - }; - - this.isHover = function() { - return !!~this.class.indexOf(self.settings('classPrefix') + '-hover'); - }; - } - - // Click - function click(e) { - if (!self.settings('eventsEnabled')) - return; - - // Generic event - self.dispatchEvent('click', e); - - // Are we on a node? - var element = new Element(e.target); - - if (element.isNode()) - self.dispatchEvent('clickNode', { - node: graph.nodes(element.attr('data-node-id')) - }); - else - self.dispatchEvent('clickStage'); - - e.preventDefault(); - e.stopPropagation(); - } - - // Double click - function doubleClick(e) { - if (!self.settings('eventsEnabled')) - return; - - // Generic event - self.dispatchEvent('doubleClick', e); - - // Are we on a node? - var element = new Element(e.target); - - if (element.isNode()) - self.dispatchEvent('doubleClickNode', { - node: graph.nodes(element.attr('data-node-id')) - }); - else - self.dispatchEvent('doubleClickStage'); - - e.preventDefault(); - e.stopPropagation(); - } - - // On over - function onOver(e) { - var target = e.toElement || e.target; - - if (!self.settings('eventsEnabled') || !target) - return; - - var el = new Element(target); - - if (el.isNode()) { - self.dispatchEvent('overNode', { - node: graph.nodes(el.attr('data-node-id')) - }); - } - else if (el.isEdge()) { - var edge = graph.edges(el.attr('data-edge-id')); - self.dispatchEvent('overEdge', { - edge: edge, - source: graph.nodes(edge.source), - target: graph.nodes(edge.target) - }); - } - } - - // On out - function onOut(e) { - var target = e.fromElement || e.originalTarget; - - if (!self.settings('eventsEnabled')) - return; - - var el = new Element(target); - - if (el.isNode()) { - self.dispatchEvent('outNode', { - node: graph.nodes(el.attr('data-node-id')) - }); - } - else if (el.isEdge()) { - var edge = graph.edges(el.attr('data-edge-id')); - self.dispatchEvent('outEdge', { - edge: edge, - source: graph.nodes(edge.source), - target: graph.nodes(edge.target) - }); - } - } - - // Registering Events: - - // Click - container.addEventListener('click', click, false); - sigma.utils.doubleClick(container, 'click', doubleClick); - - // Touch counterparts - container.addEventListener('touchstart', click, false); - sigma.utils.doubleClick(container, 'touchstart', doubleClick); - - // Mouseover - container.addEventListener('mouseover', onOver, true); - - // Mouseout - container.addEventListener('mouseout', onOut, true); - }; -}).call(this); - -;(function(undefined) { - 'use strict'; - - if (typeof sigma === 'undefined') - throw 'sigma is not declared'; - - // Initialize packages: - sigma.utils.pkg('sigma.misc'); - - /** - * This method listens to "overNode", "outNode", "overEdge" and "outEdge" - * events from a renderer and renders the nodes differently on the top layer. - * The goal is to make any node label readable with the mouse, and to - * highlight hovered nodes and edges. - * - * It has to be called in the scope of the related renderer. - */ - sigma.misc.drawHovers = function(prefix) { - var self = this, - hoveredNodes = {}, - hoveredEdges = {}; - - this.bind('overNode', function(event) { - var node = event.data.node; - if (!node.hidden) { - hoveredNodes[node.id] = node; - draw(); - } - }); - - this.bind('outNode', function(event) { - delete hoveredNodes[event.data.node.id]; - draw(); - }); - - this.bind('overEdge', function(event) { - var edge = event.data.edge; - if (!edge.hidden) { - hoveredEdges[edge.id] = edge; - draw(); - } - }); - - this.bind('outEdge', function(event) { - delete hoveredEdges[event.data.edge.id]; - draw(); - }); - - this.bind('render', function(event) { - draw(); - }); - - function draw() { - - var k, - source, - target, - hoveredNode, - hoveredEdge, - c = self.contexts.hover.canvas, - defaultNodeType = self.settings('defaultNodeType'), - defaultEdgeType = self.settings('defaultEdgeType'), - nodeRenderers = sigma.canvas.hovers, - edgeRenderers = sigma.canvas.edgehovers, - extremitiesRenderers = sigma.canvas.extremities, - embedSettings = self.settings.embedObjects({ - prefix: prefix - }); - - // Clear self.contexts.hover: - self.contexts.hover.clearRect(0, 0, c.width, c.height); - - // Node render: single hover - if ( - embedSettings('enableHovering') && - embedSettings('singleHover') && - Object.keys(hoveredNodes).length - ) { - hoveredNode = hoveredNodes[Object.keys(hoveredNodes)[0]]; - ( - nodeRenderers[hoveredNode.type] || - nodeRenderers[defaultNodeType] || - nodeRenderers.def - )( - hoveredNode, - self.contexts.hover, - embedSettings - ); - } - - // Node render: multiple hover - if ( - embedSettings('enableHovering') && - !embedSettings('singleHover') - ) - for (k in hoveredNodes) - ( - nodeRenderers[hoveredNodes[k].type] || - nodeRenderers[defaultNodeType] || - nodeRenderers.def - )( - hoveredNodes[k], - self.contexts.hover, - embedSettings - ); - - // Edge render: single hover - if ( - embedSettings('enableEdgeHovering') && - embedSettings('singleHover') && - Object.keys(hoveredEdges).length - ) { - hoveredEdge = hoveredEdges[Object.keys(hoveredEdges)[0]]; - source = self.graph.nodes(hoveredEdge.source); - target = self.graph.nodes(hoveredEdge.target); - - if (! hoveredEdge.hidden) { - ( - edgeRenderers[hoveredEdge.type] || - edgeRenderers[defaultEdgeType] || - edgeRenderers.def - ) ( - hoveredEdge, - source, - target, - self.contexts.hover, - embedSettings - ); - - if (embedSettings('edgeHoverExtremities')) { - ( - extremitiesRenderers[hoveredEdge.type] || - extremitiesRenderers.def - )( - hoveredEdge, - source, - target, - self.contexts.hover, - embedSettings - ); - - } else { - // Avoid edges rendered over nodes: - ( - sigma.canvas.nodes[source.type] || - sigma.canvas.nodes.def - ) ( - source, - self.contexts.hover, - embedSettings - ); - ( - sigma.canvas.nodes[target.type] || - sigma.canvas.nodes.def - ) ( - target, - self.contexts.hover, - embedSettings - ); - } - } - } - - // Edge render: multiple hover - if ( - embedSettings('enableEdgeHovering') && - !embedSettings('singleHover') - ) { - for (k in hoveredEdges) { - hoveredEdge = hoveredEdges[k]; - source = self.graph.nodes(hoveredEdge.source); - target = self.graph.nodes(hoveredEdge.target); - - if (!hoveredEdge.hidden) { - ( - edgeRenderers[hoveredEdge.type] || - edgeRenderers[defaultEdgeType] || - edgeRenderers.def - ) ( - hoveredEdge, - source, - target, - self.contexts.hover, - embedSettings - ); - - if (embedSettings('edgeHoverExtremities')) { - ( - extremitiesRenderers[hoveredEdge.type] || - extremitiesRenderers.def - )( - hoveredEdge, - source, - target, - self.contexts.hover, - embedSettings - ); - } else { - // Avoid edges rendered over nodes: - ( - sigma.canvas.nodes[source.type] || - sigma.canvas.nodes.def - ) ( - source, - self.contexts.hover, - embedSettings - ); - ( - sigma.canvas.nodes[target.type] || - sigma.canvas.nodes.def - ) ( - target, - self.contexts.hover, - embedSettings - ); - } - } - } - } - } - }; -}).call(this); diff --git a/manager/module/chart.php b/manager/module/chart.php index 6b581c0..87943e9 100755 --- a/manager/module/chart.php +++ b/manager/module/chart.php @@ -124,8 +124,11 @@ return array( 'ModuleError' => ManagerError::Success, 'labels' => $labels, - 'colors' => $colors, - 'data' => array($INCOMING, $OUTGOING, $MISSED) + 'datasets' => array(array( + 'data' => array($INCOMING, $OUTGOING, $MISSED), + 'backgroundColor' => $colors['default'], + 'hoverBackgroundColor' => $colors['hover'] + )) ); } @@ -185,9 +188,12 @@ return array( 'ModuleError' => ManagerError::Success, - 'colors' => $colors, 'labels' => array('APPELS', 'SMS'), - 'data' => array($PHONE, $SMS) + 'datasets' => array(array( + 'data' => array($PHONE, $SMS), + 'backgroundColor' => $colors['default'], + 'hoverBackgroundColor' => $colors['hover'] + )) ); } @@ -254,9 +260,12 @@ return array( 'ModuleError' => ManagerError::Success, - 'colors' => $colors, 'labels' => array('HOMME', 'FEMME'), - 'data' => array($H, $F) + 'datasets' => array(array( + 'data' => array($H, $F), + 'backgroundColor' => $colors['default'], + 'hoverBackgroundColor' => $colors['hover'] + )) ); } @@ -346,9 +355,12 @@ return array( 'ModuleError' => ManagerError::Success, - 'colors' => $colors, 'labels' => $labels, - 'data' => $age_classes + 'datasets' => array(array( + 'data' => $age_classes, + 'backgroundColor' => $colors['default'], + 'hoverBackgroundColor' => $colors['hover'] + )) ); } @@ -439,9 +451,14 @@ return array( 'ModuleError' => ManagerError::Success, - 'colors' => $colors, 'labels' => $labels, - 'data' => $relations + 'datasets' => array(array( + 'data' => $relations, + 'backgroundColor' => $colors['default'], + 'hoverBackgroundColor' => $colors['hover'], + 'borderWidth' => 1, + 'borderColor' => 'hsla(216,100%,80%,1)' + )) ); } diff --git a/test-charts/all.php b/test-charts/all.php index dbbf121..a8b4661 100644 --- a/test-charts/all.php +++ b/test-charts/all.php @@ -28,10 +28,27 @@ /* [0] Paramètres globaux =========================================================*/ Chart.defaults.global.responsive = false; + Chart.defaults.global.onClick = function(e, c){ + console.log(e); // MouseEvent + console.log(c[0]._datasetIndex, c[0]._index); // Chart data + + // _datasetIndex, quel dataset (s'il y en a plusieurs) + // _index, indice de la valeur dans le dataset + } subject = 273; + var charts = ['sexe', 'direction', 'type', 'ages', 'relation']; + + + /* [1] On crée les conteneurs + =========================================================*/ + for( var c in charts ){ + + } + + /* [1] On récupére les données SEXE =========================================================*/ /* (1) On rédige la requête */ @@ -50,11 +67,7 @@ =========================================================*/ var data = { labels: response.labels, - datasets: [{ - data: response.data, - backgroundColor: response.colors.default, - hoverBackgroundColor: response.colors.hover - }] + datasets: response.datasets } /* [3] On construit notre graphique @@ -86,11 +99,7 @@ =========================================================*/ var data = { labels: response.labels, - datasets: [{ - data: response.data, - backgroundColor: response.colors.default, - hoverBackgroundColor: response.colors.hover - }] + datasets: response.datasets } /* [3] On construit notre graphique @@ -122,11 +131,7 @@ =========================================================*/ var data = { labels: response.labels, - datasets: [{ - data: response.data, - backgroundColor: response.colors.default, - hoverBackgroundColor: response.colors.hover - }] + datasets: response.datasets } /* [3] On construit notre graphique @@ -160,11 +165,7 @@ =========================================================*/ var data = { labels: response.labels, - datasets: [{ - data: response.data, - backgroundColor: response.colors.default, - hoverBackgroundColor: response.colors.hover - }] + datasets: response.datasets } /* [3] On construit notre graphique @@ -196,17 +197,9 @@ /* [2] On construit les données =========================================================*/ - console.log(response.labels); var data = { labels: response.labels, - datasets: [{ - label: "Types de relation", - data: response.data, - borderWidth: 1, - borderColor: 'hsla(216,100%,80%,1)', - backgroundColor: response.colors.default, - hoverBackgroundColor: response.colors.hover - }] + datasets: response.datasets } /* [3] On construit notre graphique diff --git a/view.php b/view.php index 3ae7794..de18218 100755 --- a/view.php +++ b/view.php @@ -41,8 +41,8 @@ - - + + diff --git a/view/charts.php b/view/charts.php index c4e9e24..ed51d4f 100755 --- a/view/charts.php +++ b/view/charts.php @@ -10,57 +10,11 @@ -/* [1] Gestion du cercle des relations -=======================================*/ -/* (1) On recupere les donnees */ -$getData = new ModuleRequest('chart/network_data'); -$answer = $getData->dispatch(); - -// Si pas d'erreur -if( $answer->error != ManagerError::Success ) - var_dump( ManagerError::explicit($answer->error) ); - -// On enregistre les donnees -$data = $answer->get('data'); - - -/* (2) On recupere le rendu */ -$getRender = new ModuleRequest('chart/network_render', array('dataset' => $data)); -$answer = $getRender->dispatch(); - -// Si pas d'erreur -if( $answer->error != ManagerError::Success ) - var_dump( ManagerError::explicit($answer->error) ); - -// On enregistre les donnees -$render = $answer->get('render'); - ?> -
+
- -
-
SOCIOGRAMME SPATIAL
-
-
- - - - -
-
SOCIOGRAMME EGOCENTRE
-
-
- - - - -
-
SOCIOGRAMME EGOCENTRE
- -
diff --git a/view/js/charts-min.js b/view/js/charts-min.js index 684cb3f..300da6a 100644 --- a/view/js/charts-min.js +++ b/view/js/charts-min.js @@ -1 +1,2 @@ -var chartManagers={sociogram:{link:"/js/includes/charts-sociogram-min.js",loaded:!1}},sociogram;chartManagers.sociogram.loaded=include(chartManagers.sociogram.link,null,!0);include(chartManagers.sociogram.link,function(){chartManagers.sociogram.loaded||sociogramClass.prototype.overloadGraph();sociogram=new sociogramClass($("#sociogram"));sociogram.load()}); +var subPhone=$('section[data-sublink="phone"]');Chart.defaults.global.responsive=!1;Chart.defaults.global.onClick=function(a,b){console.log(a);console.log(b[0]._datasetIndex,b[0]._index)};subject=273;var charts=["sexe","direction","type","ages","relation"],types=["doughnut","doughnut","doughnut","doughnut","bar"],canvas=[],instances=[],c;for(c in charts)canvas[c]=document.createElement("canvas"),canvas[c].id=charts[c],canvas[c].width=300,canvas[c].height=300,subPhone.appendChild(canvas[c]); +for(c=0;c Date: Mon, 23 May 2016 12:07:18 +0200 Subject: [PATCH 09/12] #14; Affiche de 2 dataset en %age pour les reltype --- manager/module/chart.php | 94 +++++++++++++++++++++++++++++----------- view/js/charts-min.js | 5 ++- view/js/charts.js | 28 ++++++++---- 3 files changed, 90 insertions(+), 37 deletions(-) diff --git a/manager/module/chart.php b/manager/module/chart.php index 87943e9..808ef01 100755 --- a/manager/module/chart.php +++ b/manager/module/chart.php @@ -295,20 +295,18 @@ /* [2] On initialise les valeurs =========================================================*/ - /* (1) On initialise les compteurs */ - $age_classes = array(); - for( $i = 0 ; $i < 18 ; $i++ ) - $age_classes[$i] = 0; - - /* (2) On charge le dictionnaire */ + /* (1) On charge le dictionnaire */ $dict = self::loadDictionary(); if( $dict === false ) return array( 'ModuleError' => ManagerError::ParsingFailed ); - /* (2) On initialise les labels */ + /* (2) On initialise les compteurs et les labels */ + $age_classes = array(); $labels = array(); - foreach($dict['contacts']['age'] as $i=>$label) + foreach($dict['contacts']['age'] as $i=>$label){ array_push($labels, $label); + $age_classes[$i] = 0; + } /* [3] S'il a un journal d'appel, on renvoie les données @@ -391,20 +389,21 @@ /* [2] On initialise les valeurs =========================================================*/ - /* (1) On initialise les compteurs */ - $relations = array(); - for( $i = 0 ; $i < 18 ; $i++ ) - $relations[$i] = 0; - - /* (2) On charge le dictionnaire */ + /* (1) On charge le dictionnaire */ $dict = self::loadDictionary(); if( $dict === false ) return array( 'ModuleError' => ManagerError::ParsingFailed ); - /* (2) On initialise les labels */ - $labels = array(); - foreach($dict['contacts']['reltype'] as $i=>$label) + /* (2) On initialise les compteurs et labels */ + $relations = array(); // relations en fonction du log + $relationsByContact = array(); // relations en fonction de la répartition des contacts + $labels = array(); + + foreach($dict['contacts']['reltype'] as $i=>$label){ array_push($labels, $label); + $relations[$i] = 0; + $relationsByContact[$i] = 0; + } /* [3] S'il a un journal d'appel, on renvoie les données @@ -413,6 +412,7 @@ /* (2) On incrémente les compteurs */ + $tot = 0; foreach($data['logs'] as $log){ /* (3) On récupère le contact associé */ @@ -426,19 +426,43 @@ continue; /* (4) On incrémente le compteur de la classe d'age en question */ - if( isset($relations[ $associatedContact['reltype'] ]) ) + if( isset($relations[ $associatedContact['reltype'] ]) ){ $relations[ $associatedContact['reltype'] ]++; + $tot++; + } } + foreach($relations as $r=>$rel) + $relations[$r] = 100 * $relations[$r] / $tot; + + } + + /* [4] On récupère les données par CONTACT + =========================================================*/ + /* (1) On incrémente les compteurs */ + $tot = 0; + foreach($data['contacts'] as $contact){ + + /* (2) On incrémente le compteur de la classe d'age en question */ + if( isset($relationsByContact[ $contact['reltype'] ]) ){ + $relationsByContact[ $contact['reltype'] ]++; + $tot++; + } + } + foreach($relationsByContact as $r=>$rel) + $relationsByContact[$r] = 100 * $relationsByContact[$r] / $tot; - /* [4] Gestion des couleurs + + + /* [5] Gestion des couleurs =========================================================*/ $colors = array(); $colors['default'] = array(); + $colors['defaultContact'] = array(); $step = (int) (50/count($relations)); @@ -448,17 +472,35 @@ $colors['hover'] = self::cFilter($colors['default'], 0, 0, -2); + // Pour chaque classe PAR CONTACT, on ajoute une couleur + for( $i = 0 ; $i < count($relationsByContact) ; $i++ ) + array_push($colors['defaultContact'], 'hsla(20,100%,'.(25+(50-$step*$i)).'%,.5)'); + + $colors['hoverContact'] = self::cFilter($colors['defaultContact'], 0, 0, -2); + + return array( 'ModuleError' => ManagerError::Success, 'labels' => $labels, - 'datasets' => array(array( - 'data' => $relations, - 'backgroundColor' => $colors['default'], - 'hoverBackgroundColor' => $colors['hover'], - 'borderWidth' => 1, - 'borderColor' => 'hsla(216,100%,80%,1)' - )) + 'datasets' => array( + array( // En fonction du log + 'label' => 'Répartition des communications', + 'data' => $relations, + 'backgroundColor' => $colors['default'], + 'hoverBackgroundColor' => $colors['hover'], + 'borderWidth' => 1, + 'borderColor' => 'hsla(216,100%,80%,1)' + ), + array( // Répartition dans les contacts + 'label' => 'Répartition des contacts', + 'data' => $relationsByContact, + 'backgroundColor' => $colors['defaultContact'], + 'hoverBackgroundColor' => $colors['hoverContact'], + 'borderWidth' => 1, + 'borderColor' => 'hsla(20,100%,80%,1)' + ) + ) ); } diff --git a/view/js/charts-min.js b/view/js/charts-min.js index 300da6a..6101f44 100644 --- a/view/js/charts-min.js +++ b/view/js/charts-min.js @@ -1,2 +1,3 @@ -var subPhone=$('section[data-sublink="phone"]');Chart.defaults.global.responsive=!1;Chart.defaults.global.onClick=function(a,b){console.log(a);console.log(b[0]._datasetIndex,b[0]._index)};subject=273;var charts=["sexe","direction","type","ages","relation"],types=["doughnut","doughnut","doughnut","doughnut","bar"],canvas=[],instances=[],c;for(c in charts)canvas[c]=document.createElement("canvas"),canvas[c].id=charts[c],canvas[c].width=300,canvas[c].height=300,subPhone.appendChild(canvas[c]); -for(c=0;c Date: Tue, 24 May 2016 08:58:37 +0200 Subject: [PATCH 10/12] =?UTF-8?q?Conversion=20fonctionnelle=20de=20fichier?= =?UTF-8?q?=20.txt=20export=C3=A9=20de=20'iExplorer'=20vers=20le=20format?= =?UTF-8?q?=20.xml=20export=C3=A9=20de=20'Call=20Log'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- automate.php | 33 + config/modules.json | 9 + config/upload-auth.json | 3 +- js/includes/input-phone-matrice-min.js | 2 +- js/includes/input-phone-matrice.js | 22 + manager/module/upload.php | 121 ++ src/upload/convert_iexplorer/xdrm.txt | 1508 ++++++++++++++++++++++++ view/iexplorer-to-call-log.php | 110 ++ 8 files changed, 1806 insertions(+), 2 deletions(-) create mode 100644 src/upload/convert_iexplorer/xdrm.txt create mode 100644 view/iexplorer-to-call-log.php diff --git a/automate.php b/automate.php index 0b81975..9808d38 100755 --- a/automate.php +++ b/automate.php @@ -89,6 +89,39 @@ /* [4] Test download via AJAX =========================================================*/ + $dates = array( + '01 janv. 2015', + '02 févr. 2015', + '03 mars 2015', + '04 avr. 2015', + '05 mai 2015', + '06 juin 2015', + '07 juil. 2015', + '08 août 2015', + '09 sept. 2015', + '10 nov. 2015', + '11 oct. 2015', + '12 dec. 2015', + ); + + function correctDates($date){ + $date = str_replace('janv.', 'jan.', $date); + $date = str_replace('févr.', 'feb.', $date); + $date = str_replace('mars', 'march', $date); + $date = str_replace('avr.', 'apr.', $date); + $date = str_replace('mai', 'may', $date); + $date = str_replace('juin', 'june', $date); + $date = str_replace('juil.', 'july', $date); + $date = str_replace('août', 'aug.', $date); + + return strtotime($date); + } + + foreach($dates as $date) + var_dump( date('d/m/Y H:i:s', correctDates($date)) ); + + + $db = new lightdb('phone_db', __ROOT__.'/src/dynamic/'); var_dump( array_keys($db->index())); $db->close(); diff --git a/config/modules.json b/config/modules.json index 91b0dc2..d8fa239 100755 --- a/config/modules.json +++ b/config/modules.json @@ -262,6 +262,15 @@ }, + "iexplorer_convert": { + "description": "Convertisseur .txt (iExplorer) vers .xml (Call Log)", + "permissions": ["admin"], + "parameters": { + "file": { "description": "Fichier exporté de iExplorer", "type": "FILE" } + } + }, + + "local_data": { "description": "Upload d'une sauvegarde de formulaire local au format .json.", "permissions": ["admin"], diff --git a/config/upload-auth.json b/config/upload-auth.json index 76a23c2..552dcce 100644 --- a/config/upload-auth.json +++ b/config/upload-auth.json @@ -2,6 +2,7 @@ "root": "/src/upload", "directories": [ "call_log", - "local_data" + "local_data", + "convert_iexplorer" ] } diff --git a/js/includes/input-phone-matrice-min.js b/js/includes/input-phone-matrice-min.js index 9a2505c..31153be 100644 --- a/js/includes/input-phone-matrice-min.js +++ b/js/includes/input-phone-matrice-min.js @@ -2,4 +2,4 @@ function inputPhoneMatrice(a){this.container=a}inputPhoneMatrice.prototype={cont inputPhoneMatrice.prototype.fieldsToStorage=function(){console.log("MATRICE: FIELDS TO STORAGE");var a=(new FormDeflater(this.container,["input"],["data-name"])).deflate();crc32(JSON.stringify(a));var d={},b;for(b in a)if(a[b]instanceof Array)for(var e in a[b])null==d[b]&&(d[b]=[]),d[b].push(parseInt(a[b][e]));else null!==a[b]&&(null==d[b]&&(d[b]=[]),d[b].push(parseInt(a[b])));lsi.set("p_matrice",0,d)}; inputPhoneMatrice.prototype.storageToFields=function(){console.log("MATRICE: STORAGE TO FIELDS");var a=lsi["export"]("p_fiches"),d=lsi.get("p_matrice",0),b=lsi["export"]("p_contacts"),e=[],c;for(c in a)-1==e.indexOf(a[c].contact)&&e.push(a[c].contact);a="";for(c=0;c";0',a+=g.username,a+=""):a+="";for(var f=0;f",a+='', a+=h.username,a+=""):f",a+=""):a+=""}a+=""}this.container.innerHTML=a+"
"}; -inputPhoneMatrice.prototype.attach=function(){console.log("MATRICE: ATTACH");lsi.createDataset("p_matrice");this.storageToFields();var a=this;this.container.addEventListener("click",function(d){a.fieldsToStorage();a.storageToFields()},!1)}; +inputPhoneMatrice.prototype.attach=function(){console.log("MATRICE: ATTACH");lsi.createDataset("p_matrice");this.storageToFields();var a=this;this.container.addEventListener("click",function(d){a.fieldsToStorage();a.storageToFields()},!1);this.snake()};inputPhoneMatrice.prototype.snake=function(){this.sVelocity=[0,0];this.sLength=0;this.sStack=[]}; diff --git a/js/includes/input-phone-matrice.js b/js/includes/input-phone-matrice.js index a797aaf..6352ee0 100644 --- a/js/includes/input-phone-matrice.js +++ b/js/includes/input-phone-matrice.js @@ -170,4 +170,26 @@ inputPhoneMatrice.prototype.attach = function(){ ptr.fieldsToStorage(); ptr.storageToFields(); }, false); + + this.snake(); +}; + + + + + + + + + + +inputPhoneMatrice.prototype.snake = function(){ + + /* [0] On écrit les fonctions principales + =========================================================*/ + this.sVelocity = [0, 0]; + this.sLength = 0; + this.sStack = []; + + }; diff --git a/manager/module/upload.php b/manager/module/upload.php index 6c36338..7f4060e 100644 --- a/manager/module/upload.php +++ b/manager/module/upload.php @@ -364,6 +364,127 @@ } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /* CONVERTISSEUR .txt de iExplorer vers .xml de Call Log + * + * @file Pointeur vers $_FILES[''] + * + */ + public static function iexplorer_convert($params){ + extract($params); + + /* [1] Gestion de l'upload du fichier et de la vžérification du format + =========================================================*/ + $uploadError = self::simpleFile( + 'convert_iexplorer',// nom du dossier d'upload + 'txt', // format du fichier + $file, // Fichier lui-même + function($content){ + $lines = explode("\n", $content); + + foreach($lines as $l=>$line) + if( strlen($line) > 0 && !preg_match('/^([\w\(\):\.@-]+(?: {1,3}[\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: {1,3}[\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: {1,3}[\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: {1,3}[\w\(\):\.@-]+)*) *$/u', $line) ) + return false; + + return true; + } + ); + + /* (1) Si erreur d'upload, on la renvoie */ + if( $uploadError != ManagerError::Success ) + return array( 'ModuleError' => $uploadError ); + + + + /* [2] On récupère les colonnes avec la premiére ligne + =========================================================*/ + /* (1) On met un handler sur le fichier pour le lire */ + $path = __ROOT__."/src/upload/convert_iexplorer/".$_SESSION['username'].".txt"; + + $handler = new \SplFileObject($path, 'r'); + + /* (2) On récupère pour chaque colonne, la position dans le chaine, et la valeur */ + $data = array(); + $lineCount = 0; + while( !$handler->eof() ){ + $line = $handler->fgets(); + + /* (1) Si première ligne, c'est les intitulés */ + if( $lineCount == 0 ){ + // Si erreur dans les intitulés, on retourne une erreur + if( !preg_match('/^([\w\(\):\.@-]+(?: [\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: [\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: [\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: [\w\(\):\.@-]+)*) *$/u', $line, $matches) ) + return array( 'ModuleError' => ManagerError::FormatError ); + + // On récupère les colonnes + $columns = array_slice($matches, 1); + + // On récupére les positions des colonnes + $colpos = array(); + foreach($columns as $column) + array_push($colpos, strpos($line, $column)); + + } + + + + /* (2) S'il reconnait pas la ligne, on passe à la suivante */ + if( !preg_match('/^([\w\(\):\.@-]+(?: {1,3}[\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: {1,3}[\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: {1,3}[\w\(\):\.@-]+)*) +([\w\(\):\.@-]+(?: {1,3}[\w\(\):\.@-]+)*) *$/u', $line, $matches) ) + continue; + + /* (3) On enregistre les données sinon */ + $cur = array(); + foreach($columns as $c=>$column) + $cur[$column] = $matches[$c+1]; + + array_push($data, $cur); + + + $lineCount++; + } + + + $handler = null; + + /* (3) Restitution du retour de `unserialize` */ + return array( + 'ModuleError' => ManagerError::Success, + 'data' => $data + ); + } + + } diff --git a/src/upload/convert_iexplorer/xdrm.txt b/src/upload/convert_iexplorer/xdrm.txt new file mode 100644 index 0000000..a025c8f --- /dev/null +++ b/src/upload/convert_iexplorer/xdrm.txt @@ -0,0 +1,1508 @@ +Number Date Duration Call Type +Ketty 18 juin 2015 18:46 0:01:51 Outgoing +Agence Carmes 1 oct. 2015 13:29 0:01:13 Incoming +Orpi 30 sept. 2015 10:42 0:00:00 Missed +33603023200 30 sept. 2015 19:07 0:00:00 Missed +Agence Carmes 30 sept. 2015 11:45 0:01:33 Incoming +Orpi 30 sept. 2015 10:51 0:00:37 Outgoing +Johann Chaulet 9 juin 2015 20:10 0:00:30 Outgoing +(056) 123-1994 2 juil. 2015 15:49 0:00:10 Outgoing +33621015169 20 juin 2015 15:52 0:00:00 Canceled +Fred Port 13 août 2015 10:24 0:00:00 Canceled +33621015169 1 sept. 2015 18:27 0:07:51 Incoming +33621015169 11 juil. 2015 16:30 0:00:55 Incoming +33621015169 14 août 2015 13:40 0:25:51 Incoming +33621015169 27 août 2015 17:27 0:03:25 Incoming +Ketty 13 juil. 2015 19:06 0:00:08 Outgoing +Melanie 16 juil. 2015 10:25 0:00:01 Incoming +33621015169 25 sept. 2015 20:20 0:01:12 Incoming +Guillaume Cavanac 23 sept. 2015 19:24 0:00:32 Incoming +(096) 932-1515 13 juil. 2015 08:18 0:07:13 Outgoing +Fred Port 18 juin 2015 19:03 0:00:06 Outgoing +Mam Portable 18 sept. 2015 19:05 0:00:04 Outgoing +Yan 21 août 2015 17:05 0:01:18 Incoming +Orpi 15 sept. 2015 11:57 0:01:11 Incoming +33621015169 31 août 2015 14:37 0:03:13 Incoming +Nathalie Paton 10 sept. 2015 12:10 0:06:53 Incoming +41215082415 30 sept. 2015 16:09 0:04:34 Incoming +(061) 557-7149 11 sept. 2015 16:30 0:00:56 Outgoing +Yan 16 août 2015 14:31 0:00:00 Missed +3676 26 août 2015 15:38 0:07:05 Outgoing +Mam Portable 27 juin 2015 11:28 0:13:09 Incoming +Pierre Ratinaud 31 juil. 2015 19:12 0:01:53 Incoming +Melanie 4 juil. 2015 22:26 0:18:47 Outgoing +33621015169 20 juin 2015 15:31 0:00:00 Missed +Borderes 21 sept. 2015 19:14 0:00:49 Incoming +(060) 518-1813 1 oct. 2015 00:54 0:00:00 Canceled +Jerry 10 sept. 2015 23:48 0:00:07 Incoming +33648821791 14 sept. 2015 18:22 0:00:20 Incoming +Nikos 1 oct. 2015 16:36 0:11:13 Outgoing +Jerry 17 juil. 2015 04:04 0:00:06 Outgoing +Caroline Datchary 9 sept. 2015 16:19 0:03:03 Outgoing +Ketty 5 juil. 2015 13:55 0:03:25 Outgoing +Pierre Ratinaud 30 juil. 2015 01:41 0:00:53 Incoming +Ketty 27 juin 2015 10:42 0:10:27 Incoming +(061) 370-3040 10 sept. 2015 16:24 0:05:47 Outgoing +33648642597 16 sept. 2015 15:06 0:00:00 Missed +33669416346 10 sept. 2015 09:20 0:00:00 Missed +Orpi 16 sept. 2015 11:48 0:02:22 Incoming +Mam Portable 10 août 2015 11:43 0:02:20 Outgoing +Manu Larribeau 8 juil. 2015 18:12 0:02:36 Outgoing +Muriel 13 juin 2015 18:05 0:00:27 Outgoing +33621015169 22 sept. 2015 13:47 0:05:04 Outgoing +33621015169 1 sept. 2015 10:00 0:00:00 Missed +Nathalie Paton 15 sept. 2015 19:29 0:00:08 Outgoing +33621015169 29 août 2015 16:38 0:02:49 Incoming +Mam Portable 3 juil. 2015 18:40 0:00:05 Outgoing +33672628692 2 oct. 2015 10:23 0:00:00 Missed +33178569562 25 juin 2015 16:01 0:02:30 Incoming +33621015169 17 sept. 2015 14:39 0:00:23 Incoming +Caroline Datchary 7 sept. 2015 09:52 0:02:19 Incoming +33659046045 10 sept. 2015 09:49 0:00:50 Incoming +33516195269 27 juil. 2015 13:56 0:00:00 Missed +Stanley 25 sept. 2015 13:25 0:01:53 Incoming +33621015169 24 août 2015 14:04 0:00:04 Outgoing +33621015169 21 juin 2015 18:42 0:00:00 Canceled +33180407381 29 juin 2015 10:02 0:00:00 Canceled +Caroline Datchary 3 juil. 2015 14:33 0:03:43 Outgoing +(056) 122-1228 9 juil. 2015 13:28 0:00:09 Outgoing +(096) 932-1515 13 juil. 2015 08:16 0:01:09 Outgoing +33672628692 2 oct. 2015 12:45 0:02:41 Outgoing +33621015169 14 juil. 2015 19:11 0:02:59 Incoming +33561502598 9 sept. 2015 16:16 0:00:00 Missed +Caroline Datchary 23 sept. 2015 19:03 0:00:00 Missed +33621015169 1 oct. 2015 17:34 0:00:00 Missed +Nathalie Paton 15 sept. 2015 19:30 0:00:02 Outgoing +Ketty 10 juil. 2015 17:54 0:11:18 Incoming +33627121666 30 sept. 2015 18:43 0:00:35 Incoming +Caroline Datchary 10 sept. 2015 15:51 0:02:22 Incoming +33781344967 25 sept. 2015 20:12 0:00:00 Missed +Michèle Figeac 16 août 2015 14:22 0:00:42 Outgoing +(078) 134-4967 25 sept. 2015 19:22 0:00:02 Outgoing +Mam Portable 29 juin 2015 18:50 0:00:00 Canceled +33621015169 20 sept. 2015 11:17 0:00:00 Missed +33613197785 1 sept. 2015 19:33 0:00:21 Incoming +33681741444 9 juin 2015 19:11 0:00:00 Missed +Mam Portable 1 août 2015 18:20 0:00:00 Canceled +Yan 16 août 2015 14:32 0:09:07 Outgoing +33621015169 12 juil. 2015 12:59 0:03:39 Incoming +Mam Portable 22 août 2015 12:29 0:00:51 Outgoing +Fred Port 23 août 2015 16:12 0:01:14 Outgoing +Roux Sébastien 20 août 2015 23:13 0:00:03 Outgoing +33621015169 26 sept. 2015 16:36 0:00:03 Outgoing +Johann Chaulet 30 juil. 2015 19:49 0:00:46 Outgoing +Michèle Figeac 14 août 2015 19:45 0:00:00 Missed +33183791309 18 juin 2015 09:35 0:00:17 Incoming +Orpi 9 sept. 2015 18:04 0:05:38 Incoming +Nathalie Paton 30 juin 2015 09:20 0:00:06 Incoming +33621015169 16 sept. 2015 17:43 0:01:12 Outgoing +(056) 227-8505 23 juin 2015 15:50 0:00:58 Outgoing +33672628692 2 oct. 2015 12:37 0:00:00 Missed +Stanley 22 sept. 2015 13:44 0:02:29 Incoming +33621015169 6 sept. 2015 14:12 0:02:49 Outgoing +33486120102 21 août 2015 12:22 0:01:44 Incoming +Mam Portable 22 août 2015 12:17 0:00:00 Missed +Nathalie Paton 25 août 2015 16:10 0:00:48 Outgoing +33664792968 30 sept. 2015 15:19 0:03:51 Incoming +33621015169 16 sept. 2015 18:09 0:00:03 Outgoing +Orpi 9 sept. 2015 18:10 0:00:32 Incoming +Marcolès 30 août 2015 12:10 0:00:03 Outgoing +Mam Portable 10 juil. 2015 18:50 0:00:04 Outgoing +33621015169 24 juin 2015 20:19 0:00:00 Canceled +Mam Portable 21 août 2015 13:36 0:00:05 Outgoing +Fred Port 13 août 2015 10:58 0:00:25 Outgoing +Fred Port 14 août 2015 20:08 0:00:15 Incoming +Manu Larribeau 9 sept. 2015 18:59 0:00:32 Outgoing +33621015169 29 août 2015 15:51 0:01:26 Outgoing +33621015169 21 juin 2015 18:43 0:02:56 Incoming +Stanley 10 sept. 2015 09:34 0:03:43 Incoming +Melanie 2 juil. 2015 15:07 0:00:17 Incoming +Ketty 2 juil. 2015 19:05 0:00:00 Canceled +33290871422 14 août 2015 15:36 0:00:07 Incoming +33625326210 30 sept. 2015 14:20 0:03:43 Incoming +33621015169 25 août 2015 19:30 0:00:29 Incoming +Orpi 15 sept. 2015 11:47 0:01:33 Incoming +Ketty 12 juin 2015 18:23 0:00:04 Outgoing +Melanie 2 juil. 2015 15:08 0:02:59 Outgoing +(078) 134-4967 25 sept. 2015 19:16 0:03:17 Outgoing +3990 26 juin 2015 16:44 0:00:49 Outgoing +33672819161 9 sept. 2015 19:13 0:01:58 Incoming +Nathalie Paton 15 sept. 2015 14:07 0:00:00 Missed +Roux Sébastien 20 août 2015 15:19 0:00:06 Incoming +Jerry 30 juil. 2015 19:46 0:00:05 Outgoing +Marcolès 26 juin 2015 18:57 0:00:03 Outgoing +Nathalie Paton 26 août 2015 19:25 0:36:08 Incoming +33175623230 20 août 2015 13:46 0:00:29 Incoming +Mam Portable 21 août 2015 13:36 0:00:44 Incoming +Fred Port 13 août 2015 13:57 0:00:51 Incoming +Marcolès 1 août 2015 18:20 0:06:06 Outgoing +33621015169 28 août 2015 11:11 0:00:00 Missed +33621015169 25 août 2015 19:13 0:00:04 Outgoing +Muriel 13 juin 2015 19:15 0:00:00 Canceled +590690852756 27 juin 2015 10:58 0:00:13 Incoming +33621015169 26 août 2015 15:52 0:00:37 Incoming +33621015169 26 sept. 2015 12:03 0:09:51 Incoming +Johann Chaulet 25 sept. 2015 21:26 0:01:13 Outgoing +33621015169 19 sept. 2015 20:31 0:00:57 Incoming +Caroline Datchary 8 sept. 2015 15:52 0:00:12 Outgoing +Johann Chaulet 15 sept. 2015 14:18 0:00:18 Incoming +33659046045 15 sept. 2015 14:52 0:00:00 Missed +Mam Portable 29 juin 2015 18:45 0:00:00 Missed +(013) 055-1600 27 août 2015 15:23 0:00:11 Outgoing +33681741444 9 juin 2015 19:41 0:00:51 Outgoing +33648821791 13 sept. 2015 19:01 0:03:24 Incoming +33646883964 2 oct. 2015 14:28 0:00:22 Incoming +33621015169 24 juin 2015 19:41 0:00:00 Missed +33781344967 25 sept. 2015 20:10 0:00:00 Missed +3676 13 juil. 2015 08:03 0:06:10 Outgoing +33634018991 1 oct. 2015 16:05 0:01:53 Incoming +33970734660 18 sept. 2015 12:44 0:00:59 Incoming +Johann Chaulet 15 sept. 2015 13:56 0:00:36 Incoming +Ketty 14 juil. 2015 14:27 0:00:50 Outgoing +(065) 801-1120 2 sept. 2015 14:34 0:01:34 Outgoing +33621015169 14 juil. 2015 17:09 0:00:00 Missed +33621015169 18 sept. 2015 09:43 0:02:24 Incoming +33621015169 19 sept. 2015 20:46 0:00:17 Incoming +Ketty 20 juin 2015 12:56 0:04:22 Incoming +33186856232 24 août 2015 16:03 0:00:05 Incoming +Ketty 2 juil. 2015 19:10 0:05:38 Outgoing +Ketty 13 juil. 2015 18:50 0:08:00 Outgoing +Zaf immobilier Rachel 14 sept. 2015 09:02 0:01:07 Outgoing +Pierre Ratinaud 26 juil. 2015 00:20 0:00:25 Incoming +Yan 15 août 2015 17:45 0:00:00 Missed +Johann Chaulet 25 août 2015 22:17 0:00:41 Incoming +33621015169 2 août 2015 10:44 0:00:00 Missed +33516195257 9 juil. 2015 15:55 0:00:32 Incoming +3676 3 août 2015 14:55 0:16:18 Outgoing +Johann Chaulet 22 sept. 2015 23:34 0:00:54 Incoming +Nathalie Paton 28 août 2015 10:51 0:00:00 Missed +590690852756 17 juin 2015 20:51 0:00:31 Incoming +Yan 20 août 2015 11:46 0:11:13 Incoming +33621015169 5 sept. 2015 20:14 0:01:18 Incoming +33621015169 23 août 2015 20:21 0:00:00 Missed +33140251075 11 juin 2015 16:44 0:00:00 Missed +33621015169 19 sept. 2015 19:09 0:00:56 Incoming +Melanie 27 juin 2015 10:35 0:05:10 Incoming +33625326210 2 oct. 2015 12:49 0:00:07 Outgoing +33603023200 30 sept. 2015 19:07 0:00:01 Outgoing +33621015169 5 juil. 2015 20:18 0:00:00 Canceled +33467353653 10 sept. 2015 11:14 0:00:24 Incoming +33781344967 25 sept. 2015 20:12 0:04:09 Outgoing +Nathalie Paton 28 sept. 2015 11:49 0:11:41 Outgoing +33634018991 2 oct. 2015 10:33 0:00:00 Missed +Caroline Datchary 28 sept. 2015 12:24 0:00:00 Missed +33785240354 11 juin 2015 13:54 0:01:01 Incoming +Melanie 4 juil. 2015 21:24 0:59:16 Incoming +Marcolès 10 juil. 2015 18:49 0:00:03 Outgoing +Muriel 12 juin 2015 19:01 0:00:00 Missed +33546305800 28 juil. 2015 14:59 0:00:25 Incoming +33603023200 30 sept. 2015 19:11 0:01:53 Outgoing +Orpi 14 sept. 2015 10:06 0:02:11 Incoming +33781344967 25 sept. 2015 19:23 0:01:22 Incoming +330516602443 8 sept. 2015 12:53 0:00:00 Missed +Yan 15 août 2015 17:49 0:00:27 Outgoing +Nathalie Paton 30 sept. 2015 10:38 0:07:10 Outgoing +Johann Chaulet 6 juil. 2015 17:02 0:05:16 Outgoing +Orpi 30 sept. 2015 10:46 0:03:14 Outgoing +33603023200 1 oct. 2015 11:05 0:02:58 Incoming +Caroline Datchary 8 sept. 2015 15:53 0:02:28 Outgoing +Mam Portable 21 juin 2015 19:12 0:00:04 Outgoing +Mam Portable 3 juil. 2015 18:59 0:00:05 Outgoing +33621015169 26 sept. 2015 02:52 0:00:51 Incoming +Fred Port 18 juin 2015 19:02 0:00:05 Outgoing +Mam Portable 19 juin 2015 18:48 0:04:30 Outgoing +Johann Chaulet 30 juil. 2015 19:48 0:00:00 Missed +Mam Portable 30 août 2015 12:10 0:18:23 Outgoing +33621015169 14 août 2015 13:33 0:02:15 Incoming +33621015169 14 juin 2015 17:14 0:01:06 Outgoing +Jerry 17 juil. 2015 04:17 0:00:03 Outgoing +Muriel 13 juin 2015 18:32 0:00:00 Canceled +Roux Sébastien 26 juin 2015 19:29 0:00:26 Incoming +33613197785 1 sept. 2015 19:33 0:00:00 Missed +33621015169 14 sept. 2015 19:53 0:12:17 Incoming +Orpi 17 sept. 2015 16:23 0:00:30 Incoming +33613197785 27 août 2015 15:34 0:07:30 Incoming +33621015169 18 août 2015 15:33 0:00:00 Missed +Zaf immobilier Rachel 10 sept. 2015 15:27 0:00:13 Incoming +Orpi 15 sept. 2015 19:37 0:02:26 Incoming +33621015169 12 sept. 2015 14:28 0:00:25 Outgoing +33621015169 10 sept. 2015 14:09 0:00:00 Missed +Agence Carmes 28 sept. 2015 09:49 0:00:43 Outgoing +33669004606 16 sept. 2015 17:44 0:00:32 Incoming +Marcolès 27 sept. 2015 18:37 0:10:37 Outgoing +Ketty 14 juin 2015 15:52 0:07:39 Incoming +33621015169 18 juin 2015 21:16 0:00:00 Missed +Melanie 4 juil. 2015 23:25 0:10:14 Incoming +33628480010 10 juin 2015 10:06 0:00:25 Incoming +Melanie 16 juil. 2015 14:09 0:00:00 Canceled +33561250540 30 sept. 2015 14:50 0:00:28 Incoming +33621015169 17 sept. 2015 14:40 0:02:45 Outgoing +Caroline Datchary 28 sept. 2015 12:24 0:00:00 Missed +33621015169 30 sept. 2015 19:37 0:00:35 Incoming +33427192000 4 juil. 2015 17:11 0:07:07 Incoming +Nathalie Paton 28 août 2015 12:05 0:44:31 Outgoing +Marcolès 25 août 2015 19:26 0:03:35 Outgoing +33621015169 9 juil. 2015 13:36 0:00:00 Missed +Marcolès 18 sept. 2015 19:04 0:00:05 Outgoing +33635557151 30 sept. 2015 16:34 0:03:08 Incoming +33621015169 6 sept. 2015 15:45 0:00:00 Missed +33621015169 16 sept. 2015 19:39 0:00:01 Outgoing +Michèle Figeac 13 juil. 2015 18:26 0:18:29 Incoming +Johann Chaulet 30 juil. 2015 19:47 0:00:07 Outgoing +Fred Port 14 août 2015 17:41 0:01:51 Outgoing +Marcolès 1 oct. 2015 18:30 0:13:47 Outgoing +Melanie 4 juil. 2015 20:04 0:52:11 Outgoing +Yan 22 août 2015 17:29 0:00:00 Missed +Marcolès 3 juil. 2015 18:40 0:00:02 Outgoing +Nathalie Paton 10 juin 2015 19:50 0:00:00 Missed +(061) 319-7785 27 août 2015 15:24 0:00:48 Outgoing +Zaf immobilier Rachel 10 sept. 2015 08:59 0:02:11 Incoming +Nathalie Paton 15 sept. 2015 15:58 0:00:19 Outgoing +33662140379 30 sept. 2015 17:28 0:00:49 Incoming +Caroline Datchary 28 sept. 2015 12:30 0:00:15 Incoming +33637593137 30 sept. 2015 18:14 0:02:31 Incoming +33621015169 28 août 2015 19:40 0:00:20 Outgoing +(056) 133-6112 8 juil. 2015 14:13 0:00:39 Outgoing +(056) 122-1228 2 juil. 2015 15:59 0:00:18 Outgoing +330977401069 16 juin 2015 14:21 0:00:29 Incoming +Nathalie Paton 2 oct. 2015 11:37 0:00:00 Missed +33605181813 30 sept. 2015 23:27 0:00:00 Missed +Ketty 12 juil. 2015 20:46 0:01:09 Outgoing +33621015169 18 août 2015 15:47 0:11:15 Outgoing +Jerry 17 juil. 2015 04:04 0:00:03 Outgoing +33621015169 19 sept. 2015 15:29 0:01:20 Incoming +Nathalie Paton 2 sept. 2015 18:50 0:13:23 Outgoing +Mam Portable 11 juil. 2015 11:41 0:00:00 Missed +Nathalie Paton 30 sept. 2015 10:37 0:00:04 Outgoing +33621015169 4 juil. 2015 22:24 0:02:19 Incoming +33621015169 18 sept. 2015 19:55 0:01:13 Incoming +(080) 012-3333 13 juil. 2015 08:10 0:01:32 Outgoing +33621015169 19 sept. 2015 19:05 0:00:52 Outgoing +33621015169 14 juil. 2015 17:35 0:00:04 Outgoing +Agence Carmes 21 sept. 2015 12:38 0:05:06 Incoming +(060) 518-1813 30 sept. 2015 23:24 0:00:08 Outgoing +Fred Port 18 juin 2015 18:55 0:06:30 Outgoing +Muriel 14 juin 2015 16:01 0:00:00 Canceled +Ketty 19 juin 2015 15:18 0:02:42 Incoming +33621015169 19 sept. 2015 20:43 0:00:39 Outgoing +33621015169 11 juin 2015 16:28 0:00:04 Outgoing +33621015169 7 sept. 2015 16:27 0:04:39 Incoming +Roux Sébastien 3 août 2015 16:00 0:00:14 Incoming +33621015169 26 août 2015 14:58 0:04:24 Outgoing +Pierre Ratinaud 27 juil. 2015 05:21 0:00:00 Missed +Muriel 13 juin 2015 17:18 0:00:00 Missed +33621015169 1 août 2015 19:14 0:00:23 Incoming +Guillaume Cavanac 23 sept. 2015 23:15 0:00:02 Outgoing +33621015169 6 sept. 2015 16:12 0:00:00 Canceled +Caroline Datchary 7 juil. 2015 17:53 0:01:35 Incoming +Mam Portable 14 août 2015 19:50 0:00:00 Canceled +Agence Carmes 18 sept. 2015 15:14 0:02:33 Incoming +33625326210 2 oct. 2015 11:06 0:00:00 Missed +Marcolès 26 juin 2015 18:33 0:00:02 Outgoing +Ketty 23 juin 2015 17:33 0:04:08 Incoming +Fred Port 13 août 2015 10:25 0:00:00 Canceled +Ketty 12 juil. 2015 19:05 0:01:37 Incoming +Nathalie Paton 24 août 2015 09:48 0:07:21 Incoming +Orpi 30 sept. 2015 10:50 0:00:51 Outgoing +33621015169 2 août 2015 10:48 0:00:00 Canceled +Ketty 3 juil. 2015 18:39 0:00:04 Outgoing +Nathalie Paton 1 sept. 2015 12:11 0:00:00 Missed +Fred Port 18 juin 2015 19:03 0:11:33 Incoming +Audrey LUC 3 sept. 2015 15:03 0:03:56 Outgoing +Orpi 30 sept. 2015 10:54 0:01:54 Incoming +Johann Chaulet 1 oct. 2015 11:49 0:00:24 Incoming +Marcolès 28 août 2015 19:26 0:00:04 Outgoing +33621015169 14 août 2015 12:38 0:00:23 Incoming +33621015169 25 août 2015 15:21 0:24:07 Outgoing +Ketty 12 juin 2015 18:34 0:01:38 Incoming +Johann Chaulet 24 sept. 2015 23:18 0:00:00 Missed +Nathalie Paton 10 juin 2015 19:51 0:00:05 Outgoing +Fred Port 17 juin 2015 19:08 0:00:37 Incoming +Ketty 19 juin 2015 15:30 0:07:48 Outgoing +33621015169 26 août 2015 16:45 0:00:37 Outgoing +Michèle Figeac 10 juil. 2015 19:14 0:00:00 Missed +33603023200 30 sept. 2015 13:04 0:00:00 Missed +33603023200 30 sept. 2015 17:14 0:00:00 Missed +Stanley 19 sept. 2015 17:15 0:01:53 Incoming +François 15 août 2015 15:37 0:01:52 Outgoing +33621015169 14 août 2015 13:31 0:00:05 Outgoing +Agence Carmes 29 sept. 2015 18:02 0:00:48 Incoming +33621015169 5 sept. 2015 20:40 0:00:19 Outgoing +33621015169 28 août 2015 12:55 0:04:05 Incoming +33621015169 12 juil. 2015 16:00 0:01:58 Incoming +Melanie 2 juil. 2015 15:07 0:00:00 Canceled +Johann Chaulet 10 sept. 2015 17:14 0:01:44 Incoming +Marcolès 3 juil. 2015 18:59 0:00:05 Outgoing +Ketty 12 juil. 2015 18:22 0:00:00 Missed +33621015169 20 juin 2015 13:24 0:05:10 Incoming +Nathalie Paton 28 sept. 2015 11:48 0:00:22 Incoming +33546305800 28 juil. 2015 10:56 0:00:00 Missed +33621015169 12 sept. 2015 14:50 0:02:15 Incoming +Stanley 21 sept. 2015 14:55 0:00:00 Missed +Melanie 4 juil. 2015 20:59 0:17:31 Incoming +Caroline Datchary 4 sept. 2015 11:38 0:07:07 Incoming +Zaf immobilier Rachel 14 sept. 2015 08:47 0:01:35 Incoming +Stanley 11 sept. 2015 09:36 0:00:18 Incoming +Ketty 12 juil. 2015 19:03 0:00:06 Outgoing +33637201233 30 sept. 2015 09:51 0:03:55 Incoming +Marcolès 3 juil. 2015 19:14 0:16:19 Outgoing +Yan 22 août 2015 17:49 0:08:08 Outgoing +33648821791 14 sept. 2015 15:47 0:00:49 Outgoing +33681741444 9 juin 2015 19:41 0:00:00 Canceled +33621015169 6 juil. 2015 14:05 0:11:34 Incoming +Mam Portable 21 juin 2015 18:42 0:00:05 Outgoing +Caroline Datchary 1 sept. 2015 17:37 0:02:11 Incoming +33562278503 23 juin 2015 16:48 0:00:20 Incoming +Stanley 21 sept. 2015 16:35 0:01:06 Outgoing +(056) 122-1228 2 juil. 2015 15:58 0:00:00 Canceled +Nathalie Paton 2 oct. 2015 12:41 0:02:56 Outgoing +Marcolès 12 sept. 2015 11:46 0:02:10 Outgoing +33621015169 14 sept. 2015 13:49 0:03:53 Incoming +Marcolès 19 juin 2015 18:46 0:00:03 Outgoing +Zaf immobilier Rachel 16 sept. 2015 09:07 0:00:19 Incoming +Roux Sébastien 3 août 2015 16:13 0:00:06 Incoming +Ketty 11 juin 2015 18:47 0:01:16 Incoming +33615577149 10 sept. 2015 10:16 0:01:48 Incoming +Roux Sébastien 9 juin 2015 20:09 0:00:00 Canceled +Marcolès 26 juin 2015 18:58 0:02:59 Outgoing +Fred Port 13 août 2015 10:26 0:00:00 Canceled +33140251075 11 juin 2015 16:49 0:00:00 Missed +33621015169 17 juin 2015 16:43 0:10:40 Incoming +33781344967 25 sept. 2015 19:29 0:02:13 Incoming +Marcolès 12 sept. 2015 11:49 0:07:26 Outgoing +33178569562 16 juin 2015 12:56 0:00:20 Incoming +33621015169 1 oct. 2015 10:28 0:08:47 Incoming +33621015169 5 sept. 2015 17:51 0:00:06 Outgoing +33516195265 21 juil. 2015 15:59 0:00:13 Incoming +Michèle Figeac 1 août 2015 19:09 0:04:32 Incoming +Roux Sébastien 10 juin 2015 17:21 0:02:39 Outgoing +33621015169 10 sept. 2015 14:19 0:00:27 Outgoing +33681741444 9 juin 2015 19:40 0:00:00 Canceled +Fred Port 22 août 2015 16:15 0:01:01 Outgoing +(056) 122-1141 6 août 2015 19:16 0:00:24 Outgoing +33621015169 15 sept. 2015 08:48 0:00:04 Incoming +33621015169 28 août 2015 12:50 0:00:04 Outgoing +33617782999 30 sept. 2015 15:17 0:00:22 Incoming +Caroline Datchary 31 août 2015 11:42 0:07:10 Incoming +33634306948 10 sept. 2015 13:16 0:00:00 Missed +33621015169 9 juil. 2015 13:43 0:07:17 Incoming +33621015169 19 sept. 2015 15:20 0:01:49 Outgoing +33561102030 1 sept. 2015 14:40 0:00:57 Incoming +Agence Carmes 10 sept. 2015 17:45 0:01:05 Incoming +Marcolès 19 juin 2015 18:46 0:00:06 Outgoing +(056) 227-8505 23 juin 2015 15:35 0:01:12 Outgoing +(080) 012-3333 13 juil. 2015 08:12 0:01:04 Outgoing +33634018991 1 oct. 2015 16:09 0:00:31 Incoming +Mam Portable 21 juin 2015 18:48 0:00:04 Outgoing +Mam Portable 11 juil. 2015 11:47 0:00:04 Outgoing +(056) 227-8585 23 juin 2015 15:34 0:00:00 Canceled +33621015169 26 sept. 2015 16:48 0:01:19 Incoming +Veronique 7 sept. 2015 20:14 0:09:51 Incoming +33546305800 24 juil. 2015 11:53 0:00:00 Missed +Ketty 9 juin 2015 19:39 0:01:21 Outgoing +Fred Port 17 juin 2015 18:02 0:00:07 Outgoing +33625251503 9 sept. 2015 17:52 0:00:00 Missed +Agence Carmes 10 sept. 2015 10:40 0:04:31 Incoming +33489998877 3 sept. 2015 18:18 0:00:00 Missed +Prescilia 1 oct. 2015 15:45 0:02:10 Incoming +Muriel 13 juin 2015 19:16 0:00:00 Canceled +33621015169 3 juil. 2015 10:21 0:14:58 Incoming +Mam Portable 26 juin 2015 19:02 0:19:51 Outgoing +Yan 21 août 2015 12:12 0:04:17 Outgoing +Melanie 4 juil. 2015 22:47 0:36:02 Incoming +Pierre Ratinaud 29 juil. 2015 00:56 0:00:01 Outgoing +Michèle Figeac 16 août 2015 14:22 0:00:00 Missed +33621015169 4 juil. 2015 18:32 0:01:12 Incoming +33781344967 25 sept. 2015 19:45 0:01:07 Outgoing +Nathalie Paton 28 sept. 2015 14:28 0:08:43 Outgoing +Folio 30 sept. 2015 16:19 0:05:09 Incoming +Agence Carmes 29 sept. 2015 15:58 0:00:17 Incoming +Nathalie Paton 30 sept. 2015 10:34 0:03:18 Outgoing +Ketty 2 juil. 2015 19:05 0:00:00 Canceled +Mam Portable 18 sept. 2015 19:06 0:14:55 Incoming +Muriel 13 juin 2015 18:33 0:01:31 Outgoing +33621015169 20 sept. 2015 11:27 0:05:28 Outgoing +Caroline Datchary 1 juil. 2015 18:27 0:00:24 Incoming +33581335368 31 août 2015 15:35 0:00:06 Incoming +33626530608 14 sept. 2015 10:08 0:00:05 Outgoing +Fred Port 13 août 2015 10:25 0:00:00 Canceled +33140251075 11 juin 2015 16:46 0:00:17 Outgoing +33616712086 11 sept. 2015 11:13 0:01:00 Incoming +Mam Portable 6 sept. 2015 18:53 0:08:26 Incoming +Caroline Datchary 7 juil. 2015 17:55 0:03:28 Incoming +Nathalie Paton 29 sept. 2015 17:50 0:11:55 Outgoing +Mam Portable 19 juin 2015 18:47 0:00:12 Incoming +Mam Portable 6 sept. 2015 18:52 0:00:05 Outgoing +Fred Port 13 août 2015 15:58 0:00:30 Outgoing +33621015169 27 août 2015 09:16 0:01:30 Incoming +Roux Sébastien 10 juil. 2015 12:13 0:00:28 Incoming +(049) 105-6052 14 sept. 2015 11:41 0:04:40 Outgoing +33646883964 1 oct. 2015 10:49 0:02:38 Incoming +33147150303 10 sept. 2015 09:47 0:01:19 Incoming +33669004606 14 sept. 2015 18:00 0:01:16 Incoming +Mam Portable 19 juin 2015 18:47 0:00:09 Outgoing +33621015169 11 juin 2015 16:31 0:00:43 Incoming +Nathalie Paton 6 juil. 2015 16:36 0:04:33 Incoming +Fred Port 13 août 2015 10:24 0:00:00 Canceled +33621015169 3 sept. 2015 17:14 0:01:52 Incoming +33621015169 19 sept. 2015 20:46 0:00:08 Outgoing +33621015169 5 juil. 2015 20:08 0:00:00 Missed +33608577834 1 oct. 2015 09:26 0:00:00 Missed +(095) 453-3867 2 juil. 2015 15:58 0:00:04 Outgoing +33621015169 5 sept. 2015 18:32 0:01:39 Incoming +Agence Carmes 30 sept. 2015 11:05 0:02:43 Outgoing +Manu Larribeau 10 sept. 2015 19:05 0:00:05 Outgoing +Mam Portable 27 sept. 2015 19:27 0:08:39 Incoming +Marcolès 26 juin 2015 18:56 0:00:55 Outgoing +Mam Portable 21 août 2015 12:00 0:05:28 Incoming +Ketty 2 juil. 2015 19:05 0:00:00 Canceled +33621015169 9 juil. 2015 13:36 0:05:23 Outgoing +33621015169 9 sept. 2015 18:51 0:05:47 Incoming +33672528551 30 sept. 2015 18:22 0:00:53 Incoming +Caroline Datchary 22 sept. 2015 20:12 0:00:42 Incoming +33180407381 29 juin 2015 09:56 0:00:00 Canceled +33970734659 23 sept. 2015 14:58 0:00:00 Missed +Guillaume Cavanac 23 sept. 2015 23:15 0:00:05 Outgoing +33621015169 20 sept. 2015 11:41 0:00:00 Canceled +Nathalie Paton 25 août 2015 14:14 0:23:00 Incoming +Marcolès 6 sept. 2015 18:51 0:00:02 Outgoing +Marcolès 12 juin 2015 18:37 0:26:34 Outgoing +Marcolès 19 juin 2015 18:47 0:00:02 Outgoing +Marcolès 29 sept. 2015 18:17 0:02:54 Outgoing +Caroline Datchary 8 sept. 2015 15:43 0:02:33 Incoming +Agence Carmes 29 sept. 2015 14:12 0:01:00 Incoming +Mam Portable 29 juin 2015 18:51 0:01:30 Incoming +Caroline Datchary 23 juin 2015 15:59 0:00:00 Missed +33683142640 30 sept. 2015 12:47 0:00:00 Missed +33621015169 1 août 2015 17:24 0:15:32 Outgoing +3101 27 août 2015 10:00 0:03:00 Outgoing +Johann Chaulet 30 juil. 2015 19:44 0:00:07 Outgoing +3990 20 août 2015 13:34 0:09:34 Outgoing +Marcolès 10 août 2015 11:42 0:00:03 Outgoing +Jerry 17 juil. 2015 04:53 0:00:00 Canceled +33681741444 9 juin 2015 19:26 0:00:00 Canceled +Nathalie Paton 7 sept. 2015 14:00 0:22:29 Incoming +Mam Portable 21 juin 2015 19:25 0:02:16 Incoming +33621015169 26 sept. 2015 17:23 0:00:00 Missed +33621015169 24 août 2015 18:40 0:00:03 Outgoing +Muriel 13 juin 2015 18:31 0:00:00 Missed +33673884728 2 sept. 2015 16:00 1:13:40 Incoming +Ketty 13 juin 2015 19:07 0:00:33 Outgoing +33626530608 14 sept. 2015 10:06 0:00:00 Missed +33486120102 21 août 2015 10:25 0:00:00 Missed +Marcolès 11 sept. 2015 18:43 0:00:04 Outgoing +33621015169 16 sept. 2015 19:43 0:00:52 Incoming +33659046045 15 sept. 2015 17:33 0:00:41 Incoming +33621015169 2 oct. 2015 17:15 0:06:21 Incoming +Stanley 2 oct. 2015 18:54 0:00:40 Incoming +33601759845 3 oct. 2015 15:29 0:01:24 Incoming +Stanley 3 oct. 2015 15:41 0:00:00 Missed +Stanley 3 oct. 2015 16:17 0:01:26 Outgoing +33621015169 4 oct. 2015 15:28 0:01:30 Incoming +33621015169 4 oct. 2015 15:47 0:00:36 Outgoing +33672528551 5 oct. 2015 11:42 0:00:21 Incoming +33640134764 5 oct. 2015 14:14 0:01:20 Incoming +33621015169 5 oct. 2015 18:15 0:00:21 Incoming +33621015169 6 oct. 2015 09:18 0:01:53 Incoming +33561102030 6 oct. 2015 10:41 0:00:50 Incoming +sebastien.yann.roux@gmail.com 6 oct. 2015 19:48 0:00:00 Canceled +sebastien.yann.roux@gmail.com 6 oct. 2015 19:48 0:00:00 Canceled +Roux Sébastien 6 oct. 2015 19:50 0:00:11 Incoming +33621015169 7 oct. 2015 16:27 0:23:17 Outgoing +33625326210 8 oct. 2015 15:55 0:00:00 Missed +33625326210 8 oct. 2015 16:16 0:00:00 Canceled +33625326210 8 oct. 2015 16:20 0:01:28 Outgoing +Fred Port 8 oct. 2015 17:05 0:08:39 Outgoing +Caroline Datchary 8 oct. 2015 18:18 0:00:00 Missed +Caroline Datchary 8 oct. 2015 18:23 0:00:43 Outgoing +Guillaume Cavanac 9 oct. 2015 11:40 0:00:00 Canceled +Guillaume Cavanac 9 oct. 2015 11:41 0:02:01 Outgoing +Guillaume Cavanac 9 oct. 2015 11:47 0:00:00 Canceled +Guillaume Cavanac 9 oct. 2015 11:48 0:00:23 Outgoing +Pascal Ducournau 9 oct. 2015 12:54 0:00:00 Missed +33621015169 9 oct. 2015 17:19 0:00:00 Missed +33621015169 9 oct. 2015 20:05 0:00:27 Incoming +Marcolès 10 oct. 2015 12:08 0:04:55 Outgoing +33621015169 10 oct. 2015 13:49 0:00:04 Outgoing +33621015169 10 oct. 2015 13:49 0:06:18 Incoming +33621015169 10 oct. 2015 19:09 0:02:10 Incoming +33621015169 11 oct. 2015 14:10 0:02:28 Outgoing +Nathalie Paton 12 oct. 2015 13:12 0:00:00 Missed +Nathalie Paton 12 oct. 2015 13:19 0:00:35 Outgoing +Guillaume Cavanac 12 oct. 2015 17:32 0:00:03 Outgoing +Agence Carmes 12 oct. 2015 18:23 0:00:00 Missed +Guillaume Cavanac 12 oct. 2015 18:22 0:03:16 Incoming +Agence Carmes 12 oct. 2015 18:26 0:00:06 Outgoing +Agence Carmes 12 oct. 2015 18:31 0:03:55 Incoming +Nathalie Paton 13 oct. 2015 11:08 0:01:11 Incoming +33621015169 13 oct. 2015 12:00 0:00:25 Incoming +Nathalie Paton 13 oct. 2015 16:07 0:00:40 Incoming +Nathalie Paton 13 oct. 2015 22:32 0:00:07 Outgoing +Nathalie Paton 13 oct. 2015 22:33 0:00:07 Outgoing +Johann Chaulet 13 oct. 2015 22:34 0:00:29 Outgoing +33627474109 14 oct. 2015 10:25 0:00:00 Missed +33627474109 14 oct. 2015 10:59 0:00:00 Canceled +33627474109 14 oct. 2015 11:00 0:00:31 Outgoing +33627474109 14 oct. 2015 11:13 0:00:36 Incoming +33562757676 14 oct. 2015 15:05 0:00:00 Missed +33562757676 14 oct. 2015 15:11 0:00:10 Outgoing +(061) 557-7149 14 oct. 2015 15:14 0:02:01 Outgoing +06.73.88.47.28 14 oct. 2015 18:22 0:00:00 Canceled +Nathalie Paton 15 oct. 2015 16:59 0:01:17 Outgoing +Stanley 15 oct. 2015 18:34 0:00:00 Missed +Stanley 16 oct. 2015 12:15 0:00:44 Incoming +33621015169 16 oct. 2015 17:25 0:01:40 Outgoing +(044) 164-7499 17 oct. 2015 11:59 0:00:00 Canceled +Marcolès 17 oct. 2015 12:00 0:18:52 Outgoing +33621015169 17 oct. 2015 15:24 0:05:13 Outgoing +Agence Carmes 17 oct. 2015 19:09 0:02:17 Incoming +33621015169 18 oct. 2015 03:01 0:09:08 Incoming +33621015169 18 oct. 2015 11:36 0:00:05 Outgoing +33673884728 18 oct. 2015 18:04 0:01:42 Incoming +Caroline Datchary 19 oct. 2015 09:35 0:06:59 Incoming +33621015169 19 oct. 2015 15:55 0:08:29 Incoming +Roux Sébastien 19 oct. 2015 16:45 0:00:09 Outgoing +Roux Sébastien 20 oct. 2015 19:23 0:00:02 Outgoing +Roux Sébastien 20 oct. 2015 19:25 0:00:16 Incoming +33562275560 21 oct. 2015 15:43 0:00:00 Missed +Nathalie Paton 22 oct. 2015 15:07 0:01:19 Incoming +Nathalie Paton 22 oct. 2015 15:14 0:02:39 Outgoing +Nathalie Paton 22 oct. 2015 15:32 0:00:00 Missed +Nathalie Paton 22 oct. 2015 15:34 0:00:13 Outgoing +33661083146 22 oct. 2015 15:39 0:01:33 Incoming +Dorothy PATON 22 oct. 2015 18:12 0:00:00 Missed +Dorothy PATON 22 oct. 2015 18:15 0:02:50 Outgoing +33621015169 22 oct. 2015 19:46 0:00:52 Incoming +33561102030 23 oct. 2015 12:55 0:00:52 Incoming +33562275564 23 oct. 2015 16:38 0:00:54 Incoming +33621015169 23 oct. 2015 18:05 0:03:12 Incoming +Marcolès 23 oct. 2015 18:49 0:00:46 Outgoing +Mam Portable 23 oct. 2015 19:09 0:09:09 Incoming +33689088797 23 oct. 2015 21:32 0:00:35 Incoming +33621015169 24 oct. 2015 10:12 0:00:42 Outgoing +Agence Carmes 24 oct. 2015 17:52 0:05:04 Incoming +Soraya 24 oct. 2015 20:38 0:01:43 Outgoing +Johann Chaulet 26 oct. 2015 10:18 0:00:12 Incoming +Fred Port 27 oct. 2015 10:53 0:01:08 Incoming +33603023200 27 oct. 2015 11:29 0:00:43 Incoming +33646438967 27 oct. 2015 17:56 0:02:44 Incoming +Mam Portable 28 oct. 2015 09:04 0:03:05 Incoming +Stanley 28 oct. 2015 13:35 0:02:00 Incoming +Fred Port 28 oct. 2015 16:17 0:01:04 Outgoing +Folio 29 oct. 2015 10:24 0:00:00 Missed +Folio 29 oct. 2015 10:59 0:00:44 Outgoing +Folio 29 oct. 2015 11:21 0:02:00 Incoming +Nathalie Paton 29 oct. 2015 11:35 0:13:48 Incoming +33621015169 29 oct. 2015 14:58 0:10:14 Incoming +Nexity Buzon 30 oct. 2015 14:25 0:06:43 Outgoing +Folio 30 oct. 2015 19:15 0:00:00 Canceled +Nathalie Paton 30 oct. 2015 19:31 0:08:29 Incoming +Mam Portable 31 oct. 2015 09:44 0:00:00 Missed +Mam Portable 31 oct. 2015 12:33 0:03:24 Outgoing +Nathalie Paton 31 oct. 2015 14:33 0:03:05 Incoming +Agence Carmes 31 oct. 2015 16:37 0:05:30 Incoming +(056) 173-6073 2 nov. 2015 11:49 0:01:58 Outgoing +Zaf immobilier Rachel 2 nov. 2015 12:36 0:00:00 Missed +Zaf immobilier Rachel 2 nov. 2015 13:24 0:00:07 Outgoing +33616696888 2 nov. 2015 16:19 0:00:57 Incoming +33561832143 2 nov. 2015 17:36 0:00:00 Missed +33561832143 2 nov. 2015 17:53 0:01:19 Outgoing +Fred Port 2 nov. 2015 18:13 0:00:00 Missed +Fred Port 2 nov. 2015 18:22 0:00:06 Outgoing +Fred Port 2 nov. 2015 19:37 0:06:04 Incoming +Zaf immobilier Rachel 4 nov. 2015 09:38 0:01:29 Incoming +sebastien.yann.roux@gmail.com 4 nov. 2015 16:19 0:00:00 Canceled +Roux Sébastien 4 nov. 2015 16:19 0:00:31 Incoming +33648642597 4 nov. 2015 17:08 0:00:00 Missed +33660424535 4 nov. 2015 18:13 0:00:00 Missed +Agence Carmes 4 nov. 2015 19:02 0:00:00 Missed +Agence Carmes 4 nov. 2015 19:11 0:00:12 Outgoing +Orpi 5 nov. 2015 11:19 0:00:00 Missed +Caroline Datchary 5 nov. 2015 12:37 0:00:00 Canceled +Johann Chaulet 5 nov. 2015 12:38 0:00:14 Incoming +Caroline Datchary 5 nov. 2015 13:44 0:00:00 Missed +Fred Port 5 nov. 2015 14:47 0:00:37 Incoming +Caroline Datchary 5 nov. 2015 16:56 0:01:12 Incoming +Agence Carmes 5 nov. 2015 18:33 0:01:33 Incoming +Folio 5 nov. 2015 18:38 0:00:33 Outgoing +Nathalie Paton 5 nov. 2015 18:56 0:02:36 Incoming +33621015169 5 nov. 2015 19:11 0:00:00 Missed +33561102030 6 nov. 2015 10:01 0:00:00 Missed +Folio 6 nov. 2015 13:19 0:09:53 Incoming +Folio 6 nov. 2015 13:55 0:01:04 Incoming +Folio 6 nov. 2015 17:01 0:01:03 Incoming +Fred Port 6 nov. 2015 17:02 0:00:15 Outgoing +Fred Port 6 nov. 2015 19:49 0:00:14 Outgoing +Fred Port 6 nov. 2015 19:49 0:00:14 Incoming +33621015169 6 nov. 2015 20:20 0:00:42 Incoming +Marcolès 7 nov. 2015 18:18 0:00:02 Outgoing +Marcolès 8 nov. 2015 11:49 0:11:37 Outgoing +Soraya 8 nov. 2015 18:08 0:00:07 Outgoing +Soraya 8 nov. 2015 18:09 0:00:24 Incoming +33621015169 8 nov. 2015 19:29 0:00:00 Missed +Folio 9 nov. 2015 11:02 0:01:37 Incoming +Guillaume Favre 9 nov. 2015 12:23 0:00:26 Incoming +Nathalie Paton 9 nov. 2015 16:24 0:00:24 Outgoing +Nathalie Paton 9 nov. 2015 16:25 0:13:43 Outgoing +33646438967 9 nov. 2015 18:00 0:00:00 Missed +Fred Port 9 nov. 2015 19:42 0:00:00 Missed +33646438967 9 nov. 2015 20:59 0:00:00 Canceled +(053) 444-8125 10 nov. 2015 08:31 0:00:34 Outgoing +Zaf immobilier Rachel 10 nov. 2015 09:36 0:02:08 Incoming +33615506384 10 nov. 2015 11:05 0:00:00 Missed +33615506384 10 nov. 2015 11:27 0:01:53 Outgoing +33970240899 10 nov. 2015 11:56 0:01:37 Incoming +Fred Port 10 nov. 2015 13:15 0:05:51 Incoming +33646438967 10 nov. 2015 15:28 0:00:00 Missed +33646438967 10 nov. 2015 16:07 0:05:05 Outgoing +33615506384 10 nov. 2015 16:14 0:00:52 Outgoing +(061) 370-3040 10 nov. 2015 16:16 0:01:42 Outgoing +(056) 162-7455 10 nov. 2015 16:47 0:00:14 Outgoing +Soraya 10 nov. 2015 20:42 0:00:23 Outgoing +33621015169 11 nov. 2015 10:46 0:00:00 Missed +33615506384 11 nov. 2015 11:40 0:02:05 Incoming +33615506384 11 nov. 2015 11:44 0:00:45 Incoming +Agence Carmes 11 nov. 2015 12:04 0:00:12 Incoming +33777707192 12 nov. 2015 15:49 0:00:00 Missed +33621015169 12 nov. 2015 16:31 0:00:00 Missed +33621015169 12 nov. 2015 17:31 0:00:08 Outgoing +33621015169 12 nov. 2015 18:52 0:02:57 Incoming +Michele Lalanne 13 nov. 2015 13:57 0:00:33 Outgoing +Michele Lalanne 13 nov. 2015 14:11 0:00:00 Missed +Michele Lalanne 13 nov. 2015 14:13 0:08:20 Outgoing +33621015169 14 nov. 2015 11:37 0:03:54 Incoming +Caroline Datchary 15 nov. 2015 18:17 0:16:57 Incoming +Michèle Figeac 15 nov. 2015 18:35 0:06:10 Incoming +33615506384 16 nov. 2015 09:47 0:00:00 Missed +33615506384 16 nov. 2015 09:57 0:01:55 Outgoing +Nathalie Paton 16 nov. 2015 12:54 0:07:22 Incoming +33184729239 16 nov. 2015 13:54 0:00:29 Incoming +Zaf immobilier Rachel 17 nov. 2015 15:40 0:02:11 Incoming +Zaf immobilier Rachel 17 nov. 2015 16:51 0:00:00 Missed +Zaf immobilier Rachel 17 nov. 2015 17:05 0:00:00 Canceled +33615506384 17 nov. 2015 19:25 0:00:46 Incoming +Fred Port 17 nov. 2015 19:27 0:15:31 Incoming +33561102030 18 nov. 2015 10:36 0:02:08 Incoming +33615506384 18 nov. 2015 10:40 0:01:13 Outgoing +Zaf immobilier Rachel 18 nov. 2015 10:43 0:00:29 Outgoing +Zaf immobilier Rachel 18 nov. 2015 10:44 0:01:42 Incoming +33561102030 18 nov. 2015 10:46 0:02:24 Outgoing +Zaf immobilier Rachel 18 nov. 2015 12:03 0:00:00 Missed +Zaf immobilier Rachel 18 nov. 2015 12:04 0:00:00 Canceled +Zaf immobilier Rachel 18 nov. 2015 12:06 0:00:45 Outgoing +33561102030 18 nov. 2015 13:47 0:01:02 Incoming +Zaf immobilier Rachel 18 nov. 2015 13:49 0:00:59 Outgoing +33516195382 18 nov. 2015 16:33 0:00:00 Missed +33516195382 18 nov. 2015 16:44 0:00:05 Outgoing +Johann Chaulet 18 nov. 2015 19:38 0:00:00 Canceled +Caroline Datchary 18 nov. 2015 19:43 0:00:00 Missed +Caroline Datchary 18 nov. 2015 19:43 0:00:19 Outgoing +Johann Chaulet 19 nov. 2015 09:35 0:00:00 Canceled +Nathalie Paton 19 nov. 2015 10:35 0:00:05 Outgoing +33494582761 19 nov. 2015 10:44 0:09:07 Incoming +33494582761 19 nov. 2015 10:55 0:00:03 Outgoing +Nathalie Paton 19 nov. 2015 10:55 0:00:06 Outgoing +33494582761 19 nov. 2015 10:56 0:00:00 Canceled +33494582761 19 nov. 2015 10:57 0:09:02 Incoming +Caroline Datchary 19 nov. 2015 15:48 0:00:17 Incoming +33621015169 19 nov. 2015 19:03 0:17:56 Outgoing +33561102030 20 nov. 2015 09:49 0:00:00 Missed +(061) 370-3040 20 nov. 2015 09:58 0:00:40 Outgoing +33494582761 20 nov. 2015 12:54 0:00:00 Missed +33494582761 20 nov. 2015 14:59 0:07:04 Incoming +33621015169 20 nov. 2015 18:06 0:00:00 Missed +Agence Carmes 20 nov. 2015 18:24 0:00:00 Missed +Agence Carmes 20 nov. 2015 18:25 0:00:32 Incoming +33621015169 20 nov. 2015 18:38 0:01:06 Outgoing +Agence Carmes 20 nov. 2015 18:50 0:00:52 Incoming +33621015169 20 nov. 2015 18:59 0:00:00 Canceled +33621015169 20 nov. 2015 18:59 0:00:19 Outgoing +33621015169 20 nov. 2015 20:43 0:00:00 Canceled +33615506384 21 nov. 2015 00:08 0:00:00 Canceled +33561102030 21 nov. 2015 11:39 0:00:31 Incoming +33516195382 21 nov. 2015 12:56 0:00:00 Missed +33621015169 21 nov. 2015 18:09 0:04:30 Incoming +33494582761 21 nov. 2015 18:26 0:09:50 Incoming +33494582761 21 nov. 2015 18:36 0:10:30 Incoming +33788633602 21 nov. 2015 19:27 0:04:37 Incoming +(044) 164-7499 22 nov. 2015 12:03 0:00:00 Canceled +Marcolès 22 nov. 2015 12:04 0:21:24 Outgoing +Julien Figeac 22 nov. 2015 14:24 0:00:00 Canceled +33621015169 22 nov. 2015 14:55 0:00:19 Outgoing +33621015169 22 nov. 2015 14:56 0:00:09 Outgoing +33621015169 22 nov. 2015 15:05 0:01:21 Incoming +33621015169 22 nov. 2015 15:18 0:03:34 Incoming +33621015169 22 nov. 2015 15:29 0:00:28 Incoming +33686870048 23 nov. 2015 08:53 0:00:00 Missed +33777707192 23 nov. 2015 08:51 0:03:03 Incoming +33686870048 23 nov. 2015 08:55 0:03:20 Outgoing +33644249633 23 nov. 2015 09:59 0:00:00 Missed +33674476141 23 nov. 2015 10:41 0:02:25 Incoming +Caroline Agence Contact Ommobilier 23 nov. 2015 11:47 0:04:53 Incoming +33667142527 23 nov. 2015 11:54 0:00:38 Incoming +Stanley 23 nov. 2015 11:55 0:02:24 Incoming +33172816743 23 nov. 2015 13:02 0:00:09 Incoming +Tassel Independante 23 nov. 2015 13:11 0:03:13 Incoming +33621015169 23 nov. 2015 14:51 0:09:31 Incoming +33672528551 23 nov. 2015 16:16 0:05:35 Incoming +Visite 17h 23 nov. 2015 17:41 0:02:20 Incoming +33172816743 23 nov. 2015 17:50 0:00:06 Incoming +33676759793 23 nov. 2015 18:21 0:06:36 Incoming +33172816743 23 nov. 2015 19:35 0:00:06 Incoming +33621015169 24 nov. 2015 09:23 0:00:41 Incoming +33661083146 24 nov. 2015 10:15 0:00:48 Incoming +33172816743 24 nov. 2015 10:59 0:00:46 Incoming +Nathalie Paton 24 nov. 2015 12:07 0:08:57 Incoming +33778116236 24 nov. 2015 15:37 0:00:53 Incoming +33610710020 24 nov. 2015 16:08 0:04:32 Incoming +33785609526 25 nov. 2015 11:06 0:01:48 Incoming +33516195386 25 nov. 2015 11:09 0:00:09 Incoming +33672661333 25 nov. 2015 11:15 0:03:02 Incoming +Benoit Tudoux 25 nov. 2015 12:02 0:00:00 Missed +33676759793 25 nov. 2015 17:34 0:06:11 Incoming +33634018991 25 nov. 2015 18:06 0:02:38 Incoming +Nathalie Paton 25 nov. 2015 18:09 0:02:40 Incoming +33582052512 25 nov. 2015 18:18 0:03:45 Incoming +Nathalie Paton 25 nov. 2015 19:54 0:02:26 Outgoing +Nathalie Paton 26 nov. 2015 09:02 0:06:12 Incoming +Nathalie Paton 26 nov. 2015 09:13 0:00:00 Missed +(056) 227-2727 26 nov. 2015 10:43 0:06:08 Outgoing +Nathalie Paton 26 nov. 2015 10:56 0:01:12 Outgoing +Nathalie Paton 26 nov. 2015 14:20 0:04:36 Incoming +33785609526 26 nov. 2015 14:32 0:01:09 Incoming +33777251607 26 nov. 2015 16:46 0:01:18 Incoming +33516195386 27 nov. 2015 09:16 0:00:44 Incoming +Prescilia 27 nov. 2015 09:44 0:00:00 Missed +Prescilia 27 nov. 2015 09:45 0:01:20 Incoming +33184729239 27 nov. 2015 10:10 0:00:00 Missed +(098) 346-7056 27 nov. 2015 11:44 0:00:33 Outgoing +(056) 123-0663 27 nov. 2015 12:31 0:00:24 Outgoing +(056) 123-0663 27 nov. 2015 12:54 0:00:00 Canceled +(056) 123-0663 27 nov. 2015 12:54 0:00:00 Canceled +(056) 123-0663 27 nov. 2015 13:22 0:00:00 Canceled +(056) 123-0663 27 nov. 2015 13:22 0:00:25 Outgoing +Marcolès 27 nov. 2015 19:17 0:12:43 Outgoing +33621015169 27 nov. 2015 19:44 0:05:05 Incoming +33516195386 28 nov. 2015 10:03 0:00:16 Incoming +33663068767 29 nov. 2015 11:36 0:00:00 Missed +33663068767 29 nov. 2015 11:39 0:00:50 Outgoing +Nathalie Paton 30 nov. 2015 11:35 0:01:26 Incoming +Nathalie Paton 30 nov. 2015 11:48 0:03:59 Outgoing +33688049968 30 nov. 2015 14:48 0:00:00 Missed +33516195390 30 nov. 2015 18:49 0:00:00 Missed +33621015169 30 nov. 2015 20:41 0:10:02 Incoming +33621015169 30 nov. 2015 20:51 0:00:05 Outgoing +33621015169 30 nov. 2015 20:51 0:00:00 Missed +33621015169 30 nov. 2015 20:52 0:00:04 Outgoing +33621015169 30 nov. 2015 20:52 0:11:23 Outgoing +33777707192 1 déc. 2015 14:20 0:00:56 Incoming +33621015169 1 déc. 2015 17:05 0:01:54 Incoming +Caroline Datchary 2 déc. 2015 14:52 0:00:00 Missed +Nathalie Paton 2 déc. 2015 17:51 0:01:45 Incoming +33621015169 2 déc. 2015 19:36 0:06:24 Outgoing +Caroline Datchary 2 déc. 2015 19:52 0:18:33 Incoming +Nathalie Paton 2 déc. 2015 20:30 0:18:49 Outgoing +33621015169 3 déc. 2015 12:45 0:05:04 Incoming +33621015169 3 déc. 2015 17:02 0:00:00 Missed +33621015169 3 déc. 2015 17:04 0:02:36 Outgoing +33621015169 3 déc. 2015 17:24 0:01:07 Incoming +Marcolès 4 déc. 2015 19:10 0:18:06 Outgoing +Roux Sébastien 5 déc. 2015 15:26 0:00:00 Canceled +33621015169 5 déc. 2015 15:26 0:00:03 Outgoing +33621015169 5 déc. 2015 16:46 0:01:59 Incoming +Mam Portable 5 déc. 2015 18:37 0:01:37 Incoming +33621015169 5 déc. 2015 19:20 0:02:12 Incoming +33621015169 6 déc. 2015 12:20 0:03:35 Incoming +33621015169 7 déc. 2015 18:22 0:02:17 Incoming +Nathalie Paton 8 déc. 2015 13:33 0:00:00 Missed +Stanley 8 déc. 2015 18:29 0:01:13 Incoming +Caroline Datchary 8 déc. 2015 19:27 0:00:00 Missed +Caroline Datchary 8 déc. 2015 19:28 0:00:25 Outgoing +Theo Henri 9 déc. 2015 10:46 0:00:00 Missed +Theo Henri 9 déc. 2015 11:00 0:12:44 Outgoing +33621015169 9 déc. 2015 14:06 0:06:16 Incoming +Caroline Datchary 10 déc. 2015 10:43 0:01:04 Incoming +Michel Grossetti 10 déc. 2015 11:29 0:00:39 Incoming +33621015169 10 déc. 2015 16:19 0:00:00 Missed +33621015169 10 déc. 2015 16:24 0:00:09 Outgoing +33621015169 10 déc. 2015 16:28 0:02:12 Incoming +33621015169 10 déc. 2015 16:51 0:00:20 Outgoing +33615506384 11 déc. 2015 10:18 0:00:00 Missed +33615506384 11 déc. 2015 10:34 0:01:04 Outgoing +Nathalie Paton 11 déc. 2015 16:22 0:00:04 Outgoing +Nathalie Paton 11 déc. 2015 16:23 0:00:00 Missed +Nathalie Paton 11 déc. 2015 16:24 0:06:18 Outgoing +33621015169 11 déc. 2015 19:04 0:00:12 Outgoing +33621015169 11 déc. 2015 19:05 0:13:40 Outgoing +Stanley 12 déc. 2015 10:24 0:00:34 Outgoing +33621015169 12 déc. 2015 10:29 0:02:33 Outgoing +33621015169 12 déc. 2015 11:09 0:00:04 Outgoing +33621015169 12 déc. 2015 11:15 0:00:17 Incoming +Marcolès 12 déc. 2015 12:14 0:06:43 Outgoing +33621015169 12 déc. 2015 18:01 0:00:00 Missed +33621015169 13 déc. 2015 16:54 0:00:42 Incoming +33621015169 13 déc. 2015 17:07 0:00:04 Outgoing +Nathalie Paton 14 déc. 2015 14:02 0:04:36 Incoming +33621015169 15 déc. 2015 10:09 0:02:05 Incoming +33621015169 15 déc. 2015 19:44 0:00:05 Outgoing +33621015169 15 déc. 2015 19:50 0:00:00 Missed +33621015169 15 déc. 2015 19:50 0:00:31 Outgoing +33621015169 16 déc. 2015 13:33 0:06:27 Incoming +Johann Chaulet 17 déc. 2015 09:09 0:00:00 Missed +Johann Chaulet 17 déc. 2015 09:13 0:00:00 Missed +Johann Chaulet 17 déc. 2015 09:13 0:01:20 Outgoing +Johann Chaulet 17 déc. 2015 09:18 0:00:14 Outgoing +Johann Chaulet 17 déc. 2015 09:20 0:00:34 Outgoing +33619718038 17 déc. 2015 09:22 0:00:34 Incoming +Marc 17 déc. 2015 09:23 0:00:24 Incoming +Borderes 17 déc. 2015 15:10 0:01:44 Incoming +Guillaume Favre 18 déc. 2015 08:30 0:00:58 Incoming +Mam Portable 18 déc. 2015 12:15 0:02:06 Outgoing +33621015169 18 déc. 2015 12:25 0:01:16 Incoming +Mam Portable 18 déc. 2015 16:41 0:00:21 Outgoing +33621015169 18 déc. 2015 16:43 0:04:54 Incoming +Mam Portable 19 déc. 2015 11:03 0:00:05 Outgoing +Mam Portable 19 déc. 2015 11:03 0:00:26 Incoming +Visite 17h 19 déc. 2015 12:34 0:00:28 Outgoing +33621015169 19 déc. 2015 16:51 0:00:05 Outgoing +33621015169 19 déc. 2015 21:02 0:00:00 Missed +33621015169 21 déc. 2015 12:09 0:01:46 Outgoing +(056) 128-0247 21 déc. 2015 12:40 0:00:17 Outgoing +(056) 123-6838 21 déc. 2015 12:42 0:00:16 Outgoing +33621015169 21 déc. 2015 13:29 0:00:06 Outgoing +33621015169 21 déc. 2015 13:30 0:01:09 Incoming +Guillaume Favre 21 déc. 2015 15:16 0:00:06 Outgoing +33621015169 22 déc. 2015 01:25 0:07:41 Incoming +33621015169 22 déc. 2015 14:34 0:02:00 Incoming +Fred Port 23 déc. 2015 11:53 0:03:06 Outgoing +Fred Port 23 déc. 2015 15:43 0:00:17 Outgoing +Fred Port 23 déc. 2015 19:39 0:00:04 Outgoing +Fred Port 23 déc. 2015 19:40 0:08:24 Outgoing +Fred Port 24 déc. 2015 10:56 0:01:04 Incoming +Fred Port 24 déc. 2015 15:00 0:00:18 Outgoing +33621015169 26 déc. 2015 16:12 0:24:28 Outgoing +Fred Port 26 déc. 2015 17:54 0:00:04 Outgoing +Fred Port 26 déc. 2015 18:35 0:02:47 Incoming +Borderes 27 déc. 2015 16:44 0:00:00 Missed +33621015169 27 déc. 2015 19:07 0:00:00 Missed +33621015169 27 déc. 2015 19:09 0:00:00 Missed +33621015169 27 déc. 2015 19:06 0:03:06 Outgoing +Mam Portable 28 déc. 2015 10:46 0:00:14 Incoming +33621015169 28 déc. 2015 15:06 0:00:04 Outgoing +33621015169 28 déc. 2015 15:08 0:01:39 Incoming +33621015169 28 déc. 2015 15:31 0:00:39 Incoming +33671106789 29 déc. 2015 15:27 0:01:28 Incoming +33631749044 29 déc. 2015 16:02 0:04:33 Incoming +33698847832 29 déc. 2015 17:31 0:03:51 Incoming +33674170325 30 déc. 2015 10:44 0:03:11 Incoming +(068) 568-6535 30 déc. 2015 12:05 0:00:42 Outgoing +(056) 227-8484 30 déc. 2015 12:12 0:01:57 Outgoing +33674170325 30 déc. 2015 13:09 0:00:15 Incoming +33561992023 30 déc. 2015 13:32 0:03:01 Incoming +33621015169 30 déc. 2015 16:42 0:00:00 Missed +33661540902 30 déc. 2015 17:59 0:00:00 Missed +33621015169 30 déc. 2015 18:57 0:00:00 Missed +33621015169 30 déc. 2015 19:16 0:13:25 Outgoing +Nathalie Paton 31 déc. 2015 10:35 0:10:10 Incoming +Nathalie Paton 31 déc. 2015 10:45 0:05:14 Incoming +33631749044 31 déc. 2015 10:54 0:00:00 Missed +33631749044 31 déc. 2015 10:55 0:00:00 Missed +33631749044 31 déc. 2015 10:56 0:00:00 Missed +33631749044 31 déc. 2015 11:07 0:00:26 Incoming +33631749044 31 déc. 2015 11:10 0:00:31 Incoming +33621015169 31 déc. 2015 11:36 0:03:00 Outgoing +33621015169 31 déc. 2015 12:19 0:01:36 Outgoing +33621015169 31 déc. 2015 17:01 0:01:33 Outgoing +33621015169 31 déc. 2015 17:03 0:00:32 Incoming +Mam Portable 31 déc. 2015 17:13 0:09:31 Outgoing +33621015169 31 déc. 2015 17:55 0:00:14 Outgoing +(056) 122-4131 1 janv. 2016 12:19 0:00:00 Canceled +33621015169 1 janv. 2016 14:26 0:00:04 Outgoing +33621015169 1 janv. 2016 14:42 0:09:48 Incoming +Mam Portable 1 janv. 2016 17:41 0:01:57 Incoming +33621015169 2 janv. 2016 12:15 0:00:00 Missed +33621015169 2 janv. 2016 12:17 0:01:47 Outgoing +33621015169 2 janv. 2016 12:39 0:00:21 Outgoing +33621015169 3 janv. 2016 14:21 0:04:29 Outgoing +33661540902 4 janv. 2016 09:57 0:02:27 Incoming +33631749044 4 janv. 2016 10:21 0:01:16 Incoming +33675932124 4 janv. 2016 11:11 0:01:52 Incoming +(056) 125-3525 4 janv. 2016 11:35 0:00:50 Outgoing +(056) 125-3525 4 janv. 2016 12:24 0:04:37 Outgoing +33608016016 4 janv. 2016 12:29 0:00:00 Missed +33608016016 4 janv. 2016 12:29 0:02:01 Outgoing +(056) 125-3525 4 janv. 2016 12:43 0:00:44 Outgoing +33621015169 4 janv. 2016 14:52 0:03:02 Incoming +33608016016 4 janv. 2016 15:29 0:00:13 Incoming +Zaf immobilier Rachel 4 janv. 2016 15:50 0:00:00 Missed +(056) 125-3525 4 janv. 2016 15:49 0:01:06 Outgoing +Marcolès 4 janv. 2016 18:21 0:20:56 Outgoing +33679821966 5 janv. 2016 09:55 0:00:23 Incoming +(056) 362-1167 5 janv. 2016 15:48 0:02:43 Outgoing +33561253525 5 janv. 2016 18:34 0:01:29 Incoming +33660761349 6 janv. 2016 16:48 0:00:00 Missed +33621015169 6 janv. 2016 18:03 0:04:35 Outgoing +Marcolès 6 janv. 2016 18:36 0:00:00 Missed +Marcolès 6 janv. 2016 18:52 0:00:00 Missed +Marcolès 6 janv. 2016 18:53 0:00:02 Outgoing +Marcolès 6 janv. 2016 18:53 0:00:03 Outgoing +Marcolès 6 janv. 2016 18:53 0:00:00 Canceled +Marcolès 6 janv. 2016 18:53 0:02:40 Outgoing +33178569564 6 janv. 2016 19:33 0:00:07 Incoming +33621015169 7 janv. 2016 11:49 0:11:06 Incoming +33621015169 7 janv. 2016 15:17 0:08:57 Incoming +33562518936 7 janv. 2016 16:47 0:03:12 Incoming +Nathalie Paton 7 janv. 2016 19:26 0:00:00 Missed +Nathalie Paton 8 janv. 2016 11:41 0:02:30 Incoming +Nathalie Paton 8 janv. 2016 11:47 0:03:42 Outgoing +33621015169 8 janv. 2016 11:58 0:07:51 Outgoing +Amandine 8 janv. 2016 22:56 0:04:26 Outgoing +33621015169 9 janv. 2016 13:48 0:00:00 Canceled +33621015169 9 janv. 2016 13:48 0:00:04 Outgoing +33621015169 9 janv. 2016 13:50 0:04:16 Incoming +Mam Portable 9 janv. 2016 15:48 0:00:00 Missed +Mam Portable 9 janv. 2016 15:55 0:00:05 Outgoing +33621015169 9 janv. 2016 16:09 0:02:08 Incoming +Mam Portable 10 janv. 2016 11:59 0:00:00 Canceled +Marcolès 10 janv. 2016 11:59 0:08:57 Outgoing +33621015169 10 janv. 2016 14:31 0:00:19 Incoming +33787486962 10 janv. 2016 18:31 0:03:09 Incoming +Borderes 11 janv. 2016 16:32 0:00:44 Incoming +33621015169 11 janv. 2016 19:02 0:00:00 Missed +33621015169 11 janv. 2016 19:02 0:03:31 Outgoing +Marc 11 janv. 2016 21:49 0:00:35 Incoming +Borderes 12 janv. 2016 16:07 0:00:33 Outgoing +Borderes 12 janv. 2016 16:08 0:00:00 Missed +Borderes 12 janv. 2016 16:10 0:00:05 Incoming +Borderes 12 janv. 2016 16:10 0:00:00 Canceled +33678603139 12 janv. 2016 16:23 0:00:27 Incoming +33621015169 13 janv. 2016 21:57 0:49:32 Incoming +33617451002 14 janv. 2016 10:56 0:05:23 Incoming +33621015169 14 janv. 2016 11:17 0:00:00 Missed +33621015169 14 janv. 2016 11:51 0:02:30 Outgoing +33149542291 14 janv. 2016 15:32 0:02:46 Incoming +(056) 215-4000 14 janv. 2016 15:43 0:00:26 Outgoing +3990 15 janv. 2016 10:10 0:19:26 Incoming +3179 15 janv. 2016 10:29 0:00:25 Outgoing +33621015169 15 janv. 2016 16:15 0:00:03 Outgoing +33621015169 15 janv. 2016 16:29 0:04:03 Incoming +Thomas Mercier 15 janv. 2016 18:23 0:01:13 Incoming +Johann Chaulet 15 janv. 2016 18:39 0:04:32 Incoming +Marcolès 15 janv. 2016 18:47 0:00:55 Outgoing +Caroline Datchary 15 janv. 2016 19:04 0:01:44 Incoming +Caroline Datchary 15 janv. 2016 19:41 0:01:19 Outgoing +Thomas Mercier 15 janv. 2016 19:52 0:00:29 Incoming +Thomas Mercier 15 janv. 2016 19:55 0:00:02 Incoming +Caroline Datchary 15 janv. 2016 20:12 0:00:00 Missed +Caroline Datchary 15 janv. 2016 20:13 0:00:27 Outgoing +Caroline Datchary 15 janv. 2016 20:31 0:00:00 Canceled +3990 16 janv. 2016 09:51 0:02:37 Outgoing +(080) 880-7978 16 janv. 2016 09:53 0:00:58 Outgoing +Marcolès 16 janv. 2016 11:53 0:00:04 Outgoing +Marcolès 16 janv. 2016 11:53 0:00:02 Outgoing +33181664453 16 janv. 2016 13:06 0:00:03 Incoming +33181664453 16 janv. 2016 13:06 0:02:05 Incoming +33621015169 16 janv. 2016 14:26 0:00:04 Outgoing +33621015169 16 janv. 2016 14:40 0:00:00 Missed +33621015169 16 janv. 2016 14:41 0:08:58 Outgoing +Fred Port 16 janv. 2016 15:14 0:29:38 Outgoing +33621015169 17 janv. 2016 12:31 0:08:28 Outgoing +33980969915 17 janv. 2016 12:41 0:00:00 Missed +33980969915 17 janv. 2016 12:58 0:01:49 Incoming +33621015169 17 janv. 2016 13:07 0:00:35 Outgoing +33633816250 17 janv. 2016 16:24 0:00:40 Incoming +Marcolès 17 janv. 2016 18:19 0:00:03 Outgoing +Mam Portable 17 janv. 2016 18:20 0:18:08 Outgoing +33621015169 17 janv. 2016 18:55 0:00:00 Canceled +1099 18 janv. 2016 09:34 0:01:41 Outgoing +(056) 215-4000 18 janv. 2016 10:43 0:00:59 Outgoing +Julien Figeac 18 janv. 2016 10:47 0:07:59 Incoming +33621015169 18 janv. 2016 14:10 0:00:00 Missed +33621015169 18 janv. 2016 17:11 0:03:44 Outgoing +33621015169 18 janv. 2016 17:59 0:00:25 Outgoing +Fred Port 18 janv. 2016 18:32 0:06:13 Incoming +33666432050 19 janv. 2016 11:50 0:00:59 Incoming +Roux Sébastien 19 janv. 2016 19:15 0:00:58 Incoming +33621015169 21 janv. 2016 13:04 0:00:00 Missed +33621015169 21 janv. 2016 15:03 0:03:26 Outgoing +Julien Figeac 21 janv. 2016 15:07 0:00:00 Canceled +33621015169 23 janv. 2016 14:38 0:00:00 Missed +33621015169 23 janv. 2016 14:41 0:04:06 Outgoing +Borderes 23 janv. 2016 18:22 0:02:04 Incoming +33621015169 23 janv. 2016 18:41 0:14:56 Incoming +Marcolès 23 janv. 2016 18:58 0:09:17 Outgoing +33621015169 24 janv. 2016 14:21 0:00:00 Missed +33621015169 24 janv. 2016 14:33 0:00:00 Missed +Julien Figeac 26 janv. 2016 10:31 0:08:39 Incoming +1099 26 janv. 2016 11:46 0:01:35 Outgoing +1099 26 janv. 2016 11:48 0:21:48 Outgoing +331099 26 janv. 2016 12:10 0:06:11 Incoming +33153881942 26 janv. 2016 12:23 0:01:00 Incoming +33176455138 26 janv. 2016 12:57 0:00:06 Incoming +33621015169 26 janv. 2016 15:07 0:00:00 Missed +33621015169 26 janv. 2016 15:08 0:12:02 Outgoing +33609734397 27 janv. 2016 11:36 0:02:19 Incoming +33178569564 27 janv. 2016 13:59 0:02:24 Incoming +33673884728 27 janv. 2016 15:21 0:00:00 Missed +Marcolès 27 janv. 2016 18:15 0:12:06 Outgoing +33673884728 28 janv. 2016 10:34 0:19:17 Incoming +33519580028 28 janv. 2016 12:59 0:00:00 Missed +33621015169 28 janv. 2016 16:46 0:07:07 Incoming +33621015169 28 janv. 2016 19:08 0:00:53 Incoming +33621015169 29 janv. 2016 11:08 0:01:10 Incoming +(056) 177-7888 29 janv. 2016 11:26 0:00:56 Outgoing +(056) 162-3588 29 janv. 2016 11:31 0:00:19 Outgoing +Caroline Agence Contact Ommobilier 29 janv. 2016 11:39 0:00:00 Missed +(056) 362-1167 29 janv. 2016 11:38 0:01:02 Outgoing +Caroline Agence Contact Ommobilier 29 janv. 2016 11:44 0:00:00 Canceled +33621015169 29 janv. 2016 12:06 0:01:23 Outgoing +(056) 122-2222 29 janv. 2016 12:25 0:02:49 Outgoing +33621015169 29 janv. 2016 12:48 0:00:47 Outgoing +33621015169 29 janv. 2016 17:46 0:00:00 Missed +33621015169 29 janv. 2016 18:00 0:00:00 Missed +33621015169 29 janv. 2016 18:01 0:00:00 Missed +33621015169 29 janv. 2016 18:13 0:00:00 Missed +33621015169 29 janv. 2016 18:18 0:00:26 Outgoing +33621015169 29 janv. 2016 18:21 0:03:06 Incoming +(098) 164-8169 29 janv. 2016 21:20 0:01:32 Outgoing +(056) 162-8656 30 janv. 2016 10:50 0:00:31 Outgoing +Caroline Datchary 30 janv. 2016 11:37 0:01:23 Incoming +33783329611 30 janv. 2016 14:35 0:03:21 Incoming +33664843672 30 janv. 2016 17:31 0:00:27 Incoming +Caroline Datchary 30 janv. 2016 18:12 0:00:00 Missed +33621015169 30 janv. 2016 18:07 0:05:53 Incoming +Caroline Datchary 30 janv. 2016 18:17 0:01:05 Outgoing +Marcolès 30 janv. 2016 18:35 0:16:15 Outgoing +3990 31 janv. 2016 12:17 0:13:51 Outgoing +Fred Port 31 janv. 2016 16:09 0:08:40 Outgoing +Nathalie Paton 31 janv. 2016 16:36 0:00:08 Outgoing +Nathalie Paton 31 janv. 2016 19:19 0:06:39 Incoming +Marcolès 1 févr. 2016 18:15 0:04:29 Outgoing +33621015169 1 févr. 2016 19:25 0:00:00 Missed +33621015169 1 févr. 2016 20:02 0:17:29 Outgoing +(056) 227-2727 2 févr. 2016 09:37 0:03:36 Outgoing +33621015169 2 févr. 2016 12:38 0:00:42 Incoming +(096) 932-1515 2 févr. 2016 16:19 0:13:13 Outgoing +Nathalie Paton 3 févr. 2016 09:56 0:02:23 Incoming +Nathalie Paton 3 févr. 2016 10:09 0:02:11 Outgoing +Nathalie Paton 3 févr. 2016 10:23 0:00:48 Incoming +Guillaume Favre 3 févr. 2016 12:31 0:02:10 Incoming +(096) 932-1515 4 févr. 2016 09:38 0:07:25 Outgoing +33750932326 4 févr. 2016 12:54 0:00:00 Missed +33750932326 4 févr. 2016 12:55 0:00:00 Missed +33750932326 4 févr. 2016 13:00 0:00:52 Outgoing +33621015169 4 févr. 2016 13:50 0:00:38 Incoming +33667085496 4 févr. 2016 13:51 0:00:22 Incoming +33621015169 4 févr. 2016 14:00 0:06:17 Outgoing +33621015169 4 févr. 2016 16:19 0:00:00 Missed +Nathalie Paton 4 févr. 2016 16:38 0:01:42 Incoming +Nathalie Paton 4 févr. 2016 16:40 0:00:27 Incoming +33698285753 4 févr. 2016 21:32 0:00:00 Missed +33698285753 4 févr. 2016 21:34 0:01:42 Outgoing +33698285753 4 févr. 2016 21:44 0:00:19 Incoming +Nathalie Paton 5 févr. 2016 13:54 0:00:18 Incoming +Fred Port 5 févr. 2016 15:34 0:00:27 Outgoing +Nathalie Paton 5 févr. 2016 16:39 0:04:06 Outgoing +Johann Chaulet 5 févr. 2016 18:15 0:00:00 Missed +33621015169 5 févr. 2016 20:00 0:00:00 Missed +33621015169 6 févr. 2016 10:22 0:00:16 Incoming +33621015169 6 févr. 2016 11:36 0:00:17 Outgoing +33621015169 6 févr. 2016 12:45 0:00:00 Missed +33621015169 6 févr. 2016 12:45 0:01:26 Incoming +Fred Port 6 févr. 2016 13:04 0:00:06 Incoming +Fred Port 6 févr. 2016 13:37 0:00:00 Missed +Fred Port 6 févr. 2016 13:37 0:00:00 Missed +Fred Port 6 févr. 2016 15:09 0:00:06 Outgoing +Fred Port 6 févr. 2016 17:20 0:00:38 Outgoing +Caroline Datchary 6 févr. 2016 18:34 0:01:34 Incoming +33981896248 6 févr. 2016 21:05 0:00:25 Incoming +33981896248 6 févr. 2016 21:05 0:00:06 Outgoing +33981896248 6 févr. 2016 21:06 0:01:34 Incoming +33621015169 6 févr. 2016 21:07 0:00:09 Outgoing +33637556988 6 févr. 2016 21:14 0:00:17 Incoming +33621015169 7 févr. 2016 12:07 0:00:00 Missed +Fred Port 7 févr. 2016 12:07 0:00:00 Canceled +33621015169 7 févr. 2016 12:07 0:09:47 Outgoing +Marcolès 7 févr. 2016 12:18 0:07:53 Outgoing +33679331098 8 févr. 2016 09:25 0:00:18 Incoming +1023 8 févr. 2016 10:51 0:16:56 Outgoing +Marcolès 8 févr. 2016 11:30 0:00:00 Canceled +Bienvenue 8 févr. 2016 11:48 0:00:00 Canceled +(096) 936-3900 8 févr. 2016 11:51 0:00:27 Outgoing +33969363900 8 févr. 2016 11:52 0:00:26 Outgoing +1014 8 févr. 2016 11:59 0:04:54 Outgoing +(062) 101-5169 8 févr. 2016 13:44 0:04:13 Incoming +3990 8 févr. 2016 14:55 0:00:50 Outgoing +01023 8 févr. 2016 14:56 0:08:06 Incoming +3990 8 févr. 2016 15:04 0:02:44 Outgoing +3990 8 févr. 2016 15:07 0:15:51 Outgoing +Guillaume Favre 8 févr. 2016 16:37 0:00:57 Incoming +(053) 642-2233 8 févr. 2016 18:53 0:00:00 Missed +Bienvenue 8 févr. 2016 19:35 0:00:00 Canceled +01014 9 févr. 2016 10:12 0:02:01 Incoming +(062) 101-5169 9 févr. 2016 17:04 0:01:39 Incoming +(062) 101-5169 9 févr. 2016 17:12 0:00:18 Outgoing +Nathalie Paton 10 févr. 2016 09:36 0:14:31 Incoming +Guillaume Favre 10 févr. 2016 12:21 0:00:38 Incoming +Guillaume Favre 10 févr. 2016 12:23 0:00:24 Incoming +Nikos 10 févr. 2016 16:20 0:00:00 Missed +Nikos 10 févr. 2016 16:34 0:02:30 Outgoing +Nikos 10 févr. 2016 16:36 0:02:17 Outgoing +(062) 101-5169 10 févr. 2016 16:59 0:01:21 Incoming +Mam Portable 10 févr. 2016 19:00 0:00:05 Outgoing +Mam Portable 10 févr. 2016 19:28 0:00:34 Incoming +(076) 832-8025 11 févr. 2016 15:37 0:00:14 Incoming +33621015169 11 févr. 2016 16:45 0:00:07 Outgoing +(062) 101-5169 12 févr. 2016 16:34 0:09:53 Incoming +Mam Portable 12 févr. 2016 17:21 0:00:30 Incoming +05.61.32.69.55 12 févr. 2016 18:07 0:00:47 Outgoing +(065) 330-8800 13 févr. 2016 14:05 0:00:00 Missed +1023 14 févr. 2016 10:49 0:00:59 Outgoing +3979 14 févr. 2016 11:32 0:07:32 Outgoing +33621015169 14 févr. 2016 12:23 0:00:27 Outgoing +(062) 101-5169 14 févr. 2016 15:20 0:00:00 Missed +(062) 101-5169 14 févr. 2016 15:24 0:16:37 Outgoing +Audrey LUC 15 févr. 2016 10:46 0:03:48 Outgoing +1023 15 févr. 2016 12:15 0:08:22 Outgoing +Nathalie Paton 15 févr. 2016 14:03 0:09:45 Incoming +(062) 101-5169 15 févr. 2016 14:57 0:06:26 Incoming +Caroline Datchary 15 févr. 2016 18:38 0:07:51 Incoming +Guillaume Favre 16 févr. 2016 09:57 0:00:30 Outgoing +Guillaume Favre 16 févr. 2016 10:22 0:00:41 Incoming +(062) 101-5169 16 févr. 2016 16:25 0:02:24 Incoming +(076) 832-8025 17 févr. 2016 17:04 0:00:17 Incoming +(065) 331-1300 18 févr. 2016 10:46 0:00:43 Incoming +(056) 122-3303 18 févr. 2016 14:05 0:00:00 Missed +(056) 122-3303 18 févr. 2016 18:18 0:00:05 Outgoing +(062) 101-5169 18 févr. 2016 18:19 0:11:27 Outgoing +(065) 097-1953 19 févr. 2016 08:22 0:00:24 Incoming +(065) 097-1953 19 févr. 2016 08:55 0:00:17 Incoming +31041968 19 févr. 2016 10:00 0:00:00 Canceled +(056) 122-3303 19 févr. 2016 10:21 0:02:20 Outgoing +(062) 101-5169 19 févr. 2016 11:30 0:01:27 Incoming +(062) 101-5169 20 févr. 2016 16:32 0:03:09 Outgoing +(062) 101-5169 20 févr. 2016 19:33 0:00:53 Incoming +(062) 101-5169 20 févr. 2016 19:49 0:00:04 Outgoing +(062) 101-5169 20 févr. 2016 20:00 0:00:57 Incoming +33621015169 21 févr. 2016 13:26 0:00:04 Outgoing +(062) 101-5169 21 févr. 2016 15:20 0:10:21 Incoming +(062) 101-5169 21 févr. 2016 15:56 0:00:17 Incoming +Marcolès 21 févr. 2016 18:58 0:11:07 Outgoing +(081) 156-0561 22 févr. 2016 09:05 0:00:36 Incoming +(058) 118-0709 22 févr. 2016 12:54 0:01:46 Incoming +Borderes 23 févr. 2016 16:31 0:00:41 Incoming +(062) 101-5169 23 févr. 2016 17:43 0:36:25 Incoming +(062) 466-7744 24 févr. 2016 09:03 0:00:50 Incoming +(062) 466-7744 24 févr. 2016 12:30 0:00:07 Incoming +(078) 863-3602 24 févr. 2016 14:59 0:00:23 Incoming +(053) 442-8721 24 févr. 2016 15:13 0:00:56 Outgoing +(062) 101-5169 24 févr. 2016 20:12 0:08:09 Incoming +(056) 227-2727 25 févr. 2016 09:38 0:03:03 Outgoing +Nexity Noualhac 25 févr. 2016 09:48 0:00:05 Outgoing +(056) 273-6085 25 févr. 2016 09:48 0:00:00 Canceled +Nexity Buzon 25 févr. 2016 09:49 0:02:17 Outgoing +(062) 101-5169 26 févr. 2016 09:43 0:00:00 Missed +(062) 101-5169 26 févr. 2016 10:15 0:04:05 Outgoing +Melanie 26 févr. 2016 10:39 0:30:25 Incoming +Melanie 26 févr. 2016 11:12 0:00:00 Missed +Melanie 26 févr. 2016 11:11 0:35:24 Incoming +Nathalie Paton 26 févr. 2016 11:55 0:00:00 Missed +Melanie 26 févr. 2016 12:08 0:11:27 Incoming +(056) 271-5810 26 févr. 2016 13:59 0:02:34 Outgoing +(062) 101-5169 26 févr. 2016 16:51 0:00:57 Outgoing +3990 27 févr. 2016 10:26 0:00:00 Canceled +(096) 939-3990 27 févr. 2016 10:27 0:00:07 Outgoing +3990 27 févr. 2016 10:27 0:05:35 Outgoing +Marcolès 27 févr. 2016 11:57 0:10:53 Outgoing +(056) 230-0500 27 févr. 2016 19:57 0:00:00 Canceled +(056) 230-0500 27 févr. 2016 19:58 0:00:00 Canceled +(056) 230-0500 27 févr. 2016 19:58 0:00:00 Canceled +(056) 128-0247 27 févr. 2016 20:03 0:00:15 Outgoing +(056) 112-0060 27 févr. 2016 20:04 0:00:32 Outgoing +(056) 271-5810 29 févr. 2016 10:59 0:00:56 Outgoing +Anne Sauvageot 29 févr. 2016 11:43 0:08:53 Outgoing +33621015169 29 févr. 2016 16:34 0:00:31 Outgoing +(062) 101-5169 29 févr. 2016 18:33 0:04:10 Incoming +Fred Port 2 mars 2016 15:55 0:00:54 Outgoing +Nathalie Paton 3 mars 2016 10:58 0:05:46 Incoming +(062) 101-5169 3 mars 2016 14:43 0:00:04 Outgoing +33621015169 3 mars 2016 14:43 0:12:35 Incoming +Fred Port 4 mars 2016 09:53 0:00:05 Outgoing +François 4 mars 2016 09:54 0:01:33 Outgoing +Fred Port 4 mars 2016 10:11 0:02:08 Incoming +33621015169 4 mars 2016 15:06 0:00:03 Outgoing +(062) 101-5169 4 mars 2016 15:18 0:00:38 Incoming +(062) 101-5169 4 mars 2016 15:44 0:00:16 Incoming +(062) 101-5169 4 mars 2016 15:44 0:00:14 Incoming +(062) 101-5169 5 mars 2016 11:58 0:04:16 Incoming +Guillaume Favre 5 mars 2016 19:59 0:00:56 Outgoing +Guillaume Favre 5 mars 2016 20:01 0:00:06 Outgoing +Guillaume Favre 5 mars 2016 20:01 0:00:08 Outgoing +Guillaume Favre 5 mars 2016 20:02 0:00:37 Incoming +3979 6 mars 2016 12:42 0:10:59 Outgoing +(056) 224-9000 7 mars 2016 10:48 0:00:49 Outgoing +(056) 224-9000 7 mars 2016 10:51 0:00:59 Incoming +(016) 076-3878 8 mars 2016 12:35 0:00:00 Missed +(062) 101-5169 8 mars 2016 14:30 0:03:53 Incoming +(062) 101-5169 9 mars 2016 15:11 0:00:00 Missed +(062) 101-5169 9 mars 2016 15:11 0:00:16 Incoming +(062) 101-5169 9 mars 2016 17:33 0:00:35 Outgoing +(062) 101-5169 9 mars 2016 17:38 0:13:30 Incoming +(053) 642-2732 9 mars 2016 18:52 0:00:06 Incoming +(062) 101-5169 10 mars 2016 15:32 0:00:00 Missed +(068) 162-7708 10 mars 2016 15:32 0:01:32 Outgoing +(068) 162-7708 10 mars 2016 15:35 0:09:58 Incoming +(062) 101-5169 10 mars 2016 15:52 0:18:42 Outgoing +(056) 230-0235 10 mars 2016 19:14 0:00:00 Canceled +(056) 224-9000 11 mars 2016 10:03 0:20:24 Outgoing +(078) 281-8447 11 mars 2016 12:33 0:00:12 Incoming +33621015169 11 mars 2016 14:18 0:01:19 Outgoing +(062) 101-5169 11 mars 2016 15:19 0:01:55 Incoming +(062) 101-5169 11 mars 2016 15:21 0:00:00 Canceled +(062) 101-5169 11 mars 2016 15:22 0:01:15 Outgoing +(062) 101-5169 11 mars 2016 15:26 0:01:33 Incoming +(062) 101-5169 11 mars 2016 15:29 0:00:20 Incoming +(062) 101-5169 11 mars 2016 15:35 0:00:36 Outgoing +(062) 101-5169 11 mars 2016 16:33 0:00:21 Incoming +(016) 076-3878 11 mars 2016 18:23 0:00:00 Missed +(062) 101-5169 12 mars 2016 10:44 0:02:03 Incoming +(062) 101-5169 12 mars 2016 12:01 0:00:18 Outgoing +(062) 101-5169 12 mars 2016 18:31 0:11:32 Incoming +(062) 101-5169 12 mars 2016 19:50 0:01:06 Incoming +Melanie 12 mars 2016 20:11 0:18:07 Incoming +(062) 101-5169 12 mars 2016 20:49 0:01:18 Outgoing +Marcolès 13 mars 2016 18:36 0:08:08 Outgoing +(062) 101-5169 13 mars 2016 19:53 0:13:24 Incoming +Sylvain Parasie 14 mars 2016 14:44 0:06:09 Incoming +Nathalie Paton 15 mars 2016 10:45 0:10:38 Incoming +Nathalie Paton 15 mars 2016 14:46 0:15:23 Outgoing +(078) 351-5554 15 mars 2016 19:54 0:00:57 Incoming +Audrey LUC 16 mars 2016 11:15 0:00:00 Missed +Audrey LUC 16 mars 2016 11:17 0:00:38 Outgoing +(062) 101-5169 16 mars 2016 17:06 0:01:16 Incoming +(016) 076-5033 16 mars 2016 17:21 0:00:00 Missed +(062) 101-5169 16 mars 2016 18:55 0:09:34 Outgoing +Sylvain Parasie 16 mars 2016 19:56 0:00:30 Incoming +(062) 101-5169 17 mars 2016 09:53 0:00:00 Missed +(062) 101-5169 17 mars 2016 10:04 0:01:37 Outgoing +Johann Chaulet 17 mars 2016 17:04 0:00:17 Incoming +(016) 076-5033 17 mars 2016 18:38 0:00:03 Incoming +(062) 101-5169 17 mars 2016 20:00 0:30:09 Outgoing +(062) 101-5169 18 mars 2016 12:24 0:00:49 Incoming +(062) 101-5169 18 mars 2016 16:26 0:00:05 Outgoing +(062) 101-5169 18 mars 2016 16:38 0:01:49 Incoming +(016) 076-5033 18 mars 2016 17:49 0:00:00 Missed +(062) 101-5169 18 mars 2016 17:51 0:02:09 Outgoing +(036) 771-2930 18 mars 2016 17:57 0:00:05 Incoming +(062) 101-5169 18 mars 2016 18:50 0:01:46 Incoming +(062) 101-5169 19 mars 2016 19:22 0:02:08 Incoming +(062) 101-5169 20 mars 2016 11:30 0:17:31 Outgoing +Marcolès 20 mars 2016 11:48 0:09:29 Outgoing +(016) 076-3921 21 mars 2016 16:17 0:00:48 Incoming +(062) 101-5169 21 mars 2016 17:22 0:03:59 Incoming +(016) 076-3921 21 mars 2016 19:00 0:00:36 Incoming +(015) 590-5561 22 mars 2016 15:04 0:00:00 Missed +Unknown 22 mars 2016 19:52 0:00:00 Missed +(096) 932-4324 23 mars 2016 16:01 0:02:40 Outgoing +(016) 076-3921 24 mars 2016 11:44 0:00:15 Incoming +33621015169 24 mars 2016 15:37 0:14:31 Outgoing +33621015169 24 mars 2016 15:52 0:01:07 Outgoing +(062) 101-5169 25 mars 2016 15:12 0:00:00 Missed +(078) 058-6722 25 mars 2016 19:13 0:00:13 Outgoing +(078) 058-6722 25 mars 2016 19:18 0:01:08 Outgoing +(062) 101-5169 26 mars 2016 14:27 0:00:04 Outgoing +Marcolès 27 mars 2016 12:03 0:10:23 Outgoing +(053) 122-6440 29 mars 2016 17:08 0:00:27 Outgoing +(062) 101-5169 30 mars 2016 14:02 0:00:04 Outgoing +(062) 101-5169 30 mars 2016 14:31 0:06:08 Incoming +Nathalie Paton 30 mars 2016 17:23 0:06:12 Incoming +Nathalie Paton 31 mars 2016 11:16 0:19:33 Incoming +(062) 101-5169 31 mars 2016 12:12 0:00:00 Missed +(062) 101-5169 31 mars 2016 12:31 0:00:04 Outgoing +(062) 101-5169 31 mars 2016 12:31 0:06:52 Incoming +(062) 101-5169 31 mars 2016 12:40 0:00:10 Outgoing +Nathalie Paton 31 mars 2016 15:58 0:00:00 Missed +Nathalie Paton 31 mars 2016 17:52 0:07:17 Outgoing +(062) 101-5169 31 mars 2016 18:34 0:02:34 Outgoing +(097) 740-3390 31 mars 2016 18:37 0:00:00 Missed +Guillaume Favre 1 avr. 2016 12:26 0:00:52 Incoming +Guillaume Favre 1 avr. 2016 17:11 0:00:22 Incoming +Guillaume Favre 1 avr. 2016 17:23 0:00:33 Outgoing +(062) 101-5169 2 avr. 2016 15:23 0:01:11 Incoming +(062) 101-5169 2 avr. 2016 18:54 0:00:26 Outgoing +(062) 101-5169 2 avr. 2016 18:57 0:00:29 Incoming +(062) 101-5169 2 avr. 2016 19:56 0:01:30 Incoming +(062) 101-5169 3 avr. 2016 12:11 0:09:21 Incoming +(062) 101-5169 3 avr. 2016 15:42 0:00:39 Outgoing +Marcolès 3 avr. 2016 18:39 0:00:05 Outgoing +Mam Portable 3 avr. 2016 18:39 0:00:05 Outgoing +Mam Portable 3 avr. 2016 18:40 0:09:34 Incoming +Fanny 3 avr. 2016 19:33 0:00:00 Canceled +Fanny 3 avr. 2016 19:34 0:00:22 Outgoing +Guillaume Cavanac 4 avr. 2016 15:17 0:04:12 Incoming +Nikos 4 avr. 2016 16:16 0:02:03 Incoming +Unknown 4 avr. 2016 20:01 0:00:28 Incoming +(062) 101-5169 5 avr. 2016 18:38 0:00:03 Outgoing +(062) 101-5169 5 avr. 2016 18:43 0:07:17 Incoming +Roux Sébastien 5 avr. 2016 18:50 0:00:43 Incoming +(062) 101-5169 5 avr. 2016 18:52 0:00:27 Outgoing +(064) 860-3744 5 avr. 2016 19:50 0:00:00 Missed +Roux Sébastien 5 avr. 2016 21:09 0:01:01 Incoming +(056) 121-6716 6 avr. 2016 10:38 0:00:00 Missed +33561216716 6 avr. 2016 10:47 0:04:16 Outgoing +(017) 646-0224 6 avr. 2016 14:27 0:00:00 Missed +(062) 101-5169 6 avr. 2016 15:23 0:05:08 Incoming +(062) 101-5169 6 avr. 2016 18:46 0:10:55 Outgoing +Guillaume Cavanac 8 avr. 2016 09:24 0:02:15 Incoming +Nathalie Paton 8 avr. 2016 10:14 0:04:24 Incoming +(062) 101-5169 8 avr. 2016 21:12 0:11:27 Incoming +Caroline Datchary 9 avr. 2016 09:42 0:01:30 Incoming +Caroline Datchary 9 avr. 2016 09:44 0:01:47 Outgoing +Caroline Datchary 9 avr. 2016 12:15 0:00:56 Incoming +(062) 101-5169 10 avr. 2016 14:58 0:00:04 Outgoing +Marcolès 10 avr. 2016 18:39 0:15:47 Outgoing +(062) 101-5169 11 avr. 2016 10:25 0:31:21 Incoming +Mam Portable 11 avr. 2016 18:58 0:00:25 Incoming +Johann Chaulet 12 avr. 2016 12:49 0:00:14 Incoming +Roux Sébastien 12 avr. 2016 15:50 0:00:00 Missed +(062) 101-5169 12 avr. 2016 18:55 0:05:44 Outgoing +Roux Sébastien 12 avr. 2016 19:19 0:00:34 Incoming +(062) 101-5169 12 avr. 2016 19:30 0:01:03 Incoming +(062) 101-5169 12 avr. 2016 19:55 0:00:04 Outgoing +(062) 101-5169 12 avr. 2016 19:59 0:01:19 Incoming +Roux Sébastien 13 avr. 2016 18:32 0:00:19 Incoming +Roux Sébastien 13 avr. 2016 19:35 0:00:17 Incoming +Roux Sébastien 13 avr. 2016 19:37 0:00:15 Outgoing +(062) 101-5169 14 avr. 2016 14:13 0:00:00 Missed +(062) 101-5169 14 avr. 2016 18:18 0:00:04 Outgoing +(062) 101-5169 14 avr. 2016 18:36 0:08:11 Incoming +(062) 101-5169 15 avr. 2016 17:12 0:00:58 Outgoing +(062) 101-5169 15 avr. 2016 19:50 0:01:00 Incoming +(062) 101-5169 15 avr. 2016 20:18 0:00:22 Incoming +Roux Sébastien 16 avr. 2016 14:04 0:00:16 Incoming +Roux Sébastien 16 avr. 2016 17:07 0:00:08 Incoming +(062) 101-5169 16 avr. 2016 17:09 0:01:12 Incoming +Marcolès 17 avr. 2016 18:26 0:05:11 Outgoing +(056) 155-5949 18 avr. 2016 11:23 0:00:07 Outgoing +Guillaume Favre 18 avr. 2016 12:55 0:00:23 Incoming +Johann Chaulet 18 avr. 2016 13:45 0:00:16 Outgoing +Caroline Datchary 19 avr. 2016 10:37 0:00:00 Missed +Caroline Datchary 19 avr. 2016 11:05 0:04:05 Outgoing +(062) 101-5169 19 avr. 2016 15:32 0:00:05 Outgoing +(062) 101-5169 19 avr. 2016 15:51 0:01:59 Incoming +(062) 101-5169 19 avr. 2016 15:53 0:05:55 Outgoing +Caroline Datchary 21 avr. 2016 10:15 0:08:10 Incoming +Guillaume Favre 21 avr. 2016 18:02 0:08:50 Incoming +Melanie 22 avr. 2016 10:49 0:13:13 Incoming +Melanie 22 avr. 2016 11:13 0:02:12 Incoming +Melanie 22 avr. 2016 11:16 0:01:58 Outgoing +Melanie 22 avr. 2016 11:19 0:00:08 Outgoing +Melanie 22 avr. 2016 11:20 0:00:20 Incoming +Melanie 22 avr. 2016 11:32 0:00:03 Outgoing +Melanie 22 avr. 2016 12:46 0:00:02 Outgoing +Melanie 22 avr. 2016 12:54 0:15:03 Incoming +(062) 101-5169 22 avr. 2016 15:18 0:02:56 Outgoing +Roux Sébastien 22 avr. 2016 15:59 0:00:06 Incoming +(062) 101-5169 23 avr. 2016 13:13 0:13:49 Incoming +(056) 122-4795 23 avr. 2016 16:42 0:01:37 Outgoing +Melanie 23 avr. 2016 19:51 0:00:00 Missed +Melanie 24 avr. 2016 12:48 0:03:55 Incoming +Melanie 24 avr. 2016 12:56 0:24:46 Incoming +(062) 101-5169 24 avr. 2016 16:24 0:05:22 Incoming +Melanie 24 avr. 2016 19:21 0:28:35 Incoming +Melanie 25 avr. 2016 17:24 0:00:00 Missed +Melanie 25 avr. 2016 18:55 0:00:03 Outgoing +Melanie 25 avr. 2016 18:56 0:05:00 Incoming +Melanie 26 avr. 2016 17:48 0:00:15 Incoming +Melanie 26 avr. 2016 17:50 0:17:54 Incoming +(062) 101-5169 27 avr. 2016 13:30 0:00:04 Outgoing +(062) 101-5169 27 avr. 2016 13:31 0:00:32 Incoming +Melanie 27 avr. 2016 16:57 0:00:00 Missed +Melanie 27 avr. 2016 16:59 0:01:45 Incoming +Melanie 27 avr. 2016 19:04 0:04:52 Outgoing +(035) 909-5513 28 avr. 2016 11:52 0:01:05 Incoming +33621015169 28 avr. 2016 13:03 0:12:46 Outgoing +(062) 101-5169 28 avr. 2016 18:10 0:13:37 Incoming +Nathalie Paton 28 avr. 2016 18:39 0:04:57 Incoming +(062) 101-5169 29 avr. 2016 11:30 0:00:04 Outgoing +(062) 101-5169 29 avr. 2016 11:36 0:02:26 Incoming +Melanie 29 avr. 2016 12:39 0:05:54 Incoming +Melanie 29 avr. 2016 17:22 0:10:28 Incoming +Melanie 29 avr. 2016 17:49 0:02:11 Incoming +(062) 101-5169 29 avr. 2016 17:55 0:00:57 Incoming +(062) 101-5169 29 avr. 2016 19:52 0:01:21 Incoming +(062) 101-5169 29 avr. 2016 20:41 0:00:09 Incoming +(056) 217-6774 30 avr. 2016 12:07 0:00:12 Outgoing +(056) 153-7220 30 avr. 2016 12:17 0:00:11 Outgoing +(053) 443-5134 30 avr. 2016 12:32 0:00:43 Outgoing +(056) 113-1477 30 avr. 2016 15:15 0:00:26 Outgoing +Nathalie Paton 1 mai 2016 12:58 0:14:27 Incoming +Nathalie Paton 1 mai 2016 13:16 0:04:03 Incoming +Michèle Figeac 1 mai 2016 18:13 0:00:03 Outgoing +Michèle Figeac 1 mai 2016 18:13 0:00:02 Outgoing +Mam Portable 1 mai 2016 18:23 0:05:16 Outgoing +Melanie 1 mai 2016 18:36 0:44:54 Incoming +(014) 954-2291 2 mai 2016 14:07 0:01:04 Incoming +Nathalie Paton 2 mai 2016 17:14 0:02:06 Incoming +Nathalie Paton 2 mai 2016 17:17 0:00:46 Incoming +Nathalie Paton 2 mai 2016 17:18 0:00:00 Canceled +Nathalie Paton 2 mai 2016 17:19 0:00:00 Canceled +Nathalie Paton 2 mai 2016 19:06 0:03:52 Outgoing +Tristan Salord 4 mai 2016 10:06 0:02:20 Incoming +(056) 150-3700 4 mai 2016 10:17 0:00:40 Outgoing +Caroline Datchary 4 mai 2016 11:45 0:04:12 Incoming +(066) 096-7278 4 mai 2016 16:48 0:01:22 Outgoing +Mam Portable 4 mai 2016 19:05 0:00:21 Outgoing +(046) 731-5104 5 mai 2016 17:55 0:00:05 Outgoing +(046) 731-5104 5 mai 2016 17:57 0:00:11 Outgoing +Mam Portable 5 mai 2016 19:17 0:00:45 Incoming +Mam Portable 5 mai 2016 19:49 0:00:08 Incoming +(046) 731-5104 6 mai 2016 11:37 0:00:44 Outgoing +Tristan Salord 7 mai 2016 12:41 0:03:34 Outgoing +Fanny 7 mai 2016 16:13 0:00:00 Missed +Tristan Salord 7 mai 2016 19:28 0:00:16 Outgoing +(056) 153-7220 9 mai 2016 13:14 0:00:08 Outgoing +(056) 226-5303 9 mai 2016 13:23 0:00:24 Outgoing +Melanie 9 mai 2016 17:02 0:07:05 Incoming +(062) 101-5169 9 mai 2016 18:41 0:00:37 Incoming +(056) 226-5303 9 mai 2016 20:05 0:00:16 Outgoing +Tristan Salord 10 mai 2016 18:27 0:11:22 Outgoing +Melanie 11 mai 2016 13:05 0:00:00 Missed +Tristan Salord 11 mai 2016 17:50 0:02:36 Outgoing +Melanie 12 mai 2016 16:42 0:00:03 Outgoing +Melanie 12 mai 2016 16:48 0:08:24 Incoming +Tristan Salord 13 mai 2016 18:46 0:12:37 Outgoing +Tristan Salord 14 mai 2016 17:08 0:00:05 Outgoing +Tristan Salord 14 mai 2016 17:29 0:06:58 Incoming +Tristan Salord 15 mai 2016 00:35 0:00:40 Incoming +Melanie 15 mai 2016 11:39 0:51:14 Incoming +Guillaume Favre 16 mai 2016 15:40 0:00:07 Outgoing +Michèle Figeac 16 mai 2016 17:26 0:00:00 Missed +Marcolès 16 mai 2016 18:32 0:17:52 Outgoing +(056) 153-7220 17 mai 2016 12:00 0:00:13 Outgoing +(056) 226-5303 17 mai 2016 12:03 0:00:25 Outgoing +Tristan Salord 17 mai 2016 20:07 0:00:00 Missed +Tristan Salord 18 mai 2016 09:48 0:00:28 Outgoing +Tristan Salord 18 mai 2016 12:01 0:00:04 Outgoing +Nathalie Paton 18 mai 2016 12:14 0:00:08 Outgoing +Tristan Salord 18 mai 2016 12:39 0:00:00 Missed +Nathalie Paton 18 mai 2016 12:15 0:27:07 Incoming +Nathalie Paton 18 mai 2016 12:42 0:01:29 Incoming +Nathalie Paton 18 mai 2016 12:44 0:01:46 Incoming +Tristan Salord 18 mai 2016 12:49 0:00:04 Outgoing +Tristan Salord 18 mai 2016 13:07 0:00:06 Incoming +Tristan Salord 18 mai 2016 13:07 0:12:58 Outgoing +(081) 000-2181 18 mai 2016 14:37 0:03:37 Outgoing +Guillaume Favre 19 mai 2016 14:19 0:00:00 Missed +Guillaume Favre 19 mai 2016 14:20 0:00:00 Missed +Caroline Datchary 20 mai 2016 11:59 0:00:00 Canceled +Nathalie Paton 20 mai 2016 15:08 0:12:39 Incoming +Unknown 20 mai 2016 18:27 0:00:04 Incoming +Unknown 20 mai 2016 18:30 0:00:25 Incoming +Roux Sébastien 21 mai 2016 15:32 0:00:14 Incoming +Tristan Salord 22 mai 2016 11:17 0:19:01 Outgoing +Tristan Salord 22 mai 2016 12:08 0:01:29 Outgoing +Tristan Salord 22 mai 2016 16:30 0:01:36 Incoming +Marcolès 22 mai 2016 18:34 0:16:55 Outgoing +Tristan Salord 23 mai 2016 10:52 0:02:06 Incoming +(056) 289-2150 23 mai 2016 12:13 0:01:25 Incoming diff --git a/view/iexplorer-to-call-log.php b/view/iexplorer-to-call-log.php new file mode 100644 index 0000000..2513986 --- /dev/null +++ b/view/iexplorer-to-call-log.php @@ -0,0 +1,110 @@ +dispatch(); + + // Si erreur, on l'affiche + if( $response->error != ManagerError::Success ) + var_dump( ManagerError::explicit($response->error) ); + + // On récupère les données + $logs = $response->get('data'); + + /* [1] On associe un faux numéro à chaque utilisateur + =========================================================*/ + $falseNumbers = array(0); // Contiendra les numéros attribués + $alreadyNumbered = array(); // Contiendra aux mêmes indices que les numéros, les noms des personnes les ayant utilisé + + // Pour chaque utilisateur + foreach($logs as $i=>$log){ + + // On évite la première ligne + if( $i == 0 ){ + unset($logs[$i]); + continue; + } + + $numberIndex = array_search($log['Number'], $alreadyNumbered); + + /* (1) Si pas encore de numéro, on en attribue un */ + if( $numberIndex === false ){ + $lastnumIndex = count($falseNumbers) - 1; + $newNumber = ++$falseNumbers[$lastnumIndex]; + $numberIndex = array_push($falseNumbers, $newNumber ) - 1; + $alreadyNumbered[$numberIndex] = $log['Number']; + } + + $logs[$i]['Name'] = $logs[$i]['Number']; + $logs[$i]['Number'] = $falseNumbers[$numberIndex]; + } + + /* (2) On convertit les numéros en string */ + foreach($falseNumbers as $n=>$number){ + $stringNumber = "0600000000"; $snlen = strlen($stringNumber); + $stringValue = strval($number); $svlen = strlen($stringValue); + + $stringNumber = substr($stringNumber, 0, $snlen-$svlen); + $stringNumber .= $stringValue; + + $falseNumbers[$n] = $stringNumber; + $logs[$i]['Number'] = $falseNumbers[$numberIndex]; + } + + foreach($logs as $i=>$log){ + $numberIndex = array_search($log['Name'], $alreadyNumbered); + $logs[$i]['Number'] = $falseNumbers[$numberIndex]; + } + + /* [2] On rédige les headers + =========================================================*/ + header('Content-type: text/xml'); + + + /* [3] On rédige le contenu du fichier XML + =========================================================*/ + echo ''; + echo ''; + + // Pour chaque utilisateur + foreach($logs as $i=>$log) + echo ""; + + echo ""; + + // Si aucun fichier + }else{ ?> +
+ + +
+ From 4f27a0f4f9d962080ab8c0b80b4a08669401bd12 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Tue, 24 May 2016 08:59:33 +0200 Subject: [PATCH 11/12] minmod --- view/iexplorer-to-call-log.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/view/iexplorer-to-call-log.php b/view/iexplorer-to-call-log.php index 2513986..297f9d0 100644 --- a/view/iexplorer-to-call-log.php +++ b/view/iexplorer-to-call-log.php @@ -86,18 +86,19 @@ /* [2] On rédige les headers =========================================================*/ header('Content-type: text/xml'); + header('Content-Disposition: attachment; filename=call-log.xml'); /* [3] On rédige le contenu du fichier XML =========================================================*/ - echo ''; - echo ''; + echo "\n"; + echo "\n"; // Pour chaque utilisateur foreach($logs as $i=>$log) - echo ""; + echo "\n"; - echo ""; + echo "\n"; // Si aucun fichier }else{ ?> From 2b71b30aedde99046a0db30393928b5a09a7e3b7 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Tue, 24 May 2016 09:09:55 +0200 Subject: [PATCH 12/12] Modification lors des sModuleError's --- view/iexplorer-to-call-log.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/view/iexplorer-to-call-log.php b/view/iexplorer-to-call-log.php index 297f9d0..65d2f5e 100644 --- a/view/iexplorer-to-call-log.php +++ b/view/iexplorer-to-call-log.php @@ -32,8 +32,10 @@ $response = $request->dispatch(); // Si erreur, on l'affiche - if( $response->error != ManagerError::Success ) + if( $response->error != ManagerError::Success ){ var_dump( ManagerError::explicit($response->error) ); + exit(); + } // On récupère les données $logs = $response->get('data');