@protomaze/time 1.5.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ /** A font for the slider's labels and tooltips. Any part left out keeps its default. */
2
+ export type TimeSliderFont = {
3
+ /** CSS font-family, e.g. '"IP Klartext", sans-serif'. Default 'monospace'. */
4
+ family?: string;
5
+ /** CSS font-weight, e.g. 500 or 'bold'. Default 'normal'. */
6
+ weight?: string | number;
7
+ /** Size in pixels. Default 12. */
8
+ size?: number;
9
+ };
10
+ export declare const DEFAULT_FONT: Required<TimeSliderFont>;
11
+ export declare function resolveFont(font?: TimeSliderFont, fallback?: Required<TimeSliderFont>): Required<TimeSliderFont>;
12
+ /** The CSS / canvas font shorthand, e.g. '500 12px "IP Klartext", sans-serif'. */
13
+ export declare function toCssFont(font: Required<TimeSliderFont>): string;
package/common/Font.js ADDED
@@ -0,0 +1 @@
1
+ export const DEFAULT_FONT={family:"monospace",weight:"normal",size:12};export function resolveFont(e,i=DEFAULT_FONT){return{family:e?.family??i.family,weight:e?.weight??i.weight,size:e?.size??i.size}}export function toCssFont(e){return`${e.weight} ${e.size}px ${e.family}`}
@@ -0,0 +1,11 @@
1
+ /** The device pixel ratio the canvases and renderers draw at; 1 outside a browser. */
2
+ export declare function getDevicePixelRatio(): number;
3
+ /**
4
+ * A line of `cssWidth` CSS pixels centred near `cssPosition`, moved so it covers whole device
5
+ * pixels: it is round(cssWidth * dpr) device pixels wide (at least 1) wherever it lands, instead
6
+ * of being antialiased across one more. Both results are in CSS pixels, for a context scaled by dpr.
7
+ */
8
+ export declare function snapLineToDevicePixels(cssPosition: number, cssWidth: number, dpr: number): {
9
+ position: number;
10
+ width: number;
11
+ };
@@ -0,0 +1 @@
1
+ export function getDevicePixelRatio(){return"undefined"!=typeof window&&window.devicePixelRatio>0?window.devicePixelRatio:1}export function snapLineToDevicePixels(e,i,n){const o=Math.max(1,Math.round(i*n));return{position:(Math.round(e*n-.5*o)+.5*o)/n,width:o/n}}
@@ -46,9 +46,34 @@ export declare function formatDuration(durationOrRange: number | TimeSliderTimeR
46
46
  export declare function formatDate(time: number, padding?: boolean, locale?: string): string;
47
47
  export declare function formatDateTime(time: number, locale?: string): string;
48
48
  export declare function formatDateWithWeekDay(time: number, locale?: string): string;
49
+ /** The units a span is told in, largest first. Calendar units, so a month is a calendar month long. */
50
+ declare const SPAN_UNITS: readonly ["year", "month", "week", "day", "hour", "minute", "second"];
51
+ export type TimeSpanUnit = (typeof SPAN_UNITS)[number];
52
+ export type TimeSpanDescription = {
53
+ /** At most two units, the larger first, with a ~ when rounded: "3 months 2 weeks", "~2 years 5 months". */
54
+ text: string;
55
+ /** False when the text is rounded (it then starts with ~). */
56
+ exact: boolean;
57
+ /** For exactly one of a unit, the unit's name alone ("month"), for phrases like "Next month". */
58
+ unitName?: string;
59
+ };
60
+ /**
61
+ * A span of time as people say it: in its largest calendar unit and the one below, the last one
62
+ * rounded on what is left over and carried into the larger one where it makes a whole one
63
+ * ("12 months" becomes "1 year"), with a ~ wherever it isn't exact. Localized with
64
+ * Intl.NumberFormat unit names.
65
+ */
66
+ export declare function describeTimeSpan(startTime: number, endTime: number, locale?: string): TimeSpanDescription;
67
+ /**
68
+ * The dates a range covers, e.g. "Sep 4, 2012 – Sep 30, 2025". An end at local midnight is
69
+ * exclusive, so the last day shown is the day before it. Ranges that aren't whole days and
70
+ * last less than a week also show the time of day.
71
+ */
72
+ export declare function formatTimeSpanDates(startTime: number, endTime: number, locale?: string): string;
49
73
  export declare function formatDateRange(range: TimeSliderTimeRangeArray, locale?: string): string;
50
74
  export declare function formatTime(time: number): string;
51
75
  export declare function getTimePartsInHours(time: number): [hoursSinceYear0: number, msSinceHourStart: number];
52
76
  export declare function splitTime3(time: number): [t1: number, t2: number, t3: number];
53
77
  export declare function splitTime2(time: number): [t1: number, t2: number];
54
78
  export declare function getDurationParts(duration: number): [days: number, ms: number];
79
+ export {};
@@ -1 +1 @@
1
- import{CENTURY as e,DECADE as t,MILLENIUM as r,ONE_DAY as a,ONE_HOUR as n,ONE_MINUTE as o,ONE_MONTH as s,ONE_SECOND as u,ONE_WEEK as i,ONE_YEAR as c}from"../ticks/TimeSliderConstants.js";const l=new Date;l.setFullYear(0,0,1),l.setHours(0,0,0,0);export const year0Ms=l.getTime();const g=new Date;g.setFullYear(1e3,0,1),g.setHours(0,0,0,0);export const year1000Ms=g.getTime();export const SPLIT_PART_2=Math.pow(2,22);export const SPLIT_TIME_3=Math.pow(2,14);export const SPLIT_TIME_3_POW2=SPLIT_TIME_3*SPLIT_TIME_3;export const MONTH_NAMES=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];let m;export function setDefaultLocale(e){m=e}export function getDefaultLocale(){return m??("undefined"!=typeof navigator?navigator.language:"en")}const f=new Map;function h(e,t){const r=e??getDefaultLocale(),a=r+JSON.stringify(t);let n=f.get(a);return n||(n=new Intl.DateTimeFormat(r,t),f.set(a,n)),n}const T={en:{year:["year","years"],week:["week","weeks"],day:["day","days"]},de:{year:["Jahr","Jahre"],week:["Woche","Wochen"],day:["Tag","Tage"]},fr:{year:["an","ans"],week:["semaine","semaines"],day:["jour","jours"]},nl:{year:["jaar","jaar"],week:["week","weken"],day:["dag","dagen"]}};export function getStartOfSecond(e){return new Date(e).setMilliseconds(0)}export function getStartOfMinute(e){return new Date(e).setSeconds(0,0)}export function getStartOfHour(e){return new Date(e).setMinutes(0,0,0)}export function getStartOfDay(e){return new Date(e).setHours(0,0,0,0)}export function getStartOfWeek(e){const t=new Date(e),r=t.getDay(),a=t.getDate()-r+(0===r?-6:1);return new Date(t.setDate(a)).setHours(0,0,0,0)}function M(e){const t=new Date(e.valueOf()),r=(e.getDay()+6)%7+1;return t.setDate(t.getDate()-r+4),t}export function getISOWeekNumber(e){const t=M(e),r=new Date(t.getFullYear(),0,1),n=new Date(t.getFullYear(),t.getMonth(),t.getDate()),o=Math.round((n.getTime()-r.getTime())/a);return Math.floor(o/7)+1}export function getISOWeekYear(e){return M(e).getFullYear()}const D={en:"W",de:"KW",fr:"S",nl:"Wk"};export function getWeekLabelPrefix(e){const t=e.split("-")[0].toLowerCase();return D[t]??D.en}export function getStartOfMonth(e){const t=new Date(e);return t.setDate(1),t.setHours(0,0,0,0)}export function getStartOfXYears(e,t){const r=new Date(e),a=t*Math.floor(r.getFullYear()/t),n=new Date(a,0,1);n.setFullYear(a);return n.setHours(0,0,0,0)}export function getTimeUnitInMs(l){switch(l){case"year":return c;case"month":return s;case"week":return i;case"day":return a;case"hour":return n;case"minute":return o;case"second":return u;case"decade":return t;case"century":return e;case"millennium":return r}}export function getTimeUnitRange(e,t,r=0){const a=new Date(e),n=new Date(a),o=new Date(a);switch(t){case"year":{const e=a.getFullYear()+r;n.setFullYear(e,0,1),n.setHours(0,0,0,0),o.setFullYear(e+1,0,1),o.setHours(0,0,0,0);break}case"month":{const e=a.getFullYear(),t=a.getMonth()+r;n.setFullYear(e,t,1),n.setHours(0,0,0,0),o.setFullYear(e,t+1,1),o.setHours(0,0,0,0);break}case"week":{const e=(a.getDay()+6)%7;n.setDate(a.getDate()-e+7*r),n.setHours(0,0,0,0),o.setTime(n.getTime()),o.setDate(n.getDate()+7);break}case"day":n.setDate(a.getDate()+r),n.setHours(0,0,0,0),o.setTime(n.getTime()),o.setDate(n.getDate()+1);break;case"hour":n.setTime(a.getTime()),n.setHours(a.getHours()+r,0,0,0),o.setTime(n.getTime()),o.setHours(n.getHours()+1);break;case"minute":n.setTime(a.getTime()),n.setMinutes(a.getMinutes()+r,0,0),o.setTime(n.getTime()),o.setMinutes(n.getMinutes()+1);break;case"second":n.setTime(a.getTime()),n.setSeconds(a.getSeconds()+r,0),o.setTime(n.getTime()),o.setSeconds(n.getSeconds()+1);break;case"decade":{const e=10*Math.floor(a.getFullYear()/10)+10*r;n.setFullYear(e,0,1),n.setHours(0,0,0,0),o.setFullYear(e+10,0,1),o.setHours(0,0,0,0);break}case"century":{const e=100*Math.floor(a.getFullYear()/100)+100*r;n.setFullYear(e,0,1),n.setHours(0,0,0,0),o.setFullYear(e+100,0,1),o.setHours(0,0,0,0);break}case"millennium":{const e=1e3*Math.floor(a.getFullYear()/1e3)+1e3*r;n.setFullYear(e,0,1),n.setHours(0,0,0,0),o.setFullYear(e+1e3,0,1),o.setHours(0,0,0,0);break}default:throw new Error("Invalid category")}return{startTime:n.getTime(),endTime:o.getTime()}}export function getTimeOffset(e,t,r){const a=new Date(e),n=new Date(t);switch(r){case"year":return n.getFullYear()-a.getFullYear();case"month":return 12*(n.getFullYear()-a.getFullYear())+(n.getMonth()-a.getMonth());case"week":const e=6048e5;return Math.floor((n.getTime()-a.getTime())/e);case"day":const t=864e5;return Math.floor((n.getTime()-a.getTime())/t);case"hour":const r=36e5;return Math.floor((n.getTime()-a.getTime())/r);case"minute":const o=6e4;return Math.floor((n.getTime()-a.getTime())/o);case"second":const s=1e3;return Math.floor((n.getTime()-a.getTime())/s);case"decade":return Math.floor(n.getFullYear()/10)-Math.floor(a.getFullYear()/10);case"century":return Math.floor(n.getFullYear()/100)-Math.floor(a.getFullYear()/100);case"millennium":return Math.floor(n.getFullYear()/1e3)-Math.floor(a.getFullYear()/1e3);default:throw new Error("Invalid category")}}export function getMatchingTimeUnit(e,t){const r=["millennium","century","decade","year","month","week","day","hour","minute","second"];for(const a of r){const r=getTimeUnitRange(e,a),n=r.endTime-r.startTime;if(0===n)continue;const o=.1*n,s=Math.abs(e-r.startTime)<=o,u=Math.abs(t-r.endTime)<=o;if(s&&u)return a}}export function getLargeTimeUnitRange(e,t,r=0){const a=new Date(e),n=new Date(a),o=new Date(a),s=Math.floor(n.getFullYear()/t)*t+r*t;return n.setFullYear(s,0,1),n.setHours(0,0,0,0),o.setFullYear(s+t-1,11,31),o.setHours(23,59,59,999),{startTime:n.getTime(),endTime:o.getTime()}}export function formatDuration(t,r){const a=function(e){const t=e.split("-")[0].toLowerCase();return T[t]??T.en}(r??getDefaultLocale()),o="number"==typeof t?t:Array.isArray(t)?t[1]-t[0]:t.endTime-t.startTime,s=Math.floor(o/1e3),l=Math.floor(s/60),g=Math.floor(l/60),m=Math.floor(g/24),f=Math.floor(m/365),h=Math.floor(o%1e3),M=s%60,D=l%60,d=g%24,w=m%365,y=[];if(f>0&&y.push(`${f} ${f>1?a.year[1]:a.year[0]}`),f<1||w>Math.ceil(f/4))if(w%7==0&&0===d&&0===D&&0===M&&w>0&&o<e){const e=Math.floor(w/7);y.push(`${e} ${e>1?a.week[1]:a.week[0]}`)}else w>0&&o<e&&y.push(`${w} ${w>1?a.day[1]:a.day[0]}`),d>0&&o<c&&y.push(`${d}h`),D>0&&o<i&&y.push(`${D}m`),M>0&&o<n&&y.push(`${M}s`),o<5*u&&o>=1&&y.push(`${h}ms`),o<1&&y.push("<1ms");return y.join(" ")}export function formatDate(e,t,r){const a=new Date(e);return h(r,{day:t?"2-digit":"numeric",month:"short",year:"numeric"}).format(a)}export function formatDateTime(e,t){const r="number"==typeof e?new Date(e):e;return`${h(t,{weekday:"short",day:"2-digit",month:"short",year:"numeric"}).format(r)} ${formatTime(r.getTime())}`}export function formatDateWithWeekDay(e,t){const r="number"==typeof e?new Date(e):e;return h(t,{weekday:"short",day:"2-digit",month:"short",year:"numeric"}).format(r)}export function formatDateRange(e,t){const[r,a]=e,n=new Date(r),o=new Date(a),s=new Date(a-1),u=1===n.getDate()&&1===o.getDate(),i=u&&0===n.getMonth()&&0===o.getMonth(),c=h(t,{day:"2-digit",month:"short",year:"numeric"}),l=h(t,{month:"short",year:"numeric"}),g=h(t,{year:"numeric"});return n.getFullYear()===s.getFullYear()?n.getMonth()===s.getMonth()?n.getDate()===s.getDate()?c.format(s):u?l.format(s):c.formatRange(n,s):i?g.format(s):c.formatRange(n,s):i?g.formatRange(n,s):c.formatRange(n,s)}export function formatTime(e){const t=new Date(e),r=t.getHours(),a=t.getMinutes(),n=t.getSeconds();return(r<10?"0":"")+r+":"+(a<10?"0":"")+a+":"+(n<10?"0":"")+n}export function getTimePartsInHours(e){return[Math.floor(e/n),e%n]}export function splitTime3(e){const t=Math.floor(e/SPLIT_TIME_3_POW2),r=e%SPLIT_TIME_3_POW2;return[t,Math.floor(r/SPLIT_TIME_3),r%SPLIT_TIME_3]}export function splitTime2(e){return[Math.floor(e/SPLIT_PART_2),e%SPLIT_PART_2]}export function getDurationParts(e){return[Math.floor(e/a),e%a]}
1
+ import{CENTURY as e,DECADE as t,MILLENIUM as r,ONE_DAY as n,ONE_HOUR as a,ONE_MINUTE as o,ONE_MONTH as s,ONE_SECOND as u,ONE_WEEK as i,ONE_YEAR as c}from"../ticks/TimeSliderConstants.js";const l=new Date;l.setFullYear(0,0,1),l.setHours(0,0,0,0);export const year0Ms=l.getTime();const g=new Date;g.setFullYear(1e3,0,1),g.setHours(0,0,0,0);export const year1000Ms=g.getTime();export const SPLIT_PART_2=Math.pow(2,22);export const SPLIT_TIME_3=Math.pow(2,14);export const SPLIT_TIME_3_POW2=SPLIT_TIME_3*SPLIT_TIME_3;export const MONTH_NAMES=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];let m;export function setDefaultLocale(e){m=e}export function getDefaultLocale(){return m??("undefined"!=typeof navigator?navigator.language:"en")}const f=new Map;function h(e,t){const r=e??getDefaultLocale(),n=r+JSON.stringify(t);let a=f.get(n);return a||(a=new Intl.DateTimeFormat(r,t),f.set(n,a)),a}const T={en:{year:["year","years"],week:["week","weeks"],day:["day","days"]},de:{year:["Jahr","Jahre"],week:["Woche","Wochen"],day:["Tag","Tage"]},fr:{year:["an","ans"],week:["semaine","semaines"],day:["jour","jours"]},nl:{year:["jaar","jaar"],week:["week","weken"],day:["dag","dagen"]}};export function getStartOfSecond(e){return new Date(e).setMilliseconds(0)}export function getStartOfMinute(e){return new Date(e).setSeconds(0,0)}export function getStartOfHour(e){return new Date(e).setMinutes(0,0,0)}export function getStartOfDay(e){return new Date(e).setHours(0,0,0,0)}export function getStartOfWeek(e){const t=new Date(e),r=t.getDay(),n=t.getDate()-r+(0===r?-6:1);return new Date(t.setDate(n)).setHours(0,0,0,0)}function M(e){const t=new Date(e.valueOf()),r=(e.getDay()+6)%7+1;return t.setDate(t.getDate()-r+4),t}export function getISOWeekNumber(e){const t=M(e),r=new Date(t.getFullYear(),0,1),a=new Date(t.getFullYear(),t.getMonth(),t.getDate()),o=Math.round((a.getTime()-r.getTime())/n);return Math.floor(o/7)+1}export function getISOWeekYear(e){return M(e).getFullYear()}const D={en:"W",de:"KW",fr:"S",nl:"Wk"};export function getWeekLabelPrefix(e){const t=e.split("-")[0].toLowerCase();return D[t]??D.en}export function getStartOfMonth(e){const t=new Date(e);return t.setDate(1),t.setHours(0,0,0,0)}export function getStartOfXYears(e,t){const r=new Date(e),n=t*Math.floor(r.getFullYear()/t),a=new Date(n,0,1);a.setFullYear(n);return a.setHours(0,0,0,0)}export function getTimeUnitInMs(l){switch(l){case"year":return c;case"month":return s;case"week":return i;case"day":return n;case"hour":return a;case"minute":return o;case"second":return u;case"decade":return t;case"century":return e;case"millennium":return r}}export function getTimeUnitRange(e,t,r=0){const n=new Date(e),a=new Date(n),o=new Date(n);switch(t){case"year":{const e=n.getFullYear()+r;a.setFullYear(e,0,1),a.setHours(0,0,0,0),o.setFullYear(e+1,0,1),o.setHours(0,0,0,0);break}case"month":{const e=n.getFullYear(),t=n.getMonth()+r;a.setFullYear(e,t,1),a.setHours(0,0,0,0),o.setFullYear(e,t+1,1),o.setHours(0,0,0,0);break}case"week":{const e=(n.getDay()+6)%7;a.setDate(n.getDate()-e+7*r),a.setHours(0,0,0,0),o.setTime(a.getTime()),o.setDate(a.getDate()+7);break}case"day":a.setDate(n.getDate()+r),a.setHours(0,0,0,0),o.setTime(a.getTime()),o.setDate(a.getDate()+1);break;case"hour":a.setTime(n.getTime()),a.setHours(n.getHours()+r,0,0,0),o.setTime(a.getTime()),o.setHours(a.getHours()+1);break;case"minute":a.setTime(n.getTime()),a.setMinutes(n.getMinutes()+r,0,0),o.setTime(a.getTime()),o.setMinutes(a.getMinutes()+1);break;case"second":a.setTime(n.getTime()),a.setSeconds(n.getSeconds()+r,0),o.setTime(a.getTime()),o.setSeconds(a.getSeconds()+1);break;case"decade":{const e=10*Math.floor(n.getFullYear()/10)+10*r;a.setFullYear(e,0,1),a.setHours(0,0,0,0),o.setFullYear(e+10,0,1),o.setHours(0,0,0,0);break}case"century":{const e=100*Math.floor(n.getFullYear()/100)+100*r;a.setFullYear(e,0,1),a.setHours(0,0,0,0),o.setFullYear(e+100,0,1),o.setHours(0,0,0,0);break}case"millennium":{const e=1e3*Math.floor(n.getFullYear()/1e3)+1e3*r;a.setFullYear(e,0,1),a.setHours(0,0,0,0),o.setFullYear(e+1e3,0,1),o.setHours(0,0,0,0);break}default:throw new Error("Invalid category")}return{startTime:a.getTime(),endTime:o.getTime()}}export function getTimeOffset(e,t,r){const n=new Date(e),a=new Date(t);switch(r){case"year":return a.getFullYear()-n.getFullYear();case"month":return 12*(a.getFullYear()-n.getFullYear())+(a.getMonth()-n.getMonth());case"week":const e=6048e5;return Math.floor((a.getTime()-n.getTime())/e);case"day":const t=864e5;return Math.floor((a.getTime()-n.getTime())/t);case"hour":const r=36e5;return Math.floor((a.getTime()-n.getTime())/r);case"minute":const o=6e4;return Math.floor((a.getTime()-n.getTime())/o);case"second":const s=1e3;return Math.floor((a.getTime()-n.getTime())/s);case"decade":return Math.floor(a.getFullYear()/10)-Math.floor(n.getFullYear()/10);case"century":return Math.floor(a.getFullYear()/100)-Math.floor(n.getFullYear()/100);case"millennium":return Math.floor(a.getFullYear()/1e3)-Math.floor(n.getFullYear()/1e3);default:throw new Error("Invalid category")}}export function getMatchingTimeUnit(e,t){const r=["millennium","century","decade","year","month","week","day","hour","minute","second"];for(const n of r){const r=getTimeUnitRange(e,n),a=r.endTime-r.startTime;if(0===a)continue;const o=.1*a,s=Math.abs(e-r.startTime)<=o,u=Math.abs(t-r.endTime)<=o;if(s&&u)return n}}export function getLargeTimeUnitRange(e,t,r=0){const n=new Date(e),a=new Date(n),o=new Date(n),s=Math.floor(a.getFullYear()/t)*t+r*t;return a.setFullYear(s,0,1),a.setHours(0,0,0,0),o.setFullYear(s+t-1,11,31),o.setHours(23,59,59,999),{startTime:a.getTime(),endTime:o.getTime()}}export function formatDuration(t,r){const n=function(e){const t=e.split("-")[0].toLowerCase();return T[t]??T.en}(r??getDefaultLocale()),o="number"==typeof t?t:Array.isArray(t)?t[1]-t[0]:t.endTime-t.startTime,s=Math.floor(o/1e3),l=Math.floor(s/60),g=Math.floor(l/60),m=Math.floor(g/24),f=Math.floor(m/365),h=Math.floor(o%1e3),M=s%60,D=l%60,d=g%24,p=m%365,y=[];if(f>0&&y.push(`${f} ${f>1?n.year[1]:n.year[0]}`),f<1||p>Math.ceil(f/4))if(p%7==0&&0===d&&0===D&&0===M&&p>0&&o<e){const e=Math.floor(p/7);y.push(`${e} ${e>1?n.week[1]:n.week[0]}`)}else p>0&&o<e&&y.push(`${p} ${p>1?n.day[1]:n.day[0]}`),d>0&&o<c&&y.push(`${d}h`),D>0&&o<i&&y.push(`${D}m`),M>0&&o<a&&y.push(`${M}s`),o<5*u&&o>=1&&y.push(`${h}ms`),o<1&&y.push("<1ms");return y.join(" ")}export function formatDate(e,t,r){const n=new Date(e);return h(r,{day:t?"2-digit":"numeric",month:"short",year:"numeric"}).format(n)}export function formatDateTime(e,t){const r="number"==typeof e?new Date(e):e;return`${h(t,{weekday:"short",day:"2-digit",month:"short",year:"numeric"}).format(r)} ${formatTime(r.getTime())}`}export function formatDateWithWeekDay(e,t){const r="number"==typeof e?new Date(e):e;return h(t,{weekday:"short",day:"2-digit",month:"short",year:"numeric"}).format(r)}const d=["year","month","week","day","hour","minute","second"],p=new Map;function y(e,t){const r=e+":"+t;let n=p.get(r);return n||(n=new Intl.NumberFormat(e,{style:"unit",unit:t,unitDisplay:"long"}),p.set(r,n)),n}function w(e,t,r){const n=new Date(e);switch(t){case"year":n.setFullYear(n.getFullYear()+r);break;case"month":n.setMonth(n.getMonth()+r);break;case"week":n.setDate(n.getDate()+7*r);break;case"day":n.setDate(n.getDate()+r);break;case"hour":return e+r*a;case"minute":return e+r*o;case"second":return e+r*u}return n.getTime()}function x(e){return"second"===e?0:1e3}function F(e,t,r){let n=0;for(;w(e,r,n+1)<=t+x(r);)n++;return n}export function describeTimeSpan(e,t,r){const n=r??getDefaultLocale(),a=[];let o=e;for(const e of d){const r=F(o,t,e);if((0!==r||0!==a.length)&&(a.push([e,r]),o=w(o,e,r),2===a.length))break}0===a.length&&a.push(["second",0]);const s=t-o,u=a[a.length-1],i=Math.abs(s)<=x(u[0]);if(s>=(w(o,u[0],1)-o)/2&&u[1]++,2===a.length){const[[t,r],[o,s]]=a,u=w(e,t,r),i=w(u,t,1),c=.5*(w(u,o,1)-u);if(w(u,o,s)>=i-c){return{text:`~${describeTimeSpan(e,i,n).text.replace(/^~/,"")}`,exact:!1}}}const c=a.filter(([,e])=>e>0);0===c.length&&c.push(a[0]);const l=c.map(([e,t])=>y(n,e).format(t)).join(" ");return{text:i?l:`~${l}`,exact:i,unitName:i&&1===c.length&&1===c[0][1]?y(n,c[0][0]).formatToParts(1).filter(e=>"unit"===e.type).map(e=>e.value).join(""):void 0}}export function formatTimeSpanDates(e,t,r){const n=e=>{const t=new Date(e);return 0===t.getHours()&&0===t.getMinutes()&&0===t.getSeconds()&&0===t.getMilliseconds()},a=n(e)&&n(t),o=a&&t>e?t-1:t;return h(r,!a&&t-e<i?{day:"numeric",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"}:{day:"numeric",month:"short",year:"numeric"}).formatRange(new Date(e),new Date(Math.max(e,o)))}export function formatDateRange(e,t){const[r,n]=e,a=new Date(r),o=new Date(n),s=new Date(n-1),u=1===a.getDate()&&1===o.getDate(),i=u&&0===a.getMonth()&&0===o.getMonth(),c=h(t,{day:"2-digit",month:"short",year:"numeric"}),l=h(t,{month:"short",year:"numeric"}),g=h(t,{year:"numeric"});return a.getFullYear()===s.getFullYear()?a.getMonth()===s.getMonth()?a.getDate()===s.getDate()?c.format(s):u?l.format(s):c.formatRange(a,s):i?g.format(s):c.formatRange(a,s):i?g.formatRange(a,s):c.formatRange(a,s)}export function formatTime(e){const t=new Date(e),r=t.getHours(),n=t.getMinutes(),a=t.getSeconds();return(r<10?"0":"")+r+":"+(n<10?"0":"")+n+":"+(a<10?"0":"")+a}export function getTimePartsInHours(e){return[Math.floor(e/a),e%a]}export function splitTime3(e){const t=Math.floor(e/SPLIT_TIME_3_POW2),r=e%SPLIT_TIME_3_POW2;return[t,Math.floor(r/SPLIT_TIME_3),r%SPLIT_TIME_3]}export function splitTime2(e){return[Math.floor(e/SPLIT_PART_2),e%SPLIT_PART_2]}export function getDurationParts(e){return[Math.floor(e/n),e%n]}
@@ -1,10 +1,11 @@
1
- import { TimeUnit } from '../common/TimeTools';
1
+ import { TimeSpanDescription, TimeUnit } from '../common/TimeTools';
2
2
  import { TimeSliderElement } from "../data/TimeSliderElement";
3
3
  import { TimeSliderRange, TimeSliderTimeRange, TimeSliderTimeRangeWithDuration, TimeSliderValueRange } from "../data/TimeSliderRange";
4
4
  import { TypedEventEmitter } from "../event/TypedEventEmitter";
5
5
  import { TimeSliderLayer } from "../layer/TimeSliderLayer";
6
6
  import { TimeSliderLayerLoader } from "../layer/TimeSliderLayerLoader";
7
7
  import { TimeTicksBar } from "../ticks/TimeTicksBar";
8
+ import { TimeSliderFont } from "../common/Font";
8
9
  import { TimeSliderEngine } from "./TimeSliderEngine";
9
10
  export type TimeSliderHoverOptions = {
10
11
  time?: boolean;
@@ -68,6 +69,53 @@ export type TimeSliderSelectionRange = {
68
69
  };
69
70
  export type TimeSliderSelectionChangePhase = 'start' | 'drag' | 'end';
70
71
  export type TimeSliderSelectRangeMode = 'zoom' | 'select' | 'createSelection';
72
+ /** A tick box on the ticks bar, as passed to TimeSliderOptions.tickTooltip and the 'tickhover' event. */
73
+ export type TimeSliderTickInfo = {
74
+ startTime: number;
75
+ endTime: number;
76
+ /** The tick's time unit, when it matches one (see getMatchingTimeUnit). */
77
+ unit?: TimeUnit;
78
+ /** The label the box shows, possibly shortened to fit. */
79
+ label: string;
80
+ /** The tick's full label, e.g. "March 2024" where the box shows "Mar". Localized. */
81
+ fullLabel: string;
82
+ };
83
+ /** The 'tickhover' event: the hovered tick box and its rectangle in client (viewport) coordinates. */
84
+ export type TimeSliderTickHoverInfo = TimeSliderTickInfo & {
85
+ rect: DOMRect;
86
+ };
87
+ /** true (default): tooltip with the tick's full label. false: no built-in tooltip (use the 'tickhover' event). A function: its text, or no tooltip for undefined. */
88
+ export type TimeSliderTickTooltip = boolean | ((tick: TimeSliderTickInfo) => string | undefined);
89
+ /** Style of the slider's tooltips (hover, tick and selection label). CSS values; anything left out keeps its default. */
90
+ export type TimeSliderTooltipStyle = {
91
+ background?: string;
92
+ border?: string;
93
+ borderRadius?: string;
94
+ color?: string;
95
+ padding?: string;
96
+ maxWidth?: string;
97
+ /** Defaults to the slider's font (TimeSliderOptions.font). */
98
+ font?: TimeSliderFont;
99
+ };
100
+ /** See TimeSlider.describeStep(). */
101
+ export type TimeSliderStepDescription = {
102
+ /** The range next()/previous() goes to, limits applied. */
103
+ range: TimeSliderTimeRange;
104
+ /** False when the step wouldn't move the range (at the limits). */
105
+ canStep: boolean;
106
+ /** The step's distance, e.g. { text: "~2 years 5 months", exact: false }. */
107
+ span: TimeSpanDescription;
108
+ /** e.g. "Next month", "Previous ~2 years 5 months", "Weiter: 1 Monat". */
109
+ label: string;
110
+ };
111
+ /** See TimeSlider.describeSpan(). */
112
+ export type TimeSliderSpanDescription = {
113
+ /** e.g. "~13 years 3 months". */
114
+ text: string;
115
+ exact: boolean;
116
+ /** e.g. "Sep 4, 2012 – Sep 30, 2025". */
117
+ dates: string;
118
+ };
71
119
  export interface TimeSliderOptions {
72
120
  showCurrent?: boolean;
73
121
  showTicks?: boolean;
@@ -87,6 +135,14 @@ export interface TimeSliderOptions {
87
135
  tickLabelStyler?: TimeSliderTickLabelStyler;
88
136
  /** Outer bounds of the visible time range: zooming out never shows more than this and panning stops at its edges. Applied in setTimeRange(), so wheel, pinch, up(), fit() and animations all respect it. Either side may be -Infinity/Infinity. See also setLimits(). */
89
137
  limits?: TimeSliderTimeRange;
138
+ /** Font of the tick labels, the y-axis labels (YAxisSettings.style.labels.font overrides it per axis) and the tooltips. Defaults to 12px monospace (y-axis: 12px sans-serif). Labels are measured in it so they always fit their tick boxes, and redrawn once a web font has loaded. See also setFont(). */
139
+ font?: TimeSliderFont;
140
+ /** Background colour behind the layers and the ticks bar, e.g. '#1A1A1A'. Defaults to the theme's (#111 dark, #eee light) and is kept across theme changes. See also setBackground(). */
141
+ background?: string;
142
+ /** Tooltip on hovering a tick box, see TimeSliderTickTooltip. See also setTickTooltip() and the 'tickhover' event. */
143
+ tickTooltip?: TimeSliderTickTooltip;
144
+ /** Style of the hover, tick and selection-label tooltips. See also setTooltipStyle(). */
145
+ tooltipStyle?: TimeSliderTooltipStyle;
90
146
  }
91
147
  export declare class TimeSlider extends TypedEventEmitter<{
92
148
  live: boolean;
@@ -111,6 +167,8 @@ export declare class TimeSlider extends TypedEventEmitter<{
111
167
  hover: TimeSliderHoverInfo | null;
112
168
  /** Fires when a tick label on the ticks bar is clicked. Also still zooms/fits to that tick's range, same as before this event existed. */
113
169
  tickclick: TimeSliderTickClickInfo;
170
+ /** Fires when the pointer moves onto another tick box on the ticks bar, and with null when it leaves them. Carries the box's client rect, to render your own tooltip. */
171
+ tickhover: TimeSliderTickHoverInfo | null;
114
172
  }> {
115
173
  valueRanges: Map<string, {
116
174
  min: number;
@@ -165,6 +223,12 @@ export declare class TimeSlider extends TypedEventEmitter<{
165
223
  selectingRangeVisible: boolean;
166
224
  whenDoneSelectingRange?: () => void;
167
225
  private _limits?;
226
+ private _font?;
227
+ private _background?;
228
+ private tickTooltip;
229
+ private tooltipStyle;
230
+ private tickToolTip;
231
+ private stopWatchingFonts?;
168
232
  getLayers(): TimeSliderLayer<TimeSliderElement, import("..").TimeSliderStyle>[];
169
233
  getVisibleLayers(): TimeSliderLayer<TimeSliderElement, import("..").TimeSliderStyle>[];
170
234
  constructor(options?: TimeSliderOptions);
@@ -175,6 +239,24 @@ export declare class TimeSlider extends TypedEventEmitter<{
175
239
  setWeekLabel(weekLabel: 'default' | 'calendarWeek', weekLabelPrefix?: string): void;
176
240
  /** Overrides per-tick label color/opacity, e.g. to fade ticks outside a selection. Pass undefined to clear. */
177
241
  setTickLabelStyler(styler: TimeSliderTickLabelStyler | undefined): void;
242
+ get font(): TimeSliderFont | undefined;
243
+ /** Sets the font of the tick labels, y-axis labels and tooltips (see TimeSliderOptions.font); undefined goes back to the defaults. */
244
+ setFont(font: TimeSliderFont | undefined): void;
245
+ /** Re-measures and redraws the labels, e.g. after a web font finished loading. Done automatically on document.fonts 'loadingdone'. */
246
+ refreshFonts(): void;
247
+ private watchFontLoading;
248
+ /** Redraws the ticks bar and y-axis when the device pixel ratio changes (e.g. the window moved to another screen), so their lines stay on whole device pixels. */
249
+ private watchPixelRatio;
250
+ get background(): string | undefined;
251
+ /** Sets the background colour behind the layers and the ticks bar (see TimeSliderOptions.background); undefined goes back to the theme's. */
252
+ setBackground(background: string | undefined): void;
253
+ /** Sets the tooltip shown on hovering a tick box, see TimeSliderTickTooltip. */
254
+ setTickTooltip(tickTooltip: TimeSliderTickTooltip): void;
255
+ /** Sets the style of the hover, tick and selection-label tooltips. */
256
+ setTooltipStyle(style: TimeSliderTooltipStyle): void;
257
+ private applyTooltipStyles;
258
+ private applyTooltipStyle;
259
+ private onTickHover;
178
260
  get limits(): TimeSliderTimeRange | undefined;
179
261
  /** Sets (or with undefined clears) the outer bounds of the visible time range, see TimeSliderOptions.limits. The current range is clamped right away. */
180
262
  setLimits(limits: TimeSliderTimeRange | undefined): void;
@@ -263,6 +345,9 @@ export declare class TimeSlider extends TypedEventEmitter<{
263
345
  v: number;
264
346
  };
265
347
  private pixelsToLayerValue;
348
+ private warnedDirection;
349
+ /** Whether a layer is drawn hanging down from the top of its band (direction 'down'). Bar layers only: other layers' loaders don't support it yet, and are drawn and picked growing up. */
350
+ isHangingDown(layer: TimeSliderLayer): boolean;
266
351
  private pixelsToModelValue;
267
352
  private layerValueToPixels;
268
353
  private modelValueToPixels;
@@ -285,6 +370,7 @@ export declare class TimeSlider extends TypedEventEmitter<{
285
370
  y: number;
286
371
  } | null): void;
287
372
  initHoverTooltip(): void;
373
+ private initTickTooltip;
288
374
  initHoverListener(): () => void;
289
375
  private syncSize;
290
376
  getTimeRange(): TimeSliderTimeRangeWithDuration;
@@ -308,6 +394,27 @@ export declare class TimeSlider extends TypedEventEmitter<{
308
394
  skip(amount: number, options?: {
309
395
  animate?: number;
310
396
  }): void;
397
+ /** The range skip(amount) goes to, before the limits are applied: the next/previous matching time unit (e.g. month), or the range shifted by its own duration. */
398
+ private getSkipRange;
399
+ private clampToLimits;
400
+ /**
401
+ * Describes what next() (direction 1) or previous() (-1) would do, for the app's own buttons:
402
+ * the exact range skip() goes to (snapped to a matching time unit and clamped to the limits),
403
+ * whether that moves at all, and a localized label such as "Next month",
404
+ * "Previous 3 months 2 weeks" or "Next ~2 years 5 months" (de: "Weiter: 1 Monat").
405
+ * At the limits the label still names the regular step, and canStep is false.
406
+ */
407
+ describeStep(direction: 1 | -1): TimeSliderStepDescription;
408
+ /** Whether next() (1) or previous() (-1) would move the range, false at the limits. */
409
+ canStep(direction: 1 | -1): boolean;
410
+ /**
411
+ * Describes a range (default: the current one) for tooltips: `text` is its length as people
412
+ * say it ("~13 years 3 months"), `dates` the dates it covers ("Sep 4, 2012 – Sep 30, 2025").
413
+ * Both localized.
414
+ */
415
+ describeSpan(range?: TimeSliderTimeRange): TimeSliderSpanDescription;
416
+ /** Whether the current range already shows `range` (default: what fit() fits to, the visible layers' range), after the limits, so a fit button can be disabled. */
417
+ isFitted(range?: TimeSliderTimeRange): boolean;
311
418
  getTimeRangeLabel(): string | undefined;
312
419
  private stopFit?;
313
420
  private cancelFit;
@@ -1 +1 @@
1
- import{v4 as e}from"uuid";import{Disposables as t}from"../common/Disposables.js";import{animateRangeTransition as i}from"../common/Easing.js";import{formatDate as s,formatDateRange as n,formatDuration as l,getDefaultLocale as a,getLargeTimeUnitRange as o,getMatchingTimeUnit as r,getTimeUnitRange as h,setDefaultLocale as d}from"../common/TimeTools.js";import{TimeSliderDynamicHistogram as m}from"../data/histogram/TimeSliderDynamicHistogram.js";import{getTimeArray as c}from"../data/TimeSliderElement.js";import{TimeSliderIntervalsModel as g}from"../data/TimeSliderIntervalsModel.js";import{addDuration as u,addPadding as p,applyTimeRangeUpdate as y,clampTimeRange as v,mergeTimeRanges as T,timeRangesOverlap as f,timeRangesUnion as E}from"../data/TimeSliderRange.js";import{TypedEventEmitter as L}from"../event/TypedEventEmitter.js";import{TimeSliderHistogramLayer as x}from"../layer/TimeSliderHistogramLayer.js";import{TimeSliderIntervalsLayer as R}from"../layer/TimeSliderIntervalsLayer.js";import{CENTURY as b,DECADE as w,MILLENIUM as S,ONE_DAY as C,ONE_HOUR as V,ONE_MINUTE as k,ONE_MONTH as M,ONE_SECOND as B,ONE_WEEK as D,ONE_YEAR as z,TODAY as I}from"../ticks/TimeSliderConstants.js";import{TimeTicksBar as O}from"../ticks/TimeTicksBar.js";import{YAxisTicks as A}from"../ticks/YAxisTicks.js";const H=B,_=10*S;export class TimeSlider extends L{getLayers(){return[...this.layers]}getVisibleLayers(){return this.getLayers().filter(e=>e.visible)}constructor(i){super(),this.valueRanges=new Map,this.layers=[],this.onDestroy=[],this.ticksBar=new O,this.loaders=new Set,this.layerUnloaders=new Map,this.layerListenerStoppers=new Map,this.layerLoaders=new Map,this.pressedKeys=new Set,this.container=null,this.d=new t,this.id=e(),this.playing=!1,this.live=!1,this._showCurrent=!1,this.current=Date.now(),this.range={time:I,value:{minValue:0,maxValue:100}},this.size={width:100,height:100},this.sliderElement=document.createElement("div"),this.yAxisTicks=new A,this.hoverLine=document.createElement("div"),this.toolTip=document.createElement("div"),this.selectedLabel=document.createElement("div"),this.selectingRangeElement=document.createElement("div"),this.selectionOverlayContainer=document.createElement("div"),this._selectionRanges=[],this.selectionRangeElements=[],this.shadingElements=[],this.columnBandOverlayContainer=document.createElement("div"),this.columnBandElements=[],this.selectingRange=!1,this.selectingRangeVisible=!1,this.updateValueRangesQueue=new Map,this.showingStats=!1,this._engineReady=!1,this.mouse=null,this.updatingLabel=!1,this.hoverCleared=!0,this.dragging=!1,this.pinching=!1,this.hoverOptions=i?.hover||{},this.selectionOptions={highlight:"boolean"!=typeof i?.selection?.highlight||i.selection.highlight,label:"boolean"!=typeof i?.selection?.label||i.selection.label,shading:"boolean"!=typeof i?.selection?.shading||i.selection.shading,invertShading:"boolean"==typeof i?.selection?.invertShading&&i.selection.invertShading,pick:i?.selection?.pick||"element",columnBand:i?.selection?.columnBand},this.showCurrent=i?.showCurrent||!1,this.showTicks=i?.showTicks||!1,this.selectRangeMode=i?.selectRangeMode||"select",this.playSpeed=i?.playSpeed||"auto",this.liveRange=i?.liveRange||10*B,this.theme=i?.theme||"dark",this._locale=i?.locale||a(),d(this._locale),this.ticksBar.setLocale(this._locale),this.ticksBar.setWeekLabel(i?.weekLabel||"default",i?.weekLabelPrefix),this.ticksBar.setTickLabelStyler(i?.tickLabelStyler),this.setLimits(i?.limits),this.syncMaxValues(),this.on("time",()=>this.renderSelectionOverlay()),this.on("size",()=>this.renderSelectionOverlay())}get locale(){return this._locale}setLocale(e){this._locale=e,d(e),this.ticksBar.setLocale(e),this.emit("locale",e)}setWeekLabel(e,t){this.ticksBar.setWeekLabel(e,t)}setTickLabelStyler(e){this.ticksBar.setTickLabelStyler(e)}get limits(){return this._limits?{...this._limits}:void 0}setLimits(e){!e||e.endTime>e.startTime||(console.warn("Ignoring time slider limits whose endTime is not after startTime",e),e=void 0),this._limits=e?{startTime:e.startTime,endTime:e.endTime}:void 0,this._limits&&this.setTimeRange(this.getTimeRange())}setSelection(e){this._selectionRanges=e.map(e=>({...e})),this.renderSelectionOverlay(!0),this.emit("selectionchange",{ranges:this.getSelection(),phase:"end"})}getSelection(){return this._selectionRanges.map(e=>({...e}))}clearSelection(){this.setSelection([])}setPickMode(e){this.selectionOptions.pick=e,this.updateHover()}selectionTimeToX(e,t,i,s){return e===-1/0?0:e===1/0?s:Math.min(s,Math.max(0,(e-t)/(i-t)*s))}renderSelectionOverlay(e=!1){if(!this.container)return;const t=this.getSliderBounds();if(t.width<=0)return;const{startTime:i,endTime:s}=this.getTimeRange(),n=e=>this.selectionTimeToX(e,i,s,t.width);if((e||this.selectionRangeElements.length!==this._selectionRanges.length||this.selectionRangeElements.some((e,t)=>e.range!==this._selectionRanges[t]))&&(this.selectionRangeElements.forEach(({body:e,startHandle:t,endHandle:i})=>{e.remove(),t?.remove(),i?.remove()}),this.selectionRangeElements=this._selectionRanges.map(e=>this.createSelectionRangeElements(e))),this.selectionRangeElements.forEach(({range:e,body:t,startHandle:i,endHandle:s})=>{const l=n(e.startTime),a=n(e.endTime);t.style.left=l+"px",t.style.width=Math.max(0,a-l)+"px",i&&(i.style.left=l-4+"px"),s&&(s.style.left=a-4+"px")}),this.shadingElements.forEach(e=>e.remove()),this.shadingElements=[],this.selectionOptions.shading&&this._selectionRanges.length>0){const e=T(this._selectionRanges).map(e=>[n(e.startTime),n(e.endTime)]);(this.selectionOptions.invertShading?e:function(e,t){const i=[];let s=0;for(const[t,n]of e)t>s&&i.push([s,t]),s=Math.max(s,n);s<t&&i.push([s,t]);return i}(e,t.width)).forEach(([e,t])=>{if(t<=e)return;const i=document.createElement("div");i.style.position="absolute",i.style.top="0px",i.style.height="100%",i.style.left=e+"px",i.style.width=t-e+"px",i.style.background="rgba(0, 0, 0, 0.5)",i.style.pointerEvents="none",this.selectionOverlayContainer.appendChild(i),this.shadingElements.push(i)})}}renderColumnHoverBand(e){if(this.columnBandElements.forEach(e=>e.remove()),this.columnBandElements=[],"column"!==this.selectionOptions.pick||0===e.length||!this.container)return;const t=this.getSliderBounds();if(t.width<=0)return;const{startTime:i,endTime:s}=this.getTimeRange(),n=e=>Math.min(t.width,Math.max(0,(e-i)/(s-i)*t.width)),l=this.selectionOptions.columnBand?.color??"#fff",a=this.selectionOptions.columnBand?.opacity??.15;e.forEach(({layer:e,elements:t})=>{const i=t.map(c),s=Math.min(...i.map(e=>e[0])),o=Math.max(...i.map(e=>e[1])),r=document.createElement("div");r.style.position="absolute",r.style.left=n(s)+"px",r.style.width=Math.max(1,n(o)-n(s))+"px",r.style.top=100*(1-e.max)+"%",r.style.height=100*(e.max-e.min)+"%",r.style.background=l,r.style.opacity=String(a),r.style.pointerEvents="none",this.columnBandOverlayContainer.appendChild(r),this.columnBandElements.push(r)})}createSelectionRangeElements(e){const t=document.createElement("div");t.style.position="absolute",t.style.top="0px",t.style.height="100%",t.style.background="rgba(100, 180, 255, 0.15)",t.style.borderTop="2px solid rgba(100, 180, 255, 0.8)",t.style.borderBottom="2px solid rgba(100, 180, 255, 0.8)",t.style.boxSizing="border-box",t.style.cursor="grab",t.style.pointerEvents="auto",t.style.touchAction="none",this.selectionOverlayContainer.appendChild(t);const i=()=>{const e=document.createElement("div");return e.style.position="absolute",e.style.top="0px",e.style.height="100%",e.style.width="8px",e.style.cursor="ew-resize",e.style.pointerEvents="auto",e.style.touchAction="none",e.style.background="rgba(100, 180, 255, 0.9)",this.selectionOverlayContainer.appendChild(e),e},s=e.startTime===-1/0?null:i(),n=e.endTime===1/0?null:i();return this.wireSelectionDrag(e,t,s,n),{range:e,body:t,startHandle:s,endHandle:n}}wireSelectionDrag(e,t,i,s){const n=(e,t)=>{e.addEventListener("pointerdown",i=>{i.stopPropagation(),i.preventDefault(),e.setPointerCapture(i.pointerId);const s=t(i),n=e=>{s(e),this.renderSelectionOverlay(),this.emit("selectionchange",{ranges:this.getSelection(),phase:"drag"})};let l=!1;const a=t=>{l||(l=!0,e.hasPointerCapture(t.pointerId)&&e.releasePointerCapture(t.pointerId),e.removeEventListener("pointermove",n),e.removeEventListener("pointerup",a),e.removeEventListener("pointercancel",a),e.removeEventListener("lostpointercapture",a),this.emit("selectionchange",{ranges:this.getSelection(),phase:"end"}))};this.emit("selectionchange",{ranges:this.getSelection(),phase:"start"}),e.addEventListener("pointermove",n),e.addEventListener("pointerup",a),e.addEventListener("pointercancel",a),e.addEventListener("lostpointercapture",a)})};i&&n(i,()=>t=>{const{t:i}=this.mouseInSlider(t);e.startTime=e.endTime===1/0?i:Math.min(i,e.endTime)}),s&&n(s,()=>t=>{const{t:i}=this.mouseInSlider(t);e.endTime=e.startTime===-1/0?i:Math.max(i,e.startTime)}),n(t,t=>{const i=this.mouseInSlider(t).t,s=e.startTime,n=e.endTime;return t=>{const l=this.mouseInSlider(t).t-i;s===-1/0&&n!==1/0?e.endTime=n+l:n===1/0&&s!==-1/0?e.startTime=s+l:s!==-1/0&&n!==1/0&&(e.startTime=s+l,e.endTime=n+l)}})}startSelectingRange(e){this.selectingRange=!0,this.whenDoneSelectingRange=e}stopSelectingRange(){this.selectingRange=!1,this.whenDoneSelectingRange?.(),this.whenDoneSelectingRange=void 0,this.selectingRangeVisible&&(this.container.removeChild(this.selectingRangeElement),this.selectingRangeVisible=!1)}syncMaxValues(){return this.updateAllValueRanges(),this.on("time",()=>this.updateAllValueRanges())}syncMaxValuesForLayer(e){return this.updateValueRanges(e.yAxisGroup),e.model.on(()=>this.updateValueRanges(e.yAxisGroup))}updateAllValueRanges(){this.layers.filter(e=>e.visible).forEach(e=>{this.updateValueRanges(e.yAxisGroup,!0)}),this.renderYAxis()}updateValueRanges(e,t){this.updateValueRangesQueue.has(e)||this.updateValueRangesQueue.set(e,{lastUpdate:0,pending:void 0});const i=this.updateValueRangesQueue.get(e);if(Date.now()-i.lastUpdate<100)return i.pending&&clearTimeout(i.pending),void(i.pending=setTimeout(()=>{i.pending=void 0,this.updateValueRanges(e)},200));i.lastUpdate=Date.now();const s=new Map,n=new Map;this.layers.filter(e=>e.visible).forEach(e=>{const t=e.yAxis.group||e.id;s.has(t)||(s.set(t,-Number.MAX_VALUE),n.set(t,Number.MAX_VALUE));const i=e.model.getValueRange(this.getTimeRange());i.minValue<n.get(t)&&n.set(t,i.minValue),i.maxValue>s.get(t)&&s.set(t,i.maxValue)}),this.valueRanges.clear(),s.forEach((e,t)=>{this.valueRanges.set(t,{max:e,min:n.get(t)})}),t||this.renderYAxis(),this.emit("valueRanges",e)}renderYAxis(){const e=new Map;this.layers.filter(e=>e.visible).forEach(t=>{const i=t.yAxis.group||t.id;e.has(i)||e.set(i,{min:t.min,max:t.max,group:i}),e.set(i,{...e.get(i),...t.yAxis,min:t.min,max:t.max})});const t=[];e.forEach(e=>{if(e.visible){const i=this.valueRanges.get(e.group);i&&t.push({...e,minValue:i.min,maxValue:i.max})}}),this.yAxisTicks.render(t)}setTheme(e){this.theme=e,this.ticksBar.setDarkMode("dark"===e),this.engine?.setTheme(e)}addLayerLoader(e){this.loaders.add(e)}showStats(e){this.showingStats=e,this.engine?.showStats(e)}get engineReady(){return this._engineReady}whenReady(){return this._engineReady?Promise.resolve():new Promise(e=>{const t=this.on("ready",()=>{t(),e()})})}async setEngine(e){this._engineReady=!1,this.engine&&(this.layerUnloaders.forEach(e=>e()),this.layerUnloaders.clear(),this.engine.dispose()),this.initSliderContainer(),this.engine=e,this.engine.setTheme(this.theme),await this.engine.init(this.sliderElement),this.engine.setTheme(this.theme),this._engineReady=!0,this.emit("ready"),this.getVisibleLayers().forEach(e=>this.loadLayer(e)),this.engine.showStats(this.showingStats),this.engine.setTimeRange(this.getTimeRange()),this.engine.setValueRange(this.getValueRange()),this.engine.setSize(this.size.width,this.size.height)}initSliderContainer(){this.stopControls&&this.stopControls(),this.container?.removeChild(this.sliderElement);let e=0;this.showTicks&&(e+=24),this.showCurrent&&(e+=4),this.sliderElement=document.createElement("div"),this.sliderElement.oncontextmenu=()=>!1,this.sliderElement.tabIndex=0,this.sliderElement.style.top="0px",this.sliderElement.style.width="100%",this.sliderElement.style.outline="none",this.sliderElement.style.touchAction="none",this.sliderElement.style.height=`calc(100% - ${0+e}px)`,this.container?.appendChild(this.sliderElement),this.initSelectLabel(),this.stopControls=this.initControls(),this.onDestroy.push(()=>{this.container?.removeChild(this.sliderElement)})}get showCurrent(){return this._showCurrent}set showCurrent(e){if(e!==this._showCurrent){this._showCurrent=e,this.ticksBar.showLine=!this.showCurrent;let t=0,i=0;this.showTicks&&(i+=24),this.showCurrent&&(i+=4),this.sliderElement.style.height=`calc(100% - ${t+i}px)`}}async init(e,t){const i="string"==typeof e?document.getElementById(e):e;if(this.container=i,i){i.oncontextmenu=()=>!1;let e=0,s=0;if(this.showTicks){s+=24;const e=document.createElement("div");e.style.width="100%",e.style.height="24px",e.style.position="absolute",e.style.bottom="0px",e.style.touchAction="none",i.appendChild(e),this.ticksBar.init(e,e=>{const t=r(e.startTime,e.endTime);this.emit("tickclick",{startTime:e.startTime,endTime:e.endTime,unit:t}),this.fit({range:e,animate:1e3})}),this.ticksBar.showLine=!this.showCurrent}this.showCurrent&&(s+=4),this.sliderElement.oncontextmenu=()=>!1,this.sliderElement.tabIndex=0,this.sliderElement.style.top=`${e}px`,this.sliderElement.style.width="100%",this.sliderElement.style.outline="none",this.sliderElement.style.touchAction="none",this.sliderElement.style.height=`calc(100% - ${e+s}px)`,i.appendChild(this.sliderElement),this.yAxisTicks.canvas.style.top=`${e}px`,this.yAxisTicks.canvas.style.height=`calc(100% - ${e+s}px)`,i.appendChild(this.yAxisTicks.canvas),this.selectionOverlayContainer.style.position="absolute",this.selectionOverlayContainer.style.width="100%",this.selectionOverlayContainer.style.overflow="hidden",this.selectionOverlayContainer.style.pointerEvents="none",this.selectionOverlayContainer.style.top=`${e}px`,this.selectionOverlayContainer.style.height=`calc(100% - ${e+s}px)`,i.appendChild(this.selectionOverlayContainer),this.onDestroy.push(()=>i.removeChild(this.selectionOverlayContainer)),this.columnBandOverlayContainer.style.position="absolute",this.columnBandOverlayContainer.style.width="100%",this.columnBandOverlayContainer.style.overflow="hidden",this.columnBandOverlayContainer.style.pointerEvents="none",this.columnBandOverlayContainer.style.top=`${e}px`,this.columnBandOverlayContainer.style.height=`calc(100% - ${e+s}px)`,i.appendChild(this.columnBandOverlayContainer),this.onDestroy.push(()=>i.removeChild(this.columnBandOverlayContainer)),this.initHoverTooltip(),this.initSelectLabel(),this.setSize(this.sliderElement.clientWidth,this.sliderElement.clientHeight),this.syncSize(),this.setTimeRange($(t)),this.setValueRange($(t))}else console.warn(`Element ${e} not found`)}initSelectLabel(){this.selectedLabel.style.position="absolute",this.selectedLabel.style.padding="4px 8px",this.selectedLabel.style.background="rgba(0, 0, 0, 0.75)",this.selectedLabel.style.color="white",this.selectedLabel.style.fontSize="12px",this.selectedLabel.style.maxWidth="200px",this.selectedLabel.style.fontFamily="monospace",this.selectedLabel.style.borderRadius="4px",this.selectedLabel.style.pointerEvents="none",this.selectedLabel.style.display="none",this.selectedLabel.style.zIndex="10",this.sliderElement.appendChild(this.selectedLabel)}getLabel(e,t,i,s){if(t.labeler){const n=t.labeler(e,i,s);return null===n?null:"string"==typeof n?`<div>${n}</div>`:n?n.outerHTML:`<div>${e.name||e.id}</div>`}return`<div>${e.name||e.id}</div>`}getLayerById(e){return this.layers.find(t=>t.id===e)}select(e,t){this.stopSelectingRange(),this.stopUpdatingLabel?.();const i=()=>{this.updatingLabel=!1,this.stopUpdatingLabel?.(),this.stopUpdatingLabel=void 0,this.selectedLabel.style.display="none",this.layers.forEach(e=>e.select([])),this.selectedLayerId=void 0,this.emit("select",[])},s=e=>{this.layers.filter(t=>!e.some(e=>t.id===e.layer.id)).forEach(e=>e.select([])),e.forEach(e=>{e.layer.select(e.elements)}),this.emit("select",e)};if(t){s(this.getVisibleLayers().map(e=>{const i=e.model.dataInTimeRange(t);return{layer:e,elements:i}}).filter(e=>e.elements.length>0))}else if(e){const t=this.findElements(e,24);if(s(t.map(e=>({layer:e.layer,elements:e.elements})).flat()),1===t.length&&t[0].elements.length>0&&this.selectionOptions.label){const e=t[0].elements[0],s=t[0].layer;this.selectedLabel.innerHTML=this.getLabel(e,s,t[0].t,t[0].v)||"",this.selectedLabel.style.display="block";const n=this.selectedLabel.offsetWidth,l=this.selectedLabel.offsetHeight;this.selectedLayerId=s.id;const a=Array.isArray(e.t);this.updatingLabel=!0;const o=()=>{if(!this.updatingLabel)return;const{startTime:i,duration:s,endTime:o}=this.getTimeRange(),r=t[0].layer.model.get(e.id);if(r){const e=f(this.getTimeRange(),r.t);if(e){const o=this.getSliderBounds();let h=(.5*(e.startTime+e.endTime)-i)/s*o.width;const d="number"==typeof r.v?r.v:.5*(r.v[1]+r.v[0]);let m=this.modelValueToPixels(d,t[0].layer);a?m-=.5*l:m+=8;const c=.5*n,g=.5*l,u=o.width-.5*n,p=o.height-.5*l,y=Math.min(u,Math.max(c,h)),v=Math.min(p,Math.max(g,m));this.selectedLabel.style.display="block",this.selectedLabel.style.left=y-.5*n+"px",this.selectedLabel.style.top=`${v}px`}else this.selectedLabel.style.display="none"}else this.selectedLabel.style.display="none"};o();const r=s instanceof x&&s.histogram instanceof m;this.stopUpdatingLabel=this.on("time",r?i:o)}}else i()}listenToClicks(){let e=null;const t=t=>{e={x:t.clientX,y:t.clientY}},i=t=>{if(!e)return;const i=t.clientX-e.x,s=t.clientY-e.y;i*i+s*s<=25&&(this.stopSelectingRange(),this.select(e)),e=null},s=()=>{e=null};return this.sliderElement.addEventListener("pointerdown",t),this.sliderElement.addEventListener("pointerup",i),this.sliderElement.addEventListener("pointercancel",s),()=>{this.sliderElement.removeEventListener("pointerdown",t),this.sliderElement.removeEventListener("pointerup",i),this.sliderElement.removeEventListener("pointercancel",s)}}getSliderBounds(){return this.sliderElement.getBoundingClientRect()}mouseInSlider(e){const t=this.getSliderBounds(),i="x"in e?e:{x:e.clientX,y:e.clientY},s={x:i.x-t.left,y:i.y-t.top},n={x:s.x/t.width,y:-s.y/t.height+1},{startTime:l,duration:a}=this.getTimeRange(),o=l+n.x*a,{minValue:r,maxValue:h}=this.getValueRange();return{absolute:s,normalized:n,t:o,v:r+n.x*(h-r)}}pixelsToLayerValue(e,t){if(!e)return{yInLayer:0,toLayerHeight:1};const i="normalized"in e?e.normalized:this.mouseInSlider(e).normalized;let s=t.min;const n=t.max-s;return{yInLayer:(i.y-s)/n,toLayerHeight:n/this.getSliderBounds().height}}pixelsToModelValue(e,t){const{yInLayer:i,toLayerHeight:s}=this.pixelsToLayerValue(e,t);let n=0,l=100;if(t.model instanceof g){const e=this.valueRanges.get(t.yAxisGroup)||{min:0,max:1e3};n=e.min,l=e.max-n}return{yInModel:n+i*l,toModelHeight:s*l}}layerValueToPixels(e,t){let i=t.min;return(1-(i+(t.max-i)*e))*this.getSliderBounds().height}modelValueToPixels(e,t){let i=0,s=100;if(t.model instanceof g){const e=t.model.getValueRange(this.getTimeRange());i=e.minValue,s=e.maxValue-i}const n=(e-i)/s;return this.layerValueToPixels(n,t)}findElements(e,t){if("column"===this.selectionOptions.pick)return this.findColumnElements(e);const{width:i,height:s}=this.getSliderBounds(),{normalized:n}=this.mouseInSlider(e),{startTime:l,endTime:a,duration:o}=this.getTimeRange(),r=l+n.x*o,h=t||12,d=h/i*o,m={startTime:r-1,endTime:r+1},c={startTime:r-.5*d,endTime:r+.5*d};return this.getVisibleLayers().map(e=>{const{yInModel:t,toModelHeight:i}=this.pixelsToModelValue({normalized:n},e),s=e instanceof R?0:h*i,l=e instanceof R?m:c,a={minValue:t-.5*s,maxValue:t+.5*s},o=e.model.dataInRange({...l,...a});return{layer:e,elements:o,t:r,v:t}}).filter(e=>e.elements.length>0)}findColumnElements(e,t){const{width:i}=this.getSliderBounds(),{normalized:s}=this.mouseInSlider(e),{startTime:n,duration:l}=this.getTimeRange(),a=n+s.x*l,o=(t||12)/i*l,r={startTime:a-1,endTime:a+1},h={startTime:a-.5*o,endTime:a+.5*o};return this.getVisibleLayers().map(e=>{const{yInModel:t}=this.pixelsToModelValue({normalized:s},e),i=e instanceof R?r:h,n=e.model.dataInTimeRange(i);return{layer:e,elements:n,t:a,v:t}}).filter(e=>e.elements.length>0)}clearHoverEvent(){this.hoverCleared||(this.hoverCleared=!0,this.renderColumnHoverBand([]),this.emit("hover",null))}updateHover(e){if(this.dragging)return this.hoverLine.style.display="none",this.toolTip.style.display="none",void this.clearHoverEvent();if(void 0!==e&&(this.mouse=e),this.mouse){const e=this.getSliderBounds(),{normalized:t}=this.mouseInSlider(this.mouse),{startTime:i,endTime:n,duration:l}=this.getTimeRange(),a=i+t.x*l,o=this.findElements(this.mouse);this.renderColumnHoverBand(o),this.hoverCleared=!1,this.emit("hover",{elements:o.map(e=>({layer:e.layer,elements:e.elements})),t:a,clientX:this.mouse.x,clientY:this.mouse.y});const r=o.map(e=>e.elements.map(t=>({e:t,l:e.layer,t:e.t,v:e.v}))).flat();let h;if(r.length>0)if(r.length>3){h=r.slice(0,2).map(e=>this.getLabel(e.e,e.l,e.t,e.v)).flat().filter(e=>null!==e).join(""),h+=`<div>And ${r.length-2} more...</div>`}else{h=o.map(e=>e.elements.map(t=>this.getLabel(t,e.layer,e.t,e.v))).flat().filter(e=>null!==e).join("")}if(!1===this.hoverOptions.tooltip)this.toolTip.style.display="none";else{const t=new Date(a),i=t.getHours(),n=t.getMinutes(),l=t.getSeconds(),o=s(a,!0,this._locale),r=`${(i<10?"0":"")+i}:${(n<10?"0":"")+n}:${(l<10?"0":"")+l}`;let d="";const m=this.ticksBar.cache.high.spacing;(void 0===this.hoverOptions.date?m>=V:this.hoverOptions.date)&&(d+=`<div>${o}</div>`),(void 0===this.hoverOptions.time?m<=C:this.hoverOptions.time)&&(d+=`<div>${r}</div>`),h&&(d+=h),this.toolTip.innerHTML=d,this.toolTip.style.display=d.length>0?"block":"none";const c=88,g=36;let u=this.mouse.x+10,p=this.mouse.y+10;const y=e.left+e.width-c-5,v=e.top+e.height-g-5;u>y&&(u=y),p>v&&(p=v),this.toolTip.style.left=`${u}px`,this.toolTip.style.top=`${p}px`}}else this.hoverLine.style.display="none",this.toolTip.style.display="none",this.clearHoverEvent()}initHoverTooltip(){this.toolTip.style.position="absolute",this.toolTip.style.padding="4px 8px",this.toolTip.style.background="rgba(0, 0, 0, 0.75)",this.toolTip.style.color="white",this.toolTip.style.fontSize="12px",this.toolTip.style.fontFamily="monospace",this.toolTip.style.borderRadius="4px",this.toolTip.style.pointerEvents="none",this.toolTip.style.display="none",this.toolTip.style.zIndex="10",this.toolTip.style.overflow="hidden",document.body.appendChild(this.toolTip)}initHoverListener(){const e=()=>{this.updateHover(null)},t=e=>{this.updateHover({x:e.clientX,y:e.clientY})};this.sliderElement.addEventListener("mouseleave",e),this.sliderElement.addEventListener("mousemove",t);const i=this.listenToClicks();return()=>{this.sliderElement.removeEventListener("mouseleave",e),this.sliderElement.removeEventListener("mousemove",t),i()}}syncSize(){const e=()=>{const{offsetWidth:e,offsetHeight:t}=this.sliderElement;this.setSize(e,t)},t=new ResizeObserver(()=>{e()});this.container&&(t.observe(this.container),e()),this.onDestroy.push(()=>{t.disconnect()})}getTimeRange(){return u(this.range.time)}getValueRange(){return this.range.value}setTimeRange(e,t){const i=this.getTimeRange(),s=y(i,e),n=this._limits?v(s,this._limits):s;n.startTime===i.startTime&&n.endTime===i.endTime||(this.range.time=n,this.engine?.setTimeRange(n),this.showTicks&&this.ticksBar.setTimeRange(n),this.emit("time",n),this.updateHover())}setValueRange(e){const t={...this.getValueRange(),...e};this.range.value=t,this.engine?.setValueRange(t),this.emit("value",this.getValueRange()),this.updateHover()}setSize(e,t){this.size={width:e,height:t},this.showTicks&&this.ticksBar.setWidth(e);const i=this.getTimeRange(),s=this.getValueRange();this.engine?.setSize(e,t),this.setTimeRange(i),this.setValueRange(s),this.updateHover(),this.emit("size",{width:e,height:t})}up(e){const{startTime:t,endTime:i,duration:s}=this.getTimeRange();let n={startTime:0,endTime:Date.now()};const l=.5*(t+i);if(s>=.9*S){const e=U(10*s);n=o(l,e)}else{const e=[{threshold:b,unit:"millennium"},{threshold:w,unit:"century"},{threshold:z,unit:"decade"},{threshold:.9*M,unit:"year"},{threshold:D,unit:"month"},{threshold:C,unit:"week"},{threshold:V,unit:"day"},{threshold:k,unit:"hour"},{threshold:B,unit:"minute"},{threshold:-1,unit:"second"}];let t=0;for(;s<.9*e[t].threshold;)t++;const i=e[t].unit;n=h(l,i)}this.fit({...e,range:n})}down(e){const{startTime:t,endTime:i,duration:s}=this.getTimeRange();let n={startTime:0,endTime:Date.now()};const l=t+("number"==typeof e?.xRatio?e.xRatio:.5)*s;if(s>=.9*S){const e=U(s/10);n=o(l,e)}else{let e=0;const t=[{threshold:S,unit:"century"},{threshold:b,unit:"decade"},{threshold:w,unit:"year"},{threshold:z,unit:"month"},{threshold:1.1*M,unit:"week"},{threshold:D,unit:"day"},{threshold:C,unit:"hour"},{threshold:V,unit:"minute"},{threshold:k,unit:"second"}].reverse();for(;s>1.1*t[e].threshold;)e++;const i=t[e].unit;n=h(l,i)}this.fit({...e,range:n})}next(e){this.skip(1,e)}previous(e){this.skip(-1,e)}skip(e,t){let i={startTime:0,endTime:Date.now()};const{startTime:s,endTime:n,duration:l}=this.getTimeRange(),a=.5*(s+n);if(l>=9*S){const e=U(l);i=o(a,e)}else{const t=r(s,n);i=t?h(a,t,Math.round(e)):{startTime:s+e*l,endTime:n+e*l}}this.fit({...t,range:i})}getTimeRangeLabel(){const{startTime:e,endTime:t}=this.getTimeRange(),i=new Date(e);return 0===i.getHours()&&0===i.getMinutes()&&0===i.getSeconds()&&0===i.getMilliseconds()?n([e,t],this._locale):void 0}cancelFit(){this.stopFit?.(),this.stopFit=void 0}fit(e){this.cancelFit();const t=e?.range||this.getLayersTimeRange(),s=e?.padding?p(t,e.padding):t;if(e?.animate){const t=this.getTimeRange();this.stopFit=i(t.startTime,t.endTime,s.startTime,s.endTime,e.animate,(e,t)=>this.setTimeRange({startTime:e,endTime:t}))}else this.setTimeRange(s)}getLayersTimeRange(){if(0===this.getVisibleLayers().length)return I;{let e=this.getVisibleLayers()[0].model.getTimeRange();return this.getVisibleLayers().slice(1).forEach(t=>e=E(e,t.model.getTimeRange())),e}}loadLayer(e){if(e.timeSlider&&e.timeSlider.id!==this.id)console.warn("Layer already has been loaded by another time slider, create a new layer with the same model and style if you want to add it to another time slider");else if(this.engine&&this._engineReady&&!this.layerUnloaders.has(e.id)){const t=[...this.loaders].filter(e=>this.engine instanceof e.engineClass).find(t=>t.canLoad(e));if(t){const i=Date.now(),s=t.load(e,this.engine.scene,this.engine,this);e.timeSlider=this,e.loaded=!0;const n=e.name||e.constructor.name+"#"+e.id.slice(e.id.length-4).toUpperCase();console.log(`Loaded ${n} in ${l(Date.now()-i)}`);const a=this.syncMaxValuesForLayer(e);this.layerLoaders.set(e.id,t),this.layerUnloaders.set(e.id,()=>{a(),s(),this.layerLoaders.delete(e.id),e.loaded=!1})}else console.warn("No loader found for",{layer:e})}}unloadLayer(e){this.selectedLayerId===e.id&&this.select(null),this.layerUnloaders.has(e.id)&&(this.layerUnloaders.get(e.id)(),this.layerUnloaders.delete(e.id))}addLayer(e){if(this.layers.push(e),e.visible){this.loadLayer(e);const t=e.model.getTimeRange();if(1===this.getVisibleLayers().length)this.setTimeRange(t);else{const e=E(this.getTimeRange(),t);this.setTimeRange(e)}}const t=[e.on("visible",()=>{e.visible?this.loadLayer(e):this.unloadLayer(e),this.updateAllValueRanges()}),e.on("min",()=>this.onLayerBandChange(e)),e.on("max",()=>this.onLayerBandChange(e))];this.layerListenerStoppers.set(e.id,()=>t.forEach(e=>e()))}onLayerBandChange(e){const t=this.layerLoaders.get(e.id);t&&!t.followsLayerBand&&(this.layerUnloaders.get(e.id)?.(),this.layerUnloaders.delete(e.id),this.loadLayer(e)),this.updateAllValueRanges()}removeLayer(e){const t="string"==typeof e?e:e.id,i=this.layers.find(e=>e.id===t);i&&(this.unloadLayer(i),this.layerUnloaders.delete(t),this.layerListenerStoppers.get(t)?.(),this.layerListenerStoppers.delete(t),this.layers=this.layers.filter(e=>e.id!==t))}destroy(){this.layers.map(e=>e.id).forEach(e=>this.removeLayer(e)),this.onDestroy.forEach(e=>e())}addHorizontalPinchZoom(){let e=null,t=null;const i=()=>{this.pinching=!1,e=null,t=null},s=i=>{if(2===i.touches.length){i.preventDefault(),this.dragging=!1,this.pinching=!0;const s=this.mouseInSlider(i.touches[0]).t,n=this.mouseInSlider(i.touches[1]).t;e=Math.min(s,n),t=Math.max(s,n)}},n=s=>{if(2===s.touches.length){if(null===e||null===t)return;s.preventDefault(),this.dragging=!1;const i=this.mouseInSlider(s.touches[0]).normalized.x,n=this.mouseInSlider(s.touches[1]).normalized.x,l=Math.min(i,n),a=Math.max(i,n),o=(t-e)/(a-l);let r=e-l*o,h=t+(1-a)*o;if(o>_){const e=.5*(r+h);r=e-.5*_,h=e+.5*_}if(o<H){const e=.5*(r+h);r=e-.5*H,h=e+.5*H}this.setTimeRange({startTime:r,endTime:h})}else i()},l=()=>{i()};this.sliderElement.addEventListener("touchstart",s,{passive:!1}),this.sliderElement.addEventListener("touchmove",n,{passive:!1}),this.sliderElement.addEventListener("touchend",l),this.sliderElement.addEventListener("touchcancel",l),this.onDestroy.push(()=>{this.sliderElement.removeEventListener("touchstart",s),this.sliderElement.removeEventListener("touchmove",n),this.sliderElement.removeEventListener("touchend",l),this.sliderElement.removeEventListener("touchcancel",l)})}initControls(){const e=new t;this.dragging=!1;let i,s=0,n=0,l=0;e.add(this.initHoverListener());const a=e=>{this.sliderElement.focus(),e.preventDefault();const t=this.mouseInSlider(e);this.zoom(.001*e.deltaY,{x:t.normalized.x})};let o=0,r=-1;const h=e=>{const t=Date.now(),i=t-o;r>=0&&i<300&&i>0&&(this.select(null),e.preventDefault(),this.zoom(-.75,{animate:1e3,x:r})),o=t},d=e=>{e.preventDefault(),this.select(null);const t=this.mouseInSlider(e);this.zoom(-.75,{animate:1e3,x:t.normalized.x})};this.selectingRangeElement.style.height="calc(100% - 6px)",this.selectingRangeElement.style.pointerEvents="none",this.selectingRangeElement.style.position="absolute",this.selectingRangeElement.style.border="3px solid #df0",this.selectingRangeElement.style.background="#df04",this.selectingRangeElement.style.top="0px";const m=e=>{if(this.sliderElement.focus(),2===e.button||0===e.button){e.preventDefault();const t=this.mouseInSlider(e),i=t.normalized.x,{startTime:a,duration:o}=this.getTimeRange();s=a+i*o,n=Date.now(),l=t.absolute.x,(e.ctrlKey||2===e.button)&&(this.selectingRange=!0),this.selectingRange?(this.selectingRangeElement.style.left=t.absolute.x+"px",this.selectingRangeElement.style.width="0px",this.container.appendChild(this.selectingRangeElement),this.selectingRangeVisible=!0):this.dragging=!0}},c=e=>{const t=this.mouseInSlider(e);return i=t.normalized.x,t},g=e=>{const t=c(e);if(this.selectingRange){const e=Math.abs(l-t.absolute.x),i=Math.min(l,t.absolute.x);this.selectingRangeElement.style.left=i+"px",this.selectingRangeElement.style.width=e-6+"px"}if(!this.dragging||Date.now()-n<100&&Math.abs(l-t.absolute.x)<10)return;e.preventDefault();const i=t.normalized.x,{startTime:a,endTime:o}=this.getTimeRange(),r=a+i*(o-a)-s;this.setTimeRange({startTime:a-r,endTime:o-r})},u=e=>{const t=this.getSliderBounds(),n=t.height-(e.clientY-t.top);if(!this.dragging&&!this.pinching&&n<8)this.ticksBar.click(e);else if(i=void 0,this.dragging=!1,this.selectingRange){const t=this.mouseInSlider(e).normalized.x,{startTime:i,endTime:n}=this.getTimeRange(),l=i+t*(n-i);"zoom"===this.selectRangeMode&&this.fit({range:{startTime:Math.min(s,l),endTime:Math.max(s,l)},animate:1e3}),"select"===this.selectRangeMode&&this.select(null,{startTime:Math.min(s,l),endTime:Math.max(s,l)}),"createSelection"===this.selectRangeMode&&this.setSelection([...this.getSelection(),{startTime:Math.min(s,l),endTime:Math.max(s,l)}])}this.stopSelectingRange()},p=e=>{this.pressedKeys.add(e.code)},y=e=>{this.pressedKeys.delete(e.code),"Space"===e.code&&(e.preventDefault(),this.togglePlaying()),"KeyF"===e.code&&(e.preventDefault(),this.fit({animate:3e3}));const t=void 0;"ArrowRight"===e.code&&(e.preventDefault(),this.skip(1,{animate:t})),"ArrowLeft"===e.code&&(e.preventDefault(),this.skip(-1,{animate:t})),"ArrowUp"===e.code&&(e.preventDefault(),this.up({animate:t})),"ArrowDown"===e.code&&(e.preventDefault(),this.down({xRatio:i,animate:t}))};let v=!0;const T=()=>{if(v){const e=this.pressedKeys.has("ShiftLeft")?.05:.01,t=2*e,s=this.pressedKeys.has("KeyD"),n=this.pressedKeys.has("KeyA");s&&this.shift(-e),n&&this.shift(e),this.pressedKeys.has("KeyW")&&this.zoom(-t,{x:s||n?void 0:i}),this.pressedKeys.has("KeyS")&&this.zoom(t,{x:s||n?void 0:i}),requestAnimationFrame(T)}};T(),e.add(()=>v=!1);const f=e=>e.preventDefault();this.sliderElement.addEventListener("contextmenu",f),this.sliderElement.addEventListener("wheel",a),this.sliderElement.addEventListener("pointerdown",m),this.sliderElement.addEventListener("pointermove",g),this.sliderElement.addEventListener("pointerup",u),this.sliderElement.addEventListener("dblclick",d),this.sliderElement.addEventListener("touchend",h),this.sliderElement.addEventListener("touchstart",e=>{if(1===e.touches.length){const t=this.mouseInSlider(e.touches[0]);r=t.normalized.x}else r=-1}),this.sliderElement.addEventListener("keydown",p),this.sliderElement.addEventListener("keyup",y),this.addHorizontalPinchZoom();const E=()=>{this.sliderElement.removeEventListener("contextmenu",f),this.sliderElement.removeEventListener("wheel",a),this.sliderElement.removeEventListener("pointerdown",m),this.sliderElement.removeEventListener("pointermove",g),this.sliderElement.removeEventListener("pointerup",u),this.sliderElement.removeEventListener("dblclick",d),this.sliderElement.removeEventListener("touchend",h),this.sliderElement.removeEventListener("keydown",p),this.sliderElement.removeEventListener("keyup",y)};return this.onDestroy.push(E),e.add(E),e.disposeLater()}shift(e,t){const{startTime:i,endTime:s,duration:n}=this.getTimeRange(),l=t?.absolute?e:e*n;this.fit({...t,range:{startTime:i-l,endTime:s-l}})}zoom(e,t){const i="number"==typeof t?.x?t.x:.5,{startTime:s,endTime:n,duration:l}=this.getTimeRange(),a={startTime:s-e*l*i,endTime:n+e*l*(1-i)},o=a.endTime-a.startTime,r=a.startTime+.5*o;o>_&&(a.startTime=r-.5*_,a.endTime=r+.5*_),o<H&&(a.startTime=r-.5*H,a.endTime=r+.5*H),this.fit({range:a,animate:t?.animate})}togglePlaying(){this.playing?this.pause():this.play()}isPlaying(){return this.playing}play(){if(this.playing)return;this.playing=!0,this.emit("playing",!0);let e=Date.now();const t=()=>{if(this.playing){if(this.live){const{duration:e}=this.getTimeRange(),t=Date.now(),i=10/this.size.width;this.setTimeRange({startTime:t-(1-i)*e,endTime:t+i*e}),this.setCurrent(t)}else if("auto"!==this.playSpeed||this.showCurrent){const t=Date.now(),i=t-e,s=this.current-this.getTimeRange().startTime,n="auto"===this.playSpeed?1:this.playSpeed;this.shift(-n*i,{absolute:!0}),this.setCurrent(this.getTimeRange().startTime+s),e=t}else{const t=Date.now();let i=5e-5*(t-e);this.pressedKeys.has("ShiftLeft")&&(i*=5);const s=this.current-this.getTimeRange().startTime;this.shift(-i),this.setCurrent(this.getTimeRange().startTime+s),e=t}requestAnimationFrame(t)}};t()}setCurrent(e){this.current!==e&&(Math.abs(e-Date.now())>100&&this.live&&(this.pause(),this.setLive(!1)),this.current=e,this.emit("current",e))}pause(){this.playing&&(this.playing=!1,this.emit("playing",!1),this.setLive(!1))}setLive(e){if(this.live!==e){if(this.live=e,e){const e=this.getTimeRange();this.fit({range:{startTime:e.endTime-this.liveRange,endTime:e.endTime},animate:B}),this.play()}this.emit("live",e)}}}function $(e){return{startTime:"number"==typeof e?.startTime?e.startTime:"number"==typeof e?.endTime?e.duration?e.endTime-e.duration:e.endTime-C:e?.duration?Date.now()-.5*e.duration:Date.now()-.5*C,endTime:"number"==typeof e?.endTime?e.endTime:"number"==typeof e?.startTime?e.duration?e.startTime+e.duration:e.startTime+C:e?.duration?Date.now()+.5*e.duration:Date.now()+.5*C,duration:e?.duration?e.duration:"number"==typeof e?.startTime&&"number"==typeof e?.endTime?e.endTime+e.startTime:C,minValue:"number"==typeof e?.minValue?e.minValue:"number"==typeof e?.maxValue?e.maxValue>0?0:e.maxValue-100:0,maxValue:"number"==typeof e?.maxValue?e.maxValue:"number"==typeof e?.minValue?e.minValue<0?0:e.minValue+100:100}}function U(e){const t=e/315576e5,i=Math.log10(t),s=Math.ceil(i);return Math.pow(10,s)}
1
+ import{v4 as e}from"uuid";import{Disposables as t}from"../common/Disposables.js";import{animateRangeTransition as i}from"../common/Easing.js";import{describeTimeSpan as s,formatDate as n,formatDateRange as o,formatDuration as l,formatTimeSpanDates as a,getDefaultLocale as r,getLargeTimeUnitRange as h,getMatchingTimeUnit as d,getTimeUnitRange as m,setDefaultLocale as c}from"../common/TimeTools.js";import{TimeSliderDynamicHistogram as u}from"../data/histogram/TimeSliderDynamicHistogram.js";import{getTimeArray as g}from"../data/TimeSliderElement.js";import{TimeSliderIntervalsModel as p}from"../data/TimeSliderIntervalsModel.js";import{addDuration as y,addPadding as T,applyTimeRangeUpdate as v,clampTimeRange as f,mergeTimeRanges as E,timeRangesOverlap as x,timeRangesUnion as L}from"../data/TimeSliderRange.js";import{TypedEventEmitter as b}from"../event/TypedEventEmitter.js";import{TimeSliderHistogramLayer as R}from"../layer/TimeSliderHistogramLayer.js";import{TimeSliderIntervalsLayer as w}from"../layer/TimeSliderIntervalsLayer.js";import{CENTURY as S,DECADE as k,MILLENIUM as C,ONE_DAY as V,ONE_HOUR as M,ONE_MINUTE as B,ONE_MONTH as D,ONE_SECOND as z,ONE_WEEK as H,ONE_YEAR as _,TODAY as A}from"../ticks/TimeSliderConstants.js";import{TimeTicksBar as I}from"../ticks/TimeTicksBar.js";import{YAxisTicks as O}from"../ticks/YAxisTicks.js";import{resolveFont as $,toCssFont as F}from"../common/Font.js";const U=z,P=10*C;export class TimeSlider extends b{getLayers(){return[...this.layers]}getVisibleLayers(){return this.getLayers().filter(e=>e.visible)}constructor(i){super(),this.valueRanges=new Map,this.layers=[],this.onDestroy=[],this.ticksBar=new I,this.loaders=new Set,this.layerUnloaders=new Map,this.layerListenerStoppers=new Map,this.layerLoaders=new Map,this.pressedKeys=new Set,this.container=null,this.d=new t,this.id=e(),this.playing=!1,this.live=!1,this._showCurrent=!1,this.current=Date.now(),this.range={time:A,value:{minValue:0,maxValue:100}},this.size={width:100,height:100},this.sliderElement=document.createElement("div"),this.yAxisTicks=new O,this.hoverLine=document.createElement("div"),this.toolTip=document.createElement("div"),this.selectedLabel=document.createElement("div"),this.selectingRangeElement=document.createElement("div"),this.selectionOverlayContainer=document.createElement("div"),this._selectionRanges=[],this.selectionRangeElements=[],this.shadingElements=[],this.columnBandOverlayContainer=document.createElement("div"),this.columnBandElements=[],this.selectingRange=!1,this.selectingRangeVisible=!1,this.tickTooltip=!0,this.tooltipStyle={},this.tickToolTip=document.createElement("div"),this.updateValueRangesQueue=new Map,this.showingStats=!1,this._engineReady=!1,this.mouse=null,this.updatingLabel=!1,this.warnedDirection=new Set,this.hoverCleared=!0,this.dragging=!1,this.pinching=!1,this.hoverOptions=i?.hover||{},this.selectionOptions={highlight:"boolean"!=typeof i?.selection?.highlight||i.selection.highlight,label:"boolean"!=typeof i?.selection?.label||i.selection.label,shading:"boolean"!=typeof i?.selection?.shading||i.selection.shading,invertShading:"boolean"==typeof i?.selection?.invertShading&&i.selection.invertShading,pick:i?.selection?.pick||"element",columnBand:i?.selection?.columnBand},this.showCurrent=i?.showCurrent||!1,this.showTicks=i?.showTicks||!1,this.selectRangeMode=i?.selectRangeMode||"select",this.playSpeed=i?.playSpeed||"auto",this.liveRange=i?.liveRange||10*z,this.theme=i?.theme||"dark",this._locale=i?.locale||r(),c(this._locale),this.ticksBar.setLocale(this._locale),this.ticksBar.setWeekLabel(i?.weekLabel||"default",i?.weekLabelPrefix),this.ticksBar.setTickLabelStyler(i?.tickLabelStyler),this.setLimits(i?.limits),this.tickTooltip=i?.tickTooltip??!0,this.tooltipStyle=i?.tooltipStyle??{},this.ticksBar.onHoverTick=e=>this.onTickHover(e),this.setBackground(i?.background),this.setFont(i?.font),this.syncMaxValues(),this.on("time",()=>this.renderSelectionOverlay()),this.on("size",()=>this.renderSelectionOverlay())}get locale(){return this._locale}setLocale(e){this._locale=e,c(e),this.ticksBar.setLocale(e),this.emit("locale",e)}setWeekLabel(e,t){this.ticksBar.setWeekLabel(e,t)}setTickLabelStyler(e){this.ticksBar.setTickLabelStyler(e)}get font(){return this._font?{...this._font}:void 0}setFont(e){this._font=e?{...e}:void 0;const t=$(e);this.ticksBar.setFont(F(t),t.size),this.yAxisTicks.setFont(e),this.applyTooltipStyles(),this.renderYAxis(),e&&"undefined"!=typeof document&&document.fonts&&document.fonts.load(F(t)).then(()=>this.refreshFonts(),()=>{})}refreshFonts(){this.ticksBar.refreshFont(),this.renderYAxis()}watchFontLoading(){if(this.stopWatchingFonts||"undefined"==typeof document||!document.fonts)return;const e=document.fonts,t=()=>this.refreshFonts();e.addEventListener("loadingdone",t),e.ready.then(t,()=>{}),this.stopWatchingFonts=()=>e.removeEventListener("loadingdone",t),this.onDestroy.push(()=>this.stopWatchingFonts?.())}watchPixelRatio(){if("undefined"==typeof window||!window.matchMedia)return;let e=()=>{};const t=()=>{const i=window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`),s=()=>{e(),this.ticksBar.draw(),this.renderYAxis(),t()};i.addEventListener("change",s),e=()=>i.removeEventListener("change",s)};t(),this.onDestroy.push(()=>e())}get background(){return this._background}setBackground(e){this._background=e,this.engine?.setBackground(e),this.ticksBar.setBackground(e)}setTickTooltip(e){this.tickTooltip=e,!1===e&&(this.tickToolTip.style.display="none")}setTooltipStyle(e){this.tooltipStyle={...e},this.applyTooltipStyles()}applyTooltipStyles(){this.applyTooltipStyle(this.toolTip),this.applyTooltipStyle(this.tickToolTip),this.applyTooltipStyle(this.selectedLabel,"200px")}applyTooltipStyle(e,t=""){const i=this.tooltipStyle;e.style.background=i.background??"rgba(0, 0, 0, 0.75)",e.style.color=i.color??"white",e.style.border=i.border??"none",e.style.borderRadius=i.borderRadius??"4px",e.style.padding=i.padding??"4px 8px",e.style.maxWidth=i.maxWidth??t,e.style.font=F($(i.font??this._font))}onTickHover(e){if(!e)return this.tickToolTip.style.display="none",void this.emit("tickhover",null);const t=this.ticksBar.getCanvasRect(),i=new DOMRect(t.left+e.x,t.top,e.width,t.height),s={startTime:e.startTime,endTime:e.endTime,unit:d(e.startTime,e.endTime),label:e.label,fullLabel:this.ticksBar.getFullLabel(e)};this.emit("tickhover",{...s,rect:i});const n=!1===this.tickTooltip?void 0:"function"==typeof this.tickTooltip?this.tickTooltip(s):s.fullLabel;if(!n)return void(this.tickToolTip.style.display="none");this.tickToolTip.textContent=n,this.tickToolTip.style.display="block";const o=this.tickToolTip.offsetWidth,l=this.tickToolTip.offsetHeight,a=Math.min(Math.max(4,i.left+.5*i.width-.5*o),window.innerWidth-o-4),r=i.top-l-6;this.tickToolTip.style.left=`${a}px`,this.tickToolTip.style.top=`${r>=4?r:i.bottom+6}px`}get limits(){return this._limits?{...this._limits}:void 0}setLimits(e){!e||e.endTime>e.startTime||(console.warn("Ignoring time slider limits whose endTime is not after startTime",e),e=void 0),this._limits=e?{startTime:e.startTime,endTime:e.endTime}:void 0,this._limits&&this.setTimeRange(this.getTimeRange())}setSelection(e){this._selectionRanges=e.map(e=>({...e})),this.renderSelectionOverlay(!0),this.emit("selectionchange",{ranges:this.getSelection(),phase:"end"})}getSelection(){return this._selectionRanges.map(e=>({...e}))}clearSelection(){this.setSelection([])}setPickMode(e){this.selectionOptions.pick=e,this.updateHover()}selectionTimeToX(e,t,i,s){return e===-1/0?0:e===1/0?s:Math.min(s,Math.max(0,(e-t)/(i-t)*s))}renderSelectionOverlay(e=!1){if(!this.container)return;const t=this.getSliderBounds();if(t.width<=0)return;const{startTime:i,endTime:s}=this.getTimeRange(),n=e=>this.selectionTimeToX(e,i,s,t.width);if((e||this.selectionRangeElements.length!==this._selectionRanges.length||this.selectionRangeElements.some((e,t)=>e.range!==this._selectionRanges[t]))&&(this.selectionRangeElements.forEach(({body:e,startHandle:t,endHandle:i})=>{e.remove(),t?.remove(),i?.remove()}),this.selectionRangeElements=this._selectionRanges.map(e=>this.createSelectionRangeElements(e))),this.selectionRangeElements.forEach(({range:e,body:t,startHandle:i,endHandle:s})=>{const o=n(e.startTime),l=n(e.endTime);t.style.left=o+"px",t.style.width=Math.max(0,l-o)+"px",i&&(i.style.left=o-4+"px"),s&&(s.style.left=l-4+"px")}),this.shadingElements.forEach(e=>e.remove()),this.shadingElements=[],this.selectionOptions.shading&&this._selectionRanges.length>0){const e=E(this._selectionRanges).map(e=>[n(e.startTime),n(e.endTime)]);(this.selectionOptions.invertShading?e:function(e,t){const i=[];let s=0;for(const[t,n]of e)t>s&&i.push([s,t]),s=Math.max(s,n);s<t&&i.push([s,t]);return i}(e,t.width)).forEach(([e,t])=>{if(t<=e)return;const i=document.createElement("div");i.style.position="absolute",i.style.top="0px",i.style.height="100%",i.style.left=e+"px",i.style.width=t-e+"px",i.style.background="rgba(0, 0, 0, 0.5)",i.style.pointerEvents="none",this.selectionOverlayContainer.appendChild(i),this.shadingElements.push(i)})}}renderColumnHoverBand(e){if(this.columnBandElements.forEach(e=>e.remove()),this.columnBandElements=[],"column"!==this.selectionOptions.pick||0===e.length||!this.container)return;const t=this.getSliderBounds();if(t.width<=0)return;const{startTime:i,endTime:s}=this.getTimeRange(),n=e=>Math.min(t.width,Math.max(0,(e-i)/(s-i)*t.width)),o=this.selectionOptions.columnBand?.color??"#fff",l=this.selectionOptions.columnBand?.opacity??.15;e.forEach(({layer:e,elements:t})=>{const i=t.map(g),s=Math.min(...i.map(e=>e[0])),a=Math.max(...i.map(e=>e[1])),r=document.createElement("div");r.style.position="absolute",r.style.left=n(s)+"px",r.style.width=Math.max(1,n(a)-n(s))+"px",r.style.top=100*(1-e.max)+"%",r.style.height=100*(e.max-e.min)+"%",r.style.background=o,r.style.opacity=String(l),r.style.pointerEvents="none",this.columnBandOverlayContainer.appendChild(r),this.columnBandElements.push(r)})}createSelectionRangeElements(e){const t=document.createElement("div");t.style.position="absolute",t.style.top="0px",t.style.height="100%",t.style.background="rgba(100, 180, 255, 0.15)",t.style.borderTop="2px solid rgba(100, 180, 255, 0.8)",t.style.borderBottom="2px solid rgba(100, 180, 255, 0.8)",t.style.boxSizing="border-box",t.style.cursor="grab",t.style.pointerEvents="auto",t.style.touchAction="none",this.selectionOverlayContainer.appendChild(t);const i=()=>{const e=document.createElement("div");return e.style.position="absolute",e.style.top="0px",e.style.height="100%",e.style.width="8px",e.style.cursor="ew-resize",e.style.pointerEvents="auto",e.style.touchAction="none",e.style.background="rgba(100, 180, 255, 0.9)",this.selectionOverlayContainer.appendChild(e),e},s=e.startTime===-1/0?null:i(),n=e.endTime===1/0?null:i();return this.wireSelectionDrag(e,t,s,n),{range:e,body:t,startHandle:s,endHandle:n}}wireSelectionDrag(e,t,i,s){const n=(e,t)=>{e.addEventListener("pointerdown",i=>{i.stopPropagation(),i.preventDefault(),e.setPointerCapture(i.pointerId);const s=t(i),n=e=>{s(e),this.renderSelectionOverlay(),this.emit("selectionchange",{ranges:this.getSelection(),phase:"drag"})};let o=!1;const l=t=>{o||(o=!0,e.hasPointerCapture(t.pointerId)&&e.releasePointerCapture(t.pointerId),e.removeEventListener("pointermove",n),e.removeEventListener("pointerup",l),e.removeEventListener("pointercancel",l),e.removeEventListener("lostpointercapture",l),this.emit("selectionchange",{ranges:this.getSelection(),phase:"end"}))};this.emit("selectionchange",{ranges:this.getSelection(),phase:"start"}),e.addEventListener("pointermove",n),e.addEventListener("pointerup",l),e.addEventListener("pointercancel",l),e.addEventListener("lostpointercapture",l)})};i&&n(i,()=>t=>{const{t:i}=this.mouseInSlider(t);e.startTime=e.endTime===1/0?i:Math.min(i,e.endTime)}),s&&n(s,()=>t=>{const{t:i}=this.mouseInSlider(t);e.endTime=e.startTime===-1/0?i:Math.max(i,e.startTime)}),n(t,t=>{const i=this.mouseInSlider(t).t,s=e.startTime,n=e.endTime;return t=>{const o=this.mouseInSlider(t).t-i;s===-1/0&&n!==1/0?e.endTime=n+o:n===1/0&&s!==-1/0?e.startTime=s+o:s!==-1/0&&n!==1/0&&(e.startTime=s+o,e.endTime=n+o)}})}startSelectingRange(e){this.selectingRange=!0,this.whenDoneSelectingRange=e}stopSelectingRange(){this.selectingRange=!1,this.whenDoneSelectingRange?.(),this.whenDoneSelectingRange=void 0,this.selectingRangeVisible&&(this.container.removeChild(this.selectingRangeElement),this.selectingRangeVisible=!1)}syncMaxValues(){return this.updateAllValueRanges(),this.on("time",()=>this.updateAllValueRanges())}syncMaxValuesForLayer(e){return this.updateValueRanges(e.yAxisGroup),e.model.on(()=>this.updateValueRanges(e.yAxisGroup))}updateAllValueRanges(){this.layers.filter(e=>e.visible).forEach(e=>{this.updateValueRanges(e.yAxisGroup,!0)}),this.renderYAxis()}updateValueRanges(e,t){this.updateValueRangesQueue.has(e)||this.updateValueRangesQueue.set(e,{lastUpdate:0,pending:void 0});const i=this.updateValueRangesQueue.get(e);if(Date.now()-i.lastUpdate<100)return i.pending&&clearTimeout(i.pending),void(i.pending=setTimeout(()=>{i.pending=void 0,this.updateValueRanges(e)},200));i.lastUpdate=Date.now();const s=new Map,n=new Map;this.layers.filter(e=>e.visible).forEach(e=>{const t=e.yAxis.group||e.id;s.has(t)||(s.set(t,-Number.MAX_VALUE),n.set(t,Number.MAX_VALUE));const i=e.model.getValueRange(this.getTimeRange());i.minValue<n.get(t)&&n.set(t,i.minValue),i.maxValue>s.get(t)&&s.set(t,i.maxValue)}),this.valueRanges.clear(),s.forEach((e,t)=>{this.valueRanges.set(t,{max:e,min:n.get(t)})}),t||this.renderYAxis(),this.emit("valueRanges",e)}renderYAxis(){const e=new Map;this.layers.filter(e=>e.visible).forEach(t=>{const i=t.yAxis.group||t.id;e.has(i)||e.set(i,{min:t.min,max:t.max,group:i}),e.set(i,{...e.get(i),...t.yAxis,min:t.min,max:t.max,direction:this.isHangingDown(t)?"down":"up"})});const t=[];e.forEach(e=>{if(e.visible){const i=this.valueRanges.get(e.group);i&&t.push({...e,minValue:i.min,maxValue:i.max})}}),this.yAxisTicks.render(t)}setTheme(e){this.theme=e,this.ticksBar.setDarkMode("dark"===e),this.engine?.setTheme(e)}addLayerLoader(e){this.loaders.add(e)}showStats(e){this.showingStats=e,this.engine?.showStats(e)}get engineReady(){return this._engineReady}whenReady(){return this._engineReady?Promise.resolve():new Promise(e=>{const t=this.on("ready",()=>{t(),e()})})}async setEngine(e){this._engineReady=!1,this.engine&&(this.layerUnloaders.forEach(e=>e()),this.layerUnloaders.clear(),this.engine.dispose()),this.initSliderContainer(),this.engine=e,this.engine.setTheme(this.theme),this.engine.setBackground(this._background),await this.engine.init(this.sliderElement),this.engine.setTheme(this.theme),this._engineReady=!0,this.emit("ready"),this.getVisibleLayers().forEach(e=>this.loadLayer(e)),this.engine.showStats(this.showingStats),this.engine.setTimeRange(this.getTimeRange()),this.engine.setValueRange(this.getValueRange()),this.engine.setSize(this.size.width,this.size.height)}initSliderContainer(){this.stopControls&&this.stopControls(),this.container?.removeChild(this.sliderElement);let e=0;this.showTicks&&(e+=24),this.showCurrent&&(e+=4),this.sliderElement=document.createElement("div"),this.sliderElement.oncontextmenu=()=>!1,this.sliderElement.tabIndex=0,this.sliderElement.style.top="0px",this.sliderElement.style.width="100%",this.sliderElement.style.outline="none",this.sliderElement.style.touchAction="none",this.sliderElement.style.height=`calc(100% - ${0+e}px)`,this.container?.appendChild(this.sliderElement),this.initSelectLabel(),this.stopControls=this.initControls(),this.onDestroy.push(()=>{this.container?.removeChild(this.sliderElement)})}get showCurrent(){return this._showCurrent}set showCurrent(e){if(e!==this._showCurrent){this._showCurrent=e,this.ticksBar.showLine=!this.showCurrent;let t=0,i=0;this.showTicks&&(i+=24),this.showCurrent&&(i+=4),this.sliderElement.style.height=`calc(100% - ${t+i}px)`}}async init(e,t){const i="string"==typeof e?document.getElementById(e):e;if(this.container=i,i){i.oncontextmenu=()=>!1;let e=0,s=0;if(this.showTicks){s+=24;const e=document.createElement("div");e.style.width="100%",e.style.height="24px",e.style.position="absolute",e.style.bottom="0px",e.style.touchAction="none",i.appendChild(e),this.ticksBar.init(e,e=>{const t=d(e.startTime,e.endTime);this.emit("tickclick",{startTime:e.startTime,endTime:e.endTime,unit:t}),this.fit({range:e,animate:1e3})}),this.ticksBar.showLine=!this.showCurrent}this.showCurrent&&(s+=4),this.sliderElement.oncontextmenu=()=>!1,this.sliderElement.tabIndex=0,this.sliderElement.style.top=`${e}px`,this.sliderElement.style.width="100%",this.sliderElement.style.outline="none",this.sliderElement.style.touchAction="none",this.sliderElement.style.height=`calc(100% - ${e+s}px)`,i.appendChild(this.sliderElement),this.yAxisTicks.canvas.style.top=`${e}px`,this.yAxisTicks.canvas.style.height=`calc(100% - ${e+s}px)`,i.appendChild(this.yAxisTicks.canvas),this.selectionOverlayContainer.style.position="absolute",this.selectionOverlayContainer.style.width="100%",this.selectionOverlayContainer.style.overflow="hidden",this.selectionOverlayContainer.style.pointerEvents="none",this.selectionOverlayContainer.style.top=`${e}px`,this.selectionOverlayContainer.style.height=`calc(100% - ${e+s}px)`,i.appendChild(this.selectionOverlayContainer),this.onDestroy.push(()=>i.removeChild(this.selectionOverlayContainer)),this.columnBandOverlayContainer.style.position="absolute",this.columnBandOverlayContainer.style.width="100%",this.columnBandOverlayContainer.style.overflow="hidden",this.columnBandOverlayContainer.style.pointerEvents="none",this.columnBandOverlayContainer.style.top=`${e}px`,this.columnBandOverlayContainer.style.height=`calc(100% - ${e+s}px)`,i.appendChild(this.columnBandOverlayContainer),this.onDestroy.push(()=>i.removeChild(this.columnBandOverlayContainer)),this.initHoverTooltip(),this.initTickTooltip(),this.watchFontLoading(),this.watchPixelRatio(),this.initSelectLabel(),this.setSize(this.sliderElement.clientWidth,this.sliderElement.clientHeight),this.syncSize(),this.setTimeRange(j(t)),this.setValueRange(j(t))}else console.warn(`Element ${e} not found`)}initSelectLabel(){this.selectedLabel.style.position="absolute",this.applyTooltipStyle(this.selectedLabel,"200px"),this.selectedLabel.style.pointerEvents="none",this.selectedLabel.style.display="none",this.selectedLabel.style.zIndex="10",this.sliderElement.appendChild(this.selectedLabel)}getLabel(e,t,i,s){if(t.labeler){const n=t.labeler(e,i,s);return null===n?null:"string"==typeof n?`<div>${n}</div>`:n?n.outerHTML:`<div>${e.name||e.id}</div>`}return`<div>${e.name||e.id}</div>`}getLayerById(e){return this.layers.find(t=>t.id===e)}select(e,t){this.stopSelectingRange(),this.stopUpdatingLabel?.();const i=()=>{this.updatingLabel=!1,this.stopUpdatingLabel?.(),this.stopUpdatingLabel=void 0,this.selectedLabel.style.display="none",this.layers.forEach(e=>e.select([])),this.selectedLayerId=void 0,this.emit("select",[])},s=e=>{this.layers.filter(t=>!e.some(e=>t.id===e.layer.id)).forEach(e=>e.select([])),e.forEach(e=>{e.layer.select(e.elements)}),this.emit("select",e)};if(t){s(this.getVisibleLayers().map(e=>{const i=e.model.dataInTimeRange(t);return{layer:e,elements:i}}).filter(e=>e.elements.length>0))}else if(e){const t=this.findElements(e,24);if(s(t.map(e=>({layer:e.layer,elements:e.elements})).flat()),1===t.length&&t[0].elements.length>0&&this.selectionOptions.label){const e=t[0].elements[0],s=t[0].layer;this.selectedLabel.innerHTML=this.getLabel(e,s,t[0].t,t[0].v)||"",this.selectedLabel.style.display="block";const n=this.selectedLabel.offsetWidth,o=this.selectedLabel.offsetHeight;this.selectedLayerId=s.id;const l=Array.isArray(e.t);this.updatingLabel=!0;const a=()=>{if(!this.updatingLabel)return;const{startTime:i,duration:s,endTime:a}=this.getTimeRange(),r=t[0].layer.model.get(e.id);if(r){const e=x(this.getTimeRange(),r.t);if(e){const a=this.getSliderBounds();let h=(.5*(e.startTime+e.endTime)-i)/s*a.width;const d="number"==typeof r.v?r.v:.5*(r.v[1]+r.v[0]);let m=this.modelValueToPixels(d,t[0].layer);l?m-=.5*o:m+=8;const c=.5*n,u=.5*o,g=a.width-.5*n,p=a.height-.5*o,y=Math.min(g,Math.max(c,h)),T=Math.min(p,Math.max(u,m));this.selectedLabel.style.display="block",this.selectedLabel.style.left=y-.5*n+"px",this.selectedLabel.style.top=`${T}px`}else this.selectedLabel.style.display="none"}else this.selectedLabel.style.display="none"};a();const r=s instanceof R&&s.histogram instanceof u;this.stopUpdatingLabel=this.on("time",r?i:a)}}else i()}listenToClicks(){let e=null;const t=t=>{e={x:t.clientX,y:t.clientY}},i=t=>{if(!e)return;const i=t.clientX-e.x,s=t.clientY-e.y;i*i+s*s<=25&&(this.stopSelectingRange(),this.select(e)),e=null},s=()=>{e=null};return this.sliderElement.addEventListener("pointerdown",t),this.sliderElement.addEventListener("pointerup",i),this.sliderElement.addEventListener("pointercancel",s),()=>{this.sliderElement.removeEventListener("pointerdown",t),this.sliderElement.removeEventListener("pointerup",i),this.sliderElement.removeEventListener("pointercancel",s)}}getSliderBounds(){return this.sliderElement.getBoundingClientRect()}mouseInSlider(e){const t=this.getSliderBounds(),i="x"in e?e:{x:e.clientX,y:e.clientY},s={x:i.x-t.left,y:i.y-t.top},n={x:s.x/t.width,y:-s.y/t.height+1},{startTime:o,duration:l}=this.getTimeRange(),a=o+n.x*l,{minValue:r,maxValue:h}=this.getValueRange();return{absolute:s,normalized:n,t:a,v:r+n.x*(h-r)}}pixelsToLayerValue(e,t){if(!e)return{yInLayer:0,toLayerHeight:1};const i="normalized"in e?e.normalized:this.mouseInSlider(e).normalized;let s=t.min,n=t.max;const o=n-s;return{yInLayer:this.isHangingDown(t)?(n-i.y)/o:(i.y-s)/o,toLayerHeight:o/this.getSliderBounds().height}}isHangingDown(e){return"down"===e.direction&&(e instanceof w||(this.warnedDirection.has(e.id)||(this.warnedDirection.add(e.id),console.warn("Layer direction 'down' is only supported for bar (intervals / histogram) layers, ignoring it for",e.name)),!1))}pixelsToModelValue(e,t){const{yInLayer:i,toLayerHeight:s}=this.pixelsToLayerValue(e,t);let n=0,o=100;if(t.model instanceof p){const e=this.valueRanges.get(t.yAxisGroup)||{min:0,max:1e3};n=e.min,o=e.max-n}return{yInModel:n+i*o,toModelHeight:s*o}}layerValueToPixels(e,t){let i=t.min,s=t.max;const n=s-i,o=this.getSliderBounds().height;return(1-(this.isHangingDown(t)?s-n*e:i+n*e))*o}modelValueToPixels(e,t){let i=0,s=100;if(t.model instanceof p){const e=t.model.getValueRange(this.getTimeRange());i=e.minValue,s=e.maxValue-i}const n=(e-i)/s;return this.layerValueToPixels(n,t)}findElements(e,t){if("column"===this.selectionOptions.pick)return this.findColumnElements(e);const{width:i,height:s}=this.getSliderBounds(),{normalized:n}=this.mouseInSlider(e),{startTime:o,endTime:l,duration:a}=this.getTimeRange(),r=o+n.x*a,h=t||12,d=h/i*a,m={startTime:r-1,endTime:r+1},c={startTime:r-.5*d,endTime:r+.5*d};return this.getVisibleLayers().map(e=>{const{yInModel:t,toModelHeight:i}=this.pixelsToModelValue({normalized:n},e),s=e instanceof w?0:h*i,o=e instanceof w?m:c,l={minValue:t-.5*s,maxValue:t+.5*s},a=e.model.dataInRange({...o,...l});return{layer:e,elements:a,t:r,v:t}}).filter(e=>e.elements.length>0)}findColumnElements(e,t){const{width:i}=this.getSliderBounds(),{normalized:s}=this.mouseInSlider(e),{startTime:n,duration:o}=this.getTimeRange(),l=n+s.x*o,a=(t||12)/i*o,r={startTime:l-1,endTime:l+1},h={startTime:l-.5*a,endTime:l+.5*a};return this.getVisibleLayers().map(e=>{const{yInModel:t}=this.pixelsToModelValue({normalized:s},e),i=e instanceof w?r:h,n=e.model.dataInTimeRange(i);return{layer:e,elements:n,t:l,v:t}}).filter(e=>e.elements.length>0)}clearHoverEvent(){this.hoverCleared||(this.hoverCleared=!0,this.renderColumnHoverBand([]),this.emit("hover",null))}updateHover(e){if(this.dragging)return this.hoverLine.style.display="none",this.toolTip.style.display="none",void this.clearHoverEvent();if(void 0!==e&&(this.mouse=e),this.mouse){const e=this.getSliderBounds(),{normalized:t}=this.mouseInSlider(this.mouse),{startTime:i,endTime:s,duration:o}=this.getTimeRange(),l=i+t.x*o,a=this.findElements(this.mouse);this.renderColumnHoverBand(a),this.hoverCleared=!1,this.emit("hover",{elements:a.map(e=>({layer:e.layer,elements:e.elements})),t:l,clientX:this.mouse.x,clientY:this.mouse.y});const r=a.map(e=>e.elements.map(t=>({e:t,l:e.layer,t:e.t,v:e.v}))).flat();let h;if(r.length>0)if(r.length>3){h=r.slice(0,2).map(e=>this.getLabel(e.e,e.l,e.t,e.v)).flat().filter(e=>null!==e).join(""),h+=`<div>And ${r.length-2} more...</div>`}else{h=a.map(e=>e.elements.map(t=>this.getLabel(t,e.layer,e.t,e.v))).flat().filter(e=>null!==e).join("")}if(!1===this.hoverOptions.tooltip)this.toolTip.style.display="none";else{const t=new Date(l),i=t.getHours(),s=t.getMinutes(),o=t.getSeconds(),a=n(l,!0,this._locale),r=`${(i<10?"0":"")+i}:${(s<10?"0":"")+s}:${(o<10?"0":"")+o}`;let d="";const m=this.ticksBar.cache.high.spacing;(void 0===this.hoverOptions.date?m>=M:this.hoverOptions.date)&&(d+=`<div>${a}</div>`),(void 0===this.hoverOptions.time?m<=V:this.hoverOptions.time)&&(d+=`<div>${r}</div>`),h&&(d+=h),this.toolTip.innerHTML=d,this.toolTip.style.display=d.length>0?"block":"none";const c=88,u=36;let g=this.mouse.x+10,p=this.mouse.y+10;const y=e.left+e.width-c-5,T=e.top+e.height-u-5;g>y&&(g=y),p>T&&(p=T),this.toolTip.style.left=`${g}px`,this.toolTip.style.top=`${p}px`}}else this.hoverLine.style.display="none",this.toolTip.style.display="none",this.clearHoverEvent()}initHoverTooltip(){this.toolTip.style.position="fixed",this.applyTooltipStyle(this.toolTip),this.toolTip.style.pointerEvents="none",this.toolTip.style.display="none",this.toolTip.style.zIndex="10",this.toolTip.style.overflow="hidden",document.body.appendChild(this.toolTip),this.onDestroy.push(()=>this.toolTip.remove())}initTickTooltip(){this.tickToolTip.style.position="fixed",this.applyTooltipStyle(this.tickToolTip),this.tickToolTip.style.pointerEvents="none",this.tickToolTip.style.display="none",this.tickToolTip.style.zIndex="10",this.tickToolTip.style.whiteSpace="pre-line",document.body.appendChild(this.tickToolTip),this.onDestroy.push(()=>this.tickToolTip.remove())}initHoverListener(){const e=()=>{this.updateHover(null)},t=e=>{this.updateHover({x:e.clientX,y:e.clientY})};this.sliderElement.addEventListener("mouseleave",e),this.sliderElement.addEventListener("mousemove",t);const i=this.listenToClicks();return()=>{this.sliderElement.removeEventListener("mouseleave",e),this.sliderElement.removeEventListener("mousemove",t),i()}}syncSize(){const e=()=>{const{offsetWidth:e,offsetHeight:t}=this.sliderElement;this.setSize(e,t)},t=new ResizeObserver(()=>{e()});this.container&&(t.observe(this.container),e()),this.onDestroy.push(()=>{t.disconnect()})}getTimeRange(){return y(this.range.time)}getValueRange(){return this.range.value}setTimeRange(e,t){const i=this.getTimeRange(),s=v(i,e),n=this._limits?f(s,this._limits):s;n.startTime===i.startTime&&n.endTime===i.endTime||(this.range.time=n,this.engine?.setTimeRange(n),this.showTicks&&this.ticksBar.setTimeRange(n),this.emit("time",n),this.updateHover())}setValueRange(e){const t={...this.getValueRange(),...e};this.range.value=t,this.engine?.setValueRange(t),this.emit("value",this.getValueRange()),this.updateHover()}setSize(e,t){this.size={width:e,height:t},this.showTicks&&this.ticksBar.setWidth(e);const i=this.getTimeRange(),s=this.getValueRange();this.engine?.setSize(e,t),this.setTimeRange(i),this.setValueRange(s),this.renderYAxis(),this.updateHover(),this.emit("size",{width:e,height:t})}up(e){const{startTime:t,endTime:i,duration:s}=this.getTimeRange();let n={startTime:0,endTime:Date.now()};const o=.5*(t+i);if(s>=.9*C){const e=X(10*s);n=h(o,e)}else{const e=[{threshold:S,unit:"millennium"},{threshold:k,unit:"century"},{threshold:_,unit:"decade"},{threshold:.9*D,unit:"year"},{threshold:H,unit:"month"},{threshold:V,unit:"week"},{threshold:M,unit:"day"},{threshold:B,unit:"hour"},{threshold:z,unit:"minute"},{threshold:-1,unit:"second"}];let t=0;for(;s<.9*e[t].threshold;)t++;const i=e[t].unit;n=m(o,i)}this.fit({...e,range:n})}down(e){const{startTime:t,endTime:i,duration:s}=this.getTimeRange();let n={startTime:0,endTime:Date.now()};const o=t+("number"==typeof e?.xRatio?e.xRatio:.5)*s;if(s>=.9*C){const e=X(s/10);n=h(o,e)}else{let e=0;const t=[{threshold:C,unit:"century"},{threshold:S,unit:"decade"},{threshold:k,unit:"year"},{threshold:_,unit:"month"},{threshold:1.1*D,unit:"week"},{threshold:H,unit:"day"},{threshold:V,unit:"hour"},{threshold:M,unit:"minute"},{threshold:B,unit:"second"}].reverse();for(;s>1.1*t[e].threshold;)e++;const i=t[e].unit;n=m(o,i)}this.fit({...e,range:n})}next(e){this.skip(1,e)}previous(e){this.skip(-1,e)}skip(e,t){this.fit({...t,range:this.getSkipRange(e)})}getSkipRange(e){const{startTime:t,endTime:i,duration:s}=this.getTimeRange(),n=.5*(t+i);if(s>=9*C){const e=X(s);return h(n,e)}const o=d(t,i);return o?m(n,o,Math.round(e)):{startTime:t+e*s,endTime:i+e*s}}clampToLimits(e){return this._limits?f(e,this._limits):e}describeStep(e){const t=this.getTimeRange(),i=this.getSkipRange(e),n=this.clampToLimits(i),o=Math.abs(n.startTime-t.startTime)>W||Math.abs(n.endTime-t.endTime)>W,l=o?n:i,a=e>0?s(t.startTime,l.startTime,this._locale):s(l.startTime,t.startTime,this._locale);return{range:n,canStep:o,span:a,label:Y(e,a,this._locale)}}canStep(e){return this.describeStep(e).canStep}describeSpan(e){const{startTime:t,endTime:i}=e??this.getTimeRange(),n=s(t,i,this._locale);return{text:n.text,exact:n.exact,dates:a(t,i,this._locale)}}isFitted(e){const t=this.clampToLimits(e??this.getLayersTimeRange()),i=this.getTimeRange(),s=Math.max(W,.001*(t.endTime-t.startTime));return Math.abs(i.startTime-t.startTime)<=s&&Math.abs(i.endTime-t.endTime)<=s}getTimeRangeLabel(){const{startTime:e,endTime:t}=this.getTimeRange(),i=new Date(e);return 0===i.getHours()&&0===i.getMinutes()&&0===i.getSeconds()&&0===i.getMilliseconds()?o([e,t],this._locale):void 0}cancelFit(){this.stopFit?.(),this.stopFit=void 0}fit(e){this.cancelFit();const t=e?.range||this.getLayersTimeRange(),s=e?.padding?T(t,e.padding):t;if(e?.animate){const t=this.getTimeRange();this.stopFit=i(t.startTime,t.endTime,s.startTime,s.endTime,e.animate,(e,t)=>this.setTimeRange({startTime:e,endTime:t}))}else this.setTimeRange(s)}getLayersTimeRange(){if(0===this.getVisibleLayers().length)return A;{let e=this.getVisibleLayers()[0].model.getTimeRange();return this.getVisibleLayers().slice(1).forEach(t=>e=L(e,t.model.getTimeRange())),e}}loadLayer(e){if(e.timeSlider&&e.timeSlider.id!==this.id)console.warn("Layer already has been loaded by another time slider, create a new layer with the same model and style if you want to add it to another time slider");else if(this.engine&&this._engineReady&&!this.layerUnloaders.has(e.id)){const t=[...this.loaders].filter(e=>this.engine instanceof e.engineClass).find(t=>t.canLoad(e));if(t){const i=Date.now(),s=t.load(e,this.engine.scene,this.engine,this);e.timeSlider=this,e.loaded=!0;const n=e.name||e.constructor.name+"#"+e.id.slice(e.id.length-4).toUpperCase();console.log(`Loaded ${n} in ${l(Date.now()-i)}`);const o=this.syncMaxValuesForLayer(e);this.layerLoaders.set(e.id,t),this.layerUnloaders.set(e.id,()=>{o(),s(),this.layerLoaders.delete(e.id),e.loaded=!1})}else console.warn("No loader found for",{layer:e})}}unloadLayer(e){this.selectedLayerId===e.id&&this.select(null),this.layerUnloaders.has(e.id)&&(this.layerUnloaders.get(e.id)(),this.layerUnloaders.delete(e.id))}addLayer(e){if(this.layers.push(e),e.visible){this.loadLayer(e);const t=e.model.getTimeRange();if(1===this.getVisibleLayers().length)this.setTimeRange(t);else{const e=L(this.getTimeRange(),t);this.setTimeRange(e)}}const t=[e.on("visible",()=>{e.visible?this.loadLayer(e):this.unloadLayer(e),this.updateAllValueRanges()}),e.on("min",()=>this.onLayerBandChange(e)),e.on("max",()=>this.onLayerBandChange(e)),e.on("direction",()=>this.onLayerBandChange(e))];this.layerListenerStoppers.set(e.id,()=>t.forEach(e=>e()))}onLayerBandChange(e){const t=this.layerLoaders.get(e.id);t&&!t.followsLayerBand&&(this.layerUnloaders.get(e.id)?.(),this.layerUnloaders.delete(e.id),this.loadLayer(e)),this.updateAllValueRanges()}removeLayer(e){const t="string"==typeof e?e:e.id,i=this.layers.find(e=>e.id===t);i&&(this.unloadLayer(i),this.layerUnloaders.delete(t),this.layerListenerStoppers.get(t)?.(),this.layerListenerStoppers.delete(t),this.layers=this.layers.filter(e=>e.id!==t))}destroy(){this.layers.map(e=>e.id).forEach(e=>this.removeLayer(e)),this.onDestroy.forEach(e=>e())}addHorizontalPinchZoom(){let e=null,t=null;const i=()=>{this.pinching=!1,e=null,t=null},s=i=>{if(2===i.touches.length){i.preventDefault(),this.dragging=!1,this.pinching=!0;const s=this.mouseInSlider(i.touches[0]).t,n=this.mouseInSlider(i.touches[1]).t;e=Math.min(s,n),t=Math.max(s,n)}},n=s=>{if(2===s.touches.length){if(null===e||null===t)return;s.preventDefault(),this.dragging=!1;const i=this.mouseInSlider(s.touches[0]).normalized.x,n=this.mouseInSlider(s.touches[1]).normalized.x,o=Math.min(i,n),l=Math.max(i,n),a=(t-e)/(l-o);let r=e-o*a,h=t+(1-l)*a;if(a>P){const e=.5*(r+h);r=e-.5*P,h=e+.5*P}if(a<U){const e=.5*(r+h);r=e-.5*U,h=e+.5*U}this.setTimeRange({startTime:r,endTime:h})}else i()},o=()=>{i()};this.sliderElement.addEventListener("touchstart",s,{passive:!1}),this.sliderElement.addEventListener("touchmove",n,{passive:!1}),this.sliderElement.addEventListener("touchend",o),this.sliderElement.addEventListener("touchcancel",o),this.onDestroy.push(()=>{this.sliderElement.removeEventListener("touchstart",s),this.sliderElement.removeEventListener("touchmove",n),this.sliderElement.removeEventListener("touchend",o),this.sliderElement.removeEventListener("touchcancel",o)})}initControls(){const e=new t;this.dragging=!1;let i,s=0,n=0,o=0;e.add(this.initHoverListener());const l=e=>{this.sliderElement.focus(),e.preventDefault();const t=this.mouseInSlider(e);this.zoom(.001*e.deltaY,{x:t.normalized.x})};let a=0,r=-1;const h=e=>{const t=Date.now(),i=t-a;r>=0&&i<300&&i>0&&(this.select(null),e.preventDefault(),this.zoom(-.75,{animate:1e3,x:r})),a=t},d=e=>{e.preventDefault(),this.select(null);const t=this.mouseInSlider(e);this.zoom(-.75,{animate:1e3,x:t.normalized.x})};this.selectingRangeElement.style.height="calc(100% - 6px)",this.selectingRangeElement.style.pointerEvents="none",this.selectingRangeElement.style.position="absolute",this.selectingRangeElement.style.border="3px solid #df0",this.selectingRangeElement.style.background="#df04",this.selectingRangeElement.style.top="0px";const m=e=>{if(this.sliderElement.focus(),2===e.button||0===e.button){e.preventDefault();const t=this.mouseInSlider(e),i=t.normalized.x,{startTime:l,duration:a}=this.getTimeRange();s=l+i*a,n=Date.now(),o=t.absolute.x,(e.ctrlKey||2===e.button)&&(this.selectingRange=!0),this.selectingRange?(this.selectingRangeElement.style.left=t.absolute.x+"px",this.selectingRangeElement.style.width="0px",this.container.appendChild(this.selectingRangeElement),this.selectingRangeVisible=!0):this.dragging=!0}},c=e=>{const t=this.mouseInSlider(e);return i=t.normalized.x,t},u=e=>{const t=c(e);if(this.selectingRange){const e=Math.abs(o-t.absolute.x),i=Math.min(o,t.absolute.x);this.selectingRangeElement.style.left=i+"px",this.selectingRangeElement.style.width=e-6+"px"}if(!this.dragging||Date.now()-n<100&&Math.abs(o-t.absolute.x)<10)return;e.preventDefault();const i=t.normalized.x,{startTime:l,endTime:a}=this.getTimeRange(),r=l+i*(a-l)-s;this.setTimeRange({startTime:l-r,endTime:a-r})},g=e=>{const t=this.getSliderBounds(),n=t.height-(e.clientY-t.top);if(!this.dragging&&!this.pinching&&n<8)this.ticksBar.click(e);else if(i=void 0,this.dragging=!1,this.selectingRange){const t=this.mouseInSlider(e).normalized.x,{startTime:i,endTime:n}=this.getTimeRange(),o=i+t*(n-i);"zoom"===this.selectRangeMode&&this.fit({range:{startTime:Math.min(s,o),endTime:Math.max(s,o)},animate:1e3}),"select"===this.selectRangeMode&&this.select(null,{startTime:Math.min(s,o),endTime:Math.max(s,o)}),"createSelection"===this.selectRangeMode&&this.setSelection([...this.getSelection(),{startTime:Math.min(s,o),endTime:Math.max(s,o)}])}this.stopSelectingRange()},p=e=>{this.pressedKeys.add(e.code)},y=e=>{this.pressedKeys.delete(e.code),"Space"===e.code&&(e.preventDefault(),this.togglePlaying()),"KeyF"===e.code&&(e.preventDefault(),this.fit({animate:3e3}));const t=void 0;"ArrowRight"===e.code&&(e.preventDefault(),this.skip(1,{animate:t})),"ArrowLeft"===e.code&&(e.preventDefault(),this.skip(-1,{animate:t})),"ArrowUp"===e.code&&(e.preventDefault(),this.up({animate:t})),"ArrowDown"===e.code&&(e.preventDefault(),this.down({xRatio:i,animate:t}))};let T=!0;const v=()=>{if(T){const e=this.pressedKeys.has("ShiftLeft")?.05:.01,t=2*e,s=this.pressedKeys.has("KeyD"),n=this.pressedKeys.has("KeyA");s&&this.shift(-e),n&&this.shift(e),this.pressedKeys.has("KeyW")&&this.zoom(-t,{x:s||n?void 0:i}),this.pressedKeys.has("KeyS")&&this.zoom(t,{x:s||n?void 0:i}),requestAnimationFrame(v)}};v(),e.add(()=>T=!1);const f=e=>e.preventDefault();this.sliderElement.addEventListener("contextmenu",f),this.sliderElement.addEventListener("wheel",l),this.sliderElement.addEventListener("pointerdown",m),this.sliderElement.addEventListener("pointermove",u),this.sliderElement.addEventListener("pointerup",g),this.sliderElement.addEventListener("dblclick",d),this.sliderElement.addEventListener("touchend",h),this.sliderElement.addEventListener("touchstart",e=>{if(1===e.touches.length){const t=this.mouseInSlider(e.touches[0]);r=t.normalized.x}else r=-1}),this.sliderElement.addEventListener("keydown",p),this.sliderElement.addEventListener("keyup",y),this.addHorizontalPinchZoom();const E=()=>{this.sliderElement.removeEventListener("contextmenu",f),this.sliderElement.removeEventListener("wheel",l),this.sliderElement.removeEventListener("pointerdown",m),this.sliderElement.removeEventListener("pointermove",u),this.sliderElement.removeEventListener("pointerup",g),this.sliderElement.removeEventListener("dblclick",d),this.sliderElement.removeEventListener("touchend",h),this.sliderElement.removeEventListener("keydown",p),this.sliderElement.removeEventListener("keyup",y)};return this.onDestroy.push(E),e.add(E),e.disposeLater()}shift(e,t){const{startTime:i,endTime:s,duration:n}=this.getTimeRange(),o=t?.absolute?e:e*n;this.fit({...t,range:{startTime:i-o,endTime:s-o}})}zoom(e,t){const i="number"==typeof t?.x?t.x:.5,{startTime:s,endTime:n,duration:o}=this.getTimeRange(),l={startTime:s-e*o*i,endTime:n+e*o*(1-i)},a=l.endTime-l.startTime,r=l.startTime+.5*a;a>P&&(l.startTime=r-.5*P,l.endTime=r+.5*P),a<U&&(l.startTime=r-.5*U,l.endTime=r+.5*U),this.fit({range:l,animate:t?.animate})}togglePlaying(){this.playing?this.pause():this.play()}isPlaying(){return this.playing}play(){if(this.playing)return;this.playing=!0,this.emit("playing",!0);let e=Date.now();const t=()=>{if(this.playing){if(this.live){const{duration:e}=this.getTimeRange(),t=Date.now(),i=10/this.size.width;this.setTimeRange({startTime:t-(1-i)*e,endTime:t+i*e}),this.setCurrent(t)}else if("auto"!==this.playSpeed||this.showCurrent){const t=Date.now(),i=t-e,s=this.current-this.getTimeRange().startTime,n="auto"===this.playSpeed?1:this.playSpeed;this.shift(-n*i,{absolute:!0}),this.setCurrent(this.getTimeRange().startTime+s),e=t}else{const t=Date.now();let i=5e-5*(t-e);this.pressedKeys.has("ShiftLeft")&&(i*=5);const s=this.current-this.getTimeRange().startTime;this.shift(-i),this.setCurrent(this.getTimeRange().startTime+s),e=t}requestAnimationFrame(t)}};t()}setCurrent(e){this.current!==e&&(Math.abs(e-Date.now())>100&&this.live&&(this.pause(),this.setLive(!1)),this.current=e,this.emit("current",e))}pause(){this.playing&&(this.playing=!1,this.emit("playing",!1),this.setLive(!1))}setLive(e){if(this.live!==e){if(this.live=e,e){const e=this.getTimeRange();this.fit({range:{startTime:e.endTime-this.liveRange,endTime:e.endTime},animate:z}),this.play()}this.emit("live",e)}}}function j(e){return{startTime:"number"==typeof e?.startTime?e.startTime:"number"==typeof e?.endTime?e.duration?e.endTime-e.duration:e.endTime-V:e?.duration?Date.now()-.5*e.duration:Date.now()-.5*V,endTime:"number"==typeof e?.endTime?e.endTime:"number"==typeof e?.startTime?e.duration?e.startTime+e.duration:e.startTime+V:e?.duration?Date.now()+.5*e.duration:Date.now()+.5*V,duration:e?.duration?e.duration:"number"==typeof e?.startTime&&"number"==typeof e?.endTime?e.endTime+e.startTime:V,minValue:"number"==typeof e?.minValue?e.minValue:"number"==typeof e?.maxValue?e.maxValue>0?0:e.maxValue-100:0,maxValue:"number"==typeof e?.maxValue?e.maxValue:"number"==typeof e?.minValue?e.minValue<0?0:e.minValue+100:100}}const W=z,K={en:{next:"Next {span}",previous:"Previous {span}",nextUnit:"Next {unit}",previousUnit:"Previous {unit}"},de:{next:"Weiter: {span}",previous:"Zurück: {span}"},fr:{next:"Suivant : {span}",previous:"Précédent : {span}"},nl:{next:"Volgende: {span}",previous:"Vorige: {span}"}};function Y(e,t,i){const s=K[i.split("-")[0].toLowerCase()];if(!s)return(e>0?"→ ":"← ")+t.text;const n=e>0?s.nextUnit:s.previousUnit;return n&&t.unitName?n.replace("{unit}",t.unitName):(e>0?s.next:s.previous).replace("{span}",t.text)}function X(e){const t=e/315576e5,i=Math.log10(t),s=Math.ceil(i);return Math.pow(10,s)}
@@ -14,8 +14,13 @@ export declare abstract class TimeSliderEngine<T> extends TypedEventEmitter<{
14
14
  private valueRange;
15
15
  private size;
16
16
  theme: 'dark' | 'light';
17
+ /** Clear colour behind the layers; undefined for the theme's default (#111 dark, #eee light). */
18
+ background?: string;
17
19
  constructor(scene: T);
18
20
  setTheme(theme: 'dark' | 'light'): void;
21
+ /** Sets the clear colour behind the layers, kept across theme changes; undefined goes back to the theme's default. */
22
+ setBackground(background: string | undefined): void;
23
+ protected getClearColor(): string;
19
24
  getTimeRange(): {
20
25
  duration: number;
21
26
  startTime: number;
@@ -1 +1 @@
1
- import{TypedEventEmitter as e}from"../event/TypedEventEmitter.js";import{TODAY as t}from"../ticks/TimeSliderConstants.js";import{addDuration as i}from"../data/TimeSliderRange.js";export class TimeSliderEngine extends e{constructor(e){super(),this.scene=e,this.timeRange=t,this.valueRange={minValue:0,maxValue:100},this.size={width:1e3,height:100},this.theme="dark"}setTheme(e){this.theme=e,this.setTimeRange(this.timeRange)}getTimeRange(){return{...this.timeRange,duration:this.timeRange.endTime-this.timeRange.startTime}}getValueRange(){return this.valueRange}getSize(){return this.size}showStats(e){}dispose(){this.destroy(),this.size={width:1e3,height:100}}setTimeRange(e){if(e.startTime!==this.timeRange.startTime||e.endTime!==this.timeRange.endTime){this.timeRange=e;const t=i(e);this.updateTimeRange(t),this.emit("time",t)}}setValueRange(e){e.minValue===this.valueRange.minValue&&e.maxValue===this.valueRange.maxValue||(this.valueRange=e,this.updateValueRange(e),this.emit("value",e))}setSize(e,t){this.size.width===e&&this.size.height===t||(this.size={width:e,height:t},this.updateSize(e,t),this.emit("size",this.size))}}
1
+ import{TypedEventEmitter as e}from"../event/TypedEventEmitter.js";import{TODAY as t}from"../ticks/TimeSliderConstants.js";import{addDuration as i}from"../data/TimeSliderRange.js";export class TimeSliderEngine extends e{constructor(e){super(),this.scene=e,this.timeRange=t,this.valueRange={minValue:0,maxValue:100},this.size={width:1e3,height:100},this.theme="dark"}setTheme(e){this.theme=e,this.setTimeRange(this.timeRange)}setBackground(e){this.background=e,this.setTheme(this.theme)}getClearColor(){return this.background??("dark"===this.theme?"#111":"#eee")}getTimeRange(){return{...this.timeRange,duration:this.timeRange.endTime-this.timeRange.startTime}}getValueRange(){return this.valueRange}getSize(){return this.size}showStats(e){}dispose(){this.destroy(),this.size={width:1e3,height:100}}setTimeRange(e){if(e.startTime!==this.timeRange.startTime||e.endTime!==this.timeRange.endTime){this.timeRange=e;const t=i(e);this.updateTimeRange(t),this.emit("time",t)}}setValueRange(e){e.minValue===this.valueRange.minValue&&e.maxValue===this.valueRange.maxValue||(this.valueRange=e,this.updateValueRange(e),this.emit("value",e))}setSize(e,t){this.size.width===e&&this.size.height===t||(this.size={width:e,height:t},this.updateSize(e,t),this.emit("size",this.size))}}
package/index.d.ts CHANGED
@@ -54,6 +54,9 @@ export * from './ticks/TimeSliderConstants';
54
54
  export * from './ticks/TimeTicksBar';
55
55
  export * from './ticks/YAxisTicks';
56
56
  export * from './common/TimeTools';
57
+ export * from './common/Font';
58
+ export * from './common/Pixels';
59
+ export { snapLinesToPixels } from './three/PixelSnap';
57
60
  export * from './common/Easing';
58
61
  export * from './common/Clamper';
59
62
  export * from './common/Disposables';
package/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./engine/TimeSlider.js";export*from"./engine/TimeSliderEngine.js";export*from"./engine/PinchZoom.js";export*from"./engine/HorizontalPinchZoom.js";export*from"./three/ThreeTimeSlider.js";export*from"./three/ThreeTimeSliderEngine.js";export*from"./three/Three3DTimeSliderEngine.js";export*from"./three/react/ReactThreeTimeSlider.js";export*from"./three/layer/ThreeTimeLayerLoader.js";export*from"./three/layer/ThreePointsTimeLayerLoader.js";export*from"./three/layer/ThreeIntervalsTimeLayerLoader.js";export*from"./three/layer/Three3DPointsTimeLayerLoader.js";export*from"./three/layer/Three3DIntervalsTimeLayerLoader.js";export*from"./react/ReactTimeSlider.js";export*from"./react/ColoredSlider.js";export{ColoredSliderExtendsInput}from"./react/ColoredSliderExtendsInput.js";export*from"./react/FullscreenButton.js";export*from"./react/MaterialIconButton.js";export*from"./react/MaterialTextButton.js";export*from"./react/DateLabelWithPicker.js";export*from"./react/TimeLabelWithPicker.js";export*from"./react/DateRangeLabelWithPicker.js";export*from"./react/YearScroller.js";export*from"./react/PointerClick.js";export*from"./react/PinchZoomHook.js";export*from"./react/DeepMemoHook.js";export*from"./data/TimeSliderElement.js";export*from"./data/TimeSliderModel.js";export*from"./data/TimeSliderPointsModel.js";export*from"./data/TimeSliderIntervalsModel.js";export*from"./data/CompositeTimeSliderModel.js";export*from"./data/TimeSliderRange.js";export*from"./data/TimeSliderBush.js";export*from"./data/IntervalLaneManager.js";export*from"./data/TimeSliderLanesModel.js";export*from"./data/histogram/TimeSliderHistogram.js";export*from"./data/histogram/TimeSliderFixedHistogram.js";export*from"./data/histogram/TimeSliderDynamicHistogram.js";export*from"./data/histogram/TimeSliderSplittingFixedHistogram.js";export*from"./layer/TimeSliderLayer.js";export*from"./layer/TimeSliderLayerLoader.js";export*from"./layer/TimeSliderPointsLayer.js";export*from"./layer/TimeSliderIntervalsLayer.js";export*from"./layer/TimeSliderHistogramLayer.js";export*from"./style/TimeSliderStyle.js";export*from"./style/TimeSliderStyler.js";export*from"./style/TimeSliderColors.js";export*from"./style/TimeSliderLabeler.js";export*from"./style/TimeSliderPointsStyle.js";export*from"./style/TimeSliderIntervalsStyle.js";export*from"./style/TimeSliderHistogramStyle.js";export*from"./ticks/TimeSliderConstants.js";export*from"./ticks/TimeTicksBar.js";export*from"./ticks/YAxisTicks.js";export*from"./common/TimeTools.js";export*from"./common/Easing.js";export*from"./common/Clamper.js";export*from"./common/Disposables.js";export*from"./common/Expand.js";export*from"./common/GlslTools.js";export*from"./event/TypedEventEmitter.js";
1
+ export*from"./engine/TimeSlider.js";export*from"./engine/TimeSliderEngine.js";export*from"./engine/PinchZoom.js";export*from"./engine/HorizontalPinchZoom.js";export*from"./three/ThreeTimeSlider.js";export*from"./three/ThreeTimeSliderEngine.js";export*from"./three/Three3DTimeSliderEngine.js";export*from"./three/react/ReactThreeTimeSlider.js";export*from"./three/layer/ThreeTimeLayerLoader.js";export*from"./three/layer/ThreePointsTimeLayerLoader.js";export*from"./three/layer/ThreeIntervalsTimeLayerLoader.js";export*from"./three/layer/Three3DPointsTimeLayerLoader.js";export*from"./three/layer/Three3DIntervalsTimeLayerLoader.js";export*from"./react/ReactTimeSlider.js";export*from"./react/ColoredSlider.js";export{ColoredSliderExtendsInput}from"./react/ColoredSliderExtendsInput.js";export*from"./react/FullscreenButton.js";export*from"./react/MaterialIconButton.js";export*from"./react/MaterialTextButton.js";export*from"./react/DateLabelWithPicker.js";export*from"./react/TimeLabelWithPicker.js";export*from"./react/DateRangeLabelWithPicker.js";export*from"./react/YearScroller.js";export*from"./react/PointerClick.js";export*from"./react/PinchZoomHook.js";export*from"./react/DeepMemoHook.js";export*from"./data/TimeSliderElement.js";export*from"./data/TimeSliderModel.js";export*from"./data/TimeSliderPointsModel.js";export*from"./data/TimeSliderIntervalsModel.js";export*from"./data/CompositeTimeSliderModel.js";export*from"./data/TimeSliderRange.js";export*from"./data/TimeSliderBush.js";export*from"./data/IntervalLaneManager.js";export*from"./data/TimeSliderLanesModel.js";export*from"./data/histogram/TimeSliderHistogram.js";export*from"./data/histogram/TimeSliderFixedHistogram.js";export*from"./data/histogram/TimeSliderDynamicHistogram.js";export*from"./data/histogram/TimeSliderSplittingFixedHistogram.js";export*from"./layer/TimeSliderLayer.js";export*from"./layer/TimeSliderLayerLoader.js";export*from"./layer/TimeSliderPointsLayer.js";export*from"./layer/TimeSliderIntervalsLayer.js";export*from"./layer/TimeSliderHistogramLayer.js";export*from"./style/TimeSliderStyle.js";export*from"./style/TimeSliderStyler.js";export*from"./style/TimeSliderColors.js";export*from"./style/TimeSliderLabeler.js";export*from"./style/TimeSliderPointsStyle.js";export*from"./style/TimeSliderIntervalsStyle.js";export*from"./style/TimeSliderHistogramStyle.js";export*from"./ticks/TimeSliderConstants.js";export*from"./ticks/TimeTicksBar.js";export*from"./ticks/YAxisTicks.js";export*from"./common/TimeTools.js";export*from"./common/Font.js";export*from"./common/Pixels.js";export{snapLinesToPixels}from"./three/PixelSnap.js";export*from"./common/Easing.js";export*from"./common/Clamper.js";export*from"./common/Disposables.js";export*from"./common/Expand.js";export*from"./common/GlslTools.js";export*from"./event/TypedEventEmitter.js";
@@ -12,17 +12,21 @@ export type TimeSliderLayerOptions<E extends TimeSliderElement, S extends TimeSl
12
12
  name?: string;
13
13
  min?: number;
14
14
  max?: number;
15
+ /** 'up' (default): value 0 at the band's min (bottom), bars grow upwards. 'down': value 0 at the band's max (top), bars hang down. The layer's y-axis, hover values and picking follow it. Bar layers (intervals, histograms) only for now. */
16
+ direction?: TimeSliderLayerDirection;
15
17
  visible?: boolean;
16
18
  style?: S;
17
19
  styler?: TimeSliderStyler<E, S>;
18
20
  labeler?: TimeSliderLabeler<E>;
19
21
  yAxis?: YAxisSettings;
20
22
  };
23
+ export type TimeSliderLayerDirection = 'up' | 'down';
21
24
  export declare abstract class TimeSliderLayer<E extends TimeSliderElement = TimeSliderElement, S extends TimeSliderStyle = TimeSliderStyle> extends TypedEventEmitter<{
22
25
  selection: E[];
23
26
  name: string;
24
27
  min: number;
25
28
  max: number;
29
+ direction: TimeSliderLayerDirection;
26
30
  visible: boolean;
27
31
  loaded?: boolean;
28
32
  invalidate: string;
@@ -32,6 +36,7 @@ export declare abstract class TimeSliderLayer<E extends TimeSliderElement = Time
32
36
  id: string;
33
37
  private _min;
34
38
  private _max;
39
+ private _direction;
35
40
  private _name;
36
41
  private _visible;
37
42
  private _loaded;
@@ -54,6 +59,9 @@ export declare abstract class TimeSliderLayer<E extends TimeSliderElement = Time
54
59
  set min(newMin: number);
55
60
  get max(): number;
56
61
  set max(newMax: number);
62
+ get direction(): TimeSliderLayerDirection;
63
+ /** See TimeSliderLayerOptions.direction. */
64
+ set direction(newDirection: TimeSliderLayerDirection);
57
65
  get timeSlider(): TimeSlider | undefined;
58
66
  set timeSlider(timeSlider: TimeSlider | undefined);
59
67
  get name(): string;
@@ -1 +1 @@
1
- import{v4 as i}from"uuid";import{TypedEventEmitter as t}from"../event/TypedEventEmitter.js";export class TimeSliderLayer extends t{constructor(t,e){super(),this.model=t,this._visible=!0,this._loaded=!1,this.id=e?.id||i(),this.style=e?.style||this.getDefaultStyle(),this._styler=e?.styler,this.labeler=e?.labeler,this.yAxis=e?.yAxis||{},this._name=e?.name||t.name||"Unknown",this._min=e?.min||0,this._max=e?.max||1,this._visible="boolean"!=typeof e?.visible||e.visible}get yAxisGroup(){return this.yAxis.group||this.id}get styler(){return this._styler}set styler(i){this._styler=i,this.invalidateAll()}invalidate(i){const t="string"==typeof i?i:i.id;this.emit("invalidate",t)}invalidateAll(){this.emit("invalidateAll")}select(i){this.emit("selection",i)}get min(){return this._min}set min(i){i!==this._min&&(this._min=i,this.emit("min",i))}get max(){return this._max}set max(i){i!==this._max&&(this._max=i,this.emit("max",i))}get timeSlider(){return this._timeSlider}set timeSlider(i){this._timeSlider=i}get name(){return this._name}set name(i){i!==this._name&&(this._name=i,this.emit("name",i))}get visible(){return this._visible}set visible(i){i!==this._visible&&(this._visible=i,this.emit("visible",i),this.shouldModelLoad())}get loaded(){return this._loaded}set loaded(i){i!==this._loaded&&(this._loaded=i,this.shouldModelLoad(),this.emit("loaded",i))}shouldModelLoad(){const i=this.loaded&&this.visible;this.model.loaded(this.id,i)}}
1
+ import{v4 as i}from"uuid";import{TypedEventEmitter as t}from"../event/TypedEventEmitter.js";export class TimeSliderLayer extends t{constructor(t,e){super(),this.model=t,this._visible=!0,this._loaded=!1,this.id=e?.id||i(),this.style=e?.style||this.getDefaultStyle(),this._styler=e?.styler,this.labeler=e?.labeler,this.yAxis=e?.yAxis||{},this._name=e?.name||t.name||"Unknown",this._min=e?.min||0,this._max=e?.max||1,this._direction=e?.direction||"up",this._visible="boolean"!=typeof e?.visible||e.visible}get yAxisGroup(){return this.yAxis.group||this.id}get styler(){return this._styler}set styler(i){this._styler=i,this.invalidateAll()}invalidate(i){const t="string"==typeof i?i:i.id;this.emit("invalidate",t)}invalidateAll(){this.emit("invalidateAll")}select(i){this.emit("selection",i)}get min(){return this._min}set min(i){i!==this._min&&(this._min=i,this.emit("min",i))}get max(){return this._max}set max(i){i!==this._max&&(this._max=i,this.emit("max",i))}get direction(){return this._direction}set direction(i){i!==this._direction&&(this._direction=i,this.emit("direction",i))}get timeSlider(){return this._timeSlider}set timeSlider(i){this._timeSlider=i}get name(){return this._name}set name(i){i!==this._name&&(this._name=i,this.emit("name",i))}get visible(){return this._visible}set visible(i){i!==this._visible&&(this._visible=i,this.emit("visible",i),this.shouldModelLoad())}get loaded(){return this._loaded}set loaded(i){i!==this._loaded&&(this._loaded=i,this.shouldModelLoad(),this.emit("loaded",i))}shouldModelLoad(){const i=this.loaded&&this.visible;this.model.loaded(this.id,i)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protomaze/time",
3
- "version": "1.5.4",
3
+ "version": "1.7.0",
4
4
  "description": "Protomaze Time sliders",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1 +1 @@
1
- import e,{forwardRef as t,useCallback as n,useEffect as r,useImperativeHandle as i,useLayoutEffect as a,useMemo as o,useRef as l,useState as s}from"react";import{Disposables as c}from"../common/Disposables.js";import{formatDuration as m}from"../common/TimeTools.js";import{TimeSlider as d}from"../engine/TimeSlider.js";import{ONE_YEAR as p}from"../ticks/TimeSliderConstants.js";import{ColoredSlider as u}from"./ColoredSlider.js";import{DateLabelWithPicker as h}from"./DateLabelWithPicker.js";import{DateRangeLabelWithPicker as g}from"./DateRangeLabelWithPicker.js";import{MaterialIconButton as f}from"./MaterialIconButton.js";import{MaterialTextButton as x}from"./MaterialTextButton.js";import{TimeLabelWithPicker as w}from"./TimeLabelWithPicker.js";const y={padding:"4px 8px",background:"#0008",borderRadius:"4px",color:"#fff",fontFamily:"monospace",fontSize:"12px"},b=[Date.now()-p,Date.now()];function v(e,t){return e[0]===t[0]&&e[1]===t[1]}function E(e){return JSON.stringify(e)}export const ReactTimeSlider=t(({timeRange:t,onTimeRange:n,selectRangeButton:a,noPlayButton:p,noTimeLabels:k,noRangeLabel:C,noDateLabels:L,theme:R,canHideLabels:S,verticalGap:N,engine:I,layers:_,loaders:D,showStats:z,currentSlider:j,...M},P)=>{const W=o(()=>new d(M),[]);r(()=>{M?.showCurrent&&(W.showCurrent=M?.showCurrent)},[M.showCurrent]),r(()=>{M?.showTicks&&(W.showTicks=M?.showTicks)},[M.showTicks]),r(()=>{M?.playSpeed&&(W.playSpeed=M?.playSpeed)},[M.playSpeed]),r(()=>{M?.liveRange&&(W.liveRange=M?.liveRange)},[M.liveRange]),r(()=>{M?.locale&&W.setLocale(M.locale)},[M.locale]),r(()=>{M?.weekLabel&&W.setWeekLabel(M.weekLabel,M.weekLabelPrefix)},[M.weekLabel,M.weekLabelPrefix]),r(()=>{W.setTickLabelStyler(M?.tickLabelStyler)},[M.tickLabelStyler]),r(()=>{W.setLimits(M?.limits)},[M.limits?.startTime,M.limits?.endTime]);const[,B]=s(0);r(()=>W.on("locale",()=>B(e=>e+1)),[W]);const F=N||4,G=22+F+8+"px",H=22+F+22+F+8+"px",O=l(null),X=l(null),A=l(null),[J,U]=s(!1),[V,Y]=s(!1),[$,q]=s(!1),[K,Q]=s(!1),[Z,ee]=s(!1),[te,ne]=s(Date.now()),re=l(null);useMaterialIcons();const[ie,ae]=s(t||b),oe=l(t||b),le=void 0!==t,se=l(new Set),[ce,me]=s(!1);r(()=>{if(le){const e=E(t);if(J&&!v(t,oe.current)&&!se.current.has(e)){oe.current=t;const[e,n]=t;W.setTimeRange({startTime:e,endTime:n})}se.current.delete(e)}},[J,t,le]),r(()=>{const e=new c;return e.add(W.on("time",e=>{const{startTime:t,endTime:r}=e,i=[t,r];v(i,oe.current)||(oe.current=i,se.current.add(E(i)),ae(i),n?.(i))})),e.disposeLater()},[W]),r(()=>{U(!1);const e=new c;return W.init(X.current).then(()=>U(!0)),e.add(W.on("time",e=>{if(e.startTime!==oe.current[0]||e.endTime!==oe.current[1]){const{startTime:t,endTime:r}=e,i=[t,r];oe.current=i,ae(i),n?.(i)}})),e.add(W.on("playing",Y)),e.add(W.on("live",q)),e.add(W.on("current",ne)),e.disposeLater()},[W]),r(()=>{D?.forEach(e=>W.addLayerLoader(e))},[D]),r(()=>{"boolean"==typeof M.showCurrent&&(W.showCurrent=M.showCurrent)},[M.showCurrent]),i(P,()=>({get timeSlider(){return W}})),r(()=>{if(M.showCurrent){const e=setInterval(()=>{A.current&&(A.current.available=Date.now())},10);return()=>clearInterval(e)}},[M.showCurrent]),r(()=>{J&&W.showStats(!0===z)},[J,z]),r(()=>{},[J]),r(()=>{J&&W.setEngine(I)},[J,I]),r(()=>{if(J){const e=new Set,t=new Set;W.getLayers().forEach(e=>t.add(e.id)),_.forEach(n=>{t.delete(n.id),e.add(n.id)}),W.getLayers().forEach(t=>e.delete(t.id));const n=_.filter(t=>e.has(t.id)),r=W.getLayers().filter(e=>t.has(e.id));r.length===W.getLayers().length&&n.length>0&&W.setTimeRange(n[0].model.getTimeRange()),r.forEach(e=>W.removeLayer(e)),n.forEach(e=>W.addLayer(e))}},[J,_]);r(()=>{const e=e=>{re.current&&e.target&&e.target.parentElement?.getAttribute("layerpanelbutton")!==W.id&&!re.current.contains(e.target)&&ee(!1)};if(Z)return document.addEventListener("mousedown",e),()=>{document.removeEventListener("mousedown",e)}},[Z]);const de=e=>{const t=W.current,{duration:n,startTime:r}=W.getTimeRange(),i=(t-r)/n;i>1||i<0?W.setTimeRange({startTime:e-.5*n,endTime:e+.5*n}):W.setTimeRange({startTime:e-i*n,endTime:e+(1-i)*n}),W.setCurrent(e)};r(()=>{const e=()=>{if(O.current){let e=560;M.showCurrent&&(e+=160),p&&(e-=30),C&&(e-=160),k&&(e-=140),L&&(e-=185);const t=O.current.getBoundingClientRect().width;Q(t<e)}};e();const t=new ResizeObserver(e);return O.current&&t.observe(O.current),window.addEventListener("resize",e),()=>{t.disconnect(),window.removeEventListener("resize",e)}},[M.showCurrent,p,C,k,L]);let pe=42;M.showCurrent&&(pe+=4),K&&!S&&(pe+=26);const ue="dark"===R;return r(()=>{R&&W.setTheme(R)},[R]),e.createElement("div",{ref:O,style:{display:"block",position:"relative",borderRadius:"inherit",background:ue?"#222":"#ddd",width:"100%",height:"100%",padding:"8px",boxSizing:"border-box"}},Z&&e.createElement("div",{ref:re,style:{position:"absolute",top:G,left:"8px",maxWidth:"200px",display:"flex",gap:"8px",alignItems:"center",paddingRight:"8px",paddingBottom:"8px",paddingTop:"8px",flexDirection:"column",zIndex:1e3,background:ue?"#222":"#ddd"}},W.getLayers().map(t=>e.createElement(T,{key:t.id,layer:t,timeSlider:W,isDarkMode:ue}))),e.createElement("div",{style:{position:"absolute",top:"8px",left:"8px",display:"flex",gap:"8px",alignItems:"center"}},e.createElement(f,{layerpanelbutton:W.id,iconName:"layers",onClick:e=>ee(e=>!e)}),e.createElement(f,{iconName:"skip_previous",onClick:()=>W.up({animate:1e3}),rotate:90}),!K&&!C&&e.createElement("span",{style:{...y}},m(ie)),e.createElement(f,{iconName:"arrow_range",onClick:()=>W.fit({animate:3e3})}),a&&e.createElement(f,{iconName:"ink_selection",active:ce,onClick:()=>{me(!0),W.startSelectingRange(()=>me(!1))}})),K&&!S&&M.showCurrent&&!C&&e.createElement("div",{style:{position:"absolute",top:G,left:"8px",display:"flex",gap:"8px",height:"22px"}},e.createElement("span",{style:{...y}},m(ie))),K&&!S&&!M.showCurrent&&!C&&e.createElement("div",{style:{position:"absolute",top:"8px",left:"50%",transform:"translateX(-50%)",display:"flex",gap:"8px",height:"22px"}},e.createElement("span",{style:{...y}},m(ie))),(!K||!S)&&e.createElement("div",{style:{position:"absolute",top:K?G:"8px",right:K?"8px":"50%",transform:K?void 0:"translateX(50%)",padding:0,display:"flex",gap:"8px",height:"22px",width:!K||M.showCurrent?void 0:"calc(100% - 16px)"}},!M.showCurrent&&(!k||!k)&&e.createElement(e.Fragment,null,!k&&e.createElement(w,{theme:R,time:ie[0],setTime:e=>W.setTimeRange({startTime:e})}),K&&!k&&!L&&!M.showCurrent&&e.createElement("div",{style:{flexGrow:1}}),!L&&e.createElement(g,{theme:R,range:[ie[0],ie[1]],setRange:e=>W.setTimeRange({startTime:e[0],endTime:e[1]})}),K&&!k&&!L&&!M.showCurrent&&e.createElement("div",{style:{flexGrow:1}}),!k&&e.createElement(w,{theme:R,time:ie[1]-1,setTime:e=>W.setTimeRange({endTime:e+1})})),M.showCurrent&&e.createElement(e.Fragment,null,!L&&e.createElement(h,{theme:R,time:te,setTime:de}),!k&&e.createElement(w,{theme:R,time:te,setTime:de}))),e.createElement("div",{style:{position:"absolute",top:"8px",right:"8px",display:"flex",gap:"8px",alignItems:"center"}},M.showCurrent&&e.createElement(e.Fragment,null,e.createElement(x,{text:"Live",onClick:()=>{W.live?W.pause():W.setLive(!0)},color:$?"#fff":ue?"#888":"#bbb"},e.createElement("span",{className:"material-symbols-outlined",style:{fontSize:"18px",zIndex:1,lineHeight:1}},"radio_button_unchecked"),$&&e.createElement("span",{className:"material-symbols-outlined",style:{fontSize:"18px",lineHeight:1,marginLeft:"-24px",color:"red"}},"radio_button_checked"))),e.createElement(f,{iconName:"skip_previous",onClick:()=>W.previous({animate:1e3})}),!p&&!V&&e.createElement(f,{iconName:"play_arrow",onClick:()=>W.play()}),!p&&V&&e.createElement(f,{iconName:"pause",onClick:()=>W.pause()}),e.createElement(f,{iconName:"skip_next",onClick:()=>W.next({animate:1e3})})),M.showCurrent&&e.createElement(u,{ref:A,value:te,min:ie[0],max:ie[1],onValue:e=>{W.pause(),W.setCurrent(e)},onPointerMove:e=>W.updateHover({x:e.clientX,y:e.clientY}),onPointerLeave:()=>W.updateHover(null),matchWidth:!0,style:{width:"calc(100% - 16px)",left:"8px",position:"absolute",bottom:"26px",zIndex:1},...j}),e.createElement("div",{ref:X,style:{position:"absolute",top:K&&!S?H:G,left:"8px",borderRadius:"inherit",width:"calc(100% - 16px)",height:`calc(100% - ${pe}px)`,background:ue?"#111":"#eee",overflow:"hidden"}}))});function T({layer:t,timeSlider:n,isDarkMode:i}){const[a,o]=s(t.visible);return r(()=>{t.on("visible",e=>o(e))},[]),e.createElement("div",{style:{display:"flex",gap:"8px",alignItems:"center",background:i?"#222":"#ddd",borderRadius:"4px",width:"100%"}},a&&e.createElement(f,{iconName:"visibility",onClick:()=>t.visible=!1}),!a&&e.createElement(f,{iconName:"visibility_off",color:"#888",onClick:()=>t.visible=!0}),e.createElement("span",{style:{...y,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",flexGrow:1,minWidth:0}},t.name||t.constructor.name+"#"+t.id.slice(t.id.length-4).toUpperCase()),e.createElement(f,{iconName:"center_focus_weak",onClick:()=>n.fit({animate:3e3,range:t.model.getTimeRange()})}))}export function useMaterialIcons(e="outlined"){r(()=>{const e=document.createElement("link");return e.rel="stylesheet",e.href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200",document.head.appendChild(e),()=>{document.head.removeChild(e)}},[e])}
1
+ import e,{forwardRef as t,useCallback as n,useEffect as r,useImperativeHandle as i,useLayoutEffect as o,useMemo as a,useRef as l,useState as s}from"react";import{Disposables as c}from"../common/Disposables.js";import{formatDuration as m}from"../common/TimeTools.js";import{TimeSlider as d}from"../engine/TimeSlider.js";import{ONE_YEAR as p}from"../ticks/TimeSliderConstants.js";import{ColoredSlider as u}from"./ColoredSlider.js";import{DateLabelWithPicker as g}from"./DateLabelWithPicker.js";import{DateRangeLabelWithPicker as h}from"./DateRangeLabelWithPicker.js";import{MaterialIconButton as f}from"./MaterialIconButton.js";import{MaterialTextButton as w}from"./MaterialTextButton.js";import{TimeLabelWithPicker as x}from"./TimeLabelWithPicker.js";const y={padding:"4px 8px",background:"#0008",borderRadius:"4px",color:"#fff",fontFamily:"monospace",fontSize:"12px"},b=[Date.now()-p,Date.now()];function v(e,t){return e[0]===t[0]&&e[1]===t[1]}function k(e){return JSON.stringify(e)}export const ReactTimeSlider=t(({timeRange:t,onTimeRange:n,selectRangeButton:o,noPlayButton:p,noTimeLabels:E,noRangeLabel:C,noDateLabels:L,theme:S,canHideLabels:R,verticalGap:N,engine:I,layers:_,loaders:z,showStats:D,currentSlider:j,...M},P)=>{const B=a(()=>new d(M),[]);r(()=>{M?.showCurrent&&(B.showCurrent=M?.showCurrent)},[M.showCurrent]),r(()=>{M?.showTicks&&(B.showTicks=M?.showTicks)},[M.showTicks]),r(()=>{M?.playSpeed&&(B.playSpeed=M?.playSpeed)},[M.playSpeed]),r(()=>{M?.liveRange&&(B.liveRange=M?.liveRange)},[M.liveRange]),r(()=>{M?.locale&&B.setLocale(M.locale)},[M.locale]),r(()=>{M?.weekLabel&&B.setWeekLabel(M.weekLabel,M.weekLabelPrefix)},[M.weekLabel,M.weekLabelPrefix]),r(()=>{B.setTickLabelStyler(M?.tickLabelStyler)},[M.tickLabelStyler]),r(()=>{B.setLimits(M?.limits)},[M.limits?.startTime,M.limits?.endTime]),r(()=>{B.setFont(M?.font)},[M.font?.family,M.font?.weight,M.font?.size]),r(()=>{B.setBackground(M?.background)},[M.background]),r(()=>{B.setTickTooltip(M?.tickTooltip??!0)},[M.tickTooltip]),r(()=>{B.setTooltipStyle(M?.tooltipStyle??{})},[JSON.stringify(M.tooltipStyle??{})]);const[,W]=s(0);r(()=>B.on("locale",()=>W(e=>e+1)),[B]);const F=N||4,G=22+F+8+"px",H=22+F+22+F+8+"px",O=l(null),X=l(null),A=l(null),[J,U]=s(!1),[V,Y]=s(!1),[$,q]=s(!1),[K,Q]=s(!1),[Z,ee]=s(!1),[te,ne]=s(Date.now()),re=l(null);useMaterialIcons();const[ie,oe]=s(t||b),ae=l(t||b),le=void 0!==t,se=l(new Set),[ce,me]=s(!1);r(()=>{if(le){const e=k(t);if(J&&!v(t,ae.current)&&!se.current.has(e)){ae.current=t;const[e,n]=t;B.setTimeRange({startTime:e,endTime:n})}se.current.delete(e)}},[J,t,le]),r(()=>{const e=new c;return e.add(B.on("time",e=>{const{startTime:t,endTime:r}=e,i=[t,r];v(i,ae.current)||(ae.current=i,se.current.add(k(i)),oe(i),n?.(i))})),e.disposeLater()},[B]),r(()=>{U(!1);const e=new c;return B.init(X.current).then(()=>U(!0)),e.add(B.on("time",e=>{if(e.startTime!==ae.current[0]||e.endTime!==ae.current[1]){const{startTime:t,endTime:r}=e,i=[t,r];ae.current=i,oe(i),n?.(i)}})),e.add(B.on("playing",Y)),e.add(B.on("live",q)),e.add(B.on("current",ne)),e.disposeLater()},[B]),r(()=>{z?.forEach(e=>B.addLayerLoader(e))},[z]),r(()=>{"boolean"==typeof M.showCurrent&&(B.showCurrent=M.showCurrent)},[M.showCurrent]),i(P,()=>({get timeSlider(){return B}})),r(()=>{if(M.showCurrent){const e=setInterval(()=>{A.current&&(A.current.available=Date.now())},10);return()=>clearInterval(e)}},[M.showCurrent]),r(()=>{J&&B.showStats(!0===D)},[J,D]),r(()=>{},[J]),r(()=>{J&&B.setEngine(I)},[J,I]),r(()=>{if(J){const e=new Set,t=new Set;B.getLayers().forEach(e=>t.add(e.id)),_.forEach(n=>{t.delete(n.id),e.add(n.id)}),B.getLayers().forEach(t=>e.delete(t.id));const n=_.filter(t=>e.has(t.id)),r=B.getLayers().filter(e=>t.has(e.id));r.length===B.getLayers().length&&n.length>0&&B.setTimeRange(n[0].model.getTimeRange()),r.forEach(e=>B.removeLayer(e)),n.forEach(e=>B.addLayer(e))}},[J,_]);r(()=>{const e=e=>{re.current&&e.target&&e.target.parentElement?.getAttribute("layerpanelbutton")!==B.id&&!re.current.contains(e.target)&&ee(!1)};if(Z)return document.addEventListener("mousedown",e),()=>{document.removeEventListener("mousedown",e)}},[Z]);const de=e=>{const t=B.current,{duration:n,startTime:r}=B.getTimeRange(),i=(t-r)/n;i>1||i<0?B.setTimeRange({startTime:e-.5*n,endTime:e+.5*n}):B.setTimeRange({startTime:e-i*n,endTime:e+(1-i)*n}),B.setCurrent(e)};r(()=>{const e=()=>{if(O.current){let e=560;M.showCurrent&&(e+=160),p&&(e-=30),C&&(e-=160),E&&(e-=140),L&&(e-=185);const t=O.current.getBoundingClientRect().width;Q(t<e)}};e();const t=new ResizeObserver(e);return O.current&&t.observe(O.current),window.addEventListener("resize",e),()=>{t.disconnect(),window.removeEventListener("resize",e)}},[M.showCurrent,p,C,E,L]);let pe=42;M.showCurrent&&(pe+=4),K&&!R&&(pe+=26);const ue="dark"===S;return r(()=>{S&&B.setTheme(S)},[S]),e.createElement("div",{ref:O,style:{display:"block",position:"relative",borderRadius:"inherit",background:ue?"#222":"#ddd",width:"100%",height:"100%",padding:"8px",boxSizing:"border-box"}},Z&&e.createElement("div",{ref:re,style:{position:"absolute",top:G,left:"8px",maxWidth:"200px",display:"flex",gap:"8px",alignItems:"center",paddingRight:"8px",paddingBottom:"8px",paddingTop:"8px",flexDirection:"column",zIndex:1e3,background:ue?"#222":"#ddd"}},B.getLayers().map(t=>e.createElement(T,{key:t.id,layer:t,timeSlider:B,isDarkMode:ue}))),e.createElement("div",{style:{position:"absolute",top:"8px",left:"8px",display:"flex",gap:"8px",alignItems:"center"}},e.createElement(f,{layerpanelbutton:B.id,iconName:"layers",onClick:e=>ee(e=>!e)}),e.createElement(f,{iconName:"skip_previous",onClick:()=>B.up({animate:1e3}),rotate:90}),!K&&!C&&e.createElement("span",{style:{...y}},m(ie)),e.createElement(f,{iconName:"arrow_range",onClick:()=>B.fit({animate:3e3})}),o&&e.createElement(f,{iconName:"ink_selection",active:ce,onClick:()=>{me(!0),B.startSelectingRange(()=>me(!1))}})),K&&!R&&M.showCurrent&&!C&&e.createElement("div",{style:{position:"absolute",top:G,left:"8px",display:"flex",gap:"8px",height:"22px"}},e.createElement("span",{style:{...y}},m(ie))),K&&!R&&!M.showCurrent&&!C&&e.createElement("div",{style:{position:"absolute",top:"8px",left:"50%",transform:"translateX(-50%)",display:"flex",gap:"8px",height:"22px"}},e.createElement("span",{style:{...y}},m(ie))),(!K||!R)&&e.createElement("div",{style:{position:"absolute",top:K?G:"8px",right:K?"8px":"50%",transform:K?void 0:"translateX(50%)",padding:0,display:"flex",gap:"8px",height:"22px",width:!K||M.showCurrent?void 0:"calc(100% - 16px)"}},!M.showCurrent&&(!E||!E)&&e.createElement(e.Fragment,null,!E&&e.createElement(x,{theme:S,time:ie[0],setTime:e=>B.setTimeRange({startTime:e})}),K&&!E&&!L&&!M.showCurrent&&e.createElement("div",{style:{flexGrow:1}}),!L&&e.createElement(h,{theme:S,range:[ie[0],ie[1]],setRange:e=>B.setTimeRange({startTime:e[0],endTime:e[1]})}),K&&!E&&!L&&!M.showCurrent&&e.createElement("div",{style:{flexGrow:1}}),!E&&e.createElement(x,{theme:S,time:ie[1]-1,setTime:e=>B.setTimeRange({endTime:e+1})})),M.showCurrent&&e.createElement(e.Fragment,null,!L&&e.createElement(g,{theme:S,time:te,setTime:de}),!E&&e.createElement(x,{theme:S,time:te,setTime:de}))),e.createElement("div",{style:{position:"absolute",top:"8px",right:"8px",display:"flex",gap:"8px",alignItems:"center"}},M.showCurrent&&e.createElement(e.Fragment,null,e.createElement(w,{text:"Live",onClick:()=>{B.live?B.pause():B.setLive(!0)},color:$?"#fff":ue?"#888":"#bbb"},e.createElement("span",{className:"material-symbols-outlined",style:{fontSize:"18px",zIndex:1,lineHeight:1}},"radio_button_unchecked"),$&&e.createElement("span",{className:"material-symbols-outlined",style:{fontSize:"18px",lineHeight:1,marginLeft:"-24px",color:"red"}},"radio_button_checked"))),e.createElement(f,{iconName:"skip_previous",onClick:()=>B.previous({animate:1e3})}),!p&&!V&&e.createElement(f,{iconName:"play_arrow",onClick:()=>B.play()}),!p&&V&&e.createElement(f,{iconName:"pause",onClick:()=>B.pause()}),e.createElement(f,{iconName:"skip_next",onClick:()=>B.next({animate:1e3})})),M.showCurrent&&e.createElement(u,{ref:A,value:te,min:ie[0],max:ie[1],onValue:e=>{B.pause(),B.setCurrent(e)},onPointerMove:e=>B.updateHover({x:e.clientX,y:e.clientY}),onPointerLeave:()=>B.updateHover(null),matchWidth:!0,style:{width:"calc(100% - 16px)",left:"8px",position:"absolute",bottom:"26px",zIndex:1},...j}),e.createElement("div",{ref:X,style:{position:"absolute",top:K&&!R?H:G,left:"8px",borderRadius:"inherit",width:"calc(100% - 16px)",height:`calc(100% - ${pe}px)`,background:M.background??(ue?"#111":"#eee"),overflow:"hidden"}}))});function T({layer:t,timeSlider:n,isDarkMode:i}){const[o,a]=s(t.visible);return r(()=>{t.on("visible",e=>a(e))},[]),e.createElement("div",{style:{display:"flex",gap:"8px",alignItems:"center",background:i?"#222":"#ddd",borderRadius:"4px",width:"100%"}},o&&e.createElement(f,{iconName:"visibility",onClick:()=>t.visible=!1}),!o&&e.createElement(f,{iconName:"visibility_off",color:"#888",onClick:()=>t.visible=!0}),e.createElement("span",{style:{...y,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",flexGrow:1,minWidth:0}},t.name||t.constructor.name+"#"+t.id.slice(t.id.length-4).toUpperCase()),e.createElement(f,{iconName:"center_focus_weak",onClick:()=>n.fit({animate:3e3,range:t.model.getTimeRange()})}))}export function useMaterialIcons(e="outlined"){r(()=>{const e=document.createElement("link");return e.rel="stylesheet",e.href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200",document.head.appendChild(e),()=>{document.head.removeChild(e)}},[e])}
@@ -0,0 +1,11 @@
1
+ import { Object3D, Vector2, WebGLRenderer } from "three";
2
+ export { getDevicePixelRatio } from "../common/Pixels";
3
+ /**
4
+ * Makes every line in `scene` (Line / LineSegments / GridHelper with a LineBasicMaterial) land on
5
+ * device pixel centres, so a grid or tick line is exactly one device pixel wide wherever it is.
6
+ * Materials are patched once (vertex shader), so this is cheap to call before every render; the
7
+ * resolution follows the renderer's drawing buffer.
8
+ */
9
+ export declare function snapLinesToPixels(scene: Object3D, renderer: WebGLRenderer, resolution: {
10
+ value: Vector2;
11
+ }): void;
@@ -0,0 +1 @@
1
+ import{Line as e,LineBasicMaterial as o}from"three";export{getDevicePixelRatio}from"../common/Pixels.js";const i="protomazePixelSnap";export function snapLinesToPixels(n,r,t){r.getDrawingBufferSize(t.value),n.traverse(n=>{if(!(n instanceof e))return;(Array.isArray(n.material)?n.material:[n.material]).forEach(e=>{e instanceof o&&!e.userData[i]&&(e.userData[i]=!0,e.onBeforeCompile=e=>{e.uniforms.snapResolution=t,e.vertexShader="uniform vec2 snapResolution;\n"+e.vertexShader.replace("#include <project_vertex>","#include <project_vertex>\n if (gl_Position.w > 0.0) {\n vec2 snapPixel = floor((gl_Position.xy / gl_Position.w * 0.5 + 0.5) * snapResolution) + 0.5;\n gl_Position.xy = (snapPixel / snapResolution * 2.0 - 1.0) * gl_Position.w;\n }")},e.customProgramCacheKey=()=>i,e.needsUpdate=!0)})})}
@@ -14,7 +14,15 @@ export declare class Three3DTimeSliderEngine extends TimeSliderEngine<Scene> {
14
14
  camera: PerspectiveCamera;
15
15
  renderer?: WebGLRenderer;
16
16
  private container?;
17
- constructor();
17
+ private antialias;
18
+ /**
19
+ * @param options.antialias Multisampling (default true). Grid and tick lines are snapped to device
20
+ * pixels either way, but with multisampling the GPU still spreads each line a little into its
21
+ * neighbours; false draws them exactly one device pixel wide (and bar edges on whole pixels).
22
+ */
23
+ constructor(options?: {
24
+ antialias?: boolean;
25
+ });
18
26
  private statsDom?;
19
27
  private stats?;
20
28
  private disposables?;
@@ -1 +1 @@
1
- import{BufferGeometry as e,GridHelper as t,Group as s,Line as i,LineBasicMaterial as o,Mesh as a,MeshBasicMaterial as n,PerspectiveCamera as r,PlaneGeometry as d,Scene as l,Vector2 as h,Vector3 as m,WebGLRenderer as c}from"three";import p from"three/examples/jsm/libs/stats.module.js";import{Disposables as w}from"../common/Disposables.js";import{getStartOfWeek as x}from"../common/TimeTools.js";import{TimeSliderEngine as u}from"../engine/TimeSliderEngine.js";import{ONE_MONTH as v,ONE_SECOND as f,ONE_WEEK as b}from"../ticks/TimeSliderConstants.js";import{TimeTicksBar as y}from"../ticks/TimeTicksBar.js";export class ScaledPerspectiveCamera extends r{updateMatrixWorld(e){super.updateMatrixWorld(e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(e,t,...s){super.updateWorldMatrix(e,t,...s),this.matrixWorldInverse.copy(this.matrixWorld).invert()}}export class Three3DTimeSliderEngine extends u{constructor(){super(new l),this.camera=new ScaledPerspectiveCamera}showStats(e){if(this.container)if(e){this.stats=new p;const e=this.stats.dom;e.style.position="absolute",e.style.top="auto",e.style.bottom="32px",e.style.left="8px",this.container.appendChild(e),this.statsDom=e}else this.statsDom&&(this.container.removeChild(this.statsDom),this.statsDom=void 0,this.stats=void 0)}setTheme(e){super.setTheme(e),this.onThemeChange?.(e)}async init(r){const l=new c({antialias:!0});this.renderer=l,this.disposables=new w,this.container=r,this.renderer.setClearColor("#111"),r.appendChild(l.domElement),this.disposables.add(()=>r.removeChild(l.domElement)),this.camera.position.z=2.073,this.container.addEventListener("keydown",e=>{const t=1/this.camera.scale.x;"KeyQ"===e.code&&(e.preventDefault(),this.camera.rotateY(t)),"KeyE"===e.code&&(e.preventDefault(),this.camera.rotateY(-t))});const h=new s,p=new s;h.scale.x=f,h.position.z=.9,this.scene.add(h);const u=new d(100,1),g=new n({color:"#444"}),T=new a(u,g);T.position.z=.5,T.position.y=-.001,T.rotateX(-.5*Math.PI),h.add(T);const C=new a(u,g);C.position.z=.5,C.position.y=-.001,C.rotateX(-.5*Math.PI),p.add(C);const z=new o({color:"#444"}),S=(new e).setFromPoints([new m(0,0,0),new m(0,1,0),new m(0,1,1)]),P=(new e).setFromPoints([new m(-1,0,0),new m(1,0,0)]),j=new i(P,z),D=new i(P,z);D.position.y=1,p.add(j),p.add(D),p.position.z=.9,this.scene.add(p);const M=[],V=new o({color:"#000"}),k=(new e).setFromPoints([new m(0,0,1),new m(0,0,-1e3)]),W=new s;p.add(W);const E=[];let I,F,R,X;const K=()=>{Date.now();const e=this.getSize().width,{startTime:t,duration:s,endTime:o}=this.getTimeRange(),a=s,[n]=y.getSpacing(e,a),r=.5*(o+t);if(n<v){p.visible=!1,h.scale.x=n;let e=r%n;n>=b&&(e=r-x(r)),h.position.x=-e,h.visible=!0}else{h.visible=!1;let e=0,a=0;y.generateTicks(n,t-3*s,o+3*s,(n,r)=>{t-s<=n&&n<=o+s&&(M[e]||(M[e]=new i(S,z),p.add(M[e])),M[e].position.x=n,M[e].visible=!0,e++),E[a]||(E[a]=new i(k,V),p.add(E[a])),E[a].position.x=n,E[a].visible=!0,a++});for(let e=a;e<E.length;e++)E[e].visible=!1;for(let t=e;t<M.length;t++)M[t].visible=!1;X&&(X.scale.x=1e3*s),C.scale.x=s,C.position.x=r,X&&(X.position.x=-500*s),j.scale.x=1e3*s,D.scale.x=1e3*s,p.position.x=-r,p.visible=!0}};this.onThemeChange=e=>{const s="dark"===e?"#000":"#fff",i="dark"===e?"#444":"#bbb";this.renderer?.setClearColor("dark"===e?"#111":"#eee"),I&&h.remove(I),I?.dispose(),I=new t(1e3,1e3,s,s),h.add(I),R&&h.remove(R),R?.dispose(),R=new t(1e3,1e3,i,i),R.position.z=500,R.position.y=1,h.add(R),F&&h.remove(F),F?.dispose(),F=new t(1e3,1e3,i,i),F.position.y=-499,F.rotateX(-.5*Math.PI),h.add(F),X&&p.remove(X),X?.dispose(),X=new t(200,200,s,s),X.position.z=-101,p.add(X),g.color.set(i),z.color.set(i),V.color.set(s),K()},this.onThemeChange(this.theme),this.disposables.add(this.on("time",K)),this.disposables.add(this.on("size",K)),this.updateValueRange({minValue:0,maxValue:1});let O=!0;this.disposables.add(()=>O=!1),this.disposables.add(()=>this.scene.clear()),this.disposables.add(()=>l.dispose());const Y=()=>{O&&(l.render(this.scene,this.camera),this.stats&&this.stats.update(),requestAnimationFrame(Y))};Y()}updateTimeRange(e){const t=this.camera.scale.x,{duration:s}=e;this.camera.scale.x=s,t!==s&&(this.camera.rotation.y=this.camera.rotation.y*t/s),this.camera.updateProjectionMatrix()}updateValueRange(e){const t=e.maxValue-e.minValue;this.camera.scale.y=t,this.camera.position.y=e.minValue+.5*t,this.camera.updateProjectionMatrix()}updateSize(e,t){this.renderer?.setSize(e,t,!0)}destroy(){this.disposables?.dispose()}setHorizontalCompression(e){if(!this.renderer)return;if(e<=1)return void this.camera.clearViewOffset();const{width:t,height:s}=this.renderer.getSize(new h),i=t*e,o=s,a=(i-t)/2,n=t,r=s;this.camera.setViewOffset(i,o,a,0,n,r)}}
1
+ import{BufferGeometry as e,GridHelper as t,Group as s,Line as i,LineBasicMaterial as o,Mesh as a,MeshBasicMaterial as n,PerspectiveCamera as r,PlaneGeometry as d,Scene as l,Vector2 as h,Vector3 as m,WebGLRenderer as c}from"three";import p from"three/examples/jsm/libs/stats.module.js";import{Disposables as w}from"../common/Disposables.js";import{getDevicePixelRatio as x,snapLinesToPixels as u}from"./PixelSnap.js";import{getStartOfWeek as v}from"../common/TimeTools.js";import{TimeSliderEngine as f}from"../engine/TimeSliderEngine.js";import{ONE_MONTH as b,ONE_SECOND as g,ONE_WEEK as y}from"../ticks/TimeSliderConstants.js";import{TimeTicksBar as C}from"../ticks/TimeTicksBar.js";export class ScaledPerspectiveCamera extends r{updateMatrixWorld(e){super.updateMatrixWorld(e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(e,t,...s){super.updateWorldMatrix(e,t,...s),this.matrixWorldInverse.copy(this.matrixWorld).invert()}}export class Three3DTimeSliderEngine extends f{constructor(e){super(new l),this.camera=new ScaledPerspectiveCamera,this.antialias=e?.antialias??!0}showStats(e){if(this.container)if(e){this.stats=new p;const e=this.stats.dom;e.style.position="absolute",e.style.top="auto",e.style.bottom="32px",e.style.left="8px",this.container.appendChild(e),this.statsDom=e}else this.statsDom&&(this.container.removeChild(this.statsDom),this.statsDom=void 0,this.stats=void 0)}setTheme(e){super.setTheme(e),this.onThemeChange?.(e)}async init(r){const l=new c({antialias:this.antialias});l.setPixelRatio(x());const p={value:new h};this.renderer=l,this.disposables=new w,this.container=r,this.renderer.setClearColor(this.getClearColor()),r.appendChild(l.domElement),this.disposables.add(()=>r.removeChild(l.domElement)),this.camera.position.z=2.073,this.container.addEventListener("keydown",e=>{const t=1/this.camera.scale.x;"KeyQ"===e.code&&(e.preventDefault(),this.camera.rotateY(t)),"KeyE"===e.code&&(e.preventDefault(),this.camera.rotateY(-t))});const f=new s,T=new s;f.scale.x=g,f.position.z=.9,this.scene.add(f);const P=new d(100,1),z=new n({color:"#444"}),S=new a(P,z);S.position.z=.5,S.position.y=-.001,S.rotateX(-.5*Math.PI),f.add(S);const j=new a(P,z);j.position.z=.5,j.position.y=-.001,j.rotateX(-.5*Math.PI),T.add(j);const D=new o({color:"#444"}),M=(new e).setFromPoints([new m(0,0,0),new m(0,1,0),new m(0,1,1)]),V=(new e).setFromPoints([new m(-1,0,0),new m(1,0,0)]),R=new i(V,D),W=new i(V,D);W.position.y=1,T.add(R),T.add(W),T.position.z=.9,this.scene.add(T);const k=[],E=new o({color:"#000"}),I=(new e).setFromPoints([new m(0,0,1),new m(0,0,-1e3)]),F=new s;T.add(F);const X=[];let K,O,Y,q;const A=()=>{Date.now();const e=this.getSize().width,{startTime:t,duration:s,endTime:o}=this.getTimeRange(),a=s,[n]=C.getSpacing(e,a),r=.5*(o+t);if(n<b){T.visible=!1,f.scale.x=n;let e=r%n;n>=y&&(e=r-v(r)),f.position.x=-e,f.visible=!0}else{f.visible=!1;let e=0,a=0;C.generateTicks(n,t-3*s,o+3*s,(n,r)=>{t-s<=n&&n<=o+s&&(k[e]||(k[e]=new i(M,D),T.add(k[e])),k[e].position.x=n,k[e].visible=!0,e++),X[a]||(X[a]=new i(I,E),T.add(X[a])),X[a].position.x=n,X[a].visible=!0,a++});for(let e=a;e<X.length;e++)X[e].visible=!1;for(let t=e;t<k.length;t++)k[t].visible=!1;q&&(q.scale.x=1e3*s),j.scale.x=s,j.position.x=r,q&&(q.position.x=-500*s),R.scale.x=1e3*s,W.scale.x=1e3*s,T.position.x=-r,T.visible=!0}};this.onThemeChange=e=>{const s="dark"===e?"#000":"#fff",i="dark"===e?"#444":"#bbb";this.renderer?.setClearColor(this.getClearColor()),K&&f.remove(K),K?.dispose(),K=new t(1e3,1e3,s,s),f.add(K),Y&&f.remove(Y),Y?.dispose(),Y=new t(1e3,1e3,i,i),Y.position.z=500,Y.position.y=1,f.add(Y),O&&f.remove(O),O?.dispose(),O=new t(1e3,1e3,i,i),O.position.y=-499,O.rotateX(-.5*Math.PI),f.add(O),q&&T.remove(q),q?.dispose(),q=new t(200,200,s,s),q.position.z=-101,T.add(q),z.color.set(i),D.color.set(i),E.color.set(s),A()},this.onThemeChange(this.theme),this.disposables.add(this.on("time",A)),this.disposables.add(this.on("size",A)),this.updateValueRange({minValue:0,maxValue:1});let B=!0;this.disposables.add(()=>B=!1),this.disposables.add(()=>this.scene.clear()),this.disposables.add(()=>l.dispose());const H=()=>{B&&(l.getPixelRatio()!==x()&&l.setPixelRatio(x()),u(this.scene,l,p),l.render(this.scene,this.camera),this.stats&&this.stats.update(),requestAnimationFrame(H))};H()}updateTimeRange(e){const t=this.camera.scale.x,{duration:s}=e;this.camera.scale.x=s,t!==s&&(this.camera.rotation.y=this.camera.rotation.y*t/s),this.camera.updateProjectionMatrix()}updateValueRange(e){const t=e.maxValue-e.minValue;this.camera.scale.y=t,this.camera.position.y=e.minValue+.5*t,this.camera.updateProjectionMatrix()}updateSize(e,t){this.renderer?.setPixelRatio(x()),this.renderer?.setSize(e,t,!0)}destroy(){this.disposables?.dispose()}setHorizontalCompression(e){if(!this.renderer)return;if(e<=1)return void this.camera.clearViewOffset();const{width:t,height:s}=this.renderer.getSize(new h),i=t*e,o=s,a=(i-t)/2,n=t,r=s;this.camera.setViewOffset(i,o,a,0,n,r)}}
@@ -5,7 +5,15 @@ export declare class ThreeTimeSliderEngine extends TimeSliderEngine<Scene> {
5
5
  camera: OrthographicCamera;
6
6
  renderer?: WebGLRenderer;
7
7
  private container?;
8
- constructor();
8
+ private antialias;
9
+ /**
10
+ * @param options.antialias Multisampling (default true). Grid and tick lines are snapped to device
11
+ * pixels either way, but with multisampling the GPU still spreads each line a little into its
12
+ * neighbours; false draws them exactly one device pixel wide (and bar edges on whole pixels).
13
+ */
14
+ constructor(options?: {
15
+ antialias?: boolean;
16
+ });
9
17
  private statsDom?;
10
18
  private stats?;
11
19
  private disposables?;
@@ -1 +1 @@
1
- import{BufferGeometry as e,GridHelper as s,Group as t,Line as i,LineBasicMaterial as o,OrthographicCamera as a,Scene as n,Vector3 as r,WebGLRenderer as h}from"three";import m from"three/examples/jsm/libs/stats.module.js";import{Disposables as d}from"../common/Disposables.js";import{TimeSliderEngine as l}from"../engine/TimeSliderEngine.js";import{TimeTicksBar as c}from"../ticks/TimeTicksBar.js";import{getStartOfWeek as p}from"../common/TimeTools.js";import{ONE_MONTH as b,ONE_SECOND as T,ONE_WEEK as u}from"../ticks/TimeSliderConstants.js";export class ThreeTimeSliderEngine extends l{constructor(){super(new n),this.camera=new a(0,1,1,0)}showStats(e){if(this.container)if(e){this.stats=new m;const e=this.stats.dom;e.style.position="absolute",e.style.top="auto",e.style.bottom="32px",e.style.left="8px",this.container.appendChild(e),this.statsDom=e}else this.statsDom&&(this.container.removeChild(this.statsDom),this.statsDom=void 0,this.stats=void 0)}setTheme(e){super.setTheme(e),this.onThemeChange?.(e)}async init(a){const n="dark"===this.theme;this.container=a;const m=new h({antialias:!0});this.renderer=m,this.disposables=new d,this.renderer.setClearColor(n?"#111":"#eee"),a.appendChild(this.renderer.domElement),this.disposables.add(()=>a.removeChild(m.domElement)),this.camera.position.z=10;const l=new t,g=new t;l.scale.x=T,this.scene.add(l);const w=new o({color:n?"#444":"#bbb"}),f=(new e).setFromPoints([new r(0,0,0),new r(0,1,0),new r(0,1,1)]);this.scene.add(g);const x=[];let C;const v=()=>{const e=this.getSize().width,{startTime:s,duration:t,endTime:o}=this.getTimeRange(),a=t,[n]=c.getSpacing(e,a),r=.5*(o+s);if(n<b){g.visible=!1,l.scale.x=n;let e=r%n;n>=u&&(e=r-p(r)),l.position.x=-e,l.visible=!0}else{l.visible=!1;let e=0;c.generateTicks(n,s-3*t,o+3*t,(a,n)=>{s-t<=a&&a<=o+t&&(x[e]||(x[e]=new i(f,w),g.add(x[e])),x[e].position.x=a,x[e].visible=!0,e++)});for(let s=e;s<x.length;s++)x[s].visible=!1;g.position.x=-r,g.visible=!0}};this.onThemeChange=e=>{const t="dark"===e?"#444":"#bbb";this.renderer?.setClearColor("dark"===e?"#111":"#eee"),C&&l.remove(C),C=new s(1e3,1e3,t,t),C.position.y=-499,C.rotateX(-.5*Math.PI),l.add(C),w.color.set(t),v()},this.onThemeChange(this.theme),this.disposables.add(this.on("time",v)),this.disposables.add(this.on("size",v));let j=!0;this.disposables.add(()=>j=!1),this.disposables.add(()=>this.scene.clear()),this.disposables.add(()=>m.dispose());const S=()=>{j&&(m.render(this.scene,this.camera),this.stats&&this.stats.update(),requestAnimationFrame(S))};S()}destroy(){this.disposables?.dispose()}updateTimeRange(e){const{startTime:s,duration:t}=e;this.camera.left=-.5*t,this.camera.right=.5*t,this.camera.updateProjectionMatrix()}updateValueRange(e){this.camera.top=e.maxValue,this.camera.bottom=e.minValue,this.camera.updateProjectionMatrix()}updateSize(e,s){this.renderer?.setSize(e,s)}}
1
+ import{BufferGeometry as e,GridHelper as t,Group as s,Line as i,LineBasicMaterial as o,OrthographicCamera as a,Scene as n,Vector2 as r,Vector3 as h,WebGLRenderer as l}from"three";import m from"three/examples/jsm/libs/stats.module.js";import{Disposables as d}from"../common/Disposables.js";import{getDevicePixelRatio as c,snapLinesToPixels as p}from"./PixelSnap.js";import{TimeSliderEngine as b}from"../engine/TimeSliderEngine.js";import{TimeTicksBar as g}from"../ticks/TimeTicksBar.js";import{getStartOfWeek as u}from"../common/TimeTools.js";import{ONE_MONTH as T,ONE_SECOND as x,ONE_WEEK as w}from"../ticks/TimeSliderConstants.js";export class ThreeTimeSliderEngine extends b{constructor(e){super(new n),this.camera=new a(0,1,1,0),this.antialias=e?.antialias??!0}showStats(e){if(this.container)if(e){this.stats=new m;const e=this.stats.dom;e.style.position="absolute",e.style.top="auto",e.style.bottom="32px",e.style.left="8px",this.container.appendChild(e),this.statsDom=e}else this.statsDom&&(this.container.removeChild(this.statsDom),this.statsDom=void 0,this.stats=void 0)}setTheme(e){super.setTheme(e),this.onThemeChange?.(e)}async init(a){const n="dark"===this.theme;this.container=a;const m=new l({antialias:this.antialias});m.setPixelRatio(c());const b={value:new r};this.renderer=m,this.disposables=new d,this.renderer.setClearColor(this.getClearColor()),a.appendChild(this.renderer.domElement),this.disposables.add(()=>a.removeChild(m.domElement)),this.camera.position.z=10;const C=new s,f=new s;C.scale.x=x,this.scene.add(C);const v=new o({color:n?"#444":"#bbb"}),j=(new e).setFromPoints([new h(0,0,0),new h(0,1,0),new h(0,1,1)]);this.scene.add(f);const P=[];let S;const y=()=>{const e=this.getSize().width,{startTime:t,duration:s,endTime:o}=this.getTimeRange(),a=s,[n]=g.getSpacing(e,a),r=.5*(o+t);if(n<T){f.visible=!1,C.scale.x=n;let e=r%n;n>=w&&(e=r-u(r)),C.position.x=-e,C.visible=!0}else{C.visible=!1;let e=0;g.generateTicks(n,t-3*s,o+3*s,(a,n)=>{t-s<=a&&a<=o+s&&(P[e]||(P[e]=new i(j,v),f.add(P[e])),P[e].position.x=a,P[e].visible=!0,e++)});for(let t=e;t<P.length;t++)P[t].visible=!1;f.position.x=-r,f.visible=!0}};this.onThemeChange=e=>{const s="dark"===e?"#444":"#bbb";this.renderer?.setClearColor(this.getClearColor()),S&&C.remove(S),S=new t(1e3,1e3,s,s),S.position.y=-499,S.rotateX(-.5*Math.PI),C.add(S),v.color.set(s),y()},this.onThemeChange(this.theme),this.disposables.add(this.on("time",y)),this.disposables.add(this.on("size",y));let R=!0;this.disposables.add(()=>R=!1),this.disposables.add(()=>this.scene.clear()),this.disposables.add(()=>m.dispose());const k=()=>{R&&(m.getPixelRatio()!==c()&&m.setPixelRatio(c()),p(this.scene,m,b),m.render(this.scene,this.camera),this.stats&&this.stats.update(),requestAnimationFrame(k))};k()}destroy(){this.disposables?.dispose()}updateTimeRange(e){const{startTime:t,duration:s}=e;this.camera.left=-.5*s,this.camera.right=.5*s,this.camera.updateProjectionMatrix()}updateValueRange(e){this.camera.top=e.maxValue,this.camera.bottom=e.minValue,this.camera.updateProjectionMatrix()}updateSize(e,t){this.renderer?.setPixelRatio(c()),this.renderer?.setSize(e,t)}}
@@ -1 +1 @@
1
- import{BoxGeometry as e,Color as n,DoubleSide as t,InstancedMesh as o,Object3D as i,ShaderMaterial as l,Vector2 as r}from"three";import{Disposables as a}from"../../common/Disposables.js";import{toGLSLColor as s,toGLSLFloat as d}from"../../common/GlslTools.js";import{getTimePartsInHours as c}from"../../common/TimeTools.js";import{TimeSliderSplittingFixedHistogram as m}from"../../data/histogram/TimeSliderSplittingFixedHistogram.js";import{TimeSliderHistogramLayer as f}from"../../layer/TimeSliderHistogramLayer.js";import{TimeSliderIntervalsLayer as v}from"../../layer/TimeSliderIntervalsLayer.js";import{Three3DTimeSliderEngine as g}from"../Three3DTimeSliderEngine.js";import{GrowingInstanceMatrix as h}from"./GrowingInstanceMatrix.js";const p=123456;export class Three3DIntervalsTimeLayerLoader{constructor(){this.engineClass=g,this.followsLayerBand=!0}canLoad(e){return e instanceof v}load(v,g,u,x){return this.canLoad(v)?function(v,g,u,x){const b=new a,y=v.model,{material:S,dispose:w}=(v.style,function(e,o,i){const v=void 0!==e.styler,g=function(e){return e instanceof f&&void 0!==e.histogram.classifier&&e.histogram instanceof m}(e),h=new a,u=()=>{const n=e.style,t=n.border?n.border.width||1:0,o="boolean"!=typeof n.border?.centered||n.border?.centered;let i="",l="";if(g){const n=e.maxClassifications||4;for(let e=0;e<n;e+=4)i+=`attribute vec4 split${e/4};`,i+=`flat out vec4 vSplit${e/4};`,l+=`vSplit${e/4} = split${e/4};`;console.log("createVertexShader",{splitAttributes:i,maxSplits:n})}return`\n // precision mediump float;\n // precision highp float;\n \n uniform vec2 layerRange;\n const bool borderCentered = ${o};\n const float borderWidth = ${d(t)};\n const float infinityDuration = ${d(p)};\n const float msInHour = 3600000.0;\n\n ${i}\n\n uniform vec2 originTime;\n uniform vec2 nowTime;\n uniform vec2 valueRange;\n uniform int selection;\n uniform vec2 worldPerPixel;\n // uniform float split;\n // attribute float split;\n \n ${v?"attribute vec3 borderColor;":""}\n ${v?"varying vec3 fillColor;":""}\n ${v?"varying vec3 strokeColor;":""}\n varying vec2 vUv;\n\n // flat out float vSplit;\n flat out int vSelected;\n flat out int vTopBottom;\n flat out vec2 vSize;\n flat out vec2 vTransform;\n\n void main() {\n ${v?"fillColor = instanceColor;":""}\n ${v?"strokeColor = borderColor;":""}\n vUv = uv;\n // vSplit = split;\n ${l}\n \n bool selected = gl_InstanceID == selection;\n vSelected = selected ? 1 : 0;\n\n float minValue = valueRange.x;\n float valueSize = valueRange.y - minValue;\n\n float width = length(instanceMatrix[0].xyz);\n float height = length(instanceMatrix[1].xyz) / valueSize;\n float depth = length(instanceMatrix[2].xyz);\n\n height *= (layerRange.y - layerRange.x);\n\n if (selected) {\n depth += 0.00001;\n }\n\n vec3 pos = vec3(instanceMatrix[3].xyz);\n\n float deltaHours = pos.x - originTime.x;\n float deltaMs = pos.z - originTime.y;\n float relativeTime = deltaHours * msInHour + deltaMs;\n pos.x = relativeTime;\n \n pos.y = layerRange.x + ((pos.y - minValue) / valueSize) * (layerRange.y - layerRange.x);\n pos.z = 1.0 - 0.5 * depth;\n \n if (selected) {\n pos.z += 0.00001;\n }\n\n if (borderCentered) {\n vec3 borderOffset = borderWidth * vec3(worldPerPixel, 0.0);\n width += borderOffset.x;\n height += borderOffset.y;\n depth += borderOffset.z;\n }\n\n // Analyze the normal to determine the face\n // Use abs() to handle both positive and negative directions\n vec3 absNormal = abs(normal);\n\n if (absNormal.x > 0.99) {\n // Side is on the X-axis (left/right face)\n // The 2D plane is defined by the geometry's Depth and Height\n vSize = vec2(depth, height);\n vTransform = vec2(0.001, worldPerPixel.y);\n vTopBottom = 0;\n } else if (absNormal.y > 0.99) {\n // Side is on the Y-axis (top/bottom face)\n // The 2D plane is defined by the geometry's Width and Depth\n vSize = vec2(width, depth);\n vTransform = vec2(worldPerPixel.x, 0.001);\n vTopBottom = normal.y > 0.99 ? 1 : 2;\n } else {\n // Side is on the Z-axis (front/back face)\n // The 2D plane is defined by the geometry's Width and Height\n vSize = vec2(width, height);\n vTransform = vec2(worldPerPixel);\n vTopBottom = 0;\n }\n\n mat4 adjustedInstanceMatrix = instanceMatrix;\n adjustedInstanceMatrix[0].xyz *= width / length(instanceMatrix[0].xyz);\n adjustedInstanceMatrix[1].xyz *= height / length(instanceMatrix[1].xyz);\n adjustedInstanceMatrix[2].xyz *= depth / length(instanceMatrix[2].xyz);\n adjustedInstanceMatrix[3].xyz = pos;\n \n vec4 localPos = adjustedInstanceMatrix * vec4(position, 1.0);\n gl_Position = projectionMatrix * modelViewMatrix * localPos;\n }\n `},x=()=>{const t=e.style,o=new n(t.fill?.color||"#df0"),i=t.fill?.opacity||.5,l=new n(t.border?.color||"#000"),r=t.border?t.border.opacity||1:0,a=t.border?t.border.width||1:0,c="number"==typeof t.border?.smoothing?Math.max(0,t.border.smoothing):1;let m="";if(g){const n=e.maxClassifications||4,t=e.classificationColors||[];m+=`const int maxSplits = ${n};`;let o="",i="";for(let e=0;e<t.length;e++)m+=`\n const vec3 splitColor${e} = ${s(t[e]||"#777")};\n `,i+=`\n if (idx == ${e}) return splitColor${e};\n `;for(let e=0;e<n;e+=4)m+=`\n flat in vec4 vSplit${e/4};\n `,o+=`\n if (idx < ${e+4}) return vSplit${e/4}[idx - ${e}];\n `;m+=`\n float getSplit(int idx) {\n ${o}\n return 0.0;\n }\n vec3 getSplitColor(int idx) {\n ${i}\n return fillColor; // Fallback to default fill color\n }\n `,console.log("createFragmentShader",{splitVars:m,maxSplits:n,classificationColors:t})}return`\n // precision mediump float;\n // precision highp float;\n const float fillOpacity = ${d(i)};\n const float borderOpacity = ${d(r)};\n const float borderWidth = ${d(a)};\n const float borderSmoothing = ${d(c)};\n const bool borderSidesVertical = ${"vertical"===t.border?.sides?"true":"false"};\n\n ${v?"varying vec3 fillColor;":`const vec3 fillColor = vec3(${d(o.r)},${d(o.g)},${d(o.b)});`}\n ${v?"varying vec3 strokeColor;":`const vec3 strokeColor = vec3(${d(l.r)},${d(l.g)},${d(l.b)});`}\n\n varying vec2 vUv;\n flat in int vSelected;\n flat in int vTopBottom;\n flat in vec2 vSize;\n flat in vec2 vTransform;\n\n ${m}\n\n // Coverage of the inside of an edge at lo; smoothing 0 gives a hard step (smoothstep is undefined for equal edges).\n float edgeAlpha(float lo, float smoothing, float x) {\n return smoothing > 0.0 ? smoothstep(lo, lo + smoothing, x) : step(lo, x);\n }\n\n void main() {\n ${g?"\n bool selected = vSelected > 0;\n vec3 finalFillColor = selected ? vec3(1.0 - fillColor) : fillColor;\n vec3 finalBorderColor = selected ? vec3(1.0 - strokeColor) : strokeColor;\n\n vec3 segmentFillColor;\n float segmentStart = 0.0;\n float segmentEnd = 1.0;\n if (vTopBottom < 1) {\n // Determine which segment the current fragment is in\n int segment = 0;\n for (int i = 0; i < maxSplits; i++) {\n float spliti = getSplit(i);\n if (vUv.y <= spliti) {\n segmentEnd = spliti;\n segment = i;\n break;\n }\n else {\n segmentStart = spliti;\n }\n // if (vUv.y <= segmentStart + spliti) {\n // segmentEnd = segmentStart + spliti;\n // segment = i;\n // break;\n // }\n // else {\n // segmentStart += spliti;\n // }\n }\n\n // if (segment == 0 && segmentStart > 0.0) {\n // segmentEnd = 1.0;\n // for (int i = maxSplits - 1; i >= 0; i--) {\n // float spliti = getSplit(i);\n // if (spliti > 0.0) {\n // segmentStart = 1.0 - spliti;\n // segment = i;\n // break;\n // }\n // }\n // }\n \n segmentFillColor = getSplitColor(segment);\n }\n else if (vTopBottom < 2) {\n // Determine the last segment with a value\n int segment = 0;\n for (int i = maxSplits - 1; i >= 0; i--) {\n if (getSplit(i) < 0.99999) {\n segment = i + 1;\n break;\n }\n }\n segmentFillColor = getSplitColor(segment);\n }\n else {\n // Determine the first segment with a value\n int segment = 0;\n for (int i = 0; i < maxSplits; i++) {\n if (getSplit(i) > 0.0) {\n segment = i;\n break;\n }\n }\n segmentFillColor = getSplitColor(segment);\n }\n\n vec3 segmentBorderColor = segmentFillColor;\n float segmentSize = segmentEnd - segmentStart;\n\n // Half border size in world units\n vec2 bwWorld = borderWidth * vTransform;\n // Half border UV width if centered, full if inside-only\n vec2 bw = (bwWorld / vSize) / vec2(1.0, segmentSize);\n float smoothingPixels = borderSmoothing;\n vec2 smoothing = smoothingPixels * vTransform / vSize;\n\n // Adjust UV coordinates for the current segment (y-axis only)\n float uvYAdjusted = (vUv.y - segmentStart) / segmentSize;\n\n // Calculate alpha for borders and fill for the adjusted UV\n float alphaX = edgeAlpha(bw.x, smoothing.x, vUv.x) *\n edgeAlpha(bw.x, smoothing.x, 1.0 - vUv.x);\n // borderSidesVertical: skip the border between stacked segments (top/bottom), keep only left/right.\n float alphaY = borderSidesVertical ? 1.0 : edgeAlpha(bw.y, smoothing.y, uvYAdjusted) *\n edgeAlpha(bw.y, smoothing.y, 1.0 - uvYAdjusted);\n float fillAlpha = alphaX * alphaY;\n\n vec3 color = mix(segmentBorderColor, segmentFillColor, fillAlpha);\n float alpha = mix(borderOpacity, fillOpacity, fillAlpha);\n\n gl_FragColor = linearToOutputTexel(vec4(color, alpha));\n ":"\n bool selected = vSelected > 0;\n vec3 finalFillColor = selected ? vec3(1.0 - fillColor) : fillColor;\n vec3 finalBorderColor = selected ? vec3(1.0 - strokeColor) : strokeColor;\n\n // Half border size in world units\n vec2 bwWorld = borderWidth * vTransform;\n\n // Half border UV width if centered, full if inside-only\n vec2 bw = bwWorld / vSize;\n\n float smoothingPixels = borderSmoothing;\n vec2 smoothing = smoothingPixels * vTransform / vSize;\n\n float alphaX = edgeAlpha(bw.x, smoothing.x, vUv.x) *\n edgeAlpha(bw.x, smoothing.x, 1.0 - vUv.x);\n float alphaY = borderSidesVertical ? 1.0 : edgeAlpha(bw.y, smoothing.y, vUv.y) *\n edgeAlpha(bw.y, smoothing.y, 1.0 - vUv.y);\n\n float fillAlpha = alphaX * alphaY;\n\n vec3 color = mix(finalBorderColor, finalFillColor, fillAlpha);\n float alpha = mix(borderOpacity, fillOpacity, fillAlpha);\n\n gl_FragColor = linearToOutputTexel(vec4(color, alpha));\n "}\n }\n `},b=new l({transparent:!0,side:t,uniforms:{worldPerPixel:{value:new r(i.camera.scale.x/i.getSize().width,i.camera.scale.y/i.getSize().height)},layerRange:{value:new r(e.min,e.max)},selection:{value:-1},valueRange:{value:new r(0,1)},originTime:{value:new r(0,0)}},vertexShader:u(),fragmentShader:x()}),y=()=>{b.uniforms.worldPerPixel.value.set(i.camera.scale.x/i.getSize().width,i.camera.scale.y/i.getSize().height)},S=()=>{const e=i.getTimeRange(),n=e.startTime+.5*e.duration,[t,o]=c(n);b.uniforms.originTime.value.set(t,o)},w=()=>{const n=o.valueRanges.get(e.yAxis.group||e.id);if(n){const e=b.uniforms.valueRange.value;e.x===n.min&&e.y===n.max||b.uniforms.valueRange.value.set(n.min,n.max)}},C=()=>{y(),S()};h.add(o.on("valueRanges",w)),h.add(i.on("time",C)),h.add(i.on("value",y)),h.add(i.on("size",y)),h.add(e.on("loaded",w)),h.add(e.on("visible",w));const T=()=>b.uniforms.layerRange.value.set(e.min,e.max);return h.add(e.on("min",T)),h.add(e.on("max",T)),S(),w(),h.add(e.on("invalidateAll",()=>{b.vertexShader=u(),b.fragmentShader=x(),b.needsUpdate=!0})),{material:b,dispose:()=>h.dispose()}}(v,u,x));b.add(w);const C=new e(1,1,1),T=new o(C,S,y.data.length);T.frustumCulled=!1,T.receiveShadow=!0,T.castShadow=!0;const $=function(e,n,t){const o=new h(n,e=>{const n=new i,t=.5*(e.t[0]+e.t[1]),[o,l]=c(t),r=e.v[1]-e.v[0],a=e.v[0]+.5*r;n.position.set(o,a,l);const s=e.t[1]-e.t[0];return n.scale.set(s,r,.1),n.updateMatrix(),n.matrix},t);return o.registerMesh(e),o}(T,y,{colorProvider:v.styler?e=>{const n=v.styler(e);return n?{fill:n.fill?.color,stroke:n.border?.color}:void 0}:void 0,defaultFill:v.style.fill?.color,defaultBorder:v.style.border?.color,splitProvider:v instanceof f&&v.histogram instanceof m?e=>e.attributes.splits||[1]:void 0,maxSplits:v instanceof f?v.maxClassifications:void 0});b.add(()=>$.dispose()),u.selectionOptions.highlight&&b.add(v.on("selection",e=>{if(e.length>0){const n=$.getIndex(e[0]);S.uniforms.selection.value="number"==typeof n?n:-1}else S.uniforms.selection.value=-1}));return g.add(T),b.add(()=>g.remove(T)),b.disposeLater()}(v,g,x,u):(console.error("Layer not supported by ThreePointsTimeLayerLoader",{layer:v}),()=>{})}}
1
+ import{BoxGeometry as e,Color as n,DoubleSide as t,InstancedMesh as o,Object3D as i,ShaderMaterial as r,Vector2 as l}from"three";import{getDevicePixelRatio as a}from"../PixelSnap.js";import{Disposables as s}from"../../common/Disposables.js";import{toGLSLColor as d,toGLSLFloat as m}from"../../common/GlslTools.js";import{getTimePartsInHours as c}from"../../common/TimeTools.js";import{TimeSliderSplittingFixedHistogram as f}from"../../data/histogram/TimeSliderSplittingFixedHistogram.js";import{TimeSliderHistogramLayer as v}from"../../layer/TimeSliderHistogramLayer.js";import{TimeSliderIntervalsLayer as g}from"../../layer/TimeSliderIntervalsLayer.js";import{Three3DTimeSliderEngine as h}from"../Three3DTimeSliderEngine.js";import{GrowingInstanceMatrix as p}from"./GrowingInstanceMatrix.js";const u=123456;export class Three3DIntervalsTimeLayerLoader{constructor(){this.engineClass=h,this.followsLayerBand=!0}canLoad(e){return e instanceof g}load(g,h,x,y){return this.canLoad(g)?function(g,h,x,y){const b=new s,S=g.model,{material:w,dispose:C}=(g.style,function(e,o,i){const g=void 0!==e.styler,h=function(e){return e instanceof v&&void 0!==e.histogram.classifier&&e.histogram instanceof f}(e),p=new s,x=()=>{const n=e.style,t=n.border?n.border.width||1:0,o="boolean"!=typeof n.border?.centered||n.border?.centered;let i="",r="";if(h){const n=e.maxClassifications||4;for(let e=0;e<n;e+=4)i+=`attribute vec4 split${e/4};`,i+=`flat out vec4 vSplit${e/4};`,r+=`vSplit${e/4} = split${e/4};`;console.log("createVertexShader",{splitAttributes:i,maxSplits:n})}return`\n // precision mediump float;\n // precision highp float;\n \n uniform vec2 layerRange;\n uniform float layerDirection;\n const bool borderCentered = ${o};\n const float borderWidth = ${m(t)};\n uniform float pixelRatio;\n // Borders are a whole number of device pixels wide, so with smoothing 0 they're crisp wherever they land.\n float snappedBorderWidth() { return borderWidth > 0.0 ? max(1.0, floor(borderWidth * pixelRatio + 0.5)) / pixelRatio : 0.0; }\n const float infinityDuration = ${m(u)};\n const float msInHour = 3600000.0;\n\n ${i}\n\n uniform vec2 originTime;\n uniform vec2 nowTime;\n uniform vec2 valueRange;\n uniform int selection;\n uniform vec2 worldPerPixel;\n // uniform float split;\n // attribute float split;\n \n ${g?"attribute vec3 borderColor;":""}\n ${g?"varying vec3 fillColor;":""}\n ${g?"varying vec3 strokeColor;":""}\n varying vec2 vUv;\n\n // flat out float vSplit;\n flat out int vSelected;\n flat out int vTopBottom;\n flat out vec2 vSize;\n flat out vec2 vTransform;\n\n void main() {\n ${g?"fillColor = instanceColor;":""}\n ${g?"strokeColor = borderColor;":""}\n vUv = uv;\n // vSplit = split;\n ${r}\n \n bool selected = gl_InstanceID == selection;\n vSelected = selected ? 1 : 0;\n\n float minValue = valueRange.x;\n float valueSize = valueRange.y - minValue;\n\n float width = length(instanceMatrix[0].xyz);\n float height = length(instanceMatrix[1].xyz) / valueSize;\n float depth = length(instanceMatrix[2].xyz);\n\n height *= abs(layerRange.y - layerRange.x);\n\n if (selected) {\n depth += 0.00001;\n }\n\n vec3 pos = vec3(instanceMatrix[3].xyz);\n\n float deltaHours = pos.x - originTime.x;\n float deltaMs = pos.z - originTime.y;\n float relativeTime = deltaHours * msInHour + deltaMs;\n pos.x = relativeTime;\n \n pos.y = layerRange.x + ((pos.y - minValue) / valueSize) * (layerRange.y - layerRange.x);\n pos.z = 1.0 - 0.5 * depth;\n \n if (selected) {\n pos.z += 0.00001;\n }\n\n if (borderCentered) {\n vec3 borderOffset = snappedBorderWidth() * vec3(worldPerPixel, 0.0);\n width += borderOffset.x;\n height += borderOffset.y;\n depth += borderOffset.z;\n }\n\n // Analyze the normal to determine the face\n // Use abs() to handle both positive and negative directions\n vec3 absNormal = abs(normal);\n\n if (absNormal.x > 0.99) {\n // Side is on the X-axis (left/right face)\n // The 2D plane is defined by the geometry's Depth and Height\n vSize = vec2(depth, height);\n vTransform = vec2(0.001, worldPerPixel.y);\n vTopBottom = 0;\n // Hanging down: value 0 is at the top, so stacked segments run top to bottom.\n if (layerDirection < 0.0) vUv.y = 1.0 - vUv.y;\n } else if (absNormal.y > 0.99) {\n // Side is on the Y-axis (top/bottom face)\n // The 2D plane is defined by the geometry's Width and Depth\n vSize = vec2(width, depth);\n vTransform = vec2(worldPerPixel.x, 0.001);\n // 1: the face at the bars' far end (last segment), 2: at value 0 (first segment); hanging down swaps them.\n vTopBottom = (normal.y > 0.99) == (layerDirection > 0.0) ? 1 : 2;\n } else {\n // Side is on the Z-axis (front/back face)\n // The 2D plane is defined by the geometry's Width and Height\n vSize = vec2(width, height);\n vTransform = vec2(worldPerPixel);\n vTopBottom = 0;\n if (layerDirection < 0.0) vUv.y = 1.0 - vUv.y;\n }\n\n mat4 adjustedInstanceMatrix = instanceMatrix;\n adjustedInstanceMatrix[0].xyz *= width / length(instanceMatrix[0].xyz);\n adjustedInstanceMatrix[1].xyz *= height / length(instanceMatrix[1].xyz);\n adjustedInstanceMatrix[2].xyz *= depth / length(instanceMatrix[2].xyz);\n adjustedInstanceMatrix[3].xyz = pos;\n \n vec4 localPos = adjustedInstanceMatrix * vec4(position, 1.0);\n gl_Position = projectionMatrix * modelViewMatrix * localPos;\n }\n `},y=()=>{const t=e.style,o=new n(t.fill?.color||"#df0"),i=t.fill?.opacity||.5,r=new n(t.border?.color||"#000"),l=t.border?t.border.opacity||1:0,a=t.border?t.border.width||1:0,s="number"==typeof t.border?.smoothing?Math.max(0,t.border.smoothing):1;let c="";if(h){const n=e.maxClassifications||4,t=e.classificationColors||[];c+=`const int maxSplits = ${n};`;let o="",i="";for(let e=0;e<t.length;e++)c+=`\n const vec3 splitColor${e} = ${d(t[e]||"#777")};\n `,i+=`\n if (idx == ${e}) return splitColor${e};\n `;for(let e=0;e<n;e+=4)c+=`\n flat in vec4 vSplit${e/4};\n `,o+=`\n if (idx < ${e+4}) return vSplit${e/4}[idx - ${e}];\n `;c+=`\n float getSplit(int idx) {\n ${o}\n return 0.0;\n }\n vec3 getSplitColor(int idx) {\n ${i}\n return fillColor; // Fallback to default fill color\n }\n `,console.log("createFragmentShader",{splitVars:c,maxSplits:n,classificationColors:t})}return`\n // precision mediump float;\n // precision highp float;\n const float fillOpacity = ${m(i)};\n const float borderOpacity = ${m(l)};\n const float borderWidth = ${m(a)};\n uniform float pixelRatio;\n // Borders are a whole number of device pixels wide, so with smoothing 0 they're crisp wherever they land.\n float snappedBorderWidth() { return borderWidth > 0.0 ? max(1.0, floor(borderWidth * pixelRatio + 0.5)) / pixelRatio : 0.0; }\n const float borderSmoothing = ${m(s)};\n const bool borderSidesVertical = ${"vertical"===t.border?.sides?"true":"false"};\n\n ${g?"varying vec3 fillColor;":`const vec3 fillColor = vec3(${m(o.r)},${m(o.g)},${m(o.b)});`}\n ${g?"varying vec3 strokeColor;":`const vec3 strokeColor = vec3(${m(r.r)},${m(r.g)},${m(r.b)});`}\n\n varying vec2 vUv;\n flat in int vSelected;\n flat in int vTopBottom;\n flat in vec2 vSize;\n flat in vec2 vTransform;\n\n ${c}\n\n // Coverage of the inside of an edge at lo; smoothing 0 gives a hard step (smoothstep is undefined for equal edges).\n float edgeAlpha(float lo, float smoothing, float x) {\n return smoothing > 0.0 ? smoothstep(lo, lo + smoothing, x) : step(lo, x);\n }\n\n void main() {\n ${h?"\n bool selected = vSelected > 0;\n vec3 finalFillColor = selected ? vec3(1.0 - fillColor) : fillColor;\n vec3 finalBorderColor = selected ? vec3(1.0 - strokeColor) : strokeColor;\n\n vec3 segmentFillColor;\n float segmentStart = 0.0;\n float segmentEnd = 1.0;\n if (vTopBottom < 1) {\n // Determine which segment the current fragment is in\n int segment = 0;\n for (int i = 0; i < maxSplits; i++) {\n float spliti = getSplit(i);\n if (vUv.y <= spliti) {\n segmentEnd = spliti;\n segment = i;\n break;\n }\n else {\n segmentStart = spliti;\n }\n // if (vUv.y <= segmentStart + spliti) {\n // segmentEnd = segmentStart + spliti;\n // segment = i;\n // break;\n // }\n // else {\n // segmentStart += spliti;\n // }\n }\n\n // if (segment == 0 && segmentStart > 0.0) {\n // segmentEnd = 1.0;\n // for (int i = maxSplits - 1; i >= 0; i--) {\n // float spliti = getSplit(i);\n // if (spliti > 0.0) {\n // segmentStart = 1.0 - spliti;\n // segment = i;\n // break;\n // }\n // }\n // }\n \n segmentFillColor = getSplitColor(segment);\n }\n else if (vTopBottom < 2) {\n // Determine the last segment with a value\n int segment = 0;\n for (int i = maxSplits - 1; i >= 0; i--) {\n if (getSplit(i) < 0.99999) {\n segment = i + 1;\n break;\n }\n }\n segmentFillColor = getSplitColor(segment);\n }\n else {\n // Determine the first segment with a value\n int segment = 0;\n for (int i = 0; i < maxSplits; i++) {\n if (getSplit(i) > 0.0) {\n segment = i;\n break;\n }\n }\n segmentFillColor = getSplitColor(segment);\n }\n\n vec3 segmentBorderColor = segmentFillColor;\n float segmentSize = segmentEnd - segmentStart;\n\n // Half border size in world units\n vec2 bwWorld = snappedBorderWidth() * vTransform;\n // Half border UV width if centered, full if inside-only\n vec2 bw = (bwWorld / vSize) / vec2(1.0, segmentSize);\n float smoothingPixels = borderSmoothing;\n vec2 smoothing = smoothingPixels * vTransform / vSize;\n\n // Adjust UV coordinates for the current segment (y-axis only)\n float uvYAdjusted = (vUv.y - segmentStart) / segmentSize;\n\n // Calculate alpha for borders and fill for the adjusted UV\n float alphaX = edgeAlpha(bw.x, smoothing.x, vUv.x) *\n edgeAlpha(bw.x, smoothing.x, 1.0 - vUv.x);\n // borderSidesVertical: skip the border between stacked segments (top/bottom), keep only left/right.\n float alphaY = borderSidesVertical ? 1.0 : edgeAlpha(bw.y, smoothing.y, uvYAdjusted) *\n edgeAlpha(bw.y, smoothing.y, 1.0 - uvYAdjusted);\n float fillAlpha = alphaX * alphaY;\n\n vec3 color = mix(segmentBorderColor, segmentFillColor, fillAlpha);\n float alpha = mix(borderOpacity, fillOpacity, fillAlpha);\n\n gl_FragColor = linearToOutputTexel(vec4(color, alpha));\n ":"\n bool selected = vSelected > 0;\n vec3 finalFillColor = selected ? vec3(1.0 - fillColor) : fillColor;\n vec3 finalBorderColor = selected ? vec3(1.0 - strokeColor) : strokeColor;\n\n // Half border size in world units\n vec2 bwWorld = snappedBorderWidth() * vTransform;\n\n // Half border UV width if centered, full if inside-only\n vec2 bw = bwWorld / vSize;\n\n float smoothingPixels = borderSmoothing;\n vec2 smoothing = smoothingPixels * vTransform / vSize;\n\n float alphaX = edgeAlpha(bw.x, smoothing.x, vUv.x) *\n edgeAlpha(bw.x, smoothing.x, 1.0 - vUv.x);\n float alphaY = borderSidesVertical ? 1.0 : edgeAlpha(bw.y, smoothing.y, vUv.y) *\n edgeAlpha(bw.y, smoothing.y, 1.0 - vUv.y);\n\n float fillAlpha = alphaX * alphaY;\n\n vec3 color = mix(finalBorderColor, finalFillColor, fillAlpha);\n float alpha = mix(borderOpacity, fillOpacity, fillAlpha);\n\n gl_FragColor = linearToOutputTexel(vec4(color, alpha));\n "}\n }\n `},b=new r({transparent:!0,side:t,uniforms:{worldPerPixel:{value:new l(i.camera.scale.x/i.getSize().width,i.camera.scale.y/i.getSize().height)},pixelRatio:{value:a()},layerRange:{value:new l(e.min,e.max)},layerDirection:{value:1},selection:{value:-1},valueRange:{value:new l(0,1)},originTime:{value:new l(0,0)}},vertexShader:x(),fragmentShader:y()}),S=()=>{b.uniforms.pixelRatio.value=a(),b.uniforms.worldPerPixel.value.set(i.camera.scale.x/i.getSize().width,i.camera.scale.y/i.getSize().height)},w=()=>{const e=i.getTimeRange(),n=e.startTime+.5*e.duration,[t,o]=c(n);b.uniforms.originTime.value.set(t,o)},C=()=>{const n=o.valueRanges.get(e.yAxis.group||e.id);if(n){const e=b.uniforms.valueRange.value;e.x===n.min&&e.y===n.max||b.uniforms.valueRange.value.set(n.min,n.max)}},T=()=>{S(),w()};p.add(o.on("valueRanges",C)),p.add(i.on("time",T)),p.add(i.on("value",S)),p.add(i.on("size",S)),p.add(e.on("loaded",C)),p.add(e.on("visible",C));const $=()=>{const n="down"===e.direction;b.uniforms.layerRange.value.set(n?e.max:e.min,n?e.min:e.max),b.uniforms.layerDirection.value=n?-1:1};return p.add(e.on("min",$)),p.add(e.on("max",$)),p.add(e.on("direction",$)),$(),w(),C(),p.add(e.on("invalidateAll",()=>{b.vertexShader=x(),b.fragmentShader=y(),b.needsUpdate=!0})),{material:b,dispose:()=>p.dispose()}}(g,x,y));b.add(C);const T=new e(1,1,1),$=new o(T,w,S.data.length);$.frustumCulled=!1,$.receiveShadow=!0,$.castShadow=!0;const z=function(e,n,t){const o=new p(n,e=>{const n=new i,t=.5*(e.t[0]+e.t[1]),[o,r]=c(t),l=e.v[1]-e.v[0],a=e.v[0]+.5*l;n.position.set(o,a,r);const s=e.t[1]-e.t[0];return n.scale.set(s,l,.1),n.updateMatrix(),n.matrix},t);return o.registerMesh(e),o}($,S,{colorProvider:g.styler?e=>{const n=g.styler(e);return n?{fill:n.fill?.color,stroke:n.border?.color}:void 0}:void 0,defaultFill:g.style.fill?.color,defaultBorder:g.style.border?.color,splitProvider:g instanceof v&&g.histogram instanceof f?e=>e.attributes.splits||[1]:void 0,maxSplits:g instanceof v?g.maxClassifications:void 0});b.add(()=>z.dispose()),x.selectionOptions.highlight&&b.add(g.on("selection",e=>{if(e.length>0){const n=z.getIndex(e[0]);w.uniforms.selection.value="number"==typeof n?n:-1}else w.uniforms.selection.value=-1}));return h.add($),b.add(()=>h.remove($)),b.disposeLater()}(g,h,y,x):(console.error("Layer not supported by ThreePointsTimeLayerLoader",{layer:g}),()=>{})}}
@@ -1 +1 @@
1
- import{Color as e,InstancedMesh as o,Object3D as n,PlaneGeometry as r,ShaderMaterial as t,Vector2 as l}from"three";import{Disposables as i}from"../../common/Disposables.js";import{toGLSLFloat as a}from"../../common/GlslTools.js";import{getTimePartsInHours as s}from"../../common/TimeTools.js";import{TimeSliderIntervalsLayer as d}from"../../layer/TimeSliderIntervalsLayer.js";import{GrowingInstanceMatrix as c}from"./GrowingInstanceMatrix.js";import{ThreeTimeLayerLoader as m}from"./ThreeTimeLayerLoader.js";const v=123456;export class ThreeIntervalsTimeLayerLoader extends m{constructor(){super(...arguments),this.followsLayerBand=!0}canLoad(e){return e instanceof d}load(d,m,f,u){return this.canLoad(d)?function(d,m,f,u){const g=new i,h=d.model,{material:p,dispose:x}=function(o,n,r){const d=void 0!==o.styler,c=new i,m=()=>{const e=o.style,n=e.border?e.border.width||1:0;return`\n // precision mediump float;\n // precision highp float;\n \n const bool borderCentered = ${"boolean"!=typeof e.border?.centered||e.border?.centered};\n const float borderWidth = ${a(n)};\n uniform vec2 layerRange;\n const float infinityDuration = ${a(v)};\n const float msInHour = 3600000.0;\n\n uniform vec2 originTime;\n uniform vec2 valueRange;\n uniform int selection;\n uniform vec2 worldPerPixel;\n \n ${d?"attribute vec3 borderColor;":""}\n ${d?"varying vec3 fillColor;":""}\n ${d?"varying vec3 strokeColor;":""}\n \n varying vec2 vUv;\n flat out int vSelected;\n flat out vec2 vSize;\n\n void main() {\n ${d?"fillColor = instanceColor;":""}\n ${d?"strokeColor = borderColor;":""}\n vUv = uv;\n \n bool selected = gl_InstanceID == selection;\n vSelected = selected ? 1 : 0;\n\n float minValue = valueRange.x;\n float valueSize = valueRange.y - minValue;\n \n vSize.x = length(instanceMatrix[0].xyz);\n vSize.y = length(instanceMatrix[1].xyz) / valueSize;\n\n vSize.y *= (layerRange.y - layerRange.x);\n \n vec3 pos = vec3(instanceMatrix[3].xyz);\n float deltaHours = pos.x - originTime.x;\n float deltaMs = pos.z - originTime.y;\n float relativeTime = deltaHours * 3600000.0 + deltaMs;\n pos.x = relativeTime;\n pos.y = layerRange.x + ((pos.y - minValue) / valueSize) * (layerRange.y - layerRange.x);\n pos.z = 0.0;\n \n if (selected) {\n pos.z += 0.0001;\n }\n\n if (borderCentered) {\n vec3 borderOffset = borderWidth * vec3(worldPerPixel, 0.0);\n vSize += borderOffset.xy;\n }\n \n mat4 adjustedInstanceMatrix = instanceMatrix;\n adjustedInstanceMatrix[0].xyz *= vSize.x / length(instanceMatrix[0].xyz);\n adjustedInstanceMatrix[1].xyz *= vSize.y / length(instanceMatrix[1].xyz);\n adjustedInstanceMatrix[3].xyz = pos;\n \n vec4 localPos = adjustedInstanceMatrix * vec4(position, 1.0);\n gl_Position = projectionMatrix * modelViewMatrix * localPos;\n }\n `},f=()=>{const n=o.style,r=new e(n.fill?.color||"#df0"),t=n.fill?.opacity||.5,l=new e(n.border?.color||"#000"),i=n.border?n.border.opacity||1:0,s=n.border?n.border.width||1:0,c="number"==typeof n.border?.smoothing?Math.max(0,n.border.smoothing):1.5;return`\n // precision mediump float;\n // precision highp float;\n\n const float fillOpacity = ${a(t)};\n const float borderOpacity = ${a(i)};\n const float borderWidth = ${a(s)};\n const float borderSmoothing = ${a(c)};\n const bool borderSidesVertical = ${"vertical"===n.border?.sides?"true":"false"};\n\n ${d?"varying vec3 fillColor;":`const vec3 fillColor = vec3(${a(r.r)},${a(r.g)},${a(r.b)});`}\n ${d?"varying vec3 strokeColor;":`const vec3 strokeColor = vec3(${a(l.r)},${a(l.g)},${a(l.b)});`}\n \n uniform vec2 worldPerPixel;\n \n varying vec2 vUv;\n flat in int vSelected;\n flat in vec2 vSize;\n\n // Coverage of the inside of an edge at lo; smoothing 0 gives a hard step (smoothstep is undefined for equal edges).\n float edgeAlpha(float lo, float smoothing, float x) {\n return smoothing > 0.0 ? smoothstep(lo, lo + smoothing, x) : step(lo, x);\n }\n\n void main() {\n bool selected = vSelected > 0;\n vec3 finalFillColor = selected ? vec3(1.0 - fillColor) : fillColor;\n vec3 finalBorderColor = selected ? vec3(1.0 - strokeColor) : strokeColor;\n\n // Half border size in world units\n vec2 bwWorld = borderWidth * worldPerPixel;\n\n // Half border UV width if centered, full if inside-only\n vec2 bw = bwWorld / vSize;\n\n float smoothingPixels = borderSmoothing;\n vec2 smoothing = smoothingPixels * worldPerPixel / vSize;\n\n float alphaX = edgeAlpha(bw.x, smoothing.x, vUv.x) *\n edgeAlpha(bw.x, smoothing.x, 1.0 - vUv.x);\n float alphaY = borderSidesVertical ? 1.0 : edgeAlpha(bw.y, smoothing.y, vUv.y) *\n edgeAlpha(bw.y, smoothing.y, 1.0 - vUv.y);\n\n float fillAlpha = alphaX * alphaY;\n float borderAlpha = 1.0 - fillAlpha;\n\n vec3 color = mix(finalBorderColor, finalFillColor, fillAlpha);\n float alpha = mix(borderOpacity, fillOpacity, fillAlpha);\n\n gl_FragColor = linearToOutputTexel(vec4(color, alpha));\n }\n `},u=new t({transparent:!0,uniforms:{worldPerPixel:{value:new l((n.camera.right-n.camera.left)/n.getSize().width,(n.camera.top-n.camera.bottom)/n.getSize().height)},layerRange:{value:new l(o.min,o.max)},selection:{value:-1},valueRange:{value:new l(0,1)},originTime:{value:new l(0,0)}},vertexShader:m(),fragmentShader:f()}),g=()=>{u.uniforms.worldPerPixel.value.set((n.camera.right-n.camera.left)/n.getSize().width,(n.camera.top-n.camera.bottom)/n.getSize().height)},h=()=>{const e=n.getTimeRange(),o=e.startTime+.5*e.duration,[r,t]=s(o);u.uniforms.originTime.value.set(r,t)},p=()=>{const e=r.valueRanges.get(o.yAxis.group||o.id);if(e){const o=u.uniforms.valueRange.value;o.x===e.min&&o.y===e.max||u.uniforms.valueRange.value.set(e.min,e.max)}},x=()=>{g(),h(),p()};c.add(r.on("valueRanges",p)),c.add(n.on("time",x)),c.add(n.on("value",g)),c.add(n.on("size",g)),c.add(o.on("loaded",p)),c.add(o.on("visible",p));const y=()=>u.uniforms.layerRange.value.set(o.min,o.max);return c.add(o.on("min",y)),c.add(o.on("max",y)),h(),p(),c.add(o.on("invalidate",()=>{u.vertexShader=m(),u.fragmentShader=f(),u.needsUpdate=!0})),{material:u,dispose:()=>c.dispose()}}(d,m,u);g.add(x);const y=new r(1,1),b=new o(y,p,h.data.length);b.frustumCulled=!1;const w=function(e,o,r,t,l){const i=new c(o,e=>{const o=new n,r=e.v[1]-e.v[0],t=e.v[0]+.5*r,l=.5*(e.t[0]+e.t[1]),[i,a]=s(l);o.position.set(i,t,a);const d=e.t[1]-e.t[0];return o.scale.set(d,r,.1),o.updateMatrix(),o.matrix},l);return i.registerMesh(e),i}(b,h,d.min,d.max,{colorProvider:d.styler?e=>{const o=d.styler(e);return o?{fill:o.fill?.color,stroke:o.border?.color}:void 0}:void 0,defaultFill:d.style.fill?.color,defaultBorder:d.style.border?.color});u.selectionOptions.highlight&&g.add(d.on("selection",e=>{if(e.length>0){const o=w.getIndex(e[0]);p.uniforms.selection.value="number"==typeof o?o:-1}else p.uniforms.selection.value=-1}));return f.add(b),g.add(()=>f.remove(b)),g.disposeLater()}(d,f,m,u):(console.error("Layer not supported by ThreePointsTimeLayerLoader",{layer:d}),()=>{})}}
1
+ import{Color as e,InstancedMesh as o,Object3D as n,PlaneGeometry as r,ShaderMaterial as t,Vector2 as i}from"three";import{getDevicePixelRatio as a}from"../PixelSnap.js";import{Disposables as l}from"../../common/Disposables.js";import{toGLSLFloat as s}from"../../common/GlslTools.js";import{getTimePartsInHours as d}from"../../common/TimeTools.js";import{TimeSliderIntervalsLayer as c}from"../../layer/TimeSliderIntervalsLayer.js";import{GrowingInstanceMatrix as m}from"./GrowingInstanceMatrix.js";import{ThreeTimeLayerLoader as v}from"./ThreeTimeLayerLoader.js";const f=123456;export class ThreeIntervalsTimeLayerLoader extends v{constructor(){super(...arguments),this.followsLayerBand=!0}canLoad(e){return e instanceof c}load(c,v,u,p){return this.canLoad(c)?function(c,v,u,p){const h=new l,g=c.model,{material:x,dispose:y}=function(o,n,r){const c=void 0!==o.styler,m=new l,v=()=>{const e=o.style,n=e.border?e.border.width||1:0;return`\n // precision mediump float;\n // precision highp float;\n \n const bool borderCentered = ${"boolean"!=typeof e.border?.centered||e.border?.centered};\n const float borderWidth = ${s(n)};\n uniform float pixelRatio;\n // Borders are a whole number of device pixels wide, so with smoothing 0 they're crisp wherever they land.\n float snappedBorderWidth() { return borderWidth > 0.0 ? max(1.0, floor(borderWidth * pixelRatio + 0.5)) / pixelRatio : 0.0; }\n uniform vec2 layerRange;\n const float infinityDuration = ${s(f)};\n const float msInHour = 3600000.0;\n\n uniform vec2 originTime;\n uniform vec2 valueRange;\n uniform int selection;\n uniform vec2 worldPerPixel;\n \n ${c?"attribute vec3 borderColor;":""}\n ${c?"varying vec3 fillColor;":""}\n ${c?"varying vec3 strokeColor;":""}\n \n varying vec2 vUv;\n flat out int vSelected;\n flat out vec2 vSize;\n\n void main() {\n ${c?"fillColor = instanceColor;":""}\n ${c?"strokeColor = borderColor;":""}\n vUv = uv;\n \n bool selected = gl_InstanceID == selection;\n vSelected = selected ? 1 : 0;\n\n float minValue = valueRange.x;\n float valueSize = valueRange.y - minValue;\n \n vSize.x = length(instanceMatrix[0].xyz);\n vSize.y = length(instanceMatrix[1].xyz) / valueSize;\n\n vSize.y *= abs(layerRange.y - layerRange.x);\n \n vec3 pos = vec3(instanceMatrix[3].xyz);\n float deltaHours = pos.x - originTime.x;\n float deltaMs = pos.z - originTime.y;\n float relativeTime = deltaHours * 3600000.0 + deltaMs;\n pos.x = relativeTime;\n pos.y = layerRange.x + ((pos.y - minValue) / valueSize) * (layerRange.y - layerRange.x);\n pos.z = 0.0;\n \n if (selected) {\n pos.z += 0.0001;\n }\n\n if (borderCentered) {\n vec3 borderOffset = snappedBorderWidth() * vec3(worldPerPixel, 0.0);\n vSize += borderOffset.xy;\n }\n \n mat4 adjustedInstanceMatrix = instanceMatrix;\n adjustedInstanceMatrix[0].xyz *= vSize.x / length(instanceMatrix[0].xyz);\n adjustedInstanceMatrix[1].xyz *= vSize.y / length(instanceMatrix[1].xyz);\n adjustedInstanceMatrix[3].xyz = pos;\n \n vec4 localPos = adjustedInstanceMatrix * vec4(position, 1.0);\n gl_Position = projectionMatrix * modelViewMatrix * localPos;\n }\n `},u=()=>{const n=o.style,r=new e(n.fill?.color||"#df0"),t=n.fill?.opacity||.5,i=new e(n.border?.color||"#000"),a=n.border?n.border.opacity||1:0,l=n.border?n.border.width||1:0,d="number"==typeof n.border?.smoothing?Math.max(0,n.border.smoothing):1.5;return`\n // precision mediump float;\n // precision highp float;\n\n const float fillOpacity = ${s(t)};\n const float borderOpacity = ${s(a)};\n const float borderWidth = ${s(l)};\n uniform float pixelRatio;\n // Borders are a whole number of device pixels wide, so with smoothing 0 they're crisp wherever they land.\n float snappedBorderWidth() { return borderWidth > 0.0 ? max(1.0, floor(borderWidth * pixelRatio + 0.5)) / pixelRatio : 0.0; }\n const float borderSmoothing = ${s(d)};\n const bool borderSidesVertical = ${"vertical"===n.border?.sides?"true":"false"};\n\n ${c?"varying vec3 fillColor;":`const vec3 fillColor = vec3(${s(r.r)},${s(r.g)},${s(r.b)});`}\n ${c?"varying vec3 strokeColor;":`const vec3 strokeColor = vec3(${s(i.r)},${s(i.g)},${s(i.b)});`}\n \n uniform vec2 worldPerPixel;\n \n varying vec2 vUv;\n flat in int vSelected;\n flat in vec2 vSize;\n\n // Coverage of the inside of an edge at lo; smoothing 0 gives a hard step (smoothstep is undefined for equal edges).\n float edgeAlpha(float lo, float smoothing, float x) {\n return smoothing > 0.0 ? smoothstep(lo, lo + smoothing, x) : step(lo, x);\n }\n\n void main() {\n bool selected = vSelected > 0;\n vec3 finalFillColor = selected ? vec3(1.0 - fillColor) : fillColor;\n vec3 finalBorderColor = selected ? vec3(1.0 - strokeColor) : strokeColor;\n\n // Half border size in world units\n vec2 bwWorld = snappedBorderWidth() * worldPerPixel;\n\n // Half border UV width if centered, full if inside-only\n vec2 bw = bwWorld / vSize;\n\n float smoothingPixels = borderSmoothing;\n vec2 smoothing = smoothingPixels * worldPerPixel / vSize;\n\n float alphaX = edgeAlpha(bw.x, smoothing.x, vUv.x) *\n edgeAlpha(bw.x, smoothing.x, 1.0 - vUv.x);\n float alphaY = borderSidesVertical ? 1.0 : edgeAlpha(bw.y, smoothing.y, vUv.y) *\n edgeAlpha(bw.y, smoothing.y, 1.0 - vUv.y);\n\n float fillAlpha = alphaX * alphaY;\n float borderAlpha = 1.0 - fillAlpha;\n\n vec3 color = mix(finalBorderColor, finalFillColor, fillAlpha);\n float alpha = mix(borderOpacity, fillOpacity, fillAlpha);\n\n gl_FragColor = linearToOutputTexel(vec4(color, alpha));\n }\n `},p=new t({transparent:!0,uniforms:{worldPerPixel:{value:new i((n.camera.right-n.camera.left)/n.getSize().width,(n.camera.top-n.camera.bottom)/n.getSize().height)},pixelRatio:{value:a()},layerRange:{value:new i(o.min,o.max)},layerDirection:{value:1},selection:{value:-1},valueRange:{value:new i(0,1)},originTime:{value:new i(0,0)}},vertexShader:v(),fragmentShader:u()}),h=()=>{p.uniforms.pixelRatio.value=a(),p.uniforms.worldPerPixel.value.set((n.camera.right-n.camera.left)/n.getSize().width,(n.camera.top-n.camera.bottom)/n.getSize().height)},g=()=>{const e=n.getTimeRange(),o=e.startTime+.5*e.duration,[r,t]=d(o);p.uniforms.originTime.value.set(r,t)},x=()=>{const e=r.valueRanges.get(o.yAxis.group||o.id);if(e){const o=p.uniforms.valueRange.value;o.x===e.min&&o.y===e.max||p.uniforms.valueRange.value.set(e.min,e.max)}},y=()=>{h(),g(),x()};m.add(r.on("valueRanges",x)),m.add(n.on("time",y)),m.add(n.on("value",h)),m.add(n.on("size",h)),m.add(o.on("loaded",x)),m.add(o.on("visible",x));const b=()=>{const e="down"===o.direction;p.uniforms.layerRange.value.set(e?o.max:o.min,e?o.min:o.max),p.uniforms.layerDirection.value=e?-1:1};return m.add(o.on("min",b)),m.add(o.on("max",b)),m.add(o.on("direction",b)),b(),g(),x(),m.add(o.on("invalidate",()=>{p.vertexShader=v(),p.fragmentShader=u(),p.needsUpdate=!0})),{material:p,dispose:()=>m.dispose()}}(c,v,p);h.add(y);const b=new r(1,1),w=new o(b,x,g.data.length);w.frustumCulled=!1;const S=function(e,o,r,t,i){const a=new m(o,e=>{const o=new n,r=e.v[1]-e.v[0],t=e.v[0]+.5*r,i=.5*(e.t[0]+e.t[1]),[a,l]=d(i);o.position.set(a,t,l);const s=e.t[1]-e.t[0];return o.scale.set(s,r,.1),o.updateMatrix(),o.matrix},i);return a.registerMesh(e),a}(w,g,c.min,c.max,{colorProvider:c.styler?e=>{const o=c.styler(e);return o?{fill:o.fill?.color,stroke:o.border?.color}:void 0}:void 0,defaultFill:c.style.fill?.color,defaultBorder:c.style.border?.color});p.selectionOptions.highlight&&h.add(c.on("selection",e=>{if(e.length>0){const o=S.getIndex(e[0]);x.uniforms.selection.value="number"==typeof o?o:-1}else x.uniforms.selection.value=-1}));return u.add(w),h.add(()=>u.remove(w)),h.disposeLater()}(c,u,v,p):(console.error("Layer not supported by ThreePointsTimeLayerLoader",{layer:c}),()=>{})}}
@@ -9,6 +9,15 @@ export type TimeTicksCache = {
9
9
  segments: TimeTicksSegment[];
10
10
  spacing: number;
11
11
  };
12
+ /** The clickable tick box under the pointer: its range, the label it shows and its box in canvas pixels. */
13
+ export type TimeTicksHover = {
14
+ startTime: number;
15
+ endTime: number;
16
+ spacing: number;
17
+ label: string;
18
+ x: number;
19
+ width: number;
20
+ };
12
21
  export declare class TimeTicksBar {
13
22
  private canvas;
14
23
  private current;
@@ -21,6 +30,16 @@ export declare class TimeTicksBar {
21
30
  private weekLabel;
22
31
  private weekLabelPrefix?;
23
32
  private tickLabelStyler?;
33
+ private font;
34
+ private dpr;
35
+ private fontSize;
36
+ /** How much more text fits per pixel in this font than in the reference monospace one. */
37
+ private spaceScale;
38
+ private container?;
39
+ private background?;
40
+ private hoverTick?;
41
+ /** Called when the tick box under the pointer changes, with undefined when there is none. */
42
+ onHoverTick?: (hover: TimeTicksHover | undefined) => void;
24
43
  needsUpdate: boolean;
25
44
  private animationFrameId?;
26
45
  cache: {
@@ -38,6 +57,22 @@ export declare class TimeTicksBar {
38
57
  color?: string;
39
58
  opacity?: number;
40
59
  } | undefined) | undefined): void;
60
+ /** Sets the label font (CSS shorthand, e.g. '500 12px "IP Klartext", sans-serif'). Labels are measured in it, so they always fit their tick boxes. */
61
+ setFont(font: string, size: number): void;
62
+ /** Re-measures the font, e.g. once a web font has finished loading. */
63
+ refreshFont(): void;
64
+ private updateSpaceScale;
65
+ /** Background of the ticks bar; undefined leaves it transparent, showing the container. */
66
+ setBackground(background: string | undefined): void;
67
+ /** A tick's label with all the room it wants, e.g. "March 2024"; ticks under a day get their date and time range. Localized. */
68
+ getFullLabel(tick: {
69
+ startTime: number;
70
+ endTime: number;
71
+ spacing: number;
72
+ }): string;
73
+ /** A client x as a fraction of the bar's width (CSS pixels; the backing store is in device pixels). */
74
+ private toXRatio;
75
+ getCanvasRect(): DOMRect;
41
76
  init(container: HTMLDivElement, updateTime: (newRange: TimeSliderTimeRange) => void): void;
42
77
  dispose(): void;
43
78
  click(e: MouseEvent): void;
@@ -49,6 +84,12 @@ export declare class TimeTicksBar {
49
84
  static getSpacing(widthPx: number, durationMs: number): number[];
50
85
  draw(): void;
51
86
  private actualDraw;
87
+ /**
88
+ * Labels for a row of ticks, measured in the current font: the longest label variant that fits
89
+ * every tick's box, the same variant for the whole row (so months don't alternate between
90
+ * "Jan23" and "F23"). A label that still doesn't fit its box is left out ('').
91
+ */
92
+ private fitRowLabels;
52
93
  static splitInTimeUnits(timeSlider: TimeSlider, unit: TimeUnit): {
53
94
  start: number;
54
95
  end: number;
@@ -1 +1 @@
1
- import{getDefaultLocale as t,getISOWeekNumber as e,getISOWeekYear as i,getStartOfDay as a,getStartOfMonth as s,getStartOfWeek as n,getStartOfXYears as r,getTimeUnitInMs as o,getWeekLabelPrefix as l}from"../common/TimeTools.js";import{CENTURY as h,DECADE as c,MILLENIUM as $,ONE_DAY as g,ONE_HOUR as d,ONE_MINUTE as m,ONE_MONTH as u,ONE_SECOND as T,ONE_WEEK as p,ONE_YEAR as w}from"./TimeSliderConstants.js";export class TimeTicksBar{constructor(){this.canvas=document.createElement("canvas"),this.current={startTimeMs:Date.now(),endTimeMs:Date.now()+g,widthPx:100,heightPx:24},this.xHover=void 0,this.hoverRange=void 0,this.updateTime=()=>{},this.showLine=!0,this.darkMode=!0,this.locale=t(),this.weekLabel="default",this.needsUpdate=!0,this.cache={high:{segments:[],spacing:1},medium:{segments:[],spacing:1}}}setDarkMode(t){this.darkMode=t,this.draw()}setLocale(t){this.locale=t,this.draw()}setWeekLabel(t,e){this.weekLabel=t,this.weekLabelPrefix=e,this.draw()}setTickLabelStyler(t){this.tickLabelStyler=t,this.draw()}init(t,e){this.updateTime=e,this.canvas.style.position="absolute",this.canvas.style.top="0px",this.canvas.style.left="0px",this.canvas.style.width="100%",this.canvas.style.height="100%",this.canvas.style.touchAction="none",this.canvas.style.cursor="pointer",t.appendChild(this.canvas),this.canvas.addEventListener("pointermove",t=>{this.xHover=(t.clientX-this.canvas.getBoundingClientRect().left)/this.canvas.width,this.draw()}),this.canvas.addEventListener("click",t=>{this.click(t)},{capture:!0}),this.canvas.addEventListener("pointerleave",t=>{this.xHover=void 0,this.hoverRange=void 0,this.draw()});const i=()=>{this.needsUpdate&&(this.actualDraw(),this.needsUpdate=!1),this.animationFrameId=requestAnimationFrame(i)};this.animationFrameId=requestAnimationFrame(i)}dispose(){this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=void 0)}click(t){this.xHover=(t.clientX-this.canvas.getBoundingClientRect().left)/this.canvas.width,this.draw(),this.hoverRange&&this.updateTime(this.hoverRange),this.xHover=void 0}setTimeRange(t){this.current.startTimeMs=t.startTime,this.current.endTimeMs=t.endTime,this.draw()}setWidth(t){this.current.widthPx=t,this.draw()}static getSpacing(t,e){const i=e/t,a=[T,5*T,10*T,15*T,30*T,m,5*m,10*m,15*m,30*m,d,3*d,6*d,g,p,u,w,c,h,$,1e4*w];let s=a[0],n=a[0],r=a[0];for(const t of a){if(t/i>=(t<m?56:t<g?36:30)){s=t;break}r=n,n=t}return[s,n,r]}draw(){this.needsUpdate=!0}actualDraw(){const{startTimeMs:t,endTimeMs:e,widthPx:i,heightPx:a}=this.current;if(i<=0)return;const s=this.canvas;s.width=i,s.height=a;const n=s.getContext("2d");n.clearRect(0,0,i,a);const r=e-t,[o,l,h]=TimeTicksBar.getSpacing(i,r);this.cache.low=l<g?{segments:this.drawTicks(h,4,!1,t,e,r,i,n),spacing:h}:void 0,this.cache.medium={segments:this.drawTicks(l,6,!1,t,e,r,i,n),spacing:l},this.cache.high={segments:this.drawTicks(o,8,!0,t,e,r,i,n),spacing:o}}static splitInTimeUnits(t,e){const{startTime:i,endTime:a}=t.getTimeRange(),s=o(e),n=[];return TimeTicksBar.generateTicks(s,i,a,(t,e)=>{n.push({start:t,end:e})}),n}static generateTicks(t,e,i,o){let l=Math.floor(e/t)*t;t>d&&t<p?l=a(l):t===p?l=n(l):t===u?l=s(l):t>=w&&(l=r(l,t/w));let h=e=>e+t;t===u?h=e=>s(e+1.1*t):t>=w&&(h=e=>{const i=new Date(e),a=new Date(i.getFullYear()+t/w,0,1);return a.setFullYear(i.getFullYear()+t/w),a.setHours(0,0,0,0)});let c=l;for(;c<=i+t;){const t=h(c);o(c,t),c=t}}drawTicks(t,e,i,a,s,n,r,o){const l=[];return o.strokeStyle="#888",o.fillStyle=this.darkMode?"white":"black",o.font="12px monospace",o.textAlign="center",o.textBaseline="top",this.showLine&&(o.beginPath(),o.moveTo(0,0),o.lineWidth=1,o.lineTo(r,0),o.stroke()),TimeTicksBar.generateTicks(t,a,s,(h,c)=>{const $=(h-a)/n*r;l.push({start:h,end:c});const d=t>=g&&i?24:e,m=(c-h)/n*r,u=formatTimeLabel(h,t,m,this.locale,this.weekLabel,this.weekLabelPrefix);if(i){const e="number"==typeof this.xHover?a+this.xHover*(s-a):void 0,i="number"==typeof e&&h<e&&e<c;i&&(o.fillStyle=this.darkMode?"rgba(225,255,0,0.2)":"rgba(225,255,0,0.4)",o.fillRect($,0,m,24),o.fillStyle=t>=g?this.darkMode?"#df0":"black":this.darkMode?"white":"black",i&&(this.hoverRange={startTime:h,endTime:c}))}if(o.beginPath(),o.moveTo($,0),o.lineWidth=e/4,o.lineTo($,d),o.stroke(),i){const e=this.tickLabelStyler?.({startTime:h,endTime:c}),i=o.globalAlpha;if(e?.color&&(o.fillStyle=e.color),"number"==typeof e?.opacity&&(o.globalAlpha=e.opacity),t>=g){const t=.5*m;o.fillText(u,$+t,8)}else o.fillText(u,$,8);o.globalAlpha=i}o.fillStyle=this.darkMode?"white":"black"}),l}}export function formatTimeLabel(a,s,n,r,o,h){const c="number"==typeof a?new Date(a):a,$=r??t(),T=c.getFullYear().toString().slice(2),f=((c.getMonth()+1).toString().padStart(2,"0"),c.getDate()),k=c.getHours().toString().padStart(2,"0"),v=c.getMinutes().toString().padStart(2,"0"),S=c.getSeconds().toString().padStart(2,"0"),L=c.toLocaleString($,{month:"short"});if(s>w){const t=new Date(c.getTime()+s),e=c.getFullYear(),i=t.getFullYear();if(n<75){if(n<60){const t=(i-e).toString();return`${e.toString().padStart(4,"0").slice(0,4-t.length)}${"X".repeat(t.length)}`}return`${e}-${i}`}return`${e} - ${i}`}if(s===w)return`${c.getFullYear()}`;if(s===u){if(n<64){if(n<52){if(n<40){return`${c.toLocaleString($,{month:"narrow"})}${T}`}return`${L}${T}`}return`${L} ${T}`}return n<120?`${L} ${c.getFullYear()}`:`${c.toLocaleString($,{month:"long"})} ${c.getFullYear()}`}if(s===p){const t=new Date(c.getTime()+1e5);if("calendarWeek"===o){const a=e(t),s=h??l($);return n<28?`${a}`:n<60?`${s}${a}`:`${s}${a} ${i(t)}`}const a=t.toLocaleString($,{month:"short"}),s=t.getDate(),r=(t.getFullYear().toString().slice(2),new Date(c.getTime()+6*g)),d=r.toLocaleString($,{month:"short"}),m=r.getDate();r.getFullYear().toString().slice(2);return a===d?n<130?n<110?n<95?n<70?n<60?n<48?formatTimeLabel(t.getTime(),g,n,$):`${s}-${m}${d.slice(0,2)}`:`${s}-${m}${d}`:`${s} - ${m} ${d}`:`${s} - ${m} ${d} ${T}`:`${s} - ${m} ${d} ${r.getFullYear()}`:`${s} - ${m} ${r.toLocaleString($,{month:"long"})} ${r.getFullYear()}`:n<185?n<130?n<115?n<95?n<85?n<75?n<65?n<56?n<48?formatTimeLabel(t.getTime(),g,n,$):`${s}${a.slice(0,1)}${m}${d.slice(0,1)}`:`${s}${a.slice(0,1)}-${m}${d.slice(0,1)}`:`${s}${a.slice(0,2)}-${m}${d.slice(0,2)}`:`${s}${a}-${m}${d}`:`${s}${a} - ${m}${d}`:`${s} ${a} - ${m} ${d}`:`${s} ${a} - ${m} ${d} ${T}`:`${s} ${a} - ${m} ${d} ${r.getFullYear()}`:`${s} ${t.toLocaleString($,{month:"long"})} - ${m} ${r.toLocaleString($,{month:"long"})} ${r.getFullYear()}`}return s>=g||s>=d&&"12"===k?n<170?n<140?n<100?n<90?n<75?n<56&&s>=g?n<48?`${f}${L.slice(0,2)}`:`${f}${L}`:`${f} ${L}`:`${c.toLocaleString($,{weekday:"short"})} ${f} ${L}`:`${c.toLocaleString($,{weekday:"short"})} ${f} ${L} ${T}`:`${c.toLocaleString($,{weekday:"short"})} ${f} ${L} ${c.getFullYear()}`:`${c.toLocaleString($,{weekday:"long"})} ${f} ${L} ${c.getFullYear()}`:`${c.toLocaleString($,{weekday:"long"})} ${f} ${c.toLocaleString($,{month:"long"})} ${c.getFullYear()}`:s>=d?`${c.getHours()}h`:s>=m?"12"===k&&"00"===v?`${f} ${L}`:`${k}:${v}`:"12"===k&&"00"===v&&"00"===S?`${f} ${L}`:`${k}:${v}:${S}`}
1
+ import{getDevicePixelRatio as t,snapLineToDevicePixels as e}from"../common/Pixels.js";import{formatTimeSpanDates as i,getDefaultLocale as s,getISOWeekNumber as a,getISOWeekYear as n,getStartOfDay as o,getStartOfMonth as r,getStartOfWeek as h,getStartOfXYears as l,getTimeUnitInMs as c,getWeekLabelPrefix as d}from"../common/TimeTools.js";import{CENTURY as m,DECADE as g,MILLENIUM as $,ONE_DAY as u,ONE_HOUR as p,ONE_MINUTE as T,ONE_MONTH as f,ONE_SECOND as w,ONE_WEEK as k,ONE_YEAR as S}from"./TimeSliderConstants.js";const v="12px monospace",b="0123456789 JanFebMarAprMayJunJulAugSepOctNovDec";export class TimeTicksBar{constructor(){this.canvas=document.createElement("canvas"),this.current={startTimeMs:Date.now(),endTimeMs:Date.now()+u,widthPx:100,heightPx:24},this.xHover=void 0,this.hoverRange=void 0,this.updateTime=()=>{},this.showLine=!0,this.darkMode=!0,this.locale=s(),this.weekLabel="default",this.font=v,this.dpr=1,this.fontSize=12,this.spaceScale=1,this.needsUpdate=!0,this.cache={high:{segments:[],spacing:1},medium:{segments:[],spacing:1}}}setDarkMode(t){this.darkMode=t,this.draw()}setLocale(t){this.locale=t,this.draw()}setWeekLabel(t,e){this.weekLabel=t,this.weekLabelPrefix=e,this.draw()}setTickLabelStyler(t){this.tickLabelStyler=t,this.draw()}setFont(t,e){this.font=t,this.fontSize=e,this.updateSpaceScale(),this.draw()}refreshFont(){this.updateSpaceScale(),this.draw()}updateSpaceScale(){const t=this.canvas.getContext("2d");if(!t)return;t.font=v;const e=t.measureText(b).width;t.font=this.font;const i=t.measureText(b).width;this.spaceScale=i>0?Math.min(3,Math.max(.25,e/i)):1}setBackground(t){this.background=t,this.container&&(this.container.style.background=t??"")}getFullLabel(t){return t.spacing>=u?formatTimeLabel(t.startTime,t.spacing,1e5,this.locale,this.weekLabel,this.weekLabelPrefix):i(t.startTime,t.endTime,this.locale)}toXRatio(t){const e=this.canvas.getBoundingClientRect();return e.width>0?(t-e.left)/e.width:0}getCanvasRect(){return this.canvas.getBoundingClientRect()}init(t,e){this.updateTime=e,this.container=t,this.setBackground(this.background),this.canvas.style.position="absolute",this.canvas.style.top="0px",this.canvas.style.left="0px",this.canvas.style.width="100%",this.canvas.style.height="100%",this.canvas.style.touchAction="none",this.canvas.style.cursor="pointer",t.appendChild(this.canvas),this.canvas.addEventListener("pointermove",t=>{this.xHover=this.toXRatio(t.clientX),this.draw()}),this.canvas.addEventListener("click",t=>{this.click(t)},{capture:!0}),this.canvas.addEventListener("pointerleave",t=>{this.xHover=void 0,this.hoverRange=void 0,this.draw()});const i=()=>{this.needsUpdate&&(this.actualDraw(),this.needsUpdate=!1),this.animationFrameId=requestAnimationFrame(i)};this.animationFrameId=requestAnimationFrame(i)}dispose(){this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=void 0)}click(t){this.xHover=this.toXRatio(t.clientX),this.draw(),this.hoverRange&&this.updateTime(this.hoverRange),this.xHover=void 0}setTimeRange(t){this.current.startTimeMs=t.startTime,this.current.endTimeMs=t.endTime,this.draw()}setWidth(t){this.current.widthPx=t,this.draw()}static getSpacing(t,e){const i=e/t,s=[w,5*w,10*w,15*w,30*w,T,5*T,10*T,15*T,30*T,p,3*p,6*p,u,k,f,S,g,m,$,1e4*S];let a=s[0],n=s[0],o=s[0];for(const t of s){if(t/i>=(t<T?56:t<u?36:30)){a=t;break}o=n,n=t}return[a,n,o]}draw(){this.needsUpdate=!0}actualDraw(){const{startTimeMs:e,endTimeMs:i,widthPx:s,heightPx:a}=this.current;if(s<=0)return;const n=this.canvas;this.dpr=t(),n.width=Math.round(s*this.dpr),n.height=Math.round(a*this.dpr);const o=n.getContext("2d");o.setTransform(this.dpr,0,0,this.dpr,0,0),o.clearRect(0,0,s,a);const r=this.hoverTick;this.hoverTick=void 0;const h=i-e,[l,c,d]=TimeTicksBar.getSpacing(s,h);this.cache.low=c<u?{segments:this.drawTicks(d,4,!1,e,i,h,s,o),spacing:d}:void 0,this.cache.medium={segments:this.drawTicks(c,6,!1,e,i,h,s,o),spacing:c},this.cache.high={segments:this.drawTicks(l,8,!0,e,i,h,s,o),spacing:l};const m=this.hoverTick;m?.startTime===r?.startTime&&m?.endTime===r?.endTime&&m?.x===r?.x||this.onHoverTick?.(m)}fitRowLabels(t,e,i){if(0===e.length)return[];const s=(t,e)=>formatTimeLabel(t,i,e,this.locale,this.weekLabel,this.weekLabelPrefix);let a=Math.min(...e.map(t=>t.box))*this.spaceScale,n=[],o=[];for(let i=0;i<12;i++){n=e.map(t=>s(t.time,a)),o=n.map(e=>t.measureText(e).width);const i=Math.min(...e.map((t,e)=>o[e]>0?(t.box-2)/o[e]:1/0));if(i>=1||a<=1)break;a=Math.min(a-1,a*i)}return n.map((t,i)=>o[i]>e[i].box?"":t)}static splitInTimeUnits(t,e){const{startTime:i,endTime:s}=t.getTimeRange(),a=c(e),n=[];return TimeTicksBar.generateTicks(a,i,s,(t,e)=>{n.push({start:t,end:e})}),n}static generateTicks(t,e,i,s){let a=Math.floor(e/t)*t;t>p&&t<k?a=o(a):t===k?a=h(a):t===f?a=r(a):t>=S&&(a=l(a,t/S));let n=e=>e+t;t===f?n=e=>r(e+1.1*t):t>=S&&(n=e=>{const i=new Date(e),s=new Date(i.getFullYear()+t/S,0,1);return s.setFullYear(i.getFullYear()+t/S),s.setHours(0,0,0,0)});let c=a;for(;c<=i+t;){const t=n(c);s(c,t),c=t}}drawTicks(t,i,s,a,n,o,r,h){const l=[];if(h.strokeStyle="#888",h.fillStyle=this.darkMode?"white":"black",h.font=this.font,h.textAlign="center",h.textBaseline="top",this.showLine){const t=e(.5,1,this.dpr);h.beginPath(),h.moveTo(0,t.position),h.lineWidth=t.width,h.lineTo(r,t.position),h.stroke()}const c=new Map;if(s){const e=[];TimeTicksBar.generateTicks(t,a,n,(t,i)=>{e.push({time:t,box:(i-t)/o*r})}),this.fitRowLabels(h,e,t).forEach((t,i)=>c.set(e[i].time,t))}return TimeTicksBar.generateTicks(t,a,n,(d,m)=>{const g=(d-a)/o*r;l.push({start:d,end:m});const $=t>=u&&s?24:i,p=(m-d)/o*r,T=c.get(d)??"";if(s){const e="number"==typeof this.xHover?a+this.xHover*(n-a):void 0,i="number"==typeof e&&d<e&&e<m;i&&(h.fillStyle=this.darkMode?"rgba(225,255,0,0.2)":"rgba(225,255,0,0.4)",h.fillRect(g,0,p,24),h.fillStyle=t>=u?this.darkMode?"#df0":"black":this.darkMode?"white":"black",i&&(this.hoverRange={startTime:d,endTime:m}),this.hoverTick={startTime:d,endTime:m,spacing:t,label:T,x:g,width:p})}const f=e(g,i/4,this.dpr);if(h.beginPath(),h.moveTo(f.position,0),h.lineWidth=f.width,h.lineTo(f.position,$),h.stroke(),s){const e=this.tickLabelStyler?.({startTime:d,endTime:m}),i=h.globalAlpha;e?.color&&(h.fillStyle=e.color),"number"==typeof e?.opacity&&(h.globalAlpha=e.opacity);const s=Math.max(1,.5*(24-this.fontSize)+2);if(t>=u){const t=.5*p;h.fillText(T,g+t,s)}else h.fillText(T,g,s);h.globalAlpha=i}h.fillStyle=this.darkMode?"white":"black"}),l}}export function formatTimeLabel(t,e,i,o,r,h){const l="number"==typeof t?new Date(t):t,c=o??s(),m=l.getFullYear().toString().slice(2),g=((l.getMonth()+1).toString().padStart(2,"0"),l.getDate()),$=l.getHours().toString().padStart(2,"0"),w=l.getMinutes().toString().padStart(2,"0"),v=l.getSeconds().toString().padStart(2,"0"),b=l.toLocaleString(c,{month:"short"});if(e>S){const t=new Date(l.getTime()+e),s=l.getFullYear(),a=t.getFullYear();if(i<75){if(i<60){const t=(a-s).toString();return`${s.toString().padStart(4,"0").slice(0,4-t.length)}${"X".repeat(t.length)}`}return`${s}-${a}`}return`${s} - ${a}`}if(e===S)return`${l.getFullYear()}`;if(e===f){if(i<64){if(i<52){if(i<40){return`${l.toLocaleString(c,{month:"narrow"})}${m}`}return`${b}${m}`}return`${b} ${m}`}return i<120?`${b} ${l.getFullYear()}`:`${l.toLocaleString(c,{month:"long"})} ${l.getFullYear()}`}if(e===k){const t=new Date(l.getTime()+1e5);if("calendarWeek"===r){const e=a(t),s=h??d(c);return i<28?`${e}`:i<60?`${s}${e}`:`${s}${e} ${n(t)}`}const e=t.toLocaleString(c,{month:"short"}),s=t.getDate(),o=(t.getFullYear().toString().slice(2),new Date(l.getTime()+6*u)),g=o.toLocaleString(c,{month:"short"}),$=o.getDate();o.getFullYear().toString().slice(2);return e===g?i<130?i<110?i<95?i<70?i<60?i<48?formatTimeLabel(t.getTime(),u,i,c):`${s}-${$}${g.slice(0,2)}`:`${s}-${$}${g}`:`${s} - ${$} ${g}`:`${s} - ${$} ${g} ${m}`:`${s} - ${$} ${g} ${o.getFullYear()}`:`${s} - ${$} ${o.toLocaleString(c,{month:"long"})} ${o.getFullYear()}`:i<185?i<130?i<115?i<95?i<85?i<75?i<65?i<56?i<48?formatTimeLabel(t.getTime(),u,i,c):`${s}${e.slice(0,1)}${$}${g.slice(0,1)}`:`${s}${e.slice(0,1)}-${$}${g.slice(0,1)}`:`${s}${e.slice(0,2)}-${$}${g.slice(0,2)}`:`${s}${e}-${$}${g}`:`${s}${e} - ${$}${g}`:`${s} ${e} - ${$} ${g}`:`${s} ${e} - ${$} ${g} ${m}`:`${s} ${e} - ${$} ${g} ${o.getFullYear()}`:`${s} ${t.toLocaleString(c,{month:"long"})} - ${$} ${o.toLocaleString(c,{month:"long"})} ${o.getFullYear()}`}return e>=u||e>=p&&"12"===$?i<170?i<140?i<100?i<90?i<75?i<56&&e>=u?i<48?`${g}${b.slice(0,2)}`:`${g}${b}`:`${g} ${b}`:`${l.toLocaleString(c,{weekday:"short"})} ${g} ${b}`:`${l.toLocaleString(c,{weekday:"short"})} ${g} ${b} ${m}`:`${l.toLocaleString(c,{weekday:"short"})} ${g} ${b} ${l.getFullYear()}`:`${l.toLocaleString(c,{weekday:"long"})} ${g} ${b} ${l.getFullYear()}`:`${l.toLocaleString(c,{weekday:"long"})} ${g} ${l.toLocaleString(c,{month:"long"})} ${l.getFullYear()}`:e>=p?`${l.getHours()}h`:e>=T?"12"===$&&"00"===w?`${g} ${b}`:`${$}:${w}`:"12"===$&&"00"===w&&"00"===v?`${g} ${b}`:`${$}:${w}:${v}`}
@@ -1,3 +1,4 @@
1
+ import { TimeSliderFont } from "../common/Font";
1
2
  export type YAxisSettings = {
2
3
  visible?: boolean;
3
4
  group?: string;
@@ -6,6 +7,8 @@ export type YAxisSettings = {
6
7
  labels?: {
7
8
  position?: 'left' | 'right' | 'center' | 'none';
8
9
  color?: string;
10
+ /** Overrides the slider's font (TimeSliderOptions.font) for this axis' labels. */
11
+ font?: TimeSliderFont;
9
12
  stroke?: {
10
13
  color?: string;
11
14
  width?: number;
@@ -19,12 +22,17 @@ export type YAxisSettings = {
19
22
  };
20
23
  export declare class YAxisTicks {
21
24
  canvas: HTMLCanvasElement;
25
+ private font;
26
+ /** The labels' font for axes that don't set their own; undefined for the default 12px sans-serif. */
27
+ setFont(font: TimeSliderFont | undefined): void;
22
28
  constructor();
23
29
  render(currents: (YAxisSettings & {
24
30
  min: number;
25
31
  max: number;
26
32
  minValue: number;
27
33
  maxValue: number;
34
+ /** 'down': the axis' min value at the band's top, values increasing downwards (layers hanging down). */
35
+ direction?: 'up' | 'down';
28
36
  })[]): void;
29
37
  }
30
38
  export declare function getNiceTicks(min: number, max: number, targetCount?: number): number[];
@@ -1 +1 @@
1
- export class YAxisTicks{constructor(){this.canvas=document.createElement("canvas"),this.canvas.style.top="0px",this.canvas.style.width="100%",this.canvas.style.outline="none",this.canvas.style.touchAction="none",this.canvas.style.pointerEvents="none",this.canvas.style.position="absolute",this.canvas.style.height="100%"}render(t){const e=this.canvas,i=e.parentElement?.getBoundingClientRect();if(!i)return;e.width===i.width&&e.height===i.height||(e.width=i.width,e.height=i.height);const s=e.getContext("2d");if(s){s.clearRect(0,0,e.width,e.height);for(const i of t){if(!1===i.visible)continue;const{min:t,max:n,minValue:o,maxValue:l}=i,h=e.height*(1-n),a=e.height*(1-t),c=a-h,r=getNiceTicks(o,l),d=i.format??(t=>t.toString()),f=i.style?.labels?.position??"right",g=i.style?.labels?.color??"#777",u=void 0!==i.style?.labels?.stroke,m=i.style?.labels?.stroke?.color??"#000",x=i.style?.labels?.stroke?.width??1,w=i.style?.lines?.color??"#7773",y=parseFloat(i.style?.lines?.width??"1");s.save(),s.lineWidth=y,s.fillStyle=g,s.font="12px sans-serif",s.textBaseline="middle",s.textAlign="left"===f?"right":"right"===f?"left":"center";for(const t of r){const i=(t-o)/(l-o);if(i<0||i>1)continue;const n=a-i*c;if(!(n<h+4||n>a-4)&&(s.beginPath(),s.moveTo(0,n),s.lineTo(e.width,n),s.strokeStyle=w,s.stroke(),"none"!==f)){const i=d(t),o=s.measureText(i).width;let l,h="center";"left"===f?(h="right",l=Math.max(4+o,4+o),l=Math.min(l,e.width/2-4)):"right"===f?(h="left",l=Math.min(e.width-4-o,e.width-4-o),l=Math.max(l,e.width/2+4)):(h="center",l=e.width/2),u&&(s.lineWidth=2*x,s.strokeStyle=m,s.strokeText(i,l,n)),s.textAlign=h,s.fillText(i,l,n)}}s.restore()}}}}export function getNiceTicks(t,e,i=6){const s=niceNum(e-t,!1),n=niceNum(s/(i-1),!0),o=Math.floor(t/n)*n,l=Math.ceil(e/n)*n,h=[];for(let i=o;i<=l+n/2;i+=n)i>=t&&i<=e&&h.push(parseFloat(i.toFixed(10)));return h}export function niceNum(t,e){const i=Math.floor(Math.log10(t)),s=t/Math.pow(10,i);let n=0;return n=e?s<1.5?1:s<3?2:s<7?5:10:s<=1?1:s<=2?2:s<=5?5:10,n*Math.pow(10,i)}
1
+ import{resolveFont as t,toCssFont as e}from"../common/Font.js";import{getDevicePixelRatio as i,snapLineToDevicePixels as n}from"../common/Pixels.js";const o={family:"sans-serif",weight:"normal",size:12};export class YAxisTicks{setFont(e){this.font=e?t(e,o):o}constructor(){this.canvas=document.createElement("canvas"),this.font=o,this.canvas.style.top="0px",this.canvas.style.width="100%",this.canvas.style.outline="none",this.canvas.style.touchAction="none",this.canvas.style.pointerEvents="none",this.canvas.style.position="absolute",this.canvas.style.height="100%"}render(o){const s=this.canvas;if(!s.parentElement)return;const l=s.getBoundingClientRect();if(l.width<=0||l.height<=0)return;const r=i(),a=Math.round(l.width*r),h=Math.round(l.height*r);s.width===a&&s.height===h||(s.width=a,s.height=h);const c=l.width,f=l.height,m=s.getContext("2d");if(m){m.setTransform(1,0,0,1,0,0),m.clearRect(0,0,s.width,s.height),m.setTransform(r,0,0,r,0,0);for(const i of o){if(!1===i.visible)continue;const{min:o,max:s,minValue:l,maxValue:a}=i,h=f*(1-s),d=f*(1-o),u=d-h,g=getNiceTicks(l,a),y=i.format??(t=>t.toString()),p=i.style?.labels?.position??"right",x=i.style?.labels?.color??"#777",v=void 0!==i.style?.labels?.stroke,w=i.style?.labels?.stroke?.color??"#000",M=i.style?.labels?.stroke?.width??1,k=i.style?.lines?.color??"#7773",T=parseFloat(i.style?.lines?.width??"1");m.save(),m.fillStyle=x,m.font=e(t(i.style?.labels?.font,this.font)),m.textBaseline="middle",m.lineJoin="round",m.miterLimit=2,m.textAlign="left"===p?"right":"right"===p?"left":"center";for(const t of g){const e=(t-l)/(a-l);if(e<0||e>1)continue;const o="down"===i.direction?h+e*u:d-e*u;if(o<h+4||o>d-4)continue;const s=n(o,T,r);if(m.beginPath(),m.moveTo(0,s.position),m.lineTo(c,s.position),m.lineWidth=s.width,m.strokeStyle=k,m.stroke(),"none"!==p){const e=y(t),i=m.measureText(e).width;let n,s="center";"left"===p?(s="right",n=Math.max(4+i,4+i),n=Math.min(n,c/2-4)):"right"===p?(s="left",n=Math.min(c-4-i,c-4-i),n=Math.max(n,c/2+4)):(s="center",n=c/2),m.textAlign=s,v&&(m.lineWidth=2*M,m.strokeStyle=w,m.strokeText(e,n,o)),m.fillText(e,n,o)}}m.restore()}}}}export function getNiceTicks(t,e,i=6){const n=niceNum(e-t,!1),o=niceNum(n/(i-1),!0),s=Math.floor(t/o)*o,l=Math.ceil(e/o)*o,r=[];for(let i=s;i<=l+o/2;i+=o)i>=t&&i<=e&&r.push(parseFloat(i.toFixed(10)));return r}export function niceNum(t,e){const i=Math.floor(Math.log10(t)),n=t/Math.pow(10,i);let o=0;return o=e?n<1.5?1:n<3?2:n<7?5:10:n<=1?1:n<=2?2:n<=5?5:10,o*Math.pow(10,i)}