import { ScaleType } from '../../../scales/constants' ; import { computeContinuousDataDomain , ContinuousDomain } from '../../../utils/domain' ; import { GroupId } from '../../../utils/ids' ; import { Logger } from '../../../utils/logger' ; import { getYNiceFromSpec , getYScaleTypeFromSpec } from '../scales/get_api_scales' ; import { ScaleConfigs } from '../state/selectors/get_api_scale_configs' ; import { getSpecDomainGroupId } from '../state/utils/spec' ; import { groupBy } from '../utils/group_data_series' ; import { DataSeries } from '../utils/series' ; import { BasicSeriesSpec , DomainPaddingUnit , SeriesScales , SeriesType , StackMode , YDomainRange } from '../utils/specs' ; import { YDomain } from './types' ; internal export type YBasicSeriesSpec = Pick < BasicSeriesSpec , 'id' | 'seriesType' | 'yScaleType' | 'groupId' | 'stackAccessors' | 'useDefaultGroupDomain' > & { stackMode ? : StackMode ; enableHistogramMode ? : boolean ; } ; internal export function mergeYDomain ( dataSeries , yScaleAPIConfig ) { const dataSeriesByGroupId = groupBy ( dataSeries , ( { spec } ) => getSpecDomainGroupId ( spec ) , true ) ; return dataSeriesByGroupId . reduce < YDomain [ ] > ( ( acc , groupedDataSeries ) => { const stacked = groupedDataSeries . filter ( ( { isStacked , isFiltered } ) => isStacked && ! isFiltered ) ; const nonStacked = groupedDataSeries . filter ( ( { isStacked , isFiltered } ) => ! isStacked && ! isFiltered ) ; const hasNonZeroBaselineTypes = groupedDataSeries . some ( ( { seriesType , isFiltered } ) => seriesType === SeriesType . Bar || ( seriesType === SeriesType . Area && ! isFiltered ) ) ; const domain = mergeYDomainForGroup ( stacked , nonStacked , hasNonZeroBaselineTypes , yScaleAPIConfig ) ; return domain ? [ ... acc , domain ] : acc ; } , [ ] ) ; } function mergeYDomainForGroup ( stacked , nonStacked , hasZeroBaselineSpecs , yScaleConfig ) { const dataSeries = [ ... stacked , ... nonStacked ] ; if ( dataSeries . length === 0 ) return null ; const [ { isStacked , stackMode , spec } ] = dataSeries ; const groupId = getSpecDomainGroupId ( spec ) ; const { customDomain , type , nice , desiredTickCount } = yScaleConfig [ groupId ] ; const newCustomDomain = customDomain ? { ... customDomain } : { min : NaN , max : NaN } ; const { paddingUnit , padding , constrainPadding } = newCustomDomain ; let mergedDomain ; if ( isStacked && stackMode === StackMode . Percentage ) { mergedDomain = computeContinuousDataDomain ( [ 0 , 1 ] , type , customDomain ) ; } else { const stackedDomain = computeYDomain ( stacked , hasZeroBaselineSpecs , type , newCustomDomain ) ; const nonStackedDomain = computeYDomain ( nonStacked , hasZeroBaselineSpecs , type , newCustomDomain ) ; mergedDomain = computeContinuousDataDomain ( [ ... stackedDomain , ... nonStackedDomain ] , type , newCustomDomain ) ; const [ computedDomainMin , computedDomainMax ] = mergedDomain ; if ( newCustomDomain && Number . isFinite ( newCustomDomain . min ) && Number . isFinite ( newCustomDomain . max ) ) { mergedDomain = [ newCustomDomain . min , newCustomDomain . max ] ; } else if ( newCustomDomain && Number . isFinite ( newCustomDomain . min ) ) { if ( newCustomDomain . min > computedDomainMax ) { Logger . warn ( `custom yDomain for ${ groupId } is invalid, custom min is greater than computed max.` ) ; mergedDomain = [ newCustomDomain . min , newCustomDomain . min ] ; } else { mergedDomain = [ newCustomDomain . min , computedDomainMax ] ; } } else if ( newCustomDomain && Number . isFinite ( newCustomDomain . max ) ) { if ( computedDomainMin > newCustomDomain . max ) { Logger . warn ( `custom yDomain for ${ groupId } is invalid, custom max is less than computed max.` ) ; mergedDomain = [ newCustomDomain . max , newCustomDomain . max ] ; } else { mergedDomain = [ computedDomainMin , newCustomDomain . max ] ; } } } return { type , nice , isBandScale : false , groupId , domain : mergedDomain , logBase : customDomain ?. logBase , logMinLimit : customDomain ?. logMinLimit , desiredTickCount , domainPixelPadding : paddingUnit === DomainPaddingUnit . Pixel ? padding : 0 , constrainDomainPadding : constrainPadding , } ; } function computeYDomain ( dataSeries , hasZeroBaselineSpecs , scaleType , customDomain ) { const yValues = new Set < any > ( ) ; dataSeries . forEach ( ( { data } ) => { for ( const datum of data ) { yValues . add ( datum . y1 ) ; if ( hasZeroBaselineSpecs && datum . y0 !== null ) yValues . add ( datum . y0 ) ; } } ) ; if ( yValues . size === 0 ) { return [ ] ; } const domainOptions = { ... customDomain , padding : 0 } ; return computeContinuousDataDomain ( [ ... yValues ] , scaleType , domainOptions ) ; } internal export function groupSeriesByYGroup ( specs ) { const specsByGroupIds = new Map < GroupId , { stackMode : StackMode | undefined ; stacked : YBasicSeriesSpec [ ] ; nonStacked : YBasicSeriesSpec [ ] ; } > ( ) ; const histogramEnabled = isHistogramEnabled ( specs ) ; specs . forEach ( ( spec ) => { const group = specsByGroupIds . get ( spec . groupId ) || { stackMode : undefined , stacked : [ ] , nonStacked : [ ] , } ; if ( isStackedSpec ( spec , histogramEnabled ) ) { group . stacked . push ( spec ) ; } else { group . nonStacked . push ( spec ) ; } if ( group . stackMode === undefined && spec . stackMode !== undefined ) { group . stackMode = spec . stackMode ; } if ( spec . stackMode !== undefined && group . stackMode !== undefined && group . stackMode !== spec . stackMode ) { Logger . warn ( `Is not possible to mix different stackModes, please align all stackMode on the same GroupId
      to the same mode. The default behaviour will be to use the first encountered stackMode on the series` ) ; } specsByGroupIds . set ( spec . groupId , group ) ; } ) ; return specsByGroupIds ; } internal export function isHistogramEnabled ( specs ) { return specs . some ( ( { seriesType , enableHistogramMode } ) => seriesType === SeriesType . Bar && enableHistogramMode ) ; } internal export function isStackedSpec ( spec , histogramEnabled ) { const isBarAndHistogram = spec . seriesType === SeriesType . Bar && histogramEnabled ; const hasStackAccessors = spec . stackAccessors && spec . stackAccessors . length > 0 ; return isBarAndHistogram || hasStackAccessors ; } internal export function coerceYScaleTypes ( series ) { const scaleTypes = new Set ( series . map ( ( s ) => getYScaleTypeFromSpec ( s . yScaleType ) ) ) ; const niceDomains = series . map ( ( s ) => getYNiceFromSpec ( s . yNice ) ) ; const type = scaleTypes . size === 1 ? scaleTypes . values ( ) . next ( ) . value : ScaleType . Linear ; return { type , nice : ! niceDomains . includes ( false ) } ; }