import { Color } from '../../../common/colors' ; import { Scale } from '../../../scales' ; import { isNil } from '../../../utils/common' ; import { Dimensions } from '../../../utils/dimensions' ; import { BandedAccessorType , PointGeometry } from '../../../utils/geometry' ; import { PointStyle } from '../../../utils/themes/theme' ; import { GeometryType , IndexedGeometryMap } from '../utils/indexed_geometry_map' ; import { DataSeries , DataSeriesDatum , FilledValues , XYChartSeriesIdentifier } from '../utils/series' ; import { PointStyleAccessor , StackMode } from '../utils/specs' ; import { buildPointGeometryStyles } from './point_style' ; import { getY0ScaledValueFn , getY1ScaledValueFn , getYDatumValueFn , isDatumFilled , isYValueDefinedFn , MarkSizeOptions , YDefinedFn , } from './utils' ; internal export function renderPoints ( shift , dataSeries , xScale , yScale , panel , color , pointStyle , isBandChart , markSizeOptions , useSpatialIndex , styleAccessor ? ) { const indexedGeometryMap = new IndexedGeometryMap ( ) ; const getRadius = markSizeOptions . enabled ? getRadiusFn ( dataSeries . data , pointStyle . strokeWidth , markSizeOptions . ratio ) : ( ) => 0 ; const geometryType = useSpatialIndex ? GeometryType . spatial : GeometryType . linear ; const y1Fn = getY1ScaledValueFn ( yScale ) ; const y0Fn = getY0ScaledValueFn ( yScale ) ; const yDefined = isYValueDefinedFn ( yScale , xScale ) ; const pointGeometries = dataSeries . data . reduce ( ( acc , datum , dataIndex ) => { const { x : xValue , mark } = datum ; const prev = dataSeries . data [ dataIndex - 1 ] ; const next = dataSeries . data [ dataIndex + 1 ] ; if ( ! xScale . isValueInDomain ( xValue ) ) return acc ; const x = xScale . scale ( xValue ) ; if ( Number . isNaN ( x ) ) return acc ; const points = [ ] ; const yDatumKeyNames = isBandChart ? [ 'y0' , 'y1' ] : [ 'y1' ] ; yDatumKeyNames . forEach ( ( yDatumKeyName , keyIndex ) => { const valueAccessor = getYDatumValueFn ( yDatumKeyName ) ; const y = yDatumKeyName === 'y1' ? y1Fn ( datum ) : y0Fn ( datum ) ; const originalY = getDatumYValue ( datum , keyIndex === 0 , isBandChart , dataSeries . stackMode ) ; const seriesIdentifier = { key : dataSeries . key , specId : dataSeries . specId , yAccessor : dataSeries . yAccessor , splitAccessors : dataSeries . splitAccessors , seriesKeys : dataSeries . seriesKeys , smVerticalAccessorValue : dataSeries . smVerticalAccessorValue , smHorizontalAccessorValue : dataSeries . smHorizontalAccessorValue , } ; const styleOverrides = getPointStyleOverrides ( datum , seriesIdentifier , styleAccessor ) ; const style = buildPointGeometryStyles ( color , pointStyle , styleOverrides ) ; const orphan = isOrphanDataPoint ( dataIndex , dataSeries . data . length , yDefined , prev , next ) ; const radius = markSizeOptions . enabled ? Math . max ( getRadius ( mark ) , pointStyle . radius ) : styleOverrides ?. radius ?? pointStyle . radius ; const pointGeometry = { x , y : y === null ? NaN : y , radius , color , style , value : { x : xValue , y : originalY , mark , accessor : isBandChart && keyIndex === 0 ? BandedAccessorType . Y0 : BandedAccessorType . Y1 , datum : datum . datum , } , transform : { x : shift , y : 0 , } , seriesIdentifier , panel , orphan , } ; const isInYDomain = yScale . isValueInDomain ( valueAccessor ( datum ) ) ; indexedGeometryMap . set ( pointGeometry , geometryType ) ; if ( y !== null && yDefined ( datum , valueAccessor ) && isInYDomain && ! isDatumFilled ( datum ) ) { points . push ( pointGeometry ) ; } } ) ; return [ ... acc , ... points ] ; } , [ ] as PointGeometry [ ] ) ; return { pointGeometries , indexedGeometryMap , } ; } internal export function getPointStyleOverrides ( datum , seriesIdentifier , pointStyleAccessor ? ) { const styleOverride = pointStyleAccessor && pointStyleAccessor ( datum , seriesIdentifier ) ; if ( ! styleOverride ) { return ; } if ( typeof styleOverride === 'string' ) { return { stroke : styleOverride , } ; } return styleOverride ; } param datum param lookingForY0 param isBandChart param stackMode internal export function getDatumYValue ( { y1 , y0 , initialY1 , initialY0 } , lookingForY0 , isBandChart , stackMode ? ) { if ( isBandChart ) { return stackMode === StackMode . Percentage ? ( lookingForY0 ? y0 : y1 ) : lookingForY0 ? initialY0 : initialY1 ; } return stackMode === StackMode . Percentage ? ( isNil ( y1 ) || isNil ( initialY1 ) ? null : y1 - ( y0 ?? 0 ) ) : initialY1 ; } todo param { DataSeriesDatum [ ] } data param { number } lineWidth param { number = internal export function getRadiusFn ( data , lineWidth , markSizeRatio = 50 ) { if ( data . length === 0 ) { return ( ) => 0 ; } const { min , max } = data . reduce ( ( acc , { mark } ) => mark === null ? acc : { min : Math . min ( acc . min , mark / 2 ) , max : Math . max ( acc . max , mark / 2 ) , } , { min : Infinity , max : - Infinity } ) ; const adjustedMarkSizeRatio = Math . min ( Math . max ( markSizeRatio , 0 ) , 100 ) ; const radiusStep = ( max - min || max * 100 ) / Math . pow ( adjustedMarkSizeRatio , 2 ) ; return function getRadius ( mark , defaultRadius = 0 ) : number { if ( mark === null ) { return defaultRadius ; } const circleRadius = ( mark / 2 - min ) / radiusStep ; const baseMagicNumber = 2 ; return circleRadius ? Math . sqrt ( circleRadius + baseMagicNumber ) + lineWidth : lineWidth ; } ; } function yAccessorForOrphanCheck ( datum ) { return datum . filled ?. y1 ? null : datum . y1 ; } function isOrphanDataPoint ( index , length , yDefined , prev ? , next ? ) { if ( index === 0 && ( isNil ( next ) || ! yDefined ( next , yAccessorForOrphanCheck ) ) ) { return true ; } if ( index === length - 1 && ( isNil ( prev ) || ! yDefined ( prev , yAccessorForOrphanCheck ) ) ) { return true ; } return ( ( isNil ( prev ) || ! yDefined ( prev , yAccessorForOrphanCheck ) ) && ( isNil ( next ) || ! yDefined ( next , yAccessorForOrphanCheck ) ) ) ; }