chartogram
Advanced tools
Comparing version 0.1.4 to 0.1.5
@@ -1,2 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).chartogram=e()}(this,function(){"use strict";function t(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=[],r=!0,a=!1,i=void 0;try{for(var o,c=t[Symbol.iterator]();!(r=(o=c.next()).done)&&(n.push(o.value),!e||n.length!==e);r=!0);}catch(t){a=!0,i=t}finally{try{r||null==c.return||c.return()}finally{if(a)throw i}}return n}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function e(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{},a=Object.keys(r);"function"==typeof Object.getOwnPropertySymbols&&(a=a.concat(Object.getOwnPropertySymbols(r).filter(function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable}))),a.forEach(function(e){n(t,e,r[e])})}return t}function n(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function r(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}return function(n,a){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Title",o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};n.innerHTML='\n\t\t<header class="chartogram__header">\n\t\t\t<h1 class="chartogram__title">'.concat(i,'</h1>\n\t\t</header>\n\t\t<div class="chartogram__plan-with-axes">\n\t\t\t<div class="chartogram__plan">\n\t\t\t\t<div class="chartogram__top-border"></div>\n\t\t\t\t<div class="chartogram__canvas-wrapper">\n\t\t\t\t\t<svg class="chartogram__canvas" preserveAspectRatio="none"></svg>\n\t\t\t\t\t<div class="chartogram__x"></div>\n\t\t\t\t\t<div class="chartogram__y-wrapper">\n\t\t\t\t\t\t<div class="chartogram__y"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class="chartogram__timeline">\n\t\t\t<div class="chartogram__timeline-canvas-padding">\n\t\t\t\t<svg class="chartogram__timeline-canvas" preserveAspectRatio="none"></svg>\n\t\t\t</div>\n\t\t\t<div class="chartogram__timeline-overlay-left"></div>\n\t\t\t<div class="chartogram__timeline-overlay-right"></div>\n\t\t\t<div class="chartogram__timeline-window">\n\t\t\t\t<button type="button" class="chartogram__reset-button chartogram__timeline-window__drag"></button>\n\t\t\t\t<button type="button" class="chartogram__reset-button chartogram__timeline-window__left-handle"></button>\n\t\t\t\t<button type="button" class="chartogram__reset-button chartogram__timeline-window__right-handle"></button>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class="chartogram__chart-togglers"></div>\n\t'),n.classList.add("chartogram");var c,l,d,s,u,h,m,v,f,g,p,y,_,b,w,x=o.gaugeMarkTicksCount||6,A=o.timelineWindowSize||40,E=(o.timelineChartMaxPoints,o.months||["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]),L=o.weekdays||["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],S=document.querySelector(".chartogram__plan"),C=document.querySelector(".chartogram__canvas"),M=document.querySelector(".chartogram__canvas-wrapper"),k=document.querySelector(".chartogram__x"),z=document.querySelector(".chartogram__y"),N=document.querySelector(".chartogram__timeline"),T=document.querySelector(".chartogram__timeline-overlay-left"),q=document.querySelector(".chartogram__timeline-window__left-handle"),O=document.querySelector(".chartogram__timeline-window"),j=document.querySelector(".chartogram__timeline-window__drag"),B=document.querySelector(".chartogram__timeline-window__right-handle"),R=document.querySelector(".chartogram__timeline-overlay-right"),X=document.querySelector(".chartogram__timeline-canvas"),D=1;function I(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function Y(t,e){return n=t.map(F),r=e.map(F),n.map(function(t,e){return"".concat(t,",").concat(r[e])});var n,r}function F(t){return Math.round(t*Number.MAX_SAFE_INTEGER)/Number.MAX_SAFE_INTEGER}function P(t,e,n,r){for(var a=0;a<x;){var i=document.createElement("div"),o=e+a*(n-e)/(x-1);r&&(o=r(o)),i.appendChild(document.createTextNode(o)),t.appendChild(i),a++}}function H(t){var e=t[0]===a.x,n=1/0,i=-1/0,o=t,c=Array.isArray(o),l=0;for(o=c?o:o[Symbol.iterator]();;){var d;if(c){if(l>=o.length)break;d=o[l++]}else{if((l=o.next()).done)break;d=l.value}var s=d;e?(s.min=s.points[0],s.max=s.points[s.points.length-1]):(s.min=0,s.max=Math.max.apply(Math,r(s.points))),n=Math.min(n,s.min),i=Math.max(i,s.max)}e||(n=0);var u=n,h=i-n,m=t,v=Array.isArray(m),f=0;for(m=v?m:m[Symbol.iterator]();;){var g;if(v){if(f>=m.length)break;g=m[f++]}else{if((f=m.next()).done)break;g=f.value}var p=g;p.normalized={points:p.points.map(function(t){return(t-u)/h}),shift:u,scale:h}}}function J(n){I(C);var i=a.x.points,o=i[0],g=i[i.length-1],p=g-o,y=o+c*p,_=g-(1-l)*p,b=i.findIndex(function(t){return t>y})-1;b<0&&(b=0);var w=i.findIndex(function(t){return t>_});w<0&&(w=i.length-1),f=a.x.normalized.points.slice(b,w+1);var A=-1/0;s=[];var L=a.y,S=Array.isArray(L),M=0;for(L=S?L:L[Symbol.iterator]();;){var N;if(S){if(M>=L.length)break;N=L[M++]}else{if((M=L.next()).done)break;N=M.value}var T=N;if(d[T.id]){var q=T.points.slice(b,w+1),O=Math.max.apply(Math,r(q));s.push(e({},T,{points:q,min:0,max:O,normalized:e({},T.normalized,{points:T.normalized.points.slice(b,w+1)})})),A=Math.max.apply(Math,[A].concat(r(q)))}}var j=(y-a.x.normalized.shift)/a.x.normalized.scale,B=(_-a.x.normalized.shift)/a.x.normalized.scale,R=(0-a.y[0].normalized.shift)/a.y[0].normalized.scale,F=(A-a.y[0].normalized.shift)/a.y[0].normalized.scale;C.setAttribute("viewBox","".concat(j," ").concat(R," ").concat(B-j," ").concat(F-R));var H=function(t,e){for(;;){if(t<e)return e;if(t%e==0)return t;t--}}(A,10),J=(A-0)/(H-0),K=function(t,e){for(var n=new Array(x),r=0;r<x;)n[r]=t+r*(e-t)/(x-1),r++;return n}(0,H),Q=Array.isArray(K),U=0;for(K=Q?K:K[Symbol.iterator]();;){var V;if(Q){if(U>=K.length)break;V=K[U++]}else{if((U=K.next()).done)break;V=U.value}var Z=V;C.appendChild(W((Z-a.y[0].normalized.shift)/a.y[0].normalized.scale,j,B,R,F))}var $=f.slice(),tt=$[0],et=$[$.length-1],nt=(j-tt)/($[1]-tt),rt=(et-B)/(et-$[$.length-2]);$[0]=j,$[$.length-1]=B;var at=s,it=Array.isArray(at),ot=0;for(at=it?at:at[Symbol.iterator]();;){var ct;if(it){if(ot>=at.length)break;ct=at[ot++]}else{if((ot=at.next()).done)break;ct=ot.value}var lt=ct,dt=(lt.id,lt.color),st=lt.normalized.points.slice(),ut=st[0],ht=st[st.length-1];st[0]=ut+(st[1]-ut)*nt,st[st.length-1]=ht-(ht-st[st.length-2])*rt;var mt=document.createElement("polyline");mt.setAttribute("stroke",dt),mt.setAttribute("points",Y($,st.map(function(t){return F-D*t})).join(" ")),mt.classList.add("chartogram__graph"),C.appendChild(mt)}C.innerHTML+="",function(t,e,n,r,a){I(k),I(z),P(k,t,e,function(t){var e=new Date(t);return"".concat(E[e.getMonth()]," ").concat(e.getDate())}),P(z,n,r),z.style.height="".concat(100/a,"%")}(y,_,0,H,J),m=R,v=F,u=j,h=B,n&&function(){var e=a.x.normalized.points,n=a.y.filter(function(t){return d[t.id]}),i=(Math.min.apply(Math,r(n.map(function(t){return t.min})))-n[0].normalized.shift)/n[0].normalized.scale,o=(Math.max.apply(Math,r(n.map(function(t){return t.max})))-n[0].normalized.shift)/n[0].normalized.scale;I(X),X.setAttribute("viewBox","".concat(0," ").concat(i," ").concat(1," ").concat(o-i));for(var c=n,l=Array.isArray(c),s=0,c=l?c:c[Symbol.iterator]();;){var u;if(l){if(s>=c.length)break;u=c[s++]}else{if((s=c.next()).done)break;u=s.value}var h=u,m=(h.id,h.color),v=h.normalized.points,f=G(e,v,80),g=t(f,2),p=g[0],y=g[1],_=document.createElement("polyline");_.setAttribute("stroke",m),_.setAttribute("points",Y(p,y.map(function(t){return o-i-t})).join(" ")),_.classList.add("chartogram__graph"),X.appendChild(_)}X.innerHTML+=""}()}function W(t,e,n,r,a){var i=document.createElement("line");return i.classList.add("chartogram__grid-line"),i.setAttribute("x1",F(e)),i.setAttribute("x2",F(n)),i.setAttribute("y1",F(a-r-t)),i.setAttribute("y2",F(a-r-t)),i}function G(t,e,n){var a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:Math.max.apply(Math,r(e)),i=arguments.length>4&&void 0!==arguments[4]?arguments[4]:.025,o=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0,c=arguments.length>6&&void 0!==arguments[6]?arguments[6]:new Array(t.length),l=arguments.length>7&&void 0!==arguments[7]?arguments[7]:new Array(t.length),d=arguments.length>8&&void 0!==arguments[8]?arguments[8]:0;if(o+2>t.length-1){for(;o<e.length;)c[d]=t[o],l[d]=e[o],d++,o++;return c=c.slice(0,d),l=l.slice(0,d),c.length<=n?[c,l]:(t.length/c.length<1.1&&(i=Math.min(i+.025,1)),G(c,l,n,a,i))}var s=(e[o+2]+e[o])/2;return Math.abs(s-e[o+1])/a<i?(c[d]=t[o],c[d+1]=t[o+2],l[d]=e[o],l[d+1]=e[o+2],G(t,e,n,a,i,o+2,c,l,d+1)):(c[d]=t[o],c[d+1]=t[o+1],c[d+2]=t[o+2],l[d]=e[o],l[d+1]=e[o+1],l[d+2]=e[o+2],G(t,e,n,a,i,o+2,c,l,d+2))}function K(t){var e=t.id,n=t.name,r=t.color,a=document.createElement("button");a.setAttribute("type","button"),a.classList.add("chartogram__chart-toggler"),a.classList.add("chartogram__chart-toggler--on"),a.classList.add("chartogram__reset-button");var i="http://www.w3.org/2000/svg",o=document.createElementNS(i,"svg");o.setAttributeNS(null,"viewBox","0 0 19 19"),o.classList.add("chartogram__chart-toggler-check");var c=document.createElementNS(i,"circle");c.setAttribute("cx","9.5"),c.setAttribute("cy","9.5"),c.setAttribute("r","9.5"),c.setAttribute("fill",r),o.appendChild(c);var l=document.createElementNS(i,"circle");l.setAttribute("cx","9.5"),l.setAttribute("cy","9.5"),l.setAttribute("r","8"),l.classList.add("chartogram__chart-toggler-check-circle"),o.appendChild(l);var s=document.createElementNS(i,"path");return s.setAttribute("d","M13.64 4.94l-6.2 6.34-1.69-1.9c-.73-.63-1.89.1-1.36 1.06l2 3.38c.3.43 1.04.85 1.78 0 .32-.42 6.31-7.93 6.31-7.93.74-.84-.2-1.58-.84-.95z"),s.setAttribute("fill","white"),s.classList.add("chartogram__chart-toggler-check-mark"),o.appendChild(s),a.appendChild(o),a.appendChild(document.createTextNode(n)),a.addEventListener("click",function(){var t=!d[e];(t||1!==Object.keys(d).filter(function(t){return!1!==d[t]}).length)&&(d[e]=t,J(!0),a.classList.toggle("chartogram__chart-toggler--on"))}),a}function Q(t){var e,n,r,a,i="left"===t?q:B,o=parseFloat(getComputedStyle(O).borderLeftWidth);return U(i,function(i){e=N.getBoundingClientRect();var c=O.getBoundingClientRect();"left"===t?(n=e.x,r=c.x+c.width-2*o,a=i-c.x):(n=c.x+2*o,r=e.x+e.width,a=i-(c.x+c.width))},function(i){i-=a,i=((i=Math.max(Math.min(i,r),n))-e.x)/e.width,"left"===t?c=i:l=i,V()})}function U(t,e,n){function r(t){n(t.changedTouches[0].clientX,t.changedTouches[0].clientY)}function a(t){n(t.clientX,t.clientY)}function i(){window.removeEventListener("pointermove",a),window.removeEventListener("touchmove",r),window.removeEventListener("pointerup",i),window.removeEventListener("pointercancel",i),window.removeEventListener("touchend",i),window.removeEventListener("touchcancel",i)}function o(t){if(t.touches.length>1)return i();e(t.changedTouches[0].clientX,t.changedTouches[0].clientY),window.addEventListener("touchmove",r),window.addEventListener("touchend",i),window.addEventListener("touchcancel",i)}function c(t){e(t.clientX,t.clientY),window.addEventListener("pointermove",a),window.addEventListener("pointerup",i),window.addEventListener("pointercancel",i)}return t.addEventListener("touchstart",o),t.addEventListener("pointerdown",c),function(){i(),t.removeEventListener(c),t.removeEventListener(o)}}function V(){var t;t=c,T.style.right="".concat(100*(1-t),"%"),O.style.left="".concat(100*t,"%"),function(t){R.style.left="".concat(100*t,"%"),O.style.right="".concat(100*(1-t),"%")}(l),J(!1)}function Z(){g&&(_=void 0,S.removeChild(g),g=void 0,function(){for(var t=b,e=Array.isArray(t),n=0,t=e?t:t[Symbol.iterator]();;){var r;if(e){if(n>=t.length)break;r=t[n++]}else{if((n=t.next()).done)break;r=n.value}var a=r;M.removeChild(a)}b=void 0}(),C.removeChild(w),w=void 0)}Q("left"),Q("right"),U(j,function(t){$=N.getBoundingClientRect(),tt=O.getBoundingClientRect(),rt=t-tt.x,et=$.x,nt=$.x+($.width-tt.width)},function(t){t-=rt,t=((t=Math.max(Math.min(t,nt),et))-$.x)/$.width,c=t,l=t+tt.width/$.width,V()}),H([a.x]),H(a.y),function(){d={};for(var t=a.y,e=Array.isArray(t),n=0,t=e?t:t[Symbol.iterator]();;){var r;if(e){if(n>=t.length)break;r=t[n++]}else{if((n=t.next()).done)break;r=n.value}var i=r;d[i.id]=!0}if(a.x.points.length>A){var o=a.x.points[0],s=a.x.points[a.x.points.length-1],u=a.x.points[a.x.points.length-A];c=(u-o)/(s-o)}else c=0;l=1,V(),J(!0),w=void 0;var h=document.querySelector(".chartogram__chart-togglers");I(h);for(var m=a.y,v=Array.isArray(m),f=0,m=v?m:m[Symbol.iterator]();;){var g;if(v){if(f>=m.length)break;g=m[f++]}else{if((f=m.next()).done)break;g=f.value}var p=g;h.appendChild(K(p))}}(),function(){var t,e;function n(n){var r=(n-t.x)/t.width,i=u+r*(h-u),o=f.findIndex(function(t){return t>=i}),c=o-1;if(e(o)||(o=-1),e(c)||(c=-1),o<0){if(c<0)return Z();i=f[c]}else if(c<0)i=f[o];else{var l=f[c],x=f[o],A=i-l,k=x-i;i=A>k?x:l}if(i!==_){_=i,g||function(){(g=document.createElement("div")).classList.add("chartogram__tooltip"),S.appendChild(g),(p=document.createElement("h1")).classList.add("chartogram__tooltip-header"),g.appendChild(p),(y=document.createElement("dl")).classList.add("chartogram__tooltip-values"),g.appendChild(y);for(var t=a.y,e=Array.isArray(t),n=0,t=e?t:t[Symbol.iterator]();;){var r;if(e){if(n>=t.length)break;r=t[n++]}else{if((n=t.next()).done)break;r=n.value}var i=r;if(d[i.id]){var o=document.createElement("dt");o.style.color=i.color,y.appendChild(o);var c=document.createElement("dd");c.style.color=i.color,y.appendChild(c)}}}();var z=new Date(i*a.x.normalized.scale+a.x.normalized.shift);p.textContent="".concat(L[z.getDay()],", ").concat(E[z.getMonth()]," ").concat(z.getDate());for(var N=f.indexOf(i),T=0;2*T<y.childNodes.length;)y.childNodes[2*T].textContent=s[T].points[N],y.childNodes[2*T+1].textContent=a.y[T].name,T++;var q=(i-u)/(h-u);g.style.left="".concat(100*q,"%"),function(t,e){b||function(){b=[];for(var t=0;t<s.length;){var e=document.createElement("div");e.classList.add("chartogram__tooltip-point"),e.style.color=s[t].color,b.push(e),M.appendChild(e),t++}}();for(var n=0;n<b.length;){var r=b[n];r.style.left="".concat(100*e,"%");var a=s[n].normalized.points[t],i=a/v;r.style.bottom="".concat(100*i,"%"),n++}}(N,q),function(t){w||((w=document.createElementNS("http://www.w3.org/2000/svg","line")).setAttributeNS(null,"class","chartogram__tooltip-line"),C.insertBefore(w,C.querySelector("polyline"))),w.setAttributeNS(null,"x1",F(t)),w.setAttributeNS(null,"x2",F(t)),w.setAttributeNS(null,"y1",F(m)),w.setAttributeNS(null,"y2",F(v))}(i)}}function r(){t=C.getBoundingClientRect(),e=function(t){return!(t<0)&&f[t]>=u&&f[t]<=h}}function i(t){if(t.touches.length>1)return l();r(),C.addEventListener("touchend",l),C.addEventListener("touchmove",o),C.addEventListener("touchend",l),C.addEventListener("touchcancel",l),o(t)}function o(e){var r=e.changedTouches[0].clientX,a=e.changedTouches[0].clientY;r<t.x||r>t.x+t.width||a<t.y||a>t.y+t.height?l():n(r)}function c(t){n(t.clientX,t.clientY)}function l(){C.removeEventListener("pointermove",c),C.removeEventListener("pointerleave",l),C.removeEventListener("pointercancel",l),C.removeEventListener("touchmove",o),C.removeEventListener("touchend",l),C.removeEventListener("touchcancel",l),Z()}function x(){r(),C.addEventListener("pointermove",c),C.addEventListener("pointerleave",l),C.addEventListener("pointercancel",l)}C.addEventListener("touchstart",i),C.addEventListener("pointerenter",x)}();var $,tt,et,nt,rt}}); | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).chartogram=e()}(this,function(){"use strict";function t(t){return function(t){if(Array.isArray(t)){for(var e=0,i=new Array(t.length);e<t.length;e++)i[e]=t[e];return i}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}function e(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function i(t,e,i,n,a){for(var o=0;o<n;){var r=document.createElement("div"),s=e+o*(i-e)/(n-1);a&&(s=a(s)),r.appendChild(document.createTextNode(s)),t.appendChild(r),o++}}function n(t,e,i){function n(t){i(t.changedTouches[0].clientX,t.changedTouches[0].clientY)}function a(t){i(t.clientX,t.clientY)}function o(){window.removeEventListener("pointermove",a),window.removeEventListener("touchmove",n),window.removeEventListener("pointerup",o),window.removeEventListener("pointercancel",o),window.removeEventListener("touchend",o),window.removeEventListener("touchcancel",o)}function r(t){if(t.touches.length>1)return o();e(t.changedTouches[0].clientX,t.changedTouches[0].clientY),window.addEventListener("touchmove",n),window.addEventListener("touchend",o),window.addEventListener("touchcancel",o)}function s(t){e(t.clientX,t.clientY),window.addEventListener("pointermove",a),window.addEventListener("pointerup",o),window.addEventListener("pointercancel",o)}return t.addEventListener("touchstart",r),t.addEventListener("pointerdown",s),function(){o(),t.removeEventListener(s),t.removeEventListener(r)}}function a(t){return function(t){if(Array.isArray(t)){for(var e=0,i=new Array(t.length);e<t.length;e++)i[e]=t[e];return i}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}function o(t){for(var e=1;e<arguments.length;e++){var i=null!=arguments[e]?arguments[e]:{},n=Object.keys(i);"function"==typeof Object.getOwnPropertySymbols&&(n=n.concat(Object.getOwnPropertySymbols(i).filter(function(t){return Object.getOwnPropertyDescriptor(i,t).enumerable}))),n.forEach(function(e){l(t,e,i[e])})}return t}function r(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var i=[],n=!0,a=!1,o=void 0;try{for(var r,s=t[Symbol.iterator]();!(n=(r=s.next()).done)&&(i.push(r.value),!e||i.length!==e);n=!0);}catch(t){a=!0,o=t}finally{try{n||null==s.return||s.return()}finally{if(a)throw o}}return i}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function s(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}function l(t,e,i){return e in t?Object.defineProperty(t,e,{value:i,enumerable:!0,configurable:!0,writable:!0}):t[e]=i,t}var c=function(){function c(s,d){var h,u,v,m,p,f=this,g=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Title",y=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,c),l(this,"onResize",function(t){f.setState({aspectRatio:f.getCanvasAspectRatio(),timelineAspectRatio:f.getTimelineCanvasAspectRatio()})}),l(this,"onResizeThrottled",(h=this.onResize,u=33,m=0,p=function(){v=void 0,m=Date.now(),h()},function(){var t=Date.now(),e=u-(t-m);e<=0?(v&&(clearTimeout(v),v=void 0),m=t,h()):v||(v=setTimeout(p,e))})),l(this,"fixSvgCoordinate",function(t){var e=f.props.precisionFactor;return Math.round(t*e)/e}),l(this,"mapX",function(t){var e=f.props.canvasWidth,i=f.state,n=i.minX;return(t-n)/(i.maxX-n)*e}),l(this,"mapY",function(t){var e=f.props.canvasWidth,i=f.state,n=i.minY;return(t-n)/(i.maxY-n)*e/i.aspectRatio}),l(this,"mapXForTimeline",function(t){var e=f.props.canvasWidth,i=f.data,n=i.minX;return(t-n)/(i.maxX-n)*e}),l(this,"mapYForTimeline",function(t){var e=f.props.canvasWidth,i=f.state,n=i.minYGlobal;return(t-n)/(i.maxYGlobal-n)*e/i.timelineAspectRatio}),l(this,"createGridLine",function(t){var e=f.state,i=e.minX,n=e.maxX,a=(e.minY,e.maxY),o=e.yScale,r=document.createElement("line");return r.classList.add("chartogram__grid-line"),r.setAttribute("x1",f.fixSvgCoordinate(f.mapX(i))),r.setAttribute("x2",f.fixSvgCoordinate(f.mapX(n))),r.setAttribute("y1",f.fixSvgCoordinate(f.mapY(a-o*t))),r.setAttribute("y2",f.fixSvgCoordinate(f.mapY(a-o*t))),r}),l(this,"drawGauges",function(t,n,a,o,r){var s=f.props,l=s.gaugeTickMarksCount,c=s.months;e(f.xAxis),e(f.yAxis),i(f.xAxis,t,n,l,function(t){var e=new Date(t);return"".concat(c[e.getMonth()]," ").concat(e.getDate())}),i(f.yAxis,a,o,l),f.yAxis.style.height="".concat(100/r,"%")}),l(this,"renderTimeline",function(){var i=f.props.canvasWidth,n=f.data,a=n.x,o=n.y,s=f.state,l=s.timelineAspectRatio,c=s.maxYGlobal,d=s.yScale;e(f.timelineCanvas),f.timelineCanvas.setAttribute("viewBox","0 0 ".concat(i," ").concat(f.fixSvgCoordinate(i/l)));var h=function(){if(v){if(m>=u.length)return"break";p=u[m++]}else{if((m=u.next()).done)return"break";p=m.value}var e=p,i=e.id,n=e.color,o=e.points;if(f.state.y.find(function(t){return t.id===i}).isShown){var s=r(function e(i,n,a){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:Math.max.apply(Math,t(n)),r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:.025,s=arguments.length>5&&void 0!==arguments[5]?arguments[5]:0,l=arguments.length>6&&void 0!==arguments[6]?arguments[6]:new Array(i.length),c=arguments.length>7&&void 0!==arguments[7]?arguments[7]:new Array(i.length),d=arguments.length>8&&void 0!==arguments[8]?arguments[8]:0;if(s+2>i.length-1){for(;s<n.length;)l[d]=i[s],c[d]=n[s],d++,s++;return l=l.slice(0,d),c=c.slice(0,d),l.length<=a?[l,c]:(i.length/l.length<1.1&&(r=Math.min(r+.025,1)),e(l,c,a,o,r))}var h=(n[s+2]+n[s])/2;return Math.abs(h-n[s+1])/o<r?(l[d]=i[s],l[d+1]=i[s+2],c[d]=n[s],c[d+1]=n[s+2],e(i,n,a,o,r,s+2,l,c,d+1)):(l[d]=i[s],l[d+1]=i[s+1],l[d+2]=i[s+2],c[d]=n[s],c[d+1]=n[s+1],c[d+2]=n[s+2],e(i,n,a,o,r,s+2,l,c,d+2))}(a.points,o,80),2),l=s[0],h=s[1],g=document.createElement("polyline");g.setAttribute("stroke",n),g.setAttribute("points",f.createPolylinePoints(l.map(f.mapXForTimeline),h.map(function(t){return f.mapYForTimeline(c-t*d)})).join(" ")),g.classList.add("chartogram__graph"),f.timelineCanvas.appendChild(g)}},u=o,v=Array.isArray(u),m=0;for(u=v?u:u[Symbol.iterator]();;){var p;if("break"===h())break}f.timelineCanvas.innerHTML+=""}),l(this,"createGraphToggler",function(t){var e=t.id,i=t.name,n=t.color,a=document.createElement("button");a.setAttribute("type","button"),a.classList.add("chartogram__chart-toggler"),a.classList.add("chartogram__chart-toggler--on"),a.classList.add("chartogram__reset-button");var r="http://www.w3.org/2000/svg",s=document.createElementNS(r,"svg");s.setAttribute("viewBox","0 0 19 19"),s.setAttribute("class","chartogram__chart-toggler-check");var l=document.createElementNS(r,"circle");l.setAttribute("cx","9.5"),l.setAttribute("cy","9.5"),l.setAttribute("r","9.5"),l.setAttribute("fill",n),s.appendChild(l);var c=document.createElementNS(r,"circle");c.setAttribute("cx","9.5"),c.setAttribute("cy","9.5"),c.setAttribute("r","8"),c.setAttribute("class","chartogram__chart-toggler-check-circle"),s.appendChild(c);var d=document.createElementNS(r,"path");return d.setAttribute("d","M13.64 4.94l-6.2 6.34-1.69-1.9c-.73-.63-1.89.1-1.36 1.06l2 3.38c.3.43 1.04.85 1.78 0 .32-.42 6.31-7.93 6.31-7.93.74-.84-.2-1.58-.84-.95z"),d.setAttribute("fill","white"),d.setAttribute("class","chartogram__chart-toggler-check-mark"),s.appendChild(d),a.appendChild(s),a.appendChild(document.createTextNode(i)),a.addEventListener("click",function(){var t=f.state.y.find(function(t){return t.id===e});if(t.isShown&&1===f.state.y.filter(function(t){return t.isShown}).length)return;t.isShown=!t.isShown,f.setState(o({},f.state,f.calculateMinMaxY(f.state.y))),a.classList.toggle("chartogram__chart-toggler--on")}),a}),l(this,"setUpTimelineWindowHandle",function(t){var e,i,a,o,r="left"===t?f.timelineWindowLeftHandle:f.timelineWindowRightHandle,s=parseFloat(getComputedStyle(f.timelineWindow).borderLeftWidth);return n(r,function(n){e=f.timeline.getBoundingClientRect();var r=f.timelineWindow.getBoundingClientRect();"left"===t?(i=e.left,a=r.left+r.width-2*s,o=n-r.left):(i=r.left+2*s,a=e.left+e.width,o=n-(r.left+r.width))},function(n){n-=o;var r=((n=Math.max(Math.min(n,a),i))-e.left)/e.width;"left"===t?f.updateBounds(r,f.state.toRatio):f.updateBounds(f.state.fromRatio,r)})}),l(this,"setUpTimelineWindow",function(){var t,e,i,a,o;return n(f.timelineWindowDrag,function(n){t=f.timeline.getBoundingClientRect(),e=f.timelineWindow.getBoundingClientRect(),o=n-e.left,i=t.left,a=t.left+(t.width-e.width)},function(n){n-=o;var r=((n=Math.max(Math.min(n,a),i))-t.left)/t.width;f.updateBounds(r,r+e.width/t.width)})}),l(this,"setTimelineWindowLeft",function(t){f.timelineOverlayLeft.style.right="".concat(100*(1-t),"%"),f.timelineWindow.style.left="".concat(100*t,"%")}),l(this,"setTimelineWindowRight",function(t){f.timelineOverlayRight.style.left="".concat(100*t,"%"),f.timelineWindow.style.right="".concat(100*(1-t),"%")}),l(this,"setUpCanvasTooltipListener",function(){var t,e,i=f.props,n=i.weekdays,a=i.months,o=function(i){var o,r=f.state,s=r.minX,l=r.maxX,c=r.xPoints,d=r.y,h=s+(i-t.left)/t.width*(l-s),u=c.findIndex(function(t){return t>=h}),v=u-1;if(e(u)||(u=-1),e(v)||(v=-1),u<0){if(v<0)return f.removeTooltip();o=v}else if(v<0)o=u;else{var m=c[v],p=c[u];o=h-m>p-h?u:v}var g=c[o];if(g!==f.tooltipForX){f.tooltipForX=g,f.tooltip||f.addTooltip();var y=new Date(g);f.tooltipDate.textContent="".concat(n[y.getDay()],", ").concat(a[y.getMonth()]," ").concat(y.getDate());var w=0,b=d,_=Array.isArray(b),x=0;for(b=_?b:b[Symbol.iterator]();;){var S;if(_){if(x>=b.length)break;S=b[x++]}else{if((x=b.next()).done)break;S=x.value}var A=S,L=A.isShown,T=A.points,C=A.name;L&&(f.tooltipValues.childNodes[2*w].textContent=T[o],f.tooltipValues.childNodes[2*w+1].textContent=C,w++)}var E=(g-s)/(l-s);f.tooltip.style.left="".concat(100*E,"%"),f.updateTooltipPoints(o,E),f.updateTooltipLine(g)}},r=function(){t=f.canvas.getBoundingClientRect(),e=function(t){var e=f.state.xPoints;return t>=0&&t<e.length}},s=function(t){if(t.touches.length>1)return d();r(),f.canvas.addEventListener("touchend",d),f.canvas.addEventListener("touchmove",l),f.canvas.addEventListener("touchend",d),f.canvas.addEventListener("touchcancel",d),l(t)};function l(e){var i=e.changedTouches[0].clientX,n=e.changedTouches[0].clientY;i<t.left||i>t.left+t.width||n<t.top||n>t.top+t.height?d():o(i,n)}function c(t){o(t.clientX,t.clientY)}f.canvas.addEventListener("touchstart",s);var d=function t(){f.canvas.removeEventListener("pointermove",c),f.canvas.removeEventListener("pointerleave",t),f.canvas.removeEventListener("pointercancel",t),f.canvas.removeEventListener("touchmove",l),f.canvas.removeEventListener("touchend",t),f.canvas.removeEventListener("touchcancel",t),f.removeTooltip()},h=function(){r(),f.canvas.addEventListener("pointermove",c),f.canvas.addEventListener("pointerleave",d),f.canvas.addEventListener("pointercancel",d)};return f.canvas.addEventListener("pointerenter",h),function(){d(),f.canvas.removeEventListener(h),f.canvas.removeEventListener(s)}}),l(this,"addTooltip",function(){var t=f.state.y;f.tooltip=document.createElement("div"),f.tooltip.classList.add("chartogram__tooltip"),f.tooltipContainer.appendChild(f.tooltip),f.tooltipDate=document.createElement("h1"),f.tooltipDate.classList.add("chartogram__tooltip-header"),f.tooltip.appendChild(f.tooltipDate),f.tooltipValues=document.createElement("dl"),f.tooltipValues.classList.add("chartogram__tooltip-values"),f.tooltip.appendChild(f.tooltipValues);var e=t,i=Array.isArray(e),n=0;for(e=i?e:e[Symbol.iterator]();;){var a;if(i){if(n>=e.length)break;a=e[n++]}else{if((n=e.next()).done)break;a=n.value}var o=a,r=o.isShown,s=o.color;if(r){var l=document.createElement("dt");l.style.color=s,f.tooltipValues.appendChild(l);var c=document.createElement("dd");c.style.color=s,f.tooltipValues.appendChild(c)}}}),l(this,"addTooltipLine",function(){f.tooltipLine=document.createElementNS("http://www.w3.org/2000/svg","line"),f.tooltipLine.setAttributeNS(null,"class","chartogram__tooltip-line"),f.canvas.insertBefore(f.tooltipLine,f.canvas.querySelector("polyline"))}),l(this,"removeTooltip",function(){f.tooltip&&(f.tooltipForX=void 0,f.tooltipContainer.removeChild(f.tooltip),f.tooltip=void 0,f.removeTooltipPoints(),f.removeTooltipLine())}),l(this,"removeTooltipLine",function(){f.canvas.removeChild(f.tooltipLine),f.tooltipLine=void 0}),l(this,"addTooltipPoints",function(){f.tooltipPoints=[];var t=f.state.y,e=Array.isArray(t),i=0;for(t=e?t:t[Symbol.iterator]();;){var n;if(e){if(i>=t.length)break;n=t[i++]}else{if((i=t.next()).done)break;n=i.value}var a=n;if(a.isShown){var o=document.createElement("div");o.classList.add("chartogram__tooltip-point"),o.style.color=a.color,f.tooltipPoints.push(o),f.canvasWrapper.appendChild(o)}}}),l(this,"removeTooltipPoints",function(){var t=f.tooltipPoints,e=Array.isArray(t),i=0;for(t=e?t:t[Symbol.iterator]();;){var n;if(e){if(i>=t.length)break;n=t[i++]}else{if((i=t.next()).done)break;n=i.value}var a=n;f.canvasWrapper.removeChild(a)}f.tooltipPoints=void 0}),l(this,"updateTooltipLine",function(t){var e=f.props.canvasWidth,i=f.state.aspectRatio;f.tooltipLine||f.addTooltipLine(),f.tooltipLine.setAttributeNS(null,"x1",f.fixSvgCoordinate(f.mapX(t))),f.tooltipLine.setAttributeNS(null,"x2",f.fixSvgCoordinate(f.mapX(t))),f.tooltipLine.setAttributeNS(null,"y1",0),f.tooltipLine.setAttributeNS(null,"y2",f.fixSvgCoordinate(e/i))}),l(this,"updateTooltipPoints",function(t,e){var i=f.state,n=i.maxY,a=i.y;f.tooltipPoints||f.addTooltipPoints();for(var o=0;o<f.tooltipPoints.length;){var r=f.tooltipPoints[o],s=a[o].points[t]/n;r.style.left="".concat(100*e,"%"),r.style.bottom="".concat(100*s,"%"),o++}}),this.props=o({title:g,gaugeTickMarksCount:6,timelineWindowSize:40,canvasWidth:512,precisionFactor:Math.pow(10,y.precision||3),months:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},y),this.rootNode=s,this.data=o({},d,{minX:Math.min.apply(Math,a(d.x.points)),maxX:Math.max.apply(Math,a(d.x.points)),y:d.y.map(function(t){return o({},t,{min:Math.min.apply(Math,a(t.points)),max:Math.max.apply(Math,a(t.points))})})})}var d,h,u;return d=c,(h=[{key:"componentDidMount",value:function(){this.rootNode.classList.add("chartogram"),this.rootNode.innerHTML='\n\t\t\t<header class="chartogram__header">\n\t\t\t\t<h1 class="chartogram__title">'.concat(this.props.title,'</h1>\n\t\t\t</header>\n\t\t\t<div class="chartogram__plan-with-axes">\n\t\t\t\t<div class="chartogram__plan">\n\t\t\t\t\t<div class="chartogram__top-border"></div>\n\t\t\t\t\t<div class="chartogram__canvas-wrapper">\n\t\t\t\t\t\t<svg class="chartogram__canvas"></svg>\n\t\t\t\t\t\t<div class="chartogram__x"></div>\n\t\t\t\t\t\t<div class="chartogram__y-wrapper">\n\t\t\t\t\t\t\t<div class="chartogram__y"></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class="chartogram__timeline">\n\t\t\t\t<div class="chartogram__timeline-canvas-padding">\n\t\t\t\t\t<svg class="chartogram__timeline-canvas" preserveAspectRatio="none"></svg>\n\t\t\t\t</div>\n\t\t\t\t<div class="chartogram__timeline-overlay-left"></div>\n\t\t\t\t<div class="chartogram__timeline-overlay-right"></div>\n\t\t\t\t<div class="chartogram__timeline-window">\n\t\t\t\t\t<button type="button" class="chartogram__reset-button chartogram__timeline-window__drag"></button>\n\t\t\t\t\t<button type="button" class="chartogram__reset-button chartogram__timeline-window__left-handle"></button>\n\t\t\t\t\t<button type="button" class="chartogram__reset-button chartogram__timeline-window__right-handle"></button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class="chartogram__chart-togglers"></div>\n\t\t'),this.tooltipContainer=this.rootNode.querySelector(".chartogram__plan"),this.canvas=this.rootNode.querySelector(".chartogram__canvas"),this.canvasWrapper=this.rootNode.querySelector(".chartogram__canvas-wrapper"),this.xAxis=this.rootNode.querySelector(".chartogram__x"),this.yAxis=this.rootNode.querySelector(".chartogram__y"),this.timeline=this.rootNode.querySelector(".chartogram__timeline"),this.timelineOverlayLeft=this.rootNode.querySelector(".chartogram__timeline-overlay-left"),this.timelineWindowLeftHandle=this.rootNode.querySelector(".chartogram__timeline-window__left-handle"),this.timelineWindow=this.rootNode.querySelector(".chartogram__timeline-window"),this.timelineWindowDrag=this.rootNode.querySelector(".chartogram__timeline-window__drag"),this.timelineWindowRightHandle=this.rootNode.querySelector(".chartogram__timeline-window__right-handle"),this.timelineOverlayRight=this.rootNode.querySelector(".chartogram__timeline-overlay-right"),this.timelineCanvas=this.rootNode.querySelector(".chartogram__timeline-canvas"),this.setUpTimelineWindowHandle("left"),this.setUpTimelineWindowHandle("right"),this.setUpTimelineWindow(),this.setUpCanvasTooltipListener();var t=this.rootNode.querySelector(".chartogram__chart-togglers");e(t);var i=this.data.y,n=Array.isArray(i),a=0;for(i=n?i:i[Symbol.iterator]();;){var o;if(n){if(a>=i.length)break;o=i[a++]}else{if((a=i.next()).done)break;o=a.value}var r=o;t.appendChild(this.createGraphToggler(r))}this.state=this.getInitialState(),this.updateTimelineBounds(this.state.fromRatio,this.state.toRatio),window.addEventListener("resize",this.onResizeThrottled)}},{key:"componentWillUnmount",value:function(){window.removeEventListener("resize",this.onResizeThrottled)}},{key:"getCanvasAspectRatio",value:function(){var t=this.canvas.getBoundingClientRect();return t.width/t.height}},{key:"getTimelineCanvasAspectRatio",value:function(){var t=this.timelineCanvas.getBoundingClientRect();return t.width/t.height}},{key:"setState",value:function(t,e){this.state=o({},this.state,t),this.render(e)}},{key:"getInitialState",value:function(){var t,e=this.props.timelineWindowSize,i=this.data,n=i.minX,a=i.maxX;t=this.data.x.points.length>e?this.data.x.points.length-e:0;var r=(this.data.x.points[t]-n)/(a-n);return o({},this.createState(r,1),{aspectRatio:this.getCanvasAspectRatio(),timelineAspectRatio:this.getTimelineCanvasAspectRatio(),yScale:1})}},{key:"createState",value:function(t,e){var i,n,r=this,s=this.data.x,l=this.data.minX+t*(this.data.maxX-this.data.minX),c=this.data.minX+e*(this.data.maxX-this.data.minX);i=l===this.data.minX?0:s.points.findIndex(function(t){return t>l})-1,n=c===this.data.maxX?s.points.length-1:s.points.findIndex(function(t){return t>c});var d=s.points.slice(i,n+1),h=d.slice();d.length>=2&&(s.points[i]!==l&&(h[0]=l),s.points[n]!==c&&(h[h.length-1]=c));var u=this.data.y.map(function(t,e){var h=r.data.y[e].points.slice(i,n+1),u=h.slice();if(d.length>=2){if(s.points[i]!==l){var v=r.data.y[e].points[i],m=v+(r.data.y[e].points[i+1]-v)*((l-r.data.x.points[i])/(r.data.x.points[i+1]-r.data.x.points[i]));u[0]=m}if(s.points[n]!==c){var p=r.data.y[e].points[n],f=r.data.y[e].points[n-1],g=f+(p-f)*((c-r.data.x.points[n-1])/(r.data.x.points[n]-r.data.x.points[n-1]));u[u.length-1]=g}}return o({},r.data.y[e],r.state?r.state.y[e]:{isShown:!0},{points:h,graphPoints:u,min:0,max:Math.max.apply(Math,a(u))})});return o({minX:l,maxX:c,fromIndex:i,toIndex:n,fromRatio:t,toRatio:e,xPoints:d,xGraphPoints:h},this.calculateMinMaxY(u),{y:u})}},{key:"calculateMinMaxY",value:function(t){var e=1/0,i=-1/0,n=t,a=Array.isArray(n),o=0;for(n=a?n:n[Symbol.iterator]();;){var r;if(a){if(o>=n.length)break;r=n[o++]}else{if((o=n.next()).done)break;r=o.value}var s=r;s.isShown&&(e=Math.min(e,s.min),i=Math.max(i,s.max))}e=0;var l=1/0,c=-1/0,d=function(){if(u){if(v>=h.length)return"break";m=h[v++]}else{if((v=h.next()).done)return"break";m=v.value}var e=m;t.find(function(t){return t.id===e.id}).isShown&&(l=Math.min(l,e.min),c=Math.max(c,e.max))},h=this.data.y,u=Array.isArray(h),v=0;for(h=u?h:h[Symbol.iterator]();;){var m;if("break"===d())break}return{minY:e,maxY:i,minYGlobal:l=0,maxYGlobal:c}}},{key:"updateTimelineBounds",value:function(t,e){this.setTimelineWindowLeft(t),this.setTimelineWindowRight(e)}},{key:"updateBounds",value:function(t,e){this.updateTimelineBounds(t,e),this.setState(this.createState(t,e),!1)}},{key:"createPolylinePoints",value:function(t,e){return i=t.map(this.fixSvgCoordinate),n=e.map(this.fixSvgCoordinate),i.map(function(t,e){return"".concat(t,",").concat(n[e])});var i,n}},{key:"render",value:function(){var t=this,i=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],n=this.props,a=n.canvasWidth,o=n.gaugeTickMarksCount,r=this.state,s=r.minX,l=r.maxX,c=r.minY,d=r.maxY,h=r.yScale,u=r.xGraphPoints,v=r.aspectRatio;e(this.canvas),this.canvas.setAttribute("viewBox","0 0 ".concat(a," ").concat(this.fixSvgCoordinate(a/v)));var m=c,p=function(t,e){for(t=Math.floor(t);;){if(t<e)return e;if(t%e==0)return t;t--}}(d,10),f=(d-c)/(p-m),g=function(t,e,i){for(var n=new Array(i),a=0;a<i;)n[a]=t+a*(e-t)/(i-1),a++;return n}(m,p,o),y=Array.isArray(g),w=0;for(g=y?g:g[Symbol.iterator]();;){var b;if(y){if(w>=g.length)break;b=g[w++]}else{if((w=g.next()).done)break;b=w.value}var _=b;this.canvas.appendChild(this.createGridLine(_))}var x=this.state.y,S=Array.isArray(x),A=0;for(x=S?x:x[Symbol.iterator]();;){var L;if(S){if(A>=x.length)break;L=x[A++]}else{if((A=x.next()).done)break;L=A.value}var T=L,C=T.color,E=T.graphPoints;if(T.isShown){var k=document.createElement("polyline");k.setAttribute("stroke",C),k.setAttribute("points",this.createPolylinePoints(u.map(this.mapX),E.map(function(e){return t.mapY(d-e*h)})).join(" ")),k.classList.add("chartogram__graph"),this.canvas.appendChild(k)}}this.canvas.innerHTML+="",this.drawGauges(s,l,m,p,f),i&&this.renderTimeline()}}])&&s(d.prototype,h),u&&s(d,u),c}();return function(t,e,i,n){var a=new c(t,e,i,n);return a.componentDidMount(),a.render(),function(){a.componentWillUnmount()}}}); | ||
//# sourceMappingURL=chartogram.js.map |
@@ -8,1033 +8,14 @@ "use strict"; | ||
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } | ||
var _Chartogram = _interopRequireDefault(require("./Chartogram")); | ||
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } | ||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } | ||
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } | ||
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } | ||
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } | ||
function chartogram(rootNode, data) { | ||
var title = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'Title'; | ||
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
// clearElement(rootNode) | ||
rootNode.innerHTML = "\n\t\t<header class=\"chartogram__header\">\n\t\t\t<h1 class=\"chartogram__title\">".concat(title, "</h1>\n\t\t</header>\n\t\t<div class=\"chartogram__plan-with-axes\">\n\t\t\t<div class=\"chartogram__plan\">\n\t\t\t\t<div class=\"chartogram__top-border\"></div>\n\t\t\t\t<div class=\"chartogram__canvas-wrapper\">\n\t\t\t\t\t<svg class=\"chartogram__canvas\" preserveAspectRatio=\"none\"></svg>\n\t\t\t\t\t<div class=\"chartogram__x\"></div>\n\t\t\t\t\t<div class=\"chartogram__y-wrapper\">\n\t\t\t\t\t\t<div class=\"chartogram__y\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"chartogram__timeline\">\n\t\t\t<div class=\"chartogram__timeline-canvas-padding\">\n\t\t\t\t<svg class=\"chartogram__timeline-canvas\" preserveAspectRatio=\"none\"></svg>\n\t\t\t</div>\n\t\t\t<div class=\"chartogram__timeline-overlay-left\"></div>\n\t\t\t<div class=\"chartogram__timeline-overlay-right\"></div>\n\t\t\t<div class=\"chartogram__timeline-window\">\n\t\t\t\t<button type=\"button\" class=\"chartogram__reset-button chartogram__timeline-window__drag\"></button>\n\t\t\t\t<button type=\"button\" class=\"chartogram__reset-button chartogram__timeline-window__left-handle\"></button>\n\t\t\t\t<button type=\"button\" class=\"chartogram__reset-button chartogram__timeline-window__right-handle\"></button>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"chartogram__chart-togglers\"></div>\n\t"); | ||
rootNode.classList.add('chartogram'); | ||
var GAUGE_TICK_MARKS_COUNT = options.gaugeMarkTicksCount || 6; | ||
var TIMELINE_WINDOW_SIZE = options.timelineWindowSize || 40; | ||
var TIMELINE_CHART_MAX_POINTS = options.timelineChartMaxPoints || 80; | ||
var MONTHS = options.months || ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; | ||
var WEEKDAYS = options.weekdays || ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; | ||
var tooltipContainer = document.querySelector('.chartogram__plan'); | ||
var canvas = document.querySelector('.chartogram__canvas'); | ||
var canvasWrapper = document.querySelector('.chartogram__canvas-wrapper'); | ||
var xAxis = document.querySelector('.chartogram__x'); | ||
var yAxis = document.querySelector('.chartogram__y'); | ||
var timeline = document.querySelector('.chartogram__timeline'); | ||
var timelineOverlayLeft = document.querySelector('.chartogram__timeline-overlay-left'); | ||
var timelineWindowLeftHandle = document.querySelector('.chartogram__timeline-window__left-handle'); | ||
var timelineWindow = document.querySelector('.chartogram__timeline-window'); | ||
var timelineWindowDrag = document.querySelector('.chartogram__timeline-window__drag'); | ||
var timelineWindowRightHandle = document.querySelector('.chartogram__timeline-window__right-handle'); | ||
var timelineOverlayRight = document.querySelector('.chartogram__timeline-overlay-right'); | ||
var timelineCanvas = document.querySelector('.chartogram__timeline-canvas'); | ||
var timelineWindowFrom; | ||
var timelineWindowTo; | ||
var showGraphs; | ||
var showGraphsNext; | ||
var timelineWindowGraphs; | ||
var timelineWindowMinX; | ||
var timelineWindowMaxX; | ||
var timelineWindowMinY; | ||
var timelineWindowMaxY; | ||
var timelineWindowX; | ||
var tooltip; | ||
var tooltipDate; | ||
var tooltipValues; | ||
var tooltipForX; | ||
var tooltipPoints; | ||
var tooltipLine; // A stub for possible animations. | ||
var yScale = 1; | ||
setUpTimelineWindowHandle('left'); | ||
setUpTimelineWindowHandle('right'); | ||
setUpTimelineWindow(); | ||
normalizeDataPoints(); | ||
displayGraphs(); | ||
setUpCanvas(); | ||
function clearElement(element) { | ||
while (element.firstChild) { | ||
element.removeChild(element.firstChild); | ||
} | ||
} | ||
function commaJoin(a, b) { | ||
return a.map(function (ai, i) { | ||
return "".concat(ai, ",").concat(b[i]); | ||
}); | ||
} | ||
function createPolylinePoints(x, y) { | ||
// return commaJoin(x, y) | ||
return commaJoin(x.map(fixSvgCoordinate), y.map(fixSvgCoordinate)); | ||
} // Firefox is buggy with too high and too fractional SVG coordinates. | ||
function fixSvgCoordinate(x) { | ||
return Math.round(x * Number.MAX_SAFE_INTEGER) / Number.MAX_SAFE_INTEGER; | ||
} | ||
function getLowerSiblingDivisibleBy(n, divider) { | ||
while (true) { | ||
if (n < divider) { | ||
return divider; | ||
} | ||
if (n % divider === 0) { | ||
return n; | ||
} | ||
n--; | ||
} | ||
} | ||
function divideInterval(min, max) { | ||
var points = new Array(GAUGE_TICK_MARKS_COUNT); | ||
var i = 0; | ||
while (i < GAUGE_TICK_MARKS_COUNT) { | ||
points[i] = min + i * (max - min) / (GAUGE_TICK_MARKS_COUNT - 1); | ||
i++; | ||
} | ||
return points; | ||
} | ||
function drawGauge(element, min, max, transform) { | ||
var i = 0; | ||
while (i < GAUGE_TICK_MARKS_COUNT) { | ||
var tickMark = document.createElement('div'); | ||
var value = min + i * (max - min) / (GAUGE_TICK_MARKS_COUNT - 1); | ||
if (transform) { | ||
value = transform(value); | ||
} | ||
tickMark.appendChild(document.createTextNode(value)); | ||
element.appendChild(tickMark); | ||
i++; | ||
} | ||
} // Chrome and Firefox can't handle timestamps in milliseconds for point coordinates (draws nothing). | ||
// Reducing timestamps to lower numbers to work around that bug. | ||
function normalizeDataPoints() { | ||
normalizePoints([data.x]); | ||
normalizePoints(data.y); | ||
} | ||
function normalizePoints(all) { | ||
var isX = all[0] === data.x; | ||
var min = Infinity; | ||
var max = -Infinity; | ||
for (var _iterator = all, _isArray = Array.isArray(_iterator), _i2 = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i2 >= _iterator.length) break; | ||
_ref = _iterator[_i2++]; | ||
} else { | ||
_i2 = _iterator.next(); | ||
if (_i2.done) break; | ||
_ref = _i2.value; | ||
} | ||
var one = _ref; | ||
if (isX) { | ||
one.min = one.points[0]; | ||
one.max = one.points[one.points.length - 1]; | ||
} else { | ||
// For Y min is always 0 by design. | ||
one.min = 0; // one.min = Math.min(...one.points) | ||
one.max = Math.max.apply(Math, _toConsumableArray(one.points)); | ||
} | ||
min = Math.min(min, one.min); | ||
max = Math.max(max, one.max); | ||
} // For Y min is always 0 by design. | ||
if (!isX) { | ||
min = 0; | ||
} | ||
var shift = min; | ||
var scale = max - min; | ||
for (var _iterator2 = all, _isArray2 = Array.isArray(_iterator2), _i3 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { | ||
var _ref2; | ||
if (_isArray2) { | ||
if (_i3 >= _iterator2.length) break; | ||
_ref2 = _iterator2[_i3++]; | ||
} else { | ||
_i3 = _iterator2.next(); | ||
if (_i3.done) break; | ||
_ref2 = _i3.value; | ||
} | ||
var _one = _ref2; | ||
_one.normalized = { | ||
points: _one.points.map(function (_) { | ||
return (_ - shift) / scale; | ||
}), | ||
shift: shift, | ||
scale: scale | ||
}; | ||
} | ||
} | ||
function displayGraphs() { | ||
showGraphs = {}; | ||
for (var _iterator3 = data.y, _isArray3 = Array.isArray(_iterator3), _i4 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { | ||
var _ref3; | ||
if (_isArray3) { | ||
if (_i4 >= _iterator3.length) break; | ||
_ref3 = _iterator3[_i4++]; | ||
} else { | ||
_i4 = _iterator3.next(); | ||
if (_i4.done) break; | ||
_ref3 = _i4.value; | ||
} | ||
var y = _ref3; | ||
showGraphs[y.id] = true; | ||
} | ||
showGraphsNext = undefined; | ||
if (data.x.points.length > TIMELINE_WINDOW_SIZE) { | ||
var xMin = data.x.points[0]; | ||
var xMax = data.x.points[data.x.points.length - 1]; | ||
var xFrom = data.x.points[data.x.points.length - TIMELINE_WINDOW_SIZE]; | ||
timelineWindowFrom = (xFrom - xMin) / (xMax - xMin); | ||
} else { | ||
timelineWindowFrom = 0; | ||
} | ||
timelineWindowTo = 1; | ||
updateTimelineWindow(); | ||
drawGraphs(true); | ||
tooltipLine = undefined; // Add graph togglers. | ||
var graphTogglers = document.querySelector('.chartogram__chart-togglers'); | ||
clearElement(graphTogglers); | ||
for (var _iterator4 = data.y, _isArray4 = Array.isArray(_iterator4), _i5 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { | ||
var _ref4; | ||
if (_isArray4) { | ||
if (_i5 >= _iterator4.length) break; | ||
_ref4 = _iterator4[_i5++]; | ||
} else { | ||
_i5 = _iterator4.next(); | ||
if (_i5.done) break; | ||
_ref4 = _i5.value; | ||
} | ||
var _y2 = _ref4; | ||
graphTogglers.appendChild(createGraphToggler(_y2)); | ||
} | ||
} | ||
function drawGraphs(redrawTimeline) { | ||
// Clear canvas. | ||
clearElement(canvas); // Calculate bounds. | ||
var xPoints = data.x.points; | ||
var minXOverall = xPoints[0]; | ||
var maxXOverall = xPoints[xPoints.length - 1]; | ||
var deltaX = maxXOverall - minXOverall; | ||
var minX = minXOverall + timelineWindowFrom * deltaX; | ||
var maxX = maxXOverall - (1 - timelineWindowTo) * deltaX; | ||
var minXIndex = xPoints.findIndex(function (x) { | ||
return x > minX; | ||
}) - 1; | ||
if (minXIndex < 0) { | ||
minXIndex = 0; | ||
} | ||
var maxXIndex = xPoints.findIndex(function (x) { | ||
return x > maxX; | ||
}); | ||
if (maxXIndex < 0) { | ||
maxXIndex = xPoints.length - 1; | ||
} | ||
timelineWindowX = data.x.normalized.points.slice(minXIndex, maxXIndex + 1); // let minY = Infinity | ||
// Min Y is always 0 by design. | ||
var minY = 0; | ||
var maxY = -Infinity; | ||
timelineWindowGraphs = []; | ||
for (var _iterator5 = data.y, _isArray5 = Array.isArray(_iterator5), _i6 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { | ||
var _ref5; | ||
if (_isArray5) { | ||
if (_i6 >= _iterator5.length) break; | ||
_ref5 = _iterator5[_i6++]; | ||
} else { | ||
_i6 = _iterator5.next(); | ||
if (_i6.done) break; | ||
_ref5 = _i6.value; | ||
} | ||
var y = _ref5; | ||
if (!showGraphs[y.id]) { | ||
continue; | ||
} | ||
var points = y.points.slice(minXIndex, maxXIndex + 1); // Min Y is always 0 by design. | ||
var min = 0; // const min = Math.min(...points) | ||
var max = Math.max.apply(Math, _toConsumableArray(points)); | ||
timelineWindowGraphs.push(_objectSpread({}, y, { | ||
points: points, | ||
min: min, | ||
max: max, | ||
normalized: _objectSpread({}, y.normalized, { | ||
points: y.normalized.points.slice(minXIndex, maxXIndex + 1) | ||
}) | ||
})); // minY = Math.min(minY, ...points) | ||
maxY = Math.max.apply(Math, [maxY].concat(_toConsumableArray(points))); | ||
} // Set canvas `viewBox`. | ||
var minXNormalized = (minX - data.x.normalized.shift) / data.x.normalized.scale; | ||
var maxXNormalized = (maxX - data.x.normalized.shift) / data.x.normalized.scale; | ||
var minYNormalized = (minY - data.y[0].normalized.shift) / data.y[0].normalized.scale; | ||
var maxYNormalized = (maxY - data.y[0].normalized.shift) / data.y[0].normalized.scale; | ||
canvas.setAttribute('viewBox', "".concat(minXNormalized, " ").concat(minYNormalized, " ").concat(maxXNormalized - minXNormalized, " ").concat(maxYNormalized - minYNormalized)); // Calculate grid lines' coordinates. | ||
var minY_ = 0; | ||
var maxY_ = getLowerSiblingDivisibleBy(maxY, 10); | ||
var yAxisScale = (maxY - minY) / (maxY_ - minY); | ||
var yAxisTickMarks = divideInterval(minY_, maxY_); // Draw grid lines. | ||
for (var _iterator6 = yAxisTickMarks, _isArray6 = Array.isArray(_iterator6), _i7 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { | ||
var _ref6; | ||
if (_isArray6) { | ||
if (_i7 >= _iterator6.length) break; | ||
_ref6 = _iterator6[_i7++]; | ||
} else { | ||
_i7 = _iterator6.next(); | ||
if (_i7.done) break; | ||
_ref6 = _i7.value; | ||
} | ||
var _y3 = _ref6; | ||
canvas.appendChild(createGridLine((_y3 - data.y[0].normalized.shift) / data.y[0].normalized.scale, minXNormalized, maxXNormalized, minYNormalized, maxYNormalized)); | ||
} // Trim X axis. | ||
var _x = timelineWindowX.slice(); | ||
var _minX = _x[0]; | ||
var _maxX = _x[_x.length - 1]; | ||
var trimLeftRatio = (minXNormalized - _minX) / (_x[1] - _minX); | ||
var trimRightRatio = (_maxX - maxXNormalized) / (_maxX - _x[_x.length - 2]); | ||
_x[0] = minXNormalized; | ||
_x[_x.length - 1] = maxXNormalized; // Draw charts. | ||
for (var _iterator7 = timelineWindowGraphs, _isArray7 = Array.isArray(_iterator7), _i8 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { | ||
var _ref7; | ||
if (_isArray7) { | ||
if (_i8 >= _iterator7.length) break; | ||
_ref7 = _iterator7[_i8++]; | ||
} else { | ||
_i8 = _iterator7.next(); | ||
if (_i8.done) break; | ||
_ref7 = _i8.value; | ||
} | ||
var _ref8 = _ref7, | ||
id = _ref8.id, | ||
color = _ref8.color, | ||
_points = _ref8.normalized.points; | ||
// Trim chart. | ||
var _y = _points.slice(); | ||
var _minY = _y[0]; | ||
var _maxY = _y[_y.length - 1]; | ||
_y[0] = _minY + (_y[1] - _minY) * trimLeftRatio; | ||
_y[_y.length - 1] = _maxY - (_maxY - _y[_y.length - 2]) * trimRightRatio; // Draw chart. | ||
var graph = document.createElement('polyline'); | ||
graph.setAttribute('stroke', color); | ||
graph.setAttribute('points', createPolylinePoints(_x, _y.map(function (y) { | ||
return maxYNormalized - yScale * y; | ||
})).join(' ')); | ||
graph.classList.add('chartogram__graph'); | ||
canvas.appendChild(graph); | ||
} // A workaround to fix WebKit bug when it's not re-rendering the <svg/>. | ||
// https://stackoverflow.com/questions/30905493/how-to-force-webkit-to-update-svg-use-elements-after-changes-to-original | ||
canvas.innerHTML += ''; // Draw gauges. | ||
drawGauges(minX, maxX, minY_, maxY_, yAxisScale); // Draw timeline graph. | ||
timelineWindowMinY = minYNormalized; | ||
timelineWindowMaxY = maxYNormalized; | ||
timelineWindowMinX = minXNormalized; | ||
timelineWindowMaxX = maxXNormalized; | ||
if (redrawTimeline) { | ||
drawTimeline(); | ||
} | ||
} // function animateScale(scale) { | ||
// console.log(scale) | ||
// animateScaleTo = scale | ||
// animateScaleStartedAt = Date.now() | ||
// previousYScale = yScale | ||
// requestAnimationFrame(animateScaleTick) | ||
// } | ||
// function animateScaleTick() { | ||
// const elapsed = Date.now() - animateScaleStartedAt | ||
// yScale = previousYScale + (animateScaleTo - previousYScale) * elapsed / 300 | ||
// drawGraphs(true) | ||
// if (elapsed < 300) { | ||
// requestAnimationFrame(animateScaleTick) | ||
// } | ||
// } | ||
function createGridLine(y, minX, maxX, minY, maxY) { | ||
var line = document.createElement('line'); | ||
line.classList.add('chartogram__grid-line'); | ||
line.setAttribute('x1', fixSvgCoordinate(minX)); | ||
line.setAttribute('x2', fixSvgCoordinate(maxX)); | ||
line.setAttribute('y1', fixSvgCoordinate(maxY - minY - y)); | ||
line.setAttribute('y2', fixSvgCoordinate(maxY - minY - y)); | ||
return line; | ||
} | ||
function drawGauges(minX, maxX, minY, maxY, yAxisScale) { | ||
clearElement(xAxis); | ||
clearElement(yAxis); | ||
drawGauge(xAxis, minX, maxX, function (timestamp) { | ||
var date = new Date(timestamp); | ||
return "".concat(MONTHS[date.getMonth()], " ").concat(date.getDate()); | ||
}); | ||
drawGauge(yAxis, minY, maxY); | ||
yAxis.style.height = "".concat(100 / yAxisScale, "%"); | ||
} | ||
function simplifyGraph(x, y, maxPoints) { | ||
var yMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Math.max.apply(Math, _toConsumableArray(y)); | ||
var threshold = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0.025; | ||
var i = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0; | ||
var _x = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : new Array(x.length); | ||
var _y = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : new Array(x.length); | ||
var _i = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : 0; | ||
if (i + 2 > x.length - 1) { | ||
while (i < y.length) { | ||
_x[_i] = x[i]; | ||
_y[_i] = y[i]; | ||
_i++; | ||
i++; | ||
} | ||
_x = _x.slice(0, _i); | ||
_y = _y.slice(0, _i); | ||
if (_x.length <= maxPoints) { | ||
return [_x, _y]; | ||
} else { | ||
if (x.length / _x.length < 1.1) { | ||
threshold = Math.min(threshold + 0.025, 1); | ||
} | ||
return simplifyGraph(_x, _y, maxPoints, yMax, threshold); | ||
} | ||
} | ||
var y0 = (y[i + 2] + y[i]) / 2; | ||
if (Math.abs(y0 - y[i + 1]) / yMax < threshold) { | ||
_x[_i] = x[i]; | ||
_x[_i + 1] = x[i + 2]; | ||
_y[_i] = y[i]; | ||
_y[_i + 1] = y[i + 2]; | ||
return simplifyGraph(x, y, maxPoints, yMax, threshold, i + 2, _x, _y, _i + 1); | ||
} else { | ||
_x[_i] = x[i]; | ||
_x[_i + 1] = x[i + 1]; | ||
_x[_i + 2] = x[i + 2]; | ||
_y[_i] = y[i]; | ||
_y[_i + 1] = y[i + 1]; | ||
_y[_i + 2] = y[i + 2]; | ||
return simplifyGraph(x, y, maxPoints, yMax, threshold, i + 2, _x, _y, _i + 2); | ||
} | ||
} | ||
function drawTimeline() { | ||
var x = data.x.normalized.points; | ||
var graphs = data.y.filter(function (_) { | ||
return showGraphs[_.id]; | ||
}); | ||
var minX = 0; | ||
var maxX = 1; | ||
var minY = (Math.min.apply(Math, _toConsumableArray(graphs.map(function (_) { | ||
return _.min; | ||
}))) - graphs[0].normalized.shift) / graphs[0].normalized.scale; | ||
var maxY = (Math.max.apply(Math, _toConsumableArray(graphs.map(function (_) { | ||
return _.max; | ||
}))) - graphs[0].normalized.shift) / graphs[0].normalized.scale; | ||
clearElement(timelineCanvas); // Set canvas `viewBox`. | ||
timelineCanvas.setAttribute('viewBox', "".concat(minX, " ").concat(minY, " ").concat(maxX - minX, " ").concat(maxY - minY)); | ||
for (var _iterator8 = graphs, _isArray8 = Array.isArray(_iterator8), _i9 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { | ||
var _ref9; | ||
if (_isArray8) { | ||
if (_i9 >= _iterator8.length) break; | ||
_ref9 = _iterator8[_i9++]; | ||
} else { | ||
_i9 = _iterator8.next(); | ||
if (_i9.done) break; | ||
_ref9 = _i9.value; | ||
} | ||
var _ref10 = _ref9, | ||
id = _ref10.id, | ||
color = _ref10.color, | ||
points = _ref10.normalized.points; | ||
var _simplifyGraph = simplifyGraph(x, points, 80), | ||
_simplifyGraph2 = _slicedToArray(_simplifyGraph, 2), | ||
_x = _simplifyGraph2[0], | ||
_y = _simplifyGraph2[1]; | ||
var graph = document.createElement('polyline'); | ||
graph.setAttribute('stroke', color); | ||
graph.setAttribute('points', createPolylinePoints(_x, _y.map(function (y) { | ||
return maxY - minY - y; | ||
})).join(' ')); | ||
graph.classList.add('chartogram__graph'); | ||
timelineCanvas.appendChild(graph); | ||
} // A workaround to fix WebKit bug when it's not re-rendering the <svg/>. | ||
// https://stackoverflow.com/questions/30905493/how-to-force-webkit-to-update-svg-use-elements-after-changes-to-original | ||
timelineCanvas.innerHTML += ''; | ||
} | ||
function createGraphToggler(_ref11) { | ||
var id = _ref11.id, | ||
name = _ref11.name, | ||
color = _ref11.color; | ||
var toggler = document.createElement('button'); | ||
toggler.setAttribute('type', 'button'); | ||
toggler.classList.add('chartogram__chart-toggler'); | ||
toggler.classList.add('chartogram__chart-toggler--on'); | ||
toggler.classList.add('chartogram__reset-button'); // Add check. | ||
var xmlns = 'http://www.w3.org/2000/svg'; | ||
var check = document.createElementNS(xmlns, 'svg'); | ||
check.setAttributeNS(null, 'viewBox', '0 0 19 19'); | ||
check.classList.add('chartogram__chart-toggler-check'); // Add background circle. | ||
var backgroundCircle = document.createElementNS(xmlns, 'circle'); | ||
backgroundCircle.setAttribute('cx', '9.5'); | ||
backgroundCircle.setAttribute('cy', '9.5'); | ||
backgroundCircle.setAttribute('r', '9.5'); | ||
backgroundCircle.setAttribute('fill', color); | ||
check.appendChild(backgroundCircle); // Add check circle. | ||
var checkCircle = document.createElementNS(xmlns, 'circle'); | ||
checkCircle.setAttribute('cx', '9.5'); | ||
checkCircle.setAttribute('cy', '9.5'); | ||
checkCircle.setAttribute('r', '8'); | ||
checkCircle.classList.add('chartogram__chart-toggler-check-circle'); | ||
check.appendChild(checkCircle); // Add check mark. | ||
var checkMark = document.createElementNS(xmlns, 'path'); | ||
checkMark.setAttribute('d', 'M13.64 4.94l-6.2 6.34-1.69-1.9c-.73-.63-1.89.1-1.36 1.06l2 3.38c.3.43 1.04.85 1.78 0 .32-.42 6.31-7.93 6.31-7.93.74-.84-.2-1.58-.84-.95z'); | ||
checkMark.setAttribute('fill', 'white'); | ||
checkMark.classList.add('chartogram__chart-toggler-check-mark'); | ||
check.appendChild(checkMark); // Add checkmark. | ||
toggler.appendChild(check); // Add graph name. | ||
toggler.appendChild(document.createTextNode(name)); // On click. | ||
toggler.addEventListener('click', function () { | ||
var show = !showGraphs[id]; // Won't allow hiding all graphs. | ||
if (!show) { | ||
var graphsShown = Object.keys(showGraphs).filter(function (id) { | ||
return showGraphs[id] !== false; | ||
}).length; | ||
if (graphsShown === 1) { | ||
return; | ||
} | ||
} | ||
showGraphs[id] = show; | ||
drawGraphs(true); | ||
toggler.classList.toggle('chartogram__chart-toggler--on'); | ||
}); | ||
return toggler; | ||
} | ||
function getMaxY(graphs) { | ||
var maxY = 0; | ||
for (var _iterator9 = graphs, _isArray9 = Array.isArray(_iterator9), _i10 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { | ||
var _ref12; | ||
if (_isArray9) { | ||
if (_i10 >= _iterator9.length) break; | ||
_ref12 = _iterator9[_i10++]; | ||
} else { | ||
_i10 = _iterator9.next(); | ||
if (_i10.done) break; | ||
_ref12 = _i10.value; | ||
} | ||
var graph = _ref12; | ||
maxY = Math.max.apply(Math, [maxY].concat(_toConsumableArray(graph.points))); | ||
} | ||
return maxY; | ||
} | ||
function setUpTimelineWindowHandle(side) { | ||
var handle = side === 'left' ? timelineWindowLeftHandle : timelineWindowRightHandle; | ||
var handleWidth = parseFloat(getComputedStyle(timelineWindow).borderLeftWidth); | ||
var timelineCoordinates; | ||
var minX; | ||
var maxX; | ||
var deltaX; | ||
var startedX; | ||
function onDrag(x) { | ||
x = x - deltaX; | ||
x = Math.max(Math.min(x, maxX), minX); | ||
x = (x - timelineCoordinates.x) / timelineCoordinates.width; | ||
if (side === 'left') { | ||
timelineWindowFrom = x; | ||
} else { | ||
timelineWindowTo = x; | ||
} | ||
updateTimelineWindow(); | ||
} | ||
function onDragStart(x) { | ||
timelineCoordinates = timeline.getBoundingClientRect(); | ||
var timelineWindowCoordinates = timelineWindow.getBoundingClientRect(); | ||
if (side === 'left') { | ||
minX = timelineCoordinates.x; | ||
maxX = timelineWindowCoordinates.x + timelineWindowCoordinates.width - 2 * handleWidth; | ||
deltaX = x - timelineWindowCoordinates.x; | ||
} else { | ||
minX = timelineWindowCoordinates.x + 2 * handleWidth; | ||
maxX = timelineCoordinates.x + timelineCoordinates.width; | ||
deltaX = x - (timelineWindowCoordinates.x + timelineWindowCoordinates.width); | ||
} | ||
} | ||
return setUpDrag(handle, onDragStart, onDrag); | ||
} | ||
function setUpTimelineWindow() { | ||
var timelineCoordinates; | ||
var timelineWindowCoordinates; | ||
var minX; | ||
var maxX; | ||
var innerX; | ||
function onDrag(x) { | ||
x = x - innerX; | ||
x = Math.max(Math.min(x, maxX), minX); | ||
x = (x - timelineCoordinates.x) / timelineCoordinates.width; | ||
timelineWindowFrom = x; | ||
timelineWindowTo = x + timelineWindowCoordinates.width / timelineCoordinates.width; | ||
updateTimelineWindow(); | ||
} | ||
function onDragStart(x) { | ||
timelineCoordinates = timeline.getBoundingClientRect(); | ||
timelineWindowCoordinates = timelineWindow.getBoundingClientRect(); | ||
innerX = x - timelineWindowCoordinates.x; | ||
minX = timelineCoordinates.x; | ||
maxX = timelineCoordinates.x + (timelineCoordinates.width - timelineWindowCoordinates.width); | ||
} | ||
return setUpDrag(timelineWindowDrag, onDragStart, onDrag); | ||
} | ||
function setUpDrag(element, onDragStart, onDrag) { | ||
function onTouchMove(event) { | ||
onDrag(event.changedTouches[0].clientX, event.changedTouches[0].clientY); | ||
} | ||
function onPointerMove(event) { | ||
onDrag(event.clientX, event.clientY); | ||
} | ||
function onDragStop() { | ||
window.removeEventListener('pointermove', onPointerMove); | ||
window.removeEventListener('touchmove', onTouchMove); | ||
window.removeEventListener('pointerup', onDragStop); | ||
window.removeEventListener('pointercancel', onDragStop); | ||
window.removeEventListener('touchend', onDragStop); | ||
window.removeEventListener('touchcancel', onDragStop); | ||
} | ||
function onTouchStart(event) { | ||
// Ignore multitouch. | ||
if (event.touches.length > 1) { | ||
// Reset. | ||
return onDragStop(); | ||
} | ||
onDragStart(event.changedTouches[0].clientX, event.changedTouches[0].clientY); | ||
window.addEventListener('touchmove', onTouchMove); | ||
window.addEventListener('touchend', onDragStop); | ||
window.addEventListener('touchcancel', onDragStop); | ||
} // Safari doesn't support pointer events. | ||
// https://caniuse.com/#feat=pointer | ||
element.addEventListener('touchstart', onTouchStart); | ||
function onPointerDown(event) { | ||
onDragStart(event.clientX, event.clientY); | ||
window.addEventListener('pointermove', onPointerMove); | ||
window.addEventListener('pointerup', onDragStop); | ||
window.addEventListener('pointercancel', onDragStop); | ||
} | ||
element.addEventListener('pointerdown', onPointerDown); | ||
return function () { | ||
onDragStop(); | ||
element.removeEventListener(onPointerDown); | ||
element.removeEventListener(onTouchStart); | ||
}; | ||
} | ||
function setTimelineWindowLeft(x) { | ||
timelineOverlayLeft.style.right = "".concat(100 * (1 - x), "%"); | ||
timelineWindow.style.left = "".concat(100 * x, "%"); | ||
} | ||
function setTimelineWindowRight(x) { | ||
timelineOverlayRight.style.left = "".concat(100 * x, "%"); | ||
timelineWindow.style.right = "".concat(100 * (1 - x), "%"); | ||
} | ||
function updateTimelineWindow() { | ||
setTimelineWindowLeft(timelineWindowFrom); | ||
setTimelineWindowRight(timelineWindowTo); | ||
drawGraphs(false); | ||
} | ||
function setUpCanvas() { | ||
var canvasDimensions; | ||
var isIndexInBounds; | ||
function onTrack(screenX) { | ||
var xScreenRatio = (screenX - canvasDimensions.x) / canvasDimensions.width; | ||
var x = timelineWindowMinX + xScreenRatio * (timelineWindowMaxX - timelineWindowMinX); | ||
var xHigherIndex = timelineWindowX.findIndex(function (_) { | ||
return _ >= x; | ||
}); | ||
var xLowerIndex = xHigherIndex - 1; | ||
if (!isIndexInBounds(xHigherIndex)) { | ||
xHigherIndex = -1; | ||
} | ||
if (!isIndexInBounds(xLowerIndex)) { | ||
xLowerIndex = -1; | ||
} | ||
if (xHigherIndex < 0) { | ||
if (xLowerIndex < 0) { | ||
return removeTooltip(); | ||
} else { | ||
x = timelineWindowX[xLowerIndex]; | ||
} | ||
} else { | ||
if (xLowerIndex < 0) { | ||
x = timelineWindowX[xHigherIndex]; | ||
} else { | ||
var xLower = timelineWindowX[xLowerIndex]; | ||
var xHigher = timelineWindowX[xHigherIndex]; | ||
var deltaLower = x - xLower; | ||
var deltaHigher = xHigher - x; | ||
x = deltaLower > deltaHigher ? xHigher : xLower; | ||
} | ||
} | ||
if (x !== tooltipForX) { | ||
tooltipForX = x; | ||
if (!tooltip) { | ||
addTooltip(); | ||
} | ||
var date = new Date(x * data.x.normalized.scale + data.x.normalized.shift); | ||
tooltipDate.textContent = "".concat(WEEKDAYS[date.getDay()], ", ").concat(MONTHS[date.getMonth()], " ").concat(date.getDate()); | ||
var xIndex = timelineWindowX.indexOf(x); | ||
var i = 0; | ||
while (2 * i < tooltipValues.childNodes.length) { | ||
tooltipValues.childNodes[2 * i].textContent = timelineWindowGraphs[i].points[xIndex]; | ||
tooltipValues.childNodes[2 * i + 1].textContent = data.y[i].name; | ||
i++; | ||
} | ||
var xRatio = (x - timelineWindowMinX) / (timelineWindowMaxX - timelineWindowMinX); | ||
tooltip.style.left = "".concat(xRatio * 100, "%"); | ||
updateTooltipPoints(xIndex, xRatio); | ||
updateTooltipLine(x); | ||
} | ||
} | ||
function onTrackStart() { | ||
canvasDimensions = canvas.getBoundingClientRect(); | ||
isIndexInBounds = function isIndexInBounds(index) { | ||
if (index < 0) { | ||
return false; | ||
} | ||
return timelineWindowX[index] >= timelineWindowMinX && timelineWindowX[index] <= timelineWindowMaxX; | ||
}; | ||
} | ||
function onTouchStart(event) { | ||
// Ignore multitouch. | ||
if (event.touches.length > 1) { | ||
// Reset. | ||
return onTrackStop(); | ||
} | ||
onTrackStart(); | ||
canvas.addEventListener('touchend', onTrackStop); | ||
canvas.addEventListener('touchmove', onTouchMove); | ||
canvas.addEventListener('touchend', onTrackStop); | ||
canvas.addEventListener('touchcancel', onTrackStop); | ||
onTouchMove(event); | ||
} // Safari doesn't support pointer events. | ||
// https://caniuse.com/#feat=pointer | ||
canvas.addEventListener('touchstart', onTouchStart); | ||
function onTouchMove(event) { | ||
var x = event.changedTouches[0].clientX; | ||
var y = event.changedTouches[0].clientY; // Emulate 'pointerleave' behavior. | ||
if (x < canvasDimensions.x || x > canvasDimensions.x + canvasDimensions.width || y < canvasDimensions.y || y > canvasDimensions.y + canvasDimensions.height) { | ||
onTrackStop(); | ||
} else { | ||
onTrack(x, y); | ||
} | ||
} | ||
function onPointerMove(event) { | ||
onTrack(event.clientX, event.clientY); | ||
} | ||
function onTrackStop() { | ||
canvas.removeEventListener('pointermove', onPointerMove); | ||
canvas.removeEventListener('pointerleave', onTrackStop); | ||
canvas.removeEventListener('pointercancel', onTrackStop); | ||
canvas.removeEventListener('touchmove', onTouchMove); | ||
canvas.removeEventListener('touchend', onTrackStop); | ||
canvas.removeEventListener('touchcancel', onTrackStop); | ||
removeTooltip(); | ||
} | ||
function onPointerEnter() { | ||
onTrackStart(); | ||
canvas.addEventListener('pointermove', onPointerMove); | ||
canvas.addEventListener('pointerleave', onTrackStop); | ||
canvas.addEventListener('pointercancel', onTrackStop); | ||
} | ||
canvas.addEventListener('pointerenter', onPointerEnter); | ||
return function () { | ||
onTrackStop(); | ||
canvas.removeEventListener(onPointerEnter); | ||
canvas.removeEventListener(onTouchStart); | ||
}; | ||
} | ||
function addTooltip() { | ||
// Create tooltip. | ||
tooltip = document.createElement('div'); | ||
tooltip.classList.add('chartogram__tooltip'); | ||
tooltipContainer.appendChild(tooltip); // Add tooltip title. | ||
tooltipDate = document.createElement('h1'); | ||
tooltipDate.classList.add('chartogram__tooltip-header'); | ||
tooltip.appendChild(tooltipDate); // Add graph values. | ||
tooltipValues = document.createElement('dl'); | ||
tooltipValues.classList.add('chartogram__tooltip-values'); | ||
tooltip.appendChild(tooltipValues); // Add graph values. | ||
for (var _iterator10 = data.y, _isArray10 = Array.isArray(_iterator10), _i11 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { | ||
var _ref13; | ||
if (_isArray10) { | ||
if (_i11 >= _iterator10.length) break; | ||
_ref13 = _iterator10[_i11++]; | ||
} else { | ||
_i11 = _iterator10.next(); | ||
if (_i11.done) break; | ||
_ref13 = _i11.value; | ||
} | ||
var y = _ref13; | ||
if (showGraphs[y.id]) { | ||
// Add graph value. | ||
var tooltipValue = document.createElement('dt'); | ||
tooltipValue.style.color = y.color; | ||
tooltipValues.appendChild(tooltipValue); // Add graph name. | ||
var tooltipName = document.createElement('dd'); | ||
tooltipName.style.color = y.color; | ||
tooltipValues.appendChild(tooltipName); | ||
} | ||
} | ||
} | ||
function removeTooltip() { | ||
if (tooltip) { | ||
tooltipForX = undefined; | ||
tooltipContainer.removeChild(tooltip); | ||
tooltip = undefined; | ||
removeTooltipPoints(); | ||
removeTooltipLine(); | ||
} | ||
} | ||
function addTooltipLine() { | ||
var xmlns = 'http://www.w3.org/2000/svg'; | ||
tooltipLine = document.createElementNS(xmlns, 'line'); | ||
tooltipLine.setAttributeNS(null, 'class', 'chartogram__tooltip-line'); | ||
canvas.insertBefore(tooltipLine, canvas.querySelector('polyline')); | ||
} | ||
function removeTooltipLine() { | ||
canvas.removeChild(tooltipLine); | ||
tooltipLine = undefined; | ||
} | ||
function addTooltipPoints() { | ||
tooltipPoints = []; | ||
var i = 0; | ||
while (i < timelineWindowGraphs.length) { | ||
var point = document.createElement('div'); | ||
point.classList.add('chartogram__tooltip-point'); | ||
point.style.color = timelineWindowGraphs[i].color; | ||
tooltipPoints.push(point); | ||
canvasWrapper.appendChild(point); | ||
i++; | ||
} | ||
} | ||
function removeTooltipPoints() { | ||
for (var _iterator11 = tooltipPoints, _isArray11 = Array.isArray(_iterator11), _i12 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { | ||
var _ref14; | ||
if (_isArray11) { | ||
if (_i12 >= _iterator11.length) break; | ||
_ref14 = _iterator11[_i12++]; | ||
} else { | ||
_i12 = _iterator11.next(); | ||
if (_i12.done) break; | ||
_ref14 = _i12.value; | ||
} | ||
var point = _ref14; | ||
canvasWrapper.removeChild(point); | ||
} | ||
tooltipPoints = undefined; | ||
} | ||
function updateTooltipLine(x) { | ||
if (!tooltipLine) { | ||
addTooltipLine(); | ||
} | ||
tooltipLine.setAttributeNS(null, 'x1', fixSvgCoordinate(x)); | ||
tooltipLine.setAttributeNS(null, 'x2', fixSvgCoordinate(x)); | ||
tooltipLine.setAttributeNS(null, 'y1', fixSvgCoordinate(timelineWindowMinY)); | ||
tooltipLine.setAttributeNS(null, 'y2', fixSvgCoordinate(timelineWindowMaxY)); | ||
} | ||
function updateTooltipPoints(xIndex, xRatio) { | ||
if (!tooltipPoints) { | ||
addTooltipPoints(); | ||
} | ||
var i = 0; | ||
while (i < tooltipPoints.length) { | ||
var point = tooltipPoints[i]; | ||
point.style.left = "".concat(xRatio * 100, "%"); | ||
var y = timelineWindowGraphs[i].normalized.points[xIndex]; | ||
var yRatio = y / timelineWindowMaxY; | ||
point.style.bottom = "".concat(yRatio * 100, "%"); | ||
i++; | ||
} | ||
} | ||
function chartogram(rootNode, data, title, options) { | ||
var chartogram = new _Chartogram.default(rootNode, data, title, options); | ||
chartogram.componentDidMount(); | ||
chartogram.render(); | ||
return function () { | ||
chartogram.componentWillUnmount(); | ||
}; | ||
} | ||
//# sourceMappingURL=index.js.map |
1038
modules/index.js
@@ -1,1032 +0,10 @@ | ||
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } | ||
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } | ||
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } | ||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } | ||
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } | ||
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } | ||
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } } | ||
export default function chartogram(rootNode, data) { | ||
var title = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'Title'; | ||
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
// clearElement(rootNode) | ||
rootNode.innerHTML = "\n\t\t<header class=\"chartogram__header\">\n\t\t\t<h1 class=\"chartogram__title\">".concat(title, "</h1>\n\t\t</header>\n\t\t<div class=\"chartogram__plan-with-axes\">\n\t\t\t<div class=\"chartogram__plan\">\n\t\t\t\t<div class=\"chartogram__top-border\"></div>\n\t\t\t\t<div class=\"chartogram__canvas-wrapper\">\n\t\t\t\t\t<svg class=\"chartogram__canvas\" preserveAspectRatio=\"none\"></svg>\n\t\t\t\t\t<div class=\"chartogram__x\"></div>\n\t\t\t\t\t<div class=\"chartogram__y-wrapper\">\n\t\t\t\t\t\t<div class=\"chartogram__y\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"chartogram__timeline\">\n\t\t\t<div class=\"chartogram__timeline-canvas-padding\">\n\t\t\t\t<svg class=\"chartogram__timeline-canvas\" preserveAspectRatio=\"none\"></svg>\n\t\t\t</div>\n\t\t\t<div class=\"chartogram__timeline-overlay-left\"></div>\n\t\t\t<div class=\"chartogram__timeline-overlay-right\"></div>\n\t\t\t<div class=\"chartogram__timeline-window\">\n\t\t\t\t<button type=\"button\" class=\"chartogram__reset-button chartogram__timeline-window__drag\"></button>\n\t\t\t\t<button type=\"button\" class=\"chartogram__reset-button chartogram__timeline-window__left-handle\"></button>\n\t\t\t\t<button type=\"button\" class=\"chartogram__reset-button chartogram__timeline-window__right-handle\"></button>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"chartogram__chart-togglers\"></div>\n\t"); | ||
rootNode.classList.add('chartogram'); | ||
var GAUGE_TICK_MARKS_COUNT = options.gaugeMarkTicksCount || 6; | ||
var TIMELINE_WINDOW_SIZE = options.timelineWindowSize || 40; | ||
var TIMELINE_CHART_MAX_POINTS = options.timelineChartMaxPoints || 80; | ||
var MONTHS = options.months || ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; | ||
var WEEKDAYS = options.weekdays || ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; | ||
var tooltipContainer = document.querySelector('.chartogram__plan'); | ||
var canvas = document.querySelector('.chartogram__canvas'); | ||
var canvasWrapper = document.querySelector('.chartogram__canvas-wrapper'); | ||
var xAxis = document.querySelector('.chartogram__x'); | ||
var yAxis = document.querySelector('.chartogram__y'); | ||
var timeline = document.querySelector('.chartogram__timeline'); | ||
var timelineOverlayLeft = document.querySelector('.chartogram__timeline-overlay-left'); | ||
var timelineWindowLeftHandle = document.querySelector('.chartogram__timeline-window__left-handle'); | ||
var timelineWindow = document.querySelector('.chartogram__timeline-window'); | ||
var timelineWindowDrag = document.querySelector('.chartogram__timeline-window__drag'); | ||
var timelineWindowRightHandle = document.querySelector('.chartogram__timeline-window__right-handle'); | ||
var timelineOverlayRight = document.querySelector('.chartogram__timeline-overlay-right'); | ||
var timelineCanvas = document.querySelector('.chartogram__timeline-canvas'); | ||
var timelineWindowFrom; | ||
var timelineWindowTo; | ||
var showGraphs; | ||
var showGraphsNext; | ||
var timelineWindowGraphs; | ||
var timelineWindowMinX; | ||
var timelineWindowMaxX; | ||
var timelineWindowMinY; | ||
var timelineWindowMaxY; | ||
var timelineWindowX; | ||
var tooltip; | ||
var tooltipDate; | ||
var tooltipValues; | ||
var tooltipForX; | ||
var tooltipPoints; | ||
var tooltipLine; // A stub for possible animations. | ||
var yScale = 1; | ||
setUpTimelineWindowHandle('left'); | ||
setUpTimelineWindowHandle('right'); | ||
setUpTimelineWindow(); | ||
normalizeDataPoints(); | ||
displayGraphs(); | ||
setUpCanvas(); | ||
function clearElement(element) { | ||
while (element.firstChild) { | ||
element.removeChild(element.firstChild); | ||
} | ||
} | ||
function commaJoin(a, b) { | ||
return a.map(function (ai, i) { | ||
return "".concat(ai, ",").concat(b[i]); | ||
}); | ||
} | ||
function createPolylinePoints(x, y) { | ||
// return commaJoin(x, y) | ||
return commaJoin(x.map(fixSvgCoordinate), y.map(fixSvgCoordinate)); | ||
} // Firefox is buggy with too high and too fractional SVG coordinates. | ||
function fixSvgCoordinate(x) { | ||
return Math.round(x * Number.MAX_SAFE_INTEGER) / Number.MAX_SAFE_INTEGER; | ||
} | ||
function getLowerSiblingDivisibleBy(n, divider) { | ||
while (true) { | ||
if (n < divider) { | ||
return divider; | ||
} | ||
if (n % divider === 0) { | ||
return n; | ||
} | ||
n--; | ||
} | ||
} | ||
function divideInterval(min, max) { | ||
var points = new Array(GAUGE_TICK_MARKS_COUNT); | ||
var i = 0; | ||
while (i < GAUGE_TICK_MARKS_COUNT) { | ||
points[i] = min + i * (max - min) / (GAUGE_TICK_MARKS_COUNT - 1); | ||
i++; | ||
} | ||
return points; | ||
} | ||
function drawGauge(element, min, max, transform) { | ||
var i = 0; | ||
while (i < GAUGE_TICK_MARKS_COUNT) { | ||
var tickMark = document.createElement('div'); | ||
var value = min + i * (max - min) / (GAUGE_TICK_MARKS_COUNT - 1); | ||
if (transform) { | ||
value = transform(value); | ||
} | ||
tickMark.appendChild(document.createTextNode(value)); | ||
element.appendChild(tickMark); | ||
i++; | ||
} | ||
} // Chrome and Firefox can't handle timestamps in milliseconds for point coordinates (draws nothing). | ||
// Reducing timestamps to lower numbers to work around that bug. | ||
function normalizeDataPoints() { | ||
normalizePoints([data.x]); | ||
normalizePoints(data.y); | ||
} | ||
function normalizePoints(all) { | ||
var isX = all[0] === data.x; | ||
var min = Infinity; | ||
var max = -Infinity; | ||
for (var _iterator = all, _isArray = Array.isArray(_iterator), _i2 = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { | ||
var _ref; | ||
if (_isArray) { | ||
if (_i2 >= _iterator.length) break; | ||
_ref = _iterator[_i2++]; | ||
} else { | ||
_i2 = _iterator.next(); | ||
if (_i2.done) break; | ||
_ref = _i2.value; | ||
} | ||
var one = _ref; | ||
if (isX) { | ||
one.min = one.points[0]; | ||
one.max = one.points[one.points.length - 1]; | ||
} else { | ||
// For Y min is always 0 by design. | ||
one.min = 0; // one.min = Math.min(...one.points) | ||
one.max = Math.max.apply(Math, _toConsumableArray(one.points)); | ||
} | ||
min = Math.min(min, one.min); | ||
max = Math.max(max, one.max); | ||
} // For Y min is always 0 by design. | ||
if (!isX) { | ||
min = 0; | ||
} | ||
var shift = min; | ||
var scale = max - min; | ||
for (var _iterator2 = all, _isArray2 = Array.isArray(_iterator2), _i3 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { | ||
var _ref2; | ||
if (_isArray2) { | ||
if (_i3 >= _iterator2.length) break; | ||
_ref2 = _iterator2[_i3++]; | ||
} else { | ||
_i3 = _iterator2.next(); | ||
if (_i3.done) break; | ||
_ref2 = _i3.value; | ||
} | ||
var _one = _ref2; | ||
_one.normalized = { | ||
points: _one.points.map(function (_) { | ||
return (_ - shift) / scale; | ||
}), | ||
shift: shift, | ||
scale: scale | ||
}; | ||
} | ||
} | ||
function displayGraphs() { | ||
showGraphs = {}; | ||
for (var _iterator3 = data.y, _isArray3 = Array.isArray(_iterator3), _i4 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { | ||
var _ref3; | ||
if (_isArray3) { | ||
if (_i4 >= _iterator3.length) break; | ||
_ref3 = _iterator3[_i4++]; | ||
} else { | ||
_i4 = _iterator3.next(); | ||
if (_i4.done) break; | ||
_ref3 = _i4.value; | ||
} | ||
var y = _ref3; | ||
showGraphs[y.id] = true; | ||
} | ||
showGraphsNext = undefined; | ||
if (data.x.points.length > TIMELINE_WINDOW_SIZE) { | ||
var xMin = data.x.points[0]; | ||
var xMax = data.x.points[data.x.points.length - 1]; | ||
var xFrom = data.x.points[data.x.points.length - TIMELINE_WINDOW_SIZE]; | ||
timelineWindowFrom = (xFrom - xMin) / (xMax - xMin); | ||
} else { | ||
timelineWindowFrom = 0; | ||
} | ||
timelineWindowTo = 1; | ||
updateTimelineWindow(); | ||
drawGraphs(true); | ||
tooltipLine = undefined; // Add graph togglers. | ||
var graphTogglers = document.querySelector('.chartogram__chart-togglers'); | ||
clearElement(graphTogglers); | ||
for (var _iterator4 = data.y, _isArray4 = Array.isArray(_iterator4), _i5 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { | ||
var _ref4; | ||
if (_isArray4) { | ||
if (_i5 >= _iterator4.length) break; | ||
_ref4 = _iterator4[_i5++]; | ||
} else { | ||
_i5 = _iterator4.next(); | ||
if (_i5.done) break; | ||
_ref4 = _i5.value; | ||
} | ||
var _y2 = _ref4; | ||
graphTogglers.appendChild(createGraphToggler(_y2)); | ||
} | ||
} | ||
function drawGraphs(redrawTimeline) { | ||
// Clear canvas. | ||
clearElement(canvas); // Calculate bounds. | ||
var xPoints = data.x.points; | ||
var minXOverall = xPoints[0]; | ||
var maxXOverall = xPoints[xPoints.length - 1]; | ||
var deltaX = maxXOverall - minXOverall; | ||
var minX = minXOverall + timelineWindowFrom * deltaX; | ||
var maxX = maxXOverall - (1 - timelineWindowTo) * deltaX; | ||
var minXIndex = xPoints.findIndex(function (x) { | ||
return x > minX; | ||
}) - 1; | ||
if (minXIndex < 0) { | ||
minXIndex = 0; | ||
} | ||
var maxXIndex = xPoints.findIndex(function (x) { | ||
return x > maxX; | ||
}); | ||
if (maxXIndex < 0) { | ||
maxXIndex = xPoints.length - 1; | ||
} | ||
timelineWindowX = data.x.normalized.points.slice(minXIndex, maxXIndex + 1); // let minY = Infinity | ||
// Min Y is always 0 by design. | ||
var minY = 0; | ||
var maxY = -Infinity; | ||
timelineWindowGraphs = []; | ||
for (var _iterator5 = data.y, _isArray5 = Array.isArray(_iterator5), _i6 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { | ||
var _ref5; | ||
if (_isArray5) { | ||
if (_i6 >= _iterator5.length) break; | ||
_ref5 = _iterator5[_i6++]; | ||
} else { | ||
_i6 = _iterator5.next(); | ||
if (_i6.done) break; | ||
_ref5 = _i6.value; | ||
} | ||
var y = _ref5; | ||
if (!showGraphs[y.id]) { | ||
continue; | ||
} | ||
var points = y.points.slice(minXIndex, maxXIndex + 1); // Min Y is always 0 by design. | ||
var min = 0; // const min = Math.min(...points) | ||
var max = Math.max.apply(Math, _toConsumableArray(points)); | ||
timelineWindowGraphs.push(_objectSpread({}, y, { | ||
points: points, | ||
min: min, | ||
max: max, | ||
normalized: _objectSpread({}, y.normalized, { | ||
points: y.normalized.points.slice(minXIndex, maxXIndex + 1) | ||
}) | ||
})); // minY = Math.min(minY, ...points) | ||
maxY = Math.max.apply(Math, [maxY].concat(_toConsumableArray(points))); | ||
} // Set canvas `viewBox`. | ||
var minXNormalized = (minX - data.x.normalized.shift) / data.x.normalized.scale; | ||
var maxXNormalized = (maxX - data.x.normalized.shift) / data.x.normalized.scale; | ||
var minYNormalized = (minY - data.y[0].normalized.shift) / data.y[0].normalized.scale; | ||
var maxYNormalized = (maxY - data.y[0].normalized.shift) / data.y[0].normalized.scale; | ||
canvas.setAttribute('viewBox', "".concat(minXNormalized, " ").concat(minYNormalized, " ").concat(maxXNormalized - minXNormalized, " ").concat(maxYNormalized - minYNormalized)); // Calculate grid lines' coordinates. | ||
var minY_ = 0; | ||
var maxY_ = getLowerSiblingDivisibleBy(maxY, 10); | ||
var yAxisScale = (maxY - minY) / (maxY_ - minY); | ||
var yAxisTickMarks = divideInterval(minY_, maxY_); // Draw grid lines. | ||
for (var _iterator6 = yAxisTickMarks, _isArray6 = Array.isArray(_iterator6), _i7 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { | ||
var _ref6; | ||
if (_isArray6) { | ||
if (_i7 >= _iterator6.length) break; | ||
_ref6 = _iterator6[_i7++]; | ||
} else { | ||
_i7 = _iterator6.next(); | ||
if (_i7.done) break; | ||
_ref6 = _i7.value; | ||
} | ||
var _y3 = _ref6; | ||
canvas.appendChild(createGridLine((_y3 - data.y[0].normalized.shift) / data.y[0].normalized.scale, minXNormalized, maxXNormalized, minYNormalized, maxYNormalized)); | ||
} // Trim X axis. | ||
var _x = timelineWindowX.slice(); | ||
var _minX = _x[0]; | ||
var _maxX = _x[_x.length - 1]; | ||
var trimLeftRatio = (minXNormalized - _minX) / (_x[1] - _minX); | ||
var trimRightRatio = (_maxX - maxXNormalized) / (_maxX - _x[_x.length - 2]); | ||
_x[0] = minXNormalized; | ||
_x[_x.length - 1] = maxXNormalized; // Draw charts. | ||
for (var _iterator7 = timelineWindowGraphs, _isArray7 = Array.isArray(_iterator7), _i8 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { | ||
var _ref7; | ||
if (_isArray7) { | ||
if (_i8 >= _iterator7.length) break; | ||
_ref7 = _iterator7[_i8++]; | ||
} else { | ||
_i8 = _iterator7.next(); | ||
if (_i8.done) break; | ||
_ref7 = _i8.value; | ||
} | ||
var _ref8 = _ref7, | ||
id = _ref8.id, | ||
color = _ref8.color, | ||
_points = _ref8.normalized.points; | ||
// Trim chart. | ||
var _y = _points.slice(); | ||
var _minY = _y[0]; | ||
var _maxY = _y[_y.length - 1]; | ||
_y[0] = _minY + (_y[1] - _minY) * trimLeftRatio; | ||
_y[_y.length - 1] = _maxY - (_maxY - _y[_y.length - 2]) * trimRightRatio; // Draw chart. | ||
var graph = document.createElement('polyline'); | ||
graph.setAttribute('stroke', color); | ||
graph.setAttribute('points', createPolylinePoints(_x, _y.map(function (y) { | ||
return maxYNormalized - yScale * y; | ||
})).join(' ')); | ||
graph.classList.add('chartogram__graph'); | ||
canvas.appendChild(graph); | ||
} // A workaround to fix WebKit bug when it's not re-rendering the <svg/>. | ||
// https://stackoverflow.com/questions/30905493/how-to-force-webkit-to-update-svg-use-elements-after-changes-to-original | ||
canvas.innerHTML += ''; // Draw gauges. | ||
drawGauges(minX, maxX, minY_, maxY_, yAxisScale); // Draw timeline graph. | ||
timelineWindowMinY = minYNormalized; | ||
timelineWindowMaxY = maxYNormalized; | ||
timelineWindowMinX = minXNormalized; | ||
timelineWindowMaxX = maxXNormalized; | ||
if (redrawTimeline) { | ||
drawTimeline(); | ||
} | ||
} // function animateScale(scale) { | ||
// console.log(scale) | ||
// animateScaleTo = scale | ||
// animateScaleStartedAt = Date.now() | ||
// previousYScale = yScale | ||
// requestAnimationFrame(animateScaleTick) | ||
// } | ||
// function animateScaleTick() { | ||
// const elapsed = Date.now() - animateScaleStartedAt | ||
// yScale = previousYScale + (animateScaleTo - previousYScale) * elapsed / 300 | ||
// drawGraphs(true) | ||
// if (elapsed < 300) { | ||
// requestAnimationFrame(animateScaleTick) | ||
// } | ||
// } | ||
function createGridLine(y, minX, maxX, minY, maxY) { | ||
var line = document.createElement('line'); | ||
line.classList.add('chartogram__grid-line'); | ||
line.setAttribute('x1', fixSvgCoordinate(minX)); | ||
line.setAttribute('x2', fixSvgCoordinate(maxX)); | ||
line.setAttribute('y1', fixSvgCoordinate(maxY - minY - y)); | ||
line.setAttribute('y2', fixSvgCoordinate(maxY - minY - y)); | ||
return line; | ||
} | ||
function drawGauges(minX, maxX, minY, maxY, yAxisScale) { | ||
clearElement(xAxis); | ||
clearElement(yAxis); | ||
drawGauge(xAxis, minX, maxX, function (timestamp) { | ||
var date = new Date(timestamp); | ||
return "".concat(MONTHS[date.getMonth()], " ").concat(date.getDate()); | ||
}); | ||
drawGauge(yAxis, minY, maxY); | ||
yAxis.style.height = "".concat(100 / yAxisScale, "%"); | ||
} | ||
function simplifyGraph(x, y, maxPoints) { | ||
var yMax = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Math.max.apply(Math, _toConsumableArray(y)); | ||
var threshold = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0.025; | ||
var i = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0; | ||
var _x = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : new Array(x.length); | ||
var _y = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : new Array(x.length); | ||
var _i = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : 0; | ||
if (i + 2 > x.length - 1) { | ||
while (i < y.length) { | ||
_x[_i] = x[i]; | ||
_y[_i] = y[i]; | ||
_i++; | ||
i++; | ||
} | ||
_x = _x.slice(0, _i); | ||
_y = _y.slice(0, _i); | ||
if (_x.length <= maxPoints) { | ||
return [_x, _y]; | ||
} else { | ||
if (x.length / _x.length < 1.1) { | ||
threshold = Math.min(threshold + 0.025, 1); | ||
} | ||
return simplifyGraph(_x, _y, maxPoints, yMax, threshold); | ||
} | ||
} | ||
var y0 = (y[i + 2] + y[i]) / 2; | ||
if (Math.abs(y0 - y[i + 1]) / yMax < threshold) { | ||
_x[_i] = x[i]; | ||
_x[_i + 1] = x[i + 2]; | ||
_y[_i] = y[i]; | ||
_y[_i + 1] = y[i + 2]; | ||
return simplifyGraph(x, y, maxPoints, yMax, threshold, i + 2, _x, _y, _i + 1); | ||
} else { | ||
_x[_i] = x[i]; | ||
_x[_i + 1] = x[i + 1]; | ||
_x[_i + 2] = x[i + 2]; | ||
_y[_i] = y[i]; | ||
_y[_i + 1] = y[i + 1]; | ||
_y[_i + 2] = y[i + 2]; | ||
return simplifyGraph(x, y, maxPoints, yMax, threshold, i + 2, _x, _y, _i + 2); | ||
} | ||
} | ||
function drawTimeline() { | ||
var x = data.x.normalized.points; | ||
var graphs = data.y.filter(function (_) { | ||
return showGraphs[_.id]; | ||
}); | ||
var minX = 0; | ||
var maxX = 1; | ||
var minY = (Math.min.apply(Math, _toConsumableArray(graphs.map(function (_) { | ||
return _.min; | ||
}))) - graphs[0].normalized.shift) / graphs[0].normalized.scale; | ||
var maxY = (Math.max.apply(Math, _toConsumableArray(graphs.map(function (_) { | ||
return _.max; | ||
}))) - graphs[0].normalized.shift) / graphs[0].normalized.scale; | ||
clearElement(timelineCanvas); // Set canvas `viewBox`. | ||
timelineCanvas.setAttribute('viewBox', "".concat(minX, " ").concat(minY, " ").concat(maxX - minX, " ").concat(maxY - minY)); | ||
for (var _iterator8 = graphs, _isArray8 = Array.isArray(_iterator8), _i9 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { | ||
var _ref9; | ||
if (_isArray8) { | ||
if (_i9 >= _iterator8.length) break; | ||
_ref9 = _iterator8[_i9++]; | ||
} else { | ||
_i9 = _iterator8.next(); | ||
if (_i9.done) break; | ||
_ref9 = _i9.value; | ||
} | ||
var _ref10 = _ref9, | ||
id = _ref10.id, | ||
color = _ref10.color, | ||
points = _ref10.normalized.points; | ||
var _simplifyGraph = simplifyGraph(x, points, 80), | ||
_simplifyGraph2 = _slicedToArray(_simplifyGraph, 2), | ||
_x = _simplifyGraph2[0], | ||
_y = _simplifyGraph2[1]; | ||
var graph = document.createElement('polyline'); | ||
graph.setAttribute('stroke', color); | ||
graph.setAttribute('points', createPolylinePoints(_x, _y.map(function (y) { | ||
return maxY - minY - y; | ||
})).join(' ')); | ||
graph.classList.add('chartogram__graph'); | ||
timelineCanvas.appendChild(graph); | ||
} // A workaround to fix WebKit bug when it's not re-rendering the <svg/>. | ||
// https://stackoverflow.com/questions/30905493/how-to-force-webkit-to-update-svg-use-elements-after-changes-to-original | ||
timelineCanvas.innerHTML += ''; | ||
} | ||
function createGraphToggler(_ref11) { | ||
var id = _ref11.id, | ||
name = _ref11.name, | ||
color = _ref11.color; | ||
var toggler = document.createElement('button'); | ||
toggler.setAttribute('type', 'button'); | ||
toggler.classList.add('chartogram__chart-toggler'); | ||
toggler.classList.add('chartogram__chart-toggler--on'); | ||
toggler.classList.add('chartogram__reset-button'); // Add check. | ||
var xmlns = 'http://www.w3.org/2000/svg'; | ||
var check = document.createElementNS(xmlns, 'svg'); | ||
check.setAttributeNS(null, 'viewBox', '0 0 19 19'); | ||
check.classList.add('chartogram__chart-toggler-check'); // Add background circle. | ||
var backgroundCircle = document.createElementNS(xmlns, 'circle'); | ||
backgroundCircle.setAttribute('cx', '9.5'); | ||
backgroundCircle.setAttribute('cy', '9.5'); | ||
backgroundCircle.setAttribute('r', '9.5'); | ||
backgroundCircle.setAttribute('fill', color); | ||
check.appendChild(backgroundCircle); // Add check circle. | ||
var checkCircle = document.createElementNS(xmlns, 'circle'); | ||
checkCircle.setAttribute('cx', '9.5'); | ||
checkCircle.setAttribute('cy', '9.5'); | ||
checkCircle.setAttribute('r', '8'); | ||
checkCircle.classList.add('chartogram__chart-toggler-check-circle'); | ||
check.appendChild(checkCircle); // Add check mark. | ||
var checkMark = document.createElementNS(xmlns, 'path'); | ||
checkMark.setAttribute('d', 'M13.64 4.94l-6.2 6.34-1.69-1.9c-.73-.63-1.89.1-1.36 1.06l2 3.38c.3.43 1.04.85 1.78 0 .32-.42 6.31-7.93 6.31-7.93.74-.84-.2-1.58-.84-.95z'); | ||
checkMark.setAttribute('fill', 'white'); | ||
checkMark.classList.add('chartogram__chart-toggler-check-mark'); | ||
check.appendChild(checkMark); // Add checkmark. | ||
toggler.appendChild(check); // Add graph name. | ||
toggler.appendChild(document.createTextNode(name)); // On click. | ||
toggler.addEventListener('click', function () { | ||
var show = !showGraphs[id]; // Won't allow hiding all graphs. | ||
if (!show) { | ||
var graphsShown = Object.keys(showGraphs).filter(function (id) { | ||
return showGraphs[id] !== false; | ||
}).length; | ||
if (graphsShown === 1) { | ||
return; | ||
} | ||
} | ||
showGraphs[id] = show; | ||
drawGraphs(true); | ||
toggler.classList.toggle('chartogram__chart-toggler--on'); | ||
}); | ||
return toggler; | ||
} | ||
function getMaxY(graphs) { | ||
var maxY = 0; | ||
for (var _iterator9 = graphs, _isArray9 = Array.isArray(_iterator9), _i10 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { | ||
var _ref12; | ||
if (_isArray9) { | ||
if (_i10 >= _iterator9.length) break; | ||
_ref12 = _iterator9[_i10++]; | ||
} else { | ||
_i10 = _iterator9.next(); | ||
if (_i10.done) break; | ||
_ref12 = _i10.value; | ||
} | ||
var graph = _ref12; | ||
maxY = Math.max.apply(Math, [maxY].concat(_toConsumableArray(graph.points))); | ||
} | ||
return maxY; | ||
} | ||
function setUpTimelineWindowHandle(side) { | ||
var handle = side === 'left' ? timelineWindowLeftHandle : timelineWindowRightHandle; | ||
var handleWidth = parseFloat(getComputedStyle(timelineWindow).borderLeftWidth); | ||
var timelineCoordinates; | ||
var minX; | ||
var maxX; | ||
var deltaX; | ||
var startedX; | ||
function onDrag(x) { | ||
x = x - deltaX; | ||
x = Math.max(Math.min(x, maxX), minX); | ||
x = (x - timelineCoordinates.x) / timelineCoordinates.width; | ||
if (side === 'left') { | ||
timelineWindowFrom = x; | ||
} else { | ||
timelineWindowTo = x; | ||
} | ||
updateTimelineWindow(); | ||
} | ||
function onDragStart(x) { | ||
timelineCoordinates = timeline.getBoundingClientRect(); | ||
var timelineWindowCoordinates = timelineWindow.getBoundingClientRect(); | ||
if (side === 'left') { | ||
minX = timelineCoordinates.x; | ||
maxX = timelineWindowCoordinates.x + timelineWindowCoordinates.width - 2 * handleWidth; | ||
deltaX = x - timelineWindowCoordinates.x; | ||
} else { | ||
minX = timelineWindowCoordinates.x + 2 * handleWidth; | ||
maxX = timelineCoordinates.x + timelineCoordinates.width; | ||
deltaX = x - (timelineWindowCoordinates.x + timelineWindowCoordinates.width); | ||
} | ||
} | ||
return setUpDrag(handle, onDragStart, onDrag); | ||
} | ||
function setUpTimelineWindow() { | ||
var timelineCoordinates; | ||
var timelineWindowCoordinates; | ||
var minX; | ||
var maxX; | ||
var innerX; | ||
function onDrag(x) { | ||
x = x - innerX; | ||
x = Math.max(Math.min(x, maxX), minX); | ||
x = (x - timelineCoordinates.x) / timelineCoordinates.width; | ||
timelineWindowFrom = x; | ||
timelineWindowTo = x + timelineWindowCoordinates.width / timelineCoordinates.width; | ||
updateTimelineWindow(); | ||
} | ||
function onDragStart(x) { | ||
timelineCoordinates = timeline.getBoundingClientRect(); | ||
timelineWindowCoordinates = timelineWindow.getBoundingClientRect(); | ||
innerX = x - timelineWindowCoordinates.x; | ||
minX = timelineCoordinates.x; | ||
maxX = timelineCoordinates.x + (timelineCoordinates.width - timelineWindowCoordinates.width); | ||
} | ||
return setUpDrag(timelineWindowDrag, onDragStart, onDrag); | ||
} | ||
function setUpDrag(element, onDragStart, onDrag) { | ||
function onTouchMove(event) { | ||
onDrag(event.changedTouches[0].clientX, event.changedTouches[0].clientY); | ||
} | ||
function onPointerMove(event) { | ||
onDrag(event.clientX, event.clientY); | ||
} | ||
function onDragStop() { | ||
window.removeEventListener('pointermove', onPointerMove); | ||
window.removeEventListener('touchmove', onTouchMove); | ||
window.removeEventListener('pointerup', onDragStop); | ||
window.removeEventListener('pointercancel', onDragStop); | ||
window.removeEventListener('touchend', onDragStop); | ||
window.removeEventListener('touchcancel', onDragStop); | ||
} | ||
function onTouchStart(event) { | ||
// Ignore multitouch. | ||
if (event.touches.length > 1) { | ||
// Reset. | ||
return onDragStop(); | ||
} | ||
onDragStart(event.changedTouches[0].clientX, event.changedTouches[0].clientY); | ||
window.addEventListener('touchmove', onTouchMove); | ||
window.addEventListener('touchend', onDragStop); | ||
window.addEventListener('touchcancel', onDragStop); | ||
} // Safari doesn't support pointer events. | ||
// https://caniuse.com/#feat=pointer | ||
element.addEventListener('touchstart', onTouchStart); | ||
function onPointerDown(event) { | ||
onDragStart(event.clientX, event.clientY); | ||
window.addEventListener('pointermove', onPointerMove); | ||
window.addEventListener('pointerup', onDragStop); | ||
window.addEventListener('pointercancel', onDragStop); | ||
} | ||
element.addEventListener('pointerdown', onPointerDown); | ||
return function () { | ||
onDragStop(); | ||
element.removeEventListener(onPointerDown); | ||
element.removeEventListener(onTouchStart); | ||
}; | ||
} | ||
function setTimelineWindowLeft(x) { | ||
timelineOverlayLeft.style.right = "".concat(100 * (1 - x), "%"); | ||
timelineWindow.style.left = "".concat(100 * x, "%"); | ||
} | ||
function setTimelineWindowRight(x) { | ||
timelineOverlayRight.style.left = "".concat(100 * x, "%"); | ||
timelineWindow.style.right = "".concat(100 * (1 - x), "%"); | ||
} | ||
function updateTimelineWindow() { | ||
setTimelineWindowLeft(timelineWindowFrom); | ||
setTimelineWindowRight(timelineWindowTo); | ||
drawGraphs(false); | ||
} | ||
function setUpCanvas() { | ||
var canvasDimensions; | ||
var isIndexInBounds; | ||
function onTrack(screenX) { | ||
var xScreenRatio = (screenX - canvasDimensions.x) / canvasDimensions.width; | ||
var x = timelineWindowMinX + xScreenRatio * (timelineWindowMaxX - timelineWindowMinX); | ||
var xHigherIndex = timelineWindowX.findIndex(function (_) { | ||
return _ >= x; | ||
}); | ||
var xLowerIndex = xHigherIndex - 1; | ||
if (!isIndexInBounds(xHigherIndex)) { | ||
xHigherIndex = -1; | ||
} | ||
if (!isIndexInBounds(xLowerIndex)) { | ||
xLowerIndex = -1; | ||
} | ||
if (xHigherIndex < 0) { | ||
if (xLowerIndex < 0) { | ||
return removeTooltip(); | ||
} else { | ||
x = timelineWindowX[xLowerIndex]; | ||
} | ||
} else { | ||
if (xLowerIndex < 0) { | ||
x = timelineWindowX[xHigherIndex]; | ||
} else { | ||
var xLower = timelineWindowX[xLowerIndex]; | ||
var xHigher = timelineWindowX[xHigherIndex]; | ||
var deltaLower = x - xLower; | ||
var deltaHigher = xHigher - x; | ||
x = deltaLower > deltaHigher ? xHigher : xLower; | ||
} | ||
} | ||
if (x !== tooltipForX) { | ||
tooltipForX = x; | ||
if (!tooltip) { | ||
addTooltip(); | ||
} | ||
var date = new Date(x * data.x.normalized.scale + data.x.normalized.shift); | ||
tooltipDate.textContent = "".concat(WEEKDAYS[date.getDay()], ", ").concat(MONTHS[date.getMonth()], " ").concat(date.getDate()); | ||
var xIndex = timelineWindowX.indexOf(x); | ||
var i = 0; | ||
while (2 * i < tooltipValues.childNodes.length) { | ||
tooltipValues.childNodes[2 * i].textContent = timelineWindowGraphs[i].points[xIndex]; | ||
tooltipValues.childNodes[2 * i + 1].textContent = data.y[i].name; | ||
i++; | ||
} | ||
var xRatio = (x - timelineWindowMinX) / (timelineWindowMaxX - timelineWindowMinX); | ||
tooltip.style.left = "".concat(xRatio * 100, "%"); | ||
updateTooltipPoints(xIndex, xRatio); | ||
updateTooltipLine(x); | ||
} | ||
} | ||
function onTrackStart() { | ||
canvasDimensions = canvas.getBoundingClientRect(); | ||
isIndexInBounds = function isIndexInBounds(index) { | ||
if (index < 0) { | ||
return false; | ||
} | ||
return timelineWindowX[index] >= timelineWindowMinX && timelineWindowX[index] <= timelineWindowMaxX; | ||
}; | ||
} | ||
function onTouchStart(event) { | ||
// Ignore multitouch. | ||
if (event.touches.length > 1) { | ||
// Reset. | ||
return onTrackStop(); | ||
} | ||
onTrackStart(); | ||
canvas.addEventListener('touchend', onTrackStop); | ||
canvas.addEventListener('touchmove', onTouchMove); | ||
canvas.addEventListener('touchend', onTrackStop); | ||
canvas.addEventListener('touchcancel', onTrackStop); | ||
onTouchMove(event); | ||
} // Safari doesn't support pointer events. | ||
// https://caniuse.com/#feat=pointer | ||
canvas.addEventListener('touchstart', onTouchStart); | ||
function onTouchMove(event) { | ||
var x = event.changedTouches[0].clientX; | ||
var y = event.changedTouches[0].clientY; // Emulate 'pointerleave' behavior. | ||
if (x < canvasDimensions.x || x > canvasDimensions.x + canvasDimensions.width || y < canvasDimensions.y || y > canvasDimensions.y + canvasDimensions.height) { | ||
onTrackStop(); | ||
} else { | ||
onTrack(x, y); | ||
} | ||
} | ||
function onPointerMove(event) { | ||
onTrack(event.clientX, event.clientY); | ||
} | ||
function onTrackStop() { | ||
canvas.removeEventListener('pointermove', onPointerMove); | ||
canvas.removeEventListener('pointerleave', onTrackStop); | ||
canvas.removeEventListener('pointercancel', onTrackStop); | ||
canvas.removeEventListener('touchmove', onTouchMove); | ||
canvas.removeEventListener('touchend', onTrackStop); | ||
canvas.removeEventListener('touchcancel', onTrackStop); | ||
removeTooltip(); | ||
} | ||
function onPointerEnter() { | ||
onTrackStart(); | ||
canvas.addEventListener('pointermove', onPointerMove); | ||
canvas.addEventListener('pointerleave', onTrackStop); | ||
canvas.addEventListener('pointercancel', onTrackStop); | ||
} | ||
canvas.addEventListener('pointerenter', onPointerEnter); | ||
return function () { | ||
onTrackStop(); | ||
canvas.removeEventListener(onPointerEnter); | ||
canvas.removeEventListener(onTouchStart); | ||
}; | ||
} | ||
function addTooltip() { | ||
// Create tooltip. | ||
tooltip = document.createElement('div'); | ||
tooltip.classList.add('chartogram__tooltip'); | ||
tooltipContainer.appendChild(tooltip); // Add tooltip title. | ||
tooltipDate = document.createElement('h1'); | ||
tooltipDate.classList.add('chartogram__tooltip-header'); | ||
tooltip.appendChild(tooltipDate); // Add graph values. | ||
tooltipValues = document.createElement('dl'); | ||
tooltipValues.classList.add('chartogram__tooltip-values'); | ||
tooltip.appendChild(tooltipValues); // Add graph values. | ||
for (var _iterator10 = data.y, _isArray10 = Array.isArray(_iterator10), _i11 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { | ||
var _ref13; | ||
if (_isArray10) { | ||
if (_i11 >= _iterator10.length) break; | ||
_ref13 = _iterator10[_i11++]; | ||
} else { | ||
_i11 = _iterator10.next(); | ||
if (_i11.done) break; | ||
_ref13 = _i11.value; | ||
} | ||
var y = _ref13; | ||
if (showGraphs[y.id]) { | ||
// Add graph value. | ||
var tooltipValue = document.createElement('dt'); | ||
tooltipValue.style.color = y.color; | ||
tooltipValues.appendChild(tooltipValue); // Add graph name. | ||
var tooltipName = document.createElement('dd'); | ||
tooltipName.style.color = y.color; | ||
tooltipValues.appendChild(tooltipName); | ||
} | ||
} | ||
} | ||
function removeTooltip() { | ||
if (tooltip) { | ||
tooltipForX = undefined; | ||
tooltipContainer.removeChild(tooltip); | ||
tooltip = undefined; | ||
removeTooltipPoints(); | ||
removeTooltipLine(); | ||
} | ||
} | ||
function addTooltipLine() { | ||
var xmlns = 'http://www.w3.org/2000/svg'; | ||
tooltipLine = document.createElementNS(xmlns, 'line'); | ||
tooltipLine.setAttributeNS(null, 'class', 'chartogram__tooltip-line'); | ||
canvas.insertBefore(tooltipLine, canvas.querySelector('polyline')); | ||
} | ||
function removeTooltipLine() { | ||
canvas.removeChild(tooltipLine); | ||
tooltipLine = undefined; | ||
} | ||
function addTooltipPoints() { | ||
tooltipPoints = []; | ||
var i = 0; | ||
while (i < timelineWindowGraphs.length) { | ||
var point = document.createElement('div'); | ||
point.classList.add('chartogram__tooltip-point'); | ||
point.style.color = timelineWindowGraphs[i].color; | ||
tooltipPoints.push(point); | ||
canvasWrapper.appendChild(point); | ||
i++; | ||
} | ||
} | ||
function removeTooltipPoints() { | ||
for (var _iterator11 = tooltipPoints, _isArray11 = Array.isArray(_iterator11), _i12 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { | ||
var _ref14; | ||
if (_isArray11) { | ||
if (_i12 >= _iterator11.length) break; | ||
_ref14 = _iterator11[_i12++]; | ||
} else { | ||
_i12 = _iterator11.next(); | ||
if (_i12.done) break; | ||
_ref14 = _i12.value; | ||
} | ||
var point = _ref14; | ||
canvasWrapper.removeChild(point); | ||
} | ||
tooltipPoints = undefined; | ||
} | ||
function updateTooltipLine(x) { | ||
if (!tooltipLine) { | ||
addTooltipLine(); | ||
} | ||
tooltipLine.setAttributeNS(null, 'x1', fixSvgCoordinate(x)); | ||
tooltipLine.setAttributeNS(null, 'x2', fixSvgCoordinate(x)); | ||
tooltipLine.setAttributeNS(null, 'y1', fixSvgCoordinate(timelineWindowMinY)); | ||
tooltipLine.setAttributeNS(null, 'y2', fixSvgCoordinate(timelineWindowMaxY)); | ||
} | ||
function updateTooltipPoints(xIndex, xRatio) { | ||
if (!tooltipPoints) { | ||
addTooltipPoints(); | ||
} | ||
var i = 0; | ||
while (i < tooltipPoints.length) { | ||
var point = tooltipPoints[i]; | ||
point.style.left = "".concat(xRatio * 100, "%"); | ||
var y = timelineWindowGraphs[i].normalized.points[xIndex]; | ||
var yRatio = y / timelineWindowMaxY; | ||
point.style.bottom = "".concat(yRatio * 100, "%"); | ||
i++; | ||
} | ||
} | ||
import Chartogram from './Chartogram'; | ||
export default function chartogram(rootNode, data, title, options) { | ||
var chartogram = new Chartogram(rootNode, data, title, options); | ||
chartogram.componentDidMount(); | ||
chartogram.render(); | ||
return function () { | ||
chartogram.componentWillUnmount(); | ||
}; | ||
} | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "chartogram", | ||
"version": "0.1.4", | ||
"version": "0.1.5", | ||
"description": "Charts in JS with no dependencies", | ||
@@ -5,0 +5,0 @@ "main": "index.commonjs.js", |
@@ -82,2 +82,4 @@ # chartogram | ||
The default exported function returns another function which must be called in case of "destroying" the chart (it cleans up global event listeners). | ||
### Browser | ||
@@ -135,7 +137,7 @@ | ||
const { data, title } = this.props | ||
chartogram(this.node.current, data, title) | ||
this.cleanUp = chartogram(this.node.current, data, title) | ||
} | ||
componentWillUnmount() { | ||
// Can remove any global event listeners here. | ||
this.cleanUp | ||
} | ||
@@ -186,6 +188,6 @@ | ||
Tested in Chrome, Firefox and iOS Safari. | ||
Tested in Chrome, Firefox, Microsoft Edge and iOS Safari. | ||
Won't work in Edge or Internet Explorer. | ||
For some reason doesn't show the `.chartogram__canvas` SVG element when it's wrapped in `.chartogram__canvas-wrapper` in Internet Explorer. | ||
The styles use [CSS variables](https://caniuse.com/#feat=css-variables) which work everywhere except Internet Explorer. |
@@ -1,831 +0,10 @@ | ||
export default function chartogram(rootNode, data, title = 'Title', options = {}) { | ||
// clearElement(rootNode) | ||
rootNode.innerHTML = ` | ||
<header class="chartogram__header"> | ||
<h1 class="chartogram__title">${title}</h1> | ||
</header> | ||
<div class="chartogram__plan-with-axes"> | ||
<div class="chartogram__plan"> | ||
<div class="chartogram__top-border"></div> | ||
<div class="chartogram__canvas-wrapper"> | ||
<svg class="chartogram__canvas" preserveAspectRatio="none"></svg> | ||
<div class="chartogram__x"></div> | ||
<div class="chartogram__y-wrapper"> | ||
<div class="chartogram__y"></div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div class="chartogram__timeline"> | ||
<div class="chartogram__timeline-canvas-padding"> | ||
<svg class="chartogram__timeline-canvas" preserveAspectRatio="none"></svg> | ||
</div> | ||
<div class="chartogram__timeline-overlay-left"></div> | ||
<div class="chartogram__timeline-overlay-right"></div> | ||
<div class="chartogram__timeline-window"> | ||
<button type="button" class="chartogram__reset-button chartogram__timeline-window__drag"></button> | ||
<button type="button" class="chartogram__reset-button chartogram__timeline-window__left-handle"></button> | ||
<button type="button" class="chartogram__reset-button chartogram__timeline-window__right-handle"></button> | ||
</div> | ||
</div> | ||
<div class="chartogram__chart-togglers"></div> | ||
` | ||
import Chartogram from './Chartogram' | ||
rootNode.classList.add('chartogram') | ||
const GAUGE_TICK_MARKS_COUNT = options.gaugeMarkTicksCount || 6 | ||
const TIMELINE_WINDOW_SIZE = options.timelineWindowSize || 40 | ||
const TIMELINE_CHART_MAX_POINTS = options.timelineChartMaxPoints || 80 | ||
const MONTHS = options.months || [ | ||
'Jan', | ||
'Feb', | ||
'Mar', | ||
'Apr', | ||
'May', | ||
'Jun', | ||
'Jul', | ||
'Aug', | ||
'Sep', | ||
'Oct', | ||
'Nov', | ||
'Dec' | ||
] | ||
const WEEKDAYS = options.weekdays || [ | ||
'Sun', | ||
'Mon', | ||
'Tue', | ||
'Wed', | ||
'Thu', | ||
'Fri', | ||
'Sat' | ||
] | ||
const tooltipContainer = document.querySelector('.chartogram__plan') | ||
const canvas = document.querySelector('.chartogram__canvas') | ||
const canvasWrapper = document.querySelector('.chartogram__canvas-wrapper') | ||
const xAxis = document.querySelector('.chartogram__x') | ||
const yAxis = document.querySelector('.chartogram__y') | ||
const timeline = document.querySelector('.chartogram__timeline') | ||
const timelineOverlayLeft = document.querySelector('.chartogram__timeline-overlay-left') | ||
const timelineWindowLeftHandle = document.querySelector('.chartogram__timeline-window__left-handle') | ||
const timelineWindow = document.querySelector('.chartogram__timeline-window') | ||
const timelineWindowDrag = document.querySelector('.chartogram__timeline-window__drag') | ||
const timelineWindowRightHandle = document.querySelector('.chartogram__timeline-window__right-handle') | ||
const timelineOverlayRight = document.querySelector('.chartogram__timeline-overlay-right') | ||
const timelineCanvas = document.querySelector('.chartogram__timeline-canvas') | ||
let timelineWindowFrom | ||
let timelineWindowTo | ||
let showGraphs | ||
let showGraphsNext | ||
let timelineWindowGraphs | ||
let timelineWindowMinX | ||
let timelineWindowMaxX | ||
let timelineWindowMinY | ||
let timelineWindowMaxY | ||
let timelineWindowX | ||
let tooltip | ||
let tooltipDate | ||
let tooltipValues | ||
let tooltipForX | ||
let tooltipPoints | ||
let tooltipLine | ||
// A stub for possible animations. | ||
let yScale = 1 | ||
setUpTimelineWindowHandle('left') | ||
setUpTimelineWindowHandle('right') | ||
setUpTimelineWindow() | ||
normalizeDataPoints() | ||
displayGraphs() | ||
setUpCanvas() | ||
function clearElement(element) { | ||
while(element.firstChild) { | ||
element.removeChild(element.firstChild) | ||
} | ||
export default function chartogram(rootNode, data, title, options) { | ||
const chartogram = new Chartogram(rootNode, data, title, options) | ||
chartogram.componentDidMount() | ||
chartogram.render() | ||
return () => { | ||
chartogram.componentWillUnmount() | ||
} | ||
function commaJoin(a, b) { | ||
return a.map((ai, i) => `${ai},${b[i]}`) | ||
} | ||
function createPolylinePoints(x, y) { | ||
// return commaJoin(x, y) | ||
return commaJoin(x.map(fixSvgCoordinate), y.map(fixSvgCoordinate)) | ||
} | ||
// Firefox is buggy with too high and too fractional SVG coordinates. | ||
function fixSvgCoordinate(x) { | ||
return Math.round(x * Number.MAX_SAFE_INTEGER) / Number.MAX_SAFE_INTEGER | ||
} | ||
function getLowerSiblingDivisibleBy(n, divider) { | ||
while (true) { | ||
if (n < divider) { | ||
return divider | ||
} | ||
if (n % divider === 0) { | ||
return n | ||
} | ||
n-- | ||
} | ||
} | ||
function divideInterval(min, max) { | ||
const points = new Array(GAUGE_TICK_MARKS_COUNT) | ||
let i = 0 | ||
while (i < GAUGE_TICK_MARKS_COUNT) { | ||
points[i] = min + i * (max - min) / (GAUGE_TICK_MARKS_COUNT - 1) | ||
i++ | ||
} | ||
return points | ||
} | ||
function drawGauge(element, min, max, transform) { | ||
let i = 0 | ||
while (i < GAUGE_TICK_MARKS_COUNT) { | ||
const tickMark = document.createElement('div') | ||
let value = min + i * (max - min) / (GAUGE_TICK_MARKS_COUNT - 1) | ||
if (transform) { | ||
value = transform(value) | ||
} | ||
tickMark.appendChild(document.createTextNode(value)) | ||
element.appendChild(tickMark) | ||
i++ | ||
} | ||
} | ||
// Chrome and Firefox can't handle timestamps in milliseconds for point coordinates (draws nothing). | ||
// Reducing timestamps to lower numbers to work around that bug. | ||
function normalizeDataPoints() { | ||
normalizePoints([data.x]) | ||
normalizePoints(data.y) | ||
} | ||
function normalizePoints(all) { | ||
const isX = all[0] === data.x | ||
let min = Infinity | ||
let max = -Infinity | ||
for (const one of all) { | ||
if (isX) { | ||
one.min = one.points[0] | ||
one.max = one.points[one.points.length - 1] | ||
} else { | ||
// For Y min is always 0 by design. | ||
one.min = 0 | ||
// one.min = Math.min(...one.points) | ||
one.max = Math.max(...one.points) | ||
} | ||
min = Math.min(min, one.min) | ||
max = Math.max(max, one.max) | ||
} | ||
// For Y min is always 0 by design. | ||
if (!isX) { | ||
min = 0 | ||
} | ||
const shift = min | ||
const scale = max - min | ||
for (const one of all) { | ||
one.normalized = { | ||
points: one.points.map(_ => (_ - shift) / scale), | ||
shift, | ||
scale | ||
} | ||
} | ||
} | ||
function displayGraphs() { | ||
showGraphs = {} | ||
for (const y of data.y) { | ||
showGraphs[y.id] = true | ||
} | ||
showGraphsNext = undefined | ||
if (data.x.points.length > TIMELINE_WINDOW_SIZE) { | ||
const xMin = data.x.points[0] | ||
const xMax = data.x.points[data.x.points.length - 1] | ||
const xFrom = data.x.points[data.x.points.length - TIMELINE_WINDOW_SIZE] | ||
timelineWindowFrom = (xFrom - xMin) / (xMax - xMin) | ||
} else { | ||
timelineWindowFrom = 0 | ||
} | ||
timelineWindowTo = 1 | ||
updateTimelineWindow() | ||
drawGraphs(true) | ||
tooltipLine = undefined | ||
// Add graph togglers. | ||
const graphTogglers = document.querySelector('.chartogram__chart-togglers') | ||
clearElement(graphTogglers) | ||
for (const y of data.y) { | ||
graphTogglers.appendChild(createGraphToggler(y)) | ||
} | ||
} | ||
function drawGraphs(redrawTimeline) { | ||
// Clear canvas. | ||
clearElement(canvas) | ||
// Calculate bounds. | ||
const xPoints = data.x.points | ||
const minXOverall = xPoints[0] | ||
const maxXOverall = xPoints[xPoints.length - 1] | ||
const deltaX = maxXOverall - minXOverall | ||
const minX = minXOverall + timelineWindowFrom * deltaX | ||
const maxX = maxXOverall - (1 - timelineWindowTo) * deltaX | ||
let minXIndex = xPoints.findIndex(x => x > minX) - 1 | ||
if (minXIndex < 0) { | ||
minXIndex = 0 | ||
} | ||
let maxXIndex = xPoints.findIndex(x => x > maxX) | ||
if (maxXIndex < 0) { | ||
maxXIndex = xPoints.length - 1 | ||
} | ||
timelineWindowX = data.x.normalized.points.slice(minXIndex, maxXIndex + 1) | ||
// let minY = Infinity | ||
// Min Y is always 0 by design. | ||
const minY = 0 | ||
let maxY = -Infinity | ||
timelineWindowGraphs = [] | ||
for (const y of data.y) { | ||
if (!showGraphs[y.id]) { | ||
continue | ||
} | ||
const points = y.points.slice(minXIndex, maxXIndex + 1) | ||
// Min Y is always 0 by design. | ||
const min = 0 | ||
// const min = Math.min(...points) | ||
const max = Math.max(...points) | ||
timelineWindowGraphs.push({ | ||
...y, | ||
points, | ||
min, | ||
max, | ||
normalized: { | ||
...y.normalized, | ||
points: y.normalized.points.slice(minXIndex, maxXIndex + 1) | ||
} | ||
}) | ||
// minY = Math.min(minY, ...points) | ||
maxY = Math.max(maxY, ...points) | ||
} | ||
// Set canvas `viewBox`. | ||
const minXNormalized = (minX - data.x.normalized.shift) / data.x.normalized.scale | ||
const maxXNormalized = (maxX - data.x.normalized.shift) / data.x.normalized.scale | ||
const minYNormalized = (minY - data.y[0].normalized.shift) / data.y[0].normalized.scale | ||
const maxYNormalized = (maxY - data.y[0].normalized.shift) / data.y[0].normalized.scale | ||
canvas.setAttribute('viewBox', `${minXNormalized} ${minYNormalized} ${maxXNormalized - minXNormalized} ${maxYNormalized - minYNormalized}`) | ||
// Calculate grid lines' coordinates. | ||
const minY_ = 0 | ||
const maxY_ = getLowerSiblingDivisibleBy(maxY, 10) | ||
const yAxisScale = (maxY - minY) / (maxY_ - minY) | ||
const yAxisTickMarks = divideInterval(minY_, maxY_) | ||
// Draw grid lines. | ||
for (const y of yAxisTickMarks) { | ||
canvas.appendChild(createGridLine( | ||
(y - data.y[0].normalized.shift) / data.y[0].normalized.scale, | ||
minXNormalized, | ||
maxXNormalized, | ||
minYNormalized, | ||
maxYNormalized | ||
)) | ||
} | ||
// Trim X axis. | ||
const _x = timelineWindowX.slice() | ||
const _minX = _x[0] | ||
const _maxX = _x[_x.length - 1] | ||
const trimLeftRatio = (minXNormalized - _minX) / (_x[1] - _minX) | ||
const trimRightRatio = (_maxX - maxXNormalized) / (_maxX - _x[_x.length - 2]) | ||
_x[0] = minXNormalized | ||
_x[_x.length - 1] = maxXNormalized | ||
// Draw charts. | ||
for (const { id, color, normalized: { points } } of timelineWindowGraphs) { | ||
// Trim chart. | ||
const _y = points.slice() | ||
const _minY = _y[0] | ||
const _maxY = _y[_y.length - 1] | ||
_y[0] = _minY + (_y[1] - _minY) * trimLeftRatio | ||
_y[_y.length - 1] = _maxY - (_maxY - _y[_y.length - 2]) * trimRightRatio | ||
// Draw chart. | ||
const graph = document.createElement('polyline') | ||
graph.setAttribute('stroke', color) | ||
graph.setAttribute('points', createPolylinePoints(_x, _y.map(y => maxYNormalized - yScale * y)).join(' ')) | ||
graph.classList.add('chartogram__graph') | ||
canvas.appendChild(graph) | ||
} | ||
// A workaround to fix WebKit bug when it's not re-rendering the <svg/>. | ||
// https://stackoverflow.com/questions/30905493/how-to-force-webkit-to-update-svg-use-elements-after-changes-to-original | ||
canvas.innerHTML += '' | ||
// Draw gauges. | ||
drawGauges( | ||
minX, | ||
maxX, | ||
minY_, | ||
maxY_, | ||
yAxisScale | ||
) | ||
// Draw timeline graph. | ||
timelineWindowMinY = minYNormalized | ||
timelineWindowMaxY = maxYNormalized | ||
timelineWindowMinX = minXNormalized | ||
timelineWindowMaxX = maxXNormalized | ||
if (redrawTimeline) { | ||
drawTimeline() | ||
} | ||
} | ||
// function animateScale(scale) { | ||
// console.log(scale) | ||
// animateScaleTo = scale | ||
// animateScaleStartedAt = Date.now() | ||
// previousYScale = yScale | ||
// requestAnimationFrame(animateScaleTick) | ||
// } | ||
// function animateScaleTick() { | ||
// const elapsed = Date.now() - animateScaleStartedAt | ||
// yScale = previousYScale + (animateScaleTo - previousYScale) * elapsed / 300 | ||
// drawGraphs(true) | ||
// if (elapsed < 300) { | ||
// requestAnimationFrame(animateScaleTick) | ||
// } | ||
// } | ||
function createGridLine(y, minX, maxX, minY, maxY) { | ||
const line = document.createElement('line') | ||
line.classList.add('chartogram__grid-line') | ||
line.setAttribute('x1', fixSvgCoordinate(minX)) | ||
line.setAttribute('x2', fixSvgCoordinate(maxX)) | ||
line.setAttribute('y1', fixSvgCoordinate((maxY - minY) - y)) | ||
line.setAttribute('y2', fixSvgCoordinate((maxY - minY) - y)) | ||
return line | ||
} | ||
function drawGauges(minX, maxX, minY, maxY, yAxisScale) { | ||
clearElement(xAxis) | ||
clearElement(yAxis) | ||
drawGauge(xAxis, minX, maxX, (timestamp) => { | ||
const date = new Date(timestamp) | ||
return `${MONTHS[date.getMonth()]} ${date.getDate()}` | ||
}) | ||
drawGauge(yAxis, minY, maxY) | ||
yAxis.style.height = `${100 / yAxisScale}%` | ||
} | ||
function simplifyGraph(x, y, maxPoints, yMax = Math.max(...y), threshold = 0.025, i = 0, _x = new Array(x.length), _y = new Array(x.length), _i = 0) { | ||
if (i + 2 > x.length - 1) { | ||
while (i < y.length) { | ||
_x[_i] = x[i] | ||
_y[_i] = y[i] | ||
_i++ | ||
i++ | ||
} | ||
_x = _x.slice(0, _i) | ||
_y = _y.slice(0, _i) | ||
if (_x.length <= maxPoints) { | ||
return [_x, _y] | ||
} else { | ||
if (x.length / _x.length < 1.1) { | ||
threshold = Math.min(threshold + 0.025, 1) | ||
} | ||
return simplifyGraph(_x, _y, maxPoints, yMax, threshold) | ||
} | ||
} | ||
const y0 = (y[i + 2] + y[i]) / 2 | ||
if (Math.abs(y0 - y[i + 1]) / yMax < threshold) { | ||
_x[_i] = x[i] | ||
_x[_i + 1] = x[i + 2] | ||
_y[_i] = y[i] | ||
_y[_i + 1] = y[i + 2] | ||
return simplifyGraph(x, y, maxPoints, yMax, threshold, i + 2, _x, _y, _i + 1) | ||
} else { | ||
_x[_i] = x[i] | ||
_x[_i + 1] = x[i + 1] | ||
_x[_i + 2] = x[i + 2] | ||
_y[_i] = y[i] | ||
_y[_i + 1] = y[i + 1] | ||
_y[_i + 2] = y[i + 2] | ||
return simplifyGraph(x, y, maxPoints, yMax, threshold, i + 2, _x, _y, _i + 2) | ||
} | ||
} | ||
function drawTimeline() { | ||
const x = data.x.normalized.points | ||
const graphs = data.y.filter(_ => showGraphs[_.id]) | ||
const minX = 0 | ||
const maxX = 1 | ||
const minY = (Math.min(...graphs.map(_ => _.min)) - graphs[0].normalized.shift) / graphs[0].normalized.scale | ||
const maxY = (Math.max(...graphs.map(_ => _.max)) - graphs[0].normalized.shift) / graphs[0].normalized.scale | ||
clearElement(timelineCanvas) | ||
// Set canvas `viewBox`. | ||
timelineCanvas.setAttribute('viewBox', `${minX} ${minY} ${maxX - minX} ${maxY - minY}`) | ||
for (const { id, color, normalized: { points } } of graphs) { | ||
const [_x, _y] = simplifyGraph(x, points, 80) | ||
const graph = document.createElement('polyline') | ||
graph.setAttribute('stroke', color) | ||
graph.setAttribute('points', createPolylinePoints(_x, _y.map(y => (maxY - minY) - y)).join(' ')) | ||
graph.classList.add('chartogram__graph') | ||
timelineCanvas.appendChild(graph) | ||
} | ||
// A workaround to fix WebKit bug when it's not re-rendering the <svg/>. | ||
// https://stackoverflow.com/questions/30905493/how-to-force-webkit-to-update-svg-use-elements-after-changes-to-original | ||
timelineCanvas.innerHTML += '' | ||
} | ||
function createGraphToggler({ id, name, color }) { | ||
const toggler = document.createElement('button') | ||
toggler.setAttribute('type', 'button') | ||
toggler.classList.add('chartogram__chart-toggler') | ||
toggler.classList.add('chartogram__chart-toggler--on') | ||
toggler.classList.add('chartogram__reset-button') | ||
// Add check. | ||
const xmlns = 'http://www.w3.org/2000/svg' | ||
const check = document.createElementNS(xmlns, 'svg') | ||
check.setAttributeNS(null, 'viewBox', '0 0 19 19') | ||
check.classList.add('chartogram__chart-toggler-check') | ||
// Add background circle. | ||
const backgroundCircle = document.createElementNS(xmlns, 'circle') | ||
backgroundCircle.setAttribute('cx', '9.5') | ||
backgroundCircle.setAttribute('cy', '9.5') | ||
backgroundCircle.setAttribute('r', '9.5') | ||
backgroundCircle.setAttribute('fill', color) | ||
check.appendChild(backgroundCircle) | ||
// Add check circle. | ||
const checkCircle = document.createElementNS(xmlns, 'circle') | ||
checkCircle.setAttribute('cx', '9.5') | ||
checkCircle.setAttribute('cy', '9.5') | ||
checkCircle.setAttribute('r', '8') | ||
checkCircle.classList.add('chartogram__chart-toggler-check-circle') | ||
check.appendChild(checkCircle) | ||
// Add check mark. | ||
const checkMark = document.createElementNS(xmlns, 'path') | ||
checkMark.setAttribute('d', 'M13.64 4.94l-6.2 6.34-1.69-1.9c-.73-.63-1.89.1-1.36 1.06l2 3.38c.3.43 1.04.85 1.78 0 .32-.42 6.31-7.93 6.31-7.93.74-.84-.2-1.58-.84-.95z') | ||
checkMark.setAttribute('fill', 'white') | ||
checkMark.classList.add('chartogram__chart-toggler-check-mark') | ||
check.appendChild(checkMark) | ||
// Add checkmark. | ||
toggler.appendChild(check) | ||
// Add graph name. | ||
toggler.appendChild(document.createTextNode(name)) | ||
// On click. | ||
toggler.addEventListener('click', () => { | ||
const show = !showGraphs[id] | ||
// Won't allow hiding all graphs. | ||
if (!show) { | ||
const graphsShown = Object.keys(showGraphs).filter(id => showGraphs[id] !== false).length | ||
if (graphsShown === 1) { | ||
return | ||
} | ||
} | ||
showGraphs[id] = show | ||
drawGraphs(true) | ||
toggler.classList.toggle('chartogram__chart-toggler--on') | ||
}) | ||
return toggler | ||
} | ||
function getMaxY(graphs) { | ||
let maxY = 0 | ||
for (const graph of graphs) { | ||
maxY = Math.max(maxY, ...graph.points) | ||
} | ||
return maxY | ||
} | ||
function setUpTimelineWindowHandle(side) { | ||
const handle = side === 'left' ? timelineWindowLeftHandle : timelineWindowRightHandle | ||
let handleWidth = parseFloat(getComputedStyle(timelineWindow).borderLeftWidth) | ||
let timelineCoordinates | ||
let minX | ||
let maxX | ||
let deltaX | ||
let startedX | ||
function onDrag(x) { | ||
x = x - deltaX | ||
x = Math.max(Math.min(x, maxX), minX) | ||
x = (x - timelineCoordinates.x) / timelineCoordinates.width | ||
if (side === 'left') { | ||
timelineWindowFrom = x | ||
} else { | ||
timelineWindowTo = x | ||
} | ||
updateTimelineWindow() | ||
} | ||
function onDragStart(x) { | ||
timelineCoordinates = timeline.getBoundingClientRect() | ||
const timelineWindowCoordinates = timelineWindow.getBoundingClientRect() | ||
if (side === 'left') { | ||
minX = timelineCoordinates.x | ||
maxX = timelineWindowCoordinates.x + timelineWindowCoordinates.width - 2 * handleWidth | ||
deltaX = x - timelineWindowCoordinates.x | ||
} else { | ||
minX = timelineWindowCoordinates.x + 2 * handleWidth | ||
maxX = timelineCoordinates.x + timelineCoordinates.width | ||
deltaX = x - (timelineWindowCoordinates.x + timelineWindowCoordinates.width) | ||
} | ||
} | ||
return setUpDrag(handle, onDragStart, onDrag) | ||
} | ||
function setUpTimelineWindow() { | ||
let timelineCoordinates | ||
let timelineWindowCoordinates | ||
let minX | ||
let maxX | ||
let innerX | ||
function onDrag(x) { | ||
x = x - innerX | ||
x = Math.max(Math.min(x, maxX), minX) | ||
x = (x - timelineCoordinates.x) / timelineCoordinates.width | ||
timelineWindowFrom = x | ||
timelineWindowTo = x + timelineWindowCoordinates.width / timelineCoordinates.width | ||
updateTimelineWindow() | ||
} | ||
function onDragStart(x) { | ||
timelineCoordinates = timeline.getBoundingClientRect() | ||
timelineWindowCoordinates = timelineWindow.getBoundingClientRect() | ||
innerX = x - timelineWindowCoordinates.x | ||
minX = timelineCoordinates.x | ||
maxX = timelineCoordinates.x + (timelineCoordinates.width - timelineWindowCoordinates.width) | ||
} | ||
return setUpDrag(timelineWindowDrag, onDragStart, onDrag) | ||
} | ||
function setUpDrag(element, onDragStart, onDrag) { | ||
function onTouchMove(event) { | ||
onDrag( | ||
event.changedTouches[0].clientX, | ||
event.changedTouches[0].clientY | ||
) | ||
} | ||
function onPointerMove(event) { | ||
onDrag(event.clientX, event.clientY) | ||
} | ||
function onDragStop() { | ||
window.removeEventListener('pointermove', onPointerMove) | ||
window.removeEventListener('touchmove', onTouchMove) | ||
window.removeEventListener('pointerup', onDragStop) | ||
window.removeEventListener('pointercancel', onDragStop) | ||
window.removeEventListener('touchend', onDragStop) | ||
window.removeEventListener('touchcancel', onDragStop) | ||
} | ||
function onTouchStart(event) { | ||
// Ignore multitouch. | ||
if (event.touches.length > 1) { | ||
// Reset. | ||
return onDragStop() | ||
} | ||
onDragStart( | ||
event.changedTouches[0].clientX, | ||
event.changedTouches[0].clientY | ||
) | ||
window.addEventListener('touchmove', onTouchMove) | ||
window.addEventListener('touchend', onDragStop) | ||
window.addEventListener('touchcancel', onDragStop) | ||
} | ||
// Safari doesn't support pointer events. | ||
// https://caniuse.com/#feat=pointer | ||
element.addEventListener('touchstart', onTouchStart) | ||
function onPointerDown(event) { | ||
onDragStart(event.clientX, event.clientY) | ||
window.addEventListener('pointermove', onPointerMove) | ||
window.addEventListener('pointerup', onDragStop) | ||
window.addEventListener('pointercancel', onDragStop) | ||
} | ||
element.addEventListener('pointerdown', onPointerDown) | ||
return () => { | ||
onDragStop() | ||
element.removeEventListener(onPointerDown) | ||
element.removeEventListener(onTouchStart) | ||
} | ||
} | ||
function setTimelineWindowLeft(x) { | ||
timelineOverlayLeft.style.right = `${100 * (1 - x)}%` | ||
timelineWindow.style.left = `${100 * x}%` | ||
} | ||
function setTimelineWindowRight(x) { | ||
timelineOverlayRight.style.left = `${100 * x}%` | ||
timelineWindow.style.right = `${100 * (1 - x)}%` | ||
} | ||
function updateTimelineWindow() { | ||
setTimelineWindowLeft(timelineWindowFrom) | ||
setTimelineWindowRight(timelineWindowTo) | ||
drawGraphs(false) | ||
} | ||
function setUpCanvas() { | ||
let canvasDimensions | ||
let isIndexInBounds | ||
function onTrack(screenX) { | ||
const xScreenRatio = (screenX - canvasDimensions.x) / canvasDimensions.width | ||
let x = timelineWindowMinX + xScreenRatio * (timelineWindowMaxX - timelineWindowMinX) | ||
let xHigherIndex = timelineWindowX.findIndex(_ => _ >= x) | ||
let xLowerIndex = xHigherIndex - 1 | ||
if (!isIndexInBounds(xHigherIndex)) { | ||
xHigherIndex = -1 | ||
} | ||
if (!isIndexInBounds(xLowerIndex)) { | ||
xLowerIndex = -1 | ||
} | ||
if (xHigherIndex < 0) { | ||
if (xLowerIndex < 0) { | ||
return removeTooltip() | ||
} else { | ||
x = timelineWindowX[xLowerIndex] | ||
} | ||
} else { | ||
if (xLowerIndex < 0) { | ||
x = timelineWindowX[xHigherIndex] | ||
} else { | ||
const xLower = timelineWindowX[xLowerIndex] | ||
const xHigher = timelineWindowX[xHigherIndex] | ||
const deltaLower = x - xLower | ||
const deltaHigher = xHigher - x | ||
x = deltaLower > deltaHigher ? xHigher : xLower | ||
} | ||
} | ||
if (x !== tooltipForX) { | ||
tooltipForX = x | ||
if (!tooltip) { | ||
addTooltip() | ||
} | ||
const date = new Date(x * data.x.normalized.scale + data.x.normalized.shift) | ||
tooltipDate.textContent = `${WEEKDAYS[date.getDay()]}, ${MONTHS[date.getMonth()]} ${date.getDate()}` | ||
const xIndex = timelineWindowX.indexOf(x) | ||
let i = 0 | ||
while (2 * i < tooltipValues.childNodes.length) { | ||
tooltipValues.childNodes[2 * i].textContent = timelineWindowGraphs[i].points[xIndex] | ||
tooltipValues.childNodes[2 * i + 1].textContent = data.y[i].name | ||
i++ | ||
} | ||
const xRatio = (x - timelineWindowMinX) / (timelineWindowMaxX - timelineWindowMinX) | ||
tooltip.style.left = `${xRatio * 100}%` | ||
updateTooltipPoints(xIndex, xRatio) | ||
updateTooltipLine(x) | ||
} | ||
} | ||
function onTrackStart() { | ||
canvasDimensions = canvas.getBoundingClientRect() | ||
isIndexInBounds = (index) => { | ||
if (index < 0) { | ||
return false | ||
} | ||
return timelineWindowX[index] >= timelineWindowMinX && | ||
timelineWindowX[index] <= timelineWindowMaxX | ||
} | ||
} | ||
function onTouchStart(event) { | ||
// Ignore multitouch. | ||
if (event.touches.length > 1) { | ||
// Reset. | ||
return onTrackStop() | ||
} | ||
onTrackStart() | ||
canvas.addEventListener('touchend', onTrackStop) | ||
canvas.addEventListener('touchmove', onTouchMove) | ||
canvas.addEventListener('touchend', onTrackStop) | ||
canvas.addEventListener('touchcancel', onTrackStop) | ||
onTouchMove(event) | ||
} | ||
// Safari doesn't support pointer events. | ||
// https://caniuse.com/#feat=pointer | ||
canvas.addEventListener('touchstart', onTouchStart) | ||
function onTouchMove(event) { | ||
const x = event.changedTouches[0].clientX | ||
const y = event.changedTouches[0].clientY | ||
// Emulate 'pointerleave' behavior. | ||
if (x < canvasDimensions.x || | ||
x > canvasDimensions.x + canvasDimensions.width || | ||
y < canvasDimensions.y || | ||
y > canvasDimensions.y + canvasDimensions.height) { | ||
onTrackStop() | ||
} else { | ||
onTrack(x, y) | ||
} | ||
} | ||
function onPointerMove(event) { | ||
onTrack(event.clientX, event.clientY) | ||
} | ||
function onTrackStop() { | ||
canvas.removeEventListener('pointermove', onPointerMove) | ||
canvas.removeEventListener('pointerleave', onTrackStop) | ||
canvas.removeEventListener('pointercancel', onTrackStop) | ||
canvas.removeEventListener('touchmove', onTouchMove) | ||
canvas.removeEventListener('touchend', onTrackStop) | ||
canvas.removeEventListener('touchcancel', onTrackStop) | ||
removeTooltip() | ||
} | ||
function onPointerEnter() { | ||
onTrackStart() | ||
canvas.addEventListener('pointermove', onPointerMove) | ||
canvas.addEventListener('pointerleave', onTrackStop) | ||
canvas.addEventListener('pointercancel', onTrackStop) | ||
} | ||
canvas.addEventListener('pointerenter', onPointerEnter) | ||
return () => { | ||
onTrackStop() | ||
canvas.removeEventListener(onPointerEnter) | ||
canvas.removeEventListener(onTouchStart) | ||
} | ||
} | ||
function addTooltip() { | ||
// Create tooltip. | ||
tooltip = document.createElement('div') | ||
tooltip.classList.add('chartogram__tooltip') | ||
tooltipContainer.appendChild(tooltip) | ||
// Add tooltip title. | ||
tooltipDate = document.createElement('h1') | ||
tooltipDate.classList.add('chartogram__tooltip-header') | ||
tooltip.appendChild(tooltipDate) | ||
// Add graph values. | ||
tooltipValues = document.createElement('dl') | ||
tooltipValues.classList.add('chartogram__tooltip-values') | ||
tooltip.appendChild(tooltipValues) | ||
// Add graph values. | ||
for (const y of data.y) { | ||
if (showGraphs[y.id]) { | ||
// Add graph value. | ||
const tooltipValue = document.createElement('dt') | ||
tooltipValue.style.color = y.color | ||
tooltipValues.appendChild(tooltipValue) | ||
// Add graph name. | ||
const tooltipName = document.createElement('dd') | ||
tooltipName.style.color = y.color | ||
tooltipValues.appendChild(tooltipName) | ||
} | ||
} | ||
} | ||
function removeTooltip() { | ||
if (tooltip) { | ||
tooltipForX = undefined | ||
tooltipContainer.removeChild(tooltip) | ||
tooltip = undefined | ||
removeTooltipPoints() | ||
removeTooltipLine() | ||
} | ||
} | ||
function addTooltipLine() { | ||
const xmlns = 'http://www.w3.org/2000/svg' | ||
tooltipLine = document.createElementNS(xmlns, 'line') | ||
tooltipLine.setAttributeNS(null, 'class', 'chartogram__tooltip-line') | ||
canvas.insertBefore(tooltipLine, canvas.querySelector('polyline')) | ||
} | ||
function removeTooltipLine() { | ||
canvas.removeChild(tooltipLine) | ||
tooltipLine = undefined | ||
} | ||
function addTooltipPoints() { | ||
tooltipPoints = [] | ||
let i = 0 | ||
while (i < timelineWindowGraphs.length) { | ||
const point = document.createElement('div') | ||
point.classList.add('chartogram__tooltip-point') | ||
point.style.color = timelineWindowGraphs[i].color | ||
tooltipPoints.push(point) | ||
canvasWrapper.appendChild(point) | ||
i++ | ||
} | ||
} | ||
function removeTooltipPoints() { | ||
for (const point of tooltipPoints) { | ||
canvasWrapper.removeChild(point) | ||
} | ||
tooltipPoints = undefined | ||
} | ||
function updateTooltipLine(x) { | ||
if (!tooltipLine) { | ||
addTooltipLine() | ||
} | ||
tooltipLine.setAttributeNS(null, 'x1', fixSvgCoordinate(x)) | ||
tooltipLine.setAttributeNS(null, 'x2', fixSvgCoordinate(x)) | ||
tooltipLine.setAttributeNS(null, 'y1', fixSvgCoordinate(timelineWindowMinY)) | ||
tooltipLine.setAttributeNS(null, 'y2', fixSvgCoordinate(timelineWindowMaxY)) | ||
} | ||
function updateTooltipPoints(xIndex, xRatio) { | ||
if (!tooltipPoints) { | ||
addTooltipPoints() | ||
} | ||
let i = 0 | ||
while (i < tooltipPoints.length) { | ||
const point = tooltipPoints[i] | ||
point.style.left = `${xRatio * 100}%` | ||
const y = timelineWindowGraphs[i].normalized.points[xIndex] | ||
const yRatio = y / timelineWindowMaxY | ||
point.style.bottom = `${yRatio * 100}%` | ||
i++ | ||
} | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1004330
28
3407
190
2
1