import { DeepNonNullable } from 'utility-types' ; import { ScaleType } from '../../../scales/constants' ; import { DataSeriesDatum } from './series' ; import { Fit , FitConfig } from './specs' ; import { datumXSortPredicate } from './stacked_series_utils' ; /**
 * Fit type that requires previous and/or next `non-nullable` values
 *
 */ type BoundingFit = Exclude < Fit , 'none' | 'explicit' > ; internal export type FullDataSeriesDatum = Omit < DataSeriesDatum , 'y1' | 'x' > & DeepNonNullable < Pick < DataSeriesDatum , 'y1' | 'x' > > ; internal export type WithIndex < T > = T & { fittingIndex : number ; } ; /**
 * Returns `[x, y1]` values for a given datum with `fittingIndex`
 *
 */ const getXYValues = ( { x , y1 , fittingIndex } ) : [ number , number ] => [ typeof x === 'string' ? fittingIndex : x , y1 , ] ; internal export const getValue = ( current , currentIndex , previous , next , type , endValue ? ) : DataSeriesDatum => { if ( previous !== null && type === Fit . Carry ) { const { y1 } = previous ; return { ... current , y1 , filled : { ... current . filled , y1 , } , } ; } if ( next !== null && type === Fit . Lookahead ) { const { y1 } = next ; return { ... current , y1 , filled : { ... current . filled , y1 , } , } ; } if ( previous !== null && next !== null ) { if ( type === Fit . Average ) { const y1 = ( previous . y1 + next . y1 ) / 2 ; return { ... current , y1 , filled : { ... current . filled , y1 , } , } ; } if ( current . x !== null && previous . x !== null && next . x !== null ) { const [ x1 , y1 ] = getXYValues ( previous ) ; const [ x2 , y2 ] = getXYValues ( next ) ; const currentX = typeof current . x === 'string' ? currentIndex : current . x ; if ( type === Fit . Nearest ) { const x1Delta = Math . abs ( currentX - x1 ) ; const x2Delta = Math . abs ( currentX - x2 ) ; const y1Delta = x1Delta > x2Delta ? y2 : y1 ; return { ... current , y1 : y1Delta , filled : { ... current . filled , y1 : y1Delta , } , } ; } if ( type === Fit . Linear ) { const linearInterpolatedY1 = previous . y1 + ( currentX - x1 ) * ( ( y2 - y1 ) / ( x2 - x1 ) ) ; return { ... current , y1 : linearInterpolatedY1 , filled : { ... current . filled , y1 : linearInterpolatedY1 , } , } ; } } } else if ( ( previous !== null || next !== null ) && ( type === Fit . Nearest || endValue === 'nearest' ) ) { const nearestY1 = previous !== null ? previous . y1 : next ! . y1 ; return { ... current , y1 : nearestY1 , filled : { ... current . filled , y1 : nearestY1 , } , } ; } if ( endValue === undefined || typeof endValue === 'string' ) { return current ; } return { ... current , y1 : endValue , filled : { ... current . filled , y1 : endValue , } , } ; } ; internal export const parseConfig = ( config ? ) : FitConfig => { if ( ! config ) { return { type : Fit . None , } ; } if ( typeof config === 'string' ) { return { type : config , } ; } if ( config . type === Fit . Explicit && config . value === undefined ) { return { type : Fit . None , } ; } return { type : config . type , value : config . type === Fit . Explicit ? config . value : undefined , endValue : config . endValue , } ; } ; internal export const fitFunction = ( data , fitConfig , xScaleType , sorted = false ) : DataSeriesDatum [ ] => { const { type , value , endValue } = parseConfig ( fitConfig ) ; if ( type === Fit . None ) { return data ; } if ( type === Fit . Zero ) { return data . map ( ( datum ) => ( { ... datum , y1 : datum . y1 === null ? 0 : datum . y1 , filled : { ... datum . filled , y1 : datum . y1 === null ? 0 : undefined , } , } ) ) ; } if ( type === Fit . Explicit ) { if ( value === undefined ) { return data ; } return data . map ( ( datum ) => ( { ... datum , y1 : datum . y1 === null ? value : datum . y1 , filled : { ... datum . filled , y1 : datum . y1 === null ? value : undefined , } , } ) ) ; } const sortedData = sorted || xScaleType === ScaleType . Ordinal ? data : data . slice ( ) . sort ( datumXSortPredicate ( xScaleType ) ) ; const newData = [ ] ; let previousNonNullDatum = null ; let nextNonNullDatum = null ; for ( let i = 0 ; i < sortedData . length ; i ++ ) { let j = i ; const currentValue = sortedData [ i ] ; if ( currentValue . y1 === null && nextNonNullDatum === null && ( type === Fit . Lookahead || type === Fit . Nearest || type === Fit . Average || type === Fit . Linear || endValue === 'nearest' ) ) { for ( j = i + 1 ; j < sortedData . length ; j ++ ) { const nextValue = sortedData [ j ] ; if ( nextValue . y1 !== null && nextValue . x !== null ) { nextNonNullDatum = { ... ( nextValue as FullDataSeriesDatum ) , fittingIndex : j , } ; break ; } } } const newValue = currentValue . y1 === null ? getValue ( currentValue , i , previousNonNullDatum , nextNonNullDatum , type , endValue ) : currentValue ; newData [ i ] = newValue ; if ( currentValue . y1 !== null && currentValue . x !== null ) { previousNonNullDatum = { ... ( currentValue as FullDataSeriesDatum ) , fittingIndex : i , } ; } if ( nextNonNullDatum !== null && nextNonNullDatum . x <= currentValue . x ) { nextNonNullDatum = null ; } } return newData ; } ;