import { Colors } from '../../../../common/colors' ; import { Line } from '../../../../geoms/types' ; import { Scale } from '../../../../scales' ; import { isBandScale , isContinuousScale } from '../../../../scales/types' ; import { Position , Rotation } from '../../../../utils/common' ; import { Dimensions , Size } from '../../../../utils/dimensions' ; import { GroupId } from '../../../../utils/ids' ; import { mergeWithDefaultAnnotationLine } from '../../../../utils/themes/merge_utils' ; import { SmallMultipleScales } from '../../state/selectors/compute_small_multiple_scales' ; import { isHorizontalRotation , isVerticalRotation } from '../../state/utils/common' ; import { computeXScaleOffset } from '../../state/utils/utils' ; import { getPanelSize } from '../../utils/panel' ; import { AnnotationDomainType , LineAnnotationDatum , LineAnnotationSpec } from '../../utils/specs' ; import { AnnotationLineProps } from './types' ; function computeYDomainLineAnnotationDimensions ( annotationSpec , yScale , { vertical , horizontal } , chartRotation , axisPosition ? ) { const { id : specId , dataValues , marker : icon , markerBody : body , markerDimensions : dimension , markerPosition : specMarkerPosition , style , } = annotationSpec ; const lineStyle = mergeWithDefaultAnnotationLine ( style ) ; const color = lineStyle ?. line ?. stroke ?? Colors . Red . keyword ; const isHorizontalChartRotation = isHorizontalRotation ( chartRotation ) ; const lineProps = [ ] ; const [ domainStart , domainEnd ] = yScale . domain ; const panelSize = getPanelSize ( { vertical , horizontal } ) ; dataValues . forEach ( ( datum , i ) => { const { dataValue } = datum ; if ( ! dataValue && dataValue !== 0 ) return ; const annotationValueYPosition = yScale . scale ( dataValue ) ; if ( Number . isNaN ( annotationValueYPosition ) ) return ; if ( dataValue < domainStart || dataValue > domainEnd ) return ; vertical . domain . forEach ( ( verticalValue ) => { horizontal . domain . forEach ( ( horizontalValue ) => { const top = vertical . scale ( verticalValue ) ; const left = horizontal . scale ( horizontalValue ) ; if ( Number . isNaN ( top + left ) ) return ; const width = isHorizontalChartRotation ? horizontal . bandwidth : vertical . bandwidth ; const height = isHorizontalChartRotation ? vertical . bandwidth : horizontal . bandwidth ; const linePathPoints = getYLinePath ( { width , height } , annotationValueYPosition ) ; const alignment = getAnchorPosition ( false , chartRotation , axisPosition , specMarkerPosition ) ; const position = getMarkerPositionForYAnnotation ( panelSize , chartRotation , alignment , annotationValueYPosition , dimension ) ; const lineProp = { specId , id : getAnnotationLinePropsId ( specId , datum , i , verticalValue , horizontalValue ) , datum , linePathPoints , markers : icon ? [ { icon , body , color , dimension , position , alignment , } , ] : [ ] , panel : { ... panelSize , top , left , } , } ; lineProps . push ( lineProp ) ; } ) ; } ) ; } ) ; return lineProps ; } function computeXDomainLineAnnotationDimensions ( annotationSpec , xScale , { vertical , horizontal } , chartRotation , isHistogramMode , axisPosition ? ) { const { id : specId , dataValues , marker : icon , markerBody : body , markerDimensions : dimension , markerPosition : specMarkerPosition , style , } = annotationSpec ; const lineStyle = mergeWithDefaultAnnotationLine ( style ) ; const color = lineStyle ?. line ?. stroke ?? Colors . Red . keyword ; const lineProps = [ ] ; const isHorizontalChartRotation = isHorizontalRotation ( chartRotation ) ; const panelSize = getPanelSize ( { vertical , horizontal } ) ; dataValues . forEach ( ( datum , i ) => { const { dataValue } = datum ; let annotationValueXPosition = xScale . scale ( dataValue ) ; if ( Number . isNaN ( annotationValueXPosition ) ) { return ; } if ( isContinuousScale ( xScale ) && typeof dataValue === 'number' ) { const [ minDomain ] = xScale . domain ; const maxDomain = isHistogramMode ? xScale . domain [ 1 ] + xScale . minInterval : xScale . domain [ 1 ] ; if ( dataValue < minDomain || dataValue > maxDomain ) { return ; } if ( isHistogramMode ) { const offset = computeXScaleOffset ( xScale , true ) ; const pureScaledValue = xScale . pureScale ( dataValue ) ; if ( ! Number . isNaN ( pureScaledValue ) ) { annotationValueXPosition = pureScaledValue - offset ; } } else { annotationValueXPosition += ( xScale . bandwidth * xScale . totalBarsInCluster ) / 2 ; } } else if ( isBandScale ( xScale ) ) { annotationValueXPosition += isHistogramMode ? - ( xScale . step - xScale . originalBandwidth ) / 2 : xScale . originalBandwidth / 2 ; } else { return ; } if ( ! isFinite ( annotationValueXPosition ) ) { return ; } vertical . domain . forEach ( ( verticalValue ) => { horizontal . domain . forEach ( ( horizontalValue ) => { if ( Number . isNaN ( annotationValueXPosition ) ) return ; const top = vertical . scale ( verticalValue ) ; const left = horizontal . scale ( horizontalValue ) ; if ( Number . isNaN ( top + left ) ) return ; const width = isHorizontalChartRotation ? horizontal . bandwidth : vertical . bandwidth ; const height = isHorizontalChartRotation ? vertical . bandwidth : horizontal . bandwidth ; const linePathPoints = getXLinePath ( { width , height } , annotationValueXPosition ) ; const alignment = getAnchorPosition ( true , chartRotation , axisPosition , specMarkerPosition ) ; const position = getMarkerPositionForXAnnotation ( panelSize , chartRotation , alignment , annotationValueXPosition , dimension ) ; const lineProp = { specId , id : getAnnotationLinePropsId ( specId , datum , i , verticalValue , horizontalValue ) , datum , linePathPoints , markers : icon ? [ { icon , body , color , dimension , position , alignment , } , ] : [ ] , panel : { ... panelSize , top , left , } , } ; lineProps . push ( lineProp ) ; } ) ; } ) ; } ) ; return lineProps ; } internal export function computeLineAnnotationDimensions ( annotationSpec , chartRotation , yScales , xScale , smallMultipleScales , isHistogramMode , axisPosition ? ) { const { domainType , hideLines } = annotationSpec ; if ( hideLines ) { return null ; } if ( domainType === AnnotationDomainType . XDomain ) { return computeXDomainLineAnnotationDimensions ( annotationSpec , xScale , smallMultipleScales , chartRotation , isHistogramMode , axisPosition ) ; } const { groupId } = annotationSpec ; const yScale = yScales . get ( groupId ) ; if ( ! yScale ) { return null ; } return computeYDomainLineAnnotationDimensions ( annotationSpec , yScale , smallMultipleScales , chartRotation , axisPosition ) ; } function getAnchorPosition ( isXDomain , chartRotation , axisPosition ? , specMarkerPosition ? ) { const dflPositionFromAxis = getDefaultMarkerPositionFromAxis ( isXDomain , chartRotation , axisPosition ) ; if ( specMarkerPosition !== undefined ) { const validatedPosFromMarkerPos = validateMarkerPosition ( isXDomain , chartRotation , specMarkerPosition ) ; return validatedPosFromMarkerPos ?? dflPositionFromAxis ; } return dflPositionFromAxis ; } function validateMarkerPosition ( isXDomain , chartRotation , position ) { if ( ( isXDomain && isHorizontalRotation ( chartRotation ) ) || ( ! isXDomain && isVerticalRotation ( chartRotation ) ) ) { return position === Position . Top || position === Position . Bottom ? position : undefined ; } return position === Position . Left || position === Position . Right ? position : undefined ; } function getDefaultMarkerPositionFromAxis ( isXDomain , chartRotation , axisPosition ? ) { if ( axisPosition ) { return axisPosition ; } if ( ( isXDomain && isVerticalRotation ( chartRotation ) ) || ( ! isXDomain && isHorizontalRotation ( chartRotation ) ) ) { return Position . Left ; } return Position . Bottom ; } function getXLinePath ( { height } , value ) { return { x1 : value , y1 : 0 , x2 : value , y2 : height , } ; } function getYLinePath ( { width } , value ) { return { x1 : 0 , y1 : value , x2 : width , y2 : value , } ; } internal export function getMarkerPositionForXAnnotation ( { width , height } , rotation , position , value , { width : mWidth , height : mHeight } = { width : 0 , height : 0 } ) { switch ( position ) { case Position . Right : return { top : rotation === - 90 ? height - value - mHeight / 2 : value - mHeight / 2 , left : width , } ; case Position . Left : return { top : rotation === - 90 ? height - value - mHeight / 2 : value - mHeight / 2 , left : - mWidth , } ; case Position . Top : return { top : 0 - mHeight , left : rotation === 180 ? width - value - mWidth / 2 : value - mWidth / 2 , } ; case Position . Bottom : default : return { top : height , left : rotation === 180 ? width - value - mWidth / 2 : value - mWidth / 2 , } ; } } function getMarkerPositionForYAnnotation ( { width , height } , rotation , position , value , { width : mWidth , height : mHeight } = { width : 0 , height : 0 } ) { switch ( position ) { case Position . Right : return { top : rotation === 180 ? height - value - mHeight / 2 : value - mHeight / 2 , left : width , } ; case Position . Left : return { top : rotation === 180 ? height - value - mHeight / 2 : value - mHeight / 2 , left : - mWidth , } ; case Position . Top : return { top : - mHeight , left : rotation === 90 ? width - value - mWidth / 2 : value - mWidth / 2 , } ; case Position . Bottom : default : return { top : height , left : rotation === 90 ? width - value - mWidth / 2 : value - mWidth / 2 , } ; } } internal export function getAnnotationLinePropsId ( specId , datum , index , verticalValue ? , horizontalValue ? ) { return [ specId , verticalValue , horizontalValue , datum . header , datum . details , index ] . join ( '__' ) ; }