Skip to content

Instantly share code, notes, and snippets.

@renauld94
Last active October 25, 2018 04:44
Show Gist options
  • Save renauld94/e8b5fdda8b609859ebb784c5812f7cc3 to your computer and use it in GitHub Desktop.
Save renauld94/e8b5fdda8b609859ebb784c5812f7cc3 to your computer and use it in GitHub Desktop.
data table connecting
license: mit
!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);
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;
}
/*!
* 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);
}
}
)();
!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
<!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 &copy; <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 &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <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 &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <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>
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}}
/*!
* 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;
}));
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