@protomaze/time 1.9.1 → 1.10.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/data/TimeSliderBush.d.ts +3 -0
- package/data/TimeSliderBush.js +1 -1
- package/data/histogram/TimeSliderDynamicHistogram.d.ts +20 -1
- package/data/histogram/TimeSliderDynamicHistogram.js +1 -1
- package/data/histogram/TimeSliderHistogram.d.ts +2 -0
- package/data/histogram/TimeSliderHistogram.js +1 -1
- package/layer/TimeSliderHistogramLayer.d.ts +9 -0
- package/layer/TimeSliderHistogramLayer.js +1 -1
- package/package.json +1 -1
package/data/TimeSliderBush.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export declare class TimeSliderBush<T extends TimeSliderElement = TimeSliderElem
|
|
|
7
7
|
private insertSafely;
|
|
8
8
|
add(element: T): void;
|
|
9
9
|
insert(element: T): this;
|
|
10
|
+
/** Bulk load (RBush builds the tree directly, without insert): the ids are registered here, so get/has/update/remove work for elements given to a model's constructor too. */
|
|
11
|
+
load(elements: readonly T[]): this;
|
|
12
|
+
clear(): this;
|
|
10
13
|
remove(element: T): this;
|
|
11
14
|
has(id: string): boolean;
|
|
12
15
|
get(id: string): T | undefined;
|
package/data/TimeSliderBush.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"rbush";import{getTimeArray as r,getValueArray as i}from"./TimeSliderElement.js";import{timeSliderRangeAsObject as t}from"./TimeSliderRange.js";export class TimeSliderBush extends e{constructor(){super(),this.idMap=new Map}insertSafely(e){return this.idMap.set(e.id,e),super.insert(e),this}add(e){this.insertSafely(e)}insert(e){return this.insertSafely(e),this}remove(e){return this.idMap.delete(e.id),super.remove(e,(e,r)=>e.id===r.id),this}has(e){return this.idMap.has(e)}get(e){return this.idMap.get(e)}searchTime(e){const r=t(e);return super.search({minX:r.startTime,maxX:r.endTime,minY:Number.MIN_VALUE,maxY:Number.MAX_VALUE})}searchRange(e){return super.search({minX:e.startTime,maxX:e.endTime,minY:e.minValue,maxY:e.maxValue})}toBBox(e){const[t,s]=r(e),[n
|
|
1
|
+
import e from"rbush";import{getTimeArray as r,getValueArray as i}from"./TimeSliderElement.js";import{timeSliderRangeAsObject as t}from"./TimeSliderRange.js";export class TimeSliderBush extends e{constructor(){super(),this.idMap=new Map}insertSafely(e){return this.idMap.set(e.id,e),super.insert(e),this}add(e){this.insertSafely(e)}insert(e){return this.insertSafely(e),this}load(e){return e.forEach(e=>this.idMap.set(e.id,e)),super.load(e),this}clear(){return this.idMap?.clear(),super.clear(),this}remove(e){return this.idMap.delete(e.id),super.remove(e,(e,r)=>e.id===r.id),this}has(e){return this.idMap.has(e)}get(e){return this.idMap.get(e)}searchTime(e){const r=t(e);return super.search({minX:r.startTime,maxX:r.endTime,minY:Number.MIN_VALUE,maxY:Number.MAX_VALUE})}searchRange(e){return super.search({minX:e.startTime,maxX:e.endTime,minY:e.minValue,maxY:e.maxValue})}toBBox(e){const[t,s]=r(e),[a,n]=i(e);return{minX:t,maxX:s,minY:a,maxY:n}}compareMinX(e,i){const[t]=r(e),[s]=r(i);return t-s}compareMinY(e,r){const[t]=i(e),[s]=i(r);return t-s}}
|
|
@@ -5,17 +5,36 @@ import { TimeSliderHistogram, TimeSliderHistogramOptions } from "./TimeSliderHis
|
|
|
5
5
|
export declare class TimeSliderDynamicHistogram<M extends TimeSliderModel = TimeSliderModel> extends TimeSliderHistogram<M> {
|
|
6
6
|
protected model: TimeSliderIntervalsModel;
|
|
7
7
|
protected content: M;
|
|
8
|
-
private
|
|
8
|
+
private boxIds;
|
|
9
9
|
private needsUpdate;
|
|
10
|
+
/** Only the stacking (from the cached bins) is out of date: after reclassify. */
|
|
11
|
+
private needsRestack;
|
|
12
|
+
private cache?;
|
|
10
13
|
private animationFrameId?;
|
|
11
14
|
private maxEncountered;
|
|
12
15
|
constructor(model: TimeSliderIntervalsModel, content: M, options?: TimeSliderHistogramOptions<M>);
|
|
13
16
|
protected startUpdating(timeSlider: TimeSlider): void;
|
|
14
17
|
private invalidate;
|
|
18
|
+
private classify;
|
|
19
|
+
private getElementWeight;
|
|
20
|
+
/**
|
|
21
|
+
* Re-classifies only the given elements (e.g. those whose filter state changed), using the bins
|
|
22
|
+
* of the last full pass: each moves its weight from its old class to its new one. The bars are
|
|
23
|
+
* re-stacked once per frame. Elements the last pass didn't see, and anything when no pass is
|
|
24
|
+
* cached yet, fall back to a full pass.
|
|
25
|
+
*/
|
|
26
|
+
reclassify(ids: Iterable<string>): void;
|
|
15
27
|
protected stopUpdating(): void;
|
|
16
28
|
protected onOptionsChanged(): void;
|
|
17
29
|
private getAmountOfBoxes;
|
|
18
30
|
private getSegments;
|
|
31
|
+
/** The full pass: classifies every element in view into the bins, caches that, and stacks the bars. */
|
|
19
32
|
private calculate;
|
|
33
|
+
/** Stacks the bars from the cached per-bin, per-class sums (averages when average is set). */
|
|
34
|
+
private stack;
|
|
35
|
+
/**
|
|
36
|
+
* Writes the stacked boxes into the model. Box ids are stable (bin start + class), so a box is only
|
|
37
|
+
* updated when it changed and only removed when it's gone: after a reclassify most boxes stay as they are.
|
|
38
|
+
*/
|
|
20
39
|
private updateBoxes;
|
|
21
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getTimeUnitInMs as t}from"../../common/TimeTools.js";import{TimeTicksBar as
|
|
1
|
+
import{getTimeUnitInMs as t}from"../../common/TimeTools.js";import{TimeTicksBar as i}from"../../ticks/TimeTicksBar.js";import{timeRangeOverlaps as e}from"../TimeSliderRange.js";import{TimeSliderHistogram as s}from"./TimeSliderHistogram.js";export class TimeSliderDynamicHistogram extends s{constructor(t,i,e){super(t,i,e),this.model=t,this.content=i,this.boxIds=new Set,this.needsUpdate=!1,this.needsRestack=!1,this.maxEncountered=1e3}startUpdating(t){this.invalidate();const i=i=>{i&&e(t.getTimeRange(),i.t)&&this.invalidate()};this.d.add(this.content.on("added",t=>i(t))),this.d.add(this.content.on("updated",t=>i(t.updated))),this.d.add(this.content.on("removed",()=>this.invalidate())),this.d.add(t.on("time",()=>this.invalidate())),this.d.add(t.on("size",()=>this.invalidate()));const s=()=>{this.needsUpdate?(this.calculate(),this.needsUpdate=!1,this.needsRestack=!1):this.needsRestack&&this.cache&&(this.stack(this.cache),this.needsRestack=!1),this.animationFrameId=requestAnimationFrame(s)};this.animationFrameId=requestAnimationFrame(s)}invalidate(){this.needsUpdate=!0,this.cache=void 0}classify(t){const i=this.classifier?this.classifier(t):void 0;return null===i?null:i||"#888"}getElementWeight(t){return this.weighted?"number"==typeof t.v?t.v:.5*(t.v[1]+t.v[0]):1}reclassify(t){const i=this.cache;if(i&&!this.needsUpdate){for(const s of t){const t=i.elements.get(s),a=this.content.get(s);if(!t){if(a&&this.timeSlider&&e(this.timeSlider.getTimeRange(),a.t))return void this.invalidate();continue}const o=a?this.classify(a):null;o!==t.classification&&(t.bins.forEach(e=>{null!==t.classification&&(n(i.sums[e],t.classification,-t.weight),n(i.counts[e],t.classification,-1)),null!==o&&(this.registerClassification(o),n(i.sums[e],o,t.weight),n(i.counts[e],o,1))}),t.classification=o)}this.needsRestack=!0}else this.invalidate()}stopUpdating(){this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=void 0),this.model.clear(),this.boxIds=new Set}onOptionsChanged(){this.invalidate()}getAmountOfBoxes(){return this.mode?.amount?this.mode?.amount:Math.round(this.timeSlider.size.width/(this.mode?.pixels||12))}getSegments(){const e=this.timeSlider.getTimeRange(),s=e.startTime,n=e.endTime;if(this.mode?.autoUnits){const e=this.timeSlider.ticksBar.cache.medium.spacing,s=this.timeSlider?.getTimeRange().duration/e,n=this.timeSlider?.size.width/s<(this.mode.autoUnitsMinPixels||8)?this.timeSlider.ticksBar.cache.high:this.timeSlider.ticksBar.cache.medium,a=this.mode.minUnit?t(this.mode.minUnit):0,o=this.mode.maxUnit?t(this.mode.maxUnit):Number.MAX_VALUE;return n.spacing>=a&&n.spacing<=o?n.segments:this.mode.minUnit&&n.spacing<a?i.splitInTimeUnits(this.timeSlider,this.mode.minUnit):this.mode.maxUnit&&n.spacing>o?i.splitInTimeUnits(this.timeSlider,this.mode.maxUnit):[]}{const t=e.duration/this.getAmountOfBoxes(),i=[];for(let e=s;e<n-.5*t;e+=t)i.push({start:e,end:e+t});return i}}calculate(){const t=this.getSegments(),i={segments:t,sums:[],counts:[],elements:new Map};t.forEach((t,e)=>{const s=this.content.dataInTimeRange([t.start,t.end]),a=new Map,o=new Map;s.forEach(t=>{const s=i.elements.get(t.id);if(s)return s.bins.push(e),void(null!==s.classification&&(n(a,s.classification,s.weight),n(o,s.classification,1)));const h=this.classify(t),c=this.getElementWeight(t);i.elements.set(t.id,{bins:[e],classification:h,weight:c}),null!==h&&(this.registerClassification(h),n(a,h,c),n(o,h,1))}),i.sums.push(a),i.counts.push(o)}),this.cache=i,this.stack(i)}stack(t){const i=t.segments;let e=0;const s=[],n=new Map;i.forEach((i,s)=>{let a=0;const o=new Map;t.sums[s].forEach((i,e)=>{const n=t.counts[s].get(e)??0;if(n<=0)return;const h=this.average?i/n:i;o.set(e,h),a+=h}),n.set(s,o),a>e&&(e=a)});const a=new Map,o=new Map,h=new Set,c=Array.from(this.classOrder);n.forEach((t,e)=>{const d=i[e];let r=0;const m=[];c.forEach(c=>{let l=t.get(c);if(l)a.set(c,{index:e,height:l});else if(this.average&&!h.has(c)&&a.has(c)){let t;if(o.has(c))t=o.get(c);else{for(let s=e;s<i.length;s++){const i=n.get(s);i&&i.has(c)&&(t={index:s,height:i.get(c)})}t&&o.set(c,t)}if(t){const e=a.get(c),s=i[e.index],n=i[t.index],o=(d.start-s.start)/(n.start-s.start);l=e.height+o*(t.height-e.height)}else h.add(c)}if(l){const t={height:l,...d,bottom:r,classification:c};s.push(t),m.push(t),r+=l}}),m.forEach(t=>t.percentage=t.height/r)}),this.updateBoxes(s,e)}updateBoxes(t,i){i>this.maxEncountered&&(this.maxEncountered=i+100),this.maxValue=i;const e=new Set;t.forEach(t=>{const i=t.start+"|"+t.classification;e.add(i);const s=this.inverted?[this.maxEncountered-t.bottom-t.height,this.maxEncountered-t.bottom]:[t.bottom,t.bottom+t.height],n=this.model.get(i);if(n){if(n.t[0]===t.start&&n.t[1]===t.end&&n.v[0]===s[0]&&n.v[1]===s[1]&&n.attributes?.classification===t.classification&&n.attributes?.percentage===t.percentage)return;this.model.update(i,{t:[t.start,t.end],v:s,attributes:{classification:t.classification,percentage:t.percentage}})}else this.model.add({id:i,t:[t.start,t.end],v:s,attributes:{classification:t.classification,percentage:t.percentage}})}),this.boxIds.forEach(t=>{e.has(t)||this.model.remove(t)}),this.boxIds=e}}function n(t,i,e){t.set(i,(t.get(i)??0)+e)}
|
|
@@ -53,6 +53,8 @@ export declare abstract class TimeSliderHistogram<M extends TimeSliderModel = Ti
|
|
|
53
53
|
stop(): void;
|
|
54
54
|
get classOrder(): string[];
|
|
55
55
|
set classOrder(newOrder: string[]);
|
|
56
|
+
/** Re-classifies the given elements (ids of the content model). This histogram does a full pass; TimeSliderDynamicHistogram re-bins only these. */
|
|
57
|
+
reclassify(ids: Iterable<string>): void;
|
|
56
58
|
protected registerClassification(classification: string): void;
|
|
57
59
|
get classifier(): TimeSliderHistogramClassifier<M> | undefined;
|
|
58
60
|
set classifier(newClassifier: TimeSliderHistogramClassifier<M> | undefined);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Disposables as e}from"../../common/Disposables.js";export class TimeSliderHistogram{constructor(t,s,i){this.model=t,this.content=s,this.d=new e,this.classOrderSet=new Set,this.started=!1,this.maxValue=1,this._classifier=i?.classifier,this.classOrderSet=new Set(i?.classOrder),this._average=!0===i?.average,this._weighted=!0===i?.weighted,this._inverted=!0===i?.inverted,this._mode=i?.mode||{pixels:12}}start(e){this.started||(this.started=!0,this.timeSlider=e,this.startUpdating(e))}stop(){this.started&&(this.started=!1,this.d.dispose(),this.d=new e,this.stopUpdating())}get classOrder(){return Array.from(this.classOrderSet)}set classOrder(e){this.classOrderSet=new Set(e),this.onOptionsChanged("classOrder")}registerClassification(e){this.classOrderSet.add(e)}get classifier(){return this._classifier}set classifier(e){this._classifier=e,this.onOptionsChanged("classifier")}get mode(){return this._mode}set mode(e){JSON.stringify(e)!==JSON.stringify(this._mode)&&(this._mode=e,this.onOptionsChanged("mode"))}get average(){return this._average}set average(e){e!==this._average&&(this._average=e,this.onOptionsChanged("average"))}get inverted(){return this._inverted}set inverted(e){e!==this._inverted&&(this._inverted=e,this.onOptionsChanged("inverted"))}get weighted(){return this._weighted}set weighted(e){e!==this._weighted&&(this._weighted=e,this.onOptionsChanged("weighted"))}checkMaxValue(){if(this.timeSlider){let e=0;this.model.dataInTimeRange(this.timeSlider.getTimeRange()).forEach(t=>{t.v[1]>e&&(e=t.v[1])}),this.maxValue=e}}}
|
|
1
|
+
import{Disposables as e}from"../../common/Disposables.js";export class TimeSliderHistogram{constructor(t,s,i){this.model=t,this.content=s,this.d=new e,this.classOrderSet=new Set,this.started=!1,this.maxValue=1,this._classifier=i?.classifier,this.classOrderSet=new Set(i?.classOrder),this._average=!0===i?.average,this._weighted=!0===i?.weighted,this._inverted=!0===i?.inverted,this._mode=i?.mode||{pixels:12}}start(e){this.started||(this.started=!0,this.timeSlider=e,this.startUpdating(e))}stop(){this.started&&(this.started=!1,this.d.dispose(),this.d=new e,this.stopUpdating())}get classOrder(){return Array.from(this.classOrderSet)}set classOrder(e){this.classOrderSet=new Set(e),this.onOptionsChanged("classOrder")}reclassify(e){this.onOptionsChanged("classifier")}registerClassification(e){this.classOrderSet.add(e)}get classifier(){return this._classifier}set classifier(e){this._classifier=e,this.onOptionsChanged("classifier")}get mode(){return this._mode}set mode(e){JSON.stringify(e)!==JSON.stringify(this._mode)&&(this._mode=e,this.onOptionsChanged("mode"))}get average(){return this._average}set average(e){e!==this._average&&(this._average=e,this.onOptionsChanged("average"))}get inverted(){return this._inverted}set inverted(e){e!==this._inverted&&(this._inverted=e,this.onOptionsChanged("inverted"))}get weighted(){return this._weighted}set weighted(e){e!==this._weighted&&(this._weighted=e,this.onOptionsChanged("weighted"))}checkMaxValue(){if(this.timeSlider){let e=0;this.model.dataInTimeRange(this.timeSlider.getTimeRange()).forEach(t=>{t.v[1]>e&&(e=t.v[1])}),this.maxValue=e}}}
|
|
@@ -17,6 +17,15 @@ export declare class TimeSliderHistogramLayer<M extends TimeSliderModel = TimeSl
|
|
|
17
17
|
maxClassifications?: number;
|
|
18
18
|
_classificationColors: string[];
|
|
19
19
|
constructor(content: M, options?: TimeSliderHistogramLayerOptions<M>);
|
|
20
|
+
/**
|
|
21
|
+
* Re-classifies only these elements of the content model (ids, or elements with an id), e.g. those whose
|
|
22
|
+
* filter state changed, instead of every element: each moves from its old class to the one the classifier
|
|
23
|
+
* now gives it, and the bars are re-stacked once per frame. A new classifier (or style change) still does a
|
|
24
|
+
* full pass. Dynamic (non-fixed) histograms only re-bin these; fixed ones do a full pass.
|
|
25
|
+
*/
|
|
26
|
+
reclassify(elementsOrIds: Iterable<string | {
|
|
27
|
+
id: string;
|
|
28
|
+
}>): void;
|
|
20
29
|
getClassification(element: TimeSliderInterval<TimeSliderHistogramAttributes>, v: number): string | undefined;
|
|
21
30
|
set classificationColors(ccs: string[]);
|
|
22
31
|
get classificationColors(): string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Disposables as s}from"../common/Disposables.js";import{TimeSliderDynamicHistogram as i}from"../data/histogram/TimeSliderDynamicHistogram.js";import{TimeSliderSplittingFixedHistogram as t}from"../data/histogram/TimeSliderSplittingFixedHistogram.js";import{TimeSliderIntervalsModel as e}from"../data/TimeSliderIntervalsModel.js";import{TimeSliderIntervalsLayer as
|
|
1
|
+
import{Disposables as s}from"../common/Disposables.js";import{TimeSliderDynamicHistogram as i}from"../data/histogram/TimeSliderDynamicHistogram.js";import{TimeSliderSplittingFixedHistogram as t}from"../data/histogram/TimeSliderSplittingFixedHistogram.js";import{TimeSliderIntervalsModel as e}from"../data/TimeSliderIntervalsModel.js";import{TimeSliderIntervalsLayer as o}from"./TimeSliderIntervalsLayer.js";export class TimeSliderHistogramLayer extends o{constructor(i,t){super(new e,t),this.content=i,this.d=new s;const o=this.createHistogram({classifier:t?.classifier,...t?.style});this.histogram=o,this.maxClassifications=t?.maxClassifications,this._classificationColors=t?.classificationColors||[],t?.classificationIsColor&&!this.fixedBoxes&&(this.styler=s=>{const i=s.attributes?.classification;return i?{fill:{color:i},border:{color:i}}:void 0}),t?.classifier&&!t.labeler&&(this.fixedBoxes?t.classificationIsColor?this.labeler=(s,i,t)=>Math.abs(s.v[0]-s.v[1])+" TODO use split":this.labeler=s=>{const i=Math.abs(s.v[0]-s.v[1]);return s.attributes?.classification+": "+i+" TODO use split"}:t.classificationIsColor?this.labeler=(s,i,t)=>Math.abs(s.v[0]-s.v[1])+"":this.labeler=s=>{const i=Math.abs(s.v[0]-s.v[1]);return s.attributes?.classification+": "+i}),this.d.add(this.on("loaded",s=>{s?o.start(this.timeSlider):o.stop()})),this.d.add(this.on("invalidateAll",()=>{o.average=!0===this.style.average,o.weighted=!0===this.style.weighted,o.inverted=!0===this.style.inverted,this.style.mode&&(o.mode=this.style.mode),o.classifier=this.style.classifier,this.style.classOrder&&(o.classOrder=this.style.classOrder)}))}reclassify(s){const i=[];for(const t of s)i.push("string"==typeof t?t:t.id);this.histogram.reclassify(i)}getClassification(s,i){const t=s.v[1]-s.v[0],e=s.attributes?.splits||[1],o=i/t;let a=0;for(let s=0;s<e.length;s++)if(a+=e[s],o<a)return this.histogram.classOrder[s]}set classificationColors(s){this._classificationColors=s,this.invalidateAll()}get classificationColors(){return this._classificationColors}createHistogram(s){return this.fixedBoxes?new t(this.model,this.content,s):new i(this.model,this.content,s)}get fixedBoxes(){return void 0!==this.style.mode?.fixed||void 0!==this.style.mode?.fixedUnit}dispose(){this.model.clear(),this.d.dispose()}getDefaultStyle(){return{}}}
|