diff --git a/Resources/public/heatmap.js b/Resources/public/heatmap.js index 7241bb7..e1e2f8b 100644 --- a/Resources/public/heatmap.js +++ b/Resources/public/heatmap.js @@ -1 +1,2413 @@ -"use strict";var KimaiHeatmap=(()=>{var Wt=Object.defineProperty;var tn=Object.getOwnPropertyDescriptor;var en=Object.getOwnPropertyNames;var rn=Object.prototype.hasOwnProperty;var nn=(t,e)=>{for(var r in e)Wt(t,r,{get:e[r],enumerable:!0})},on=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of en(e))!rn.call(t,o)&&o!==r&&Wt(t,o,{get:()=>e[o],enumerable:!(n=tn(e,o))||n.enumerable});return t};var an=t=>on(Wt({},"__esModule",{value:!0}),t);var za={};nn(za,{calculateStats:()=>$r,calculateStreak:()=>zr,init:()=>Ra,renderHeatmap:()=>Vr});var St="http://www.w3.org/1999/xhtml",Pt={svg:"http://www.w3.org/2000/svg",xhtml:St,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function Tt(t){var e=t+="",r=e.indexOf(":");return r>=0&&(e=t.slice(0,r))!=="xmlns"&&(t=t.slice(r+1)),Pt.hasOwnProperty(e)?{space:Pt[e],local:t}:t}function un(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===St&&e.documentElement.namespaceURI===St?e.createElement(t):e.createElementNS(r,t)}}function sn(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Dt(t){var e=Tt(t);return(e.local?sn:un)(e)}function ln(){}function Ct(t){return t==null?ln:function(){return this.querySelector(t)}}function ce(t){typeof t!="function"&&(t=Ct(t));for(var e=this._groups,r=e.length,n=new Array(r),o=0;o=N&&(N=b+1);!(E=U[N])&&++N=0;)(i=n[o])&&(a&&i.compareDocumentPosition(a)^4&&a.parentNode.insertBefore(i,a),a=i);return this}function De(t){t||(t=_n);function e(p,w){return p&&w?t(p.__data__,w.__data__):!p-!w}for(var r=this._groups,n=r.length,o=new Array(n),a=0;ae?1:t>=e?0:NaN}function Ce(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function be(){return Array.from(this)}function Ue(){for(var t=this._groups,e=0,r=t.length;e1?this.each((e==null?kn:typeof e=="function"?Fn:An)(t,e,r??"")):Ln(this.node(),t)}function Ln(t,e){return t.style.getPropertyValue(e)||kt(t).getComputedStyle(t,null).getPropertyValue(e)}function Nn(t){return function(){delete this[t]}}function Hn(t,e){return function(){this[t]=e}}function Yn(t,e){return function(){var r=e.apply(this,arguments);r==null?delete this[t]:this[t]=r}}function He(t,e){return arguments.length>1?this.each((e==null?Nn:typeof e=="function"?Yn:Hn)(t,e)):this.node()[t]}function Ye(t){return t.trim().split(/^|\s+/)}function Ot(t){return t.classList||new Ee(t)}function Ee(t){this._node=t,this._names=Ye(t.getAttribute("class")||"")}Ee.prototype={add:function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function We(t,e){for(var r=Ot(t),n=-1,o=e.length;++n=0&&(r=e.slice(n+1),e=e.slice(0,n)),{type:e,name:r}})}function jn(t){return function(){var e=this.__on;if(e){for(var r=0,n=-1,o=e.length,a;re?1:t>=e?0:NaN}function zt(t,e){return t==null||e==null?NaN:et?1:e>=t?0:NaN}function At(t){let e,r,n;t.length!==2?(e=ot,r=(u,l)=>ot(t(u),l),n=(u,l)=>t(u)-l):(e=t===ot||t===zt?t:ao,r=t,n=t);function o(u,l,s=0,m=u.length){if(s>>1;r(u[p],l)<0?s=p+1:m=p}while(s>>1;r(u[p],l)<=0?s=p+1:m=p}while(ss&&n(u[p-1],l)>-n(u[p],l)?p-1:p}return{left:o,center:i,right:a}}function ao(){return 0}function $t(t){return t===null?NaN:+t}var je=At(ot),tr=je.right,io=je.left,uo=At($t).center,Vt=tr;var so=Math.sqrt(50),lo=Math.sqrt(10),fo=Math.sqrt(2);function Ft(t,e,r){let n=(e-t)/Math.max(0,r),o=Math.floor(Math.log10(n)),a=n/Math.pow(10,o),i=a>=so?10:a>=lo?5:a>=fo?2:1,u,l,s;return o<0?(s=Math.pow(10,-o)/i,u=Math.round(t*s),l=Math.round(e*s),u/se&&--l,s=-s):(s=Math.pow(10,o)*i,u=Math.round(t/s),l=Math.round(e/s),u*se&&--l),l0))return[];if(t===e)return[t];let n=e=o))return[];let u=a-o+1,l=new Array(u);if(n)if(i<0)for(let s=0;s=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r=o)&&(r=o)}return r}function er(t,e){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(e).domain(t);break}return this}function rr(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function at(t,e){if(!isFinite(t)||t===0)return null;var r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"),n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function q(t){return t=at(Math.abs(t)),t?t[1]:NaN}function nr(t,e){return function(r,n){for(var o=r.length,a=[],i=0,u=t[0],l=0;o>0&&u>0&&(l+u+1>n&&(u=Math.max(1,n-l)),a.push(r.substring(o-=u,o+u)),!((l+=u+1)>n));)u=t[i=(i+1)%t.length];return a.reverse().join(e)}}function or(t){return function(e){return e.replace(/[0-9]/g,function(r){return t[+r]})}}var co=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function G(t){if(!(e=co.exec(t)))throw new Error("invalid format: "+t);var e;return new Nt({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}G.prototype=Nt.prototype;function Nt(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}Nt.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function ar(t){t:for(var e=t.length,r=1,n=-1,o;r0&&(n=0);break}return n>0?t.slice(0,n)+t.slice(o+1):t}var dt;function ir(t,e){var r=at(t,e);if(!r)return dt=void 0,t.toPrecision(e);var n=r[0],o=r[1],a=o-(dt=Math.max(-8,Math.min(8,Math.floor(o/3)))*3)+1,i=n.length;return a===i?n:a>i?n+new Array(a-i+1).join("0"):a>0?n.slice(0,a)+"."+n.slice(a):"0."+new Array(1-a).join("0")+at(t,Math.max(0,e+a-1))[0]}function Zt(t,e){var r=at(t,e);if(!r)return t+"";var n=r[0],o=r[1];return o<0?"0."+new Array(-o).join("0")+n:n.length>o+1?n.slice(0,o+1)+"."+n.slice(o+1):n+new Array(o-n.length+2).join("0")}var Qt={"%":(t,e)=>(t*100).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:rr,e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>Zt(t*100,e),r:Zt,s:ir,X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function Xt(t){return t}var ur=Array.prototype.map,sr=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];function lr(t){var e=t.grouping===void 0||t.thousands===void 0?Xt:nr(ur.call(t.grouping,Number),t.thousands+""),r=t.currency===void 0?"":t.currency[0]+"",n=t.currency===void 0?"":t.currency[1]+"",o=t.decimal===void 0?".":t.decimal+"",a=t.numerals===void 0?Xt:or(ur.call(t.numerals,String)),i=t.percent===void 0?"%":t.percent+"",u=t.minus===void 0?"\u2212":t.minus+"",l=t.nan===void 0?"NaN":t.nan+"";function s(p,w){p=G(p);var h=p.fill,x=p.align,S=p.sign,U=p.symbol,L=p.zero,b=p.width,N=p.comma,k=p.precision,E=p.trim,_=p.type;_==="n"?(N=!0,_="g"):Qt[_]||(k===void 0&&(k=12),E=!0,_="g"),(L||h==="0"&&x==="=")&&(L=!0,h="0",x="=");var tt=(w&&w.prefix!==void 0?w.prefix:"")+(U==="$"?r:U==="#"&&/[boxX]/.test(_)?"0"+_.toLowerCase():""),Z=(U==="$"?n:/[%p]/.test(_)?i:"")+(w&&w.suffix!==void 0?w.suffix:""),R=Qt[_],et=/[defgprs%]/.test(_);k=k===void 0?6:/[gprs]/.test(_)?Math.max(1,Math.min(21,k)):Math.max(0,Math.min(20,k));function rt(y){var I=tt,D=Z,d,A,Q;if(_==="c")D=R(y)+D,y="";else{y=+y;var X=y<0||1/y<0;if(y=isNaN(y)?l:R(Math.abs(y),k),E&&(y=ar(y)),X&&+y==0&&S!=="+"&&(X=!1),I=(X?S==="("?S:u:S==="-"||S==="("?"":S)+I,D=(_==="s"&&!isNaN(y)&&dt!==void 0?sr[8+dt/3]:"")+D+(X&&S==="("?")":""),et){for(d=-1,A=y.length;++dQ||Q>57){D=(Q===46?o+y.slice(d+1):y.slice(d))+D,y=y.slice(0,d);break}}}N&&!L&&(y=e(y,1/0));var z=I.length+y.length+D.length,O=z>1)+I+y+D+O.slice(z);break;default:y=O+I+y+D;break}return a(y)}return rt.toString=function(){return p+""},rt}function m(p,w){var h=Math.max(-8,Math.min(8,Math.floor(q(w)/3)))*3,x=Math.pow(10,-h),S=s((p=G(p),p.type="f",p),{suffix:sr[8+h/3]});return function(U){return S(x*U)}}return{format:s,formatPrefix:m}}var Ht,Yt,Et;Gt({thousands:",",grouping:[3],currency:["$",""]});function Gt(t){return Ht=lr(t),Yt=Ht.format,Et=Ht.formatPrefix,Ht}function Jt(t){return Math.max(0,-q(Math.abs(t)))}function Kt(t,e){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(q(e)/3)))*3-q(Math.abs(t)))}function jt(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,q(e)-q(t))+1}function te(t,e,r,n){var o=Bt(t,e,r),a;switch(n=G(n??",f"),n.type){case"s":{var i=Math.max(Math.abs(t),Math.abs(e));return n.precision==null&&!isNaN(a=Kt(o,i))&&(n.precision=a),Et(n,i)}case"":case"e":case"g":case"p":case"r":{n.precision==null&&!isNaN(a=jt(o,Math.max(Math.abs(t),Math.abs(e))))&&(n.precision=a-(n.type==="e"));break}case"f":case"%":{n.precision==null&&!isNaN(a=Jt(o))&&(n.precision=a-(n.type==="%")*2);break}}return Yt(n)}function fr(t){var e=t.domain;return t.ticks=function(r){var n=e();return Lt(n[0],n[n.length-1],r??10)},t.tickFormat=function(r,n){var o=e();return te(o[0],o[o.length-1],r??10,n)},t.nice=function(r){r==null&&(r=10);var n=e(),o=0,a=n.length-1,i=n[o],u=n[a],l,s,m=10;for(u0;){if(s=pt(i,u,r),s===l)return n[o]=i,n[a]=u,e(n);if(s>0)i=Math.floor(i/s)*s,u=Math.ceil(u/s)*s;else if(s<0)i=Math.ceil(i*s)/s,u=Math.floor(u*s)/s;else break;l=s}return t},t}function yt(){var t=0,e=1,r=1,n=[.5],o=[0,1],a;function i(l){return l!=null&&l<=l?o[Vt(n,l,0,r)]:a}function u(){var l=-1;for(n=new Array(r);++l=r?[n[r-1],e]:[n[s-1],n[s]]},i.unknown=function(l){return arguments.length&&(a=l),i},i.thresholds=function(){return n.slice()},i.copy=function(){return yt().domain([t,e]).range(o).unknown(a)},er.apply(fr(i),arguments)}var ee=new Date,re=new Date;function H(t,e,r,n){function o(a){return t(a=arguments.length===0?new Date:new Date(+a)),a}return o.floor=a=>(t(a=new Date(+a)),a),o.ceil=a=>(t(a=new Date(a-1)),e(a,1),t(a),a),o.round=a=>{let i=o(a),u=o.ceil(a);return a-i(e(a=new Date(+a),i==null?1:Math.floor(i)),a),o.range=(a,i,u)=>{let l=[];if(a=o.ceil(a),u=u==null?1:Math.floor(u),!(a0))return l;let s;do l.push(s=new Date(+a)),e(a,u),t(a);while(sH(i=>{if(i>=i)for(;t(i),!a(i);)i.setTime(i-1)},(i,u)=>{if(i>=i)if(u<0)for(;++u<=0;)for(;e(i,-1),!a(i););else for(;--u>=0;)for(;e(i,1),!a(i););}),r&&(o.count=(a,i)=>(ee.setTime(+a),re.setTime(+i),t(ee),t(re),Math.floor(r(ee,re))),o.every=a=>(a=Math.floor(a),!isFinite(a)||!(a>0)?null:a>1?o.filter(n?i=>n(i)%a===0:i=>o.count(0,i)%a===0):o)),o}var $=H(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5,t=>t.getDate()-1),po=$.range,gt=H(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>t.getUTCDate()-1),ho=gt.range,cr=H(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>Math.floor(t/864e5)),yo=cr.range;function it(t){return H(e=>{e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},(e,r)=>{e.setDate(e.getDate()+r*7)},(e,r)=>(r-e-(r.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/6048e5)}var ut=it(0),J=it(1),mr=it(2),pr=it(3),K=it(4),hr=it(5),dr=it(6),yr=ut.range,xo=J.range,vo=mr.range,Mo=pr.range,wo=K.range,_o=hr.range,So=dr.range;function st(t){return H(e=>{e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCDate(e.getUTCDate()+r*7)},(e,r)=>(r-e)/6048e5)}var xt=st(0),lt=st(1),gr=st(2),xr=st(3),j=st(4),vr=st(5),Mr=st(6),wr=xt.range,To=lt.range,Do=gr.range,Co=xr.range,bo=j.range,Uo=vr.range,ko=Mr.range;var vt=H(t=>{t.setDate(1),t.setHours(0,0,0,0)},(t,e)=>{t.setMonth(t.getMonth()+e)},(t,e)=>e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12,t=>t.getMonth()),Ao=vt.range,_r=H(t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCMonth(t.getUTCMonth()+e)},(t,e)=>e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12,t=>t.getUTCMonth()),Fo=_r.range;var V=H(t=>{t.setMonth(0,1),t.setHours(0,0,0,0)},(t,e)=>{t.setFullYear(t.getFullYear()+e)},(t,e)=>e.getFullYear()-t.getFullYear(),t=>t.getFullYear());V.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:H(e=>{e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},(e,r)=>{e.setFullYear(e.getFullYear()+r*t)});var Lo=V.range,B=H(t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCFullYear(t.getUTCFullYear()+e)},(t,e)=>e.getUTCFullYear()-t.getUTCFullYear(),t=>t.getUTCFullYear());B.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:H(e=>{e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCFullYear(e.getUTCFullYear()+r*t)});var No=B.range;function oe(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function ae(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Mt(t,e,r){return{y:t,m:e,d:r,H:0,M:0,S:0,L:0}}function ie(t){var e=t.dateTime,r=t.date,n=t.time,o=t.periods,a=t.days,i=t.shortDays,u=t.months,l=t.shortMonths,s=wt(o),m=_t(o),p=wt(a),w=_t(a),h=wt(i),x=_t(i),S=wt(u),U=_t(u),L=wt(l),b=_t(l),N={a:Q,A:X,b:z,B:O,c:null,d:Ur,e:Ur,f:ra,g:ma,G:ha,H:jo,I:ta,j:ea,L:Nr,m:na,M:oa,p:Br,q:Zr,Q:Fr,s:Lr,S:aa,u:ia,U:ua,V:sa,w:la,W:fa,x:null,X:null,y:ca,Y:pa,Z:da,"%":Ar},k={a:Qr,A:Xr,b:Gr,B:Jr,c:null,d:kr,e:kr,f:va,g:ka,G:Fa,H:ya,I:ga,j:xa,L:Yr,m:Ma,M:wa,p:Kr,q:jr,Q:Fr,s:Lr,S:_a,u:Sa,U:Ta,V:Da,w:Ca,W:ba,x:null,X:null,y:Ua,Y:Aa,Z:La,"%":Ar},E={a:et,A:rt,b:y,B:I,c:D,d:Cr,e:Cr,f:Xo,g:Dr,G:Tr,H:br,I:br,j:Vo,L:Qo,m:$o,M:Bo,p:R,q:zo,Q:Jo,s:Ko,S:Zo,u:Po,U:Io,V:Oo,w:Wo,W:qo,x:d,X:A,y:Dr,Y:Tr,Z:Ro,"%":Go};N.x=_(r,N),N.X=_(n,N),N.c=_(e,N),k.x=_(r,k),k.X=_(n,k),k.c=_(e,k);function _(c,g){return function(v){var f=[],Y=-1,T=0,W=c.length,P,nt,fe;for(v instanceof Date||(v=new Date(+v));++Y53)return null;"w"in f||(f.w=1),"Z"in f?(T=ae(Mt(f.y,0,1)),W=T.getUTCDay(),T=W>4||W===0?lt.ceil(T):lt(T),T=gt.offset(T,(f.V-1)*7),f.y=T.getUTCFullYear(),f.m=T.getUTCMonth(),f.d=T.getUTCDate()+(f.w+6)%7):(T=oe(Mt(f.y,0,1)),W=T.getDay(),T=W>4||W===0?J.ceil(T):J(T),T=$.offset(T,(f.V-1)*7),f.y=T.getFullYear(),f.m=T.getMonth(),f.d=T.getDate()+(f.w+6)%7)}else("W"in f||"U"in f)&&("w"in f||(f.w="u"in f?f.u%7:"W"in f?1:0),W="Z"in f?ae(Mt(f.y,0,1)).getUTCDay():oe(Mt(f.y,0,1)).getDay(),f.m=0,f.d="W"in f?(f.w+6)%7+f.W*7-(W+5)%7:f.w+f.U*7-(W+6)%7);return"Z"in f?(f.H+=f.Z/100|0,f.M+=f.Z%100,ae(f)):oe(f)}}function Z(c,g,v,f){for(var Y=0,T=g.length,W=v.length,P,nt;Y=W)return-1;if(P=g.charCodeAt(Y++),P===37){if(P=g.charAt(Y++),nt=E[P in Sr?g.charAt(Y++):P],!nt||(f=nt(c,v,f))<0)return-1}else if(P!=v.charCodeAt(f++))return-1}return f}function R(c,g,v){var f=s.exec(g.slice(v));return f?(c.p=m.get(f[0].toLowerCase()),v+f[0].length):-1}function et(c,g,v){var f=h.exec(g.slice(v));return f?(c.w=x.get(f[0].toLowerCase()),v+f[0].length):-1}function rt(c,g,v){var f=p.exec(g.slice(v));return f?(c.w=w.get(f[0].toLowerCase()),v+f[0].length):-1}function y(c,g,v){var f=L.exec(g.slice(v));return f?(c.m=b.get(f[0].toLowerCase()),v+f[0].length):-1}function I(c,g,v){var f=S.exec(g.slice(v));return f?(c.m=U.get(f[0].toLowerCase()),v+f[0].length):-1}function D(c,g,v){return Z(c,e,g,v)}function d(c,g,v){return Z(c,r,g,v)}function A(c,g,v){return Z(c,n,g,v)}function Q(c){return i[c.getDay()]}function X(c){return a[c.getDay()]}function z(c){return l[c.getMonth()]}function O(c){return u[c.getMonth()]}function Br(c){return o[+(c.getHours()>=12)]}function Zr(c){return 1+~~(c.getMonth()/3)}function Qr(c){return i[c.getUTCDay()]}function Xr(c){return a[c.getUTCDay()]}function Gr(c){return l[c.getUTCMonth()]}function Jr(c){return u[c.getUTCMonth()]}function Kr(c){return o[+(c.getUTCHours()>=12)]}function jr(c){return 1+~~(c.getUTCMonth()/3)}return{format:function(c){var g=_(c+="",N);return g.toString=function(){return c},g},parse:function(c){var g=tt(c+="",!1);return g.toString=function(){return c},g},utcFormat:function(c){var g=_(c+="",k);return g.toString=function(){return c},g},utcParse:function(c){var g=tt(c+="",!0);return g.toString=function(){return c},g}}}var Sr={"-":"",_:" ",0:"0"},F=/^\s*\d+/,Ho=/^%/,Yo=/[\\^$*+?|[\]().{}]/g;function M(t,e,r){var n=t<0?"-":"",o=(n?-t:t)+"",a=o.length;return n+(a[e.toLowerCase(),r]))}function Wo(t,e,r){var n=F.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function Po(t,e,r){var n=F.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function Io(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function Oo(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function qo(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function Tr(t,e,r){var n=F.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function Dr(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function Ro(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function zo(t,e,r){var n=F.exec(e.slice(r,r+1));return n?(t.q=n[0]*3-3,r+n[0].length):-1}function $o(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function Cr(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function Vo(t,e,r){var n=F.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function br(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function Bo(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function Zo(t,e,r){var n=F.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function Qo(t,e,r){var n=F.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function Xo(t,e,r){var n=F.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function Go(t,e,r){var n=Ho.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function Jo(t,e,r){var n=F.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function Ko(t,e,r){var n=F.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function Ur(t,e){return M(t.getDate(),e,2)}function jo(t,e){return M(t.getHours(),e,2)}function ta(t,e){return M(t.getHours()%12||12,e,2)}function ea(t,e){return M(1+$.count(V(t),t),e,3)}function Nr(t,e){return M(t.getMilliseconds(),e,3)}function ra(t,e){return Nr(t,e)+"000"}function na(t,e){return M(t.getMonth()+1,e,2)}function oa(t,e){return M(t.getMinutes(),e,2)}function aa(t,e){return M(t.getSeconds(),e,2)}function ia(t){var e=t.getDay();return e===0?7:e}function ua(t,e){return M(ut.count(V(t)-1,t),e,2)}function Hr(t){var e=t.getDay();return e>=4||e===0?K(t):K.ceil(t)}function sa(t,e){return t=Hr(t),M(K.count(V(t),t)+(V(t).getDay()===4),e,2)}function la(t){return t.getDay()}function fa(t,e){return M(J.count(V(t)-1,t),e,2)}function ca(t,e){return M(t.getFullYear()%100,e,2)}function ma(t,e){return t=Hr(t),M(t.getFullYear()%100,e,2)}function pa(t,e){return M(t.getFullYear()%1e4,e,4)}function ha(t,e){var r=t.getDay();return t=r>=4||r===0?K(t):K.ceil(t),M(t.getFullYear()%1e4,e,4)}function da(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+M(e/60|0,"0",2)+M(e%60,"0",2)}function kr(t,e){return M(t.getUTCDate(),e,2)}function ya(t,e){return M(t.getUTCHours(),e,2)}function ga(t,e){return M(t.getUTCHours()%12||12,e,2)}function xa(t,e){return M(1+gt.count(B(t),t),e,3)}function Yr(t,e){return M(t.getUTCMilliseconds(),e,3)}function va(t,e){return Yr(t,e)+"000"}function Ma(t,e){return M(t.getUTCMonth()+1,e,2)}function wa(t,e){return M(t.getUTCMinutes(),e,2)}function _a(t,e){return M(t.getUTCSeconds(),e,2)}function Sa(t){var e=t.getUTCDay();return e===0?7:e}function Ta(t,e){return M(xt.count(B(t)-1,t),e,2)}function Er(t){var e=t.getUTCDay();return e>=4||e===0?j(t):j.ceil(t)}function Da(t,e){return t=Er(t),M(j.count(B(t),t)+(B(t).getUTCDay()===4),e,2)}function Ca(t){return t.getUTCDay()}function ba(t,e){return M(lt.count(B(t)-1,t),e,2)}function Ua(t,e){return M(t.getUTCFullYear()%100,e,2)}function ka(t,e){return t=Er(t),M(t.getUTCFullYear()%100,e,2)}function Aa(t,e){return M(t.getUTCFullYear()%1e4,e,4)}function Fa(t,e){var r=t.getUTCDay();return t=r>=4||r===0?j(t):j.ceil(t),M(t.getUTCFullYear()%1e4,e,4)}function La(){return"+0000"}function Ar(){return"%"}function Fr(t){return+t}function Lr(t){return Math.floor(+t/1e3)}var ft,ct,Wr,Pr,Ir;ue({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function ue(t){return ft=ie(t),ct=ft.format,Wr=ft.parse,Pr=ft.utcFormat,Ir=ft.utcParse,ft}var Or={cellSize:13,cellGap:2,marginTop:20,marginLeft:30,marginBottom:4},se=["#9be9a8","#40c463","#30a14e","#216e39"],Na=["Mon","","Wed","","Fri","",""],Ha=["Sun","","Tue","","Thu","","Sat"];function Ya(t){return t==="sunday"?Ha:Na}var Ea=ct("%b"),le=ct("%Y-%m-%d"),qr=ct("%a, %b %-d, %Y");function Wa(t){try{return getComputedStyle(t).getPropertyValue("--tblr-bg-surface"),se}catch{return se}}function Pa(t){let e=new Map;for(let r of t)e.set(r.date,r);return e}function Rr(t){return t==="sunday"?ut:J}function Ia(t,e,r,n="monday"){let o=Rr(n),a=o.floor(t),i=[],u=new Date(t);for(;u<=e;){let l=le(u),s=o.count(a,u),m=u.getDay(),p=n==="sunday"?m:(m+6)%7,w=m===0||m===6;i.push({date:new Date(u),dateStr:l,entry:r.get(l)||null,week:s,day:p,isWeekend:w}),u=$.offset(u,1)}return i}function Oa(){let t=document.createElement("div");return t.className="heatmap-tooltip",t.style.display="none",t}function zr(t){if(t.length===0)return 0;let e=new Set(t.filter(a=>a.hours>0).map(a=>a.date));if(e.size===0)return 0;let r=new Date;r.setHours(0,0,0,0);let n=new Date(r);e.has(le(n))||(n=$.offset(n,-1));let o=0;for(;e.has(le(n));)o++,n=$.offset(n,-1);return o}function $r(t){let e=t.filter(a=>a.hours>0);if(e.length===0)return{totalHours:0,avgHours:0,busiestDay:null};let r=Math.round(e.reduce((a,i)=>a+i.hours,0)*10)/10,n=Math.round(r/e.length*10)/10,o=e.reduce((a,i)=>i.hours>a.hours?i:a);return{totalHours:r,avgHours:n,busiestDay:{date:o.date,hours:o.hours}}}function qa(t,e){let r=t.querySelector(".heatmap-stats");r&&r.remove();let n=zr(e),o=$r(e),a=document.createElement("div");a.className="heatmap-stats";let i=[];if(i.push(`\u{1F525} ${n} days`),i.push(`Total: ${o.totalHours}h`),i.push(`Avg: ${o.avgHours}h/day`),o.busiestDay){let u=new Date(o.busiestDay.date+"T00:00:00"),l=qr(u);i.push(`Busiest: ${l} \u2014 ${o.busiestDay.hours.toFixed(1)}h`)}a.innerHTML=i.join(""),t.appendChild(a)}function Vr(t,e,r=Or,n,o,a="monday"){if(t.innerHTML="",!e.days||e.days.length===0){let d=document.createElement("div");d.textContent=o||"No tracking data available",d.style.padding="1rem",d.style.color="var(--tblr-secondary, #6c757d)",t.appendChild(d);return}let i=Pa(e.days),u=new Date(e.range.begin),l=new Date(e.range.end),s=Ia(u,l,i,a),m=ht(e.days,d=>d.hours)||1,p=Wa(t),w=yt().domain([0,m]).range(p),{cellGap:h,marginTop:x,marginLeft:S,marginBottom:U}=r,L=(ht(s,d=>d.week)??0)+1,b=t.clientWidth||800,E=Math.min(22,Math.max(10,Math.floor((b-S)/L)-h)),_=E+h,tt=S+L*_,Z=x+7*_+U,R=document.createElement("div");R.style.maxWidth=`${tt}px`,R.style.margin="0 auto",t.appendChild(R);let et=Rt(R).append("svg").attr("width",tt).attr("height",Z).attr("class","heatmap-svg"),rt=Rr(a),y=[],I=rt.floor(u);vt.range(vt.ceil(u),l).forEach(d=>{y.push({date:d,week:rt.count(I,d)})}),et.selectAll(".month-label").data(y).join("text").attr("class","heatmap-label month-label").attr("x",d=>S+d.week*_).attr("y",x-6).text(d=>Ea(d.date)),et.selectAll(".day-label").data(Ya(a)).join("text").attr("class","heatmap-label day-label").attr("x",S-6).attr("y",(d,A)=>x+A*_+E-2).attr("text-anchor","end").text(d=>d),document.querySelectorAll(".heatmap-tooltip").forEach(d=>d.remove());let D=Oa();D.style.position="fixed",document.body.appendChild(D),et.selectAll(".heatmap-cell").data(s).join("rect").attr("class",d=>{let A="heatmap-cell";return d.entry||(A+=" heatmap-empty"),d.isWeekend&&(A+=" heatmap-weekend"),A}).attr("x",d=>S+d.week*_).attr("y",d=>x+d.day*_).attr("width",E).attr("height",E).attr("fill",d=>d.entry?w(d.entry.hours):"").on("mouseenter",function(d,A){let Q=A.entry?A.entry.hours.toFixed(1):"0.0",X=A.entry?A.entry.count:0;D.innerHTML=`${qr(A.date)}
${Q}h (${X} entries)`,D.style.display="block";let z=d.target.getBoundingClientRect();D.style.left=`${z.left+E/2}px`,D.style.top=`${z.top-D.offsetHeight-8}px`}).on("mouseleave",function(){D.style.display="none"}).on("click",function(d,A){n&&n(A.dateStr)})}function Ra(t){let e=t.getAttribute("data-url");if(!e){console.error("KimaiHeatmap: missing data-url attribute");return}let r=t.getAttribute("data-timesheet-url")||"/en/timesheet/",n=t.getAttribute("data-week-start")||"monday",o=t.getAttribute("data-projects"),a=o?JSON.parse(o):[],i=null,u=h=>{let x=`${h} - ${h}`,S=`${r}?daterange=${encodeURIComponent(x)}`;i&&(S+=`&projects[]=${i}`),window.location.href=S};t.innerHTML="";let l=document.createElement("div");l.className="heatmap-wrapper";let s=document.createElement("div");s.className="heatmap-svg-area",l.appendChild(s);let m=null,p=(h,x)=>{m=h,Vr(s,h,Or,u,x,n),qa(t,h.days),s.scrollLeft=s.scrollWidth};if(a.length>0){let h=document.createElement("div");h.className="heatmap-filter";let x=document.createElement("select");x.className="form-select form-select-sm",x.setAttribute("aria-label","Filter by project");let S=document.createElement("option");S.value="",S.textContent="All Projects",x.appendChild(S);for(let U of a){let L=document.createElement("option");L.value=String(U.id),L.textContent=U.name,x.appendChild(L)}x.addEventListener("change",()=>{let U=x.value;i=U?parseInt(U,10):null;let L=U?`${e}?project=${U}`:e;fetch(L).then(b=>{if(!b.ok)throw new Error(`HTTP ${b.status}`);return b.json()}).then(b=>{p(b,"No tracking data for this project")}).catch(b=>{console.error("KimaiHeatmap: failed to load filtered data",b)})}),h.appendChild(x),l.appendChild(h)}t.appendChild(l);let w;window.addEventListener("resize",()=>{clearTimeout(w),w=setTimeout(()=>{m&&p(m)},200)}),fetch(e).then(h=>{if(!h.ok)throw new Error(`HTTP ${h.status}`);return h.json()}).then(h=>{p(h)}).catch(h=>{console.error("KimaiHeatmap: failed to load data",h),s.textContent="Failed to load heatmap data"})}return an(za);})(); +"use strict"; +var KimaiHeatmap = (() => { + var __defProp = Object.defineProperty; + var __getOwnPropDesc = Object.getOwnPropertyDescriptor; + var __getOwnPropNames = Object.getOwnPropertyNames; + var __hasOwnProp = Object.prototype.hasOwnProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + + // assets/src/heatmap.ts + var heatmap_exports = {}; + __export(heatmap_exports, { + init: () => init + }); + + // assets/src/state.ts + function createInitialState(weekStart) { + return { + mode: "year", + metric: "hours", + filters: { projectId: null, customerId: null, activityId: null }, + weekStart, + data: null + }; + } + + // assets/src/renderers/registry.ts + var renderers = /* @__PURE__ */ new Map(); + function registerRenderer(renderer) { + renderers.set(renderer.mode, renderer); + } + function getRenderer(mode) { + const r = renderers.get(mode); + if (!r) throw new Error(`Unknown heatmap mode: ${mode}`); + return r; + } + + // ../../../node_modules/d3-selection/src/namespaces.js + var xhtml = "http://www.w3.org/1999/xhtml"; + var namespaces_default = { + svg: "http://www.w3.org/2000/svg", + xhtml, + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + + // ../../../node_modules/d3-selection/src/namespace.js + function namespace_default(name) { + var prefix = name += "", i = prefix.indexOf(":"); + if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); + return namespaces_default.hasOwnProperty(prefix) ? { space: namespaces_default[prefix], local: name } : name; + } + + // ../../../node_modules/d3-selection/src/creator.js + function creatorInherit(name) { + return function() { + var document2 = this.ownerDocument, uri = this.namespaceURI; + return uri === xhtml && document2.documentElement.namespaceURI === xhtml ? document2.createElement(name) : document2.createElementNS(uri, name); + }; + } + function creatorFixed(fullname) { + return function() { + return this.ownerDocument.createElementNS(fullname.space, fullname.local); + }; + } + function creator_default(name) { + var fullname = namespace_default(name); + return (fullname.local ? creatorFixed : creatorInherit)(fullname); + } + + // ../../../node_modules/d3-selection/src/selector.js + function none() { + } + function selector_default(selector) { + return selector == null ? none : function() { + return this.querySelector(selector); + }; + } + + // ../../../node_modules/d3-selection/src/selection/select.js + function select_default(select) { + if (typeof select !== "function") select = selector_default(select); + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { + if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + subgroup[i] = subnode; + } + } + } + return new Selection(subgroups, this._parents); + } + + // ../../../node_modules/d3-selection/src/array.js + function array(x) { + return x == null ? [] : Array.isArray(x) ? x : Array.from(x); + } + + // ../../../node_modules/d3-selection/src/selectorAll.js + function empty() { + return []; + } + function selectorAll_default(selector) { + return selector == null ? empty : function() { + return this.querySelectorAll(selector); + }; + } + + // ../../../node_modules/d3-selection/src/selection/selectAll.js + function arrayAll(select) { + return function() { + return array(select.apply(this, arguments)); + }; + } + function selectAll_default(select) { + if (typeof select === "function") select = arrayAll(select); + else select = selectorAll_default(select); + for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { + if (node = group[i]) { + subgroups.push(select.call(node, node.__data__, i, group)); + parents.push(node); + } + } + } + return new Selection(subgroups, parents); + } + + // ../../../node_modules/d3-selection/src/matcher.js + function matcher_default(selector) { + return function() { + return this.matches(selector); + }; + } + function childMatcher(selector) { + return function(node) { + return node.matches(selector); + }; + } + + // ../../../node_modules/d3-selection/src/selection/selectChild.js + var find = Array.prototype.find; + function childFind(match) { + return function() { + return find.call(this.children, match); + }; + } + function childFirst() { + return this.firstElementChild; + } + function selectChild_default(match) { + return this.select(match == null ? childFirst : childFind(typeof match === "function" ? match : childMatcher(match))); + } + + // ../../../node_modules/d3-selection/src/selection/selectChildren.js + var filter = Array.prototype.filter; + function children() { + return Array.from(this.children); + } + function childrenFilter(match) { + return function() { + return filter.call(this.children, match); + }; + } + function selectChildren_default(match) { + return this.selectAll(match == null ? children : childrenFilter(typeof match === "function" ? match : childMatcher(match))); + } + + // ../../../node_modules/d3-selection/src/selection/filter.js + function filter_default(match) { + if (typeof match !== "function") match = matcher_default(match); + for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { + if ((node = group[i]) && match.call(node, node.__data__, i, group)) { + subgroup.push(node); + } + } + } + return new Selection(subgroups, this._parents); + } + + // ../../../node_modules/d3-selection/src/selection/sparse.js + function sparse_default(update) { + return new Array(update.length); + } + + // ../../../node_modules/d3-selection/src/selection/enter.js + function enter_default() { + return new Selection(this._enter || this._groups.map(sparse_default), this._parents); + } + function EnterNode(parent, datum2) { + this.ownerDocument = parent.ownerDocument; + this.namespaceURI = parent.namespaceURI; + this._next = null; + this._parent = parent; + this.__data__ = datum2; + } + EnterNode.prototype = { + constructor: EnterNode, + appendChild: function(child) { + return this._parent.insertBefore(child, this._next); + }, + insertBefore: function(child, next) { + return this._parent.insertBefore(child, next); + }, + querySelector: function(selector) { + return this._parent.querySelector(selector); + }, + querySelectorAll: function(selector) { + return this._parent.querySelectorAll(selector); + } + }; + + // ../../../node_modules/d3-selection/src/constant.js + function constant_default(x) { + return function() { + return x; + }; + } + + // ../../../node_modules/d3-selection/src/selection/data.js + function bindIndex(parent, group, enter, update, exit, data) { + var i = 0, node, groupLength = group.length, dataLength = data.length; + for (; i < dataLength; ++i) { + if (node = group[i]) { + node.__data__ = data[i]; + update[i] = node; + } else { + enter[i] = new EnterNode(parent, data[i]); + } + } + for (; i < groupLength; ++i) { + if (node = group[i]) { + exit[i] = node; + } + } + } + function bindKey(parent, group, enter, update, exit, data, key) { + var i, node, nodeByKeyValue = /* @__PURE__ */ new Map(), groupLength = group.length, dataLength = data.length, keyValues = new Array(groupLength), keyValue; + for (i = 0; i < groupLength; ++i) { + if (node = group[i]) { + keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + ""; + if (nodeByKeyValue.has(keyValue)) { + exit[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + } + } + for (i = 0; i < dataLength; ++i) { + keyValue = key.call(parent, data[i], i, data) + ""; + if (node = nodeByKeyValue.get(keyValue)) { + update[i] = node; + node.__data__ = data[i]; + nodeByKeyValue.delete(keyValue); + } else { + enter[i] = new EnterNode(parent, data[i]); + } + } + for (i = 0; i < groupLength; ++i) { + if ((node = group[i]) && nodeByKeyValue.get(keyValues[i]) === node) { + exit[i] = node; + } + } + } + function datum(node) { + return node.__data__; + } + function data_default(value, key) { + if (!arguments.length) return Array.from(this, datum); + var bind = key ? bindKey : bindIndex, parents = this._parents, groups = this._groups; + if (typeof value !== "function") value = constant_default(value); + for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) { + var parent = parents[j], group = groups[j], groupLength = group.length, data = arraylike(value.call(parent, parent && parent.__data__, j, parents)), dataLength = data.length, enterGroup = enter[j] = new Array(dataLength), updateGroup = update[j] = new Array(dataLength), exitGroup = exit[j] = new Array(groupLength); + bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); + for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) { + if (previous = enterGroup[i0]) { + if (i0 >= i1) i1 = i0 + 1; + while (!(next = updateGroup[i1]) && ++i1 < dataLength) ; + previous._next = next || null; + } + } + } + update = new Selection(update, parents); + update._enter = enter; + update._exit = exit; + return update; + } + function arraylike(data) { + return typeof data === "object" && "length" in data ? data : Array.from(data); + } + + // ../../../node_modules/d3-selection/src/selection/exit.js + function exit_default() { + return new Selection(this._exit || this._groups.map(sparse_default), this._parents); + } + + // ../../../node_modules/d3-selection/src/selection/join.js + function join_default(onenter, onupdate, onexit) { + var enter = this.enter(), update = this, exit = this.exit(); + if (typeof onenter === "function") { + enter = onenter(enter); + if (enter) enter = enter.selection(); + } else { + enter = enter.append(onenter + ""); + } + if (onupdate != null) { + update = onupdate(update); + if (update) update = update.selection(); + } + if (onexit == null) exit.remove(); + else onexit(exit); + return enter && update ? enter.merge(update).order() : update; + } + + // ../../../node_modules/d3-selection/src/selection/merge.js + function merge_default(context) { + var selection2 = context.selection ? context.selection() : context; + for (var groups0 = this._groups, groups1 = selection2._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { + for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { + if (node = group0[i] || group1[i]) { + merge[i] = node; + } + } + } + for (; j < m0; ++j) { + merges[j] = groups0[j]; + } + return new Selection(merges, this._parents); + } + + // ../../../node_modules/d3-selection/src/selection/order.js + function order_default() { + for (var groups = this._groups, j = -1, m = groups.length; ++j < m; ) { + for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next); + next = node; + } + } + } + return this; + } + + // ../../../node_modules/d3-selection/src/selection/sort.js + function sort_default(compare) { + if (!compare) compare = ascending; + function compareNode(a, b) { + return a && b ? compare(a.__data__, b.__data__) : !a - !b; + } + for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) { + for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) { + if (node = group[i]) { + sortgroup[i] = node; + } + } + sortgroup.sort(compareNode); + } + return new Selection(sortgroups, this._parents).order(); + } + function ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + + // ../../../node_modules/d3-selection/src/selection/call.js + function call_default() { + var callback = arguments[0]; + arguments[0] = this; + callback.apply(null, arguments); + return this; + } + + // ../../../node_modules/d3-selection/src/selection/nodes.js + function nodes_default() { + return Array.from(this); + } + + // ../../../node_modules/d3-selection/src/selection/node.js + function node_default() { + for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { + for (var group = groups[j], i = 0, n = group.length; i < n; ++i) { + var node = group[i]; + if (node) return node; + } + } + return null; + } + + // ../../../node_modules/d3-selection/src/selection/size.js + function size_default() { + let size = 0; + for (const node of this) ++size; + return size; + } + + // ../../../node_modules/d3-selection/src/selection/empty.js + function empty_default() { + return !this.node(); + } + + // ../../../node_modules/d3-selection/src/selection/each.js + function each_default(callback) { + for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { + if (node = group[i]) callback.call(node, node.__data__, i, group); + } + } + return this; + } + + // ../../../node_modules/d3-selection/src/selection/attr.js + function attrRemove(name) { + return function() { + this.removeAttribute(name); + }; + } + function attrRemoveNS(fullname) { + return function() { + this.removeAttributeNS(fullname.space, fullname.local); + }; + } + function attrConstant(name, value) { + return function() { + this.setAttribute(name, value); + }; + } + function attrConstantNS(fullname, value) { + return function() { + this.setAttributeNS(fullname.space, fullname.local, value); + }; + } + function attrFunction(name, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.removeAttribute(name); + else this.setAttribute(name, v); + }; + } + function attrFunctionNS(fullname, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.removeAttributeNS(fullname.space, fullname.local); + else this.setAttributeNS(fullname.space, fullname.local, v); + }; + } + function attr_default(name, value) { + var fullname = namespace_default(name); + if (arguments.length < 2) { + var node = this.node(); + return fullname.local ? node.getAttributeNS(fullname.space, fullname.local) : node.getAttribute(fullname); + } + return this.each((value == null ? fullname.local ? attrRemoveNS : attrRemove : typeof value === "function" ? fullname.local ? attrFunctionNS : attrFunction : fullname.local ? attrConstantNS : attrConstant)(fullname, value)); + } + + // ../../../node_modules/d3-selection/src/window.js + function window_default(node) { + return node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView; + } + + // ../../../node_modules/d3-selection/src/selection/style.js + function styleRemove(name) { + return function() { + this.style.removeProperty(name); + }; + } + function styleConstant(name, value, priority) { + return function() { + this.style.setProperty(name, value, priority); + }; + } + function styleFunction(name, value, priority) { + return function() { + var v = value.apply(this, arguments); + if (v == null) this.style.removeProperty(name); + else this.style.setProperty(name, v, priority); + }; + } + function style_default(name, value, priority) { + return arguments.length > 1 ? this.each((value == null ? styleRemove : typeof value === "function" ? styleFunction : styleConstant)(name, value, priority == null ? "" : priority)) : styleValue(this.node(), name); + } + function styleValue(node, name) { + return node.style.getPropertyValue(name) || window_default(node).getComputedStyle(node, null).getPropertyValue(name); + } + + // ../../../node_modules/d3-selection/src/selection/property.js + function propertyRemove(name) { + return function() { + delete this[name]; + }; + } + function propertyConstant(name, value) { + return function() { + this[name] = value; + }; + } + function propertyFunction(name, value) { + return function() { + var v = value.apply(this, arguments); + if (v == null) delete this[name]; + else this[name] = v; + }; + } + function property_default(name, value) { + return arguments.length > 1 ? this.each((value == null ? propertyRemove : typeof value === "function" ? propertyFunction : propertyConstant)(name, value)) : this.node()[name]; + } + + // ../../../node_modules/d3-selection/src/selection/classed.js + function classArray(string) { + return string.trim().split(/^|\s+/); + } + function classList(node) { + return node.classList || new ClassList(node); + } + function ClassList(node) { + this._node = node; + this._names = classArray(node.getAttribute("class") || ""); + } + ClassList.prototype = { + add: function(name) { + var i = this._names.indexOf(name); + if (i < 0) { + this._names.push(name); + this._node.setAttribute("class", this._names.join(" ")); + } + }, + remove: function(name) { + var i = this._names.indexOf(name); + if (i >= 0) { + this._names.splice(i, 1); + this._node.setAttribute("class", this._names.join(" ")); + } + }, + contains: function(name) { + return this._names.indexOf(name) >= 0; + } + }; + function classedAdd(node, names) { + var list = classList(node), i = -1, n = names.length; + while (++i < n) list.add(names[i]); + } + function classedRemove(node, names) { + var list = classList(node), i = -1, n = names.length; + while (++i < n) list.remove(names[i]); + } + function classedTrue(names) { + return function() { + classedAdd(this, names); + }; + } + function classedFalse(names) { + return function() { + classedRemove(this, names); + }; + } + function classedFunction(names, value) { + return function() { + (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names); + }; + } + function classed_default(name, value) { + var names = classArray(name + ""); + if (arguments.length < 2) { + var list = classList(this.node()), i = -1, n = names.length; + while (++i < n) if (!list.contains(names[i])) return false; + return true; + } + return this.each((typeof value === "function" ? classedFunction : value ? classedTrue : classedFalse)(names, value)); + } + + // ../../../node_modules/d3-selection/src/selection/text.js + function textRemove() { + this.textContent = ""; + } + function textConstant(value) { + return function() { + this.textContent = value; + }; + } + function textFunction(value) { + return function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + }; + } + function text_default(value) { + return arguments.length ? this.each(value == null ? textRemove : (typeof value === "function" ? textFunction : textConstant)(value)) : this.node().textContent; + } + + // ../../../node_modules/d3-selection/src/selection/html.js + function htmlRemove() { + this.innerHTML = ""; + } + function htmlConstant(value) { + return function() { + this.innerHTML = value; + }; + } + function htmlFunction(value) { + return function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + }; + } + function html_default(value) { + return arguments.length ? this.each(value == null ? htmlRemove : (typeof value === "function" ? htmlFunction : htmlConstant)(value)) : this.node().innerHTML; + } + + // ../../../node_modules/d3-selection/src/selection/raise.js + function raise() { + if (this.nextSibling) this.parentNode.appendChild(this); + } + function raise_default() { + return this.each(raise); + } + + // ../../../node_modules/d3-selection/src/selection/lower.js + function lower() { + if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild); + } + function lower_default() { + return this.each(lower); + } + + // ../../../node_modules/d3-selection/src/selection/append.js + function append_default(name) { + var create = typeof name === "function" ? name : creator_default(name); + return this.select(function() { + return this.appendChild(create.apply(this, arguments)); + }); + } + + // ../../../node_modules/d3-selection/src/selection/insert.js + function constantNull() { + return null; + } + function insert_default(name, before) { + var create = typeof name === "function" ? name : creator_default(name), select = before == null ? constantNull : typeof before === "function" ? before : selector_default(before); + return this.select(function() { + return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null); + }); + } + + // ../../../node_modules/d3-selection/src/selection/remove.js + function remove() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + } + function remove_default() { + return this.each(remove); + } + + // ../../../node_modules/d3-selection/src/selection/clone.js + function selection_cloneShallow() { + var clone = this.cloneNode(false), parent = this.parentNode; + return parent ? parent.insertBefore(clone, this.nextSibling) : clone; + } + function selection_cloneDeep() { + var clone = this.cloneNode(true), parent = this.parentNode; + return parent ? parent.insertBefore(clone, this.nextSibling) : clone; + } + function clone_default(deep) { + return this.select(deep ? selection_cloneDeep : selection_cloneShallow); + } + + // ../../../node_modules/d3-selection/src/selection/datum.js + function datum_default(value) { + return arguments.length ? this.property("__data__", value) : this.node().__data__; + } + + // ../../../node_modules/d3-selection/src/selection/on.js + function contextListener(listener) { + return function(event) { + listener.call(this, event, this.__data__); + }; + } + function parseTypenames(typenames) { + return typenames.trim().split(/^|\s+/).map(function(t) { + var name = "", i = t.indexOf("."); + if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); + return { type: t, name }; + }); + } + function onRemove(typename) { + return function() { + var on = this.__on; + if (!on) return; + for (var j = 0, i = -1, m = on.length, o; j < m; ++j) { + if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) { + this.removeEventListener(o.type, o.listener, o.options); + } else { + on[++i] = o; + } + } + if (++i) on.length = i; + else delete this.__on; + }; + } + function onAdd(typename, value, options) { + return function() { + var on = this.__on, o, listener = contextListener(value); + if (on) for (var j = 0, m = on.length; j < m; ++j) { + if ((o = on[j]).type === typename.type && o.name === typename.name) { + this.removeEventListener(o.type, o.listener, o.options); + this.addEventListener(o.type, o.listener = listener, o.options = options); + o.value = value; + return; + } + } + this.addEventListener(typename.type, listener, options); + o = { type: typename.type, name: typename.name, value, listener, options }; + if (!on) this.__on = [o]; + else on.push(o); + }; + } + function on_default(typename, value, options) { + var typenames = parseTypenames(typename + ""), i, n = typenames.length, t; + if (arguments.length < 2) { + var on = this.node().__on; + if (on) for (var j = 0, m = on.length, o; j < m; ++j) { + for (i = 0, o = on[j]; i < n; ++i) { + if ((t = typenames[i]).type === o.type && t.name === o.name) { + return o.value; + } + } + } + return; + } + on = value ? onAdd : onRemove; + for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options)); + return this; + } + + // ../../../node_modules/d3-selection/src/selection/dispatch.js + function dispatchEvent(node, type, params) { + var window2 = window_default(node), event = window2.CustomEvent; + if (typeof event === "function") { + event = new event(type, params); + } else { + event = window2.document.createEvent("Event"); + if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail; + else event.initEvent(type, false, false); + } + node.dispatchEvent(event); + } + function dispatchConstant(type, params) { + return function() { + return dispatchEvent(this, type, params); + }; + } + function dispatchFunction(type, params) { + return function() { + return dispatchEvent(this, type, params.apply(this, arguments)); + }; + } + function dispatch_default(type, params) { + return this.each((typeof params === "function" ? dispatchFunction : dispatchConstant)(type, params)); + } + + // ../../../node_modules/d3-selection/src/selection/iterator.js + function* iterator_default() { + for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { + if (node = group[i]) yield node; + } + } + } + + // ../../../node_modules/d3-selection/src/selection/index.js + var root = [null]; + function Selection(groups, parents) { + this._groups = groups; + this._parents = parents; + } + function selection() { + return new Selection([[document.documentElement]], root); + } + function selection_selection() { + return this; + } + Selection.prototype = selection.prototype = { + constructor: Selection, + select: select_default, + selectAll: selectAll_default, + selectChild: selectChild_default, + selectChildren: selectChildren_default, + filter: filter_default, + data: data_default, + enter: enter_default, + exit: exit_default, + join: join_default, + merge: merge_default, + selection: selection_selection, + order: order_default, + sort: sort_default, + call: call_default, + nodes: nodes_default, + node: node_default, + size: size_default, + empty: empty_default, + each: each_default, + attr: attr_default, + style: style_default, + property: property_default, + classed: classed_default, + text: text_default, + html: html_default, + raise: raise_default, + lower: lower_default, + append: append_default, + insert: insert_default, + remove: remove_default, + clone: clone_default, + datum: datum_default, + on: on_default, + dispatch: dispatch_default, + [Symbol.iterator]: iterator_default + }; + + // ../../../node_modules/d3-selection/src/select.js + function select_default2(selector) { + return typeof selector === "string" ? new Selection([[document.querySelector(selector)]], [document.documentElement]) : new Selection([[selector]], root); + } + + // ../../../node_modules/d3-time/src/interval.js + var t0 = /* @__PURE__ */ new Date(); + var t1 = /* @__PURE__ */ new Date(); + function timeInterval(floori, offseti, count, field) { + function interval(date) { + return floori(date = arguments.length === 0 ? /* @__PURE__ */ new Date() : /* @__PURE__ */ new Date(+date)), date; + } + interval.floor = (date) => { + return floori(date = /* @__PURE__ */ new Date(+date)), date; + }; + interval.ceil = (date) => { + return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date; + }; + interval.round = (date) => { + const d0 = interval(date), d1 = interval.ceil(date); + return date - d0 < d1 - date ? d0 : d1; + }; + interval.offset = (date, step) => { + return offseti(date = /* @__PURE__ */ new Date(+date), step == null ? 1 : Math.floor(step)), date; + }; + interval.range = (start, stop, step) => { + const range = []; + start = interval.ceil(start); + step = step == null ? 1 : Math.floor(step); + if (!(start < stop) || !(step > 0)) return range; + let previous; + do + range.push(previous = /* @__PURE__ */ new Date(+start)), offseti(start, step), floori(start); + while (previous < start && start < stop); + return range; + }; + interval.filter = (test) => { + return timeInterval((date) => { + if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1); + }, (date, step) => { + if (date >= date) { + if (step < 0) while (++step <= 0) { + while (offseti(date, -1), !test(date)) { + } + } + else while (--step >= 0) { + while (offseti(date, 1), !test(date)) { + } + } + } + }); + }; + if (count) { + interval.count = (start, end) => { + t0.setTime(+start), t1.setTime(+end); + floori(t0), floori(t1); + return Math.floor(count(t0, t1)); + }; + interval.every = (step) => { + step = Math.floor(step); + return !isFinite(step) || !(step > 0) ? null : !(step > 1) ? interval : interval.filter(field ? (d) => field(d) % step === 0 : (d) => interval.count(0, d) % step === 0); + }; + } + return interval; + } + + // ../../../node_modules/d3-time/src/duration.js + var durationSecond = 1e3; + var durationMinute = durationSecond * 60; + var durationHour = durationMinute * 60; + var durationDay = durationHour * 24; + var durationWeek = durationDay * 7; + var durationMonth = durationDay * 30; + var durationYear = durationDay * 365; + + // ../../../node_modules/d3-time/src/day.js + var timeDay = timeInterval( + (date) => date.setHours(0, 0, 0, 0), + (date, step) => date.setDate(date.getDate() + step), + (start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay, + (date) => date.getDate() - 1 + ); + var timeDays = timeDay.range; + var utcDay = timeInterval((date) => { + date.setUTCHours(0, 0, 0, 0); + }, (date, step) => { + date.setUTCDate(date.getUTCDate() + step); + }, (start, end) => { + return (end - start) / durationDay; + }, (date) => { + return date.getUTCDate() - 1; + }); + var utcDays = utcDay.range; + var unixDay = timeInterval((date) => { + date.setUTCHours(0, 0, 0, 0); + }, (date, step) => { + date.setUTCDate(date.getUTCDate() + step); + }, (start, end) => { + return (end - start) / durationDay; + }, (date) => { + return Math.floor(date / durationDay); + }); + var unixDays = unixDay.range; + + // ../../../node_modules/d3-time/src/week.js + function timeWeekday(i) { + return timeInterval((date) => { + date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); + date.setHours(0, 0, 0, 0); + }, (date, step) => { + date.setDate(date.getDate() + step * 7); + }, (start, end) => { + return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek; + }); + } + var timeSunday = timeWeekday(0); + var timeMonday = timeWeekday(1); + var timeTuesday = timeWeekday(2); + var timeWednesday = timeWeekday(3); + var timeThursday = timeWeekday(4); + var timeFriday = timeWeekday(5); + var timeSaturday = timeWeekday(6); + var timeSundays = timeSunday.range; + var timeMondays = timeMonday.range; + var timeTuesdays = timeTuesday.range; + var timeWednesdays = timeWednesday.range; + var timeThursdays = timeThursday.range; + var timeFridays = timeFriday.range; + var timeSaturdays = timeSaturday.range; + function utcWeekday(i) { + return timeInterval((date) => { + date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); + date.setUTCHours(0, 0, 0, 0); + }, (date, step) => { + date.setUTCDate(date.getUTCDate() + step * 7); + }, (start, end) => { + return (end - start) / durationWeek; + }); + } + var utcSunday = utcWeekday(0); + var utcMonday = utcWeekday(1); + var utcTuesday = utcWeekday(2); + var utcWednesday = utcWeekday(3); + var utcThursday = utcWeekday(4); + var utcFriday = utcWeekday(5); + var utcSaturday = utcWeekday(6); + var utcSundays = utcSunday.range; + var utcMondays = utcMonday.range; + var utcTuesdays = utcTuesday.range; + var utcWednesdays = utcWednesday.range; + var utcThursdays = utcThursday.range; + var utcFridays = utcFriday.range; + var utcSaturdays = utcSaturday.range; + + // ../../../node_modules/d3-time/src/month.js + var timeMonth = timeInterval((date) => { + date.setDate(1); + date.setHours(0, 0, 0, 0); + }, (date, step) => { + date.setMonth(date.getMonth() + step); + }, (start, end) => { + return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; + }, (date) => { + return date.getMonth(); + }); + var timeMonths = timeMonth.range; + var utcMonth = timeInterval((date) => { + date.setUTCDate(1); + date.setUTCHours(0, 0, 0, 0); + }, (date, step) => { + date.setUTCMonth(date.getUTCMonth() + step); + }, (start, end) => { + return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12; + }, (date) => { + return date.getUTCMonth(); + }); + var utcMonths = utcMonth.range; + + // ../../../node_modules/d3-time/src/year.js + var timeYear = timeInterval((date) => { + date.setMonth(0, 1); + date.setHours(0, 0, 0, 0); + }, (date, step) => { + date.setFullYear(date.getFullYear() + step); + }, (start, end) => { + return end.getFullYear() - start.getFullYear(); + }, (date) => { + return date.getFullYear(); + }); + timeYear.every = (k) => { + return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { + date.setFullYear(Math.floor(date.getFullYear() / k) * k); + date.setMonth(0, 1); + date.setHours(0, 0, 0, 0); + }, (date, step) => { + date.setFullYear(date.getFullYear() + step * k); + }); + }; + var timeYears = timeYear.range; + var utcYear = timeInterval((date) => { + date.setUTCMonth(0, 1); + date.setUTCHours(0, 0, 0, 0); + }, (date, step) => { + date.setUTCFullYear(date.getUTCFullYear() + step); + }, (start, end) => { + return end.getUTCFullYear() - start.getUTCFullYear(); + }, (date) => { + return date.getUTCFullYear(); + }); + utcYear.every = (k) => { + return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { + date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k); + date.setUTCMonth(0, 1); + date.setUTCHours(0, 0, 0, 0); + }, (date, step) => { + date.setUTCFullYear(date.getUTCFullYear() + step * k); + }); + }; + var utcYears = utcYear.range; + + // ../../../node_modules/d3-array/src/ascending.js + function ascending2(a, b) { + return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; + } + + // ../../../node_modules/d3-array/src/descending.js + function descending(a, b) { + return a == null || b == null ? NaN : b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + } + + // ../../../node_modules/d3-array/src/bisector.js + function bisector(f) { + let compare1, compare2, delta; + if (f.length !== 2) { + compare1 = ascending2; + compare2 = (d, x) => ascending2(f(d), x); + delta = (d, x) => f(d) - x; + } else { + compare1 = f === ascending2 || f === descending ? f : zero; + compare2 = f; + delta = f; + } + function left(a, x, lo = 0, hi = a.length) { + if (lo < hi) { + if (compare1(x, x) !== 0) return hi; + do { + const mid = lo + hi >>> 1; + if (compare2(a[mid], x) < 0) lo = mid + 1; + else hi = mid; + } while (lo < hi); + } + return lo; + } + function right(a, x, lo = 0, hi = a.length) { + if (lo < hi) { + if (compare1(x, x) !== 0) return hi; + do { + const mid = lo + hi >>> 1; + if (compare2(a[mid], x) <= 0) lo = mid + 1; + else hi = mid; + } while (lo < hi); + } + return lo; + } + function center(a, x, lo = 0, hi = a.length) { + const i = left(a, x, lo, hi - 1); + return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i; + } + return { left, center, right }; + } + function zero() { + return 0; + } + + // ../../../node_modules/d3-array/src/number.js + function number(x) { + return x === null ? NaN : +x; + } + + // ../../../node_modules/d3-array/src/bisect.js + var ascendingBisect = bisector(ascending2); + var bisectRight = ascendingBisect.right; + var bisectLeft = ascendingBisect.left; + var bisectCenter = bisector(number).center; + var bisect_default = bisectRight; + + // ../../../node_modules/d3-array/src/ticks.js + var e10 = Math.sqrt(50); + var e5 = Math.sqrt(10); + var e2 = Math.sqrt(2); + function tickSpec(start, stop, count) { + const step = (stop - start) / Math.max(0, count), power = Math.floor(Math.log10(step)), error = step / Math.pow(10, power), factor = error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1; + let i1, i2, inc; + if (power < 0) { + inc = Math.pow(10, -power) / factor; + i1 = Math.round(start * inc); + i2 = Math.round(stop * inc); + if (i1 / inc < start) ++i1; + if (i2 / inc > stop) --i2; + inc = -inc; + } else { + inc = Math.pow(10, power) * factor; + i1 = Math.round(start / inc); + i2 = Math.round(stop / inc); + if (i1 * inc < start) ++i1; + if (i2 * inc > stop) --i2; + } + if (i2 < i1 && 0.5 <= count && count < 2) return tickSpec(start, stop, count * 2); + return [i1, i2, inc]; + } + function ticks(start, stop, count) { + stop = +stop, start = +start, count = +count; + if (!(count > 0)) return []; + if (start === stop) return [start]; + const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count) : tickSpec(start, stop, count); + if (!(i2 >= i1)) return []; + const n = i2 - i1 + 1, ticks2 = new Array(n); + if (reverse) { + if (inc < 0) for (let i = 0; i < n; ++i) ticks2[i] = (i2 - i) / -inc; + else for (let i = 0; i < n; ++i) ticks2[i] = (i2 - i) * inc; + } else { + if (inc < 0) for (let i = 0; i < n; ++i) ticks2[i] = (i1 + i) / -inc; + else for (let i = 0; i < n; ++i) ticks2[i] = (i1 + i) * inc; + } + return ticks2; + } + function tickIncrement(start, stop, count) { + stop = +stop, start = +start, count = +count; + return tickSpec(start, stop, count)[2]; + } + function tickStep(start, stop, count) { + stop = +stop, start = +start, count = +count; + const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count) : tickIncrement(start, stop, count); + return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc); + } + + // ../../../node_modules/d3-array/src/max.js + function max(values, valueof) { + let max2; + if (valueof === void 0) { + for (const value of values) { + if (value != null && (max2 < value || max2 === void 0 && value >= value)) { + max2 = value; + } + } + } else { + let index = -1; + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null && (max2 < value || max2 === void 0 && value >= value)) { + max2 = value; + } + } + } + return max2; + } + + // assets/src/shared/tooltip.ts + function createTooltip() { + document.querySelectorAll(".heatmap-tooltip").forEach((el) => el.remove()); + const tip = document.createElement("div"); + tip.className = "heatmap-tooltip"; + tip.style.display = "none"; + tip.style.position = "fixed"; + document.body.appendChild(tip); + return tip; + } + function showTooltip(tip, html, anchorRect, cellSize) { + tip.innerHTML = html; + tip.style.display = "block"; + tip.style.left = `${anchorRect.left + cellSize / 2}px`; + tip.style.top = `${anchorRect.top - tip.offsetHeight - 8}px`; + } + function hideTooltip(tip) { + tip.style.display = "none"; + } + + // ../../../node_modules/d3-scale/src/init.js + function initRange(domain, range) { + switch (arguments.length) { + case 0: + break; + case 1: + this.range(domain); + break; + default: + this.range(range).domain(domain); + break; + } + return this; + } + + // ../../../node_modules/d3-format/src/formatDecimal.js + function formatDecimal_default(x) { + return Math.abs(x = Math.round(x)) >= 1e21 ? x.toLocaleString("en").replace(/,/g, "") : x.toString(10); + } + function formatDecimalParts(x, p) { + if (!isFinite(x) || x === 0) return null; + var i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e"), coefficient = x.slice(0, i); + return [ + coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, + +x.slice(i + 1) + ]; + } + + // ../../../node_modules/d3-format/src/exponent.js + function exponent_default(x) { + return x = formatDecimalParts(Math.abs(x)), x ? x[1] : NaN; + } + + // ../../../node_modules/d3-format/src/formatGroup.js + function formatGroup_default(grouping, thousands) { + return function(value, width) { + var i = value.length, t = [], j = 0, g = grouping[0], length = 0; + while (i > 0 && g > 0) { + if (length + g + 1 > width) g = Math.max(1, width - length); + t.push(value.substring(i -= g, i + g)); + if ((length += g + 1) > width) break; + g = grouping[j = (j + 1) % grouping.length]; + } + return t.reverse().join(thousands); + }; + } + + // ../../../node_modules/d3-format/src/formatNumerals.js + function formatNumerals_default(numerals) { + return function(value) { + return value.replace(/[0-9]/g, function(i) { + return numerals[+i]; + }); + }; + } + + // ../../../node_modules/d3-format/src/formatSpecifier.js + var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i; + function formatSpecifier(specifier) { + if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); + var match; + return new FormatSpecifier({ + fill: match[1], + align: match[2], + sign: match[3], + symbol: match[4], + zero: match[5], + width: match[6], + comma: match[7], + precision: match[8] && match[8].slice(1), + trim: match[9], + type: match[10] + }); + } + formatSpecifier.prototype = FormatSpecifier.prototype; + function FormatSpecifier(specifier) { + this.fill = specifier.fill === void 0 ? " " : specifier.fill + ""; + this.align = specifier.align === void 0 ? ">" : specifier.align + ""; + this.sign = specifier.sign === void 0 ? "-" : specifier.sign + ""; + this.symbol = specifier.symbol === void 0 ? "" : specifier.symbol + ""; + this.zero = !!specifier.zero; + this.width = specifier.width === void 0 ? void 0 : +specifier.width; + this.comma = !!specifier.comma; + this.precision = specifier.precision === void 0 ? void 0 : +specifier.precision; + this.trim = !!specifier.trim; + this.type = specifier.type === void 0 ? "" : specifier.type + ""; + } + FormatSpecifier.prototype.toString = function() { + return this.fill + this.align + this.sign + this.symbol + (this.zero ? "0" : "") + (this.width === void 0 ? "" : Math.max(1, this.width | 0)) + (this.comma ? "," : "") + (this.precision === void 0 ? "" : "." + Math.max(0, this.precision | 0)) + (this.trim ? "~" : "") + this.type; + }; + + // ../../../node_modules/d3-format/src/formatTrim.js + function formatTrim_default(s) { + out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) { + switch (s[i]) { + case ".": + i0 = i1 = i; + break; + case "0": + if (i0 === 0) i0 = i; + i1 = i; + break; + default: + if (!+s[i]) break out; + if (i0 > 0) i0 = 0; + break; + } + } + return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s; + } + + // ../../../node_modules/d3-format/src/formatPrefixAuto.js + var prefixExponent; + function formatPrefixAuto_default(x, p) { + var d = formatDecimalParts(x, p); + if (!d) return prefixExponent = void 0, x.toPrecision(p); + var coefficient = d[0], exponent = d[1], i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, n = coefficient.length; + return i === n ? coefficient : i > n ? coefficient + new Array(i - n + 1).join("0") : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) : "0." + new Array(1 - i).join("0") + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; + } + + // ../../../node_modules/d3-format/src/formatRounded.js + function formatRounded_default(x, p) { + var d = formatDecimalParts(x, p); + if (!d) return x + ""; + var coefficient = d[0], exponent = d[1]; + return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) : coefficient + new Array(exponent - coefficient.length + 2).join("0"); + } + + // ../../../node_modules/d3-format/src/formatTypes.js + var formatTypes_default = { + "%": (x, p) => (x * 100).toFixed(p), + "b": (x) => Math.round(x).toString(2), + "c": (x) => x + "", + "d": formatDecimal_default, + "e": (x, p) => x.toExponential(p), + "f": (x, p) => x.toFixed(p), + "g": (x, p) => x.toPrecision(p), + "o": (x) => Math.round(x).toString(8), + "p": (x, p) => formatRounded_default(x * 100, p), + "r": formatRounded_default, + "s": formatPrefixAuto_default, + "X": (x) => Math.round(x).toString(16).toUpperCase(), + "x": (x) => Math.round(x).toString(16) + }; + + // ../../../node_modules/d3-format/src/identity.js + function identity_default(x) { + return x; + } + + // ../../../node_modules/d3-format/src/locale.js + var map = Array.prototype.map; + var prefixes = ["y", "z", "a", "f", "p", "n", "\xB5", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"]; + function locale_default(locale3) { + var group = locale3.grouping === void 0 || locale3.thousands === void 0 ? identity_default : formatGroup_default(map.call(locale3.grouping, Number), locale3.thousands + ""), currencyPrefix = locale3.currency === void 0 ? "" : locale3.currency[0] + "", currencySuffix = locale3.currency === void 0 ? "" : locale3.currency[1] + "", decimal = locale3.decimal === void 0 ? "." : locale3.decimal + "", numerals = locale3.numerals === void 0 ? identity_default : formatNumerals_default(map.call(locale3.numerals, String)), percent = locale3.percent === void 0 ? "%" : locale3.percent + "", minus = locale3.minus === void 0 ? "\u2212" : locale3.minus + "", nan = locale3.nan === void 0 ? "NaN" : locale3.nan + ""; + function newFormat(specifier, options) { + specifier = formatSpecifier(specifier); + var fill = specifier.fill, align = specifier.align, sign = specifier.sign, symbol = specifier.symbol, zero2 = specifier.zero, width = specifier.width, comma = specifier.comma, precision = specifier.precision, trim = specifier.trim, type = specifier.type; + if (type === "n") comma = true, type = "g"; + else if (!formatTypes_default[type]) precision === void 0 && (precision = 12), trim = true, type = "g"; + if (zero2 || fill === "0" && align === "=") zero2 = true, fill = "0", align = "="; + var prefix = (options && options.prefix !== void 0 ? options.prefix : "") + (symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : ""), suffix = (symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : "") + (options && options.suffix !== void 0 ? options.suffix : ""); + var formatType = formatTypes_default[type], maybeSuffix = /[defgprs%]/.test(type); + precision = precision === void 0 ? 6 : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision)); + function format2(value) { + var valuePrefix = prefix, valueSuffix = suffix, i, n, c; + if (type === "c") { + valueSuffix = formatType(value) + valueSuffix; + value = ""; + } else { + value = +value; + var valueNegative = value < 0 || 1 / value < 0; + value = isNaN(value) ? nan : formatType(Math.abs(value), precision); + if (trim) value = formatTrim_default(value); + if (valueNegative && +value === 0 && sign !== "+") valueNegative = false; + valuePrefix = (valueNegative ? sign === "(" ? sign : minus : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; + valueSuffix = (type === "s" && !isNaN(value) && prefixExponent !== void 0 ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); + if (maybeSuffix) { + i = -1, n = value.length; + while (++i < n) { + if (c = value.charCodeAt(i), 48 > c || c > 57) { + valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; + value = value.slice(0, i); + break; + } + } + } + } + if (comma && !zero2) value = group(value, Infinity); + var length = valuePrefix.length + value.length + valueSuffix.length, padding = length < width ? new Array(width - length + 1).join(fill) : ""; + if (comma && zero2) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; + switch (align) { + case "<": + value = valuePrefix + value + valueSuffix + padding; + break; + case "=": + value = valuePrefix + padding + value + valueSuffix; + break; + case "^": + value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); + break; + default: + value = padding + valuePrefix + value + valueSuffix; + break; + } + return numerals(value); + } + format2.toString = function() { + return specifier + ""; + }; + return format2; + } + function formatPrefix2(specifier, value) { + var e = Math.max(-8, Math.min(8, Math.floor(exponent_default(value) / 3))) * 3, k = Math.pow(10, -e), f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier), { suffix: prefixes[8 + e / 3] }); + return function(value2) { + return f(k * value2); + }; + } + return { + format: newFormat, + formatPrefix: formatPrefix2 + }; + } + + // ../../../node_modules/d3-format/src/defaultLocale.js + var locale; + var format; + var formatPrefix; + defaultLocale({ + thousands: ",", + grouping: [3], + currency: ["$", ""] + }); + function defaultLocale(definition) { + locale = locale_default(definition); + format = locale.format; + formatPrefix = locale.formatPrefix; + return locale; + } + + // ../../../node_modules/d3-format/src/precisionFixed.js + function precisionFixed_default(step) { + return Math.max(0, -exponent_default(Math.abs(step))); + } + + // ../../../node_modules/d3-format/src/precisionPrefix.js + function precisionPrefix_default(step, value) { + return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent_default(value) / 3))) * 3 - exponent_default(Math.abs(step))); + } + + // ../../../node_modules/d3-format/src/precisionRound.js + function precisionRound_default(step, max2) { + step = Math.abs(step), max2 = Math.abs(max2) - step; + return Math.max(0, exponent_default(max2) - exponent_default(step)) + 1; + } + + // ../../../node_modules/d3-scale/src/tickFormat.js + function tickFormat(start, stop, count, specifier) { + var step = tickStep(start, stop, count), precision; + specifier = formatSpecifier(specifier == null ? ",f" : specifier); + switch (specifier.type) { + case "s": { + var value = Math.max(Math.abs(start), Math.abs(stop)); + if (specifier.precision == null && !isNaN(precision = precisionPrefix_default(step, value))) specifier.precision = precision; + return formatPrefix(specifier, value); + } + case "": + case "e": + case "g": + case "p": + case "r": { + if (specifier.precision == null && !isNaN(precision = precisionRound_default(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e"); + break; + } + case "f": + case "%": { + if (specifier.precision == null && !isNaN(precision = precisionFixed_default(step))) specifier.precision = precision - (specifier.type === "%") * 2; + break; + } + } + return format(specifier); + } + + // ../../../node_modules/d3-scale/src/linear.js + function linearish(scale) { + var domain = scale.domain; + scale.ticks = function(count) { + var d = domain(); + return ticks(d[0], d[d.length - 1], count == null ? 10 : count); + }; + scale.tickFormat = function(count, specifier) { + var d = domain(); + return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier); + }; + scale.nice = function(count) { + if (count == null) count = 10; + var d = domain(); + var i0 = 0; + var i1 = d.length - 1; + var start = d[i0]; + var stop = d[i1]; + var prestep; + var step; + var maxIter = 10; + if (stop < start) { + step = start, start = stop, stop = step; + step = i0, i0 = i1, i1 = step; + } + while (maxIter-- > 0) { + step = tickIncrement(start, stop, count); + if (step === prestep) { + d[i0] = start; + d[i1] = stop; + return domain(d); + } else if (step > 0) { + start = Math.floor(start / step) * step; + stop = Math.ceil(stop / step) * step; + } else if (step < 0) { + start = Math.ceil(start * step) / step; + stop = Math.floor(stop * step) / step; + } else { + break; + } + prestep = step; + } + return scale; + }; + return scale; + } + + // ../../../node_modules/d3-scale/src/quantize.js + function quantize() { + var x0 = 0, x1 = 1, n = 1, domain = [0.5], range = [0, 1], unknown; + function scale(x) { + return x != null && x <= x ? range[bisect_default(domain, x, 0, n)] : unknown; + } + function rescale() { + var i = -1; + domain = new Array(n); + while (++i < n) domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1); + return scale; + } + scale.domain = function(_) { + return arguments.length ? ([x0, x1] = _, x0 = +x0, x1 = +x1, rescale()) : [x0, x1]; + }; + scale.range = function(_) { + return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice(); + }; + scale.invertExtent = function(y) { + var i = range.indexOf(y); + return i < 0 ? [NaN, NaN] : i < 1 ? [x0, domain[0]] : i >= n ? [domain[n - 1], x1] : [domain[i - 1], domain[i]]; + }; + scale.unknown = function(_) { + return arguments.length ? (unknown = _, scale) : scale; + }; + scale.thresholds = function() { + return domain.slice(); + }; + scale.copy = function() { + return quantize().domain([x0, x1]).range(range).unknown(unknown); + }; + return initRange.apply(linearish(scale), arguments); + } + + // ../../../node_modules/d3-time-format/src/locale.js + function localDate(d) { + if (0 <= d.y && d.y < 100) { + var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L); + date.setFullYear(d.y); + return date; + } + return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L); + } + function utcDate(d) { + if (0 <= d.y && d.y < 100) { + var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L)); + date.setUTCFullYear(d.y); + return date; + } + return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L)); + } + function newDate(y, m, d) { + return { y, m, d, H: 0, M: 0, S: 0, L: 0 }; + } + function formatLocale(locale3) { + var locale_dateTime = locale3.dateTime, locale_date = locale3.date, locale_time = locale3.time, locale_periods = locale3.periods, locale_weekdays = locale3.days, locale_shortWeekdays = locale3.shortDays, locale_months = locale3.months, locale_shortMonths = locale3.shortMonths; + var periodRe = formatRe(locale_periods), periodLookup = formatLookup(locale_periods), weekdayRe = formatRe(locale_weekdays), weekdayLookup = formatLookup(locale_weekdays), shortWeekdayRe = formatRe(locale_shortWeekdays), shortWeekdayLookup = formatLookup(locale_shortWeekdays), monthRe = formatRe(locale_months), monthLookup = formatLookup(locale_months), shortMonthRe = formatRe(locale_shortMonths), shortMonthLookup = formatLookup(locale_shortMonths); + var formats = { + "a": formatShortWeekday, + "A": formatWeekday, + "b": formatShortMonth, + "B": formatMonth, + "c": null, + "d": formatDayOfMonth, + "e": formatDayOfMonth, + "f": formatMicroseconds, + "g": formatYearISO, + "G": formatFullYearISO, + "H": formatHour24, + "I": formatHour12, + "j": formatDayOfYear, + "L": formatMilliseconds, + "m": formatMonthNumber, + "M": formatMinutes, + "p": formatPeriod, + "q": formatQuarter, + "Q": formatUnixTimestamp, + "s": formatUnixTimestampSeconds, + "S": formatSeconds, + "u": formatWeekdayNumberMonday, + "U": formatWeekNumberSunday, + "V": formatWeekNumberISO, + "w": formatWeekdayNumberSunday, + "W": formatWeekNumberMonday, + "x": null, + "X": null, + "y": formatYear, + "Y": formatFullYear, + "Z": formatZone, + "%": formatLiteralPercent + }; + var utcFormats = { + "a": formatUTCShortWeekday, + "A": formatUTCWeekday, + "b": formatUTCShortMonth, + "B": formatUTCMonth, + "c": null, + "d": formatUTCDayOfMonth, + "e": formatUTCDayOfMonth, + "f": formatUTCMicroseconds, + "g": formatUTCYearISO, + "G": formatUTCFullYearISO, + "H": formatUTCHour24, + "I": formatUTCHour12, + "j": formatUTCDayOfYear, + "L": formatUTCMilliseconds, + "m": formatUTCMonthNumber, + "M": formatUTCMinutes, + "p": formatUTCPeriod, + "q": formatUTCQuarter, + "Q": formatUnixTimestamp, + "s": formatUnixTimestampSeconds, + "S": formatUTCSeconds, + "u": formatUTCWeekdayNumberMonday, + "U": formatUTCWeekNumberSunday, + "V": formatUTCWeekNumberISO, + "w": formatUTCWeekdayNumberSunday, + "W": formatUTCWeekNumberMonday, + "x": null, + "X": null, + "y": formatUTCYear, + "Y": formatUTCFullYear, + "Z": formatUTCZone, + "%": formatLiteralPercent + }; + var parses = { + "a": parseShortWeekday, + "A": parseWeekday, + "b": parseShortMonth, + "B": parseMonth, + "c": parseLocaleDateTime, + "d": parseDayOfMonth, + "e": parseDayOfMonth, + "f": parseMicroseconds, + "g": parseYear, + "G": parseFullYear, + "H": parseHour24, + "I": parseHour24, + "j": parseDayOfYear, + "L": parseMilliseconds, + "m": parseMonthNumber, + "M": parseMinutes, + "p": parsePeriod, + "q": parseQuarter, + "Q": parseUnixTimestamp, + "s": parseUnixTimestampSeconds, + "S": parseSeconds, + "u": parseWeekdayNumberMonday, + "U": parseWeekNumberSunday, + "V": parseWeekNumberISO, + "w": parseWeekdayNumberSunday, + "W": parseWeekNumberMonday, + "x": parseLocaleDate, + "X": parseLocaleTime, + "y": parseYear, + "Y": parseFullYear, + "Z": parseZone, + "%": parseLiteralPercent + }; + formats.x = newFormat(locale_date, formats); + formats.X = newFormat(locale_time, formats); + formats.c = newFormat(locale_dateTime, formats); + utcFormats.x = newFormat(locale_date, utcFormats); + utcFormats.X = newFormat(locale_time, utcFormats); + utcFormats.c = newFormat(locale_dateTime, utcFormats); + function newFormat(specifier, formats2) { + return function(date) { + var string = [], i = -1, j = 0, n = specifier.length, c, pad2, format2; + if (!(date instanceof Date)) date = /* @__PURE__ */ new Date(+date); + while (++i < n) { + if (specifier.charCodeAt(i) === 37) { + string.push(specifier.slice(j, i)); + if ((pad2 = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i); + else pad2 = c === "e" ? " " : "0"; + if (format2 = formats2[c]) c = format2(date, pad2); + string.push(c); + j = i + 1; + } + } + string.push(specifier.slice(j, i)); + return string.join(""); + }; + } + function newParse(specifier, Z) { + return function(string) { + var d = newDate(1900, void 0, 1), i = parseSpecifier(d, specifier, string += "", 0), week, day; + if (i != string.length) return null; + if ("Q" in d) return new Date(d.Q); + if ("s" in d) return new Date(d.s * 1e3 + ("L" in d ? d.L : 0)); + if (Z && !("Z" in d)) d.Z = 0; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + if (d.m === void 0) d.m = "q" in d ? d.q : 0; + if ("V" in d) { + if (d.V < 1 || d.V > 53) return null; + if (!("w" in d)) d.w = 1; + if ("Z" in d) { + week = utcDate(newDate(d.y, 0, 1)), day = week.getUTCDay(); + week = day > 4 || day === 0 ? utcMonday.ceil(week) : utcMonday(week); + week = utcDay.offset(week, (d.V - 1) * 7); + d.y = week.getUTCFullYear(); + d.m = week.getUTCMonth(); + d.d = week.getUTCDate() + (d.w + 6) % 7; + } else { + week = localDate(newDate(d.y, 0, 1)), day = week.getDay(); + week = day > 4 || day === 0 ? timeMonday.ceil(week) : timeMonday(week); + week = timeDay.offset(week, (d.V - 1) * 7); + d.y = week.getFullYear(); + d.m = week.getMonth(); + d.d = week.getDate() + (d.w + 6) % 7; + } + } else if ("W" in d || "U" in d) { + if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0; + day = "Z" in d ? utcDate(newDate(d.y, 0, 1)).getUTCDay() : localDate(newDate(d.y, 0, 1)).getDay(); + d.m = 0; + d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day + 5) % 7 : d.w + d.U * 7 - (day + 6) % 7; + } + if ("Z" in d) { + d.H += d.Z / 100 | 0; + d.M += d.Z % 100; + return utcDate(d); + } + return localDate(d); + }; + } + function parseSpecifier(d, specifier, string, j) { + var i = 0, n = specifier.length, m = string.length, c, parse; + while (i < n) { + if (j >= m) return -1; + c = specifier.charCodeAt(i++); + if (c === 37) { + c = specifier.charAt(i++); + parse = parses[c in pads ? specifier.charAt(i++) : c]; + if (!parse || (j = parse(d, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + function parsePeriod(d, string, i) { + var n = periodRe.exec(string.slice(i)); + return n ? (d.p = periodLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function parseShortWeekday(d, string, i) { + var n = shortWeekdayRe.exec(string.slice(i)); + return n ? (d.w = shortWeekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function parseWeekday(d, string, i) { + var n = weekdayRe.exec(string.slice(i)); + return n ? (d.w = weekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function parseShortMonth(d, string, i) { + var n = shortMonthRe.exec(string.slice(i)); + return n ? (d.m = shortMonthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function parseMonth(d, string, i) { + var n = monthRe.exec(string.slice(i)); + return n ? (d.m = monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; + } + function parseLocaleDateTime(d, string, i) { + return parseSpecifier(d, locale_dateTime, string, i); + } + function parseLocaleDate(d, string, i) { + return parseSpecifier(d, locale_date, string, i); + } + function parseLocaleTime(d, string, i) { + return parseSpecifier(d, locale_time, string, i); + } + function formatShortWeekday(d) { + return locale_shortWeekdays[d.getDay()]; + } + function formatWeekday(d) { + return locale_weekdays[d.getDay()]; + } + function formatShortMonth(d) { + return locale_shortMonths[d.getMonth()]; + } + function formatMonth(d) { + return locale_months[d.getMonth()]; + } + function formatPeriod(d) { + return locale_periods[+(d.getHours() >= 12)]; + } + function formatQuarter(d) { + return 1 + ~~(d.getMonth() / 3); + } + function formatUTCShortWeekday(d) { + return locale_shortWeekdays[d.getUTCDay()]; + } + function formatUTCWeekday(d) { + return locale_weekdays[d.getUTCDay()]; + } + function formatUTCShortMonth(d) { + return locale_shortMonths[d.getUTCMonth()]; + } + function formatUTCMonth(d) { + return locale_months[d.getUTCMonth()]; + } + function formatUTCPeriod(d) { + return locale_periods[+(d.getUTCHours() >= 12)]; + } + function formatUTCQuarter(d) { + return 1 + ~~(d.getUTCMonth() / 3); + } + return { + format: function(specifier) { + var f = newFormat(specifier += "", formats); + f.toString = function() { + return specifier; + }; + return f; + }, + parse: function(specifier) { + var p = newParse(specifier += "", false); + p.toString = function() { + return specifier; + }; + return p; + }, + utcFormat: function(specifier) { + var f = newFormat(specifier += "", utcFormats); + f.toString = function() { + return specifier; + }; + return f; + }, + utcParse: function(specifier) { + var p = newParse(specifier += "", true); + p.toString = function() { + return specifier; + }; + return p; + } + }; + } + var pads = { "-": "", "_": " ", "0": "0" }; + var numberRe = /^\s*\d+/; + var percentRe = /^%/; + var requoteRe = /[\\^$*+?|[\]().{}]/g; + function pad(value, fill, width) { + var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; + return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); + } + function requote(s) { + return s.replace(requoteRe, "\\$&"); + } + function formatRe(names) { + return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i"); + } + function formatLookup(names) { + return new Map(names.map((name, i) => [name.toLowerCase(), i])); + } + function parseWeekdayNumberSunday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.w = +n[0], i + n[0].length) : -1; + } + function parseWeekdayNumberMonday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.u = +n[0], i + n[0].length) : -1; + } + function parseWeekNumberSunday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.U = +n[0], i + n[0].length) : -1; + } + function parseWeekNumberISO(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.V = +n[0], i + n[0].length) : -1; + } + function parseWeekNumberMonday(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.W = +n[0], i + n[0].length) : -1; + } + function parseFullYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 4)); + return n ? (d.y = +n[0], i + n[0].length) : -1; + } + function parseYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2e3), i + n[0].length) : -1; + } + function parseZone(d, string, i) { + var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6)); + return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1; + } + function parseQuarter(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 1)); + return n ? (d.q = n[0] * 3 - 3, i + n[0].length) : -1; + } + function parseMonthNumber(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.m = n[0] - 1, i + n[0].length) : -1; + } + function parseDayOfMonth(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.d = +n[0], i + n[0].length) : -1; + } + function parseDayOfYear(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 3)); + return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1; + } + function parseHour24(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.H = +n[0], i + n[0].length) : -1; + } + function parseMinutes(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.M = +n[0], i + n[0].length) : -1; + } + function parseSeconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 2)); + return n ? (d.S = +n[0], i + n[0].length) : -1; + } + function parseMilliseconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 3)); + return n ? (d.L = +n[0], i + n[0].length) : -1; + } + function parseMicroseconds(d, string, i) { + var n = numberRe.exec(string.slice(i, i + 6)); + return n ? (d.L = Math.floor(n[0] / 1e3), i + n[0].length) : -1; + } + function parseLiteralPercent(d, string, i) { + var n = percentRe.exec(string.slice(i, i + 1)); + return n ? i + n[0].length : -1; + } + function parseUnixTimestamp(d, string, i) { + var n = numberRe.exec(string.slice(i)); + return n ? (d.Q = +n[0], i + n[0].length) : -1; + } + function parseUnixTimestampSeconds(d, string, i) { + var n = numberRe.exec(string.slice(i)); + return n ? (d.s = +n[0], i + n[0].length) : -1; + } + function formatDayOfMonth(d, p) { + return pad(d.getDate(), p, 2); + } + function formatHour24(d, p) { + return pad(d.getHours(), p, 2); + } + function formatHour12(d, p) { + return pad(d.getHours() % 12 || 12, p, 2); + } + function formatDayOfYear(d, p) { + return pad(1 + timeDay.count(timeYear(d), d), p, 3); + } + function formatMilliseconds(d, p) { + return pad(d.getMilliseconds(), p, 3); + } + function formatMicroseconds(d, p) { + return formatMilliseconds(d, p) + "000"; + } + function formatMonthNumber(d, p) { + return pad(d.getMonth() + 1, p, 2); + } + function formatMinutes(d, p) { + return pad(d.getMinutes(), p, 2); + } + function formatSeconds(d, p) { + return pad(d.getSeconds(), p, 2); + } + function formatWeekdayNumberMonday(d) { + var day = d.getDay(); + return day === 0 ? 7 : day; + } + function formatWeekNumberSunday(d, p) { + return pad(timeSunday.count(timeYear(d) - 1, d), p, 2); + } + function dISO(d) { + var day = d.getDay(); + return day >= 4 || day === 0 ? timeThursday(d) : timeThursday.ceil(d); + } + function formatWeekNumberISO(d, p) { + d = dISO(d); + return pad(timeThursday.count(timeYear(d), d) + (timeYear(d).getDay() === 4), p, 2); + } + function formatWeekdayNumberSunday(d) { + return d.getDay(); + } + function formatWeekNumberMonday(d, p) { + return pad(timeMonday.count(timeYear(d) - 1, d), p, 2); + } + function formatYear(d, p) { + return pad(d.getFullYear() % 100, p, 2); + } + function formatYearISO(d, p) { + d = dISO(d); + return pad(d.getFullYear() % 100, p, 2); + } + function formatFullYear(d, p) { + return pad(d.getFullYear() % 1e4, p, 4); + } + function formatFullYearISO(d, p) { + var day = d.getDay(); + d = day >= 4 || day === 0 ? timeThursday(d) : timeThursday.ceil(d); + return pad(d.getFullYear() % 1e4, p, 4); + } + function formatZone(d) { + var z = d.getTimezoneOffset(); + return (z > 0 ? "-" : (z *= -1, "+")) + pad(z / 60 | 0, "0", 2) + pad(z % 60, "0", 2); + } + function formatUTCDayOfMonth(d, p) { + return pad(d.getUTCDate(), p, 2); + } + function formatUTCHour24(d, p) { + return pad(d.getUTCHours(), p, 2); + } + function formatUTCHour12(d, p) { + return pad(d.getUTCHours() % 12 || 12, p, 2); + } + function formatUTCDayOfYear(d, p) { + return pad(1 + utcDay.count(utcYear(d), d), p, 3); + } + function formatUTCMilliseconds(d, p) { + return pad(d.getUTCMilliseconds(), p, 3); + } + function formatUTCMicroseconds(d, p) { + return formatUTCMilliseconds(d, p) + "000"; + } + function formatUTCMonthNumber(d, p) { + return pad(d.getUTCMonth() + 1, p, 2); + } + function formatUTCMinutes(d, p) { + return pad(d.getUTCMinutes(), p, 2); + } + function formatUTCSeconds(d, p) { + return pad(d.getUTCSeconds(), p, 2); + } + function formatUTCWeekdayNumberMonday(d) { + var dow = d.getUTCDay(); + return dow === 0 ? 7 : dow; + } + function formatUTCWeekNumberSunday(d, p) { + return pad(utcSunday.count(utcYear(d) - 1, d), p, 2); + } + function UTCdISO(d) { + var day = d.getUTCDay(); + return day >= 4 || day === 0 ? utcThursday(d) : utcThursday.ceil(d); + } + function formatUTCWeekNumberISO(d, p) { + d = UTCdISO(d); + return pad(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2); + } + function formatUTCWeekdayNumberSunday(d) { + return d.getUTCDay(); + } + function formatUTCWeekNumberMonday(d, p) { + return pad(utcMonday.count(utcYear(d) - 1, d), p, 2); + } + function formatUTCYear(d, p) { + return pad(d.getUTCFullYear() % 100, p, 2); + } + function formatUTCYearISO(d, p) { + d = UTCdISO(d); + return pad(d.getUTCFullYear() % 100, p, 2); + } + function formatUTCFullYear(d, p) { + return pad(d.getUTCFullYear() % 1e4, p, 4); + } + function formatUTCFullYearISO(d, p) { + var day = d.getUTCDay(); + d = day >= 4 || day === 0 ? utcThursday(d) : utcThursday.ceil(d); + return pad(d.getUTCFullYear() % 1e4, p, 4); + } + function formatUTCZone() { + return "+0000"; + } + function formatLiteralPercent() { + return "%"; + } + function formatUnixTimestamp(d) { + return +d; + } + function formatUnixTimestampSeconds(d) { + return Math.floor(+d / 1e3); + } + + // ../../../node_modules/d3-time-format/src/defaultLocale.js + var locale2; + var timeFormat; + var timeParse; + var utcFormat; + var utcParse; + defaultLocale2({ + dateTime: "%x, %X", + date: "%-m/%-d/%Y", + time: "%-I:%M:%S %p", + periods: ["AM", "PM"], + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], + shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + }); + function defaultLocale2(definition) { + locale2 = formatLocale(definition); + timeFormat = locale2.format; + timeParse = locale2.parse; + utcFormat = locale2.utcFormat; + utcParse = locale2.utcParse; + return locale2; + } + + // assets/src/shared/color-scale.ts + var FALLBACK_COLORS = ["#9be9a8", "#40c463", "#30a14e", "#216e39"]; + function buildColorScale(days, metric = "hours") { + const accessor = metric === "hours" ? (d) => d.hours : (d) => d.count; + const maxVal = max(days, accessor) || 1; + return quantize().domain([0, maxVal]).range(FALLBACK_COLORS); + } + + // assets/src/shared/date-utils.ts + var DAY_LABELS_MONDAY = ["Mon", "", "Wed", "", "Fri", "", ""]; + var DAY_LABELS_SUNDAY = ["Sun", "", "Tue", "", "Thu", "", "Sat"]; + function getDayLabels(weekStart) { + return weekStart === "sunday" ? DAY_LABELS_SUNDAY : DAY_LABELS_MONDAY; + } + var MONTH_FORMAT = timeFormat("%b"); + var DATE_FORMAT = timeFormat("%Y-%m-%d"); + var DISPLAY_FORMAT = timeFormat("%a, %b %-d, %Y"); + function buildDateMap(days) { + const map2 = /* @__PURE__ */ new Map(); + for (const d of days) { + map2.set(d.date, d); + } + return map2; + } + function getWeekInterval(weekStart) { + return weekStart === "sunday" ? timeSunday : timeMonday; + } + function generateCells(begin, end, dateMap, weekStart = "monday") { + const weekInterval = getWeekInterval(weekStart); + const firstWeekDay = weekInterval.floor(begin); + const cells = []; + let current = new Date(begin); + while (current <= end) { + const dateStr = DATE_FORMAT(current); + const weeksSinceStart = weekInterval.count(firstWeekDay, current); + const jsDay = current.getDay(); + const dayOfWeek = weekStart === "sunday" ? jsDay : (jsDay + 6) % 7; + const isWeekend = jsDay === 0 || jsDay === 6; + cells.push({ + date: new Date(current), + dateStr, + entry: dateMap.get(dateStr) || null, + week: weeksSinceStart, + day: dayOfWeek, + isWeekend + }); + current = timeDay.offset(current, 1); + } + return cells; + } + + // assets/src/renderers/year.ts + var YearModeRenderer = class { + mode = "year"; + tooltip = null; + render(ctx) { + ctx.container.innerHTML = ""; + if (!ctx.data.days || ctx.data.days.length === 0) { + const msg = document.createElement("div"); + msg.textContent = ctx.emptyMessage || "No tracking data available"; + msg.style.padding = "1rem"; + msg.style.color = "var(--tblr-secondary, #6c757d)"; + ctx.container.appendChild(msg); + return; + } + this.destroy(); + this.tooltip = createTooltip(); + const weekStart = ctx.state.weekStart; + const dateMap = buildDateMap(ctx.data.days); + const begin = new Date(ctx.data.range.begin); + const end = new Date(ctx.data.range.end); + const cells = generateCells(begin, end, dateMap, weekStart); + const colorScale = buildColorScale(ctx.data.days, ctx.state.metric); + const { cellGap, marginTop, marginLeft, marginBottom } = ctx.config; + const numWeeks = (max(cells, (c) => c.week) ?? 0) + 1; + const containerWidth = ctx.container.clientWidth || 800; + const maxCellSize = 22; + const minCellSize = 10; + const cellSize = Math.min(maxCellSize, Math.max(minCellSize, Math.floor((containerWidth - marginLeft) / numWeeks) - cellGap)); + const step = cellSize + cellGap; + const svgWidth = marginLeft + numWeeks * step; + const svgHeight = marginTop + 7 * step + marginBottom; + const wrapper = document.createElement("div"); + wrapper.style.maxWidth = `${svgWidth}px`; + wrapper.style.margin = "0 auto"; + ctx.container.appendChild(wrapper); + const svg = select_default2(wrapper).append("svg").attr("width", svgWidth).attr("height", svgHeight).attr("class", "heatmap-svg"); + const weekInterval = getWeekInterval(weekStart); + const months = []; + const firstWeekDay = weekInterval.floor(begin); + timeMonth.range(timeMonth.ceil(begin), end).forEach((m) => { + months.push({ date: m, week: weekInterval.count(firstWeekDay, m) }); + }); + svg.selectAll(".month-label").data(months).join("text").attr("class", "heatmap-label month-label").attr("x", (d) => marginLeft + d.week * step).attr("y", marginTop - 6).text((d) => MONTH_FORMAT(d.date)); + svg.selectAll(".day-label").data(getDayLabels(weekStart)).join("text").attr("class", "heatmap-label day-label").attr("x", marginLeft - 6).attr("y", (_d, i) => marginTop + i * step + cellSize - 2).attr("text-anchor", "end").text((d) => d); + const tooltip = this.tooltip; + svg.selectAll(".heatmap-cell").data(cells).join("rect").attr("class", (d) => { + let cls = "heatmap-cell"; + if (!d.entry) cls += " heatmap-empty"; + if (d.isWeekend) cls += " heatmap-weekend"; + return cls; + }).attr("x", (d) => marginLeft + d.week * step).attr("y", (d) => marginTop + d.day * step).attr("width", cellSize).attr("height", cellSize).attr("fill", (d) => { + if (!d.entry) return ""; + const val = ctx.state.metric === "hours" ? d.entry.hours : d.entry.count; + return colorScale(val); + }).on("mouseenter", function(event, d) { + const hours = d.entry ? d.entry.hours.toFixed(1) : "0.0"; + const count = d.entry ? d.entry.count : 0; + const html = `${DISPLAY_FORMAT(d.date)}
${hours}h (${count} entries)`; + const rect = event.target.getBoundingClientRect(); + showTooltip(tooltip, html, rect, cellSize); + }).on("mouseleave", function() { + hideTooltip(tooltip); + }).on("click", function(_event, d) { + if (!ctx.onCellClick) return; + ctx.onCellClick(d.dateStr); + }); + } + destroy() { + this.tooltip?.remove(); + this.tooltip = null; + } + }; + + // assets/src/shared/stats.ts + var DATE_FORMAT2 = timeFormat("%Y-%m-%d"); + var DISPLAY_FORMAT2 = timeFormat("%a, %b %-d, %Y"); + function calculateStreak(days) { + if (days.length === 0) return 0; + const tracked = new Set( + days.filter((d) => d.hours > 0).map((d) => d.date) + ); + if (tracked.size === 0) return 0; + const today = /* @__PURE__ */ new Date(); + today.setHours(0, 0, 0, 0); + let current = new Date(today); + if (!tracked.has(DATE_FORMAT2(current))) { + current = timeDay.offset(current, -1); + } + let streak = 0; + while (tracked.has(DATE_FORMAT2(current))) { + streak++; + current = timeDay.offset(current, -1); + } + return streak; + } + function calculateStats(days) { + const withEntries = days.filter((d) => d.hours > 0); + if (withEntries.length === 0) { + return { totalHours: 0, avgHours: 0, busiestDay: null }; + } + const totalHours = Math.round(withEntries.reduce((sum, d) => sum + d.hours, 0) * 10) / 10; + const avgHours = Math.round(totalHours / withEntries.length * 10) / 10; + const busiest = withEntries.reduce((best, d) => d.hours > best.hours ? d : best); + return { + totalHours, + avgHours, + busiestDay: { date: busiest.date, hours: busiest.hours } + }; + } + function renderStats(container, days) { + const existing = container.querySelector(".heatmap-stats"); + if (existing) existing.remove(); + const streak = calculateStreak(days); + const stats = calculateStats(days); + const statsDiv = document.createElement("div"); + statsDiv.className = "heatmap-stats"; + const parts = []; + parts.push(`\u{1F525} ${streak} days`); + parts.push(`Total: ${stats.totalHours}h`); + parts.push(`Avg: ${stats.avgHours}h/day`); + if (stats.busiestDay) { + const d = /* @__PURE__ */ new Date(stats.busiestDay.date + "T00:00:00"); + const label = DISPLAY_FORMAT2(d); + parts.push(`Busiest: ${label} \u2014 ${stats.busiestDay.hours.toFixed(1)}h`); + } + statsDiv.innerHTML = parts.join(""); + container.appendChild(statsDiv); + } + + // assets/src/heatmap.ts + registerRenderer(new YearModeRenderer()); + function init(container) { + const baseUrl = container.getAttribute("data-url"); + if (!baseUrl) { + console.error("KimaiHeatmap: missing data-url attribute"); + return; + } + const timesheetUrl = container.getAttribute("data-timesheet-url") || "/en/timesheet/"; + const weekStart = container.getAttribute("data-week-start") || "monday"; + const projectsJson = container.getAttribute("data-projects"); + const projects = projectsJson ? JSON.parse(projectsJson) : []; + const state = createInitialState(weekStart); + const onCellClick = (dateStr) => { + const daterange = `${dateStr} - ${dateStr}`; + let url = `${timesheetUrl}?daterange=${encodeURIComponent(daterange)}`; + if (state.filters.projectId) { + url += `&projects[]=${state.filters.projectId}`; + } + window.location.href = url; + }; + container.innerHTML = ""; + const wrapper = document.createElement("div"); + wrapper.className = "heatmap-wrapper"; + const svgArea = document.createElement("div"); + svgArea.className = "heatmap-svg-area"; + wrapper.appendChild(svgArea); + const doRender = () => { + if (!state.data) return; + const renderer = getRenderer(state.mode); + renderer.destroy?.(); + renderer.render({ + container: svgArea, + data: state.data, + state, + config: { cellSize: 13, cellGap: 2, marginTop: 20, marginLeft: 30, marginBottom: 4 }, + onCellClick, + emptyMessage: state.filters.projectId ? "No tracking data for this project" : void 0 + }); + renderStats(container, state.data.days); + svgArea.scrollLeft = svgArea.scrollWidth; + }; + if (projects.length > 0) { + const filterDiv = document.createElement("div"); + filterDiv.className = "heatmap-filter"; + const selectEl = document.createElement("select"); + selectEl.className = "form-select form-select-sm"; + selectEl.setAttribute("aria-label", "Filter by project"); + const defaultOpt = document.createElement("option"); + defaultOpt.value = ""; + defaultOpt.textContent = "All Projects"; + selectEl.appendChild(defaultOpt); + for (const p of projects) { + const opt = document.createElement("option"); + opt.value = String(p.id); + opt.textContent = p.name; + selectEl.appendChild(opt); + } + selectEl.addEventListener("change", () => { + const val = selectEl.value; + state.filters.projectId = val ? parseInt(val, 10) : null; + const fetchUrl = val ? `${baseUrl}?project=${val}` : baseUrl; + fetch(fetchUrl).then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); + }).then((data) => { + state.data = data; + doRender(); + }).catch((err) => { + console.error("KimaiHeatmap: failed to load filtered data", err); + }); + }); + filterDiv.appendChild(selectEl); + wrapper.appendChild(filterDiv); + } + container.appendChild(wrapper); + let resizeTimer; + window.addEventListener("resize", () => { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(() => { + if (state.data) doRender(); + }, 200); + }); + fetch(baseUrl).then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); + }).then((data) => { + state.data = data; + doRender(); + }).catch((err) => { + console.error("KimaiHeatmap: failed to load data", err); + svgArea.textContent = "Failed to load heatmap data"; + }); + } + return __toCommonJS(heatmap_exports); +})(); +//# sourceMappingURL=heatmap.js.map diff --git a/assets/src/heatmap.ts b/assets/src/heatmap.ts index 8ba5141..fae61ed 100644 --- a/assets/src/heatmap.ts +++ b/assets/src/heatmap.ts @@ -1,305 +1,12 @@ -import { select } from 'd3-selection'; -import { scaleQuantize } from 'd3-scale'; -import { timeMonday, timeSunday, timeDay, timeMonth } from 'd3-time'; -import { timeFormat } from 'd3-time-format'; -import { max } from 'd3-array'; -import type { DayEntry, HeatmapData, HeatmapConfig, ProjectOption } from './types'; +import type { HeatmapData, ProjectOption } from './types'; +import { createInitialState } from './state'; +import type { HeatmapState } from './state'; +import { getRenderer, registerRenderer } from './renderers/registry'; +import { YearModeRenderer } from './renderers/year'; +import { renderStats } from './shared/stats'; -const DEFAULT_CONFIG: HeatmapConfig = { - cellSize: 13, - cellGap: 2, - marginTop: 20, - marginLeft: 30, - marginBottom: 4, -}; - -const FALLBACK_COLORS = ['#9be9a8', '#40c463', '#30a14e', '#216e39']; -const DAY_LABELS_MONDAY = ['Mon', '', 'Wed', '', 'Fri', '', '']; -const DAY_LABELS_SUNDAY = ['Sun', '', 'Tue', '', 'Thu', '', 'Sat']; - -function getDayLabels(weekStart: string): string[] { - return weekStart === 'sunday' ? DAY_LABELS_SUNDAY : DAY_LABELS_MONDAY; -} -const MONTH_FORMAT = timeFormat('%b'); -const DATE_FORMAT = timeFormat('%Y-%m-%d'); -const DISPLAY_FORMAT = timeFormat('%a, %b %-d, %Y'); - -interface DayCell { - date: Date; - dateStr: string; - entry: DayEntry | null; - week: number; - day: number; - isWeekend: boolean; -} - -function resolveColors(container: HTMLElement): string[] { - try { - const style = getComputedStyle(container); - const test = style.getPropertyValue('--tblr-bg-surface'); - if (!test) return FALLBACK_COLORS; - return FALLBACK_COLORS; // Use hardcoded greens — Tabler doesn't expose a green scale via CSS vars - } catch { - return FALLBACK_COLORS; - } -} - -function buildDateMap(days: DayEntry[]): Map { - const map = new Map(); - for (const d of days) { - map.set(d.date, d); - } - return map; -} - -function getWeekInterval(weekStart: string) { - return weekStart === 'sunday' ? timeSunday : timeMonday; -} - -function generateCells( - begin: Date, - end: Date, - dateMap: Map, - weekStart: string = 'monday', -): DayCell[] { - const weekInterval = getWeekInterval(weekStart); - const firstWeekDay = weekInterval.floor(begin); - const cells: DayCell[] = []; - let current = new Date(begin); - - while (current <= end) { - const dateStr = DATE_FORMAT(current); - const weeksSinceStart = weekInterval.count(firstWeekDay, current); - const jsDay = current.getDay(); // 0=Sunday, 6=Saturday - const dayOfWeek = weekStart === 'sunday' - ? jsDay // Sunday=0 already first - : (jsDay + 6) % 7; // Monday=0, Sunday=6 - const isWeekend = jsDay === 0 || jsDay === 6; - - cells.push({ - date: new Date(current), - dateStr, - entry: dateMap.get(dateStr) || null, - week: weeksSinceStart, - day: dayOfWeek, - isWeekend, - }); - - current = timeDay.offset(current, 1); - } - - return cells; -} - -function createTooltip(): HTMLDivElement { - const tip = document.createElement('div'); - tip.className = 'heatmap-tooltip'; - tip.style.display = 'none'; - return tip; -} - -export function calculateStreak(days: DayEntry[]): number { - if (days.length === 0) return 0; - - const tracked = new Set( - days.filter((d) => d.hours > 0).map((d) => d.date), - ); - if (tracked.size === 0) return 0; - - const today = new Date(); - today.setHours(0, 0, 0, 0); - let current = new Date(today); - - // If today has no entry, start from yesterday - if (!tracked.has(DATE_FORMAT(current))) { - current = timeDay.offset(current, -1); - } - - let streak = 0; - while (tracked.has(DATE_FORMAT(current))) { - streak++; - current = timeDay.offset(current, -1); - } - - return streak; -} - -export interface HeatmapStats { - totalHours: number; - avgHours: number; - busiestDay: { date: string; hours: number } | null; -} - -export function calculateStats(days: DayEntry[]): HeatmapStats { - const withEntries = days.filter((d) => d.hours > 0); - if (withEntries.length === 0) { - return { totalHours: 0, avgHours: 0, busiestDay: null }; - } - - const totalHours = Math.round(withEntries.reduce((sum, d) => sum + d.hours, 0) * 10) / 10; - const avgHours = Math.round((totalHours / withEntries.length) * 10) / 10; - const busiest = withEntries.reduce((best, d) => (d.hours > best.hours ? d : best)); - - return { - totalHours, - avgHours, - busiestDay: { date: busiest.date, hours: busiest.hours }, - }; -} - -function renderStats(container: HTMLElement, days: DayEntry[]): void { - // Remove existing stats - const existing = container.querySelector('.heatmap-stats'); - if (existing) existing.remove(); - - const streak = calculateStreak(days); - const stats = calculateStats(days); - - const statsDiv = document.createElement('div'); - statsDiv.className = 'heatmap-stats'; - - const parts: string[] = []; - parts.push(`\u{1F525} ${streak} days`); - parts.push(`Total: ${stats.totalHours}h`); - parts.push(`Avg: ${stats.avgHours}h/day`); - - if (stats.busiestDay) { - const d = new Date(stats.busiestDay.date + 'T00:00:00'); - const label = DISPLAY_FORMAT(d); - parts.push(`Busiest: ${label} \u2014 ${stats.busiestDay.hours.toFixed(1)}h`); - } - - statsDiv.innerHTML = parts.join(''); - container.appendChild(statsDiv); -} - -export function renderHeatmap( - container: HTMLElement, - data: HeatmapData, - config: HeatmapConfig = DEFAULT_CONFIG, - onCellClick?: (dateStr: string) => void, - emptyMessage?: string, - weekStart: string = 'monday', -): void { - container.innerHTML = ''; - - if (!data.days || data.days.length === 0) { - const msg = document.createElement('div'); - msg.textContent = emptyMessage || 'No tracking data available'; - msg.style.padding = '1rem'; - msg.style.color = 'var(--tblr-secondary, #6c757d)'; - container.appendChild(msg); - return; - } - - const dateMap = buildDateMap(data.days); - const begin = new Date(data.range.begin); - const end = new Date(data.range.end); - const cells = generateCells(begin, end, dateMap, weekStart); - - const maxHours = max(data.days, (d) => d.hours) || 1; - const colors = resolveColors(container); - - const colorScale = scaleQuantize() - .domain([0, maxHours]) - .range(colors); - - const { cellGap, marginTop, marginLeft, marginBottom } = config; - const numWeeks = (max(cells, (c) => c.week) ?? 0) + 1; - - // Compute cell size to fill available width, capped at 16px - const containerWidth = container.clientWidth || 800; - const maxCellSize = 22; - const minCellSize = 10; - const cellSize = Math.min(maxCellSize, Math.max(minCellSize, Math.floor((containerWidth - marginLeft) / numWeeks) - cellGap)); - const step = cellSize + cellGap; - const svgWidth = marginLeft + numWeeks * step; - const svgHeight = marginTop + 7 * step + marginBottom; - - const wrapper = document.createElement('div'); - wrapper.style.maxWidth = `${svgWidth}px`; - wrapper.style.margin = '0 auto'; - container.appendChild(wrapper); - - const svg = select(wrapper) - .append('svg') - .attr('width', svgWidth) - .attr('height', svgHeight) - .attr('class', 'heatmap-svg'); - - // Month labels - const weekInterval = getWeekInterval(weekStart); - const months: { date: Date; week: number }[] = []; - const firstWeekDay = weekInterval.floor(begin); - timeMonth.range(timeMonth.ceil(begin), end).forEach((m) => { - months.push({ - date: m, - week: weekInterval.count(firstWeekDay, m), - }); - }); - - svg - .selectAll('.month-label') - .data(months) - .join('text') - .attr('class', 'heatmap-label month-label') - .attr('x', (d) => marginLeft + d.week * step) - .attr('y', marginTop - 6) - .text((d) => MONTH_FORMAT(d.date)); - - // Day labels - svg - .selectAll('.day-label') - .data(getDayLabels(weekStart)) - .join('text') - .attr('class', 'heatmap-label day-label') - .attr('x', marginLeft - 6) - .attr('y', (_d, i) => marginTop + i * step + cellSize - 2) - .attr('text-anchor', 'end') - .text((d) => d); - - // Tooltip (fixed positioning to escape overflow clipping) - // Remove any stale tooltip from previous renders - document.querySelectorAll('.heatmap-tooltip').forEach(el => el.remove()); - const tooltip = createTooltip(); - tooltip.style.position = 'fixed'; - document.body.appendChild(tooltip); - - // Cells - svg - .selectAll('.heatmap-cell') - .data(cells) - .join('rect') - .attr('class', (d) => { - let cls = 'heatmap-cell'; - if (!d.entry) cls += ' heatmap-empty'; - if (d.isWeekend) cls += ' heatmap-weekend'; - return cls; - }) - .attr('x', (d) => marginLeft + d.week * step) - .attr('y', (d) => marginTop + d.day * step) - .attr('width', cellSize) - .attr('height', cellSize) - .attr('fill', (d) => (d.entry ? colorScale(d.entry.hours) : '')) - .on('mouseenter', function (event: MouseEvent, d: DayCell) { - const hours = d.entry ? d.entry.hours.toFixed(1) : '0.0'; - const count = d.entry ? d.entry.count : 0; - tooltip.innerHTML = `${DISPLAY_FORMAT(d.date)}
${hours}h (${count} entries)`; - tooltip.style.display = 'block'; - - const rect = (event.target as SVGRectElement).getBoundingClientRect(); - tooltip.style.left = `${rect.left + cellSize / 2}px`; - tooltip.style.top = `${rect.top - tooltip.offsetHeight - 8}px`; - }) - .on('mouseleave', function () { - tooltip.style.display = 'none'; - }) - .on('click', function (_event: MouseEvent, d: DayCell) { - if (!onCellClick) return; - onCellClick(d.dateStr); - }); - -} +// Register built-in renderers +registerRenderer(new YearModeRenderer()); export function init(container: HTMLElement): void { const baseUrl = container.getAttribute('data-url'); @@ -313,13 +20,13 @@ export function init(container: HTMLElement): void { const projectsJson = container.getAttribute('data-projects'); const projects: ProjectOption[] = projectsJson ? JSON.parse(projectsJson) : []; - let activeProjectId: number | null = null; + const state: HeatmapState = createInitialState(weekStart); const onCellClick = (dateStr: string): void => { const daterange = `${dateStr} - ${dateStr}`; let url = `${timesheetUrl}?daterange=${encodeURIComponent(daterange)}`; - if (activeProjectId) { - url += `&projects[]=${activeProjectId}`; + if (state.filters.projectId) { + url += `&projects[]=${state.filters.projectId}`; } window.location.href = url; }; @@ -333,13 +40,19 @@ export function init(container: HTMLElement): void { svgArea.className = 'heatmap-svg-area'; wrapper.appendChild(svgArea); - // Shared state for current data (used by resize re-render and filter) - let currentData: HeatmapData | null = null; - - const doRender = (data: HeatmapData, emptyMsg?: string) => { - currentData = data; - renderHeatmap(svgArea, data, DEFAULT_CONFIG, onCellClick, emptyMsg, weekStart); - renderStats(container, data.days); + const doRender = () => { + if (!state.data) return; + const renderer = getRenderer(state.mode); + renderer.destroy?.(); + renderer.render({ + container: svgArea, + data: state.data, + state, + config: { cellSize: 13, cellGap: 2, marginTop: 20, marginLeft: 30, marginBottom: 4 }, + onCellClick, + emptyMessage: state.filters.projectId ? 'No tracking data for this project' : undefined, + }); + renderStats(container, state.data.days); svgArea.scrollLeft = svgArea.scrollWidth; }; @@ -348,25 +61,25 @@ export function init(container: HTMLElement): void { const filterDiv = document.createElement('div'); filterDiv.className = 'heatmap-filter'; - const select = document.createElement('select'); - select.className = 'form-select form-select-sm'; - select.setAttribute('aria-label', 'Filter by project'); + const selectEl = document.createElement('select'); + selectEl.className = 'form-select form-select-sm'; + selectEl.setAttribute('aria-label', 'Filter by project'); const defaultOpt = document.createElement('option'); defaultOpt.value = ''; defaultOpt.textContent = 'All Projects'; - select.appendChild(defaultOpt); + selectEl.appendChild(defaultOpt); for (const p of projects) { const opt = document.createElement('option'); opt.value = String(p.id); opt.textContent = p.name; - select.appendChild(opt); + selectEl.appendChild(opt); } - select.addEventListener('change', () => { - const val = select.value; - activeProjectId = val ? parseInt(val, 10) : null; + selectEl.addEventListener('change', () => { + const val = selectEl.value; + state.filters.projectId = val ? parseInt(val, 10) : null; const fetchUrl = val ? `${baseUrl}?project=${val}` : baseUrl; fetch(fetchUrl) @@ -375,14 +88,15 @@ export function init(container: HTMLElement): void { return res.json() as Promise; }) .then(data => { - doRender(data, 'No tracking data for this project'); + state.data = data; + doRender(); }) .catch(err => { console.error('KimaiHeatmap: failed to load filtered data', err); }); }); - filterDiv.appendChild(select); + filterDiv.appendChild(selectEl); wrapper.appendChild(filterDiv); } @@ -393,7 +107,7 @@ export function init(container: HTMLElement): void { window.addEventListener('resize', () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { - if (currentData) doRender(currentData); + if (state.data) doRender(); }, 200); }); @@ -404,7 +118,8 @@ export function init(container: HTMLElement): void { return res.json() as Promise; }) .then(data => { - doRender(data); + state.data = data; + doRender(); }) .catch(err => { console.error('KimaiHeatmap: failed to load data', err); diff --git a/assets/src/renderers/types.ts b/assets/src/renderers/types.ts index e3b0882..f57378f 100644 --- a/assets/src/renderers/types.ts +++ b/assets/src/renderers/types.ts @@ -7,6 +7,7 @@ export interface RenderContext { state: HeatmapState; config: HeatmapConfig; onCellClick?: (dateStr: string) => void; + emptyMessage?: string; } export interface ModeRenderer { diff --git a/assets/src/renderers/year.ts b/assets/src/renderers/year.ts new file mode 100644 index 0000000..6914805 --- /dev/null +++ b/assets/src/renderers/year.ts @@ -0,0 +1,131 @@ +import { select } from 'd3-selection'; +import { timeMonth } from 'd3-time'; +import { max } from 'd3-array'; +import type { ModeRenderer, RenderContext } from './types'; +import { createTooltip, showTooltip, hideTooltip } from '../shared/tooltip'; +import { buildColorScale } from '../shared/color-scale'; +import { + buildDateMap, generateCells, getWeekInterval, getDayLabels, + MONTH_FORMAT, DISPLAY_FORMAT, type DayCell, +} from '../shared/date-utils'; + +export class YearModeRenderer implements ModeRenderer { + readonly mode = 'year'; + private tooltip: HTMLDivElement | null = null; + + render(ctx: RenderContext): void { + ctx.container.innerHTML = ''; + + // Handle empty data + if (!ctx.data.days || ctx.data.days.length === 0) { + const msg = document.createElement('div'); + msg.textContent = ctx.emptyMessage || 'No tracking data available'; + msg.style.padding = '1rem'; + msg.style.color = 'var(--tblr-secondary, #6c757d)'; + ctx.container.appendChild(msg); + return; + } + + // Destroy previous tooltip + this.destroy(); + this.tooltip = createTooltip(); + + const weekStart = ctx.state.weekStart; + const dateMap = buildDateMap(ctx.data.days); + const begin = new Date(ctx.data.range.begin); + const end = new Date(ctx.data.range.end); + const cells = generateCells(begin, end, dateMap, weekStart); + + // Build color scale using state metric (hours vs count) + const colorScale = buildColorScale(ctx.data.days, ctx.state.metric); + + const { cellGap, marginTop, marginLeft, marginBottom } = ctx.config; + const numWeeks = (max(cells, (c) => c.week) ?? 0) + 1; + + // Compute cell size to fill available width, capped + const containerWidth = ctx.container.clientWidth || 800; + const maxCellSize = 22; + const minCellSize = 10; + const cellSize = Math.min(maxCellSize, Math.max(minCellSize, Math.floor((containerWidth - marginLeft) / numWeeks) - cellGap)); + const step = cellSize + cellGap; + const svgWidth = marginLeft + numWeeks * step; + const svgHeight = marginTop + 7 * step + marginBottom; + + const wrapper = document.createElement('div'); + wrapper.style.maxWidth = `${svgWidth}px`; + wrapper.style.margin = '0 auto'; + ctx.container.appendChild(wrapper); + + const svg = select(wrapper) + .append('svg') + .attr('width', svgWidth) + .attr('height', svgHeight) + .attr('class', 'heatmap-svg'); + + // Month labels + const weekInterval = getWeekInterval(weekStart); + const months: { date: Date; week: number }[] = []; + const firstWeekDay = weekInterval.floor(begin); + timeMonth.range(timeMonth.ceil(begin), end).forEach((m) => { + months.push({ date: m, week: weekInterval.count(firstWeekDay, m) }); + }); + + svg.selectAll('.month-label') + .data(months) + .join('text') + .attr('class', 'heatmap-label month-label') + .attr('x', (d) => marginLeft + d.week * step) + .attr('y', marginTop - 6) + .text((d) => MONTH_FORMAT(d.date)); + + // Day labels + svg.selectAll('.day-label') + .data(getDayLabels(weekStart)) + .join('text') + .attr('class', 'heatmap-label day-label') + .attr('x', marginLeft - 6) + .attr('y', (_d, i) => marginTop + i * step + cellSize - 2) + .attr('text-anchor', 'end') + .text((d) => d); + + // Cells with tooltip and click + const tooltip = this.tooltip; + svg.selectAll('.heatmap-cell') + .data(cells) + .join('rect') + .attr('class', (d) => { + let cls = 'heatmap-cell'; + if (!d.entry) cls += ' heatmap-empty'; + if (d.isWeekend) cls += ' heatmap-weekend'; + return cls; + }) + .attr('x', (d) => marginLeft + d.week * step) + .attr('y', (d) => marginTop + d.day * step) + .attr('width', cellSize) + .attr('height', cellSize) + .attr('fill', (d) => { + if (!d.entry) return ''; + const val = ctx.state.metric === 'hours' ? d.entry.hours : d.entry.count; + return colorScale(val); + }) + .on('mouseenter', function (event: MouseEvent, d: DayCell) { + const hours = d.entry ? d.entry.hours.toFixed(1) : '0.0'; + const count = d.entry ? d.entry.count : 0; + const html = `${DISPLAY_FORMAT(d.date)}
${hours}h (${count} entries)`; + const rect = (event.target as SVGRectElement).getBoundingClientRect(); + showTooltip(tooltip, html, rect, cellSize); + }) + .on('mouseleave', function () { + hideTooltip(tooltip); + }) + .on('click', function (_event: MouseEvent, d: DayCell) { + if (!ctx.onCellClick) return; + ctx.onCellClick(d.dateStr); + }); + } + + destroy(): void { + this.tooltip?.remove(); + this.tooltip = null; + } +}