import { Scale } from '../../../scales' ; import { AxisSpec , SettingsSpec } from '../../../specs' ; import { degToRad , getPercentageValue , HorizontalAlignment , Position , Rotation , VerticalAlignment , } from '../../../utils/common' ; import { Dimensions , innerPad , outerPad , PerSideDistance , Size } from '../../../utils/dimensions' ; import { Range } from '../../../utils/domain' ; import { AxisId } from '../../../utils/ids' ; import { Point } from '../../../utils/point' ; import { AxisStyle , TextAlignment , TextOffset , Theme } from '../../../utils/themes/theme' ; import { MIN_STROKE_WIDTH } from '../renderer/canvas/primitives/line' ; import { SmallMultipleScales } from '../state/selectors/compute_small_multiple_scales' ; import { Projection } from '../state/selectors/visible_ticks' ; import { SeriesDomainsAndData } from '../state/utils/types' ; import { isHorizontalAxis , isVerticalAxis } from './axis_type_utils' ; import { getPanelSize , hasSMDomain } from './panel' ; import { computeXScale , computeYScales } from './scales' ; type TickValue = number | string ; internal export type TextDirection = 'rtl' | 'ltr' ; internal export interface AxisTick { value : TickValue ; domainClampedValue : TickValue ; label : string ; position : number ; domainClampedPosition : number ; layer ? : number ; detailedLayer : number ; showGrid : boolean ; direction : TextDirection ; } internal export interface TickLabelBounds { maxLabelBboxWidth : number ; maxLabelBboxHeight : number ; maxLabelTextWidth : number ; maxLabelTextHeight : number ; isHidden : boolean ; } interface TickLabelProps { x : number ; y : number ; offsetX : number ; offsetY : number ; textOffsetX : number ; textOffsetY : number ; horizontalAlign : Extract < HorizontalAlignment , typeof HorizontalAlignment . Left | typeof HorizontalAlignment . Center | typeof HorizontalAlignment . Right > ; verticalAlign : Extract < VerticalAlignment , typeof VerticalAlignment . Top | typeof VerticalAlignment . Middle | typeof VerticalAlignment . Bottom > ; } internal export const defaultTickFormatter = ( tick ) => `${ tick }` ; internal export function isXDomain ( position , chartRotation ) { return isHorizontalAxis ( position ) === ( chartRotation % 180 === 0 ) ; } internal export function getScaleForAxisSpec ( { xDomain , yDomains } , { rotation : chartRotation } , totalBarsInCluster , barsPadding ? , enableHistogramMode ? ) { return ( { groupId , integersOnly , position } , range ) : Scale < number | string > | null => isXDomain ( position , chartRotation ) ? computeXScale ( { xDomain , totalBarsInCluster , range , barsPadding , enableHistogramMode , integersOnly } ) : computeYScales ( { yDomains , range , integersOnly } ) . get ( groupId ) ?? null ; } internal export function computeRotatedLabelDimensions ( unrotatedDims , degreesRotation ) { const { width , height } = unrotatedDims ; const radians = degToRad ( degreesRotation ) ; const rotatedHeight = Math . abs ( width * Math . sin ( radians ) ) + Math . abs ( height * Math . cos ( radians ) ) ; const rotatedWidth = Math . abs ( width * Math . cos ( radians ) ) + Math . abs ( height * Math . sin ( radians ) ) ; return { width : rotatedWidth , height : rotatedHeight } ; } function getUserTextOffsets ( dimensions , { x , y , reference } ) { return reference === 'global' ? { local : { x : 0 , y : 0 } , global : { x : getPercentageValue ( x , dimensions . maxLabelBboxWidth , 0 ) , y : getPercentageValue ( y , dimensions . maxLabelBboxHeight , 0 ) , } , } : { local : { x : getPercentageValue ( x , dimensions . maxLabelTextWidth , 0 ) , y : getPercentageValue ( y , dimensions . maxLabelTextHeight , 0 ) , } , global : { x : 0 , y : 0 } , } ; } const horizontalOffsetMultiplier = { [ HorizontalAlignment . Left ] : - 1 , [ HorizontalAlignment . Right ] : 1 , [ HorizontalAlignment . Center ] : 0 , } ; const verticalOffsetMultiplier = { [ VerticalAlignment . Top ] : - 1 , [ VerticalAlignment . Bottom ] : 1 , [ VerticalAlignment . Middle ] : 0 , } ; function getHorizontalAlign ( position , rotation , alignment ) { if ( alignment === HorizontalAlignment . Center || alignment === HorizontalAlignment . Right || alignment === HorizontalAlignment . Left ) { return alignment ; } if ( Math . abs ( rotation ) === 90 ) { if ( position === Position . Top ) { return rotation === 90 ? HorizontalAlignment . Right : HorizontalAlignment . Left ; } else if ( position === Position . Bottom ) { return rotation === - 90 ? HorizontalAlignment . Right : HorizontalAlignment . Left ; } } else { if ( position === Position . Left ) { return alignment === HorizontalAlignment . Near ? HorizontalAlignment . Right : HorizontalAlignment . Left ; } else if ( position === Position . Right ) { return alignment === HorizontalAlignment . Near ? HorizontalAlignment . Left : HorizontalAlignment . Right ; } } return HorizontalAlignment . Center ; } function getVerticalAlign ( position , rotation , alignment ) { if ( alignment === VerticalAlignment . Middle || alignment === VerticalAlignment . Top || alignment === VerticalAlignment . Bottom ) { return alignment ; } if ( rotation % 180 === 0 ) { if ( position === Position . Left ) { return rotation === 0 ? VerticalAlignment . Bottom : VerticalAlignment . Top ; } else if ( position === Position . Right ) { return rotation === 180 ? VerticalAlignment . Bottom : VerticalAlignment . Top ; } } else { if ( position === Position . Top ) { return alignment === VerticalAlignment . Near ? VerticalAlignment . Bottom : VerticalAlignment . Top ; } else if ( position === Position . Bottom ) { return alignment === VerticalAlignment . Near ? VerticalAlignment . Top : VerticalAlignment . Bottom ; } } return VerticalAlignment . Middle ; } internal export function getTickLabelPosition ( { tickLine , tickLabel } , tickPosition , pos , rotation , axisSize , tickDimensions , showTicks , textOffset , textAlignment ) { const { maxLabelBboxWidth , maxLabelTextWidth , maxLabelBboxHeight , maxLabelTextHeight } = tickDimensions ; const tickDimension = showTicks ? tickLine . size + tickLine . padding : 0 ; const labelInnerPadding = innerPad ( tickLabel . padding ) ; const horizontalAlign = getHorizontalAlign ( pos , rotation , textAlignment . horizontal ) ; const verticalAlign = getVerticalAlign ( pos , rotation , textAlignment . vertical ) ; const userOffsets = getUserTextOffsets ( tickDimensions , textOffset ) ; const paddedTickDimension = tickDimension + labelInnerPadding ; const axisNetSize = ( isVerticalAxis ( pos ) ? axisSize . width : axisSize . height ) - paddedTickDimension ; const labelBoxHalfGirth = isHorizontalAxis ( pos ) ? maxLabelBboxHeight / 2 : maxLabelBboxWidth / 2 ; const labelHalfWidth = maxLabelTextWidth / 2 ; return { horizontalAlign , verticalAlign , x : pos === Position . Left ? axisNetSize : pos === Position . Right ? paddedTickDimension : tickPosition , y : pos === Position . Top ? axisNetSize : pos === Position . Bottom ? paddedTickDimension : tickPosition , offsetX : userOffsets . global . x + ( isHorizontalAxis ( pos ) ? 0 : horizontalOffsetMultiplier [ pos ] * labelBoxHalfGirth ) , offsetY : userOffsets . global . y + ( isVerticalAxis ( pos ) ? 0 : verticalOffsetMultiplier [ pos ] * labelBoxHalfGirth ) , textOffsetX : userOffsets . local . x + ( isHorizontalAxis ( pos ) && rotation === 0 ? 0 : labelHalfWidth * horizontalOffsetMultiplier [ horizontalAlign ] ) , textOffsetY : userOffsets . local . y + ( maxLabelTextHeight / 2 ) * verticalOffsetMultiplier [ verticalAlign ] , } ; } internal export function getTitleDimension ( { visible , fontSize , padding , } ) { return visible && fontSize > 0 ? innerPad ( padding ) + fontSize + outerPad ( padding ) : 0 ; } internal export const getAllAxisLayersGirth = ( timeAxisLayerCount , maxLabelBoxGirth , axisHorizontal ) => { const axisLayerCount = timeAxisLayerCount > 0 && axisHorizontal ? timeAxisLayerCount : 1 ; return axisLayerCount * maxLabelBoxGirth ; } ; internal export function getPosition ( { chartDimensions } , chartMargins , { axisTitle , axisPanelTitle , tickLine , tickLabel } , { title , position , hide , timeAxisLayerCount } , { maxLabelBboxHeight , maxLabelBboxWidth } , smScales , { top : cumTopSum , bottom : cumBottomSum , left : cumLeftSum , right : cumRightSum } ) { const tickDimension = shouldShowTicks ( tickLine , hide ) ? tickLine . size + tickLine . padding : 0 ; const labelPaddingSum = tickLabel . visible ? innerPad ( tickLabel . padding ) + outerPad ( tickLabel . padding ) : 0 ; const titleDimension = title ? getTitleDimension ( axisTitle ) : 0 ; const vertical = isVerticalAxis ( position ) ; const scaleBand = vertical ? smScales . vertical : smScales . horizontal ; const panelTitleDimension = hasSMDomain ( scaleBand ) ? getTitleDimension ( axisPanelTitle ) : 0 ; const maxLabelBboxGirth = tickLabel . visible ? ( vertical ? maxLabelBboxWidth : maxLabelBboxHeight ) : 0 ; const shownLabelSize = getAllAxisLayersGirth ( timeAxisLayerCount , maxLabelBboxGirth , ! vertical ) ; const parallelSize = labelPaddingSum + shownLabelSize + tickDimension + titleDimension + panelTitleDimension ; return { leftIncrement : position === Position . Left ? parallelSize + chartMargins . left : 0 , rightIncrement : position === Position . Right ? parallelSize + chartMargins . right : 0 , topIncrement : position === Position . Top ? parallelSize + chartMargins . top : 0 , bottomIncrement : position === Position . Bottom ? parallelSize + chartMargins . bottom : 0 , dimensions : { left : position === Position . Left ? chartMargins . left + cumLeftSum : chartDimensions . left + ( position === Position . Right ? chartDimensions . width + cumRightSum : 0 ) , top : position === Position . Top ? chartMargins . top + cumTopSum : chartDimensions . top + ( position === Position . Bottom ? chartDimensions . height + cumBottomSum : 0 ) , width : vertical ? parallelSize : chartDimensions . width , height : vertical ? chartDimensions . height : parallelSize , } , } ; } internal export function shouldShowTicks ( { visible , strokeWidth , size } , axisHidden ) { return ! axisHidden && visible && size > 0 && strokeWidth >= MIN_STROKE_WIDTH ; } internal export interface AxisGeometry { anchorPoint : Point ; size : Size ; parentSize : Size ; axis : { id : AxisId ; position : Position ; panelTitle ? : string ; secondary ? : boolean ; } ; dimension : TickLabelBounds ; visibleTicks : AxisTick [ ] ; } internal export function getAxesGeometries ( chartDims , { chartPaddings , chartMargins , axes : sharedAxesStyle } , axisSpecs , axesStyles , smScales , totalGroupsCount , enableHistogramMode , visibleTicksSet ) { const panel = getPanelSize ( smScales ) ; return [ ... visibleTicksSet ] . reduce ( ( acc , [ axisId , { ticks , labelBox } ] ) => { const axisSpec = axisSpecs . get ( axisId ) ; if ( axisSpec ) { const vertical = isVerticalAxis ( axisSpec . position ) ; const axisStyle = axesStyles . get ( axisId ) ?? sharedAxesStyle ; const axisPositionData = getPosition ( chartDims , chartMargins , axisStyle , axisSpec , labelBox , smScales , acc ) ; const { dimensions , topIncrement , bottomIncrement , leftIncrement , rightIncrement } = axisPositionData ; acc . top += topIncrement ; acc . bottom += bottomIncrement ; acc . left += leftIncrement ; acc . right += rightIncrement ; acc . geoms . push ( { axis : { id : axisSpec . id , position : axisSpec . position } , anchorPoint : { x : dimensions . left , y : dimensions . top } , dimension : labelBox , visibleTicks : ticks , parentSize : { height : dimensions . height , width : dimensions . width } , size : { width : labelBox . isHidden ? 0 : vertical ? dimensions . width : panel . width , height : labelBox . isHidden ? 0 : vertical ? panel . height : dimensions . height , } , } ) ; } else { throw new Error ( `Cannot compute scale for axis spec ${ axisId }` ) ; } return acc ; } , { geoms : [ ] , top : 0 , bottom : chartPaddings . bottom , left : chartDims . leftMargin , right : chartPaddings . right } ) . geoms ; }