@protomaze/time 1.6.0 → 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,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}}
@@ -245,6 +245,8 @@ export declare class TimeSlider extends TypedEventEmitter<{
245
245
  /** Re-measures and redraws the labels, e.g. after a web font finished loading. Done automatically on document.fonts 'loadingdone'. */
246
246
  refreshFonts(): void;
247
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;
248
250
  get background(): string | undefined;
249
251
  /** Sets the background colour behind the layers and the ticks bar (see TimeSliderOptions.background); undefined goes back to the theme's. */
250
252
  setBackground(background: string | undefined): void;
@@ -343,6 +345,9 @@ export declare class TimeSlider extends TypedEventEmitter<{
343
345
  v: number;
344
346
  };
345
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;
346
351
  private pixelsToModelValue;
347
352
  private layerValueToPixels;
348
353
  private modelValueToPixels;
@@ -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{describeTimeSpan as s,formatDate as n,formatDateRange as l,formatDuration as o,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 _,ONE_YEAR as H,TODAY as I}from"../ticks/TimeSliderConstants.js";import{TimeTicksBar as O}from"../ticks/TimeTicksBar.js";import{YAxisTicks as A}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 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.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.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?.())}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 l=this.tickToolTip.offsetWidth,o=this.tickToolTip.offsetHeight,a=Math.min(Math.max(4,i.left+.5*i.width-.5*l),window.innerWidth-l-4),r=i.top-o-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 l=n(e.startTime),o=n(e.endTime);t.style.left=l+"px",t.style.width=Math.max(0,o-l)+"px",i&&(i.style.left=l-4+"px"),s&&(s.style.left=o-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)),l=this.selectionOptions.columnBand?.color??"#fff",o=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=l,r.style.opacity=String(o),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 o=t=>{l||(l=!0,e.hasPointerCapture(t.pointerId)&&e.releasePointerCapture(t.pointerId),e.removeEventListener("pointermove",n),e.removeEventListener("pointerup",o),e.removeEventListener("pointercancel",o),e.removeEventListener("lostpointercapture",o),this.emit("selectionchange",{ranges:this.getSelection(),phase:"end"}))};this.emit("selectionchange",{ranges:this.getSelection(),phase:"start"}),e.addEventListener("pointermove",n),e.addEventListener("pointerup",o),e.addEventListener("pointercancel",o),e.addEventListener("lostpointercapture",o)})};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),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.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,l=this.selectedLabel.offsetHeight;this.selectedLayerId=s.id;const o=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);o?m-=.5*l:m+=8;const c=.5*n,u=.5*l,g=a.width-.5*n,p=a.height-.5*l,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:l,duration:o}=this.getTimeRange(),a=l+n.x*o,{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;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 p){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 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:l,endTime:o,duration:a}=this.getTimeRange(),r=l+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,l=e instanceof w?m:c,o={minValue:t-.5*s,maxValue:t+.5*s},a=e.model.dataInRange({...l,...o});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:l}=this.getTimeRange(),o=n+s.x*l,a=(t||12)/i*l,r={startTime:o-1,endTime:o+1},h={startTime:o-.5*a,endTime:o+.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:o,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:l}=this.getTimeRange(),o=i+t.x*l,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:o,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(o),i=t.getHours(),s=t.getMinutes(),l=t.getSeconds(),a=n(o,!0,this._locale),r=`${(i<10?"0":"")+i}:${(s<10?"0":"")+s}:${(l<10?"0":"")+l}`;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.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*C){const e=X(10*s);n=h(l,e)}else{const e=[{threshold:S,unit:"millennium"},{threshold:k,unit:"century"},{threshold:H,unit:"decade"},{threshold:.9*D,unit:"year"},{threshold:_,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(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*C){const e=X(s/10);n=h(l,e)}else{let e=0;const t=[{threshold:C,unit:"century"},{threshold:S,unit:"decade"},{threshold:k,unit:"year"},{threshold:H,unit:"month"},{threshold:1.1*D,unit:"week"},{threshold:_,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(l,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 l=d(t,i);return l?m(n,l,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),l=Math.abs(n.startTime-t.startTime)>W||Math.abs(n.endTime-t.endTime)>W,o=l?n:i,a=e>0?s(t.startTime,o.startTime,this._locale):s(o.startTime,t.startTime,this._locale);return{range:n,canStep:l,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()?l([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 I;{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 ${o(Date.now()-i)}`);const l=this.syncMaxValuesForLayer(e);this.layerLoaders.set(e.id,t),this.layerUnloaders.set(e.id,()=>{l(),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))];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),o=Math.max(i,n),a=(t-e)/(o-l);let r=e-l*a,h=t+(1-o)*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()},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 o=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:o,duration:a}=this.getTimeRange();s=o+i*a,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},u=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:o,endTime:a}=this.getTimeRange(),r=o+i*(a-o)-s;this.setTimeRange({startTime:o-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(),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 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",o),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",o),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(),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(),o={startTime:s-e*l*i,endTime:n+e*l*(1-i)},a=o.endTime-o.startTime,r=o.startTime+.5*a;a>P&&(o.startTime=r-.5*P,o.endTime=r+.5*P),a<U&&(o.startTime=r-.5*U,o.endTime=r+.5*U),this.fit({range:o,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)}
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)}
package/index.d.ts CHANGED
@@ -55,6 +55,8 @@ export * from './ticks/TimeTicksBar';
55
55
  export * from './ticks/YAxisTicks';
56
56
  export * from './common/TimeTools';
57
57
  export * from './common/Font';
58
+ export * from './common/Pixels';
59
+ export { snapLinesToPixels } from './three/PixelSnap';
58
60
  export * from './common/Easing';
59
61
  export * from './common/Clamper';
60
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/Font.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.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Protomaze Time sliders",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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 g}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(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 h=new s,p=new s;h.scale.x=f,h.position.z=.9,this.scene.add(h);const u=new d(100,1),y=new n({color:"#444"}),C=new a(u,y);C.position.z=.5,C.position.y=-.001,C.rotateX(-.5*Math.PI),h.add(C);const T=new a(u,y);T.position.z=.5,T.position.y=-.001,T.rotateX(-.5*Math.PI),p.add(T);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"}),W=(new e).setFromPoints([new m(0,0,1),new m(0,0,-1e3)]),k=new s;p.add(k);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]=g.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;g.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(W,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),T.scale.x=s,T.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(this.getClearColor()),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),y.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 g}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(this.getClearColor()),a.appendChild(this.renderer.domElement),this.disposables.add(()=>a.removeChild(m.domElement)),this.camera.position.z=10;const l=new t,u=new t;l.scale.x=T,this.scene.add(l);const w=new o({color:n?"#444":"#bbb"}),C=(new e).setFromPoints([new r(0,0,0),new r(0,1,0),new r(0,1,1)]);this.scene.add(u);const f=[];let x;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){u.visible=!1,l.scale.x=n;let e=r%n;n>=g&&(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&&(f[e]||(f[e]=new i(C,w),u.add(f[e])),f[e].position.x=a,f[e].visible=!0,e++)});for(let s=e;s<f.length;s++)f[s].visible=!1;u.position.x=-r,u.visible=!0}};this.onThemeChange=e=>{const t="dark"===e?"#444":"#bbb";this.renderer?.setClearColor(this.getClearColor()),x&&l.remove(x),x=new s(1e3,1e3,t,t),x.position.y=-499,x.rotateX(-.5*Math.PI),l.add(x),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}),()=>{})}}
@@ -31,6 +31,7 @@ export declare class TimeTicksBar {
31
31
  private weekLabelPrefix?;
32
32
  private tickLabelStyler?;
33
33
  private font;
34
+ private dpr;
34
35
  private fontSize;
35
36
  /** How much more text fits per pixel in this font than in the reference monospace one. */
36
37
  private spaceScale;
@@ -69,6 +70,8 @@ export declare class TimeTicksBar {
69
70
  endTime: number;
70
71
  spacing: number;
71
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;
72
75
  getCanvasRect(): DOMRect;
73
76
  init(container: HTMLDivElement, updateTime: (newRange: TimeSliderTimeRange) => void): void;
74
77
  dispose(): void;
@@ -1 +1 @@
1
- import{formatTimeSpanDates as t,getDefaultLocale as e,getISOWeekNumber as i,getISOWeekYear as a,getStartOfDay as s,getStartOfMonth as n,getStartOfWeek as r,getStartOfXYears as o,getTimeUnitInMs as l,getWeekLabelPrefix as h}from"../common/TimeTools.js";import{CENTURY as c,DECADE as d,MILLENIUM as g,ONE_DAY as m,ONE_HOUR as $,ONE_MINUTE as u,ONE_MONTH as T,ONE_SECOND as p,ONE_WEEK as f,ONE_YEAR as k}from"./TimeSliderConstants.js";const w="12px monospace",v="0123456789 JanFebMarAprMayJunJulAugSepOctNovDec";export class TimeTicksBar{constructor(){this.canvas=document.createElement("canvas"),this.current={startTimeMs:Date.now(),endTimeMs:Date.now()+m,widthPx:100,heightPx:24},this.xHover=void 0,this.hoverRange=void 0,this.updateTime=()=>{},this.showLine=!0,this.darkMode=!0,this.locale=e(),this.weekLabel="default",this.font=w,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=w;const e=t.measureText(v).width;t.font=this.font;const i=t.measureText(v).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(e){return e.spacing>=m?formatTimeLabel(e.startTime,e.spacing,1e5,this.locale,this.weekLabel,this.weekLabelPrefix):t(e.startTime,e.endTime,this.locale)}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=(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=[p,5*p,10*p,15*p,30*p,u,5*u,10*u,15*u,30*u,$,3*$,6*$,m,f,T,k,d,c,g,1e4*k];let s=a[0],n=a[0],r=a[0];for(const t of a){if(t/i>=(t<u?56:t<m?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=this.hoverTick;this.hoverTick=void 0;const o=e-t,[l,h,c]=TimeTicksBar.getSpacing(i,o);this.cache.low=h<m?{segments:this.drawTicks(c,4,!1,t,e,o,i,n),spacing:c}:void 0,this.cache.medium={segments:this.drawTicks(h,6,!1,t,e,o,i,n),spacing:h},this.cache.high={segments:this.drawTicks(l,8,!0,t,e,o,i,n),spacing:l};const d=this.hoverTick;d?.startTime===r?.startTime&&d?.endTime===r?.endTime&&d?.x===r?.x||this.onHoverTick?.(d)}fitRowLabels(t,e,i){if(0===e.length)return[];const a=(t,e)=>formatTimeLabel(t,i,e,this.locale,this.weekLabel,this.weekLabelPrefix);let s=Math.min(...e.map(t=>t.box))*this.spaceScale,n=[],r=[];for(let i=0;i<12;i++){n=e.map(t=>a(t.time,s)),r=n.map(e=>t.measureText(e).width);const i=Math.min(...e.map((t,e)=>r[e]>0?(t.box-2)/r[e]:1/0));if(i>=1||s<=1)break;s=Math.min(s-1,s*i)}return n.map((t,i)=>r[i]>e[i].box?"":t)}static splitInTimeUnits(t,e){const{startTime:i,endTime:a}=t.getTimeRange(),s=l(e),n=[];return TimeTicksBar.generateTicks(s,i,a,(t,e)=>{n.push({start:t,end:e})}),n}static generateTicks(t,e,i,a){let l=Math.floor(e/t)*t;t>$&&t<f?l=s(l):t===f?l=r(l):t===T?l=n(l):t>=k&&(l=o(l,t/k));let h=e=>e+t;t===T?h=e=>n(e+1.1*t):t>=k&&(h=e=>{const i=new Date(e),a=new Date(i.getFullYear()+t/k,0,1);return a.setFullYear(i.getFullYear()+t/k),a.setHours(0,0,0,0)});let c=l;for(;c<=i+t;){const t=h(c);a(c,t),c=t}}drawTicks(t,e,i,a,s,n,r,o){const l=[];o.strokeStyle="#888",o.fillStyle=this.darkMode?"white":"black",o.font=this.font,o.textAlign="center",o.textBaseline="top",this.showLine&&(o.beginPath(),o.moveTo(0,0),o.lineWidth=1,o.lineTo(r,0),o.stroke());const h=new Map;if(i){const e=[];TimeTicksBar.generateTicks(t,a,s,(t,i)=>{e.push({time:t,box:(i-t)/n*r})}),this.fitRowLabels(o,e,t).forEach((t,i)=>h.set(e[i].time,t))}return TimeTicksBar.generateTicks(t,a,s,(c,d)=>{const g=(c-a)/n*r;l.push({start:c,end:d});const $=t>=m&&i?24:e,u=(d-c)/n*r,T=h.get(c)??"";if(i){const e="number"==typeof this.xHover?a+this.xHover*(s-a):void 0,i="number"==typeof e&&c<e&&e<d;i&&(o.fillStyle=this.darkMode?"rgba(225,255,0,0.2)":"rgba(225,255,0,0.4)",o.fillRect(g,0,u,24),o.fillStyle=t>=m?this.darkMode?"#df0":"black":this.darkMode?"white":"black",i&&(this.hoverRange={startTime:c,endTime:d}),this.hoverTick={startTime:c,endTime:d,spacing:t,label:T,x:g,width:u})}if(o.beginPath(),o.moveTo(g,0),o.lineWidth=e/4,o.lineTo(g,$),o.stroke(),i){const e=this.tickLabelStyler?.({startTime:c,endTime:d}),i=o.globalAlpha;e?.color&&(o.fillStyle=e.color),"number"==typeof e?.opacity&&(o.globalAlpha=e.opacity);const a=Math.max(1,.5*(24-this.fontSize)+2);if(t>=m){const t=.5*u;o.fillText(T,g+t,a)}else o.fillText(T,g,a);o.globalAlpha=i}o.fillStyle=this.darkMode?"white":"black"}),l}}export function formatTimeLabel(t,s,n,r,o,l){const c="number"==typeof t?new Date(t):t,d=r??e(),g=c.getFullYear().toString().slice(2),p=((c.getMonth()+1).toString().padStart(2,"0"),c.getDate()),w=c.getHours().toString().padStart(2,"0"),v=c.getMinutes().toString().padStart(2,"0"),S=c.getSeconds().toString().padStart(2,"0"),b=c.toLocaleString(d,{month:"short"});if(s>k){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===k)return`${c.getFullYear()}`;if(s===T){if(n<64){if(n<52){if(n<40){return`${c.toLocaleString(d,{month:"narrow"})}${g}`}return`${b}${g}`}return`${b} ${g}`}return n<120?`${b} ${c.getFullYear()}`:`${c.toLocaleString(d,{month:"long"})} ${c.getFullYear()}`}if(s===f){const t=new Date(c.getTime()+1e5);if("calendarWeek"===o){const e=i(t),s=l??h(d);return n<28?`${e}`:n<60?`${s}${e}`:`${s}${e} ${a(t)}`}const e=t.toLocaleString(d,{month:"short"}),s=t.getDate(),r=(t.getFullYear().toString().slice(2),new Date(c.getTime()+6*m)),$=r.toLocaleString(d,{month:"short"}),u=r.getDate();r.getFullYear().toString().slice(2);return e===$?n<130?n<110?n<95?n<70?n<60?n<48?formatTimeLabel(t.getTime(),m,n,d):`${s}-${u}${$.slice(0,2)}`:`${s}-${u}${$}`:`${s} - ${u} ${$}`:`${s} - ${u} ${$} ${g}`:`${s} - ${u} ${$} ${r.getFullYear()}`:`${s} - ${u} ${r.toLocaleString(d,{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(),m,n,d):`${s}${e.slice(0,1)}${u}${$.slice(0,1)}`:`${s}${e.slice(0,1)}-${u}${$.slice(0,1)}`:`${s}${e.slice(0,2)}-${u}${$.slice(0,2)}`:`${s}${e}-${u}${$}`:`${s}${e} - ${u}${$}`:`${s} ${e} - ${u} ${$}`:`${s} ${e} - ${u} ${$} ${g}`:`${s} ${e} - ${u} ${$} ${r.getFullYear()}`:`${s} ${t.toLocaleString(d,{month:"long"})} - ${u} ${r.toLocaleString(d,{month:"long"})} ${r.getFullYear()}`}return s>=m||s>=$&&"12"===w?n<170?n<140?n<100?n<90?n<75?n<56&&s>=m?n<48?`${p}${b.slice(0,2)}`:`${p}${b}`:`${p} ${b}`:`${c.toLocaleString(d,{weekday:"short"})} ${p} ${b}`:`${c.toLocaleString(d,{weekday:"short"})} ${p} ${b} ${g}`:`${c.toLocaleString(d,{weekday:"short"})} ${p} ${b} ${c.getFullYear()}`:`${c.toLocaleString(d,{weekday:"long"})} ${p} ${b} ${c.getFullYear()}`:`${c.toLocaleString(d,{weekday:"long"})} ${p} ${c.toLocaleString(d,{month:"long"})} ${c.getFullYear()}`:s>=$?`${c.getHours()}h`:s>=u?"12"===w&&"00"===v?`${p} ${b}`:`${w}:${v}`:"12"===w&&"00"===v&&"00"===S?`${p} ${b}`:`${w}:${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}`}
@@ -31,6 +31,8 @@ export declare class YAxisTicks {
31
31
  max: number;
32
32
  minValue: number;
33
33
  maxValue: number;
34
+ /** 'down': the axis' min value at the band's top, values increasing downwards (layers hanging down). */
35
+ direction?: 'up' | 'down';
34
36
  })[]): void;
35
37
  }
36
38
  export declare function getNiceTicks(min: number, max: number, targetCount?: number): number[];
@@ -1 +1 @@
1
- import{resolveFont as t,toCssFont as e}from"../common/Font.js";const i={family:"sans-serif",weight:"normal",size:12};export class YAxisTicks{setFont(e){this.font=e?t(e,i):i}constructor(){this.canvas=document.createElement("canvas"),this.font=i,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(i){const n=this.canvas,s=n.parentElement?.getBoundingClientRect();if(!s)return;n.width===s.width&&n.height===s.height||(n.width=s.width,n.height=s.height);const o=n.getContext("2d");if(o){o.clearRect(0,0,n.width,n.height);for(const s of i){if(!1===s.visible)continue;const{min:i,max:l,minValue:h,maxValue:a}=s,r=n.height*(1-l),c=n.height*(1-i),f=c-r,d=getNiceTicks(h,a),m=s.format??(t=>t.toString()),g=s.style?.labels?.position??"right",u=s.style?.labels?.color??"#777",y=void 0!==s.style?.labels?.stroke,w=s.style?.labels?.stroke?.color??"#000",x=s.style?.labels?.stroke?.width??1,p=s.style?.lines?.color??"#7773",v=parseFloat(s.style?.lines?.width??"1");o.save(),o.fillStyle=u,o.font=e(t(s.style?.labels?.font,this.font)),o.textBaseline="middle",o.lineJoin="round",o.miterLimit=2,o.textAlign="left"===g?"right":"right"===g?"left":"center";for(const t of d){const e=(t-h)/(a-h);if(e<0||e>1)continue;const i=c-e*f;if(!(i<r+4||i>c-4)&&(o.beginPath(),o.moveTo(0,i),o.lineTo(n.width,i),o.lineWidth=v,o.strokeStyle=p,o.stroke(),"none"!==g)){const e=m(t),s=o.measureText(e).width;let l,h="center";"left"===g?(h="right",l=Math.max(4+s,4+s),l=Math.min(l,n.width/2-4)):"right"===g?(h="left",l=Math.min(n.width-4-s,n.width-4-s),l=Math.max(l,n.width/2+4)):(h="center",l=n.width/2),o.textAlign=h,y&&(o.lineWidth=2*x,o.strokeStyle=w,o.strokeText(e,l,i)),o.fillText(e,l,i)}}o.restore()}}}}export function getNiceTicks(t,e,i=6){const n=niceNum(e-t,!1),s=niceNum(n/(i-1),!0),o=Math.floor(t/s)*s,l=Math.ceil(e/s)*s,h=[];for(let i=o;i<=l+s/2;i+=s)i>=t&&i<=e&&h.push(parseFloat(i.toFixed(10)));return h}export function niceNum(t,e){const i=Math.floor(Math.log10(t)),n=t/Math.pow(10,i);let s=0;return s=e?n<1.5?1:n<3?2:n<7?5:10:n<=1?1:n<=2?2:n<=5?5:10,s*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)}