Built with blockbuilder.org
Last active
October 25, 2018 04:44
-
-
Save renauld94/e8b5fdda8b609859ebb784c5812f7cc3 to your computer and use it in GitHub Desktop.
data table connecting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!function(r){function n(r){return r}function t(r,n){for(var t=0,e=n.length,u=Array(e);e>t;++t)u[t]=r[n[t]];return u}function e(r){function n(n,t,e,u){for(;u>e;){var f=e+u>>>1;r(n[f])<t?e=f+1:u=f}return e}function t(n,t,e,u){for(;u>e;){var f=e+u>>>1;t<r(n[f])?u=f:e=f+1}return e}return t.right=t,t.left=n,t}function u(r){function n(r,n,t){for(var u=t-n,f=(u>>>1)+1;--f>0;)e(r,f,u,n);return r}function t(r,n,t){for(var u,f=t-n;--f>0;)u=r[n],r[n]=r[n+f],r[n+f]=u,e(r,1,f,n);return r}function e(n,t,e,u){for(var f,o=n[--u+t],i=r(o);(f=t<<1)<=e&&(e>f&&r(n[u+f])>r(n[u+f+1])&&f++,!(i<=r(n[u+f])));)n[u+t]=n[u+f],t=f;n[u+t]=o}return n.sort=t,n}function f(r){function n(n,e,u,f){var o,i,a,c,l=Array(f=Math.min(u-e,f));for(i=0;f>i;++i)l[i]=n[e++];if(t(l,0,f),u>e){o=r(l[0]);do(a=r(c=n[e])>o)&&(l[0]=c,o=r(t(l,0,f)[0]));while(++e<u)}return l}var t=u(r);return n}function o(r){function n(n,t,e){for(var u=t+1;e>u;++u){for(var f=u,o=n[u],i=r(o);f>t&&r(n[f-1])>i;--f)n[f]=n[f-1];n[f]=o}return n}return n}function i(r){function n(r,n,u){return(N>u-n?e:t)(r,n,u)}function t(t,e,u){var f,o=0|(u-e)/6,i=e+o,a=u-1-o,c=e+u-1>>1,l=c-o,v=c+o,s=t[i],h=r(s),d=t[l],p=r(d),g=t[c],y=r(g),m=t[v],x=r(m),b=t[a],A=r(b);h>p&&(f=s,s=d,d=f,f=h,h=p,p=f),x>A&&(f=m,m=b,b=f,f=x,x=A,A=f),h>y&&(f=s,s=g,g=f,f=h,h=y,y=f),p>y&&(f=d,d=g,g=f,f=p,p=y,y=f),h>x&&(f=s,s=m,m=f,f=h,h=x,x=f),y>x&&(f=g,g=m,m=f,f=y,y=x,x=f),p>A&&(f=d,d=b,b=f,f=p,p=A,A=f),p>y&&(f=d,d=g,g=f,f=p,p=y,y=f),x>A&&(f=m,m=b,b=f,f=x,x=A,A=f);var k=d,O=p,w=m,E=x;t[i]=s,t[l]=t[e],t[c]=g,t[v]=t[u-1],t[a]=b;var M=e+1,U=u-2,z=E>=O&&O>=E;if(z)for(var N=M;U>=N;++N){var C=t[N],S=r(C);if(O>S)N!==M&&(t[N]=t[M],t[M]=C),++M;else if(S>O)for(;;){var q=r(t[U]);{if(!(q>O)){if(O>q){t[N]=t[M],t[M++]=t[U],t[U--]=C;break}t[N]=t[U],t[U--]=C;break}U--}}}else for(var N=M;U>=N;N++){var C=t[N],S=r(C);if(O>S)N!==M&&(t[N]=t[M],t[M]=C),++M;else if(S>E)for(;;){var q=r(t[U]);{if(!(q>E)){O>q?(t[N]=t[M],t[M++]=t[U],t[U--]=C):(t[N]=t[U],t[U--]=C);break}if(U--,N>U)break}}}if(t[e]=t[M-1],t[M-1]=k,t[u-1]=t[U+1],t[U+1]=w,n(t,e,M-1),n(t,U+2,u),z)return t;if(i>M&&U>a){for(var F,q;(F=r(t[M]))<=O&&F>=O;)++M;for(;(q=r(t[U]))<=E&&q>=E;)--U;for(var N=M;U>=N;N++){var C=t[N],S=r(C);if(O>=S&&S>=O)N!==M&&(t[N]=t[M],t[M]=C),M++;else if(E>=S&&S>=E)for(;;){var q=r(t[U]);{if(!(E>=q&&q>=E)){O>q?(t[N]=t[M],t[M++]=t[U],t[U--]=C):(t[N]=t[U],t[U--]=C);break}if(U--,N>U)break}}}}return n(t,M,U+1)}var e=o(r);return n}function a(r){for(var n=Array(r),t=-1;++t<r;)n[t]=0;return n}function c(r,n){for(var t=r.length;n>t;)r[t++]=0;return r}function l(r,n){if(n>32)throw Error("invalid array width!");return r}function v(r,n){return function(t){var e=t.length;return[r.left(t,n,0,e),r.right(t,n,0,e)]}}function s(r,n){var t=n[0],e=n[1];return function(n){var u=n.length;return[r.left(n,t,0,u),r.left(n,e,0,u)]}}function h(r){return[0,r.length]}function d(){return null}function p(){return 0}function g(r){return r+1}function y(r){return r-1}function m(r){return function(n,t){return n+ +r(t)}}function x(r){return function(n,t){return n-r(t)}}function b(){function r(r){var n=E,t=r.length;return t&&(b=b.concat(r),z=F(z,E+=t),S.forEach(function(e){e(r,n,t)})),l}function e(){for(var r=A(E,E),n=[],t=0,e=0;E>t;++t)z[t]?r[t]=e++:n.push(t);N.forEach(function(r){r(0,[],n)}),q.forEach(function(n){n(r)});for(var u,t=0,e=0;E>t;++t)(u=z[t])&&(t!==e&&(z[e]=u,b[e]=b[t]),++e);for(b.length=e;E>e;)z[--E]=0}function o(r){function e(n,e,u){T=n.map(r),V=$(k(u),0,u),T=t(T,V);var f,o=_(T),i=o[0],a=o[1];if(W)for(f=0;u>f;++f)W(T[f],f)||(z[V[f]+e]|=Y);else{for(f=0;i>f;++f)z[V[f]+e]|=Y;for(f=a;u>f;++f)z[V[f]+e]|=Y}if(!e)return P=T,Q=V,tn=i,en=a,void 0;var c=P,l=Q,v=0,s=0;for(P=Array(E),Q=A(E,E),f=0;e>v&&u>s;++f)c[v]<T[s]?(P[f]=c[v],Q[f]=l[v++]):(P[f]=T[s],Q[f]=V[s++]+e);for(;e>v;++v,++f)P[f]=c[v],Q[f]=l[v];for(;u>s;++s,++f)P[f]=T[s],Q[f]=V[s]+e;o=_(P),tn=o[0],en=o[1]}function o(r,n,t){rn.forEach(function(r){r(T,V,n,t)}),T=V=null}function a(r){for(var n,t=0,e=0;E>t;++t)z[n=Q[t]]&&(t!==e&&(P[e]=P[t]),Q[e]=r[n],++e);for(P.length=e;E>e;)Q[e++]=0;var u=_(P);tn=u[0],en=u[1]}function c(r){var n=r[0],t=r[1];if(W)return W=null,G(function(r,e){return e>=n&&t>e}),tn=n,en=t,X;var e,u,f,o=[],i=[];if(tn>n)for(e=n,u=Math.min(tn,t);u>e;++e)z[f=Q[e]]^=Y,o.push(f);else if(n>tn)for(e=tn,u=Math.min(n,en);u>e;++e)z[f=Q[e]]^=Y,i.push(f);if(t>en)for(e=Math.max(n,en),u=t;u>e;++e)z[f=Q[e]]^=Y,o.push(f);else if(en>t)for(e=Math.max(tn,t),u=en;u>e;++e)z[f=Q[e]]^=Y,i.push(f);return tn=n,en=t,N.forEach(function(r){r(Y,o,i)}),X}function l(r){return null==r?B():Array.isArray(r)?j(r):"function"==typeof r?D(r):C(r)}function C(r){return c((_=v(w,r))(P))}function j(r){return c((_=s(w,r))(P))}function B(){return c((_=h)(P))}function D(r){return _=h,G(W=r),tn=0,en=E,X}function G(r){var n,t,e,u=[],f=[];for(n=0;E>n;++n)!(z[t=Q[n]]&Y)^!!(e=r(P[n],n))&&(e?(z[t]&=Z,u.push(t)):(z[t]|=Y,f.push(t)));N.forEach(function(r){r(Y,u,f)})}function H(r){for(var n,t=[],e=en;--e>=tn&&r>0;)z[n=Q[e]]||(t.push(b[n]),--r);return t}function I(r){for(var n,t=[],e=tn;en>e&&r>0;)z[n=Q[e]]||(t.push(b[n]),--r),e++;return t}function J(r){function t(n,t,e,u){function f(){++T===L&&(m=R(m,K<<=1),B=R(B,K),L=O(K))}var l,v,s,h,p,g,y=j,m=A(T,L),x=H,k=J,w=T,M=0,U=0;for(X&&(x=k=d),j=Array(T),T=0,B=w>1?F(B,E):A(E,L),w&&(s=(v=y[0]).key);u>U&&!((h=r(n[U]))>=h);)++U;for(;u>U;){for(v&&h>=s?(p=v,g=s,m[M]=T,(v=y[++M])&&(s=v.key)):(p={key:h,value:k()},g=h),j[T]=p;!(h>g||(B[l=t[U]+e]=T,z[l]&Z||(p.value=x(p.value,b[l])),++U>=u));)h=r(n[U]);f()}for(;w>M;)j[m[M]=T]=y[M++],f();if(T>M)for(M=0;e>M;++M)B[M]=m[B[M]];l=N.indexOf(V),T>1?(V=o,W=a):(!T&&$&&(T=1,j=[{key:null,value:k()}]),1===T?(V=i,W=c):(V=d,W=d),B=null),N[l]=V}function e(){if(T>1){for(var r=T,n=j,t=A(r,r),e=0,u=0;E>e;++e)z[e]&&(t[B[u]=B[e]]=1,++u);for(j=[],T=0,e=0;r>e;++e)t[e]&&(t[e]=T++,j.push(n[e]));if(T>1)for(var e=0;u>e;++e)B[e]=t[B[e]];else B=null;N[N.indexOf(V)]=T>1?(W=a,V=o):1===T?(W=c,V=i):W=V=d}else if(1===T){if($)return;for(var e=0;E>e;++e)if(z[e])return;j=[],T=0,N[N.indexOf(V)]=V=W=d}}function o(r,n,t){if(r!==Y&&!X){var e,u,f,o;for(e=0,f=n.length;f>e;++e)z[u=n[e]]&Z||(o=j[B[u]],o.value=H(o.value,b[u]));for(e=0,f=t.length;f>e;++e)(z[u=t[e]]&Z)===r&&(o=j[B[u]],o.value=I(o.value,b[u]))}}function i(r,n,t){if(r!==Y&&!X){var e,u,f,o=j[0];for(e=0,f=n.length;f>e;++e)z[u=n[e]]&Z||(o.value=H(o.value,b[u]));for(e=0,f=t.length;f>e;++e)(z[u=t[e]]&Z)===r&&(o.value=I(o.value,b[u]))}}function a(){var r,n;for(r=0;T>r;++r)j[r].value=J();for(r=0;E>r;++r)z[r]&Z||(n=j[B[r]],n.value=H(n.value,b[r]))}function c(){var r,n=j[0];for(n.value=J(),r=0;E>r;++r)z[r]&Z||(n.value=H(n.value,b[r]))}function l(){return X&&(W(),X=!1),j}function v(r){var n=D(l(),0,j.length,r);return G.sort(n,0,n.length)}function s(r,n,t){return H=r,I=n,J=t,X=!0,S}function h(){return s(g,y,p)}function k(r){return s(m(r),x(r),p)}function w(r){function n(n){return r(n.value)}return D=f(n),G=u(n),S}function M(){return w(n)}function U(){return T}function C(){var r=N.indexOf(V);return r>=0&&N.splice(r,1),r=rn.indexOf(t),r>=0&&rn.splice(r,1),r=q.indexOf(e),r>=0&&q.splice(r,1),S}var S={top:v,all:l,reduce:s,reduceCount:h,reduceSum:k,order:w,orderNatural:M,size:U,dispose:C,remove:C};nn.push(S);var j,B,D,G,H,I,J,K=8,L=O(K),T=0,V=d,W=d,X=!0,$=r===d;return arguments.length<1&&(r=n),N.push(V),rn.push(t),q.push(e),t(P,Q,0,E),h().orderNatural()}function K(){var r=J(d),n=r.all;return delete r.all,delete r.top,delete r.order,delete r.orderNatural,delete r.size,r.value=function(){return n()[0].value},r}function L(){nn.forEach(function(r){r.dispose()});var r=S.indexOf(e);return r>=0&&S.splice(r,1),r=S.indexOf(o),r>=0&&S.splice(r,1),r=q.indexOf(a),r>=0&&q.splice(r,1),M&=Z,B()}var P,Q,T,V,W,X={filter:l,filterExact:C,filterRange:j,filterFunction:D,filterAll:B,top:H,bottom:I,group:J,groupAll:K,dispose:L,remove:L},Y=~M&-~M,Z=~Y,$=i(function(r){return T[r]}),_=h,rn=[],nn=[],tn=0,en=0;return S.unshift(e),S.push(o),q.push(a),M|=Y,(U>=32?!Y:M&(1<<U)-1)&&(z=R(z,U<<=1)),e(b,0,E),o(b,0,E),X}function a(){function r(r,n){var t;if(!h)for(t=n;E>t;++t)z[t]||(a=c(a,b[t]))}function n(r,n,t){var e,u,f;if(!h){for(e=0,f=n.length;f>e;++e)z[u=n[e]]||(a=c(a,b[u]));for(e=0,f=t.length;f>e;++e)z[u=t[e]]===r&&(a=l(a,b[u]))}}function t(){var r;for(a=v(),r=0;E>r;++r)z[r]||(a=c(a,b[r]))}function e(r,n,t){return c=r,l=n,v=t,h=!0,s}function u(){return e(g,y,p)}function f(r){return e(m(r),x(r),p)}function o(){return h&&(t(),h=!1),a}function i(){var t=N.indexOf(n);return t>=0&&N.splice(t),t=S.indexOf(r),t>=0&&S.splice(t),s}var a,c,l,v,s={reduce:e,reduceCount:u,reduceSum:f,value:o,dispose:i,remove:i},h=!0;return N.push(n),S.push(r),r(b,0,E),u()}function c(){return E}var l={add:r,remove:e,dimension:o,groupAll:a,size:c},b=[],E=0,M=0,U=8,z=C(0),N=[],S=[],q=[];return arguments.length?r(arguments[0]):l}function A(r,n){return(257>n?C:65537>n?S:q)(r)}function k(r){for(var n=A(r,r),t=-1;++t<r;)n[t]=t;return n}function O(r){return 8===r?256:16===r?65536:4294967296}b.version="1.3.11",b.permute=t;var w=b.bisect=e(n);w.by=e;var E=b.heap=u(n);E.by=u;var M=b.heapselect=f(n);M.by=f;var U=b.insertionsort=o(n);U.by=o;var z=b.quicksort=i(n);z.by=i;var N=32,C=a,S=a,q=a,F=c,R=l;"undefined"!=typeof Uint8Array&&(C=function(r){return new Uint8Array(r)},S=function(r){return new Uint16Array(r)},q=function(r){return new Uint32Array(r)},F=function(r,n){if(r.length>=n)return r;var t=new r.constructor(n);return t.set(r),t},R=function(r,n){var t;switch(n){case 16:t=S(r.length);break;case 32:t=q(r.length);break;default:throw Error("invalid array width!")}return t.set(r),t}),r.crossfilter=b}("undefined"!=typeof exports&&exports||this); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
div.dc-chart { | |
float: left; | |
} | |
.dc-chart rect.bar { | |
stroke: none; | |
cursor: pointer; | |
} | |
.dc-chart rect.bar:hover { | |
fill-opacity: .5; | |
} | |
.dc-chart rect.stack1 { | |
stroke: none; | |
fill: red; | |
} | |
.dc-chart rect.stack2 { | |
stroke: none; | |
fill: green; | |
} | |
.dc-chart rect.deselected { | |
stroke: none; | |
fill: #ccc; | |
} | |
.dc-chart .empty-chart .pie-slice path { | |
fill: #FFEEEE; | |
cursor: default; | |
} | |
.dc-chart .empty-chart .pie-slice { | |
cursor: default; | |
} | |
.dc-chart .pie-slice { | |
fill: white; | |
font-size: 12px; | |
cursor: pointer; | |
} | |
.dc-chart .pie-slice.external{ | |
fill: black; | |
} | |
.dc-chart .pie-slice :hover { | |
fill-opacity: .8; | |
} | |
.dc-chart .pie-slice.highlight { | |
fill-opacity: .8; | |
} | |
.dc-chart .selected path { | |
stroke-width: 3; | |
stroke: #ccc; | |
fill-opacity: 1; | |
} | |
.dc-chart .deselected path { | |
stroke: none; | |
fill-opacity: .5; | |
fill: #ccc; | |
} | |
.dc-chart .axis path, .axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.dc-chart .axis text { | |
font: 10px sans-serif; | |
} | |
.dc-chart .grid-line { | |
fill: none; | |
stroke: #ccc; | |
opacity: .5; | |
shape-rendering: crispEdges; | |
} | |
.dc-chart .grid-line line { | |
fill: none; | |
stroke: #ccc; | |
opacity: .5; | |
shape-rendering: crispEdges; | |
} | |
.dc-chart .brush rect.background { | |
z-index: -999; | |
} | |
.dc-chart .brush rect.extent { | |
fill: steelblue; | |
fill-opacity: .125; | |
} | |
.dc-chart .brush .resize path { | |
fill: #eee; | |
stroke: #666; | |
} | |
.dc-chart path.line { | |
fill: none; | |
stroke-width: 1.5px; | |
} | |
.dc-chart circle.dot { | |
stroke: none; | |
} | |
.dc-chart g.dc-tooltip path { | |
fill: none; | |
stroke: grey; | |
stroke-opacity: .8; | |
} | |
.dc-chart path.area { | |
fill-opacity: .3; | |
stroke: none; | |
} | |
.dc-chart .node { | |
font-size: 0.7em; | |
cursor: pointer; | |
} | |
.dc-chart .node :hover { | |
fill-opacity: .8; | |
} | |
.dc-chart .selected circle { | |
stroke-width: 3; | |
stroke: #ccc; | |
fill-opacity: 1; | |
} | |
.dc-chart .deselected circle { | |
stroke: none; | |
fill-opacity: .5; | |
fill: #ccc; | |
} | |
.dc-chart .bubble { | |
stroke: none; | |
fill-opacity: 0.6; | |
} | |
.dc-data-count { | |
float: right; | |
margin-top: 15px; | |
margin-right: 15px; | |
} | |
.dc-data-count .filter-count { | |
color: #3182bd; | |
font-weight: bold; | |
} | |
.dc-data-count .total-count { | |
color: #3182bd; | |
font-weight: bold; | |
} | |
.dc-data-table { | |
} | |
.dc-chart g.state { | |
cursor: pointer; | |
} | |
.dc-chart g.state :hover { | |
fill-opacity: .8; | |
} | |
.dc-chart g.state path { | |
stroke: white; | |
} | |
.dc-chart g.selected path { | |
} | |
.dc-chart g.deselected path { | |
fill: grey; | |
} | |
.dc-chart g.selected text { | |
} | |
.dc-chart g.deselected text { | |
display: none; | |
} | |
.dc-chart g.county path { | |
stroke: white; | |
fill: none; | |
} | |
.dc-chart g.debug rect { | |
fill: blue; | |
fill-opacity: .2; | |
} | |
.dc-chart g.row rect { | |
fill-opacity: 0.8; | |
cursor: pointer; | |
} | |
.dc-chart g.row rect:hover { | |
fill-opacity: 0.6; | |
} | |
.dc-chart g.row text { | |
fill: white; | |
font-size: 12px; | |
cursor: pointer; | |
} | |
.dc-legend { | |
font-size: 11px; | |
} | |
.dc-legend-item { | |
cursor: pointer; | |
} | |
.dc-chart g.axis text { | |
/* Makes it so the user can't accidentally click and select text that is meant as a label only */ | |
-webkit-user-select: none; /* Chrome/Safari */ | |
-moz-user-select: none; /* Firefox */ | |
-ms-user-select: none; /* IE10 */ | |
-o-user-select: none; | |
user-select: none; | |
pointer-events: none; | |
} | |
.dc-chart path.highlight { | |
stroke-width: 3; | |
fill-opacity: 1; | |
stroke-opacity: 1; | |
} | |
.dc-chart .highlight { | |
fill-opacity: 1; | |
stroke-opacity: 1; | |
} | |
.dc-chart .fadeout { | |
fill-opacity: 0.2; | |
stroke-opacity: 0.2; | |
} | |
.dc-chart path.dc-symbol, g.dc-legend-item.fadeout { | |
fill-opacity: 0.5; | |
stroke-opacity: 0.5; | |
} | |
.dc-hard .number-display { | |
float: none; | |
} | |
.dc-chart .box text { | |
font: 10px sans-serif; | |
-webkit-user-select: none; /* Chrome/Safari */ | |
-moz-user-select: none; /* Firefox */ | |
-ms-user-select: none; /* IE10 */ | |
-o-user-select: none; | |
user-select: none; | |
pointer-events: none; | |
} | |
.dc-chart .box line, | |
.dc-chart .box circle { | |
fill: #fff; | |
stroke: #000; | |
stroke-width: 1.5px; | |
} | |
.dc-chart .box rect { | |
stroke: #000; | |
stroke-width: 1.5px; | |
} | |
.dc-chart .box .center { | |
stroke-dasharray: 3,3; | |
} | |
.dc-chart .box .outlier { | |
fill: none; | |
stroke: #ccc; | |
} | |
.dc-chart .box.deselected .box { | |
fill: #ccc; | |
} | |
.dc-chart .box.deselected { | |
opacity: .5; | |
} | |
.dc-chart .symbol{ | |
stroke: none; | |
} | |
.dc-chart .heatmap .box-group.deselected rect { | |
stroke: none; | |
fill-opacity: .5; | |
fill: #ccc; | |
} | |
.dc-chart .heatmap g.axis text { | |
pointer-events: all; | |
cursor: pointer; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* dc 2.0.0-alpha.5 | |
* http://dc-js.github.io/dc.js/ | |
* Copyright 2012 Nick Zhu and other contributors | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
(function() { function _dc(d3, crossfilter) { | |
'use strict'; | |
/** | |
#### Version 2.0.0-alpha.5 | |
The entire dc.js library is scoped under the **dc** name space. It does not introduce anything else | |
into the global name space. | |
#### Function Chaining | |
Most dc functions are designed to allow function chaining, meaning they return the current chart | |
instance whenever it is appropriate. This way chart configuration can be written in the following | |
style: | |
```js | |
chart.width(300) | |
.height(300) | |
.filter('sunday') | |
``` | |
The getter forms of functions do not participate in function chaining because they necessarily | |
return values that are not the chart. (Although some, such as `.svg` and `.xAxis`, return values | |
that are chainable d3 objects.) | |
**/ | |
/*jshint -W062*/ | |
/*jshint -W079*/ | |
var dc = { | |
version: '2.0.0-alpha.5', | |
constants: { | |
CHART_CLASS: 'dc-chart', | |
DEBUG_GROUP_CLASS: 'debug', | |
STACK_CLASS: 'stack', | |
DESELECTED_CLASS: 'deselected', | |
SELECTED_CLASS: 'selected', | |
NODE_INDEX_NAME: '__index__', | |
GROUP_INDEX_NAME: '__group_index__', | |
DEFAULT_CHART_GROUP: '__default_chart_group__', | |
EVENT_DELAY: 40, | |
NEGLIGIBLE_NUMBER: 1e-10 | |
}, | |
_renderlet: null | |
}; | |
dc.chartRegistry = function () { | |
// chartGroup:string => charts:array | |
var _chartMap = {}; | |
function initializeChartGroup(group) { | |
if (!group) { | |
group = dc.constants.DEFAULT_CHART_GROUP; | |
} | |
if (!_chartMap[group]) { | |
_chartMap[group] = []; | |
} | |
return group; | |
} | |
return { | |
has: function (chart) { | |
for (var e in _chartMap) { | |
if (_chartMap[e].indexOf(chart) >= 0) { | |
return true; | |
} | |
} | |
return false; | |
}, | |
register: function (chart, group) { | |
group = initializeChartGroup(group); | |
_chartMap[group].push(chart); | |
}, | |
deregister: function (chart, group) { | |
group = initializeChartGroup(group); | |
for (var i = 0; i < _chartMap[group].length; i++) { | |
if (_chartMap[group][i].anchorName() === chart.anchorName()) { | |
_chartMap[group].splice(i, 1); | |
break; | |
} | |
} | |
}, | |
clear: function (group) { | |
if (group) { | |
delete _chartMap[group]; | |
} else { | |
_chartMap = {}; | |
} | |
}, | |
list: function (group) { | |
group = initializeChartGroup(group); | |
return _chartMap[group]; | |
} | |
}; | |
}(); | |
/*jshint +W062 */ | |
/*jshint +W079*/ | |
dc.registerChart = function (chart, group) { | |
dc.chartRegistry.register(chart, group); | |
}; | |
dc.deregisterChart = function (chart, group) { | |
dc.chartRegistry.deregister(chart, group); | |
}; | |
dc.hasChart = function (chart) { | |
return dc.chartRegistry.has(chart); | |
}; | |
dc.deregisterAllCharts = function (group) { | |
dc.chartRegistry.clear(group); | |
}; | |
/** | |
## Utilities | |
**/ | |
/** | |
#### dc.filterAll([chartGroup]) | |
Clear all filters on all charts within the given chart group. If the chart group is not given then | |
only charts that belong to the default chart group will be reset. | |
**/ | |
dc.filterAll = function (group) { | |
var charts = dc.chartRegistry.list(group); | |
for (var i = 0; i < charts.length; ++i) { | |
charts[i].filterAll(); | |
} | |
}; | |
/** | |
#### dc.refocusAll([chartGroup]) | |
Reset zoom level / focus on all charts that belong to the given chart group. If the chart group is | |
not given then only charts that belong to the default chart group will be reset. | |
**/ | |
dc.refocusAll = function (group) { | |
var charts = dc.chartRegistry.list(group); | |
for (var i = 0; i < charts.length; ++i) { | |
if (charts[i].focus) { | |
charts[i].focus(); | |
} | |
} | |
}; | |
/** | |
#### dc.renderAll([chartGroup]) | |
Re-render all charts belong to the given chart group. If the chart group is not given then only | |
charts that belong to the default chart group will be re-rendered. | |
**/ | |
dc.renderAll = function (group) { | |
var charts = dc.chartRegistry.list(group); | |
for (var i = 0; i < charts.length; ++i) { | |
charts[i].render(); | |
} | |
if (dc._renderlet !== null) { | |
dc._renderlet(group); | |
} | |
}; | |
/** | |
#### dc.redrawAll([chartGroup]) | |
Redraw all charts belong to the given chart group. If the chart group is not given then only charts | |
that belong to the default chart group will be re-drawn. Redraw is different from re-render since | |
when redrawing dc tries to update the graphic incrementally, using transitions, instead of starting | |
from scratch. | |
**/ | |
dc.redrawAll = function (group) { | |
var charts = dc.chartRegistry.list(group); | |
for (var i = 0; i < charts.length; ++i) { | |
charts[i].redraw(); | |
} | |
if (dc._renderlet !== null) { | |
dc._renderlet(group); | |
} | |
}; | |
/** | |
#### dc.disableTransitions | |
If this boolean is set truthy, all transitions will be disabled, and changes to the charts will happen | |
immediately. Default: false | |
**/ | |
dc.disableTransitions = false; | |
dc.transition = function (selections, duration, callback) { | |
if (duration <= 0 || duration === undefined || dc.disableTransitions) { | |
return selections; | |
} | |
var s = selections | |
.transition() | |
.duration(duration); | |
if (typeof(callback) === 'function') { | |
callback(s); | |
} | |
return s; | |
}; | |
dc.units = {}; | |
/** | |
#### dc.units.integers | |
`dc.units.integers` is the default value for `xUnits` for the [Coordinate Grid | |
Chart](#coordinate-grid-chart) and should be used when the x values are a sequence of integers. | |
It is a function that counts the number of integers in the range supplied in its start and end parameters. | |
```js | |
chart.xUnits(dc.units.integers) // already the default | |
``` | |
**/ | |
dc.units.integers = function (s, e) { | |
return Math.abs(e - s); | |
}; | |
/** | |
#### dc.units.ordinal | |
This argument can be passed to the `xUnits` function of the to specify ordinal units for the x | |
axis. Usually this parameter is used in combination with passing `d3.scale.ordinal()` to `.x`. | |
It just returns the domain passed to it, which for ordinal charts is an array of all values. | |
```js | |
chart.xUnits(dc.units.ordinal) | |
.x(d3.scale.ordinal()) | |
``` | |
**/ | |
dc.units.ordinal = function (s, e, domain) { | |
return domain; | |
}; | |
/** | |
#### dc.units.fp.precision(precision) | |
This function generates an argument for the [Coordinate Grid Chart's](#coordinate-grid-chart) | |
`xUnits` function specifying that the x values are floating-point numbers with the given | |
precision. | |
The returned function determines how many values at the given precision will fit into the range | |
supplied in its start and end parameters. | |
```js | |
// specify values (and ticks) every 0.1 units | |
chart.xUnits(dc.units.fp.precision(0.1) | |
// there are 500 units between 0.5 and 1 if the precision is 0.001 | |
var thousandths = dc.units.fp.precision(0.001); | |
thousandths(0.5, 1.0) // returns 500 | |
``` | |
**/ | |
dc.units.fp = {}; | |
dc.units.fp.precision = function (precision) { | |
var _f = function (s, e) { | |
var d = Math.abs((e - s) / _f.resolution); | |
if (dc.utils.isNegligible(d - Math.floor(d))) { | |
return Math.floor(d); | |
} else { | |
return Math.ceil(d); | |
} | |
}; | |
_f.resolution = precision; | |
return _f; | |
}; | |
dc.round = {}; | |
dc.round.floor = function (n) { | |
return Math.floor(n); | |
}; | |
dc.round.ceil = function (n) { | |
return Math.ceil(n); | |
}; | |
dc.round.round = function (n) { | |
return Math.round(n); | |
}; | |
dc.override = function (obj, functionName, newFunction) { | |
var existingFunction = obj[functionName]; | |
obj['_' + functionName] = existingFunction; | |
obj[functionName] = newFunction; | |
}; | |
dc.renderlet = function (_) { | |
if (!arguments.length) { | |
return dc._renderlet; | |
} | |
dc._renderlet = _; | |
return dc; | |
}; | |
dc.instanceOfChart = function (o) { | |
return o instanceof Object && o.__dcFlag__ && true; | |
}; | |
dc.errors = {}; | |
dc.errors.Exception = function (msg) { | |
var _msg = msg || 'Unexpected internal error'; | |
this.message = _msg; | |
this.toString = function () { | |
return _msg; | |
}; | |
}; | |
dc.errors.InvalidStateException = function () { | |
dc.errors.Exception.apply(this, arguments); | |
}; | |
dc.dateFormat = d3.time.format('%m/%d/%Y'); | |
dc.printers = {}; | |
dc.printers.filters = function (filters) { | |
var s = ''; | |
for (var i = 0; i < filters.length; ++i) { | |
if (i > 0) { | |
s += ', '; | |
} | |
s += dc.printers.filter(filters[i]); | |
} | |
return s; | |
}; | |
dc.printers.filter = function (filter) { | |
var s = ''; | |
if (typeof filter !== 'undefined' && filter !== null) { | |
if (filter instanceof Array) { | |
if (filter.length >= 2) { | |
s = '[' + dc.utils.printSingleValue(filter[0]) + ' -> ' + dc.utils.printSingleValue(filter[1]) + ']'; | |
} else if (filter.length >= 1) { | |
s = dc.utils.printSingleValue(filter[0]); | |
} | |
} else { | |
s = dc.utils.printSingleValue(filter); | |
} | |
} | |
return s; | |
}; | |
dc.pluck = function (n, f) { | |
if (!f) { | |
return function (d) { return d[n]; }; | |
} | |
return function (d, i) { return f.call(d, d[n], i); }; | |
}; | |
dc.utils = {}; | |
dc.utils.printSingleValue = function (filter) { | |
var s = '' + filter; | |
if (filter instanceof Date) { | |
s = dc.dateFormat(filter); | |
} else if (typeof(filter) === 'string') { | |
s = filter; | |
} else if (dc.utils.isFloat(filter)) { | |
s = dc.utils.printSingleValue.fformat(filter); | |
} else if (dc.utils.isInteger(filter)) { | |
s = Math.round(filter); | |
} | |
return s; | |
}; | |
dc.utils.printSingleValue.fformat = d3.format('.2f'); | |
// FIXME: these assume than any string r is a percentage (whether or not it | |
// includes %). They also generate strange results if l is a string. | |
dc.utils.add = function (l, r) { | |
if (typeof r === 'string') { | |
r = r.replace('%', ''); | |
} | |
if (l instanceof Date) { | |
if (typeof r === 'string') { | |
r = +r; | |
} | |
var d = new Date(); | |
d.setTime(l.getTime()); | |
d.setDate(l.getDate() + r); | |
return d; | |
} else if (typeof r === 'string') { | |
var percentage = (+r / 100); | |
return l > 0 ? l * (1 + percentage) : l * (1 - percentage); | |
} else { | |
return l + r; | |
} | |
}; | |
dc.utils.subtract = function (l, r) { | |
if (typeof r === 'string') { | |
r = r.replace('%', ''); | |
} | |
if (l instanceof Date) { | |
if (typeof r === 'string') { | |
r = +r; | |
} | |
var d = new Date(); | |
d.setTime(l.getTime()); | |
d.setDate(l.getDate() - r); | |
return d; | |
} else if (typeof r === 'string') { | |
var percentage = (+r / 100); | |
return l < 0 ? l * (1 + percentage) : l * (1 - percentage); | |
} else { | |
return l - r; | |
} | |
}; | |
dc.utils.isNumber = function (n) { | |
return n === +n; | |
}; | |
dc.utils.isFloat = function (n) { | |
return n === +n && n !== (n | 0); | |
}; | |
dc.utils.isInteger = function (n) { | |
return n === +n && n === (n | 0); | |
}; | |
dc.utils.isNegligible = function (n) { | |
return !dc.utils.isNumber(n) || (n < dc.constants.NEGLIGIBLE_NUMBER && n > -dc.constants.NEGLIGIBLE_NUMBER); | |
}; | |
dc.utils.clamp = function (val, min, max) { | |
return val < min ? min : (val > max ? max : val); | |
}; | |
var _idCounter = 0; | |
dc.utils.uniqueId = function () { | |
return ++_idCounter; | |
}; | |
dc.utils.nameToId = function (name) { | |
return name.toLowerCase().replace(/[\s]/g, '_').replace(/[\.']/g, ''); | |
}; | |
dc.utils.appendOrSelect = function (parent, selector, tag) { | |
tag = tag || selector; | |
var element = parent.select(selector); | |
if (element.empty()) { | |
element = parent.append(tag); | |
} | |
return element; | |
}; | |
dc.utils.safeNumber = function (n) { return dc.utils.isNumber(+n) ? +n : 0;}; | |
dc.logger = {}; | |
dc.logger.enableDebugLog = false; | |
dc.logger.warn = function (msg) { | |
if (console) { | |
if (console.warn) { | |
console.warn(msg); | |
} else if (console.log) { | |
console.log(msg); | |
} | |
} | |
return dc.logger; | |
}; | |
dc.logger.debug = function (msg) { | |
if (dc.logger.enableDebugLog && console) { | |
if (console.debug) { | |
console.debug(msg); | |
} else if (console.log) { | |
console.log(msg); | |
} | |
} | |
return dc.logger; | |
}; | |
dc.events = { | |
current: null | |
}; | |
/** | |
#### dc.events.trigger(function[, delay]) | |
This function triggers a throttled event function with a specified delay (in milli-seconds). Events | |
that are triggered repetitively due to user interaction such brush dragging might flood the library | |
and invoke more renders than can be executed in time. Using this function to wrap your event | |
function allows the library to smooth out the rendering by throttling events and only responding to | |
the most recent event. | |
```js | |
chart.renderlet(function(chart){ | |
// smooth the rendering through event throttling | |
dc.events.trigger(function(){ | |
// focus some other chart to the range selected by user on this chart | |
someOtherChart.focus(chart.filter()); | |
}); | |
}) | |
``` | |
**/ | |
dc.events.trigger = function (closure, delay) { | |
if (!delay) { | |
closure(); | |
return; | |
} | |
dc.events.current = closure; | |
setTimeout(function () { | |
if (closure === dc.events.current) { | |
closure(); | |
} | |
}, delay); | |
}; | |
dc.filters = {}; | |
/** | |
## Filters | |
The dc.js filters are functions which are passed into crossfilter to chose which records will be | |
accumulated to produce values for the charts. In the crossfilter model, any filters applied on one | |
dimension will affect all the other dimensions but not that one. dc always applies a filter | |
function to the dimension; the function combines multiple filters and if any of them accept a | |
record, it is filtered in. | |
These filter constructors are used as appropriate by the various charts to implement brushing. We | |
mention below which chart uses which filter. In some cases, many instances of a filter will be added. | |
**/ | |
/** | |
#### dc.filters.RangedFilter(low, high) | |
RangedFilter is a filter which accepts keys between `low` and `high`. It is used to implement X | |
axis brushing for the [coordinate grid charts](#coordinate-grid-mixin). | |
**/ | |
dc.filters.RangedFilter = function (low, high) { | |
var range = new Array(low, high); | |
range.isFiltered = function (value) { | |
return value >= this[0] && value < this[1]; | |
}; | |
return range; | |
}; | |
/** | |
#### dc.filters.TwoDimensionalFilter(array) | |
TwoDimensionalFilter is a filter which accepts a single two-dimensional value. It is used by the | |
[heat map chart](#heat-map) to include particular cells as they are clicked. (Rows and columns are | |
filtered by filtering all the cells in the row or column.) | |
**/ | |
dc.filters.TwoDimensionalFilter = function (array) { | |
if (array === null) { return null; } | |
var filter = array; | |
filter.isFiltered = function (value) { | |
return value.length && value.length === filter.length && | |
value[0] === filter[0] && value[1] === filter[1]; | |
}; | |
return filter; | |
}; | |
/** | |
#### dc.filters.RangedTwoDimensionalFilter(array) | |
The RangedTwoDimensionalFilter allows filtering all values which fit within a rectangular | |
region. It is used by the [scatter plot](#scatter-plot) to implement rectangular brushing. | |
It takes two two-dimensional points in the form `[[x1,y1],[x2,y2]]`, and normalizes them so that | |
`x1 <= x2` and `y1 <- y2`. It then returns a filter which accepts any points which are in the | |
rectangular range including the lower values but excluding the higher values. | |
If an array of two values are given to the RangedTwoDimensionalFilter, it interprets the values as | |
two x coordinates `x1` and `x2` and returns a filter which accepts any points for which `x1 <= x < | |
x2`. | |
**/ | |
dc.filters.RangedTwoDimensionalFilter = function (array) { | |
if (array === null) { return null; } | |
var filter = array; | |
var fromBottomLeft; | |
if (filter[0] instanceof Array) { | |
fromBottomLeft = [ | |
[Math.min(array[0][0], array[1][0]), Math.min(array[0][1], array[1][1])], | |
[Math.max(array[0][0], array[1][0]), Math.max(array[0][1], array[1][1])] | |
]; | |
} else { | |
fromBottomLeft = [[array[0], -Infinity], [array[1], Infinity]]; | |
} | |
filter.isFiltered = function (value) { | |
var x, y; | |
if (value instanceof Array) { | |
if (value.length !== 2) { | |
return false; | |
} | |
x = value[0]; | |
y = value[1]; | |
} else { | |
x = value; | |
y = fromBottomLeft[0][1]; | |
} | |
return x >= fromBottomLeft[0][0] && x < fromBottomLeft[1][0] && | |
y >= fromBottomLeft[0][1] && y < fromBottomLeft[1][1]; | |
}; | |
return filter; | |
}; | |
/** | |
## Base Mixin | |
Base Mixin is an abstract functional object representing a basic dc chart object | |
for all chart and widget implementations. Methods from the Base Mixin are inherited | |
and available on all chart implementation in the DC library. | |
**/ | |
dc.baseMixin = function (_chart) { | |
_chart.__dcFlag__ = dc.utils.uniqueId(); | |
var _dimension; | |
var _group; | |
var _anchor; | |
var _root; | |
var _svg; | |
var _minWidth = 200; | |
var _defaultWidth = function (element) { | |
var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width; | |
return (width && width > _minWidth) ? width : _minWidth; | |
}; | |
var _width = _defaultWidth; | |
var _minHeight = 200; | |
var _defaultHeight = function (element) { | |
var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height; | |
return (height && height > _minHeight) ? height : _minHeight; | |
}; | |
var _height = _defaultHeight; | |
var _keyAccessor = dc.pluck('key'); | |
var _valueAccessor = dc.pluck('value'); | |
var _label = dc.pluck('key'); | |
var _ordering = dc.pluck('key'); | |
var _orderSort; | |
var _renderLabel = false; | |
var _title = function (d) { | |
return _chart.keyAccessor()(d) + ': ' + _chart.valueAccessor()(d); | |
}; | |
var _renderTitle = true; | |
var _transitionDuration = 750; | |
var _filterPrinter = dc.printers.filters; | |
var _renderlets = []; | |
var _mandatoryAttributes = ['dimension', 'group']; | |
var _chartGroup = dc.constants.DEFAULT_CHART_GROUP; | |
var _listeners = d3.dispatch( | |
'preRender', | |
'postRender', | |
'preRedraw', | |
'postRedraw', | |
'filtered', | |
'zoomed'); | |
var _legend; | |
var _filters = []; | |
var _filterHandler = function (dimension, filters) { | |
dimension.filter(null); | |
if (filters.length === 0) { | |
dimension.filter(null); | |
} else { | |
dimension.filterFunction(function (d) { | |
for (var i = 0; i < filters.length; i++) { | |
var filter = filters[i]; | |
if (filter.isFiltered && filter.isFiltered(d)) { | |
return true; | |
} else if (filter <= d && filter >= d) { | |
return true; | |
} | |
} | |
return false; | |
}); | |
} | |
return filters; | |
}; | |
var _data = function (group) { | |
return group.all(); | |
}; | |
/** | |
#### .width([value]) | |
Set or get the width attribute of a chart. See `.height` below for further description of the | |
behavior. | |
**/ | |
_chart.width = function (w) { | |
if (!arguments.length) { | |
return _width(_root.node()); | |
} | |
_width = d3.functor(w || _defaultWidth); | |
return _chart; | |
}; | |
/** | |
#### .height([value]) | |
Set or get the height attribute of a chart. The height is applied to the SVG element generated by | |
the chart when rendered (or rerendered). If a value is given, then it will be used to calculate | |
the new height and the chart returned for method chaining. The value can either be a numeric, a | |
function, or falsy. If no value is specified then the value of the current height attribute will | |
be returned. | |
By default, without an explicit height being given, the chart will select the width of its | |
anchor element. If that isn't possible it defaults to 200. Setting the value falsy will return | |
the chart to the default behavior | |
Examples: | |
```js | |
chart.height(250); // Set the chart's height to 250px; | |
chart.height(function(anchor) { return doSomethingWith(anchor); }); // set the chart's height with a function | |
chart.height(null); // reset the height to the default auto calculation | |
``` | |
**/ | |
_chart.height = function (h) { | |
if (!arguments.length) { | |
return _height(_root.node()); | |
} | |
_height = d3.functor(h || _defaultHeight); | |
return _chart; | |
}; | |
/** | |
#### .minWidth([value]) | |
Set or get the minimum width attribute of a chart. This only applicable if the width is | |
calculated by dc. | |
**/ | |
_chart.minWidth = function (w) { | |
if (!arguments.length) { | |
return _minWidth; | |
} | |
_minWidth = w; | |
return _chart; | |
}; | |
/** | |
#### .minHeight([value]) | |
Set or get the minimum height attribute of a chart. This only applicable if the height is | |
calculated by dc. | |
**/ | |
_chart.minHeight = function (w) { | |
if (!arguments.length) { | |
return _minHeight; | |
} | |
_minHeight = w; | |
return _chart; | |
}; | |
/** | |
#### .dimension([value]) - **mandatory** | |
Set or get the dimension attribute of a chart. In dc a dimension can be any valid [crossfilter | |
dimension](https://github.com/square/crossfilter/wiki/API-Reference#wiki-dimension). | |
If a value is given, then it will be used as the new dimension. If no value is specified then | |
the current dimension will be returned. | |
**/ | |
_chart.dimension = function (d) { | |
if (!arguments.length) { | |
return _dimension; | |
} | |
_dimension = d; | |
_chart.expireCache(); | |
return _chart; | |
}; | |
/** | |
#### .data([callback]) | |
Set the data callback or retrieve the chart's data set. The data callback is passed the chart's | |
group and by default will return `group.all()`. This behavior may be modified to, for instance, | |
return only the top 5 groups: | |
``` | |
chart.data(function(group) { | |
return group.top(5); | |
}); | |
``` | |
**/ | |
_chart.data = function (d) { | |
if (!arguments.length) { | |
return _data.call(_chart, _group); | |
} | |
_data = d3.functor(d); | |
_chart.expireCache(); | |
return _chart; | |
}; | |
/** | |
#### .group([value, [name]]) - **mandatory** | |
Set or get the group attribute of a chart. In dc a group is a [crossfilter | |
group](https://github.com/square/crossfilter/wiki/API-Reference#wiki-group). Usually the group | |
should be created from the particular dimension associated with the same chart. If a value is | |
given, then it will be used as the new group. | |
If no value specified then the current group will be returned. | |
If `name` is specified then it will be used to generate legend label. | |
**/ | |
_chart.group = function (g, name) { | |
if (!arguments.length) { | |
return _group; | |
} | |
_group = g; | |
_chart._groupName = name; | |
_chart.expireCache(); | |
return _chart; | |
}; | |
/** | |
#### .ordering([orderFunction]) | |
Get or set an accessor to order ordinal charts | |
**/ | |
_chart.ordering = function (o) { | |
if (!arguments.length) { | |
return _ordering; | |
} | |
_ordering = o; | |
_orderSort = crossfilter.quicksort.by(_ordering); | |
_chart.expireCache(); | |
return _chart; | |
}; | |
_chart._computeOrderedGroups = function (data) { | |
var dataCopy = data.slice(0); | |
if (dataCopy.length <= 1) { | |
return dataCopy; | |
} | |
if (!_orderSort) { | |
_orderSort = crossfilter.quicksort.by(_ordering); | |
} | |
return _orderSort(dataCopy, 0, dataCopy.length); | |
}; | |
/** | |
#### .filterAll() | |
Clear all filters associated with this chart. | |
**/ | |
_chart.filterAll = function () { | |
return _chart.filter(null); | |
}; | |
/** | |
#### .select(selector) | |
Execute d3 single selection in the chart's scope using the given selector and return the d3 | |
selection. Roughly the same as: | |
```js | |
d3.select('#chart-id').select(selector); | |
``` | |
This function is **not chainable** since it does not return a chart instance; however the d3 | |
selection result can be chained to d3 function calls. | |
**/ | |
_chart.select = function (s) { | |
return _root.select(s); | |
}; | |
/** | |
#### .selectAll(selector) | |
Execute in scope d3 selectAll using the given selector and return d3 selection result. Roughly | |
the same as: | |
```js | |
d3.select('#chart-id').selectAll(selector); | |
``` | |
This function is **not chainable** since it does not return a chart instance; however the d3 | |
selection result can be chained to d3 function calls. | |
**/ | |
_chart.selectAll = function (s) { | |
return _root ? _root.selectAll(s) : null; | |
}; | |
/** | |
#### .anchor([anchorChart|anchorSelector|anchorNode], [chartGroup]) | |
Set the svg root to either be an existing chart's root; or any valid [d3 single | |
selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying a dom | |
block element such as a div; or a dom element or d3 selection. Optionally registers the chart | |
within the chartGroup. This class is called internally on chart initialization, but be called | |
again to relocate the chart. However, it will orphan any previously created SVG elements. | |
**/ | |
_chart.anchor = function (a, chartGroup) { | |
if (!arguments.length) { | |
return _anchor; | |
} | |
if (dc.instanceOfChart(a)) { | |
_anchor = a.anchor(); | |
_root = a.root(); | |
} else { | |
_anchor = a; | |
_root = d3.select(_anchor); | |
_root.classed(dc.constants.CHART_CLASS, true); | |
dc.registerChart(_chart, chartGroup); | |
} | |
_chartGroup = chartGroup; | |
return _chart; | |
}; | |
/** | |
#### .anchorName() | |
Returns the dom id for the chart's anchored location. | |
**/ | |
_chart.anchorName = function () { | |
var a = _chart.anchor(); | |
if (a && a.id) { | |
return a.id; | |
} | |
if (a && a.replace) { | |
return a.replace('#', ''); | |
} | |
return '' + _chart.chartID(); | |
}; | |
/** | |
#### .root([rootElement]) | |
Returns the root element where a chart resides. Usually it will be the parent div element where | |
the svg was created. You can also pass in a new root element however this is usually handled by | |
dc internally. Resetting the root element on a chart outside of dc internals may have | |
unexpected consequences. | |
**/ | |
_chart.root = function (r) { | |
if (!arguments.length) { | |
return _root; | |
} | |
_root = r; | |
return _chart; | |
}; | |
/** | |
#### .svg([svgElement]) | |
Returns the top svg element for this specific chart. You can also pass in a new svg element, | |
however this is usually handled by dc internally. Resetting the svg element on a chart outside | |
of dc internals may have unexpected consequences. | |
**/ | |
_chart.svg = function (_) { | |
if (!arguments.length) { | |
return _svg; | |
} | |
_svg = _; | |
return _chart; | |
}; | |
/** | |
#### .resetSvg() | |
Remove the chart's SVG elements from the dom and recreate the container SVG element. | |
**/ | |
_chart.resetSvg = function () { | |
_chart.select('svg').remove(); | |
return generateSvg(); | |
}; | |
function generateSvg() { | |
_svg = _chart.root().append('svg') | |
.attr('width', _chart.width()) | |
.attr('height', _chart.height()); | |
return _svg; | |
} | |
/** | |
#### .filterPrinter([filterPrinterFunction]) | |
Set or get the filter printer function. The filter printer function is used to generate human | |
friendly text for filter value(s) associated with the chart instance. By default dc charts use a | |
default filter printer `dc.printers.filter` that provides simple printing support for both | |
single value and ranged filters. | |
**/ | |
_chart.filterPrinter = function (_) { | |
if (!arguments.length) { | |
return _filterPrinter; | |
} | |
_filterPrinter = _; | |
return _chart; | |
}; | |
/** | |
#### .turnOnControls() & .turnOffControls() | |
Turn on/off optional control elements within the root element. dc currently supports the | |
following html control elements. | |
* root.selectAll('.reset') - elements are turned on if the chart has an active filter. This type | |
of control element is usually used to store a reset link to allow user to reset filter on a | |
certain chart. This element will be turned off automatically if the filter is cleared. | |
* root.selectAll('.filter') elements are turned on if the chart has an active filter. The text | |
content of this element is then replaced with the current filter value using the filter printer | |
function. This type of element will be turned off automatically if the filter is cleared. | |
**/ | |
_chart.turnOnControls = function () { | |
if (_root) { | |
_chart.selectAll('.reset').style('display', null); | |
_chart.selectAll('.filter').text(_filterPrinter(_chart.filters())).style('display', null); | |
} | |
return _chart; | |
}; | |
_chart.turnOffControls = function () { | |
if (_root) { | |
_chart.selectAll('.reset').style('display', 'none'); | |
_chart.selectAll('.filter').style('display', 'none').text(_chart.filter()); | |
} | |
return _chart; | |
}; | |
/** | |
#### .transitionDuration([duration]) | |
Set or get the animation transition duration(in milliseconds) for this chart instance. Default | |
duration is 750ms. | |
**/ | |
_chart.transitionDuration = function (d) { | |
if (!arguments.length) { | |
return _transitionDuration; | |
} | |
_transitionDuration = d; | |
return _chart; | |
}; | |
_chart._mandatoryAttributes = function (_) { | |
if (!arguments.length) { | |
return _mandatoryAttributes; | |
} | |
_mandatoryAttributes = _; | |
return _chart; | |
}; | |
function checkForMandatoryAttributes(a) { | |
if (!_chart[a] || !_chart[a]()) { | |
throw new dc.errors.InvalidStateException('Mandatory attribute chart.' + a + | |
' is missing on chart[#' + _chart.anchorName() + ']'); | |
} | |
} | |
/** | |
#### .render() | |
Invoking this method will force the chart to re-render everything from scratch. Generally it | |
should only be used to render the chart for the first time on the page or if you want to make | |
sure everything is redrawn from scratch instead of relying on the default incremental redrawing | |
behaviour. | |
**/ | |
_chart.render = function () { | |
_listeners.preRender(_chart); | |
if (_mandatoryAttributes) { | |
_mandatoryAttributes.forEach(checkForMandatoryAttributes); | |
} | |
var result = _chart._doRender(); | |
if (_legend) { | |
_legend.render(); | |
} | |
_chart._activateRenderlets('postRender'); | |
return result; | |
}; | |
_chart._activateRenderlets = function (event) { | |
if (_chart.transitionDuration() > 0 && _svg) { | |
_svg.transition().duration(_chart.transitionDuration()) | |
.each('end', function () { | |
runAllRenderlets(); | |
if (event) { | |
_listeners[event](_chart); | |
} | |
}); | |
} else { | |
runAllRenderlets(); | |
if (event) { | |
_listeners[event](_chart); | |
} | |
} | |
}; | |
/** | |
#### .redraw() | |
Calling redraw will cause the chart to re-render data changes incrementally. If there is no | |
change in the underlying data dimension then calling this method will have no effect on the | |
chart. Most chart interaction in dc will automatically trigger this method through internal | |
events (in particular [dc.redrawAll](#dcredrawallchartgroup)); therefore, you only need to | |
manually invoke this function if data is manipulated outside of dc's control (for example if | |
data is loaded in the background using `crossfilter.add()`). | |
**/ | |
_chart.redraw = function () { | |
_listeners.preRedraw(_chart); | |
var result = _chart._doRedraw(); | |
if (_legend) { | |
_legend.render(); | |
} | |
_chart._activateRenderlets('postRedraw'); | |
return result; | |
}; | |
_chart.redrawGroup = function () { | |
dc.redrawAll(_chart.chartGroup()); | |
}; | |
_chart.renderGroup = function () { | |
dc.renderAll(_chart.chartGroup()); | |
}; | |
_chart._invokeFilteredListener = function (f) { | |
if (f !== undefined) { | |
_listeners.filtered(_chart, f); | |
} | |
}; | |
_chart._invokeZoomedListener = function () { | |
_listeners.zoomed(_chart); | |
}; | |
var _hasFilterHandler = function (filters, filter) { | |
if (filter === null || typeof(filter) === 'undefined') { | |
return filters.length > 0; | |
} | |
return filters.some(function (f) { | |
return filter <= f && filter >= f; | |
}); | |
}; | |
/** | |
#### .hasFilterHandler([function]) | |
Set or get the has filter handler. The has filter handler is a function that checks to see if | |
the chart's current filters include a specific filter. Using a custom has filter handler allows | |
you to change the way filters are checked for and replaced. | |
```js | |
// default has filter handler | |
function (filters, filter) { | |
if (filter === null || typeof(filter) === 'undefined') { | |
return filters.length > 0; | |
} | |
return filters.some(function (f) { | |
return filter <= f && filter >= f; | |
}); | |
} | |
// custom filter handler (no-op) | |
chart.hasFilterHandler(function(filters, filter) { | |
return false; | |
}); | |
``` | |
**/ | |
_chart.hasFilterHandler = function (_) { | |
if (!arguments.length) { | |
return _hasFilterHandler; | |
} | |
_hasFilterHandler = _; | |
return _chart; | |
}; | |
/** | |
#### .hasFilter([filter]) | |
Check whether any active filter or a specific filter is associated with particular chart instance. | |
This function is **not chainable**. | |
**/ | |
_chart.hasFilter = function (filter) { | |
return _hasFilterHandler(_filters, filter); | |
}; | |
var _removeFilterHandler = function (filters, filter) { | |
for (var i = 0; i < filters.length; i++) { | |
if (filters[i] <= filter && filters[i] >= filter) { | |
filters.splice(i, 1); | |
break; | |
} | |
} | |
return filters; | |
}; | |
/** | |
#### .removeFilterHandler([function]) | |
Set or get the remove filter handler. The remove filter handler is a function that removes a | |
filter from the chart's current filters. Using a custom remove filter handler allows you to | |
change how filters are removed or perform additional work when removing a filter, e.g. when | |
using a filter server other than crossfilter. | |
Any changes should modify the `filters` array argument and return that array. | |
```js | |
// default remove filter handler | |
function (filters, filter) { | |
for (var i = 0; i < filters.length; i++) { | |
if (filters[i] <= filter && filters[i] >= filter) { | |
filters.splice(i, 1); | |
break; | |
} | |
} | |
return filters; | |
} | |
// custom filter handler (no-op) | |
chart.removeFilterHandler(function(filters, filter) { | |
return filters; | |
}); | |
``` | |
**/ | |
_chart.removeFilterHandler = function (_) { | |
if (!arguments.length) { | |
return _removeFilterHandler; | |
} | |
_removeFilterHandler = _; | |
return _chart; | |
}; | |
var _addFilterHandler = function (filters, filter) { | |
filters.push(filter); | |
return filters; | |
}; | |
/** | |
#### .addFilterHandler([function]) | |
Set or get the add filter handler. The add filter handler is a function that adds a filter to | |
the chart's filter list. Using a custom add filter handler allows you to change the way filters | |
are added or perform additional work when adding a filter, e.g. when using a filter server other | |
than crossfilter. | |
Any changes should modify the `filters` array argument and return that array. | |
```js | |
// default add filter handler | |
function (filters, filter) { | |
filters.push(filter); | |
return filters; | |
} | |
// custom filter handler (no-op) | |
chart.addFilterHandler(function(filters, filter) { | |
return filters; | |
}); | |
``` | |
**/ | |
_chart.addFilterHandler = function (_) { | |
if (!arguments.length) { | |
return _addFilterHandler; | |
} | |
_addFilterHandler = _; | |
return _chart; | |
}; | |
var _resetFilterHandler = function (filters) { | |
return []; | |
}; | |
/** | |
#### .resetFilterHandler([function]) | |
Set or get the reset filter handler. The reset filter handler is a function that resets the | |
chart's filter list by returning a new list. Using a custom reset filter handler allows you to | |
change the way filters are reset, or perform additional work when resetting the filters, | |
e.g. when using a filter server other than crossfilter. | |
This function should return an array. | |
```js | |
// default remove filter handler | |
function (filters) { | |
return []; | |
} | |
// custom filter handler (no-op) | |
chart.resetFilterHandler(function(filters) { | |
return filters; | |
}); | |
``` | |
**/ | |
_chart.resetFilterHandler = function (_) { | |
if (!arguments.length) { | |
return _resetFilterHandler; | |
} | |
_resetFilterHandler = _; | |
return _chart; | |
}; | |
function applyFilters() { | |
if (_chart.dimension() && _chart.dimension().filter) { | |
var fs = _filterHandler(_chart.dimension(), _filters); | |
_filters = fs ? fs : _filters; | |
} | |
} | |
_chart.replaceFilter = function (_) { | |
_filters = []; | |
_chart.filter(_); | |
}; | |
/** | |
#### .filter([filterValue]) | |
Filter the chart by the given value or return the current filter if the input parameter is missing. | |
```js | |
// filter by a single string | |
chart.filter('Sunday'); | |
// filter by a single age | |
chart.filter(18); | |
``` | |
**/ | |
_chart.filter = function (_) { | |
if (!arguments.length) { | |
return _filters.length > 0 ? _filters[0] : null; | |
} | |
if (_ instanceof Array && _[0] instanceof Array && !_.isFiltered) { | |
_[0].forEach(function (d) { | |
if (_chart.hasFilter(d)) { | |
_removeFilterHandler(_filters, d); | |
} else { | |
_addFilterHandler(_filters, d); | |
} | |
}); | |
} else if (_ === null) { | |
_filters = _resetFilterHandler(_filters); | |
} else { | |
if (_chart.hasFilter(_)) { | |
_removeFilterHandler(_filters, _); | |
} else { | |
_addFilterHandler(_filters, _); | |
} | |
} | |
applyFilters(); | |
_chart._invokeFilteredListener(_); | |
if (_root !== null && _chart.hasFilter()) { | |
_chart.turnOnControls(); | |
} else { | |
_chart.turnOffControls(); | |
} | |
return _chart; | |
}; | |
/** | |
#### .filters() | |
Returns all current filters. This method does not perform defensive cloning of the internal | |
filter array before returning, therefore any modification of the returned array will effect the | |
chart's internal filter storage. | |
**/ | |
_chart.filters = function () { | |
return _filters; | |
}; | |
_chart.highlightSelected = function (e) { | |
d3.select(e).classed(dc.constants.SELECTED_CLASS, true); | |
d3.select(e).classed(dc.constants.DESELECTED_CLASS, false); | |
}; | |
_chart.fadeDeselected = function (e) { | |
d3.select(e).classed(dc.constants.SELECTED_CLASS, false); | |
d3.select(e).classed(dc.constants.DESELECTED_CLASS, true); | |
}; | |
_chart.resetHighlight = function (e) { | |
d3.select(e).classed(dc.constants.SELECTED_CLASS, false); | |
d3.select(e).classed(dc.constants.DESELECTED_CLASS, false); | |
}; | |
/** | |
#### .onClick(datum) | |
This function is passed to d3 as the onClick handler for each chart. The default behavior is to | |
filter on the clicked datum (passed to the callback) and redraw the chart group. | |
**/ | |
_chart.onClick = function (d) { | |
var filter = _chart.keyAccessor()(d); | |
dc.events.trigger(function () { | |
_chart.filter(filter); | |
_chart.redrawGroup(); | |
}); | |
}; | |
/** | |
#### .filterHandler([function]) | |
Set or get the filter handler. The filter handler is a function that performs the filter action | |
on a specific dimension. Using a custom filter handler allows you to perform additional logic | |
before or after filtering. | |
```js | |
// default filter handler | |
function(dimension, filter){ | |
dimension.filter(filter); // perform filtering | |
return filter; // return the actual filter value | |
} | |
// custom filter handler | |
chart.filterHandler(function(dimension, filter){ | |
var newFilter = filter + 10; | |
dimension.filter(newFilter); | |
return newFilter; // set the actual filter value to the new value | |
}); | |
``` | |
**/ | |
_chart.filterHandler = function (_) { | |
if (!arguments.length) { | |
return _filterHandler; | |
} | |
_filterHandler = _; | |
return _chart; | |
}; | |
// abstract function stub | |
_chart._doRender = function () { | |
// do nothing in base, should be overridden by sub-function | |
return _chart; | |
}; | |
_chart._doRedraw = function () { | |
// do nothing in base, should be overridden by sub-function | |
return _chart; | |
}; | |
_chart.legendables = function () { | |
// do nothing in base, should be overridden by sub-function | |
return []; | |
}; | |
_chart.legendHighlight = function () { | |
// do nothing in base, should be overridden by sub-function | |
}; | |
_chart.legendReset = function () { | |
// do nothing in base, should be overridden by sub-function | |
}; | |
_chart.legendToggle = function () { | |
// do nothing in base, should be overriden by sub-function | |
}; | |
_chart.isLegendableHidden = function () { | |
// do nothing in base, should be overridden by sub-function | |
return false; | |
}; | |
/** | |
#### .keyAccessor([keyAccessorFunction]) | |
Set or get the key accessor function. The key accessor function is used to retrieve the key | |
value from the crossfilter group. Key values are used differently in different charts, for | |
example keys correspond to slices in a pie chart and x axis positions in a grid coordinate chart. | |
```js | |
// default key accessor | |
chart.keyAccessor(function(d) { return d.key; }); | |
// custom key accessor for a multi-value crossfilter reduction | |
chart.keyAccessor(function(p) { return p.value.absGain; }); | |
``` | |
**/ | |
_chart.keyAccessor = function (_) { | |
if (!arguments.length) { | |
return _keyAccessor; | |
} | |
_keyAccessor = _; | |
return _chart; | |
}; | |
/** | |
#### .valueAccessor([valueAccessorFunction]) | |
Set or get the value accessor function. The value accessor function is used to retrieve the | |
value from the crossfilter group. Group values are used differently in different charts, for | |
example values correspond to slice sizes in a pie chart and y axis positions in a grid | |
coordinate chart. | |
```js | |
// default value accessor | |
chart.valueAccessor(function(d) { return d.value; }); | |
// custom value accessor for a multi-value crossfilter reduction | |
chart.valueAccessor(function(p) { return p.value.percentageGain; }); | |
``` | |
**/ | |
_chart.valueAccessor = function (_) { | |
if (!arguments.length) { | |
return _valueAccessor; | |
} | |
_valueAccessor = _; | |
return _chart; | |
}; | |
/** | |
#### .label([labelFunction]) | |
Set or get the label function. The chart class will use this function to render labels for each | |
child element in the chart, e.g. slices in a pie chart or bubbles in a bubble chart. Not every | |
chart supports the label function for example bar chart and line chart do not use this function | |
at all. | |
```js | |
// default label function just return the key | |
chart.label(function(d) { return d.key; }); | |
// label function has access to the standard d3 data binding and can get quite complicated | |
chart.label(function(d) { return d.data.key + '(' + Math.floor(d.data.value / all.value() * 100) + '%)'; }); | |
``` | |
**/ | |
_chart.label = function (_) { | |
if (!arguments.length) { | |
return _label; | |
} | |
_label = _; | |
_renderLabel = true; | |
return _chart; | |
}; | |
/** | |
#### .renderLabel(boolean) | |
Turn on/off label rendering | |
**/ | |
_chart.renderLabel = function (_) { | |
if (!arguments.length) { | |
return _renderLabel; | |
} | |
_renderLabel = _; | |
return _chart; | |
}; | |
/** | |
#### .title([titleFunction]) | |
Set or get the title function. The chart class will use this function to render the svg title | |
(usually interpreted by browser as tooltips) for each child element in the chart, e.g. a slice | |
in a pie chart or a bubble in a bubble chart. Almost every chart supports the title function; | |
however in grid coordinate charts you need to turn off the brush in order to see titles, because | |
otherwise the brush layer will block tooltip triggering. | |
```js | |
// default title function just return the key | |
chart.title(function(d) { return d.key + ': ' + d.value; }); | |
// title function has access to the standard d3 data binding and can get quite complicated | |
chart.title(function(p) { | |
return p.key.getFullYear() | |
+ '\n' | |
+ 'Index Gain: ' + numberFormat(p.value.absGain) + '\n' | |
+ 'Index Gain in Percentage: ' + numberFormat(p.value.percentageGain) + '%\n' | |
+ 'Fluctuation / Index Ratio: ' + numberFormat(p.value.fluctuationPercentage) + '%'; | |
}); | |
``` | |
**/ | |
_chart.title = function (_) { | |
if (!arguments.length) { | |
return _title; | |
} | |
_title = _; | |
return _chart; | |
}; | |
/** | |
#### .renderTitle(boolean) | |
Turn on/off title rendering, or return the state of the render title flag if no arguments are | |
given. | |
**/ | |
_chart.renderTitle = function (_) { | |
if (!arguments.length) { | |
return _renderTitle; | |
} | |
_renderTitle = _; | |
return _chart; | |
}; | |
/** | |
#### .renderlet(renderletFunction) | |
A renderlet is similar to an event listener on rendering event. Multiple renderlets can be added | |
to an individual chart. Each time a chart is rerendered or redrawn the renderlets are invoked | |
right after the chart finishes its own drawing routine, giving you a way to modify the svg | |
elements. Renderlet functions take the chart instance as the only input parameter and you can | |
use the dc API or use raw d3 to achieve pretty much any effect. | |
```js | |
// renderlet function | |
chart.renderlet(function(chart){ | |
// mix of dc API and d3 manipulation | |
chart.select('g.y').style('display', 'none'); | |
// its a closure so you can also access other chart variable available in the closure scope | |
moveChart.filter(chart.filter()); | |
}); | |
``` | |
**/ | |
_chart.renderlet = function (_) { | |
_renderlets.push(_); | |
return _chart; | |
}; | |
function runAllRenderlets() { | |
for (var i = 0; i < _renderlets.length; ++i) { | |
_renderlets[i](_chart); | |
} | |
} | |
/** | |
#### .chartGroup([group]) | |
Get or set the chart group to which this chart belongs. Chart groups are rendered or redrawn | |
together since it is expected they share the same underlying crossfilter data set. | |
**/ | |
_chart.chartGroup = function (_) { | |
if (!arguments.length) { | |
return _chartGroup; | |
} | |
_chartGroup = _; | |
return _chart; | |
}; | |
/** | |
#### .expireCache() | |
Expire the internal chart cache. dc charts cache some data internally on a per chart basis to | |
speed up rendering and avoid unnecessary calculation; however it might be useful to clear the | |
cache if you have changed state which will affect rendering. For example if you invoke the | |
`crossfilter.add` function or reset group or dimension after rendering it is a good idea to | |
clear the cache to make sure charts are rendered properly. | |
**/ | |
_chart.expireCache = function () { | |
// do nothing in base, should be overridden by sub-function | |
return _chart; | |
}; | |
/** | |
#### .legend([dc.legend]) | |
Attach a dc.legend widget to this chart. The legend widget will automatically draw legend labels | |
based on the color setting and names associated with each group. | |
```js | |
chart.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5)) | |
``` | |
**/ | |
_chart.legend = function (l) { | |
if (!arguments.length) { | |
return _legend; | |
} | |
_legend = l; | |
_legend.parent(_chart); | |
return _chart; | |
}; | |
/** | |
#### .chartID() | |
Returns the internal numeric ID of the chart. | |
**/ | |
_chart.chartID = function () { | |
return _chart.__dcFlag__; | |
}; | |
/** | |
#### .options(optionsObject) | |
Set chart options using a configuration object. Each key in the object will cause the method of | |
the same name to be called with the value to set that attribute for the chart. | |
Example: | |
``` | |
chart.options({dimension: myDimension, group: myGroup}); | |
``` | |
**/ | |
_chart.options = function (opts) { | |
for (var o in opts) { | |
if (typeof(_chart[o]) === 'function') { | |
_chart[o].call(_chart, opts[o]); | |
} else { | |
dc.logger.debug('Not a valid option setter name: ' + o); | |
} | |
} | |
return _chart; | |
}; | |
/** | |
## Listeners | |
All dc chart instance supports the following listeners. | |
#### .on('preRender', function(chart){...}) | |
This listener function will be invoked before chart rendering. | |
#### .on('postRender', function(chart){...}) | |
This listener function will be invoked after chart finish rendering including all renderlets' logic. | |
#### .on('preRedraw', function(chart){...}) | |
This listener function will be invoked before chart redrawing. | |
#### .on('postRedraw', function(chart){...}) | |
This listener function will be invoked after chart finish redrawing including all renderlets' logic. | |
#### .on('filtered', function(chart, filter){...}) | |
This listener function will be invoked after a filter is applied, added or removed. | |
#### .on('zoomed', function(chart, filter){...}) | |
This listener function will be invoked after a zoom is triggered. | |
**/ | |
_chart.on = function (event, listener) { | |
_listeners.on(event, listener); | |
return _chart; | |
}; | |
return _chart; | |
}; | |
/** | |
## Margin Mixin | |
Margin is a mixin that provides margin utility functions for both the Row Chart and Coordinate Grid | |
Charts. | |
**/ | |
dc.marginMixin = function (_chart) { | |
var _margin = {top: 10, right: 50, bottom: 30, left: 30}; | |
/** | |
#### .margins([margins]) | |
Get or set the margins for a particular coordinate grid chart instance. The margins is stored as | |
an associative Javascript array. Default margins: {top: 10, right: 50, bottom: 30, left: 30}. | |
The margins can be accessed directly from the getter. | |
```js | |
var leftMargin = chart.margins().left; // 30 by default | |
chart.margins().left = 50; | |
leftMargin = chart.margins().left; // now 50 | |
``` | |
**/ | |
_chart.margins = function (m) { | |
if (!arguments.length) { | |
return _margin; | |
} | |
_margin = m; | |
return _chart; | |
}; | |
_chart.effectiveWidth = function () { | |
return _chart.width() - _chart.margins().left - _chart.margins().right; | |
}; | |
_chart.effectiveHeight = function () { | |
return _chart.height() - _chart.margins().top - _chart.margins().bottom; | |
}; | |
return _chart; | |
}; | |
/** | |
## Color Mixin | |
The Color Mixin is an abstract chart functional class providing universal coloring support | |
as a mix-in for any concrete chart implementation. | |
**/ | |
dc.colorMixin = function (_chart) { | |
var _colors = d3.scale.category20c(); | |
var _defaultAccessor = true; | |
var _colorAccessor = function (d) { return _chart.keyAccessor()(d); }; | |
/** | |
#### .colors([colorScale]) | |
Retrieve current color scale or set a new color scale. This methods accepts any function that | |
operates like a d3 scale. If not set the default is | |
`d3.scale.category20c()`. | |
```js | |
// alternate categorical scale | |
chart.colors(d3.scale.category20b()); | |
// ordinal scale | |
chart.colors(d3.scale.ordinal().range(['red','green','blue'])); | |
// convenience method, the same as above | |
chart.ordinalColors(['red','green','blue']); | |
// set a linear scale | |
chart.linearColors(["#4575b4", "#ffffbf", "#a50026"]); | |
``` | |
**/ | |
_chart.colors = function (_) { | |
if (!arguments.length) { | |
return _colors; | |
} | |
if (_ instanceof Array) { | |
_colors = d3.scale.quantize().range(_); // deprecated legacy support, note: this fails for ordinal domains | |
} else { | |
_colors = d3.functor(_); | |
} | |
return _chart; | |
}; | |
/** | |
#### .ordinalColors(r) | |
Convenience method to set the color scale to d3.scale.ordinal with range `r`. | |
**/ | |
_chart.ordinalColors = function (r) { | |
return _chart.colors(d3.scale.ordinal().range(r)); | |
}; | |
/** | |
#### .linearColors(r) | |
Convenience method to set the color scale to an Hcl interpolated linear scale with range `r`. | |
**/ | |
_chart.linearColors = function (r) { | |
return _chart.colors(d3.scale.linear() | |
.range(r) | |
.interpolate(d3.interpolateHcl)); | |
}; | |
/** | |
#### .colorAccessor([colorAccessorFunction]) | |
Set or the get color accessor function. This function will be used to map a data point in a | |
crossfilter group to a color value on the color scale. The default function uses the key | |
accessor. | |
```js | |
// default index based color accessor | |
.colorAccessor(function (d, i){return i;}) | |
// color accessor for a multi-value crossfilter reduction | |
.colorAccessor(function (d){return d.value.absGain;}) | |
``` | |
**/ | |
_chart.colorAccessor = function (_) { | |
if (!arguments.length) { | |
return _colorAccessor; | |
} | |
_colorAccessor = _; | |
_defaultAccessor = false; | |
return _chart; | |
}; | |
// what is this? | |
_chart.defaultColorAccessor = function () { | |
return _defaultAccessor; | |
}; | |
/** | |
#### .colorDomain([domain]) | |
Set or get the current domain for the color mapping function. The domain must be supplied as an | |
array. | |
Note: previously this method accepted a callback function. Instead you may use a custom scale | |
set by `.colors`. | |
**/ | |
_chart.colorDomain = function (_) { | |
if (!arguments.length) { | |
return _colors.domain(); | |
} | |
_colors.domain(_); | |
return _chart; | |
}; | |
/** | |
#### .calculateColorDomain() | |
Set the domain by determining the min and max values as retrieved by `.colorAccessor` over the | |
chart's dataset. | |
**/ | |
_chart.calculateColorDomain = function () { | |
var newDomain = [d3.min(_chart.data(), _chart.colorAccessor()), | |
d3.max(_chart.data(), _chart.colorAccessor())]; | |
_colors.domain(newDomain); | |
}; | |
/** | |
#### .getColor(d [, i]) | |
Get the color for the datum d and counter i. This is used internally by charts to retrieve a color. | |
**/ | |
_chart.getColor = function (d, i) { | |
return _colors(_colorAccessor.call(this, d, i)); | |
}; | |
/** | |
#### .colorCalculator([value]) | |
Gets or sets chart.getColor. | |
**/ | |
_chart.colorCalculator = function (_) { | |
if (!arguments.length) { | |
return _chart.getColor; | |
} | |
_chart.getColor = _; | |
return _chart; | |
}; | |
return _chart; | |
}; | |
/** | |
## Coordinate Grid Mixin | |
Includes: [Color Mixin](#color-mixin), [Margin Mixin](#margin-mixin), [Base Mixin](#base-mixin) | |
Coordinate Grid is an abstract base chart designed to support a number of coordinate grid based | |
concrete chart types, e.g. bar chart, line chart, and bubble chart. | |
**/ | |
dc.coordinateGridMixin = function (_chart) { | |
var GRID_LINE_CLASS = 'grid-line'; | |
var HORIZONTAL_CLASS = 'horizontal'; | |
var VERTICAL_CLASS = 'vertical'; | |
var Y_AXIS_LABEL_CLASS = 'y-axis-label'; | |
var X_AXIS_LABEL_CLASS = 'x-axis-label'; | |
var DEFAULT_AXIS_LABEL_PADDING = 12; | |
_chart = dc.colorMixin(dc.marginMixin(dc.baseMixin(_chart))); | |
_chart.colors(d3.scale.category10()); | |
_chart._mandatoryAttributes().push('x'); | |
function zoomHandler () { | |
_refocused = true; | |
if (_zoomOutRestrict) { | |
_chart.x().domain(constrainRange(_chart.x().domain(), _xOriginalDomain)); | |
if (_rangeChart) { | |
_chart.x().domain(constrainRange(_chart.x().domain(), _rangeChart.x().domain())); | |
} | |
} | |
var domain = _chart.x().domain(); | |
var domFilter = dc.filters.RangedFilter(domain[0], domain[1]); | |
_chart.replaceFilter(domFilter); | |
_chart.rescale(); | |
_chart.redraw(); | |
if (_rangeChart && !rangesEqual(_chart.filter(), _rangeChart.filter())) { | |
dc.events.trigger(function () { | |
_rangeChart.replaceFilter(domFilter); | |
_rangeChart.redraw(); | |
}); | |
} | |
_chart._invokeZoomedListener(); | |
dc.events.trigger(function () { | |
_chart.redrawGroup(); | |
}, dc.constants.EVENT_DELAY); | |
_refocused = !rangesEqual(domain, _xOriginalDomain); | |
} | |
var _parent; | |
var _g; | |
var _chartBodyG; | |
var _x; | |
var _xOriginalDomain; | |
var _xAxis = d3.svg.axis().orient('bottom'); | |
var _xUnits = dc.units.integers; | |
var _xAxisPadding = 0; | |
var _xElasticity = false; | |
var _xAxisLabel; | |
var _xAxisLabelPadding = 0; | |
var _lastXDomain; | |
var _y; | |
var _yAxis = d3.svg.axis().orient('left'); | |
var _yAxisPadding = 0; | |
var _yElasticity = false; | |
var _yAxisLabel; | |
var _yAxisLabelPadding = 0; | |
var _brush = d3.svg.brush(); | |
var _brushOn = true; | |
var _round; | |
var _renderHorizontalGridLine = false; | |
var _renderVerticalGridLine = false; | |
var _refocused = false; | |
var _unitCount; | |
var _zoomScale = [1, Infinity]; | |
var _zoomOutRestrict = true; | |
var _zoom = d3.behavior.zoom().on('zoom', zoomHandler); | |
var _nullZoom = d3.behavior.zoom().on('zoom', null); | |
var _hasBeenMouseZoomable = false; | |
var _rangeChart; | |
var _focusChart; | |
var _mouseZoomable = false; | |
var _clipPadding = 0; | |
var _outerRangeBandPadding = 0.5; | |
var _rangeBandPadding = 0; | |
var _useRightYAxis = false; | |
_chart.rescale = function () { | |
_unitCount = undefined; | |
}; | |
/** | |
#### .rangeChart([chart]) | |
Get or set the range selection chart associated with this instance. Setting the range selection | |
chart using this function will automatically update its selection brush when the current chart | |
zooms in. In return the given range chart will also automatically attach this chart as its focus | |
chart hence zoom in when range brush updates. See the [Nasdaq 100 | |
Index](http://dc-js.github.com/dc.js/) example for this effect in action. | |
**/ | |
_chart.rangeChart = function (_) { | |
if (!arguments.length) { | |
return _rangeChart; | |
} | |
_rangeChart = _; | |
_rangeChart.focusChart(_chart); | |
return _chart; | |
}; | |
/** | |
#### .zoomScale([extent]) | |
Get or set the scale extent for mouse zooms. | |
**/ | |
_chart.zoomScale = function (_) { | |
if (!arguments.length) { | |
return _zoomScale; | |
} | |
_zoomScale = _; | |
return _chart; | |
}; | |
/** | |
#### .zoomOutRestrict([true/false]) | |
Get or set the zoom restriction for the chart. If true limits the zoom to origional domain of the chart. | |
**/ | |
_chart.zoomOutRestrict = function (r) { | |
if (!arguments.length) { | |
return _zoomOutRestrict; | |
} | |
_zoomScale[0] = r ? 1 : 0; | |
_zoomOutRestrict = r; | |
return _chart; | |
}; | |
_chart._generateG = function (parent) { | |
if (parent === undefined) { | |
_parent = _chart.svg(); | |
} else { | |
_parent = parent; | |
} | |
_g = _parent.append('g'); | |
_chartBodyG = _g.append('g').attr('class', 'chart-body') | |
.attr('transform', 'translate(' + _chart.margins().left + ', ' + _chart.margins().top + ')') | |
.attr('clip-path', 'url(#' + getClipPathId() + ')'); | |
return _g; | |
}; | |
/** | |
#### .g([gElement]) | |
Get or set the root g element. This method is usually used to retrieve the g element in order to | |
overlay custom svg drawing programatically. **Caution**: The root g element is usually generated | |
by dc.js internals, and resetting it might produce unpredictable result. | |
**/ | |
_chart.g = function (_) { | |
if (!arguments.length) { | |
return _g; | |
} | |
_g = _; | |
return _chart; | |
}; | |
/** | |
#### .mouseZoomable([boolean]) | |
Set or get mouse zoom capability flag (default: false). When turned on the chart will be | |
zoomable using the mouse wheel. If the range selector chart is attached zooming will also update | |
the range selection brush on the associated range selector chart. | |
**/ | |
_chart.mouseZoomable = function (z) { | |
if (!arguments.length) { | |
return _mouseZoomable; | |
} | |
_mouseZoomable = z; | |
return _chart; | |
}; | |
/** | |
#### .chartBodyG() | |
Retrieve the svg group for the chart body. | |
**/ | |
_chart.chartBodyG = function (_) { | |
if (!arguments.length) { | |
return _chartBodyG; | |
} | |
_chartBodyG = _; | |
return _chart; | |
}; | |
/** | |
#### .x([xScale]) - **mandatory** | |
Get or set the x scale. The x scale can be any d3 | |
[quantitive scale](https://github.com/mbostock/d3/wiki/Quantitative-Scales) or | |
[ordinal scale](https://github.com/mbostock/d3/wiki/Ordinal-Scales). | |
```js | |
// set x to a linear scale | |
chart.x(d3.scale.linear().domain([-2500, 2500])) | |
// set x to a time scale to generate histogram | |
chart.x(d3.time.scale().domain([new Date(1985, 0, 1), new Date(2012, 11, 31)])) | |
``` | |
**/ | |
_chart.x = function (_) { | |
if (!arguments.length) { | |
return _x; | |
} | |
_x = _; | |
_xOriginalDomain = _x.domain(); | |
return _chart; | |
}; | |
_chart.xOriginalDomain = function () { | |
return _xOriginalDomain; | |
}; | |
/** | |
#### .xUnits([xUnits function]) | |
Set or get the xUnits function. The coordinate grid chart uses the xUnits function to calculate | |
the number of data projections on x axis such as the number of bars for a bar chart or the | |
number of dots for a line chart. This function is expected to return a Javascript array of all | |
data points on x axis, or the number of points on the axis. [d3 time range functions | |
d3.time.days, d3.time.months, and | |
d3.time.years](https://github.com/mbostock/d3/wiki/Time-Intervals#aliases) are all valid xUnits | |
function. dc.js also provides a few units function, see the [Utilities](#utilities) section for | |
a list of built-in units functions. The default xUnits function is dc.units.integers. | |
```js | |
// set x units to count days | |
chart.xUnits(d3.time.days); | |
// set x units to count months | |
chart.xUnits(d3.time.months); | |
``` | |
A custom xUnits function can be used as long as it follows the following interface: | |
```js | |
// units in integer | |
function(start, end, xDomain) { | |
// simply calculates how many integers in the domain | |
return Math.abs(end - start); | |
}; | |
// fixed units | |
function(start, end, xDomain) { | |
// be aware using fixed units will disable the focus/zoom ability on the chart | |
return 1000; | |
}; | |
``` | |
**/ | |
_chart.xUnits = function (_) { | |
if (!arguments.length) { | |
return _xUnits; | |
} | |
_xUnits = _; | |
return _chart; | |
}; | |
/** | |
#### .xAxis([xAxis]) | |
Set or get the x axis used by a particular coordinate grid chart instance. This function is most | |
useful when x axis customization is required. The x axis in dc.js is an instance of a [d3 | |
axis object](https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-axis); therefore it supports any | |
valid d3 axis manipulation. **Caution**: The x axis is usually generated internally by dc; | |
resetting it may cause unexpected results. | |
```js | |
// customize x axis tick format | |
chart.xAxis().tickFormat(function(v) {return v + '%';}); | |
// customize x axis tick values | |
chart.xAxis().tickValues([0, 100, 200, 300]); | |
``` | |
**/ | |
_chart.xAxis = function (_) { | |
if (!arguments.length) { | |
return _xAxis; | |
} | |
_xAxis = _; | |
return _chart; | |
}; | |
/** | |
#### .elasticX([boolean]) | |
Turn on/off elastic x axis behavior. If x axis elasticity is turned on, then the grid chart will | |
attempt to recalculate the x axis range whenever a redraw event is triggered. | |
**/ | |
_chart.elasticX = function (_) { | |
if (!arguments.length) { | |
return _xElasticity; | |
} | |
_xElasticity = _; | |
return _chart; | |
}; | |
/** | |
#### .xAxisPadding([padding]) | |
Set or get x axis padding for the elastic x axis. The padding will be added to both end of the x | |
axis if elasticX is turned on; otherwise it is ignored. | |
* padding can be an integer or percentage in string (e.g. '10%'). Padding can be applied to | |
number or date x axes. When padding a date axis, an integer represents number of days being padded | |
and a percentage string will be treated the same as an integer. | |
**/ | |
_chart.xAxisPadding = function (_) { | |
if (!arguments.length) { | |
return _xAxisPadding; | |
} | |
_xAxisPadding = _; | |
return _chart; | |
}; | |
/** | |
#### .xUnitCount() | |
Returns the number of units displayed on the x axis using the unit measure configured by | |
.xUnits. | |
**/ | |
_chart.xUnitCount = function () { | |
if (_unitCount === undefined) { | |
var units = _chart.xUnits()(_chart.x().domain()[0], _chart.x().domain()[1], _chart.x().domain()); | |
if (units instanceof Array) { | |
_unitCount = units.length; | |
} else { | |
_unitCount = units; | |
} | |
} | |
return _unitCount; | |
}; | |
/** | |
#### .useRightYAxis() | |
Gets or sets whether the chart should be drawn with a right axis instead of a left axis. When | |
used with a chart in a composite chart, allows both left and right Y axes to be shown on a | |
chart. | |
**/ | |
_chart.useRightYAxis = function (_) { | |
if (!arguments.length) { | |
return _useRightYAxis; | |
} | |
_useRightYAxis = _; | |
return _chart; | |
}; | |
/** | |
#### isOrdinal() | |
Returns true if the chart is using ordinal xUnits ([dc.units.ordinal](#dcunitsordinal)), or false | |
otherwise. Most charts behave differently with ordinal data and use the result of this method to | |
trigger the appropriate logic. | |
**/ | |
_chart.isOrdinal = function () { | |
return _chart.xUnits() === dc.units.ordinal; | |
}; | |
_chart._useOuterPadding = function () { | |
return true; | |
}; | |
_chart._ordinalXDomain = function () { | |
var groups = _chart._computeOrderedGroups(_chart.data()); | |
return groups.map(_chart.keyAccessor()); | |
}; | |
function prepareXAxis(g) { | |
if (!_chart.isOrdinal()) { | |
if (_chart.elasticX()) { | |
_x.domain([_chart.xAxisMin(), _chart.xAxisMax()]); | |
} | |
} | |
else { // _chart.isOrdinal() | |
if (_chart.elasticX() || _x.domain().length === 0) { | |
_x.domain(_chart._ordinalXDomain()); | |
} | |
} | |
// has the domain changed? | |
var xdom = _x.domain(); | |
if (!_lastXDomain || xdom.some(function (elem, i) { return elem !== _lastXDomain[i]; })) { | |
_chart.rescale(); | |
} | |
_lastXDomain = xdom; | |
// please can't we always use rangeBands for bar charts? | |
if (_chart.isOrdinal()) { | |
_x.rangeBands([0, _chart.xAxisLength()], _rangeBandPadding, | |
_chart._useOuterPadding() ? _outerRangeBandPadding : 0); | |
} else { | |
_x.range([0, _chart.xAxisLength()]); | |
} | |
_xAxis = _xAxis.scale(_chart.x()); | |
renderVerticalGridLines(g); | |
} | |
_chart.renderXAxis = function (g) { | |
var axisXG = g.selectAll('g.x'); | |
if (axisXG.empty()) { | |
axisXG = g.append('g') | |
.attr('class', 'axis x') | |
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart._xAxisY() + ')'); | |
} | |
var axisXLab = g.selectAll('text.' + X_AXIS_LABEL_CLASS); | |
if (axisXLab.empty() && _chart.xAxisLabel()) { | |
axisXLab = g.append('text') | |
.attr('transform', 'translate(' + (_chart.margins().left + _chart.xAxisLength() / 2) + ',' + | |
(_chart.height() - _xAxisLabelPadding) + ')') | |
.attr('class', X_AXIS_LABEL_CLASS) | |
.attr('text-anchor', 'middle') | |
.text(_chart.xAxisLabel()); | |
} | |
if (_chart.xAxisLabel() && axisXLab.text() !== _chart.xAxisLabel()) { | |
axisXLab.text(_chart.xAxisLabel()); | |
} | |
dc.transition(axisXG, _chart.transitionDuration()) | |
.call(_xAxis); | |
}; | |
function renderVerticalGridLines(g) { | |
var gridLineG = g.selectAll('g.' + VERTICAL_CLASS); | |
if (_renderVerticalGridLine) { | |
if (gridLineG.empty()) { | |
gridLineG = g.insert('g', ':first-child') | |
.attr('class', GRID_LINE_CLASS + ' ' + VERTICAL_CLASS) | |
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')'); | |
} | |
var ticks = _xAxis.tickValues() ? _xAxis.tickValues() : | |
(typeof _x.ticks === 'function' ? _x.ticks(_xAxis.ticks()[0]) : _x.domain()); | |
var lines = gridLineG.selectAll('line') | |
.data(ticks); | |
// enter | |
var linesGEnter = lines.enter() | |
.append('line') | |
.attr('x1', function (d) { | |
return _x(d); | |
}) | |
.attr('y1', _chart._xAxisY() - _chart.margins().top) | |
.attr('x2', function (d) { | |
return _x(d); | |
}) | |
.attr('y2', 0) | |
.attr('opacity', 0); | |
dc.transition(linesGEnter, _chart.transitionDuration()) | |
.attr('opacity', 1); | |
// update | |
dc.transition(lines, _chart.transitionDuration()) | |
.attr('x1', function (d) { | |
return _x(d); | |
}) | |
.attr('y1', _chart._xAxisY() - _chart.margins().top) | |
.attr('x2', function (d) { | |
return _x(d); | |
}) | |
.attr('y2', 0); | |
// exit | |
lines.exit().remove(); | |
} | |
else { | |
gridLineG.selectAll('line').remove(); | |
} | |
} | |
_chart._xAxisY = function () { | |
return (_chart.height() - _chart.margins().bottom); | |
}; | |
_chart.xAxisLength = function () { | |
return _chart.effectiveWidth(); | |
}; | |
/** | |
#### .xAxisLabel([labelText, [, padding]]) | |
Set or get the x axis label. If setting the label, you may optionally include additional padding to | |
the margin to make room for the label. By default the padded is set to 12 to accomodate the text height. | |
**/ | |
_chart.xAxisLabel = function (_, padding) { | |
if (!arguments.length) { | |
return _xAxisLabel; | |
} | |
_xAxisLabel = _; | |
_chart.margins().bottom -= _xAxisLabelPadding; | |
_xAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding; | |
_chart.margins().bottom += _xAxisLabelPadding; | |
return _chart; | |
}; | |
_chart._prepareYAxis = function (g) { | |
if (_y === undefined || _chart.elasticY()) { | |
_y = d3.scale.linear(); | |
var min = _chart.yAxisMin() || 0, | |
max = _chart.yAxisMax() || 0; | |
_y.domain([min, max]).rangeRound([_chart.yAxisHeight(), 0]); | |
} | |
_y.range([_chart.yAxisHeight(), 0]); | |
_yAxis = _yAxis.scale(_y); | |
if (_useRightYAxis) { | |
_yAxis.orient('right'); | |
} | |
_chart._renderHorizontalGridLinesForAxis(g, _y, _yAxis); | |
}; | |
_chart.renderYAxisLabel = function (axisClass, text, rotation, labelXPosition) { | |
labelXPosition = labelXPosition || _yAxisLabelPadding; | |
var axisYLab = _chart.g().selectAll('text.' + Y_AXIS_LABEL_CLASS + '.' + axisClass + '-label'); | |
if (axisYLab.empty() && text) { | |
var labelYPosition = (_chart.margins().top + _chart.yAxisHeight() / 2); | |
axisYLab = _chart.g().append('text') | |
.attr('transform', 'translate(' + labelXPosition + ',' + labelYPosition + '),rotate(' + rotation + ')') | |
.attr('class', Y_AXIS_LABEL_CLASS + ' ' + axisClass + '-label') | |
.attr('text-anchor', 'middle') | |
.text(text); | |
} | |
if (text && axisYLab.text() !== text) { | |
axisYLab.text(text); | |
} | |
}; | |
_chart.renderYAxisAt = function (axisClass, axis, position) { | |
var axisYG = _chart.g().selectAll('g.' + axisClass); | |
if (axisYG.empty()) { | |
axisYG = _chart.g().append('g') | |
.attr('class', 'axis ' + axisClass) | |
.attr('transform', 'translate(' + position + ',' + _chart.margins().top + ')'); | |
} | |
dc.transition(axisYG, _chart.transitionDuration()).call(axis); | |
}; | |
_chart.renderYAxis = function () { | |
var axisPosition = _useRightYAxis ? (_chart.width() - _chart.margins().right) : _chart._yAxisX(); | |
_chart.renderYAxisAt('y', _yAxis, axisPosition); | |
var labelPosition = _useRightYAxis ? (_chart.width() - _yAxisLabelPadding) : _yAxisLabelPadding; | |
var rotation = _useRightYAxis ? 90 : -90; | |
_chart.renderYAxisLabel('y', _chart.yAxisLabel(), rotation, labelPosition); | |
}; | |
_chart._renderHorizontalGridLinesForAxis = function (g, scale, axis) { | |
var gridLineG = g.selectAll('g.' + HORIZONTAL_CLASS); | |
if (_renderHorizontalGridLine) { | |
var ticks = axis.tickValues() ? axis.tickValues() : scale.ticks(axis.ticks()[0]); | |
if (gridLineG.empty()) { | |
gridLineG = g.insert('g', ':first-child') | |
.attr('class', GRID_LINE_CLASS + ' ' + HORIZONTAL_CLASS) | |
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')'); | |
} | |
var lines = gridLineG.selectAll('line') | |
.data(ticks); | |
// enter | |
var linesGEnter = lines.enter() | |
.append('line') | |
.attr('x1', 1) | |
.attr('y1', function (d) { | |
return scale(d); | |
}) | |
.attr('x2', _chart.xAxisLength()) | |
.attr('y2', function (d) { | |
return scale(d); | |
}) | |
.attr('opacity', 0); | |
dc.transition(linesGEnter, _chart.transitionDuration()) | |
.attr('opacity', 1); | |
// update | |
dc.transition(lines, _chart.transitionDuration()) | |
.attr('x1', 1) | |
.attr('y1', function (d) { | |
return scale(d); | |
}) | |
.attr('x2', _chart.xAxisLength()) | |
.attr('y2', function (d) { | |
return scale(d); | |
}); | |
// exit | |
lines.exit().remove(); | |
} | |
else { | |
gridLineG.selectAll('line').remove(); | |
} | |
}; | |
_chart._yAxisX = function () { | |
return _chart.useRightYAxis() ? _chart.width() - _chart.margins().right : _chart.margins().left; | |
}; | |
/** | |
#### .yAxisLabel([labelText, [, padding]]) | |
Set or get the y axis label. If setting the label, you may optionally include additional padding | |
to the margin to make room for the label. By default the padded is set to 12 to accomodate the | |
text height. | |
**/ | |
_chart.yAxisLabel = function (_, padding) { | |
if (!arguments.length) { | |
return _yAxisLabel; | |
} | |
_yAxisLabel = _; | |
_chart.margins().left -= _yAxisLabelPadding; | |
_yAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding; | |
_chart.margins().left += _yAxisLabelPadding; | |
return _chart; | |
}; | |
/** | |
#### .y([yScale]) | |
Get or set the y scale. The y scale is typically automatically determined by the chart implementation. | |
**/ | |
_chart.y = function (_) { | |
if (!arguments.length) { | |
return _y; | |
} | |
_y = _; | |
return _chart; | |
}; | |
/** | |
#### .yAxis([yAxis]) | |
Set or get the y axis used by the coordinate grid chart instance. This function is most useful | |
when y axis customization is required. The y axis in dc.js is simply an instance of a [d3 axis | |
object](https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-_axis); therefore it supports any | |
valid d3 axis manipulation. **Caution**: The y axis is usually generated internally by dc; | |
resetting it may cause unexpected results. | |
```js | |
// customize y axis tick format | |
chart.yAxis().tickFormat(function(v) {return v + '%';}); | |
// customize y axis tick values | |
chart.yAxis().tickValues([0, 100, 200, 300]); | |
``` | |
**/ | |
_chart.yAxis = function (y) { | |
if (!arguments.length) { | |
return _yAxis; | |
} | |
_yAxis = y; | |
return _chart; | |
}; | |
/** | |
#### .elasticY([boolean]) | |
Turn on/off elastic y axis behavior. If y axis elasticity is turned on, then the grid chart will | |
attempt to recalculate the y axis range whenever a redraw event is triggered. | |
**/ | |
_chart.elasticY = function (_) { | |
if (!arguments.length) { | |
return _yElasticity; | |
} | |
_yElasticity = _; | |
return _chart; | |
}; | |
/** | |
#### .renderHorizontalGridLines([boolean]) | |
Turn on/off horizontal grid lines. | |
**/ | |
_chart.renderHorizontalGridLines = function (_) { | |
if (!arguments.length) { | |
return _renderHorizontalGridLine; | |
} | |
_renderHorizontalGridLine = _; | |
return _chart; | |
}; | |
/** | |
#### .renderVerticalGridLines([boolean]) | |
Turn on/off vertical grid lines. | |
**/ | |
_chart.renderVerticalGridLines = function (_) { | |
if (!arguments.length) { | |
return _renderVerticalGridLine; | |
} | |
_renderVerticalGridLine = _; | |
return _chart; | |
}; | |
/** | |
#### .xAxisMin() | |
Calculates the minimum x value to display in the chart. Includes xAxisPadding if set. | |
**/ | |
_chart.xAxisMin = function () { | |
var min = d3.min(_chart.data(), function (e) { | |
return _chart.keyAccessor()(e); | |
}); | |
return dc.utils.subtract(min, _xAxisPadding); | |
}; | |
/** | |
#### .xAxisMax() | |
Calculates the maximum x value to display in the chart. Includes xAxisPadding if set. | |
**/ | |
_chart.xAxisMax = function () { | |
var max = d3.max(_chart.data(), function (e) { | |
return _chart.keyAccessor()(e); | |
}); | |
return dc.utils.add(max, _xAxisPadding); | |
}; | |
/** | |
#### .yAxisMin() | |
Calculates the minimum y value to display in the chart. Includes yAxisPadding if set. | |
**/ | |
_chart.yAxisMin = function () { | |
var min = d3.min(_chart.data(), function (e) { | |
return _chart.valueAccessor()(e); | |
}); | |
return dc.utils.subtract(min, _yAxisPadding); | |
}; | |
/** | |
#### .yAxisMax() | |
Calculates the maximum y value to display in the chart. Includes yAxisPadding if set. | |
**/ | |
_chart.yAxisMax = function () { | |
var max = d3.max(_chart.data(), function (e) { | |
return _chart.valueAccessor()(e); | |
}); | |
return dc.utils.add(max, _yAxisPadding); | |
}; | |
/** | |
#### .yAxisPadding([padding]) | |
Set or get y axis padding for the elastic y axis. The padding will be added to the top of the y | |
axis if elasticY is turned on; otherwise it is ignored. | |
* padding can be an integer or percentage in string (e.g. '10%'). Padding can be applied to | |
number or date axes. When padding a date axis, an integer represents number of days being padded | |
and a percentage string will be treated the same as an integer. | |
**/ | |
_chart.yAxisPadding = function (_) { | |
if (!arguments.length) { | |
return _yAxisPadding; | |
} | |
_yAxisPadding = _; | |
return _chart; | |
}; | |
_chart.yAxisHeight = function () { | |
return _chart.effectiveHeight(); | |
}; | |
/** | |
#### .round([rounding function]) | |
Set or get the rounding function used to quantize the selection when brushing is enabled. | |
```js | |
// set x unit round to by month, this will make sure range selection brush will | |
// select whole months | |
chart.round(d3.time.month.round); | |
``` | |
**/ | |
_chart.round = function (_) { | |
if (!arguments.length) { | |
return _round; | |
} | |
_round = _; | |
return _chart; | |
}; | |
_chart._rangeBandPadding = function (_) { | |
if (!arguments.length) { | |
return _rangeBandPadding; | |
} | |
_rangeBandPadding = _; | |
return _chart; | |
}; | |
_chart._outerRangeBandPadding = function (_) { | |
if (!arguments.length) { | |
return _outerRangeBandPadding; | |
} | |
_outerRangeBandPadding = _; | |
return _chart; | |
}; | |
dc.override(_chart, 'filter', function (_) { | |
if (!arguments.length) { | |
return _chart._filter(); | |
} | |
_chart._filter(_); | |
if (_) { | |
_chart.brush().extent(_); | |
} else { | |
_chart.brush().clear(); | |
} | |
return _chart; | |
}); | |
_chart.brush = function (_) { | |
if (!arguments.length) { | |
return _brush; | |
} | |
_brush = _; | |
return _chart; | |
}; | |
function brushHeight() { | |
return _chart._xAxisY() - _chart.margins().top; | |
} | |
_chart.renderBrush = function (g) { | |
if (_brushOn) { | |
_brush.on('brush', _chart._brushing); | |
_brush.on('brushstart', _chart._disableMouseZoom); | |
_brush.on('brushend', configureMouseZoom); | |
var gBrush = g.append('g') | |
.attr('class', 'brush') | |
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')') | |
.call(_brush.x(_chart.x())); | |
_chart.setBrushY(gBrush); | |
_chart.setHandlePaths(gBrush); | |
if (_chart.hasFilter()) { | |
_chart.redrawBrush(g); | |
} | |
} | |
}; | |
_chart.setHandlePaths = function (gBrush) { | |
gBrush.selectAll('.resize').append('path').attr('d', _chart.resizeHandlePath); | |
}; | |
_chart.setBrushY = function (gBrush) { | |
gBrush.selectAll('rect').attr('height', brushHeight()); | |
}; | |
_chart.extendBrush = function () { | |
var extent = _brush.extent(); | |
if (_chart.round()) { | |
extent[0] = extent.map(_chart.round())[0]; | |
extent[1] = extent.map(_chart.round())[1]; | |
_g.select('.brush') | |
.call(_brush.extent(extent)); | |
} | |
return extent; | |
}; | |
_chart.brushIsEmpty = function (extent) { | |
return _brush.empty() || !extent || extent[1] <= extent[0]; | |
}; | |
_chart._brushing = function () { | |
var extent = _chart.extendBrush(); | |
_chart.redrawBrush(_g); | |
if (_chart.brushIsEmpty(extent)) { | |
dc.events.trigger(function () { | |
_chart.filter(null); | |
_chart.redrawGroup(); | |
}, dc.constants.EVENT_DELAY); | |
} else { | |
var rangedFilter = dc.filters.RangedFilter(extent[0], extent[1]); | |
dc.events.trigger(function () { | |
_chart.replaceFilter(rangedFilter); | |
_chart.redrawGroup(); | |
}, dc.constants.EVENT_DELAY); | |
} | |
}; | |
_chart.redrawBrush = function (g) { | |
if (_brushOn) { | |
if (_chart.filter() && _chart.brush().empty()) { | |
_chart.brush().extent(_chart.filter()); | |
} | |
var gBrush = g.select('g.brush'); | |
gBrush.call(_chart.brush().x(_chart.x())); | |
_chart.setBrushY(gBrush); | |
} | |
_chart.fadeDeselectedArea(); | |
}; | |
_chart.fadeDeselectedArea = function () { | |
// do nothing, sub-chart should override this function | |
}; | |
// borrowed from Crossfilter example | |
_chart.resizeHandlePath = function (d) { | |
var e = +(d === 'e'), x = e ? 1 : -1, y = brushHeight() / 3; | |
/*jshint -W014 */ | |
return 'M' + (0.5 * x) + ',' + y | |
+ 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) | |
+ 'V' + (2 * y - 6) | |
+ 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y) | |
+ 'Z' | |
+ 'M' + (2.5 * x) + ',' + (y + 8) | |
+ 'V' + (2 * y - 8) | |
+ 'M' + (4.5 * x) + ',' + (y + 8) | |
+ 'V' + (2 * y - 8); | |
/*jshint +W014 */ | |
}; | |
function getClipPathId() { | |
return _chart.anchorName().replace(/[ .#]/g, '-') + '-clip'; | |
} | |
/** | |
#### .clipPadding([padding]) | |
Get or set the padding in pixels for the clip path. Once set padding will be applied evenly to | |
the top, left, right, and bottom when the clip path is generated. If set to zero, the clip area | |
will be exactly the chart body area minus the margins. Default: 5 | |
**/ | |
_chart.clipPadding = function (p) { | |
if (!arguments.length) { | |
return _clipPadding; | |
} | |
_clipPadding = p; | |
return _chart; | |
}; | |
function generateClipPath() { | |
var defs = dc.utils.appendOrSelect(_parent, 'defs'); | |
// cannot select <clippath> elements; bug in WebKit, must select by id | |
// https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I | |
var id = getClipPathId(); | |
var chartBodyClip = dc.utils.appendOrSelect(defs, '#' + id, 'clipPath').attr('id', id); | |
var padding = _clipPadding * 2; | |
dc.utils.appendOrSelect(chartBodyClip, 'rect') | |
.attr('width', _chart.xAxisLength() + padding) | |
.attr('height', _chart.yAxisHeight() + padding) | |
.attr('transform', 'translate(-' + _clipPadding + ', -' + _clipPadding + ')'); | |
} | |
_chart._preprocessData = function () {}; | |
_chart._doRender = function () { | |
_chart.resetSvg(); | |
_chart._preprocessData(); | |
_chart._generateG(); | |
generateClipPath(); | |
drawChart(true); | |
configureMouseZoom(); | |
return _chart; | |
}; | |
_chart._doRedraw = function () { | |
_chart._preprocessData(); | |
drawChart(false); | |
generateClipPath(); | |
return _chart; | |
}; | |
function drawChart (render) { | |
if (_chart.isOrdinal()) { | |
_brushOn = false; | |
} | |
prepareXAxis(_chart.g()); | |
_chart._prepareYAxis(_chart.g()); | |
_chart.plotData(); | |
if (_chart.elasticX() || _refocused || render) { | |
_chart.renderXAxis(_chart.g()); | |
} | |
if (_chart.elasticY() || render) { | |
_chart.renderYAxis(_chart.g()); | |
} | |
if (render) { | |
_chart.renderBrush(_chart.g()); | |
} else { | |
_chart.redrawBrush(_chart.g()); | |
} | |
} | |
function configureMouseZoom () { | |
if (_mouseZoomable) { | |
_chart._enableMouseZoom(); | |
} | |
else if (_hasBeenMouseZoomable) { | |
_chart._disableMouseZoom(); | |
} | |
} | |
_chart._enableMouseZoom = function () { | |
_hasBeenMouseZoomable = true; | |
_zoom.x(_chart.x()) | |
.scaleExtent(_zoomScale) | |
.size([_chart.width(), _chart.height()]) | |
.duration(_chart.transitionDuration()); | |
_chart.root().call(_zoom); | |
}; | |
_chart._disableMouseZoom = function () { | |
_chart.root().call(_nullZoom); | |
}; | |
function constrainRange(range, constraint) { | |
var constrainedRange = []; | |
constrainedRange[0] = d3.max([range[0], constraint[0]]); | |
constrainedRange[1] = d3.min([range[1], constraint[1]]); | |
return constrainedRange; | |
} | |
/** | |
#### .focus([range]) | |
Zoom this chart to focus on the given range. The given range should be an array containing only | |
2 elements (`[start, end]`) defining a range in the x domain. If the range is not given or set | |
to null, then the zoom will be reset. _For focus to work elasticX has to be turned off; | |
otherwise focus will be ignored._ | |
```js | |
chart.renderlet(function(chart){ | |
// smooth the rendering through event throttling | |
dc.events.trigger(function(){ | |
// focus some other chart to the range selected by user on this chart | |
someOtherChart.focus(chart.filter()); | |
}); | |
}) | |
``` | |
**/ | |
_chart.focus = function (range) { | |
if (hasRangeSelected(range)) { | |
_chart.x().domain(range); | |
} else { | |
_chart.x().domain(_xOriginalDomain); | |
} | |
_zoom.x(_chart.x()); | |
zoomHandler(); | |
}; | |
_chart.refocused = function () { | |
return _refocused; | |
}; | |
_chart.focusChart = function (c) { | |
if (!arguments.length) { | |
return _focusChart; | |
} | |
_focusChart = c; | |
_chart.on('filtered', function (chart) { | |
if (!chart.filter()) { | |
dc.events.trigger(function () { | |
_focusChart.x().domain(_focusChart.xOriginalDomain()); | |
}); | |
} else if (!rangesEqual(chart.filter(), _focusChart.filter())) { | |
dc.events.trigger(function () { | |
_focusChart.focus(chart.filter()); | |
}); | |
} | |
}); | |
return _chart; | |
}; | |
function rangesEqual(range1, range2) { | |
if (!range1 && !range2) { | |
return true; | |
} | |
else if (!range1 || !range2) { | |
return false; | |
} | |
else if (range1.length === 0 && range2.length === 0) { | |
return true; | |
} | |
else if (range1[0].valueOf() === range2[0].valueOf() && | |
range1[1].valueOf() === range2[1].valueOf()) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
#### .brushOn([boolean]) | |
Turn on/off the brush-based range filter. When brushing is on then user can drag the mouse | |
across a chart with a quantitative scale to perform range filtering based on the extent of the | |
brush, or click on the bars of an ordinal bar chart or slices of a pie chart to filter and | |
unfilter them. However turning on the brush filter will disable other interactive elements on | |
the chart such as highlighting, tool tips, and reference lines. Zooming will still be possible | |
if enabled, but only via scrolling (panning will be disabled.) Default: true | |
**/ | |
_chart.brushOn = function (_) { | |
if (!arguments.length) { | |
return _brushOn; | |
} | |
_brushOn = _; | |
return _chart; | |
}; | |
function hasRangeSelected(range) { | |
return range instanceof Array && range.length > 1; | |
} | |
return _chart; | |
}; | |
/** | |
## Stack Mixin | |
Stack Mixin is an mixin that provides cross-chart support of stackability using d3.layout.stack. | |
**/ | |
dc.stackMixin = function (_chart) { | |
function prepareValues (layer, layerIdx) { | |
var valAccessor = layer.accessor || _chart.valueAccessor(); | |
layer.name = String(layer.name || layerIdx); | |
layer.values = layer.group.all().map(function (d, i) { | |
return { | |
x: _chart.keyAccessor()(d, i), | |
y: layer.hidden ? null : valAccessor(d, i), | |
data: d, | |
layer: layer.name, | |
hidden: layer.hidden | |
}; | |
}); | |
layer.values = layer.values.filter(domainFilter()); | |
return layer.values; | |
} | |
var _stackLayout = d3.layout.stack() | |
.values(prepareValues); | |
var _stack = []; | |
var _titles = {}; | |
var _hidableStacks = false; | |
function domainFilter() { | |
if (!_chart.x()) { | |
return d3.functor(true); | |
} | |
var xDomain = _chart.x().domain(); | |
if (_chart.isOrdinal()) { | |
// TODO #416 | |
//var domainSet = d3.set(xDomain); | |
return function () { | |
return true; //domainSet.has(p.x); | |
}; | |
} | |
if (_chart.elasticX()) { | |
return function () { return true; }; | |
} | |
return function (p) { | |
//return true; | |
return p.x >= xDomain[0] && p.x <= xDomain[xDomain.length - 1]; | |
}; | |
} | |
/** | |
#### .stack(group[, name, accessor]) | |
Stack a new crossfilter group onto this chart with an optional custom value accessor. All stacks | |
in the same chart will share the same key accessor and therefore the same set of keys. | |
For example, in a stacked bar chart, the bars of each stack will be positioned using the same set | |
of keys on the x axis, while stacked vertically. If name is specified then it will be used to | |
generate the legend label. | |
```js | |
// stack group using default accessor | |
chart.stack(valueSumGroup) | |
// stack group using custom accessor | |
.stack(avgByDayGroup, function(d){return d.value.avgByDay;}); | |
``` | |
**/ | |
_chart.stack = function (group, name, accessor) { | |
if (!arguments.length) { | |
return _stack; | |
} | |
if (arguments.length <= 2) { | |
accessor = name; | |
} | |
var layer = {group:group}; | |
if (typeof name === 'string') { | |
layer.name = name; | |
} | |
if (typeof accessor === 'function') { | |
layer.accessor = accessor; | |
} | |
_stack.push(layer); | |
return _chart; | |
}; | |
dc.override(_chart, 'group', function (g, n, f) { | |
if (!arguments.length) { | |
return _chart._group(); | |
} | |
_stack = []; | |
_titles = {}; | |
_chart.stack(g, n); | |
if (f) { | |
_chart.valueAccessor(f); | |
} | |
return _chart._group(g, n); | |
}); | |
/** | |
#### .hidableStacks([boolean]) | |
Allow named stacks to be hidden or shown by clicking on legend items. | |
This does not affect the behavior of hideStack or showStack. | |
**/ | |
_chart.hidableStacks = function (_) { | |
if (!arguments.length) { | |
return _hidableStacks; | |
} | |
_hidableStacks = _; | |
return _chart; | |
}; | |
function findLayerByName(n) { | |
var i = _stack.map(dc.pluck('name')).indexOf(n); | |
return _stack[i]; | |
} | |
/** | |
#### .hideStack(name) | |
Hide all stacks on the chart with the given name. | |
The chart must be re-rendered for this change to appear. | |
**/ | |
_chart.hideStack = function (stackName) { | |
var layer = findLayerByName(stackName); | |
if (layer) { | |
layer.hidden = true; | |
} | |
return _chart; | |
}; | |
/** | |
#### .showStack(name) | |
Show all stacks on the chart with the given name. | |
The chart must be re-rendered for this change to appear. | |
**/ | |
_chart.showStack = function (stackName) { | |
var layer = findLayerByName(stackName); | |
if (layer) { | |
layer.hidden = false; | |
} | |
return _chart; | |
}; | |
_chart.getValueAccessorByIndex = function (index) { | |
return _stack[index].accessor || _chart.valueAccessor(); | |
}; | |
_chart.yAxisMin = function () { | |
var min = d3.min(flattenStack(), function (p) { | |
return (p.y + p.y0 < p.y0) ? (p.y + p.y0) : p.y0; | |
}); | |
return dc.utils.subtract(min, _chart.yAxisPadding()); | |
}; | |
_chart.yAxisMax = function () { | |
var max = d3.max(flattenStack(), function (p) { | |
return p.y + p.y0; | |
}); | |
return dc.utils.add(max, _chart.yAxisPadding()); | |
}; | |
function flattenStack() { | |
return _chart.data().reduce(function (all, layer) { | |
return all.concat(layer.values); | |
}, []); | |
} | |
_chart.xAxisMin = function () { | |
var min = d3.min(flattenStack(), dc.pluck('x')); | |
return dc.utils.subtract(min, _chart.xAxisPadding()); | |
}; | |
_chart.xAxisMax = function () { | |
var max = d3.max(flattenStack(), dc.pluck('x')); | |
return dc.utils.add(max, _chart.xAxisPadding()); | |
}; | |
/** | |
#### .title([stackName], [titleFunction]) | |
Set or get the title function. Chart class will use this function to render svg title (usually interpreted by | |
browser as tooltips) for each child element in the chart, i.e. a slice in a pie chart or a bubble in a bubble chart. | |
Almost every chart supports title function however in grid coordinate chart you need to turn off brush in order to | |
use title otherwise the brush layer will block tooltip trigger. | |
If the first argument is a stack name, the title function will get or set the title for that stack. If stackName | |
is not provided, the first stack is implied. | |
```js | |
// set a title function on 'first stack' | |
chart.title('first stack', function(d) { return d.key + ': ' + d.value; }); | |
// get a title function from 'second stack' | |
var secondTitleFunction = chart.title('second stack'); | |
); | |
``` | |
**/ | |
dc.override(_chart, 'title', function (stackName, titleAccessor) { | |
if (!stackName) { | |
return _chart._title(); | |
} | |
if (typeof stackName === 'function') { | |
return _chart._title(stackName); | |
} | |
if (stackName === _chart._groupName && typeof titleAccessor === 'function') { | |
return _chart._title(titleAccessor); | |
} | |
if (typeof titleAccessor !== 'function') { | |
return _titles[stackName] || _chart._title(); | |
} | |
_titles[stackName] = titleAccessor; | |
return _chart; | |
}); | |
/** | |
#### .stackLayout([layout]) | |
Gets or sets the stack layout algorithm, which computes a baseline for each stack and | |
propagates it to the next. The default is | |
[d3.layout.stack](https://github.com/mbostock/d3/wiki/Stack-Layout#stack). | |
**/ | |
_chart.stackLayout = function (stack) { | |
if (!arguments.length) { | |
return _stackLayout; | |
} | |
_stackLayout = stack; | |
return _chart; | |
}; | |
function visability(l) { | |
return !l.hidden; | |
} | |
_chart.data(function () { | |
var layers = _stack.filter(visability); | |
return layers.length ? _chart.stackLayout()(layers) : []; | |
}); | |
_chart._ordinalXDomain = function () { | |
return flattenStack().map(dc.pluck('x')); | |
}; | |
_chart.colorAccessor(function (d) { | |
var layer = this.layer || this.name || d.name || d.layer; | |
return layer; | |
}); | |
_chart.legendables = function () { | |
return _stack.map(function (layer, i) { | |
return { | |
chart:_chart, | |
name:layer.name, | |
hidden: layer.hidden || false, | |
color:_chart.getColor.call(layer, layer.values, i) | |
}; | |
}); | |
}; | |
_chart.isLegendableHidden = function (d) { | |
var layer = findLayerByName(d.name); | |
return layer ? layer.hidden : false; | |
}; | |
_chart.legendToggle = function (d) { | |
if (_hidableStacks) { | |
if (_chart.isLegendableHidden(d)) { | |
_chart.showStack(d.name); | |
} else { | |
_chart.hideStack(d.name); | |
} | |
//_chart.redraw(); | |
_chart.renderGroup(); | |
} | |
}; | |
return _chart; | |
}; | |
/** | |
## Cap Mixin | |
Cap is a mixin that groups small data elements below a _cap_ into an *others* grouping for both the | |
Row and Pie Charts. | |
The top ordered elements in the group up to the cap amount will be kept in the chart, and the rest | |
will be replaced with an *others* element, with value equal to the sum of the replaced values. The | |
keys of the elements below the cap limit are recorded in order to filter by those keys when the | |
*others* element is clicked. | |
**/ | |
dc.capMixin = function (_chart) { | |
var _cap = Infinity; | |
var _othersLabel = 'Others'; | |
var _othersGrouper = function (topRows) { | |
var topRowsSum = d3.sum(topRows, _chart.valueAccessor()), | |
allRows = _chart.group().all(), | |
allRowsSum = d3.sum(allRows, _chart.valueAccessor()), | |
topKeys = topRows.map(_chart.keyAccessor()), | |
allKeys = allRows.map(_chart.keyAccessor()), | |
topSet = d3.set(topKeys), | |
others = allKeys.filter(function (d) {return !topSet.has(d);}); | |
if (allRowsSum > topRowsSum) { | |
return topRows.concat([{'others': others, 'key': _othersLabel, 'value': allRowsSum - topRowsSum}]); | |
} | |
return topRows; | |
}; | |
_chart.cappedKeyAccessor = function (d, i) { | |
if (d.others) { | |
return d.key; | |
} | |
return _chart.keyAccessor()(d, i); | |
}; | |
_chart.cappedValueAccessor = function (d, i) { | |
if (d.others) { | |
return d.value; | |
} | |
return _chart.valueAccessor()(d, i); | |
}; | |
_chart.data(function (group) { | |
if (_cap === Infinity) { | |
return _chart._computeOrderedGroups(group.all()); | |
} else { | |
var topRows = group.top(_cap); // ordered by crossfilter group order (default value) | |
topRows = _chart._computeOrderedGroups(topRows); // re-order using ordering (default key) | |
if (_othersGrouper) { | |
return _othersGrouper(topRows); | |
} | |
return topRows; | |
} | |
}); | |
/** | |
#### .cap([count]) | |
Get or set the count of elements to that will be included in the cap. | |
**/ | |
_chart.cap = function (_) { | |
if (!arguments.length) { | |
return _cap; | |
} | |
_cap = _; | |
return _chart; | |
}; | |
/** | |
#### .othersLabel([label]) | |
Get or set the label for *Others* slice when slices cap is specified. Default label is **Others**. | |
**/ | |
_chart.othersLabel = function (_) { | |
if (!arguments.length) { | |
return _othersLabel; | |
} | |
_othersLabel = _; | |
return _chart; | |
}; | |
/** | |
#### .othersGrouper([grouperFunction]) | |
Get or set the grouper function that will perform the insertion of data for the *Others* slice | |
if the slices cap is specified. If set to a falsy value, no others will be added. By default the | |
grouper function computes the sum of all values below the cap. | |
```js | |
chart.othersGrouper(function (data) { | |
// compute the value for others, presumably the sum of all values below the cap | |
var othersSum = yourComputeOthersValueLogic(data) | |
// the keys are needed to properly filter when the others element is clicked | |
var othersKeys = yourComputeOthersKeysArrayLogic(data); | |
// add the others row to the dataset | |
data.push({'key': 'Others', 'value': othersSum, 'others': othersKeys }); | |
return data; | |
}); | |
``` | |
**/ | |
_chart.othersGrouper = function (_) { | |
if (!arguments.length) { | |
return _othersGrouper; | |
} | |
_othersGrouper = _; | |
return _chart; | |
}; | |
dc.override(_chart, 'onClick', function (d) { | |
if (d.others) { | |
_chart.filter([d.others]); | |
} | |
_chart._onClick(d); | |
}); | |
return _chart; | |
}; | |
/** | |
## Bubble Mixin | |
Includes: [Color Mixin](#color-mixin) | |
This Mixin provides reusable functionalities for any chart that needs to visualize data using bubbles. | |
**/ | |
dc.bubbleMixin = function (_chart) { | |
var _maxBubbleRelativeSize = 0.3; | |
var _minRadiusWithLabel = 10; | |
_chart.BUBBLE_NODE_CLASS = 'node'; | |
_chart.BUBBLE_CLASS = 'bubble'; | |
_chart.MIN_RADIUS = 10; | |
_chart = dc.colorMixin(_chart); | |
_chart.renderLabel(true); | |
_chart.data(function (group) { | |
return group.top(Infinity); | |
}); | |
var _r = d3.scale.linear().domain([0, 100]); | |
var _rValueAccessor = function (d) { | |
return d.r; | |
}; | |
/** | |
#### .r([bubbleRadiusScale]) | |
Get or set the bubble radius scale. By default the bubble chart uses | |
`d3.scale.linear().domain([0, 100])` as its r scale . | |
**/ | |
_chart.r = function (_) { | |
if (!arguments.length) { | |
return _r; | |
} | |
_r = _; | |
return _chart; | |
}; | |
/** | |
#### .radiusValueAccessor([radiusValueAccessor]) | |
Get or set the radius value accessor function. If set, the radius value accessor function will | |
be used to retrieve a data value for each bubble. The data retrieved then will be mapped using | |
the r scale to the actual bubble radius. This allows you to encode a data dimension using bubble | |
size. | |
**/ | |
_chart.radiusValueAccessor = function (_) { | |
if (!arguments.length) { | |
return _rValueAccessor; | |
} | |
_rValueAccessor = _; | |
return _chart; | |
}; | |
_chart.rMin = function () { | |
var min = d3.min(_chart.data(), function (e) { | |
return _chart.radiusValueAccessor()(e); | |
}); | |
return min; | |
}; | |
_chart.rMax = function () { | |
var max = d3.max(_chart.data(), function (e) { | |
return _chart.radiusValueAccessor()(e); | |
}); | |
return max; | |
}; | |
_chart.bubbleR = function (d) { | |
var value = _chart.radiusValueAccessor()(d); | |
var r = _chart.r()(value); | |
if (isNaN(r) || value <= 0) { | |
r = 0; | |
} | |
return r; | |
}; | |
var labelFunction = function (d) { | |
return _chart.label()(d); | |
}; | |
var labelOpacity = function (d) { | |
return (_chart.bubbleR(d) > _minRadiusWithLabel) ? 1 : 0; | |
}; | |
_chart._doRenderLabel = function (bubbleGEnter) { | |
if (_chart.renderLabel()) { | |
var label = bubbleGEnter.select('text'); | |
if (label.empty()) { | |
label = bubbleGEnter.append('text') | |
.attr('text-anchor', 'middle') | |
.attr('dy', '.3em') | |
.on('click', _chart.onClick); | |
} | |
label | |
.attr('opacity', 0) | |
.text(labelFunction); | |
dc.transition(label, _chart.transitionDuration()) | |
.attr('opacity', labelOpacity); | |
} | |
}; | |
_chart.doUpdateLabels = function (bubbleGEnter) { | |
if (_chart.renderLabel()) { | |
var labels = bubbleGEnter.selectAll('text') | |
.text(labelFunction); | |
dc.transition(labels, _chart.transitionDuration()) | |
.attr('opacity', labelOpacity); | |
} | |
}; | |
var titleFunction = function (d) { | |
return _chart.title()(d); | |
}; | |
_chart._doRenderTitles = function (g) { | |
if (_chart.renderTitle()) { | |
var title = g.select('title'); | |
if (title.empty()) { | |
g.append('title').text(titleFunction); | |
} | |
} | |
}; | |
_chart.doUpdateTitles = function (g) { | |
if (_chart.renderTitle()) { | |
g.selectAll('title').text(titleFunction); | |
} | |
}; | |
/** | |
#### .minRadiusWithLabel([radius]) | |
Get or set the minimum radius for label rendering. If a bubble's radius is less than this value | |
then no label will be rendered. Default: 10 | |
**/ | |
_chart.minRadiusWithLabel = function (_) { | |
if (!arguments.length) { | |
return _minRadiusWithLabel; | |
} | |
_minRadiusWithLabel = _; | |
return _chart; | |
}; | |
/** | |
#### .maxBubbleRelativeSize([relativeSize]) | |
Get or set the maximum relative size of a bubble to the length of x axis. This value is useful | |
when the difference in radius between bubbles is too great. Default: 0.3 | |
**/ | |
_chart.maxBubbleRelativeSize = function (_) { | |
if (!arguments.length) { | |
return _maxBubbleRelativeSize; | |
} | |
_maxBubbleRelativeSize = _; | |
return _chart; | |
}; | |
_chart.fadeDeselectedArea = function () { | |
if (_chart.hasFilter()) { | |
_chart.selectAll('g.' + _chart.BUBBLE_NODE_CLASS).each(function (d) { | |
if (_chart.isSelectedNode(d)) { | |
_chart.highlightSelected(this); | |
} else { | |
_chart.fadeDeselected(this); | |
} | |
}); | |
} else { | |
_chart.selectAll('g.' + _chart.BUBBLE_NODE_CLASS).each(function () { | |
_chart.resetHighlight(this); | |
}); | |
} | |
}; | |
_chart.isSelectedNode = function (d) { | |
return _chart.hasFilter(d.key); | |
}; | |
_chart.onClick = function (d) { | |
var filter = d.key; | |
dc.events.trigger(function () { | |
_chart.filter(filter); | |
_chart.redrawGroup(); | |
}); | |
}; | |
return _chart; | |
}; | |
/** | |
## Pie Chart | |
Includes: [Cap Mixin](#cap-mixin), [Color Mixin](#color-mixin), [Base Mixin](#base-mixin) | |
The pie chart implementation is usually used to visualize a small categorical distribution. The pie | |
chart uses keyAccessor to determine the slices, and valueAccessor to calculate the size of each | |
slice relative to the sum of all values. Slices are ordered by `.ordering` which defaults to sorting | |
by key. | |
Examples: | |
* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) | |
#### dc.pieChart(parent[, chartGroup]) | |
Create a pie chart instance and attaches it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created pie chart instance | |
```js | |
// create a pie chart under #chart-container1 element using the default global chart group | |
var chart1 = dc.pieChart('#chart-container1'); | |
// create a pie chart under #chart-container2 element using chart group A | |
var chart2 = dc.pieChart('#chart-container2', 'chartGroupA'); | |
``` | |
**/ | |
dc.pieChart = function (parent, chartGroup) { | |
var DEFAULT_MIN_ANGLE_FOR_LABEL = 0.5; | |
var _sliceCssClass = 'pie-slice'; | |
var _emptyCssClass = 'empty-chart'; | |
var _emptyTitle = 'empty'; | |
var _radius, | |
_innerRadius = 0; | |
var _g; | |
var _cx; | |
var _cy; | |
var _minAngleForLabel = DEFAULT_MIN_ANGLE_FOR_LABEL; | |
var _externalLabelRadius; | |
var _chart = dc.capMixin(dc.colorMixin(dc.baseMixin({}))); | |
_chart.colorAccessor(_chart.cappedKeyAccessor); | |
_chart.title(function (d) { | |
return _chart.cappedKeyAccessor(d) + ': ' + _chart.cappedValueAccessor(d); | |
}); | |
/** | |
#### .slicesCap([cap]) | |
Get or set the maximum number of slices the pie chart will generate. The top slices are determined by | |
value from high to low. Other slices exeeding the cap will be rolled up into one single *Others* slice. | |
The resulting data will still be sorted by .ordering (default by key). | |
**/ | |
_chart.slicesCap = _chart.cap; | |
_chart.label(_chart.cappedKeyAccessor); | |
_chart.renderLabel(true); | |
_chart.transitionDuration(350); | |
_chart._doRender = function () { | |
_chart.resetSvg(); | |
_g = _chart.svg() | |
.append('g') | |
.attr('transform', 'translate(' + _chart.cx() + ',' + _chart.cy() + ')'); | |
drawChart(); | |
return _chart; | |
}; | |
function drawChart() { | |
// set radius on basis of chart dimension if missing | |
_radius = _radius ? _radius : d3.min([_chart.width(), _chart.height()]) / 2; | |
var arc = buildArcs(); | |
var pie = pieLayout(); | |
var pieData; | |
// if we have data... | |
if (d3.sum(_chart.data(), _chart.valueAccessor())) { | |
pieData = pie(_chart.data()); | |
_g.classed(_emptyCssClass, false); | |
} else { | |
// otherwise we'd be getting NaNs, so override | |
// note: abuse others for its ignoring the value accessor | |
pieData = pie([{key:_emptyTitle, value:1, others: [_emptyTitle]}]); | |
_g.classed(_emptyCssClass, true); | |
} | |
if (_g) { | |
var slices = _g.selectAll('g.' + _sliceCssClass) | |
.data(pieData); | |
createElements(slices, arc, pieData); | |
updateElements(pieData, arc); | |
removeElements(slices); | |
highlightFilter(); | |
} | |
} | |
function createElements(slices, arc, pieData) { | |
var slicesEnter = createSliceNodes(slices); | |
createSlicePath(slicesEnter, arc); | |
createTitles(slicesEnter); | |
createLabels(pieData, arc); | |
} | |
function createSliceNodes(slices) { | |
var slicesEnter = slices | |
.enter() | |
.append('g') | |
.attr('class', function (d, i) { | |
return _sliceCssClass + ' _' + i; | |
}); | |
return slicesEnter; | |
} | |
function createSlicePath(slicesEnter, arc) { | |
var slicePath = slicesEnter.append('path') | |
.attr('fill', fill) | |
.on('click', onClick) | |
.attr('d', function (d, i) { | |
return safeArc(d, i, arc); | |
}); | |
dc.transition(slicePath, _chart.transitionDuration(), function (s) { | |
s.attrTween('d', tweenPie); | |
}); | |
} | |
function createTitles(slicesEnter) { | |
if (_chart.renderTitle()) { | |
slicesEnter.append('title').text(function (d) { | |
return _chart.title()(d); | |
}); | |
} | |
} | |
function positionLabels(labelsEnter, arc) { | |
dc.transition(labelsEnter, _chart.transitionDuration()) | |
.attr('transform', function (d) { | |
return labelPosition(d, arc); | |
}) | |
.attr('text-anchor', 'middle') | |
.text(function (d) { | |
var data = d.data; | |
if ((sliceHasNoData(data) || sliceTooSmall(d)) && !isSelectedSlice(d)) { | |
return ''; | |
} | |
return _chart.label()(d.data); | |
}); | |
} | |
function createLabels(pieData, arc) { | |
if (_chart.renderLabel()) { | |
var labels = _g.selectAll('text.' + _sliceCssClass) | |
.data(pieData); | |
labels.exit().remove(); | |
var labelsEnter = labels | |
.enter() | |
.append('text') | |
.attr('class', function (d, i) { | |
var classes = _sliceCssClass + ' _' + i; | |
if (_externalLabelRadius) { | |
classes += ' external'; | |
} | |
return classes; | |
}) | |
.on('click', onClick); | |
positionLabels(labelsEnter, arc); | |
} | |
} | |
function updateElements(pieData, arc) { | |
updateSlicePaths(pieData, arc); | |
updateLabels(pieData, arc); | |
updateTitles(pieData); | |
} | |
function updateSlicePaths(pieData, arc) { | |
var slicePaths = _g.selectAll('g.' + _sliceCssClass) | |
.data(pieData) | |
.select('path') | |
.attr('d', function (d, i) { | |
return safeArc(d, i, arc); | |
}); | |
dc.transition(slicePaths, _chart.transitionDuration(), | |
function (s) { | |
s.attrTween('d', tweenPie); | |
}).attr('fill', fill); | |
} | |
function updateLabels(pieData, arc) { | |
if (_chart.renderLabel()) { | |
var labels = _g.selectAll('text.' + _sliceCssClass) | |
.data(pieData); | |
positionLabels(labels, arc); | |
} | |
} | |
function updateTitles(pieData) { | |
if (_chart.renderTitle()) { | |
_g.selectAll('g.' + _sliceCssClass) | |
.data(pieData) | |
.select('title') | |
.text(function (d) { | |
return _chart.title()(d.data); | |
}); | |
} | |
} | |
function removeElements(slices) { | |
slices.exit().remove(); | |
} | |
function highlightFilter() { | |
if (_chart.hasFilter()) { | |
_chart.selectAll('g.' + _sliceCssClass).each(function (d) { | |
if (isSelectedSlice(d)) { | |
_chart.highlightSelected(this); | |
} else { | |
_chart.fadeDeselected(this); | |
} | |
}); | |
} else { | |
_chart.selectAll('g.' + _sliceCssClass).each(function () { | |
_chart.resetHighlight(this); | |
}); | |
} | |
} | |
/** | |
#### .innerRadius([innerRadius]) | |
Get or set the inner radius of the pie chart. If the inner radius is greater than 0px then the | |
pie chart will be rendered as a doughnut chart. Default inner radius is 0px. | |
**/ | |
_chart.innerRadius = function (r) { | |
if (!arguments.length) { | |
return _innerRadius; | |
} | |
_innerRadius = r; | |
return _chart; | |
}; | |
/** | |
#### .radius([radius]) | |
Get or set the outer radius. If the radius is not set, it will be half of the minimum of the | |
chart width and height. | |
**/ | |
_chart.radius = function (r) { | |
if (!arguments.length) { | |
return _radius; | |
} | |
_radius = r; | |
return _chart; | |
}; | |
/** | |
#### .cx([cx]) | |
Get or set center x coordinate position. Default is center of svg. | |
**/ | |
_chart.cx = function (cx) { | |
if (!arguments.length) { | |
return (_cx || _chart.width() / 2); | |
} | |
_cx = cx; | |
return _chart; | |
}; | |
/** | |
#### .cy([cy]) | |
Get or set center y coordinate position. Default is center of svg. | |
**/ | |
_chart.cy = function (cy) { | |
if (!arguments.length) { | |
return (_cy || _chart.height() / 2); | |
} | |
_cy = cy; | |
return _chart; | |
}; | |
function buildArcs() { | |
return d3.svg.arc().outerRadius(_radius).innerRadius(_innerRadius); | |
} | |
function isSelectedSlice(d) { | |
return _chart.hasFilter(_chart.cappedKeyAccessor(d.data)); | |
} | |
_chart._doRedraw = function () { | |
drawChart(); | |
return _chart; | |
}; | |
/** | |
#### .minAngleForLabel([minAngle]) | |
Get or set the minimal slice angle for label rendering. Any slice with a smaller angle will not | |
display a slice label. Default min angle is 0.5. | |
**/ | |
_chart.minAngleForLabel = function (_) { | |
if (!arguments.length) { | |
return _minAngleForLabel; | |
} | |
_minAngleForLabel = _; | |
return _chart; | |
}; | |
function pieLayout() { | |
return d3.layout.pie().sort(null).value(_chart.cappedValueAccessor); | |
} | |
function sliceTooSmall(d) { | |
var angle = (d.endAngle - d.startAngle); | |
return isNaN(angle) || angle < _minAngleForLabel; | |
} | |
function sliceHasNoData(d) { | |
return _chart.cappedValueAccessor(d) === 0; | |
} | |
function tweenPie(b) { | |
b.innerRadius = _innerRadius; | |
var current = this._current; | |
if (isOffCanvas(current)) { | |
current = {startAngle: 0, endAngle: 0}; | |
} | |
var i = d3.interpolate(current, b); | |
this._current = i(0); | |
return function (t) { | |
return safeArc(i(t), 0, buildArcs()); | |
}; | |
} | |
function isOffCanvas(current) { | |
return !current || isNaN(current.startAngle) || isNaN(current.endAngle); | |
} | |
function fill(d, i) { | |
return _chart.getColor(d.data, i); | |
} | |
function onClick(d, i) { | |
if (_g.attr('class') !== _emptyCssClass) { | |
_chart.onClick(d.data, i); | |
} | |
} | |
function safeArc(d, i, arc) { | |
var path = arc(d, i); | |
if (path.indexOf('NaN') >= 0) { | |
path = 'M0,0'; | |
} | |
return path; | |
} | |
/** | |
#### .emptyTitle([title]) | |
Title to use for the only slice when there is no data | |
*/ | |
_chart.emptyTitle = function (title) { | |
if (arguments.length === 0) { | |
return _emptyTitle; | |
} | |
_emptyTitle = title; | |
return _chart; | |
}; | |
/** | |
#### .externalLabels([radius]) | |
Position slice labels offset from the outer edge of the chart | |
The given argument sets the radial offset. | |
*/ | |
_chart.externalLabels = function (radius) { | |
if (arguments.length === 0) { | |
return _externalLabelRadius; | |
} else if (radius) { | |
_externalLabelRadius = radius; | |
} else { | |
_externalLabelRadius = undefined; | |
} | |
return _chart; | |
}; | |
function labelPosition(d, arc) { | |
var centroid; | |
if (_externalLabelRadius) { | |
centroid = d3.svg.arc() | |
.outerRadius(_radius + _externalLabelRadius) | |
.innerRadius(_radius + _externalLabelRadius) | |
.centroid(d); | |
} else { | |
centroid = arc.centroid(d); | |
} | |
if (isNaN(centroid[0]) || isNaN(centroid[1])) { | |
return 'translate(0,0)'; | |
} else { | |
return 'translate(' + centroid + ')'; | |
} | |
} | |
_chart.legendables = function () { | |
return _chart.data().map(function (d, i) { | |
var legendable = {name: d.key, data: d.value, others: d.others, chart:_chart}; | |
legendable.color = _chart.getColor(d, i); | |
return legendable; | |
}); | |
}; | |
_chart.legendHighlight = function (d) { | |
highlightSliceFromLegendable(d, true); | |
}; | |
_chart.legendReset = function (d) { | |
highlightSliceFromLegendable(d, false); | |
}; | |
_chart.legendToggle = function (d) { | |
_chart.onClick({key: d.name, others: d.others}); | |
}; | |
function highlightSliceFromLegendable(legendable, highlighted) { | |
_chart.selectAll('g.pie-slice').each(function (d) { | |
if (legendable.name === d.data.key) { | |
d3.select(this).classed('highlight', highlighted); | |
} | |
}); | |
} | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Bar Chart | |
Includes: [Stack Mixin](#stack Mixin), [Coordinate Grid Mixin](#coordinate-grid-mixin) | |
Concrete bar chart/histogram implementation. | |
Examples: | |
* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) | |
* [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) | |
#### dc.barChart(parent[, chartGroup]) | |
Create a bar chart instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection | compositeChart - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
If the bar chart is a sub-chart in a [Composite Chart](#composite-chart) then pass in the parent composite | |
chart instance. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created bar chart instance | |
```js | |
// create a bar chart under #chart-container1 element using the default global chart group | |
var chart1 = dc.barChart('#chart-container1'); | |
// create a bar chart under #chart-container2 element using chart group A | |
var chart2 = dc.barChart('#chart-container2', 'chartGroupA'); | |
// create a sub-chart under a composite parent chart | |
var chart3 = dc.barChart(compositeChart); | |
``` | |
**/ | |
dc.barChart = function (parent, chartGroup) { | |
var MIN_BAR_WIDTH = 1; | |
var DEFAULT_GAP_BETWEEN_BARS = 2; | |
var _chart = dc.stackMixin(dc.coordinateGridMixin({})); | |
var _gap = DEFAULT_GAP_BETWEEN_BARS; | |
var _centerBar = false; | |
var _alwaysUseRounding = false; | |
var _barWidth; | |
dc.override(_chart, 'rescale', function () { | |
_chart._rescale(); | |
_barWidth = undefined; | |
}); | |
dc.override(_chart, 'render', function () { | |
if (_chart.round() && _centerBar && !_alwaysUseRounding) { | |
dc.logger.warn('By default, brush rounding is disabled if bars are centered. ' + | |
'See dc.js bar chart API documentation for details.'); | |
} | |
_chart._render(); | |
}); | |
_chart.plotData = function () { | |
var layers = _chart.chartBodyG().selectAll('g.stack') | |
.data(_chart.data()); | |
calculateBarWidth(); | |
layers | |
.enter() | |
.append('g') | |
.attr('class', function (d, i) { | |
return 'stack ' + '_' + i; | |
}); | |
layers.each(function (d, i) { | |
var layer = d3.select(this); | |
renderBars(layer, i, d); | |
}); | |
}; | |
function barHeight(d) { | |
return dc.utils.safeNumber(Math.abs(_chart.y()(d.y + d.y0) - _chart.y()(d.y0))); | |
} | |
function renderBars(layer, layerIndex, d) { | |
var bars = layer.selectAll('rect.bar') | |
.data(d.values, dc.pluck('x')); | |
var enter = bars.enter() | |
.append('rect') | |
.attr('class', 'bar') | |
.attr('fill', dc.pluck('data', _chart.getColor)) | |
.attr('y', _chart.yAxisHeight()) | |
.attr('height', 0); | |
if (_chart.renderTitle()) { | |
enter.append('title').text(dc.pluck('data', _chart.title(d.name))); | |
} | |
if (_chart.isOrdinal()) { | |
bars.on('click', onClick); | |
} | |
dc.transition(bars, _chart.transitionDuration()) | |
.attr('x', function (d) { | |
var x = _chart.x()(d.x); | |
if (_centerBar) { | |
x -= _barWidth / 2; | |
} | |
if (_chart.isOrdinal() && _gap !== undefined) { | |
x += _gap / 2; | |
} | |
return dc.utils.safeNumber(x); | |
}) | |
.attr('y', function (d) { | |
var y = _chart.y()(d.y + d.y0); | |
if (d.y < 0) { | |
y -= barHeight(d); | |
} | |
return dc.utils.safeNumber(y); | |
}) | |
.attr('width', _barWidth) | |
.attr('height', function (d) { | |
return barHeight(d); | |
}) | |
.attr('fill', dc.pluck('data', _chart.getColor)) | |
.select('title').text(dc.pluck('data', _chart.title(d.name))); | |
dc.transition(bars.exit(), _chart.transitionDuration()) | |
.attr('height', 0) | |
.remove(); | |
} | |
function calculateBarWidth() { | |
if (_barWidth === undefined) { | |
var numberOfBars = _chart.xUnitCount(); | |
// please can't we always use rangeBands for bar charts? | |
if (_chart.isOrdinal() && _gap === undefined) { | |
_barWidth = Math.floor(_chart.x().rangeBand()); | |
} else if (_gap) { | |
_barWidth = Math.floor((_chart.xAxisLength() - (numberOfBars - 1) * _gap) / numberOfBars); | |
} else { | |
_barWidth = Math.floor(_chart.xAxisLength() / (1 + _chart.barPadding()) / numberOfBars); | |
} | |
if (_barWidth === Infinity || isNaN(_barWidth) || _barWidth < MIN_BAR_WIDTH) { | |
_barWidth = MIN_BAR_WIDTH; | |
} | |
} | |
} | |
_chart.fadeDeselectedArea = function () { | |
var bars = _chart.chartBodyG().selectAll('rect.bar'); | |
var extent = _chart.brush().extent(); | |
if (_chart.isOrdinal()) { | |
if (_chart.hasFilter()) { | |
bars.classed(dc.constants.SELECTED_CLASS, function (d) { | |
return _chart.hasFilter(d.x); | |
}); | |
bars.classed(dc.constants.DESELECTED_CLASS, function (d) { | |
return !_chart.hasFilter(d.x); | |
}); | |
} else { | |
bars.classed(dc.constants.SELECTED_CLASS, false); | |
bars.classed(dc.constants.DESELECTED_CLASS, false); | |
} | |
} else { | |
if (!_chart.brushIsEmpty(extent)) { | |
var start = extent[0]; | |
var end = extent[1]; | |
bars.classed(dc.constants.DESELECTED_CLASS, function (d) { | |
return d.x < start || d.x >= end; | |
}); | |
} else { | |
bars.classed(dc.constants.DESELECTED_CLASS, false); | |
} | |
} | |
}; | |
/** | |
#### .centerBar(boolean) | |
Whether the bar chart will render each bar centered around the data position on x axis. Default: false | |
**/ | |
_chart.centerBar = function (_) { | |
if (!arguments.length) { | |
return _centerBar; | |
} | |
_centerBar = _; | |
return _chart; | |
}; | |
function onClick(d) { | |
_chart.onClick(d.data); | |
} | |
/** | |
#### .barPadding([padding]) | |
Get or set the spacing between bars as a fraction of bar size. Valid values are between 0-1. | |
Setting this value will also remove any previously set `gap`. See the | |
[d3 docs](https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-ordinal_rangeBands) | |
for a visual description of how the padding is applied. | |
**/ | |
_chart.barPadding = function (_) { | |
if (!arguments.length) { | |
return _chart._rangeBandPadding(); | |
} | |
_chart._rangeBandPadding(_); | |
_gap = undefined; | |
return _chart; | |
}; | |
_chart._useOuterPadding = function () { | |
return _gap === undefined; | |
}; | |
/** | |
#### .outerPadding([padding]) | |
Get or set the outer padding on an ordinal bar chart. This setting has no effect on non-ordinal charts. | |
Will pad the width by `padding * barWidth` on each side of the chart. | |
Default: 0.5 | |
**/ | |
_chart.outerPadding = _chart._outerRangeBandPadding; | |
/** | |
#### .gap(gapBetweenBars) | |
Manually set fixed gap (in px) between bars instead of relying on the default auto-generated | |
gap. By default the bar chart implementation will calculate and set the gap automatically | |
based on the number of data points and the length of the x axis. | |
**/ | |
_chart.gap = function (_) { | |
if (!arguments.length) { | |
return _gap; | |
} | |
_gap = _; | |
return _chart; | |
}; | |
_chart.extendBrush = function () { | |
var extent = _chart.brush().extent(); | |
if (_chart.round() && (!_centerBar || _alwaysUseRounding)) { | |
extent[0] = extent.map(_chart.round())[0]; | |
extent[1] = extent.map(_chart.round())[1]; | |
_chart.chartBodyG().select('.brush') | |
.call(_chart.brush().extent(extent)); | |
} | |
return extent; | |
}; | |
/** | |
#### .alwaysUseRounding([boolean]) | |
Set or get whether rounding is enabled when bars are centered. Default: false. If false, using | |
rounding with centered bars will result in a warning and rounding will be ignored. This flag | |
has no effect if bars are not centered. | |
When using standard d3.js rounding methods, the brush often doesn't align correctly with | |
centered bars since the bars are offset. The rounding function must add an offset to | |
compensate, such as in the following example. | |
```js | |
chart.round(function(n) {return Math.floor(n)+0.5}); | |
``` | |
**/ | |
_chart.alwaysUseRounding = function (_) { | |
if (!arguments.length) { | |
return _alwaysUseRounding; | |
} | |
_alwaysUseRounding = _; | |
return _chart; | |
}; | |
function colorFilter(color, inv) { | |
return function () { | |
var item = d3.select(this); | |
var match = item.attr('fill') === color; | |
return inv ? !match : match; | |
}; | |
} | |
_chart.legendHighlight = function (d) { | |
if (!_chart.isLegendableHidden(d)) { | |
_chart.g().selectAll('rect.bar') | |
.classed('highlight', colorFilter(d.color)) | |
.classed('fadeout', colorFilter(d.color, true)); | |
} | |
}; | |
_chart.legendReset = function () { | |
_chart.g().selectAll('rect.bar') | |
.classed('highlight', false) | |
.classed('fadeout', false); | |
}; | |
dc.override(_chart, 'xAxisMax', function () { | |
var max = this._xAxisMax(); | |
if ('resolution' in _chart.xUnits()) { | |
var res = _chart.xUnits().resolution; | |
max += res; | |
} | |
return max; | |
}); | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Line Chart | |
Includes [Stack Mixin](#stack-mixin), [Coordinate Grid Mixin](#coordinate-grid-mixin) | |
Concrete line/area chart implementation. | |
Examples: | |
* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) | |
* [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) | |
#### dc.lineChart(parent[, chartGroup]) | |
Create a line chart instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection | compositeChart - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
If the line chart is a sub-chart in a [Composite Chart](#composite-chart) then pass in the parent composite | |
chart instance. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created line chart instance | |
```js | |
// create a line chart under #chart-container1 element using the default global chart group | |
var chart1 = dc.lineChart('#chart-container1'); | |
// create a line chart under #chart-container2 element using chart group A | |
var chart2 = dc.lineChart('#chart-container2', 'chartGroupA'); | |
// create a sub-chart under a composite parent chart | |
var chart3 = dc.lineChart(compositeChart); | |
``` | |
**/ | |
dc.lineChart = function (parent, chartGroup) { | |
var DEFAULT_DOT_RADIUS = 5; | |
var TOOLTIP_G_CLASS = 'dc-tooltip'; | |
var DOT_CIRCLE_CLASS = 'dot'; | |
var Y_AXIS_REF_LINE_CLASS = 'yRef'; | |
var X_AXIS_REF_LINE_CLASS = 'xRef'; | |
var DEFAULT_DOT_OPACITY = 1e-6; | |
var _chart = dc.stackMixin(dc.coordinateGridMixin({})); | |
var _renderArea = false; | |
var _dotRadius = DEFAULT_DOT_RADIUS; | |
var _dataPointRadius = null; | |
var _dataPointFillOpacity = DEFAULT_DOT_OPACITY; | |
var _dataPointStrokeOpacity = DEFAULT_DOT_OPACITY; | |
var _interpolate = 'linear'; | |
var _tension = 0.7; | |
var _defined; | |
var _dashStyle; | |
_chart.transitionDuration(500); | |
_chart._rangeBandPadding(1); | |
_chart.plotData = function () { | |
var chartBody = _chart.chartBodyG(); | |
var layersList = chartBody.selectAll('g.stack-list'); | |
if (layersList.empty()) { | |
layersList = chartBody.append('g').attr('class', 'stack-list'); | |
} | |
var layers = layersList.selectAll('g.stack').data(_chart.data()); | |
var layersEnter = layers | |
.enter() | |
.append('g') | |
.attr('class', function (d, i) { | |
return 'stack ' + '_' + i; | |
}); | |
drawLine(layersEnter, layers); | |
drawArea(layersEnter, layers); | |
drawDots(chartBody, layers); | |
}; | |
/** | |
#### .interpolate([value]) | |
Gets or sets the interpolator to use for lines drawn, by string name, allowing e.g. step | |
functions, splines, and cubic interpolation. This is passed to | |
[d3.svg.line.interpolate](https://github.com/mbostock/d3/wiki/SVG-Shapes#line_interpolate) and | |
[d3.svg.area.interpolate](https://github.com/mbostock/d3/wiki/SVG-Shapes#area_interpolate), | |
where you can find a complete list of valid arguments | |
**/ | |
_chart.interpolate = function (_) { | |
if (!arguments.length) { | |
return _interpolate; | |
} | |
_interpolate = _; | |
return _chart; | |
}; | |
/** | |
#### .tension([value]) Gets or sets the tension to use for lines drawn, in the range 0 to 1. | |
This parameter further customizes the interpolation behavior. It is passed to | |
[d3.svg.line.tension](https://github.com/mbostock/d3/wiki/SVG-Shapes#line_tension) and | |
[d3.svg.area.tension](https://github.com/mbostock/d3/wiki/SVG-Shapes#area_tension). Default: | |
0.7 | |
**/ | |
_chart.tension = function (_) { | |
if (!arguments.length) { | |
return _tension; | |
} | |
_tension = _; | |
return _chart; | |
}; | |
/** | |
#### .defined([value]) | |
Gets or sets a function that will determine discontinuities in the line which should be | |
skipped: the path will be broken into separate subpaths if some points are undefined. | |
This function is passed to | |
[d3.svg.line.defined](https://github.com/mbostock/d3/wiki/SVG-Shapes#line_defined) | |
Note: crossfilter will sometimes coerce nulls to 0, so you may need to carefully write | |
custom reduce functions to get this to work, depending on your data. See | |
https://github.com/dc-js/dc.js/issues/615#issuecomment-49089248 | |
**/ | |
_chart.defined = function (_) { | |
if (!arguments.length) { | |
return _defined; | |
} | |
_defined = _; | |
return _chart; | |
}; | |
/** | |
#### .dashStyle([array]) | |
Set the line's d3 dashstyle. This value becomes the 'stroke-dasharray' of line. Defaults to empty | |
array (solid line). | |
```js | |
// create a Dash Dot Dot Dot | |
chart.dashStyle([3,1,1,1]); | |
``` | |
**/ | |
_chart.dashStyle = function (_) { | |
if (!arguments.length) { | |
return _dashStyle; | |
} | |
_dashStyle = _; | |
return _chart; | |
}; | |
/** | |
#### .renderArea([boolean]) | |
Get or set render area flag. If the flag is set to true then the chart will render the area | |
beneath each line and the line chart effectively becomes an area chart. | |
**/ | |
_chart.renderArea = function (_) { | |
if (!arguments.length) { | |
return _renderArea; | |
} | |
_renderArea = _; | |
return _chart; | |
}; | |
function colors(d, i) { | |
return _chart.getColor.call(d, d.values, i); | |
} | |
function drawLine(layersEnter, layers) { | |
var line = d3.svg.line() | |
.x(function (d) { | |
return _chart.x()(d.x); | |
}) | |
.y(function (d) { | |
return _chart.y()(d.y + d.y0); | |
}) | |
.interpolate(_interpolate) | |
.tension(_tension); | |
if (_defined) { | |
line.defined(_defined); | |
} | |
var path = layersEnter.append('path') | |
.attr('class', 'line') | |
.attr('stroke', colors); | |
if (_dashStyle) { | |
path.attr('stroke-dasharray', _dashStyle); | |
} | |
dc.transition(layers.select('path.line'), _chart.transitionDuration()) | |
//.ease('linear') | |
.attr('stroke', colors) | |
.attr('d', function (d) { | |
return safeD(line(d.values)); | |
}); | |
} | |
function drawArea(layersEnter, layers) { | |
if (_renderArea) { | |
var area = d3.svg.area() | |
.x(function (d) { | |
return _chart.x()(d.x); | |
}) | |
.y(function (d) { | |
return _chart.y()(d.y + d.y0); | |
}) | |
.y0(function (d) { | |
return _chart.y()(d.y0); | |
}) | |
.interpolate(_interpolate) | |
.tension(_tension); | |
if (_defined) { | |
area.defined(_defined); | |
} | |
layersEnter.append('path') | |
.attr('class', 'area') | |
.attr('fill', colors) | |
.attr('d', function (d) { | |
return safeD(area(d.values)); | |
}); | |
dc.transition(layers.select('path.area'), _chart.transitionDuration()) | |
//.ease('linear') | |
.attr('fill', colors) | |
.attr('d', function (d) { | |
return safeD(area(d.values)); | |
}); | |
} | |
} | |
function safeD (d) { | |
return (!d || d.indexOf('NaN') >= 0) ? 'M0,0' : d; | |
} | |
function drawDots(chartBody, layers) { | |
if (!_chart.brushOn()) { | |
var tooltipListClass = TOOLTIP_G_CLASS + '-list'; | |
var tooltips = chartBody.select('g.' + tooltipListClass); | |
if (tooltips.empty()) { | |
tooltips = chartBody.append('g').attr('class', tooltipListClass); | |
} | |
layers.each(function (d, layerIndex) { | |
var points = d.values; | |
if (_defined) { | |
points = points.filter(_defined); | |
} | |
var g = tooltips.select('g.' + TOOLTIP_G_CLASS + '._' + layerIndex); | |
if (g.empty()) { | |
g = tooltips.append('g').attr('class', TOOLTIP_G_CLASS + ' _' + layerIndex); | |
} | |
createRefLines(g); | |
var dots = g.selectAll('circle.' + DOT_CIRCLE_CLASS) | |
.data(points, dc.pluck('x')); | |
dots.enter() | |
.append('circle') | |
.attr('class', DOT_CIRCLE_CLASS) | |
.attr('r', getDotRadius()) | |
.style('fill-opacity', _dataPointFillOpacity) | |
.style('stroke-opacity', _dataPointStrokeOpacity) | |
.on('mousemove', function () { | |
var dot = d3.select(this); | |
showDot(dot); | |
showRefLines(dot, g); | |
}) | |
.on('mouseout', function () { | |
var dot = d3.select(this); | |
hideDot(dot); | |
hideRefLines(g); | |
}); | |
dots | |
.attr('cx', function (d) { | |
return dc.utils.safeNumber(_chart.x()(d.x)); | |
}) | |
.attr('cy', function (d) { | |
return dc.utils.safeNumber(_chart.y()(d.y + d.y0)); | |
}) | |
.attr('fill', _chart.getColor) | |
.call(renderTitle, d); | |
dots.exit().remove(); | |
}); | |
} | |
} | |
function createRefLines(g) { | |
var yRefLine = g.select('path.' + Y_AXIS_REF_LINE_CLASS).empty() ? | |
g.append('path').attr('class', Y_AXIS_REF_LINE_CLASS) : g.select('path.' + Y_AXIS_REF_LINE_CLASS); | |
yRefLine.style('display', 'none').attr('stroke-dasharray', '5,5'); | |
var xRefLine = g.select('path.' + X_AXIS_REF_LINE_CLASS).empty() ? | |
g.append('path').attr('class', X_AXIS_REF_LINE_CLASS) : g.select('path.' + X_AXIS_REF_LINE_CLASS); | |
xRefLine.style('display', 'none').attr('stroke-dasharray', '5,5'); | |
} | |
function showDot(dot) { | |
dot.style('fill-opacity', 0.8); | |
dot.style('stroke-opacity', 0.8); | |
dot.attr('r', _dotRadius); | |
return dot; | |
} | |
function showRefLines(dot, g) { | |
var x = dot.attr('cx'); | |
var y = dot.attr('cy'); | |
var yAxisX = (_chart._yAxisX() - _chart.margins().left); | |
var yAxisRefPathD = 'M' + yAxisX + ' ' + y + 'L' + (x) + ' ' + (y); | |
var xAxisRefPathD = 'M' + x + ' ' + _chart.yAxisHeight() + 'L' + x + ' ' + y; | |
g.select('path.' + Y_AXIS_REF_LINE_CLASS).style('display', '').attr('d', yAxisRefPathD); | |
g.select('path.' + X_AXIS_REF_LINE_CLASS).style('display', '').attr('d', xAxisRefPathD); | |
} | |
function getDotRadius() { | |
return _dataPointRadius || _dotRadius; | |
} | |
function hideDot(dot) { | |
dot.style('fill-opacity', _dataPointFillOpacity) | |
.style('stroke-opacity', _dataPointStrokeOpacity) | |
.attr('r', getDotRadius()); | |
} | |
function hideRefLines(g) { | |
g.select('path.' + Y_AXIS_REF_LINE_CLASS).style('display', 'none'); | |
g.select('path.' + X_AXIS_REF_LINE_CLASS).style('display', 'none'); | |
} | |
function renderTitle(dot, d) { | |
if (_chart.renderTitle()) { | |
dot.selectAll('title').remove(); | |
dot.append('title').text(dc.pluck('data', _chart.title(d.name))); | |
} | |
} | |
/** | |
#### .dotRadius([dotRadius]) | |
Get or set the radius (in px) for dots displayed on the data points. Default dot radius is 5. | |
**/ | |
_chart.dotRadius = function (_) { | |
if (!arguments.length) { | |
return _dotRadius; | |
} | |
_dotRadius = _; | |
return _chart; | |
}; | |
/** | |
#### .renderDataPoints([options]) | |
Always show individual dots for each datapoint. | |
Options, if given, is an object that can contain the following: | |
* fillOpacity (default 0.8) | |
* strokeOpacity (default 0.8) | |
* radius (default 2) | |
If `options` is falsy, it disables data point rendering. | |
If no `options` are provided, the current `options` values are instead returned. | |
Example: | |
``` | |
chart.renderDataPoints({radius: 2, fillOpacity: 0.8, strokeOpacity: 0.8}) | |
``` | |
**/ | |
_chart.renderDataPoints = function (options) { | |
if (!arguments.length) { | |
return { | |
fillOpacity: _dataPointFillOpacity, | |
strokeOpacity: _dataPointStrokeOpacity, | |
radius: _dataPointRadius | |
}; | |
} else if (!options) { | |
_dataPointFillOpacity = DEFAULT_DOT_OPACITY; | |
_dataPointStrokeOpacity = DEFAULT_DOT_OPACITY; | |
_dataPointRadius = null; | |
} else { | |
_dataPointFillOpacity = options.fillOpacity || 0.8; | |
_dataPointStrokeOpacity = options.strokeOpacity || 0.8; | |
_dataPointRadius = options.radius || 2; | |
} | |
return _chart; | |
}; | |
function colorFilter(color, dashstyle, inv) { | |
return function () { | |
var item = d3.select(this); | |
var match = (item.attr('stroke') === color && | |
item.attr('stroke-dasharray') === ((dashstyle instanceof Array) ? | |
dashstyle.join(',') : null)) || item.attr('fill') === color; | |
return inv ? !match : match; | |
}; | |
} | |
_chart.legendHighlight = function (d) { | |
if (!_chart.isLegendableHidden(d)) { | |
_chart.g().selectAll('path.line, path.area') | |
.classed('highlight', colorFilter(d.color, d.dashstyle)) | |
.classed('fadeout', colorFilter(d.color, d.dashstyle, true)); | |
} | |
}; | |
_chart.legendReset = function () { | |
_chart.g().selectAll('path.line, path.area') | |
.classed('highlight', false) | |
.classed('fadeout', false); | |
}; | |
dc.override(_chart, 'legendables', function () { | |
var legendables = _chart._legendables(); | |
if (!_dashStyle) { | |
return legendables; | |
} | |
return legendables.map(function (l) { | |
l.dashstyle = _dashStyle; | |
return l; | |
}); | |
}); | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Data Count Widget | |
Includes: [Base Mixin](#base-mixin) | |
The data count widget is a simple widget designed to display the number of records selected by the | |
current filters out of the total number of records in the data set. Once created the data count widget | |
will automatically update the text content of the following elements under the parent element. | |
* '.total-count' - total number of records | |
* '.filter-count' - number of records matched by the current filters | |
Examples: | |
* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) | |
#### dc.dataCount(parent[, chartGroup]) | |
Create a data count widget and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this widget should be placed in. | |
The data count widget will only react to filter changes in the chart group. | |
Returns: | |
A newly created data count widget instance | |
#### .dimension(allData) - **mandatory** | |
For the data count widget the only valid dimension is the entire data set. | |
#### .group(groupAll) - **mandatory** | |
For the data count widget the only valid group is the group returned by `dimension.groupAll()`. | |
```js | |
var ndx = crossfilter(data); | |
var all = ndx.groupAll(); | |
dc.dataCount('.dc-data-count') | |
.dimension(ndx) | |
.group(all); | |
``` | |
**/ | |
dc.dataCount = function (parent, chartGroup) { | |
var _formatNumber = d3.format(',d'); | |
var _chart = dc.baseMixin({}); | |
var _html = {some:'', all:''}; | |
/** | |
#### html([object]) | |
Gets or sets an optional object specifying HTML templates to use depending how many items are | |
selected. The text `%total-count` will replaced with the total number of records, and the text | |
`%filter-count` will be replaced with the number of selected records. | |
- all: HTML template to use if all items are selected | |
- some: HTML template to use if not all items are selected | |
```js | |
counter.html({ | |
some: '%filter-count out of %total-count records selected', | |
all: 'All records selected. Click on charts to apply filters' | |
}) | |
``` | |
**/ | |
_chart.html = function (s) { | |
if (!arguments.length) { | |
return _html; | |
} | |
if (s.all) { | |
_html.all = s.all; | |
} | |
if (s.some) { | |
_html.some = s.some; | |
} | |
return _chart; | |
}; | |
/** | |
#### formatNumber([formatter]) | |
Gets or sets an optional function to format the filter count and total count. | |
```js | |
counter.formatNumber(d3.format('.2g')) | |
``` | |
**/ | |
_chart.formatNumber = function (s) { | |
if (!arguments.length) { | |
return _formatNumber; | |
} | |
_formatNumber = s; | |
return _chart; | |
}; | |
_chart._doRender = function () { | |
var tot = _chart.dimension().size(), | |
val = _chart.group().value(); | |
var all = _formatNumber(tot); | |
var selected = _formatNumber(val); | |
if ((tot === val) && (_html.all !== '')) { | |
_chart.root().html(_html.all.replace('%total-count', all).replace('%filter-count', selected)); | |
} else if (_html.some !== '') { | |
_chart.root().html(_html.some.replace('%total-count', all).replace('%filter-count', selected)); | |
} else { | |
_chart.selectAll('.total-count').text(all); | |
_chart.selectAll('.filter-count').text(selected); | |
} | |
return _chart; | |
}; | |
_chart._doRedraw = function () { | |
return _chart._doRender(); | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Data Table Widget | |
Includes: [Base Mixin](#base-mixin) | |
The data table is a simple widget designed to list crossfilter focused data set (rows being | |
filtered) in a good old tabular fashion. | |
Examples: | |
* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) | |
#### dc.dataTable(parent[, chartGroup]) | |
Create a data table widget instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created data table widget instance | |
**/ | |
dc.dataTable = function (parent, chartGroup) { | |
var LABEL_CSS_CLASS = 'dc-table-label'; | |
var ROW_CSS_CLASS = 'dc-table-row'; | |
var COLUMN_CSS_CLASS = 'dc-table-column'; | |
var GROUP_CSS_CLASS = 'dc-table-group'; | |
var HEAD_CSS_CLASS = 'dc-table-head'; | |
var _chart = dc.baseMixin({}); | |
var _size = 25; | |
var _columns = []; | |
var _sortBy = function (d) { | |
return d; | |
}; | |
var _order = d3.ascending; | |
_chart._doRender = function () { | |
_chart.selectAll('tbody').remove(); | |
renderRows(renderGroups()); | |
return _chart; | |
}; | |
_chart._doColumnValueFormat = function (v, d) { | |
return ((typeof v === 'function') ? | |
v(d) : // v as function | |
((typeof v === 'string') ? | |
d[v] : // v is field name string | |
v.format(d) // v is Object, use fn (element 2) | |
) | |
); | |
}; | |
_chart._doColumnHeaderFormat = function (d) { | |
// if 'function', convert to string representation | |
// show a string capitalized | |
// if an object then display it's label string as-is. | |
return (typeof d === 'function') ? | |
_chart._doColumnHeaderFnToString(d) : | |
((typeof d === 'string') ? | |
_chart._doColumnHeaderCapitalize(d) : String(d.label)); | |
}; | |
_chart._doColumnHeaderCapitalize = function (s) { | |
// capitalize | |
return s.charAt(0).toUpperCase() + s.slice(1); | |
}; | |
_chart._doColumnHeaderFnToString = function (f) { | |
// columnString(f) { | |
var s = String(f); | |
var i1 = s.indexOf('return '); | |
if (i1 >= 0) { | |
var i2 = s.lastIndexOf(';'); | |
if (i2 >= 0) { | |
s = s.substring(i1 + 7, i2); | |
var i3 = s.indexOf('numberFormat'); | |
if (i3 >= 0) { | |
s = s.replace('numberFormat', ''); | |
} | |
} | |
} | |
return s; | |
}; | |
function renderGroups() { | |
// The 'original' example uses all 'functions'. | |
// If all 'functions' are used, then don't remove/add a header, and leave | |
// the html alone. This preserves the functionality of earlier releases. | |
// A 2nd option is a string representing a field in the data. | |
// A third option is to supply an Object such as an array of 'information', and | |
// supply your own _doColumnHeaderFormat and _doColumnValueFormat functions to | |
// create what you need. | |
var bAllFunctions = true; | |
_columns.forEach(function (f) { | |
bAllFunctions = bAllFunctions & (typeof f === 'function'); | |
}); | |
if (!bAllFunctions) { | |
_chart.selectAll('th').remove(); | |
var headcols = _chart.root().selectAll('th') | |
.data(_columns); | |
var headGroup = headcols | |
.enter() | |
.append('th'); | |
headGroup | |
.attr('class', HEAD_CSS_CLASS) | |
.html(function (d) { | |
return (_chart._doColumnHeaderFormat(d)); | |
}); | |
} | |
var groups = _chart.root().selectAll('tbody') | |
.data(nestEntries(), function (d) { | |
return _chart.keyAccessor()(d); | |
}); | |
var rowGroup = groups | |
.enter() | |
.append('tbody'); | |
rowGroup | |
.append('tr') | |
.attr('class', GROUP_CSS_CLASS) | |
.append('td') | |
.attr('class', LABEL_CSS_CLASS) | |
.attr('colspan', _columns.length) | |
.html(function (d) { | |
return _chart.keyAccessor()(d); | |
}); | |
groups.exit().remove(); | |
return rowGroup; | |
} | |
function nestEntries() { | |
var entries; | |
if (_order === d3.ascending) { | |
entries = _chart.dimension().bottom(_size); | |
} else { | |
entries = _chart.dimension().top(_size); | |
} | |
return d3.nest() | |
.key(_chart.group()) | |
.sortKeys(_order) | |
.entries(entries.sort(function (a, b) { | |
return _order(_sortBy(a), _sortBy(b)); | |
})); | |
} | |
function renderRows(groups) { | |
var rows = groups.order() | |
.selectAll('tr.' + ROW_CSS_CLASS) | |
.data(function (d) { | |
return d.values; | |
}); | |
var rowEnter = rows.enter() | |
.append('tr') | |
.attr('class', ROW_CSS_CLASS); | |
_columns.forEach(function (v, i) { | |
rowEnter.append('td') | |
.attr('class', COLUMN_CSS_CLASS + ' _' + i) | |
.html(function (d) { | |
return _chart._doColumnValueFormat(v, d); | |
}); | |
}); | |
rows.exit().remove(); | |
return rows; | |
} | |
_chart._doRedraw = function () { | |
return _chart._doRender(); | |
}; | |
/** | |
#### .size([size]) | |
Get or set the table size which determines the number of rows displayed by the widget. | |
**/ | |
_chart.size = function (s) { | |
if (!arguments.length) { | |
return _size; | |
} | |
_size = s; | |
return _chart; | |
}; | |
/** | |
#### .columns([columnFunctionArray]) | |
Get or set column functions. The data table widget now supports several methods of specifying | |
the columns to display. The original method, first shown below, uses an array of functions to | |
generate dynamic columns. Column functions are simple javascript functions with only one input | |
argument `d` which represents a row in the data set. The return value of these functions will be | |
used directly to generate table content for each cell. However, this method requires the .html | |
table entry to have a fixed set of column headers. | |
```js | |
chart.columns([ | |
function(d) { | |
return d.date; | |
}, | |
function(d) { | |
return d.open; | |
}, | |
function(d) { | |
return d.close; | |
}, | |
function(d) { | |
return numberFormat(d.close - d.open); | |
}, | |
function(d) { | |
return d.volume; | |
} | |
]); | |
``` | |
The next example shows you can simply list the data (d) content directly without | |
specifying it as a function, except where necessary (ie, computed columns). Note | |
the data element accessor name is capitalized when displayed in the table. You can | |
also mix in functions as desired or necessary, but you must use the | |
Object = [Label, Fn] method as shown below. | |
You may wish to override the following two functions, which are internally used to | |
translate the column information or function into a displayed header. The first one | |
is used on the simple "string" column specifier, the second is used to transform the | |
String(fn) into something displayable. For the Stock example, the function for Change | |
becomes a header of 'd.close - d.open'. | |
_chart._doColumnHeaderCapitalize _chart._doColumnHeaderFnToString | |
You may use your own Object definition, however you must then override | |
_chart._doColumnHeaderFormat , _chart._doColumnValueFormat | |
Be aware that fields without numberFormat specification will be displayed just as | |
they are stored in the data, unformatted. | |
```js | |
chart.columns([ | |
"date", // d["date"], ie, a field accessor; capitalized automatically | |
"open", // ... | |
"close", // ... | |
["Change", // Specify an Object = [Label, Fn] | |
function (d) { | |
return numberFormat(d.close - d.open); | |
}], | |
"volume" // d["volume"], ie, a field accessor; capitalized automatically | |
]); | |
``` | |
A third example, where all fields are specified using the Object = [Label, Fn] method. | |
```js | |
chart.columns([ | |
["Date", // Specify an Object = [Label, Fn] | |
function (d) { | |
return d.date; | |
}], | |
["Open", | |
function (d) { | |
return numberFormat(d.open); | |
}], | |
["Close", | |
function (d) { | |
return numberFormat(d.close); | |
}], | |
["Change", | |
function (d) { | |
return numberFormat(d.close - d.open); | |
}], | |
["Volume", | |
function (d) { | |
return d.volume; | |
}] | |
]); | |
``` | |
**/ | |
_chart.columns = function (_) { | |
if (!arguments.length) { | |
return _columns; | |
} | |
_columns = _; | |
return _chart; | |
}; | |
/** | |
#### .sortBy([sortByFunction]) | |
Get or set sort-by function. This function works as a value accessor at row level and returns a | |
particular field to be sorted by. Default value: identity function | |
```js | |
chart.sortBy(function(d) { | |
return d.date; | |
}); | |
``` | |
**/ | |
_chart.sortBy = function (_) { | |
if (!arguments.length) { | |
return _sortBy; | |
} | |
_sortBy = _; | |
return _chart; | |
}; | |
/** | |
#### .order([order]) | |
Get or set sort order. Default value: ``` d3.ascending ``` | |
```js | |
chart.order(d3.descending); | |
``` | |
**/ | |
_chart.order = function (_) { | |
if (!arguments.length) { | |
return _order; | |
} | |
_order = _; | |
return _chart; | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Data Grid Widget | |
Includes: [Base Mixin](#base-mixin) | |
Data grid is a simple widget designed to list the filtered records, providing | |
a simple way to define how the items are displayed. | |
Examples: | |
* [List of members of the european parliament](http://europarl.me/dc.js/web/ep/index.html) | |
#### dc.dataGrid(parent[, chartGroup]) | |
Create a data grid widget instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created data grid widget instance | |
**/ | |
dc.dataGrid = function (parent, chartGroup) { | |
var LABEL_CSS_CLASS = 'dc-grid-label'; | |
var ITEM_CSS_CLASS = 'dc-grid-item'; | |
var GROUP_CSS_CLASS = 'dc-grid-group'; | |
var GRID_CSS_CLASS = 'dc-grid-top'; | |
var _chart = dc.baseMixin({}); | |
var _size = 999; // shouldn't be needed, but you might | |
var _html = function (d) { return 'you need to provide an html() handling param: ' + JSON.stringify(d); }; | |
var _sortBy = function (d) { | |
return d; | |
}; | |
var _order = d3.ascending; | |
var _htmlGroup = function (d) { | |
return '<div class=\'' + GROUP_CSS_CLASS + '\'><h1 class=\'' + LABEL_CSS_CLASS + '\'>' + | |
_chart.keyAccessor()(d) + '</h1></div>'; | |
}; | |
_chart._doRender = function () { | |
_chart.selectAll('div.' + GRID_CSS_CLASS).remove(); | |
renderItems(renderGroups()); | |
return _chart; | |
}; | |
function renderGroups() { | |
var groups = _chart.root().selectAll('div.' + GRID_CSS_CLASS) | |
.data(nestEntries(), function (d) { | |
return _chart.keyAccessor()(d); | |
}); | |
var itemGroup = groups | |
.enter() | |
.append('div') | |
.attr('class', GRID_CSS_CLASS); | |
if (_htmlGroup) { | |
itemGroup | |
.html(function (d) { | |
return _htmlGroup(d); | |
}); | |
} | |
groups.exit().remove(); | |
return itemGroup; | |
} | |
function nestEntries() { | |
var entries = _chart.dimension().top(_size); | |
return d3.nest() | |
.key(_chart.group()) | |
.sortKeys(_order) | |
.entries(entries.sort(function (a, b) { | |
return _order(_sortBy(a), _sortBy(b)); | |
})); | |
} | |
function renderItems(groups) { | |
var items = groups.order() | |
.selectAll('div.' + ITEM_CSS_CLASS) | |
.data(function (d) { | |
return d.values; | |
}); | |
items.enter() | |
.append('div') | |
.attr('class', ITEM_CSS_CLASS) | |
.html(function (d) { | |
return _html(d); | |
}); | |
items.exit().remove(); | |
return items; | |
} | |
_chart._doRedraw = function () { | |
return _chart._doRender(); | |
}; | |
/** | |
#### .size([size]) | |
Get or set the grid size which determines the number of items displayed by the widget. | |
**/ | |
_chart.size = function (s) { | |
if (!arguments.length) { | |
return _size; | |
} | |
_size = s; | |
return _chart; | |
}; | |
/** | |
#### .html( function (data) { return '<html>'; }) | |
Get or set the function that formats an item. The data grid widget uses a | |
function to generate dynamic html. Use your favourite templating engine or | |
generate the string directly. | |
```js | |
chart.html(function (d) { return '<div class='item '+data.exampleCategory+''>'+data.exampleString+'</div>';}); | |
``` | |
**/ | |
_chart.html = function (_) { | |
if (!arguments.length) { | |
return _html; | |
} | |
_html = _; | |
return _chart; | |
}; | |
/** | |
#### .htmlGroup( function (data) { return '<html>'; }) | |
Get or set the function that formats a group label. | |
```js | |
chart.htmlGroup (function (d) { return '<h2>'.d.key . 'with ' . d.values.length .' items</h2>'}); | |
``` | |
**/ | |
_chart.htmlGroup = function (_) { | |
if (!arguments.length) { | |
return _htmlGroup; | |
} | |
_htmlGroup = _; | |
return _chart; | |
}; | |
/** | |
#### .sortBy([sortByFunction]) | |
Get or set sort-by function. This function works as a value accessor at the item | |
level and returns a particular field to be sorted. | |
by. Default: identity function | |
```js | |
chart.sortBy(function(d) { | |
return d.date; | |
}); | |
``` | |
**/ | |
_chart.sortBy = function (_) { | |
if (!arguments.length) { | |
return _sortBy; | |
} | |
_sortBy = _; | |
return _chart; | |
}; | |
/** | |
#### .order([order]) | |
Get or set sort order function. Default value: ``` d3.ascending ``` | |
```js | |
chart.order(d3.descending); | |
``` | |
**/ | |
_chart.order = function (_) { | |
if (!arguments.length) { | |
return _order; | |
} | |
_order = _; | |
return _chart; | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Bubble Chart | |
Includes: [Bubble Mixin](#bubble-mixin), [Coordinate Grid Mixin](#coordinate-grid-mixin) | |
A concrete implementation of a general purpose bubble chart that allows data visualization using the | |
following dimensions: | |
* x axis position | |
* y axis position | |
* bubble radius | |
* color | |
Examples: | |
* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) | |
* [US Venture Capital Landscape 2011](http://dc-js.github.com/dc.js/vc/index.html) | |
#### dc.bubbleChart(parent[, chartGroup]) | |
Create a bubble chart instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection | compositeChart - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created bubble chart instance | |
```js | |
// create a bubble chart under #chart-container1 element using the default global chart group | |
var bubbleChart1 = dc.bubbleChart('#chart-container1'); | |
// create a bubble chart under #chart-container2 element using chart group A | |
var bubbleChart2 = dc.bubbleChart('#chart-container2', 'chartGroupA'); | |
``` | |
**/ | |
dc.bubbleChart = function (parent, chartGroup) { | |
var _chart = dc.bubbleMixin(dc.coordinateGridMixin({})); | |
var _elasticRadius = false; | |
_chart.transitionDuration(750); | |
var bubbleLocator = function (d) { | |
return 'translate(' + (bubbleX(d)) + ',' + (bubbleY(d)) + ')'; | |
}; | |
/** | |
#### .elasticRadius([boolean]) | |
Turn on or off the elastic bubble radius feature, or return the value of the flag. If this | |
feature is turned on, then bubble radii will be automatically rescaled to fit the chart better. | |
**/ | |
_chart.elasticRadius = function (_) { | |
if (!arguments.length) { | |
return _elasticRadius; | |
} | |
_elasticRadius = _; | |
return _chart; | |
}; | |
_chart.plotData = function () { | |
if (_elasticRadius) { | |
_chart.r().domain([_chart.rMin(), _chart.rMax()]); | |
} | |
_chart.r().range([_chart.MIN_RADIUS, _chart.xAxisLength() * _chart.maxBubbleRelativeSize()]); | |
var bubbleG = _chart.chartBodyG().selectAll('g.' + _chart.BUBBLE_NODE_CLASS) | |
.data(_chart.data(), function (d) { return d.key; }); | |
renderNodes(bubbleG); | |
updateNodes(bubbleG); | |
removeNodes(bubbleG); | |
_chart.fadeDeselectedArea(); | |
}; | |
function renderNodes(bubbleG) { | |
var bubbleGEnter = bubbleG.enter().append('g'); | |
bubbleGEnter | |
.attr('class', _chart.BUBBLE_NODE_CLASS) | |
.attr('transform', bubbleLocator) | |
.append('circle').attr('class', function (d, i) { | |
return _chart.BUBBLE_CLASS + ' _' + i; | |
}) | |
.on('click', _chart.onClick) | |
.attr('fill', _chart.getColor) | |
.attr('r', 0); | |
dc.transition(bubbleG, _chart.transitionDuration()) | |
.selectAll('circle.' + _chart.BUBBLE_CLASS) | |
.attr('r', function (d) { | |
return _chart.bubbleR(d); | |
}) | |
.attr('opacity', function (d) { | |
return (_chart.bubbleR(d) > 0) ? 1 : 0; | |
}); | |
_chart._doRenderLabel(bubbleGEnter); | |
_chart._doRenderTitles(bubbleGEnter); | |
} | |
function updateNodes(bubbleG) { | |
dc.transition(bubbleG, _chart.transitionDuration()) | |
.attr('transform', bubbleLocator) | |
.selectAll('circle.' + _chart.BUBBLE_CLASS) | |
.attr('fill', _chart.getColor) | |
.attr('r', function (d) { | |
return _chart.bubbleR(d); | |
}) | |
.attr('opacity', function (d) { | |
return (_chart.bubbleR(d) > 0) ? 1 : 0; | |
}); | |
_chart.doUpdateLabels(bubbleG); | |
_chart.doUpdateTitles(bubbleG); | |
} | |
function removeNodes(bubbleG) { | |
bubbleG.exit().remove(); | |
} | |
function bubbleX(d) { | |
var x = _chart.x()(_chart.keyAccessor()(d)); | |
if (isNaN(x)) { | |
x = 0; | |
} | |
return x; | |
} | |
function bubbleY(d) { | |
var y = _chart.y()(_chart.valueAccessor()(d)); | |
if (isNaN(y)) { | |
y = 0; | |
} | |
return y; | |
} | |
_chart.renderBrush = function () { | |
// override default x axis brush from parent chart | |
}; | |
_chart.redrawBrush = function () { | |
// override default x axis brush from parent chart | |
_chart.fadeDeselectedArea(); | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Composite Chart | |
Includes: [Coordinate Grid Mixin](#coordinate-grid-mixin) | |
Composite charts are a special kind of chart that render multiple charts on the same Coordinate | |
Grid. You can overlay (compose) different bar/line/area charts in a single composite chart to | |
achieve some quite flexible charting effects. | |
#### dc.compositeChart(parent[, chartGroup]) | |
Create a composite chart instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created composite chart instance | |
```js | |
// create a composite chart under #chart-container1 element using the default global chart group | |
var compositeChart1 = dc.compositeChart('#chart-container1'); | |
// create a composite chart under #chart-container2 element using chart group A | |
var compositeChart2 = dc.compositeChart('#chart-container2', 'chartGroupA'); | |
``` | |
**/ | |
dc.compositeChart = function (parent, chartGroup) { | |
var SUB_CHART_CLASS = 'sub'; | |
var DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING = 12; | |
var _chart = dc.coordinateGridMixin({}); | |
var _children = []; | |
var _childOptions = {}; | |
var _shareColors = false, | |
_shareTitle = true; | |
var _rightYAxis = d3.svg.axis(), | |
_rightYAxisLabel = 0, | |
_rightYAxisLabelPadding = DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING, | |
_rightY, | |
_rightAxisGridLines = false; | |
_chart._mandatoryAttributes([]); | |
_chart.transitionDuration(500); | |
dc.override(_chart, '_generateG', function () { | |
var g = this.__generateG(); | |
for (var i = 0; i < _children.length; ++i) { | |
var child = _children[i]; | |
generateChildG(child, i); | |
if (!child.dimension()) { | |
child.dimension(_chart.dimension()); | |
} | |
if (!child.group()) { | |
child.group(_chart.group()); | |
} | |
child.chartGroup(_chart.chartGroup()); | |
child.svg(_chart.svg()); | |
child.xUnits(_chart.xUnits()); | |
child.transitionDuration(_chart.transitionDuration()); | |
child.brushOn(_chart.brushOn()); | |
child.renderTitle(_chart.renderTitle()); | |
} | |
return g; | |
}); | |
_chart._brushing = function () { | |
var extent = _chart.extendBrush(); | |
var brushIsEmpty = _chart.brushIsEmpty(extent); | |
for (var i = 0; i < _children.length; ++i) { | |
_children[i].filter(null); | |
if (!brushIsEmpty) { | |
_children[i].filter(extent); | |
} | |
} | |
}; | |
_chart._prepareYAxis = function () { | |
if (leftYAxisChildren().length !== 0) { prepareLeftYAxis(); } | |
if (rightYAxisChildren().length !== 0) { prepareRightYAxis(); } | |
if (leftYAxisChildren().length > 0 && !_rightAxisGridLines) { | |
_chart._renderHorizontalGridLinesForAxis(_chart.g(), _chart.y(), _chart.yAxis()); | |
} | |
else if (rightYAxisChildren().length > 0) { | |
_chart._renderHorizontalGridLinesForAxis(_chart.g(), _rightY, _rightYAxis); | |
} | |
}; | |
_chart.renderYAxis = function () { | |
if (leftYAxisChildren().length !== 0) { | |
_chart.renderYAxisAt('y', _chart.yAxis(), _chart.margins().left); | |
_chart.renderYAxisLabel('y', _chart.yAxisLabel(), -90); | |
} | |
if (rightYAxisChildren().length !== 0) { | |
_chart.renderYAxisAt('yr', _chart.rightYAxis(), _chart.width() - _chart.margins().right); | |
_chart.renderYAxisLabel('yr', _chart.rightYAxisLabel(), 90, _chart.width() - _rightYAxisLabelPadding); | |
} | |
}; | |
function prepareRightYAxis() { | |
if (_chart.rightY() === undefined || _chart.elasticY()) { | |
_chart.rightY(d3.scale.linear()); | |
_chart.rightY().domain([rightYAxisMin(), rightYAxisMax()]).rangeRound([_chart.yAxisHeight(), 0]); | |
} | |
_chart.rightY().range([_chart.yAxisHeight(), 0]); | |
_chart.rightYAxis(_chart.rightYAxis().scale(_chart.rightY())); | |
_chart.rightYAxis().orient('right'); | |
} | |
function prepareLeftYAxis() { | |
if (_chart.y() === undefined || _chart.elasticY()) { | |
_chart.y(d3.scale.linear()); | |
_chart.y().domain([yAxisMin(), yAxisMax()]).rangeRound([_chart.yAxisHeight(), 0]); | |
} | |
_chart.y().range([_chart.yAxisHeight(), 0]); | |
_chart.yAxis(_chart.yAxis().scale(_chart.y())); | |
_chart.yAxis().orient('left'); | |
} | |
function generateChildG(child, i) { | |
child._generateG(_chart.g()); | |
child.g().attr('class', SUB_CHART_CLASS + ' _' + i); | |
} | |
_chart.plotData = function () { | |
for (var i = 0; i < _children.length; ++i) { | |
var child = _children[i]; | |
if (!child.g()) { | |
generateChildG(child, i); | |
} | |
if (_shareColors) { | |
child.colors(_chart.colors()); | |
} | |
child.x(_chart.x()); | |
child.xAxis(_chart.xAxis()); | |
if (child.useRightYAxis()) { | |
child.y(_chart.rightY()); | |
child.yAxis(_chart.rightYAxis()); | |
} | |
else { | |
child.y(_chart.y()); | |
child.yAxis(_chart.yAxis()); | |
} | |
child.plotData(); | |
child._activateRenderlets(); | |
} | |
}; | |
/** | |
#### .useRightAxisGridLines(bool) | |
Get or set whether to draw gridlines from the right y axis. Drawing from the left y axis is the | |
default behavior. This option is only respected when subcharts with both left and right y-axes | |
are present. | |
**/ | |
_chart.useRightAxisGridLines = function (_) { | |
if (!arguments) { | |
return _rightAxisGridLines; | |
} | |
_rightAxisGridLines = _; | |
return _chart; | |
}; | |
/** | |
#### .childOptions({object}) | |
Get or set chart-specific options for all child charts. This is equivalent to calling `.options` | |
on each child chart. | |
**/ | |
_chart.childOptions = function (_) { | |
if (!arguments.length) { | |
return _childOptions; | |
} | |
_childOptions = _; | |
_children.forEach(function (child) { | |
child.options(_childOptions); | |
}); | |
return _chart; | |
}; | |
_chart.fadeDeselectedArea = function () { | |
for (var i = 0; i < _children.length; ++i) { | |
var child = _children[i]; | |
child.brush(_chart.brush()); | |
child.fadeDeselectedArea(); | |
} | |
}; | |
/** | |
#### .rightYAxisLabel([labelText]) | |
Set or get the right y axis label. | |
**/ | |
_chart.rightYAxisLabel = function (_, padding) { | |
if (!arguments.length) { | |
return _rightYAxisLabel; | |
} | |
_rightYAxisLabel = _; | |
_chart.margins().right -= _rightYAxisLabelPadding; | |
_rightYAxisLabelPadding = (padding === undefined) ? DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING : padding; | |
_chart.margins().right += _rightYAxisLabelPadding; | |
return _chart; | |
}; | |
/** | |
#### .compose(subChartArray) | |
Combine the given charts into one single composite coordinate grid chart. | |
```js | |
// compose the given charts in the array into one single composite chart | |
moveChart.compose([ | |
// when creating sub-chart you need to pass in the parent chart | |
dc.lineChart(moveChart) | |
.group(indexAvgByMonthGroup) // if group is missing then parent's group will be used | |
.valueAccessor(function (d){return d.value.avg;}) | |
// most of the normal functions will continue to work in a composed chart | |
.renderArea(true) | |
.stack(monthlyMoveGroup, function (d){return d.value;}) | |
.title(function (d){ | |
var value = d.value.avg?d.value.avg:d.value; | |
if(isNaN(value)) value = 0; | |
return dateFormat(d.key) + '\n' + numberFormat(value); | |
}), | |
dc.barChart(moveChart) | |
.group(volumeByMonthGroup) | |
.centerBar(true) | |
]); | |
``` | |
**/ | |
_chart.compose = function (charts) { | |
_children = charts; | |
_children.forEach(function (child) { | |
child.height(_chart.height()); | |
child.width(_chart.width()); | |
child.margins(_chart.margins()); | |
if (_shareTitle) { | |
child.title(_chart.title()); | |
} | |
child.options(_childOptions); | |
}); | |
return _chart; | |
}; | |
/** | |
#### .children() | |
Returns the child charts which are composed into the composite chart. | |
**/ | |
_chart.children = function () { | |
return _children; | |
}; | |
/** | |
#### .shareColors([boolean]) | |
Get or set color sharing for the chart. If set, the `.colors()` value from this chart | |
will be shared with composed children. Additionally if the child chart implements | |
Stackable and has not set a custom .colorAccessor, then it will generate a color | |
specific to its order in the composition. | |
**/ | |
_chart.shareColors = function (_) { | |
if (!arguments.length) { | |
return _shareColors; | |
} | |
_shareColors = _; | |
return _chart; | |
}; | |
/** | |
#### .shareTitle([[boolean]) | |
Get or set title sharing for the chart. If set, the `.title()` value from this chart will be | |
shared with composed children. Default value is true. | |
**/ | |
_chart.shareTitle = function (_) { | |
if (!arguments.length) { | |
return _shareTitle; | |
} | |
_shareTitle = _; | |
return _chart; | |
}; | |
/** | |
#### .rightY([yScale]) | |
Get or set the y scale for the right axis. The right y scale is typically automatically | |
generated by the chart implementation. | |
**/ | |
_chart.rightY = function (_) { | |
if (!arguments.length) { | |
return _rightY; | |
} | |
_rightY = _; | |
return _chart; | |
}; | |
function leftYAxisChildren() { | |
return _children.filter(function (child) { | |
return !child.useRightYAxis(); | |
}); | |
} | |
function rightYAxisChildren() { | |
return _children.filter(function (child) { | |
return child.useRightYAxis(); | |
}); | |
} | |
function getYAxisMin(charts) { | |
return charts.map(function (c) { | |
return c.yAxisMin(); | |
}); | |
} | |
delete _chart.yAxisMin; | |
function yAxisMin() { | |
return d3.min(getYAxisMin(leftYAxisChildren())); | |
} | |
function rightYAxisMin() { | |
return d3.min(getYAxisMin(rightYAxisChildren())); | |
} | |
function getYAxisMax(charts) { | |
return charts.map(function (c) { | |
return c.yAxisMax(); | |
}); | |
} | |
delete _chart.yAxisMax; | |
function yAxisMax() { | |
return dc.utils.add(d3.max(getYAxisMax(leftYAxisChildren())), _chart.yAxisPadding()); | |
} | |
function rightYAxisMax() { | |
return dc.utils.add(d3.max(getYAxisMax(rightYAxisChildren())), _chart.yAxisPadding()); | |
} | |
function getAllXAxisMinFromChildCharts() { | |
return _children.map(function (c) { | |
return c.xAxisMin(); | |
}); | |
} | |
dc.override(_chart, 'xAxisMin', function () { | |
return dc.utils.subtract(d3.min(getAllXAxisMinFromChildCharts()), _chart.xAxisPadding()); | |
}); | |
function getAllXAxisMaxFromChildCharts() { | |
return _children.map(function (c) { | |
return c.xAxisMax(); | |
}); | |
} | |
dc.override(_chart, 'xAxisMax', function () { | |
return dc.utils.add(d3.max(getAllXAxisMaxFromChildCharts()), _chart.xAxisPadding()); | |
}); | |
_chart.legendables = function () { | |
return _children.reduce(function (items, child) { | |
if (_shareColors) { | |
child.colors(_chart.colors()); | |
} | |
items.push.apply(items, child.legendables()); | |
return items; | |
}, []); | |
}; | |
_chart.legendHighlight = function (d) { | |
for (var j = 0; j < _children.length; ++j) { | |
var child = _children[j]; | |
child.legendHighlight(d); | |
} | |
}; | |
_chart.legendReset = function (d) { | |
for (var j = 0; j < _children.length; ++j) { | |
var child = _children[j]; | |
child.legendReset(d); | |
} | |
}; | |
_chart.legendToggle = function () { | |
console.log('composite should not be getting legendToggle itself'); | |
}; | |
/** | |
#### .rightYAxis([yAxis]) | |
Set or get the right y axis used by the composite chart. This function is most useful when y | |
axis customization is required. The y axis in dc.js is an instance of a [d3 axis | |
object](https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-_axis) therefore it supports any valid | |
d3 axis manipulation. **Caution**: The y axis is usually generated internally by dc; | |
resetting it may cause unexpected results. | |
```jså | |
// customize y axis tick format | |
chart.rightYAxis().tickFormat(function (v) {return v + '%';}); | |
// customize y axis tick values | |
chart.rightYAxis().tickValues([0, 100, 200, 300]); | |
``` | |
**/ | |
_chart.rightYAxis = function (rightYAxis) { | |
if (!arguments.length) { | |
return _rightYAxis; | |
} | |
_rightYAxis = rightYAxis; | |
return _chart; | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Series Chart | |
Includes: [Composite Chart](#composite chart) | |
A series chart is a chart that shows multiple series of data overlaid on one chart, where the | |
series is specified in the data. It is a specialization of Composite Chart and inherits all | |
composite features other than recomposing the chart. | |
#### dc.seriesChart(parent[, chartGroup]) | |
Create a series chart instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created series chart instance | |
```js | |
// create a series chart under #chart-container1 element using the default global chart group | |
var seriesChart1 = dc.seriesChart("#chart-container1"); | |
// create a series chart under #chart-container2 element using chart group A | |
var seriesChart2 = dc.seriesChart("#chart-container2", "chartGroupA"); | |
``` | |
**/ | |
dc.seriesChart = function (parent, chartGroup) { | |
var _chart = dc.compositeChart(parent, chartGroup); | |
function keySort(a, b) { | |
return d3.ascending(_chart.keyAccessor()(a), _chart.keyAccessor()(b)); | |
} | |
var _charts = {}; | |
var _chartFunction = dc.lineChart; | |
var _seriesAccessor; | |
var _seriesSort = d3.ascending; | |
var _valueSort = keySort; | |
_chart._mandatoryAttributes().push('seriesAccessor', 'chart'); | |
_chart.shareColors(true); | |
_chart._preprocessData = function () { | |
var keep = []; | |
var childrenChanged; | |
var nester = d3.nest().key(_seriesAccessor); | |
if (_seriesSort) { | |
nester.sortKeys(_seriesSort); | |
} | |
if (_valueSort) { | |
nester.sortValues(_valueSort); | |
} | |
var nesting = nester.entries(_chart.data()); | |
var children = | |
nesting.map(function (sub, i) { | |
var subChart = _charts[sub.key] || _chartFunction.call(_chart, _chart, chartGroup, sub.key, i); | |
if (!_charts[sub.key]) { | |
childrenChanged = true; | |
} | |
_charts[sub.key] = subChart; | |
keep.push(sub.key); | |
return subChart | |
.dimension(_chart.dimension()) | |
.group({all:d3.functor(sub.values)}, sub.key) | |
.keyAccessor(_chart.keyAccessor()) | |
.valueAccessor(_chart.valueAccessor()) | |
.brushOn(_chart.brushOn()); | |
}); | |
// this works around the fact compositeChart doesn't really | |
// have a removal interface | |
Object.keys(_charts) | |
.filter(function (c) {return keep.indexOf(c) === -1;}) | |
.forEach(function (c) { | |
clearChart(c); | |
childrenChanged = true; | |
}); | |
_chart._compose(children); | |
if (childrenChanged && _chart.legend()) { | |
_chart.legend().render(); | |
} | |
}; | |
function clearChart(c) { | |
if (_charts[c].g()) { | |
_charts[c].g().remove(); | |
} | |
delete _charts[c]; | |
} | |
function resetChildren() { | |
Object.keys(_charts).map(clearChart); | |
_charts = {}; | |
} | |
/** | |
#### .chart([function]) | |
Get or set the chart function, which generates the child charts. Default: dc.lineChart | |
``` | |
// put interpolation on the line charts used for the series | |
chart.chart(function(c) { return dc.lineChart(c).interpolate('basis'); }) | |
// do a scatter series chart | |
chart.chart(dc.scatterPlot) | |
``` | |
**/ | |
_chart.chart = function (_) { | |
if (!arguments.length) { | |
return _chartFunction; | |
} | |
_chartFunction = _; | |
resetChildren(); | |
return _chart; | |
}; | |
/** | |
#### .seriesAccessor([accessor]) | |
Get or set accessor function for the displayed series. Given a datum, this function | |
should return the series that datum belongs to. | |
**/ | |
_chart.seriesAccessor = function (_) { | |
if (!arguments.length) { | |
return _seriesAccessor; | |
} | |
_seriesAccessor = _; | |
resetChildren(); | |
return _chart; | |
}; | |
/** | |
#### .seriesSort([sortFunction]) | |
Get or set a function to sort the list of series by, given series values. | |
Example: | |
``` | |
chart.seriesSort(d3.descending); | |
``` | |
**/ | |
_chart.seriesSort = function (_) { | |
if (!arguments.length) { | |
return _seriesSort; | |
} | |
_seriesSort = _; | |
resetChildren(); | |
return _chart; | |
}; | |
/** | |
#### .valueSort([sortFunction]) | |
Get or set a function to sort each series values by. By default this is the key accessor which, | |
for example, will ensure a lineChart series connects its points in increasing key/x order, | |
rather than haphazardly. | |
**/ | |
_chart.valueSort = function (_) { | |
if (!arguments.length) { | |
return _valueSort; | |
} | |
_valueSort = _; | |
resetChildren(); | |
return _chart; | |
}; | |
// make compose private | |
_chart._compose = _chart.compose; | |
delete _chart.compose; | |
return _chart; | |
}; | |
/** | |
## Geo Choropleth Chart | |
Includes: [Color Mixin](#color-mixin), [Base Mixin](#base-mixin) | |
The geo choropleth chart is designed as an easy way to create a crossfilter driven choropleth map | |
from GeoJson data. This chart implementation was inspired by [the great d3 choropleth | |
example](http://bl.ocks.org/4060606). | |
Examples: | |
* [US Venture Capital Landscape 2011](http://dc-js.github.com/dc.js/vc/index.html) | |
#### dc.geoChoroplethChart(parent[, chartGroup]) | |
Create a choropleth chart instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created choropleth chart instance | |
```js | |
// create a choropleth chart under '#us-chart' element using the default global chart group | |
var chart1 = dc.geoChoroplethChart('#us-chart'); | |
// create a choropleth chart under '#us-chart2' element using chart group A | |
var chart2 = dc.compositeChart('#us-chart2', 'chartGroupA'); | |
``` | |
**/ | |
dc.geoChoroplethChart = function (parent, chartGroup) { | |
var _chart = dc.colorMixin(dc.baseMixin({})); | |
_chart.colorAccessor(function (d) { | |
return d || 0; | |
}); | |
var _geoPath = d3.geo.path(); | |
var _projectionFlag; | |
var _geoJsons = []; | |
_chart._doRender = function () { | |
_chart.resetSvg(); | |
for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) { | |
var states = _chart.svg().append('g') | |
.attr('class', 'layer' + layerIndex); | |
var regionG = states.selectAll('g.' + geoJson(layerIndex).name) | |
.data(geoJson(layerIndex).data) | |
.enter() | |
.append('g') | |
.attr('class', geoJson(layerIndex).name); | |
regionG | |
.append('path') | |
.attr('fill', 'white') | |
.attr('d', _geoPath); | |
regionG.append('title'); | |
plotData(layerIndex); | |
} | |
_projectionFlag = false; | |
}; | |
function plotData(layerIndex) { | |
var data = generateLayeredData(); | |
if (isDataLayer(layerIndex)) { | |
var regionG = renderRegionG(layerIndex); | |
renderPaths(regionG, layerIndex, data); | |
renderTitle(regionG, layerIndex, data); | |
} | |
} | |
function generateLayeredData() { | |
var data = {}; | |
var groupAll = _chart.data(); | |
for (var i = 0; i < groupAll.length; ++i) { | |
data[_chart.keyAccessor()(groupAll[i])] = _chart.valueAccessor()(groupAll[i]); | |
} | |
return data; | |
} | |
function isDataLayer(layerIndex) { | |
return geoJson(layerIndex).keyAccessor; | |
} | |
function renderRegionG(layerIndex) { | |
var regionG = _chart.svg() | |
.selectAll(layerSelector(layerIndex)) | |
.classed('selected', function (d) { | |
return isSelected(layerIndex, d); | |
}) | |
.classed('deselected', function (d) { | |
return isDeselected(layerIndex, d); | |
}) | |
.attr('class', function (d) { | |
var layerNameClass = geoJson(layerIndex).name; | |
var regionClass = dc.utils.nameToId(geoJson(layerIndex).keyAccessor(d)); | |
var baseClasses = layerNameClass + ' ' + regionClass; | |
if (isSelected(layerIndex, d)) { | |
baseClasses += ' selected'; | |
} | |
if (isDeselected(layerIndex, d)) { | |
baseClasses += ' deselected'; | |
} | |
return baseClasses; | |
}); | |
return regionG; | |
} | |
function layerSelector(layerIndex) { | |
return 'g.layer' + layerIndex + ' g.' + geoJson(layerIndex).name; | |
} | |
function isSelected(layerIndex, d) { | |
return _chart.hasFilter() && _chart.hasFilter(getKey(layerIndex, d)); | |
} | |
function isDeselected(layerIndex, d) { | |
return _chart.hasFilter() && !_chart.hasFilter(getKey(layerIndex, d)); | |
} | |
function getKey(layerIndex, d) { | |
return geoJson(layerIndex).keyAccessor(d); | |
} | |
function geoJson(index) { | |
return _geoJsons[index]; | |
} | |
function renderPaths(regionG, layerIndex, data) { | |
var paths = regionG | |
.select('path') | |
.attr('fill', function () { | |
var currentFill = d3.select(this).attr('fill'); | |
if (currentFill) { | |
return currentFill; | |
} | |
return 'none'; | |
}) | |
.on('click', function (d) { | |
return _chart.onClick(d, layerIndex); | |
}); | |
dc.transition(paths, _chart.transitionDuration()).attr('fill', function (d, i) { | |
return _chart.getColor(data[geoJson(layerIndex).keyAccessor(d)], i); | |
}); | |
} | |
_chart.onClick = function (d, layerIndex) { | |
var selectedRegion = geoJson(layerIndex).keyAccessor(d); | |
dc.events.trigger(function () { | |
_chart.filter(selectedRegion); | |
_chart.redrawGroup(); | |
}); | |
}; | |
function renderTitle(regionG, layerIndex, data) { | |
if (_chart.renderTitle()) { | |
regionG.selectAll('title').text(function (d) { | |
var key = getKey(layerIndex, d); | |
var value = data[key]; | |
return _chart.title()({key: key, value: value}); | |
}); | |
} | |
} | |
_chart._doRedraw = function () { | |
for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) { | |
plotData(layerIndex); | |
if (_projectionFlag) { | |
_chart.svg().selectAll('g.' + geoJson(layerIndex).name + ' path').attr('d', _geoPath); | |
} | |
} | |
_projectionFlag = false; | |
}; | |
/** | |
#### .overlayGeoJson(json, name, keyAccessor) - **mandatory** | |
Use this function to insert a new GeoJson map layer. This function can be invoked multiple times | |
if you have multiple GeoJson data layers to render on top of each other. If you overlay multiple | |
layers with the same name the new overlay will override the existing one. | |
Parameters: | |
* json - GeoJson feed | |
* name - name of the layer | |
* keyAccessor - accessor function used to extract 'key' from the GeoJson data. The key extracted by | |
this function should match the keys returned by the crossfilter groups. | |
```js | |
// insert a layer for rendering US states | |
chart.overlayGeoJson(statesJson.features, 'state', function(d) { | |
return d.properties.name; | |
}); | |
``` | |
**/ | |
_chart.overlayGeoJson = function (json, name, keyAccessor) { | |
for (var i = 0; i < _geoJsons.length; ++i) { | |
if (_geoJsons[i].name === name) { | |
_geoJsons[i].data = json; | |
_geoJsons[i].keyAccessor = keyAccessor; | |
return _chart; | |
} | |
} | |
_geoJsons.push({name: name, data: json, keyAccessor: keyAccessor}); | |
return _chart; | |
}; | |
/** | |
#### .projection(projection) | |
Set custom geo projection function. See the available [d3 geo projection | |
functions](https://github.com/mbostock/d3/wiki/Geo-Projections). Default value: albersUsa. | |
**/ | |
_chart.projection = function (projection) { | |
_geoPath.projection(projection); | |
_projectionFlag = true; | |
return _chart; | |
}; | |
/** | |
#### .geoJsons() | |
Returns all GeoJson layers currently registered with this chart. The returned array is a | |
reference to this chart's internal data structure, so any modification to this array will also | |
modify this chart's internal registration. | |
Returns an array of objects containing fields {name, data, accessor} | |
**/ | |
_chart.geoJsons = function () { | |
return _geoJsons; | |
}; | |
/** | |
#### .geoPath() | |
Returns the [d3.geo.path](https://github.com/mbostock/d3/wiki/Geo-Paths#path) object used to | |
render the projection and features. Can be useful for figuring out the bounding box of the | |
feature set and thus a way to calculate scale and translation for the projection. | |
**/ | |
_chart.geoPath = function () { | |
return _geoPath; | |
}; | |
/** | |
#### .removeGeoJson(name) | |
Remove a GeoJson layer from this chart by name | |
**/ | |
_chart.removeGeoJson = function (name) { | |
var geoJsons = []; | |
for (var i = 0; i < _geoJsons.length; ++i) { | |
var layer = _geoJsons[i]; | |
if (layer.name !== name) { | |
geoJsons.push(layer); | |
} | |
} | |
_geoJsons = geoJsons; | |
return _chart; | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Bubble Overlay Chart | |
Includes: [Bubble Mixin](#bubble-mixin), [Base Mixin](#base-mixin) | |
The bubble overlay chart is quite different from the typical bubble chart. With the bubble overlay | |
chart you can arbitrarily place bubbles on an existing svg or bitmap image, thus changing the | |
typical x and y positioning while retaining the capability to visualize data using bubble radius | |
and coloring. | |
Examples: | |
* [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) | |
#### dc.bubbleOverlay(parent[, chartGroup]) | |
Create a bubble overlay chart instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
off-screen. Typically this element should also be the parent of the underlying image. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created bubble overlay chart instance | |
```js | |
// create a bubble overlay chart on top of the '#chart-container1 svg' element using the default global chart group | |
var bubbleChart1 = dc.bubbleOverlayChart('#chart-container1').svg(d3.select('#chart-container1 svg')); | |
// create a bubble overlay chart on top of the '#chart-container2 svg' element using chart group A | |
var bubbleChart2 = dc.compositeChart('#chart-container2', 'chartGroupA').svg(d3.select('#chart-container2 svg')); | |
``` | |
#### .svg(imageElement) - **mandatory** | |
Set the underlying svg image element. Unlike other dc charts this chart will not generate a svg | |
element; therefore the bubble overlay chart will not work if this function is not invoked. If the | |
underlying image is a bitmap, then an empty svg will need to be created on top of the image. | |
```js | |
// set up underlying svg element | |
chart.svg(d3.select('#chart svg')); | |
``` | |
**/ | |
dc.bubbleOverlay = function (root, chartGroup) { | |
var BUBBLE_OVERLAY_CLASS = 'bubble-overlay'; | |
var BUBBLE_NODE_CLASS = 'node'; | |
var BUBBLE_CLASS = 'bubble'; | |
var _chart = dc.bubbleMixin(dc.baseMixin({})); | |
var _g; | |
var _points = []; | |
_chart.transitionDuration(750); | |
_chart.radiusValueAccessor(function (d) { | |
return d.value; | |
}); | |
/** | |
#### .point(name, x, y) - **mandatory** | |
Set up a data point on the overlay. The name of a data point should match a specific 'key' among | |
data groups generated using keyAccessor. If a match is found (point name <-> data group key) | |
then a bubble will be generated at the position specified by the function. x and y | |
value specified here are relative to the underlying svg. | |
**/ | |
_chart.point = function (name, x, y) { | |
_points.push({name: name, x: x, y: y}); | |
return _chart; | |
}; | |
_chart._doRender = function () { | |
_g = initOverlayG(); | |
_chart.r().range([_chart.MIN_RADIUS, _chart.width() * _chart.maxBubbleRelativeSize()]); | |
initializeBubbles(); | |
_chart.fadeDeselectedArea(); | |
return _chart; | |
}; | |
function initOverlayG() { | |
_g = _chart.select('g.' + BUBBLE_OVERLAY_CLASS); | |
if (_g.empty()) { | |
_g = _chart.svg().append('g').attr('class', BUBBLE_OVERLAY_CLASS); | |
} | |
return _g; | |
} | |
function initializeBubbles() { | |
var data = mapData(); | |
_points.forEach(function (point) { | |
var nodeG = getNodeG(point, data); | |
var circle = nodeG.select('circle.' + BUBBLE_CLASS); | |
if (circle.empty()) { | |
circle = nodeG.append('circle') | |
.attr('class', BUBBLE_CLASS) | |
.attr('r', 0) | |
.attr('fill', _chart.getColor) | |
.on('click', _chart.onClick); | |
} | |
dc.transition(circle, _chart.transitionDuration()) | |
.attr('r', function (d) { | |
return _chart.bubbleR(d); | |
}); | |
_chart._doRenderLabel(nodeG); | |
_chart._doRenderTitles(nodeG); | |
}); | |
} | |
function mapData() { | |
var data = {}; | |
_chart.data().forEach(function (datum) { | |
data[_chart.keyAccessor()(datum)] = datum; | |
}); | |
return data; | |
} | |
function getNodeG(point, data) { | |
var bubbleNodeClass = BUBBLE_NODE_CLASS + ' ' + dc.utils.nameToId(point.name); | |
var nodeG = _g.select('g.' + dc.utils.nameToId(point.name)); | |
if (nodeG.empty()) { | |
nodeG = _g.append('g') | |
.attr('class', bubbleNodeClass) | |
.attr('transform', 'translate(' + point.x + ',' + point.y + ')'); | |
} | |
nodeG.datum(data[point.name]); | |
return nodeG; | |
} | |
_chart._doRedraw = function () { | |
updateBubbles(); | |
_chart.fadeDeselectedArea(); | |
return _chart; | |
}; | |
function updateBubbles() { | |
var data = mapData(); | |
_points.forEach(function (point) { | |
var nodeG = getNodeG(point, data); | |
var circle = nodeG.select('circle.' + BUBBLE_CLASS); | |
dc.transition(circle, _chart.transitionDuration()) | |
.attr('r', function (d) { | |
return _chart.bubbleR(d); | |
}) | |
.attr('fill', _chart.getColor); | |
_chart.doUpdateLabels(nodeG); | |
_chart.doUpdateTitles(nodeG); | |
}); | |
} | |
_chart.debug = function (flag) { | |
if (flag) { | |
var debugG = _chart.select('g.' + dc.constants.DEBUG_GROUP_CLASS); | |
if (debugG.empty()) { | |
debugG = _chart.svg() | |
.append('g') | |
.attr('class', dc.constants.DEBUG_GROUP_CLASS); | |
} | |
var debugText = debugG.append('text') | |
.attr('x', 10) | |
.attr('y', 20); | |
debugG | |
.append('rect') | |
.attr('width', _chart.width()) | |
.attr('height', _chart.height()) | |
.on('mousemove', function () { | |
var position = d3.mouse(debugG.node()); | |
var msg = position[0] + ', ' + position[1]; | |
debugText.text(msg); | |
}); | |
} else { | |
_chart.selectAll('.debug').remove(); | |
} | |
return _chart; | |
}; | |
_chart.anchor(root, chartGroup); | |
return _chart; | |
}; | |
/** | |
## Row Chart | |
Includes: [Cap Mixin](#cap-mixin), [Margin Mixin](#margin-mixin), [Color Mixin](#color-mixin), [Base Mixin](#base-mixin) | |
Concrete row chart implementation. | |
#### dc.rowChart(parent[, chartGroup]) | |
Create a row chart instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created row chart instance | |
```js | |
// create a row chart under #chart-container1 element using the default global chart group | |
var chart1 = dc.rowChart('#chart-container1'); | |
// create a row chart under #chart-container2 element using chart group A | |
var chart2 = dc.rowChart('#chart-container2', 'chartGroupA'); | |
``` | |
**/ | |
dc.rowChart = function (parent, chartGroup) { | |
var _g; | |
var _labelOffsetX = 10; | |
var _labelOffsetY = 15; | |
var _hasLabelOffsetY = false; | |
var _dyOffset = '0.35em'; // this helps center labels https://github.com/mbostock/d3/wiki/SVG-Shapes#svg_text | |
var _titleLabelOffsetX = 2; | |
var _gap = 5; | |
var _fixedBarHeight = false; | |
var _rowCssClass = 'row'; | |
var _titleRowCssClass = 'titlerow'; | |
var _renderTitleLabel = false; | |
var _chart = dc.capMixin(dc.marginMixin(dc.colorMixin(dc.baseMixin({})))); | |
var _x; | |
var _elasticX; | |
var _xAxis = d3.svg.axis().orient('bottom'); | |
var _rowData; | |
_chart.rowsCap = _chart.cap; | |
function calculateAxisScale() { | |
if (!_x || _elasticX) { | |
var extent = d3.extent(_rowData, _chart.cappedValueAccessor); | |
if (extent[0] > 0) { | |
extent[0] = 0; | |
} | |
_x = d3.scale.linear().domain(extent) | |
.range([0, _chart.effectiveWidth()]); | |
} | |
_xAxis.scale(_x); | |
} | |
function drawAxis() { | |
var axisG = _g.select('g.axis'); | |
calculateAxisScale(); | |
if (axisG.empty()) { | |
axisG = _g.append('g').attr('class', 'axis') | |
.attr('transform', 'translate(0, ' + _chart.effectiveHeight() + ')'); | |
} | |
dc.transition(axisG, _chart.transitionDuration()) | |
.call(_xAxis); | |
} | |
_chart._doRender = function () { | |
_chart.resetSvg(); | |
_g = _chart.svg() | |
.append('g') | |
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')'); | |
drawChart(); | |
return _chart; | |
}; | |
_chart.title(function (d) { | |
return _chart.cappedKeyAccessor(d) + ': ' + _chart.cappedValueAccessor(d); | |
}); | |
_chart.label(_chart.cappedKeyAccessor); | |
/** | |
#### .x([scale]) | |
Gets or sets the x scale. The x scale can be any d3 | |
[quantitive scale](https://github.com/mbostock/d3/wiki/Quantitative-Scales) | |
**/ | |
_chart.x = function (x) { | |
if (!arguments.length) { | |
return _x; | |
} | |
_x = x; | |
return _chart; | |
}; | |
function drawGridLines() { | |
_g.selectAll('g.tick') | |
.select('line.grid-line') | |
.remove(); | |
_g.selectAll('g.tick') | |
.append('line') | |
.attr('class', 'grid-line') | |
.attr('x1', 0) | |
.attr('y1', 0) | |
.attr('x2', 0) | |
.attr('y2', function () { | |
return -_chart.effectiveHeight(); | |
}); | |
} | |
function drawChart() { | |
_rowData = _chart.data(); | |
drawAxis(); | |
drawGridLines(); | |
var rows = _g.selectAll('g.' + _rowCssClass) | |
.data(_rowData); | |
createElements(rows); | |
removeElements(rows); | |
updateElements(rows); | |
} | |
function createElements(rows) { | |
var rowEnter = rows.enter() | |
.append('g') | |
.attr('class', function (d, i) { | |
return _rowCssClass + ' _' + i; | |
}); | |
rowEnter.append('rect').attr('width', 0); | |
createLabels(rowEnter); | |
updateLabels(rows); | |
} | |
function removeElements(rows) { | |
rows.exit().remove(); | |
} | |
function rootValue() { | |
var root = _x(0); | |
return (root === -Infinity || root !== root) ? _x(1) : root; | |
} | |
function updateElements(rows) { | |
var n = _rowData.length; | |
var height; | |
if (!_fixedBarHeight) { | |
height = (_chart.effectiveHeight() - (n + 1) * _gap) / n; | |
} else { | |
height = _fixedBarHeight; | |
} | |
// vertically align label in center unless they override the value via property setter | |
if (!_hasLabelOffsetY) { | |
_labelOffsetY = height / 2; | |
} | |
var rect = rows.attr('transform', function (d, i) { | |
return 'translate(0,' + ((i + 1) * _gap + i * height) + ')'; | |
}).select('rect') | |
.attr('height', height) | |
.attr('fill', _chart.getColor) | |
.on('click', onClick) | |
.classed('deselected', function (d) { | |
return (_chart.hasFilter()) ? !isSelectedRow(d) : false; | |
}) | |
.classed('selected', function (d) { | |
return (_chart.hasFilter()) ? isSelectedRow(d) : false; | |
}); | |
dc.transition(rect, _chart.transitionDuration()) | |
.attr('width', function (d) { | |
return Math.abs(rootValue() - _x(_chart.valueAccessor()(d))); | |
}) | |
.attr('transform', translateX); | |
createTitles(rows); | |
updateLabels(rows); | |
} | |
function createTitles(rows) { | |
if (_chart.renderTitle()) { | |
rows.selectAll('title').remove(); | |
rows.append('title').text(_chart.title()); | |
} | |
} | |
function createLabels(rowEnter) { | |
if (_chart.renderLabel()) { | |
rowEnter.append('text') | |
.on('click', onClick); | |
} | |
if (_chart.renderTitleLabel()) { | |
rowEnter.append('text') | |
.attr('class', _titleRowCssClass) | |
.on('click', onClick); | |
} | |
} | |
function updateLabels(rows) { | |
if (_chart.renderLabel()) { | |
var lab = rows.select('text') | |
.attr('x', _labelOffsetX) | |
.attr('y', _labelOffsetY) | |
.attr('dy', _dyOffset) | |
.on('click', onClick) | |
.attr('class', function (d, i) { | |
return _rowCssClass + ' _' + i; | |
}) | |
.text(function (d) { | |
return _chart.label()(d); | |
}); | |
dc.transition(lab, _chart.transitionDuration()) | |
.attr('transform', translateX); | |
} | |
if (_chart.renderTitleLabel()) { | |
var titlelab = rows.select('.' + _titleRowCssClass) | |
.attr('x', _chart.effectiveWidth() - _titleLabelOffsetX) | |
.attr('y', _labelOffsetY) | |
.attr('text-anchor', 'end') | |
.on('click', onClick) | |
.attr('class', function (d, i) { | |
return _titleRowCssClass + ' _' + i ; | |
}) | |
.text(function (d) { | |
return _chart.title()(d); | |
}); | |
dc.transition(titlelab, _chart.transitionDuration()) | |
.attr('transform', translateX); | |
} | |
} | |
/** | |
#### .renderTitleLabel(boolean) | |
Turn on/off Title label rendering (values) using SVG style of text-anchor 'end' | |
**/ | |
_chart.renderTitleLabel = function (_) { | |
if (!arguments.length) { | |
return _renderTitleLabel; | |
} | |
_renderTitleLabel = _; | |
return _chart; | |
}; | |
function onClick(d) { | |
_chart.onClick(d); | |
} | |
function translateX(d) { | |
var x = _x(_chart.cappedValueAccessor(d)), | |
x0 = rootValue(), | |
s = x > x0 ? x0 : x; | |
return 'translate(' + s + ',0)'; | |
} | |
_chart._doRedraw = function () { | |
drawChart(); | |
return _chart; | |
}; | |
/** | |
#### .xAxis() | |
Get the x axis for the row chart instance. Note: not settable for row charts. | |
See the [d3 axis object](https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-axis) documention for more information. | |
```js | |
// customize x axis tick format | |
chart.xAxis().tickFormat(function (v) {return v + '%';}); | |
// customize x axis tick values | |
chart.xAxis().tickValues([0, 100, 200, 300]); | |
``` | |
**/ | |
_chart.xAxis = function () { | |
return _xAxis; | |
}; | |
/** | |
#### .fixedBarHeight([height]) | |
Get or set the fixed bar height. Default is [false] which will auto-scale bars. | |
For example, if you want to fix the height for a specific number of bars (useful in TopN charts) | |
you could fix height as follows (where count = total number of bars in your TopN and gap is | |
your vertical gap space). | |
```js | |
chart.fixedBarHeight( chartheight - (count + 1) * gap / count); | |
``` | |
**/ | |
_chart.fixedBarHeight = function (g) { | |
if (!arguments.length) { | |
return _fixedBarHeight; | |
} | |
_fixedBarHeight = g; | |
return _chart; | |
}; | |
/** | |
#### .gap([gap]) | |
Get or set the vertical gap space between rows on a particular row chart instance. Default gap is 5px; | |
**/ | |
_chart.gap = function (g) { | |
if (!arguments.length) { | |
return _gap; | |
} | |
_gap = g; | |
return _chart; | |
}; | |
/** | |
#### .elasticX([boolean]) | |
Get or set the elasticity on x axis. If this attribute is set to true, then the x axis will rescle to auto-fit the | |
data range when filtered. | |
**/ | |
_chart.elasticX = function (_) { | |
if (!arguments.length) { | |
return _elasticX; | |
} | |
_elasticX = _; | |
return _chart; | |
}; | |
/** | |
#### .labelOffsetX([x]) | |
Get or set the x offset (horizontal space to the top left corner of a row) for labels on a particular row chart. | |
Default x offset is 10px; | |
**/ | |
_chart.labelOffsetX = function (o) { | |
if (!arguments.length) { | |
return _labelOffsetX; | |
} | |
_labelOffsetX = o; | |
return _chart; | |
}; | |
/** | |
#### .labelOffsetY([y]) | |
Get or set the y offset (vertical space to the top left corner of a row) for labels on a particular row chart. | |
Default y offset is 15px; | |
**/ | |
_chart.labelOffsetY = function (o) { | |
if (!arguments.length) { | |
return _labelOffsetY; | |
} | |
_labelOffsetY = o; | |
_hasLabelOffsetY = true; | |
return _chart; | |
}; | |
/** | |
#### .titleLabelOffsetx([x]) | |
Get of set the x offset (horizontal space between right edge of row and right edge or text. | |
Default x offset is 2px; | |
**/ | |
_chart.titleLabelOffsetX = function (o) { | |
if (!arguments.length) { | |
return _titleLabelOffsetX; | |
} | |
_titleLabelOffsetX = o; | |
return _chart; | |
}; | |
function isSelectedRow (d) { | |
return _chart.hasFilter(_chart.cappedKeyAccessor(d)); | |
} | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Legend | |
Legend is a attachable widget that can be added to other dc charts to render horizontal legend | |
labels. | |
```js | |
chart.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5)) | |
``` | |
Examples: | |
* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) | |
* [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) | |
**/ | |
dc.legend = function () { | |
var LABEL_GAP = 2; | |
var _legend = {}, | |
_parent, | |
_x = 0, | |
_y = 0, | |
_itemHeight = 12, | |
_gap = 5, | |
_horizontal = false, | |
_legendWidth = 560, | |
_itemWidth = 70; | |
var _g; | |
_legend.parent = function (p) { | |
if (!arguments.length) { | |
return _parent; | |
} | |
_parent = p; | |
return _legend; | |
}; | |
_legend.render = function () { | |
_parent.svg().select('g.dc-legend').remove(); | |
_g = _parent.svg().append('g') | |
.attr('class', 'dc-legend') | |
.attr('transform', 'translate(' + _x + ',' + _y + ')'); | |
var legendables = _parent.legendables(); | |
var itemEnter = _g.selectAll('g.dc-legend-item') | |
.data(legendables) | |
.enter() | |
.append('g') | |
.attr('class', 'dc-legend-item') | |
.on('mouseover', function (d) { | |
_parent.legendHighlight(d); | |
}) | |
.on('mouseout', function (d) { | |
_parent.legendReset(d); | |
}) | |
.on('click', function (d) { | |
d.chart.legendToggle(d); | |
}); | |
_g.selectAll('g.dc-legend-item') | |
.classed('fadeout', function (d) { | |
return d.chart.isLegendableHidden(d); | |
}); | |
if (legendables.some(dc.pluck('dashstyle'))) { | |
itemEnter | |
.append('line') | |
.attr('x1', 0) | |
.attr('y1', _itemHeight / 2) | |
.attr('x2', _itemHeight) | |
.attr('y2', _itemHeight / 2) | |
.attr('stroke-width', 2) | |
.attr('stroke-dasharray', dc.pluck('dashstyle')) | |
.attr('stroke', dc.pluck('color')); | |
} else { | |
itemEnter | |
.append('rect') | |
.attr('width', _itemHeight) | |
.attr('height', _itemHeight) | |
.attr('fill', function (d) {return d ? d.color : 'blue';}); | |
} | |
itemEnter.append('text') | |
.text(dc.pluck('name')) | |
.attr('x', _itemHeight + LABEL_GAP) | |
.attr('y', function () { | |
return _itemHeight / 2 + (this.clientHeight ? this.clientHeight : 13) / 2 - 2; | |
}); | |
var _cumulativeLegendTextWidth = 0; | |
var row = 0; | |
itemEnter.attr('transform', function (d, i) { | |
if (_horizontal) { | |
var translateBy = 'translate(' + _cumulativeLegendTextWidth + ',' + row * legendItemHeight() + ')'; | |
if ((_cumulativeLegendTextWidth + _itemWidth) >= _legendWidth) { | |
++row ; | |
_cumulativeLegendTextWidth = 0 ; | |
} else { | |
_cumulativeLegendTextWidth += _itemWidth; | |
} | |
return translateBy; | |
} | |
else { | |
return 'translate(0,' + i * legendItemHeight() + ')'; | |
} | |
}); | |
}; | |
function legendItemHeight() { | |
return _gap + _itemHeight; | |
} | |
/** | |
#### .x([value]) | |
Set or get x coordinate for legend widget. Default: 0. | |
**/ | |
_legend.x = function (x) { | |
if (!arguments.length) { | |
return _x; | |
} | |
_x = x; | |
return _legend; | |
}; | |
/** | |
#### .y([value]) | |
Set or get y coordinate for legend widget. Default: 0. | |
**/ | |
_legend.y = function (y) { | |
if (!arguments.length) { | |
return _y; | |
} | |
_y = y; | |
return _legend; | |
}; | |
/** | |
#### .gap([value]) | |
Set or get gap between legend items. Default: 5. | |
**/ | |
_legend.gap = function (gap) { | |
if (!arguments.length) { | |
return _gap; | |
} | |
_gap = gap; | |
return _legend; | |
}; | |
/** | |
#### .itemHeight([value]) | |
Set or get legend item height. Default: 12. | |
**/ | |
_legend.itemHeight = function (h) { | |
if (!arguments.length) { | |
return _itemHeight; | |
} | |
_itemHeight = h; | |
return _legend; | |
}; | |
/** | |
#### .horizontal([boolean]) | |
Position legend horizontally instead of vertically | |
**/ | |
_legend.horizontal = function (_) { | |
if (!arguments.length) { | |
return _horizontal; | |
} | |
_horizontal = _; | |
return _legend; | |
}; | |
/** | |
#### .legendWidth([value]) | |
Maximum width for horizontal legend. Default: 560. | |
**/ | |
_legend.legendWidth = function (_) { | |
if (!arguments.length) { | |
return _legendWidth; | |
} | |
_legendWidth = _; | |
return _legend; | |
}; | |
/** | |
#### .itemWidth([value]) | |
legendItem width for horizontal legend. Default: 70. | |
**/ | |
_legend.itemWidth = function (_) { | |
if (!arguments.length) { | |
return _itemWidth; | |
} | |
_itemWidth = _; | |
return _legend; | |
}; | |
return _legend; | |
}; | |
/** | |
## Scatter Plot | |
Includes: [Coordinate Grid Mixin](#coordinate-grid-mixin) | |
A scatter plot chart | |
#### dc.scatterPlot(parent[, chartGroup]) | |
Create a scatter plot instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection | compositeChart - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
If the scatter plot is a sub-chart in a [Composite Chart](#composite-chart) then pass in the parent composite | |
chart instance. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created scatter plot instance | |
```js | |
// create a scatter plot under #chart-container1 element using the default global chart group | |
var chart1 = dc.scatterPlot('#chart-container1'); | |
// create a scatter plot under #chart-container2 element using chart group A | |
var chart2 = dc.scatterPlot('#chart-container2', 'chartGroupA'); | |
// create a sub-chart under a composite parent chart | |
var chart3 = dc.scatterPlot(compositeChart); | |
``` | |
**/ | |
dc.scatterPlot = function (parent, chartGroup) { | |
var _chart = dc.coordinateGridMixin({}); | |
var _symbol = d3.svg.symbol(); | |
var _existenceAccessor = function (d) { return d.value; }; | |
var originalKeyAccessor = _chart.keyAccessor(); | |
_chart.keyAccessor(function (d) { return originalKeyAccessor(d)[0]; }); | |
_chart.valueAccessor(function (d) { return originalKeyAccessor(d)[1]; }); | |
_chart.colorAccessor(function () { return _chart._groupName; }); | |
var _locator = function (d) { | |
return 'translate(' + _chart.x()(_chart.keyAccessor()(d)) + ',' + | |
_chart.y()(_chart.valueAccessor()(d)) + ')'; | |
}; | |
var _symbolSize = 3; | |
var _highlightedSize = 5; | |
var _hiddenSize = 0; | |
_symbol.size(function (d) { | |
if (!_existenceAccessor(d)) { | |
return _hiddenSize; | |
} else if (this.filtered) { | |
return Math.pow(_highlightedSize, 2); | |
} else { | |
return Math.pow(_symbolSize, 2); | |
} | |
}); | |
dc.override(_chart, '_filter', function (filter) { | |
if (!arguments.length) { | |
return _chart.__filter(); | |
} | |
return _chart.__filter(dc.filters.RangedTwoDimensionalFilter(filter)); | |
}); | |
_chart.plotData = function () { | |
var symbols = _chart.chartBodyG().selectAll('path.symbol') | |
.data(_chart.data()); | |
symbols | |
.enter() | |
.append('path') | |
.attr('class', 'symbol') | |
.attr('opacity', 0) | |
.attr('fill', _chart.getColor) | |
.attr('transform', _locator); | |
dc.transition(symbols, _chart.transitionDuration()) | |
.attr('opacity', function (d) { return _existenceAccessor(d) ? 1 : 0; }) | |
.attr('fill', _chart.getColor) | |
.attr('transform', _locator) | |
.attr('d', _symbol); | |
dc.transition(symbols.exit(), _chart.transitionDuration()) | |
.attr('opacity', 0).remove(); | |
}; | |
/** | |
#### .existenceAccessor([accessor]) | |
Get or set the existence accessor. If a point exists, it is drawn with symbolSize radius and | |
opacity 1; if it does not exist, it is drawn with hiddenSize radius and opacity 0. By default, | |
the existence accessor checks if the reduced value is truthy. | |
**/ | |
_chart.existenceAccessor = function (acc) { | |
if (!arguments.length) { | |
return _existenceAccessor; | |
} | |
_existenceAccessor = acc; | |
return this; | |
}; | |
/** | |
#### .symbol([type]) | |
Get or set the symbol type used for each point. By default the symbol is a circle. See the D3 | |
[docs](https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-symbol_type) for acceptable types. | |
Type can be a constant or an accessor. | |
**/ | |
_chart.symbol = function (type) { | |
if (!arguments.length) { | |
return _symbol.type(); | |
} | |
_symbol.type(type); | |
return _chart; | |
}; | |
/** | |
#### .symbolSize([radius]) | |
Set or get radius for symbols. Default: 3. | |
**/ | |
_chart.symbolSize = function (s) { | |
if (!arguments.length) { | |
return _symbolSize; | |
} | |
_symbolSize = s; | |
return _chart; | |
}; | |
/** | |
#### .highlightedSize([radius]) | |
Set or get radius for highlighted symbols. Default: 4. | |
**/ | |
_chart.highlightedSize = function (s) { | |
if (!arguments.length) { | |
return _highlightedSize; | |
} | |
_highlightedSize = s; | |
return _chart; | |
}; | |
/** | |
#### .hiddenSize([radius]) | |
Set or get radius for symbols when the group is empty. Default: 0. | |
**/ | |
_chart.hiddenSize = function (s) { | |
if (!arguments.length) { | |
return _hiddenSize; | |
} | |
_hiddenSize = s; | |
return _chart; | |
}; | |
_chart.legendables = function () { | |
return [{chart: _chart, name: _chart._groupName, color: _chart.getColor()}]; | |
}; | |
_chart.legendHighlight = function (d) { | |
resizeSymbolsWhere(function (symbol) { | |
return symbol.attr('fill') === d.color; | |
}, _highlightedSize); | |
_chart.selectAll('.chart-body path.symbol').filter(function () { | |
return d3.select(this).attr('fill') !== d.color; | |
}).classed('fadeout', true); | |
}; | |
_chart.legendReset = function (d) { | |
resizeSymbolsWhere(function (symbol) { | |
return symbol.attr('fill') === d.color; | |
}, _symbolSize); | |
_chart.selectAll('.chart-body path.symbol').filter(function () { | |
return d3.select(this).attr('fill') !== d.color; | |
}).classed('fadeout', false); | |
}; | |
function resizeSymbolsWhere(condition, size) { | |
var symbols = _chart.selectAll('.chart-body path.symbol').filter(function () { | |
return condition(d3.select(this)); | |
}); | |
var oldSize = _symbol.size(); | |
_symbol.size(Math.pow(size, 2)); | |
dc.transition(symbols, _chart.transitionDuration()).attr('d', _symbol); | |
_symbol.size(oldSize); | |
} | |
_chart.setHandlePaths = function () { | |
// no handle paths for poly-brushes | |
}; | |
_chart.extendBrush = function () { | |
var extent = _chart.brush().extent(); | |
if (_chart.round()) { | |
extent[0] = extent[0].map(_chart.round()); | |
extent[1] = extent[1].map(_chart.round()); | |
_chart.g().select('.brush') | |
.call(_chart.brush().extent(extent)); | |
} | |
return extent; | |
}; | |
_chart.brushIsEmpty = function (extent) { | |
return _chart.brush().empty() || !extent || extent[0][0] >= extent[1][0] || extent[0][1] >= extent[1][1]; | |
}; | |
function resizeFiltered(filter) { | |
var symbols = _chart.selectAll('.chart-body path.symbol').each(function (d) { | |
this.filtered = filter && filter.isFiltered(d.key); | |
}); | |
dc.transition(symbols, _chart.transitionDuration()).attr('d', _symbol); | |
} | |
_chart._brushing = function () { | |
var extent = _chart.extendBrush(); | |
_chart.redrawBrush(_chart.g()); | |
if (_chart.brushIsEmpty(extent)) { | |
dc.events.trigger(function () { | |
_chart.filter(null); | |
_chart.redrawGroup(); | |
}); | |
resizeFiltered(false); | |
} else { | |
var ranged2DFilter = dc.filters.RangedTwoDimensionalFilter(extent); | |
dc.events.trigger(function () { | |
_chart.filter(null); | |
_chart.filter(ranged2DFilter); | |
_chart.redrawGroup(); | |
}, dc.constants.EVENT_DELAY); | |
resizeFiltered(ranged2DFilter); | |
} | |
}; | |
_chart.setBrushY = function (gBrush) { | |
gBrush.call(_chart.brush().y(_chart.y())); | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Number Display Widget | |
Includes: [Base Mixin](#base-mixin) | |
A display of a single numeric value. | |
Examples: | |
* [Test Example](http://dc-js.github.io/dc.js/examples/number.html) | |
#### dc.numberDisplay(parent[, chartGroup]) | |
Create a Number Display instance and attach it to the given parent element. | |
Unlike other charts, you do not need to set a dimension. Instead a group object must be provided and | |
a valueAccessor that returns a single value. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
The number display widget will only react to filter changes in the chart group. | |
Returns: | |
A newly created number display instance | |
```js | |
// create a number display under #chart-container1 element using the default global chart group | |
var display1 = dc.numberDisplay('#chart-container1'); | |
``` | |
**/ | |
dc.numberDisplay = function (parent, chartGroup) { | |
var SPAN_CLASS = 'number-display'; | |
var _formatNumber = d3.format('.2s'); | |
var _chart = dc.baseMixin({}); | |
var _html = {one:'', some:'', none:''}; | |
// dimension not required | |
_chart._mandatoryAttributes(['group']); | |
/** | |
#### .html([object]) | |
Gets or sets an optional object specifying HTML templates to use depending on the number | |
displayed. The text `%number` will be replaced with the current value. | |
- one: HTML template to use if the number is 1 | |
- zero: HTML template to use if the number is 0 | |
- some: HTML template to use otherwise | |
```js | |
numberWidget.html({ | |
one:'%number record', | |
some:'%number records', | |
none:'no records'}) | |
``` | |
**/ | |
_chart.html = function (s) { | |
if (!arguments.length) { | |
return _html; | |
} | |
if (s.none) { | |
_html.none = s.none;//if none available | |
} else if (s.one) { | |
_html.none = s.one;//if none not available use one | |
} else if (s.some) { | |
_html.none = s.some;//if none and one not available use some | |
} | |
if (s.one) { | |
_html.one = s.one;//if one available | |
} else if (s.some) { | |
_html.one = s.some;//if one not available use some | |
} | |
if (s.some) { | |
_html.some = s.some;//if some available | |
} else if (s.one) { | |
_html.some = s.one;//if some not available use one | |
} | |
return _chart; | |
}; | |
/** | |
#### .value() | |
Calculate and return the underlying value of the display | |
**/ | |
_chart.value = function () { | |
return _chart.data(); | |
}; | |
_chart.data(function (group) { | |
var valObj = group.value ? group.value() : group.top(1)[0]; | |
return _chart.valueAccessor()(valObj); | |
}); | |
_chart.transitionDuration(250); // good default | |
_chart._doRender = function () { | |
var newValue = _chart.value(), | |
span = _chart.selectAll('.' + SPAN_CLASS); | |
if (span.empty()) { | |
span = span.data([0]) | |
.enter() | |
.append('span') | |
.attr('class', SPAN_CLASS); | |
} | |
span.transition() | |
.duration(_chart.transitionDuration()) | |
.ease('quad-out-in') | |
.tween('text', function () { | |
var interp = d3.interpolateNumber(this.lastValue || 0, newValue); | |
this.lastValue = newValue; | |
return function (t) { | |
var html = null, num = _chart.formatNumber()(interp(t)); | |
if (newValue === 0 && (_html.none !== '')) { | |
html = _html.none; | |
} else if (newValue === 1 && (_html.one !== '')) { | |
html = _html.one; | |
} else if (_html.some !== '') { | |
html = _html.some; | |
} | |
this.innerHTML = html ? html.replace('%number', num) : num; | |
}; | |
}); | |
}; | |
_chart._doRedraw = function () { | |
return _chart._doRender(); | |
}; | |
/** | |
#### .formatNumber([formatter]) | |
Get or set a function to format the value for the display. By default `d3.format('.2s');` is used. | |
**/ | |
_chart.formatNumber = function (_) { | |
if (!arguments.length) { | |
return _formatNumber; | |
} | |
_formatNumber = _; | |
return _chart; | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
/** | |
## Heat Map | |
Includes: [Color Mixin](#color-mixin), [Margin Mixin](#margin-mixin), [Base Mixin](#base-mixin) | |
A heat map is matrix that represents the values of two dimensions of data using colors. | |
#### dc.heatMap(parent[, chartGroup]) | |
Create a heat map instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created heat map instance | |
```js | |
// create a heat map under #chart-container1 element using the default global chart group | |
var heatMap1 = dc.heatMap('#chart-container1'); | |
// create a heat map under #chart-container2 element using chart group A | |
var heatMap2 = dc.heatMap('#chart-container2', 'chartGroupA'); | |
``` | |
**/ | |
dc.heatMap = function (parent, chartGroup) { | |
var DEFAULT_BORDER_RADIUS = 6.75; | |
var _chartBody; | |
var _cols; | |
var _rows; | |
var _xBorderRadius = DEFAULT_BORDER_RADIUS; | |
var _yBorderRadius = DEFAULT_BORDER_RADIUS; | |
var _chart = dc.colorMixin(dc.marginMixin(dc.baseMixin({}))); | |
_chart._mandatoryAttributes(['group']); | |
_chart.title(_chart.colorAccessor()); | |
var _xAxisOnClick = function (d) { filterAxis(0, d); }; | |
var _yAxisOnClick = function (d) { filterAxis(1, d); }; | |
var _boxOnClick = function (d) { | |
var filter = d.key; | |
dc.events.trigger(function () { | |
_chart.filter(filter); | |
_chart.redrawGroup(); | |
}); | |
}; | |
function filterAxis(axis, value) { | |
var cellsOnAxis = _chart.selectAll('.box-group').filter(function (d) { | |
return d.key[axis] === value; | |
}); | |
var unfilteredCellsOnAxis = cellsOnAxis.filter(function (d) { | |
return !_chart.hasFilter(d.key); | |
}); | |
dc.events.trigger(function () { | |
if (unfilteredCellsOnAxis.empty()) { | |
cellsOnAxis.each(function (d) { | |
_chart.filter(d.key); | |
}); | |
} else { | |
unfilteredCellsOnAxis.each(function (d) { | |
_chart.filter(d.key); | |
}); | |
} | |
_chart.redrawGroup(); | |
}); | |
} | |
dc.override(_chart, 'filter', function (filter) { | |
if (!arguments.length) { | |
return _chart._filter(); | |
} | |
return _chart._filter(dc.filters.TwoDimensionalFilter(filter)); | |
}); | |
function uniq(d, i, a) { | |
return !i || a[i - 1] !== d; | |
} | |
/** | |
#### .rows([values]) | |
Gets or sets the values used to create the rows of the heatmap, as an array. By default, all | |
the values will be fetched from the data using the value accessor, and they will be sorted in | |
ascending order. | |
**/ | |
_chart.rows = function (_) { | |
if (arguments.length) { | |
_rows = _; | |
return _chart; | |
} | |
if (_rows) { | |
return _rows; | |
} | |
var rowValues = _chart.data().map(_chart.valueAccessor()); | |
rowValues.sort(d3.ascending); | |
return d3.scale.ordinal().domain(rowValues.filter(uniq)); | |
}; | |
/** | |
#### .cols([keys]) | |
Gets or sets the keys used to create the columns of the heatmap, as an array. By default, all | |
the values will be fetched from the data using the key accessor, and they will be sorted in | |
ascending order. | |
**/ | |
_chart.cols = function (_) { | |
if (arguments.length) { | |
_cols = _; | |
return _chart; | |
} | |
if (_cols) { | |
return _cols; | |
} | |
var colValues = _chart.data().map(_chart.keyAccessor()); | |
colValues.sort(d3.ascending); | |
return d3.scale.ordinal().domain(colValues.filter(uniq)); | |
}; | |
_chart._doRender = function () { | |
_chart.resetSvg(); | |
_chartBody = _chart.svg() | |
.append('g') | |
.attr('class', 'heatmap') | |
.attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')'); | |
return _chart._doRedraw(); | |
}; | |
_chart._doRedraw = function () { | |
var rows = _chart.rows(), | |
cols = _chart.cols(), | |
rowCount = rows.domain().length, | |
colCount = cols.domain().length, | |
boxWidth = Math.floor(_chart.effectiveWidth() / colCount), | |
boxHeight = Math.floor(_chart.effectiveHeight() / rowCount); | |
cols.rangeRoundBands([0, _chart.effectiveWidth()]); | |
rows.rangeRoundBands([_chart.effectiveHeight(), 0]); | |
var boxes = _chartBody.selectAll('g.box-group').data(_chart.data(), function (d, i) { | |
return _chart.keyAccessor()(d, i) + '\0' + _chart.valueAccessor()(d, i); | |
}); | |
var gEnter = boxes.enter().append('g') | |
.attr('class', 'box-group'); | |
gEnter.append('rect') | |
.attr('class', 'heat-box') | |
.attr('fill', 'white') | |
.on('click', _chart.boxOnClick()); | |
if (_chart.renderTitle()) { | |
gEnter.append('title') | |
.text(_chart.title()); | |
} | |
dc.transition(boxes.selectAll('rect'), _chart.transitionDuration()) | |
.attr('x', function (d, i) { return cols(_chart.keyAccessor()(d, i)); }) | |
.attr('y', function (d, i) { return rows(_chart.valueAccessor()(d, i)); }) | |
.attr('rx', _xBorderRadius) | |
.attr('ry', _yBorderRadius) | |
.attr('fill', _chart.getColor) | |
.attr('width', boxWidth) | |
.attr('height', boxHeight); | |
boxes.exit().remove(); | |
var gCols = _chartBody.selectAll('g.cols'); | |
if (gCols.empty()) { | |
gCols = _chartBody.append('g').attr('class', 'cols axis'); | |
} | |
var gColsText = gCols.selectAll('text').data(cols.domain()); | |
gColsText.enter().append('text') | |
.attr('x', function (d) { return cols(d) + boxWidth / 2; }) | |
.style('text-anchor', 'middle') | |
.attr('y', _chart.effectiveHeight()) | |
.attr('dy', 12) | |
.on('click', _chart.xAxisOnClick()) | |
.text(function (d) { return d; }); | |
dc.transition(gColsText, _chart.transitionDuration()) | |
.text(function (d) { return d; }) | |
.attr('x', function (d) { return cols(d) + boxWidth / 2; }); | |
gColsText.exit().remove(); | |
var gRows = _chartBody.selectAll('g.rows'); | |
if (gRows.empty()) { | |
gRows = _chartBody.append('g').attr('class', 'rows axis'); | |
} | |
var gRowsText = gRows.selectAll('text').data(rows.domain()); | |
gRowsText.enter().append('text') | |
.attr('dy', 6) | |
.style('text-anchor', 'end') | |
.attr('x', 0) | |
.attr('dx', -2) | |
.on('click', _chart.yAxisOnClick()) | |
.text(function (d) { return d; }); | |
dc.transition(gRowsText, _chart.transitionDuration()) | |
.text(function (d) { return d; }) | |
.attr('y', function (d) { return rows(d) + boxHeight / 2; }); | |
gRowsText.exit().remove(); | |
if (_chart.hasFilter()) { | |
_chart.selectAll('g.box-group').each(function (d) { | |
if (_chart.isSelectedNode(d)) { | |
_chart.highlightSelected(this); | |
} else { | |
_chart.fadeDeselected(this); | |
} | |
}); | |
} else { | |
_chart.selectAll('g.box-group').each(function () { | |
_chart.resetHighlight(this); | |
}); | |
} | |
return _chart; | |
}; | |
/** | |
#### .boxOnClick([handler]) | |
Gets or sets the handler that fires when an individual cell is clicked in the heatmap. | |
By default, filtering of the cell will be toggled. | |
**/ | |
_chart.boxOnClick = function (f) { | |
if (!arguments.length) { | |
return _boxOnClick; | |
} | |
_boxOnClick = f; | |
return _chart; | |
}; | |
/** | |
#### .xAxisOnClick([handler]) | |
Gets or sets the handler that fires when a column tick is clicked in the x axis. | |
By default, if any cells in the column are unselected, the whole column will be selected, | |
otherwise the whole column will be unselected. | |
**/ | |
_chart.xAxisOnClick = function (f) { | |
if (!arguments.length) { | |
return _xAxisOnClick; | |
} | |
_xAxisOnClick = f; | |
return _chart; | |
}; | |
/** | |
#### .yAxisOnClick([handler]) | |
Gets or sets the handler that fires when a row tick is clicked in the y axis. | |
By default, if any cells in the row are unselected, the whole row will be selected, | |
otherwise the whole row will be unselected. | |
**/ | |
_chart.yAxisOnClick = function (f) { | |
if (!arguments.length) { | |
return _yAxisOnClick; | |
} | |
_yAxisOnClick = f; | |
return _chart; | |
}; | |
/** | |
#### .xBorderRadius([value]) | |
Gets or sets the X border radius. Set to 0 to get full rectangles. Default: 6.75 | |
*/ | |
_chart.xBorderRadius = function (d) { | |
if (!arguments.length) { | |
return _xBorderRadius; | |
} | |
_xBorderRadius = d; | |
return _chart; | |
}; | |
/** | |
#### .xBorderRadius([value]) | |
Gets or sets the Y border radius. Set to 0 to get full rectangles. Default: 6.75 | |
*/ | |
_chart.yBorderRadius = function (d) { | |
if (!arguments.length) { | |
return _yBorderRadius; | |
} | |
_yBorderRadius = d; | |
return _chart; | |
}; | |
_chart.isSelectedNode = function (d) { | |
return _chart.hasFilter(d.key); | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
// https://github.com/d3/d3-plugins/blob/master/box/box.js | |
(function () { | |
// Inspired by http://informationandvisualization.de/blog/box-plot | |
d3.box = function () { | |
var width = 1, | |
height = 1, | |
duration = 0, | |
domain = null, | |
value = Number, | |
whiskers = boxWhiskers, | |
quartiles = boxQuartiles, | |
tickFormat = null; | |
// For each small multiple… | |
function box(g) { | |
g.each(function (d, i) { | |
d = d.map(value).sort(d3.ascending); | |
var g = d3.select(this), | |
n = d.length, | |
min = d[0], | |
max = d[n - 1]; | |
// Compute quartiles. Must return exactly 3 elements. | |
var quartileData = d.quartiles = quartiles(d); | |
// Compute whiskers. Must return exactly 2 elements, or null. | |
var whiskerIndices = whiskers && whiskers.call(this, d, i), | |
whiskerData = whiskerIndices && whiskerIndices.map(function (i) { return d[i]; }); | |
// Compute outliers. If no whiskers are specified, all data are 'outliers'. | |
// We compute the outliers as indices, so that we can join across transitions! | |
var outlierIndices = whiskerIndices ? | |
d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n)) : d3.range(n); | |
// Compute the new x-scale. | |
var x1 = d3.scale.linear() | |
.domain(domain && domain.call(this, d, i) || [min, max]) | |
.range([height, 0]); | |
// Retrieve the old x-scale, if this is an update. | |
var x0 = this.__chart__ || d3.scale.linear() | |
.domain([0, Infinity]) | |
.range(x1.range()); | |
// Stash the new scale. | |
this.__chart__ = x1; | |
// Note: the box, median, and box tick elements are fixed in number, | |
// so we only have to handle enter and update. In contrast, the outliers | |
// and other elements are variable, so we need to exit them! Variable | |
// elements also fade in and out. | |
// Update center line: the vertical line spanning the whiskers. | |
var center = g.selectAll('line.center') | |
.data(whiskerData ? [whiskerData] : []); | |
center.enter().insert('line', 'rect') | |
.attr('class', 'center') | |
.attr('x1', width / 2) | |
.attr('y1', function (d) { return x0(d[0]); }) | |
.attr('x2', width / 2) | |
.attr('y2', function (d) { return x0(d[1]); }) | |
.style('opacity', 1e-6) | |
.transition() | |
.duration(duration) | |
.style('opacity', 1) | |
.attr('y1', function (d) { return x1(d[0]); }) | |
.attr('y2', function (d) { return x1(d[1]); }); | |
center.transition() | |
.duration(duration) | |
.style('opacity', 1) | |
.attr('y1', function (d) { return x1(d[0]); }) | |
.attr('y2', function (d) { return x1(d[1]); }); | |
center.exit().transition() | |
.duration(duration) | |
.style('opacity', 1e-6) | |
.attr('y1', function (d) { return x1(d[0]); }) | |
.attr('y2', function (d) { return x1(d[1]); }) | |
.remove(); | |
// Update innerquartile box. | |
var box = g.selectAll('rect.box') | |
.data([quartileData]); | |
box.enter().append('rect') | |
.attr('class', 'box') | |
.attr('x', 0) | |
.attr('y', function (d) { return x0(d[2]); }) | |
.attr('width', width) | |
.attr('height', function (d) { return x0(d[0]) - x0(d[2]); }) | |
.transition() | |
.duration(duration) | |
.attr('y', function (d) { return x1(d[2]); }) | |
.attr('height', function (d) { return x1(d[0]) - x1(d[2]); }); | |
box.transition() | |
.duration(duration) | |
.attr('y', function (d) { return x1(d[2]); }) | |
.attr('height', function (d) { return x1(d[0]) - x1(d[2]); }); | |
// Update median line. | |
var medianLine = g.selectAll('line.median') | |
.data([quartileData[1]]); | |
medianLine.enter().append('line') | |
.attr('class', 'median') | |
.attr('x1', 0) | |
.attr('y1', x0) | |
.attr('x2', width) | |
.attr('y2', x0) | |
.transition() | |
.duration(duration) | |
.attr('y1', x1) | |
.attr('y2', x1); | |
medianLine.transition() | |
.duration(duration) | |
.attr('y1', x1) | |
.attr('y2', x1); | |
// Update whiskers. | |
var whisker = g.selectAll('line.whisker') | |
.data(whiskerData || []); | |
whisker.enter().insert('line', 'circle, text') | |
.attr('class', 'whisker') | |
.attr('x1', 0) | |
.attr('y1', x0) | |
.attr('x2', width) | |
.attr('y2', x0) | |
.style('opacity', 1e-6) | |
.transition() | |
.duration(duration) | |
.attr('y1', x1) | |
.attr('y2', x1) | |
.style('opacity', 1); | |
whisker.transition() | |
.duration(duration) | |
.attr('y1', x1) | |
.attr('y2', x1) | |
.style('opacity', 1); | |
whisker.exit().transition() | |
.duration(duration) | |
.attr('y1', x1) | |
.attr('y2', x1) | |
.style('opacity', 1e-6) | |
.remove(); | |
// Update outliers. | |
var outlier = g.selectAll('circle.outlier') | |
.data(outlierIndices, Number); | |
outlier.enter().insert('circle', 'text') | |
.attr('class', 'outlier') | |
.attr('r', 5) | |
.attr('cx', width / 2) | |
.attr('cy', function (i) { return x0(d[i]); }) | |
.style('opacity', 1e-6) | |
.transition() | |
.duration(duration) | |
.attr('cy', function (i) { return x1(d[i]); }) | |
.style('opacity', 1); | |
outlier.transition() | |
.duration(duration) | |
.attr('cy', function (i) { return x1(d[i]); }) | |
.style('opacity', 1); | |
outlier.exit().transition() | |
.duration(duration) | |
.attr('cy', function (i) { return x1(d[i]); }) | |
.style('opacity', 1e-6) | |
.remove(); | |
// Compute the tick format. | |
var format = tickFormat || x1.tickFormat(8); | |
// Update box ticks. | |
var boxTick = g.selectAll('text.box') | |
.data(quartileData); | |
boxTick.enter().append('text') | |
.attr('class', 'box') | |
.attr('dy', '.3em') | |
.attr('dx', function (d, i) { return i & 1 ? 6 : -6; }) | |
.attr('x', function (d, i) { return i & 1 ? width : 0; }) | |
.attr('y', x0) | |
.attr('text-anchor', function (d, i) { return i & 1 ? 'start' : 'end'; }) | |
.text(format) | |
.transition() | |
.duration(duration) | |
.attr('y', x1); | |
boxTick.transition() | |
.duration(duration) | |
.text(format) | |
.attr('y', x1); | |
// Update whisker ticks. These are handled separately from the box | |
// ticks because they may or may not exist, and we want don't want | |
// to join box ticks pre-transition with whisker ticks post-. | |
var whiskerTick = g.selectAll('text.whisker') | |
.data(whiskerData || []); | |
whiskerTick.enter().append('text') | |
.attr('class', 'whisker') | |
.attr('dy', '.3em') | |
.attr('dx', 6) | |
.attr('x', width) | |
.attr('y', x0) | |
.text(format) | |
.style('opacity', 1e-6) | |
.transition() | |
.duration(duration) | |
.attr('y', x1) | |
.style('opacity', 1); | |
whiskerTick.transition() | |
.duration(duration) | |
.text(format) | |
.attr('y', x1) | |
.style('opacity', 1); | |
whiskerTick.exit().transition() | |
.duration(duration) | |
.attr('y', x1) | |
.style('opacity', 1e-6) | |
.remove(); | |
}); | |
d3.timer.flush(); | |
} | |
box.width = function (x) { | |
if (!arguments.length) { | |
return width; | |
} | |
width = x; | |
return box; | |
}; | |
box.height = function (x) { | |
if (!arguments.length) { | |
return height; | |
} | |
height = x; | |
return box; | |
}; | |
box.tickFormat = function (x) { | |
if (!arguments.length) { | |
return tickFormat; | |
} | |
tickFormat = x; | |
return box; | |
}; | |
box.duration = function (x) { | |
if (!arguments.length) { | |
return duration; | |
} | |
duration = x; | |
return box; | |
}; | |
box.domain = function (x) { | |
if (!arguments.length) { | |
return domain; | |
} | |
domain = x === null ? x : d3.functor(x); | |
return box; | |
}; | |
box.value = function (x) { | |
if (!arguments.length) { | |
return value; | |
} | |
value = x; | |
return box; | |
}; | |
box.whiskers = function (x) { | |
if (!arguments.length) { | |
return whiskers; | |
} | |
whiskers = x; | |
return box; | |
}; | |
box.quartiles = function (x) { | |
if (!arguments.length) { | |
return quartiles; | |
} | |
quartiles = x; | |
return box; | |
}; | |
return box; | |
}; | |
function boxWhiskers(d) { | |
return [0, d.length - 1]; | |
} | |
function boxQuartiles(d) { | |
return [ | |
d3.quantile(d, 0.25), | |
d3.quantile(d, 0.5), | |
d3.quantile(d, 0.75) | |
]; | |
} | |
})(); | |
/** | |
## Box Plot | |
Includes: [Coordinate Grid Mixin](#coordinate-grid-mixin) | |
A box plot is a chart that depicts numerical data via their quartile ranges. | |
#### dc.boxPlot(parent[, chartGroup]) | |
Create a box plot instance and attach it to the given parent element. | |
Parameters: | |
* parent : string | node | selection - any valid | |
[d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) representing | |
a dom block element such as a div; or a dom element or d3 selection. | |
* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. | |
Interaction with a chart will only trigger events and redraws within the chart's group. | |
Returns: | |
A newly created box plot instance | |
```js | |
// create a box plot under #chart-container1 element using the default global chart group | |
var boxPlot1 = dc.boxPlot('#chart-container1'); | |
// create a box plot under #chart-container2 element using chart group A | |
var boxPlot2 = dc.boxPlot('#chart-container2', 'chartGroupA'); | |
``` | |
**/ | |
dc.boxPlot = function (parent, chartGroup) { | |
var _chart = dc.coordinateGridMixin({}); | |
// Returns a function to compute the interquartile range. | |
function DEFAULT_WHISKERS_IQR (k) { | |
return function (d) { | |
var q1 = d.quartiles[0], | |
q3 = d.quartiles[2], | |
iqr = (q3 - q1) * k, | |
i = -1, | |
j = d.length; | |
/*jshint -W116*/ | |
/*jshint -W035*/ | |
while (d[++i] < q1 - iqr) {} | |
while (d[--j] > q3 + iqr) {} | |
/*jshint +W116*/ | |
return [i, j]; | |
/*jshint +W035*/ | |
}; | |
} | |
var _whiskerIqrFactor = 1.5; | |
var _whiskersIqr = DEFAULT_WHISKERS_IQR; | |
var _whiskers = _whiskersIqr(_whiskerIqrFactor); | |
var _box = d3.box(); | |
var _tickFormat = null; | |
var _boxWidth = function (innerChartWidth, xUnits) { | |
if (_chart.isOrdinal()) { | |
return _chart.x().rangeBand(); | |
} else { | |
return innerChartWidth / (1 + _chart.boxPadding()) / xUnits; | |
} | |
}; | |
// default padding to handle min/max whisker text | |
_chart.yAxisPadding(12); | |
// default to ordinal | |
_chart.x(d3.scale.ordinal()); | |
_chart.xUnits(dc.units.ordinal); | |
// valueAccessor should return an array of values that can be coerced into numbers | |
// or if data is overloaded for a static array of arrays, it should be `Number`. | |
// Empty arrays are not included. | |
_chart.data(function (group) { | |
return group.all().map(function (d) { | |
d.map = function (accessor) { return accessor.call(d, d); }; | |
return d; | |
}).filter(function (d) { | |
var values = _chart.valueAccessor()(d); | |
return values.length !== 0; | |
}); | |
}); | |
/** | |
#### .boxPadding([padding]) | |
Get or set the spacing between boxes as a fraction of box size. Valid values are within 0-1. | |
See the [d3 docs](https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-ordinal_rangeBands) | |
for a visual description of how the padding is applied. | |
Default: 0.8 | |
**/ | |
_chart.boxPadding = _chart._rangeBandPadding; | |
_chart.boxPadding(0.8); | |
/** | |
#### .outerPadding([padding]) | |
Get or set the outer padding on an ordinal box chart. This setting has no effect on non-ordinal charts | |
or on charts with a custom `.boxWidth`. Will pad the width by `padding * barWidth` on each side of the chart. | |
Default: 0.5 | |
**/ | |
_chart.outerPadding = _chart._outerRangeBandPadding; | |
_chart.outerPadding(0.5); | |
/** | |
#### .boxWidth(width || function(innerChartWidth, xUnits) { ... }) | |
Get or set the numerical width of the boxplot box. The width may also be a function taking as | |
parameters the chart width excluding the right and left margins, as well as the number of x | |
units. | |
**/ | |
_chart.boxWidth = function (_) { | |
if (!arguments.length) { | |
return _boxWidth; | |
} | |
_boxWidth = d3.functor(_); | |
return _chart; | |
}; | |
var boxTransform = function (d, i) { | |
var xOffset = _chart.x()(_chart.keyAccessor()(d, i)); | |
return 'translate(' + xOffset + ', 0)'; | |
}; | |
_chart._preprocessData = function () { | |
if (_chart.elasticX()) { | |
_chart.x().domain([]); | |
} | |
}; | |
_chart.plotData = function () { | |
var _calculatedBoxWidth = _boxWidth(_chart.effectiveWidth(), _chart.xUnitCount()); | |
_box.whiskers(_whiskers) | |
.width(_calculatedBoxWidth) | |
.height(_chart.effectiveHeight()) | |
.value(_chart.valueAccessor()) | |
.domain(_chart.y().domain()) | |
.duration(_chart.transitionDuration()) | |
.tickFormat(_tickFormat); | |
var boxesG = _chart.chartBodyG().selectAll('g.box').data(_chart.data(), function (d) { return d.key; }); | |
renderBoxes(boxesG); | |
updateBoxes(boxesG); | |
removeBoxes(boxesG); | |
_chart.fadeDeselectedArea(); | |
}; | |
function renderBoxes(boxesG) { | |
var boxesGEnter = boxesG.enter().append('g'); | |
boxesGEnter | |
.attr('class', 'box') | |
.attr('transform', boxTransform) | |
.call(_box) | |
.on('click', function (d) { | |
_chart.filter(d.key); | |
_chart.redrawGroup(); | |
}); | |
} | |
function updateBoxes(boxesG) { | |
dc.transition(boxesG, _chart.transitionDuration()) | |
.attr('transform', boxTransform) | |
.call(_box) | |
.each(function () { | |
d3.select(this).select('rect.box').attr('fill', _chart.getColor); | |
}); | |
} | |
function removeBoxes(boxesG) { | |
boxesG.exit().remove().call(_box); | |
} | |
_chart.fadeDeselectedArea = function () { | |
if (_chart.hasFilter()) { | |
_chart.g().selectAll('g.box').each(function (d) { | |
if (_chart.isSelectedNode(d)) { | |
_chart.highlightSelected(this); | |
} else { | |
_chart.fadeDeselected(this); | |
} | |
}); | |
} else { | |
_chart.g().selectAll('g.box').each(function () { | |
_chart.resetHighlight(this); | |
}); | |
} | |
}; | |
_chart.isSelectedNode = function (d) { | |
return _chart.hasFilter(d.key); | |
}; | |
_chart.yAxisMin = function () { | |
var min = d3.min(_chart.data(), function (e) { | |
return d3.min(_chart.valueAccessor()(e)); | |
}); | |
return dc.utils.subtract(min, _chart.yAxisPadding()); | |
}; | |
_chart.yAxisMax = function () { | |
var max = d3.max(_chart.data(), function (e) { | |
return d3.max(_chart.valueAccessor()(e)); | |
}); | |
return dc.utils.add(max, _chart.yAxisPadding()); | |
}; | |
/** | |
#### .tickFormat() | |
Set the numerical format of the boxplot median, whiskers and quartile labels. Defaults to | |
integer formatting. | |
```js | |
// format ticks to 2 decimal places | |
chart.tickFormat(d3.format('.2f')); | |
``` | |
**/ | |
_chart.tickFormat = function (x) { | |
if (!arguments.length) { | |
return _tickFormat; | |
} | |
_tickFormat = x; | |
return _chart; | |
}; | |
return _chart.anchor(parent, chartGroup); | |
}; | |
// Renamed functions | |
dc.abstractBubbleChart = dc.bubbleMixin; | |
dc.baseChart = dc.baseMixin; | |
dc.capped = dc.capMixin; | |
dc.colorChart = dc.colorMixin; | |
dc.coordinateGridChart = dc.coordinateGridMixin; | |
dc.marginable = dc.marginMixin; | |
dc.stackableChart = dc.stackMixin; | |
// Expose d3 and crossfilter, so that clients in browserify | |
// case can obtain them if they need them. | |
dc.d3 = d3; | |
dc.crossfilter = crossfilter; | |
return dc;} | |
if(typeof define === "function" && define.amd) { | |
define(["d3", "crossfilter"], _dc); | |
} else if(typeof module === "object" && module.exports) { | |
var _d3 = require('d3'); | |
var _crossfilter = require('crossfilter'); | |
// When using npm + browserify, 'crossfilter' is a function, | |
// since package.json specifies index.js as main function, and it | |
// does special handling. When using bower + browserify, | |
// there's no main in bower.json (in fact, there's no bower.json), | |
// so we need to fix it. | |
if (typeof _crossfilter !== "function") { | |
_crossfilter = _crossfilter.crossfilter; | |
} | |
module.exports = _dc(_d3, _crossfilter); | |
} else { | |
this.dc = _dc(d3, crossfilter); | |
} | |
} | |
)(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
!function(){function a(a,b){"use strict";var c={version:"2.0.0-alpha.5",constants:{CHART_CLASS:"dc-chart",DEBUG_GROUP_CLASS:"debug",STACK_CLASS:"stack",DESELECTED_CLASS:"deselected",SELECTED_CLASS:"selected",NODE_INDEX_NAME:"__index__",GROUP_INDEX_NAME:"__group_index__",DEFAULT_CHART_GROUP:"__default_chart_group__",EVENT_DELAY:40,NEGLIGIBLE_NUMBER:1e-10},_renderlet:null};c.chartRegistry=function(){function a(a){return a||(a=c.constants.DEFAULT_CHART_GROUP),b[a]||(b[a]=[]),a}var b={};return{has:function(a){for(var c in b)if(b[c].indexOf(a)>=0)return!0;return!1},register:function(c,d){d=a(d),b[d].push(c)},deregister:function(c,d){d=a(d);for(var e=0;e<b[d].length;e++)if(b[d][e].anchorName()===c.anchorName()){b[d].splice(e,1);break}},clear:function(a){a?delete b[a]:b={}},list:function(c){return c=a(c),b[c]}}}(),c.registerChart=function(a,b){c.chartRegistry.register(a,b)},c.deregisterChart=function(a,b){c.chartRegistry.deregister(a,b)},c.hasChart=function(a){return c.chartRegistry.has(a)},c.deregisterAllCharts=function(a){c.chartRegistry.clear(a)},c.filterAll=function(a){for(var b=c.chartRegistry.list(a),d=0;d<b.length;++d)b[d].filterAll()},c.refocusAll=function(a){for(var b=c.chartRegistry.list(a),d=0;d<b.length;++d)b[d].focus&&b[d].focus()},c.renderAll=function(a){for(var b=c.chartRegistry.list(a),d=0;d<b.length;++d)b[d].render();null!==c._renderlet&&c._renderlet(a)},c.redrawAll=function(a){for(var b=c.chartRegistry.list(a),d=0;d<b.length;++d)b[d].redraw();null!==c._renderlet&&c._renderlet(a)},c.disableTransitions=!1,c.transition=function(a,b,d){if(0>=b||void 0===b||c.disableTransitions)return a;var e=a.transition().duration(b);return"function"==typeof d&&d(e),e},c.units={},c.units.integers=function(a,b){return Math.abs(b-a)},c.units.ordinal=function(a,b,c){return c},c.units.fp={},c.units.fp.precision=function(a){var b=function(a,d){var e=Math.abs((d-a)/b.resolution);return c.utils.isNegligible(e-Math.floor(e))?Math.floor(e):Math.ceil(e)};return b.resolution=a,b},c.round={},c.round.floor=function(a){return Math.floor(a)},c.round.ceil=function(a){return Math.ceil(a)},c.round.round=function(a){return Math.round(a)},c.override=function(a,b,c){var d=a[b];a["_"+b]=d,a[b]=c},c.renderlet=function(a){return arguments.length?(c._renderlet=a,c):c._renderlet},c.instanceOfChart=function(a){return a instanceof Object&&a.__dcFlag__&&!0},c.errors={},c.errors.Exception=function(a){var b=a||"Unexpected internal error";this.message=b,this.toString=function(){return b}},c.errors.InvalidStateException=function(){c.errors.Exception.apply(this,arguments)},c.dateFormat=a.time.format("%m/%d/%Y"),c.printers={},c.printers.filters=function(a){for(var b="",d=0;d<a.length;++d)d>0&&(b+=", "),b+=c.printers.filter(a[d]);return b},c.printers.filter=function(a){var b="";return"undefined"!=typeof a&&null!==a&&(a instanceof Array?a.length>=2?b="["+c.utils.printSingleValue(a[0])+" -> "+c.utils.printSingleValue(a[1])+"]":a.length>=1&&(b=c.utils.printSingleValue(a[0])):b=c.utils.printSingleValue(a)),b},c.pluck=function(a,b){return b?function(c,d){return b.call(c,c[a],d)}:function(b){return b[a]}},c.utils={},c.utils.printSingleValue=function(a){var b=""+a;return a instanceof Date?b=c.dateFormat(a):"string"==typeof a?b=a:c.utils.isFloat(a)?b=c.utils.printSingleValue.fformat(a):c.utils.isInteger(a)&&(b=Math.round(a)),b},c.utils.printSingleValue.fformat=a.format(".2f"),c.utils.add=function(a,b){if("string"==typeof b&&(b=b.replace("%","")),a instanceof Date){"string"==typeof b&&(b=+b);var c=new Date;return c.setTime(a.getTime()),c.setDate(a.getDate()+b),c}if("string"==typeof b){var d=+b/100;return a>0?a*(1+d):a*(1-d)}return a+b},c.utils.subtract=function(a,b){if("string"==typeof b&&(b=b.replace("%","")),a instanceof Date){"string"==typeof b&&(b=+b);var c=new Date;return c.setTime(a.getTime()),c.setDate(a.getDate()-b),c}if("string"==typeof b){var d=+b/100;return 0>a?a*(1+d):a*(1-d)}return a-b},c.utils.isNumber=function(a){return a===+a},c.utils.isFloat=function(a){return a===+a&&a!==(0|a)},c.utils.isInteger=function(a){return a===+a&&a===(0|a)},c.utils.isNegligible=function(a){return!c.utils.isNumber(a)||a<c.constants.NEGLIGIBLE_NUMBER&&a>-c.constants.NEGLIGIBLE_NUMBER},c.utils.clamp=function(a,b,c){return b>a?b:a>c?c:a};var d=0;return c.utils.uniqueId=function(){return++d},c.utils.nameToId=function(a){return a.toLowerCase().replace(/[\s]/g,"_").replace(/[\.']/g,"")},c.utils.appendOrSelect=function(a,b,c){c=c||b;var d=a.select(b);return d.empty()&&(d=a.append(c)),d},c.utils.safeNumber=function(a){return c.utils.isNumber(+a)?+a:0},c.logger={},c.logger.enableDebugLog=!1,c.logger.warn=function(a){return console&&(console.warn?console.warn(a):console.log&&console.log(a)),c.logger},c.logger.debug=function(a){return c.logger.enableDebugLog&&console&&(console.debug?console.debug(a):console.log&&console.log(a)),c.logger},c.events={current:null},c.events.trigger=function(a,b){return b?(c.events.current=a,void setTimeout(function(){a===c.events.current&&a()},b)):void a()},c.filters={},c.filters.RangedFilter=function(a,b){var c=new Array(a,b);return c.isFiltered=function(a){return a>=this[0]&&a<this[1]},c},c.filters.TwoDimensionalFilter=function(a){if(null===a)return null;var b=a;return b.isFiltered=function(a){return a.length&&a.length===b.length&&a[0]===b[0]&&a[1]===b[1]},b},c.filters.RangedTwoDimensionalFilter=function(a){if(null===a)return null;var b,c=a;return b=c[0]instanceof Array?[[Math.min(a[0][0],a[1][0]),Math.min(a[0][1],a[1][1])],[Math.max(a[0][0],a[1][0]),Math.max(a[0][1],a[1][1])]]:[[a[0],-1/0],[a[1],1/0]],c.isFiltered=function(a){var c,d;if(a instanceof Array){if(2!==a.length)return!1;c=a[0],d=a[1]}else c=a,d=b[0][1];return c>=b[0][0]&&c<b[1][0]&&d>=b[0][1]&&d<b[1][1]},c},c.baseMixin=function(d){function e(){return m=d.root().append("svg").attr("width",d.width()).attr("height",d.height())}function f(a){if(!d[a]||!d[a]())throw new c.errors.InvalidStateException("Mandatory attribute chart."+a+" is missing on chart[#"+d.anchorName()+"]")}function g(){if(d.dimension()&&d.dimension().filter){var a=J(d.dimension(),I);I=a?a:I}}function h(){for(var a=0;a<E.length;++a)E[a](d)}d.__dcFlag__=c.utils.uniqueId();var i,j,k,l,m,n,o,p=200,q=function(a){var b=a&&a.getBoundingClientRect&&a.getBoundingClientRect().width;return b&&b>p?b:p},r=q,s=200,t=function(a){var b=a&&a.getBoundingClientRect&&a.getBoundingClientRect().height;return b&&b>s?b:s},u=t,v=c.pluck("key"),w=c.pluck("value"),x=c.pluck("key"),y=c.pluck("key"),z=!1,A=function(a){return d.keyAccessor()(a)+": "+d.valueAccessor()(a)},B=!0,C=750,D=c.printers.filters,E=[],F=["dimension","group"],G=c.constants.DEFAULT_CHART_GROUP,H=a.dispatch("preRender","postRender","preRedraw","postRedraw","filtered","zoomed"),I=[],J=function(a,b){return a.filter(null),0===b.length?a.filter(null):a.filterFunction(function(a){for(var c=0;c<b.length;c++){var d=b[c];if(d.isFiltered&&d.isFiltered(a))return!0;if(a>=d&&d>=a)return!0}return!1}),b},K=function(a){return a.all()};d.width=function(b){return arguments.length?(r=a.functor(b||q),d):r(l.node())},d.height=function(b){return arguments.length?(u=a.functor(b||t),d):u(l.node())},d.minWidth=function(a){return arguments.length?(p=a,d):p},d.minHeight=function(a){return arguments.length?(s=a,d):s},d.dimension=function(a){return arguments.length?(i=a,d.expireCache(),d):i},d.data=function(b){return arguments.length?(K=a.functor(b),d.expireCache(),d):K.call(d,j)},d.group=function(a,b){return arguments.length?(j=a,d._groupName=b,d.expireCache(),d):j},d.ordering=function(a){return arguments.length?(y=a,n=b.quicksort.by(y),d.expireCache(),d):y},d._computeOrderedGroups=function(a){var c=a.slice(0);return c.length<=1?c:(n||(n=b.quicksort.by(y)),n(c,0,c.length))},d.filterAll=function(){return d.filter(null)},d.select=function(a){return l.select(a)},d.selectAll=function(a){return l?l.selectAll(a):null},d.anchor=function(b,e){return arguments.length?(c.instanceOfChart(b)?(k=b.anchor(),l=b.root()):(k=b,l=a.select(k),l.classed(c.constants.CHART_CLASS,!0),c.registerChart(d,e)),G=e,d):k},d.anchorName=function(){var a=d.anchor();return a&&a.id?a.id:a&&a.replace?a.replace("#",""):""+d.chartID()},d.root=function(a){return arguments.length?(l=a,d):l},d.svg=function(a){return arguments.length?(m=a,d):m},d.resetSvg=function(){return d.select("svg").remove(),e()},d.filterPrinter=function(a){return arguments.length?(D=a,d):D},d.turnOnControls=function(){return l&&(d.selectAll(".reset").style("display",null),d.selectAll(".filter").text(D(d.filters())).style("display",null)),d},d.turnOffControls=function(){return l&&(d.selectAll(".reset").style("display","none"),d.selectAll(".filter").style("display","none").text(d.filter())),d},d.transitionDuration=function(a){return arguments.length?(C=a,d):C},d._mandatoryAttributes=function(a){return arguments.length?(F=a,d):F},d.render=function(){H.preRender(d),F&&F.forEach(f);var a=d._doRender();return o&&o.render(),d._activateRenderlets("postRender"),a},d._activateRenderlets=function(a){d.transitionDuration()>0&&m?m.transition().duration(d.transitionDuration()).each("end",function(){h(),a&&H[a](d)}):(h(),a&&H[a](d))},d.redraw=function(){H.preRedraw(d);var a=d._doRedraw();return o&&o.render(),d._activateRenderlets("postRedraw"),a},d.redrawGroup=function(){c.redrawAll(d.chartGroup())},d.renderGroup=function(){c.renderAll(d.chartGroup())},d._invokeFilteredListener=function(a){void 0!==a&&H.filtered(d,a)},d._invokeZoomedListener=function(){H.zoomed(d)};var L=function(a,b){return null===b||"undefined"==typeof b?a.length>0:a.some(function(a){return a>=b&&b>=a})};d.hasFilterHandler=function(a){return arguments.length?(L=a,d):L},d.hasFilter=function(a){return L(I,a)};var M=function(a,b){for(var c=0;c<a.length;c++)if(a[c]<=b&&a[c]>=b){a.splice(c,1);break}return a};d.removeFilterHandler=function(a){return arguments.length?(M=a,d):M};var N=function(a,b){return a.push(b),a};d.addFilterHandler=function(a){return arguments.length?(N=a,d):N};var O=function(){return[]};return d.resetFilterHandler=function(a){return arguments.length?(O=a,d):O},d.replaceFilter=function(a){I=[],d.filter(a)},d.filter=function(a){return arguments.length?(a instanceof Array&&a[0]instanceof Array&&!a.isFiltered?a[0].forEach(function(a){d.hasFilter(a)?M(I,a):N(I,a)}):null===a?I=O(I):d.hasFilter(a)?M(I,a):N(I,a),g(),d._invokeFilteredListener(a),null!==l&&d.hasFilter()?d.turnOnControls():d.turnOffControls(),d):I.length>0?I[0]:null},d.filters=function(){return I},d.highlightSelected=function(b){a.select(b).classed(c.constants.SELECTED_CLASS,!0),a.select(b).classed(c.constants.DESELECTED_CLASS,!1)},d.fadeDeselected=function(b){a.select(b).classed(c.constants.SELECTED_CLASS,!1),a.select(b).classed(c.constants.DESELECTED_CLASS,!0)},d.resetHighlight=function(b){a.select(b).classed(c.constants.SELECTED_CLASS,!1),a.select(b).classed(c.constants.DESELECTED_CLASS,!1)},d.onClick=function(a){var b=d.keyAccessor()(a);c.events.trigger(function(){d.filter(b),d.redrawGroup()})},d.filterHandler=function(a){return arguments.length?(J=a,d):J},d._doRender=function(){return d},d._doRedraw=function(){return d},d.legendables=function(){return[]},d.legendHighlight=function(){},d.legendReset=function(){},d.legendToggle=function(){},d.isLegendableHidden=function(){return!1},d.keyAccessor=function(a){return arguments.length?(v=a,d):v},d.valueAccessor=function(a){return arguments.length?(w=a,d):w},d.label=function(a){return arguments.length?(x=a,z=!0,d):x},d.renderLabel=function(a){return arguments.length?(z=a,d):z},d.title=function(a){return arguments.length?(A=a,d):A},d.renderTitle=function(a){return arguments.length?(B=a,d):B},d.renderlet=function(a){return E.push(a),d},d.chartGroup=function(a){return arguments.length?(G=a,d):G},d.expireCache=function(){return d},d.legend=function(a){return arguments.length?(o=a,o.parent(d),d):o},d.chartID=function(){return d.__dcFlag__},d.options=function(a){for(var b in a)"function"==typeof d[b]?d[b].call(d,a[b]):c.logger.debug("Not a valid option setter name: "+b);return d},d.on=function(a,b){return H.on(a,b),d},d},c.marginMixin=function(a){var b={top:10,right:50,bottom:30,left:30};return a.margins=function(c){return arguments.length?(b=c,a):b},a.effectiveWidth=function(){return a.width()-a.margins().left-a.margins().right},a.effectiveHeight=function(){return a.height()-a.margins().top-a.margins().bottom},a},c.colorMixin=function(b){var c=a.scale.category20c(),d=!0,e=function(a){return b.keyAccessor()(a)};return b.colors=function(d){return arguments.length?(c=d instanceof Array?a.scale.quantize().range(d):a.functor(d),b):c},b.ordinalColors=function(c){return b.colors(a.scale.ordinal().range(c))},b.linearColors=function(c){return b.colors(a.scale.linear().range(c).interpolate(a.interpolateHcl))},b.colorAccessor=function(a){return arguments.length?(e=a,d=!1,b):e},b.defaultColorAccessor=function(){return d},b.colorDomain=function(a){return arguments.length?(c.domain(a),b):c.domain()},b.calculateColorDomain=function(){var d=[a.min(b.data(),b.colorAccessor()),a.max(b.data(),b.colorAccessor())];c.domain(d)},b.getColor=function(a,b){return c(e.call(this,a,b))},b.colorCalculator=function(a){return arguments.length?(b.getColor=a,b):b.getColor},b},c.coordinateGridMixin=function(b){function d(){U=!0,W&&(b.x().domain(l(b.x().domain(),y)),F&&b.x().domain(l(b.x().domain(),F.x().domain())));var a=b.x().domain(),d=c.filters.RangedFilter(a[0],a[1]);b.replaceFilter(d),b.rescale(),b.redraw(),F&&!m(b.filter(),F.filter())&&c.events.trigger(function(){F.replaceFilter(d),F.redraw()}),b._invokeZoomedListener(),c.events.trigger(function(){b.redrawGroup()},c.constants.EVENT_DELAY),U=!m(a,y)}function e(a){b.isOrdinal()?(b.elasticX()||0===x.domain().length)&&x.domain(b._ordinalXDomain()):b.elasticX()&&x.domain([b.xAxisMin(),b.xAxisMax()]);var c=x.domain();(!A||c.some(function(a,b){return a!==A[b]}))&&b.rescale(),A=c,b.isOrdinal()?x.rangeBands([0,b.xAxisLength()],bb,b._useOuterPadding()?ab:0):x.range([0,b.xAxisLength()]),H=H.scale(b.x()),f(a)}function f(a){var d=a.selectAll("g."+q);if(T){d.empty()&&(d=a.insert("g",":first-child").attr("class",o+" "+q).attr("transform","translate("+b.margins().left+","+b.margins().top+")"));var e=H.tickValues()?H.tickValues():"function"==typeof x.ticks?x.ticks(H.ticks()[0]):x.domain(),f=d.selectAll("line").data(e),g=f.enter().append("line").attr("x1",function(a){return x(a)}).attr("y1",b._xAxisY()-b.margins().top).attr("x2",function(a){return x(a)}).attr("y2",0).attr("opacity",0);c.transition(g,b.transitionDuration()).attr("opacity",1),c.transition(f,b.transitionDuration()).attr("x1",function(a){return x(a)}).attr("y1",b._xAxisY()-b.margins().top).attr("x2",function(a){return x(a)}).attr("y2",0),f.exit().remove()}else d.selectAll("line").remove()}function g(){return b._xAxisY()-b.margins().top}function h(){return b.anchorName().replace(/[ .#]/g,"-")+"-clip"}function i(){var a=c.utils.appendOrSelect(u,"defs"),d=h(),e=c.utils.appendOrSelect(a,"#"+d,"clipPath").attr("id",d),f=2*_;c.utils.appendOrSelect(e,"rect").attr("width",b.xAxisLength()+f).attr("height",b.yAxisHeight()+f).attr("transform","translate(-"+_+", -"+_+")")}function j(a){b.isOrdinal()&&(R=!1),e(b.g()),b._prepareYAxis(b.g()),b.plotData(),(b.elasticX()||U||a)&&b.renderXAxis(b.g()),(b.elasticY()||a)&&b.renderYAxis(b.g()),a?b.renderBrush(b.g()):b.redrawBrush(b.g())}function k(){$?b._enableMouseZoom():Z&&b._disableMouseZoom()}function l(b,c){var d=[];return d[0]=a.max([b[0],c[0]]),d[1]=a.min([b[1],c[1]]),d}function m(a,b){return a||b?a&&b?0===a.length&&0===b.length?!0:a[0].valueOf()===b[0].valueOf()&&a[1].valueOf()===b[1].valueOf()?!0:!1:!1:!0}function n(a){return a instanceof Array&&a.length>1}var o="grid-line",p="horizontal",q="vertical",r="y-axis-label",s="x-axis-label",t=12;b=c.colorMixin(c.marginMixin(c.baseMixin(b))),b.colors(a.scale.category10()),b._mandatoryAttributes().push("x");var u,v,w,x,y,z,A,B,C,D,E,F,G,H=a.svg.axis().orient("bottom"),I=c.units.integers,J=0,K=!1,L=0,M=a.svg.axis().orient("left"),N=0,O=!1,P=0,Q=a.svg.brush(),R=!0,S=!1,T=!1,U=!1,V=[1,1/0],W=!0,X=a.behavior.zoom().on("zoom",d),Y=a.behavior.zoom().on("zoom",null),Z=!1,$=!1,_=0,ab=.5,bb=0,cb=!1;return b.rescale=function(){E=void 0},b.rangeChart=function(a){return arguments.length?(F=a,F.focusChart(b),b):F},b.zoomScale=function(a){return arguments.length?(V=a,b):V},b.zoomOutRestrict=function(a){return arguments.length?(V[0]=a?1:0,W=a,b):W},b._generateG=function(a){return u=void 0===a?b.svg():a,v=u.append("g"),w=v.append("g").attr("class","chart-body").attr("transform","translate("+b.margins().left+", "+b.margins().top+")").attr("clip-path","url(#"+h()+")"),v},b.g=function(a){return arguments.length?(v=a,b):v},b.mouseZoomable=function(a){return arguments.length?($=a,b):$},b.chartBodyG=function(a){return arguments.length?(w=a,b):w},b.x=function(a){return arguments.length?(x=a,y=x.domain(),b):x},b.xOriginalDomain=function(){return y},b.xUnits=function(a){return arguments.length?(I=a,b):I},b.xAxis=function(a){return arguments.length?(H=a,b):H},b.elasticX=function(a){return arguments.length?(K=a,b):K},b.xAxisPadding=function(a){return arguments.length?(J=a,b):J},b.xUnitCount=function(){if(void 0===E){var a=b.xUnits()(b.x().domain()[0],b.x().domain()[1],b.x().domain());E=a instanceof Array?a.length:a}return E},b.useRightYAxis=function(a){return arguments.length?(cb=a,b):cb},b.isOrdinal=function(){return b.xUnits()===c.units.ordinal},b._useOuterPadding=function(){return!0},b._ordinalXDomain=function(){var a=b._computeOrderedGroups(b.data());return a.map(b.keyAccessor())},b.renderXAxis=function(a){var d=a.selectAll("g.x");d.empty()&&(d=a.append("g").attr("class","axis x").attr("transform","translate("+b.margins().left+","+b._xAxisY()+")"));var e=a.selectAll("text."+s);e.empty()&&b.xAxisLabel()&&(e=a.append("text").attr("transform","translate("+(b.margins().left+b.xAxisLength()/2)+","+(b.height()-L)+")").attr("class",s).attr("text-anchor","middle").text(b.xAxisLabel())),b.xAxisLabel()&&e.text()!==b.xAxisLabel()&&e.text(b.xAxisLabel()),c.transition(d,b.transitionDuration()).call(H)},b._xAxisY=function(){return b.height()-b.margins().bottom},b.xAxisLength=function(){return b.effectiveWidth()},b.xAxisLabel=function(a,c){return arguments.length?(z=a,b.margins().bottom-=L,L=void 0===c?t:c,b.margins().bottom+=L,b):z},b._prepareYAxis=function(c){if(void 0===B||b.elasticY()){B=a.scale.linear();var d=b.yAxisMin()||0,e=b.yAxisMax()||0;B.domain([d,e]).rangeRound([b.yAxisHeight(),0])}B.range([b.yAxisHeight(),0]),M=M.scale(B),cb&&M.orient("right"),b._renderHorizontalGridLinesForAxis(c,B,M)},b.renderYAxisLabel=function(a,c,d,e){e=e||P;var f=b.g().selectAll("text."+r+"."+a+"-label");if(f.empty()&&c){var g=b.margins().top+b.yAxisHeight()/2;f=b.g().append("text").attr("transform","translate("+e+","+g+"),rotate("+d+")").attr("class",r+" "+a+"-label").attr("text-anchor","middle").text(c)}c&&f.text()!==c&&f.text(c)},b.renderYAxisAt=function(a,d,e){var f=b.g().selectAll("g."+a);f.empty()&&(f=b.g().append("g").attr("class","axis "+a).attr("transform","translate("+e+","+b.margins().top+")")),c.transition(f,b.transitionDuration()).call(d)},b.renderYAxis=function(){var a=cb?b.width()-b.margins().right:b._yAxisX();b.renderYAxisAt("y",M,a);var c=cb?b.width()-P:P,d=cb?90:-90;b.renderYAxisLabel("y",b.yAxisLabel(),d,c)},b._renderHorizontalGridLinesForAxis=function(a,d,e){var f=a.selectAll("g."+p);if(S){var g=e.tickValues()?e.tickValues():d.ticks(e.ticks()[0]);f.empty()&&(f=a.insert("g",":first-child").attr("class",o+" "+p).attr("transform","translate("+b.margins().left+","+b.margins().top+")"));var h=f.selectAll("line").data(g),i=h.enter().append("line").attr("x1",1).attr("y1",function(a){return d(a)}).attr("x2",b.xAxisLength()).attr("y2",function(a){return d(a)}).attr("opacity",0);c.transition(i,b.transitionDuration()).attr("opacity",1),c.transition(h,b.transitionDuration()).attr("x1",1).attr("y1",function(a){return d(a)}).attr("x2",b.xAxisLength()).attr("y2",function(a){return d(a)}),h.exit().remove()}else f.selectAll("line").remove()},b._yAxisX=function(){return b.useRightYAxis()?b.width()-b.margins().right:b.margins().left},b.yAxisLabel=function(a,c){return arguments.length?(C=a,b.margins().left-=P,P=void 0===c?t:c,b.margins().left+=P,b):C},b.y=function(a){return arguments.length?(B=a,b):B},b.yAxis=function(a){return arguments.length?(M=a,b):M},b.elasticY=function(a){return arguments.length?(O=a,b):O},b.renderHorizontalGridLines=function(a){return arguments.length?(S=a,b):S},b.renderVerticalGridLines=function(a){return arguments.length?(T=a,b):T},b.xAxisMin=function(){var d=a.min(b.data(),function(a){return b.keyAccessor()(a)});return c.utils.subtract(d,J)},b.xAxisMax=function(){var d=a.max(b.data(),function(a){return b.keyAccessor()(a)});return c.utils.add(d,J)},b.yAxisMin=function(){var d=a.min(b.data(),function(a){return b.valueAccessor()(a)});return c.utils.subtract(d,N)},b.yAxisMax=function(){var d=a.max(b.data(),function(a){return b.valueAccessor()(a)});return c.utils.add(d,N)},b.yAxisPadding=function(a){return arguments.length?(N=a,b):N},b.yAxisHeight=function(){return b.effectiveHeight()},b.round=function(a){return arguments.length?(D=a,b):D},b._rangeBandPadding=function(a){return arguments.length?(bb=a,b):bb},b._outerRangeBandPadding=function(a){return arguments.length?(ab=a,b):ab},c.override(b,"filter",function(a){return arguments.length?(b._filter(a),a?b.brush().extent(a):b.brush().clear(),b):b._filter()}),b.brush=function(a){return arguments.length?(Q=a,b):Q},b.renderBrush=function(a){if(R){Q.on("brush",b._brushing),Q.on("brushstart",b._disableMouseZoom),Q.on("brushend",k);var c=a.append("g").attr("class","brush").attr("transform","translate("+b.margins().left+","+b.margins().top+")").call(Q.x(b.x()));b.setBrushY(c),b.setHandlePaths(c),b.hasFilter()&&b.redrawBrush(a)}},b.setHandlePaths=function(a){a.selectAll(".resize").append("path").attr("d",b.resizeHandlePath)},b.setBrushY=function(a){a.selectAll("rect").attr("height",g())},b.extendBrush=function(){var a=Q.extent();return b.round()&&(a[0]=a.map(b.round())[0],a[1]=a.map(b.round())[1],v.select(".brush").call(Q.extent(a))),a},b.brushIsEmpty=function(a){return Q.empty()||!a||a[1]<=a[0]},b._brushing=function(){var a=b.extendBrush();if(b.redrawBrush(v),b.brushIsEmpty(a))c.events.trigger(function(){b.filter(null),b.redrawGroup()},c.constants.EVENT_DELAY);else{var d=c.filters.RangedFilter(a[0],a[1]);c.events.trigger(function(){b.replaceFilter(d),b.redrawGroup()},c.constants.EVENT_DELAY)}},b.redrawBrush=function(a){if(R){b.filter()&&b.brush().empty()&&b.brush().extent(b.filter());var c=a.select("g.brush");c.call(b.brush().x(b.x())),b.setBrushY(c)}b.fadeDeselectedArea()},b.fadeDeselectedArea=function(){},b.resizeHandlePath=function(a){var b=+("e"===a),c=b?1:-1,d=g()/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)},b.clipPadding=function(a){return arguments.length?(_=a,b):_},b._preprocessData=function(){},b._doRender=function(){return b.resetSvg(),b._preprocessData(),b._generateG(),i(),j(!0),k(),b},b._doRedraw=function(){return b._preprocessData(),j(!1),i(),b},b._enableMouseZoom=function(){Z=!0,X.x(b.x()).scaleExtent(V).size([b.width(),b.height()]).duration(b.transitionDuration()),b.root().call(X)},b._disableMouseZoom=function(){b.root().call(Y)},b.focus=function(a){b.x().domain(n(a)?a:y),X.x(b.x()),d()},b.refocused=function(){return U},b.focusChart=function(a){return arguments.length?(G=a,b.on("filtered",function(a){a.filter()?m(a.filter(),G.filter())||c.events.trigger(function(){G.focus(a.filter())}):c.events.trigger(function(){G.x().domain(G.xOriginalDomain())})}),b):G},b.brushOn=function(a){return arguments.length?(R=a,b):R},b},c.stackMixin=function(b){function d(a,c){var d=a.accessor||b.valueAccessor();return a.name=String(a.name||c),a.values=a.group.all().map(function(c,e){return{x:b.keyAccessor()(c,e),y:a.hidden?null:d(c,e),data:c,layer:a.name,hidden:a.hidden}}),a.values=a.values.filter(e()),a.values}function e(){if(!b.x())return a.functor(!0);var c=b.x().domain();return b.isOrdinal()?function(){return!0}:b.elasticX()?function(){return!0}:function(a){return a.x>=c[0]&&a.x<=c[c.length-1]}}function f(a){var b=j.map(c.pluck("name")).indexOf(a);return j[b]}function g(){return b.data().reduce(function(a,b){return a.concat(b.values)},[])}function h(a){return!a.hidden}var i=a.layout.stack().values(d),j=[],k={},l=!1;return b.stack=function(a,c,d){if(!arguments.length)return j;arguments.length<=2&&(d=c);var e={group:a};return"string"==typeof c&&(e.name=c),"function"==typeof d&&(e.accessor=d),j.push(e),b},c.override(b,"group",function(a,c,d){return arguments.length?(j=[],k={},b.stack(a,c),d&&b.valueAccessor(d),b._group(a,c)):b._group()}),b.hidableStacks=function(a){return arguments.length?(l=a,b):l},b.hideStack=function(a){var c=f(a);return c&&(c.hidden=!0),b},b.showStack=function(a){var c=f(a);return c&&(c.hidden=!1),b},b.getValueAccessorByIndex=function(a){return j[a].accessor||b.valueAccessor()},b.yAxisMin=function(){var d=a.min(g(),function(a){return a.y+a.y0<a.y0?a.y+a.y0:a.y0});return c.utils.subtract(d,b.yAxisPadding())},b.yAxisMax=function(){var d=a.max(g(),function(a){return a.y+a.y0});return c.utils.add(d,b.yAxisPadding())},b.xAxisMin=function(){var d=a.min(g(),c.pluck("x"));return c.utils.subtract(d,b.xAxisPadding())},b.xAxisMax=function(){var d=a.max(g(),c.pluck("x"));return c.utils.add(d,b.xAxisPadding())},c.override(b,"title",function(a,c){return a?"function"==typeof a?b._title(a):a===b._groupName&&"function"==typeof c?b._title(c):"function"!=typeof c?k[a]||b._title():(k[a]=c,b):b._title()}),b.stackLayout=function(a){return arguments.length?(i=a,b):i},b.data(function(){var a=j.filter(h);return a.length?b.stackLayout()(a):[]}),b._ordinalXDomain=function(){return g().map(c.pluck("x"))},b.colorAccessor(function(a){var b=this.layer||this.name||a.name||a.layer;return b}),b.legendables=function(){return j.map(function(a,c){return{chart:b,name:a.name,hidden:a.hidden||!1,color:b.getColor.call(a,a.values,c)}})},b.isLegendableHidden=function(a){var b=f(a.name);return b?b.hidden:!1},b.legendToggle=function(a){l&&(b.isLegendableHidden(a)?b.showStack(a.name):b.hideStack(a.name),b.renderGroup())},b},c.capMixin=function(b){var d=1/0,e="Others",f=function(c){var d=a.sum(c,b.valueAccessor()),f=b.group().all(),g=a.sum(f,b.valueAccessor()),h=c.map(b.keyAccessor()),i=f.map(b.keyAccessor()),j=a.set(h),k=i.filter(function(a){return!j.has(a)});return g>d?c.concat([{others:k,key:e,value:g-d}]):c};return b.cappedKeyAccessor=function(a,c){return a.others?a.key:b.keyAccessor()(a,c)},b.cappedValueAccessor=function(a,c){return a.others?a.value:b.valueAccessor()(a,c)},b.data(function(a){if(1/0===d)return b._computeOrderedGroups(a.all());var c=a.top(d);return c=b._computeOrderedGroups(c),f?f(c):c}),b.cap=function(a){return arguments.length?(d=a,b):d},b.othersLabel=function(a){return arguments.length?(e=a,b):e},b.othersGrouper=function(a){return arguments.length?(f=a,b):f},c.override(b,"onClick",function(a){a.others&&b.filter([a.others]),b._onClick(a)}),b},c.bubbleMixin=function(b){var d=.3,e=10;b.BUBBLE_NODE_CLASS="node",b.BUBBLE_CLASS="bubble",b.MIN_RADIUS=10,b=c.colorMixin(b),b.renderLabel(!0),b.data(function(a){return a.top(1/0)});var f=a.scale.linear().domain([0,100]),g=function(a){return a.r};b.r=function(a){return arguments.length?(f=a,b):f},b.radiusValueAccessor=function(a){return arguments.length?(g=a,b):g},b.rMin=function(){var c=a.min(b.data(),function(a){return b.radiusValueAccessor()(a)});return c},b.rMax=function(){var c=a.max(b.data(),function(a){return b.radiusValueAccessor()(a)});return c},b.bubbleR=function(a){var c=b.radiusValueAccessor()(a),d=b.r()(c);return(isNaN(d)||0>=c)&&(d=0),d};var h=function(a){return b.label()(a)},i=function(a){return b.bubbleR(a)>e?1:0};b._doRenderLabel=function(a){if(b.renderLabel()){var d=a.select("text");d.empty()&&(d=a.append("text").attr("text-anchor","middle").attr("dy",".3em").on("click",b.onClick)),d.attr("opacity",0).text(h),c.transition(d,b.transitionDuration()).attr("opacity",i)}},b.doUpdateLabels=function(a){if(b.renderLabel()){var d=a.selectAll("text").text(h);c.transition(d,b.transitionDuration()).attr("opacity",i)}};var j=function(a){return b.title()(a)};return b._doRenderTitles=function(a){if(b.renderTitle()){var c=a.select("title");c.empty()&&a.append("title").text(j)}},b.doUpdateTitles=function(a){b.renderTitle()&&a.selectAll("title").text(j)},b.minRadiusWithLabel=function(a){return arguments.length?(e=a,b):e},b.maxBubbleRelativeSize=function(a){return arguments.length?(d=a,b):d},b.fadeDeselectedArea=function(){b.selectAll("g."+b.BUBBLE_NODE_CLASS).each(b.hasFilter()?function(a){b.isSelectedNode(a)?b.highlightSelected(this):b.fadeDeselected(this)}:function(){b.resetHighlight(this)})},b.isSelectedNode=function(a){return b.hasFilter(a.key)},b.onClick=function(a){var d=a.key;c.events.trigger(function(){b.filter(d),b.redrawGroup()})},b},c.pieChart=function(b,d){function e(){D=D?D:a.min([O.width(),O.height()])/2;var b,c=r(),d=t();if(a.sum(O.data(),O.valueAccessor())?(b=d(O.data()),E.classed(K,!1)):(b=d([{key:L,value:1,others:[L]}]),E.classed(K,!0)),E){var e=E.selectAll("g."+J).data(b);f(e,c,b),l(b,c),p(e),q()}}function f(a,b,c){var d=g(a);h(d,b),i(d),k(c,b)}function g(a){var b=a.enter().append("g").attr("class",function(a,b){return J+" _"+b});return b}function h(a,b){var d=a.append("path").attr("fill",y).on("click",z).attr("d",function(a,c){return A(a,c,b)});c.transition(d,O.transitionDuration(),function(a){a.attrTween("d",w)})}function i(a){O.renderTitle()&&a.append("title").text(function(a){return O.title()(a)})}function j(a,b){c.transition(a,O.transitionDuration()).attr("transform",function(a){return B(a,b)}).attr("text-anchor","middle").text(function(a){var b=a.data;return!v(b)&&!u(a)||s(a)?O.label()(a.data):""})}function k(a,b){if(O.renderLabel()){var c=E.selectAll("text."+J).data(a);c.exit().remove();var d=c.enter().append("text").attr("class",function(a,b){var c=J+" _"+b;return H&&(c+=" external"),c}).on("click",z);j(d,b)}}function l(a,b){m(a,b),n(a,b),o(a)}function m(a,b){var d=E.selectAll("g."+J).data(a).select("path").attr("d",function(a,c){return A(a,c,b)});c.transition(d,O.transitionDuration(),function(a){a.attrTween("d",w)}).attr("fill",y)}function n(a,b){if(O.renderLabel()){var c=E.selectAll("text."+J).data(a);j(c,b)}}function o(a){O.renderTitle()&&E.selectAll("g."+J).data(a).select("title").text(function(a){return O.title()(a.data)})}function p(a){a.exit().remove()}function q(){O.selectAll("g."+J).each(O.hasFilter()?function(a){s(a)?O.highlightSelected(this):O.fadeDeselected(this)}:function(){O.resetHighlight(this)})}function r(){return a.svg.arc().outerRadius(D).innerRadius(M)}function s(a){return O.hasFilter(O.cappedKeyAccessor(a.data))}function t(){return a.layout.pie().sort(null).value(O.cappedValueAccessor)}function u(a){var b=a.endAngle-a.startAngle;return isNaN(b)||N>b}function v(a){return 0===O.cappedValueAccessor(a)}function w(b){b.innerRadius=M;var c=this._current;x(c)&&(c={startAngle:0,endAngle:0});var d=a.interpolate(c,b);return this._current=d(0),function(a){return A(d(a),0,r())}}function x(a){return!a||isNaN(a.startAngle)||isNaN(a.endAngle)}function y(a,b){return O.getColor(a.data,b)}function z(a,b){E.attr("class")!==K&&O.onClick(a.data,b)}function A(a,b,c){var d=c(a,b);return d.indexOf("NaN")>=0&&(d="M0,0"),d}function B(b,c){var d;return d=H?a.svg.arc().outerRadius(D+H).innerRadius(D+H).centroid(b):c.centroid(b),isNaN(d[0])||isNaN(d[1])?"translate(0,0)":"translate("+d+")"}function C(b,c){O.selectAll("g.pie-slice").each(function(d){b.name===d.data.key&&a.select(this).classed("highlight",c)})}var D,E,F,G,H,I=.5,J="pie-slice",K="empty-chart",L="empty",M=0,N=I,O=c.capMixin(c.colorMixin(c.baseMixin({})));return O.colorAccessor(O.cappedKeyAccessor),O.title(function(a){return O.cappedKeyAccessor(a)+": "+O.cappedValueAccessor(a)}),O.slicesCap=O.cap,O.label(O.cappedKeyAccessor),O.renderLabel(!0),O.transitionDuration(350),O._doRender=function(){return O.resetSvg(),E=O.svg().append("g").attr("transform","translate("+O.cx()+","+O.cy()+")"),e(),O | |
},O.innerRadius=function(a){return arguments.length?(M=a,O):M},O.radius=function(a){return arguments.length?(D=a,O):D},O.cx=function(a){return arguments.length?(F=a,O):F||O.width()/2},O.cy=function(a){return arguments.length?(G=a,O):G||O.height()/2},O._doRedraw=function(){return e(),O},O.minAngleForLabel=function(a){return arguments.length?(N=a,O):N},O.emptyTitle=function(a){return 0===arguments.length?L:(L=a,O)},O.externalLabels=function(a){return 0===arguments.length?H:(H=a?a:void 0,O)},O.legendables=function(){return O.data().map(function(a,b){var c={name:a.key,data:a.value,others:a.others,chart:O};return c.color=O.getColor(a,b),c})},O.legendHighlight=function(a){C(a,!0)},O.legendReset=function(a){C(a,!1)},O.legendToggle=function(a){O.onClick({key:a.name,others:a.others})},O.anchor(b,d)},c.barChart=function(b,d){function e(a){return c.utils.safeNumber(Math.abs(m.y()(a.y+a.y0)-m.y()(a.y0)))}function f(a,b,d){var f=a.selectAll("rect.bar").data(d.values,c.pluck("x")),g=f.enter().append("rect").attr("class","bar").attr("fill",c.pluck("data",m.getColor)).attr("y",m.yAxisHeight()).attr("height",0);m.renderTitle()&&g.append("title").text(c.pluck("data",m.title(d.name))),m.isOrdinal()&&f.on("click",h),c.transition(f,m.transitionDuration()).attr("x",function(a){var b=m.x()(a.x);return o&&(b-=j/2),m.isOrdinal()&&void 0!==n&&(b+=n/2),c.utils.safeNumber(b)}).attr("y",function(a){var b=m.y()(a.y+a.y0);return a.y<0&&(b-=e(a)),c.utils.safeNumber(b)}).attr("width",j).attr("height",function(a){return e(a)}).attr("fill",c.pluck("data",m.getColor)).select("title").text(c.pluck("data",m.title(d.name))),c.transition(f.exit(),m.transitionDuration()).attr("height",0).remove()}function g(){if(void 0===j){var a=m.xUnitCount();j=Math.floor(m.isOrdinal()&&void 0===n?m.x().rangeBand():n?(m.xAxisLength()-(a-1)*n)/a:m.xAxisLength()/(1+m.barPadding())/a),(1/0===j||isNaN(j)||k>j)&&(j=k)}}function h(a){m.onClick(a.data)}function i(b,c){return function(){var d=a.select(this),e=d.attr("fill")===b;return c?!e:e}}var j,k=1,l=2,m=c.stackMixin(c.coordinateGridMixin({})),n=l,o=!1,p=!1;return c.override(m,"rescale",function(){m._rescale(),j=void 0}),c.override(m,"render",function(){m.round()&&o&&!p&&c.logger.warn("By default, brush rounding is disabled if bars are centered. See dc.js bar chart API documentation for details."),m._render()}),m.plotData=function(){var b=m.chartBodyG().selectAll("g.stack").data(m.data());g(),b.enter().append("g").attr("class",function(a,b){return"stack _"+b}),b.each(function(b,c){var d=a.select(this);f(d,c,b)})},m.fadeDeselectedArea=function(){var a=m.chartBodyG().selectAll("rect.bar"),b=m.brush().extent();if(m.isOrdinal())m.hasFilter()?(a.classed(c.constants.SELECTED_CLASS,function(a){return m.hasFilter(a.x)}),a.classed(c.constants.DESELECTED_CLASS,function(a){return!m.hasFilter(a.x)})):(a.classed(c.constants.SELECTED_CLASS,!1),a.classed(c.constants.DESELECTED_CLASS,!1));else if(m.brushIsEmpty(b))a.classed(c.constants.DESELECTED_CLASS,!1);else{var d=b[0],e=b[1];a.classed(c.constants.DESELECTED_CLASS,function(a){return a.x<d||a.x>=e})}},m.centerBar=function(a){return arguments.length?(o=a,m):o},m.barPadding=function(a){return arguments.length?(m._rangeBandPadding(a),n=void 0,m):m._rangeBandPadding()},m._useOuterPadding=function(){return void 0===n},m.outerPadding=m._outerRangeBandPadding,m.gap=function(a){return arguments.length?(n=a,m):n},m.extendBrush=function(){var a=m.brush().extent();return!m.round()||o&&!p||(a[0]=a.map(m.round())[0],a[1]=a.map(m.round())[1],m.chartBodyG().select(".brush").call(m.brush().extent(a))),a},m.alwaysUseRounding=function(a){return arguments.length?(p=a,m):p},m.legendHighlight=function(a){m.isLegendableHidden(a)||m.g().selectAll("rect.bar").classed("highlight",i(a.color)).classed("fadeout",i(a.color,!0))},m.legendReset=function(){m.g().selectAll("rect.bar").classed("highlight",!1).classed("fadeout",!1)},c.override(m,"xAxisMax",function(){var a=this._xAxisMax();if("resolution"in m.xUnits()){var b=m.xUnits().resolution;a+=b}return a}),m.anchor(b,d)},c.lineChart=function(b,d){function e(a,b){return z.getColor.call(a,a.values,b)}function f(b,d){var f=a.svg.line().x(function(a){return z.x()(a.x)}).y(function(a){return z.y()(a.y+a.y0)}).interpolate(F).tension(G);r&&f.defined(r);var g=b.append("path").attr("class","line").attr("stroke",e);s&&g.attr("stroke-dasharray",s),c.transition(d.select("path.line"),z.transitionDuration()).attr("stroke",e).attr("d",function(a){return h(f(a.values))})}function g(b,d){if(A){var f=a.svg.area().x(function(a){return z.x()(a.x)}).y(function(a){return z.y()(a.y+a.y0)}).y0(function(a){return z.y()(a.y0)}).interpolate(F).tension(G);r&&f.defined(r),b.append("path").attr("class","area").attr("fill",e).attr("d",function(a){return h(f(a.values))}),c.transition(d.select("path.area"),z.transitionDuration()).attr("fill",e).attr("d",function(a){return h(f(a.values))})}}function h(a){return!a||a.indexOf("NaN")>=0?"M0,0":a}function i(b,d){if(!z.brushOn()){var e=u+"-list",f=b.select("g."+e);f.empty()&&(f=b.append("g").attr("class",e)),d.each(function(b,d){var e=b.values;r&&(e=e.filter(r));var g=f.select("g."+u+"._"+d);g.empty()&&(g=f.append("g").attr("class",u+" _"+d)),j(g);var h=g.selectAll("circle."+v).data(e,c.pluck("x"));h.enter().append("circle").attr("class",v).attr("r",m()).style("fill-opacity",D).style("stroke-opacity",E).on("mousemove",function(){var b=a.select(this);k(b),l(b,g)}).on("mouseout",function(){var b=a.select(this);n(b),o(g)}),h.attr("cx",function(a){return c.utils.safeNumber(z.x()(a.x))}).attr("cy",function(a){return c.utils.safeNumber(z.y()(a.y+a.y0))}).attr("fill",z.getColor).call(p,b),h.exit().remove()})}}function j(a){var b=a.select("path."+w).empty()?a.append("path").attr("class",w):a.select("path."+w);b.style("display","none").attr("stroke-dasharray","5,5");var c=a.select("path."+x).empty()?a.append("path").attr("class",x):a.select("path."+x);c.style("display","none").attr("stroke-dasharray","5,5")}function k(a){return a.style("fill-opacity",.8),a.style("stroke-opacity",.8),a.attr("r",B),a}function l(a,b){var c=a.attr("cx"),d=a.attr("cy"),e=z._yAxisX()-z.margins().left,f="M"+e+" "+d+"L"+c+" "+d,g="M"+c+" "+z.yAxisHeight()+"L"+c+" "+d;b.select("path."+w).style("display","").attr("d",f),b.select("path."+x).style("display","").attr("d",g)}function m(){return C||B}function n(a){a.style("fill-opacity",D).style("stroke-opacity",E).attr("r",m())}function o(a){a.select("path."+w).style("display","none"),a.select("path."+x).style("display","none")}function p(a,b){z.renderTitle()&&(a.selectAll("title").remove(),a.append("title").text(c.pluck("data",z.title(b.name))))}function q(b,c,d){return function(){var e=a.select(this),f=e.attr("stroke")===b&&e.attr("stroke-dasharray")===(c instanceof Array?c.join(","):null)||e.attr("fill")===b;return d?!f:f}}var r,s,t=5,u="dc-tooltip",v="dot",w="yRef",x="xRef",y=1e-6,z=c.stackMixin(c.coordinateGridMixin({})),A=!1,B=t,C=null,D=y,E=y,F="linear",G=.7;return z.transitionDuration(500),z._rangeBandPadding(1),z.plotData=function(){var a=z.chartBodyG(),b=a.selectAll("g.stack-list");b.empty()&&(b=a.append("g").attr("class","stack-list"));var c=b.selectAll("g.stack").data(z.data()),d=c.enter().append("g").attr("class",function(a,b){return"stack _"+b});f(d,c),g(d,c),i(a,c)},z.interpolate=function(a){return arguments.length?(F=a,z):F},z.tension=function(a){return arguments.length?(G=a,z):G},z.defined=function(a){return arguments.length?(r=a,z):r},z.dashStyle=function(a){return arguments.length?(s=a,z):s},z.renderArea=function(a){return arguments.length?(A=a,z):A},z.dotRadius=function(a){return arguments.length?(B=a,z):B},z.renderDataPoints=function(a){return arguments.length?(a?(D=a.fillOpacity||.8,E=a.strokeOpacity||.8,C=a.radius||2):(D=y,E=y,C=null),z):{fillOpacity:D,strokeOpacity:E,radius:C}},z.legendHighlight=function(a){z.isLegendableHidden(a)||z.g().selectAll("path.line, path.area").classed("highlight",q(a.color,a.dashstyle)).classed("fadeout",q(a.color,a.dashstyle,!0))},z.legendReset=function(){z.g().selectAll("path.line, path.area").classed("highlight",!1).classed("fadeout",!1)},c.override(z,"legendables",function(){var a=z._legendables();return s?a.map(function(a){return a.dashstyle=s,a}):a}),z.anchor(b,d)},c.dataCount=function(b,d){var e=a.format(",d"),f=c.baseMixin({}),g={some:"",all:""};return f.html=function(a){return arguments.length?(a.all&&(g.all=a.all),a.some&&(g.some=a.some),f):g},f.formatNumber=function(a){return arguments.length?(e=a,f):e},f._doRender=function(){var a=f.dimension().size(),b=f.group().value(),c=e(a),d=e(b);return a===b&&""!==g.all?f.root().html(g.all.replace("%total-count",c).replace("%filter-count",d)):""!==g.some?f.root().html(g.some.replace("%total-count",c).replace("%filter-count",d)):(f.selectAll(".total-count").text(c),f.selectAll(".filter-count").text(d)),f},f._doRedraw=function(){return f._doRender()},f.anchor(b,d)},c.dataTable=function(b,d){function e(){var a=!0;if(o.forEach(function(b){a&="function"==typeof b}),!a){m.selectAll("th").remove();var b=m.root().selectAll("th").data(o),c=b.enter().append("th");c.attr("class",l).html(function(a){return m._doColumnHeaderFormat(a)})}var d=m.root().selectAll("tbody").data(f(),function(a){return m.keyAccessor()(a)}),e=d.enter().append("tbody");return e.append("tr").attr("class",k).append("td").attr("class",h).attr("colspan",o.length).html(function(a){return m.keyAccessor()(a)}),d.exit().remove(),e}function f(){var b;return b=q===a.ascending?m.dimension().bottom(n):m.dimension().top(n),a.nest().key(m.group()).sortKeys(q).entries(b.sort(function(a,b){return q(p(a),p(b))}))}function g(a){var b=a.order().selectAll("tr."+i).data(function(a){return a.values}),c=b.enter().append("tr").attr("class",i);return o.forEach(function(a,b){c.append("td").attr("class",j+" _"+b).html(function(b){return m._doColumnValueFormat(a,b)})}),b.exit().remove(),b}var h="dc-table-label",i="dc-table-row",j="dc-table-column",k="dc-table-group",l="dc-table-head",m=c.baseMixin({}),n=25,o=[],p=function(a){return a},q=a.ascending;return m._doRender=function(){return m.selectAll("tbody").remove(),g(e()),m},m._doColumnValueFormat=function(a,b){return"function"==typeof a?a(b):"string"==typeof a?b[a]:a.format(b)},m._doColumnHeaderFormat=function(a){return"function"==typeof a?m._doColumnHeaderFnToString(a):"string"==typeof a?m._doColumnHeaderCapitalize(a):String(a.label)},m._doColumnHeaderCapitalize=function(a){return a.charAt(0).toUpperCase()+a.slice(1)},m._doColumnHeaderFnToString=function(a){var b=String(a),c=b.indexOf("return ");if(c>=0){var d=b.lastIndexOf(";");if(d>=0){b=b.substring(c+7,d);var e=b.indexOf("numberFormat");e>=0&&(b=b.replace("numberFormat",""))}}return b},m._doRedraw=function(){return m._doRender()},m.size=function(a){return arguments.length?(n=a,m):n},m.columns=function(a){return arguments.length?(o=a,m):o},m.sortBy=function(a){return arguments.length?(p=a,m):p},m.order=function(a){return arguments.length?(q=a,m):q},m.anchor(b,d)},c.dataGrid=function(b,d){function e(){var a=l.root().selectAll("div."+k).data(f(),function(a){return l.keyAccessor()(a)}),b=a.enter().append("div").attr("class",k);return q&&b.html(function(a){return q(a)}),a.exit().remove(),b}function f(){var b=l.dimension().top(m);return a.nest().key(l.group()).sortKeys(p).entries(b.sort(function(a,b){return p(o(a),o(b))}))}function g(a){var b=a.order().selectAll("div."+i).data(function(a){return a.values});return b.enter().append("div").attr("class",i).html(function(a){return n(a)}),b.exit().remove(),b}var h="dc-grid-label",i="dc-grid-item",j="dc-grid-group",k="dc-grid-top",l=c.baseMixin({}),m=999,n=function(a){return"you need to provide an html() handling param: "+JSON.stringify(a)},o=function(a){return a},p=a.ascending,q=function(a){return"<div class='"+j+"'><h1 class='"+h+"'>"+l.keyAccessor()(a)+"</h1></div>"};return l._doRender=function(){return l.selectAll("div."+k).remove(),g(e()),l},l._doRedraw=function(){return l._doRender()},l.size=function(a){return arguments.length?(m=a,l):m},l.html=function(a){return arguments.length?(n=a,l):n},l.htmlGroup=function(a){return arguments.length?(q=a,l):q},l.sortBy=function(a){return arguments.length?(o=a,l):o},l.order=function(a){return arguments.length?(p=a,l):p},l.anchor(b,d)},c.bubbleChart=function(a,b){function d(a){var b=a.enter().append("g");b.attr("class",i.BUBBLE_NODE_CLASS).attr("transform",k).append("circle").attr("class",function(a,b){return i.BUBBLE_CLASS+" _"+b}).on("click",i.onClick).attr("fill",i.getColor).attr("r",0),c.transition(a,i.transitionDuration()).selectAll("circle."+i.BUBBLE_CLASS).attr("r",function(a){return i.bubbleR(a)}).attr("opacity",function(a){return i.bubbleR(a)>0?1:0}),i._doRenderLabel(b),i._doRenderTitles(b)}function e(a){c.transition(a,i.transitionDuration()).attr("transform",k).selectAll("circle."+i.BUBBLE_CLASS).attr("fill",i.getColor).attr("r",function(a){return i.bubbleR(a)}).attr("opacity",function(a){return i.bubbleR(a)>0?1:0}),i.doUpdateLabels(a),i.doUpdateTitles(a)}function f(a){a.exit().remove()}function g(a){var b=i.x()(i.keyAccessor()(a));return isNaN(b)&&(b=0),b}function h(a){var b=i.y()(i.valueAccessor()(a));return isNaN(b)&&(b=0),b}var i=c.bubbleMixin(c.coordinateGridMixin({})),j=!1;i.transitionDuration(750);var k=function(a){return"translate("+g(a)+","+h(a)+")"};return i.elasticRadius=function(a){return arguments.length?(j=a,i):j},i.plotData=function(){j&&i.r().domain([i.rMin(),i.rMax()]),i.r().range([i.MIN_RADIUS,i.xAxisLength()*i.maxBubbleRelativeSize()]);var a=i.chartBodyG().selectAll("g."+i.BUBBLE_NODE_CLASS).data(i.data(),function(a){return a.key});d(a),e(a),f(a),i.fadeDeselectedArea()},i.renderBrush=function(){},i.redrawBrush=function(){i.fadeDeselectedArea()},i.anchor(a,b)},c.compositeChart=function(b,d){function e(){(void 0===u.rightY()||u.elasticY())&&(u.rightY(a.scale.linear()),u.rightY().domain([l(),o()]).rangeRound([u.yAxisHeight(),0])),u.rightY().range([u.yAxisHeight(),0]),u.rightYAxis(u.rightYAxis().scale(u.rightY())),u.rightYAxis().orient("right")}function f(){(void 0===u.y()||u.elasticY())&&(u.y(a.scale.linear()),u.y().domain([k(),n()]).rangeRound([u.yAxisHeight(),0])),u.y().range([u.yAxisHeight(),0]),u.yAxis(u.yAxis().scale(u.y())),u.yAxis().orient("left")}function g(a,b){a._generateG(u.g()),a.g().attr("class",s+" _"+b)}function h(){return v.filter(function(a){return!a.useRightYAxis()})}function i(){return v.filter(function(a){return a.useRightYAxis()})}function j(a){return a.map(function(a){return a.yAxisMin()})}function k(){return a.min(j(h()))}function l(){return a.min(j(i()))}function m(a){return a.map(function(a){return a.yAxisMax()})}function n(){return c.utils.add(a.max(m(h())),u.yAxisPadding())}function o(){return c.utils.add(a.max(m(i())),u.yAxisPadding())}function p(){return v.map(function(a){return a.xAxisMin()})}function q(){return v.map(function(a){return a.xAxisMax()})}var r,s="sub",t=12,u=c.coordinateGridMixin({}),v=[],w={},x=!1,y=!0,z=a.svg.axis(),A=0,B=t,C=!1;return u._mandatoryAttributes([]),u.transitionDuration(500),c.override(u,"_generateG",function(){for(var a=this.__generateG(),b=0;b<v.length;++b){var c=v[b];g(c,b),c.dimension()||c.dimension(u.dimension()),c.group()||c.group(u.group()),c.chartGroup(u.chartGroup()),c.svg(u.svg()),c.xUnits(u.xUnits()),c.transitionDuration(u.transitionDuration()),c.brushOn(u.brushOn()),c.renderTitle(u.renderTitle())}return a}),u._brushing=function(){for(var a=u.extendBrush(),b=u.brushIsEmpty(a),c=0;c<v.length;++c)v[c].filter(null),b||v[c].filter(a)},u._prepareYAxis=function(){0!==h().length&&f(),0!==i().length&&e(),h().length>0&&!C?u._renderHorizontalGridLinesForAxis(u.g(),u.y(),u.yAxis()):i().length>0&&u._renderHorizontalGridLinesForAxis(u.g(),r,z)},u.renderYAxis=function(){0!==h().length&&(u.renderYAxisAt("y",u.yAxis(),u.margins().left),u.renderYAxisLabel("y",u.yAxisLabel(),-90)),0!==i().length&&(u.renderYAxisAt("yr",u.rightYAxis(),u.width()-u.margins().right),u.renderYAxisLabel("yr",u.rightYAxisLabel(),90,u.width()-B))},u.plotData=function(){for(var a=0;a<v.length;++a){var b=v[a];b.g()||g(b,a),x&&b.colors(u.colors()),b.x(u.x()),b.xAxis(u.xAxis()),b.useRightYAxis()?(b.y(u.rightY()),b.yAxis(u.rightYAxis())):(b.y(u.y()),b.yAxis(u.yAxis())),b.plotData(),b._activateRenderlets()}},u.useRightAxisGridLines=function(a){return arguments?(C=a,u):C},u.childOptions=function(a){return arguments.length?(w=a,v.forEach(function(a){a.options(w)}),u):w},u.fadeDeselectedArea=function(){for(var a=0;a<v.length;++a){var b=v[a];b.brush(u.brush()),b.fadeDeselectedArea()}},u.rightYAxisLabel=function(a,b){return arguments.length?(A=a,u.margins().right-=B,B=void 0===b?t:b,u.margins().right+=B,u):A},u.compose=function(a){return v=a,v.forEach(function(a){a.height(u.height()),a.width(u.width()),a.margins(u.margins()),y&&a.title(u.title()),a.options(w)}),u},u.children=function(){return v},u.shareColors=function(a){return arguments.length?(x=a,u):x},u.shareTitle=function(a){return arguments.length?(y=a,u):y},u.rightY=function(a){return arguments.length?(r=a,u):r},delete u.yAxisMin,delete u.yAxisMax,c.override(u,"xAxisMin",function(){return c.utils.subtract(a.min(p()),u.xAxisPadding())}),c.override(u,"xAxisMax",function(){return c.utils.add(a.max(q()),u.xAxisPadding())}),u.legendables=function(){return v.reduce(function(a,b){return x&&b.colors(u.colors()),a.push.apply(a,b.legendables()),a},[])},u.legendHighlight=function(a){for(var b=0;b<v.length;++b){var c=v[b];c.legendHighlight(a)}},u.legendReset=function(a){for(var b=0;b<v.length;++b){var c=v[b];c.legendReset(a)}},u.legendToggle=function(){console.log("composite should not be getting legendToggle itself")},u.rightYAxis=function(a){return arguments.length?(z=a,u):z},u.anchor(b,d)},c.seriesChart=function(b,d){function e(b,c){return a.ascending(i.keyAccessor()(b),i.keyAccessor()(c))}function f(a){j[a].g()&&j[a].g().remove(),delete j[a]}function g(){Object.keys(j).map(f),j={}}var h,i=c.compositeChart(b,d),j={},k=c.lineChart,l=a.ascending,m=e;return i._mandatoryAttributes().push("seriesAccessor","chart"),i.shareColors(!0),i._preprocessData=function(){var b,c=[],e=a.nest().key(h);l&&e.sortKeys(l),m&&e.sortValues(m);var g=e.entries(i.data()),n=g.map(function(e,f){var g=j[e.key]||k.call(i,i,d,e.key,f);return j[e.key]||(b=!0),j[e.key]=g,c.push(e.key),g.dimension(i.dimension()).group({all:a.functor(e.values)},e.key).keyAccessor(i.keyAccessor()).valueAccessor(i.valueAccessor()).brushOn(i.brushOn())});Object.keys(j).filter(function(a){return-1===c.indexOf(a)}).forEach(function(a){f(a),b=!0}),i._compose(n),b&&i.legend()&&i.legend().render()},i.chart=function(a){return arguments.length?(k=a,g(),i):k},i.seriesAccessor=function(a){return arguments.length?(h=a,g(),i):h},i.seriesSort=function(a){return arguments.length?(l=a,g(),i):l},i.valueSort=function(a){return arguments.length?(m=a,g(),i):m},i._compose=i.compose,delete i.compose,i},c.geoChoroplethChart=function(b,d){function e(a){var b=f();if(g(a)){var c=h(a);n(c,a,b),o(c,a,b)}}function f(){for(var a={},b=p.data(),c=0;c<b.length;++c)a[p.keyAccessor()(b[c])]=p.valueAccessor()(b[c]);return a}function g(a){return m(a).keyAccessor}function h(a){var b=p.svg().selectAll(i(a)).classed("selected",function(b){return j(a,b)}).classed("deselected",function(b){return k(a,b)}).attr("class",function(b){var d=m(a).name,e=c.utils.nameToId(m(a).keyAccessor(b)),f=d+" "+e;return j(a,b)&&(f+=" selected"),k(a,b)&&(f+=" deselected"),f});return b}function i(a){return"g.layer"+a+" g."+m(a).name}function j(a,b){return p.hasFilter()&&p.hasFilter(l(a,b))}function k(a,b){return p.hasFilter()&&!p.hasFilter(l(a,b))}function l(a,b){return m(a).keyAccessor(b)}function m(a){return s[a]}function n(b,d,e){var f=b.select("path").attr("fill",function(){var b=a.select(this).attr("fill");return b?b:"none"}).on("click",function(a){return p.onClick(a,d)});c.transition(f,p.transitionDuration()).attr("fill",function(a,b){return p.getColor(e[m(d).keyAccessor(a)],b)})}function o(a,b,c){p.renderTitle()&&a.selectAll("title").text(function(a){var d=l(b,a),e=c[d];return p.title()({key:d,value:e})})}var p=c.colorMixin(c.baseMixin({}));p.colorAccessor(function(a){return a||0});var q,r=a.geo.path(),s=[];return p._doRender=function(){p.resetSvg();for(var a=0;a<s.length;++a){var b=p.svg().append("g").attr("class","layer"+a),c=b.selectAll("g."+m(a).name).data(m(a).data).enter().append("g").attr("class",m(a).name);c.append("path").attr("fill","white").attr("d",r),c.append("title"),e(a)}q=!1},p.onClick=function(a,b){var d=m(b).keyAccessor(a);c.events.trigger(function(){p.filter(d),p.redrawGroup()})},p._doRedraw=function(){for(var a=0;a<s.length;++a)e(a),q&&p.svg().selectAll("g."+m(a).name+" path").attr("d",r);q=!1},p.overlayGeoJson=function(a,b,c){for(var d=0;d<s.length;++d)if(s[d].name===b)return s[d].data=a,s[d].keyAccessor=c,p;return s.push({name:b,data:a,keyAccessor:c}),p},p.projection=function(a){return r.projection(a),q=!0,p},p.geoJsons=function(){return s},p.geoPath=function(){return r},p.removeGeoJson=function(a){for(var b=[],c=0;c<s.length;++c){var d=s[c];d.name!==a&&b.push(d)}return s=b,p},p.anchor(b,d)},c.bubbleOverlay=function(b,d){function e(){return j=n.select("g."+k),j.empty()&&(j=n.svg().append("g").attr("class",k)),j}function f(){var a=g();o.forEach(function(b){var d=h(b,a),e=d.select("circle."+m);e.empty()&&(e=d.append("circle").attr("class",m).attr("r",0).attr("fill",n.getColor).on("click",n.onClick)),c.transition(e,n.transitionDuration()).attr("r",function(a){return n.bubbleR(a)}),n._doRenderLabel(d),n._doRenderTitles(d)})}function g(){var a={};return n.data().forEach(function(b){a[n.keyAccessor()(b)]=b}),a}function h(a,b){var d=l+" "+c.utils.nameToId(a.name),e=j.select("g."+c.utils.nameToId(a.name));return e.empty()&&(e=j.append("g").attr("class",d).attr("transform","translate("+a.x+","+a.y+")")),e.datum(b[a.name]),e}function i(){var a=g();o.forEach(function(b){var d=h(b,a),e=d.select("circle."+m);c.transition(e,n.transitionDuration()).attr("r",function(a){return n.bubbleR(a)}).attr("fill",n.getColor),n.doUpdateLabels(d),n.doUpdateTitles(d)})}var j,k="bubble-overlay",l="node",m="bubble",n=c.bubbleMixin(c.baseMixin({})),o=[];return n.transitionDuration(750),n.radiusValueAccessor(function(a){return a.value}),n.point=function(a,b,c){return o.push({name:a,x:b,y:c}),n},n._doRender=function(){return j=e(),n.r().range([n.MIN_RADIUS,n.width()*n.maxBubbleRelativeSize()]),f(),n.fadeDeselectedArea(),n},n._doRedraw=function(){return i(),n.fadeDeselectedArea(),n},n.debug=function(b){if(b){var d=n.select("g."+c.constants.DEBUG_GROUP_CLASS);d.empty()&&(d=n.svg().append("g").attr("class",c.constants.DEBUG_GROUP_CLASS));var e=d.append("text").attr("x",10).attr("y",20);d.append("rect").attr("width",n.width()).attr("height",n.height()).on("mousemove",function(){var b=a.mouse(d.node()),c=b[0]+", "+b[1];e.text(c)})}else n.selectAll(".debug").remove();return n},n.anchor(b,d),n},c.rowChart=function(b,d){function e(){if(!t||u){var b=a.extent(v,G.cappedValueAccessor);b[0]>0&&(b[0]=0),t=a.scale.linear().domain(b).range([0,G.effectiveWidth()])}H.scale(t)}function f(){var a=s.select("g.axis");e(),a.empty()&&(a=s.append("g").attr("class","axis").attr("transform","translate(0, "+G.effectiveHeight()+")")),c.transition(a,G.transitionDuration()).call(H)}function g(){s.selectAll("g.tick").select("line.grid-line").remove(),s.selectAll("g.tick").append("line").attr("class","grid-line").attr("x1",0).attr("y1",0).attr("x2",0).attr("y2",function(){return-G.effectiveHeight()})}function h(){v=G.data(),f(),g();var a=s.selectAll("g."+D).data(v);i(a),j(a),l(a)}function i(a){var b=a.enter().append("g").attr("class",function(a,b){return D+" _"+b});b.append("rect").attr("width",0),n(b),o(a)}function j(a){a.exit().remove()}function k(){var a=t(0);return a===-1/0||a!==a?t(1):a}function l(a){var b,d=v.length;b=C?C:(G.effectiveHeight()-(d+1)*B)/d,y||(x=b/2);var e=a.attr("transform",function(a,c){return"translate(0,"+((c+1)*B+c*b)+")"}).select("rect").attr("height",b).attr("fill",G.getColor).on("click",p).classed("deselected",function(a){return G.hasFilter()?!r(a):!1}).classed("selected",function(a){return G.hasFilter()?r(a):!1});c.transition(e,G.transitionDuration()).attr("width",function(a){return Math.abs(k()-t(G.valueAccessor()(a)))}).attr("transform",q),m(a),o(a)}function m(a){G.renderTitle()&&(a.selectAll("title").remove(),a.append("title").text(G.title()))}function n(a){G.renderLabel()&&a.append("text").on("click",p),G.renderTitleLabel()&&a.append("text").attr("class",E).on("click",p)}function o(a){if(G.renderLabel()){var b=a.select("text").attr("x",w).attr("y",x).attr("dy",z).on("click",p).attr("class",function(a,b){return D+" _"+b}).text(function(a){return G.label()(a)});c.transition(b,G.transitionDuration()).attr("transform",q)}if(G.renderTitleLabel()){var d=a.select("."+E).attr("x",G.effectiveWidth()-A).attr("y",x).attr("text-anchor","end").on("click",p).attr("class",function(a,b){return E+" _"+b}).text(function(a){return G.title()(a)});c.transition(d,G.transitionDuration()).attr("transform",q)}}function p(a){G.onClick(a)}function q(a){var b=t(G.cappedValueAccessor(a)),c=k(),d=b>c?c:b;return"translate("+d+",0)"}function r(a){return G.hasFilter(G.cappedKeyAccessor(a))}var s,t,u,v,w=10,x=15,y=!1,z="0.35em",A=2,B=5,C=!1,D="row",E="titlerow",F=!1,G=c.capMixin(c.marginMixin(c.colorMixin(c.baseMixin({})))),H=a.svg.axis().orient("bottom");return G.rowsCap=G.cap,G._doRender=function(){return G.resetSvg(),s=G.svg().append("g").attr("transform","translate("+G.margins().left+","+G.margins().top+")"),h(),G},G.title(function(a){return G.cappedKeyAccessor(a)+": "+G.cappedValueAccessor(a)}),G.label(G.cappedKeyAccessor),G.x=function(a){return arguments.length?(t=a,G):t},G.renderTitleLabel=function(a){return arguments.length?(F=a,G):F},G._doRedraw=function(){return h(),G},G.xAxis=function(){return H},G.fixedBarHeight=function(a){return arguments.length?(C=a,G):C},G.gap=function(a){return arguments.length?(B=a,G):B},G.elasticX=function(a){return arguments.length?(u=a,G):u},G.labelOffsetX=function(a){return arguments.length?(w=a,G):w},G.labelOffsetY=function(a){return arguments.length?(x=a,y=!0,G):x},G.titleLabelOffsetX=function(a){return arguments.length?(A=a,G):A},G.anchor(b,d)},c.legend=function(){function a(){return j+i}var b,d,e=2,f={},g=0,h=0,i=12,j=5,k=!1,l=560,m=70;return f.parent=function(a){return arguments.length?(b=a,f):b},f.render=function(){b.svg().select("g.dc-legend").remove(),d=b.svg().append("g").attr("class","dc-legend").attr("transform","translate("+g+","+h+")");var f=b.legendables(),j=d.selectAll("g.dc-legend-item").data(f).enter().append("g").attr("class","dc-legend-item").on("mouseover",function(a){b.legendHighlight(a)}).on("mouseout",function(a){b.legendReset(a)}).on("click",function(a){a.chart.legendToggle(a)});d.selectAll("g.dc-legend-item").classed("fadeout",function(a){return a.chart.isLegendableHidden(a)}),f.some(c.pluck("dashstyle"))?j.append("line").attr("x1",0).attr("y1",i/2).attr("x2",i).attr("y2",i/2).attr("stroke-width",2).attr("stroke-dasharray",c.pluck("dashstyle")).attr("stroke",c.pluck("color")):j.append("rect").attr("width",i).attr("height",i).attr("fill",function(a){return a?a.color:"blue"}),j.append("text").text(c.pluck("name")).attr("x",i+e).attr("y",function(){return i/2+(this.clientHeight?this.clientHeight:13)/2-2});var n=0,o=0;j.attr("transform",function(b,c){if(k){var d="translate("+n+","+o*a()+")";return n+m>=l?(++o,n=0):n+=m,d}return"translate(0,"+c*a()+")"})},f.x=function(a){return arguments.length?(g=a,f):g},f.y=function(a){return arguments.length?(h=a,f):h},f.gap=function(a){return arguments.length?(j=a,f):j},f.itemHeight=function(a){return arguments.length?(i=a,f):i},f.horizontal=function(a){return arguments.length?(k=a,f):k},f.legendWidth=function(a){return arguments.length?(l=a,f):l},f.itemWidth=function(a){return arguments.length?(m=a,f):m},f},c.scatterPlot=function(b,d){function e(b,d){var e=g.selectAll(".chart-body path.symbol").filter(function(){return b(a.select(this))}),f=h.size();h.size(Math.pow(d,2)),c.transition(e,g.transitionDuration()).attr("d",h),h.size(f)}function f(a){var b=g.selectAll(".chart-body path.symbol").each(function(b){this.filtered=a&&a.isFiltered(b.key)});c.transition(b,g.transitionDuration()).attr("d",h)}var g=c.coordinateGridMixin({}),h=a.svg.symbol(),i=function(a){return a.value},j=g.keyAccessor();g.keyAccessor(function(a){return j(a)[0]}),g.valueAccessor(function(a){return j(a)[1]}),g.colorAccessor(function(){return g._groupName});var k=function(a){return"translate("+g.x()(g.keyAccessor()(a))+","+g.y()(g.valueAccessor()(a))+")"},l=3,m=5,n=0;return h.size(function(a){return i(a)?this.filtered?Math.pow(m,2):Math.pow(l,2):n}),c.override(g,"_filter",function(a){return arguments.length?g.__filter(c.filters.RangedTwoDimensionalFilter(a)):g.__filter()}),g.plotData=function(){var a=g.chartBodyG().selectAll("path.symbol").data(g.data());a.enter().append("path").attr("class","symbol").attr("opacity",0).attr("fill",g.getColor).attr("transform",k),c.transition(a,g.transitionDuration()).attr("opacity",function(a){return i(a)?1:0}).attr("fill",g.getColor).attr("transform",k).attr("d",h),c.transition(a.exit(),g.transitionDuration()).attr("opacity",0).remove()},g.existenceAccessor=function(a){return arguments.length?(i=a,this):i},g.symbol=function(a){return arguments.length?(h.type(a),g):h.type()},g.symbolSize=function(a){return arguments.length?(l=a,g):l},g.highlightedSize=function(a){return arguments.length?(m=a,g):m},g.hiddenSize=function(a){return arguments.length?(n=a,g):n},g.legendables=function(){return[{chart:g,name:g._groupName,color:g.getColor()}]},g.legendHighlight=function(b){e(function(a){return a.attr("fill")===b.color},m),g.selectAll(".chart-body path.symbol").filter(function(){return a.select(this).attr("fill")!==b.color}).classed("fadeout",!0)},g.legendReset=function(b){e(function(a){return a.attr("fill")===b.color},l),g.selectAll(".chart-body path.symbol").filter(function(){return a.select(this).attr("fill")!==b.color}).classed("fadeout",!1)},g.setHandlePaths=function(){},g.extendBrush=function(){var a=g.brush().extent();return g.round()&&(a[0]=a[0].map(g.round()),a[1]=a[1].map(g.round()),g.g().select(".brush").call(g.brush().extent(a))),a},g.brushIsEmpty=function(a){return g.brush().empty()||!a||a[0][0]>=a[1][0]||a[0][1]>=a[1][1]},g._brushing=function(){var a=g.extendBrush();if(g.redrawBrush(g.g()),g.brushIsEmpty(a))c.events.trigger(function(){g.filter(null),g.redrawGroup()}),f(!1);else{var b=c.filters.RangedTwoDimensionalFilter(a);c.events.trigger(function(){g.filter(null),g.filter(b),g.redrawGroup()},c.constants.EVENT_DELAY),f(b)}},g.setBrushY=function(a){a.call(g.brush().y(g.y()))},g.anchor(b,d)},c.numberDisplay=function(b,d){var e="number-display",f=a.format(".2s"),g=c.baseMixin({}),h={one:"",some:"",none:""};return g._mandatoryAttributes(["group"]),g.html=function(a){return arguments.length?(a.none?h.none=a.none:a.one?h.none=a.one:a.some&&(h.none=a.some),a.one?h.one=a.one:a.some&&(h.one=a.some),a.some?h.some=a.some:a.one&&(h.some=a.one),g):h},g.value=function(){return g.data()},g.data(function(a){var b=a.value?a.value():a.top(1)[0];return g.valueAccessor()(b)}),g.transitionDuration(250),g._doRender=function(){var b=g.value(),c=g.selectAll("."+e);c.empty()&&(c=c.data([0]).enter().append("span").attr("class",e)),c.transition().duration(g.transitionDuration()).ease("quad-out-in").tween("text",function(){var c=a.interpolateNumber(this.lastValue||0,b);return this.lastValue=b,function(a){var d=null,e=g.formatNumber()(c(a));0===b&&""!==h.none?d=h.none:1===b&&""!==h.one?d=h.one:""!==h.some&&(d=h.some),this.innerHTML=d?d.replace("%number",e):e}})},g._doRedraw=function(){return g._doRender()},g.formatNumber=function(a){return arguments.length?(f=a,g):f},g.anchor(b,d)},c.heatMap=function(b,d){function e(a,b){var d=m.selectAll(".box-group").filter(function(c){return c.key[a]===b}),e=d.filter(function(a){return!m.hasFilter(a.key)});c.events.trigger(function(){e.empty()?d.each(function(a){m.filter(a.key) | |
}):e.each(function(a){m.filter(a.key)}),m.redrawGroup()})}function f(a,b,c){return!b||c[b-1]!==a}var g,h,i,j=6.75,k=j,l=j,m=c.colorMixin(c.marginMixin(c.baseMixin({})));m._mandatoryAttributes(["group"]),m.title(m.colorAccessor());var n=function(a){e(0,a)},o=function(a){e(1,a)},p=function(a){var b=a.key;c.events.trigger(function(){m.filter(b),m.redrawGroup()})};return c.override(m,"filter",function(a){return arguments.length?m._filter(c.filters.TwoDimensionalFilter(a)):m._filter()}),m.rows=function(b){if(arguments.length)return i=b,m;if(i)return i;var c=m.data().map(m.valueAccessor());return c.sort(a.ascending),a.scale.ordinal().domain(c.filter(f))},m.cols=function(b){if(arguments.length)return h=b,m;if(h)return h;var c=m.data().map(m.keyAccessor());return c.sort(a.ascending),a.scale.ordinal().domain(c.filter(f))},m._doRender=function(){return m.resetSvg(),g=m.svg().append("g").attr("class","heatmap").attr("transform","translate("+m.margins().left+","+m.margins().top+")"),m._doRedraw()},m._doRedraw=function(){var a=m.rows(),b=m.cols(),d=a.domain().length,e=b.domain().length,f=Math.floor(m.effectiveWidth()/e),h=Math.floor(m.effectiveHeight()/d);b.rangeRoundBands([0,m.effectiveWidth()]),a.rangeRoundBands([m.effectiveHeight(),0]);var i=g.selectAll("g.box-group").data(m.data(),function(a,b){return m.keyAccessor()(a,b)+"\x00"+m.valueAccessor()(a,b)}),j=i.enter().append("g").attr("class","box-group");j.append("rect").attr("class","heat-box").attr("fill","white").on("click",m.boxOnClick()),m.renderTitle()&&j.append("title").text(m.title()),c.transition(i.selectAll("rect"),m.transitionDuration()).attr("x",function(a,c){return b(m.keyAccessor()(a,c))}).attr("y",function(b,c){return a(m.valueAccessor()(b,c))}).attr("rx",k).attr("ry",l).attr("fill",m.getColor).attr("width",f).attr("height",h),i.exit().remove();var n=g.selectAll("g.cols");n.empty()&&(n=g.append("g").attr("class","cols axis"));var o=n.selectAll("text").data(b.domain());o.enter().append("text").attr("x",function(a){return b(a)+f/2}).style("text-anchor","middle").attr("y",m.effectiveHeight()).attr("dy",12).on("click",m.xAxisOnClick()).text(function(a){return a}),c.transition(o,m.transitionDuration()).text(function(a){return a}).attr("x",function(a){return b(a)+f/2}),o.exit().remove();var p=g.selectAll("g.rows");p.empty()&&(p=g.append("g").attr("class","rows axis"));var q=p.selectAll("text").data(a.domain());return q.enter().append("text").attr("dy",6).style("text-anchor","end").attr("x",0).attr("dx",-2).on("click",m.yAxisOnClick()).text(function(a){return a}),c.transition(q,m.transitionDuration()).text(function(a){return a}).attr("y",function(b){return a(b)+h/2}),q.exit().remove(),m.selectAll("g.box-group").each(m.hasFilter()?function(a){m.isSelectedNode(a)?m.highlightSelected(this):m.fadeDeselected(this)}:function(){m.resetHighlight(this)}),m},m.boxOnClick=function(a){return arguments.length?(p=a,m):p},m.xAxisOnClick=function(a){return arguments.length?(n=a,m):n},m.yAxisOnClick=function(a){return arguments.length?(o=a,m):o},m.xBorderRadius=function(a){return arguments.length?(k=a,m):k},m.yBorderRadius=function(a){return arguments.length?(l=a,m):l},m.isSelectedNode=function(a){return m.hasFilter(a.key)},m.anchor(b,d)},function(){function b(a){return[0,a.length-1]}function c(b){return[a.quantile(b,.25),a.quantile(b,.5),a.quantile(b,.75)]}a.box=function(){function d(b){b.each(function(b,c){b=b.map(i).sort(a.ascending);var d=a.select(this),m=b.length,n=b[0],o=b[m-1],p=b.quartiles=k(b),q=j&&j.call(this,b,c),r=q&&q.map(function(a){return b[a]}),s=q?a.range(0,q[0]).concat(a.range(q[1]+1,m)):a.range(m),t=a.scale.linear().domain(h&&h.call(this,b,c)||[n,o]).range([f,0]),u=this.__chart__||a.scale.linear().domain([0,1/0]).range(t.range());this.__chart__=t;var v=d.selectAll("line.center").data(r?[r]:[]);v.enter().insert("line","rect").attr("class","center").attr("x1",e/2).attr("y1",function(a){return u(a[0])}).attr("x2",e/2).attr("y2",function(a){return u(a[1])}).style("opacity",1e-6).transition().duration(g).style("opacity",1).attr("y1",function(a){return t(a[0])}).attr("y2",function(a){return t(a[1])}),v.transition().duration(g).style("opacity",1).attr("y1",function(a){return t(a[0])}).attr("y2",function(a){return t(a[1])}),v.exit().transition().duration(g).style("opacity",1e-6).attr("y1",function(a){return t(a[0])}).attr("y2",function(a){return t(a[1])}).remove();var w=d.selectAll("rect.box").data([p]);w.enter().append("rect").attr("class","box").attr("x",0).attr("y",function(a){return u(a[2])}).attr("width",e).attr("height",function(a){return u(a[0])-u(a[2])}).transition().duration(g).attr("y",function(a){return t(a[2])}).attr("height",function(a){return t(a[0])-t(a[2])}),w.transition().duration(g).attr("y",function(a){return t(a[2])}).attr("height",function(a){return t(a[0])-t(a[2])});var x=d.selectAll("line.median").data([p[1]]);x.enter().append("line").attr("class","median").attr("x1",0).attr("y1",u).attr("x2",e).attr("y2",u).transition().duration(g).attr("y1",t).attr("y2",t),x.transition().duration(g).attr("y1",t).attr("y2",t);var y=d.selectAll("line.whisker").data(r||[]);y.enter().insert("line","circle, text").attr("class","whisker").attr("x1",0).attr("y1",u).attr("x2",e).attr("y2",u).style("opacity",1e-6).transition().duration(g).attr("y1",t).attr("y2",t).style("opacity",1),y.transition().duration(g).attr("y1",t).attr("y2",t).style("opacity",1),y.exit().transition().duration(g).attr("y1",t).attr("y2",t).style("opacity",1e-6).remove();var z=d.selectAll("circle.outlier").data(s,Number);z.enter().insert("circle","text").attr("class","outlier").attr("r",5).attr("cx",e/2).attr("cy",function(a){return u(b[a])}).style("opacity",1e-6).transition().duration(g).attr("cy",function(a){return t(b[a])}).style("opacity",1),z.transition().duration(g).attr("cy",function(a){return t(b[a])}).style("opacity",1),z.exit().transition().duration(g).attr("cy",function(a){return t(b[a])}).style("opacity",1e-6).remove();var A=l||t.tickFormat(8),B=d.selectAll("text.box").data(p);B.enter().append("text").attr("class","box").attr("dy",".3em").attr("dx",function(a,b){return 1&b?6:-6}).attr("x",function(a,b){return 1&b?e:0}).attr("y",u).attr("text-anchor",function(a,b){return 1&b?"start":"end"}).text(A).transition().duration(g).attr("y",t),B.transition().duration(g).text(A).attr("y",t);var C=d.selectAll("text.whisker").data(r||[]);C.enter().append("text").attr("class","whisker").attr("dy",".3em").attr("dx",6).attr("x",e).attr("y",u).text(A).style("opacity",1e-6).transition().duration(g).attr("y",t).style("opacity",1),C.transition().duration(g).text(A).attr("y",t).style("opacity",1),C.exit().transition().duration(g).attr("y",t).style("opacity",1e-6).remove()}),a.timer.flush()}var e=1,f=1,g=0,h=null,i=Number,j=b,k=c,l=null;return d.width=function(a){return arguments.length?(e=a,d):e},d.height=function(a){return arguments.length?(f=a,d):f},d.tickFormat=function(a){return arguments.length?(l=a,d):l},d.duration=function(a){return arguments.length?(g=a,d):g},d.domain=function(b){return arguments.length?(h=null===b?b:a.functor(b),d):h},d.value=function(a){return arguments.length?(i=a,d):i},d.whiskers=function(a){return arguments.length?(j=a,d):j},d.quartiles=function(a){return arguments.length?(k=a,d):k},d}}(),c.boxPlot=function(b,d){function e(a){return function(b){for(var c=b.quartiles[0],d=b.quartiles[2],e=(d-c)*a,f=-1,g=b.length;b[++f]<c-e;);for(;b[--g]>d+e;);return[f,g]}}function f(a){var b=a.enter().append("g");b.attr("class","box").attr("transform",p).call(m).on("click",function(a){i.filter(a.key),i.redrawGroup()})}function g(b){c.transition(b,i.transitionDuration()).attr("transform",p).call(m).each(function(){a.select(this).select("rect.box").attr("fill",i.getColor)})}function h(a){a.exit().remove().call(m)}var i=c.coordinateGridMixin({}),j=1.5,k=e,l=k(j),m=a.box(),n=null,o=function(a,b){return i.isOrdinal()?i.x().rangeBand():a/(1+i.boxPadding())/b};i.yAxisPadding(12),i.x(a.scale.ordinal()),i.xUnits(c.units.ordinal),i.data(function(a){return a.all().map(function(a){return a.map=function(b){return b.call(a,a)},a}).filter(function(a){var b=i.valueAccessor()(a);return 0!==b.length})}),i.boxPadding=i._rangeBandPadding,i.boxPadding(.8),i.outerPadding=i._outerRangeBandPadding,i.outerPadding(.5),i.boxWidth=function(b){return arguments.length?(o=a.functor(b),i):o};var p=function(a,b){var c=i.x()(i.keyAccessor()(a,b));return"translate("+c+", 0)"};return i._preprocessData=function(){i.elasticX()&&i.x().domain([])},i.plotData=function(){var a=o(i.effectiveWidth(),i.xUnitCount());m.whiskers(l).width(a).height(i.effectiveHeight()).value(i.valueAccessor()).domain(i.y().domain()).duration(i.transitionDuration()).tickFormat(n);var b=i.chartBodyG().selectAll("g.box").data(i.data(),function(a){return a.key});f(b),g(b),h(b),i.fadeDeselectedArea()},i.fadeDeselectedArea=function(){i.g().selectAll("g.box").each(i.hasFilter()?function(a){i.isSelectedNode(a)?i.highlightSelected(this):i.fadeDeselected(this)}:function(){i.resetHighlight(this)})},i.isSelectedNode=function(a){return i.hasFilter(a.key)},i.yAxisMin=function(){var b=a.min(i.data(),function(b){return a.min(i.valueAccessor()(b))});return c.utils.subtract(b,i.yAxisPadding())},i.yAxisMax=function(){var b=a.max(i.data(),function(b){return a.max(i.valueAccessor()(b))});return c.utils.add(b,i.yAxisPadding())},i.tickFormat=function(a){return arguments.length?(n=a,i):n},i.anchor(b,d)},c.abstractBubbleChart=c.bubbleMixin,c.baseChart=c.baseMixin,c.capped=c.capMixin,c.colorChart=c.colorMixin,c.coordinateGridChart=c.coordinateGridMixin,c.marginable=c.marginMixin,c.stackableChart=c.stackMixin,c.d3=a,c.crossfilter=b,c}if("function"==typeof define&&define.amd)define(["d3","crossfilter"],a);else if("object"==typeof module&&module.exports){var b=require("d3"),c=require("crossfilter");"function"!=typeof c&&(c=c.crossfilter),module.exports=a(b,c)}else this.dc=a(d3,crossfilter)}(); | |
//# sourceMappingURL=dc.min.js.map |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<meta name="description" content="Sanisphere coverage pharmacies and Universe"> | |
<meta name="author" content="Simon Renauld"> | |
<link rel=" icon" href="favicon.icon" type="image/x-icon"> | |
</head> | |
<html> | |
<title>Sanisphere Coverage</title> | |
<!--.Leaflet, Dynamic Charting, Crossfilter, DataTables, MarkerCluster, core JavaScript --> | |
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script src="bower_components/crossfilter/crossfilter.min.js" charset="utf-8"></script> | |
<script src="bower_components/dcjs/dc.min.js" charset="utf-8"></script> | |
<script src="bower_components/leaflet.markercluster/leaflet.markercluster.js"></script> | |
<script src="plugins/L.D3SvgOverlay.min.js"></script> | |
<script src="plugins/leaflet-bing-layer.js"></script> | |
<script src="plugins/leaflet-search.js"></script> | |
<script type="text/javascript" src='bower_components/dcjs/jquery.js'></script> | |
<script type="text/javascript" src="plugins/labs-common.js"></script> | |
<script type="text/javascript" src="geojson/ward.js"></script> | |
<!-- Datasets SHAPEFILES to geojson--> | |
<script src="pharmacy.geojson.js"></script> | |
<script src="hospital.geojson.js"></script> | |
<!-- Bootstrap core CSS --> | |
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"> | |
<!-- Leaflet, Dynamic Charting, Crossfilter, DataTables, MarkerCluster, core CSS --> | |
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" /> | |
<link rel="stylesheet" href="bower_components/dcjs/dc.css" /> | |
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css" integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA==" crossorigin=""/> | |
<link rel="stylesheet" href="bower_components/leaflet.markercluster/dist/MarkerCluster.Default.css" /> | |
<link rel="stylesheet" href="css/leaflet-search.css" /> | |
<link rel="stylesheet" href="css/style.css" /> | |
<link type="text/css" href="css/dc.css" rel="stylesheet"/> | |
<link type="text/css" href="css/bootstrap.min.css" rel="stylesheet"> | |
<script src="https://cdn.datatables.net/1.10.5/js/jquery.dataTables.min.js" type="text/javascript"></script> | |
<style> | |
.leaflet-marker-icon { | |
color: #fff; | |
font-size: 16px; | |
line-height: 16px; | |
text-align: center; | |
vertical-align: middle; | |
box-shadow: 2px 1px 4px rgba(0,0,0,0.3); | |
border-radius: 8px; | |
border:1px solid #fff; | |
} | |
.search-tip b { | |
color: #fff; | |
} | |
.pharmacy.search-tip b, | |
.pharmacy.leaflet-marker-icon { | |
background: #f6f | |
} | |
.hospital.search-tip b, | |
.hospital.leaflet-marker-icon { | |
background: #66f | |
} | |
.search-tip { | |
white-space: nowrap; | |
} | |
.search-tip b { | |
display: inline-block; | |
clear: left; | |
float: right; | |
padding: 0 8px; | |
margin-left: 4px; | |
} | |
<style> | |
html, body { | |
height: 100%; | |
margin: 0; | |
} | |
#map { | |
width: 600px; | |
height: 400px; | |
} | |
</style> | |
<!-- Custom styles for this template --> | |
<link href="css/construxn.css" rel="stylesheet"> | |
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> | |
<!--[if lt IE 9]> | |
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> | |
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> | |
<![endif]--> | |
</head> | |
<body> | |
<div class='col-md-7'> | |
<div class="col-md-8"> | |
</div> | |
<div class="container"> | |
<div class='row'> | |
<div id='map'></div> | |
</div> | |
</div> | |
</style> | |
</div> | |
<div class='col-md-2' id="District-chart"> | |
<h6>District | |
<a class="reset" href="javascript:districtChart.filterAll();dc.redrawAll();" style="display: none;">reset</a> | |
</h6> | |
</div> | |
<div class='col-md-3'> | |
<div class='row col-md-12' id="ward_type-chart"> | |
<h6>ward | |
<a class="reset" href="javascript:wardTypesChart.filterAll();dc.redrawAll();" style="display: none;">reset</a> | |
</h6> | |
</div> | |
<div class='row col-md-12' id="city-chart"> | |
<h6>City | |
<a class="reset" href="javascript:citiesChart.filterAll();dc.redrawAll();" style="display: none;">reset</a> | |
</h6> | |
</div> | |
<div class='row col-md-8' id="type-chart"> | |
<h6>Type | |
<a class="reset" href="javascript:.typechart.filterAll();dc.redrawAll();" style="display: none;">reset</a> | |
</h6> | |
</div> | |
<div class='row'> | |
<div class='col-xs-12'> | |
<h3>Data Table</h3> | |
<table class='table table-hover' id='dc-table-chart'> | |
<thead> | |
<tr class='header'> | |
<th>Issuance Date</th> | |
<th>Contact</th> | |
<th>City</th> | |
<th>Permit Type</th> | |
<th>Address</th> | |
</tr> | |
</thead> | |
</table> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Create a new group to which we can (later) add or remove our markers / spots on the map | |
var markersLayer = new L.LayerGroup(); // NOTE: Layer is created here! | |
///////////////////////////////////////////////////// | |
// Create a new cluster group to which we can (later) add or remove our markers / spots on the map | |
var clusterLayer = new L.MarkerClusterGroup(); | |
//////////////////////////////////////////////////////////////////////////////////////////// | |
// Set up the base map tile layers (this is the terrain, imagery, etc) | |
/////////////////////////////////////////////////////////////////////////////////////////// | |
var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> ', | |
maxZoom: 18 | |
}); | |
var cartodb_light = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{ | |
attribution: 'Map data © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="http://cartodb.com/attributions">CartoDB</a> ', | |
maxZoom:18 | |
}); | |
var cartodb_dark = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',{ | |
attribution: 'Map data © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="http://cartodb.com/attributions">CartoDB</a>', | |
maxZoom:18 | |
}); | |
// Our basemaps (tile layers) | |
var baseMaps = { | |
"OpenStreetMap": cartodb_light, | |
"Esri Basemaps": cartodb_dark, | |
"OpenStreetMap": osm | |
}; | |
// Our overlays | |
var overlays = { | |
"Clustered Markers": clusterLayer, | |
"Individual Markers": markersLayer | |
}; | |
////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Initialize the Leaflet map and set an initial location and zoom level | |
//////////////////////////////////////////////////////////////////////////////////////////////////// | |
var map = L.map('map', { | |
center: [10.762622, 106.660172], | |
zoom: 10, | |
layers: [cartodb_light, clusterLayer] | |
}), | |
/////////////////////////////////////////////////////////////// | |
/// ADD HOSPITAL AND PHARMACIES SEARCH MULTI LAYER | |
///////////////////////////////////////////////////////////////// | |
geojsonOpts = { | |
pointToLayer: function(feature, latlng) { | |
return L.marker(latlng, { | |
icon: L.divIcon({ | |
className: feature.properties.amenity, | |
iconSize: L.point(16, 16), | |
html: feature.properties.amenity[0].toUpperCase(), | |
}) | |
}).bindPopup(feature.properties.amenity+'<br><b>'+feature.properties.name_full+'<br><b>' | |
+feature.properties.id+'</b>'); | |
} | |
}; | |
var poiLayers = L.layerGroup([ | |
L.geoJson(pharmacy, geojsonOpts), | |
L.geoJson(hospital, geojsonOpts) | |
]) | |
.addTo(map); | |
L.control.search({ | |
layer: poiLayers, | |
initial: false, | |
propertyName: 'name_full', | |
buildTip: function(text, val) { | |
var type = val.layer.feature.properties.amenity; | |
return '<a href="#" class="'+type+'">'+text+'<b>'+type+'</b></a>'; | |
} | |
}) | |
.addTo(map); | |
var ward = []; | |
var wardOverlay = L.d3SvgOverlay(function(sel, proj) { | |
var upd = sel.selectAll('path').data(ward); | |
upd.enter() | |
.append('path') | |
.attr('d', proj.pathFromGeojson) | |
.attr('stroke', 'red') | |
.attr('fill-opacity', '0.2'); | |
upd.attr('stroke-width', 1 / proj.scale); | |
}); | |
///////////////////////////////////////////////////////// | |
// control that shows state info on hover of the ward | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
//////////////////////////////////////////////////////// | |
// control that shows state info on hover | |
var info = L.control(); | |
info.onAdd = function (map) { | |
this._div = L.DomUtil.create('div', 'info'); | |
this.update(); | |
return this._div; | |
}; | |
info.update = function (props) { | |
this._div.innerHTML = '<h4>Geography</h4>' + (props ? | |
'<b>' + props.ADM2_NAME + '</b><br />'+ props.ADM3_NAME + '</b><br />' : 'Hover over a ward'); | |
}; | |
info.addTo(map); | |
// get color depending on population density value | |
function getColor(d) { | |
return d > 98500 ? '#800026' : | |
d > 70000 ? '#BD0026' : | |
d > 60000 ? '#E31A1C' : | |
d > 35000 ? '#FC4E2A' : | |
d > 20000 ? '#FD8D3C' : | |
d > 10000 ? '#FEB24C' : | |
d > 5000 ? '#FED976' : | |
'#FFEDA0'; | |
} | |
function style(feature) { | |
return { | |
weight: 2, | |
opacity: 0.5, | |
color: 'white', | |
dashArray: '3', | |
fillOpacity: 0.0, | |
fillColor: getColor(feature.properties.POP) | |
}; | |
} | |
function highlightFeature(e) { | |
var layer = e.target; | |
layer.setStyle({ | |
weight: 1, | |
color: '#666', | |
dashArray: '', | |
fillOpacity: 0.0 | |
}); | |
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) { | |
layer.bringToFront(); | |
} | |
info.update(layer.feature.properties); | |
} | |
var geojson; | |
function resetHighlight(e) { | |
geojson.resetStyle(e.target); | |
info.update(); | |
} | |
function zoomToFeature(e) { | |
map.fitBounds(e.target.getBounds()); | |
} | |
function onEachFeature(feature, layer) { | |
layer.on({ | |
mouseover: highlightFeature, | |
mouseout: resetHighlight, | |
click: zoomToFeature | |
}); | |
} | |
geojson = L.geoJson(warddata, { | |
style: style, | |
onEachFeature: onEachFeature | |
}).addTo(map); | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
///////// DISPLAY and PROJECT GEOJSON SHAPEFILES WARDS AND DISTRICTS | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
var district = []; | |
var districtOverlay = L.d3SvgOverlay(function(sel, proj) { | |
var upd = sel.selectAll('path').data(district); | |
upd.enter() | |
.append('path') | |
.attr('d', proj.pathFromGeojson) | |
.attr('stroke', 'black') | |
.attr('fill-opacity', '0.1'); | |
upd.attr('stroke-width', 1 / proj.scale); | |
}); | |
//////////////////////// Add the control base layers to the map //////////////////////////////////////////////// | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
//L.control.layers(baseMaps, overlays).addTo(map); | |
L.control.layers(baseMaps, overlays).addTo(map); | |
L.control.layers({"District": districtOverlay, "ward": wardOverlay}).addTo(map); | |
d3.json("geojson/ward.geo.json", function(data) { ward = data.features; wardOverlay.addTo(map) }); | |
d3.json("geojson/district.geo.json", function(data) { district = data.features; districtOverlay.addTo(map) }); | |
////////////////////////CHARTS!!!////////////////////////////////////// | |
//////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Initialize our dc.js charts, passing the DOM Id in which we want the chart rendered as an argument | |
var citiesChart = dc.rowChart("#city-chart"); | |
var districtChart = dc.rowChart("#District-chart"); | |
var wardTypesChart = dc.rowChart("#ward_type-chart"); | |
var typeChart = dc.rowChart("#type-chart"); | |
var dataCount = dc.dataCount('#data-count'); | |
var datatable = $('#dc-table-chart'); | |
// A common color for all of the bar and row charts | |
var commonChartBarColor = '#b3d9ff'; | |
// This is where we will hold our crossfilter data | |
var xdata = null; | |
var all = null; | |
var City = null; | |
var locations = null; | |
// Called when dc.js is filtered (typically from user click interaction) | |
var onFilt = function(chart, filter) { | |
updateMap(locations.top(Infinity)); | |
}; | |
// Updates the displayed map markers to reflect the crossfilter dimension passed in | |
var updateMap = function(locs) { | |
// clear the existing markers from the map | |
markersLayer.clearLayers(); | |
clusterLayer.clearLayers(); | |
locs.forEach( function(d, i) { | |
}); | |
}; | |
/////////////////////////////////////////////////////// | |
// http://wallinm1.github.io/map-dashboard/ | |
///////////////////////////////////////////////////// | |
///////// VIEW FOR DINAMICS CHARTS AND MAP | |
/////////////////////////////////////////////////////// | |
// d3's JSON call to grab the JSON data | |
d3.csv("universe_test.csv", function(error, data) { | |
// used by d3's dateFormat to parse the date correctly | |
var dateFormat = d3.time.format("%Y-%m-%dT%H:%M:%S"); | |
// add map markers to map layer | |
data.forEach( function(d,i) { | |
d.date_e = dateFormat.parse(d.date_entered); | |
d.date_i = dateFormat.parse(d.date_issued); | |
}); | |
// Construct the charts | |
xdata = crossfilter(data); | |
var all = xdata.groupAll(); | |
dataCount.dimension(xdata) | |
.group(all); | |
// Define the crossfilter dimensions | |
cities = xdata.dimension(function (d) { return d.City; }); | |
locations = xdata.dimension(function (d) { return d.all; }); | |
var districttype = xdata.dimension(function (d) { return d.zip; }); | |
var wardTypes = xdata.dimension(function (d) { return d.ward; }); | |
var coordinates = xdata.dimension(function(d) { return d.geo; }); | |
var type = xdata.dimension(function(d) { return d.type; }); | |
// Marker Chart | |
typeChart.width($('#type-chart').innerWidth()-30) | |
.height(250) | |
.colors(commonChartBarColor) | |
.margins({top: 10, left: 20, right: 10, bottom: 20}) | |
.group(type.group()) | |
.dimension(type) | |
.elasticX(true) | |
.on("filtered", onFilt); | |
// Start constructing the charts and setting each chart's options | |
citiesChart.width($('#city-chart').innerWidth()-30) | |
.height(250) | |
.colors(commonChartBarColor) | |
.margins({top: 10, left: 20, right: 10, bottom: 20}) | |
.group(cities.group()) | |
.dimension(cities) | |
.elasticX(true) | |
.on("filtered", onFilt); | |
districtChart.width($('#District-chart').innerWidth()-30) | |
.height(window.innerHeight - 50) | |
.colors(commonChartBarColor) | |
.margins({top: 10, left: 10, right: 10, bottom: 20}) | |
.group(districttype.group()) | |
.dimension(districttype) | |
.elasticX(true) | |
.on("filtered", onFilt); | |
wardTypesChart.width($('#ward_type-chart').innerWidth()-30) | |
.height(400) | |
.colors(commonChartBarColor) | |
.margins({top: 10, left: 20, right: 10, bottom: 20}) | |
.group(wardTypes.group()) | |
.dimension(wardTypes) | |
.elasticX(true) | |
.on("filtered", onFilt); | |
dataCount | |
.dimension(xdata) | |
.group(all); | |
//////////////////////////////////////////////////////////////////////////////////////////////////////// | |
//VIEW FOR DATA TABLE /////////////////////////////////////////// | |
////////////////////////////////////////////////////////// | |
//table | |
//dimension for table search | |
var tableDimension = xdata.dimension(function (d) { return d.pop.toLowerCase() + ' ' + | |
d.City.toLowerCase() + ' ' + | |
d.Province.toLowerCase() + ' ' + | |
d.zip.toLowerCase() + ' ' + | |
d.ward.toLowerCase();}); | |
//set options and columns | |
var dataTableOptions = { | |
"bSort": true, | |
columnDefs: [ | |
{ | |
targets: 0, | |
data: function (d) { return d.date_entered; }, | |
type: 'date', | |
defaultContent: 'Not found' | |
}, | |
{ | |
targets: 1, | |
data: function (d) { return d.amenity; }, | |
defaultContent: '' | |
}, | |
{ | |
targets: 2, | |
data: function (d) { return d.type; }, | |
defaultContent: '' | |
}, | |
{ | |
targets: 3, | |
data: function (d) { return d.date_issued;}, | |
defaultContent: '' | |
}, | |
{ | |
targets: 4, | |
data: function (d) {return d.name_full;}, | |
defaultContent: '' | |
}, | |
{ | |
targets: 5, //search column | |
data: function (d) {return d.pop;}, | |
defaultContent: '', | |
visible: false | |
} | |
] | |
}; | |
//initialize datatable | |
datatable.dataTable(dataTableOptions); | |
//row details | |
function format ( d ) { | |
return '<b>Purpose: </b>' + d.purpose; | |
} | |
datatable.DataTable().on('click', 'tr[role="row"]', function () { | |
var tr = $(this); | |
var row = datatable.DataTable().row( tr ); | |
if ( row.child.isShown() ) { | |
// This row is already open - close it | |
row.child.hide(); | |
tr.removeClass('shown'); | |
} | |
else { | |
// Open this row | |
row.child( format(row.data()) ).show(); | |
tr.addClass('shown'); | |
} | |
} ); | |
//custom refresh function, see http://stackoverflow.com/questions/21113513/dcjs-reorder-datatable-by-column/21116676#21116676 | |
function RefreshTable() { | |
dc.events.trigger(function () { | |
alldata = tableDimension.top(Infinity); | |
datatable.fnClearTable(); | |
datatable.fnAddData(alldata); | |
datatable.fnDraw(); | |
}); | |
} | |
//call RefreshTable when dc-charts are filtered | |
for (var i = 0; i < dc.chartRegistry.list().length; i++) { | |
var chartI = dc.chartRegistry.list()[i]; | |
chartI.on("filtered", RefreshTable); | |
} | |
//filter all charts when using the datatables search box | |
$(":input").on('keyup',function(){ | |
text_filter(tableDimension, this.value);//cities is the dimension for the data table | |
function text_filter(dim,q){ | |
if (q!='') { | |
dim.filter(function(d){ | |
return d.indexOf (q.toLowerCase()) !== -1; | |
}); | |
} else { | |
dim.filterAll(); | |
} | |
RefreshTable(); | |
dc.redrawAll();} | |
}); | |
//initial table refresh | |
RefreshTable(); | |
//initialize other charts | |
dc.renderAll(); | |
}); | |
</script> | |
<!-- Bootstrap core JavaScript | |
================================================== --> | |
<!-- Placed at the end of the document so the pages load faster --> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> | |
</body> | |
</html> | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc{cursor:pointer;*cursor:hand}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("../images/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("../images/sort_asc.png")}table.dataTable thead .sorting_desc{background-image:url("../images/sort_desc.png")}table.dataTable thead .sorting_asc_disabled{background-image:url("../images/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("../images/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#ffffff}table.dataTable tbody tr.selected{background-color:#B0BED9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table,.dataTables_wrapper.no-footer div.dataTables_scrollBody table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* jQuery JavaScript Library v2.1.4 | |
* http://jquery.com/ | |
* | |
* Includes Sizzle.js | |
* http://sizzlejs.com/ | |
* | |
* Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors | |
* Released under the MIT license | |
* http://jquery.org/license | |
* | |
* Date: 2015-04-28T16:01Z | |
*/ | |
(function( global, factory ) { | |
if ( typeof module === "object" && typeof module.exports === "object" ) { | |
// For CommonJS and CommonJS-like environments where a proper `window` | |
// is present, execute the factory and get jQuery. | |
// For environments that do not have a `window` with a `document` | |
// (such as Node.js), expose a factory as module.exports. | |
// This accentuates the need for the creation of a real `window`. | |
// e.g. var jQuery = require("jquery")(window); | |
// See ticket #14549 for more info. | |
module.exports = global.document ? | |
factory( global, true ) : | |
function( w ) { | |
if ( !w.document ) { | |
throw new Error( "jQuery requires a window with a document" ); | |
} | |
return factory( w ); | |
}; | |
} else { | |
factory( global ); | |
} | |
// Pass this if window is not defined yet | |
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { | |
// Support: Firefox 18+ | |
// Can't be in strict mode, several libs including ASP.NET trace | |
// the stack via arguments.caller.callee and Firefox dies if | |
// you try to trace through "use strict" call chains. (#13335) | |
// | |
var arr = []; | |
var slice = arr.slice; | |
var concat = arr.concat; | |
var push = arr.push; | |
var indexOf = arr.indexOf; | |
var class2type = {}; | |
var toString = class2type.toString; | |
var hasOwn = class2type.hasOwnProperty; | |
var support = {}; | |
var | |
// Use the correct document accordingly with window argument (sandbox) | |
document = window.document, | |
version = "2.1.4", | |
// Define a local copy of jQuery | |
jQuery = function( selector, context ) { | |
// The jQuery object is actually just the init constructor 'enhanced' | |
// Need init if jQuery is called (just allow error to be thrown if not included) | |
return new jQuery.fn.init( selector, context ); | |
}, | |
// Support: Android<4.1 | |
// Make sure we trim BOM and NBSP | |
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, | |
// Matches dashed string for camelizing | |
rmsPrefix = /^-ms-/, | |
rdashAlpha = /-([\da-z])/gi, | |
// Used by jQuery.camelCase as callback to replace() | |
fcamelCase = function( all, letter ) { | |
return letter.toUpperCase(); | |
}; | |
jQuery.fn = jQuery.prototype = { | |
// The current version of jQuery being used | |
jquery: version, | |
constructor: jQuery, | |
// Start with an empty selector | |
selector: "", | |
// The default length of a jQuery object is 0 | |
length: 0, | |
toArray: function() { | |
return slice.call( this ); | |
}, | |
// Get the Nth element in the matched element set OR | |
// Get the whole matched element set as a clean array | |
get: function( num ) { | |
return num != null ? | |
// Return just the one element from the set | |
( num < 0 ? this[ num + this.length ] : this[ num ] ) : | |
// Return all the elements in a clean array | |
slice.call( this ); | |
}, | |
// Take an array of elements and push it onto the stack | |
// (returning the new matched element set) | |
pushStack: function( elems ) { | |
// Build a new jQuery matched element set | |
var ret = jQuery.merge( this.constructor(), elems ); | |
// Add the old object onto the stack (as a reference) | |
ret.prevObject = this; | |
ret.context = this.context; | |
// Return the newly-formed element set | |
return ret; | |
}, | |
// Execute a callback for every element in the matched set. | |
// (You can seed the arguments with an array of args, but this is | |
// only used internally.) | |
each: function( callback, args ) { | |
return jQuery.each( this, callback, args ); | |
}, | |
map: function( callback ) { | |
return this.pushStack( jQuery.map(this, function( elem, i ) { | |
return callback.call( elem, i, elem ); | |
})); | |
}, | |
slice: function() { | |
return this.pushStack( slice.apply( this, arguments ) ); | |
}, | |
first: function() { | |
return this.eq( 0 ); | |
}, | |
last: function() { | |
return this.eq( -1 ); | |
}, | |
eq: function( i ) { | |
var len = this.length, | |
j = +i + ( i < 0 ? len : 0 ); | |
return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); | |
}, | |
end: function() { | |
return this.prevObject || this.constructor(null); | |
}, | |
// For internal use only. | |
// Behaves like an Array's method, not like a jQuery method. | |
push: push, | |
sort: arr.sort, | |
splice: arr.splice | |
}; | |
jQuery.extend = jQuery.fn.extend = function() { | |
var options, name, src, copy, copyIsArray, clone, | |
target = arguments[0] || {}, | |
i = 1, | |
length = arguments.length, | |
deep = false; | |
// Handle a deep copy situation | |
if ( typeof target === "boolean" ) { | |
deep = target; | |
// Skip the boolean and the target | |
target = arguments[ i ] || {}; | |
i++; | |
} | |
// Handle case when target is a string or something (possible in deep copy) | |
if ( typeof target !== "object" && !jQuery.isFunction(target) ) { | |
target = {}; | |
} | |
// Extend jQuery itself if only one argument is passed | |
if ( i === length ) { | |
target = this; | |
i--; | |
} | |
for ( ; i < length; i++ ) { | |
// Only deal with non-null/undefined values | |
if ( (options = arguments[ i ]) != null ) { | |
// Extend the base object | |
for ( name in options ) { | |
src = target[ name ]; | |
copy = options[ name ]; | |
// Prevent never-ending loop | |
if ( target === copy ) { | |
continue; | |
} | |
// Recurse if we're merging plain objects or arrays | |
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { | |
if ( copyIsArray ) { | |
copyIsArray = false; | |
clone = src && jQuery.isArray(src) ? src : []; | |
} else { | |
clone = src && jQuery.isPlainObject(src) ? src : {}; | |
} | |
// Never move original objects, clone them | |
target[ name ] = jQuery.extend( deep, clone, copy ); | |
// Don't bring in undefined values | |
} else if ( copy !== undefined ) { | |
target[ name ] = copy; | |
} | |
} | |
} | |
} | |
// Return the modified object | |
return target; | |
}; | |
jQuery.extend({ | |
// Unique for each copy of jQuery on the page | |
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), | |
// Assume jQuery is ready without the ready module | |
isReady: true, | |
error: function( msg ) { | |
throw new Error( msg ); | |
}, | |
noop: function() {}, | |
isFunction: function( obj ) { | |
return jQuery.type(obj) === "function"; | |
}, | |
isArray: Array.isArray, | |
isWindow: function( obj ) { | |
return obj != null && obj === obj.window; | |
}, | |
isNumeric: function( obj ) { | |
// parseFloat NaNs numeric-cast false positives (null|true|false|"") | |
// ...but misinterprets leading-number strings, particularly hex literals ("0x...") | |
// subtraction forces infinities to NaN | |
// adding 1 corrects loss of precision from parseFloat (#15100) | |
return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; | |
}, | |
isPlainObject: function( obj ) { | |
// Not plain objects: | |
// - Any object or value whose internal [[Class]] property is not "[object Object]" | |
// - DOM nodes | |
// - window | |
if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { | |
return false; | |
} | |
if ( obj.constructor && | |
!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { | |
return false; | |
} | |
// If the function hasn't returned already, we're confident that | |
// |obj| is a plain object, created by {} or constructed with new Object | |
return true; | |
}, | |
isEmptyObject: function( obj ) { | |
var name; | |
for ( name in obj ) { | |
return false; | |
} | |
return true; | |
}, | |
type: function( obj ) { | |
if ( obj == null ) { | |
return obj + ""; | |
} | |
// Support: Android<4.0, iOS<6 (functionish RegExp) | |
return typeof obj === "object" || typeof obj === "function" ? | |
class2type[ toString.call(obj) ] || "object" : | |
typeof obj; | |
}, | |
// Evaluates a script in a global context | |
globalEval: function( code ) { | |
var script, | |
indirect = eval; | |
code = jQuery.trim( code ); | |
if ( code ) { | |
// If the code includes a valid, prologue position | |
// strict mode pragma, execute code by injecting a | |
// script tag into the document. | |
if ( code.indexOf("use strict") === 1 ) { | |
script = document.createElement("script"); | |
script.text = code; | |
document.head.appendChild( script ).parentNode.removeChild( script ); | |
} else { | |
// Otherwise, avoid the DOM node creation, insertion | |
// and removal by using an indirect global eval | |
indirect( code ); | |
} | |
} | |
}, | |
// Convert dashed to camelCase; used by the css and data modules | |
// Support: IE9-11+ | |
// Microsoft forgot to hump their vendor prefix (#9572) | |
camelCase: function( string ) { | |
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); | |
}, | |
nodeName: function( elem, name ) { | |
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); | |
}, | |
// args is for internal usage only | |
each: function( obj, callback, args ) { | |
var value, | |
i = 0, | |
length = obj.length, | |
isArray = isArraylike( obj ); | |
if ( args ) { | |
if ( isArray ) { | |
for ( ; i < length; i++ ) { | |
value = callback.apply( obj[ i ], args ); | |
if ( value === false ) { | |
break; | |
} | |
} | |
} else { | |
for ( i in obj ) { | |
value = callback.apply( obj[ i ], args ); | |
if ( value === false ) { | |
break; | |
} | |
} | |
} | |
// A special, fast, case for the most common use of each | |
} else { | |
if ( isArray ) { | |
for ( ; i < length; i++ ) { | |
value = callback.call( obj[ i ], i, obj[ i ] ); | |
if ( value === false ) { | |
break; | |
} | |
} | |
} else { | |
for ( i in obj ) { | |
value = callback.call( obj[ i ], i, obj[ i ] ); | |
if ( value === false ) { | |
break; | |
} | |
} | |
} | |
} | |
return obj; | |
}, | |
// Support: Android<4.1 | |
trim: function( text ) { | |
return text == null ? | |
"" : | |
( text + "" ).replace( rtrim, "" ); | |
}, | |
// results is for internal usage only | |
makeArray: function( arr, results ) { | |
var ret = results || []; | |
if ( arr != null ) { | |
if ( isArraylike( Object(arr) ) ) { | |
jQuery.merge( ret, | |
typeof arr === "string" ? | |
[ arr ] : arr | |
); | |
} else { | |
push.call( ret, arr ); | |
} | |
} | |
return ret; | |
}, | |
inArray: function( elem, arr, i ) { | |
return arr == null ? -1 : indexOf.call( arr, elem, i ); | |
}, | |
merge: function( first, second ) { | |
var len = +second.length, | |
j = 0, | |
i = first.length; | |
for ( ; j < len; j++ ) { | |
first[ i++ ] = second[ j ]; | |
} | |
first.length = i; | |
return first; | |
}, | |
grep: function( elems, callback, invert ) { | |
var callbackInverse, | |
matches = [], | |
i = 0, | |
length = elems.length, | |
callbackExpect = !invert; | |
// Go through the array, only saving the items | |
// that pass the validator function | |
for ( ; i < length; i++ ) { | |
callbackInverse = !callback( elems[ i ], i ); | |
if ( callbackInverse !== callbackExpect ) { | |
matches.push( elems[ i ] ); | |
} | |
} | |
return matches; | |
}, | |
// arg is for internal usage only | |
map: function( elems, callback, arg ) { | |
var value, | |
i = 0, | |
length = elems.length, | |
isArray = isArraylike( elems ), | |
ret = []; | |
// Go through the array, translating each of the items to their new values | |
if ( isArray ) { | |
for ( ; i < length; i++ ) { | |
value = callback( elems[ i ], i, arg ); | |
if ( value != null ) { | |
ret.push( value ); | |
} | |
} | |
// Go through every key on the object, | |
} else { | |
for ( i in elems ) { | |
value = callback( elems[ i ], i, arg ); | |
if ( value != null ) { | |
ret.push( value ); | |
} | |
} | |
} | |
// Flatten any nested arrays | |
return concat.apply( [], ret ); | |
}, | |
// A global GUID counter for objects | |
guid: 1, | |
// Bind a function to a context, optionally partially applying any | |
// arguments. | |
proxy: function( fn, context ) { | |
var tmp, args, proxy; | |
if ( typeof context === "string" ) { | |
tmp = fn[ context ]; | |
context = fn; | |
fn = tmp; | |
} | |
// Quick check to determine if target is callable, in the spec | |
// this throws a TypeError, but we will just return undefined. | |
if ( !jQuery.isFunction( fn ) ) { | |
return undefined; | |
} | |
// Simulated bind | |
args = slice.call( arguments, 2 ); | |
proxy = function() { | |
return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); | |
}; | |
// Set the guid of unique handler to the same of original handler, so it can be removed | |
proxy.guid = fn.guid = fn.guid || jQuery.guid++; | |
return proxy; | |
}, | |
now: Date.now, | |
// jQuery.support is not used in Core but other projects attach their | |
// properties to it so it needs to exist. | |
support: support | |
}); | |
// Populate the class2type map | |
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { | |
class2type[ "[object " + name + "]" ] = name.toLowerCase(); | |
}); | |
function isArraylike( obj ) { | |
// Support: iOS 8.2 (not reproducible in simulator) | |
// `in` check used to prevent JIT error (gh-2145) | |
// hasOwn isn't used here due to false negatives | |
// regarding Nodelist length in IE | |
var length = "length" in obj && obj.length, | |
type = jQuery.type( obj ); | |
if ( type === "function" || jQuery.isWindow( obj ) ) { | |
return false; | |
} | |
if ( obj.nodeType === 1 && length ) { | |
return true; | |
} | |
return type === "array" || length === 0 || | |
typeof length === "number" && length > 0 && ( length - 1 ) in obj; | |
} | |
var Sizzle = | |
/*! | |
* Sizzle CSS Selector Engine v2.2.0-pre | |
* http://sizzlejs.com/ | |
* | |
* Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors | |
* Released under the MIT license | |
* http://jquery.org/license | |
* | |
* Date: 2014-12-16 | |
*/ | |
(function( window ) { | |
var i, | |
support, | |
Expr, | |
getText, | |
isXML, | |
tokenize, | |
compile, | |
select, | |
outermostContext, | |
sortInput, | |
hasDuplicate, | |
// Local document vars | |
setDocument, | |
document, | |
docElem, | |
documentIsHTML, | |
rbuggyQSA, | |
rbuggyMatches, | |
matches, | |
contains, | |
// Instance-specific data | |
expando = "sizzle" + 1 * new Date(), | |
preferredDoc = window.document, | |
dirruns = 0, | |
done = 0, | |
classCache = createCache(), | |
tokenCache = createCache(), | |
compilerCache = createCache(), | |
sortOrder = function( a, b ) { | |
if ( a === b ) { | |
hasDuplicate = true; | |
} | |
return 0; | |
}, | |
// General-purpose constants | |
MAX_NEGATIVE = 1 << 31, | |
// Instance methods | |
hasOwn = ({}).hasOwnProperty, | |
arr = [], | |
pop = arr.pop, | |
push_native = arr.push, | |
push = arr.push, | |
slice = arr.slice, | |
// Use a stripped-down indexOf as it's faster than native | |
// http://jsperf.com/thor-indexof-vs-for/5 | |
indexOf = function( list, elem ) { | |
var i = 0, | |
len = list.length; | |
for ( ; i < len; i++ ) { | |
if ( list[i] === elem ) { | |
return i; | |
} | |
} | |
return -1; | |
}, | |
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", | |
// Regular expressions | |
// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace | |
whitespace = "[\\x20\\t\\r\\n\\f]", | |
// http://www.w3.org/TR/css3-syntax/#characters | |
characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", | |
// Loosely modeled on CSS identifier characters | |
// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors | |
// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier | |
identifier = characterEncoding.replace( "w", "w#" ), | |
// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors | |
attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + | |
// Operator (capture 2) | |
"*([*^$|!~]?=)" + whitespace + | |
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" | |
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + | |
"*\\]", | |
pseudos = ":(" + characterEncoding + ")(?:\\((" + | |
// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: | |
// 1. quoted (capture 3; capture 4 or capture 5) | |
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + | |
// 2. simple (capture 6) | |
"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + | |
// 3. anything else (capture 2) | |
".*" + | |
")\\)|)", | |
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter | |
rwhitespace = new RegExp( whitespace + "+", "g" ), | |
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), | |
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), | |
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), | |
rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), | |
rpseudo = new RegExp( pseudos ), | |
ridentifier = new RegExp( "^" + identifier + "$" ), | |
matchExpr = { | |
"ID": new RegExp( "^#(" + characterEncoding + ")" ), | |
"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), | |
"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), | |
"ATTR": new RegExp( "^" + attributes ), | |
"PSEUDO": new RegExp( "^" + pseudos ), | |
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + | |
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + | |
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ), | |
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ), | |
// For use in libraries implementing .is() | |
// We use this for POS matching in `select` | |
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + | |
whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) | |
}, | |
rinputs = /^(?:input|select|textarea|button)$/i, | |
rheader = /^h\d$/i, | |
rnative = /^[^{]+\{\s*\[native \w/, | |
// Easily-parseable/retrievable ID or TAG or CLASS selectors | |
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, | |
rsibling = /[+~]/, | |
rescape = /'|\\/g, | |
// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters | |
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), | |
funescape = function( _, escaped, escapedWhitespace ) { | |
var high = "0x" + escaped - 0x10000; | |
// NaN means non-codepoint | |
// Support: Firefox<24 | |
// Workaround erroneous numeric interpretation of +"0x" | |
return high !== high || escapedWhitespace ? | |
escaped : | |
high < 0 ? | |
// BMP codepoint | |
String.fromCharCode( high + 0x10000 ) : | |
// Supplemental Plane codepoint (surrogate pair) | |
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); | |
}, | |
// Used for iframes | |
// See setDocument() | |
// Removing the function wrapper causes a "Permission Denied" | |
// error in IE | |
unloadHandler = function() { | |
setDocument(); | |
}; | |
// Optimize for push.apply( _, NodeList ) | |
try { | |
push.apply( | |
(arr = slice.call( preferredDoc.childNodes )), | |
preferredDoc.childNodes | |
); | |
// Support: Android<4.0 | |
// Detect silently failing push.apply | |
arr[ preferredDoc.childNodes.length ].nodeType; | |
} catch ( e ) { | |
push = { apply: arr.length ? | |
// Leverage slice if possible | |
function( target, els ) { | |
push_native.apply( target, slice.call(els) ); | |
} : | |
// Support: IE<9 | |
// Otherwise append directly | |
function( target, els ) { | |
var j = target.length, | |
i = 0; | |
// Can't trust NodeList.length | |
while ( (target[j++] = els[i++]) ) {} | |
target.length = j - 1; | |
} | |
}; | |
} | |
function Sizzle( selector, context, results, seed ) { | |
var match, elem, m, nodeType, | |
// QSA vars | |
i, groups, old, nid, newContext, newSelector; | |
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { | |
setDocument( context ); | |
} | |
context = context || document; | |
results = results || []; | |
nodeType = context.nodeType; | |
if ( typeof selector !== "string" || !selector || | |
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { | |
return results; | |
} | |
if ( !seed && documentIsHTML ) { | |
// Try to shortcut find operations when possible (e.g., not under DocumentFragment) | |
if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { | |
// Speed-up: Sizzle("#ID") | |
if ( (m = match[1]) ) { | |
if ( nodeType === 9 ) { | |
elem = context.getElementById( m ); | |
// Check parentNode to catch when Blackberry 4.6 returns | |
// nodes that are no longer in the document (jQuery #6963) | |
if ( elem && elem.parentNode ) { | |
// Handle the case where IE, Opera, and Webkit return items | |
// by name instead of ID | |
if ( elem.id === m ) { | |
results.push( elem ); | |
return results; | |
} | |
} else { | |
return results; | |
} | |
} else { | |
// Context is not a document | |
if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && | |
contains( context, elem ) && elem.id === m ) { | |
results.push( elem ); | |
return results; | |
} | |
} | |
// Speed-up: Sizzle("TAG") | |
} else if ( match[2] ) { | |
push.apply( results, context.getElementsByTagName( selector ) ); | |
return results; | |
// Speed-up: Sizzle(".CLASS") | |
} else if ( (m = match[3]) && support.getElementsByClassName ) { | |
push.apply( results, context.getElementsByClassName( m ) ); | |
return results; | |
} | |
} | |
// QSA path | |
if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { | |
nid = old = expando; | |
newContext = context; | |
newSelector = nodeType !== 1 && selector; | |
// qSA works strangely on Element-rooted queries | |
// We can work around this by specifying an extra ID on the root | |
// and working up from there (Thanks to Andrew Dupont for the technique) | |
// IE 8 doesn't work on object elements | |
if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { | |
groups = tokenize( selector ); | |
if ( (old = context.getAttribute("id")) ) { | |
nid = old.replace( rescape, "\\$&" ); | |
} else { | |
context.setAttribute( "id", nid ); | |
} | |
nid = "[id='" + nid + "'] "; | |
i = groups.length; | |
while ( i-- ) { | |
groups[i] = nid + toSelector( groups[i] ); | |
} | |
newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; | |
newSelector = groups.join(","); | |
} | |
if ( newSelector ) { | |
try { | |
push.apply( results, | |
newContext.querySelectorAll( newSelector ) | |
); | |
return results; | |
} catch(qsaError) { | |
} finally { | |
if ( !old ) { | |
context.removeAttribute("id"); | |
} | |
} | |
} | |
} | |
} | |
// All others | |
return select( selector.replace( rtrim, "$1" ), context, results, seed ); | |
} | |
/** | |
* Create key-value caches of limited size | |
* @returns {Function(string, Object)} Returns the Object data after storing it on itself with | |
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) | |
* deleting the oldest entry | |
*/ | |
function createCache() { | |
var keys = []; | |
function cache( key, value ) { | |
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157) | |
if ( keys.push( key + " " ) > Expr.cacheLength ) { | |
// Only keep the most recent entries | |
delete cache[ keys.shift() ]; | |
} | |
return (cache[ key + " " ] = value); | |
} | |
return cache; | |
} | |
/** | |
* Mark a function for special use by Sizzle | |
* @param {Function} fn The function to mark | |
*/ | |
function markFunction( fn ) { | |
fn[ expando ] = true; | |
return fn; | |
} | |
/** | |
* Support testing using an element | |
* @param {Function} fn Passed the created div and expects a boolean result | |
*/ | |
function assert( fn ) { | |
var div = document.createElement("div"); | |
try { | |
return !!fn( div ); | |
} catch (e) { | |
return false; | |
} finally { | |
// Remove from its parent by default | |
if ( div.parentNode ) { | |
div.parentNode.removeChild( div ); | |
} | |
// release memory in IE | |
div = null; | |
} | |
} | |
/** | |
* Adds the same handler for all of the specified attrs | |
* @param {String} attrs Pipe-separated list of attributes | |
* @param {Function} handler The method that will be applied | |
*/ | |
function addHandle( attrs, handler ) { | |
var arr = attrs.split("|"), | |
i = attrs.length; | |
while ( i-- ) { | |
Expr.attrHandle[ arr[i] ] = handler; | |
} | |
} | |
/** | |
* Checks document order of two siblings | |
* @param {Element} a | |
* @param {Element} b | |
* @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b | |
*/ | |
function siblingCheck( a, b ) { | |
var cur = b && a, | |
diff = cur && a.nodeType === 1 && b.nodeType === 1 && | |
( ~b.sourceIndex || MAX_NEGATIVE ) - | |
( ~a.sourceIndex || MAX_NEGATIVE ); | |
// Use IE sourceIndex if available on both nodes | |
if ( diff ) { | |
return diff; | |
} | |
// Check if b follows a | |
if ( cur ) { | |
while ( (cur = cur.nextSibling) ) { | |
if ( cur === b ) { | |
return -1; | |
} | |
} | |
} | |
return a ? 1 : -1; | |
} | |
/** | |
* Returns a function to use in pseudos for input types | |
* @param {String} type | |
*/ | |
function createInputPseudo( type ) { | |
return function( elem ) { | |
var name = elem.nodeName.toLowerCase(); | |
return name === "input" && elem.type === type; | |
}; | |
} | |
/** | |
* Returns a function to use in pseudos for buttons | |
* @param {String} type | |
*/ | |
function createButtonPseudo( type ) { | |
return function( elem ) { | |
var name = elem.nodeName.toLowerCase(); | |
return (name === "input" || name === "button") && elem.type === type; | |
}; | |
} | |
/** | |
* Returns a function to use in pseudos for positionals | |
* @param {Function} fn | |
*/ | |
function createPositionalPseudo( fn ) { | |
return markFunction(function( argument ) { | |
argument = +argument; | |
return markFunction(function( seed, matches ) { | |
var j, | |
matchIndexes = fn( [], seed.length, argument ), | |
i = matchIndexes.length; | |
// Match elements found at the specified indexes | |
while ( i-- ) { | |
if ( seed[ (j = matchIndexes[i]) ] ) { | |
seed[j] = !(matches[j] = seed[j]); | |
} | |
} | |
}); | |
}); | |
} | |
/** | |
* Checks a node for validity as a Sizzle context | |
* @param {Element|Object=} context | |
* @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value | |
*/ | |
function testContext( context ) { | |
return context && typeof context.getElementsByTagName !== "undefined" && context; | |
} | |
// Expose support vars for convenience | |
support = Sizzle.support = {}; | |
/** | |
* Detects XML nodes | |
* @param {Element|Object} elem An element or a document | |
* @returns {Boolean} True iff elem is a non-HTML XML node | |
*/ | |
isXML = Sizzle.isXML = function( elem ) { | |
// documentElement is verified for cases where it doesn't yet exist | |
// (such as loading iframes in IE - #4833) | |
var documentElement = elem && (elem.ownerDocument || elem).documentElement; | |
return documentElement ? documentElement.nodeName !== "HTML" : false; | |
}; | |
/** | |
* Sets document-related variables once based on the current document | |
* @param {Element|Object} [doc] An element or document object to use to set the document | |
* @returns {Object} Returns the current document | |
*/ | |
setDocument = Sizzle.setDocument = function( node ) { | |
var hasCompare, parent, | |
doc = node ? node.ownerDocument || node : preferredDoc; | |
// If no document and documentElement is available, return | |
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { | |
return document; | |
} | |
// Set our document | |
document = doc; | |
docElem = doc.documentElement; | |
parent = doc.defaultView; | |
// Support: IE>8 | |
// If iframe document is assigned to "document" variable and if iframe has been reloaded, | |
// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 | |
// IE6-8 do not support the defaultView property so parent will be undefined | |
if ( parent && parent !== parent.top ) { | |
// IE11 does not have attachEvent, so all must suffer | |
if ( parent.addEventListener ) { | |
parent.addEventListener( "unload", unloadHandler, false ); | |
} else if ( parent.attachEvent ) { | |
parent.attachEvent( "onunload", unloadHandler ); | |
} | |
} | |
/* Support tests | |
---------------------------------------------------------------------- */ | |
documentIsHTML = !isXML( doc ); | |
/* Attributes | |
---------------------------------------------------------------------- */ | |
// Support: IE<8 | |
// Verify that getAttribute really returns attributes and not properties | |
// (excepting IE8 booleans) | |
support.attributes = assert(function( div ) { | |
div.className = "i"; | |
return !div.getAttribute("className"); | |
}); | |
/* getElement(s)By* | |
---------------------------------------------------------------------- */ | |
// Check if getElementsByTagName("*") returns only elements | |
support.getElementsByTagName = assert(function( div ) { | |
div.appendChild( doc.createComment("") ); | |
return !div.getElementsByTagName("*").length; | |
}); | |
// Support: IE<9 | |
support.getElementsByClassName = rnative.test( doc.getElementsByClassName ); | |
// Support: IE<10 | |
// Check if getElementById returns elements by name | |
// The broken getElementById methods don't pick up programatically-set names, | |
// so use a roundabout getElementsByName test | |
support.getById = assert(function( div ) { | |
docElem.appendChild( div ).id = expando; | |
return !doc.getElementsByName || !doc.getElementsByName( expando ).length; | |
}); | |
// ID find and filter | |
if ( support.getById ) { | |
Expr.find["ID"] = function( id, context ) { | |
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { | |
var m = context.getElementById( id ); | |
// Check parentNode to catch when Blackberry 4.6 returns | |
// nodes that are no longer in the document #6963 | |
return m && m.parentNode ? [ m ] : []; | |
} | |
}; | |
Expr.filter["ID"] = function( id ) { | |
var attrId = id.replace( runescape, funescape ); | |
return function( elem ) { | |
return elem.getAttribute("id") === attrId; | |
}; | |
}; | |
} else { | |
// Support: IE6/7 | |
// getElementById is not reliable as a find shortcut | |
delete Expr.find["ID"]; | |
Expr.filter["ID"] = function( id ) { | |
var attrId = id.replace( runescape, funescape ); | |
return function( elem ) { | |
var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); | |
return node && node.value === attrId; | |
}; | |
}; | |
} | |
// Tag | |
Expr.find["TAG"] = support.getElementsByTagName ? | |
function( tag, context ) { | |
if ( typeof context.getElementsByTagName !== "undefined" ) { | |
return context.getElementsByTagName( tag ); | |
// DocumentFragment nodes don't have gEBTN | |
} else if ( support.qsa ) { | |
return context.querySelectorAll( tag ); | |
} | |
} : | |
function( tag, context ) { | |
var elem, | |
tmp = [], | |
i = 0, | |
// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too | |
results = context.getElementsByTagName( tag ); | |
// Filter out possible comments | |
if ( tag === "*" ) { | |
while ( (elem = results[i++]) ) { | |
if ( elem.nodeType === 1 ) { | |
tmp.push( elem ); | |
} | |
} | |
return tmp; | |
} | |
return results; | |
}; | |
// Class | |
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { | |
if ( documentIsHTML ) { | |
return context.getElementsByClassName( className ); | |
} | |
}; | |
/* QSA/matchesSelector | |
---------------------------------------------------------------------- */ | |
// QSA and matchesSelector support | |
// matchesSelector(:active) reports false when true (IE9/Opera 11.5) | |
rbuggyMatches = []; | |
// qSa(:focus) reports false when true (Chrome 21) | |
// We allow this because of a bug in IE8/9 that throws an error | |
// whenever `document.activeElement` is accessed on an iframe | |
// So, we allow :focus to pass through QSA all the time to avoid the IE error | |
// See http://bugs.jquery.com/ticket/13378 | |
rbuggyQSA = []; | |
if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { | |
// Build QSA regex | |
// Regex strategy adopted from Diego Perini | |
assert(function( div ) { | |
// Select is set to empty string on purpose | |
// This is to test IE's treatment of not explicitly | |
// setting a boolean content attribute, | |
// since its presence should be enough | |
// http://bugs.jquery.com/ticket/12359 | |
docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" + | |
"<select id='" + expando + "-\f]' msallowcapture=''>" + | |
"<option selected=''></option></select>"; | |
// Support: IE8, Opera 11-12.16 | |
// Nothing should be selected when empty strings follow ^= or $= or *= | |
// The test attribute must be unknown in Opera but "safe" for WinRT | |
// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section | |
if ( div.querySelectorAll("[msallowcapture^='']").length ) { | |
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); | |
} | |
// Support: IE8 | |
// Boolean attributes and "value" are not treated correctly | |
if ( !div.querySelectorAll("[selected]").length ) { | |
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); | |
} | |
// Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+ | |
if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { | |
rbuggyQSA.push("~="); | |
} | |
// Webkit/Opera - :checked should return selected option elements | |
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked | |
// IE8 throws error here and will not see later tests | |
if ( !div.querySelectorAll(":checked").length ) { | |
rbuggyQSA.push(":checked"); | |
} | |
// Support: Safari 8+, iOS 8+ | |
// https://bugs.webkit.org/show_bug.cgi?id=136851 | |
// In-page `selector#id sibing-combinator selector` fails | |
if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { | |
rbuggyQSA.push(".#.+[+~]"); | |
} | |
}); | |
assert(function( div ) { | |
// Support: Windows 8 Native Apps | |
// The type and name attributes are restricted during .innerHTML assignment | |
var input = doc.createElement("input"); | |
input.setAttribute( "type", "hidden" ); | |
div.appendChild( input ).setAttribute( "name", "D" ); | |
// Support: IE8 | |
// Enforce case-sensitivity of name attribute | |
if ( div.querySelectorAll("[name=d]").length ) { | |
rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); | |
} | |
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) | |
// IE8 throws error here and will not see later tests | |
if ( !div.querySelectorAll(":enabled").length ) { | |
rbuggyQSA.push( ":enabled", ":disabled" ); | |
} | |
// Opera 10-11 does not throw on post-comma invalid pseudos | |
div.querySelectorAll("*,:x"); | |
rbuggyQSA.push(",.*:"); | |
}); | |
} | |
if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || | |
docElem.webkitMatchesSelector || | |
docElem.mozMatchesSelector || | |
docElem.oMatchesSelector || | |
docElem.msMatchesSelector) )) ) { | |
assert(function( div ) { | |
// Check to see if it's possible to do matchesSelector | |
// on a disconnected node (IE 9) | |
support.disconnectedMatch = matches.call( div, "div" ); | |
// This should fail with an exception | |
// Gecko does not error, returns false instead | |
matches.call( div, "[s!='']:x" ); | |
rbuggyMatches.push( "!=", pseudos ); | |
}); | |
} | |
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); | |
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); | |
/* Contains | |
---------------------------------------------------------------------- */ | |
hasCompare = rnative.test( docElem.compareDocumentPosition ); | |
// Element contains another | |
// Purposefully does not implement inclusive descendent | |
// As in, an element does not contain itself | |
contains = hasCompare || rnative.test( docElem.contains ) ? | |
function( a, b ) { | |
var adown = a.nodeType === 9 ? a.documentElement : a, | |
bup = b && b.parentNode; | |
return a === bup || !!( bup && bup.nodeType === 1 && ( | |
adown.contains ? | |
adown.contains( bup ) : | |
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 | |
)); | |
} : | |
function( a, b ) { | |
if ( b ) { | |
while ( (b = b.parentNode) ) { | |
if ( b === a ) { | |
return true; | |
} | |
} | |
} | |
return false; | |
}; | |
/* Sorting | |
---------------------------------------------------------------------- */ | |
// Document order sorting | |
sortOrder = hasCompare ? | |
function( a, b ) { | |
// Flag for duplicate removal | |
if ( a === b ) { | |
hasDuplicate = true; | |
return 0; | |
} | |
// Sort on method existence if only one input has compareDocumentPosition | |
var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; | |
if ( compare ) { | |
return compare; | |
} | |
// Calculate position if both inputs belong to the same document | |
compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? | |
a.compareDocumentPosition( b ) : | |
// Otherwise we know they are disconnected | |
1; | |
// Disconnected nodes | |
if ( compare & 1 || | |
(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { | |
// Choose the first element that is related to our preferred document | |
if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { | |
return -1; | |
} | |
if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { | |
return 1; | |
} | |
// Maintain original order | |
return sortInput ? | |
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : | |
0; | |
} | |
return compare & 4 ? -1 : 1; | |
} : | |
function( a, b ) { | |
// Exit early if the nodes are identical | |
if ( a === b ) { | |
hasDuplicate = true; | |
return 0; | |
} | |
var cur, | |
i = 0, | |
aup = a.parentNode, | |
bup = b.parentNode, | |
ap = [ a ], | |
bp = [ b ]; | |
// Parentless nodes are either documents or disconnected | |
if ( !aup || !bup ) { | |
return a === doc ? -1 : | |
b === doc ? 1 : | |
aup ? -1 : | |
bup ? 1 : | |
sortInput ? | |
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : | |
0; | |
// If the nodes are siblings, we can do a quick check | |
} else if ( aup === bup ) { | |
return siblingCheck( a, b ); | |
} | |
// Otherwise we need full lists of their ancestors for comparison | |
cur = a; | |
while ( (cur = cur.parentNode) ) { | |
ap.unshift( cur ); | |
} | |
cur = b; | |
while ( (cur = cur.parentNode) ) { | |
bp.unshift( cur ); | |
} | |
// Walk down the tree looking for a discrepancy | |
while ( ap[i] === bp[i] ) { | |
i++; | |
} | |
return i ? | |
// Do a sibling check if the nodes have a common ancestor | |
siblingCheck( ap[i], bp[i] ) : | |
// Otherwise nodes in our document sort first | |
ap[i] === preferredDoc ? -1 : | |
bp[i] === preferredDoc ? 1 : | |
0; | |
}; | |
return doc; | |
}; | |
Sizzle.matches = function( expr, elements ) { | |
return Sizzle( expr, null, null, elements ); | |
}; | |
Sizzle.matchesSelector = function( elem, expr ) { | |
// Set document vars if needed | |
if ( ( elem.ownerDocument || elem ) !== document ) { | |
setDocument( elem ); | |
} | |
// Make sure that attribute selectors are quoted | |
expr = expr.replace( rattributeQuotes, "='$1']" ); | |
if ( support.matchesSelector && documentIsHTML && | |
( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && | |
( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { | |
try { | |
var ret = matches.call( elem, expr ); | |
// IE 9's matchesSelector returns false on disconnected nodes | |
if ( ret || support.disconnectedMatch || | |
// As well, disconnected nodes are said to be in a document | |
// fragment in IE 9 | |
elem.document && elem.document.nodeType !== 11 ) { | |
return ret; | |
} | |
} catch (e) {} | |
} | |
return Sizzle( expr, document, null, [ elem ] ).length > 0; | |
}; | |
Sizzle.contains = function( context, elem ) { | |
// Set document vars if needed | |
if ( ( context.ownerDocument || context ) !== document ) { | |
setDocument( context ); | |
} | |
return contains( context, elem ); | |
}; | |
Sizzle.attr = function( elem, name ) { | |
// Set document vars if needed | |
if ( ( elem.ownerDocument || elem ) !== document ) { | |
setDocument( elem ); | |
} | |
var fn = Expr.attrHandle[ name.toLowerCase() ], | |
// Don't get fooled by Object.prototype properties (jQuery #13807) | |
val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? | |
fn( elem, name, !documentIsHTML ) : | |
undefined; | |
return val !== undefined ? | |
val : | |
support.attributes || !documentIsHTML ? | |
elem.getAttribute( name ) : | |
(val = elem.getAttributeNode(name)) && val.specified ? | |
val.value : | |
null; | |
}; | |
Sizzle.error = function( msg ) { | |
throw new Error( "Syntax error, unrecognized expression: " + msg ); | |
}; | |
/** | |
* Document sorting and removing duplicates | |
* @param {ArrayLike} results | |
*/ | |
Sizzle.uniqueSort = function( results ) { | |
var elem, | |
duplicates = [], | |
j = 0, | |
i = 0; | |
// Unless we *know* we can detect duplicates, assume their presence | |
hasDuplicate = !support.detectDuplicates; | |
sortInput = !support.sortStable && results.slice( 0 ); | |
results.sort( sortOrder ); | |
if ( hasDuplicate ) { | |
while ( (elem = results[i++]) ) { | |
if ( elem === results[ i ] ) { | |
j = duplicates.push( i ); | |
} | |
} | |
while ( j-- ) { | |
results.splice( duplicates[ j ], 1 ); | |
} | |
} | |
// Clear input after sorting to release objects | |
// See https://github.com/jquery/sizzle/pull/225 | |
sortInput = null; | |
return results; | |
}; | |
/** | |
* Utility function for retrieving the text value of an array of DOM nodes | |
* @param {Array|Element} elem | |
*/ | |
getText = Sizzle.getText = function( elem ) { | |
var node, | |
ret = "", | |
i = 0, | |
nodeType = elem.nodeType; | |
if ( !nodeType ) { | |
// If no nodeType, this is expected to be an array | |
while ( (node = elem[i++]) ) { | |
// Do not traverse comment nodes | |
ret += getText( node ); | |
} | |
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { | |
// Use textContent for elements | |
// innerText usage removed for consistency of new lines (jQuery #11153) | |
if ( typeof elem.textContent === "string" ) { | |
return elem.textContent; | |
} else { | |
// Traverse its children | |
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { | |
ret += getText( elem ); | |
} | |
} | |
} else if ( nodeType === 3 || nodeType === 4 ) { | |
return elem.nodeValue; | |
} | |
// Do not include comment or processing instruction nodes | |
return ret; | |
}; | |
Expr = Sizzle.selectors = { | |
// Can be adjusted by the user | |
cacheLength: 50, | |
createPseudo: markFunction, | |
match: matchExpr, | |
attrHandle: {}, | |
find: {}, | |
relative: { | |
">": { dir: "parentNode", first: true }, | |
" ": { dir: "parentNode" }, | |
"+": { dir: "previousSibling", first: true }, | |
"~": { dir: "previousSibling" } | |
}, | |
preFilter: { | |
"ATTR": function( match ) { | |
match[1] = match[1].replace( runescape, funescape ); | |
// Move the given value to match[3] whether quoted or unquoted | |
match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); | |
if ( match[2] === "~=" ) { | |
match[3] = " " + match[3] + " "; | |
} | |
return match.slice( 0, 4 ); | |
}, | |
"CHILD": function( match ) { | |
/* matches from matchExpr["CHILD"] | |
1 type (only|nth|...) | |
2 what (child|of-type) | |
3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) | |
4 xn-component of xn+y argument ([+-]?\d*n|) | |
5 sign of xn-component | |
6 x of xn-component | |
7 sign of y-component | |
8 y of y-component | |
*/ | |
match[1] = match[1].toLowerCase(); | |
if ( match[1].slice( 0, 3 ) === "nth" ) { | |
// nth-* requires argument | |
if ( !match[3] ) { | |
Sizzle.error( match[0] ); | |
} | |
// numeric x and y parameters for Expr.filter.CHILD | |
// remember that false/true cast respectively to 0/1 | |
match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); | |
match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); | |
// other types prohibit arguments | |
} else if ( match[3] ) { | |
Sizzle.error( match[0] ); | |
} | |
return match; | |
}, | |
"PSEUDO": function( match ) { | |
var excess, | |
unquoted = !match[6] && match[2]; | |
if ( matchExpr["CHILD"].test( match[0] ) ) { | |
return null; | |
} | |
// Accept quoted arguments as-is | |
if ( match[3] ) { | |
match[2] = match[4] || match[5] || ""; | |
// Strip excess characters from unquoted arguments | |
} else if ( unquoted && rpseudo.test( unquoted ) && | |
// Get excess from tokenize (recursively) | |
(excess = tokenize( unquoted, true )) && | |
// advance to the next closing parenthesis | |
(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { | |
// excess is a negative index | |
match[0] = match[0].slice( 0, excess ); | |
match[2] = unquoted.slice( 0, excess ); | |
} | |
// Return only captures needed by the pseudo filter method (type and argument) | |
return match.slice( 0, 3 ); | |
} | |
}, | |
filter: { | |
"TAG": function( nodeNameSelector ) { | |
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); | |
return nodeNameSelector === "*" ? | |
function() { return true; } : | |
function( elem ) { | |
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; | |
}; | |
}, | |
"CLASS": function( className ) { | |
var pattern = classCache[ className + " " ]; | |
return pattern || | |
(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && | |
classCache( className, function( elem ) { | |
return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); | |
}); | |
}, | |
"ATTR": function( name, operator, check ) { | |
return function( elem ) { | |
var result = Sizzle.attr( elem, name ); | |
if ( result == null ) { | |
return operator === "!="; | |
} | |
if ( !operator ) { | |
return true; | |
} | |
result += ""; | |
return operator === "=" ? result === check : | |
operator === "!=" ? result !== check : | |
operator === "^=" ? check && result.indexOf( check ) === 0 : | |
operator === "*=" ? check && result.indexOf( check ) > -1 : | |
operator === "$=" ? check && result.slice( -check.length ) === check : | |
operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : | |
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : | |
false; | |
}; | |
}, | |
"CHILD": function( type, what, argument, first, last ) { | |
var simple = type.slice( 0, 3 ) !== "nth", | |
forward = type.slice( -4 ) !== "last", | |
ofType = what === "of-type"; | |
return first === 1 && last === 0 ? | |
// Shortcut for :nth-*(n) | |
function( elem ) { | |
return !!elem.parentNode; | |
} : | |
function( elem, context, xml ) { | |
var cache, outerCache, node, diff, nodeIndex, start, | |
dir = simple !== forward ? "nextSibling" : "previousSibling", | |
parent = elem.parentNode, | |
name = ofType && elem.nodeName.toLowerCase(), | |
useCache = !xml && !ofType; | |
if ( parent ) { | |
// :(first|last|only)-(child|of-type) | |
if ( simple ) { | |
while ( dir ) { | |
node = elem; | |
while ( (node = node[ dir ]) ) { | |
if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { | |
return false; | |
} | |
} | |
// Reverse direction for :only-* (if we haven't yet done so) | |
start = dir = type === "only" && !start && "nextSibling"; | |
} | |
return true; | |
} | |
start = [ forward ? parent.firstChild : parent.lastChild ]; | |
// non-xml :nth-child(...) stores cache data on `parent` | |
if ( forward && useCache ) { | |
// Seek `elem` from a previously-cached index | |
outerCache = parent[ expando ] || (parent[ expando ] = {}); | |
cache = outerCache[ type ] || []; | |
nodeIndex = cache[0] === dirruns && cache[1]; | |
diff = cache[0] === dirruns && cache[2]; | |
node = nodeIndex && parent.childNodes[ nodeIndex ]; | |
while ( (node = ++nodeIndex && node && node[ dir ] || | |
// Fallback to seeking `elem` from the start | |
(diff = nodeIndex = 0) || start.pop()) ) { | |
// When found, cache indexes on `parent` and break | |
if ( node.nodeType === 1 && ++diff && node === elem ) { | |
outerCache[ type ] = [ dirruns, nodeIndex, diff ]; | |
break; | |
} | |
} | |
// Use previously-cached element index if available | |
} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { | |
diff = cache[1]; | |
// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) | |
} else { | |
// Use the same loop as above to seek `elem` from the start | |
while ( (node = ++nodeIndex && node && node[ dir ] || | |
(diff = nodeIndex = 0) || start.pop()) ) { | |
if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { | |
// Cache the index of each encountered element | |
if ( useCache ) { | |
(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; | |
} | |
if ( node === elem ) { | |
break; | |
} | |
} | |
} | |
} | |
// Incorporate the offset, then check against cycle size | |
diff -= last; | |
return diff === first || ( diff % first === 0 && diff / first >= 0 ); | |
} | |
}; | |
}, | |
"PSEUDO": function( pseudo, argument ) { | |
// pseudo-class names are case-insensitive | |
// http://www.w3.org/TR/selectors/#pseudo-classes | |
// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters | |
// Remember that setFilters inherits from pseudos | |
var args, | |
fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || | |
Sizzle.error( "unsupported pseudo: " + pseudo ); | |
// The user may use createPseudo to indicate that | |
// arguments are needed to create the filter function | |
// just as Sizzle does | |
if ( fn[ expando ] ) { | |
return fn( argument ); | |
} | |
// But maintain support for old signatures | |
if ( fn.length > 1 ) { | |
args = [ pseudo, pseudo, "", argument ]; | |
return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? | |
markFunction(function( seed, matches ) { | |
var idx, | |
matched = fn( seed, argument ), | |
i = matched.length; | |
while ( i-- ) { | |
idx = indexOf( seed, matched[i] ); | |
seed[ idx ] = !( matches[ idx ] = matched[i] ); | |
} | |
}) : | |
function( elem ) { | |
return fn( elem, 0, args ); | |
}; | |
} | |
return fn; | |
} | |
}, | |
pseudos: { | |
// Potentially complex pseudos | |
"not": markFunction(function( selector ) { | |
// Trim the selector passed to compile | |
// to avoid treating leading and trailing | |
// spaces as combinators | |
var input = [], | |
results = [], | |
matcher = compile( selector.replace( rtrim, "$1" ) ); | |
return matcher[ expando ] ? | |
markFunction(function( seed, matches, context, xml ) { | |
var elem, | |
unmatched = matcher( seed, null, xml, [] ), | |
i = seed.length; | |
// Match elements unmatched by `matcher` | |
while ( i-- ) { | |
if ( (elem = unmatched[i]) ) { | |
seed[i] = !(matches[i] = elem); | |
} | |
} | |
}) : | |
function( elem, context, xml ) { | |
input[0] = elem; | |
matcher( input, null, xml, results ); | |
// Don't keep the element (issue #299) | |
input[0] = null; | |
return !results.pop(); | |
}; | |
}), | |
"has": markFunction(function( selector ) { | |
return function( elem ) { | |
return Sizzle( selector, elem ).length > 0; | |
}; | |
}), | |
"contains": markFunction(function( text ) { | |
text = text.replace( runescape, funescape ); | |
return function( elem ) { | |
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; | |
}; | |
}), | |
// "Whether an element is represented by a :lang() selector | |
// is based solely on the element's language value | |
// being equal to the identifier C, | |
// or beginning with the identifier C immediately followed by "-". | |
// The matching of C against the element's language value is performed case-insensitively. | |
// The identifier C does not have to be a valid language name." | |
// http://www.w3.org/TR/selectors/#lang-pseudo | |
"lang": markFunction( function( lang ) { | |
// lang value must be a valid identifier | |
if ( !ridentifier.test(lang || "") ) { | |
Sizzle.error( "unsupported lang: " + lang ); | |
} | |
lang = lang.replace( runescape, funescape ).toLowerCase(); | |
return function( elem ) { | |
var elemLang; | |
do { | |
if ( (elemLang = documentIsHTML ? | |
elem.lang : | |
elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { | |
elemLang = elemLang.toLowerCase(); | |
return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; | |
} | |
} while ( (elem = elem.parentNode) && elem.nodeType === 1 ); | |
return false; | |
}; | |
}), | |
// Miscellaneous | |
"target": function( elem ) { | |
var hash = window.location && window.location.hash; | |
return hash && hash.slice( 1 ) === elem.id; | |
}, | |
"root": function( elem ) { | |
return elem === docElem; | |
}, | |
"focus": function( elem ) { | |
return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); | |
}, | |
// Boolean properties | |
"enabled": function( elem ) { | |
return elem.disabled === false; | |
}, | |
"disabled": function( elem ) { | |
return elem.disabled === true; | |
}, | |
"checked": function( elem ) { | |
// In CSS3, :checked should return both checked and selected elements | |
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked | |
var nodeName = elem.nodeName.toLowerCase(); | |
return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); | |
}, | |
"selected": function( elem ) { | |
// Accessing this property makes selected-by-default | |
// options in Safari work properly | |
if ( elem.parentNode ) { | |
elem.parentNode.selectedIndex; | |
} | |
return elem.selected === true; | |
}, | |
// Contents | |
"empty": function( elem ) { | |
// http://www.w3.org/TR/selectors/#empty-pseudo | |
// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), | |
// but not by others (comment: 8; processing instruction: 7; etc.) | |
// nodeType < 6 works because attributes (2) do not appear as children | |
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { | |
if ( elem.nodeType < 6 ) { | |
return false; | |
} | |
} | |
return true; | |
}, | |
"parent": function( elem ) { | |
return !Expr.pseudos["empty"]( elem ); | |
}, | |
// Element/input types | |
"header": function( elem ) { | |
return rheader.test( elem.nodeName ); | |
}, | |
"input": function( elem ) { | |
return rinputs.test( elem.nodeName ); | |
}, | |
"button": function( elem ) { | |
var name = elem.nodeName.toLowerCase(); | |
return name === "input" && elem.type === "button" || name === "button"; | |
}, | |
"text": function( elem ) { | |
var attr; | |
return elem.nodeName.toLowerCase() === "input" && | |
elem.type === "text" && | |
// Support: IE<8 | |
// New HTML5 attribute values (e.g., "search") appear with elem.type === "text" | |
( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); | |
}, | |
// Position-in-collection | |
"first": createPositionalPseudo(function() { | |
return [ 0 ]; | |
}), | |
"last": createPositionalPseudo(function( matchIndexes, length ) { | |
return [ length - 1 ]; | |
}), | |
"eq": createPositionalPseudo(function( matchIndexes, length, argument ) { | |
return [ argument < 0 ? argument + length : argument ]; | |
}), | |
"even": createPositionalPseudo(function( matchIndexes, length ) { | |
var i = 0; | |
for ( ; i < length; i += 2 ) { | |
matchIndexes.push( i ); | |
} | |
return matchIndexes; | |
}), | |
"odd": createPositionalPseudo(function( matchIndexes, length ) { | |
var i = 1; | |
for ( ; i < length; i += 2 ) { | |
matchIndexes.push( i ); | |
} | |
return matchIndexes; | |
}), | |
"lt": createPositionalPseudo(function( matchIndexes, length, argument ) { | |
var i = argument < 0 ? argument + length : argument; | |
for ( ; --i >= 0; ) { | |
matchIndexes.push( i ); | |
} | |
return matchIndexes; | |
}), | |
"gt": createPositionalPseudo(function( matchIndexes, length, argument ) { | |
var i = argument < 0 ? argument + length : argument; | |
for ( ; ++i < length; ) { | |
matchIndexes.push( i ); | |
} | |
return matchIndexes; | |
}) | |
} | |
}; | |
Expr.pseudos["nth"] = Expr.pseudos["eq"]; | |
// Add button/input type pseudos | |
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { | |
Expr.pseudos[ i ] = createInputPseudo( i ); | |
} | |
for ( i in { submit: true, reset: true } ) { | |
Expr.pseudos[ i ] = createButtonPseudo( i ); | |
} | |
// Easy API for creating new setFilters | |
function setFilters() {} | |
setFilters.prototype = Expr.filters = Expr.pseudos; | |
Expr.setFilters = new setFilters(); | |
tokenize = Sizzle.tokenize = function( selector, parseOnly ) { | |
var matched, match, tokens, type, | |
soFar, groups, preFilters, | |
cached = tokenCache[ selector + " " ]; | |
if ( cached ) { | |
return parseOnly ? 0 : cached.slice( 0 ); | |
} | |
soFar = selector; | |
groups = []; | |
preFilters = Expr.preFilter; | |
while ( soFar ) { | |
// Comma and first run | |
if ( !matched || (match = rcomma.exec( soFar )) ) { | |
if ( match ) { | |
// Don't consume trailing commas as valid | |
soFar = soFar.slice( match[0].length ) || soFar; | |
} | |
groups.push( (tokens = []) ); | |
} | |
matched = false; | |
// Combinators | |
if ( (match = rcombinators.exec( soFar )) ) { | |
matched = match.shift(); | |
tokens.push({ | |
value: matched, | |
// Cast descendant combinators to space | |
type: match[0].replace( rtrim, " " ) | |
}); | |
soFar = soFar.slice( matched.length ); | |
} | |
// Filters | |
for ( type in Expr.filter ) { | |
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || | |
(match = preFilters[ type ]( match ))) ) { | |
matched = match.shift(); | |
tokens.push({ | |
value: matched, | |
type: type, | |
matches: match | |
}); | |
soFar = soFar.slice( matched.length ); | |
} | |
} | |
if ( !matched ) { | |
break; | |
} | |
} | |
// Return the length of the invalid excess | |
// if we're just parsing | |
// Otherwise, throw an error or return tokens | |
return parseOnly ? | |
soFar.length : | |
soFar ? | |
Sizzle.error( selector ) : | |
// Cache the tokens | |
tokenCache( selector, groups ).slice( 0 ); | |
}; | |
function toSelector( tokens ) { | |
var i = 0, | |
len = tokens.length, | |
selector = ""; | |
for ( ; i < len; i++ ) { | |
selector += tokens[i].value; | |
} | |
return selector; | |
} | |
function addCombinator( matcher, combinator, base ) { | |
var dir = combinator.dir, | |
checkNonElements = base && dir === "parentNode", | |
doneName = done++; | |
return combinator.first ? | |
// Check against closest ancestor/preceding element | |
function( elem, context, xml ) { | |
while ( (elem = elem[ dir ]) ) { | |
if ( elem.nodeType === 1 || checkNonElements ) { | |
return matcher( elem, context, xml ); | |
} | |
} | |
} : | |
// Check against all ancestor/preceding elements | |
function( elem, context, xml ) { | |
var oldCache, outerCache, | |
newCache = [ dirruns, doneName ]; | |
// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching | |
if ( xml ) { | |
while ( (elem = elem[ dir ]) ) { | |
if ( elem.nodeType === 1 || checkNonElements ) { | |
if ( matcher( elem, context, xml ) ) { | |
return true; | |
} | |
} | |
} | |
} else { | |
while ( (elem = elem[ dir ]) ) { | |
if ( elem.nodeType === 1 || checkNonElements ) { | |
outerCache = elem[ expando ] || (elem[ expando ] = {}); | |
if ( (oldCache = outerCache[ dir ]) && | |
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { | |
// Assign to newCache so results back-propagate to previous elements | |
return (newCache[ 2 ] = oldCache[ 2 ]); | |
} else { | |
// Reuse newcache so results back-propagate to previous elements | |
outerCache[ dir ] = newCache; | |
// A match means we're done; a fail means we have to keep checking | |
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { | |
return true; | |
} | |
} | |
} | |
} | |
} | |
}; | |
} | |
function elementMatcher( matchers ) { | |
return matchers.length > 1 ? | |
function( elem, context, xml ) { | |
var i = matchers.length; | |
while ( i-- ) { | |
if ( !matchers[i]( elem, context, xml ) ) { | |
return false; | |
} | |
} | |
return true; | |
} : | |
matchers[0]; | |
} | |
function multipleContexts( selector, contexts, results ) { | |
var i = 0, | |
len = contexts.length; | |
for ( ; i < len; i++ ) { | |
Sizzle( selector, contexts[i], results ); | |
} | |
return results; | |
} | |
function condense( unmatched, map, filter, context, xml ) { | |
var elem, | |
newUnmatched = [], | |
i = 0, | |
len = unmatched.length, | |
mapped = map != null; | |
for ( ; i < len; i++ ) { | |
if ( (elem = unmatched[i]) ) { | |
if ( !filter || filter( elem, context, xml ) ) { | |
newUnmatched.push( elem ); | |
if ( mapped ) { | |
map.push( i ); | |
} | |
} | |
} | |
} | |
return newUnmatched; | |
} | |
function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { | |
if ( postFilter && !postFilter[ expando ] ) { | |
postFilter = setMatcher( postFilter ); | |
} | |
if ( postFinder && !postFinder[ expando ] ) { | |
postFinder = setMatcher( postFinder, postSelector ); | |
} | |
return markFunction(function( seed, results, context, xml ) { | |
var temp, i, elem, | |
preMap = [], | |
postMap = [], | |
preexisting = results.length, | |
// Get initial elements from seed or context | |
elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), | |
// Prefilter to get matcher input, preserving a map for seed-results synchronization | |
matcherIn = preFilter && ( seed || !selector ) ? | |
condense( elems, preMap, preFilter, context, xml ) : | |
elems, | |
matcherOut = matcher ? | |
// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, | |
postFinder || ( seed ? preFilter : preexisting || postFilter ) ? | |
// ...intermediate processing is necessary | |
[] : | |
// ...otherwise use results directly | |
results : | |
matcherIn; | |
// Find primary matches | |
if ( matcher ) { | |
matcher( matcherIn, matcherOut, context, xml ); | |
} | |
// Apply postFilter | |
if ( postFilter ) { | |
temp = condense( matcherOut, postMap ); | |
postFilter( temp, [], context, xml ); | |
// Un-match failing elements by moving them back to matcherIn | |
i = temp.length; | |
while ( i-- ) { | |
if ( (elem = temp[i]) ) { | |
matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); | |
} | |
} | |
} | |
if ( seed ) { | |
if ( postFinder || preFilter ) { | |
if ( postFinder ) { | |
// Get the final matcherOut by condensing this intermediate into postFinder contexts | |
temp = []; | |
i = matcherOut.length; | |
while ( i-- ) { | |
if ( (elem = matcherOut[i]) ) { | |
// Restore matcherIn since elem is not yet a final match | |
temp.push( (matcherIn[i] = elem) ); | |
} | |
} | |
postFinder( null, (matcherOut = []), temp, xml ); | |
} | |
// Move matched elements from seed to results to keep them synchronized | |
i = matcherOut.length; | |
while ( i-- ) { | |
if ( (elem = matcherOut[i]) && | |
(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { | |
seed[temp] = !(results[temp] = elem); | |
} | |
} | |
} | |
// Add elements to results, through postFinder if defined | |
} else { | |
matcherOut = condense( | |
matcherOut === results ? | |
matcherOut.splice( preexisting, matcherOut.length ) : | |
matcherOut | |
); | |
if ( postFinder ) { | |
postFinder( null, results, matcherOut, xml ); | |
} else { | |
push.apply( results, matcherOut ); | |
} | |
} | |
}); | |
} | |
function matcherFromTokens( tokens ) { | |
var checkContext, matcher, j, | |
len = tokens.length, | |
leadingRelative = Expr.relative[ tokens[0].type ], | |
implicitRelative = leadingRelative || Expr.relative[" "], | |
i = leadingRelative ? 1 : 0, | |
// The foundational matcher ensures that elements are reachable from top-level context(s) | |
matchContext = addCombinator( function( elem ) { | |
return elem === checkContext; | |
}, implicitRelative, true ), | |
matchAnyContext = addCombinator( function( elem ) { | |
return indexOf( checkContext, elem ) > -1; | |
}, implicitRelative, true ), | |
matchers = [ function( elem, context, xml ) { | |
var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( | |
(checkContext = context).nodeType ? | |
matchContext( elem, context, xml ) : | |
matchAnyContext( elem, context, xml ) ); | |
// Avoid hanging onto element (issue #299) | |
checkContext = null; | |
return ret; | |
} ]; | |
for ( ; i < len; i++ ) { | |
if ( (matcher = Expr.relative[ tokens[i].type ]) ) { | |
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; | |
} else { | |
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); | |
// Return special upon seeing a positional matcher | |
if ( matcher[ expando ] ) { | |
// Find the next relative operator (if any) for proper handling | |
j = ++i; | |
for ( ; j < len; j++ ) { | |
if ( Expr.relative[ tokens[j].type ] ) { | |
break; | |
} | |
} | |
return setMatcher( | |
i > 1 && elementMatcher( matchers ), | |
i > 1 && toSelector( | |
// If the preceding token was a descendant combinator, insert an implicit any-element `*` | |
tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) | |
).replace( rtrim, "$1" ), | |
matcher, | |
i < j && matcherFromTokens( tokens.slice( i, j ) ), | |
j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), | |
j < len && toSelector( tokens ) | |
); | |
} | |
matchers.push( matcher ); | |
} | |
} | |
return elementMatcher( matchers ); | |
} | |
function matcherFromGroupMatchers( elementMatchers, setMatchers ) { | |
var bySet = setMatchers.length > 0, | |
byElement = elementMatchers.length > 0, | |
superMatcher = function( seed, context, xml, results, outermost ) { | |
var elem, j, matcher, | |
matchedCount = 0, | |
i = "0", | |
unmatched = seed && [], | |
setMatched = [], | |
contextBackup = outermostContext, | |
// We must always have either seed elements or outermost context | |
elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), | |
// Use integer dirruns iff this is the outermost matcher | |
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), | |
len = elems.length; | |
if ( outermost ) { | |
outermostContext = context !== document && context; | |
} | |
// Add elements passing elementMatchers directly to results | |
// Keep `i` a string if there are no elements so `matchedCount` will be "00" below | |
// Support: IE<9, Safari | |
// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id | |
for ( ; i !== len && (elem = elems[i]) != null; i++ ) { | |
if ( byElement && elem ) { | |
j = 0; | |
while ( (matcher = elementMatchers[j++]) ) { | |
if ( matcher( elem, context, xml ) ) { | |
results.push( elem ); | |
break; | |
} | |
} | |
if ( outermost ) { | |
dirruns = dirrunsUnique; | |
} | |
} | |
// Track unmatched elements for set filters | |
if ( bySet ) { | |
// They will have gone through all possible matchers | |
if ( (elem = !matcher && elem) ) { | |
matchedCount--; | |
} | |
// Lengthen the array for every element, matched or not | |
if ( seed ) { | |
unmatched.push( elem ); | |
} | |
} | |
} | |
// Apply set filters to unmatched elements | |
matchedCount += i; | |
if ( bySet && i !== matchedCount ) { | |
j = 0; | |
while ( (matcher = setMatchers[j++]) ) { | |
matcher( unmatched, setMatched, context, xml ); | |
} | |
if ( seed ) { | |
// Reintegrate element matches to eliminate the need for sorting | |
if ( matchedCount > 0 ) { | |
while ( i-- ) { | |
if ( !(unmatched[i] || setMatched[i]) ) { | |
setMatched[i] = pop.call( results ); | |
} | |
} | |
} | |
// Discard index placeholder values to get only actual matches | |
setMatched = condense( setMatched ); | |
} | |
// Add matches to results | |
push.apply( results, setMatched ); | |
// Seedless set matches succeeding multiple successful matchers stipulate sorting | |
if ( outermost && !seed && setMatched.length > 0 && | |
( matchedCount + setMatchers.length ) > 1 ) { | |
Sizzle.uniqueSort( results ); | |
} | |
} | |
// Override manipulation of globals by nested matchers | |
if ( outermost ) { | |
dirruns = dirrunsUnique; | |
outermostContext = contextBackup; | |
} | |
return unmatched; | |
}; | |
return bySet ? | |
markFunction( superMatcher ) : | |
superMatcher; | |
} | |
compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { | |
var i, | |
setMatchers = [], | |
elementMatchers = [], | |
cached = compilerCache[ selector + " " ]; | |
if ( !cached ) { | |
// Generate a function of recursive functions that can be used to check each element | |
if ( !match ) { | |
match = tokenize( selector ); | |
} | |
i = match.length; | |
while ( i-- ) { | |
cached = matcherFromTokens( match[i] ); | |
if ( cached[ expando ] ) { | |
setMatchers.push( cached ); | |
} else { | |
elementMatchers.push( cached ); | |
} | |
} | |
// Cache the compiled function | |
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); | |
// Save selector and tokenization | |
cached.selector = selector; | |
} | |
return cached; | |
}; | |
/** | |
* A low-level selection function that works with Sizzle's compiled | |
* selector functions | |
* @param {String|Function} selector A selector or a pre-compiled | |
* selector function built with Sizzle.compile | |
* @param {Element} context | |
* @param {Array} [results] | |
* @param {Array} [seed] A set of elements to match against | |
*/ | |
select = Sizzle.select = function( selector, context, results, seed ) { | |
var i, tokens, token, type, find, | |
compiled = typeof selector === "function" && selector, | |
match = !seed && tokenize( (selector = compiled.selector || selector) ); | |
results = results || []; | |
// Try to minimize operations if there is no seed and only one group | |
if ( match.length === 1 ) { | |
// Take a shortcut and set the context if the root selector is an ID | |
tokens = match[0] = match[0].slice( 0 ); | |
if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && | |
support.getById && context.nodeType === 9 && documentIsHTML && | |
Expr.relative[ tokens[1].type ] ) { | |
context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; | |
if ( !context ) { | |
return results; | |
// Precompiled matchers will still verify ancestry, so step up a level | |
} else if ( compiled ) { | |
context = context.parentNode; | |
} | |
selector = selector.slice( tokens.shift().value.length ); | |
} | |
// Fetch a seed set for right-to-left matching | |
i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; | |
while ( i-- ) { | |
token = tokens[i]; | |
// Abort if we hit a combinator | |
if ( Expr.relative[ (type = token.type) ] ) { | |
break; | |
} | |
if ( (find = Expr.find[ type ]) ) { | |
// Search, expanding context for leading sibling combinators | |
if ( (seed = find( | |
token.matches[0].replace( runescape, funescape ), | |
rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context | |
)) ) { | |
// If seed is empty or no tokens remain, we can return early | |
tokens.splice( i, 1 ); | |
selector = seed.length && toSelector( tokens ); | |
if ( !selector ) { | |
push.apply( results, seed ); | |
return results; | |
} | |
break; | |
} | |
} | |
} | |
} | |
// Compile and execute a filtering function if one is not provided | |
// Provide `match` to avoid retokenization if we modified the selector above | |
( compiled || compile( selector, match ) )( | |
seed, | |
context, | |
!documentIsHTML, | |
results, | |
rsibling.test( selector ) && testContext( context.parentNode ) || context | |
); | |
return results; | |
}; | |
// One-time assignments | |
// Sort stability | |
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; | |
// Support: Chrome 14-35+ | |
// Always assume duplicates if they aren't passed to the comparison function | |
support.detectDuplicates = !!hasDuplicate; | |
// Initialize against the default document | |
setDocument(); | |
// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) | |
// Detached nodes confoundingly follow *each other* | |
support.sortDetached = assert(function( div1 ) { | |
// Should return 1, but returns 4 (following) | |
return div1.compareDocumentPosition( document.createElement("div") ) & 1; | |
}); | |
// Support: IE<8 | |
// Prevent attribute/property "interpolation" | |
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx | |
if ( !assert(function( div ) { | |
div.innerHTML = "<a href='#'></a>"; | |
return div.firstChild.getAttribute("href") === "#" ; | |
}) ) { | |
addHandle( "type|href|height|width", function( elem, name, isXML ) { | |
if ( !isXML ) { | |
return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); | |
} | |
}); | |
} | |
// Support: IE<9 | |
// Use defaultValue in place of getAttribute("value") | |
if ( !support.attributes || !assert(function( div ) { | |
div.innerHTML = "<input/>"; | |
div.firstChild.setAttribute( "value", "" ); | |
return div.firstChild.getAttribute( "value" ) === ""; | |
}) ) { | |
addHandle( "value", function( elem, name, isXML ) { | |
if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { | |
return elem.defaultValue; | |
} | |
}); | |
} | |
// Support: IE<9 | |
// Use getAttributeNode to fetch booleans when getAttribute lies | |
if ( !assert(function( div ) { | |
return div.getAttribute("disabled") == null; | |
}) ) { | |
addHandle( booleans, function( elem, name, isXML ) { | |
var val; | |
if ( !isXML ) { | |
return elem[ name ] === true ? name.toLowerCase() : | |
(val = elem.getAttributeNode( name )) && val.specified ? | |
val.value : | |
null; | |
} | |
}); | |
} | |
return Sizzle; | |
})( window ); | |
jQuery.find = Sizzle; | |
jQuery.expr = Sizzle.selectors; | |
jQuery.expr[":"] = jQuery.expr.pseudos; | |
jQuery.unique = Sizzle.uniqueSort; | |
jQuery.text = Sizzle.getText; | |
jQuery.isXMLDoc = Sizzle.isXML; | |
jQuery.contains = Sizzle.contains; | |
var rneedsContext = jQuery.expr.match.needsContext; | |
var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); | |
var risSimple = /^.[^:#\[\.,]*$/; | |
// Implement the identical functionality for filter and not | |
function winnow( elements, qualifier, not ) { | |
if ( jQuery.isFunction( qualifier ) ) { | |
return jQuery.grep( elements, function( elem, i ) { | |
/* jshint -W018 */ | |
return !!qualifier.call( elem, i, elem ) !== not; | |
}); | |
} | |
if ( qualifier.nodeType ) { | |
return jQuery.grep( elements, function( elem ) { | |
return ( elem === qualifier ) !== not; | |
}); | |
} | |
if ( typeof qualifier === "string" ) { | |
if ( risSimple.test( qualifier ) ) { | |
return jQuery.filter( qualifier, elements, not ); | |
} | |
qualifier = jQuery.filter( qualifier, elements ); | |
} | |
return jQuery.grep( elements, function( elem ) { | |
return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; | |
}); | |
} | |
jQuery.filter = function( expr, elems, not ) { | |
var elem = elems[ 0 ]; | |
if ( not ) { | |
expr = ":not(" + expr + ")"; | |
} | |
return elems.length === 1 && elem.nodeType === 1 ? | |
jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : | |
jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { | |
return elem.nodeType === 1; | |
})); | |
}; | |
jQuery.fn.extend({ | |
find: function( selector ) { | |
var i, | |
len = this.length, | |
ret = [], | |
self = this; | |
if ( typeof selector !== "string" ) { | |
return this.pushStack( jQuery( selector ).filter(function() { | |
for ( i = 0; i < len; i++ ) { | |
if ( jQuery.contains( self[ i ], this ) ) { | |
return true; | |
} | |
} | |
}) ); | |
} | |
for ( i = 0; i < len; i++ ) { | |
jQuery.find( selector, self[ i ], ret ); | |
} | |
// Needed because $( selector, context ) becomes $( context ).find( selector ) | |
ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); | |
ret.selector = this.selector ? this.selector + " " + selector : selector; | |
return ret; | |
}, | |
filter: function( selector ) { | |
return this.pushStack( winnow(this, selector || [], false) ); | |
}, | |
not: function( selector ) { | |
return this.pushStack( winnow(this, selector || [], true) ); | |
}, | |
is: function( selector ) { | |
return !!winnow( | |
this, | |
// If this is a positional/relative selector, check membership in the returned set | |
// so $("p:first").is("p:last") won't return true for a doc with two "p". | |
typeof selector === "string" && rneedsContext.test( selector ) ? | |
jQuery( selector ) : | |
selector || [], | |
false | |
).length; | |
} | |
}); | |
// Initialize a jQuery object | |
// A central reference to the root jQuery(document) | |
var rootjQuery, | |
// A simple way to check for HTML strings | |
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521) | |
// Strict HTML recognition (#11290: must start with <) | |
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, | |
init = jQuery.fn.init = function( selector, context ) { | |
var match, elem; | |
// HANDLE: $(""), $(null), $(undefined), $(false) | |
if ( !selector ) { | |
return this; | |
} | |
// Handle HTML strings | |
if ( typeof selector === "string" ) { | |
if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { | |
// Assume that strings that start and end with <> are HTML and skip the regex check | |
match = [ null, selector, null ]; | |
} else { | |
match = rquickExpr.exec( selector ); | |
} | |
// Match html or make sure no context is specified for #id | |
if ( match && (match[1] || !context) ) { | |
// HANDLE: $(html) -> $(array) | |
if ( match[1] ) { | |
context = context instanceof jQuery ? context[0] : context; | |
// Option to run scripts is true for back-compat | |
// Intentionally let the error be thrown if parseHTML is not present | |
jQuery.merge( this, jQuery.parseHTML( | |
match[1], | |
context && context.nodeType ? context.ownerDocument || context : document, | |
true | |
) ); | |
// HANDLE: $(html, props) | |
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { | |
for ( match in context ) { | |
// Properties of context are called as methods if possible | |
if ( jQuery.isFunction( this[ match ] ) ) { | |
this[ match ]( context[ match ] ); | |
// ...and otherwise set as attributes | |
} else { | |
this.attr( match, context[ match ] ); | |
} | |
} | |
} | |
return this; | |
// HANDLE: $(#id) | |
} else { | |
elem = document.getElementById( match[2] ); | |
// Support: Blackberry 4.6 | |
// gEBID returns nodes no longer in the document (#6963) | |
if ( elem && elem.parentNode ) { | |
// Inject the element directly into the jQuery object | |
this.length = 1; | |
this[0] = elem; | |
} | |
this.context = document; | |
this.selector = selector; | |
return this; | |
} | |
// HANDLE: $(expr, $(...)) | |
} else if ( !context || context.jquery ) { | |
return ( context || rootjQuery ).find( selector ); | |
// HANDLE: $(expr, context) | |
// (which is just equivalent to: $(context).find(expr) | |
} else { | |
return this.constructor( context ).find( selector ); | |
} | |
// HANDLE: $(DOMElement) | |
} else if ( selector.nodeType ) { | |
this.context = this[0] = selector; | |
this.length = 1; | |
return this; | |
// HANDLE: $(function) | |
// Shortcut for document ready | |
} else if ( jQuery.isFunction( selector ) ) { | |
return typeof rootjQuery.ready !== "undefined" ? | |
rootjQuery.ready( selector ) : | |
// Execute immediately if ready is not present | |
selector( jQuery ); | |
} | |
if ( selector.selector !== undefined ) { | |
this.selector = selector.selector; | |
this.context = selector.context; | |
} | |
return jQuery.makeArray( selector, this ); | |
}; | |
// Give the init function the jQuery prototype for later instantiation | |
init.prototype = jQuery.fn; | |
// Initialize central reference | |
rootjQuery = jQuery( document ); | |
var rparentsprev = /^(?:parents|prev(?:Until|All))/, | |
// Methods guaranteed to produce a unique set when starting from a unique set | |
guaranteedUnique = { | |
children: true, | |
contents: true, | |
next: true, | |
prev: true | |
}; | |
jQuery.extend({ | |
dir: function( elem, dir, until ) { | |
var matched = [], | |
truncate = until !== undefined; | |
while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { | |
if ( elem.nodeType === 1 ) { | |
if ( truncate && jQuery( elem ).is( until ) ) { | |
break; | |
} | |
matched.push( elem ); | |
} | |
} | |
return matched; | |
}, | |
sibling: function( n, elem ) { | |
var matched = []; | |
for ( ; n; n = n.nextSibling ) { | |
if ( n.nodeType === 1 && n !== elem ) { | |
matched.push( n ); | |
} | |
} | |
return matched; | |
} | |
}); | |
jQuery.fn.extend({ | |
has: function( target ) { | |
var targets = jQuery( target, this ), | |
l = targets.length; | |
return this.filter(function() { | |
var i = 0; | |
for ( ; i < l; i++ ) { | |
if ( jQuery.contains( this, targets[i] ) ) { | |
return true; | |
} | |
} | |
}); | |
}, | |
closest: function( selectors, context ) { | |
var cur, | |
i = 0, | |
l = this.length, | |
matched = [], | |
pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? | |
jQuery( selectors, context || this.context ) : | |
0; | |
for ( ; i < l; i++ ) { | |
for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { | |
// Always skip document fragments | |
if ( cur.nodeType < 11 && (pos ? | |
pos.index(cur) > -1 : | |
// Don't pass non-elements to Sizzle | |
cur.nodeType === 1 && | |
jQuery.find.matchesSelector(cur, selectors)) ) { | |
matched.push( cur ); | |
break; | |
} | |
} | |
} | |
return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); | |
}, | |
// Determine the position of an element within the set | |
index: function( elem ) { | |
// No argument, return index in parent | |
if ( !elem ) { | |
return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; | |
} | |
// Index in selector | |
if ( typeof elem === "string" ) { | |
return indexOf.call( jQuery( elem ), this[ 0 ] ); | |
} | |
// Locate the position of the desired element | |
return indexOf.call( this, | |
// If it receives a jQuery object, the first element is used | |
elem.jquery ? elem[ 0 ] : elem | |
); | |
}, | |
add: function( selector, context ) { | |
return this.pushStack( | |
jQuery.unique( | |
jQuery.merge( this.get(), jQuery( selector, context ) ) | |
) | |
); | |
}, | |
addBack: function( selector ) { | |
return this.add( selector == null ? | |
this.prevObject : this.prevObject.filter(selector) | |
); | |
} | |
}); | |
function sibling( cur, dir ) { | |
while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} | |
return cur; | |
} | |
jQuery.each({ | |
parent: function( elem ) { | |
var parent = elem.parentNode; | |
return parent && parent.nodeType !== 11 ? parent : null; | |
}, | |
parents: function( elem ) { | |
return jQuery.dir( elem, "parentNode" ); | |
}, | |
parentsUntil: function( elem, i, until ) { | |
return jQuery.dir( elem, "parentNode", until ); | |
}, | |
next: function( elem ) { | |
return sibling( elem, "nextSibling" ); | |
}, | |
prev: function( elem ) { | |
return sibling( elem, "previousSibling" ); | |
}, | |
nextAll: function( elem ) { | |
return jQuery.dir( elem, "nextSibling" ); | |
}, | |
prevAll: function( elem ) { | |
return jQuery.dir( elem, "previousSibling" ); | |
}, | |
nextUntil: function( elem, i, until ) { | |
return jQuery.dir( elem, "nextSibling", until ); | |
}, | |
prevUntil: function( elem, i, until ) { | |
return jQuery.dir( elem, "previousSibling", until ); | |
}, | |
siblings: function( elem ) { | |
return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); | |
}, | |
children: function( elem ) { | |
return jQuery.sibling( elem.firstChild ); | |
}, | |
contents: function( elem ) { | |
return elem.contentDocument || jQuery.merge( [], elem.childNodes ); | |
} | |
}, function( name, fn ) { | |
jQuery.fn[ name ] = function( until, selector ) { | |
var matched = jQuery.map( this, fn, until ); | |
if ( name.slice( -5 ) !== "Until" ) { | |
selector = until; | |
} | |
if ( selector && typeof selector === "string" ) { | |
matched = jQuery.filter( selector, matched ); | |
} | |
if ( this.length > 1 ) { | |
// Remove duplicates | |
if ( !guaranteedUnique[ name ] ) { | |
jQuery.unique( matched ); | |
} | |
// Reverse order for parents* and prev-derivatives | |
if ( rparentsprev.test( name ) ) { | |
matched.reverse(); | |
} | |
} | |
return this.pushStack( matched ); | |
}; | |
}); | |
var rnotwhite = (/\S+/g); | |
// String to Object options format cache | |
var optionsCache = {}; | |
// Convert String-formatted options into Object-formatted ones and store in cache | |
function createOptions( options ) { | |
var object = optionsCache[ options ] = {}; | |
jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { | |
object[ flag ] = true; | |
}); | |
return object; | |
} | |
/* | |
* Create a callback list using the following parameters: | |
* | |
* options: an optional list of space-separated options that will change how | |
* the callback list behaves or a more traditional option object | |
* | |
* By default a callback list will act like an event callback list and can be | |
* "fired" multiple times. | |
* | |
* Possible options: | |
* | |
* once: will ensure the callback list can only be fired once (like a Deferred) | |
* | |
* memory: will keep track of previous values and will call any callback added | |
* after the list has been fired right away with the latest "memorized" | |
* values (like a Deferred) | |
* | |
* unique: will ensure a callback can only be added once (no duplicate in the list) | |
* | |
* stopOnFalse: interrupt callings when a callback returns false | |
* | |
*/ | |
jQuery.Callbacks = function( options ) { | |
// Convert options from String-formatted to Object-formatted if needed | |
// (we check in cache first) | |
options = typeof options === "string" ? | |
( optionsCache[ options ] || createOptions( options ) ) : | |
jQuery.extend( {}, options ); | |
var // Last fire value (for non-forgettable lists) | |
memory, | |
// Flag to know if list was already fired | |
fired, | |
// Flag to know if list is currently firing | |
firing, | |
// First callback to fire (used internally by add and fireWith) | |
firingStart, | |
// End of the loop when firing | |
firingLength, | |
// Index of currently firing callback (modified by remove if needed) | |
firingIndex, | |
// Actual callback list | |
list = [], | |
// Stack of fire calls for repeatable lists | |
stack = !options.once && [], | |
// Fire callbacks | |
fire = function( data ) { | |
memory = options.memory && data; | |
fired = true; | |
firingIndex = firingStart || 0; | |
firingStart = 0; | |
firingLength = list.length; | |
firing = true; | |
for ( ; list && firingIndex < firingLength; firingIndex++ ) { | |
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { | |
memory = false; // To prevent further calls using add | |
break; | |
} | |
} | |
firing = false; | |
if ( list ) { | |
if ( stack ) { | |
if ( stack.length ) { | |
fire( stack.shift() ); | |
} | |
} else if ( memory ) { | |
list = []; | |
} else { | |
self.disable(); | |
} | |
} | |
}, | |
// Actual Callbacks object | |
self = { | |
// Add a callback or a collection of callbacks to the list | |
add: function() { | |
if ( list ) { | |
// First, we save the current length | |
var start = list.length; | |
(function add( args ) { | |
jQuery.each( args, function( _, arg ) { | |
var type = jQuery.type( arg ); | |
if ( type === "function" ) { | |
if ( !options.unique || !self.has( arg ) ) { | |
list.push( arg ); | |
} | |
} else if ( arg && arg.length && type !== "string" ) { | |
// Inspect recursively | |
add( arg ); | |
} | |
}); | |
})( arguments ); | |
// Do we need to add the callbacks to the | |
// current firing batch? | |
if ( firing ) { | |
firingLength = list.length; | |
// With memory, if we're not firing then | |
// we should call right away | |
} else if ( memory ) { | |
firingStart = start; | |
fire( memory ); | |
} | |
} | |
return this; | |
}, | |
// Remove a callback from the list | |
remove: function() { | |
if ( list ) { | |
jQuery.each( arguments, function( _, arg ) { | |
var index; | |
while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { | |
list.splice( index, 1 ); | |
// Handle firing indexes | |
if ( firing ) { | |
if ( index <= firingLength ) { | |
firingLength--; | |
} | |
if ( index <= firingIndex ) { | |
firingIndex--; | |
} | |
} | |
} | |
}); | |
} | |
return this; | |
}, | |
// Check if a given callback is in the list. | |
// If no argument is given, return whether or not list has callbacks attached. | |
has: function( fn ) { | |
return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); | |
}, | |
// Remove all callbacks from the list | |
empty: function() { | |
list = []; | |
firingLength = 0; | |
return this; | |
}, | |
// Have the list do nothing anymore | |
disable: function() { | |
list = stack = memory = undefined; | |
return this; | |
}, | |
// Is it disabled? | |
disabled: function() { | |
return !list; | |
}, | |
// Lock the list in its current state | |
lock: function() { | |
stack = undefined; | |
if ( !memory ) { | |
self.disable(); | |
} | |
return this; | |
}, | |
// Is it locked? | |
locked: function() { | |
return !stack; | |
}, | |
// Call all callbacks with the given context and arguments | |
fireWith: function( context, args ) { | |
if ( list && ( !fired || stack ) ) { | |
args = args || []; | |
args = [ context, args.slice ? args.slice() : args ]; | |
if ( firing ) { | |
stack.push( args ); | |
} else { | |
fire( args ); | |
} | |
} | |
return this; | |
}, | |
// Call all the callbacks with the given arguments | |
fire: function() { | |
self.fireWith( this, arguments ); | |
return this; | |
}, | |
// To know if the callbacks have already been called at least once | |
fired: function() { | |
return !!fired; | |
} | |
}; | |
return self; | |
}; | |
jQuery.extend({ | |
Deferred: function( func ) { | |
var tuples = [ | |
// action, add listener, listener list, final state | |
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], | |
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], | |
[ "notify", "progress", jQuery.Callbacks("memory") ] | |
], | |
state = "pending", | |
promise = { | |
state: function() { | |
return state; | |
}, | |
always: function() { | |
deferred.done( arguments ).fail( arguments ); | |
return this; | |
}, | |
then: function( /* fnDone, fnFail, fnProgress */ ) { | |
var fns = arguments; | |
return jQuery.Deferred(function( newDefer ) { | |
jQuery.each( tuples, function( i, tuple ) { | |
var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; | |
// deferred[ done | fail | progress ] for forwarding actions to newDefer | |
deferred[ tuple[1] ](function() { | |
var returned = fn && fn.apply( this, arguments ); | |
if ( returned && jQuery.isFunction( returned.promise ) ) { | |
returned.promise() | |
.done( newDefer.resolve ) | |
.fail( newDefer.reject ) | |
.progress( newDefer.notify ); | |
} else { | |
newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); | |
} | |
}); | |
}); | |
fns = null; | |
}).promise(); | |
}, | |
// Get a promise for this deferred | |
// If obj is provided, the promise aspect is added to the object | |
promise: function( obj ) { | |
return obj != null ? jQuery.extend( obj, promise ) : promise; | |
} | |
}, | |
deferred = {}; | |
// Keep pipe for back-compat | |
promise.pipe = promise.then; | |
// Add list-specific methods | |
jQuery.each( tuples, function( i, tuple ) { | |
var list = tuple[ 2 ], | |
stateString = tuple[ 3 ]; | |
// promise[ done | fail | progress ] = list.add | |
promise[ tuple[1] ] = list.add; | |
// Handle state | |
if ( stateString ) { | |
list.add(function() { | |
// state = [ resolved | rejected ] | |
state = stateString; | |
// [ reject_list | resolve_list ].disable; progress_list.lock | |
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); | |
} | |
// deferred[ resolve | reject | notify ] | |
deferred[ tuple[0] ] = function() { | |
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); | |
return this; | |
}; | |
deferred[ tuple[0] + "With" ] = list.fireWith; | |
}); | |
// Make the deferred a promise | |
promise.promise( deferred ); | |
// Call given func if any | |
if ( func ) { | |
func.call( deferred, deferred ); | |
} | |
// All done! | |
return deferred; | |
}, | |
// Deferred helper | |
when: function( subordinate /* , ..., subordinateN */ ) { | |
var i = 0, | |
resolveValues = slice.call( arguments ), | |
length = resolveValues.length, | |
// the count of uncompleted subordinates | |
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, | |
// the master Deferred. If resolveValues consist of only a single Deferred, just use that. | |
deferred = remaining === 1 ? subordinate : jQuery.Deferred(), | |
// Update function for both resolve and progress values | |
updateFunc = function( i, contexts, values ) { | |
return function( value ) { | |
contexts[ i ] = this; | |
values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; | |
if ( values === progressValues ) { | |
deferred.notifyWith( contexts, values ); | |
} else if ( !( --remaining ) ) { | |
deferred.resolveWith( contexts, values ); | |
} | |
}; | |
}, | |
progressValues, progressContexts, resolveContexts; | |
// Add listeners to Deferred subordinates; treat others as resolved | |
if ( length > 1 ) { | |
progressValues = new Array( length ); | |
progressContexts = new Array( length ); | |
resolveContexts = new Array( length ); | |
for ( ; i < length; i++ ) { | |
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { | |
resolveValues[ i ].promise() | |
.done( updateFunc( i, resolveContexts, resolveValues ) ) | |
.fail( deferred.reject ) | |
.progress( updateFunc( i, progressContexts, progressValues ) ); | |
} else { | |
--remaining; | |
} | |
} | |
} | |
// If we're not waiting on anything, resolve the master | |
if ( !remaining ) { | |
deferred.resolveWith( resolveContexts, resolveValues ); | |
} | |
return deferred.promise(); | |
} | |
}); | |
// The deferred used on DOM ready | |
var readyList; | |
jQuery.fn.ready = function( fn ) { | |
// Add the callback | |
jQuery.ready.promise().done( fn ); | |
return this; | |
}; | |
jQuery.extend({ | |
// Is the DOM ready to be used? Set to true once it occurs. | |
isReady: false, | |
// A counter to track how many items to wait for before | |
// the ready event fires. See #6781 | |
readyWait: 1, | |
// Hold (or release) the ready event | |
holdReady: function( hold ) { | |
if ( hold ) { | |
jQuery.readyWait++; | |
} else { | |
jQuery.ready( true ); | |
} | |
}, | |
// Handle when the DOM is ready | |
ready: function( wait ) { | |
// Abort if there are pending holds or we're already ready | |
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { | |
return; | |
} | |
// Remember that the DOM is ready | |
jQuery.isReady = true; | |
// If a normal DOM Ready event fired, decrement, and wait if need be | |
if ( wait !== true && --jQuery.readyWait > 0 ) { | |
return; | |
} | |
// If there are functions bound, to execute | |
readyList.resolveWith( document, [ jQuery ] ); | |
// Trigger any bound ready events | |
if ( jQuery.fn.triggerHandler ) { | |
jQuery( document ).triggerHandler( "ready" ); | |
jQuery( document ).off( "ready" ); | |
} | |
} | |
}); | |
/** | |
* The ready event handler and self cleanup method | |
*/ | |
function completed() { | |
document.removeEventListener( "DOMContentLoaded", completed, false ); | |
window.removeEventListener( "load", completed, false ); | |
jQuery.ready(); | |
} | |
jQuery.ready.promise = function( obj ) { | |
if ( !readyList ) { | |
readyList = jQuery.Deferred(); | |
// Catch cases where $(document).ready() is called after the browser event has already occurred. | |
// We once tried to use readyState "interactive" here, but it caused issues like the one | |
// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 | |
if ( document.readyState === "complete" ) { | |
// Handle it asynchronously to allow scripts the opportunity to delay ready | |
setTimeout( jQuery.ready ); | |
} else { | |
// Use the handy event callback | |
document.addEventListener( "DOMContentLoaded", completed, false ); | |
// A fallback to window.onload, that will always work | |
window.addEventListener( "load", completed, false ); | |
} | |
} | |
return readyList.promise( obj ); | |
}; | |
// Kick off the DOM ready check even if the user does not | |
jQuery.ready.promise(); | |
// Multifunctional method to get and set values of a collection | |
// The value/s can optionally be executed if it's a function | |
var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { | |
var i = 0, | |
len = elems.length, | |
bulk = key == null; | |
// Sets many values | |
if ( jQuery.type( key ) === "object" ) { | |
chainable = true; | |
for ( i in key ) { | |
jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); | |
} | |
// Sets one value | |
} else if ( value !== undefined ) { | |
chainable = true; | |
if ( !jQuery.isFunction( value ) ) { | |
raw = true; | |
} | |
if ( bulk ) { | |
// Bulk operations run against the entire set | |
if ( raw ) { | |
fn.call( elems, value ); | |
fn = null; | |
// ...except when executing function values | |
} else { | |
bulk = fn; | |
fn = function( elem, key, value ) { | |
return bulk.call( jQuery( elem ), value ); | |
}; | |
} | |
} | |
if ( fn ) { | |
for ( ; i < len; i++ ) { | |
fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); | |
} | |
} | |
} | |
return chainable ? | |
elems : | |
// Gets | |
bulk ? | |
fn.call( elems ) : | |
len ? fn( elems[0], key ) : emptyGet; | |
}; | |
/** | |
* Determines whether an object can have data | |
*/ | |
jQuery.acceptData = function( owner ) { | |
// Accepts only: | |
// - Node | |
// - Node.ELEMENT_NODE | |
// - Node.DOCUMENT_NODE | |
// - Object | |
// - Any | |
/* jshint -W018 */ | |
return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); | |
}; | |
function Data() { | |
// Support: Android<4, | |
// Old WebKit does not have Object.preventExtensions/freeze method, | |
// return new empty object instead with no [[set]] accessor | |
Object.defineProperty( this.cache = {}, 0, { | |
get: function() { | |
return {}; | |
} | |
}); | |
this.expando = jQuery.expando + Data.uid++; | |
} | |
Data.uid = 1; | |
Data.accepts = jQuery.acceptData; | |
Data.prototype = { | |
key: function( owner ) { | |
// We can accept data for non-element nodes in modern browsers, | |
// but we should not, see #8335. | |
// Always return the key for a frozen object. | |
if ( !Data.accepts( owner ) ) { | |
return 0; | |
} | |
var descriptor = {}, | |
// Check if the owner object already has a cache key | |
unlock = owner[ this.expando ]; | |
// If not, create one | |
if ( !unlock ) { | |
unlock = Data.uid++; | |
// Secure it in a non-enumerable, non-writable property | |
try { | |
descriptor[ this.expando ] = { value: unlock }; | |
Object.defineProperties( owner, descriptor ); | |
// Support: Android<4 | |
// Fallback to a less secure definition | |
} catch ( e ) { | |
descriptor[ this.expando ] = unlock; | |
jQuery.extend( owner, descriptor ); | |
} | |
} | |
// Ensure the cache object | |
if ( !this.cache[ unlock ] ) { | |
this.cache[ unlock ] = {}; | |
} | |
return unlock; | |
}, | |
set: function( owner, data, value ) { | |
var prop, | |
// There may be an unlock assigned to this node, | |
// if there is no entry for this "owner", create one inline | |
// and set the unlock as though an owner entry had always existed | |
unlock = this.key( owner ), | |
cache = this.cache[ unlock ]; | |
// Handle: [ owner, key, value ] args | |
if ( typeof data === "string" ) { | |
cache[ data ] = value; | |
// Handle: [ owner, { properties } ] args | |
} else { | |
// Fresh assignments by object are shallow copied | |
if ( jQuery.isEmptyObject( cache ) ) { | |
jQuery.extend( this.cache[ unlock ], data ); | |
// Otherwise, copy the properties one-by-one to the cache object | |
} else { | |
for ( prop in data ) { | |
cache[ prop ] = data[ prop ]; | |
} | |
} | |
} | |
return cache; | |
}, | |
get: function( owner, key ) { | |
// Either a valid cache is found, or will be created. | |
// New caches will be created and the unlock returned, | |
// allowing direct access to the newly created | |
// empty data object. A valid owner object must be provided. | |
var cache = this.cache[ this.key( owner ) ]; | |
return key === undefined ? | |
cache : cache[ key ]; | |
}, | |
access: function( owner, key, value ) { | |
var stored; | |
// In cases where either: | |
// | |
// 1. No key was specified | |
// 2. A string key was specified, but no value provided | |
// | |
// Take the "read" path and allow the get method to determine | |
// which value to return, respectively either: | |
// | |
// 1. The entire cache object | |
// 2. The data stored at the key | |
// | |
if ( key === undefined || | |
((key && typeof key === "string") && value === undefined) ) { | |
stored = this.get( owner, key ); | |
return stored !== undefined ? | |
stored : this.get( owner, jQuery.camelCase(key) ); | |
} | |
// [*]When the key is not a string, or both a key and value | |
// are specified, set or extend (existing objects) with either: | |
// | |
// 1. An object of properties | |
// 2. A key and value | |
// | |
this.set( owner, key, value ); | |
// Since the "set" path can have two possible entry points | |
// return the expected data based on which path was taken[*] | |
return value !== undefined ? value : key; | |
}, | |
remove: function( owner, key ) { | |
var i, name, camel, | |
unlock = this.key( owner ), | |
cache = this.cache[ unlock ]; | |
if ( key === undefined ) { | |
this.cache[ unlock ] = {}; | |
} else { | |
// Support array or space separated string of keys | |
if ( jQuery.isArray( key ) ) { | |
// If "name" is an array of keys... | |
// When data is initially created, via ("key", "val") signature, | |
// keys will be converted to camelCase. | |
// Since there is no way to tell _how_ a key was added, remove | |
// both plain key and camelCase key. #12786 | |
// This will only penalize the array argument path. | |
name = key.concat( key.map( jQuery.camelCase ) ); | |
} else { | |
camel = jQuery.camelCase( key ); | |
// Try the string as a key before any manipulation | |
if ( key in cache ) { | |
name = [ key, camel ]; | |
} else { | |
// If a key with the spaces exists, use it. | |
// Otherwise, create an array by matching non-whitespace | |
name = camel; | |
name = name in cache ? | |
[ name ] : ( name.match( rnotwhite ) || [] ); | |
} | |
} | |
i = name.length; | |
while ( i-- ) { | |
delete cache[ name[ i ] ]; | |
} | |
} | |
}, | |
hasData: function( owner ) { | |
return !jQuery.isEmptyObject( | |
this.cache[ owner[ this.expando ] ] || {} | |
); | |
}, | |
discard: function( owner ) { | |
if ( owner[ this.expando ] ) { | |
delete this.cache[ owner[ this.expando ] ]; | |
} | |
} | |
}; | |
var data_priv = new Data(); | |
var data_user = new Data(); | |
// Implementation Summary | |
// | |
// 1. Enforce API surface and semantic compatibility with 1.9.x branch | |
// 2. Improve the module's maintainability by reducing the storage | |
// paths to a single mechanism. | |
// 3. Use the same single mechanism to support "private" and "user" data. | |
// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) | |
// 5. Avoid exposing implementation details on user objects (eg. expando properties) | |
// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 | |
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, | |
rmultiDash = /([A-Z])/g; | |
function dataAttr( elem, key, data ) { | |
var name; | |
// If nothing was found internally, try to fetch any | |
// data from the HTML5 data-* attribute | |
if ( data === undefined && elem.nodeType === 1 ) { | |
name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); | |
data = elem.getAttribute( name ); | |
if ( typeof data === "string" ) { | |
try { | |
data = data === "true" ? true : | |
data === "false" ? false : | |
data === "null" ? null : | |
// Only convert to a number if it doesn't change the string | |
+data + "" === data ? +data : | |
rbrace.test( data ) ? jQuery.parseJSON( data ) : | |
data; | |
} catch( e ) {} | |
// Make sure we set the data so it isn't changed later | |
data_user.set( elem, key, data ); | |
} else { | |
data = undefined; | |
} | |
} | |
return data; | |
} | |
jQuery.extend({ | |
hasData: function( elem ) { | |
return data_user.hasData( elem ) || data_priv.hasData( elem ); | |
}, | |
data: function( elem, name, data ) { | |
return data_user.access( elem, name, data ); | |
}, | |
removeData: function( elem, name ) { | |
data_user.remove( elem, name ); | |
}, | |
// TODO: Now that all calls to _data and _removeData have been replaced | |
// with direct calls to data_priv methods, these can be deprecated. | |
_data: function( elem, name, data ) { | |
return data_priv.access( elem, name, data ); | |
}, | |
_removeData: function( elem, name ) { | |
data_priv.remove( elem, name ); | |
} | |
}); | |
jQuery.fn.extend({ | |
data: function( key, value ) { | |
var i, name, data, | |
elem = this[ 0 ], | |
attrs = elem && elem.attributes; | |
// Gets all values | |
if ( key === undefined ) { | |
if ( this.length ) { | |
data = data_user.get( elem ); | |
if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { | |
i = attrs.length; | |
while ( i-- ) { | |
// Support: IE11+ | |
// The attrs elements can be null (#14894) | |
if ( attrs[ i ] ) { | |
name = attrs[ i ].name; | |
if ( name.indexOf( "data-" ) === 0 ) { | |
name = jQuery.camelCase( name.slice(5) ); | |
dataAttr( elem, name, data[ name ] ); | |
} | |
} | |
} | |
data_priv.set( elem, "hasDataAttrs", true ); | |
} | |
} | |
return data; | |
} | |
// Sets multiple values | |
if ( typeof key === "object" ) { | |
return this.each(function() { | |
data_user.set( this, key ); | |
}); | |
} | |
return access( this, function( value ) { | |
var data, | |
camelKey = jQuery.camelCase( key ); | |
// The calling jQuery object (element matches) is not empty | |
// (and therefore has an element appears at this[ 0 ]) and the | |
// `value` parameter was not undefined. An empty jQuery object | |
// will result in `undefined` for elem = this[ 0 ] which will | |
// throw an exception if an attempt to read a data cache is made. | |
if ( elem && value === undefined ) { | |
// Attempt to get data from the cache | |
// with the key as-is | |
data = data_user.get( elem, key ); | |
if ( data !== undefined ) { | |
return data; | |
} | |
// Attempt to get data from the cache | |
// with the key camelized | |
data = data_user.get( elem, camelKey ); | |
if ( data !== undefined ) { | |
return data; | |
} | |
// Attempt to "discover" the data in | |
// HTML5 custom data-* attrs | |
data = dataAttr( elem, camelKey, undefined ); | |
if ( data !== undefined ) { | |
return data; | |
} | |
// We tried really hard, but the data doesn't exist. | |
return; | |
} | |
// Set the data... | |
this.each(function() { | |
// First, attempt to store a copy or reference of any | |
// data that might've been store with a camelCased key. | |
var data = data_user.get( this, camelKey ); | |
// For HTML5 data-* attribute interop, we have to | |
// store property names with dashes in a camelCase form. | |
// This might not apply to all properties...* | |
data_user.set( this, camelKey, value ); | |
// *... In the case of properties that might _actually_ | |
// have dashes, we need to also store a copy of that | |
// unchanged property. | |
if ( key.indexOf("-") !== -1 && data !== undefined ) { | |
data_user.set( this, key, value ); | |
} | |
}); | |
}, null, value, arguments.length > 1, null, true ); | |
}, | |
removeData: function( key ) { | |
return this.each(function() { | |
data_user.remove( this, key ); | |
}); | |
} | |
}); | |
jQuery.extend({ | |
queue: function( elem, type, data ) { | |
var queue; | |
if ( elem ) { | |
type = ( type || "fx" ) + "queue"; | |
queue = data_priv.get( elem, type ); | |
// Speed up dequeue by getting out quickly if this is just a lookup | |
if ( data ) { | |
if ( !queue || jQuery.isArray( data ) ) { | |
queue = data_priv.access( elem, type, jQuery.makeArray(data) ); | |
} else { | |
queue.push( data ); | |
} | |
} | |
return queue || []; | |
} | |
}, | |
dequeue: function( elem, type ) { | |
type = type || "fx"; | |
var queue = jQuery.queue( elem, type ), | |
startLength = queue.length, | |
fn = queue.shift(), | |
hooks = jQuery._queueHooks( elem, type ), | |
next = function() { | |
jQuery.dequeue( elem, type ); | |
}; | |
// If the fx queue is dequeued, always remove the progress sentinel | |
if ( fn === "inprogress" ) { | |
fn = queue.shift(); | |
startLength--; | |
} | |
if ( fn ) { | |
// Add a progress sentinel to prevent the fx queue from being | |
// automatically dequeued | |
if ( type === "fx" ) { | |
queue.unshift( "inprogress" ); | |
} | |
// Clear up the last queue stop function | |
delete hooks.stop; | |
fn.call( elem, next, hooks ); | |
} | |
if ( !startLength && hooks ) { | |
hooks.empty.fire(); | |
} | |
}, | |
// Not public - generate a queueHooks object, or return the current one | |
_queueHooks: function( elem, type ) { | |
var key = type + "queueHooks"; | |
return data_priv.get( elem, key ) || data_priv.access( elem, key, { | |
empty: jQuery.Callbacks("once memory").add(function() { | |
data_priv.remove( elem, [ type + "queue", key ] ); | |
}) | |
}); | |
} | |
}); | |
jQuery.fn.extend({ | |
queue: function( type, data ) { | |
var setter = 2; | |
if ( typeof type !== "string" ) { | |
data = type; | |
type = "fx"; | |
setter--; | |
} | |
if ( arguments.length < setter ) { | |
return jQuery.queue( this[0], type ); | |
} | |
return data === undefined ? | |
this : | |
this.each(function() { | |
var queue = jQuery.queue( this, type, data ); | |
// Ensure a hooks for this queue | |
jQuery._queueHooks( this, type ); | |
if ( type === "fx" && queue[0] !== "inprogress" ) { | |
jQuery.dequeue( this, type ); | |
} | |
}); | |
}, | |
dequeue: function( type ) { | |
return this.each(function() { | |
jQuery.dequeue( this, type ); | |
}); | |
}, | |
clearQueue: function( type ) { | |
return this.queue( type || "fx", [] ); | |
}, | |
// Get a promise resolved when queues of a certain type | |
// are emptied (fx is the type by default) | |
promise: function( type, obj ) { | |
var tmp, | |
count = 1, | |
defer = jQuery.Deferred(), | |
elements = this, | |
i = this.length, | |
resolve = function() { | |
if ( !( --count ) ) { | |
defer.resolveWith( elements, [ elements ] ); | |
} | |
}; | |
if ( typeof type !== "string" ) { | |
obj = type; | |
type = undefined; | |
} | |
type = type || "fx"; | |
while ( i-- ) { | |
tmp = data_priv.get( elements[ i ], type + "queueHooks" ); | |
if ( tmp && tmp.empty ) { | |
count++; | |
tmp.empty.add( resolve ); | |
} | |
} | |
resolve(); | |
return defer.promise( obj ); | |
} | |
}); | |
var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; | |
var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; | |
var isHidden = function( elem, el ) { | |
// isHidden might be called from jQuery#filter function; | |
// in that case, element will be second argument | |
elem = el || elem; | |
return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); | |
}; | |
var rcheckableType = (/^(?:checkbox|radio)$/i); | |
(function() { | |
var fragment = document.createDocumentFragment(), | |
div = fragment.appendChild( document.createElement( "div" ) ), | |
input = document.createElement( "input" ); | |
// Support: Safari<=5.1 | |
// Check state lost if the name is set (#11217) | |
// Support: Windows Web Apps (WWA) | |
// `name` and `type` must use .setAttribute for WWA (#14901) | |
input.setAttribute( "type", "radio" ); | |
input.setAttribute( "checked", "checked" ); | |
input.setAttribute( "name", "t" ); | |
div.appendChild( input ); | |
// Support: Safari<=5.1, Android<4.2 | |
// Older WebKit doesn't clone checked state correctly in fragments | |
support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; | |
// Support: IE<=11+ | |
// Make sure textarea (and checkbox) defaultValue is properly cloned | |
div.innerHTML = "<textarea>x</textarea>"; | |
support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; | |
})(); | |
var strundefined = typeof undefined; | |
support.focusinBubbles = "onfocusin" in window; | |
var | |
rkeyEvent = /^key/, | |
rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, | |
rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, | |
rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; | |
function returnTrue() { | |
return true; | |
} | |
function returnFalse() { | |
return false; | |
} | |
function safeActiveElement() { | |
try { | |
return document.activeElement; | |
} catch ( err ) { } | |
} | |
/* | |
* Helper functions for managing events -- not part of the public interface. | |
* Props to Dean Edwards' addEvent library for many of the ideas. | |
*/ | |
jQuery.event = { | |
global: {}, | |
add: function( elem, types, handler, data, selector ) { | |
var handleObjIn, eventHandle, tmp, | |
events, t, handleObj, | |
special, handlers, type, namespaces, origType, | |
elemData = data_priv.get( elem ); | |
// Don't attach events to noData or text/comment nodes (but allow plain objects) | |
if ( !elemData ) { | |
return; | |
} | |
// Caller can pass in an object of custom data in lieu of the handler | |
if ( handler.handler ) { | |
handleObjIn = handler; | |
handler = handleObjIn.handler; | |
selector = handleObjIn.selector; | |
} | |
// Make sure that the handler has a unique ID, used to find/remove it later | |
if ( !handler.guid ) { | |
handler.guid = jQuery.guid++; | |
} | |
// Init the element's event structure and main handler, if this is the first | |
if ( !(events = elemData.events) ) { | |
events = elemData.events = {}; | |
} | |
if ( !(eventHandle = elemData.handle) ) { | |
eventHandle = elemData.handle = function( e ) { | |
// Discard the second event of a jQuery.event.trigger() and | |
// when an event is called after a page has unloaded | |
return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? | |
jQuery.event.dispatch.apply( elem, arguments ) : undefined; | |
}; | |
} | |
// Handle multiple events separated by a space | |
types = ( types || "" ).match( rnotwhite ) || [ "" ]; | |
t = types.length; | |
while ( t-- ) { | |
tmp = rtypenamespace.exec( types[t] ) || []; | |
type = origType = tmp[1]; | |
namespaces = ( tmp[2] || "" ).split( "." ).sort(); | |
// There *must* be a type, no attaching namespace-only handlers | |
if ( !type ) { | |
continue; | |
} | |
// If event changes its type, use the special event handlers for the changed type | |
special = jQuery.event.special[ type ] || {}; | |
// If selector defined, determine special event api type, otherwise given type | |
type = ( selector ? special.delegateType : special.bindType ) || type; | |
// Update special based on newly reset type | |
special = jQuery.event.special[ type ] || {}; | |
// handleObj is passed to all event handlers | |
handleObj = jQuery.extend({ | |
type: type, | |
origType: origType, | |
data: data, | |
handler: handler, | |
guid: handler.guid, | |
selector: selector, | |
needsContext: selector && jQuery.expr.match.needsContext.test( selector ), | |
namespace: namespaces.join(".") | |
}, handleObjIn ); | |
// Init the event handler queue if we're the first | |
if ( !(handlers = events[ type ]) ) { | |
handlers = events[ type ] = []; | |
handlers.delegateCount = 0; | |
// Only use addEventListener if the special events handler returns false | |
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { | |
if ( elem.addEventListener ) { | |
elem.addEventListener( type, eventHandle, false ); | |
} | |
} | |
} | |
if ( special.add ) { | |
special.add.call( elem, handleObj ); | |
if ( !handleObj.handler.guid ) { | |
handleObj.handler.guid = handler.guid; | |
} | |
} | |
// Add to the element's handler list, delegates in front | |
if ( selector ) { | |
handlers.splice( handlers.delegateCount++, 0, handleObj ); | |
} else { | |
handlers.push( handleObj ); | |
} | |
// Keep track of which events have ever been used, for event optimization | |
jQuery.event.global[ type ] = true; | |
} | |
}, | |
// Detach an event or set of events from an element | |
remove: function( elem, types, handler, selector, mappedTypes ) { | |
var j, origCount, tmp, | |
events, t, handleObj, | |
special, handlers, type, namespaces, origType, | |
elemData = data_priv.hasData( elem ) && data_priv.get( elem ); | |
if ( !elemData || !(events = elemData.events) ) { | |
return; | |
} | |
// Once for each type.namespace in types; type may be omitted | |
types = ( types || "" ).match( rnotwhite ) || [ "" ]; | |
t = types.length; | |
while ( t-- ) { | |
tmp = rtypenamespace.exec( types[t] ) || []; | |
type = origType = tmp[1]; | |
namespaces = ( tmp[2] || "" ).split( "." ).sort(); | |
// Unbind all events (on this namespace, if provided) for the element | |
if ( !type ) { | |
for ( type in events ) { | |
jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); | |
} | |
continue; | |
} | |
special = jQuery.event.special[ type ] || {}; | |
type = ( selector ? special.delegateType : special.bindType ) || type; | |
handlers = events[ type ] || []; | |
tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); | |
// Remove matching events | |
origCount = j = handlers.length; | |
while ( j-- ) { | |
handleObj = handlers[ j ]; | |
if ( ( mappedTypes || origType === handleObj.origType ) && | |
( !handler || handler.guid === handleObj.guid ) && | |
( !tmp || tmp.test( handleObj.namespace ) ) && | |
( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { | |
handlers.splice( j, 1 ); | |
if ( handleObj.selector ) { | |
handlers.delegateCount--; | |
} | |
if ( special.remove ) { | |
special.remove.call( elem, handleObj ); | |
} | |
} | |
} | |
// Remove generic event handler if we removed something and no more handlers exist | |
// (avoids potential for endless recursion during removal of special event handlers) | |
if ( origCount && !handlers.length ) { | |
if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { | |
jQuery.removeEvent( elem, type, elemData.handle ); | |
} | |
delete events[ type ]; | |
} | |
} | |
// Remove the expando if it's no longer used | |
if ( jQuery.isEmptyObject( events ) ) { | |
delete elemData.handle; | |
data_priv.remove( elem, "events" ); | |
} | |
}, | |
trigger: function( event, data, elem, onlyHandlers ) { | |
var i, cur, tmp, bubbleType, ontype, handle, special, | |
eventPath = [ elem || document ], | |
type = hasOwn.call( event, "type" ) ? event.type : event, | |
namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; | |
cur = tmp = elem = elem || document; | |
// Don't do events on text and comment nodes | |
if ( elem.nodeType === 3 || elem.nodeType === 8 ) { | |
return; | |
} | |
// focus/blur morphs to focusin/out; ensure we're not firing them right now | |
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { | |
return; | |
} | |
if ( type.indexOf(".") >= 0 ) { | |
// Namespaced trigger; create a regexp to match event type in handle() | |
namespaces = type.split("."); | |
type = namespaces.shift(); | |
namespaces.sort(); | |
} | |
ontype = type.indexOf(":") < 0 && "on" + type; | |
// Caller can pass in a jQuery.Event object, Object, or just an event type string | |
event = event[ jQuery.expando ] ? | |
event : | |
new jQuery.Event( type, typeof event === "object" && event ); | |
// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) | |
event.isTrigger = onlyHandlers ? 2 : 3; | |
event.namespace = namespaces.join("."); | |
event.namespace_re = event.namespace ? | |
new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : | |
null; | |
// Clean up the event in case it is being reused | |
event.result = undefined; | |
if ( !event.target ) { | |
event.target = elem; | |
} | |
// Clone any incoming data and prepend the event, creating the handler arg list | |
data = data == null ? | |
[ event ] : | |
jQuery.makeArray( data, [ event ] ); | |
// Allow special events to draw outside the lines | |
special = jQuery.event.special[ type ] || {}; | |
if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { | |
return; | |
} | |
// Determine event propagation path in advance, per W3C events spec (#9951) | |
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724) | |
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { | |
bubbleType = special.delegateType || type; | |
if ( !rfocusMorph.test( bubbleType + type ) ) { | |
cur = cur.parentNode; | |
} | |
for ( ; cur; cur = cur.parentNode ) { | |
eventPath.push( cur ); | |
tmp = cur; | |
} | |
// Only add window if we got to document (e.g., not plain obj or detached DOM) | |
if ( tmp === (elem.ownerDocument || document) ) { | |
eventPath.push( tmp.defaultView || tmp.parentWindow || window ); | |
} | |
} | |
// Fire handlers on the event path | |
i = 0; | |
while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { | |
event.type = i > 1 ? | |
bubbleType : | |
special.bindType || type; | |
// jQuery handler | |
handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); | |
if ( handle ) { | |
handle.apply( cur, data ); | |
} | |
// Native handler | |
handle = ontype && cur[ ontype ]; | |
if ( handle && handle.apply && jQuery.acceptData( cur ) ) { | |
event.result = handle.apply( cur, data ); | |
if ( event.result === false ) { | |
event.preventDefault(); | |
} | |
} | |
} | |
event.type = type; | |
// If nobody prevented the default action, do it now | |
if ( !onlyHandlers && !event.isDefaultPrevented() ) { | |
if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && | |
jQuery.acceptData( elem ) ) { | |
// Call a native DOM method on the target with the same name name as the event. | |
// Don't do default actions on window, that's where global variables be (#6170) | |
if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { | |
// Don't re-trigger an onFOO event when we call its FOO() method | |
tmp = elem[ ontype ]; | |
if ( tmp ) { | |
elem[ ontype ] = null; | |
} | |
// Prevent re-triggering of the same event, since we already bubbled it above | |
jQuery.event.triggered = type; | |
elem[ type ](); | |
jQuery.event.triggered = undefined; | |
if ( tmp ) { | |
elem[ ontype ] = tmp; | |
} | |
} | |
} | |
} | |
return event.result; | |
}, | |
dispatch: function( event ) { | |
// Make a writable jQuery.Event from the native event object | |
event = jQuery.event.fix( event ); | |
var i, j, ret, matched, handleObj, | |
handlerQueue = [], | |
args = slice.call( arguments ), | |
handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], | |
special = jQuery.event.special[ event.type ] || {}; | |
// Use the fix-ed jQuery.Event rather than the (read-only) native event | |
args[0] = event; | |
event.delegateTarget = this; | |
// Call the preDispatch hook for the mapped type, and let it bail if desired | |
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { | |
return; | |
} | |
// Determine handlers | |
handlerQueue = jQuery.event.handlers.call( this, event, handlers ); | |
// Run delegates first; they may want to stop propagation beneath us | |
i = 0; | |
while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { | |
event.currentTarget = matched.elem; | |
j = 0; | |
while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { | |
// Triggered event must either 1) have no namespace, or 2) have namespace(s) | |
// a subset or equal to those in the bound event (both can have no namespace). | |
if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { | |
event.handleObj = handleObj; | |
event.data = handleObj.data; | |
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) | |
.apply( matched.elem, args ); | |
if ( ret !== undefined ) { | |
if ( (event.result = ret) === false ) { | |
event.preventDefault(); | |
event.stopPropagation(); | |
} | |
} | |
} | |
} | |
} | |
// Call the postDispatch hook for the mapped type | |
if ( special.postDispatch ) { | |
special.postDispatch.call( this, event ); | |
} | |
return event.result; | |
}, | |
handlers: function( event, handlers ) { | |
var i, matches, sel, handleObj, | |
handlerQueue = [], | |
delegateCount = handlers.delegateCount, | |
cur = event.target; | |
// Find delegate handlers | |
// Black-hole SVG <use> instance trees (#13180) | |
// Avoid non-left-click bubbling in Firefox (#3861) | |
if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { | |
for ( ; cur !== this; cur = cur.parentNode || this ) { | |
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) | |
if ( cur.disabled !== true || event.type !== "click" ) { | |
matches = []; | |
for ( i = 0; i < delegateCount; i++ ) { | |
handleObj = handlers[ i ]; | |
// Don't conflict with Object.prototype properties (#13203) | |
sel = handleObj.selector + " "; | |
if ( matches[ sel ] === undefined ) { | |
matches[ sel ] = handleObj.needsContext ? | |
jQuery( sel, this ).index( cur ) >= 0 : | |
jQuery.find( sel, this, null, [ cur ] ).length; | |
} | |
if ( matches[ sel ] ) { | |
matches.push( handleObj ); | |
} | |
} | |
if ( matches.length ) { | |
handlerQueue.push({ elem: cur, handlers: matches }); | |
} | |
} | |
} | |
} | |
// Add the remaining (directly-bound) handlers | |
if ( delegateCount < handlers.length ) { | |
handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); | |
} | |
return handlerQueue; | |
}, | |
// Includes some event props shared by KeyEvent and MouseEvent | |
props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), | |
fixHooks: {}, | |
keyHooks: { | |
props: "char charCode key keyCode".split(" "), | |
filter: function( event, original ) { | |
// Add which for key events | |
if ( event.which == null ) { | |
event.which = original.charCode != null ? original.charCode : original.keyCode; | |
} | |
return event; | |
} | |
}, | |
mouseHooks: { | |
props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), | |
filter: function( event, original ) { | |
var eventDoc, doc, body, | |
button = original.button; | |
// Calculate pageX/Y if missing and clientX/Y available | |
if ( event.pageX == null && original.clientX != null ) { | |
eventDoc = event.target.ownerDocument || document; | |
doc = eventDoc.documentElement; | |
body = eventDoc.body; | |
event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); | |
event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); | |
} | |
// Add which for click: 1 === left; 2 === middle; 3 === right | |
// Note: button is not normalized, so don't use it | |
if ( !event.which && button !== undefined ) { | |
event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); | |
} | |
return event; | |
} | |
}, | |
fix: function( event ) { | |
if ( event[ jQuery.expando ] ) { | |
return event; | |
} | |
// Create a writable copy of the event object and normalize some properties | |
var i, prop, copy, | |
type = event.type, | |
originalEvent = event, | |
fixHook = this.fixHooks[ type ]; | |
if ( !fixHook ) { | |
this.fixHooks[ type ] = fixHook = | |
rmouseEvent.test( type ) ? this.mouseHooks : | |
rkeyEvent.test( type ) ? this.keyHooks : | |
{}; | |
} | |
copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; | |
event = new jQuery.Event( originalEvent ); | |
i = copy.length; | |
while ( i-- ) { | |
prop = copy[ i ]; | |
event[ prop ] = originalEvent[ prop ]; | |
} | |
// Support: Cordova 2.5 (WebKit) (#13255) | |
// All events should have a target; Cordova deviceready doesn't | |
if ( !event.target ) { | |
event.target = document; | |
} | |
// Support: Safari 6.0+, Chrome<28 | |
// Target should not be a text node (#504, #13143) | |
if ( event.target.nodeType === 3 ) { | |
event.target = event.target.parentNode; | |
} | |
return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; | |
}, | |
special: { | |
load: { | |
// Prevent triggered image.load events from bubbling to window.load | |
noBubble: true | |
}, | |
focus: { | |
// Fire native event if possible so blur/focus sequence is correct | |
trigger: function() { | |
if ( this !== safeActiveElement() && this.focus ) { | |
this.focus(); | |
return false; | |
} | |
}, | |
delegateType: "focusin" | |
}, | |
blur: { | |
trigger: function() { | |
if ( this === safeActiveElement() && this.blur ) { | |
this.blur(); | |
return false; | |
} | |
}, | |
delegateType: "focusout" | |
}, | |
click: { | |
// For checkbox, fire native event so checked state will be right | |
trigger: function() { | |
if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { | |
this.click(); | |
return false; | |
} | |
}, | |
// For cross-browser consistency, don't fire native .click() on links | |
_default: function( event ) { | |
return jQuery.nodeName( event.target, "a" ); | |
} | |
}, | |
beforeunload: { | |
postDispatch: function( event ) { | |
// Support: Firefox 20+ | |
// Firefox doesn't alert if the returnValue field is not set. | |
if ( event.result !== undefined && event.originalEvent ) { | |
event.originalEvent.returnValue = event.result; | |
} | |
} | |
} | |
}, | |
simulate: function( type, elem, event, bubble ) { | |
// Piggyback on a donor event to simulate a different one. | |
// Fake originalEvent to avoid donor's stopPropagation, but if the | |
// simulated event prevents default then we do the same on the donor. | |
var e = jQuery.extend( | |
new jQuery.Event(), | |
event, | |
{ | |
type: type, | |
isSimulated: true, | |
originalEvent: {} | |
} | |
); | |
if ( bubble ) { | |
jQuery.event.trigger( e, null, elem ); | |
} else { | |
jQuery.event.dispatch.call( elem, e ); | |
} | |
if ( e.isDefaultPrevented() ) { | |
event.preventDefault(); | |
} | |
} | |
}; | |
jQuery.removeEvent = function( elem, type, handle ) { | |
if ( elem.removeEventListener ) { | |
elem.removeEventListener( type, handle, false ); | |
} | |
}; | |
jQuery.Event = function( src, props ) { | |
// Allow instantiation without the 'new' keyword | |
if ( !(this instanceof jQuery.Event) ) { | |
return new jQuery.Event( src, props ); | |
} | |
// Event object | |
if ( src && src.type ) { | |
this.originalEvent = src; | |
this.type = src.type; | |
// Events bubbling up the document may have been marked as prevented | |
// by a handler lower down the tree; reflect the correct value. | |
this.isDefaultPrevented = src.defaultPrevented || | |
src.defaultPrevented === undefined && | |
// Support: Android<4.0 | |
src.returnValue === false ? | |
returnTrue : | |
returnFalse; | |
// Event type | |
} else { | |
this.type = src; | |
} | |
// Put explicitly provided properties onto the event object | |
if ( props ) { | |
jQuery.extend( this, props ); | |
} | |
// Create a timestamp if incoming event doesn't have one | |
this.timeStamp = src && src.timeStamp || jQuery.now(); | |
// Mark it as fixed | |
this[ jQuery.expando ] = true; | |
}; | |
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding | |
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html | |
jQuery.Event.prototype = { | |
isDefaultPrevented: returnFalse, | |
isPropagationStopped: returnFalse, | |
isImmediatePropagationStopped: returnFalse, | |
preventDefault: function() { | |
var e = this.originalEvent; | |
this.isDefaultPrevented = returnTrue; | |
if ( e && e.preventDefault ) { | |
e.preventDefault(); | |
} | |
}, | |
stopPropagation: function() { | |
var e = this.originalEvent; | |
this.isPropagationStopped = returnTrue; | |
if ( e && e.stopPropagation ) { | |
e.stopPropagation(); | |
} | |
}, | |
stopImmediatePropagation: function() { | |
var e = this.originalEvent; | |
this.isImmediatePropagationStopped = returnTrue; | |
if ( e && e.stopImmediatePropagation ) { | |
e.stopImmediatePropagation(); | |
} | |
this.stopPropagation(); | |
} | |
}; | |
// Create mouseenter/leave events using mouseover/out and event-time checks | |
// Support: Chrome 15+ | |
jQuery.each({ | |
mouseenter: "mouseover", | |
mouseleave: "mouseout", | |
pointerenter: "pointerover", | |
pointerleave: "pointerout" | |
}, function( orig, fix ) { | |
jQuery.event.special[ orig ] = { | |
delegateType: fix, | |
bindType: fix, | |
handle: function( event ) { | |
var ret, | |
target = this, | |
related = event.relatedTarget, | |
handleObj = event.handleObj; | |
// For mousenter/leave call the handler if related is outside the target. | |
// NB: No relatedTarget if the mouse left/entered the browser window | |
if ( !related || (related !== target && !jQuery.contains( target, related )) ) { | |
event.type = handleObj.origType; | |
ret = handleObj.handler.apply( this, arguments ); | |
event.type = fix; | |
} | |
return ret; | |
} | |
}; | |
}); | |
// Support: Firefox, Chrome, Safari | |
// Create "bubbling" focus and blur events | |
if ( !support.focusinBubbles ) { | |
jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { | |
// Attach a single capturing handler on the document while someone wants focusin/focusout | |
var handler = function( event ) { | |
jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); | |
}; | |
jQuery.event.special[ fix ] = { | |
setup: function() { | |
var doc = this.ownerDocument || this, | |
attaches = data_priv.access( doc, fix ); | |
if ( !attaches ) { | |
doc.addEventListener( orig, handler, true ); | |
} | |
data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); | |
}, | |
teardown: function() { | |
var doc = this.ownerDocument || this, | |
attaches = data_priv.access( doc, fix ) - 1; | |
if ( !attaches ) { | |
doc.removeEventListener( orig, handler, true ); | |
data_priv.remove( doc, fix ); | |
} else { | |
data_priv.access( doc, fix, attaches ); | |
} | |
} | |
}; | |
}); | |
} | |
jQuery.fn.extend({ | |
on: function( types, selector, data, fn, /*INTERNAL*/ one ) { | |
var origFn, type; | |
// Types can be a map of types/handlers | |
if ( typeof types === "object" ) { | |
// ( types-Object, selector, data ) | |
if ( typeof selector !== "string" ) { | |
// ( types-Object, data ) | |
data = data || selector; | |
selector = undefined; | |
} | |
for ( type in types ) { | |
this.on( type, selector, data, types[ type ], one ); | |
} | |
return this; | |
} | |
if ( data == null && fn == null ) { | |
// ( types, fn ) | |
fn = selector; | |
data = selector = undefined; | |
} else if ( fn == null ) { | |
if ( typeof selector === "string" ) { | |
// ( types, selector, fn ) | |
fn = data; | |
data = undefined; | |
} else { | |
// ( types, data, fn ) | |
fn = data; | |
data = selector; | |
selector = undefined; | |
} | |
} | |
if ( fn === false ) { | |
fn = returnFalse; | |
} else if ( !fn ) { | |
return this; | |
} | |
if ( one === 1 ) { | |
origFn = fn; | |
fn = function( event ) { | |
// Can use an empty set, since event contains the info | |
jQuery().off( event ); | |
return origFn.apply( this, arguments ); | |
}; | |
// Use same guid so caller can remove using origFn | |
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); | |
} | |
return this.each( function() { | |
jQuery.event.add( this, types, fn, data, selector ); | |
}); | |
}, | |
one: function( types, selector, data, fn ) { | |
return this.on( types, selector, data, fn, 1 ); | |
}, | |
off: function( types, selector, fn ) { | |
var handleObj, type; | |
if ( types && types.preventDefault && types.handleObj ) { | |
// ( event ) dispatched jQuery.Event | |
handleObj = types.handleObj; | |
jQuery( types.delegateTarget ).off( | |
handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, | |
handleObj.selector, | |
handleObj.handler | |
); | |
return this; | |
} | |
if ( typeof types === "object" ) { | |
// ( types-object [, selector] ) | |
for ( type in types ) { | |
this.off( type, selector, types[ type ] ); | |
} | |
return this; | |
} | |
if ( selector === false || typeof selector === "function" ) { | |
// ( types [, fn] ) | |
fn = selector; | |
selector = undefined; | |
} | |
if ( fn === false ) { | |
fn = returnFalse; | |
} | |
return this.each(function() { | |
jQuery.event.remove( this, types, fn, selector ); | |
}); | |
}, | |
trigger: function( type, data ) { | |
return this.each(function() { | |
jQuery.event.trigger( type, data, this ); | |
}); | |
}, | |
triggerHandler: function( type, data ) { | |
var elem = this[0]; | |
if ( elem ) { | |
return jQuery.event.trigger( type, data, elem, true ); | |
} | |
} | |
}); | |
var | |
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, | |
rtagName = /<([\w:]+)/, | |
rhtml = /<|&#?\w+;/, | |
rnoInnerhtml = /<(?:script|style|link)/i, | |
// checked="checked" or checked | |
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, | |
rscriptType = /^$|\/(?:java|ecma)script/i, | |
rscriptTypeMasked = /^true\/(.*)/, | |
rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g, | |
// We have to close these tags to support XHTML (#13200) | |
wrapMap = { | |
// Support: IE9 | |
option: [ 1, "<select multiple='multiple'>", "</select>" ], | |
thead: [ 1, "<table>", "</table>" ], | |
col: [ 2, "<table><colgroup>", "</colgroup></table>" ], | |
tr: [ 2, "<table><tbody>", "</tbody></table>" ], | |
td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], | |
_default: [ 0, "", "" ] | |
}; | |
// Support: IE9 | |
wrapMap.optgroup = wrapMap.option; | |
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; | |
wrapMap.th = wrapMap.td; | |
// Support: 1.x compatibility | |
// Manipulating tables requires a tbody | |
function manipulationTarget( elem, content ) { | |
return jQuery.nodeName( elem, "table" ) && | |
jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? | |
elem.getElementsByTagName("tbody")[0] || | |
elem.appendChild( elem.ownerDocument.createElement("tbody") ) : | |
elem; | |
} | |
// Replace/restore the type attribute of script elements for safe DOM manipulation | |
function disableScript( elem ) { | |
elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; | |
return elem; | |
} | |
function restoreScript( elem ) { | |
var match = rscriptTypeMasked.exec( elem.type ); | |
if ( match ) { | |
elem.type = match[ 1 ]; | |
} else { | |
elem.removeAttribute("type"); | |
} | |
return elem; | |
} | |
// Mark scripts as having already been evaluated | |
function setGlobalEval( elems, refElements ) { | |
var i = 0, | |
l = elems.length; | |
for ( ; i < l; i++ ) { | |
data_priv.set( | |
elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) | |
); | |
} | |
} | |
function cloneCopyEvent( src, dest ) { | |
var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; | |
if ( dest.nodeType !== 1 ) { | |
return; | |
} | |
// 1. Copy private data: events, handlers, etc. | |
if ( data_priv.hasData( src ) ) { | |
pdataOld = data_priv.access( src ); | |
pdataCur = data_priv.set( dest, pdataOld ); | |
events = pdataOld.events; | |
if ( events ) { | |
delete pdataCur.handle; | |
pdataCur.events = {}; | |
for ( type in events ) { | |
for ( i = 0, l = events[ type ].length; i < l; i++ ) { | |
jQuery.event.add( dest, type, events[ type ][ i ] ); | |
} | |
} | |
} | |
} | |
// 2. Copy user data | |
if ( data_user.hasData( src ) ) { | |
udataOld = data_user.access( src ); | |
udataCur = jQuery.extend( {}, udataOld ); | |
data_user.set( dest, udataCur ); | |
} | |
} | |
function getAll( context, tag ) { | |
var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : | |
context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : | |
[]; | |
return tag === undefined || tag && jQuery.nodeName( context, tag ) ? | |
jQuery.merge( [ context ], ret ) : | |
ret; | |
} | |
// Fix IE bugs, see support tests | |
function fixInput( src, dest ) { | |
var nodeName = dest.nodeName.toLowerCase(); | |
// Fails to persist the checked state of a cloned checkbox or radio button. | |
if ( nodeName === "input" && rcheckableType.test( src.type ) ) { | |
dest.checked = src.checked; | |
// Fails to return the selected option to the default selected state when cloning options | |
} else if ( nodeName === "input" || nodeName === "textarea" ) { | |
dest.defaultValue = src.defaultValue; | |
} | |
} | |
jQuery.extend({ | |
clone: function( elem, dataAndEvents, deepDataAndEvents ) { | |
var i, l, srcElements, destElements, | |
clone = elem.cloneNode( true ), | |
inPage = jQuery.contains( elem.ownerDocument, elem ); | |
// Fix IE cloning issues | |
if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && | |
!jQuery.isXMLDoc( elem ) ) { | |
// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 | |
destElements = getAll( clone ); | |
srcElements = getAll( elem ); | |
for ( i = 0, l = srcElements.length; i < l; i++ ) { | |
fixInput( srcElements[ i ], destElements[ i ] ); | |
} | |
} | |
// Copy the events from the original to the clone | |
if ( dataAndEvents ) { | |
if ( deepDataAndEvents ) { | |
srcElements = srcElements || getAll( elem ); | |
destElements = destElements || getAll( clone ); | |
for ( i = 0, l = srcElements.length; i < l; i++ ) { | |
cloneCopyEvent( srcElements[ i ], destElements[ i ] ); | |
} | |
} else { | |
cloneCopyEvent( elem, clone ); | |
} | |
} | |
// Preserve script evaluation history | |
destElements = getAll( clone, "script" ); | |
if ( destElements.length > 0 ) { | |
setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); | |
} | |
// Return the cloned set | |
return clone; | |
}, | |
buildFragment: function( elems, context, scripts, selection ) { | |
var elem, tmp, tag, wrap, contains, j, | |
fragment = context.createDocumentFragment(), | |
nodes = [], | |
i = 0, | |
l = elems.length; | |
for ( ; i < l; i++ ) { | |
elem = elems[ i ]; | |
if ( elem || elem === 0 ) { | |
// Add nodes directly | |
if ( jQuery.type( elem ) === "object" ) { | |
// Support: QtWebKit, PhantomJS | |
// push.apply(_, arraylike) throws on ancient WebKit | |
jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); | |
// Convert non-html into a text node | |
} else if ( !rhtml.test( elem ) ) { | |
nodes.push( context.createTextNode( elem ) ); | |
// Convert html into DOM nodes | |
} else { | |
tmp = tmp || fragment.appendChild( context.createElement("div") ); | |
// Deserialize a standard representation | |
tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); | |
wrap = wrapMap[ tag ] || wrapMap._default; | |
tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ]; | |
// Descend through wrappers to the right content | |
j = wrap[ 0 ]; | |
while ( j-- ) { | |
tmp = tmp.lastChild; | |
} | |
// Support: QtWebKit, PhantomJS | |
// push.apply(_, arraylike) throws on ancient WebKit | |
jQuery.merge( nodes, tmp.childNodes ); | |
// Remember the top-level container | |
tmp = fragment.firstChild; | |
// Ensure the created nodes are orphaned (#12392) | |
tmp.textContent = ""; | |
} | |
} | |
} | |
// Remove wrapper from fragment | |
fragment.textContent = ""; | |
i = 0; | |
while ( (elem = nodes[ i++ ]) ) { | |
// #4087 - If origin and destination elements are the same, and this is | |
// that element, do not do anything | |
if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { | |
continue; | |
} | |
contains = jQuery.contains( elem.ownerDocument, elem ); | |
// Append to fragment | |
tmp = getAll( fragment.appendChild( elem ), "script" ); | |
// Preserve script evaluation history | |
if ( contains ) { | |
setGlobalEval( tmp ); | |
} | |
// Capture executables | |
if ( scripts ) { | |
j = 0; | |
while ( (elem = tmp[ j++ ]) ) { | |
if ( rscriptType.test( elem.type || "" ) ) { | |
scripts.push( elem ); | |
} | |
} | |
} | |
} | |
return fragment; | |
}, | |
cleanData: function( elems ) { | |
var data, elem, type, key, | |
special = jQuery.event.special, | |
i = 0; | |
for ( ; (elem = elems[ i ]) !== undefined; i++ ) { | |
if ( jQuery.acceptData( elem ) ) { | |
key = elem[ data_priv.expando ]; | |
if ( key && (data = data_priv.cache[ key ]) ) { | |
if ( data.events ) { | |
for ( type in data.events ) { | |
if ( special[ type ] ) { | |
jQuery.event.remove( elem, type ); | |
// This is a shortcut to avoid jQuery.event.remove's overhead | |
} else { | |
jQuery.removeEvent( elem, type, data.handle ); | |
} | |
} | |
} | |
if ( data_priv.cache[ key ] ) { | |
// Discard any remaining `private` data | |
delete data_priv.cache[ key ]; | |
} | |
} | |
} | |
// Discard any remaining `user` data | |
delete data_user.cache[ elem[ data_user.expando ] ]; | |
} | |
} | |
}); | |
jQuery.fn.extend({ | |
text: function( value ) { | |
return access( this, function( value ) { | |
return value === undefined ? | |
jQuery.text( this ) : | |
this.empty().each(function() { | |
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { | |
this.textContent = value; | |
} | |
}); | |
}, null, value, arguments.length ); | |
}, | |
append: function() { | |
return this.domManip( arguments, function( elem ) { | |
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { | |
var target = manipulationTarget( this, elem ); | |
target.appendChild( elem ); | |
} | |
}); | |
}, | |
prepend: function() { | |
return this.domManip( arguments, function( elem ) { | |
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { | |
var target = manipulationTarget( this, elem ); | |
target.insertBefore( elem, target.firstChild ); | |
} | |
}); | |
}, | |
before: function() { | |
return this.domManip( arguments, function( elem ) { | |
if ( this.parentNode ) { | |
this.parentNode.insertBefore( elem, this ); | |
} | |
}); | |
}, | |
after: function() { | |
return this.domManip( arguments, function( elem ) { | |
if ( this.parentNode ) { | |
this.parentNode.insertBefore( elem, this.nextSibling ); | |
} | |
}); | |
}, | |
remove: function( selector, keepData /* Internal Use Only */ ) { | |
var elem, | |
elems = selector ? jQuery.filter( selector, this ) : this, | |
i = 0; | |
for ( ; (elem = elems[i]) != null; i++ ) { | |
if ( !keepData && elem.nodeType === 1 ) { | |
jQuery.cleanData( getAll( elem ) ); | |
} | |
if ( elem.parentNode ) { | |
if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { | |
setGlobalEval( getAll( elem, "script" ) ); | |
} | |
elem.parentNode.removeChild( elem ); | |
} | |
} | |
return this; | |
}, | |
empty: function() { | |
var elem, | |
i = 0; | |
for ( ; (elem = this[i]) != null; i++ ) { | |
if ( elem.nodeType === 1 ) { | |
// Prevent memory leaks | |
jQuery.cleanData( getAll( elem, false ) ); | |
// Remove any remaining nodes | |
elem.textContent = ""; | |
} | |
} | |
return this; | |
}, | |
clone: function( dataAndEvents, deepDataAndEvents ) { | |
dataAndEvents = dataAndEvents == null ? false : dataAndEvents; | |
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; | |
return this.map(function() { | |
return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); | |
}); | |
}, | |
html: function( value ) { | |
return access( this, function( value ) { | |
var elem = this[ 0 ] || {}, | |
i = 0, | |
l = this.length; | |
if ( value === undefined && elem.nodeType === 1 ) { | |
return elem.innerHTML; | |
} | |
// See if we can take a shortcut and just use innerHTML | |
if ( typeof value === "string" && !rnoInnerhtml.test( value ) && | |
!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { | |
value = value.replace( rxhtmlTag, "<$1></$2>" ); | |
try { | |
for ( ; i < l; i++ ) { | |
elem = this[ i ] || {}; | |
// Remove element nodes and prevent memory leaks | |
if ( elem.nodeType === 1 ) { | |
jQuery.cleanData( getAll( elem, false ) ); | |
elem.innerHTML = value; | |
} | |
} | |
elem = 0; | |
// If using innerHTML throws an exception, use the fallback method | |
} catch( e ) {} | |
} | |
if ( elem ) { | |
this.empty().append( value ); | |
} | |
}, null, value, arguments.length ); | |
}, | |
replaceWith: function() { | |
var arg = arguments[ 0 ]; | |
// Make the changes, replacing each context element with the new content | |
this.domManip( arguments, function( elem ) { | |
arg = this.parentNode; | |
jQuery.cleanData( getAll( this ) ); | |
if ( arg ) { | |
arg.replaceChild( elem, this ); | |
} | |
}); | |
// Force removal if there was no new content (e.g., from empty arguments) | |
return arg && (arg.length || arg.nodeType) ? this : this.remove(); | |
}, | |
detach: function( selector ) { | |
return this.remove( selector, true ); | |
}, | |
domManip: function( args, callback ) { | |
// Flatten any nested arrays | |
args = concat.apply( [], args ); | |
var fragment, first, scripts, hasScripts, node, doc, | |
i = 0, | |
l = this.length, | |
set = this, | |
iNoClone = l - 1, | |
value = args[ 0 ], | |
isFunction = jQuery.isFunction( value ); | |
// We can't cloneNode fragments that contain checked, in WebKit | |
if ( isFunction || | |
( l > 1 && typeof value === "string" && | |
!support.checkClone && rchecked.test( value ) ) ) { | |
return this.each(function( index ) { | |
var self = set.eq( index ); | |
if ( isFunction ) { | |
args[ 0 ] = value.call( this, index, self.html() ); | |
} | |
self.domManip( args, callback ); | |
}); | |
} | |
if ( l ) { | |
fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); | |
first = fragment.firstChild; | |
if ( fragment.childNodes.length === 1 ) { | |
fragment = first; | |
} | |
if ( first ) { | |
scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); | |
hasScripts = scripts.length; | |
// Use the original fragment for the last item instead of the first because it can end up | |
// being emptied incorrectly in certain situations (#8070). | |
for ( ; i < l; i++ ) { | |
node = fragment; | |
if ( i !== iNoClone ) { | |
node = jQuery.clone( node, true, true ); | |
// Keep references to cloned scripts for later restoration | |
if ( hasScripts ) { | |
// Support: QtWebKit | |
// jQuery.merge because push.apply(_, arraylike) throws | |
jQuery.merge( scripts, getAll( node, "script" ) ); | |
} | |
} | |
callback.call( this[ i ], node, i ); | |
} | |
if ( hasScripts ) { | |
doc = scripts[ scripts.length - 1 ].ownerDocument; | |
// Reenable scripts | |
jQuery.map( scripts, restoreScript ); | |
// Evaluate executable scripts on first document insertion | |
for ( i = 0; i < hasScripts; i++ ) { | |
node = scripts[ i ]; | |
if ( rscriptType.test( node.type || "" ) && | |
!data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { | |
if ( node.src ) { | |
// Optional AJAX dependency, but won't run scripts if not present | |
if ( jQuery._evalUrl ) { | |
jQuery._evalUrl( node.src ); | |
} | |
} else { | |
jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); | |
} | |
} | |
} | |
} | |
} | |
} | |
return this; | |
} | |
}); | |
jQuery.each({ | |
appendTo: "append", | |
prependTo: "prepend", | |
insertBefore: "before", | |
insertAfter: "after", | |
replaceAll: "replaceWith" | |
}, function( name, original ) { | |
jQuery.fn[ name ] = function( selector ) { | |
var elems, | |
ret = [], | |
insert = jQuery( selector ), | |
last = insert.length - 1, | |
i = 0; | |
for ( ; i <= last; i++ ) { | |
elems = i === last ? this : this.clone( true ); | |
jQuery( insert[ i ] )[ original ]( elems ); | |
// Support: QtWebKit | |
// .get() because push.apply(_, arraylike) throws | |
push.apply( ret, elems.get() ); | |
} | |
return this.pushStack( ret ); | |
}; | |
}); | |
var iframe, | |
elemdisplay = {}; | |
/** | |
* Retrieve the actual display of a element | |
* @param {String} name nodeName of the element | |
* @param {Object} doc Document object | |
*/ | |
// Called only from within defaultDisplay | |
function actualDisplay( name, doc ) { | |
var style, | |
elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), | |
// getDefaultComputedStyle might be reliably used only on attached element | |
display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? | |
// Use of this method is a temporary fix (more like optimization) until something better comes along, | |
// since it was removed from specification and supported only in FF | |
style.display : jQuery.css( elem[ 0 ], "display" ); | |
// We don't have any data stored on the element, | |
// so use "detach" method as fast way to get rid of the element | |
elem.detach(); | |
return display; | |
} | |
/** | |
* Try to determine the default display value of an element | |
* @param {String} nodeName | |
*/ | |
function defaultDisplay( nodeName ) { | |
var doc = document, | |
display = elemdisplay[ nodeName ]; | |
if ( !display ) { | |
display = actualDisplay( nodeName, doc ); | |
// If the simple way fails, read from inside an iframe | |
if ( display === "none" || !display ) { | |
// Use the already-created iframe if possible | |
iframe = (iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" )).appendTo( doc.documentElement ); | |
// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse | |
doc = iframe[ 0 ].contentDocument; | |
// Support: IE | |
doc.write(); | |
doc.close(); | |
display = actualDisplay( nodeName, doc ); | |
iframe.detach(); | |
} | |
// Store the correct default display | |
elemdisplay[ nodeName ] = display; | |
} | |
return display; | |
} | |
var rmargin = (/^margin/); | |
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); | |
var getStyles = function( elem ) { | |
// Support: IE<=11+, Firefox<=30+ (#15098, #14150) | |
// IE throws on elements created in popups | |
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle" | |
if ( elem.ownerDocument.defaultView.opener ) { | |
return elem.ownerDocument.defaultView.getComputedStyle( elem, null ); | |
} | |
return window.getComputedStyle( elem, null ); | |
}; | |
function curCSS( elem, name, computed ) { | |
var width, minWidth, maxWidth, ret, | |
style = elem.style; | |
computed = computed || getStyles( elem ); | |
// Support: IE9 | |
// getPropertyValue is only needed for .css('filter') (#12537) | |
if ( computed ) { | |
ret = computed.getPropertyValue( name ) || computed[ name ]; | |
} | |
if ( computed ) { | |
if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { | |
ret = jQuery.style( elem, name ); | |
} | |
// Support: iOS < 6 | |
// A tribute to the "awesome hack by Dean Edwards" | |
// iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels | |
// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values | |
if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { | |
// Remember the original values | |
width = style.width; | |
minWidth = style.minWidth; | |
maxWidth = style.maxWidth; | |
// Put in the new values to get a computed value out | |
style.minWidth = style.maxWidth = style.width = ret; | |
ret = computed.width; | |
// Revert the changed values | |
style.width = width; | |
style.minWidth = minWidth; | |
style.maxWidth = maxWidth; | |
} | |
} | |
return ret !== undefined ? | |
// Support: IE | |
// IE returns zIndex value as an integer. | |
ret + "" : | |
ret; | |
} | |
function addGetHookIf( conditionFn, hookFn ) { | |
// Define the hook, we'll check on the first run if it's really needed. | |
return { | |
get: function() { | |
if ( conditionFn() ) { | |
// Hook not needed (or it's not possible to use it due | |
// to missing dependency), remove it. | |
delete this.get; | |
return; | |
} | |
// Hook needed; redefine it so that the support test is not executed again. | |
return (this.get = hookFn).apply( this, arguments ); | |
} | |
}; | |
} | |
(function() { | |
var pixelPositionVal, boxSizingReliableVal, | |
docElem = document.documentElement, | |
container = document.createElement( "div" ), | |
div = document.createElement( "div" ); | |
if ( !div.style ) { | |
return; | |
} | |
// Support: IE9-11+ | |
// Style of cloned element affects source element cloned (#8908) | |
div.style.backgroundClip = "content-box"; | |
div.cloneNode( true ).style.backgroundClip = ""; | |
support.clearCloneStyle = div.style.backgroundClip === "content-box"; | |
container.style.cssText = "border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;" + | |
"position:absolute"; | |
container.appendChild( div ); | |
// Executing both pixelPosition & boxSizingReliable tests require only one layout | |
// so they're executed at the same time to save the second computation. | |
function computePixelPositionAndBoxSizingReliable() { | |
div.style.cssText = | |
// Support: Firefox<29, Android 2.3 | |
// Vendor-prefix box-sizing | |
"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;" + | |
"box-sizing:border-box;display:block;margin-top:1%;top:1%;" + | |
"border:1px;padding:1px;width:4px;position:absolute"; | |
div.innerHTML = ""; | |
docElem.appendChild( container ); | |
var divStyle = window.getComputedStyle( div, null ); | |
pixelPositionVal = divStyle.top !== "1%"; | |
boxSizingReliableVal = divStyle.width === "4px"; | |
docElem.removeChild( container ); | |
} | |
// Support: node.js jsdom | |
// Don't assume that getComputedStyle is a property of the global object | |
if ( window.getComputedStyle ) { | |
jQuery.extend( support, { | |
pixelPosition: function() { | |
// This test is executed only once but we still do memoizing | |
// since we can use the boxSizingReliable pre-computing. | |
// No need to check if the test was already performed, though. | |
computePixelPositionAndBoxSizingReliable(); | |
return pixelPositionVal; | |
}, | |
boxSizingReliable: function() { | |
if ( boxSizingReliableVal == null ) { | |
computePixelPositionAndBoxSizingReliable(); | |
} | |
return boxSizingReliableVal; | |
}, | |
reliableMarginRight: function() { | |
// Support: Android 2.3 | |
// Check if div with explicit width and no margin-right incorrectly | |
// gets computed margin-right based on width of container. (#3333) | |
// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right | |
// This support function is only executed once so no memoizing is needed. | |
var ret, | |
marginDiv = div.appendChild( document.createElement( "div" ) ); | |
// Reset CSS: box-sizing; display; margin; border; padding | |
marginDiv.style.cssText = div.style.cssText = | |
// Support: Firefox<29, Android 2.3 | |
// Vendor-prefix box-sizing | |
"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" + | |
"box-sizing:content-box;display:block;margin:0;border:0;padding:0"; | |
marginDiv.style.marginRight = marginDiv.style.width = "0"; | |
div.style.width = "1px"; | |
docElem.appendChild( container ); | |
ret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight ); | |
docElem.removeChild( container ); | |
div.removeChild( marginDiv ); | |
return ret; | |
} | |
}); | |
} | |
})(); | |
// A method for quickly swapping in/out CSS properties to get correct calculations. | |
jQuery.swap = function( elem, options, callback, args ) { | |
var ret, name, | |
old = {}; | |
// Remember the old values, and insert the new ones | |
for ( name in options ) { | |
old[ name ] = elem.style[ name ]; | |
elem.style[ name ] = options[ name ]; | |
} | |
ret = callback.apply( elem, args || [] ); | |
// Revert the old values | |
for ( name in options ) { | |
elem.style[ name ] = old[ name ]; | |
} | |
return ret; | |
}; | |
var | |
// Swappable if display is none or starts with table except "table", "table-cell", or "table-caption" | |
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display | |
rdisplayswap = /^(none|table(?!-c[ea]).+)/, | |
rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ), | |
rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ), | |
cssShow = { position: "absolute", visibility: "hidden", display: "block" }, | |
cssNormalTransform = { | |
letterSpacing: "0", | |
fontWeight: "400" | |
}, | |
cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; | |
// Return a css property mapped to a potentially vendor prefixed property | |
function vendorPropName( style, name ) { | |
// Shortcut for names that are not vendor prefixed | |
if ( name in style ) { | |
return name; | |
} | |
// Check for vendor prefixed names | |
var capName = name[0].toUpperCase() + name.slice(1), | |
origName = name, | |
i = cssPrefixes.length; | |
while ( i-- ) { | |
name = cssPrefixes[ i ] + capName; | |
if ( name in style ) { | |
return name; | |
} | |
} | |
return origName; | |
} | |
function setPositiveNumber( elem, value, subtract ) { | |
var matches = rnumsplit.exec( value ); | |
return matches ? | |
// Guard against undefined "subtract", e.g., when used as in cssHooks | |
Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : | |
value; | |
} | |
function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { | |
var i = extra === ( isBorderBox ? "border" : "content" ) ? | |
// If we already have the right measurement, avoid augmentation | |
4 : | |
// Otherwise initialize for horizontal or vertical properties | |
name === "width" ? 1 : 0, | |
val = 0; | |
for ( ; i < 4; i += 2 ) { | |
// Both box models exclude margin, so add it if we want it | |
if ( extra === "margin" ) { | |
val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); | |
} | |
if ( isBorderBox ) { | |
// border-box includes padding, so remove it if we want content | |
if ( extra === "content" ) { | |
val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); | |
} | |
// At this point, extra isn't border nor margin, so remove border | |
if ( extra !== "margin" ) { | |
val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); | |
} | |
} else { | |
// At this point, extra isn't content, so add padding | |
val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); | |
// At this point, extra isn't content nor padding, so add border | |
if ( extra !== "padding" ) { | |
val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); | |
} | |
} | |
} | |
return val; | |
} | |
function getWidthOrHeight( elem, name, extra ) { | |
// Start with offset property, which is equivalent to the border-box value | |
var valueIsBorderBox = true, | |
val = name === "width" ? elem.offsetWidth : elem.offsetHeight, | |
styles = getStyles( elem ), | |
isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; | |
// Some non-html elements return undefined for offsetWidth, so check for null/undefined | |
// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 | |
// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 | |
if ( val <= 0 || val == null ) { | |
// Fall back to computed then uncomputed css if necessary | |
val = curCSS( elem, name, styles ); | |
if ( val < 0 || val == null ) { | |
val = elem.style[ name ]; | |
} | |
// Computed unit is not pixels. Stop here and return. | |
if ( rnumnonpx.test(val) ) { | |
return val; | |
} | |
// Check for style in case a browser which returns unreliable values | |
// for getComputedStyle silently falls back to the reliable elem.style | |
valueIsBorderBox = isBorderBox && | |
( support.boxSizingReliable() || val === elem.style[ name ] ); | |
// Normalize "", auto, and prepare for extra | |
val = parseFloat( val ) || 0; | |
} | |
// Use the active box-sizing model to add/subtract irrelevant styles | |
return ( val + | |
augmentWidthOrHeight( | |
elem, | |
name, | |
extra || ( isBorderBox ? "border" : "content" ), | |
valueIsBorderBox, | |
styles | |
) | |
) + "px"; | |
} | |
function showHide( elements, show ) { | |
var display, elem, hidden, | |
values = [], | |
index = 0, | |
length = elements.length; | |
for ( ; index < length; index++ ) { | |
elem = elements[ index ]; | |
if ( !elem.style ) { | |
continue; | |
} | |
values[ index ] = data_priv.get( elem, "olddisplay" ); | |
display = elem.style.display; | |
if ( show ) { | |
// Reset the inline display of this element to learn if it is | |
// being hidden by cascaded rules or not | |
if ( !values[ index ] && display === "none" ) { | |
elem.style.display = ""; | |
} | |
// Set elements which have been overridden with display: none | |
// in a stylesheet to whatever the default browser style is | |
// for such an element | |
if ( elem.style.display === "" && isHidden( elem ) ) { | |
values[ index ] = data_priv.access( elem, "olddisplay", defaultDisplay(elem.nodeName) ); | |
} | |
} else { | |
hidden = isHidden( elem ); | |
if ( display !== "none" || !hidden ) { | |
data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); | |
} | |
} | |
} | |
// Set the display of most of the elements in a second loop | |
// to avoid the constant reflow | |
for ( index = 0; index < length; index++ ) { | |
elem = elements[ index ]; | |
if ( !elem.style ) { | |
continue; | |
} | |
if ( !show || elem.style.display === "none" || elem.style.display === "" ) { | |
elem.style.display = show ? values[ index ] || "" : "none"; | |
} | |
} | |
return elements; | |
} | |
jQuery.extend({ | |
// Add in style property hooks for overriding the default | |
// behavior of getting and setting a style property | |
cssHooks: { | |
opacity: { | |
get: function( elem, computed ) { | |
if ( computed ) { | |
// We should always get a number back from opacity | |
var ret = curCSS( elem, "opacity" ); | |
return ret === "" ? "1" : ret; | |
} | |
} | |
} | |
}, | |
// Don't automatically add "px" to these possibly-unitless properties | |
cssNumber: { | |
"columnCount": true, | |
"fillOpacity": true, | |
"flexGrow": true, | |
"flexShrink": true, | |
"fontWeight": true, | |
"lineHeight": true, | |
"opacity": true, | |
"order": true, | |
"orphans": true, | |
"widows": true, | |
"zIndex": true, | |
"zoom": true | |
}, | |
// Add in properties whose names you wish to fix before | |
// setting or getting the value | |
cssProps: { | |
"float": "cssFloat" | |
}, | |
// Get and set the style property on a DOM Node | |
style: function( elem, name, value, extra ) { | |
// Don't set styles on text and comment nodes | |
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { | |
return; | |
} | |
// Make sure that we're working with the right name | |
var ret, type, hooks, | |
origName = jQuery.camelCase( name ), | |
style = elem.style; | |
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); | |
// Gets hook for the prefixed version, then unprefixed version | |
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; | |
// Check if we're setting a value | |
if ( value !== undefined ) { | |
type = typeof value; | |
// Convert "+=" or "-=" to relative numbers (#7345) | |
if ( type === "string" && (ret = rrelNum.exec( value )) ) { | |
value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); | |
// Fixes bug #9237 | |
type = "number"; | |
} | |
// Make sure that null and NaN values aren't set (#7116) | |
if ( value == null || value !== value ) { | |
return; | |
} | |
// If a number, add 'px' to the (except for certain CSS properties) | |
if ( type === "number" && !jQuery.cssNumber[ origName ] ) { | |
value += "px"; | |
} | |
// Support: IE9-11+ | |
// background-* props affect original clone's values | |
if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { | |
style[ name ] = "inherit"; | |
} | |
// If a hook was provided, use that value, otherwise just set the specified value | |
if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { | |
style[ name ] = value; | |
} | |
} else { | |
// If a hook was provided get the non-computed value from there | |
if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { | |
return ret; | |
} | |
// Otherwise just get the value from the style object | |
return style[ name ]; | |
} | |
}, | |
css: function( elem, name, extra, styles ) { | |
var val, num, hooks, | |
origName = jQuery.camelCase( name ); | |
// Make sure that we're working with the right name | |
name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); | |
// Try prefixed name followed by the unprefixed name | |
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; | |
// If a hook was provided get the computed value from there | |
if ( hooks && "get" in hooks ) { | |
val = hooks.get( elem, true, extra ); | |
} | |
// Otherwise, if a way to get the computed value exists, use that | |
if ( val === undefined ) { | |
val = curCSS( elem, name, styles ); | |
} | |
// Convert "normal" to computed value | |
if ( val === "normal" && name in cssNormalTransform ) { | |
val = cssNormalTransform[ name ]; | |
} | |
// Make numeric if forced or a qualifier was provided and val looks numeric | |
if ( extra === "" || extra ) { | |
num = parseFloat( val ); | |
return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; | |
} | |
return val; | |
} | |
}); | |
jQuery.each([ "height", "width" ], function( i, name ) { | |
jQuery.cssHooks[ name ] = { | |
get: function( elem, computed, extra ) { | |
if ( computed ) { | |
// Certain elements can have dimension info if we invisibly show them | |
// but it must have a current display style that would benefit | |
return rdisplayswap.test( jQuery.css( elem, "display" ) ) && elem.offsetWidth === 0 ? | |
jQuery.swap( elem, cssShow, function() { | |
return getWidthOrHeight( elem, name, extra ); | |
}) : | |
getWidthOrHeight( elem, name, extra ); | |
} | |
}, | |
set: function( elem, value, extra ) { | |
var styles = extra && getStyles( elem ); | |
return setPositiveNumber( elem, value, extra ? | |
augmentWidthOrHeight( | |
elem, | |
name, | |
extra, | |
jQuery.css( elem, "boxSizing", false, styles ) === "border-box", | |
styles | |
) : 0 | |
); | |
} | |
}; | |
}); | |
// Support: Android 2.3 | |
jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight, | |
function( elem, computed ) { | |
if ( computed ) { | |
return jQuery.swap( elem, { "display": "inline-block" }, | |
curCSS, [ elem, "marginRight" ] ); | |
} | |
} | |
); | |
// These hooks are used by animate to expand properties | |
jQuery.each({ | |
margin: "", | |
padding: "", | |
border: "Width" | |
}, function( prefix, suffix ) { | |
jQuery.cssHooks[ prefix + suffix ] = { | |
expand: function( value ) { | |
var i = 0, | |
expanded = {}, | |
// Assumes a single number if not a string | |
parts = typeof value === "string" ? value.split(" ") : [ value ]; | |
for ( ; i < 4; i++ ) { | |
expanded[ prefix + cssExpand[ i ] + suffix ] = | |
parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; | |
} | |
return expanded; | |
} | |
}; | |
if ( !rmargin.test( prefix ) ) { | |
jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; | |
} | |
}); | |
jQuery.fn.extend({ | |
css: function( name, value ) { | |
return access( this, function( elem, name, value ) { | |
var styles, len, | |
map = {}, | |
i = 0; | |
if ( jQuery.isArray( name ) ) { | |
styles = getStyles( elem ); | |
len = name.length; | |
for ( ; i < len; i++ ) { | |
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); | |
} | |
return map; | |
} | |
return value !== undefined ? | |
jQuery.style( elem, name, value ) : | |
jQuery.css( elem, name ); | |
}, name, value, arguments.length > 1 ); | |
}, | |
show: function() { | |
return showHide( this, true ); | |
}, | |
hide: function() { | |
return showHide( this ); | |
}, | |
toggle: function( state ) { | |
if ( typeof state === "boolean" ) { | |
return state ? this.show() : this.hide(); | |
} | |
return this.each(function() { | |
if ( isHidden( this ) ) { | |
jQuery( this ).show(); | |
} else { | |
jQuery( this ).hide(); | |
} | |
}); | |
} | |
}); | |
function Tween( elem, options, prop, end, easing ) { | |
return new Tween.prototype.init( elem, options, prop, end, easing ); | |
} | |
jQuery.Tween = Tween; | |
Tween.prototype = { | |
constructor: Tween, | |
init: function( elem, options, prop, end, easing, unit ) { | |
this.elem = elem; | |
this.prop = prop; | |
this.easing = easing || "swing"; | |
this.options = options; | |
this.start = this.now = this.cur(); | |
this.end = end; | |
this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); | |
}, | |
cur: function() { | |
var hooks = Tween.propHooks[ this.prop ]; | |
return hooks && hooks.get ? | |
hooks.get( this ) : | |
Tween.propHooks._default.get( this ); | |
}, | |
run: function( percent ) { | |
var eased, | |
hooks = Tween.propHooks[ this.prop ]; | |
if ( this.options.duration ) { | |
this.pos = eased = jQuery.easing[ this.easing ]( | |
percent, this.options.duration * percent, 0, 1, this.options.duration | |
); | |
} else { | |
this.pos = eased = percent; | |
} | |
this.now = ( this.end - this.start ) * eased + this.start; | |
if ( this.options.step ) { | |
this.options.step.call( this.elem, this.now, this ); | |
} | |
if ( hooks && hooks.set ) { | |
hooks.set( this ); | |
} else { | |
Tween.propHooks._default.set( this ); | |
} | |
return this; | |
} | |
}; | |
Tween.prototype.init.prototype = Tween.prototype; | |
Tween.propHooks = { | |
_default: { | |
get: function( tween ) { | |
var result; | |
if ( tween.elem[ tween.prop ] != null && | |
(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) { | |
return tween.elem[ tween.prop ]; | |
} | |
// Passing an empty string as a 3rd parameter to .css will automatically | |
// attempt a parseFloat and fallback to a string if the parse fails. | |
// Simple values such as "10px" are parsed to Float; | |
// complex values such as "rotate(1rad)" are returned as-is. | |
result = jQuery.css( tween.elem, tween.prop, "" ); | |
// Empty strings, null, undefined and "auto" are converted to 0. | |
return !result || result === "auto" ? 0 : result; | |
}, | |
set: function( tween ) { | |
// Use step hook for back compat. | |
// Use cssHook if its there. | |
// Use .style if available and use plain properties where available. | |
if ( jQuery.fx.step[ tween.prop ] ) { | |
jQuery.fx.step[ tween.prop ]( tween ); | |
} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { | |
jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); | |
} else { | |
tween.elem[ tween.prop ] = tween.now; | |
} | |
} | |
} | |
}; | |
// Support: IE9 | |
// Panic based approach to setting things on disconnected nodes | |
Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { | |
set: function( tween ) { | |
if ( tween.elem.nodeType && tween.elem.parentNode ) { | |
tween.elem[ tween.prop ] = tween.now; | |
} | |
} | |
}; | |
jQuery.easing = { | |
linear: function( p ) { | |
return p; | |
}, | |
swing: function( p ) { | |
return 0.5 - Math.cos( p * Math.PI ) / 2; | |
} | |
}; | |
jQuery.fx = Tween.prototype.init; | |
// Back Compat <1.8 extension point | |
jQuery.fx.step = {}; | |
var | |
fxNow, timerId, | |
rfxtypes = /^(?:toggle|show|hide)$/, | |
rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ), | |
rrun = /queueHooks$/, | |
animationPrefilters = [ defaultPrefilter ], | |
tweeners = { | |
"*": [ function( prop, value ) { | |
var tween = this.createTween( prop, value ), | |
target = tween.cur(), | |
parts = rfxnum.exec( value ), | |
unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), | |
// Starting value computation is required for potential unit mismatches | |
start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) && | |
rfxnum.exec( jQuery.css( tween.elem, prop ) ), | |
scale = 1, | |
maxIterations = 20; | |
if ( start && start[ 3 ] !== unit ) { | |
// Trust units reported by jQuery.css | |
unit = unit || start[ 3 ]; | |
// Make sure we update the tween properties later on | |
parts = parts || []; | |
// Iteratively approximate from a nonzero starting point | |
start = +target || 1; | |
do { | |
// If previous iteration zeroed out, double until we get *something*. | |
// Use string for doubling so we don't accidentally see scale as unchanged below | |
scale = scale || ".5"; | |
// Adjust and apply | |
start = start / scale; | |
jQuery.style( tween.elem, prop, start + unit ); | |
// Update scale, tolerating zero or NaN from tween.cur(), | |
// break the loop if scale is unchanged or perfect, or if we've just had enough | |
} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations ); | |
} | |
// Update tween properties | |
if ( parts ) { | |
start = tween.start = +start || +target || 0; | |
tween.unit = unit; | |
// If a +=/-= token was provided, we're doing a relative animation | |
tween.end = parts[ 1 ] ? | |
start + ( parts[ 1 ] + 1 ) * parts[ 2 ] : | |
+parts[ 2 ]; | |
} | |
return tween; | |
} ] | |
}; | |
// Animations created synchronously will run synchronously | |
function createFxNow() { | |
setTimeout(function() { | |
fxNow = undefined; | |
}); | |
return ( fxNow = jQuery.now() ); | |
} | |
// Generate parameters to create a standard animation | |
function genFx( type, includeWidth ) { | |
var which, | |
i = 0, | |
attrs = { height: type }; | |
// If we include width, step value is 1 to do all cssExpand values, | |
// otherwise step value is 2 to skip over Left and Right | |
includeWidth = includeWidth ? 1 : 0; | |
for ( ; i < 4 ; i += 2 - includeWidth ) { | |
which = cssExpand[ i ]; | |
attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; | |
} | |
if ( includeWidth ) { | |
attrs.opacity = attrs.width = type; | |
} | |
return attrs; | |
} | |
function createTween( value, prop, animation ) { | |
var tween, | |
collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ), | |
index = 0, | |
length = collection.length; | |
for ( ; index < length; index++ ) { | |
if ( (tween = collection[ index ].call( animation, prop, value )) ) { | |
// We're done with this property | |
return tween; | |
} | |
} | |
} | |
function defaultPrefilter( elem, props, opts ) { | |
/* jshint validthis: true */ | |
var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay, | |
anim = this, | |
orig = {}, | |
style = elem.style, | |
hidden = elem.nodeType && isHidden( elem ), | |
dataShow = data_priv.get( elem, "fxshow" ); | |
// Handle queue: false promises | |
if ( !opts.queue ) { | |
hooks = jQuery._queueHooks( elem, "fx" ); | |
if ( hooks.unqueued == null ) { | |
hooks.unqueued = 0; | |
oldfire = hooks.empty.fire; | |
hooks.empty.fire = function() { | |
if ( !hooks.unqueued ) { | |
oldfire(); | |
} | |
}; | |
} | |
hooks.unqueued++; | |
anim.always(function() { | |
// Ensure the complete handler is called before this completes | |
anim.always(function() { | |
hooks.unqueued--; | |
if ( !jQuery.queue( elem, "fx" ).length ) { | |
hooks.empty.fire(); | |
} | |
}); | |
}); | |
} | |
// Height/width overflow pass | |
if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) { | |
// Make sure that nothing sneaks out | |
// Record all 3 overflow attributes because IE9-10 do not | |
// change the overflow attribute when overflowX and | |
// overflowY are set to the same value | |
opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; | |
// Set display property to inline-block for height/width | |
// animations on inline elements that are having width/height animated | |
display = jQuery.css( elem, "display" ); | |
// Test default display if display is currently "none" | |
checkDisplay = display === "none" ? | |
data_priv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display; | |
if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) { | |
style.display = "inline-block"; | |
} | |
} | |
if ( opts.overflow ) { | |
style.overflow = "hidden"; | |
anim.always(function() { | |
style.overflow = opts.overflow[ 0 ]; | |
style.overflowX = opts.overflow[ 1 ]; | |
style.overflowY = opts.overflow[ 2 ]; | |
}); | |
} | |
// show/hide pass | |
for ( prop in props ) { | |
value = props[ prop ]; | |
if ( rfxtypes.exec( value ) ) { | |
delete props[ prop ]; | |
toggle = toggle || value === "toggle"; | |
if ( value === ( hidden ? "hide" : "show" ) ) { | |
// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden | |
if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { | |
hidden = true; | |
} else { | |
continue; | |
} | |
} | |
orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); | |
// Any non-fx value stops us from restoring the original display value | |
} else { | |
display = undefined; | |
} | |
} | |
if ( !jQuery.isEmptyObject( orig ) ) { | |
if ( dataShow ) { | |
if ( "hidden" in dataShow ) { | |
hidden = dataShow.hidden; | |
} | |
} else { | |
dataShow = data_priv.access( elem, "fxshow", {} ); | |
} | |
// Store state if its toggle - enables .stop().toggle() to "reverse" | |
if ( toggle ) { | |
dataShow.hidden = !hidden; | |
} | |
if ( hidden ) { | |
jQuery( elem ).show(); | |
} else { | |
anim.done(function() { | |
jQuery( elem ).hide(); | |
}); | |
} | |
anim.done(function() { | |
var prop; | |
data_priv.remove( elem, "fxshow" ); | |
for ( prop in orig ) { | |
jQuery.style( elem, prop, orig[ prop ] ); | |
} | |
}); | |
for ( prop in orig ) { | |
tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); | |
if ( !( prop in dataShow ) ) { | |
dataShow[ prop ] = tween.start; | |
if ( hidden ) { | |
tween.end = tween.start; | |
tween.start = prop === "width" || prop === "height" ? 1 : 0; | |
} | |
} | |
} | |
// If this is a noop like .hide().hide(), restore an overwritten display value | |
} else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) { | |
style.display = display; | |
} | |
} | |
function propFilter( props, specialEasing ) { | |
var index, name, easing, value, hooks; | |
// camelCase, specialEasing and expand cssHook pass | |
for ( index in props ) { | |
name = jQuery.camelCase( index ); | |
easing = specialEasing[ name ]; | |
value = props[ index ]; | |
if ( jQuery.isArray( value ) ) { | |
easing = value[ 1 ]; | |
value = props[ index ] = value[ 0 ]; | |
} | |
if ( index !== name ) { | |
props[ name ] = value; | |
delete props[ index ]; | |
} | |
hooks = jQuery.cssHooks[ name ]; | |
if ( hooks && "expand" in hooks ) { | |
value = hooks.expand( value ); | |
delete props[ name ]; | |
// Not quite $.extend, this won't overwrite existing keys. | |
// Reusing 'index' because we have the correct "name" | |
for ( index in value ) { | |
if ( !( index in props ) ) { | |
props[ index ] = value[ index ]; | |
specialEasing[ index ] = easing; | |
} | |
} | |
} else { | |
specialEasing[ name ] = easing; | |
} | |
} | |
} | |
function Animation( elem, properties, options ) { | |
var result, | |
stopped, | |
index = 0, | |
length = animationPrefilters.length, | |
deferred = jQuery.Deferred().always( function() { | |
// Don't match elem in the :animated selector | |
delete tick.elem; | |
}), | |
tick = function() { | |
if ( stopped ) { | |
return false; | |
} | |
var currentTime = fxNow || createFxNow(), | |
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), | |
// Support: Android 2.3 | |
// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) | |
temp = remaining / animation.duration || 0, | |
percent = 1 - temp, | |
index = 0, | |
length = animation.tweens.length; | |
for ( ; index < length ; index++ ) { | |
animation.tweens[ index ].run( percent ); | |
} | |
deferred.notifyWith( elem, [ animation, percent, remaining ]); | |
if ( percent < 1 && length ) { | |
return remaining; | |
} else { | |
deferred.resolveWith( elem, [ animation ] ); | |
return false; | |
} | |
}, | |
animation = deferred.promise({ | |
elem: elem, | |
props: jQuery.extend( {}, properties ), | |
opts: jQuery.extend( true, { specialEasing: {} }, options ), | |
originalProperties: properties, | |
originalOptions: options, | |
startTime: fxNow || createFxNow(), | |
duration: options.duration, | |
tweens: [], | |
createTween: function( prop, end ) { | |
var tween = jQuery.Tween( elem, animation.opts, prop, end, | |
animation.opts.specialEasing[ prop ] || animation.opts.easing ); | |
animation.tweens.push( tween ); | |
return tween; | |
}, | |
stop: function( gotoEnd ) { | |
var index = 0, | |
// If we are going to the end, we want to run all the tweens | |
// otherwise we skip this part | |
length = gotoEnd ? animation.tweens.length : 0; | |
if ( stopped ) { | |
return this; | |
} | |
stopped = true; | |
for ( ; index < length ; index++ ) { | |
animation.tweens[ index ].run( 1 ); | |
} | |
// Resolve when we played the last frame; otherwise, reject | |
if ( gotoEnd ) { | |
deferred.resolveWith( elem, [ animation, gotoEnd ] ); | |
} else { | |
deferred.rejectWith( elem, [ animation, gotoEnd ] ); | |
} | |
return this; | |
} | |
}), | |
props = animation.props; | |
propFilter( props, animation.opts.specialEasing ); | |
for ( ; index < length ; index++ ) { | |
result = animationPrefilters[ index ].call( animation, elem, props, animation.opts ); | |
if ( result ) { | |
return result; | |
} | |
} | |
jQuery.map( props, createTween, animation ); | |
if ( jQuery.isFunction( animation.opts.start ) ) { | |
animation.opts.start.call( elem, animation ); | |
} | |
jQuery.fx.timer( | |
jQuery.extend( tick, { | |
elem: elem, | |
anim: animation, | |
queue: animation.opts.queue | |
}) | |
); | |
// attach callbacks from options | |
return animation.progress( animation.opts.progress ) | |
.done( animation.opts.done, animation.opts.complete ) | |
.fail( animation.opts.fail ) | |
.always( animation.opts.always ); | |
} | |
jQuery.Animation = jQuery.extend( Animation, { | |
tweener: function( props, callback ) { | |
if ( jQuery.isFunction( props ) ) { | |
callback = props; | |
props = [ "*" ]; | |
} else { | |
props = props.split(" "); | |
} | |
var prop, | |
index = 0, | |
length = props.length; | |
for ( ; index < length ; index++ ) { | |
prop = props[ index ]; | |
tweeners[ prop ] = tweeners[ prop ] || []; | |
tweeners[ prop ].unshift( callback ); | |
} | |
}, | |
prefilter: function( callback, prepend ) { | |
if ( prepend ) { | |
animationPrefilters.unshift( callback ); | |
} else { | |
animationPrefilters.push( callback ); | |
} | |
} | |
}); | |
jQuery.speed = function( speed, easing, fn ) { | |
var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { | |
complete: fn || !fn && easing || | |
jQuery.isFunction( speed ) && speed, | |
duration: speed, | |
easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing | |
}; | |
opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : | |
opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; | |
// Normalize opt.queue - true/undefined/null -> "fx" | |
if ( opt.queue == null || opt.queue === true ) { | |
opt.queue = "fx"; | |
} | |
// Queueing | |
opt.old = opt.complete; | |
opt.complete = function() { | |
if ( jQuery.isFunction( opt.old ) ) { | |
opt.old.call( this ); | |
} | |
if ( opt.queue ) { | |
jQuery.dequeue( this, opt.queue ); | |
} | |
}; | |
return opt; | |
}; | |
jQuery.fn.extend({ | |
fadeTo: function( speed, to, easing, callback ) { | |
// Show any hidden elements after setting opacity to 0 | |
return this.filter( isHidden ).css( "opacity", 0 ).show() | |
// Animate to the value specified | |
.end().animate({ opacity: to }, speed, easing, callback ); | |
}, | |
animate: function( prop, speed, easing, callback ) { | |
var empty = jQuery.isEmptyObject( prop ), | |
optall = jQuery.speed( speed, easing, callback ), | |
doAnimation = function() { | |
// Operate on a copy of prop so per-property easing won't be lost | |
var anim = Animation( this, jQuery.extend( {}, prop ), optall ); | |
// Empty animations, or finishing resolves immediately | |
if ( empty || data_priv.get( this, "finish" ) ) { | |
anim.stop( true ); | |
} | |
}; | |
doAnimation.finish = doAnimation; | |
return empty || optall.queue === false ? | |
this.each( doAnimation ) : | |
this.queue( optall.queue, doAnimation ); | |
}, | |
stop: function( type, clearQueue, gotoEnd ) { | |
var stopQueue = function( hooks ) { | |
var stop = hooks.stop; | |
delete hooks.stop; | |
stop( gotoEnd ); | |
}; | |
if ( typeof type !== "string" ) { | |
gotoEnd = clearQueue; | |
clearQueue = type; | |
type = undefined; | |
} | |
if ( clearQueue && type !== false ) { | |
this.queue( type || "fx", [] ); | |
} | |
return this.each(function() { | |
var dequeue = true, | |
index = type != null && type + "queueHooks", | |
timers = jQuery.timers, | |
data = data_priv.get( this ); | |
if ( index ) { | |
if ( data[ index ] && data[ index ].stop ) { | |
stopQueue( data[ index ] ); | |
} | |
} else { | |
for ( index in data ) { | |
if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { | |
stopQueue( data[ index ] ); | |
} | |
} | |
} | |
for ( index = timers.length; index--; ) { | |
if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { | |
timers[ index ].anim.stop( gotoEnd ); | |
dequeue = false; | |
timers.splice( index, 1 ); | |
} | |
} | |
// Start the next in the queue if the last step wasn't forced. | |
// Timers currently will call their complete callbacks, which | |
// will dequeue but only if they were gotoEnd. | |
if ( dequeue || !gotoEnd ) { | |
jQuery.dequeue( this, type ); | |
} | |
}); | |
}, | |
finish: function( type ) { | |
if ( type !== false ) { | |
type = type || "fx"; | |
} | |
return this.each(function() { | |
var index, | |
data = data_priv.get( this ), | |
queue = data[ type + "queue" ], | |
hooks = data[ type + "queueHooks" ], | |
timers = jQuery.timers, | |
length = queue ? queue.length : 0; | |
// Enable finishing flag on private data | |
data.finish = true; | |
// Empty the queue first | |
jQuery.queue( this, type, [] ); | |
if ( hooks && hooks.stop ) { | |
hooks.stop.call( this, true ); | |
} | |
// Look for any active animations, and finish them | |
for ( index = timers.length; index--; ) { | |
if ( timers[ index ].elem === this && timers[ index ].queue === type ) { | |
timers[ index ].anim.stop( true ); | |
timers.splice( index, 1 ); | |
} | |
} | |
// Look for any animations in the old queue and finish them | |
for ( index = 0; index < length; index++ ) { | |
if ( queue[ index ] && queue[ index ].finish ) { | |
queue[ index ].finish.call( this ); | |
} | |
} | |
// Turn off finishing flag | |
delete data.finish; | |
}); | |
} | |
}); | |
jQuery.each([ "toggle", "show", "hide" ], function( i, name ) { | |
var cssFn = jQuery.fn[ name ]; | |
jQuery.fn[ name ] = function( speed, easing, callback ) { | |
return speed == null || typeof speed === "boolean" ? | |
cssFn.apply( this, arguments ) : | |
this.animate( genFx( name, true ), speed, easing, callback ); | |
}; | |
}); | |
// Generate shortcuts for custom animations | |
jQuery.each({ | |
slideDown: genFx("show"), | |
slideUp: genFx("hide"), | |
slideToggle: genFx("toggle"), | |
fadeIn: { opacity: "show" }, | |
fadeOut: { opacity: "hide" }, | |
fadeToggle: { opacity: "toggle" } | |
}, function( name, props ) { | |
jQuery.fn[ name ] = function( speed, easing, callback ) { | |
return this.animate( props, speed, easing, callback ); | |
}; | |
}); | |
jQuery.timers = []; | |
jQuery.fx.tick = function() { | |
var timer, | |
i = 0, | |
timers = jQuery.timers; | |
fxNow = jQuery.now(); | |
for ( ; i < timers.length; i++ ) { | |
timer = timers[ i ]; | |
// Checks the timer has not already been removed | |
if ( !timer() && timers[ i ] === timer ) { | |
timers.splice( i--, 1 ); | |
} | |
} | |
if ( !timers.length ) { | |
jQuery.fx.stop(); | |
} | |
fxNow = undefined; | |
}; | |
jQuery.fx.timer = function( timer ) { | |
jQuery.timers.push( timer ); | |
if ( timer() ) { | |
jQuery.fx.start(); | |
} else { | |
jQuery.timers.pop(); | |
} | |
}; | |
jQuery.fx.interval = 13; | |
jQuery.fx.start = function() { | |
if ( !timerId ) { | |
timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval ); | |
} | |
}; | |
jQuery.fx.stop = function() { | |
clearInterval( timerId ); | |
timerId = null; | |
}; | |
jQuery.fx.speeds = { | |
slow: 600, | |
fast: 200, | |
// Default speed | |
_default: 400 | |
}; | |
// Based off of the plugin by Clint Helfers, with permission. | |
// http://blindsignals.com/index.php/2009/07/jquery-delay/ | |
jQuery.fn.delay = function( time, type ) { | |
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; | |
type = type || "fx"; | |
return this.queue( type, function( next, hooks ) { | |
var timeout = setTimeout( next, time ); | |
hooks.stop = function() { | |
clearTimeout( timeout ); | |
}; | |
}); | |
}; | |
(function() { | |
var input = document.createElement( "input" ), | |
select = document.createElement( "select" ), | |
opt = select.appendChild( document.createElement( "option" ) ); | |
input.type = "checkbox"; | |
// Support: iOS<=5.1, Android<=4.2+ | |
// Default value for a checkbox should be "on" | |
support.checkOn = input.value !== ""; | |
// Support: IE<=11+ | |
// Must access selectedIndex to make default options select | |
support.optSelected = opt.selected; | |
// Support: Android<=2.3 | |
// Options inside disabled selects are incorrectly marked as disabled | |
select.disabled = true; | |
support.optDisabled = !opt.disabled; | |
// Support: IE<=11+ | |
// An input loses its value after becoming a radio | |
input = document.createElement( "input" ); | |
input.value = "t"; | |
input.type = "radio"; | |
support.radioValue = input.value === "t"; | |
})(); | |
var nodeHook, boolHook, | |
attrHandle = jQuery.expr.attrHandle; | |
jQuery.fn.extend({ | |
attr: function( name, value ) { | |
return access( this, jQuery.attr, name, value, arguments.length > 1 ); | |
}, | |
removeAttr: function( name ) { | |
return this.each(function() { | |
jQuery.removeAttr( this, name ); | |
}); | |
} | |
}); | |
jQuery.extend({ | |
attr: function( elem, name, value ) { | |
var hooks, ret, | |
nType = elem.nodeType; | |
// don't get/set attributes on text, comment and attribute nodes | |
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { | |
return; | |
} | |
// Fallback to prop when attributes are not supported | |
if ( typeof elem.getAttribute === strundefined ) { | |
return jQuery.prop( elem, name, value ); | |
} | |
// All attributes are lowercase | |
// Grab necessary hook if one is defined | |
if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { | |
name = name.toLowerCase(); | |
hooks = jQuery.attrHooks[ name ] || | |
( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook ); | |
} | |
if ( value !== undefined ) { | |
if ( value === null ) { | |
jQuery.removeAttr( elem, name ); | |
} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { | |
return ret; | |
} else { | |
elem.setAttribute( name, value + "" ); | |
return value; | |
} | |
} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { | |
return ret; | |
} else { | |
ret = jQuery.find.attr( elem, name ); | |
// Non-existent attributes return null, we normalize to undefined | |
return ret == null ? | |
undefined : | |
ret; | |
} | |
}, | |
removeAttr: function( elem, value ) { | |
var name, propName, | |
i = 0, | |
attrNames = value && value.match( rnotwhite ); | |
if ( attrNames && elem.nodeType === 1 ) { | |
while ( (name = attrNames[i++]) ) { | |
propName = jQuery.propFix[ name ] || name; | |
// Boolean attributes get special treatment (#10870) | |
if ( jQuery.expr.match.bool.test( name ) ) { | |
// Set corresponding property to false | |
elem[ propName ] = false; | |
} | |
elem.removeAttribute( name ); | |
} | |
} | |
}, | |
attrHooks: { | |
type: { | |
set: function( elem, value ) { | |
if ( !support.radioValue && value === "radio" && | |
jQuery.nodeName( elem, "input" ) ) { | |
var val = elem.value; | |
elem.setAttribute( "type", value ); | |
if ( val ) { | |
elem.value = val; | |
} | |
return value; | |
} | |
} | |
} | |
} | |
}); | |
// Hooks for boolean attributes | |
boolHook = { | |
set: function( elem, value, name ) { | |
if ( value === false ) { | |
// Remove boolean attributes when set to false | |
jQuery.removeAttr( elem, name ); | |
} else { | |
elem.setAttribute( name, name ); | |
} | |
return name; | |
} | |
}; | |
jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { | |
var getter = attrHandle[ name ] || jQuery.find.attr; | |
attrHandle[ name ] = function( elem, name, isXML ) { | |
var ret, handle; | |
if ( !isXML ) { | |
// Avoid an infinite loop by temporarily removing this function from the getter | |
handle = attrHandle[ name ]; | |
attrHandle[ name ] = ret; | |
ret = getter( elem, name, isXML ) != null ? | |
name.toLowerCase() : | |
null; | |
attrHandle[ name ] = handle; | |
} | |
return ret; | |
}; | |
}); | |
var rfocusable = /^(?:input|select|textarea|button)$/i; | |
jQuery.fn.extend({ | |
prop: function( name, value ) { | |
return access( this, jQuery.prop, name, value, arguments.length > 1 ); | |
}, | |
removeProp: function( name ) { | |
return this.each(function() { | |
delete this[ jQuery.propFix[ name ] || name ]; | |
}); | |
} | |
}); | |
jQuery.extend({ | |
propFix: { | |
"for": "htmlFor", | |
"class": "className" | |
}, | |
prop: function( elem, name, value ) { | |
var ret, hooks, notxml, | |
nType = elem.nodeType; | |
// Don't get/set properties on text, comment and attribute nodes | |
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { | |
return; | |
} | |
notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); | |
if ( notxml ) { | |
// Fix name and attach hooks | |
name = jQuery.propFix[ name ] || name; | |
hooks = jQuery.propHooks[ name ]; | |
} | |
if ( value !== undefined ) { | |
return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? | |
ret : | |
( elem[ name ] = value ); | |
} else { | |
return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? | |
ret : | |
elem[ name ]; | |
} | |
}, | |
propHooks: { | |
tabIndex: { | |
get: function( elem ) { | |
return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ? | |
elem.tabIndex : | |
-1; | |
} | |
} | |
} | |
}); | |
if ( !support.optSelected ) { | |
jQuery.propHooks.selected = { | |
get: function( elem ) { | |
var parent = elem.parentNode; | |
if ( parent && parent.parentNode ) { | |
parent.parentNode.selectedIndex; | |
} | |
return null; | |
} | |
}; | |
} | |
jQuery.each([ | |
"tabIndex", | |
"readOnly", | |
"maxLength", | |
"cellSpacing", | |
"cellPadding", | |
"rowSpan", | |
"colSpan", | |
"useMap", | |
"frameBorder", | |
"contentEditable" | |
], function() { | |
jQuery.propFix[ this.toLowerCase() ] = this; | |
}); | |
var rclass = /[\t\r\n\f]/g; | |
jQuery.fn.extend({ | |
addClass: function( value ) { | |
var classes, elem, cur, clazz, j, finalValue, | |
proceed = typeof value === "string" && value, | |
i = 0, | |
len = this.length; | |
if ( jQuery.isFunction( value ) ) { | |
return this.each(function( j ) { | |
jQuery( this ).addClass( value.call( this, j, this.className ) ); | |
}); | |
} | |
if ( proceed ) { | |
// The disjunction here is for better compressibility (see removeClass) | |
classes = ( value || "" ).match( rnotwhite ) || []; | |
for ( ; i < len; i++ ) { | |
elem = this[ i ]; | |
cur = elem.nodeType === 1 && ( elem.className ? | |
( " " + elem.className + " " ).replace( rclass, " " ) : | |
" " | |
); | |
if ( cur ) { | |
j = 0; | |
while ( (clazz = classes[j++]) ) { | |
if ( cur.indexOf( " " + clazz + " " ) < 0 ) { | |
cur += clazz + " "; | |
} | |
} | |
// only assign if different to avoid unneeded rendering. | |
finalValue = jQuery.trim( cur ); | |
if ( elem.className !== finalValue ) { | |
elem.className = finalValue; | |
} | |
} | |
} | |
} | |
return this; | |
}, | |
removeClass: function( value ) { | |
var classes, elem, cur, clazz, j, finalValue, | |
proceed = arguments.length === 0 || typeof value === "string" && value, | |
i = 0, | |
len = this.length; | |
if ( jQuery.isFunction( value ) ) { | |
return this.each(function( j ) { | |
jQuery( this ).removeClass( value.call( this, j, this.className ) ); | |
}); | |
} | |
if ( proceed ) { | |
classes = ( value || "" ).match( rnotwhite ) || []; | |
for ( ; i < len; i++ ) { | |
elem = this[ i ]; | |
// This expression is here for better compressibility (see addClass) | |
cur = elem.nodeType === 1 && ( elem.className ? | |
( " " + elem.className + " " ).replace( rclass, " " ) : | |
"" | |
); | |
if ( cur ) { | |
j = 0; | |
while ( (clazz = classes[j++]) ) { | |
// Remove *all* instances | |
while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { | |
cur = cur.replace( " " + clazz + " ", " " ); | |
} | |
} | |
// Only assign if different to avoid unneeded rendering. | |
finalValue = value ? jQuery.trim( cur ) : ""; | |
if ( elem.className !== finalValue ) { | |
elem.className = finalValue; | |
} | |
} | |
} | |
} | |
return this; | |
}, | |
toggleClass: function( value, stateVal ) { | |
var type = typeof value; | |
if ( typeof stateVal === "boolean" && type === "string" ) { | |
return stateVal ? this.addClass( value ) : this.removeClass( value ); | |
} | |
if ( jQuery.isFunction( value ) ) { | |
return this.each(function( i ) { | |
jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); | |
}); | |
} | |
return this.each(function() { | |
if ( type === "string" ) { | |
// Toggle individual class names | |
var className, | |
i = 0, | |
self = jQuery( this ), | |
classNames = value.match( rnotwhite ) || []; | |
while ( (className = classNames[ i++ ]) ) { | |
// Check each className given, space separated list | |
if ( self.hasClass( className ) ) { | |
self.removeClass( className ); | |
} else { | |
self.addClass( className ); | |
} | |
} | |
// Toggle whole class name | |
} else if ( type === strundefined || type === "boolean" ) { | |
if ( this.className ) { | |
// store className if set | |
data_priv.set( this, "__className__", this.className ); | |
} | |
// If the element has a class name or if we're passed `false`, | |
// then remove the whole classname (if there was one, the above saved it). | |
// Otherwise bring back whatever was previously saved (if anything), | |
// falling back to the empty string if nothing was stored. | |
this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || ""; | |
} | |
}); | |
}, | |
hasClass: function( selector ) { | |
var className = " " + selector + " ", | |
i = 0, | |
l = this.length; | |
for ( ; i < l; i++ ) { | |
if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { | |
return true; | |
} | |
} | |
return false; | |
} | |
}); | |
var rreturn = /\r/g; | |
jQuery.fn.extend({ | |
val: function( value ) { | |
var hooks, ret, isFunction, | |
elem = this[0]; | |
if ( !arguments.length ) { | |
if ( elem ) { | |
hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; | |
if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { | |
return ret; | |
} | |
ret = elem.value; | |
return typeof ret === "string" ? | |
// Handle most common string cases | |
ret.replace(rreturn, "") : | |
// Handle cases where value is null/undef or number | |
ret == null ? "" : ret; | |
} | |
return; | |
} | |
isFunction = jQuery.isFunction( value ); | |
return this.each(function( i ) { | |
var val; | |
if ( this.nodeType !== 1 ) { | |
return; | |
} | |
if ( isFunction ) { | |
val = value.call( this, i, jQuery( this ).val() ); | |
} else { | |
val = value; | |
} | |
// Treat null/undefined as ""; convert numbers to string | |
if ( val == null ) { | |
val = ""; | |
} else if ( typeof val === "number" ) { | |
val += ""; | |
} else if ( jQuery.isArray( val ) ) { | |
val = jQuery.map( val, function( value ) { | |
return value == null ? "" : value + ""; | |
}); | |
} | |
hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; | |
// If set returns undefined, fall back to normal setting | |
if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { | |
this.value = val; | |
} | |
}); | |
} | |
}); | |
jQuery.extend({ | |
valHooks: { | |
option: { | |
get: function( elem ) { | |
var val = jQuery.find.attr( elem, "value" ); | |
return val != null ? | |
val : | |
// Support: IE10-11+ | |
// option.text throws exceptions (#14686, #14858) | |
jQuery.trim( jQuery.text( elem ) ); | |
} | |
}, | |
select: { | |
get: function( elem ) { | |
var value, option, | |
options = elem.options, | |
index = elem.selectedIndex, | |
one = elem.type === "select-one" || index < 0, | |
values = one ? null : [], | |
max = one ? index + 1 : options.length, | |
i = index < 0 ? | |
max : | |
one ? index : 0; | |
// Loop through all the selected options | |
for ( ; i < max; i++ ) { | |
option = options[ i ]; | |
// IE6-9 doesn't update selected after form reset (#2551) | |
if ( ( option.selected || i === index ) && | |
// Don't return options that are disabled or in a disabled optgroup | |
( support.optDisabled ? !option.disabled : option.getAttribute( "disabled" ) === null ) && | |
( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { | |
// Get the specific value for the option | |
value = jQuery( option ).val(); | |
// We don't need an array for one selects | |
if ( one ) { | |
return value; | |
} | |
// Multi-Selects return an array | |
values.push( value ); | |
} | |
} | |
return values; | |
}, | |
set: function( elem, value ) { | |
var optionSet, option, | |
options = elem.options, | |
values = jQuery.makeArray( value ), | |
i = options.length; | |
while ( i-- ) { | |
option = options[ i ]; | |
if ( (option.selected = jQuery.inArray( option.value, values ) >= 0) ) { | |
optionSet = true; | |
} | |
} | |
// Force browsers to behave consistently when non-matching value is set | |
if ( !optionSet ) { | |
elem.selectedIndex = -1; | |
} | |
return values; | |
} | |
} | |
} | |
}); | |
// Radios and checkboxes getter/setter | |
jQuery.each([ "radio", "checkbox" ], function() { | |
jQuery.valHooks[ this ] = { | |
set: function( elem, value ) { | |
if ( jQuery.isArray( value ) ) { | |
return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); | |
} | |
} | |
}; | |
if ( !support.checkOn ) { | |
jQuery.valHooks[ this ].get = function( elem ) { | |
return elem.getAttribute("value") === null ? "on" : elem.value; | |
}; | |
} | |
}); | |
// Return jQuery for attributes-only inclusion | |
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + | |
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + | |
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { | |
// Handle event binding | |
jQuery.fn[ name ] = function( data, fn ) { | |
return arguments.length > 0 ? | |
this.on( name, null, data, fn ) : | |
this.trigger( name ); | |
}; | |
}); | |
jQuery.fn.extend({ | |
hover: function( fnOver, fnOut ) { | |
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); | |
}, | |
bind: function( types, data, fn ) { | |
return this.on( types, null, data, fn ); | |
}, | |
unbind: function( types, fn ) { | |
return this.off( types, null, fn ); | |
}, | |
delegate: function( selector, types, data, fn ) { | |
return this.on( types, selector, data, fn ); | |
}, | |
undelegate: function( selector, types, fn ) { | |
// ( namespace ) or ( selector, types [, fn] ) | |
return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); | |
} | |
}); | |
var nonce = jQuery.now(); | |
var rquery = (/\?/); | |
// Support: Android 2.3 | |
// Workaround failure to string-cast null input | |
jQuery.parseJSON = function( data ) { | |
return JSON.parse( data + "" ); | |
}; | |
// Cross-browser xml parsing | |
jQuery.parseXML = function( data ) { | |
var xml, tmp; | |
if ( !data || typeof data !== "string" ) { | |
return null; | |
} | |
// Support: IE9 | |
try { | |
tmp = new DOMParser(); | |
xml = tmp.parseFromString( data, "text/xml" ); | |
} catch ( e ) { | |
xml = undefined; | |
} | |
if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { | |
jQuery.error( "Invalid XML: " + data ); | |
} | |
return xml; | |
}; | |
var | |
rhash = /#.*$/, | |
rts = /([?&])_=[^&]*/, | |
rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, | |
// #7653, #8125, #8152: local protocol detection | |
rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, | |
rnoContent = /^(?:GET|HEAD)$/, | |
rprotocol = /^\/\//, | |
rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/, | |
/* Prefilters | |
* 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) | |
* 2) These are called: | |
* - BEFORE asking for a transport | |
* - AFTER param serialization (s.data is a string if s.processData is true) | |
* 3) key is the dataType | |
* 4) the catchall symbol "*" can be used | |
* 5) execution will start with transport dataType and THEN continue down to "*" if needed | |
*/ | |
prefilters = {}, | |
/* Transports bindings | |
* 1) key is the dataType | |
* 2) the catchall symbol "*" can be used | |
* 3) selection will start with transport dataType and THEN go to "*" if needed | |
*/ | |
transports = {}, | |
// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression | |
allTypes = "*/".concat( "*" ), | |
// Document location | |
ajaxLocation = window.location.href, | |
// Segment location into parts | |
ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; | |
// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport | |
function addToPrefiltersOrTransports( structure ) { | |
// dataTypeExpression is optional and defaults to "*" | |
return function( dataTypeExpression, func ) { | |
if ( typeof dataTypeExpression !== "string" ) { | |
func = dataTypeExpression; | |
dataTypeExpression = "*"; | |
} | |
var dataType, | |
i = 0, | |
dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || []; | |
if ( jQuery.isFunction( func ) ) { | |
// For each dataType in the dataTypeExpression | |
while ( (dataType = dataTypes[i++]) ) { | |
// Prepend if requested | |
if ( dataType[0] === "+" ) { | |
dataType = dataType.slice( 1 ) || "*"; | |
(structure[ dataType ] = structure[ dataType ] || []).unshift( func ); | |
// Otherwise append | |
} else { | |
(structure[ dataType ] = structure[ dataType ] || []).push( func ); | |
} | |
} | |
} | |
}; | |
} | |
// Base inspection function for prefilters and transports | |
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { | |
var inspected = {}, | |
seekingTransport = ( structure === transports ); | |
function inspect( dataType ) { | |
var selected; | |
inspected[ dataType ] = true; | |
jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { | |
var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); | |
if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) { | |
options.dataTypes.unshift( dataTypeOrTransport ); | |
inspect( dataTypeOrTransport ); | |
return false; | |
} else if ( seekingTransport ) { | |
return !( selected = dataTypeOrTransport ); | |
} | |
}); | |
return selected; | |
} | |
return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); | |
} | |
// A special extend for ajax options | |
// that takes "flat" options (not to be deep extended) | |
// Fixes #9887 | |
function ajaxExtend( target, src ) { | |
var key, deep, | |
flatOptions = jQuery.ajaxSettings.flatOptions || {}; | |
for ( key in src ) { | |
if ( src[ key ] !== undefined ) { | |
( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ]; | |
} | |
} | |
if ( deep ) { | |
jQuery.extend( true, target, deep ); | |
} | |
return target; | |
} | |
/* Handles responses to an ajax request: | |
* - finds the right dataType (mediates between content-type and expected dataType) | |
* - returns the corresponding response | |
*/ | |
function ajaxHandleResponses( s, jqXHR, responses ) { | |
var ct, type, finalDataType, firstDataType, | |
contents = s.contents, | |
dataTypes = s.dataTypes; | |
// Remove auto dataType and get content-type in the process | |
while ( dataTypes[ 0 ] === "*" ) { | |
dataTypes.shift(); | |
if ( ct === undefined ) { | |
ct = s.mimeType || jqXHR.getResponseHeader("Content-Type"); | |
} | |
} | |
// Check if we're dealing with a known content-type | |
if ( ct ) { | |
for ( type in contents ) { | |
if ( contents[ type ] && contents[ type ].test( ct ) ) { | |
dataTypes.unshift( type ); | |
break; | |
} | |
} | |
} | |
// Check to see if we have a response for the expected dataType | |
if ( dataTypes[ 0 ] in responses ) { | |
finalDataType = dataTypes[ 0 ]; | |
} else { | |
// Try convertible dataTypes | |
for ( type in responses ) { | |
if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { | |
finalDataType = type; | |
break; | |
} | |
if ( !firstDataType ) { | |
firstDataType = type; | |
} | |
} | |
// Or just use first one | |
finalDataType = finalDataType || firstDataType; | |
} | |
// If we found a dataType | |
// We add the dataType to the list if needed | |
// and return the corresponding response | |
if ( finalDataType ) { | |
if ( finalDataType !== dataTypes[ 0 ] ) { | |
dataTypes.unshift( finalDataType ); | |
} | |
return responses[ finalDataType ]; | |
} | |
} | |
/* Chain conversions given the request and the original response | |
* Also sets the responseXXX fields on the jqXHR instance | |
*/ | |
function ajaxConvert( s, response, jqXHR, isSuccess ) { | |
var conv2, current, conv, tmp, prev, | |
converters = {}, | |
// Work with a copy of dataTypes in case we need to modify it for conversion | |
dataTypes = s.dataTypes.slice(); | |
// Create converters map with lowercased keys | |
if ( dataTypes[ 1 ] ) { | |
for ( conv in s.converters ) { | |
converters[ conv.toLowerCase() ] = s.converters[ conv ]; | |
} | |
} | |
current = dataTypes.shift(); | |
// Convert to each sequential dataType | |
while ( current ) { | |
if ( s.responseFields[ current ] ) { | |
jqXHR[ s.responseFields[ current ] ] = response; | |
} | |
// Apply the dataFilter if provided | |
if ( !prev && isSuccess && s.dataFilter ) { | |
response = s.dataFilter( response, s.dataType ); | |
} | |
prev = current; | |
current = dataTypes.shift(); | |
if ( current ) { | |
// There's only work to do if current dataType is non-auto | |
if ( current === "*" ) { | |
current = prev; | |
// Convert response if prev dataType is non-auto and differs from current | |
} else if ( prev !== "*" && prev !== current ) { | |
// Seek a direct converter | |
conv = converters[ prev + " " + current ] || converters[ "* " + current ]; | |
// If none found, seek a pair | |
if ( !conv ) { | |
for ( conv2 in converters ) { | |
// If conv2 outputs current | |
tmp = conv2.split( " " ); | |
if ( tmp[ 1 ] === current ) { | |
// If prev can be converted to accepted input | |
conv = converters[ prev + " " + tmp[ 0 ] ] || | |
converters[ "* " + tmp[ 0 ] ]; | |
if ( conv ) { | |
// Condense equivalence converters | |
if ( conv === true ) { | |
conv = converters[ conv2 ]; | |
// Otherwise, insert the intermediate dataType | |
} else if ( converters[ conv2 ] !== true ) { | |
current = tmp[ 0 ]; | |
dataTypes.unshift( tmp[ 1 ] ); | |
} | |
break; | |
} | |
} | |
} | |
} | |
// Apply converter (if not an equivalence) | |
if ( conv !== true ) { | |
// Unless errors are allowed to bubble, catch and return them | |
if ( conv && s[ "throws" ] ) { | |
response = conv( response ); | |
} else { | |
try { | |
response = conv( response ); | |
} catch ( e ) { | |
return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; | |
} | |
} | |
} | |
} | |
} | |
} | |
return { state: "success", data: response }; | |
} | |
jQuery.extend({ | |
// Counter for holding the number of active queries | |
active: 0, | |
// Last-Modified header cache for next request | |
lastModified: {}, | |
etag: {}, | |
ajaxSettings: { | |
url: ajaxLocation, | |
type: "GET", | |
isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), | |
global: true, | |
processData: true, | |
async: true, | |
contentType: "application/x-www-form-urlencoded; charset=UTF-8", | |
/* | |
timeout: 0, | |
data: null, | |
dataType: null, | |
username: null, | |
password: null, | |
cache: null, | |
throws: false, | |
traditional: false, | |
headers: {}, | |
*/ | |
accepts: { | |
"*": allTypes, | |
text: "text/plain", | |
html: "text/html", | |
xml: "application/xml, text/xml", | |
json: "application/json, text/javascript" | |
}, | |
contents: { | |
xml: /xml/, | |
html: /html/, | |
json: /json/ | |
}, | |
responseFields: { | |
xml: "responseXML", | |
text: "responseText", | |
json: "responseJSON" | |
}, | |
// Data converters | |
// Keys separate source (or catchall "*") and destination types with a single space | |
converters: { | |
// Convert anything to text | |
"* text": String, | |
// Text to html (true = no transformation) | |
"text html": true, | |
// Evaluate text as a json expression | |
"text json": jQuery.parseJSON, | |
// Parse text as xml | |
"text xml": jQuery.parseXML | |
}, | |
// For options that shouldn't be deep extended: | |
// you can add your own custom options here if | |
// and when you create one that shouldn't be | |
// deep extended (see ajaxExtend) | |
flatOptions: { | |
url: true, | |
context: true | |
} | |
}, | |
// Creates a full fledged settings object into target | |
// with both ajaxSettings and settings fields. | |
// If target is omitted, writes into ajaxSettings. | |
ajaxSetup: function( target, settings ) { | |
return settings ? | |
// Building a settings object | |
ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : | |
// Extending ajaxSettings | |
ajaxExtend( jQuery.ajaxSettings, target ); | |
}, | |
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), | |
ajaxTransport: addToPrefiltersOrTransports( transports ), | |
// Main method | |
ajax: function( url, options ) { | |
// If url is an object, simulate pre-1.5 signature | |
if ( typeof url === "object" ) { | |
options = url; | |
url = undefined; | |
} | |
// Force options to be an object | |
options = options || {}; | |
var transport, | |
// URL without anti-cache param | |
cacheURL, | |
// Response headers | |
responseHeadersString, | |
responseHeaders, | |
// timeout handle | |
timeoutTimer, | |
// Cross-domain detection vars | |
parts, | |
// To know if global events are to be dispatched | |
fireGlobals, | |
// Loop variable | |
i, | |
// Create the final options object | |
s = jQuery.ajaxSetup( {}, options ), | |
// Callbacks context | |
callbackContext = s.context || s, | |
// Context for global events is callbackContext if it is a DOM node or jQuery collection | |
globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? | |
jQuery( callbackContext ) : | |
jQuery.event, | |
// Deferreds | |
deferred = jQuery.Deferred(), | |
completeDeferred = jQuery.Callbacks("once memory"), | |
// Status-dependent callbacks | |
statusCode = s.statusCode || {}, | |
// Headers (they are sent all at once) | |
requestHeaders = {}, | |
requestHeadersNames = {}, | |
// The jqXHR state | |
state = 0, | |
// Default abort message | |
strAbort = "canceled", | |
// Fake xhr | |
jqXHR = { | |
readyState: 0, | |
// Builds headers hashtable if needed | |
getResponseHeader: function( key ) { | |
var match; | |
if ( state === 2 ) { | |
if ( !responseHeaders ) { | |
responseHeaders = {}; | |
while ( (match = rheaders.exec( responseHeadersString )) ) { | |
responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; | |
} | |
} | |
match = responseHeaders[ key.toLowerCase() ]; | |
} | |
return match == null ? null : match; | |
}, | |
// Raw string | |
getAllResponseHeaders: function() { | |
return state === 2 ? responseHeadersString : null; | |
}, | |
// Caches the header | |
setRequestHeader: function( name, value ) { | |
var lname = name.toLowerCase(); | |
if ( !state ) { | |
name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; | |
requestHeaders[ name ] = value; | |
} | |
return this; | |
}, | |
// Overrides response content-type header | |
overrideMimeType: function( type ) { | |
if ( !state ) { | |
s.mimeType = type; | |
} | |
return this; | |
}, | |
// Status-dependent callbacks | |
statusCode: function( map ) { | |
var code; | |
if ( map ) { | |
if ( state < 2 ) { | |
for ( code in map ) { | |
// Lazy-add the new callback in a way that preserves old ones | |
statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; | |
} | |
} else { | |
// Execute the appropriate callbacks | |
jqXHR.always( map[ jqXHR.status ] ); | |
} | |
} | |
return this; | |
}, | |
// Cancel the request | |
abort: function( statusText ) { | |
var finalText = statusText || strAbort; | |
if ( transport ) { | |
transport.abort( finalText ); | |
} | |
done( 0, finalText ); | |
return this; | |
} | |
}; | |
// Attach deferreds | |
deferred.promise( jqXHR ).complete = completeDeferred.add; | |
jqXHR.success = jqXHR.done; | |
jqXHR.error = jqXHR.fail; | |
// Remove hash character (#7531: and string promotion) | |
// Add protocol if not provided (prefilters might expect it) | |
// Handle falsy url in the settings object (#10093: consistency with old signature) | |
// We also use the url parameter if available | |
s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ) | |
.replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); | |
// Alias method option to type as per ticket #12004 | |
s.type = options.method || options.type || s.method || s.type; | |
// Extract dataTypes list | |
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ]; | |
// A cross-domain request is in order when we have a protocol:host:port mismatch | |
if ( s.crossDomain == null ) { | |
parts = rurl.exec( s.url.toLowerCase() ); | |
s.crossDomain = !!( parts && | |
( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] || | |
( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !== | |
( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) ) | |
); | |
} | |
// Convert data if not already a string | |
if ( s.data && s.processData && typeof s.data !== "string" ) { | |
s.data = jQuery.param( s.data, s.traditional ); | |
} | |
// Apply prefilters | |
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); | |
// If request was aborted inside a prefilter, stop there | |
if ( state === 2 ) { | |
return jqXHR; | |
} | |
// We can fire global events as of now if asked to | |
// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) | |
fireGlobals = jQuery.event && s.global; | |
// Watch for a new set of requests | |
if ( fireGlobals && jQuery.active++ === 0 ) { | |
jQuery.event.trigger("ajaxStart"); | |
} | |
// Uppercase the type | |
s.type = s.type.toUpperCase(); | |
// Determine if request has content | |
s.hasContent = !rnoContent.test( s.type ); | |
// Save the URL in case we're toying with the If-Modified-Since | |
// and/or If-None-Match header later on | |
cacheURL = s.url; | |
// More options handling for requests with no content | |
if ( !s.hasContent ) { | |
// If data is available, append data to url | |
if ( s.data ) { | |
cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data ); | |
// #9682: remove data so that it's not used in an eventual retry | |
delete s.data; | |
} | |
// Add anti-cache in url if needed | |
if ( s.cache === false ) { | |
s.url = rts.test( cacheURL ) ? | |
// If there is already a '_' parameter, set its value | |
cacheURL.replace( rts, "$1_=" + nonce++ ) : | |
// Otherwise add one to the end | |
cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++; | |
} | |
} | |
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. | |
if ( s.ifModified ) { | |
if ( jQuery.lastModified[ cacheURL ] ) { | |
jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); | |
} | |
if ( jQuery.etag[ cacheURL ] ) { | |
jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); | |
} | |
} | |
// Set the correct header, if data is being sent | |
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { | |
jqXHR.setRequestHeader( "Content-Type", s.contentType ); | |
} | |
// Set the Accepts header for the server, depending on the dataType | |
jqXHR.setRequestHeader( | |
"Accept", | |
s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? | |
s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : | |
s.accepts[ "*" ] | |
); | |
// Check for headers option | |
for ( i in s.headers ) { | |
jqXHR.setRequestHeader( i, s.headers[ i ] ); | |
} | |
// Allow custom headers/mimetypes and early abort | |
if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { | |
// Abort if not done already and return | |
return jqXHR.abort(); | |
} | |
// Aborting is no longer a cancellation | |
strAbort = "abort"; | |
// Install callbacks on deferreds | |
for ( i in { success: 1, error: 1, complete: 1 } ) { | |
jqXHR[ i ]( s[ i ] ); | |
} | |
// Get transport | |
transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); | |
// If no transport, we auto-abort | |
if ( !transport ) { | |
done( -1, "No Transport" ); | |
} else { | |
jqXHR.readyState = 1; | |
// Send global event | |
if ( fireGlobals ) { | |
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); | |
} | |
// Timeout | |
if ( s.async && s.timeout > 0 ) { | |
timeoutTimer = setTimeout(function() { | |
jqXHR.abort("timeout"); | |
}, s.timeout ); | |
} | |
try { | |
state = 1; | |
transport.send( requestHeaders, done ); | |
} catch ( e ) { | |
// Propagate exception as error if not done | |
if ( state < 2 ) { | |
done( -1, e ); | |
// Simply rethrow otherwise | |
} else { | |
throw e; | |
} | |
} | |
} | |
// Callback for when everything is done | |
function done( status, nativeStatusText, responses, headers ) { | |
var isSuccess, success, error, response, modified, | |
statusText = nativeStatusText; | |
// Called once | |
if ( state === 2 ) { | |
return; | |
} | |
// State is "done" now | |
state = 2; | |
// Clear timeout if it exists | |
if ( timeoutTimer ) { | |
clearTimeout( timeoutTimer ); | |
} | |
// Dereference transport for early garbage collection | |
// (no matter how long the jqXHR object will be used) | |
transport = undefined; | |
// Cache response headers | |
responseHeadersString = headers || ""; | |
// Set readyState | |
jqXHR.readyState = status > 0 ? 4 : 0; | |
// Determine if successful | |
isSuccess = status >= 200 && status < 300 || status === 304; | |
// Get response data | |
if ( responses ) { | |
response = ajaxHandleResponses( s, jqXHR, responses ); | |
} | |
// Convert no matter what (that way responseXXX fields are always set) | |
response = ajaxConvert( s, response, jqXHR, isSuccess ); | |
// If successful, handle type chaining | |
if ( isSuccess ) { | |
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. | |
if ( s.ifModified ) { | |
modified = jqXHR.getResponseHeader("Last-Modified"); | |
if ( modified ) { | |
jQuery.lastModified[ cacheURL ] = modified; | |
} | |
modified = jqXHR.getResponseHeader("etag"); | |
if ( modified ) { | |
jQuery.etag[ cacheURL ] = modified; | |
} | |
} | |
// if no content | |
if ( status === 204 || s.type === "HEAD" ) { | |
statusText = "nocontent"; | |
// if not modified | |
} else if ( status === 304 ) { | |
statusText = "notmodified"; | |
// If we have data, let's convert it | |
} else { | |
statusText = response.state; | |
success = response.data; | |
error = response.error; | |
isSuccess = !error; | |
} | |
} else { | |
// Extract error from statusText and normalize for non-aborts | |
error = statusText; | |
if ( status || !statusText ) { | |
statusText = "error"; | |
if ( status < 0 ) { | |
status = 0; | |
} | |
} | |
} | |
// Set data for the fake xhr object | |
jqXHR.status = status; | |
jqXHR.statusText = ( nativeStatusText || statusText ) + ""; | |
// Success/Error | |
if ( isSuccess ) { | |
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); | |
} else { | |
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); | |
} | |
// Status-dependent callbacks | |
jqXHR.statusCode( statusCode ); | |
statusCode = undefined; | |
if ( fireGlobals ) { | |
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", | |
[ jqXHR, s, isSuccess ? success : error ] ); | |
} | |
// Complete | |
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); | |
if ( fireGlobals ) { | |
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); | |
// Handle the global AJAX counter | |
if ( !( --jQuery.active ) ) { | |
jQuery.event.trigger("ajaxStop"); | |
} | |
} | |
} | |
return jqXHR; | |
}, | |
getJSON: function( url, data, callback ) { | |
return jQuery.get( url, data, callback, "json" ); | |
}, | |
getScript: function( url, callback ) { | |
return jQuery.get( url, undefined, callback, "script" ); | |
} | |
}); | |
jQuery.each( [ "get", "post" ], function( i, method ) { | |
jQuery[ method ] = function( url, data, callback, type ) { | |
// Shift arguments if data argument was omitted | |
if ( jQuery.isFunction( data ) ) { | |
type = type || callback; | |
callback = data; | |
data = undefined; | |
} | |
return jQuery.ajax({ | |
url: url, | |
type: method, | |
dataType: type, | |
data: data, | |
success: callback | |
}); | |
}; | |
}); | |
jQuery._evalUrl = function( url ) { | |
return jQuery.ajax({ | |
url: url, | |
type: "GET", | |
dataType: "script", | |
async: false, | |
global: false, | |
"throws": true | |
}); | |
}; | |
jQuery.fn.extend({ | |
wrapAll: function( html ) { | |
var wrap; | |
if ( jQuery.isFunction( html ) ) { | |
return this.each(function( i ) { | |
jQuery( this ).wrapAll( html.call(this, i) ); | |
}); | |
} | |
if ( this[ 0 ] ) { | |
// The elements to wrap the target around | |
wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); | |
if ( this[ 0 ].parentNode ) { | |
wrap.insertBefore( this[ 0 ] ); | |
} | |
wrap.map(function() { | |
var elem = this; | |
while ( elem.firstElementChild ) { | |
elem = elem.firstElementChild; | |
} | |
return elem; | |
}).append( this ); | |
} | |
return this; | |
}, | |
wrapInner: function( html ) { | |
if ( jQuery.isFunction( html ) ) { | |
return this.each(function( i ) { | |
jQuery( this ).wrapInner( html.call(this, i) ); | |
}); | |
} | |
return this.each(function() { | |
var self = jQuery( this ), | |
contents = self.contents(); | |
if ( contents.length ) { | |
contents.wrapAll( html ); | |
} else { | |
self.append( html ); | |
} | |
}); | |
}, | |
wrap: function( html ) { | |
var isFunction = jQuery.isFunction( html ); | |
return this.each(function( i ) { | |
jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); | |
}); | |
}, | |
unwrap: function() { | |
return this.parent().each(function() { | |
if ( !jQuery.nodeName( this, "body" ) ) { | |
jQuery( this ).replaceWith( this.childNodes ); | |
} | |
}).end(); | |
} | |
}); | |
jQuery.expr.filters.hidden = function( elem ) { | |
// Support: Opera <= 12.12 | |
// Opera reports offsetWidths and offsetHeights less than zero on some elements | |
return elem.offsetWidth <= 0 && elem.offsetHeight <= 0; | |
}; | |
jQuery.expr.filters.visible = function( elem ) { | |
return !jQuery.expr.filters.hidden( elem ); | |
}; | |
var r20 = /%20/g, | |
rbracket = /\[\]$/, | |
rCRLF = /\r?\n/g, | |
rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, | |
rsubmittable = /^(?:input|select|textarea|keygen)/i; | |
function buildParams( prefix, obj, traditional, add ) { | |
var name; | |
if ( jQuery.isArray( obj ) ) { | |
// Serialize array item. | |
jQuery.each( obj, function( i, v ) { | |
if ( traditional || rbracket.test( prefix ) ) { | |
// Treat each array item as a scalar. | |
add( prefix, v ); | |
} else { | |
// Item is non-scalar (array or object), encode its numeric index. | |
buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add ); | |
} | |
}); | |
} else if ( !traditional && jQuery.type( obj ) === "object" ) { | |
// Serialize object item. | |
for ( name in obj ) { | |
buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); | |
} | |
} else { | |
// Serialize scalar item. | |
add( prefix, obj ); | |
} | |
} | |
// Serialize an array of form elements or a set of | |
// key/values into a query string | |
jQuery.param = function( a, traditional ) { | |
var prefix, | |
s = [], | |
add = function( key, value ) { | |
// If value is a function, invoke it and return its value | |
value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value ); | |
s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); | |
}; | |
// Set traditional to true for jQuery <= 1.3.2 behavior. | |
if ( traditional === undefined ) { | |
traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; | |
} | |
// If an array was passed in, assume that it is an array of form elements. | |
if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { | |
// Serialize the form elements | |
jQuery.each( a, function() { | |
add( this.name, this.value ); | |
}); | |
} else { | |
// If traditional, encode the "old" way (the way 1.3.2 or older | |
// did it), otherwise encode params recursively. | |
for ( prefix in a ) { | |
buildParams( prefix, a[ prefix ], traditional, add ); | |
} | |
} | |
// Return the resulting serialization | |
return s.join( "&" ).replace( r20, "+" ); | |
}; | |
jQuery.fn.extend({ | |
serialize: function() { | |
return jQuery.param( this.serializeArray() ); | |
}, | |
serializeArray: function() { | |
return this.map(function() { | |
// Can add propHook for "elements" to filter or add form elements | |
var elements = jQuery.prop( this, "elements" ); | |
return elements ? jQuery.makeArray( elements ) : this; | |
}) | |
.filter(function() { | |
var type = this.type; | |
// Use .is( ":disabled" ) so that fieldset[disabled] works | |
return this.name && !jQuery( this ).is( ":disabled" ) && | |
rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && | |
( this.checked || !rcheckableType.test( type ) ); | |
}) | |
.map(function( i, elem ) { | |
var val = jQuery( this ).val(); | |
return val == null ? | |
null : | |
jQuery.isArray( val ) ? | |
jQuery.map( val, function( val ) { | |
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; | |
}) : | |
{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; | |
}).get(); | |
} | |
}); | |
jQuery.ajaxSettings.xhr = function() { | |
try { | |
return new XMLHttpRequest(); | |
} catch( e ) {} | |
}; | |
var xhrId = 0, | |
xhrCallbacks = {}, | |
xhrSuccessStatus = { | |
// file protocol always yields status code 0, assume 200 | |
0: 200, | |
// Support: IE9 | |
// #1450: sometimes IE returns 1223 when it should be 204 | |
1223: 204 | |
}, | |
xhrSupported = jQuery.ajaxSettings.xhr(); | |
// Support: IE9 | |
// Open requests must be manually aborted on unload (#5280) | |
// See https://support.microsoft.com/kb/2856746 for more info | |
if ( window.attachEvent ) { | |
window.attachEvent( "onunload", function() { | |
for ( var key in xhrCallbacks ) { | |
xhrCallbacks[ key ](); | |
} | |
}); | |
} | |
support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); | |
support.ajax = xhrSupported = !!xhrSupported; | |
jQuery.ajaxTransport(function( options ) { | |
var callback; | |
// Cross domain only allowed if supported through XMLHttpRequest | |
if ( support.cors || xhrSupported && !options.crossDomain ) { | |
return { | |
send: function( headers, complete ) { | |
var i, | |
xhr = options.xhr(), | |
id = ++xhrId; | |
xhr.open( options.type, options.url, options.async, options.username, options.password ); | |
// Apply custom fields if provided | |
if ( options.xhrFields ) { | |
for ( i in options.xhrFields ) { | |
xhr[ i ] = options.xhrFields[ i ]; | |
} | |
} | |
// Override mime type if needed | |
if ( options.mimeType && xhr.overrideMimeType ) { | |
xhr.overrideMimeType( options.mimeType ); | |
} | |
// X-Requested-With header | |
// For cross-domain requests, seeing as conditions for a preflight are | |
// akin to a jigsaw puzzle, we simply never set it to be sure. | |
// (it can always be set on a per-request basis or even using ajaxSetup) | |
// For same-domain requests, won't change header if already provided. | |
if ( !options.crossDomain && !headers["X-Requested-With"] ) { | |
headers["X-Requested-With"] = "XMLHttpRequest"; | |
} | |
// Set headers | |
for ( i in headers ) { | |
xhr.setRequestHeader( i, headers[ i ] ); | |
} | |
// Callback | |
callback = function( type ) { | |
return function() { | |
if ( callback ) { | |
delete xhrCallbacks[ id ]; | |
callback = xhr.onload = xhr.onerror = null; | |
if ( type === "abort" ) { | |
xhr.abort(); | |
} else if ( type === "error" ) { | |
complete( | |
// file: protocol always yields status 0; see #8605, #14207 | |
xhr.status, | |
xhr.statusText | |
); | |
} else { | |
complete( | |
xhrSuccessStatus[ xhr.status ] || xhr.status, | |
xhr.statusText, | |
// Support: IE9 | |
// Accessing binary-data responseText throws an exception | |
// (#11426) | |
typeof xhr.responseText === "string" ? { | |
text: xhr.responseText | |
} : undefined, | |
xhr.getAllResponseHeaders() | |
); | |
} | |
} | |
}; | |
}; | |
// Listen to events | |
xhr.onload = callback(); | |
xhr.onerror = callback("error"); | |
// Create the abort callback | |
callback = xhrCallbacks[ id ] = callback("abort"); | |
try { | |
// Do send the request (this may raise an exception) | |
xhr.send( options.hasContent && options.data || null ); | |
} catch ( e ) { | |
// #14683: Only rethrow if this hasn't been notified as an error yet | |
if ( callback ) { | |
throw e; | |
} | |
} | |
}, | |
abort: function() { | |
if ( callback ) { | |
callback(); | |
} | |
} | |
}; | |
} | |
}); | |
// Install script dataType | |
jQuery.ajaxSetup({ | |
accepts: { | |
script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" | |
}, | |
contents: { | |
script: /(?:java|ecma)script/ | |
}, | |
converters: { | |
"text script": function( text ) { | |
jQuery.globalEval( text ); | |
return text; | |
} | |
} | |
}); | |
// Handle cache's special case and crossDomain | |
jQuery.ajaxPrefilter( "script", function( s ) { | |
if ( s.cache === undefined ) { | |
s.cache = false; | |
} | |
if ( s.crossDomain ) { | |
s.type = "GET"; | |
} | |
}); | |
// Bind script tag hack transport | |
jQuery.ajaxTransport( "script", function( s ) { | |
// This transport only deals with cross domain requests | |
if ( s.crossDomain ) { | |
var script, callback; | |
return { | |
send: function( _, complete ) { | |
script = jQuery("<script>").prop({ | |
async: true, | |
charset: s.scriptCharset, | |
src: s.url | |
}).on( | |
"load error", | |
callback = function( evt ) { | |
script.remove(); | |
callback = null; | |
if ( evt ) { | |
complete( evt.type === "error" ? 404 : 200, evt.type ); | |
} | |
} | |
); | |
document.head.appendChild( script[ 0 ] ); | |
}, | |
abort: function() { | |
if ( callback ) { | |
callback(); | |
} | |
} | |
}; | |
} | |
}); | |
var oldCallbacks = [], | |
rjsonp = /(=)\?(?=&|$)|\?\?/; | |
// Default jsonp settings | |
jQuery.ajaxSetup({ | |
jsonp: "callback", | |
jsonpCallback: function() { | |
var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); | |
this[ callback ] = true; | |
return callback; | |
} | |
}); | |
// Detect, normalize options and install callbacks for jsonp requests | |
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { | |
var callbackName, overwritten, responseContainer, | |
jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? | |
"url" : | |
typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data" | |
); | |
// Handle iff the expected data type is "jsonp" or we have a parameter to set | |
if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { | |
// Get callback name, remembering preexisting value associated with it | |
callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? | |
s.jsonpCallback() : | |
s.jsonpCallback; | |
// Insert callback into url or form data | |
if ( jsonProp ) { | |
s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); | |
} else if ( s.jsonp !== false ) { | |
s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; | |
} | |
// Use data converter to retrieve json after script execution | |
s.converters["script json"] = function() { | |
if ( !responseContainer ) { | |
jQuery.error( callbackName + " was not called" ); | |
} | |
return responseContainer[ 0 ]; | |
}; | |
// force json dataType | |
s.dataTypes[ 0 ] = "json"; | |
// Install callback | |
overwritten = window[ callbackName ]; | |
window[ callbackName ] = function() { | |
responseContainer = arguments; | |
}; | |
// Clean-up function (fires after converters) | |
jqXHR.always(function() { | |
// Restore preexisting value | |
window[ callbackName ] = overwritten; | |
// Save back as free | |
if ( s[ callbackName ] ) { | |
// make sure that re-using the options doesn't screw things around | |
s.jsonpCallback = originalSettings.jsonpCallback; | |
// save the callback name for future use | |
oldCallbacks.push( callbackName ); | |
} | |
// Call if it was a function and we have a response | |
if ( responseContainer && jQuery.isFunction( overwritten ) ) { | |
overwritten( responseContainer[ 0 ] ); | |
} | |
responseContainer = overwritten = undefined; | |
}); | |
// Delegate to script | |
return "script"; | |
} | |
}); | |
// data: string of html | |
// context (optional): If specified, the fragment will be created in this context, defaults to document | |
// keepScripts (optional): If true, will include scripts passed in the html string | |
jQuery.parseHTML = function( data, context, keepScripts ) { | |
if ( !data || typeof data !== "string" ) { | |
return null; | |
} | |
if ( typeof context === "boolean" ) { | |
keepScripts = context; | |
context = false; | |
} | |
context = context || document; | |
var parsed = rsingleTag.exec( data ), | |
scripts = !keepScripts && []; | |
// Single tag | |
if ( parsed ) { | |
return [ context.createElement( parsed[1] ) ]; | |
} | |
parsed = jQuery.buildFragment( [ data ], context, scripts ); | |
if ( scripts && scripts.length ) { | |
jQuery( scripts ).remove(); | |
} | |
return jQuery.merge( [], parsed.childNodes ); | |
}; | |
// Keep a copy of the old load method | |
var _load = jQuery.fn.load; | |
/** | |
* Load a url into a page | |
*/ | |
jQuery.fn.load = function( url, params, callback ) { | |
if ( typeof url !== "string" && _load ) { | |
return _load.apply( this, arguments ); | |
} | |
var selector, type, response, | |
self = this, | |
off = url.indexOf(" "); | |
if ( off >= 0 ) { | |
selector = jQuery.trim( url.slice( off ) ); | |
url = url.slice( 0, off ); | |
} | |
// If it's a function | |
if ( jQuery.isFunction( params ) ) { | |
// We assume that it's the callback | |
callback = params; | |
params = undefined; | |
// Otherwise, build a param string | |
} else if ( params && typeof params === "object" ) { | |
type = "POST"; | |
} | |
// If we have elements to modify, make the request | |
if ( self.length > 0 ) { | |
jQuery.ajax({ | |
url: url, | |
// if "type" variable is undefined, then "GET" method will be used | |
type: type, | |
dataType: "html", | |
data: params | |
}).done(function( responseText ) { | |
// Save response for use in complete callback | |
response = arguments; | |
self.html( selector ? | |
// If a selector was specified, locate the right elements in a dummy div | |
// Exclude scripts to avoid IE 'Permission Denied' errors | |
jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) : | |
// Otherwise use the full result | |
responseText ); | |
}).complete( callback && function( jqXHR, status ) { | |
self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] ); | |
}); | |
} | |
return this; | |
}; | |
// Attach a bunch of functions for handling common AJAX events | |
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) { | |
jQuery.fn[ type ] = function( fn ) { | |
return this.on( type, fn ); | |
}; | |
}); | |
jQuery.expr.filters.animated = function( elem ) { | |
return jQuery.grep(jQuery.timers, function( fn ) { | |
return elem === fn.elem; | |
}).length; | |
}; | |
var docElem = window.document.documentElement; | |
/** | |
* Gets a window from an element | |
*/ | |
function getWindow( elem ) { | |
return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView; | |
} | |
jQuery.offset = { | |
setOffset: function( elem, options, i ) { | |
var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, | |
position = jQuery.css( elem, "position" ), | |
curElem = jQuery( elem ), | |
props = {}; | |
// Set position first, in-case top/left are set even on static elem | |
if ( position === "static" ) { | |
elem.style.position = "relative"; | |
} | |
curOffset = curElem.offset(); | |
curCSSTop = jQuery.css( elem, "top" ); | |
curCSSLeft = jQuery.css( elem, "left" ); | |
calculatePosition = ( position === "absolute" || position === "fixed" ) && | |
( curCSSTop + curCSSLeft ).indexOf("auto") > -1; | |
// Need to be able to calculate position if either | |
// top or left is auto and position is either absolute or fixed | |
if ( calculatePosition ) { | |
curPosition = curElem.position(); | |
curTop = curPosition.top; | |
curLeft = curPosition.left; | |
} else { | |
curTop = parseFloat( curCSSTop ) || 0; | |
curLeft = parseFloat( curCSSLeft ) || 0; | |
} | |
if ( jQuery.isFunction( options ) ) { | |
options = options.call( elem, i, curOffset ); | |
} | |
if ( options.top != null ) { | |
props.top = ( options.top - curOffset.top ) + curTop; | |
} | |
if ( options.left != null ) { | |
props.left = ( options.left - curOffset.left ) + curLeft; | |
} | |
if ( "using" in options ) { | |
options.using.call( elem, props ); | |
} else { | |
curElem.css( props ); | |
} | |
} | |
}; | |
jQuery.fn.extend({ | |
offset: function( options ) { | |
if ( arguments.length ) { | |
return options === undefined ? | |
this : | |
this.each(function( i ) { | |
jQuery.offset.setOffset( this, options, i ); | |
}); | |
} | |
var docElem, win, | |
elem = this[ 0 ], | |
box = { top: 0, left: 0 }, | |
doc = elem && elem.ownerDocument; | |
if ( !doc ) { | |
return; | |
} | |
docElem = doc.documentElement; | |
// Make sure it's not a disconnected DOM node | |
if ( !jQuery.contains( docElem, elem ) ) { | |
return box; | |
} | |
// Support: BlackBerry 5, iOS 3 (original iPhone) | |
// If we don't have gBCR, just use 0,0 rather than error | |
if ( typeof elem.getBoundingClientRect !== strundefined ) { | |
box = elem.getBoundingClientRect(); | |
} | |
win = getWindow( doc ); | |
return { | |
top: box.top + win.pageYOffset - docElem.clientTop, | |
left: box.left + win.pageXOffset - docElem.clientLeft | |
}; | |
}, | |
position: function() { | |
if ( !this[ 0 ] ) { | |
return; | |
} | |
var offsetParent, offset, | |
elem = this[ 0 ], | |
parentOffset = { top: 0, left: 0 }; | |
// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent | |
if ( jQuery.css( elem, "position" ) === "fixed" ) { | |
// Assume getBoundingClientRect is there when computed position is fixed | |
offset = elem.getBoundingClientRect(); | |
} else { | |
// Get *real* offsetParent | |
offsetParent = this.offsetParent(); | |
// Get correct offsets | |
offset = this.offset(); | |
if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) { | |
parentOffset = offsetParent.offset(); | |
} | |
// Add offsetParent borders | |
parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ); | |
parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true ); | |
} | |
// Subtract parent offsets and element margins | |
return { | |
top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), | |
left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true ) | |
}; | |
}, | |
offsetParent: function() { | |
return this.map(function() { | |
var offsetParent = this.offsetParent || docElem; | |
while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) { | |
offsetParent = offsetParent.offsetParent; | |
} | |
return offsetParent || docElem; | |
}); | |
} | |
}); | |
// Create scrollLeft and scrollTop methods | |
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) { | |
var top = "pageYOffset" === prop; | |
jQuery.fn[ method ] = function( val ) { | |
return access( this, function( elem, method, val ) { | |
var win = getWindow( elem ); | |
if ( val === undefined ) { | |
return win ? win[ prop ] : elem[ method ]; | |
} | |
if ( win ) { | |
win.scrollTo( | |
!top ? val : window.pageXOffset, | |
top ? val : window.pageYOffset | |
); | |
} else { | |
elem[ method ] = val; | |
} | |
}, method, val, arguments.length, null ); | |
}; | |
}); | |
// Support: Safari<7+, Chrome<37+ | |
// Add the top/left cssHooks using jQuery.fn.position | |
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 | |
// Blink bug: https://code.google.com/p/chromium/issues/detail?id=229280 | |
// getComputedStyle returns percent when specified for top/left/bottom/right; | |
// rather than make the css module depend on the offset module, just check for it here | |
jQuery.each( [ "top", "left" ], function( i, prop ) { | |
jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, | |
function( elem, computed ) { | |
if ( computed ) { | |
computed = curCSS( elem, prop ); | |
// If curCSS returns percentage, fallback to offset | |
return rnumnonpx.test( computed ) ? | |
jQuery( elem ).position()[ prop ] + "px" : | |
computed; | |
} | |
} | |
); | |
}); | |
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods | |
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { | |
jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { | |
// Margin is only for outerHeight, outerWidth | |
jQuery.fn[ funcName ] = function( margin, value ) { | |
var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), | |
extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); | |
return access( this, function( elem, type, value ) { | |
var doc; | |
if ( jQuery.isWindow( elem ) ) { | |
// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there | |
// isn't a whole lot we can do. See pull request at this URL for discussion: | |
// https://github.com/jquery/jquery/pull/764 | |
return elem.document.documentElement[ "client" + name ]; | |
} | |
// Get document width or height | |
if ( elem.nodeType === 9 ) { | |
doc = elem.documentElement; | |
// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], | |
// whichever is greatest | |
return Math.max( | |
elem.body[ "scroll" + name ], doc[ "scroll" + name ], | |
elem.body[ "offset" + name ], doc[ "offset" + name ], | |
doc[ "client" + name ] | |
); | |
} | |
return value === undefined ? | |
// Get width or height on the element, requesting but not forcing parseFloat | |
jQuery.css( elem, type, extra ) : | |
// Set width or height on the element | |
jQuery.style( elem, type, value, extra ); | |
}, type, chainable ? margin : undefined, chainable, null ); | |
}; | |
}); | |
}); | |
// The number of elements contained in the matched element set | |
jQuery.fn.size = function() { | |
return this.length; | |
}; | |
jQuery.fn.andSelf = jQuery.fn.addBack; | |
// Register as a named AMD module, since jQuery can be concatenated with other | |
// files that may use define, but not via a proper concatenation script that | |
// understands anonymous AMD modules. A named AMD is safest and most robust | |
// way to register. Lowercase jquery is used because AMD module names are | |
// derived from file names, and jQuery is normally delivered in a lowercase | |
// file name. Do this after creating the global so that if an AMD module wants | |
// to call noConflict to hide this version of jQuery, it will work. | |
// Note that for maximum portability, libraries that are not jQuery should | |
// declare themselves as anonymous modules, and avoid setting a global if an | |
// AMD loader is present. jQuery is a special case. For more information, see | |
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon | |
if ( typeof define === "function" && define.amd ) { | |
define( "jquery", [], function() { | |
return jQuery; | |
}); | |
} | |
var | |
// Map over jQuery in case of overwrite | |
_jQuery = window.jQuery, | |
// Map over the $ in case of overwrite | |
_$ = window.$; | |
jQuery.noConflict = function( deep ) { | |
if ( window.$ === jQuery ) { | |
window.$ = _$; | |
} | |
if ( deep && window.jQuery === jQuery ) { | |
window.jQuery = _jQuery; | |
} | |
return jQuery; | |
}; | |
// Expose jQuery and $ identifiers, even in AMD | |
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557) | |
// and CommonJS for browser emulators (#13566) | |
if ( typeof noGlobal === strundefined ) { | |
window.jQuery = window.$ = jQuery; | |
} | |
return jQuery; | |
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
KMIST_TEMP_ID | ID POP | type | pop | name | Country | Province | City | zip | ward | name_full | latitude | longitude | geo | amenity | date_entered | date_issued | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
KTMPVN12119 | 2018 | No | Nha Thuoc An Binh | Ho Chi Minh Province | Ho Chi Minh | Thu Duc | Linh Trung | Ho Chi Minh ,Nha Thuoc An Binh ,Thu Duc, Linh Trung | 10.8539167 | 106.7716921 | 106.7716921,10.8539167 | pharmacy | 2015-10-30T00:00:00 | 2015-10-30T00:00:00 | |||
KTMPVN12144 | 17019 | 2018 | yes | Nha Thuoc Thuy Nguyen | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 21 | Ho Chi Minh ,Nha Thuoc Thuy Nguyen ,Binh Thanh, 21 | 10.798508 | 106.711179 | 106.711179,10.798508 | pharmacy | 2015-10-30T00:00:01 | 2015-10-30T00:00:01 | ||
KTMPVN12149 | 2018 | No | Nha Thuoc Tay Anh Phuong | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 13 | Ho Chi Minh ,Nha Thuoc Tay Anh Phuong ,Binh Thanh, 13 | 10.813232 | 106.70039 | 106.70039,10.813232 | pharmacy | 2015-10-30T00:00:02 | 2015-10-30T00:00:02 | |||
KTMPVN12162 | 2018 | No | Nha Thuoc The Ky | Ho Chi Minh Province | Ho Chi Minh | 1 | NA | Ho Chi Minh ,Nha Thuoc The Ky ,1, NA | 10.7732238 | 106.6986981 | 106.6986981,10.7732238 | pharmacy | 2015-10-30T00:00:03 | 2015-10-30T00:00:03 | |||
KTMPVN12191 | 2018 | No | Nha Thuoc Kim Ngan | Ho Chi Minh Province | Ho Chi Minh | Tan Phu | Hoa Thanh | Ho Chi Minh ,Nha Thuoc Kim Ngan ,Tan Phu, Hoa Thanh | 10.7840823 | 106.6392241 | 106.6392241,10.7840823 | pharmacy | 2015-10-30T00:00:04 | 2015-10-30T00:00:04 | |||
KTMPVN12199 | 2018 | No | Nha Thuoc Mai Khuong | Ho Chi Minh Province | Ho Chi Minh | 11 | 10 | Ho Chi Minh ,Nha Thuoc Mai Khuong ,11, 10 | 10.762648 | 106.642335 | 106.642335,10.762648 | pharmacy | 2015-10-30T00:00:05 | 2015-10-30T00:00:05 | |||
KTMPVN12201 | 2018 | No | Nha Thuoc Mai Thao | Ho Chi Minh Province | Ho Chi Minh | Tan Phu | Tan Thoi Hoa | Ho Chi Minh ,Nha Thuoc Mai Thao ,Tan Phu, Tan Thoi Hoa | 10.765549 | 106.63155 | 106.63155,10.765549 | pharmacy | 2015-10-30T00:00:06 | 2015-10-30T00:00:06 | |||
KTMPVN12242 | 2018 | No | Nha Thuoc Anh Phuong | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | Binh Hung Hoa | Ho Chi Minh ,Nha Thuoc Anh Phuong ,Binh Thanh, Binh Hung Hoa | 10.8176735 | 106.6042386 | 106.6042386,10.8176735 | pharmacy | 2015-10-30T00:00:07 | 2015-10-30T00:00:07 | |||
KTMPVN12275 | 2018 | No | Nha Thuoc Thu Huong | Ho Chi Minh Province | Ho Chi Minh | Go Vap | 18 | Ho Chi Minh ,Nha Thuoc Thu Huong ,Go Vap, 18 | 10.8515163 | 106.6622063 | 106.6622063,10.8515163 | pharmacy | 2015-10-30T00:00:08 | 2015-10-30T00:00:08 | |||
KTMPVN12277 | 23815 | 2018 | yes | Nha Thuoc Ngoc Diep | Ho Chi Minh Province | Ho Chi Minh | Phu Nhuan | 5 | Ho Chi Minh ,Nha Thuoc Ngoc Diep ,Phu Nhuan, 5 | 10.8049906 | 106.6870048 | 106.6870048,10.8049906 | pharmacy | 2015-10-30T00:00:09 | 2015-10-30T00:00:09 | ||
KTMPVN12391 | 2018 | No | Nha Thuoc Thien Phuc | Ho Chi Minh Province | Ho Chi Minh | 10 | 4 | Ho Chi Minh ,Nha Thuoc Thien Phuc ,10, 4 | 10.7622278 | 106.6699374 | 106.6699374,10.7622278 | pharmacy | 2015-10-30T00:00:10 | 2015-10-30T00:00:10 | |||
KTMPVN12399 | 7923 | 2018 | yes | Nha Thuoc Minh Chau | Ho Chi Minh Province | Ho Chi Minh | 10 | 12 | Ho Chi Minh ,Nha Thuoc Minh Chau ,10, 12 | 10.7728525 | 106.665413 | 106.665413,10.7728525 | pharmacy | 2015-10-30T00:00:11 | 2015-10-30T00:00:11 | ||
KTMPVN13749 | 2018 | No | Nha Thuoc Hoang Tri | Ho Chi Minh Province | Ho Chi Minh | 3 | 13 | Ho Chi Minh ,Nha Thuoc Hoang Tri ,3, 13 | 10.785097 | 106.678377 | 106.678377,10.785097 | pharmacy | 2015-10-30T00:00:12 | 2015-10-30T00:00:12 | |||
KTMPVN13750 | 2018 | No | Nha Thuoc Duc Thinh | Ho Chi Minh Province | Ho Chi Minh | Tan Binh | Tan Thanh | Ho Chi Minh ,Nha Thuoc Duc Thinh ,Tan Binh, Tan Thanh | 10.787494 | 106.640947 | 106.640947,10.787494 | pharmacy | 2015-10-30T00:00:13 | 2015-10-30T00:00:13 | |||
KTMPVN13751 | 8701 | 2018 | yes | Nha Thuoc Phu Thinh | Ho Chi Minh Province | Ho Chi Minh | Go Vap | 5 | Ho Chi Minh ,Nha Thuoc Phu Thinh ,Go Vap, 5 | 10.8283487 | 106.6898297 | 106.6898297,10.8283487 | pharmacy | 2015-10-30T00:00:14 | 2015-10-30T00:00:14 | ||
KTMPVN13752 | 2018 | No | Nha Thuoc Truang An | Ho Chi Minh Province | Ho Chi Minh | 7 | Phu Thuan | Ho Chi Minh ,Nha Thuoc Truang An ,7, Phu Thuan | 10.731497 | 106.731839 | 106.731839,10.731497 | pharmacy | 2015-10-30T00:00:15 | 2015-10-30T00:00:15 | |||
KTMPVN13753 | 14091 | 2018 | yes | Nha Thuoc Bao Tran | Ho Chi Minh Province | Ho Chi Minh | Phu Nhuan | 5 | Ho Chi Minh ,Nha Thuoc Bao Tran ,Phu Nhuan, 5 | 10.8043036 | 106.683745 | 106.683745,10.8043036 | pharmacy | 2015-10-30T00:00:16 | 2015-10-30T00:00:16 | ||
KTMPVN13754 | 2018 | No | Nha Thuoc Minh Tam | Ho Chi Minh Province | Ho Chi Minh | 5 | 9 | Ho Chi Minh ,Nha Thuoc Minh Tam ,5, 9 | 10.760286 | 106.67118 | 106.67118,10.760286 | pharmacy | 2015-10-30T00:00:17 | 2015-10-30T00:00:17 | |||
KTMPVN13755 | 2018 | No | Nha Thuoc Minh Thu | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | Binh Tri Dong | Ho Chi Minh ,Nha Thuoc Minh Thu ,Binh Thanh, Binh Tri Dong | 10.75636 | 106.623667 | 106.623667,10.75636 | pharmacy | 2015-10-30T00:00:18 | 2015-10-30T00:00:18 | |||
KTMPVN13756 | 2018 | No | Nha Thuoc Tu Nhan Nam Phuong | Ho Chi Minh Province | Ho Chi Minh | Tan Phu | Hoa Thanh | Ho Chi Minh ,Nha Thuoc Tu Nhan Nam Phuong ,Tan Phu, Hoa Thanh | 10.775237 | 106.633015 | 106.633015,10.775237 | pharmacy | 2015-10-30T00:00:19 | 2015-10-30T00:00:19 | |||
KTMPVN13757 | 2018 | No | Hieu Thuoc Tay So 1 - Cholipharco | Ho Chi Minh Province | Ho Chi Minh | 5 | 2 | Ho Chi Minh ,Hieu Thuoc Tay So 1 - Cholipharco ,5, 2 | 10.75829 | 106.68113 | 106.68113,10.75829 | pharmacy | 2015-10-30T00:00:20 | 2015-10-30T00:00:20 | |||
KTMPVN13758 | 2018 | No | Hieu Thuoc So 1 | Ho Chi Minh Province | Ho Chi Minh | 3 | 1 | Ho Chi Minh ,Hieu Thuoc So 1 ,3, 1 | 10.769171 | 106.677917 | 106.677917,10.769171 | pharmacy | 2015-10-30T00:00:21 | 2015-10-30T00:00:21 | |||
KTMPVN13759 | 2018 | No | Nha Thuoc Quynh Giao | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | Binh Tri Dong | Ho Chi Minh ,Nha Thuoc Quynh Giao ,Binh Thanh, Binh Tri Dong | 10.758138 | 106.611612 | 106.611612,10.758138 | pharmacy | 2015-10-30T00:00:22 | 2015-10-30T00:00:22 | |||
KTMPVN13760 | 2018 | No | Nha Thuoc Thanh Son | Ho Chi Minh Province | Ho Chi Minh | 1 | Nguyen Thai Binh | Ho Chi Minh ,Nha Thuoc Thanh Son ,1, Nguyen Thai Binh | 10.77002 | 106.700455 | 106.700455,10.77002 | pharmacy | 2015-10-30T00:00:23 | 2015-10-30T00:00:23 | |||
KTMPVN13761 | 2018 | No | Nha Thuoc Diem Ha | Ho Chi Minh Province | Ho Chi Minh | 1 | Ben Than | Ho Chi Minh ,Nha Thuoc Diem Ha ,1, Ben Than | 10.77186 | 106.697069 | 106.697069,10.77186 | pharmacy | 2015-10-30T00:00:24 | 2015-10-30T00:00:24 | |||
KTMPVN13762 | 2018 | No | Hieu Thuoc So 7 | Ho Chi Minh Province | Ho Chi Minh | 1 | Ben Nghe | Ho Chi Minh ,Hieu Thuoc So 7 ,1, Ben Nghe | 10.771181 | 106.702459 | 106.702459,10.771181 | pharmacy | 2015-10-30T00:00:25 | 2015-10-30T00:00:25 | |||
KTMPVN13763 | 2018 | No | Nha Thuoc Tu Nhan Hai | Ho Chi Minh Province | Ho Chi Minh | 1 | Pham Ngu Lao | Ho Chi Minh ,Nha Thuoc Tu Nhan Hai ,1, Pham Ngu Lao | 10.766903 | 106.687943 | 106.687943,10.766903 | pharmacy | 2015-10-30T00:00:26 | 2015-10-30T00:00:26 | |||
KTMPVN13764 | 2018 | No | Nha Thuoc Dang Khoa | Ho Chi Minh Province | Ho Chi Minh | 1 | Da Kao | Ho Chi Minh ,Nha Thuoc Dang Khoa ,1, Da Kao | 10.791071 | 106.696652 | 106.696652,10.791071 | pharmacy | 2015-10-30T00:00:27 | 2015-10-30T00:00:27 | |||
KTMPVN13765 | 2018 | No | Nha Thuoc Tu Nhan Da Kao | Ho Chi Minh Province | Ho Chi Minh | 1 | Da Kao | Ho Chi Minh ,Nha Thuoc Tu Nhan Da Kao ,1, Da Kao | 10.792805 | 106.696823 | 106.696823,10.792805 | pharmacy | 2015-10-30T00:00:28 | 2015-10-30T00:00:28 | |||
KTMPVN13766 | 2018 | No | Nha Thuoc Chau Ngoc | Ho Chi Minh Province | Ho Chi Minh | 1 | Ben Nghe | Ho Chi Minh ,Nha Thuoc Chau Ngoc ,1, Ben Nghe | 10.77673 | 106.6999 | 106.6999,10.77673 | pharmacy | 2015-10-30T00:00:29 | 2015-10-30T00:00:29 | |||
KTMPVN13767 | 2018 | No | Nha Thuoc Tu Nhan 183 | Ho Chi Minh Province | Ho Chi Minh | 1 | Ngu Lao | Ho Chi Minh ,Nha Thuoc Tu Nhan 183 ,1, Ngu Lao | 10.766118 | 106.691269 | 106.691269,10.766118 | pharmacy | 2015-10-30T00:00:30 | 2015-10-30T00:00:30 | |||
KTMPVN13768 | 2018 | No | Hieu Thuoc So 12 | Ho Chi Minh Province | Ho Chi Minh | 1 | Ngu Lao | Ho Chi Minh ,Hieu Thuoc So 12 ,1, Ngu Lao | 10.76983 | 106.68842 | 106.68842,10.76983 | pharmacy | 2015-10-30T00:00:31 | 2015-10-30T00:00:31 | |||
KTMPVN13769 | 2018 | No | Nha Thuoc TOT (Nha Thuoc Thu Nhan Phuong Anh Cu) | Ho Chi Minh Province | Ho Chi Minh | 1 | Da Kao | Ho Chi Minh ,Nha Thuoc TOT (Nha Thuoc Thu Nhan Phuong Anh Cu) ,1, Da Kao | 10.792765 | 106.696252 | 106.696252,10.792765 | pharmacy | 2015-10-30T00:00:32 | 2015-10-30T00:00:32 | |||
KTMPVN13770 | 2018 | No | Hieu Thuoc So 7 | Ho Chi Minh Province | Ho Chi Minh | 1 | Ben Thanh | Ho Chi Minh ,Hieu Thuoc So 7 ,1, Ben Thanh | 10.773284 | 106.698318 | 106.698318,10.773284 | pharmacy | 2015-10-30T00:00:33 | 2015-10-30T00:00:33 | |||
KTMPVN13771 | 2018 | No | Nha Thuoc Ngoc Duyen | Ho Chi Minh Province | Ho Chi Minh | 1 | Da Kao | Ho Chi Minh ,Nha Thuoc Ngoc Duyen ,1, Da Kao | 10.791822 | 106.700154 | 106.700154,10.791822 | pharmacy | 2015-10-30T00:00:34 | 2015-10-30T00:00:34 | |||
KTMPVN13772 | 2018 | No | Nha Thuoc Hong An | Ho Chi Minh Province | Ho Chi Minh | 1 | Cau Kho | Ho Chi Minh ,Nha Thuoc Hong An ,1, Cau Kho | 10.758499 | 106.688412 | 106.688412,10.758499 | pharmacy | 2015-10-30T00:00:35 | 2015-10-30T00:00:35 | |||
KTMPVN13773 | 2018 | No | Nha Thuoc Minh Tam | Ho Chi Minh Province | Ho Chi Minh | 10 | 8 | Ho Chi Minh ,Nha Thuoc Minh Tam ,10, 8 | 10.765563 | 106.666625 | 106.666625,10.765563 | pharmacy | 2015-10-30T00:00:36 | 2015-10-30T00:00:36 | |||
KTMPVN13774 | 2018 | No | Nha Thuoc Hong Hoa | Ho Chi Minh Province | Ho Chi Minh | 1 | Cau Kho | Ho Chi Minh ,Nha Thuoc Hong Hoa ,1, Cau Kho | 10.75854 | 106.688171 | 106.688171,10.75854 | pharmacy | 2015-10-30T00:00:37 | 2015-10-30T00:00:37 | |||
KTMPVN13775 | 2018 | No | Nha Thuoc Nguyen Chau | Ho Chi Minh Province | Ho Chi Minh | 1 | Tan Dinh | Ho Chi Minh ,Nha Thuoc Nguyen Chau ,1, Tan Dinh | 10.791851 | 106.690237 | 106.690237,10.791851 | pharmacy | 2015-10-30T00:00:38 | 2015-10-30T00:00:38 | |||
KTMPVN13776 | 2018 | No | Nha Thuoc So 12 | Ho Chi Minh Province | Ho Chi Minh | 3 | 4 | Ho Chi Minh ,Nha Thuoc So 12 ,3, 4 | 10.774042 | 106.680621 | 106.680621,10.774042 | pharmacy | 2015-10-30T00:00:39 | 2015-10-30T00:00:39 | |||
KTMPVN13777 | 2018 | No | Nha Thuoc Thai Hoa | Ho Chi Minh Province | Ho Chi Minh | 3 | 11 | Ho Chi Minh ,Nha Thuoc Thai Hoa ,3, 11 | 10.783241 | 106.671699 | 106.671699,10.783241 | pharmacy | 2015-10-30T00:00:40 | 2015-10-30T00:00:40 | |||
KTMPVN13778 | 2018 | No | Nha Thuoc Thanh Mau | Ho Chi Minh Province | Ho Chi Minh | 3 | 4 | Ho Chi Minh ,Nha Thuoc Thanh Mau ,3, 4 | 10.77491 | 106.68202 | 106.68202,10.77491 | pharmacy | 2015-10-30T00:00:41 | 2015-10-30T00:00:41 | |||
KTMPVN13779 | 2018 | No | Nha Thuoc Tuan Ngoc | Ho Chi Minh Province | Ho Chi Minh | 3 | 9 | Ho Chi Minh ,Nha Thuoc Tuan Ngoc ,3, 9 | 10.78153 | 106.6817 | 106.6817,10.78153 | pharmacy | 2015-10-30T00:00:42 | 2015-10-30T00:00:42 | |||
KTMPVN13780 | 2018 | No | Hieu Thuoc So 11 | Ho Chi Minh Province | Ho Chi Minh | 3 | 8 | Ho Chi Minh ,Hieu Thuoc So 11 ,3, 8 | 10.787781 | 106.688404 | 106.688404,10.787781 | pharmacy | 2015-10-30T00:00:43 | 2015-10-30T00:00:43 | |||
KTMPVN13781 | 2018 | No | Nha Thuoc Thu Nhan Ngoc Lan | Ho Chi Minh Province | Ho Chi Minh | 3 | 2 | Ho Chi Minh ,Nha Thuoc Thu Nhan Ngoc Lan ,3, 2 | 10.768066 | 106.680382 | 106.680382,10.768066 | pharmacy | 2015-10-30T00:00:44 | 2015-10-30T00:00:44 | |||
KTMPVN13782 | 2018 | No | Nha Thuoc Thanh Tuan | Ho Chi Minh Province | Ho Chi Minh | 3 | 4 | Ho Chi Minh ,Nha Thuoc Thanh Tuan ,3, 4 | 10.77394 | 106.68058 | 106.68058,10.77394 | pharmacy | 2015-10-30T00:00:45 | 2015-10-30T00:00:45 | |||
KTMPVN13783 | 2018 | No | Nha Thuoc Duc Tri | Ho Chi Minh Province | Ho Chi Minh | 3 | 14 | Ho Chi Minh ,Nha Thuoc Duc Tri ,3, 14 | 10.789509 | 106.67473 | 106.67473,10.789509 | pharmacy | 2015-10-30T00:00:46 | 2015-10-30T00:00:46 | |||
KTMPVN13784 | 2018 | No | Nha Thuoc Huong Giang | Ho Chi Minh Province | Ho Chi Minh | 3 | 1 | Ho Chi Minh ,Nha Thuoc Huong Giang ,3, 1 | 10.768096 | 106.679144 | 106.679144,10.768096 | pharmacy | 2015-10-30T00:00:47 | 2015-10-30T00:00:47 | |||
KTMPVN13785 | 2018 | No | Nha Thuoc Thu Nhan Yen Anh | Ho Chi Minh Province | Ho Chi Minh | 3 | 7 | Ho Chi Minh ,Nha Thuoc Thu Nhan Yen Anh ,3, 7 | 10.776225 | 106.685877 | 106.685877,10.776225 | pharmacy | 2015-10-30T00:00:48 | 2015-10-30T00:00:48 | |||
KTMPVN13786 | 2018 | No | Nha Thuoc Thanh Xuan | Ho Chi Minh Province | Ho Chi Minh | 3 | 2 | Ho Chi Minh ,Nha Thuoc Thanh Xuan ,3, 2 | 10.765755 | 106.681907 | 106.681907,10.765755 | pharmacy | 2015-10-30T00:00:49 | 2015-10-30T00:00:49 | |||
KTMPVN13787 | 2018 | No | Nha Thuoc So 8 | Ho Chi Minh Province | Ho Chi Minh | 4 | https://www.google.com/maps/place/Nh%C3%A0+Thu%E1%BB%91c+Th%C3%A1nh+M%E1%BA%ABu/@10.7752568,106.6796974,17z/data=!3m1!4b1!4m5!3m4!1s0x31752f269f4237c9:0xce4e8c80dc54d195!8m2!3d10.7752568!4d106.6818861 | Ho Chi Minh ,Nha Thuoc So 8 ,4, https://www.google.com/maps/place/Nh%C3%A0+Thu%E1%BB%91c+Th%C3%A1nh+M%E1%BA%ABu/@10.7752568,106.6796974,17z/data=!3m1!4b1!4m5!3m4!1s0x31752f269f4237c9:0xce4e8c80dc54d195!8m2!3d10.7752568!4d106.6818861 | 10.760427 | 106.696539 | 106.696539,10.760427 | pharmacy | 2015-10-30T00:00:50 | 2015-10-30T00:00:50 | |||
KTMPVN13788 | Other periods | No | Nha Thuoc Huu Nghi 1 | Ho Chi Minh Province | Ho Chi Minh | 4 | 12 | Ho Chi Minh ,Nha Thuoc Huu Nghi 1 ,4, 12 | 10.764252 | 106.702627 | 106.702627,10.764252 | pharmacy | 2015-10-30T00:00:51 | 2015-10-30T00:00:51 | |||
KTMPVN13789 | Other periods | No | Nha Thuoc Duc Minh | Ho Chi Minh Province | Ho Chi Minh | 5 | 1 | Ho Chi Minh ,Nha Thuoc Duc Minh ,5, 1 | 10.754157 | 106.677553 | 106.677553,10.754157 | pharmacy | 2015-10-30T00:00:52 | 2015-10-30T00:00:52 | |||
KTMPVN13790 | Other periods | No | Nha Thuoc Thanh Chau | Ho Chi Minh Province | Ho Chi Minh | 6 | 14 | Ho Chi Minh ,Nha Thuoc Thanh Chau ,6, 14 | 10.75501 | 106.63746 | 106.63746,10.75501 | pharmacy | 2015-10-30T00:00:53 | 2015-10-30T00:00:53 | |||
KTMPVN13791 | Other periods | No | Nha Thuoc 126 | Ho Chi Minh Province | Ho Chi Minh | 11 | 3 | Ho Chi Minh ,Nha Thuoc 126 ,11, 3 | 10.758647 | 106.638158 | 106.638158,10.758647 | pharmacy | 2015-10-30T00:00:54 | 2015-10-30T00:00:54 | |||
KTMPVN13792 | Other periods | No | Nha Thuoc Thu Nhan Thanh Dat | Ho Chi Minh Province | Ho Chi Minh | 6 | 11 | Ho Chi Minh ,Nha Thuoc Thu Nhan Thanh Dat ,6, 11 | 10.747032 | 106.635195 | 106.635195,10.747032 | pharmacy | 2015-10-30T00:00:55 | 2015-10-30T00:00:55 | |||
KTMPVN13793 | Other periods | No | Nha Thuoc Thanh Nguyen | Ho Chi Minh Province | Ho Chi Minh | 6 | 13 | Ho Chi Minh ,Nha Thuoc Thanh Nguyen ,6, 13 | 10.755671 | 106.632925 | 106.632925,10.755671 | pharmacy | 2015-10-30T00:00:56 | 2015-10-30T00:00:56 | |||
KTMPVN13794 | Other periods | No | Nha Thuoc Minh Phung | Ho Chi Minh Province | Ho Chi Minh | 6 | 6 | Ho Chi Minh ,Nha Thuoc Minh Phung ,6, 6 | 10.752336 | 106.643394 | 106.643394,10.752336 | pharmacy | 2015-10-30T00:00:57 | 2015-10-30T00:00:57 | |||
KTMPVN13795 | 9085 | Other periods | yes | Nha Thuoc Minh Phuong | Ho Chi Minh Province | Ho Chi Minh | 8 | 3 | Ho Chi Minh ,Nha Thuoc Minh Phuong ,8, 3 | 10.74532 | 106.68333 | 106.68333,10.74532 | pharmacy | 2015-10-30T00:00:58 | 2015-10-30T00:00:58 | ||
KTMPVN13796 | Other periods | No | Nha Thuoc Tay Hong Duc | Ho Chi Minh Province | Ho Chi Minh | 8 | 8 | Ho Chi Minh ,Nha Thuoc Tay Hong Duc ,8, 8 | 10.750535 | 106.680275 | 106.680275,10.750535 | pharmacy | 2015-10-30T00:00:59 | 2015-10-30T00:00:59 | |||
KTMPVN13797 | Other periods | No | Nha Thuoc Minh Quyen | Ho Chi Minh Province | Ho Chi Minh | 10 | 10 | Ho Chi Minh ,Nha Thuoc Minh Quyen ,10, 10 | 10.769736 | 106.675441 | 106.675441,10.769736 | pharmacy | 2015-10-30T00:00:60 | 2015-10-30T00:00:60 | |||
KTMPVN13798 | 9140 | Never covered | yes | Nha Thuoc Quoc Long | Ho Chi Minh Province | Ho Chi Minh | Tan Binh | 6 | Ho Chi Minh ,Nha Thuoc Quoc Long ,Tan Binh, 6 | 10.786809 | 106.664231 | 106.664231,10.786809 | pharmacy | 2015-10-30T00:00:61 | 2015-10-30T00:00:61 | ||
KTMPVN13799 | Never covered | No | Nha Thuoc Minh Hoa | Ho Chi Minh Province | Ho Chi Minh | 10 | 13 | Ho Chi Minh ,Nha Thuoc Minh Hoa ,10, 13 | 10.778452 | 106.672677 | 106.672677,10.778452 | pharmacy | 2015-10-30T00:00:62 | 2015-10-30T00:00:62 | |||
KTMPVN13800 | Never covered | No | Nha Thuoc Hai Thuong | Ho Chi Minh Province | Ho Chi Minh | 10 | 9 | Ho Chi Minh ,Nha Thuoc Hai Thuong ,10, 9 | 10.767884 | 106.670103 | 106.670103,10.767884 | pharmacy | 2015-10-30T00:00:63 | 2015-10-30T00:00:63 | |||
KTMPVN13801 | Never covered | No | Nha Thuoc Hanh Nhan | Ho Chi Minh Province | Ho Chi Minh | 10 | 9 | Ho Chi Minh ,Nha Thuoc Hanh Nhan ,10, 9 | 10.767838 | 106.66964 | 106.66964,10.767838 | pharmacy | 2015-10-30T00:00:64 | 2015-10-30T00:00:64 | |||
KTMPVN13802 | 8035 | Never covered | yes | Nha Thuoc Nguyen | Ho Chi Minh Province | Ho Chi Minh | 11 | 3 | Ho Chi Minh ,Nha Thuoc Nguyen ,11, 3 | 10.7621985 | 106.6420012 | 106.6420012,10.7621985 | pharmacy | 2015-10-30T00:00:65 | 2015-10-30T00:00:65 | ||
KTMPVN13803 | 8032 | Never covered | yes | Nha Thuoc Thu Nhan Van Nga | Ho Chi Minh Province | Ho Chi Minh | 11 | 10 | Ho Chi Minh ,Nha Thuoc Thu Nhan Van Nga ,11, 10 | 10.759683 | 106.644413 | 106.644413,10.759683 | pharmacy | 2015-10-30T00:00:66 | 2015-10-30T00:00:66 | ||
KTMPVN13804 | Never covered | No | Nha Thuoc Tu Nhan Thanh Dung | Ho Chi Minh Province | Ho Chi Minh | 11 | 2 | Ho Chi Minh ,Nha Thuoc Tu Nhan Thanh Dung ,11, 2 | 10.757347 | 106.647778 | 106.647778,10.757347 | pharmacy | 2015-10-30T00:00:67 | 2015-10-30T00:00:67 | |||
KTMPVN13805 | Never covered | No | Nha Thuoc Bao Tram | Ho Chi Minh Province | Ho Chi Minh | 9 | Phuoc Binh | Ho Chi Minh ,Nha Thuoc Bao Tram ,9, Phuoc Binh | 10.816884 | 106.773838 | 106.773838,10.816884 | pharmacy | 2015-10-30T00:00:68 | 2015-10-30T00:00:68 | |||
KTMPVN13806 | 8322 | Never covered | yes | Nha Thuoc Thu Nhan An Thoi | Ho Chi Minh Province | Ho Chi Minh | 11 | 1 | Ho Chi Minh ,Nha Thuoc Thu Nhan An Thoi ,11, 1 | 10.758315 | 106.641652 | 106.641652,10.758315 | pharmacy | 2015-10-30T00:00:69 | 2015-10-30T00:00:69 | ||
KTMPVN13807 | Never covered | No | Nha Thuoc Tay Khanh An | Ho Chi Minh Province | Ho Chi Minh | 11 | 1 | Ho Chi Minh ,Nha Thuoc Tay Khanh An ,11, 1 | 10.757164 | 106.641855 | 106.641855,10.757164 | pharmacy | 2015-10-30T00:00:70 | 2015-10-30T00:00:70 | |||
KTMPVN13808 | Never covered | No | Nha Thuoc Thu Nhan 340C | Ho Chi Minh Province | Ho Chi Minh | 11 | 2 | Ho Chi Minh ,Nha Thuoc Thu Nhan 340C ,11, 2 | 10.75818 | 106.643986 | 106.643986,10.75818 | pharmacy | 2015-10-30T00:00:71 | 2015-10-30T00:00:71 | |||
KTMPVN13809 | 16476 | Never covered | yes | Nha Thuoc Thien Phuoc | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 7 | Ho Chi Minh ,Nha Thuoc Thien Phuoc ,Binh Thanh, 7 | 10.810993 | 106.691252 | 106.691252,10.810993 | pharmacy | 2015-10-30T00:00:72 | 2015-10-30T00:00:72 | ||
KTMPVN13810 | Never covered | No | Nha Thuoc Van Kiep | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 3 | Ho Chi Minh ,Nha Thuoc Van Kiep ,Binh Thanh, 3 | 10.799327 | 106.693638 | 106.693638,10.799327 | pharmacy | 2015-10-30T00:00:73 | 2015-10-30T00:00:73 | |||
KTMPVN13811 | Never covered | No | Nha Thuoc Van Dung | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 26 | Ho Chi Minh ,Nha Thuoc Van Dung ,Binh Thanh, 26 | 10.814319 | 106.712681 | 106.712681,10.814319 | pharmacy | 2015-10-30T00:00:74 | 2015-10-30T00:00:74 | |||
KTMPVN13812 | Never covered | No | Nha Thuoc Thanh Nhu | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 6 | Ho Chi Minh ,Nha Thuoc Thanh Nhu ,Binh Thanh, 6 | 10.806628 | 106.689928 | 106.689928,10.806628 | pharmacy | 2015-10-30T00:00:75 | 2015-10-30T00:00:75 | |||
KTMPVN13813 | Never covered | No | Nha Thuoc Thanh Phuong | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 12 | Ho Chi Minh ,Nha Thuoc Thanh Phuong ,Binh Thanh, 12 | 10.815422 | 106.695324 | 106.695324,10.815422 | pharmacy | 2015-10-30T00:00:76 | 2015-10-30T00:00:76 | |||
KTMPVN13814 | Never covered | No | Nha Thuoc Tien Thinh | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 25 | Ho Chi Minh ,Nha Thuoc Tien Thinh ,Binh Thanh, 25 | 10.809141 | 106.713793 | 106.713793,10.809141 | pharmacy | 2015-10-30T00:00:77 | 2015-10-30T00:00:77 | |||
KTMPVN13815 | Never covered | No | Hieu Thuoc So 21 | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 17 | Ho Chi Minh ,Hieu Thuoc So 21 ,Binh Thanh, 17 | 10.793925 | 106.708226 | 106.708226,10.793925 | pharmacy | 2015-10-30T00:00:78 | 2015-10-30T00:00:78 | |||
KTMPVN13816 | Never covered | No | Nha Thuoc Tu Nhan Hong Thinh | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 5 | Ho Chi Minh ,Nha Thuoc Tu Nhan Hong Thinh ,Binh Thanh, 5 | 10.808786 | 106.688332 | 106.688332,10.808786 | pharmacy | 2015-10-30T00:00:79 | 2015-10-30T00:00:79 | |||
KTMPVN13817 | Never covered | No | Nha Thuoc Quynh Lien | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 26 | Ho Chi Minh ,Nha Thuoc Quynh Lien ,Binh Thanh, 26 | 10.810892 | 106.713491 | 106.713491,10.810892 | pharmacy | 2015-10-30T00:00:80 | 2015-10-30T00:00:80 | |||
KTMPVN13818 | 8881 | Never covered | yes | Nha Thuoc Binh Dan | Ho Chi Minh Province | Ho Chi Minh | Go Vap | 16 | Ho Chi Minh ,Nha Thuoc Binh Dan ,Go Vap, 16 | 10.850073 | 106.664382 | 106.664382,10.850073 | pharmacy | 2015-10-30T00:00:81 | 2015-10-30T00:00:81 | ||
KTMPVN13819 | Never covered | No | Nha Thuoc Tu Nhan Phung Hoang | Ho Chi Minh Province | Ho Chi Minh | Go Vap | 10 | Ho Chi Minh ,Nha Thuoc Tu Nhan Phung Hoang ,Go Vap, 10 | 10.834031 | 106.665102 | 106.665102,10.834031 | pharmacy | 2015-10-30T00:00:82 | 2015-10-30T00:00:82 | |||
KTMPVN13820 | Never covered | No | Nha Thuoc Hoa An | Ho Chi Minh Province | Ho Chi Minh | Go Vap | 1 | Ho Chi Minh ,Nha Thuoc Hoa An ,Go Vap, 1 | 10.818045 | 106.68914 | 106.68914,10.818045 | pharmacy | 2015-10-30T00:00:83 | 2015-10-30T00:00:83 | |||
KTMPVN13821 | 8502 | Never covered | yes | Nha Thuoc Nhu Ha | Ho Chi Minh Province | Ho Chi Minh | Go Vap | 4 | Ho Chi Minh ,Nha Thuoc Nhu Ha ,Go Vap, 4 | 10.82329 | 106.685701 | 106.685701,10.82329 | pharmacy | 2015-10-30T00:00:84 | 2015-10-30T00:00:84 | ||
KTMPVN13822 | Never covered | No | Nha Thuoc Minh Trang | Ho Chi Minh Province | Ho Chi Minh | Go Vap | 5 | Ho Chi Minh ,Nha Thuoc Minh Trang ,Go Vap, 5 | 10.822742 | 106.689126 | 106.689126,10.822742 | pharmacy | 2015-10-30T00:00:85 | 2015-10-30T00:00:85 | |||
KTMPVN13823 | Never covered | No | Nha Thuoc Tu Nhan Cam Anh | Ho Chi Minh Province | Ho Chi Minh | Go Vap | 17 | Ho Chi Minh ,Nha Thuoc Tu Nhan Cam Anh ,Go Vap, 17 | 10.836752 | 106.675649 | 106.675649,10.836752 | pharmacy | 2015-10-30T00:00:86 | 2015-10-30T00:00:86 | |||
KTMPVN13824 | Never covered | No | Nha Thuoc Tan My | Ho Chi Minh Province | Ho Chi Minh | Hoc Mon | Ba Diem | Ho Chi Minh ,Nha Thuoc Tan My ,Hoc Mon, Ba Diem | 10.855955 | 106.606553 | 106.606553,10.855955 | pharmacy | 2015-10-30T00:00:87 | 2015-10-30T00:00:87 | |||
KTMPVN13825 | Never covered | No | Nha Thuoc Ngoc Lan | Ho Chi Minh Province | Ho Chi Minh | 12 | Tan Thoi Nhat | Ho Chi Minh ,Nha Thuoc Ngoc Lan ,12, Tan Thoi Nhat | 10.840756 | 106.616288 | 106.616288,10.840756 | pharmacy | 2015-10-30T00:00:88 | 2015-10-30T00:00:88 | |||
KTMPVN13826 | Never covered | No | Nha Thuoc Tu Nhan Nhat Phuong | Ho Chi Minh Province | Ho Chi Minh | 7 | Phu Thuan | Ho Chi Minh ,Nha Thuoc Tu Nhan Nhat Phuong ,7, Phu Thuan | 10.736643 | 106.730591 | 106.730591,10.736643 | pharmacy | 2015-10-30T00:00:89 | 2015-10-30T00:00:89 | |||
KTMPVN13827 | Never covered | No | Nha Thuoc Tu Nhan Ngoc Minh | Ho Chi Minh Province | Ho Chi Minh | 7 | Tan Thuan Tay | Ho Chi Minh ,Nha Thuoc Tu Nhan Ngoc Minh ,7, Tan Thuan Tay | 10.75196 | 106.728488 | 106.728488,10.75196 | pharmacy | 2015-10-30T00:00:90 | 2015-10-30T00:00:90 | |||
KTMPVN13828 | 16072 | Never covered | yes | Nha Thuoc Tu Nhan Ngoc Nhung | Ho Chi Minh Province | Ho Chi Minh | Phu Nhuan | 3 | Ho Chi Minh ,Nha Thuoc Tu Nhan Ngoc Nhung ,Phu Nhuan, 3 | 10.80379 | 106.681164 | 106.681164,10.80379 | pharmacy | 2015-10-30T00:00:91 | 2015-10-30T00:00:91 | ||
KTMPVN13829 | Never covered | No | Nha Thuoc Tu Nhan SO 4 | Ho Chi Minh Province | Ho Chi Minh | Phu Nhuan | 8 | Ho Chi Minh ,Nha Thuoc Tu Nhan SO 4 ,Phu Nhuan, 8 | 10.797726 | 106.672923 | 106.672923,10.797726 | pharmacy | 2015-10-30T00:00:92 | 2015-10-30T00:00:92 | |||
KTMPVN13830 | Never covered | No | Nha Thuoc Hoang Diem | Ho Chi Minh Province | Ho Chi Minh | Phu Nhuan | 10 | Ho Chi Minh ,Nha Thuoc Hoang Diem ,Phu Nhuan, 10 | 10.795421 | 106.669673 | 106.669673,10.795421 | pharmacy | 2015-10-30T00:00:93 | 2015-10-30T00:00:93 | |||
KTMPVN13831 | Never covered | No | Nha Thuoc Thanh Tra | Ho Chi Minh Province | Ho Chi Minh | Phu Nhuan | 17 | Ho Chi Minh ,Nha Thuoc Thanh Tra ,Phu Nhuan, 17 | 10.793208 | 106.681997 | 106.681997,10.793208 | pharmacy | 2015-10-30T00:00:94 | 2015-10-30T00:00:94 | |||
KTMPVN13832 | Never covered | No | Nha Thuoc Kim Hoa | Ho Chi Minh Province | Ho Chi Minh | Binh Thanh | 5 | Ho Chi Minh ,Nha Thuoc Kim Hoa ,Binh Thanh, 5 | 10.807825 | 106.68662 | 106.68662,10.807825 | pharmacy | 2015-10-30T00:00:95 | 2015-10-30T00:00:95 | |||
KTMPVN13833 | Never covered | No | Nha Thuoc Quang Minh | Ho Chi Minh Province | Ho Chi Minh | Phu Nhuan | 10 | Ho Chi Minh ,Nha Thuoc Quang Minh ,Phu Nhuan, 10 | 10.793424 | 106.67244 | 106.67244,10.793424 | pharmacy | 2015-10-30T00:00:96 | 2015-10-30T00:00:96 | |||
KTMPVN13834 | Never covered | No | Nha Thuoc Hung | Ho Chi Minh Province | Ho Chi Minh | Phu Nhuan | 13 | Ho Chi Minh ,Nha Thuoc Hung ,Phu Nhuan, 13 | 10.79184 | 106.671506 | 106.671506,10.79184 | pharmacy | 2015-10-30T00:00:97 | 2015-10-30T00:00:97 | |||
KTMPVN13835 | Never covered | No | Nha Thuoc Chan Tu | Ho Chi Minh Province | Ho Chi Minh | Tan Binh | 13 | Ho Chi Minh ,Nha Thuoc Chan Tu ,Tan Binh, 13 | 10.801295 | 106.63786 | 106.63786,10.801295 | pharmacy | 2015-10-30T00:00:98 | 2015-10-30T00:00:98 | |||
KTMPVN00001 | 10334 | Other periods | yes | NT DAPHARCO SO 9 | DA NANG | THANH KHE | 16.0701763 | 108.2135215 | 108.2135215,16.0701763 | pharmacy | 2015-10-30T00:00:99 | 2015-10-30T00:00:99 | |||||
KTMPVN00002 | 6312 | Other periods | yes | NT TRUNG VIET | DA NANG | HAI CHAU | 16.0691608 | 108.2137037 | 108.2137037,16.0691608 | pharmacy | 2015-10-30T00:00:100 | 2015-10-30T00:00:100 | |||||
KTMPVN00004 | 8860 | Other periods | yes | NT DAPHARCO 11 | DA NANG | HAI CHAU | 16.044735 | 108.208859 | 108.208859,16.044735 | pharmacy | 2015-10-30T00:00:101 | 2015-10-30T00:00:101 | |||||
KTMPVN00005 | 8911 | Other periods | yes | NT THUY MINH | DA NANG | SON TRA | 16.0640144 | 108.2337785 | 108.2337785,16.0640144 | pharmacy | 2015-10-30T00:00:102 | 2015-10-30T00:00:102 | |||||
KTMPVN00012 | 8859 | Other periods | yes | NT NGUYET | DA NANG | THANH KHE | 16.0668663 | 108.2120472 | 108.2120472,16.0668663 | pharmacy | 2015-10-30T00:00:103 | 2015-10-30T00:00:103 | |||||
KTMPVN00016 | 9236 | Other periods | yes | HT DAPHARCO 52 | DA NANG | HAI CHAU | 16.0507116 | 108.2156497 | 108.2156497,16.0507116 | pharmacy | 2015-10-30T00:00:104 | 2015-10-30T00:00:104 | |||||
KTMPVN00039 | 9184 | Other periods | yes | NT PHUOC THANH | DA NANG | THANH KHE | 16.0737228 | 108.1771444 | 108.1771444,16.0737228 | pharmacy | 2015-10-30T00:00:105 | 2015-10-30T00:00:105 | |||||
KTMPVN00059 | 13906 | Other periods | yes | NT HUU PHUC (NT DAPHARCO 63 CU) | DA NANG | HAI CHAU | 16.07239 | 108.21793 | 108.21793,16.07239 | pharmacy | 2015-10-30T00:00:106 | 2015-10-30T00:00:106 | |||||
KTMPVN00061 | 14037 | Other periods | yes | NT HOANG HONG DUC | DA NANG | HAI CHAU | 16.0371987 | 108.2202368 | 108.2202368,16.0371987 | pharmacy | 2015-10-30T00:00:107 | 2015-10-30T00:00:107 | |||||
KTMPVN00062 | 8275 | Other periods | yes | NT DAPHARCO 167 (135 CU) | DA NANG | THANH KHE | 16.0648981 | 108.1883484 | 108.1883484,16.0648981 | pharmacy | 2015-10-30T00:00:108 | 2015-10-30T00:00:108 | |||||
KTMPVN00068 | 6804 | Other periods | yes | NT THANH HUYEN | DA NANG | THANH KHE | 16.05455 | 108.20878 | 108.20878,16.05455 | pharmacy | 2015-10-30T00:00:109 | 2015-10-30T00:00:109 | |||||
KTMPVN00085 | 14069 | Other periods | yes | HT DAPHARCO 110 | DA NANG | NGU HANH SON | 16.0465658 | 108.2385628 | 108.2385628,16.0465658 | pharmacy | 2015-10-30T00:00:110 | 2015-10-30T00:00:110 | |||||
KTMPVN00092 | 8900 | Other periods | yes | NT LIEN VIET | DA NANG | THANH KHE | 16.0658643 | 108.2018943 | 108.201894299999,16.0658642999999 | pharmacy | 2015-10-30T00:00:111 | 2015-10-30T00:00:111 | |||||
KTMPVN00098 | 6927 | Other periods | yes | NT THANH TAM | DA NANG | THANH KHE | 16.0742044 | 108.1774508 | 108.1774508,16.0742044 | pharmacy | 2015-10-30T00:00:112 | 2015-10-30T00:00:112 | |||||
KTMPVN00100 | 9330 | Other periods | yes | NT HA PHUC | DA NANG | THANH KHE | 16.0605497 | 108.1894719 | 108.1894719,16.0605497 | pharmacy | 2015-10-30T00:00:113 | 2015-10-30T00:00:113 | |||||
KTMPVN00103 | 10221 | Other periods | yes | NT ANH THY | DA NANG | HAI CHAU | 16.0455456 | 108.2212099 | 108.2212099,16.0455456 | pharmacy | 2015-10-30T00:00:114 | 2015-10-30T00:00:114 | |||||
KTMPVN00106 | 13991 | Other periods | yes | NT THANH VINH 2 | DA NANG | LIEN CHIEU | 16.0769079 | 108.1482868 | 108.1482868,16.0769079 | pharmacy | 2015-10-30T00:00:115 | 2015-10-30T00:00:115 | |||||
KTMPVN00123 | 8224 | Other periods | yes | NT NHAT AN (THUAN THANH cU) | DA NANG | THANH KHE | 16.059067 | 108.1857192 | 108.1857192,16.059067 | pharmacy | 2015-10-30T00:00:116 | 2015-10-30T00:00:116 | |||||
KTMPVN00124 | 9277 | Other periods | yes | NT NGOC ANH | DA NANG | HAI CHAU | 16.0363914 | 108.2222811 | 108.222281099999,16.0363914 | pharmacy | 2015-10-30T00:00:117 | 2015-10-30T00:00:117 | |||||
KTMPVN00132 | 14106 | Other periods | yes | NT THANH VINH 3 | DA NANG | THANH KHE | 16.0663311 | 108.188499 | 108.188499,16.0663311 | pharmacy | 2015-10-30T00:00:118 | 2015-10-30T00:00:118 | |||||
KTMPVN00136 | 14064 | Other periods | yes | NT TUYET TRINH | DA NANG | LIEN CHIEU | 16.0700492 | 108.1531162 | 108.1531162,16.0700492 | pharmacy | 2015-10-30T00:00:119 | 2015-10-30T00:00:119 | |||||
KTMPVN00145 | 14080 | Other periods | yes | NT THANH VINH 4 | DA NANG | LIEN CHIEU | 16.0721743 | 108.1391263 | 108.1391263,16.0721743 | pharmacy | 2015-10-30T00:00:120 | 2015-10-30T00:00:120 | |||||
KTMPVN00165 | 8821 | Other periods | yes | NT MAI THAO | CAN THO | NINH KIEU | 10.031408 | 105.785629 | 105.785629,10.031408 | pharmacy | 2015-10-30T00:00:121 | 2015-10-30T00:00:121 | |||||
KTMPVN00168 | 8390 | Other periods | yes | NT HOANG YEN | CAN THO | NINH KIEU | 10.0344663 | 105.7862213 | 105.7862213,10.0344663 | pharmacy | 2015-10-30T00:00:122 | 2015-10-30T00:00:122 | |||||
KTMPVN00189 | 10311 | Other periods | yes | NT LAN HUONG | CAN THO | NINH KIEU | 10.026877 | 105.769426 | 105.769426,10.026877 | pharmacy | 2015-10-30T00:00:123 | 2015-10-30T00:00:123 | |||||
KTMPVN00195 | 16046 | Other periods | yes | NT HAI YEN | CAN THO | NINH KIEU | 10.029123 | 105.779347 | 105.779347,10.029123 | pharmacy | 2015-10-30T00:00:124 | 2015-10-30T00:00:124 | |||||
KTMPVN00196 | 8818 | Other periods | yes | NT HUYNH LOC | CAN THO | NINH KIEU | 10.045091 | 105.779402 | 105.779402,10.045091 | pharmacy | 2015-10-30T00:00:125 | 2015-10-30T00:00:125 | |||||
KTMPVN00222 | 17258 | Other periods | yes | NT KIM PHUOC | CAN THO | NINH KIEU | 10.032784 | 105.784264 | 105.784264,10.032784 | pharmacy | 2015-10-30T00:00:126 | 2015-10-30T00:00:126 | |||||
KTMPVN00224 | 6711 | Other periods | yes | NT THIEN THANH | CAN THO | NINH KIEU | 10.0279099 | 105.7803011 | 105.7803011,10.0279099 | pharmacy | 2015-10-30T00:00:127 | 2015-10-30T00:00:127 | |||||
KTMPVN00255 | 8081 | Other periods | yes | NT NGO QUYEN | CAN THO | NINH KIEU | 10.0339615 | 105.7874734 | 105.7874734,10.0339615 | pharmacy | 2015-10-30T00:00:128 | 2015-10-30T00:00:128 | |||||
KTMPVN00290 | 9250 | Other periods | yes | NT THANH MAI | HA NOI | HOAN KIEM | 21.03158 | 105.85752 | 105.85752,21.03158 | pharmacy | 2015-10-30T00:00:129 | 2015-10-30T00:00:129 | |||||
KTMPVN00292 | 17512 | Other periods | yes | NT 59 QUOC TU GIAM | HA NOI | DONG DA | 21.02718 | 105.835871 | 105.835871,21.02718 | pharmacy | 2015-10-30T00:00:130 | 2015-10-30T00:00:130 | |||||
KTMPVN00295 | 16238 | Other periods | yes | NT 37E VAN MIEU | HA NOI | DONG DA | 21.027982 | 105.8360182 | 105.8360182,21.027982 | pharmacy | 2015-10-30T00:00:131 | 2015-10-30T00:00:131 | |||||
KTMPVN00302 | 16223 | Other periods | yes | NT 31B VAN MIEU | HA NOI | DONG DA | 21.028956 | 105.836312 | 105.836312,21.028956 | pharmacy | 2015-10-30T00:00:132 | 2015-10-30T00:00:132 | |||||
KTMPVN00305 | 11788 | Other periods | yes | NT DUC CHUNG | HA NOI | DONG DA | 21.00407 | 105.84015 | 105.84015,21.00407 | pharmacy | 2015-10-30T00:00:133 | 2015-10-30T00:00:133 | |||||
KTMPVN00306 | 7731 | Other periods | yes | NT CHUC HOA | HA NOI | LONG BIEN | 21.06243 | 105.89476 | 105.89476,21.06243 | pharmacy | 2015-10-30T00:00:134 | 2015-10-30T00:00:134 | |||||
KTMPVN00307 | 7699 | Other periods | yes | NT NAM ANH | HA NOI | HAI BA TRUNG | 21.01871 | 105.855429 | 105.855429,21.01871 | pharmacy | 2015-10-30T00:00:135 | 2015-10-30T00:00:135 | |||||
KTMPVN00309 | 7464 | Other periods | yes | NT TAM CHINH | HA NOI | HAI BA TRUNG | 21.017776 | 105.855429 | 105.855429,21.017776 | pharmacy | 2015-10-30T00:00:136 | 2015-10-30T00:00:136 | |||||
KTMPVN00312 | 9274 | Other periods | yes | NT VIEN KIEM NGHIEM | HA NOI | HOAN KIEM | 21.024939 | 105.850553 | 105.850553,21.024939 | pharmacy | 2015-10-30T00:00:137 | 2015-10-30T00:00:137 | |||||
KTMPVN00314 | 4759 | Other periods | yes | NT MINH CHINH 42 | HA NOI | HOAN KIEM | 21.019387 | 105.847693 | 105.847693,21.019387 | pharmacy | 2015-10-30T00:00:138 | 2015-10-30T00:00:138 | |||||
KTMPVN00339 | 8645 | Other periods | yes | NT TU NHAN 531 AU CO | HA NOI | TAY HO | 21.0799779 | 105.8187801 | 105.8187801,21.0799779 | pharmacy | 2015-10-30T00:00:139 | 2015-10-30T00:00:139 | |||||
KTMPVN00340 | 6907 | Other periods | yes | NT MAI HOA | HA NOI | BA DINH | 21.0267974 | 105.8084287 | 105.8084287,21.0267974 | pharmacy | 2015-10-30T00:00:140 | 2015-10-30T00:00:140 | |||||
KTMPVN00360 | 7889 | Other periods | yes | NT HUONG GIANG | HA NOI | HAI BA TRUNG | 21.0013936 | 105.8413205 | 105.8413205,21.0013936 | pharmacy | 2015-10-30T00:00:141 | 2015-10-30T00:00:141 | |||||
KTMPVN00363 | 7682 | Other periods | yes | NT TRONG TAN 57 VIEN NHI | HA NOI | BA DINH | 21.0259543 | 105.8099353 | 105.8099353,21.0259543 | pharmacy | 2015-10-30T00:00:142 | 2015-10-30T00:00:142 | |||||
KTMPVN00366 | 9312 | Other periods | yes | NT TRUONG THO 48 | HA NOI | HOAN KIEM | 21.02459 | 105.84913 | 105.84913,21.02459 | pharmacy | 2015-10-30T00:00:143 | 2015-10-30T00:00:143 | |||||
KTMPVN00375 | 7748 | Other periods | yes | NT 95 | HA NOI | HAI BA TRUNG | 21.0028466 | 105.8581825 | 105.8581825,21.0028466 | pharmacy | 2015-10-30T00:00:144 | 2015-10-30T00:00:144 | |||||
KTMPVN00378 | 14871 | Other periods | yes | NT MINH HANH | HA NOI | HAI BA TRUNG | 21 | 105.841 | 105.841,21 | pharmacy | 2015-10-30T00:00:145 | 2015-10-30T00:00:145 | |||||
KTMPVN00385 | 6704 | Other periods | yes | NT PHAP SO 2 | HA NOI | HA DONG | 20.971325 | 105.775451 | 105.775451,20.971325 | pharmacy | 2015-10-30T00:00:146 | 2015-10-30T00:00:146 | |||||
KTMPVN00408 | 7750 | Other periods | yes | NT MINH HOP | HA NOI | HAI BA TRUNG | 21.000785 | 105.841457 | 105.841457,21.000785 | pharmacy | 2015-10-30T00:00:147 | 2015-10-30T00:00:147 | |||||
KTMPVN00411 | 9127 | Other periods | yes | NT PHUC HUNG | HA NOI | DONG DA | 21.00361 | 105.83483 | 105.83483,21.00361 | pharmacy | 2015-10-30T00:00:148 | 2015-10-30T00:00:148 | |||||
KTMPVN00468 | 7827 | Other periods | yes | NT NAM HA | HA NOI | HOANG MAI | 20.9909 | 105.83149 | 105.83149,20.9909 | pharmacy | 2015-10-30T00:00:149 | 2015-10-30T00:00:149 | |||||
KTMPVN00473 | 9212 | Other periods | yes | NT NGOC KHUE | HA NOI | DONG DA | 21.01525 | 105.82751 | 105.82751,21.01525 | pharmacy | 2015-10-30T00:00:150 | 2015-10-30T00:00:150 | |||||
KTMPVN00481 | 8544 | Other periods | yes | NT LE TRANG | HA NOI | CAU GIAY | 21.0442752 | 105.7944863 | 105.7944863,21.0442752 | pharmacy | 2015-10-30T00:00:151 | 2015-10-30T00:00:151 | |||||
KTMPVN00486 | 17504 | Other periods | yes | NT 37D VAN MIEU | HA NOI | DONG DA | 21.028383 | 105.836151 | 105.836151,21.028383 | pharmacy | 2015-10-30T00:00:152 | 2015-10-30T00:00:152 | |||||
KTMPVN00500 | 7841 | Other periods | yes | NT AN KHANH | HA NOI | HOANG MAI | 20.99039 | 105.83194 | 105.83194,20.99039 | pharmacy | 2015-10-30T00:00:153 | 2015-10-30T00:00:153 | |||||
KTMPVN00501 | 7896 | Other periods | yes | NT PHUC 1 | HA NOI | BA DINH | 21.026405 | 105.810339 | 105.810339,21.026405 | pharmacy | 2015-10-30T00:00:154 | 2015-10-30T00:00:154 | |||||
KTMPVN00512 | 14021 | Other periods | yes | NT THANH TU | HA NOI | THANH XUAN | 20.99426 | 105.81419 | 105.81419,20.99426 | pharmacy | 2015-10-30T00:00:155 | 2015-10-30T00:00:155 | |||||
KTMPVN00514 | 17934 | Other periods | yes | NT HOA BANG | HA NOI | CAU GIAY | 21.024011 | 105.784633 | 105.784633,21.024011 | pharmacy | 2015-10-30T00:00:156 | 2015-10-30T00:00:156 | |||||
KTMPVN00518 | 7792 | Other periods | yes | NT SO 17 PHO VIEN 103 | HA NOI | HA DONG | 20.968039 | 105.787077 | 105.787077,20.968039 | pharmacy | 2015-10-30T00:00:157 | 2015-10-30T00:00:157 | |||||
KTMPVN00521 | 7931 | Other periods | yes | NT THUY TIEN | HA NOI | BA DINH | 21.0260282 | 105.8099834 | 105.8099834,21.0260282 | pharmacy | 2015-10-30T00:00:158 | 2015-10-30T00:00:158 | |||||
KTMPVN00527 | 14832 | Other periods | yes | NT NHAT QUANG | HA NOI | DONG DA | 21.02774 | 105.8261 | 105.8261,21.02774 | pharmacy | 2015-10-30T00:00:159 | 2015-10-30T00:00:159 | |||||
KTMPVN00529 | 16215 | Other periods | yes | NT HOA MAI | HA NOI | HOAN KIEM | 21.031593 | 105.857298 | 105.857298,21.031593 | pharmacy | 2015-10-30T00:00:160 | 2015-10-30T00:00:160 | |||||
KTMPVN00580 | 9563 | Other periods | yes | NT HA PHUONG | HA NOI | HOAN KIEM | 21.0186 | 105.8621 | 105.8621,21.0186 | pharmacy | 2015-10-30T00:00:161 | 2015-10-30T00:00:161 | |||||
KTMPVN00590 | 7404 | Other periods | yes | NT ANH DUC | HA NOI | HOANG MAI | 20.992056 | 105.844087 | 105.844087,20.992056 | pharmacy | 2015-10-30T00:00:162 | 2015-10-30T00:00:162 | |||||
KTMPVN00592 | 16187 | Other periods | yes | NT MINH QUANG | HA NOI | THANH XUAN | 20.99584 | 105.815068 | 105.815068,20.99584 | pharmacy | 2015-10-30T00:00:163 | 2015-10-30T00:00:163 | |||||
KTMPVN00594 | 7100 | Other periods | yes | NT TRONG TAN 61 VIEN NHI | HA NOI | BA DINH | 21.02573 | 105.80985 | 105.80985,21.02573 | pharmacy | 2015-10-30T00:00:164 | 2015-10-30T00:00:164 | |||||
KTMPVN01261 | 15981 | Other periods | yes | NT THU LAN 2 | DA NANG | THANH KHE | 16.0689063 | 108.2014717 | 108.2014717,16.0689063 | pharmacy | 2015-10-30T00:00:165 | 2015-10-30T00:00:165 | |||||
KTMPVN01265 | 14061 | Other periods | yes | QT DAPHARCO 117 | DA NANG | LIEN CHIEU | 16.06983 | 108.15231 | 108.15231,16.06983 | pharmacy | 2015-10-30T00:00:166 | 2015-10-30T00:00:166 | |||||
KTMPVN01266 | 15979 | Other periods | yes | NT THU LAN 3 | DA NANG | HAI CHAU | 16.054656 | 108.211943 | 108.211943,16.054656 | pharmacy | 2015-10-30T00:00:167 | 2015-10-30T00:00:167 | |||||
KTMPVN01338 | 8909 | Other periods | yes | NT THINH DUC XUYEN | DA NANG | SON TRA | 16.0553794 | 108.2379836 | 108.2379836,16.0553794 | pharmacy | 2015-10-30T00:00:168 | 2015-10-30T00:00:168 | |||||
KTMPVN01339 | 8808 | Other periods | yes | NT LUONG PHUONG | DA NANG | SON TRA | 16.0559315 | 108.2397444 | 108.2397444,16.0559315 | pharmacy | 2015-10-30T00:00:169 | 2015-10-30T00:00:169 | |||||
KTMPVN01340 | 7567 | Other periods | yes | MINH NGOC | HA NOI | BA DINH | 21.02672 | 105.80712 | 105.80712,21.02672 | pharmacy | 2015-10-30T00:00:170 | 2015-10-30T00:00:170 | |||||
KTMPVN01341 | 7607 | Other periods | yes | TRUNG HA | HA NOI | BA DINH | 21.0399112 | 105.8128839 | 105.8128839,21.0399112 | pharmacy | 2015-10-30T00:00:171 | 2015-10-30T00:00:171 | |||||
KTMPVN01642 | 11608 | Other periods | yes | NT HOANG NA (NT KIM QUYEN) | CAN THO | 10.0138853 | 105.7632102 | 105.763210199999,10.0138853 | pharmacy | 2015-10-30T00:00:172 | 2015-10-30T00:00:172 | ||||||
KTMPVN01645 | 8184 | Other periods | yes | NT DUY KHANG | CAN THO | NINH KIEU | 10.0360027 | 105.7575462 | 105.7575462,10.0360027 | pharmacy | 2015-10-30T00:00:173 | 2015-10-30T00:00:173 | |||||
KTMPVN01646 | 9105 | Other periods | yes | NT THANH DAT | CAN THO | NINH KIEU | 10.0392663 | 105.754305 | 105.754305,10.0392663 | pharmacy | 2015-10-30T00:00:174 | 2015-10-30T00:00:174 | |||||
KTMPVN01647 | 16444 | Other periods | yes | NT BAO THI 2 | CAN THO | NINH KIEU | 10.038475 | 105.75454 | 105.75454,10.038475 | pharmacy | 2015-10-30T00:00:175 | 2015-10-30T00:00:175 | |||||
KTMPVN01715 | 9185 | Other periods | yes | NT NGUYET HUONG | HA NOI | DONG DA | 21.02243 | 105.82643 | 105.82643,21.02243 | pharmacy | 2015-10-30T00:00:176 | 2015-10-30T00:00:176 | |||||
KTMPVN01719 | 16195 | Other periods | yes | SIEU THI THUOC VIET | HA NOI | HAI BA TRUNG | 21.019602 | 105.817412 | 105.817412,21.019602 | pharmacy | 2015-10-30T00:00:177 | 2015-10-30T00:00:177 | |||||
KTMPVN01721 | 12745 | Other periods | yes | NT 6 TRUONG DINH | HA NOI | HAI BA TRUNG | 20.9961562 | 105.8500219 | 105.8500219,20.9961561999999 | pharmacy | 2015-10-30T00:00:178 | 2015-10-30T00:00:178 | |||||
KTMPVN01727 | 17476 | Other periods | yes | NT VIET DUC | HA NOI | HOAN KIEM | 21.024706 | 105.848534 | 105.848534,21.024706 | pharmacy | 2015-10-30T00:00:179 | 2015-10-30T00:00:179 | |||||
KTMPVN01731 | 10884 | Other periods | yes | NT MINH TUYEN | HA NOI | TAY HO | 21.04169 | 105.82621 | 105.82621,21.04169 | pharmacy | 2015-10-30T00:00:180 | 2015-10-30T00:00:180 | |||||
KTMPVN01817 | 14117 | Other periods | yes | Nha Thuoc Phuoc Thien | Da Nang | Hai Chau | 16.05113 | 108.2196 | 108.2196,16.05113 | pharmacy | 2015-10-30T00:00:181 | 2015-10-30T00:00:181 | |||||
KTMPVN01818 | 9336 | Other periods | yes | Nha Thuoc Minh Tam | Ha Noi | Hai Ba Trung | 21.01885 | 105.85953 | 105.85953,21.01885 | pharmacy | 2015-10-30T00:00:182 | 2015-10-30T00:00:182 | |||||
KTMPVN01826 | 17640 | Other periods | yes | Nha Thuoc 24H | Ha Noi | Hoan Kiem | 21.028124 | 105.84783 | 105.84783,21.028124 | pharmacy | 2015-10-30T00:00:183 | 2015-10-30T00:00:183 | |||||
KTMPVN01827 | 9115 | Other periods | yes | Nha Thuoc Trung Son | Can Tho | Ninh Kieu | 10.020637 | 105.0781628 | 105.0781628,10.020637 | pharmacy | 2015-10-30T00:00:184 | 2015-10-30T00:00:184 | |||||
KTMPVN01831 | 8760 | Other periods | yes | Nha Thuoc Dapharco 176 | Da Nang | Hai Chau | 16.0733488 | 108.2134377 | 108.2134377,16.0733487999999 | pharmacy | 2015-10-30T00:00:185 | 2015-10-30T00:00:185 | |||||
KTMPVN01833 | 17623 | Other periods | yes | Nha Thuoc Truc Thuoc CTy TNHH Nha Thuoc 24H.VN | Ha Noi | Hoan Kiem | 21.02805 | 105.647768 | 105.647768,21.02805 | pharmacy | 2015-10-30T00:00:186 | 2015-10-30T00:00:186 | |||||
KTMPVN01854 | 9337 | Other periods | yes | Nha Thuoc Nghia Hung | Ha Noi | Hai Ba Trung | 21.001064 | 105.860012 | 105.860012,21.001064 | pharmacy | 2015-10-30T00:00:187 | 2015-10-30T00:00:187 | |||||
KTMPVN01859 | 8380 | Other periods | yes | Nha Thuoc Phuoc Thien 2 | Da Nang | Thanh Khe | 16.0669856 | 108.213333 | 108.213333,16.0669856 | pharmacy | 2015-10-30T00:00:188 | 2015-10-30T00:00:188 | |||||
KTMPVN01880 | 9096 | Other periods | yes | Nha Thuoc Tam An | Ha Noi | Cau Giay | 21.037565 | 105.793698 | 105.793698,21.037565 | pharmacy | 2015-10-30T00:00:189 | 2015-10-30T00:00:189 | |||||
KTMPVN01893 | 9275 | Other periods | yes | Nha Thuoc Minh Tien- DS Nguyen Thi Quynh Oanh | Ha Noi | Ba Dinh | 21.03781 | 105.81183 | 105.81183,21.03781 | pharmacy | 2015-10-30T00:00:190 | 2015-10-30T00:00:190 | |||||
KTMPVN01905 | 7682 | Other periods | yes | Nha Thuoc Trong Tan- Gia Huy | Ha Noi | Ba Dinh | 21.0259543 | 105.8099353 | 105.8099353,21.0259543 | pharmacy | 2015-10-30T00:00:191 | 2015-10-30T00:00:191 | |||||
KTMPVN01912 | 8798 | Other periods | yes | Nha Thuoc Minh Tuyen | Can Tho | Ninh Kieu | 10.037237 | 105.776925 | 105.776925,10.037237 | pharmacy | 2015-10-30T00:00:192 | 2015-10-30T00:00:192 | |||||
KTMPVN01977 | 11387 | Other periods | yes | Nha Thuoc Duc Nhung | Ha Noi | Thanh Xuan | 21.00458 | 105.81134 | 105.81134,21.00458 | pharmacy | 2015-10-30T00:00:193 | 2015-10-30T00:00:193 | |||||
KTMPVN01984 | 14111 | Other periods | yes | Nha Thuoc Phuoc Thien 3 | Da Nang | Thanh Khe | 16.07275 | 108.213055 | 108.213055,16.07275 | pharmacy | 2015-10-30T00:00:194 | 2015-10-30T00:00:194 | |||||
KTMPVN02007 | 9230 | Other periods | yes | Nha Thuoc Nghia Hung 1 | Ha Noi | Ba Dinh | 21.03479 | 105.82742 | 105.82742,21.03479 | pharmacy | 2015-10-30T00:00:195 | 2015-10-30T00:00:195 | |||||
KTMPVN02027 | 8131 | Other periods | yes | Nha Thuoc Anh Dao | Can Tho | Binh Thuy | 10.037262 | 105.744203 | 105.744203,10.037262 | pharmacy | 2015-10-30T00:00:196 | 2015-10-30T00:00:196 | |||||
KTMPVN02036 | 11902 | Other periods | yes | Nha Thuoc Nghia Hung | Ha Noi | Dong Da | 21.00487 | 105.82283 | 105.82283,21.00487 | pharmacy | 2015-10-30T00:00:197 | 2015-10-30T00:00:197 | |||||
KTMPVN02042 | 9252 | Other periods | yes | Nha Thuoc Duc Hanh | Ha Noi | Thanh Xuan | 20.99785 | 105.82212 | 105.82212,20.99785 | pharmacy | 2015-10-30T00:00:198 | 2015-10-30T00:00:198 | |||||
KTMPVN02048 | 11431 | Other periods | yes | Nha Thuoc Nguyen Thi Quynh Mai | Ha Noi | Thanh Xuan | 20.997924 | 105.822185 | 105.822185,20.997924 | pharmacy | 2015-10-30T00:00:199 | 2015-10-30T00:00:199 | |||||
KTMPVN02061 | 14109 | Other periods | yes | Nha Thuoc Minh Tam | Da Nang | Lien Chieu | 16.0705992 | 108.1484232 | 108.1484232,16.0705992 | pharmacy | 2015-10-30T00:00:200 | 2015-10-30T00:00:200 | |||||
KTMPVN02062 | 9238 | Other periods | yes | Nha Thuoc Phuong Dong- DS Tran Lan Phuong | Ha Noi | Dong Da | 21.00427 | 105.84011 | 105.84011,21.00427 | pharmacy | 2015-10-30T00:00:201 | 2015-10-30T00:00:201 | |||||
KTMPVN02064 | 9234 | Other periods | yes | Nha Thuoc Nghia Hung | Ha Noi | Ba Dinh | 21.03901 | 105.81463 | 105.81463,21.03901 | pharmacy | 2015-10-30T00:00:202 | 2015-10-30T00:00:202 | |||||
KTMPVN02072 | 13911 | Other periods | yes | Nha Thuoc Bay Dao | Da Nang | Cam Le | 16.0155494 | 108.2053188 | 108.2053188,16.0155493999999 | pharmacy | 2015-10-30T00:00:203 | 2015-10-30T00:00:203 | |||||
KTMPVN02087 | 9276 | Other periods | yes | Nha Thuoc Dapharco 22 (Chi Hung) | Da Nang | Hai Chau | 16.0703796 | 108.22399 | 108.22399,16.0703796 | pharmacy | 2015-10-30T00:00:204 | 2015-10-30T00:00:204 | |||||
KTMPVN02090 | 7417 | 2018 | yes | Nha Thuoc Dapharco 32 | Da Nang | Thanh Khe | 16.0662083 | 108.2020277 | 108.2020277,16.0662083 | pharmacy | 2015-10-30T00:00:205 | 2015-10-30T00:00:205 | |||||
KTMPVN02099 | 10009 | 2018 | yes | Nha Thuoc Thai Han | Can Tho | Ninh Kieu | 10.029881 | 105.753115 | 105.753115,10.029881 | pharmacy | 2015-10-30T00:00:206 | 2015-10-30T00:00:206 | |||||
KTMPVN02104 | 14114 | 2018 | yes | Nha Thuoc Phuoc Thien 5 | Da Nang | Hai Chau | 16.0563214 | 108.2171324 | 108.2171324,16.0563214 | pharmacy | 2015-10-30T00:00:207 | 2015-10-30T00:00:207 | |||||
KTMPVN02112 | 6876 | 2018 | yes | Nha Thuoc Tri Anh | Da Nang | Cam Le | 16.021553 | 108.2127422 | 108.2127422,16.021553 | pharmacy | 2015-10-30T00:00:208 | 2015-10-30T00:00:208 | |||||
KTMPVN02113 | 8847 | 2018 | yes | Nha Thuoc Hung Cuong | Ha Noi | Thanh Xuan | 20.9982238 | 105.8130232 | 105.8130232,20.9982238 | pharmacy | 2015-10-30T00:00:209 | 2015-10-30T00:00:209 | |||||
KTMPVN02142 | 11156 | 2018 | yes | Nha Thuoc Thao Phuong | Ha Noi | Hoang Mai | 20.99225 | 105.849425 | 105.849425,20.99225 | pharmacy | 2015-10-30T00:00:210 | 2015-10-30T00:00:210 | |||||
KTMPVN02143 | 22700 | 2018 | yes | Nha Thuoc Trung Hau | Ha Noi | Cau Giay | 21.041645 | 105.774991 | 105.774991,21.041645 | pharmacy | 2015-10-30T00:00:211 | 2015-10-30T00:00:211 | |||||
KTMPVN02162 | 9125 | 2018 | yes | Nha Thuoc An Phuoc | Da Nang | Thanh Khe | 16.0593038 | 108.2095367 | 108.2095367,16.0593038 | pharmacy | 2015-10-30T00:00:212 | 2015-10-30T00:00:212 | |||||
KTMPVN02168 | 14115 | 2018 | yes | Nha Thuoc 34- DSDH Le Thuy Hang | Ha Noi | Cau Giay | 21.03389 | 105.79908 | 105.79908,21.03389 | pharmacy | 2015-10-30T00:00:213 | 2015-10-30T00:00:213 | |||||
KTMPVN02172 | 9043 | 2018 | yes | Nha Thuoc Hoa Hong | Can Tho | Ninh Kieu | 10.038374 | 105.787349 | 105.787349,10.038374 | pharmacy | 2015-10-30T00:00:214 | 2015-10-30T00:00:214 | |||||
KTMPVN02193 | 17530 | 2018 | yes | Nha Thuoc Vu Bich Hanh | Ha Noi | Dong Da | 21.027075 | 105.835953 | 105.835953,21.027075 | pharmacy | 2015-10-30T00:00:215 | 2015-10-30T00:00:215 | |||||
KTMPVN02257 | 9276 | 2018 | yes | Nha Thuoc Dapharco 22 (Chi Ha) | Da Nang | Hai Chau | 16.0703796 | 108.22399 | 108.22399,16.0703796 | pharmacy | 2015-10-30T00:00:216 | 2015-10-30T00:00:216 | |||||
KTMPVN02259 | 8801 | 2018 | yes | Nha Thuoc Thu Lan | Da Nang | Son Tra | 16.0558193 | 108.2395127 | 108.2395127,16.0558193 | pharmacy | 2015-10-30T00:00:217 | 2015-10-30T00:00:217 | |||||
KTMPVN02292 | 14149 | 2018 | yes | Quay Thuoc Dapharco 93 | Da Nang | Son Tra | 16.0572193 | 108.2441929 | 108.2441929,16.0572193 | pharmacy | 2015-10-30T00:00:218 | 2015-10-30T00:00:218 | |||||
KTMPVN02302 | 8869 | 2018 | yes | Nha Thuoc Dapharco 12 | Da Nang | Hai Chau | 16.0457239 | 108.2191711 | 108.2191711,16.0457239 | pharmacy | 2015-10-30T00:00:219 | 2015-10-30T00:00:219 | |||||
KTMPVN02334 | 4798 | 2018 | yes | Cong Ty TNHH AnDa Viet Nam | Ha Noi | Hoan Kiem | 21.01788 | 105.85391 | 105.85391,21.01788 | pharmacy | 2015-10-30T00:00:220 | 2015-10-30T00:00:220 | |||||
KTMPVN02351 | 14075 | 2018 | yes | Nha Thuoc Ly Viet Than | Ha Noi | Thanh Xuan | 20.99534 | 105.79881 | 105.79881,20.99534 | pharmacy | 2015-10-30T00:00:221 | 2015-10-30T00:00:221 | |||||
KTMPVN02352 | 4343 | 2018 | yes | Nha Thuoc Yen Thanh | Ha Noi | Hoang Mai | 20.991263 | 105.844597 | 105.844597,20.991263 | pharmacy | 2015-10-30T00:00:222 | 2015-10-30T00:00:222 | |||||
KTMPVN02363 | 14052 | 2018 | yes | Nha Thuoc An Phuoc 3 | Da Nang | Hai Chau | 16.042659 | 108.2174915 | 108.2174915,16.042659 | pharmacy | 2015-10-30T00:00:223 | 2015-10-30T00:00:223 | |||||
KTMPVN02408 | 14054 | 2018 | yes | Nha Thuoc Dapharco 25 | Da Nang | Hai Chau | 16.0553094 | 108.2438222 | 108.2438222,16.0553094 | pharmacy | 2015-10-30T00:00:224 | 2015-10-30T00:00:224 | |||||
KTMPVN02412 | 19080 | 2018 | yes | Nha Thuoc Duc Tri | Da Nang | Thanh Khe | 16.0724173 | 108.2080278 | 108.2080278,16.0724173 | pharmacy | 2015-10-30T00:00:225 | 2015-10-30T00:00:225 | |||||
KTMPVN02435 | 8728 | 2018 | yes | Nha Thuoc Luong Thi Tuyet | Ha Noi | Thanh Xuan | 20.9868898 | 105.7967734 | 105.7967734,20.9868898 | pharmacy | 2015-10-30T00:00:226 | 2015-10-30T00:00:226 | |||||
KTMPVN02438 | 8977 | 2018 | yes | Nha Thuoc Thanh Thanh | Da Nang | Ngu Hanh Son | 16.0538621 | 108.2381689 | 108.2381689,16.0538621 | pharmacy | 2015-10-30T00:00:227 | 2015-10-30T00:00:227 | |||||
KTMPVN02449 | 7780 | 2018 | yes | Nha Thuoc Binh Minh | Ha Noi | Hoan Kiem | 21.023252 | 105.846465 | 105.846465,21.023252 | pharmacy | 2015-10-30T00:00:228 | 2015-10-30T00:00:228 | |||||
KTMPVN02455 | 11229 | 2018 | yes | Nha Thuoc Minh Son | Ha Noi | Dong Da | 21.01297 | 105.83233 | 105.83233,21.01297 | pharmacy | 2015-10-30T00:00:229 | 2015-10-30T00:00:229 | |||||
KTMPVN02469 | 8948 | 2018 | yes | Nha Thuoc Dapharco 59 | Da Nang | Thanh Khe | 16.0661076 | 108.207384 | 108.207384,16.0661076 | pharmacy | 2015-10-30T00:00:230 | 2015-10-30T00:00:230 | |||||
KTMPVN02480 | 17198 | 2018 | yes | Nha Thuoc Hong Hanh | Ha Noi | Hai Ba Trung | 21.0004 | 105.87139 | 105.87139,21.0004 | pharmacy | 2015-10-30T00:00:231 | 2015-10-30T00:00:231 | |||||
KTMPVN02489 | 8970 | 2018 | yes | Nha Thuoc Tu Nhan DS Nguyen Huy Am | Ha Noi | Long Bien | 21.055719 | 105.866742 | 105.866742,21.055719 | pharmacy | 2015-10-30T00:00:232 | 2015-10-30T00:00:232 | |||||
KTMPVN02498 | 8504 | 2018 | yes | Nha Thuoc Phap So III | Ha Noi | Thanh Xuan | 20.998 | 105.82206 | 105.82206,20.998 | pharmacy | 2015-10-30T00:00:233 | 2015-10-30T00:00:233 | |||||
KTMPVN02509 | 19914 | 2018 | yes | Nha Thuoc Phuoc Nhan | Da Nang | Ngu Hanh Son | 16.0453629 | 108.2417138 | 108.2417138,16.0453629 | pharmacy | 2015-10-30T00:00:234 | 2015-10-30T00:00:234 | |||||
KTMPVN02513 | 13921 | 2018 | yes | Nha Thuoc Hue Ha | Ha Noi | Ba Dinh | 21.045679 | 105.849152 | 105.849152,21.045679 | pharmacy | 2015-10-30T00:00:235 | 2015-10-30T00:00:235 | |||||
KTMPVN02529 | 4746 | 2018 | yes | Nha Thuoc Dapharco 57 | Da Nang | Hai Chau | 16.0706607 | 108.2152327 | 108.2152327,16.0706607 | pharmacy | 2015-10-30T00:00:236 | 2015-10-30T00:00:236 | |||||
KTMPVN02592 | 9278 | 2018 | yes | Nha Thuoc Loi Nhan 2 | Can Tho | Ninh Kieu | 10.0203856 | 105.7588457 | 105.7588457,10.0203856 | pharmacy | 2015-10-30T00:00:237 | 2015-10-30T00:00:237 | |||||
KTMPVN02609 | 11389 | 2018 | yes | Nha Thuoc Thien Phuc | Can Tho | Ninh Kieu | 10.029001 | 105.754274 | 105.754274,10.029001 | pharmacy | 2015-10-30T00:00:238 | 2015-10-30T00:00:238 | |||||
KTMPVN02610 | 6702 | 2018 | yes | Nha Thuoc Phuc Hung 2 | Ha Noi | Dong Da | 21.0107511 | 105.8332771 | 105.8332771,21.0107511 | pharmacy | 2015-10-30T00:00:239 | 2015-10-30T00:00:239 | |||||
KTMPVN02618 | 14003 | 2018 | yes | Nha Thuoc Hoai Nam- DSDH Phung Thi Nga | Ha Noi | Thanh Xuan | 20.9433 | 105.81419 | 105.81419,20.9433 | pharmacy | 2015-10-30T00:00:240 | 2015-10-30T00:00:240 | |||||
KTMPVN02620 | 10008 | 2018 | yes | Nha Thuoc Minh Thi | Can Tho | Ninh Kieu | 10.026873 | 105.757262 | 105.757262,10.026873 | pharmacy | 2015-10-30T00:00:241 | 2015-10-30T00:00:241 | |||||
KTMPVN02625 | 14039 | 2018 | yes | Quay Thuoc Dapharco 107 | Da Nang | Son Tra | 16.054624 | 108.2405619 | 108.240561899999,16.054624 | pharmacy | 2015-10-30T00:00:242 | 2015-10-30T00:00:242 | |||||
KTMPVN02640 | 18867 | 2018 | yes | Quay Thuoc Dapharco 133 | Da Nang | Cam Le | 16.0136649 | 108.2075297 | 108.2075297,16.0136649 | pharmacy | 2015-10-30T00:00:243 | 2015-10-30T00:00:243 | |||||
KTMPVN02673 | 6640 | 2018 | yes | Nha Thuoc So 7 Viet Cuong | Ha Noi | Ha Dong | 20.971446 | 105.775539 | 105.775539,20.971446 | pharmacy | 2015-10-30T00:00:244 | 2015-10-30T00:00:244 | |||||
KTMPVN02675 | 8868 | 2018 | yes | Nha Thuoc Dapharco 35 | Da Nang | Hai Chau | 16.0430281 | 108.2176884 | 108.2176884,16.0430281 | pharmacy | 2015-10-30T00:00:245 | 2015-10-30T00:00:245 | |||||
KTMPVN02682 | 10728 | 2018 | yes | Nha Thuoc Tan Hieu | Ha Noi | Dong Da | 21.011881 | 105.815292 | 105.815292,21.011881 | pharmacy | 2015-10-30T00:00:246 | 2015-10-30T00:00:246 | |||||
KTMPVN02735 | 8935 | 2018 | yes | Nha Thuoc Hoang Anh | Da Nang | Hai Chau | 16.0297165 | 108.2225072 | 108.2225072,16.0297165 | pharmacy | 2015-10-30T00:00:247 | 2015-10-30T00:00:247 | |||||
KTMPVN02753 | 7806 | 2018 | yes | Nha Thuoc Bao Chau | Can Tho | Ninh Kieu | 10.028412 | 105.754537 | 105.754537,10.028412 | pharmacy | 2015-10-30T00:00:248 | 2015-10-30T00:00:248 | |||||
KTMPVN02761 | 11242 | 2018 | yes | Nha Thuoc Thanh Van | Can Tho | Ninh Kieu | 10.014713 | 105.762205 | 105.762205,10.014713 | pharmacy | 2015-10-30T00:00:249 | 2015-10-30T00:00:249 | |||||
KTMPVN02764 | 15816 | 2018 | yes | Nha Thuoc Phuong Mai | Ha Noi | Dong Da | 21.004482 | 105.836689 | 105.836689,21.004482 | pharmacy | 2015-10-30T00:00:250 | 2015-10-30T00:00:250 | |||||
KTMPVN02819 | 17355 | 2018 | yes | Nha Thuoc Minh Nguyet | Can Tho | Ninh Kieu | 10.051989 | 105.752451 | 105.752451,10.051989 | pharmacy | 2015-10-30T00:00:251 | 2015-10-30T00:00:251 | |||||
KTMPVN02824 | 14846 | 2018 | yes | Nha Thuoc Minh Trang | Ha Noi | Nam Tu Liem | 20.9891 | 105.79301 | 105.79301,20.9891 | pharmacy | 2015-10-30T00:00:252 | 2015-10-30T00:00:252 | |||||
KTMPVN02826 | 12154 | 2018 | yes | Nha Thuoc Que Anh | Can Tho | Ninh Kieu | 10.0248347 | 105.7579345 | 105.7579345,10.0248347 | pharmacy | 2015-10-30T00:00:253 | 2015-10-30T00:00:253 | |||||
KTMPVN02846 | 13979 | 2018 | yes | Nha Thuoc Hong Phuc | Ha Noi | Hoang Mai | 20.990268 | 105.845214 | 105.845214,20.990268 | pharmacy | 2015-10-30T00:00:254 | 2015-10-30T00:00:254 | |||||
KTMPVN02850 | 14860 | 2018 | yes | Nha Thuoc Gia Khanh | Can Tho | Ninh Kieu | 10.043673 | 105.77854 | 105.77854,10.043673 | pharmacy | 2015-10-30T00:00:255 | 2015-10-30T00:00:255 | |||||
KTMPVN02866 | 11232 | 2018 | yes | Quay Thuoc Ngo Quyen 2 | Can Tho | Phong Dien | 10.032786 | 105.780869 | 105.780869,10.032786 | pharmacy | 2015-10-30T00:00:256 | 2015-10-30T00:00:256 | |||||
KTMPVN02881 | 8905 | 2018 | yes | Nha Thuoc Nguyen Tri Phuong | Da Nang | Hai Chau | 16.0508561 | 108.2132046 | 108.2132046,16.0508561 | pharmacy | 2015-10-30T00:00:257 | 2015-10-30T00:00:257 | |||||
KTMPVN02889 | 16202 | 2018 | yes | Nha Thuoc 88 Phap Dai Lang | Ha Noi | Dong Da | 21.019368 | 105.805424 | 105.805424,21.019368 | pharmacy | 2015-10-30T00:00:258 | 2015-10-30T00:00:258 | |||||
KTMPVN02895 | 6174 | 2018 | yes | Nha Thuoc Binh An | Can Tho | Ninh Kieu | 10.043826 | 105.781094 | 105.781094,10.043826 | pharmacy | 2015-10-30T00:00:259 | 2015-10-30T00:00:259 | |||||
KTMPVN02903 | 17641 | 2018 | yes | Nha Thuoc Duc Tin | Da Nang | Ngu Hanh Son | 16.0423228 | 108.2420266 | 108.2420266,16.0423228 | pharmacy | 2015-10-30T00:00:260 | 2015-10-30T00:00:260 | |||||
KTMPVN02919 | 6371 | 2018 | yes | Nha Thuoc Suc Khoe Vang | Da Nang | Hai Chau | 16.0738241 | 108.2149084 | 108.2149084,16.0738241 | pharmacy | 2015-10-30T00:00:261 | 2015-10-30T00:00:261 | |||||
KTMPVN02950 | 9006 | 2018 | yes | Nha Thuoc Thanh Thao | Can Tho | Ninh Kieu | 10.047065 | 105.776632 | 105.776632,10.047065 | pharmacy | 2015-10-30T00:00:262 | 2015-10-30T00:00:262 | |||||
KTMPVN02965 | 8954 | 2018 | yes | Nha Thuoc Ngu Dang | Can Tho | Ninh Kieu | 10.035848 | 105.757121 | 105.757121,10.035848 | pharmacy | 2015-10-30T00:00:263 | 2015-10-30T00:00:263 | |||||
KTMPVN02972 | 11573 | 2018 | yes | Nha Thuoc Duc Huy | Ha Noi | Tay Ho | 21.0463909 | 105.8123612 | 105.8123612,21.0463909 | pharmacy | 2015-10-30T00:00:264 | 2015-10-30T00:00:264 | |||||
KTMPVN02983 | 8962 | 2018 | yes | Nha Thuoc Phan Thi Hoi | Ha Noi | Thanh Xuan | 20.9925896 | 105.8136078 | 105.8136078,20.9925896 | pharmacy | 2015-10-30T00:00:265 | 2015-10-30T00:00:265 | |||||
KTMPVN02987 | 11606 | 2018 | yes | Nha Thuoc An Khanh | Ha Noi | Dong Da | 21.01581 | 105.8376 | 105.8376,21.01581 | pharmacy | 2015-10-30T00:00:266 | 2015-10-30T00:00:266 | |||||
KTMPVN02988 | 8653 | 2018 | yes | Nha Thuoc An Que | Ha Noi | Nam Tu Liem | 20.9852017 | 105.7935442 | 105.7935442,20.9852017 | pharmacy | 2015-10-30T00:00:267 | 2015-10-30T00:00:267 | |||||
KTMPVN02989 | 8931 | 2018 | yes | Nha Thuoc 109 Quan Nhan | Ha Noi | Thanh Xuan | 21.005526 | 105.811381 | 105.811381,21.005526 | pharmacy | 2015-10-30T00:00:268 | 2015-10-30T00:00:268 | |||||
KTMPVN02995 | 8535 | 2018 | yes | Nha Thuoc Hong Diem | Da Nang | Hai Chau | 16.0720412 | 108.2156034 | 108.2156034,16.0720412 | pharmacy | 2015-10-30T00:00:269 | 2015-10-30T00:00:269 | |||||
KTMPVN03008 | 11231 | 2018 | yes | Nha Thuoc Duong Minh Hoan | Ha Noi | Thanh Xuan | 20.99911 | 105.81403 | 105.81403,20.99911 | pharmacy | 2015-10-30T00:00:271 | 2015-10-30T00:00:271 | |||||
KTMPVN03011 | 17216 | 2018 | yes | Nha Thuoc Ha Ly Tuan | Ha Noi | Hai Ba Trung | 21.009696 | 105.865603 | 105.865603,21.009696 | pharmacy | 2015-10-30T00:00:272 | 2015-10-30T00:00:272 | |||||
KTMPVN03027 | 8138 | 2018 | yes | Nha Thuoc Hong Nguyet | Da Nang | Son Tra | 16.1008613 | 108.2503675 | 108.2503675,16.1008613 | pharmacy | 2015-10-30T00:00:273 | 2015-10-30T00:00:273 | |||||
KTMPVN03049 | 7397 | 2018 | yes | Nha Thuoc Duc Hien | Ha Noi | Ha Dong | 20.9632679 | 105.7633322 | 105.7633322,20.9632678999999 | pharmacy | 2015-10-30T00:00:274 | 2015-10-30T00:00:274 | |||||
KTMPVN03089 | 9248 | 2018 | yes | Nha Thuoc Tu Nhan 3T | Ha Noi | Dong Da | 21.0091 | 105.819493 | 105.819493,21.0091 | pharmacy | 2015-10-30T00:00:275 | 2015-10-30T00:00:275 | |||||
KTMPVN03138 | 15812 | 2018 | yes | Nha Thuoc Thien Nhan | Can Tho | Binh Thuy | 10.084699 | 105.733972 | 105.733972,10.084699 | pharmacy | 2015-10-30T00:00:276 | 2015-10-30T00:00:276 | |||||
KTMPVN03149 | 17758 | 2018 | yes | Nha Thuoc Ngoc Anh | Can Tho | Ninh Kieu | 10.043946 | 105.765248 | 105.765248,10.043946 | pharmacy | 2015-10-30T00:00:277 | 2015-10-30T00:00:277 | |||||
KTMPVN03150 | 11097 | 2018 | yes | Nha Thuoc Linh Thuy | Can Tho | Ninh Kieu | 10.020956 | 105.766173 | 105.766173,10.020956 | pharmacy | 2015-10-30T00:00:278 | 2015-10-30T00:00:278 | |||||
KTMPVN03187 | 8258 | 2018 | yes | Nha Thuoc Tra An | Can Tho | Binh Thuy | 10.08538 | 105.73474 | 105.73474,10.08538 | pharmacy | 2015-10-30T00:00:279 | 2015-10-30T00:00:279 | |||||
KTMPVN03190 | 8978 | 2018 | yes | Quay Thuoc Dapharco 102 | Da Nang | Son Tra | 16.06775 | 108.23291 | 108.23291,16.06775 | pharmacy | 2015-10-30T00:00:280 | 2015-10-30T00:00:280 | |||||
KTMPVN03194 | 11381 | 2018 | yes | Nha Thuoc Huyen Trang | Ha Noi | Hoan Kiem | 21.020898 | 105.86295 | 105.86295,21.020898 | pharmacy | 2015-10-30T00:00:281 | 2015-10-30T00:00:281 | |||||
KTMPVN03201 | 14380 | 2018 | yes | Nha Thuoc Ngoc Huyen | Da Nang | Son Tra | 16.1004123 | 108.25309 | 108.25309,16.1004123 | pharmacy | 2015-10-30T00:00:282 | 2015-10-30T00:00:282 | |||||
KTMPVN03226 | 7517 | 2018 | yes | Nha Thuoc Hai Binh Ii | Ha Noi | Dong Da | 21.0175774 | 105.8260806 | 105.8260806,21.0175774 | pharmacy | 2015-10-30T00:00:283 | 2015-10-30T00:00:283 | |||||
KTMPVN03254 | 16204 | 2018 | yes | Nha Thuoc Ngan | Ha Noi | Hoan Kiem | 21.033413 | 105.856501 | 105.856501,21.033413 | pharmacy | 2015-10-30T00:00:284 | 2015-10-30T00:00:284 | |||||
KTMPVN03265 | 15760 | 2018 | yes | Nha Thuoc Minh Khang | Can Tho | Ninh Kieu | 10.030684 | 105.751976 | 105.751976,10.030684 | pharmacy | 2015-10-30T00:00:285 | 2015-10-30T00:00:285 | |||||
KTMPVN03298 | 8965 | 2018 | yes | Nha Thuoc Van Duc | Can Tho | Ninh Kieu | 10.012177 | 105.757677 | 105.757677,10.012177 | pharmacy | 2015-10-30T00:00:286 | 2015-10-30T00:00:286 | |||||
KTMPVN03311 | 4578 | 2018 | yes | Nha Thuoc SKV Phuc Anh | Da Nang | Hai Chau | 16.0581243 | 108.2131374 | 108.2131374,16.0581243 | pharmacy | 2015-10-30T00:00:287 | 2015-10-30T00:00:287 | |||||
KTMPVN03330 | 22252 | 2018 | yes | Nha Thuoc Hong Thu | Ha Noi | Dong Da | 21.026919 | 105.801773 | 105.801773,21.026919 | pharmacy | 2015-10-30T00:00:288 | 2015-10-30T00:00:288 | |||||
KTMPVN03342 | 9065 | 2018 | yes | Nha Thuoc Tan Tai | Can Tho | Ninh Kieu | 10.037945 | 105.75909 | 105.75909,10.037945 | pharmacy | 2015-10-30T00:00:289 | 2015-10-30T00:00:289 | |||||
KTMPVN03356 | 8740 | 2018 | yes | Nha Thuoc Luu Quang Ngoc | Ha Noi | Hoang Mai | 20.9817266 | 105.8806001 | 105.8806001,20.9817266 | pharmacy | 2015-10-30T00:00:290 | 2015-10-30T00:00:290 | |||||
KTMPVN03405 | 8867 | 2018 | yes | Nha Thuoc Ngoc My | Ha Noi | Long Bien | 21.024973 | 105.907913 | 105.907913,21.024973 | pharmacy | 2015-10-30T00:00:291 | 2015-10-30T00:00:291 | |||||
KTMPVN03418 | 11814 | 2018 | yes | Nha Thuoc Ngoc Linh | Can Tho | Ninh Kieu | 10.0250909 | 105.7678945 | 105.7678945,10.0250909 | pharmacy | 2015-10-30T00:00:292 | 2015-10-30T00:00:292 | |||||
KTMPVN03429 | 9095 | 2018 | yes | Nha Thuoc Nguyen Lam | Ha Noi | Long Bien | 21.033696 | 105.910014 | 105.910014,21.033696 | pharmacy | 2015-10-30T00:00:293 | 2015-10-30T00:00:293 | |||||
KTMPVN03434 | 8763 | 2018 | yes | Nha Thuoc Ngoc Anh | Da Nang | Thanh Khe | 16.0627586 | 108.2089081 | 108.2089081,16.0627586 | pharmacy | 2015-10-30T00:00:294 | 2015-10-30T00:00:294 | |||||
KTMPVN03441 | 11288 | 2018 | yes | Nha Thuoc Van Vinh | Ha Noi | Thanh Xuan | 20.9965 | 105.81456 | 105.81456,20.9965 | pharmacy | 2015-10-30T00:00:295 | 2015-10-30T00:00:295 | |||||
KTMPVN03506 | 6304 | 2018 | yes | Nha Thuoc Minh Huy | Ha Noi | Ha Dong | 20.969898 | 105.768259 | 105.768259,20.969898 | pharmacy | 2015-10-30T00:00:296 | 2015-10-30T00:00:296 | |||||
KTMPVN03530 | 17358 | 2018 | yes | Nha Thuoc Huynh Mai | Can Tho | Ninh Kieu | 10.040785 | 105.767863 | 105.767863,10.040785 | pharmacy | 2015-10-30T00:00:297 | 2015-10-30T00:00:297 | |||||
KTMPVN03563 | 16185 | 2018 | yes | Nha Thuoc Duc Anh 8 | Ha Noi | Cau Giay | 21.009442 | 105.807739 | 105.807739,21.009442 | pharmacy | 2015-10-30T00:00:298 | 2015-10-30T00:00:298 | |||||
KTMPVN03577 | 9168 | 2018 | yes | Nha Thuoc Lam Hoang | Ha Noi | Hai Ba Trung | 21.01431 | 105.864772 | 105.864772,21.01431 | pharmacy | 2015-10-30T00:00:299 | 2015-10-30T00:00:299 | |||||
KTMPVN03586 | 17423 | 2018 | yes | Nha Thuoc Quynh Anh | Can Tho | Cai Rang | 10.014336 | 105.793293 | 105.793293,10.014336 | pharmacy | 2015-10-30T00:00:300 | 2015-10-30T00:00:300 | |||||
KTMPVN03618 | 6447 | 2018 | yes | Nha Thuoc Tien Dung | Ha Noi | Hai Ba Trung | 20.99897 | 105.85297 | 105.85297,20.99897 | pharmacy | 2015-10-30T00:00:301 | 2015-10-30T00:00:301 | |||||
KTMPVN03638 | 14888 | 2018 | yes | Nha Thuoc Phuc Hung | Ha Noi | Hai Ba Trung | 21 | 105.85 | 105.85,21 | pharmacy | 2015-10-30T00:00:302 | 2015-10-30T00:00:302 | |||||
KTMPVN03646 | 14077 | 2018 | yes | Quay Thuoc Dapharco 82 | Da Nang | Ngu Hanh Son | 16.0296556 | 108.2473091 | 108.2473091,16.0296556 | pharmacy | 2015-10-30T00:00:303 | 2015-10-30T00:00:303 | |||||
KTMPVN03702 | 7789 | 2018 | yes | Nha Thuoc So 5 Long Tam | Ha Noi | Hai Ba Trung | 21.000823 | 105.841502 | 105.841502,21.000823 | pharmacy | 2015-10-30T00:00:304 | 2015-10-30T00:00:304 | |||||
KTMPVN03705 | 11487 | 2018 | yes | Nha Thuoc An Hung | Ha Noi | Thanh Xuan | 20.9951532 | 105.8268598 | 105.8268598,20.9951532 | pharmacy | 2015-10-30T00:00:305 | 2015-10-30T00:00:305 | |||||
KTMPVN03706 | 16477 | 2018 | yes | Nha Thuoc Thanh Uy | Da Nang | Son Tra | 16.066231 | 108.242662 | 108.242662,16.066231 | pharmacy | 2015-10-30T00:00:306 | 2015-10-30T00:00:306 | |||||
KTMPVN03722 | 17541 | 2018 | yes | Nha Thuoc An Binh | Ha Noi | Long Bien | 21.067328 | 105.898472 | 105.898472,21.067328 | pharmacy | 2015-10-30T00:00:307 | 2015-10-30T00:00:307 | |||||
KTMPVN03765 | 19024 | 2018 | yes | Nha Thuoc Nguyen Thi Tuong Van | Ha Noi | Ba Dinh | 21.045781 | 105.846236 | 105.846236,21.045781 | pharmacy | 2015-10-30T00:00:308 | 2015-10-30T00:00:308 | |||||
KTMPVN03801 | 14051 | 2018 | yes | Nha Thuoc Tu Nhan Tam Phat | Ha Noi | Thanh Xuan | 20.989473 | 105.81358 | 105.81358,20.989473 | pharmacy | 2015-10-30T00:00:309 | 2015-10-30T00:00:309 | |||||
KTMPVN03809 | 9142 | 2018 | yes | Nha Thuoc Minh Hao | Ha Noi | Thanh Xuan | 20.99277 | 105.79915 | 105.79915,20.99277 | pharmacy | 2015-10-30T00:00:310 | 2015-10-30T00:00:310 | |||||
KTMPVN03820 | 11287 | 2018 | yes | Nha Thuoc Minh Vu | Ha Noi | Thanh Xuan | 20.9895315 | 105.8154015 | 105.8154015,20.9895315 | pharmacy | 2015-10-30T00:00:311 | 2015-10-30T00:00:311 | |||||
KTMPVN03840 | 8974 | 2018 | yes | Nha Thuoc Huy Hoang | Ha Noi | Hoan Kiem | 21.040128 | 105.849359 | 105.849359,21.040128 | pharmacy | 2015-10-30T00:00:312 | 2015-10-30T00:00:312 | |||||
KTMPVN03851 | 22637 | 2018 | yes | Nha Thuoc Tam Duc- DS Le Thi Quynh Nga | Ha Noi | Hoang Mai | 20.974124 | 105.835887 | 105.835887,20.974124 | pharmacy | 2015-10-30T00:00:313 | 2015-10-30T00:00:313 | |||||
KTMPVN03883 | 15535 | 2018 | yes | Nha Thuoc Thai An | Can Tho | Binh Thuy | 10.047399 | 105.758943 | 105.758943,10.047399 | pharmacy | 2015-10-30T00:00:314 | 2015-10-30T00:00:314 | |||||
KTMPVN03900 | 16502 | 2018 | yes | Nha Thuoc Bao Thi III | Can Tho | Cai Rang | 9.987301 | 105.75991 | 105.75991,9.987301 | pharmacy | 2015-10-30T00:00:315 | 2015-10-30T00:00:315 | |||||
KTMPVN03913 | 8946 | 2018 | yes | Nha Thuoc Hong Ky | Ha Noi | Thanh Xuan | 20.9974051 | 105.813609 | 105.813609,20.9974051 | pharmacy | 2015-10-30T00:00:316 | 2015-10-30T00:00:316 | |||||
KTMPVN03926 | 11621 | 2018 | yes | Nha Thuoc Xuan Hung | Ha Noi | Ba Dinh | 21.04744 | 105.847719 | 105.847719,21.04744 | pharmacy | 2015-10-30T00:00:317 | 2015-10-30T00:00:317 | |||||
KTMPVN03927 | 14010 | 2018 | yes | Nha Thuoc Mai Huyen | Ha Noi | Hoan Kiem | 21.027053 | 105.861211 | 105.861211,21.027053 | pharmacy | 2015-10-30T00:00:318 | 2015-10-30T00:00:318 | |||||
KTMPVN03941 | 16242 | 2018 | yes | Nha Thuoc Minh Phuoc | Ha Noi | Cau Giay | 21.023897 | 105.797557 | 105.797557,21.023897 | pharmacy | 2015-10-30T00:00:319 | 2015-10-30T00:00:319 | |||||
KTMPVN03960 | 4625 | 2018 | yes | NT Khanh Phuong-CTy CPDP Khanh Phuong Viet | Da Nang | Hai Chau | 16.0549768 | 108.2123137 | 108.2123137,16.0549768 | pharmacy | 2015-10-30T00:00:320 | 2015-10-30T00:00:320 | |||||
KTMPVN03966 | 24055 | 2018 | yes | Nha Thuoc 163 Tam Trinh | Ha Noi | Hoang Mai | 10.8069 | 106.719 | 106.719,10.8069 | pharmacy | 2015-10-30T00:00:321 | 2015-10-30T00:00:321 | |||||
KTMPVN03985 | 6846 | 2018 | yes | Nha Thuoc Hoang Hai | Ha Noi | Tu Liem | 21.079534 | 105.785654 | 105.785654,21.079534 | pharmacy | 2015-10-30T00:00:322 | 2015-10-30T00:00:322 | |||||
KTMPVN03988 | 7568 | 2018 | yes | Nha Thuoc Nguyen Thi Xuan Hoa | Ha Noi | Dong Da | 21.020203 | 105.8075973 | 105.8075973,21.020203 | pharmacy | 2015-10-30T00:00:323 | 2015-10-30T00:00:323 | |||||
KTMPVN03989 | 6954 | 2018 | yes | Nha Thuoc An Khang | Ha Noi | Ha Dong | 20.9705436 | 105.7860227 | 105.7860227,20.9705436 | pharmacy | 2015-10-30T00:00:324 | 2015-10-30T00:00:324 | |||||
KTMPVN03993 | 16994 | 2018 | yes | Nha Thuoc Nghia Hai | Ha Noi | Dong Da | 21.009418 | 105.819403 | 105.819403,21.009418 | pharmacy | 2015-10-30T00:00:325 | 2015-10-30T00:00:325 | |||||
KTMPVN04022 | 17695 | 2018 | yes | Nha Thuoc Cao Quy Loc | Da Nang | Thanh Khe | 16.0670918 | 108.2015181 | 108.2015181,16.0670918 | pharmacy | 2015-10-30T00:00:326 | 2015-10-30T00:00:326 | |||||
KTMPVN04034 | 14050 | 2018 | yes | Nha Thuoc Cao Linh | Da Nang | Ngu Hanh Son | 16.041706 | 108.2428954 | 108.2428954,16.041706 | pharmacy | 2015-10-30T00:00:327 | 2015-10-30T00:00:327 | |||||
KTMPVN04092 | 18180 | 2018 | yes | Nha Thuoc An Phuc | Can Tho | Ninh Kieu | 10.046285 | 105.76776 | 105.76776,10.046285 | pharmacy | 2015-10-30T00:00:328 | 2015-10-30T00:00:328 | |||||
KTMPVN04094 | 10548 | 2018 | yes | Nha Thuoc Hai Duong- DS Pham Thai Hong Duong | Ha Noi | Long Bien | 21.039492 | 105.865653 | 105.865653,21.039492 | pharmacy | 2015-10-30T00:00:329 | 2015-10-30T00:00:329 | |||||
KTMPVN04137 | 8771 | 2018 | yes | Nha Thuoc Anh Thong | Can Tho | Binh Thuy | 10.05928 | 105.766523 | 105.766523,10.05928 | pharmacy | 2015-10-30T00:00:330 | 2015-10-30T00:00:330 | |||||
KTMPVN04149 | 11124 | 2018 | yes | NHA THUOC MY DUYEN | Can Tho | Ninh Kieu | 10.014245 | 105.762404 | 105.762404,10.014245 | pharmacy | 2015-10-30T00:00:331 | 2015-10-30T00:00:331 | |||||
KTMPVN04151 | 23150 | 2018 | yes | Nha Thuoc Tue Minh | Ha Noi | Ha Dong | 20.79574 | 106.7137 | 106.7137,20.79574 | pharmacy | 2015-10-30T00:00:332 | 2015-10-30T00:00:332 | |||||
KTMPVN04226 | 6449 | 2018 | yes | Nha Thuoc Thu An | Ha Noi | Ha Dong | 20.965778 | 105.766073 | 105.766073,20.965778 | pharmacy | 2015-10-30T00:00:333 | 2015-10-30T00:00:333 | |||||
KTMPVN04241 | 11385 | 2018 | yes | Nha Thuoc Ngoc Van | Ha Noi | Dong Da | 21.02314 | 105.8017 | 105.8017,21.02314 | pharmacy | 2015-10-30T00:00:334 | 2015-10-30T00:00:334 | |||||
KTMPVN04257 | 7220 | 2018 | yes | Quay Thuoc Anh Tuan | Ha Noi | Tu Liem | 21.0211902 | 105.7753811 | 105.7753811,21.0211902 | pharmacy | 2015-10-30T00:00:335 | 2015-10-30T00:00:335 | |||||
KTMPVN04300 | 7729 | 2018 | yes | Nha Thuoc Duong Lam | Ha Noi | Cau Giay | 21.03355 | 105.77875 | 105.77875,21.03355 | pharmacy | 2015-10-30T00:00:336 | 2015-10-30T00:00:336 | |||||
KTMPVN04310 | 8832 | 2018 | yes | Nha Thuoc An Tho | Can Tho | Binh Thuy | 10.066925 | 105.755967 | 105.755967,10.066925 | pharmacy | 2015-10-30T00:00:337 | 2015-10-30T00:00:337 | |||||
KTMPVN04335 | 14837 | 2018 | yes | Nha Thuoc Trung Ha | Ha Noi | Ba Dinh | 21.03596 | 105.812336 | 105.812336,21.03596 | pharmacy | 2015-10-30T00:00:338 | 2015-10-30T00:00:338 | |||||
KTMPVN04337 | 23828 | 2018 | yes | Nha Thuoc Duc Cuong | Ha Noi | Hai Ba Trung | 20.99695 | 105.8459 | 105.8459,20.99695 | pharmacy | 2015-10-30T00:00:339 | 2015-10-30T00:00:339 | |||||
KTMPVN04338 | 14035 | 2018 | yes | Nha Thuoc Minh Huong | Ha Noi | Thanh Xuan | 20.98281 | 105.81377 | 105.81377,20.98281 | pharmacy | 2015-10-30T00:00:340 | 2015-10-30T00:00:340 | |||||
KTMPVN04354 | 7550 | 2018 | yes | Nha Thuoc Phu Cuong | Ha Noi | Long Bien | 21.021843 | 105.903681 | 105.903681,21.021843 | pharmacy | 2015-10-30T00:00:341 | 2015-10-30T00:00:341 | |||||
KTMPVN04407 | 16181 | 2018 | yes | Nha Thuoc Phuong | Ha Noi | Cau Giay | 21.009994 | 105.807551 | 105.807551,21.009994 | pharmacy | 2015-10-30T00:00:342 | 2015-10-30T00:00:342 | |||||
KTMPVN04430 | 9000 | 2018 | yes | Nha Thuoc Vinh Trung | Da Nang | Thanh Khe | 16.0666131 | 108.2110401 | 108.211040099999,16.0666130999999 | pharmacy | 2015-10-30T00:00:343 | 2015-10-30T00:00:343 | |||||
KTMPVN04433 | 16255 | 2018 | yes | Nha Thuoc Ha Phuong | Ha Noi | Cau Giay | 21.043061 | 105.792703 | 105.792703,21.043061 | pharmacy | 2015-10-30T00:00:344 | 2015-10-30T00:00:344 | |||||
KTMPVN04440 | 17406 | 2018 | yes | Nha Thuoc Thai Tran | Can Tho | Ninh Kieu | 10.035507 | 105.775507 | 105.775507,10.035507 | pharmacy | 2015-10-30T00:00:345 | 2015-10-30T00:00:345 | |||||
KTMPVN04456 | 11121 | 2018 | yes | Nha Thuoc Quang Linh | Ha Noi | Thanh Xuan | 20.98972 | 105.81546 | 105.81546,20.98972 | pharmacy | 2015-10-30T00:00:346 | 2015-10-30T00:00:346 | |||||
KTMPVN04490 | 16239 | 2018 | yes | Nha Thuoc Hong Dao | Can Tho | Binh Thuy | 10.060778 | 105.764803 | 105.764803,10.060778 | pharmacy | 2015-10-30T00:00:347 | 2015-10-30T00:00:347 | |||||
KTMPVN04515 | 11143 | 2018 | yes | Nha Thuoc Pham Van Lai | Ha Noi | Hoang Mai | 20.98223 | 105.83258 | 105.83258,20.98223 | pharmacy | 2015-10-30T00:00:348 | 2015-10-30T00:00:348 | |||||
KTMPVN04546 | 9081 | 2018 | yes | Nha Thuoc Thai An 2 | Can Tho | Ninh Kieu | 10.044757 | 105.766365 | 105.766365,10.044757 | pharmacy | 2015-10-30T00:00:349 | 2015-10-30T00:00:349 | |||||
KTMPVN04571 | 22669 | 2018 | yes | Nha Thuoc Minh Ha 1 | Ha Noi | Cau Giay | 21.034829 | 105.784652 | 105.784652,21.034829 | pharmacy | 2015-10-30T00:00:350 | 2015-10-30T00:00:350 | |||||
KTMPVN04665 | 11842 | 2018 | yes | Nha Thuoc Minh Hieu II | Ha Noi | Dong Da | 21.0068 | 105.8205 | 105.8205,21.0068 | pharmacy | 2015-10-30T00:00:351 | 2015-10-30T00:00:351 | |||||
KTMPVN04767 | 12798 | 2018 | yes | Nha Thuoc Ngoc Quyen | Can Tho | Ninh Kieu | 10.031876 | 105.77501 | 105.77501,10.031876 | pharmacy | 2015-10-30T00:00:352 | 2015-10-30T00:00:352 | |||||
KTMPVN04783 | 11597 | 2018 | yes | Nha Thuoc Thanh An | Ha Noi | Tay Ho | 21.0472231 | 105.8110643 | 105.8110643,21.0472231 | pharmacy | 2015-10-30T00:00:353 | 2015-10-30T00:00:353 | |||||
KTMPVN04842 | 17227 | 2018 | yes | Nha Thuoc Ngoc Diep | Ha Noi | Cau Giay | 21.019413 | 105.800103 | 105.800103,21.019413 | pharmacy | 2015-10-30T00:00:354 | 2015-10-30T00:00:354 | |||||
KTMPVN04843 | 11241 | 2018 | yes | Nha Thuoc HP | Can Tho | Binh Thuy | 10.05451 | 105.771217 | 105.771217,10.05451 | pharmacy | 2015-10-30T00:00:355 | 2015-10-30T00:00:355 | |||||
KTMPVN04880 | 6450 | 2018 | yes | Nha Thuoc Huong Giang | Ha Noi | Ha Dong | 20.970377 | 105.7681695 | 105.7681695,20.970377 | pharmacy | 2015-10-30T00:00:356 | 2015-10-30T00:00:356 | |||||
KTMPVN05016 | 13990 | 2018 | yes | Nha Thuoc Tuyet Anh | Ha Noi | Cau Giay | 21.04149 | 105.80253 | 105.80253,21.04149 | pharmacy | 2015-10-30T00:00:357 | 2015-10-30T00:00:357 | |||||
KTMPVN05061 | 16246 | 2018 | yes | NT Nguyen Thi Chung | Ha Noi | Cau Giay | 21.02567 | 105.7961 | 105.7961,21.02567 | pharmacy | 2015-10-30T00:00:358 | 2015-10-30T00:00:358 | |||||
KTMPVN05102 | 24069 | 2018 | yes | Nha Thuoc Tran At Dau | Ha Noi | Bac Tu Liem | 10.809842 | 106.712629 | 106.712629,10.809842 | pharmacy | 2015-10-30T00:00:359 | 2015-10-30T00:00:359 | |||||
KTMPVN05111 | 8149 | 2018 | yes | Nha Thuoc Thuy Nga | Can Tho | Ninh Kieu | 10.0391813 | 105.7862143 | 105.7862143,10.0391813 | pharmacy | 2015-10-30T00:00:360 | 2015-10-30T00:00:360 | |||||
KTMPVN05112 | 8810 | 2018 | yes | Nha Thuoc Minh Chau | Da Nang | Hai Chau | 16.0632037 | 108.214167 | 108.214167,16.0632037 | pharmacy | 2015-10-30T00:00:361 | 2015-10-30T00:00:361 | |||||
KTMPVN05143 | 16180 | 2018 | yes | Nha Thuoc Ngoc Mai | Ha Noi | Cau Giay | 21.009763 | 105.80758 | 105.80758,21.009763 | pharmacy | 2015-10-30T00:00:362 | 2015-10-30T00:00:362 | |||||
KTMPVN05154 | 8774 | 2018 | yes | Nha Thuoc Song Hong | Ha Noi | Hoan Kiem | 21.030239 | 105850025 | 105850025,21.030239 | pharmacy | 2015-10-30T00:00:363 | 2015-10-30T00:00:363 | |||||
KTMPVN05158 | 7992 | 2018 | yes | Nha Thuoc Phuong Linh | Ha Noi | Cau Giay | 21.016881 | 105.803154 | 105.803154,21.016881 | pharmacy | 2015-10-30T00:00:364 | 2015-10-30T00:00:364 | |||||
KTMPVN05219 | 17586 | 2018 | yes | Nha Thuoc 40 Ngo Sy Lien | Ha Noi | DONG DA | 21.027362 | 105.838738 | 105.838738,21.027362 | pharmacy | 2015-10-30T00:00:365 | 2015-10-30T00:00:365 | |||||
KTMPVN05280 | 17481 | 2018 | yes | Nha Thuoc Viet Duc 2 | Ha Noi | 15 Pho Phu Doan- P. Hang Trong | 21.029705 | 105.847681 | 105.847681,21.029705 | pharmacy | 2015-10-30T00:00:366 | 2015-10-30T00:00:366 | |||||
KTMPVN05302 | 19038 | 2018 | yes | Nha Thuoc Thinh Thao | Da Nang | Thanh Khe | 16.0709113 | 108.1915452 | 108.1915452,16.0709113 | pharmacy | 2015-10-30T00:00:367 | 2015-10-30T00:00:367 | |||||
KTMPVN05420 | 9217 | 2018 | yes | Nha Thuoc Thien Kim | Can Tho | Ninh Kieu | 10.021066 | 105.766174 | 105.766174,10.021066 | pharmacy | 2015-10-30T00:00:368 | 2015-10-30T00:00:368 | |||||
KTMPVN05422 | 11485 | 2018 | yes | Nha Thuoc Phuc Loi | Can Tho | Ninh Kieu | 10.013894 | 105.757951 | 105.757951,10.013894 | pharmacy | 2015-10-30T00:00:369 | 2015-10-30T00:00:369 | |||||
KTMPVN05579 | 16705 | 2018 | yes | NT An Tam | Da Nang | Son Tra | 16.0628641 | 108.2341839 | 108.2341839,16.0628641 | pharmacy | 2015-10-30T00:00:370 | 2015-10-30T00:00:370 | |||||
KTMPVN05683 | 16195 | 2018 | yes | Cong Ty Co Phan Sieu Thi Thuoc Viet | Ha Noi | Ba Dinh | 21.019602 | 105.817412 | 105.817412,21.019602 | pharmacy | 2015-10-30T00:00:371 | 2015-10-30T00:00:371 | |||||
KTMPVN05794 | 5194 | 2018 | yes | Nha Thuoc Duc Lan | Ha Noi | Hoang Mai | 20.9822926 | 105.8498522 | 105.8498522,20.9822926 | pharmacy | 2015-10-30T00:00:372 | 2015-10-30T00:00:372 | |||||
KTMPVN05876 | 14107 | 2018 | yes | Nha Thuoc Thanh Thuy | Can Tho | Ninh Kieu | 10.0346065 | 105.7879168 | 105.787916799999,10.0346064999999 | pharmacy | 2015-10-30T00:00:373 | 2015-10-30T00:00:373 | |||||
KTMPVN06189 | 8826 | 2018 | yes | Nha Thuoc Kim Hien | Can Tho | Ninh Kieu | 10.014724 | 105.75489 | 105.75489,10.014724 | pharmacy | 2015-10-30T00:00:374 | 2015-10-30T00:00:374 | |||||
KTMPVN06265 | 9007 | 2018 | yes | Nha Thuoc Thanh Lieu | Can Tho | Binh Thuy | 10.054631 | 105.725364 | 105.725364,10.054631 | pharmacy | 2015-10-30T00:00:375 | 2015-10-30T00:00:375 | |||||
KTMPVN06446 | 4315 | 2018 | yes | Nha Thuoc Van Kha | Da Nang | Hai Chau | 16.0529773 | 108.2178659 | 108.217865899999,16.0529773 | pharmacy | 2015-10-30T00:00:376 | 2015-10-30T00:00:376 | |||||
KTMPVN06518 | 11073 | 2018 | yes | Nha Thuoc Trung Tam | Ha Noi | Thanh Xuan | 20.98798 | 105.8195 | 105.8195,20.98798 | pharmacy | 2015-10-30T00:00:377 | 2015-10-30T00:00:377 | |||||
KTMPVN06538 | 6904 | 2018 | yes | Nha Thuoc Tan Thanh | Ha Noi | Thanh Xuan | 20.998089 | 105.82118 | 105.82118,20.998089 | pharmacy | 2015-10-30T00:00:378 | 2015-10-30T00:00:378 | |||||
KTMPVN06581 | 8123 | 2018 | yes | Nha Thuoc Thanh Trang | Da Nang | Son Tra | 16.0756552 | 108.2308014 | 108.2308014,16.0756552 | pharmacy | 2015-10-30T00:00:379 | 2015-10-30T00:00:379 | |||||
KTMPVN06635 | 9045 | 2018 | yes | Nha Thuoc Xuan Thanh Binh | Can Tho | Binh Thuy | 10.040328 | 105.719923 | 105.719923,10.040328 | pharmacy | 2015-10-30T00:00:380 | 2015-10-30T00:00:380 | |||||
KTMPVN06676 | 11375 | 2018 | yes | Nha Thuoc Huy Hoang | Ha Noi | Hoan Kiem | 21.036535 | 105.851363 | 105.851363,21.036535 | pharmacy | 2015-10-30T00:00:381 | 2015-10-30T00:00:381 | |||||
KTMPVN06696 | 7630 | 2018 | yes | Nha Thuoc Hong Phuc | Ha Noi | Tu Liem | 21.0498113 | 105.7897019 | 105.7897019,21.0498113 | pharmacy | 2015-10-30T00:00:382 | 2015-10-30T00:00:382 | |||||
KTMPVN11803 | 7465 | 2018 | yes | Nha Thuoc 24 Hang Non | Ha Noi | 21.032439 | 105.848088 | 105.848088,21.032439 | pharmacy | 2015-10-30T00:00:383 | 2015-10-30T00:00:383 | ||||||
KTMPVN11804 | 11139 | 2018 | yes | Nha Thuoc Hai Binh | Ha Noi | Thanh Xuan | 20.99976 | 105.81621 | 105.81621,20.99976 | pharmacy | 2015-10-30T00:00:384 | 2015-10-30T00:00:384 | |||||
KTMPVN11818 | 17512 | 2018 | yes | Nha Thuoc 59 Quoc Tu Giam | Ha Noi | Dong Da | 21.02718 | 105.835871 | 105.835871,21.02718 | pharmacy | 2015-10-30T00:00:385 | 2015-10-30T00:00:385 | |||||
KTMPVN11819 | 9300 | 2018 | yes | Nha Thuoc Minh Hieu | Ha Noi | 21.00557 | 105.82158 | 105.82158,21.00557 | pharmacy | 2015-10-30T00:00:386 | 2015-10-30T00:00:386 | ||||||
KTMPVN11824 | 9197 | 2018 | yes | Nha Thuoc Nguyen Bang | Ha Noi | 21.010311 | 105.818748 | 105.818748,21.010311 | pharmacy | 2015-10-30T00:00:387 | 2015-10-30T00:00:387 | ||||||
KTMPVN11830 | 16202 | 2018 | yes | Nha Thuoc 88 Phap Dai Lang | Ha Noi | Dong Da | 21.019368 | 105.805424 | 105.805424,21.019368 | pharmacy | 2015-10-30T00:00:388 | 2015-10-30T00:00:388 | |||||
KTMPVN11876 | 11780 | 2018 | yes | Nha Thuoc Duc Duy | Ha Noi | 21.0483197 | 105.8084283 | 105.8084283,21.0483197 | pharmacy | 2015-10-30T00:00:389 | 2015-10-30T00:00:389 | ||||||
KTMPVN11880 | 17673 | 2018 | yes | Nha Thuoc Do Tuyet Mai | Ha Noi | Tay Ho | 21.043217 | 105.821851 | 105.821851,21.043217 | pharmacy | 2015-10-30T00:00:390 | 2015-10-30T00:00:390 | |||||
KTMPVN11980 | 9213 | 2018 | yes | Nha Thuoc Nguyen Phuong Anh | Ha Noi | 21.00402 | 105.83945 | 105.83945,21.00402 | pharmacy | 2015-10-30T00:00:391 | 2015-10-30T00:00:391 | ||||||
KTMPVN12478 | 11886 | 2018 | yes | Nha Thuoc Thanh Binh | Can Tho | 10.0215144 | 105.7704323 | 105.7704323,10.0215144 | pharmacy | 2015-10-30T00:00:392 | 2015-10-30T00:00:392 | ||||||
KTMPVN12587 | 7682 | 2018 | yes | Nha Thuoc Trong Tan 3 | Ha Noi | Ba Dinh | 21.0259543 | 105.8099353 | 105.8099353,21.0259543 | pharmacy | 2015-10-30T00:00:393 | 2015-10-30T00:00:393 | |||||
KTMPVN12603 | 9161 | 2018 | yes | Nha Thuoc Vinh Ha- Duoc Sy Dang Thi Ngoc | Ha Noi | 21.01603 | 105.8248 | 105.8248,21.01603 | pharmacy | 2015-10-30T00:00:394 | 2015-10-30T00:00:394 | ||||||
KTMPVN12740 | 7100 | 2018 | yes | Nha Thuoc Trong Tan- Vu Hien | Ha Noi | 21.02573 | 105.80985 | 105.80985,21.02573 | pharmacy | 2015-10-30T00:00:395 | 2015-10-30T00:00:395 | ||||||
KTMPVN12955 | 8457 | 2018 | yes | Nha Thuoc Thanh Phuong | Da Nang | Hai Chau | 16.0611506 | 108.2209782 | 108.2209782,16.0611506 | pharmacy | 2015-10-30T00:00:396 | 2015-10-30T00:00:396 | |||||
KTMPVN13139 | 11486 | 2018 | yes | Nha Thuoc Thuan An | Can Tho | Ninh Kieu | 10.0371864 | 105.7588729 | 105.7588729,10.0371864 | pharmacy | 2015-10-30T00:00:397 | 2015-10-30T00:00:397 | |||||
KTMPVN13203 | 7876 | 2018 | yes | Quay Thuoc Viet Cuong So 2 | Ha Noi | 21.04964 | 105.78988 | 105.78988,21.04964 | pharmacy | 2015-10-30T00:00:398 | 2015-10-30T00:00:398 | ||||||
KTMPVN13206 | 11624 | 2018 | yes | Nha Thuoc Luong Thien Quan | Ha Noi | 21.046646 | 105.846832 | 105.846832,21.046646 | pharmacy | 2015-10-30T00:00:399 | 2015-10-30T00:00:399 | ||||||
KTMPVN13210 | 9247 | 2018 | yes | NHA THUOC TRONG TIN | Ha Noi | 21.02299 | 105.81569 | 105.81569,21.02299 | pharmacy | 2015-10-30T00:00:400 | 2015-10-30T00:00:400 | ||||||
KTMPVN13247 | 7404 | 2018 | yes | Nha Thuoc DS Ha Minh Hien | Ha Noi | Hai Ba Trung | 20.992056 | 105.844087 | 105.844087,20.992056 | pharmacy | 2015-10-30T00:00:401 | 2015-10-30T00:00:401 | |||||
KTMPVN13268 | 16204 | 2018 | yes | Nha Thuoc Thu Uyen | Ha Noi | Hoan Kiem | 21.033413 | 105.856501 | 105.856501,21.033413 | pharmacy | 2015-10-30T00:00:402 | 2015-10-30T00:00:402 | |||||
KTMPVN16378 | 6174 | 2018 | yes | BINH AN | Can Tho | NINH KIEU | 10.043826 | 105.781094 | 105.781094,10.043826 | pharmacy | 2015-10-30T00:00:403 | 2015-10-30T00:00:403 | |||||
KTMPVN16385 | 17623 | 2018 | yes | 24H 91 | Ha Noi | HOAN KIEM | 21.02805 | 105.647768 | 105.647768,21.02805 | pharmacy | 2015-10-30T00:00:404 | 2015-10-30T00:00:404 | |||||
KTMPVN16507 | 14072 | 2018 | yes | Nha Thuoc DS Pham Thi Tuyet Huong | Ha Noi | Cau Giay | 21.035351 | 105.80037 | 105.80037,21.035351 | pharmacy | 2015-10-30T00:00:405 | 2015-10-30T00:00:405 | |||||
KTMPVN16512 | 16447 | 2018 | yes | Nha Thuoc Minh Thu | Ha Noi | Cau Giay | 21.012649 | 105.803646 | 105.803646,21.012649 | pharmacy | 2015-10-30T00:00:406 | 2015-10-30T00:00:406 | |||||
KTMPVN16532 | 7992 | 2018 | yes | Nha Thuoc Phuong Linh | Ha Noi | Cau Giay | 21.016881 | 105.803154 | 105.803154,21.016881 | pharmacy | 2015-10-30T00:00:407 | 2015-10-30T00:00:407 | |||||
KTMPVN16538 | 9078 | 2018 | yes | Nha Thuoc 12 Thanh Binh | Ha Noi | Ha Dong | 20.7992042 | 105.7801415 | 105.7801415,20.7992042 | pharmacy | 2015-10-30T00:00:408 | 2015-10-30T00:00:408 | |||||
KTMPVN16544 | 16042 | 2018 | yes | Nha Thuoc Nhat Anh | Ha Noi | Ha Dong | 20.982587 | 105.787999 | 105.787999,20.982587 | pharmacy | 2015-10-30T00:00:409 | 2015-10-30T00:00:409 | |||||
KTMPVN16546 | 11740 | 2018 | yes | Nha Thuoc Thien Nhan | Ha Noi | Ha Dong | 20.98101 | 105.78575 | 105.78575,20.98101 | pharmacy | 2015-10-30T00:00:410 | 2015-10-30T00:00:410 | |||||
KTMPVN16604 | 9008 | 2018 | yes | Nha Thuoc Phu Vien | Ha Noi | Long Bien | 21.033691 | 105.869844 | 105.869844,21.033691 | pharmacy | 2015-10-30T00:00:411 | 2015-10-30T00:00:411 | |||||
KTMPVN18003 | 6755 | 2018 | yes | DAPHARCO63 | Da Nang | HAI CHAU | 16.0722928 | 108.2177819 | 108.217781899999,16.0722928 | pharmacy | 2015-10-30T00:00:412 | 2015-10-30T00:00:412 | |||||
KTMPVN18004 | 17486 | 2018 | yes | NGAN THUY | Can Tho | NINH KIEU | 10.031573 | 105.782485 | 105.782485,10.031573 | pharmacy | 2015-10-30T00:00:413 | 2015-10-30T00:00:413 | |||||
KTMPVN18005 | 6959 | 2018 | yes | TRONG TAN 57 | Ha Noi | BA DINH | 21.0259151 | 105.8100046 | 105.8100046,21.0259151 | pharmacy | 2015-10-30T00:00:414 | 2015-10-30T00:00:414 | |||||
KTMPVN18006 | 15980 | 2018 | yes | THU LAN 2 | Da Nang | THANH KHE | 16.069023 | 108.201546 | 108.201546,16.069023 | pharmacy | 2015-10-30T00:00:415 | 2015-10-30T00:00:415 | |||||
KTMPVN18008 | 8381 | 2018 | yes | PHUOC THIEN 1 | Da Nang | HAI CHAU | 16.0516018 | 108.2165764 | 108.2165764,16.0516018 | pharmacy | 2015-10-30T00:00:416 | 2015-10-30T00:00:416 | |||||
KTMPVN18009 | 17281 | 2018 | yes | MANH TY 4 | Hue | HUE | 16.472028 | 107.588069 | 107.588069,16.472028 | pharmacy | 2015-10-30T00:00:417 | 2015-10-30T00:00:417 | |||||
KTMPVN18012 | 16183 | 2018 | yes | DUC ANH 8 | Ha Noi | CAU GIAY | 21.009175 | 105.808023 | 105.808023,21.009175 | pharmacy | 2015-10-30T00:00:418 | 2015-10-30T00:00:418 | |||||
KTMPVN18014 | 14033 | 2018 | yes | NGOC ANH | Ha Noi | THANH XUAN | 20.98469 | 105.81512 | 105.81512,20.98469 | pharmacy | 2015-10-30T00:00:419 | 2015-10-30T00:00:419 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment