s?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0;
+if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}();
\ No newline at end of file
diff --git a/silk/static/silk/lib/distrochart.css b/silk/static/silk/lib/distrochart.css
new file mode 100644
index 00000000..7b8eef8e
--- /dev/null
+++ b/silk/static/silk/lib/distrochart.css
@@ -0,0 +1,142 @@
+/*Primary Chart*/
+
+/*Nested divs for responsiveness*/
+.chart-wrapper {
+ max-width: 800px; /*Overwritten by the JS*/
+ min-width: 304px;
+ margin-bottom: 8px;
+ background-color: #FAF7F7;
+}
+.chart-wrapper .inner-wrapper {
+ position: relative;
+ padding-bottom: 50%; /*Overwritten by the JS*/
+ width: 100%;
+}
+.chart-wrapper .outer-box {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+.chart-wrapper .inner-box {
+ width: 100%;
+ height: 100%;
+}
+
+.chart-wrapper text {
+ font-family: sans-serif;
+ font-size: 13px;
+}
+
+.chart-wrapper .axis path,
+.chart-wrapper .axis line {
+ fill: none;
+ stroke: #888;
+ stroke-width: 2px;
+ shape-rendering: crispEdges;
+}
+
+.chart-wrapper .y.axis .tick line {
+ stroke: lightgrey;
+ opacity: 0.6;
+ stroke-dasharray: 2,1;
+ stroke-width: 1;
+ shape-rendering: crispEdges;
+
+}
+
+.chart-wrapper .x.axis .domain {
+ display: none;
+}
+
+.chart-wrapper div.tooltip {
+ position: absolute;
+ text-align: left;
+ padding: 3px;
+ font: 12px sans-serif;
+ background: lightcyan;
+ border: 0px;
+ border-radius: 1px;
+ pointer-events: none;
+ opacity: 0.7;
+}
+
+/*Box Plot*/
+.chart-wrapper .box-plot .box {
+ fill-opacity: 0.4;
+ stroke-width: 2;
+}
+.chart-wrapper .box-plot line {
+ stroke-width: 2px;
+}
+.chart-wrapper .box-plot circle {
+ fill: white;
+ stroke: black;
+}
+
+.chart-wrapper .box-plot .median {
+ stroke: black;
+}
+
+.chart-wrapper .box-plot circle.median {
+ /*the script makes the circles the same color as the box, you can override this in the js*/
+ fill: white !important;
+}
+
+.chart-wrapper .box-plot .mean {
+ stroke: white;
+ stroke-dasharray: 2,1;
+ stroke-width: 1px;
+}
+
+@media (max-width:500px){
+ .chart-wrapper .box-plot circle {display: none;}
+}
+
+/*Violin Plot*/
+
+.chart-wrapper .violin-plot .area {
+ shape-rendering: geometricPrecision;
+ opacity: 0.4;
+}
+
+.chart-wrapper .violin-plot .line {
+ fill: none;
+ stroke-width: 2px;
+ shape-rendering: geometricPrecision;
+}
+
+/*Notch Plot*/
+.chart-wrapper .notch-plot .notch {
+ fill-opacity: 0.4;
+ stroke-width: 2;
+}
+
+/* Point Plots*/
+.chart-wrapper .points-plot .point {
+ stroke: black;
+ stroke-width: 1px;
+}
+
+.chart-wrapper .metrics-lines {
+ stroke-width: 4px;
+}
+
+/* Non-Chart Styles for demo*/
+.chart-options {
+ min-width: 200px;
+ font-size: 13px;
+ font-family: sans-serif;
+}
+.chart-options button {
+ margin: 3px;
+ padding: 3px;
+ font-size: 12px;
+}
+.chart-options p {
+ display: inline;
+}
+@media (max-width:500px){
+ .chart-options p {display: block;}
+}
\ No newline at end of file
diff --git a/silk/static/silk/lib/distrochart.js b/silk/static/silk/lib/distrochart.js
new file mode 100644
index 00000000..bd1bdff8
--- /dev/null
+++ b/silk/static/silk/lib/distrochart.js
@@ -0,0 +1,1541 @@
+/**
+ * @fileOverview A D3 based distribution chart system. Supports: Box plots, Violin plots, Notched box plots, trend lines, beeswarm plot
+ * @version 3.0
+ */
+
+
+/**
+ * Creates a box plot, violin plot, and or notched box plot
+ * @param settings Configuration options for the base plot
+ * @param settings.data The data for the plot
+ * @param settings.xName The name of the column that should be used for the x groups
+ * @param settings.yName The name of the column used for the y values
+ * @param {string} settings.selector The selector string for the main chart div
+ * @param [settings.axisLabels={}] Defaults to the xName and yName
+ * @param [settings.yTicks = 1] 1 = default ticks. 2 = double, 0.5 = half
+ * @param [settings.scale='linear'] 'linear' or 'log' - y scale of the chart
+ * @param [settings.chartSize={width:800, height:400}] The height and width of the chart itself (doesn't include the container)
+ * @param [settings.margin={top: 15, right: 60, bottom: 40, left: 50}] The margins around the chart (inside the main div)
+ * @param [settings.constrainExtremes=false] Should the y scale include outliers?
+ * @returns {object} chart A chart object
+ */
+function makeDistroChart(settings) {
+
+ var chart = {};
+
+ // Defaults
+ chart.settings = {
+ data: null,
+ xName: null,
+ yName: null,
+ selector: null,
+ axisLables: null,
+ yTicks: 1,
+ scale: 'linear',
+ chartSize: {width: 800, height: 400},
+ margin: {top: 15, right: 60, bottom: 40, left: 50},
+ constrainExtremes: false,
+ color: d3.scale.category10()
+ };
+ for (var setting in settings) {
+ chart.settings[setting] = settings[setting]
+ }
+
+
+ function formatAsFloat(d) {
+ if (d % 1 !== 0) {
+ return d3.format(".2f")(d);
+ } else {
+ return d3.format(".0f")(d);
+ }
+ }
+
+ function logFormatNumber(d) {
+ var x = Math.log(d) / Math.log(10) + 1e-6;
+ return Math.abs(x - Math.floor(x)) < 0.6 ? formatAsFloat(d) : "";
+ }
+
+ chart.yFormatter = formatAsFloat;
+
+ chart.data = chart.settings.data;
+
+ chart.groupObjs = {}; //The data organized by grouping and sorted as well as any metadata for the groups
+ chart.objs = {mainDiv: null, chartDiv: null, g: null, xAxis: null, yAxis: null};
+ chart.colorFunct = null;
+
+ /**
+ * Takes an array, function, or object mapping and created a color function from it
+ * @param {function|[]|object} colorOptions
+ * @returns {function} Function to be used to determine chart colors
+ */
+ function getColorFunct(colorOptions) {
+ if (typeof colorOptions == 'function') {
+ return colorOptions
+ } else if (Array.isArray(colorOptions)) {
+ // If an array is provided, map it to the domain
+ var colorMap = {}, cColor = 0;
+ for (var cName in chart.groupObjs) {
+ colorMap[cName] = colorOptions[cColor];
+ cColor = (cColor + 1) % colorOptions.length;
+ }
+ return function (group) {
+ return colorMap[group];
+ }
+ } else if (typeof colorOptions == 'object') {
+ // if an object is provided, assume it maps to the colors
+ return function (group) {
+ return colorOptions[group];
+ }
+ } else {
+ return d3.scale.category10();
+ }
+ }
+
+ /**
+ * Takes a percentage as returns the values that correspond to that percentage of the group range witdh
+ * @param objWidth Percentage of range band
+ * @param gName The bin name to use to get the x shift
+ * @returns {{left: null, right: null, middle: null}}
+ */
+ function getObjWidth(objWidth, gName) {
+ var objSize = {left: null, right: null, middle: null};
+ var width = chart.xScale.rangeBand() * (objWidth / 100);
+ var padding = (chart.xScale.rangeBand() - width) / 2;
+ var gShift = chart.xScale(gName);
+ objSize.middle = chart.xScale.rangeBand() / 2 + gShift;
+ objSize.left = padding + gShift;
+ objSize.right = objSize.left + width;
+ return objSize;
+ }
+
+ /**
+ * Adds jitter to the scatter point plot
+ * @param doJitter true or false, add jitter to the point
+ * @param width percent of the range band to cover with the jitter
+ * @returns {number}
+ */
+ function addJitter(doJitter, width) {
+ if (doJitter !== true || width == 0) {
+ return 0
+ }
+ return Math.floor(Math.random() * width) - width / 2;
+ }
+
+ function shallowCopy(oldObj) {
+ var newObj = {};
+ for (var i in oldObj) {
+ if (oldObj.hasOwnProperty(i)) {
+ newObj[i] = oldObj[i];
+ }
+ }
+ return newObj;
+ }
+
+ /**
+ * Closure that creates the tooltip hover function
+ * @param groupName Name of the x group
+ * @param metrics Object to use to get values for the group
+ * @returns {Function} A function that provides the values for the tooltip
+ */
+ function tooltipHover(groupName, metrics) {
+ var tooltipString = "Group: " + groupName;
+ tooltipString += "
Max: " + formatAsFloat(metrics.max, 0.1);
+ tooltipString += "
Q3: " + formatAsFloat(metrics.quartile3);
+ tooltipString += "
Median: " + formatAsFloat(metrics.median);
+ tooltipString += "
Q1: " + formatAsFloat(metrics.quartile1);
+ tooltipString += "
Min: " + formatAsFloat(metrics.min);
+ return function () {
+ chart.objs.tooltip.transition().duration(200).style("opacity", 0.9);
+ chart.objs.tooltip.html(tooltipString)
+ };
+ }
+
+ /**
+ * Parse the data and calculates base values for the plots
+ */
+ !function prepareData() {
+ function calcMetrics(values) {
+
+ var metrics = { //These are the original non�scaled values
+ max: null,
+ upperOuterFence: null,
+ upperInnerFence: null,
+ quartile3: null,
+ median: null,
+ mean: null,
+ iqr: null,
+ quartile1: null,
+ lowerInnerFence: null,
+ lowerOuterFence: null,
+ min: null
+ };
+
+ metrics.min = d3.min(values);
+ metrics.quartile1 = d3.quantile(values, 0.25);
+ metrics.median = d3.median(values);
+ metrics.mean = d3.mean(values);
+ metrics.quartile3 = d3.quantile(values, 0.75);
+ metrics.max = d3.max(values);
+ metrics.iqr = metrics.quartile3 - metrics.quartile1;
+
+ //The inner fences are the closest value to the IQR without going past it (assumes sorted lists)
+ var LIF = metrics.quartile1 - (1.5 * metrics.iqr);
+ var UIF = metrics.quartile3 + (1.5 * metrics.iqr);
+ for (var i = 0; i <= values.length; i++) {
+ if (values[i] < LIF) {
+ continue;
+ }
+ if (!metrics.lowerInnerFence && values[i] >= LIF) {
+ metrics.lowerInnerFence = values[i];
+ continue;
+ }
+ if (values[i] > UIF) {
+ metrics.upperInnerFence = values[i - 1];
+ break;
+ }
+ }
+
+
+ metrics.lowerOuterFence = metrics.quartile1 - (3 * metrics.iqr);
+ metrics.upperOuterFence = metrics.quartile3 + (3 * metrics.iqr);
+ if (!metrics.lowerInnerFence) {
+ metrics.lowerInnerFence = metrics.min;
+ }
+ if (!metrics.upperInnerFence) {
+ metrics.upperInnerFence = metrics.max;
+ }
+ return metrics
+ }
+
+ var current_x = null;
+ var current_y = null;
+ var current_row;
+
+ // Group the values
+ for (current_row = 0; current_row < chart.data.length; current_row++) {
+ current_x = chart.data[current_row][chart.settings.xName];
+ current_y = chart.data[current_row][chart.settings.yName];
+
+ if (chart.groupObjs.hasOwnProperty(current_x)) {
+ chart.groupObjs[current_x].values.push(current_y);
+ } else {
+ chart.groupObjs[current_x] = {};
+ chart.groupObjs[current_x].values = [current_y];
+ }
+ }
+
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].values.sort(d3.ascending);
+ chart.groupObjs[cName].metrics = {};
+ chart.groupObjs[cName].metrics = calcMetrics(chart.groupObjs[cName].values);
+
+ }
+ }();
+
+ /**
+ * Prepare the chart settings and chart div and svg
+ */
+ !function prepareSettings() {
+ //Set base settings
+ chart.margin = chart.settings.margin;
+ chart.divWidth = chart.settings.chartSize.width;
+ chart.divHeight = chart.settings.chartSize.height;
+ chart.width = chart.divWidth - chart.margin.left - chart.margin.right;
+ chart.height = chart.divHeight - chart.margin.top - chart.margin.bottom;
+
+ if (chart.settings.axisLabels) {
+ chart.xAxisLable = chart.settings.axisLabels.xAxis;
+ chart.yAxisLable = chart.settings.axisLabels.yAxis;
+ } else {
+ chart.xAxisLable = chart.settings.xName;
+ chart.yAxisLable = chart.settings.yName;
+ }
+
+ if (chart.settings.scale === 'log') {
+ chart.yScale = d3.scale.log();
+ chart.yFormatter = logFormatNumber;
+ } else {
+ chart.yScale = d3.scale.linear();
+ }
+
+ if (chart.settings.constrainExtremes === true) {
+ var fences = [];
+ for (var cName in chart.groupObjs) {
+ fences.push(chart.groupObjs[cName].metrics.lowerInnerFence);
+ fences.push(chart.groupObjs[cName].metrics.upperInnerFence);
+ }
+ chart.range = d3.extent(fences);
+
+ } else {
+ chart.range = d3.extent(chart.data, function (d) {return d[chart.settings.yName];});
+ }
+
+ chart.colorFunct = getColorFunct(chart.settings.colors);
+
+ // Build Scale functions
+ chart.yScale.range([chart.height, 0]).domain(chart.range).nice().clamp(true);
+ chart.xScale = d3.scale.ordinal().domain(Object.keys(chart.groupObjs)).rangeBands([0, chart.width]);
+
+ //Build Axes Functions
+ chart.objs.yAxis = d3.svg.axis()
+ .scale(chart.yScale)
+ .orient("left")
+ .tickFormat(chart.yFormatter)
+ .outerTickSize(0)
+ .innerTickSize(-chart.width + (chart.margin.right + chart.margin.left));
+ chart.objs.yAxis.ticks(chart.objs.yAxis.ticks()*chart.settings.yTicks);
+ chart.objs.xAxis = d3.svg.axis().scale(chart.xScale).orient("bottom").tickSize(5);
+ }();
+
+ /**
+ * Updates the chart based on the current settings and window size
+ * @returns {*}
+ */
+ chart.update = function () {
+ // Update chart size based on view port size
+ chart.width = parseInt(chart.objs.chartDiv.style("width"), 10) - (chart.margin.left + chart.margin.right);
+ chart.height = parseInt(chart.objs.chartDiv.style("height"), 10) - (chart.margin.top + chart.margin.bottom);
+
+ // Update scale functions
+ chart.xScale.rangeBands([0, chart.width]);
+ chart.yScale.range([chart.height, 0]);
+
+ // Update the yDomain if the Violin plot clamp is set to -1 meaning it will extend the violins to make nice points
+ if (chart.violinPlots && chart.violinPlots.options.show == true && chart.violinPlots.options._yDomainVP != null) {
+ chart.yScale.domain(chart.violinPlots.options._yDomainVP).nice().clamp(true);
+ } else {
+ chart.yScale.domain(chart.range).nice().clamp(true);
+ }
+
+ //Update axes
+ chart.objs.g.select('.x.axis').attr("transform", "translate(0," + chart.height + ")").call(chart.objs.xAxis)
+ .selectAll("text")
+ .attr("y", 5)
+ .attr("x", -5)
+ .attr("transform", "rotate(-45)")
+ .style("text-anchor", "end");
+ chart.objs.g.select('.x.axis .label').attr("x", chart.width / 2);
+ chart.objs.g.select('.y.axis').call(chart.objs.yAxis.innerTickSize(-chart.width));
+ chart.objs.g.select('.y.axis .label').attr("x", -chart.height / 2);
+ chart.objs.chartDiv.select('svg').attr("width", chart.width + (chart.margin.left + chart.margin.right)).attr("height", chart.height + (chart.margin.top + chart.margin.bottom));
+
+ return chart;
+ };
+
+ /**
+ * Prepare the chart html elements
+ */
+ !function prepareChart() {
+ // Build main div and chart div
+ chart.objs.mainDiv = d3.select(chart.settings.selector)
+ .style("max-width", chart.divWidth + "px");
+ // Add all the divs to make it centered and responsive
+ chart.objs.mainDiv.append("div")
+ .attr("class", "inner-wrapper")
+ .style("padding-bottom", (chart.divHeight / chart.divWidth) * 100 + "%")
+ .append("div").attr("class", "outer-box")
+ .append("div").attr("class", "inner-box");
+ // Capture the inner div for the chart (where the chart actually is)
+ chart.selector = chart.settings.selector + " .inner-box";
+ chart.objs.chartDiv = d3.select(chart.selector);
+ d3.select(window).on('resize.' + chart.selector, chart.update);
+
+ // Create the svg
+ chart.objs.g = chart.objs.chartDiv.append("svg")
+ .attr("class", "chart-area")
+ .attr("width", chart.width + (chart.margin.left + chart.margin.right))
+ .attr("height", chart.height + (chart.margin.top + chart.margin.bottom))
+ .append("g")
+ .attr("transform", "translate(" + chart.margin.left + "," + chart.margin.top + ")");
+
+ // Create axes
+ chart.objs.axes = chart.objs.g.append("g").attr("class", "axis");
+ chart.objs.axes.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + chart.height + ")")
+ .call(chart.objs.xAxis);
+ chart.objs.axes.append("g")
+ .attr("class", "y axis")
+ .call(chart.objs.yAxis)
+ .append("text")
+ .attr("class", "label")
+ .attr("transform", "rotate(-90)")
+ .attr("y", -42)
+ .attr("x", -chart.height / 2)
+ .attr("dy", ".71em")
+ .style("text-anchor", "middle")
+ .text(chart.yAxisLable);
+
+ // Create tooltip div
+ chart.objs.tooltip = chart.objs.mainDiv.append('div').attr('class', 'tooltip');
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].g = chart.objs.g.append("g").attr("class", "group");
+ chart.groupObjs[cName].g.on("mouseover", function () {
+ chart.objs.tooltip
+ .style("display", null)
+ .style("left", (d3.event.pageX) + "px")
+ .style("top", (d3.event.pageY - 28) + "px");
+ }).on("mouseout", function () {
+ chart.objs.tooltip.style("display", "none");
+ }).on("mousemove", tooltipHover(cName, chart.groupObjs[cName].metrics))
+ }
+ chart.update();
+ }();
+
+ /**
+ * Render a violin plot on the current chart
+ * @param options
+ * @param [options.showViolinPlot=true] True or False, show the violin plot
+ * @param [options.resolution=100 default]
+ * @param [options.bandwidth=10 default] May need higher bandwidth for larger data sets
+ * @param [options.width=50] The max percent of the group rangeBand that the violin can be
+ * @param [options.interpolation=''] How to render the violin
+ * @param [options.clamp=0 default]
+ * 0 = keep data within chart min and max, clamp once data = 0. May extend beyond data set min and max
+ * 1 = clamp at min and max of data set. Possibly no tails
+ * -1 = extend chart axis to make room for data to interpolate to 0. May extend axis and data set min and max
+ * @param [options.colors=chart default] The color mapping for the violin plot
+ * @returns {*} The chart object
+ */
+ chart.renderViolinPlot = function (options) {
+ chart.violinPlots = {};
+
+ var defaultOptions = {
+ show: true,
+ showViolinPlot: true,
+ resolution: 100,
+ bandwidth: 20,
+ width: 50,
+ interpolation: 'cardinal',
+ clamp: 1,
+ colors: chart.colorFunct,
+ _yDomainVP: null // If the Violin plot is set to close all violin plots, it may need to extend the domain, that extended domain is stored here
+ };
+ chart.violinPlots.options = shallowCopy(defaultOptions);
+ for (var option in options) {
+ chart.violinPlots.options[option] = options[option]
+ }
+ var vOpts = chart.violinPlots.options;
+
+ // Create violin plot objects
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].violin = {};
+ chart.groupObjs[cName].violin.objs = {};
+ }
+
+ /**
+ * Take a new set of options and redraw the violin
+ * @param updateOptions
+ */
+ chart.violinPlots.change = function (updateOptions) {
+ if (updateOptions) {
+ for (var key in updateOptions) {
+ vOpts[key] = updateOptions[key]
+ }
+ }
+
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].violin.objs.g.remove()
+ }
+
+ chart.violinPlots.prepareViolin();
+ chart.violinPlots.update();
+ };
+
+ chart.violinPlots.reset = function () {
+ chart.violinPlots.change(defaultOptions)
+ };
+ chart.violinPlots.show = function (opts) {
+ if (opts !== undefined) {
+ opts.show = true;
+ if (opts.reset) {
+ chart.violinPlots.reset()
+ }
+ } else {
+ opts = {show: true};
+ }
+ chart.violinPlots.change(opts);
+
+ };
+
+ chart.violinPlots.hide = function (opts) {
+ if (opts !== undefined) {
+ opts.show = false;
+ if (opts.reset) {
+ chart.violinPlots.reset()
+ }
+ } else {
+ opts = {show: false};
+ }
+ chart.violinPlots.change(opts);
+
+ };
+
+ /**
+ * Update the violin obj values
+ */
+ chart.violinPlots.update = function () {
+ var cName, cViolinPlot;
+
+ for (cName in chart.groupObjs) {
+ cViolinPlot = chart.groupObjs[cName].violin;
+
+ // Build the violins sideways, so use the yScale for the xScale and make a new yScale
+ var xVScale = chart.yScale.copy();
+
+
+ // Create the Kernel Density Estimator Function
+ cViolinPlot.kde = kernelDensityEstimator(eKernel(vOpts.bandwidth), xVScale.ticks(vOpts.resolution));
+ cViolinPlot.kdedata = cViolinPlot.kde(chart.groupObjs[cName].values);
+
+ var interpolateMax = chart.groupObjs[cName].metrics.max,
+ interpolateMin = chart.groupObjs[cName].metrics.min;
+
+ if (vOpts.clamp == 0 || vOpts.clamp == -1) { //
+ // When clamp is 0, calculate the min and max that is needed to bring the violin plot to a point
+ // interpolateMax = the Minimum value greater than the max where y = 0
+ interpolateMax = d3.min(cViolinPlot.kdedata.filter(function (d) {
+ return (d.x > chart.groupObjs[cName].metrics.max && d.y == 0)
+ }), function (d) {
+ return d.x;
+ });
+ // interpolateMin = the Maximum value less than the min where y = 0
+ interpolateMin = d3.max(cViolinPlot.kdedata.filter(function (d) {
+ return (d.x < chart.groupObjs[cName].metrics.min && d.y == 0)
+ }), function (d) {
+ return d.x;
+ });
+ // If clamp is -1 we need to extend the axises so that the violins come to a point
+ if (vOpts.clamp == -1) {
+ kdeTester = eKernelTest(eKernel(vOpts.bandwidth), chart.groupObjs[cName].values);
+ if (!interpolateMax) {
+ var interMaxY = kdeTester(chart.groupObjs[cName].metrics.max);
+ var interMaxX = chart.groupObjs[cName].metrics.max;
+ var count = 25; // Arbitrary limit to make sure we don't get an infinite loop
+ while (count > 0 && interMaxY != 0) {
+ interMaxY = kdeTester(interMaxX);
+ interMaxX += 1;
+ count -= 1;
+ }
+ interpolateMax = interMaxX;
+ }
+ if (!interpolateMin) {
+ var interMinY = kdeTester(chart.groupObjs[cName].metrics.min);
+ var interMinX = chart.groupObjs[cName].metrics.min;
+ var count = 25; // Arbitrary limit to make sure we don't get an infinite loop
+ while (count > 0 && interMinY != 0) {
+ interMinY = kdeTester(interMinX);
+ interMinX -= 1;
+ count -= 1;
+ }
+ interpolateMin = interMinX;
+ }
+
+ }
+ // Check to see if the new values are outside the existing chart range
+ // If they are assign them to the master _yDomainVP
+ if (!vOpts._yDomainVP) vOpts._yDomainVP = chart.range.slice(0);
+ if (interpolateMin && interpolateMin < vOpts._yDomainVP[0]) {
+ vOpts._yDomainVP[0] = interpolateMin;
+ }
+ if (interpolateMax && interpolateMax > vOpts._yDomainVP[1]) {
+ vOpts._yDomainVP[1] = interpolateMax;
+ }
+
+
+ }
+
+
+ if (vOpts.showViolinPlot) {
+ chart.update();
+ xVScale = chart.yScale.copy();
+
+ // Need to recalculate the KDE because the xVScale changed
+ cViolinPlot.kde = kernelDensityEstimator(eKernel(vOpts.bandwidth), xVScale.ticks(vOpts.resolution));
+ cViolinPlot.kdedata = cViolinPlot.kde(chart.groupObjs[cName].values);
+ }
+
+ cViolinPlot.kdedata = cViolinPlot.kdedata
+ .filter(function (d) {
+ return (!interpolateMin || d.x >= interpolateMin)
+ })
+ .filter(function (d) {
+ return (!interpolateMax || d.x <= interpolateMax)
+ });
+ }
+ for (cName in chart.groupObjs) {
+ cViolinPlot = chart.groupObjs[cName].violin;
+
+ // Get the violin width
+ var objBounds = getObjWidth(vOpts.width, cName);
+ var width = (objBounds.right - objBounds.left) / 2;
+
+ var yVScale = d3.scale.linear()
+ .range([width, 0])
+ .domain([0, d3.max(cViolinPlot.kdedata, function (d) {return d.y;})])
+ .clamp(true);
+
+ var area = d3.svg.area()
+ .interpolate(vOpts.interpolation)
+ .x(function (d) {return xVScale(d.x);})
+ .y0(width)
+ .y1(function (d) {return yVScale(d.y);});
+
+ var line = d3.svg.line()
+ .interpolate(vOpts.interpolation)
+ .x(function (d) {return xVScale(d.x);})
+ .y(function (d) {return yVScale(d.y)});
+
+ if (cViolinPlot.objs.left.area) {
+ cViolinPlot.objs.left.area
+ .datum(cViolinPlot.kdedata)
+ .attr("d", area);
+ cViolinPlot.objs.left.line
+ .datum(cViolinPlot.kdedata)
+ .attr("d", line);
+
+ cViolinPlot.objs.right.area
+ .datum(cViolinPlot.kdedata)
+ .attr("d", area);
+ cViolinPlot.objs.right.line
+ .datum(cViolinPlot.kdedata)
+ .attr("d", line);
+ }
+
+ // Rotate the violins
+ cViolinPlot.objs.left.g.attr("transform", "rotate(90,0,0) translate(0,-" + objBounds.left + ") scale(1,-1)");
+ cViolinPlot.objs.right.g.attr("transform", "rotate(90,0,0) translate(0,-" + objBounds.right + ")");
+ }
+ };
+
+ /**
+ * Create the svg elements for the violin plot
+ */
+ chart.violinPlots.prepareViolin = function () {
+ var cName, cViolinPlot;
+
+ if (vOpts.colors) {
+ chart.violinPlots.color = getColorFunct(vOpts.colors);
+ } else {
+ chart.violinPlots.color = chart.colorFunct
+ }
+
+ if (vOpts.show == false) {return}
+
+ for (cName in chart.groupObjs) {
+ cViolinPlot = chart.groupObjs[cName].violin;
+
+ cViolinPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "violin-plot");
+ cViolinPlot.objs.left = {area: null, line: null, g: null};
+ cViolinPlot.objs.right = {area: null, line: null, g: null};
+
+ cViolinPlot.objs.left.g = cViolinPlot.objs.g.append("g");
+ cViolinPlot.objs.right.g = cViolinPlot.objs.g.append("g");
+
+ if (vOpts.showViolinPlot !== false) {
+ //Area
+ cViolinPlot.objs.left.area = cViolinPlot.objs.left.g.append("path")
+ .attr("class", "area")
+ .style("fill", chart.violinPlots.color(cName));
+ cViolinPlot.objs.right.area = cViolinPlot.objs.right.g.append("path")
+ .attr("class", "area")
+ .style("fill", chart.violinPlots.color(cName));
+
+ //Lines
+ cViolinPlot.objs.left.line = cViolinPlot.objs.left.g.append("path")
+ .attr("class", "line")
+ .attr("fill", 'none')
+ .style("stroke", chart.violinPlots.color(cName));
+ cViolinPlot.objs.right.line = cViolinPlot.objs.right.g.append("path")
+ .attr("class", "line")
+ .attr("fill", 'none')
+ .style("stroke", chart.violinPlots.color(cName));
+ }
+
+ }
+
+ };
+
+
+ function kernelDensityEstimator(kernel, x) {
+ return function (sample) {
+ return x.map(function (x) {
+ return {x:x, y:d3.mean(sample, function (v) {return kernel(x - v);})};
+ });
+ };
+ }
+
+ function eKernel(scale) {
+ return function (u) {
+ return Math.abs(u /= scale) <= 1 ? .75 * (1 - u * u) / scale : 0;
+ };
+ }
+
+ // Used to find the roots for adjusting violin axis
+ // Given an array, find the value for a single point, even if it is not in the domain
+ function eKernelTest(kernel, array) {
+ return function (testX) {
+ return d3.mean(array, function (v) {return kernel(testX - v);})
+ }
+ }
+
+ chart.violinPlots.prepareViolin();
+
+ d3.select(window).on('resize.' + chart.selector + '.violinPlot', chart.violinPlots.update);
+ chart.violinPlots.update();
+ return chart;
+ };
+
+ /**
+ * Render a box plot on the current chart
+ * @param options
+ * @param [options.show=true] Toggle the whole plot on and off
+ * @param [options.showBox=true] Show the box part of the box plot
+ * @param [options.showWhiskers=true] Show the whiskers
+ * @param [options.showMedian=true] Show the median line
+ * @param [options.showMean=false] Show the mean line
+ * @param [options.medianCSize=3] The size of the circle on the median
+ * @param [options.showOutliers=true] Plot outliers
+ * @param [options.boxwidth=30] The max percent of the group rangeBand that the box can be
+ * @param [options.lineWidth=boxWidth] The max percent of the group rangeBand that the line can be
+ * @param [options.outlierScatter=false] Spread out the outliers so they don't all overlap (in development)
+ * @param [options.outlierCSize=2] Size of the outliers
+ * @param [options.colors=chart default] The color mapping for the box plot
+ * @returns {*} The chart object
+ */
+ chart.renderBoxPlot = function (options) {
+ chart.boxPlots = {};
+
+ // Defaults
+ var defaultOptions = {
+ show: true,
+ showBox: true,
+ showWhiskers: true,
+ showMedian: true,
+ showMean: false,
+ medianCSize: 3.5,
+ showOutliers: true,
+ boxWidth: 30,
+ lineWidth: null,
+ scatterOutliers: false,
+ outlierCSize: 2.5,
+ colors: chart.colorFunct
+ };
+ chart.boxPlots.options = shallowCopy(defaultOptions);
+ for (var option in options) {
+ chart.boxPlots.options[option] = options[option]
+ }
+ var bOpts = chart.boxPlots.options;
+
+ //Create box plot objects
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].boxPlot = {};
+ chart.groupObjs[cName].boxPlot.objs = {};
+ }
+
+
+ /**
+ * Calculates all the outlier points for each group
+ */
+ !function calcAllOutliers() {
+
+ /**
+ * Create lists of the outliers for each content group
+ * @param cGroup The object to modify
+ * @return null Modifies the object in place
+ */
+ function calcOutliers(cGroup) {
+ var cExtremes = [];
+ var cOutliers = [];
+ var cOut, idx;
+ for (idx = 0; idx <= cGroup.values.length; idx++) {
+ cOut = {value: cGroup.values[idx]};
+
+ if (cOut.value < cGroup.metrics.lowerInnerFence) {
+ if (cOut.value < cGroup.metrics.lowerOuterFence) {
+ cExtremes.push(cOut);
+ } else {
+ cOutliers.push(cOut);
+ }
+ } else if (cOut.value > cGroup.metrics.upperInnerFence) {
+ if (cOut.value > cGroup.metrics.upperOuterFence) {
+ cExtremes.push(cOut);
+ } else {
+ cOutliers.push(cOut);
+ }
+ }
+ }
+ cGroup.boxPlot.objs.outliers = cOutliers;
+ cGroup.boxPlot.objs.extremes = cExtremes;
+ }
+
+ for (var cName in chart.groupObjs) {
+ calcOutliers(chart.groupObjs[cName]);
+ }
+ }();
+
+ /**
+ * Take updated options and redraw the box plot
+ * @param updateOptions
+ */
+ chart.boxPlots.change = function (updateOptions) {
+ if (updateOptions) {
+ for (var key in updateOptions) {
+ bOpts[key] = updateOptions[key]
+ }
+ }
+
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].boxPlot.objs.g.remove()
+ }
+ chart.boxPlots.prepareBoxPlot();
+ chart.boxPlots.update()
+ };
+
+ chart.boxPlots.reset = function () {
+ chart.boxPlots.change(defaultOptions)
+ };
+ chart.boxPlots.show = function (opts) {
+ if (opts !== undefined) {
+ opts.show = true;
+ if (opts.reset) {
+ chart.boxPlots.reset()
+ }
+ } else {
+ opts = {show: true};
+ }
+ chart.boxPlots.change(opts)
+
+ };
+ chart.boxPlots.hide = function (opts) {
+ if (opts !== undefined) {
+ opts.show = false;
+ if (opts.reset) {
+ chart.boxPlots.reset()
+ }
+ } else {
+ opts = {show: false};
+ }
+ chart.boxPlots.change(opts)
+ };
+
+ /**
+ * Update the box plot obj values
+ */
+ chart.boxPlots.update = function () {
+ var cName, cBoxPlot;
+
+ for (cName in chart.groupObjs) {
+ cBoxPlot = chart.groupObjs[cName].boxPlot;
+
+ // Get the box width
+ var objBounds = getObjWidth(bOpts.boxWidth, cName);
+ var width = (objBounds.right - objBounds.left);
+
+ var sMetrics = {}; //temp var for scaled (plottable) metric values
+ for (var attr in chart.groupObjs[cName].metrics) {
+ sMetrics[attr] = null;
+ sMetrics[attr] = chart.yScale(chart.groupObjs[cName].metrics[attr]);
+ }
+
+ // Box
+ if (cBoxPlot.objs.box) {
+ cBoxPlot.objs.box
+ .attr("x", objBounds.left)
+ .attr('width', width)
+ .attr("y", sMetrics.quartile3)
+ .attr("rx", 1)
+ .attr("ry", 1)
+ .attr("height", -sMetrics.quartile3 + sMetrics.quartile1)
+ }
+
+ // Lines
+ var lineBounds = null;
+ if (bOpts.lineWidth) {
+ lineBounds = getObjWidth(bOpts.lineWidth, cName)
+ } else {
+ lineBounds = objBounds
+ }
+ // --Whiskers
+ if (cBoxPlot.objs.upperWhisker) {
+ cBoxPlot.objs.upperWhisker.fence
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', sMetrics.upperInnerFence)
+ .attr("y2", sMetrics.upperInnerFence);
+ cBoxPlot.objs.upperWhisker.line
+ .attr("x1", lineBounds.middle)
+ .attr("x2", lineBounds.middle)
+ .attr('y1', sMetrics.quartile3)
+ .attr("y2", sMetrics.upperInnerFence);
+
+ cBoxPlot.objs.lowerWhisker.fence
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', sMetrics.lowerInnerFence)
+ .attr("y2", sMetrics.lowerInnerFence);
+ cBoxPlot.objs.lowerWhisker.line
+ .attr("x1", lineBounds.middle)
+ .attr("x2", lineBounds.middle)
+ .attr('y1', sMetrics.quartile1)
+ .attr("y2", sMetrics.lowerInnerFence);
+ }
+
+ // --Median
+ if (cBoxPlot.objs.median) {
+ cBoxPlot.objs.median.line
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', sMetrics.median)
+ .attr("y2", sMetrics.median);
+ cBoxPlot.objs.median.circle
+ .attr("cx", lineBounds.middle)
+ .attr("cy", sMetrics.median)
+ }
+
+ // --Mean
+ if (cBoxPlot.objs.mean) {
+ cBoxPlot.objs.mean.line
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', sMetrics.mean)
+ .attr("y2", sMetrics.mean);
+ cBoxPlot.objs.mean.circle
+ .attr("cx", lineBounds.middle)
+ .attr("cy", sMetrics.mean);
+ }
+
+ // Outliers
+
+ var pt;
+ if (cBoxPlot.objs.outliers) {
+ for (pt in cBoxPlot.objs.outliers) {
+ cBoxPlot.objs.outliers[pt].point
+ .attr("cx", objBounds.middle + addJitter(bOpts.scatterOutliers, width))
+ .attr("cy", chart.yScale(cBoxPlot.objs.outliers[pt].value));
+ }
+ }
+ if (cBoxPlot.objs.extremes) {
+ for (pt in cBoxPlot.objs.extremes) {
+ cBoxPlot.objs.extremes[pt].point
+ .attr("cx", objBounds.middle + addJitter(bOpts.scatterOutliers, width))
+ .attr("cy", chart.yScale(cBoxPlot.objs.extremes[pt].value));
+ }
+ }
+ }
+ };
+
+ /**
+ * Create the svg elements for the box plot
+ */
+ chart.boxPlots.prepareBoxPlot = function () {
+ var cName, cBoxPlot;
+
+ if (bOpts.colors) {
+ chart.boxPlots.colorFunct = getColorFunct(bOpts.colors);
+ } else {
+ chart.boxPlots.colorFunct = chart.colorFunct
+ }
+
+ if (bOpts.show == false) {
+ return
+ }
+
+ for (cName in chart.groupObjs) {
+ cBoxPlot = chart.groupObjs[cName].boxPlot;
+
+ cBoxPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "box-plot");
+
+ //Plot Box (default show)
+ if (bOpts.showBox) {
+ cBoxPlot.objs.box = cBoxPlot.objs.g.append("rect")
+ .attr("class", "box")
+ .style("fill", chart.boxPlots.colorFunct(cName))
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ //A stroke is added to the box with the group color, it is
+ // hidden by default and can be shown through css with stroke-width
+ }
+
+ //Plot Median (default show)
+ if (bOpts.showMedian) {
+ cBoxPlot.objs.median = {line: null, circle: null};
+ cBoxPlot.objs.median.line = cBoxPlot.objs.g.append("line")
+ .attr("class", "median");
+ cBoxPlot.objs.median.circle = cBoxPlot.objs.g.append("circle")
+ .attr("class", "median")
+ .attr('r', bOpts.medianCSize)
+ .style("fill", chart.boxPlots.colorFunct(cName));
+ }
+
+ // Plot Mean (default no plot)
+ if (bOpts.showMean) {
+ cBoxPlot.objs.mean = {line: null, circle: null};
+ cBoxPlot.objs.mean.line = cBoxPlot.objs.g.append("line")
+ .attr("class", "mean");
+ cBoxPlot.objs.mean.circle = cBoxPlot.objs.g.append("circle")
+ .attr("class", "mean")
+ .attr('r', bOpts.medianCSize)
+ .style("fill", chart.boxPlots.colorFunct(cName));
+ }
+
+ // Plot Whiskers (default show)
+ if (bOpts.showWhiskers) {
+ cBoxPlot.objs.upperWhisker = {fence: null, line: null};
+ cBoxPlot.objs.lowerWhisker = {fence: null, line: null};
+ cBoxPlot.objs.upperWhisker.fence = cBoxPlot.objs.g.append("line")
+ .attr("class", "upper whisker")
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ cBoxPlot.objs.upperWhisker.line = cBoxPlot.objs.g.append("line")
+ .attr("class", "upper whisker")
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+
+ cBoxPlot.objs.lowerWhisker.fence = cBoxPlot.objs.g.append("line")
+ .attr("class", "lower whisker")
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ cBoxPlot.objs.lowerWhisker.line = cBoxPlot.objs.g.append("line")
+ .attr("class", "lower whisker")
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ }
+
+ // Plot outliers (default show)
+ if (bOpts.showOutliers) {
+ if (!cBoxPlot.objs.outliers) calcAllOutliers();
+ var pt;
+ if (cBoxPlot.objs.outliers.length) {
+ var outDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot outliers");
+ for (pt in cBoxPlot.objs.outliers) {
+ cBoxPlot.objs.outliers[pt].point = outDiv.append("circle")
+ .attr("class", "outlier")
+ .attr('r', bOpts.outlierCSize)
+ .style("fill", chart.boxPlots.colorFunct(cName));
+ }
+ }
+
+ if (cBoxPlot.objs.extremes.length) {
+ var extDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot extremes");
+ for (pt in cBoxPlot.objs.extremes) {
+ cBoxPlot.objs.extremes[pt].point = extDiv.append("circle")
+ .attr("class", "extreme")
+ .attr('r', bOpts.outlierCSize)
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ }
+ }
+ }
+
+
+ }
+ };
+ chart.boxPlots.prepareBoxPlot();
+
+ d3.select(window).on('resize.' + chart.selector + '.boxPlot', chart.boxPlots.update);
+ chart.boxPlots.update();
+ return chart;
+
+ };
+
+ /**
+ * Render a notched box on the current chart
+ * @param options
+ * @param [options.show=true] Toggle the whole plot on and off
+ * @param [options.showNotchBox=true] Show the notch box
+ * @param [options.showLines=false] Show lines at the confidence intervals
+ * @param [options.boxWidth=35] The width of the widest part of the box
+ * @param [options.medianWidth=20] The width of the narrowist part of the box
+ * @param [options.lineWidth=50] The width of the confidence interval lines
+ * @param [options.notchStyle=null] null=traditional style, 'box' cuts out the whole notch in right angles
+ * @param [options.colors=chart default] The color mapping for the notch boxes
+ * @returns {*} The chart object
+ */
+ chart.renderNotchBoxes = function (options) {
+ chart.notchBoxes = {};
+
+ //Defaults
+ var defaultOptions = {
+ show: true,
+ showNotchBox: true,
+ showLines: false,
+ boxWidth: 35,
+ medianWidth: 20,
+ lineWidth: 50,
+ notchStyle: null,
+ colors: null
+ };
+ chart.notchBoxes.options = shallowCopy(defaultOptions);
+ for (var option in options) {
+ chart.notchBoxes.options[option] = options[option]
+ }
+ var nOpts = chart.notchBoxes.options;
+
+ //Create notch objects
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].notchBox = {};
+ chart.groupObjs[cName].notchBox.objs = {};
+ }
+
+ /**
+ * Makes the svg path string for a notched box
+ * @param cNotch Current notch box object
+ * @param notchBounds objBound object
+ * @returns {string} A string in the proper format for a svg polygon
+ */
+ function makeNotchBox(cNotch, notchBounds) {
+ var scaledValues = [];
+ if (nOpts.notchStyle == 'box') {
+ scaledValues = [
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile1)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.medianLeft, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.medianLeft, chart.yScale(cNotch.metrics.median)],
+ [notchBounds.medianLeft, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile3)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile3)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.medianRight, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.medianRight, chart.yScale(cNotch.metrics.median)],
+ [notchBounds.medianRight, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile1)]
+ ];
+ } else {
+ scaledValues = [
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile1)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.medianLeft, chart.yScale(cNotch.metrics.median)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile3)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile3)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.medianRight, chart.yScale(cNotch.metrics.median)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile1)]
+ ];
+ }
+ return scaledValues.map(function (d) {
+ return [d[0], d[1]].join(",");
+ }).join(" ");
+ }
+
+ /**
+ * Calculate the confidence intervals
+ */
+ !function calcNotches() {
+ var cNotch, modifier;
+ for (var cName in chart.groupObjs) {
+ cNotch = chart.groupObjs[cName];
+ modifier = (1.57 * (cNotch.metrics.iqr / Math.sqrt(cNotch.values.length)));
+ cNotch.metrics.upperNotch = cNotch.metrics.median + modifier;
+ cNotch.metrics.lowerNotch = cNotch.metrics.median - modifier;
+ }
+ }();
+
+ /**
+ * Take a new set of options and redraw the notch boxes
+ * @param updateOptions
+ */
+ chart.notchBoxes.change = function (updateOptions) {
+ if (updateOptions) {
+ for (var key in updateOptions) {
+ nOpts[key] = updateOptions[key]
+ }
+ }
+
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].notchBox.objs.g.remove()
+ }
+ chart.notchBoxes.prepareNotchBoxes();
+ chart.notchBoxes.update();
+ };
+
+ chart.notchBoxes.reset = function () {
+ chart.notchBoxes.change(defaultOptions)
+ };
+ chart.notchBoxes.show = function (opts) {
+ if (opts !== undefined) {
+ opts.show = true;
+ if (opts.reset) {
+ chart.notchBoxes.reset()
+ }
+ } else {
+ opts = {show: true};
+ }
+ chart.notchBoxes.change(opts)
+ };
+ chart.notchBoxes.hide = function (opts) {
+ if (opts !== undefined) {
+ opts.show = false;
+ if (opts.reset) {
+ chart.notchBoxes.reset()
+ }
+ } else {
+ opts = {show: false};
+ }
+ chart.notchBoxes.change(opts)
+ };
+
+ /**
+ * Update the notch box obj values
+ */
+ chart.notchBoxes.update = function () {
+ var cName, cGroup;
+
+ for (cName in chart.groupObjs) {
+ cGroup = chart.groupObjs[cName];
+
+ // Get the box size
+ var boxBounds = getObjWidth(nOpts.boxWidth, cName);
+ var medianBounds = getObjWidth(nOpts.medianWidth, cName);
+
+ var notchBounds = {
+ boxLeft: boxBounds.left,
+ boxRight: boxBounds.right,
+ middle: boxBounds.middle,
+ medianLeft: medianBounds.left,
+ medianRight: medianBounds.right
+ };
+
+ // Notch Box
+ if (cGroup.notchBox.objs.notch) {
+ cGroup.notchBox.objs.notch
+ .attr("points", makeNotchBox(cGroup, notchBounds));
+ }
+ if (cGroup.notchBox.objs.upperLine) {
+ var lineBounds = null;
+ if (nOpts.lineWidth) {
+ lineBounds = getObjWidth(nOpts.lineWidth, cName)
+ } else {
+ lineBounds = objBounds
+ }
+
+ var confidenceLines = {
+ upper: chart.yScale(cGroup.metrics.upperNotch),
+ lower: chart.yScale(cGroup.metrics.lowerNotch)
+ };
+ cGroup.notchBox.objs.upperLine
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', confidenceLines.upper)
+ .attr("y2", confidenceLines.upper);
+ cGroup.notchBox.objs.lowerLine
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', confidenceLines.lower)
+ .attr("y2", confidenceLines.lower);
+ }
+ }
+ };
+
+ /**
+ * Create the svg elements for the notch boxes
+ */
+ chart.notchBoxes.prepareNotchBoxes = function () {
+ var cName, cNotch;
+
+ if (nOpts && nOpts.colors) {
+ chart.notchBoxes.colorFunct = getColorFunct(nOpts.colors);
+ } else {
+ chart.notchBoxes.colorFunct = chart.colorFunct
+ }
+
+ if (nOpts.show == false) {
+ return
+ }
+
+ for (cName in chart.groupObjs) {
+ cNotch = chart.groupObjs[cName].notchBox;
+
+ cNotch.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "notch-plot");
+
+ // Plot Box (default show)
+ if (nOpts.showNotchBox) {
+ cNotch.objs.notch = cNotch.objs.g.append("polygon")
+ .attr("class", "notch")
+ .style("fill", chart.notchBoxes.colorFunct(cName))
+ .style("stroke", chart.notchBoxes.colorFunct(cName));
+ //A stroke is added to the notch with the group color, it is
+ // hidden by default and can be shown through css with stroke-width
+ }
+
+ //Plot Confidence Lines (default hide)
+ if (nOpts.showLines) {
+ cNotch.objs.upperLine = cNotch.objs.g.append("line")
+ .attr("class", "upper confidence line")
+ .style("stroke", chart.notchBoxes.colorFunct(cName));
+
+ cNotch.objs.lowerLine = cNotch.objs.g.append("line")
+ .attr("class", "lower confidence line")
+ .style("stroke", chart.notchBoxes.colorFunct(cName));
+ }
+ }
+ };
+ chart.notchBoxes.prepareNotchBoxes();
+
+ d3.select(window).on('resize.' + chart.selector + '.notchBox', chart.notchBoxes.update);
+ chart.notchBoxes.update();
+ return chart;
+ };
+
+ /**
+ * Render a raw data in various forms
+ * @param options
+ * @param [options.show=true] Toggle the whole plot on and off
+ * @param [options.showPlot=false] True or false, show points
+ * @param [options.plotType='none'] Options: no scatter = (false or 'none'); scatter points= (true or [amount=% of width (default=10)]); beeswarm points = ('beeswarm')
+ * @param [options.pointSize=6] Diameter of the circle in pizels (not the radius)
+ * @param [options.showLines=['median']] Can equal any of the metrics lines
+ * @param [options.showbeanLines=false] Options: no lines = false
+ * @param [options.beanWidth=20] % width
+ * @param [options.colors=chart default]
+ * @returns {*} The chart object
+ *
+ */
+ chart.renderDataPlots = function (options) {
+ chart.dataPlots = {};
+
+
+ //Defaults
+ var defaultOptions = {
+ show: true,
+ showPlot: false,
+ plotType: 'none',
+ pointSize: 6,
+ showLines: false,//['median'],
+ showBeanLines: false,
+ beanWidth: 20,
+ colors: null
+ };
+ chart.dataPlots.options = shallowCopy(defaultOptions);
+ for (var option in options) {
+ chart.dataPlots.options[option] = options[option]
+ }
+ var dOpts = chart.dataPlots.options;
+
+ //Create notch objects
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].dataPlots = {};
+ chart.groupObjs[cName].dataPlots.objs = {};
+ }
+ // The lines don't fit into a group bucket so they live under the dataPlot object
+ chart.dataPlots.objs = {};
+
+ /**
+ * Take updated options and redraw the data plots
+ * @param updateOptions
+ */
+ chart.dataPlots.change = function (updateOptions) {
+ if (updateOptions) {
+ for (var key in updateOptions) {
+ dOpts[key] = updateOptions[key]
+ }
+ }
+
+ chart.dataPlots.objs.g.remove();
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].dataPlots.objs.g.remove()
+ }
+ chart.dataPlots.preparePlots();
+ chart.dataPlots.update()
+ };
+
+ chart.dataPlots.reset = function () {
+ chart.dataPlots.change(defaultOptions)
+ };
+ chart.dataPlots.show = function (opts) {
+ if (opts !== undefined) {
+ opts.show = true;
+ if (opts.reset) {
+ chart.dataPlots.reset()
+ }
+ } else {
+ opts = {show: true};
+ }
+ chart.dataPlots.change(opts)
+ };
+ chart.dataPlots.hide = function (opts) {
+ if (opts !== undefined) {
+ opts.show = false;
+ if (opts.reset) {
+ chart.dataPlots.reset()
+ }
+ } else {
+ opts = {show: false};
+ }
+ chart.dataPlots.change(opts)
+ };
+
+ /**
+ * Update the data plot obj values
+ */
+ chart.dataPlots.update = function () {
+ var cName, cGroup, cPlot;
+
+ // Metrics lines
+ if (chart.dataPlots.objs.g) {
+ var halfBand = chart.xScale.rangeBand() / 2; // find the middle of each band
+ for (var cMetric in chart.dataPlots.objs.lines) {
+ chart.dataPlots.objs.lines[cMetric].line
+ .x(function (d) {
+ return chart.xScale(d.x) + halfBand
+ });
+ chart.dataPlots.objs.lines[cMetric].g
+ .datum(chart.dataPlots.objs.lines[cMetric].values)
+ .attr('d', chart.dataPlots.objs.lines[cMetric].line);
+ }
+ }
+
+
+ for (cName in chart.groupObjs) {
+ cGroup = chart.groupObjs[cName];
+ cPlot = cGroup.dataPlots;
+
+ if (cPlot.objs.points) {
+ if (dOpts.plotType == 'beeswarm') {
+ var swarmBounds = getObjWidth(100, cName);
+ var yPtScale = chart.yScale.copy()
+ .range([Math.floor(chart.yScale.range()[0] / dOpts.pointSize), 0])
+ .interpolate(d3.interpolateRound)
+ .domain(chart.yScale.domain());
+ var maxWidth = Math.floor(chart.xScale.rangeBand() / dOpts.pointSize);
+ var ptsObj = {};
+ var cYBucket = null;
+ // Bucket points
+ for (var pt = 0; pt < cGroup.values.length; pt++) {
+ cYBucket = yPtScale(cGroup.values[pt]);
+ if (ptsObj.hasOwnProperty(cYBucket) !== true) {
+ ptsObj[cYBucket] = [];
+ }
+ ptsObj[cYBucket].push(cPlot.objs.points.pts[pt]
+ .attr("cx", swarmBounds.middle)
+ .attr("cy", yPtScale(cGroup.values[pt]) * dOpts.pointSize));
+ }
+ // Plot buckets
+ var rightMax = Math.min(swarmBounds.right - dOpts.pointSize);
+ for (var row in ptsObj) {
+ var leftMin = swarmBounds.left + (Math.max((maxWidth - ptsObj[row].length) / 2, 0) * dOpts.pointSize);
+ var col = 0;
+ for (pt in ptsObj[row]) {
+ ptsObj[row][pt].attr("cx", Math.min(leftMin + col * dOpts.pointSize, rightMax) + dOpts.pointSize / 2);
+ col++
+ }
+ }
+ } else { // For scatter points and points with no scatter
+ var plotBounds = null,
+ scatterWidth = 0,
+ width = 0;
+ if (dOpts.plotType == 'scatter' || typeof dOpts.plotType == 'number') {
+ //Default scatter percentage is 20% of box width
+ scatterWidth = typeof dOpts.plotType == 'number' ? dOpts.plotType : 20;
+ }
+
+ plotBounds = getObjWidth(scatterWidth, cName);
+ width = plotBounds.right - plotBounds.left;
+
+ for (var pt = 0; pt < cGroup.values.length; pt++) {
+ cPlot.objs.points.pts[pt]
+ .attr("cx", plotBounds.middle + addJitter(true, width))
+ .attr("cy", chart.yScale(cGroup.values[pt]));
+ }
+ }
+ }
+
+
+ if (cPlot.objs.bean) {
+ var beanBounds = getObjWidth(dOpts.beanWidth, cName);
+ for (var pt = 0; pt < cGroup.values.length; pt++) {
+ cPlot.objs.bean.lines[pt]
+ .attr("x1", beanBounds.left)
+ .attr("x2", beanBounds.right)
+ .attr('y1', chart.yScale(cGroup.values[pt]))
+ .attr("y2", chart.yScale(cGroup.values[pt]));
+ }
+ }
+ }
+ };
+
+ /**
+ * Create the svg elements for the data plots
+ */
+ chart.dataPlots.preparePlots = function () {
+ var cName, cPlot;
+
+ if (dOpts && dOpts.colors) {
+ chart.dataPlots.colorFunct = getColorFunct(dOpts.colors);
+ } else {
+ chart.dataPlots.colorFunct = chart.colorFunct
+ }
+
+ if (dOpts.show == false) {
+ return
+ }
+
+ // Metrics lines
+ chart.dataPlots.objs.g = chart.objs.g.append("g").attr("class", "metrics-lines");
+ if (dOpts.showLines && dOpts.showLines.length > 0) {
+ chart.dataPlots.objs.lines = {};
+ var cMetric;
+ for (var line in dOpts.showLines) {
+ cMetric = dOpts.showLines[line];
+ chart.dataPlots.objs.lines[cMetric] = {};
+ chart.dataPlots.objs.lines[cMetric].values = [];
+ for (var cGroup in chart.groupObjs) {
+ chart.dataPlots.objs.lines[cMetric].values.push({
+ x: cGroup,
+ y: chart.groupObjs[cGroup].metrics[cMetric]
+ })
+ }
+ chart.dataPlots.objs.lines[cMetric].line = d3.svg.line()
+ .interpolate("cardinal")
+ .y(function (d) {
+ return chart.yScale(d.y)
+ });
+ chart.dataPlots.objs.lines[cMetric].g = chart.dataPlots.objs.g.append("path")
+ .attr("class", "line " + cMetric)
+ .attr("data-metric", cMetric)
+ .style("fill", 'none')
+ .style("stroke", chart.colorFunct(cMetric));
+ }
+
+ }
+
+
+ for (cName in chart.groupObjs) {
+
+ cPlot = chart.groupObjs[cName].dataPlots;
+ cPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "data-plot");
+
+ // Points Plot
+ if (dOpts.showPlot) {
+ cPlot.objs.points = {g: null, pts: []};
+ cPlot.objs.points.g = cPlot.objs.g.append("g").attr("class", "points-plot");
+ for (var pt = 0; pt < chart.groupObjs[cName].values.length; pt++) {
+ cPlot.objs.points.pts.push(cPlot.objs.points.g.append("circle")
+ .attr("class", "point")
+ .attr('r', dOpts.pointSize / 2)// Options is diameter, r takes radius so divide by 2
+ .style("fill", chart.dataPlots.colorFunct(cName)));
+ }
+ }
+
+
+ // Bean lines
+ if (dOpts.showBeanLines) {
+ cPlot.objs.bean = {g: null, lines: []};
+ cPlot.objs.bean.g = cPlot.objs.g.append("g").attr("class", "bean-plot");
+ for (var pt = 0; pt < chart.groupObjs[cName].values.length; pt++) {
+ cPlot.objs.bean.lines.push(cPlot.objs.bean.g.append("line")
+ .attr("class", "bean line")
+ .style("stroke-width", '1')
+ .style("stroke", chart.dataPlots.colorFunct(cName)));
+ }
+ }
+ }
+
+ };
+ chart.dataPlots.preparePlots();
+
+ d3.select(window).on('resize.' + chart.selector + '.dataPlot', chart.dataPlots.update);
+ chart.dataPlots.update();
+ return chart;
+ };
+
+ return chart;
+}
diff --git a/silk/static/silk/lib/strftime.min.js b/silk/static/silk/lib/strftime.min.js
new file mode 100644
index 00000000..88dd19a7
--- /dev/null
+++ b/silk/static/silk/lib/strftime.min.js
@@ -0,0 +1,23 @@
+(function(){function q(c,g,n){function i(c,a,e,f){for(var b="",d=null,g=!1,l=c.length,n=!1,j=0;j99?Math.floor(f%1E3):Math.floor(f%1E3)>9?"0"+Math.floor(f%1E3):"00"+Math.floor(f%1E3);break;case 77:b+=h(a.getMinutes(),d);break;case 80:b+=a.getHours()<12?e.am:e.pm;break;case 82:b+=i(e.formats.R,a,e,f);break;case 83:b+=h(a.getSeconds(),d);break;case 84:b+=i(e.formats.T,a,e,f);break;case 85:b+=h(v(a,"sunday"),d);break;case 87:b+=h(v(a,"monday"),d);break;case 88:b+=i(e.formats.X,
+a,e,f);break;case 89:b+=a.getFullYear();break;case 90:o&&k===0?b+="GMT":(d=a.toString().match(/\(([\w\s]+)\)/),b+=d&&d[1]||"");break;case 97:b+=e.shortDays[a.getDay()];break;case 98:b+=e.shortMonths[a.getMonth()];break;case 99:b+=i(e.formats.c,a,e,f);break;case 100:b+=h(a.getDate(),d);break;case 101:b+=h(a.getDate(),d==null?" ":d);break;case 104:b+=e.shortMonths[a.getMonth()];break;case 106:d=new Date(a.getFullYear(),0,1);d=Math.ceil((a.getTime()-d.getTime())/864E5);b+=d>99?d:d>9?"0"+d:"00"+d;break;
+case 107:b+=h(a.getHours(),d==null?" ":d);break;case 108:b+=h(u(a.getHours()),d==null?" ":d);break;case 109:b+=h(a.getMonth()+1,d);break;case 110:b+="\n";break;case 111:d=a.getDate();b+=e.ordinalSuffixes?String(d)+(e.ordinalSuffixes[d-1]||w(d)):String(d)+w(d);break;case 112:b+=a.getHours()<12?e.AM:e.PM;break;case 114:b+=i(e.formats.r,a,e,f);break;case 115:b+=Math.floor(f/1E3);break;case 116:b+="\t";break;case 117:d=a.getDay();b+=d===0?7:d;break;case 118:b+=i(e.formats.v,a,e,f);break;case 119:b+=a.getDay();
+break;case 120:b+=i(e.formats.x,a,e,f);break;case 121:b+=(""+a.getFullYear()).slice(2);break;case 122:o&&k===0?b+=n?"+00:00":"+0000":(d=k!==0?k/6E4:-a.getTimezoneOffset(),g=n?":":"",m=Math.abs(d%60),b+=(d<0?"-":"+")+h(Math.floor(Math.abs(d/60)))+g+h(m));break;default:g&&(b+="%"),b+=c[j]}d=null;g=!1}else m===37?g=!0:b+=c[j]}return b}var j=c||x,k=g||0,o=n||!1,p=0,r,l=function(c,a){var e;if(a){if(e=a.getTime(),o){var f=(a.getTimezoneOffset()||0)*6E4,a=new Date(e+f+k);if((a.getTimezoneOffset()||0)*6E4!==
+f)f=(a.getTimezoneOffset()||0)*6E4,a=new Date(e+f+k)}}else e=Date.now(),e>p?(p=e,r=new Date(p),e=p,o&&(r=new Date(p+(r.getTimezoneOffset()||0)*6E4+k))):e=p,a=r;return i(c,a,j,e)};l.localize=function(c){return new q(c||j,k,o)};l.localizeByIdentifier=function(c){var a=y[c];return!a?(t('[WARNING] No locale found with identifier "'+c+'".'),l):l.localize(a)};l.timezone=function(c){var a=k,e=o,f=typeof c;if(f==="number"||f==="string")e=!0,f==="string"?(a=c[0]==="-"?-1:1,f=parseInt(c.slice(1,3),10),c=parseInt(c.slice(3,
+5),10),a=a*(60*f+c)*6E4):f==="number"&&(a=c*6E4);return new q(j,a,e)};l.utc=function(){return new q(j,k,!0)};return l}function h(c,g){if(g===""||c>9)return c;g==null&&(g="0");return g+c}function u(c){if(c===0)return 12;else if(c>12)return c-12;return c}function v(c,g){var g=g||"sunday",h=c.getDay();g==="monday"&&(h===0?h=6:h--);var i=Date.UTC(c.getFullYear(),0,1),j=Date.UTC(c.getFullYear(),c.getMonth(),c.getDate());return Math.floor((Math.floor((j-i)/864E5)+7-h)/7)}function w(c){var g=c%10;c%=100;
+if(c>=11&&c<=13||g===0||g>=4)return"th";switch(g){case 1:return"st";case 2:return"nd";case 3:return"rd"}}function t(c){typeof console!=="undefined"&&typeof console.warn=="function"&&console.warn(c)}var y={de_DE:{days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],shortDays:["So","Mo","Di","Mi","Do","Fr","Sa"],months:["Januar","Februar","M\u00e4rz","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],shortMonths:["Jan","Feb","M\u00e4r","Apr",
+"Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%d.%m.%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},en_CA:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr",
+"May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],ordinalSuffixes:["st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th","st"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%d/%m/%y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%r",x:"%D"}},en_US:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu",
+"Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],ordinalSuffixes:["st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th","st"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%m/%d/%y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",
+T:"%H:%M:%S",v:"%e-%b-%Y",X:"%r",x:"%D"}},es_MX:{days:["domingo","lunes","martes","mi\u00e9rcoles","jueves","viernes","s\u00e1bado"],shortDays:["dom","lun","mar","mi\u00e9","jue","vie","s\u00e1b"],months:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre"," diciembre"],shortMonths:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%d/%m/%Y",F:"%Y-%m-%d",R:"%H:%M",
+r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},fr_FR:{days:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],shortDays:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],months:["janvier","f\u00e9vrier","mars","avril","mai","juin","juillet","ao\u00fbt","septembre","octobre","novembre","d\u00e9cembre"],shortMonths:["janv.","f\u00e9vr.","mars","avril","mai","juin","juil.","ao\u00fbt","sept.","oct.","nov.","d\u00e9c."],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",
+D:"%d/%m/%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},it_IT:{days:["domenica","luned\u00ec","marted\u00ec","mercoled\u00ec","gioved\u00ec","venerd\u00ec","sabato"],shortDays:["dom","lun","mar","mer","gio","ven","sab"],months:["gennaio","febbraio","marzo","aprile","maggio","giugno","luglio","agosto","settembre","ottobre","novembre","dicembre"],shortMonths:["pr","mag","giu","lug","ago","set","ott","nov","dic"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",
+D:"%d/%m/%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},nl_NL:{days:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],shortDays:["zo","ma","di","wo","do","vr","za"],months:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],shortMonths:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%d-%m-%y",
+F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},pt_BR:{days:["domingo","segunda","ter\u00e7a","quarta","quinta","sexta","s\u00e1bado"],shortDays:["Dom","Seg","Ter","Qua","Qui","Sex","S\u00e1b"],months:["janeiro","fevereiro","mar\u00e7o","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro"],shortMonths:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",
+D:"%d-%m-%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},ru_RU:{days:["\u0412\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435","\u041f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a","\u0412\u0442\u043e\u0440\u043d\u0438\u043a","\u0421\u0440\u0435\u0434\u0430","\u0427\u0435\u0442\u0432\u0435\u0440\u0433","\u041f\u044f\u0442\u043d\u0438\u0446\u0430","\u0421\u0443\u0431\u0431\u043e\u0442\u0430"],shortDays:["\u0412\u0441","\u041f\u043d","\u0412\u0442",
+"\u0421\u0440","\u0427\u0442","\u041f\u0442","\u0421\u0431"],months:["\u042f\u043d\u0432\u0430\u0440\u044c","\u0424\u0435\u0432\u0440\u0430\u043b\u044c","\u041c\u0430\u0440\u0442","\u0410\u043f\u0440\u0435\u043b\u044c","\u041c\u0430\u0439","\u0418\u044e\u043d\u044c","\u0418\u044e\u043b\u044c","\u0410\u0432\u0433\u0443\u0441\u0442","\u0421\u0435\u043d\u0442\u044f\u0431\u0440\u044c","\u041e\u043a\u0442\u044f\u0431\u0440\u044c","\u041d\u043e\u044f\u0431\u0440\u044c","\u0414\u0435\u043a\u0430\u0431\u0440\u044c"],
+shortMonths:["\u044f\u043d\u0432","\u0444\u0435\u0432","\u043c\u0430\u0440","\u0430\u043f\u0440","\u043c\u0430\u0439","\u0438\u044e\u043d","\u0438\u044e\u043b","\u0430\u0432\u0433","\u0441\u0435\u043d","\u043e\u043a\u0442","\u043d\u043e\u044f","\u0434\u0435\u043a"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X",D:"%d.%m.%y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},tr_TR:{days:["Pazar","Pazartesi","Sal\u0131","\u00c7ar\u015famba","Per\u015fembe",
+"Cuma","Cumartesi"],shortDays:["Paz","Pzt","Sal","\u00c7r\u015f","Pr\u015f","Cum","Cts"],months:["Ocak","\u015eubat","Mart","Nisan","May\u0131s","Haziran","Temmuz","A\u011fustos","Eyl\u00fcl","Ekim","Kas\u0131m","Aral\u0131k"],shortMonths:["Oca","\u015eub","Mar","Nis","May","Haz","Tem","A\u011fu","Eyl","Eki","Kas","Ara"],AM:"\u00d6\u00d6",PM:"\u00d6S",am:"\u00d6\u00d6",pm:"\u00d6S",formats:{c:"%a %d %b %Y %X %Z",D:"%d-%m-%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",
+x:"%D"}},zh_CN:{days:["\u661f\u671f\u65e5","\u661f\u671f\u4e00","\u661f\u671f\u4e8c","\u661f\u671f\u4e09","\u661f\u671f\u56db","\u661f\u671f\u4e94","\u661f\u671f\u516d"],shortDays:["\u65e5","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d"],months:["\u4e00\u6708\u4efd","\u4e8c\u6708\u4efd","\u4e09\u6708\u4efd","\u56db\u6708\u4efd","\u4e94\u6708\u4efd","\u516d\u6708\u4efd","\u4e03\u6708\u4efd","\u516b\u6708\u4efd","\u4e5d\u6708\u4efd","\u5341\u6708\u4efd","\u5341\u4e00\u6708\u4efd","\u5341\u4e8c\u6708\u4efd"],
+shortMonths:["\u4e00\u6708","\u4e8c\u6708","\u4e09\u6708","\u56db\u6708","\u4e94\u6708","\u516d\u6708","\u4e03\u6708","\u516b\u6708","\u4e5d\u6708","\u5341\u6708","\u5341\u4e00\u6708","\u5341\u4e8c\u6708"],AM:"\u4e0a\u5348",PM:"\u4e0b\u5348",am:"\u4e0a\u5348",pm:"\u4e0b\u5348",formats:{c:"%a %d %b %Y %X %Z",D:"%d/%m/%y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%r",x:"%D"}}},x=y.en_US,z=new q(x,0,!1),s;typeof module!=="undefined"?s=module.exports=z:(s=function(){return this||
+(0,eval)("this")}(),s.strftime=z);if(typeof Date.now!=="function")Date.now=function(){return+new Date}})();
\ No newline at end of file
diff --git a/silk/static/silk/lib/url.js b/silk/static/silk/lib/url.js
new file mode 100644
index 00000000..94cdad7c
--- /dev/null
+++ b/silk/static/silk/lib/url.js
@@ -0,0 +1,199 @@
+(function() {
+ var url = (function() {
+
+ function _t() {
+ return new RegExp(/(.*?)\.?([^\.]*?)\.?(com|net|org|biz|ws|in|me|co\.uk|co|org\.uk|ltd\.uk|plc\.uk|me\.uk|edu|mil|br\.com|cn\.com|eu\.com|hu\.com|no\.com|qc\.com|sa\.com|se\.com|se\.net|us\.com|uy\.com|ac|co\.ac|gv\.ac|or\.ac|ac\.ac|af|am|as|at|ac\.at|co\.at|gv\.at|or\.at|asn\.au|com\.au|edu\.au|org\.au|net\.au|id\.au|be|ac\.be|adm\.br|adv\.br|am\.br|arq\.br|art\.br|bio\.br|cng\.br|cnt\.br|com\.br|ecn\.br|eng\.br|esp\.br|etc\.br|eti\.br|fm\.br|fot\.br|fst\.br|g12\.br|gov\.br|ind\.br|inf\.br|jor\.br|lel\.br|med\.br|mil\.br|net\.br|nom\.br|ntr\.br|odo\.br|org\.br|ppg\.br|pro\.br|psc\.br|psi\.br|rec\.br|slg\.br|tmp\.br|tur\.br|tv\.br|vet\.br|zlg\.br|br|ab\.ca|bc\.ca|mb\.ca|nb\.ca|nf\.ca|ns\.ca|nt\.ca|on\.ca|pe\.ca|qc\.ca|sk\.ca|yk\.ca|ca|cc|ac\.cn|com\.cn|edu\.cn|gov\.cn|org\.cn|bj\.cn|sh\.cn|tj\.cn|cq\.cn|he\.cn|nm\.cn|ln\.cn|jl\.cn|hl\.cn|js\.cn|zj\.cn|ah\.cn|gd\.cn|gx\.cn|hi\.cn|sc\.cn|gz\.cn|yn\.cn|xz\.cn|sn\.cn|gs\.cn|qh\.cn|nx\.cn|xj\.cn|tw\.cn|hk\.cn|mo\.cn|cn|cx|cz|de|dk|fo|com\.ec|tm\.fr|com\.fr|asso\.fr|presse\.fr|fr|gf|gs|co\.il|net\.il|ac\.il|k12\.il|gov\.il|muni\.il|ac\.in|co\.in|org\.in|ernet\.in|gov\.in|net\.in|res\.in|is|it|ac\.jp|co\.jp|go\.jp|or\.jp|ne\.jp|ac\.kr|co\.kr|go\.kr|ne\.kr|nm\.kr|or\.kr|li|lt|lu|asso\.mc|tm\.mc|com\.mm|org\.mm|net\.mm|edu\.mm|gov\.mm|ms|nl|no|nu|pl|ro|org\.ro|store\.ro|tm\.ro|firm\.ro|www\.ro|arts\.ro|rec\.ro|info\.ro|nom\.ro|nt\.ro|se|si|com\.sg|org\.sg|net\.sg|gov\.sg|sk|st|tf|ac\.th|co\.th|go\.th|mi\.th|net\.th|or\.th|tm|to|com\.tr|edu\.tr|gov\.tr|k12\.tr|net\.tr|org\.tr|com\.tw|org\.tw|net\.tw|ac\.uk|uk\.com|uk\.net|gb\.com|gb\.net|vg|sh|kz|ch|info|ua|gov|name|pro|ie|hk|com\.hk|org\.hk|net\.hk|edu\.hk|us|tk|cd|by|ad|lv|eu\.lv|bz|es|jp|cl|ag|mobi|eu|co\.nz|org\.nz|net\.nz|maori\.nz|iwi\.nz|io|la|md|sc|sg|vc|tw|travel|my|se|tv|pt|com\.pt|edu\.pt|asia|fi|com\.ve|net\.ve|fi|org\.ve|web\.ve|info\.ve|co\.ve|tel|im|gr|ru|net\.ru|org\.ru|hr|com\.hr|ly|xyz)$/);
+ }
+
+ function _d(s) {
+ return decodeURIComponent(s.replace(/\+/g, ' '));
+ }
+
+ function _i(arg, str) {
+ var sptr = arg.charAt(0),
+ split = str.split(sptr);
+
+ if (sptr === arg) { return split; }
+
+ arg = parseInt(arg.substring(1), 10);
+
+ return split[arg < 0 ? split.length + arg : arg - 1];
+ }
+
+ function _f(arg, str) {
+ var sptr = arg.charAt(0),
+ split = str.split('&'),
+ field = [],
+ params = {},
+ tmp = [],
+ arg2 = arg.substring(1);
+
+ for (var i = 0, ii = split.length; i < ii; i++) {
+ field = split[i].match(/(.*?)=(.*)/);
+
+ // TODO: regex should be able to handle this.
+ if ( ! field) {
+ field = [split[i], split[i], ''];
+ }
+
+ if (field[1].replace(/\s/g, '') !== '') {
+ field[2] = _d(field[2] || '');
+
+ // If we have a match just return it right away.
+ if (arg2 === field[1]) { return field[2]; }
+
+ // Check for array pattern.
+ tmp = field[1].match(/(.*)\[([0-9]+)\]/);
+
+ if (tmp) {
+ params[tmp[1]] = params[tmp[1]] || [];
+
+ params[tmp[1]][tmp[2]] = field[2];
+ }
+ else {
+ params[field[1]] = field[2];
+ }
+ }
+ }
+
+ if (sptr === arg) { return params; }
+
+ return params[arg2];
+ }
+
+ return function(arg, url) {
+ var _l = {}, tmp, tmp2;
+
+ if (arg === 'tld?') { return _t(); }
+
+ url = url || window.location.toString();
+
+ if ( ! arg) { return url; }
+
+ arg = arg.toString();
+
+ if (tmp = url.match(/^mailto:([^\/].+)/)) {
+ _l.protocol = 'mailto';
+ _l.email = tmp[1];
+ }
+ else {
+
+ // Ignore Hashbangs.
+ if (tmp = url.match(/(.*?)\/#\!(.*)/)) {
+ url = tmp[1] + tmp[2];
+ }
+
+ // Hash.
+ if (tmp = url.match(/(.*?)#(.*)/)) {
+ _l.hash = tmp[2];
+ url = tmp[1];
+ }
+
+ // Return hash parts.
+ if (_l.hash && arg.match(/^#/)) { return _f(arg, _l.hash); }
+
+ // Query
+ if (tmp = url.match(/(.*?)\?(.*)/)) {
+ _l.query = tmp[2];
+ url = tmp[1];
+ }
+
+ // Return query parts.
+ if (_l.query && arg.match(/^\?/)) { return _f(arg, _l.query); }
+
+ // Protocol.
+ if (tmp = url.match(/(.*?)\:?\/\/(.*)/)) {
+ _l.protocol = tmp[1].toLowerCase();
+ url = tmp[2];
+ }
+
+ // Path.
+ if (tmp = url.match(/(.*?)(\/.*)/)) {
+ _l.path = tmp[2];
+ url = tmp[1];
+ }
+
+ // Clean up path.
+ _l.path = (_l.path || '').replace(/^([^\/])/, '/$1');
+
+ // Return path parts.
+ if (arg.match(/^[\-0-9]+$/)) { arg = arg.replace(/^([^\/])/, '/$1'); }
+ if (arg.match(/^\//)) { return _i(arg, _l.path.substring(1)); }
+
+ // File.
+ tmp = _i('/-1', _l.path.substring(1));
+
+ if (tmp && (tmp = tmp.match(/(.*?)\.(.*)/))) {
+ _l.file = tmp[0];
+ _l.filename = tmp[1];
+ _l.fileext = tmp[2];
+ }
+
+ // Port.
+ if (tmp = url.match(/(.*)\:([0-9]+)$/)) {
+ _l.port = tmp[2];
+ url = tmp[1];
+ }
+
+ // Auth.
+ if (tmp = url.match(/(.*?)@(.*)/)) {
+ _l.auth = tmp[1];
+ url = tmp[2];
+ }
+
+ // User and pass.
+ if (_l.auth) {
+ tmp = _l.auth.match(/(.*)\:(.*)/);
+
+ _l.user = tmp ? tmp[1] : _l.auth;
+ _l.pass = tmp ? tmp[2] : undefined;
+ }
+
+ // Hostname.
+ _l.hostname = url.toLowerCase();
+
+ // Return hostname parts.
+ if (arg.charAt(0) === '.') { return _i(arg, _l.hostname); }
+
+ // Domain, tld and sub domain.
+ if (_t()) {
+ tmp = _l.hostname.match(_t());
+
+ if (tmp) {
+ _l.tld = tmp[3];
+ _l.domain = tmp[2] ? tmp[2] + '.' + tmp[3] : undefined;
+ _l.sub = tmp[1] || undefined;
+ }
+ }
+
+ // Set port and protocol defaults if not set.
+ _l.port = _l.port || (_l.protocol === 'https' ? '443' : '80');
+ _l.protocol = _l.protocol || (_l.port === '443' ? 'https' : 'http');
+ }
+
+ // Return arg.
+ if (arg in _l) { return _l[arg]; }
+
+ // Return everything.
+ if (arg === '{}') { return _l; }
+
+ // Default to undefined for no match.
+ return undefined;
+ };
+ })();
+
+ if (typeof window.define === 'function' && window.define.amd) {
+ window.define('js-url', [], function () {
+ return url;
+ });
+ } else {
+ if(typeof window.jQuery !== 'undefined') {
+ window.jQuery.extend({
+ url: function(arg, url) { return window.url(arg, url); }
+ });
+ }
+
+ window.url = url;
+ }
+
+})();
diff --git a/silk/static/silk/lib/url.min.js b/silk/static/silk/lib/url.min.js
new file mode 100644
index 00000000..375c79a6
--- /dev/null
+++ b/silk/static/silk/lib/url.min.js
@@ -0,0 +1 @@
+/*! js-url - v2.5.0 - 2017-04-22 */!function(){var a=function(){function a(){}function b(a){return decodeURIComponent(a.replace(/\+/g," "))}function c(a,b){var c=a.charAt(0),d=b.split(c);return c===a?d:(a=parseInt(a.substring(1),10),d[a<0?d.length+a:a-1])}function d(a,c){for(var d=a.charAt(0),e=c.split("&"),f=[],g={},h=[],i=a.substring(1),j=0,k=e.length;j
+
+
+ {{ block.super }}
+
+
+
+{% endblock %}
+
+{% block style %}
+ {{ block.super }}
+
+
+{% endblock %}
+
+{% block filter %}
+
+
+ {{ block.super }}
+{% endblock %}
+
+{% block filters %}
+ {% request_filter options_paths options_status_codes options_methods view_names filters %}
+{% endblock %}
+
+{% block data %}
+
+
+
+
+
+
+{% endblock %}
diff --git a/silk/templates/silk/inclusion/request_filter.html b/silk/templates/silk/inclusion/request_filter.html
new file mode 100644
index 00000000..cac5933f
--- /dev/null
+++ b/silk/templates/silk/inclusion/request_filter.html
@@ -0,0 +1,174 @@
+
+ Request
+
+
+ Date Range
+
+ View
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/silk/templates/silk/inclusion/root_menu.html b/silk/templates/silk/inclusion/root_menu.html
index 19091d92..3421f02a 100644
--- a/silk/templates/silk/inclusion/root_menu.html
+++ b/silk/templates/silk/inclusion/root_menu.html
@@ -6,6 +6,15 @@
+{% if SILKY_DISTRIBUTION_TAB %}
+
+{% endif %}