import { format } from 'd3-format' ; import { equalArrays , IEventListener , ISequence } from '../internal' ; import { Category , dialogAddons , SortByDefault , toolbar } from './annotations' ; import Column , { dirty , dirtyCaches , dirtyHeader , dirtyValues , groupRendererChanged , labelChanged , metaDataChanged , rendererTypeChanged , summaryRendererChanged , visibilityChanged , widthChanged , DEFAULT_COLOR , } from './Column' ; import { IDataRow , IGroup , ECompareValueType , IValueColumnDesc , ITypeFactory } from './interfaces' ; import { INumberColumn , EAdvancedSortMethod , INumberDesc , INumberFilter , IMappingFunction , IColorMappingFunction , IMapAbleColumn , } from './INumberColumn' ; import { restoreMapping , ScaleMappingFunction } from './MappingFunction' ; import { isMissingValue , isUnknown , missingGroup } from './missing' ; import type { dataLoaded } from './ValueColumn' ; import ValueColumn from './ValueColumn' ; import { noNumberFilter , isDummyNumberFilter , restoreNumberFilter , toCompareGroupValue , isEqualNumberFilter , isNumberIncluded , } from './internalNumber' ; import { integrateDefaults } from './internal' ; export declare type INumberColumnDesc = INumberDesc & IValueColumnDesc < number > ; asMemberOf event export declare function mappingChanged_NC ( previous , current ) ; asMemberOf event export declare function colorMappingChanged_NC ( previous , current ) ; asMemberOf event export declare function filterChanged_NC ( previous , current ) ; asMemberOf event export declare function sortMethodChanged_NC ( previous , current ) ; asMemberOf event export declare function groupingChanged_NC ( previous , current ) ; /**
 * a number column mapped from an original input scale to an output range
 */ @ toolbar ( 'rename' , 'clone' , 'sort' , 'sortBy' , 'groupBy' , 'sortGroupBy' , 'filterNumber' , 'colorMapped' , 'editMapping' ) @ dialogAddons ( 'sortGroup' , 'sortNumber' ) @ dialogAddons ( 'group' , 'groupNumber' ) @ Category ( 'number' ) @ SortByDefault ( 'descending' ) export default class NumberColumn extends ValueColumn < number > implements INumberColumn , IMapAbleColumn { static readonly EVENT_MAPPING_CHANGED = 'mappingChanged' ; static readonly EVENT_COLOR_MAPPING_CHANGED = 'colorMappingChanged' ; static readonly EVENT_FILTER_CHANGED = 'filterChanged' ; static readonly EVENT_SORTMETHOD_CHANGED = 'sortMethodChanged' ; static readonly EVENT_GROUPING_CHANGED = 'groupingChanged' ; private mapping : IMappingFunction ; private colorMapping : IColorMappingFunction ; private original : IMappingFunction ; private private currentFilter : INumberFilter = noNumberFilter ( ) ; private private readonly filterAccuracy : number = 0.001 ; private readonly numberFormat : ( n : number ) => string = format ( '.2f' ) ; private currentGroupThresholds : number [ ] = [ ] ; private groupSortMethod : EAdvancedSortMethod = EAdvancedSortMethod . median ; constructor ( id , desc , factory ) { super ( id , integrateDefaults ( desc , { groupRenderer : 'boxplot' , summaryRenderer : 'histogram' , } ) ) ; this . mapping = restoreMapping ( desc , factory ) ; this . original = this . mapping . clone ( ) ; this . colorMapping = factory . colorMappingFunction ( desc . colorMapping || desc . color ) ; if ( desc . numberFormat ) { this . numberFormat = format ( desc . numberFormat ) ; } if ( desc . filterAccuracy ) { this . filterAccuracy = desc . filterAccuracy ; } } getNumberFormat ( ) { return this . numberFormat ; } dump ( toDescRef ) { const r = super . dump ( toDescRef ) ; r . map = this . mapping . toJSON ( ) ; r . colorMapping = this . colorMapping . toJSON ( ) ; r . filter = isDummyNumberFilter ( this . currentFilter ) ? null : this . currentFilter ; r . groupSortMethod = this . groupSortMethod ; if ( this . currentGroupThresholds ) { r . stratifyThresholds = this . currentGroupThresholds ; } return r ; } restore ( dump , factory ) { super . restore ( dump , factory ) ; if ( dump . map ) { this . mapping = factory . mappingFunction ( dump . map ) ; } else if ( dump . domain ) { this . mapping = new ScaleMappingFunction ( dump . domain , 'linear' , dump . range || [ 0 , 1 ] ) ; } if ( dump . colorMapping ) { this . colorMapping = factory . colorMappingFunction ( dump . colorMapping ) ; } if ( dump . groupSortMethod ) { this . groupSortMethod = dump . groupSortMethod ; } if ( dump . filter ) { this . currentFilter = restoreNumberFilter ( dump . filter ) ; } if ( dump . stratifyThresholds ) { this . currentGroupThresholds = dump . stratifyThresholds ; } if ( dump . stratifyThreshholds ) { this . currentGroupThresholds = dump . stratifyThreshholds ; } } protected createEventList ( ) { return super . createEventList ( ) . concat ( [ NumberColumn . EVENT_MAPPING_CHANGED , NumberColumn . EVENT_COLOR_MAPPING_CHANGED , NumberColumn . EVENT_FILTER_CHANGED , NumberColumn . EVENT_SORTMETHOD_CHANGED , NumberColumn . EVENT_GROUPING_CHANGED , ] ) ; } on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) ; on ( type , listener ) { return super . on ( type as any , listener ) ; } getLabel ( row ) { if ( ( this . desc as any ) . numberFormat ) { const raw = this . getRawValue ( row ) ; if ( Number . isNaN ( raw ) ) { return 'NaN' ; } if ( ! isFinite ( raw ) ) { return raw . toString ( ) ; } return this . numberFormat ( raw ) ; } const v = super . getValue ( row ) ; if ( typeof v === 'number' ) { return this . numberFormat ( + v ) ; } return String ( v ) ; } getRange ( ) { return this . mapping . getRange ( this . numberFormat ) ; } getRawValue ( row ) { const v = super . getValue ( row ) ; if ( isMissingValue ( v ) ) { return NaN ; } return + v ; } getExportValue ( row , format ) { return format === 'json' ? this . getRawValue ( row ) : super . getExportValue ( row , format ) ; } getValue ( row ) { const v = this . getNumber ( row ) ; if ( Number . isNaN ( v ) ) { return null ; } return v ; } getNumber ( row ) { const v = this . getRawValue ( row ) ; if ( Number . isNaN ( v ) ) { return NaN ; } return this . mapping . apply ( v ) ; } iterNumber ( row ) { return [ this . getNumber ( row ) ] ; } iterRawNumber ( row ) { return [ this . getRawNumber ( row ) ] ; } getRawNumber ( row ) { return this . getRawValue ( row ) ; } toCompareValue ( row , valueCache ? ) { return valueCache != null ? valueCache : this . getNumber ( row ) ; } toCompareValueType ( ) { return ECompareValueType . FLOAT ; } toCompareGroupValue ( rows , _group , valueCache ? ) { return toCompareGroupValue ( rows , this , this . groupSortMethod , valueCache ) ; } toCompareGroupValueType ( ) { return ECompareValueType . FLOAT ; } getOriginalMapping ( ) { return this . original . clone ( ) ; } getMapping ( ) { return this . mapping . clone ( ) ; } setMapping ( mapping ) { if ( this . mapping . eq ( mapping ) ) { return ; } this . fire ( [ NumberColumn . EVENT_MAPPING_CHANGED , Column . EVENT_DIRTY_VALUES , Column . EVENT_DIRTY_CACHES , Column . EVENT_DIRTY_HEADER , Column . EVENT_DIRTY , ] , this . mapping . clone ( ) , ( this . mapping = mapping ) ) ; } getColor ( row ) { const v = this . getNumber ( row ) ; if ( Number . isNaN ( v ) ) { return DEFAULT_COLOR ; } return this . colorMapping . apply ( v ) ; } getColorMapping ( ) { return this . colorMapping . clone ( ) ; } setColorMapping ( mapping ) { if ( this . colorMapping . eq ( mapping ) ) { return ; } this . fire ( [ NumberColumn . EVENT_COLOR_MAPPING_CHANGED , Column . EVENT_DIRTY_VALUES , Column . EVENT_DIRTY_HEADER , Column . EVENT_DIRTY , ] , this . colorMapping . clone ( ) , ( this . colorMapping = mapping ) ) ; } isFiltered ( ) { return ! isDummyNumberFilter ( this . currentFilter ) ; } getFilter ( ) { return Object . assign ( { } , this . currentFilter ) ; } setFilter ( value ) { value = value || { min : Number . NEGATIVE_INFINITY , max : Number . POSITIVE_INFINITY , filterMissing : false } ; if ( isEqualNumberFilter ( value , this . currentFilter , this . filterAccuracy ) ) { return ; } const bak = this . getFilter ( ) ; this . currentFilter . min = isUnknown ( value . min ) ? Number . NEGATIVE_INFINITY : value . min ; this . currentFilter . max = isUnknown ( value . max ) ? Number . POSITIVE_INFINITY : value . max ; this . currentFilter . filterMissing = value . filterMissing ; this . fire ( [ NumberColumn . EVENT_FILTER_CHANGED , Column . EVENT_DIRTY_VALUES , Column . EVENT_DIRTY ] , bak , this . getFilter ( ) ) ; } param row returns { boolean } filter ( row ) { return isNumberIncluded ( this . currentFilter , this . getRawNumber ( row ) ) ; } clearFilter ( ) { const was = this . isFiltered ( ) ; this . setFilter ( null ) ; return was ; } getGroupThresholds ( ) { return this . currentGroupThresholds . slice ( ) ; } setGroupThresholds ( value ) { if ( equalArrays ( this . currentGroupThresholds , value ) ) { return ; } const bak = this . getGroupThresholds ( ) ; this . currentGroupThresholds = value . slice ( ) ; this . fire ( [ NumberColumn . EVENT_GROUPING_CHANGED , Column . EVENT_DIRTY_VALUES , Column . EVENT_DIRTY ] , bak , value ) ; } group ( row ) { const value = this . getRawNumber ( row ) ; if ( Number . isNaN ( value ) ) { return Object . assign ( { } , missingGroup ) ; } let threshold = this . currentGroupThresholds ; if ( threshold . length === 0 ) { const d = this . mapping . domain ; threshold = [ ( d [ 1 ] - d [ 0 ] ) / 2 ] ; } const thresholdIndex = threshold . findIndex ( ( t ) => value <= t ) ; switch ( thresholdIndex ) { case - 1 : return { name : `${ this . label } > ${ this . numberFormat ( threshold [ threshold . length - 1 ] ) }` , color : this . colorMapping . apply ( 1 ) , } ; case 0 : return { name : `${ this . label } <= ${ this . numberFormat ( threshold [ 0 ] ) }` , color : this . colorMapping . apply ( 0 ) , } ; default : return { name : `${ this . numberFormat ( threshold [ thresholdIndex - 1 ] ) } <= ${ this . label } <= ${ this . numberFormat ( threshold [ thresholdIndex ] ) }` , color : this . colorMapping . apply ( this . mapping . apply ( ( threshold [ thresholdIndex - 1 ] + threshold [ thresholdIndex ] ) / 2 ) ) , } ; } } getSortMethod ( ) { return this . groupSortMethod ; } setSortMethod ( sortMethod ) { if ( this . groupSortMethod === sortMethod ) { return ; } this . fire ( [ NumberColumn . EVENT_SORTMETHOD_CHANGED ] , this . groupSortMethod , ( this . groupSortMethod = sortMethod ) ) ; if ( ! this . isGroupSortedByMe ( ) . asc ) { this . toggleMyGroupSorting ( ) ; } } }