@protomaze/time 1.6.0 → 1.8.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.
- package/common/Pixels.d.ts +11 -0
- package/common/Pixels.js +1 -0
- package/demo/Common.d.ts +1 -1
- package/demo/Random.d.ts +3 -3
- package/engine/TimeSlider.d.ts +20 -0
- package/engine/TimeSlider.js +1 -1
- package/engine/TimeSliderEngine.d.ts +4 -0
- package/engine/TimeSliderEngine.js +1 -1
- package/index.d.ts +2 -0
- package/index.js +1 -1
- package/layer/TimeSliderLayer.d.ts +20 -0
- package/layer/TimeSliderLayer.js +1 -1
- package/package.json +1 -1
- package/react/ReactTimeSlider.js +1 -1
- package/style/TimeSliderIntervalsStyle.d.ts +7 -2
- package/three/PixelSnap.d.ts +11 -0
- package/three/PixelSnap.js +1 -0
- package/three/Three3DTimeSliderEngine.d.ts +9 -1
- package/three/Three3DTimeSliderEngine.js +1 -1
- package/three/ThreeTimeSliderEngine.d.ts +9 -1
- package/three/ThreeTimeSliderEngine.js +1 -1
- package/three/layer/Three3DIntervalsTimeLayerLoader.js +1 -1
- package/three/layer/ThreeIntervalsTimeLayerLoader.js +1 -1
- package/ticks/TimeTicksBar.d.ts +3 -0
- package/ticks/TimeTicksBar.js +1 -1
- package/ticks/YAxisTicks.d.ts +18 -1
- package/ticks/YAxisTicks.js +1 -1
|
@@ -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
|
+
};
|
package/common/Pixels.js
ADDED
|
@@ -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}}
|
package/demo/Common.d.ts
CHANGED
package/demo/Random.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare function createRandomDemo(): {
|
|
|
10
10
|
color?: string;
|
|
11
11
|
opacity?: number;
|
|
12
12
|
width?: number;
|
|
13
|
-
sides?: "all" | "vertical";
|
|
13
|
+
sides?: "all" | "vertical" | "vertical+base";
|
|
14
14
|
smoothing?: number;
|
|
15
15
|
};
|
|
16
16
|
}> | import("..").TimeSliderHistogramLayer<import("..").CompositeTimeSliderModel | import("..").TimeSliderModel<any>>)[];
|
|
@@ -24,7 +24,7 @@ export declare function createRandomDemo(): {
|
|
|
24
24
|
color?: string;
|
|
25
25
|
opacity?: number;
|
|
26
26
|
width?: number;
|
|
27
|
-
sides?: "all" | "vertical";
|
|
27
|
+
sides?: "all" | "vertical" | "vertical+base";
|
|
28
28
|
smoothing?: number;
|
|
29
29
|
};
|
|
30
30
|
}>)[] | (import("..").TimeSliderIntervalsLayer<import("..").TimeSliderInterval<any>, {
|
|
@@ -37,7 +37,7 @@ export declare function createRandomDemo(): {
|
|
|
37
37
|
color?: string;
|
|
38
38
|
opacity?: number;
|
|
39
39
|
width?: number;
|
|
40
|
-
sides?: "all" | "vertical";
|
|
40
|
+
sides?: "all" | "vertical" | "vertical+base";
|
|
41
41
|
smoothing?: number;
|
|
42
42
|
};
|
|
43
43
|
}> | import("..").TimeSliderHistogramLayer<import("..").CompositeTimeSliderModel | import("..").TimeSliderModel<any>>)[])[];
|
package/engine/TimeSlider.d.ts
CHANGED
|
@@ -143,7 +143,10 @@ export interface TimeSliderOptions {
|
|
|
143
143
|
tickTooltip?: TimeSliderTickTooltip;
|
|
144
144
|
/** Style of the hover, tick and selection-label tooltips. See also setTooltipStyle(). */
|
|
145
145
|
tooltipStyle?: TimeSliderTooltipStyle;
|
|
146
|
+
/** 'front' (default): the y-axis grid lines are drawn over the layers. 'behind': under them, like chart.js; the labels stay in front. See also setGridOrder(). */
|
|
147
|
+
gridOrder?: TimeSliderGridOrder;
|
|
146
148
|
}
|
|
149
|
+
export type TimeSliderGridOrder = 'front' | 'behind';
|
|
147
150
|
export declare class TimeSlider extends TypedEventEmitter<{
|
|
148
151
|
live: boolean;
|
|
149
152
|
playing: boolean;
|
|
@@ -229,6 +232,7 @@ export declare class TimeSlider extends TypedEventEmitter<{
|
|
|
229
232
|
private tooltipStyle;
|
|
230
233
|
private tickToolTip;
|
|
231
234
|
private stopWatchingFonts?;
|
|
235
|
+
private _gridOrder;
|
|
232
236
|
getLayers(): TimeSliderLayer<TimeSliderElement, import("..").TimeSliderStyle>[];
|
|
233
237
|
getVisibleLayers(): TimeSliderLayer<TimeSliderElement, import("..").TimeSliderStyle>[];
|
|
234
238
|
constructor(options?: TimeSliderOptions);
|
|
@@ -245,9 +249,22 @@ export declare class TimeSlider extends TypedEventEmitter<{
|
|
|
245
249
|
/** Re-measures and redraws the labels, e.g. after a web font finished loading. Done automatically on document.fonts 'loadingdone'. */
|
|
246
250
|
refreshFonts(): void;
|
|
247
251
|
private watchFontLoading;
|
|
252
|
+
/** 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. */
|
|
253
|
+
private watchPixelRatio;
|
|
248
254
|
get background(): string | undefined;
|
|
249
255
|
/** Sets the background colour behind the layers and the ticks bar (see TimeSliderOptions.background); undefined goes back to the theme's. */
|
|
250
256
|
setBackground(background: string | undefined): void;
|
|
257
|
+
get gridOrder(): TimeSliderGridOrder;
|
|
258
|
+
/** Draws the y-axis grid lines in front of the layers ('front', default) or behind them ('behind'). */
|
|
259
|
+
setGridOrder(gridOrder: TimeSliderGridOrder): void;
|
|
260
|
+
/**
|
|
261
|
+
* Grid lines (gridOrder 'behind') and baselines are drawn on a canvas behind the engine's. Only
|
|
262
|
+
* while there is something to show there, the engine clears to transparent and the slider paints
|
|
263
|
+
* the background itself; otherwise the engine draws it as before.
|
|
264
|
+
*/
|
|
265
|
+
private updateBehindLayers;
|
|
266
|
+
/** Puts the canvas of lines behind the layers into the slider element, under the engine's canvas. */
|
|
267
|
+
private attachBehindCanvas;
|
|
251
268
|
/** Sets the tooltip shown on hovering a tick box, see TimeSliderTickTooltip. */
|
|
252
269
|
setTickTooltip(tickTooltip: TimeSliderTickTooltip): void;
|
|
253
270
|
/** Sets the style of the hover, tick and selection-label tooltips. */
|
|
@@ -343,6 +360,9 @@ export declare class TimeSlider extends TypedEventEmitter<{
|
|
|
343
360
|
v: number;
|
|
344
361
|
};
|
|
345
362
|
private pixelsToLayerValue;
|
|
363
|
+
private warnedDirection;
|
|
364
|
+
/** 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. */
|
|
365
|
+
isHangingDown(layer: TimeSliderLayer): boolean;
|
|
346
366
|
private pixelsToModelValue;
|
|
347
367
|
private layerValueToPixels;
|
|
348
368
|
private modelValueToPixels;
|
package/engine/TimeSlider.js
CHANGED
|
@@ -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 g}from"../data/histogram/TimeSliderDynamicHistogram.js";import{getTimeArray as u}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 B,ONE_MINUTE as M,ONE_MONTH as D,ONE_SECOND as _,ONE_WEEK as O,ONE_YEAR as A,TODAY as H}from"../ticks/TimeSliderConstants.js";import{TimeTicksBar as z}from"../ticks/TimeTicksBar.js";import{YAxisTicks as I}from"../ticks/YAxisTicks.js";import{resolveFont as $,toCssFont as F}from"../common/Font.js";const U=_,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 z,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:H,value:{minValue:0,maxValue:100}},this.size={width:100,height:100},this.sliderElement=document.createElement("div"),this.yAxisTicks=new I,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._gridOrder="front",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*_,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._gridOrder=i?.gridOrder??"front",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),this.updateBehindLayers()}get gridOrder(){return this._gridOrder}setGridOrder(e){this._gridOrder=e,this.updateBehindLayers(),this.renderYAxis()}updateBehindLayers(){const e="behind"===this._gridOrder||this.getVisibleLayers().some(e=>e.baseline);this.engine?.setTransparent(e),this.sliderElement.style.background=e?this._background??("dark"===this.theme?"#111":"#eee"):""}attachBehindCanvas(){const e=this.yAxisTicks.behindCanvas;e.style.height=this.sliderElement.style.height,e.parentElement!==this.sliderElement&&this.sliderElement.insertBefore(e,this.sliderElement.firstChild)}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(u),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})}});const i=this.getVisibleLayers().filter(e=>e.baseline).map(e=>({y:this.isHangingDown(e)?e.max:e.min,color:e.baseline.color,width:e.baseline.width??1}));this.yAxisTicks.render(t,{gridBehind:"behind"===this._gridOrder,baselines:i})}setTheme(e){this.theme=e,this.updateBehindLayers(),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),this.attachBehindCanvas(),this.updateBehindLayers(),this.renderYAxis()}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)`,this.yAxisTicks.behindCanvas.style.height=this.sliderElement.style.height}}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(Y(t)),this.setValueRange(Y(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,g=.5*o,u=a.width-.5*n,p=a.height-.5*o,y=Math.min(u,Math.max(c,h)),T=Math.min(p,Math.max(g,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 g;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>=B: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,g=36;let u=this.mouse.x+10,p=this.mouse.y+10;const y=e.left+e.width-c-5,T=e.top+e.height-g-5;u>y&&(u=y),p>T&&(p=T),this.toolTip.style.left=`${u}px`,this.toolTip.style.top=`${p}px`}}else this.hoverLine.style.display="none",this.toolTip.style.display="none",this.clearHoverEvent()}initHoverTooltip(){this.toolTip.style.position="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:A,unit:"decade"},{threshold:.9*D,unit:"year"},{threshold:O,unit:"month"},{threshold:V,unit:"week"},{threshold:B,unit:"day"},{threshold:M,unit:"hour"},{threshold:_,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:A,unit:"month"},{threshold:1.1*D,unit:"week"},{threshold:O,unit:"day"},{threshold:V,unit:"hour"},{threshold:B,unit:"minute"},{threshold:M,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)>j||Math.abs(n.endTime-t.endTime)>j,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:K(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(j,.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 H;{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(),this.updateBehindLayers()}),e.on("min",()=>this.onLayerBandChange(e)),e.on("max",()=>this.onLayerBandChange(e)),e.on("direction",()=>this.onLayerBandChange(e)),e.on("baseline",()=>{this.updateBehindLayers(),this.renderYAxis()})];this.layerListenerStoppers.set(e.id,()=>t.forEach(e=>e())),this.updateBehindLayers(),this.renderYAxis()}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),this.updateBehindLayers(),this.renderYAxis())}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},g=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})},u=e=>{const t=this.getSliderBounds(),n=t.height-(e.clientY-t.top);if(!this.dragging&&!this.pinching&&n<8)this.ticksBar.click(e);else if(i=void 0,this.dragging=!1,this.selectingRange){const t=this.mouseInSlider(e).normalized.x,{startTime:i,endTime:n}=this.getTimeRange(),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",g),this.sliderElement.addEventListener("pointerup",u),this.sliderElement.addEventListener("dblclick",d),this.sliderElement.addEventListener("touchend",h),this.sliderElement.addEventListener("touchstart",e=>{if(1===e.touches.length){const t=this.mouseInSlider(e.touches[0]);r=t.normalized.x}else r=-1}),this.sliderElement.addEventListener("keydown",p),this.sliderElement.addEventListener("keyup",y),this.addHorizontalPinchZoom();const E=()=>{this.sliderElement.removeEventListener("contextmenu",f),this.sliderElement.removeEventListener("wheel",l),this.sliderElement.removeEventListener("pointerdown",m),this.sliderElement.removeEventListener("pointermove",g),this.sliderElement.removeEventListener("pointerup",u),this.sliderElement.removeEventListener("dblclick",d),this.sliderElement.removeEventListener("touchend",h),this.sliderElement.removeEventListener("keydown",p),this.sliderElement.removeEventListener("keyup",y)};return this.onDestroy.push(E),e.add(E),e.disposeLater()}shift(e,t){const{startTime:i,endTime:s,duration:n}=this.getTimeRange(),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:_}),this.play()}this.emit("live",e)}}}function Y(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 j=_,W={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 K(e,t,i){const s=W[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)}
|
|
@@ -20,6 +20,10 @@ export declare abstract class TimeSliderEngine<T> extends TypedEventEmitter<{
|
|
|
20
20
|
setTheme(theme: 'dark' | 'light'): void;
|
|
21
21
|
/** Sets the clear colour behind the layers, kept across theme changes; undefined goes back to the theme's default. */
|
|
22
22
|
setBackground(background: string | undefined): void;
|
|
23
|
+
/** When true the engine clears to transparent, so content behind its canvas (grid lines, baselines, the background painted by the slider) shows between the layers. */
|
|
24
|
+
transparent: boolean;
|
|
25
|
+
setTransparent(transparent: boolean): void;
|
|
26
|
+
protected getClearAlpha(): number;
|
|
23
27
|
protected getClearColor(): string;
|
|
24
28
|
getTimeRange(): {
|
|
25
29
|
duration: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{TypedEventEmitter as e}from"../event/TypedEventEmitter.js";import{TODAY as t}from"../ticks/TimeSliderConstants.js";import{addDuration as i}from"../data/TimeSliderRange.js";export class TimeSliderEngine extends e{constructor(e){super(),this.scene=e,this.timeRange=t,this.valueRange={minValue:0,maxValue:100},this.size={width:1e3,height:100},this.theme="dark"}setTheme(e){this.theme=e,this.setTimeRange(this.timeRange)}setBackground(e){this.background=e,this.setTheme(this.theme)}getClearColor(){return this.background??("dark"===this.theme?"#111":"#eee")}getTimeRange(){return{...this.timeRange,duration:this.timeRange.endTime-this.timeRange.startTime}}getValueRange(){return this.valueRange}getSize(){return this.size}showStats(e){}dispose(){this.destroy(),this.size={width:1e3,height:100}}setTimeRange(e){if(e.startTime!==this.timeRange.startTime||e.endTime!==this.timeRange.endTime){this.timeRange=e;const t=i(e);this.updateTimeRange(t),this.emit("time",t)}}setValueRange(e){e.minValue===this.valueRange.minValue&&e.maxValue===this.valueRange.maxValue||(this.valueRange=e,this.updateValueRange(e),this.emit("value",e))}setSize(e,t){this.size.width===e&&this.size.height===t||(this.size={width:e,height:t},this.updateSize(e,t),this.emit("size",this.size))}}
|
|
1
|
+
import{TypedEventEmitter as e}from"../event/TypedEventEmitter.js";import{TODAY as t}from"../ticks/TimeSliderConstants.js";import{addDuration as i}from"../data/TimeSliderRange.js";export class TimeSliderEngine extends e{constructor(e){super(),this.scene=e,this.timeRange=t,this.valueRange={minValue:0,maxValue:100},this.size={width:1e3,height:100},this.theme="dark",this.transparent=!1}setTheme(e){this.theme=e,this.setTimeRange(this.timeRange)}setBackground(e){this.background=e,this.setTheme(this.theme)}setTransparent(e){e!==this.transparent&&(this.transparent=e,this.setTheme(this.theme))}getClearAlpha(){return this.transparent?0:1}getClearColor(){return this.background??("dark"===this.theme?"#111":"#eee")}getTimeRange(){return{...this.timeRange,duration:this.timeRange.endTime-this.timeRange.startTime}}getValueRange(){return this.valueRange}getSize(){return this.size}showStats(e){}dispose(){this.destroy(),this.size={width:1e3,height:100}}setTimeRange(e){if(e.startTime!==this.timeRange.startTime||e.endTime!==this.timeRange.endTime){this.timeRange=e;const t=i(e);this.updateTimeRange(t),this.emit("time",t)}}setValueRange(e){e.minValue===this.valueRange.minValue&&e.maxValue===this.valueRange.maxValue||(this.valueRange=e,this.updateValueRange(e),this.emit("value",e))}setSize(e,t){this.size.width===e&&this.size.height===t||(this.size={width:e,height:t},this.updateSize(e,t),this.emit("size",this.size))}}
|
package/index.d.ts
CHANGED
|
@@ -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,29 @@ 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;
|
|
17
|
+
/** A horizontal line across the whole slider at the layer's value-0 edge (band min for 'up', max for 'down'), drawn behind the bars and on whole device pixels. */
|
|
18
|
+
baseline?: TimeSliderLayerBaseline;
|
|
15
19
|
visible?: boolean;
|
|
16
20
|
style?: S;
|
|
17
21
|
styler?: TimeSliderStyler<E, S>;
|
|
18
22
|
labeler?: TimeSliderLabeler<E>;
|
|
19
23
|
yAxis?: YAxisSettings;
|
|
20
24
|
};
|
|
25
|
+
export type TimeSliderLayerDirection = 'up' | 'down';
|
|
26
|
+
/** See TimeSliderLayerOptions.baseline. */
|
|
27
|
+
export type TimeSliderLayerBaseline = {
|
|
28
|
+
color: string;
|
|
29
|
+
width?: number;
|
|
30
|
+
};
|
|
21
31
|
export declare abstract class TimeSliderLayer<E extends TimeSliderElement = TimeSliderElement, S extends TimeSliderStyle = TimeSliderStyle> extends TypedEventEmitter<{
|
|
22
32
|
selection: E[];
|
|
23
33
|
name: string;
|
|
24
34
|
min: number;
|
|
25
35
|
max: number;
|
|
36
|
+
direction: TimeSliderLayerDirection;
|
|
37
|
+
baseline: TimeSliderLayerBaseline | undefined;
|
|
26
38
|
visible: boolean;
|
|
27
39
|
loaded?: boolean;
|
|
28
40
|
invalidate: string;
|
|
@@ -32,6 +44,8 @@ export declare abstract class TimeSliderLayer<E extends TimeSliderElement = Time
|
|
|
32
44
|
id: string;
|
|
33
45
|
private _min;
|
|
34
46
|
private _max;
|
|
47
|
+
private _direction;
|
|
48
|
+
private _baseline?;
|
|
35
49
|
private _name;
|
|
36
50
|
private _visible;
|
|
37
51
|
private _loaded;
|
|
@@ -54,6 +68,12 @@ export declare abstract class TimeSliderLayer<E extends TimeSliderElement = Time
|
|
|
54
68
|
set min(newMin: number);
|
|
55
69
|
get max(): number;
|
|
56
70
|
set max(newMax: number);
|
|
71
|
+
get direction(): TimeSliderLayerDirection;
|
|
72
|
+
/** See TimeSliderLayerOptions.direction. */
|
|
73
|
+
set direction(newDirection: TimeSliderLayerDirection);
|
|
74
|
+
get baseline(): TimeSliderLayerBaseline | undefined;
|
|
75
|
+
/** See TimeSliderLayerOptions.baseline; undefined removes it. */
|
|
76
|
+
set baseline(newBaseline: TimeSliderLayerBaseline | undefined);
|
|
57
77
|
get timeSlider(): TimeSlider | undefined;
|
|
58
78
|
set timeSlider(timeSlider: TimeSlider | undefined);
|
|
59
79
|
get name(): string;
|
package/layer/TimeSliderLayer.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{v4 as i}from"uuid";import{TypedEventEmitter as
|
|
1
|
+
import{v4 as i}from"uuid";import{TypedEventEmitter as e}from"../event/TypedEventEmitter.js";export class TimeSliderLayer extends e{constructor(e,t){super(),this.model=e,this._visible=!0,this._loaded=!1,this.id=t?.id||i(),this.style=t?.style||this.getDefaultStyle(),this._styler=t?.styler,this.labeler=t?.labeler,this.yAxis=t?.yAxis||{},this._name=t?.name||e.name||"Unknown",this._min=t?.min||0,this._max=t?.max||1,this._direction=t?.direction||"up",this._baseline=t?.baseline,this._visible="boolean"!=typeof t?.visible||t.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 e="string"==typeof i?i:i.id;this.emit("invalidate",e)}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 baseline(){return this._baseline}set baseline(i){this._baseline=i,this.emit("baseline",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
package/react/ReactTimeSlider.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e,{forwardRef as t,useCallback as n,useEffect as r,useImperativeHandle as i,useLayoutEffect as o,useMemo as a,useRef as l,useState as s}from"react";import{Disposables as c}from"../common/Disposables.js";import{formatDuration as m}from"../common/TimeTools.js";import{TimeSlider as d}from"../engine/TimeSlider.js";import{ONE_YEAR as p}from"../ticks/TimeSliderConstants.js";import{ColoredSlider as u}from"./ColoredSlider.js";import{DateLabelWithPicker as g}from"./DateLabelWithPicker.js";import{DateRangeLabelWithPicker as h}from"./DateRangeLabelWithPicker.js";import{MaterialIconButton as f}from"./MaterialIconButton.js";import{MaterialTextButton as w}from"./MaterialTextButton.js";import{TimeLabelWithPicker as x}from"./TimeLabelWithPicker.js";const y={padding:"4px 8px",background:"#0008",borderRadius:"4px",color:"#fff",fontFamily:"monospace",fontSize:"12px"},b=[Date.now()-p,Date.now()];function v(e,t){return e[0]===t[0]&&e[1]===t[1]}function k(e){return JSON.stringify(e)}export const ReactTimeSlider=t(({timeRange:t,onTimeRange:n,selectRangeButton:o,noPlayButton:p,noTimeLabels:E,noRangeLabel:C,noDateLabels:L,theme:S,canHideLabels:R,verticalGap:N,engine:I,layers:_,loaders:z,showStats:D,currentSlider:j,...M},
|
|
1
|
+
import e,{forwardRef as t,useCallback as n,useEffect as r,useImperativeHandle as i,useLayoutEffect as o,useMemo as a,useRef as l,useState as s}from"react";import{Disposables as c}from"../common/Disposables.js";import{formatDuration as m}from"../common/TimeTools.js";import{TimeSlider as d}from"../engine/TimeSlider.js";import{ONE_YEAR as p}from"../ticks/TimeSliderConstants.js";import{ColoredSlider as u}from"./ColoredSlider.js";import{DateLabelWithPicker as g}from"./DateLabelWithPicker.js";import{DateRangeLabelWithPicker as h}from"./DateRangeLabelWithPicker.js";import{MaterialIconButton as f}from"./MaterialIconButton.js";import{MaterialTextButton as w}from"./MaterialTextButton.js";import{TimeLabelWithPicker as x}from"./TimeLabelWithPicker.js";const y={padding:"4px 8px",background:"#0008",borderRadius:"4px",color:"#fff",fontFamily:"monospace",fontSize:"12px"},b=[Date.now()-p,Date.now()];function v(e,t){return e[0]===t[0]&&e[1]===t[1]}function k(e){return JSON.stringify(e)}export const ReactTimeSlider=t(({timeRange:t,onTimeRange:n,selectRangeButton:o,noPlayButton:p,noTimeLabels:E,noRangeLabel:C,noDateLabels:L,theme:S,canHideLabels:R,verticalGap:N,engine:I,layers:_,loaders:z,showStats:D,currentSlider:j,...M},O)=>{const P=a(()=>new d(M),[]);r(()=>{M?.showCurrent&&(P.showCurrent=M?.showCurrent)},[M.showCurrent]),r(()=>{M?.showTicks&&(P.showTicks=M?.showTicks)},[M.showTicks]),r(()=>{M?.playSpeed&&(P.playSpeed=M?.playSpeed)},[M.playSpeed]),r(()=>{M?.liveRange&&(P.liveRange=M?.liveRange)},[M.liveRange]),r(()=>{M?.locale&&P.setLocale(M.locale)},[M.locale]),r(()=>{M?.weekLabel&&P.setWeekLabel(M.weekLabel,M.weekLabelPrefix)},[M.weekLabel,M.weekLabelPrefix]),r(()=>{P.setTickLabelStyler(M?.tickLabelStyler)},[M.tickLabelStyler]),r(()=>{P.setLimits(M?.limits)},[M.limits?.startTime,M.limits?.endTime]),r(()=>{P.setFont(M?.font)},[M.font?.family,M.font?.weight,M.font?.size]),r(()=>{P.setBackground(M?.background)},[M.background]),r(()=>{P.setTickTooltip(M?.tickTooltip??!0)},[M.tickTooltip]),r(()=>{P.setTooltipStyle(M?.tooltipStyle??{})},[JSON.stringify(M.tooltipStyle??{})]),r(()=>{P.setGridOrder(M?.gridOrder??"front")},[M.gridOrder]);const[,B]=s(0);r(()=>P.on("locale",()=>B(e=>e+1)),[P]);const W=N||4,F=22+W+8+"px",G=22+W+22+W+8+"px",H=l(null),X=l(null),A=l(null),[J,U]=s(!1),[V,Y]=s(!1),[$,q]=s(!1),[K,Q]=s(!1),[Z,ee]=s(!1),[te,ne]=s(Date.now()),re=l(null);useMaterialIcons();const[ie,oe]=s(t||b),ae=l(t||b),le=void 0!==t,se=l(new Set),[ce,me]=s(!1);r(()=>{if(le){const e=k(t);if(J&&!v(t,ae.current)&&!se.current.has(e)){ae.current=t;const[e,n]=t;P.setTimeRange({startTime:e,endTime:n})}se.current.delete(e)}},[J,t,le]),r(()=>{const e=new c;return e.add(P.on("time",e=>{const{startTime:t,endTime:r}=e,i=[t,r];v(i,ae.current)||(ae.current=i,se.current.add(k(i)),oe(i),n?.(i))})),e.disposeLater()},[P]),r(()=>{U(!1);const e=new c;return P.init(X.current).then(()=>U(!0)),e.add(P.on("time",e=>{if(e.startTime!==ae.current[0]||e.endTime!==ae.current[1]){const{startTime:t,endTime:r}=e,i=[t,r];ae.current=i,oe(i),n?.(i)}})),e.add(P.on("playing",Y)),e.add(P.on("live",q)),e.add(P.on("current",ne)),e.disposeLater()},[P]),r(()=>{z?.forEach(e=>P.addLayerLoader(e))},[z]),r(()=>{"boolean"==typeof M.showCurrent&&(P.showCurrent=M.showCurrent)},[M.showCurrent]),i(O,()=>({get timeSlider(){return P}})),r(()=>{if(M.showCurrent){const e=setInterval(()=>{A.current&&(A.current.available=Date.now())},10);return()=>clearInterval(e)}},[M.showCurrent]),r(()=>{J&&P.showStats(!0===D)},[J,D]),r(()=>{},[J]),r(()=>{J&&P.setEngine(I)},[J,I]),r(()=>{if(J){const e=new Set,t=new Set;P.getLayers().forEach(e=>t.add(e.id)),_.forEach(n=>{t.delete(n.id),e.add(n.id)}),P.getLayers().forEach(t=>e.delete(t.id));const n=_.filter(t=>e.has(t.id)),r=P.getLayers().filter(e=>t.has(e.id));r.length===P.getLayers().length&&n.length>0&&P.setTimeRange(n[0].model.getTimeRange()),r.forEach(e=>P.removeLayer(e)),n.forEach(e=>P.addLayer(e))}},[J,_]);r(()=>{const e=e=>{re.current&&e.target&&e.target.parentElement?.getAttribute("layerpanelbutton")!==P.id&&!re.current.contains(e.target)&&ee(!1)};if(Z)return document.addEventListener("mousedown",e),()=>{document.removeEventListener("mousedown",e)}},[Z]);const de=e=>{const t=P.current,{duration:n,startTime:r}=P.getTimeRange(),i=(t-r)/n;i>1||i<0?P.setTimeRange({startTime:e-.5*n,endTime:e+.5*n}):P.setTimeRange({startTime:e-i*n,endTime:e+(1-i)*n}),P.setCurrent(e)};r(()=>{const e=()=>{if(H.current){let e=560;M.showCurrent&&(e+=160),p&&(e-=30),C&&(e-=160),E&&(e-=140),L&&(e-=185);const t=H.current.getBoundingClientRect().width;Q(t<e)}};e();const t=new ResizeObserver(e);return H.current&&t.observe(H.current),window.addEventListener("resize",e),()=>{t.disconnect(),window.removeEventListener("resize",e)}},[M.showCurrent,p,C,E,L]);let pe=42;M.showCurrent&&(pe+=4),K&&!R&&(pe+=26);const ue="dark"===S;return r(()=>{S&&P.setTheme(S)},[S]),e.createElement("div",{ref:H,style:{display:"block",position:"relative",borderRadius:"inherit",background:ue?"#222":"#ddd",width:"100%",height:"100%",padding:"8px",boxSizing:"border-box"}},Z&&e.createElement("div",{ref:re,style:{position:"absolute",top:F,left:"8px",maxWidth:"200px",display:"flex",gap:"8px",alignItems:"center",paddingRight:"8px",paddingBottom:"8px",paddingTop:"8px",flexDirection:"column",zIndex:1e3,background:ue?"#222":"#ddd"}},P.getLayers().map(t=>e.createElement(T,{key:t.id,layer:t,timeSlider:P,isDarkMode:ue}))),e.createElement("div",{style:{position:"absolute",top:"8px",left:"8px",display:"flex",gap:"8px",alignItems:"center"}},e.createElement(f,{layerpanelbutton:P.id,iconName:"layers",onClick:e=>ee(e=>!e)}),e.createElement(f,{iconName:"skip_previous",onClick:()=>P.up({animate:1e3}),rotate:90}),!K&&!C&&e.createElement("span",{style:{...y}},m(ie)),e.createElement(f,{iconName:"arrow_range",onClick:()=>P.fit({animate:3e3})}),o&&e.createElement(f,{iconName:"ink_selection",active:ce,onClick:()=>{me(!0),P.startSelectingRange(()=>me(!1))}})),K&&!R&&M.showCurrent&&!C&&e.createElement("div",{style:{position:"absolute",top:F,left:"8px",display:"flex",gap:"8px",height:"22px"}},e.createElement("span",{style:{...y}},m(ie))),K&&!R&&!M.showCurrent&&!C&&e.createElement("div",{style:{position:"absolute",top:"8px",left:"50%",transform:"translateX(-50%)",display:"flex",gap:"8px",height:"22px"}},e.createElement("span",{style:{...y}},m(ie))),(!K||!R)&&e.createElement("div",{style:{position:"absolute",top:K?F:"8px",right:K?"8px":"50%",transform:K?void 0:"translateX(50%)",padding:0,display:"flex",gap:"8px",height:"22px",width:!K||M.showCurrent?void 0:"calc(100% - 16px)"}},!M.showCurrent&&(!E||!E)&&e.createElement(e.Fragment,null,!E&&e.createElement(x,{theme:S,time:ie[0],setTime:e=>P.setTimeRange({startTime:e})}),K&&!E&&!L&&!M.showCurrent&&e.createElement("div",{style:{flexGrow:1}}),!L&&e.createElement(h,{theme:S,range:[ie[0],ie[1]],setRange:e=>P.setTimeRange({startTime:e[0],endTime:e[1]})}),K&&!E&&!L&&!M.showCurrent&&e.createElement("div",{style:{flexGrow:1}}),!E&&e.createElement(x,{theme:S,time:ie[1]-1,setTime:e=>P.setTimeRange({endTime:e+1})})),M.showCurrent&&e.createElement(e.Fragment,null,!L&&e.createElement(g,{theme:S,time:te,setTime:de}),!E&&e.createElement(x,{theme:S,time:te,setTime:de}))),e.createElement("div",{style:{position:"absolute",top:"8px",right:"8px",display:"flex",gap:"8px",alignItems:"center"}},M.showCurrent&&e.createElement(e.Fragment,null,e.createElement(w,{text:"Live",onClick:()=>{P.live?P.pause():P.setLive(!0)},color:$?"#fff":ue?"#888":"#bbb"},e.createElement("span",{className:"material-symbols-outlined",style:{fontSize:"18px",zIndex:1,lineHeight:1}},"radio_button_unchecked"),$&&e.createElement("span",{className:"material-symbols-outlined",style:{fontSize:"18px",lineHeight:1,marginLeft:"-24px",color:"red"}},"radio_button_checked"))),e.createElement(f,{iconName:"skip_previous",onClick:()=>P.previous({animate:1e3})}),!p&&!V&&e.createElement(f,{iconName:"play_arrow",onClick:()=>P.play()}),!p&&V&&e.createElement(f,{iconName:"pause",onClick:()=>P.pause()}),e.createElement(f,{iconName:"skip_next",onClick:()=>P.next({animate:1e3})})),M.showCurrent&&e.createElement(u,{ref:A,value:te,min:ie[0],max:ie[1],onValue:e=>{P.pause(),P.setCurrent(e)},onPointerMove:e=>P.updateHover({x:e.clientX,y:e.clientY}),onPointerLeave:()=>P.updateHover(null),matchWidth:!0,style:{width:"calc(100% - 16px)",left:"8px",position:"absolute",bottom:"26px",zIndex:1},...j}),e.createElement("div",{ref:X,style:{position:"absolute",top:K&&!R?G:F,left:"8px",borderRadius:"inherit",width:"calc(100% - 16px)",height:`calc(100% - ${pe}px)`,background:M.background??(ue?"#111":"#eee"),overflow:"hidden"}}))});function T({layer:t,timeSlider:n,isDarkMode:i}){const[o,a]=s(t.visible);return r(()=>{t.on("visible",e=>a(e))},[]),e.createElement("div",{style:{display:"flex",gap:"8px",alignItems:"center",background:i?"#222":"#ddd",borderRadius:"4px",width:"100%"}},o&&e.createElement(f,{iconName:"visibility",onClick:()=>t.visible=!1}),!o&&e.createElement(f,{iconName:"visibility_off",color:"#888",onClick:()=>t.visible=!0}),e.createElement("span",{style:{...y,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",flexGrow:1,minWidth:0}},t.name||t.constructor.name+"#"+t.id.slice(t.id.length-4).toUpperCase()),e.createElement(f,{iconName:"center_focus_weak",onClick:()=>n.fit({animate:3e3,range:t.model.getTimeRange()})}))}export function useMaterialIcons(e="outlined"){r(()=>{const e=document.createElement("link");return e.rel="stylesheet",e.href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200",document.head.appendChild(e),()=>{document.head.removeChild(e)}},[e])}
|
|
@@ -9,8 +9,13 @@ export type TimeSliderIntervalsStyle = TimeSliderStyle & {
|
|
|
9
9
|
color?: string;
|
|
10
10
|
opacity?: number;
|
|
11
11
|
width?: number;
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* 'all' (default) draws a border on every side, including between stacked segments. 'vertical' only draws
|
|
14
|
+
* left/right, between neighbouring bars. 'vertical+base' also draws the value-0 edge (the bottom, or the top
|
|
15
|
+
* for a layer hanging down) at the same width, so two bars meeting base to base are divided exactly like two
|
|
16
|
+
* neighbours: by one border width when centred (the bars overlap there), two when not. In whole device pixels.
|
|
17
|
+
*/
|
|
18
|
+
sides?: 'all' | 'vertical' | 'vertical+base';
|
|
14
19
|
/** Anti-aliasing ramp between border and fill, in pixels. 0 gives a hard edge (crisp borders between neighbouring bars). Defaults to 1.5 (2D) / 1 (3D). */
|
|
15
20
|
smoothing?: number;
|
|
16
21
|
};
|
|
@@ -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
|
-
|
|
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{
|
|
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 g,ONE_SECOND as b,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,alpha:!0});l.domElement.style.position="relative",l.setPixelRatio(x());const p={value:new h};this.renderer=l,this.disposables=new w,this.container=r,this.renderer.setClearColor(this.getClearColor(),this.getClearAlpha()),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=b,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 A=[];let X,K,O,Y;const q=()=>{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<g){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++),A[a]||(A[a]=new i(I,E),T.add(A[a])),A[a].position.x=n,A[a].visible=!0,a++});for(let e=a;e<A.length;e++)A[e].visible=!1;for(let t=e;t<k.length;t++)k[t].visible=!1;Y&&(Y.scale.x=1e3*s),j.scale.x=s,j.position.x=r,Y&&(Y.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(),this.getClearAlpha()),X&&f.remove(X),X?.dispose(),X=new t(1e3,1e3,s,s),f.add(X),O&&f.remove(O),O?.dispose(),O=new t(1e3,1e3,i,i),O.position.z=500,O.position.y=1,f.add(O),K&&f.remove(K),K?.dispose(),K=new t(1e3,1e3,i,i),K.position.y=-499,K.rotateX(-.5*Math.PI),f.add(K),Y&&T.remove(Y),Y?.dispose(),Y=new t(200,200,s,s),Y.position.z=-101,T.add(Y),z.color.set(i),D.color.set(i),E.color.set(s),q()},this.onThemeChange(this.theme),this.disposables.add(this.on("time",q)),this.disposables.add(this.on("size",q)),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
|
-
|
|
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
|
|
1
|
+
import{BufferGeometry as e,GridHelper as t,Group as s,Line as i,LineBasicMaterial as a,OrthographicCamera as o,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 p,snapLinesToPixels as c}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 C}from"../ticks/TimeSliderConstants.js";export class ThreeTimeSliderEngine extends b{constructor(e){super(new n),this.camera=new o(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(o){const n="dark"===this.theme;this.container=o;const m=new l({antialias:this.antialias,alpha:!0});m.domElement.style.position="relative",m.setPixelRatio(p());const b={value:new r};this.renderer=m,this.disposables=new d,this.renderer.setClearColor(this.getClearColor(),this.getClearAlpha()),o.appendChild(this.renderer.domElement),this.disposables.add(()=>o.removeChild(m.domElement)),this.camera.position.z=10;const w=new s,f=new s;w.scale.x=x,this.scene.add(w);const v=new a({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:a}=this.getTimeRange(),o=s,[n]=g.getSpacing(e,o),r=.5*(a+t);if(n<T){f.visible=!1,w.scale.x=n;let e=r%n;n>=C&&(e=r-u(r)),w.position.x=-e,w.visible=!0}else{w.visible=!1;let e=0;g.generateTicks(n,t-3*s,a+3*s,(o,n)=>{t-s<=o&&o<=a+s&&(P[e]||(P[e]=new i(j,v),f.add(P[e])),P[e].position.x=o,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(),this.getClearAlpha()),S&&w.remove(S),S=new t(1e3,1e3,s,s),S.position.y=-499,S.rotateX(-.5*Math.PI),w.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()!==p()&&m.setPixelRatio(p()),c(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(p()),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 f}from"../../common/TimeTools.js";import{TimeSliderSplittingFixedHistogram as c}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,y,x){return this.canLoad(g)?function(g,h,y,x){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 c}(e),p=new s,y=()=>{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 const bool borderBaseEdge = ${"vertical+base"===e.style.border?.sides?"true":"false"};\n float snappedBorderWidth() { return borderWidth > 0.0 ? max(1.0, floor(borderWidth * pixelRatio + 0.5)) / pixelRatio : 0.0; }\n // The value-0 edge is as wide as the sides: bars meeting base to base overlap like neighbours do, so they're divided alike.\n float snappedBaseWidth() { return snappedBorderWidth(); }\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 `},x=()=>{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 f="";if(h){const n=e.maxClassifications||4,t=e.classificationColors||[];f+=`const int maxSplits = ${n};`;let o="",i="";for(let e=0;e<t.length;e++)f+=`\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)f+=`\n flat in vec4 vSplit${e/4};\n `,o+=`\n if (idx < ${e+4}) return vSplit${e/4}[idx - ${e}];\n `;f+=`\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:f,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 const bool borderBaseEdge = ${"vertical+base"===e.style.border?.sides?"true":"false"};\n float snappedBorderWidth() { return borderWidth > 0.0 ? max(1.0, floor(borderWidth * pixelRatio + 0.5)) / pixelRatio : 0.0; }\n // The value-0 edge is as wide as the sides: bars meeting base to base overlap like neighbours do, so they're divided alike.\n float snappedBaseWidth() { return snappedBorderWidth(); }\n const float borderSmoothing = ${m(s)};\n const bool borderSidesVertical = ${"vertical"===t.border?.sides||"vertical+base"===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 ${f}\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 // Base edge: on the first segment's value-0 side (vUv.y is flipped for layers hanging down).\n if (borderBaseEdge && vTopBottom == 0 && segmentStart == 0.0) alphaY *= edgeAlpha(snappedBaseWidth() * vTransform.y / vSize.y / segmentSize, smoothing.y, 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 // Base edge: the value-0 side (vUv.y is flipped for layers hanging down).\n if (borderBaseEdge && vTopBottom == 0) alphaY *= edgeAlpha(snappedBaseWidth() * vTransform.y / vSize.y, smoothing.y, 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:y(),fragmentShader:x()}),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]=f(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=y(),b.fragmentShader=x(),b.needsUpdate=!0})),{material:b,dispose:()=>p.dispose()}}(g,y,x));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]=f(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 c?e=>e.attributes.splits||[1]:void 0,maxSplits:g instanceof v?g.maxClassifications:void 0});b.add(()=>z.dispose()),y.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,x,y):(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 i,Vector2 as t}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 v}from"./GrowingInstanceMatrix.js";import{ThreeTimeLayerLoader as m}from"./ThreeTimeLayerLoader.js";const f=123456;export class ThreeIntervalsTimeLayerLoader extends m{constructor(){super(...arguments),this.followsLayerBand=!0}canLoad(e){return e instanceof c}load(c,m,h,u){return this.canLoad(c)?function(c,m,h,u){const p=new l,g=c.model,{material:y,dispose:x}=function(o,n,r){const c=void 0!==o.styler,v=new l,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 = ${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 const bool borderBaseEdge = ${"vertical+base"===o.style.border?.sides?"true":"false"};\n float snappedBorderWidth() { return borderWidth > 0.0 ? max(1.0, floor(borderWidth * pixelRatio + 0.5)) / pixelRatio : 0.0; }\n // The value-0 edge is as wide as the sides: bars meeting base to base overlap like neighbours do, so they're divided alike.\n float snappedBaseWidth() { return snappedBorderWidth(); }\n uniform vec2 layerRange;\n uniform float layerDirection;\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 // Hanging down, value 0 is at the top: vUv.y = 0 stays the value-0 edge.\n if (layerDirection < 0.0) vUv.y = 1.0 - vUv.y;\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 `},h=()=>{const n=o.style,r=new e(n.fill?.color||"#df0"),i=n.fill?.opacity||.5,t=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(i)};\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 const bool borderBaseEdge = ${"vertical+base"===o.style.border?.sides?"true":"false"};\n float snappedBorderWidth() { return borderWidth > 0.0 ? max(1.0, floor(borderWidth * pixelRatio + 0.5)) / pixelRatio : 0.0; }\n // The value-0 edge is as wide as the sides: bars meeting base to base overlap like neighbours do, so they're divided alike.\n float snappedBaseWidth() { return snappedBorderWidth(); }\n const float borderSmoothing = ${s(d)};\n const bool borderSidesVertical = ${"vertical"===n.border?.sides||"vertical+base"===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(t.r)},${s(t.g)},${s(t.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 // Base edge: the value-0 side (vUv.y is flipped for layers hanging down).\n if (borderBaseEdge) alphaY *= edgeAlpha(snappedBaseWidth() * worldPerPixel.y / vSize.y, smoothing.y, 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 i({transparent:!0,uniforms:{worldPerPixel:{value:new t((n.camera.right-n.camera.left)/n.getSize().width,(n.camera.top-n.camera.bottom)/n.getSize().height)},pixelRatio:{value:a()},layerRange:{value:new t(o.min,o.max)},layerDirection:{value:1},selection:{value:-1},valueRange:{value:new t(0,1)},originTime:{value:new t(0,0)}},vertexShader:m(),fragmentShader:h()}),p=()=>{u.uniforms.pixelRatio.value=a(),u.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,i]=d(o);u.uniforms.originTime.value.set(r,i)},y=()=>{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=()=>{p(),g(),y()};v.add(r.on("valueRanges",y)),v.add(n.on("time",x)),v.add(n.on("value",p)),v.add(n.on("size",p)),v.add(o.on("loaded",y)),v.add(o.on("visible",y));const b=()=>{const e="down"===o.direction;u.uniforms.layerRange.value.set(e?o.max:o.min,e?o.min:o.max),u.uniforms.layerDirection.value=e?-1:1};return v.add(o.on("min",b)),v.add(o.on("max",b)),v.add(o.on("direction",b)),b(),g(),y(),v.add(o.on("invalidate",()=>{u.vertexShader=m(),u.fragmentShader=h(),u.needsUpdate=!0})),{material:u,dispose:()=>v.dispose()}}(c,m,u);p.add(x);const b=new r(1,1),w=new o(b,y,g.data.length);w.frustumCulled=!1;const S=function(e,o,r,i,t){const a=new v(o,e=>{const o=new n,r=e.v[1]-e.v[0],i=e.v[0]+.5*r,t=.5*(e.t[0]+e.t[1]),[a,l]=d(t);o.position.set(a,i,l);const s=e.t[1]-e.t[0];return o.scale.set(s,r,.1),o.updateMatrix(),o.matrix},t);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});u.selectionOptions.highlight&&p.add(c.on("selection",e=>{if(e.length>0){const o=S.getIndex(e[0]);y.uniforms.selection.value="number"==typeof o?o:-1}else y.uniforms.selection.value=-1}));return h.add(w),p.add(()=>h.remove(w)),p.disposeLater()}(c,h,m,u):(console.error("Layer not supported by ThreePointsTimeLayerLoader",{layer:c}),()=>{})}}
|
package/ticks/TimeTicksBar.d.ts
CHANGED
|
@@ -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;
|
package/ticks/TimeTicksBar.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
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}`}
|
package/ticks/YAxisTicks.d.ts
CHANGED
|
@@ -20,18 +20,35 @@ export type YAxisSettings = {
|
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
|
+
/** A horizontal line behind the layers, see TimeSliderLayerOptions.baseline. y is a fraction of the height from the bottom, like layer min/max. */
|
|
24
|
+
export type YAxisBaseline = {
|
|
25
|
+
y: number;
|
|
26
|
+
color: string;
|
|
27
|
+
width: number;
|
|
28
|
+
};
|
|
23
29
|
export declare class YAxisTicks {
|
|
30
|
+
/** In front of the layers: labels, and the grid lines unless they go behind. */
|
|
24
31
|
canvas: HTMLCanvasElement;
|
|
32
|
+
/** Behind the layers (between the slider's background and a transparent engine): baselines, and the grid lines with gridOrder 'behind'. */
|
|
33
|
+
behindCanvas: HTMLCanvasElement;
|
|
25
34
|
private font;
|
|
26
35
|
/** The labels' font for axes that don't set their own; undefined for the default 12px sans-serif. */
|
|
27
36
|
setFont(font: TimeSliderFont | undefined): void;
|
|
28
37
|
constructor();
|
|
38
|
+
/** Sizes a canvas' backing store to its own box in device pixels and clears it; the context then draws in CSS pixels. */
|
|
39
|
+
private prepare;
|
|
29
40
|
render(currents: (YAxisSettings & {
|
|
30
41
|
min: number;
|
|
31
42
|
max: number;
|
|
32
43
|
minValue: number;
|
|
33
44
|
maxValue: number;
|
|
34
|
-
|
|
45
|
+
/** 'down': the axis' min value at the band's top, values increasing downwards (layers hanging down). */
|
|
46
|
+
direction?: 'up' | 'down';
|
|
47
|
+
})[], options?: {
|
|
48
|
+
/** Draw the grid lines behind the layers (on behindCanvas) instead of in front. Labels stay in front. */
|
|
49
|
+
gridBehind?: boolean;
|
|
50
|
+
baselines?: YAxisBaseline[];
|
|
51
|
+
}): void;
|
|
35
52
|
}
|
|
36
53
|
export declare function getNiceTicks(min: number, max: number, targetCount?: number): number[];
|
|
37
54
|
export declare function niceNum(range: number, round: boolean): number;
|
package/ticks/YAxisTicks.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveFont as t,toCssFont as e}from"../common/Font.js";const
|
|
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.behindCanvas=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%",this.behindCanvas.style.position="absolute",this.behindCanvas.style.width="100%",this.behindCanvas.style.pointerEvents="none"}prepare(t,e){if(!t.parentElement)return;const i=t.getBoundingClientRect();if(i.width<=0||i.height<=0)return;const n=Math.round(i.width*e),o=Math.round(i.height*e);t.width===n&&t.height===o||(t.width=n,t.height=o);const s=t.getContext("2d");return s?(s.setTransform(1,0,0,1,0,0),s.clearRect(0,0,t.width,t.height),s.setTransform(e,0,0,e,0,0),{ctx:s,width:i.width,height:i.height}):void 0}render(o,s){const h=i(),l=this.prepare(this.canvas,h),a=this.prepare(this.behindCanvas,h);if(!l)return;const{ctx:r,width:c,height:d}=l,f=s?.gridBehind&&a?a.ctx:r;if(a)for(const t of s?.baselines??[]){const e=n(a.height*(1-t.y),t.width,h);a.ctx.beginPath(),a.ctx.moveTo(0,e.position),a.ctx.lineTo(a.width,e.position),a.ctx.lineWidth=e.width,a.ctx.strokeStyle=t.color,a.ctx.stroke()}for(const i of o){if(!1===i.visible)continue;const{min:o,max:s,minValue:l,maxValue:a}=i,m=d*(1-s),u=d*(1-o),g=u-m,p=getNiceTicks(l,a),x=i.format??(t=>t.toString()),v=i.style?.labels?.position??"right",y=i.style?.labels?.color??"#777",w=void 0!==i.style?.labels?.stroke,b=i.style?.labels?.stroke?.color??"#000",k=i.style?.labels?.stroke?.width??1,M=i.style?.lines?.color??"#7773",T=parseFloat(i.style?.lines?.width??"1");r.save(),r.fillStyle=y,r.font=e(t(i.style?.labels?.font,this.font)),r.textBaseline="middle",r.lineJoin="round",r.miterLimit=2,r.textAlign="left"===v?"right":"right"===v?"left":"center";for(const t of p){const e=(t-l)/(a-l);if(e<0||e>1)continue;const o="down"===i.direction?m+e*g:u-e*g;if(o<m+4||o>u-4)continue;const s=n(o,T,h);if(f.beginPath(),f.moveTo(0,s.position),f.lineTo(c,s.position),f.lineWidth=s.width,f.strokeStyle=M,f.stroke(),"none"!==v){const e=x(t),i=r.measureText(e).width;let n,s="center";"left"===v?(s="right",n=Math.max(4+i,4+i),n=Math.min(n,c/2-4)):"right"===v?(s="left",n=Math.min(c-4-i,c-4-i),n=Math.max(n,c/2+4)):(s="center",n=c/2),r.textAlign=s,w&&(r.lineWidth=2*k,r.strokeStyle=b,r.strokeText(e,n,o)),r.fillText(e,n,o)}}r.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,h=Math.ceil(e/o)*o,l=[];for(let i=s;i<=h+o/2;i+=o)i>=t&&i<=e&&l.push(parseFloat(i.toFixed(10)));return l}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)}
|