import { Color } from '../../../../common/colors' ; import { getPredicateFn , Predicate } from '../../../../common/predicate' ; import { SeriesIdentifier , SeriesKey } from '../../../../common/series_id' ; import { Scale } from '../../../../scales' ; import { SettingsSpec , TickFormatter } from '../../../../specs' ; import { TextMeasure } from '../../../../utils/bbox/canvas_text_bbox_calculator' ; import { isUniqueArray , mergePartial , Rotation } from '../../../../utils/common' ; import { CurveType } from '../../../../utils/curves' ; import { Dimensions , Size } from '../../../../utils/dimensions' ; import { AreaGeometry , BarGeometry , BubbleGeometry , LineGeometry , PerPanel , PointGeometry , } from '../../../../utils/geometry' ; import { GroupId , SpecId } from '../../../../utils/ids' ; import { getRenderingCompareFn } from '../../../../utils/series_sort' ; import { ColorConfig , Theme } from '../../../../utils/themes/theme' ; import { XDomain } from '../../domains/types' ; import { mergeXDomain } from '../../domains/x_domain' ; import { isStackedSpec , mergeYDomain } from '../../domains/y_domain' ; import { renderArea } from '../../rendering/area' ; import { renderBars } from '../../rendering/bars' ; import { renderBubble } from '../../rendering/bubble' ; import { renderLine } from '../../rendering/line' ; import { defaultXYSeriesSort } from '../../utils/default_series_sort_fn' ; import { fillSeries } from '../../utils/fill_series' ; import { groupBy } from '../../utils/group_data_series' ; import { IndexedGeometryMap } from '../../utils/indexed_geometry_map' ; import { computeXScale , computeYScales } from '../../utils/scales' ; import { DataSeries , getDataSeriesFromSpecs , getFormattedDataSeries , getSeriesKey , isBandedSpec , } from '../../utils/series' ; import { AxisSpec , BasicSeriesSpec , Fit , FitConfig , HistogramModeAlignment , HistogramModeAlignments , isAreaSeriesSpec , isBarSeriesSpec , isBubbleSeriesSpec , isLineSeriesSpec , } from '../../utils/specs' ; import { SmallMultipleScales } from '../selectors/compute_small_multiple_scales' ; import { ScaleConfigs } from '../selectors/get_api_scale_configs' ; import { SmallMultiplesGroupBy } from '../selectors/get_specs' ; import { isHorizontalRotation } from './common' ; import { getAxesSpecForSpecId , getSpecDomainGroupId , getSpecsById } from './spec' ; import { ComputedGeometries , GeometriesCounts , SeriesDomainsAndData , Transform } from './types' ; internal param dataSeries export function getCustomSeriesColors ( dataSeries ) { const updatedCustomSeriesColors = new Map < SeriesKey , Color > ( ) ; const counters = new Map < SpecId , number > ( ) ; dataSeries . forEach ( ( ds ) => { const { spec , specId } = ds ; const dataSeriesKey = { specId : ds . specId , yAccessor : ds . yAccessor , splitAccessors : ds . splitAccessors , smVerticalAccessorValue : undefined , smHorizontalAccessorValue : undefined , } ; const seriesKey = getSeriesKey ( dataSeriesKey , ds . groupId ) ; if ( ! spec || ! spec . color ) { return ; } let color ; if ( ! color && spec . color ) { if ( typeof spec . color === 'string' ) { color = spec . color ; } else { const counter = counters . get ( specId ) || 0 ; color = Array . isArray ( spec . color ) ? spec . color [ counter % spec . color . length ] : spec . color ( ds ) ; counters . set ( specId , counter + 1 ) ; } } if ( color ) { updatedCustomSeriesColors . set ( seriesKey , color ) ; } } ) ; return updatedCustomSeriesColors ; } internal export function computeSeriesDomains ( seriesSpecs , scaleConfigs , deselectedDataSeries = [ ] , settingsSpec ? , smallMultiples ? ) { const orderOrdinalBinsBy = settingsSpec ?. orderOrdinalBinsBy ; const { dataSeries , xValues , fallbackScale , smHValues , smVValues } = getDataSeriesFromSpecs ( seriesSpecs , deselectedDataSeries , orderOrdinalBinsBy , smallMultiples ) ; const xDomain = mergeXDomain ( scaleConfigs . x , xValues , fallbackScale ) ; const filledDataSeries = fillSeries ( dataSeries , xValues , xDomain . type ) ; const seriesSortFn = getRenderingCompareFn ( ( a , b ) => { return defaultXYSeriesSort ( a as DataSeries , b as DataSeries ) ; } ) ; const formattedDataSeries = getFormattedDataSeries ( seriesSpecs , filledDataSeries , xValues , xDomain . type ) . sort ( seriesSortFn ) ; const yDomains = mergeYDomain ( formattedDataSeries , scaleConfigs . y ) ; const horizontalPredicate = smallMultiples ?. horizontal ?. sort ?? Predicate . DataIndex ; const smHDomain = [ ... smHValues ] . sort ( getPredicateFn ( horizontalPredicate ) ) ; const verticalPredicate = smallMultiples ?. vertical ?. sort ?? Predicate . DataIndex ; const smVDomain = [ ... smVValues ] . sort ( getPredicateFn ( verticalPredicate ) ) ; return { xDomain , yDomains , smHDomain , smVDomain , formattedDataSeries , } ; } internal export function computeSeriesGeometries ( seriesSpecs , { xDomain , yDomains , formattedDataSeries : nonFilteredDataSeries } , seriesColorMap , chartTheme , { rotation : chartRotation } , axesSpecs , smallMultiplesScales , enableHistogramMode , fallbackTickFormatter , measureText ) { const chartColors = chartTheme . colors ; const formattedDataSeries = nonFilteredDataSeries . filter ( ( { isFiltered } ) => ! isFiltered ) ; const barDataSeries = formattedDataSeries . filter ( ( { spec } ) => isBarSeriesSpec ( spec ) ) ; const dataSeriesGroupedByPanel = groupBy ( barDataSeries , [ 'smVerticalAccessorValue' , 'smHorizontalAccessorValue' ] , false ) ; const barIndexByPanel = Object . keys ( dataSeriesGroupedByPanel ) . reduce < Record < string , string [ ] > > ( ( acc , panelKey ) => { const panelBars = dataSeriesGroupedByPanel [ panelKey ] ; const barDataSeriesByBarIndex = groupBy ( panelBars , ( d ) => getBarIndexKey ( d , enableHistogramMode ) , false ) ; acc [ panelKey ] = Object . keys ( barDataSeriesByBarIndex ) ; return acc ; } , { } ) ; const { horizontal , vertical } = smallMultiplesScales ; const yScales = computeYScales ( { yDomains , range : [ isHorizontalRotation ( chartRotation ) ? vertical . bandwidth : horizontal . bandwidth , 0 ] , } ) ; const computedGeoms = renderGeometries ( formattedDataSeries , xDomain , yScales , vertical , horizontal , barIndexByPanel , seriesSpecs , seriesColorMap , chartColors . defaultVizColor , axesSpecs , chartTheme , enableHistogramMode , chartRotation , fallbackTickFormatter , measureText ) ; const totalBarsInCluster = Object . values ( barIndexByPanel ) . reduce ( ( acc , curr ) => Math . max ( acc , curr . length ) , 0 ) ; const xScale = computeXScale ( { xDomain , totalBarsInCluster , range : [ 0 , isHorizontalRotation ( chartRotation ) ? horizontal . bandwidth : vertical . bandwidth ] , barsPadding : enableHistogramMode ? chartTheme . scales . histogramPadding : chartTheme . scales . barsPadding , enableHistogramMode , } ) ; return { scales : { xScale , yScales } , ... computedGeoms } ; } internal export function setBarSeriesAccessors ( isHistogramMode , seriesSpecs ) { if ( isHistogramMode ) { for ( const [ , spec ] of seriesSpecs ) { if ( isBarSeriesSpec ( spec ) ) spec . stackAccessors = [ ... ( spec . stackAccessors || spec . yAccessors ) , ... ( spec . splitSeriesAccessors || [ ] ) ] ; } } } internal export function isHistogramModeEnabled ( seriesSpecs ) { return seriesSpecs . some ( ( spec ) => isBarSeriesSpec ( spec ) && spec . enableHistogramMode ) ; } internal export function computeXScaleOffset ( xScale , enableHistogramMode , histogramModeAlignment = HistogramModeAlignments . Start ) { if ( ! enableHistogramMode ) { return 0 ; } const { bandwidth , barsPadding } = xScale ; const band = bandwidth / ( 1 - barsPadding ) ; const halfPadding = ( band - bandwidth ) / 2 ; const startAlignmentOffset = bandwidth / 2 + halfPadding ; switch ( histogramModeAlignment ) { case HistogramModeAlignments . Center : return 0 ; case HistogramModeAlignments . End : return - startAlignmentOffset ; default : return startAlignmentOffset ; } } function renderGeometries ( dataSeries , xDomain , yScales , smVScale , smHScale , barIndexOrderPerPanel , seriesSpecs , seriesColorsMap , defaultColor , axesSpecs , chartTheme , enableHistogramMode , chartRotation , fallBackTickFormatter , measureText ) { const len = dataSeries . length ; let i ; const points = [ ] ; const bars = [ ] ; const areas = [ ] ; const lines = [ ] ; const bubbles = [ ] ; const geometriesIndex = new IndexedGeometryMap ( ) ; const isMixedChart = isUniqueArray ( seriesSpecs , ( { seriesType } ) => seriesType ) && seriesSpecs . length > 1 ; const geometriesCounts = { points : 0 , bars : 0 , areas : 0 , areasPoints : 0 , lines : 0 , linePoints : 0 , bubbles : 0 , bubblePoints : 0 , } ; const barsPadding = enableHistogramMode ? chartTheme . scales . histogramPadding : chartTheme . scales . barsPadding ; for ( i = 0 ; i < len ; i ++ ) { const ds = dataSeries [ i ] ; const spec = getSpecsById < BasicSeriesSpec > ( seriesSpecs , ds . specId ) ; if ( spec === undefined ) { continue ; } const yScale = yScales . get ( getSpecDomainGroupId ( ds . spec ) ) ; if ( ! yScale ) { continue ; } const barPanelKey = [ ds . smVerticalAccessorValue , ds . smHorizontalAccessorValue ] . join ( '|' ) ; const barIndexOrder = barIndexOrderPerPanel [ barPanelKey ] ; const xScale = computeXScale ( { xDomain , totalBarsInCluster : barIndexOrder ?. length ?? 0 , range : [ 0 , isHorizontalRotation ( chartRotation ) ? smHScale . bandwidth : smVScale . bandwidth ] , barsPadding , enableHistogramMode , } ) ; const { stackMode } = ds ; const leftPos = smHScale . scale ( ds . smHorizontalAccessorValue ) || 0 ; const topPos = smVScale . scale ( ds . smVerticalAccessorValue ) || 0 ; const panel = { width : smHScale . bandwidth , height : smVScale . bandwidth , top : topPos , left : leftPos , } ; const dataSeriesKey = getSeriesKey ( { specId : ds . specId , yAccessor : ds . yAccessor , splitAccessors : ds . splitAccessors , } , ds . groupId ) ; const color = seriesColorsMap . get ( dataSeriesKey ) || defaultColor ; if ( isBarSeriesSpec ( spec ) ) { const shift = barIndexOrder . indexOf ( getBarIndexKey ( ds , enableHistogramMode ) ) ; if ( shift === - 1 ) continue ; const barSeriesStyle = mergePartial ( chartTheme . barSeriesStyle , spec . barSeriesStyle ) ; const { yAxis } = getAxesSpecForSpecId ( axesSpecs , spec . groupId , chartRotation ) ; const valueFormatter = yAxis ?. tickFormat ?? fallBackTickFormatter ; const displayValueSettings = spec . displayValueSettings ? { valueFormatter , ... spec . displayValueSettings } : undefined ; const renderedBars = renderBars ( measureText , shift , ds , xScale , yScale , panel , chartRotation , spec . minBarHeight ?? 0 , color , barSeriesStyle , displayValueSettings , spec . styleAccessor , stackMode ) ; geometriesIndex . merge ( renderedBars . indexedGeometryMap ) ; bars . push ( { panel , value : renderedBars . barGeometries } ) ; geometriesCounts . bars += renderedBars . barGeometries . length ; } else if ( isBubbleSeriesSpec ( spec ) ) { const bubbleShift = barIndexOrder && barIndexOrder . length > 0 ? barIndexOrder . length : 1 ; const bubbleSeriesStyle = spec . bubbleSeriesStyle ? mergePartial ( chartTheme . bubbleSeriesStyle , spec . bubbleSeriesStyle ) : chartTheme . bubbleSeriesStyle ; const xScaleOffset = computeXScaleOffset ( xScale , enableHistogramMode ) ; const renderedBubbles = renderBubble ( ( xScale . bandwidth * bubbleShift ) / 2 , ds , xScale , yScale , color , panel , isBandedSpec ( spec ) , xScaleOffset , bubbleSeriesStyle , { enabled : spec . markSizeAccessor !== undefined , ratio : chartTheme . markSizeRatio , } , isMixedChart , spec . pointStyleAccessor ) ; geometriesIndex . merge ( renderedBubbles . indexedGeometryMap ) ; bubbles . push ( { panel , value : renderedBubbles . bubbleGeometry , } ) ; geometriesCounts . bubblePoints += renderedBubbles . bubbleGeometry . points . length ; geometriesCounts . bubbles += 1 ; } else if ( isLineSeriesSpec ( spec ) ) { const lineShift = barIndexOrder && barIndexOrder . length > 0 ? barIndexOrder . length : 1 ; const lineSeriesStyle = spec . lineSeriesStyle ? mergePartial ( chartTheme . lineSeriesStyle , spec . lineSeriesStyle ) : chartTheme . lineSeriesStyle ; const xScaleOffset = computeXScaleOffset ( xScale , enableHistogramMode , spec . histogramModeAlignment ) ; const renderedLines = renderLine ( ( xScale . bandwidth * lineShift ) / 2 , ds , xScale , yScale , panel , color , spec . curve || CurveType . LINEAR , isBandedSpec ( spec ) , xScaleOffset , lineSeriesStyle , { enabled : spec . markSizeAccessor !== undefined && lineSeriesStyle . point . visible , ratio : chartTheme . markSizeRatio , } , hasFitFnConfigured ( spec . fit ) , spec . pointStyleAccessor ) ; geometriesIndex . merge ( renderedLines . indexedGeometryMap ) ; lines . push ( { panel , value : renderedLines . lineGeometry , } ) ; geometriesCounts . linePoints += renderedLines . lineGeometry . points . length ; geometriesCounts . lines += 1 ; } else if ( isAreaSeriesSpec ( spec ) ) { const areaShift = barIndexOrder && barIndexOrder . length > 0 ? barIndexOrder . length : 1 ; const areaSeriesStyle = spec . areaSeriesStyle ? mergePartial ( chartTheme . areaSeriesStyle , spec . areaSeriesStyle ) : chartTheme . areaSeriesStyle ; const xScaleOffset = computeXScaleOffset ( xScale , enableHistogramMode , spec . histogramModeAlignment ) ; const renderedAreas = renderArea ( ( xScale . bandwidth * areaShift ) / 2 , ds , xScale , yScale , panel , color , spec . curve || CurveType . LINEAR , isBandedSpec ( spec ) , xScaleOffset , areaSeriesStyle , { enabled : spec . markSizeAccessor !== undefined && areaSeriesStyle . point . visible , ratio : chartTheme . markSizeRatio , } , spec . stackAccessors ? spec . stackAccessors . length > 0 : false , hasFitFnConfigured ( spec . fit ) , spec . pointStyleAccessor ) ; geometriesIndex . merge ( renderedAreas . indexedGeometryMap ) ; areas . push ( { panel , value : renderedAreas . areaGeometry , } ) ; geometriesCounts . areasPoints += renderedAreas . areaGeometry . points . length ; geometriesCounts . areas += 1 ; } } return { geometries : { points , bars , areas , lines , bubbles , } , geometriesIndex , geometriesCounts , } ; } internal export function computeChartTransform ( { width , height } , chartRotation ) { return { x : chartRotation === 90 || chartRotation === 180 ? width : 0 , y : chartRotation === - 90 || chartRotation === 180 ? height : 0 , rotate : chartRotation , } ; } function hasFitFnConfigured ( fit ? ) { return Boolean ( fit && ( ( fit as FitConfig ) . type || fit ) !== Fit . None ) ; } internal export function getBarIndexKey ( { spec , specId , groupId , yAccessor , splitAccessors } , histogramModeEnabled ) { const isStacked = isStackedSpec ( spec , histogramModeEnabled ) ; if ( isStacked ) { return [ groupId , '__stacked__' ] . join ( '__-__' ) ; } return [ groupId , specId , ... splitAccessors . values ( ) , yAccessor ] . join ( '__-__' ) ; }