import { abortAbleAll , IAbortAblePromise } from 'lineupengine' ; import { ANOTHER_ROUND } from '../internal/scheduler' ; import { IForEachAble , lazySeq , boxplotBuilder , categoricalStatsBuilder , categoricalValueCacheBuilder , dateStatsBuilder , dateValueCacheBuilder , IAdvancedBoxPlotData , ICategoricalStatistics , IDateStatistics , IStatistics , numberStatsBuilder , dateValueCache2Value , categoricalValueCache2Value , joinIndexArrays , IBuilder , ISequence , IStringStatistics , stringStatsBuilder , } from '../internal' ; import { CategoricalColumn , Column , ICompareValue , DateColumn , ICategoricalLikeColumn , IDataRow , IDateColumn , IGroup , ImpositionCompositeColumn , IndicesArray , INumberColumn , NumberColumn , OrdinalColumn , Ranking , UIntTypedArray , ICategory , StringColumn , isMapAbleColumn , } from '../model' ; import type { IRenderTask , IRenderTasks } from '../renderer' ; import type { CompareLookup } from './sort' ; /**
 * a render task that is already resolved
 */ export class TaskNow < T > implements IRenderTask < T > { constructor ( public readonly v ) { } then < U = void > ( onfullfilled ) { return onfullfilled ( this . v ) ; } } /**
 * factory function for
 */ export function taskNow < T > ( v ) { return new TaskNow ( v ) ; } /**
 * a render task based on an abortable promise
 */ export class TaskLater < T > implements IRenderTask < T > { constructor ( public readonly v ) { } then < U = void > ( onfullfilled ) { return this . v . then ( onfullfilled ) ; } } export function taskLater < T > ( v ) { return new TaskLater ( v ) ; } /**
 * similar to Promise.all
 */ export function tasksAll < T > ( tasks ) { if ( tasks . every ( ( t ) => t instanceof TaskNow ) ) { return taskNow ( tasks . map ( ( d ) => ( d as TaskNow < T > ) . v ) ) ; } return taskLater ( abortAbleAll ( ( tasks as ( TaskNow < T > | TaskLater < T > ) [ ] ) . map ( ( d ) => d . v ) ) ) ; } export interface IRenderTaskExecutor extends IRenderTasks { setData ( data ) : void ; dirtyColumn ( col , type ) : void ; dirtyRanking ( ranking , type ) : void ; groupCompare ( ranking , group , rows ) : IRenderTask < ICompareValue [ ] > ; preCompute ( ranking , groups , maxDataIndex ) : void ; preComputeCol ( col ) : void ; preComputeData ( ranking ) : void ; copyData2Summary ( ranking ) : void ; copyCache ( col , from ) : void ; sort ( ranking , group , indices , singleCall , maxDataIndex , lookups ? ) : Promise < IndicesArray > ; terminate ( ) : void ; valueCache ( col ) : undefined | ( ( dataIndex : number ) => any ) ; } export class MultiIndices { private _joined : IndicesArray | null = null ; constructor ( public readonly indices , private readonly maxDataIndex ) { } get joined ( ) { if ( this . indices . length === 1 ) { return this . indices [ 0 ] ; } if ( this . indices . length === 0 ) { return new Uint8Array ( 0 ) ; } if ( this . _joined ) { return this . _joined ; } return ( this . _joined = joinIndexArrays ( this . indices , this . maxDataIndex ) ) ; } } /**
 * number of data points to build per iteration / chunk
 */ const CHUNK_SIZE = 100 ; export interface ARenderTaskOptions { stringTopNCount : number | readonly string [ ] ; } export class ARenderTasks { protected readonly valueCacheData = new Map < string , Float32Array | UIntTypedArray | Int32Array | Float64Array | readonly string [ ] > ( ) ; protected readonly byIndex = ( i ) => this . data [ i ] ; protected readonly options : ARenderTaskOptions ; protected data : IDataRow [ ] = [ ] ; constructor ( options = { } ) { this . options = Object . assign ( { stringTopNCount : 10 , } , options ) ; } protected byOrder ( indices ) { return lazySeq ( indices ) . map ( this . byIndex ) ; } protected byOrderAcc < T > ( indices , acc ) { return lazySeq ( indices ) . map ( ( i ) => acc ( this . data [ i ] ) ) ; } param builder param order param acc param build private builder < T , BR , B extends { push : ( v : T ) => void ; build : ( ) => BR ; } , R = BR > ( builder , order , acc , build ? ) { let i = 0 ; const nextData = ( currentChunkSize = CHUNK_SIZE ) => { let chunkCounter = currentChunkSize ; const data = this . data ; for ( ; i < data . length && chunkCounter > 0 ; ++ i , -- chunkCounter ) { builder . push ( acc ( i ) ) ; } if ( i < data . length ) { return ANOTHER_ROUND ; } return { done : true , value : build ? build ( builder . build ( ) ) : ( builder . build ( ) as unknown as R ) , } ; } ; let o = 0 ; const orders = order instanceof MultiIndices ? order . indices : [ order ] ; const nextOrder = ( currentChunkSize = CHUNK_SIZE ) => { let chunkCounter = currentChunkSize ; while ( o < orders . length ) { const actOrder = orders [ o ] ! ; for ( ; i < actOrder . length && chunkCounter > 0 ; ++ i , -- chunkCounter ) { builder . push ( acc ( actOrder [ i ] ) ) ; } if ( i < actOrder . length ) { return ANOTHER_ROUND ; } o ++ ; i = 0 ; } return { done : true , value : build ? build ( builder . build ( ) ) : ( builder . build ( ) as unknown as R ) , } ; } ; return { next : order == null ? nextData : nextOrder } ; } private builderForEach < T , BR , B extends { pushAll : ( v : IForEachAble < T > ) => void ; build : ( ) => BR ; } , R = BR > ( builder , order , acc , build ? ) { return this . builder ( { push : builder . pushAll , build : builder . build , } , order , acc , build ) ; } protected boxplotBuilder < R = IAdvancedBoxPlotData > ( order , col , raw ? , build ? ) { const b = boxplotBuilder ( ) ; return this . numberStatsBuilder ( b , order , col , raw , build ) ; } protected resolveDomain ( col , raw ) { const domain = raw && isMapAbleColumn ( col ) ? col . getMapping ( ) . domain : [ 0 , 1 ] ; return [ domain [ 0 ] , domain [ domain . length - 1 ] ] ; } protected statsBuilder < R = IStatistics > ( order , col , numberOfBins , raw ? , build ? ) { const b = numberStatsBuilder ( this . resolveDomain ( col , raw ?? false ) , numberOfBins ) ; return this . numberStatsBuilder ( b , order , col , raw , build ) ; } private numberStatsBuilder < R , B extends IBuilder < number , BR > , BR > ( b , order , col , raw ? , build ? ) { if ( col instanceof NumberColumn || col instanceof OrdinalColumn || col instanceof ImpositionCompositeColumn ) { const key = raw ? `${ col . id }:r` : col . id ; const dacc = raw ? ( i ) => col . getRawNumber ( this . data [ i ] ) : ( i ) => col . getNumber ( this . data [ i ] ) ; if ( order == null && ! this . valueCacheData . has ( key ) ) { const vs = new Float32Array ( this . data . length ) ; let i = 0 ; return this . builder ( { push : ( v ) => { b . push ( v ) ; vs [ i ++ ] = v ; } , build : ( ) => { this . setValueCacheData ( key , vs ) ; return b . build ( ) ; } , } , null , dacc , build ) ; } const cache = this . valueCacheData . get ( key ) as UIntTypedArray ; const acc = cache ? ( i ) => cache [ i ] : dacc ; return this . builder ( b , order , acc , build ) ; } const acc = raw ? ( i ) => col . iterRawNumber ( this . data [ i ] ) : ( i ) => col . iterNumber ( this . data [ i ] ) ; return this . builderForEach ( b , order , acc , build ) ; } protected dateStatsBuilder < R = IDateStatistics > ( order , col , template ? , build ? ) { const b = dateStatsBuilder ( template ) ; if ( col instanceof DateColumn ) { if ( order == null ) { const vs = dateValueCacheBuilder ( this . data . length ) ; return this . builder ( { push : ( v ) => { b . push ( v ) ; vs . push ( v ) ; } , build : ( ) => { this . setValueCacheData ( col . id , vs . cache ) ; return b . build ( ) ; } , } , null , ( i ) => col . getDate ( this . data [ i ] ) , build ) ; } const cache = this . valueCacheData . get ( col . id ) as UIntTypedArray ; const acc = cache ? ( i ) => dateValueCache2Value ( cache [ i ] ) : ( i ) => col . getDate ( this . data [ i ] ) ; return this . builder ( b , order , acc , build ) ; } return this . builderForEach ( b , order , ( i ) => col . iterDate ( this . data [ i ] ) , build ) ; } protected categoricalStatsBuilder < R = ICategoricalStatistics > ( order , col , build ? ) { const b = categoricalStatsBuilder ( col . categories ) ; if ( col instanceof CategoricalColumn || col instanceof OrdinalColumn ) { if ( order == null ) { const vs = categoricalValueCacheBuilder ( this . data . length , col . categories ) ; return this . builder ( { push : ( v ) => { b . push ( v ) ; vs . push ( v ) ; } , build : ( ) => { this . setValueCacheData ( col . id , vs . cache ) ; return b . build ( ) ; } , } , null , ( i ) => col . getCategory ( this . data [ i ] ) , build ) ; } const cache = this . valueCacheData . get ( col . id ) as UIntTypedArray ; const acc = cache ? ( i ) => categoricalValueCache2Value ( cache [ i ] , col . categories ) : ( i ) => col . getCategory ( this . data [ i ] ) ; return this . builder ( b , order , acc , build ) ; } return this . builderForEach ( b , order , ( i ) => col . iterCategory ( this . data [ i ] ) , build ) ; } protected stringStatsBuilder < R = IStringStatistics > ( order , col , topN ? , build ? ) { const b = stringStatsBuilder ( topN ?? this . options . stringTopNCount ) ; if ( order == null ) { let i = 0 ; const vs = Array ( this . data . length ) . fill ( null ) ; return this . builder ( { push : ( v ) => { b . push ( v ) ; vs [ i ++ ] = v ; } , build : ( ) => { this . setValueCacheData ( col . id , vs ) ; return b . build ( ) ; } , } , null , ( i ) => col . getValue ( this . data [ i ] ) , build ) ; } return this . builder ( b , order , ( i ) => col . getValue ( this . data [ i ] ) , build ) ; } dirtyColumn ( col , type ) { if ( type !== 'data' ) { return ; } this . valueCacheData . delete ( col . id ) ; this . valueCacheData . delete ( `${ col . id }:r` ) ; } protected setValueCacheData ( key , value ) { if ( value == null ) { this . valueCacheData . delete ( key ) ; } else { this . valueCacheData . set ( key , value ) ; } } valueCache ( col ) { const v = this . valueCacheData . get ( col . id ) ; if ( ! v ) { return undefined ; } if ( col instanceof DateColumn ) { return ( dataIndex ) => dateValueCache2Value ( v [ dataIndex ] as number ) ; } if ( col instanceof CategoricalColumn || col instanceof OrdinalColumn ) { return ( dataIndex ) => categoricalValueCache2Value ( v [ dataIndex ] as number , col . categories ) ; } return ( dataIndex ) => v [ dataIndex ] ; } }