'use strict' ; import type Axis from './Axis.js' ; import type { AxisLabelFormatterContextObject , AxisLabelOptions , AxisOptions } from './AxisOptions' ; import type CSSObject from '../Renderer/CSSObject' ; import type PositionObject from '../Renderer/PositionObject' ; import type TickLike from './TickLike' ; import type SVGAttributes from '../Renderer/SVG/SVGAttributes' ; import type SVGElement from '../Renderer/SVG/SVGElement' ; import type SVGPath from '../Renderer/SVG/SVGPath' ; import type SVGRenderer from '../Renderer/SVG/SVGRenderer' ; import type TimeTicksInfoObject from './TimeTicksInfoObject' ; import F from '../FormatUtilities.js' ; import H from '../Globals.js' ; const { deg2rad } = H ; import U from '../Utilities.js' ; const { clamp , correctFloat , defined , destroyObjectProperties , extend , fireEvent , isNumber , merge , objectEach , pick } = U ; declare module './AxisOptions' { interface AxisLabelFormatterContextObject { tickPositionInfo ? : TimeTicksInfoObject ; } } class name param { Highcharts . Axis } axis param { number } pos param { string } type param { boolean } noLabel param { Object } parameters class Tick { public constructor ( axis , pos , type ? , noLabel ? , parameters ? ) { name type { Highcharts . Axis } this . axis = axis ; name type { number } this . pos = pos ; name type { string } this . type = type || '' ; this . parameters = parameters || { } ; name type { number | undefined } this . tickmarkOffset = this . parameters . tickmarkOffset ; this . options = this . parameters . options ; fireEvent ( this , 'init' ) ; if ( ! type && ! noLabel ) { this . addLabel ( ) ; } } public axis : Axis ; public gridLine ? : SVGElement ; public isActive ? : boolean ; public isFirst ? : boolean ; public isNew : boolean = true ; public isNewLabel : boolean = true ; public isLast ? : boolean ; public label ? : SVGElement ; public labelPos ? : PositionObject ; public mark ? : SVGElement ; public movedLabel ? : SVGElement ; public options ? : DeepPartial < AxisOptions > ; public parameters : Tick . ParametersObject ; public pos : number ; public rotation ? : number ; public shortenLabel ? : Function ; public slotWidth ? : number ; public tickmarkOffset ? : number ; public type : string ; private function public addLabel ( ) { const tick = this , axis = tick . axis , options = axis . options , chart = axis . chart , categories = axis . categories , log = axis . logarithmic , names = axis . names , pos = tick . pos , labelOptions = pick ( tick . options && tick . options . labels , options . labels ) as any , tickPositions = axis . tickPositions , isFirst = pos === tickPositions [ 0 ] , isLast = pos === tickPositions [ tickPositions . length - 1 ] , animateLabels = ( ! labelOptions . step || labelOptions . step === 1 ) && axis . tickInterval === 1 , tickPositionInfo = tickPositions . info ; let label = tick . label , dateTimeLabelFormat , dateTimeLabelFormats , i ; let value = this . parameters . category || ( categories ? pick ( categories [ pos ] , names [ pos ] , pos ) : pos ) ; if ( log && isNumber ( value ) ) { value = correctFloat ( log . lin2log ( value ) ) ; } if ( axis . dateTime ) { if ( tickPositionInfo ) { dateTimeLabelFormats = chart . time . resolveDTLFormat ( ( options . dateTimeLabelFormats as any ) [ ( ! options . grid && tickPositionInfo . higherRanks [ pos ] ) || tickPositionInfo . unitName ] ) ; dateTimeLabelFormat = dateTimeLabelFormats . main ; } else if ( isNumber ( value ) ) { dateTimeLabelFormat = axis . dateTime . getXDateFormat ( value , ( options . dateTimeLabelFormats || { } ) as any ) ; } } name readonly type { boolean | undefined } tick . isFirst = isFirst ; name readonly type { boolean | undefined } tick . isLast = isLast ; const ctx = { axis , chart , dateTimeLabelFormat : dateTimeLabelFormat as any , isFirst , isLast , pos , tick : tick as any , tickPositionInfo , value } ; fireEvent ( this , 'labelFormat' , ctx ) ; const labelFormatter = ( ctx ) : string => { if ( labelOptions . formatter ) { return labelOptions . formatter . call ( ctx , ctx ) ; } if ( labelOptions . format ) { ctx . text = axis . defaultLabelFormatter . call ( ctx ) ; return F . format ( labelOptions . format , ctx , chart ) ; } return axis . defaultLabelFormatter . call ( ctx , ctx ) ; } ; const str = labelFormatter . call ( ctx , ctx ) ; const list = dateTimeLabelFormats && dateTimeLabelFormats . list as any ; if ( list ) { tick . shortenLabel = function ( ) : void { for ( i = 0 ; i < list . length ; i ++ ) { extend ( ctx , { dateTimeLabelFormat : list [ i ] } ) ; ( label as any ) . attr ( { text : labelFormatter . call ( ctx , ctx ) } ) ; if ( ( label as any ) . getBBox ( ) . width < axis . getSlotWidth ( tick as any ) - 2 * labelOptions . padding ) { return ; } } ( label as any ) . attr ( { text : '' } ) ; } ; } else { tick . shortenLabel = void 0 ; } if ( animateLabels && axis . _addedPlotLB ) { tick . moveLabel ( str , labelOptions ) ; } if ( ! defined ( label ) && ! tick . movedLabel ) { name type { Highcharts . SVGElement | undefined } tick . label = label = tick . createLabel ( { x : 0 , y : 0 } , str , labelOptions ) ; tick . rotation = 0 ; } else if ( label && label . textStr !== str && ! animateLabels ) { if ( label . textWidth && ! labelOptions . style . width && ! ( label . styles as any ) . width ) { label . css ( { width : null as any } ) ; } label . attr ( { text : str } ) ; label . textPxLength = label . getBBox ( ) . width ; } } private function public createLabel ( xy , str , labelOptions ) { const axis = this . axis , chart = axis . chart , label = defined ( str ) && labelOptions . enabled ? chart . renderer . text ( str , xy . x , xy . y , labelOptions . useHTML ) . add ( axis . labelGroup ) : null as any ; if ( label ) { if ( ! chart . styledMode ) { label . css ( merge ( labelOptions . style ) ) ; } label . textPxLength = label . getBBox ( ) . width ; } return label ; } private function public destroy ( ) { destroyObjectProperties ( this , this . axis ) ; } private function param { boolean } horiz param { number } tickPos param { number } tickmarkOffset param { boolean } old return { Highcharts . PositionObject } emits public getPosition ( horiz , tickPos , tickmarkOffset , old ? ) { const axis = this . axis , chart = axis . chart , cHeight = ( old && chart . oldChartHeight ) || chart . chartHeight , pos = { x : horiz ? correctFloat ( ( axis . translate ( tickPos + tickmarkOffset , null , null , old ) as any ) + axis . transB ) : ( axis . left + axis . offset + ( axis . opposite ? ( ( ( old && chart . oldChartWidth as any ) || ( chart . chartWidth as any ) ) - axis . right - axis . left ) : 0 ) ) , y : horiz ? ( ( cHeight as any ) - axis . bottom + axis . offset - ( axis . opposite ? axis . height : 0 ) ) : correctFloat ( ( cHeight as any ) - ( axis . translate ( tickPos + tickmarkOffset , null , null , old ) as any ) - axis . transB ) } ; pos . y = clamp ( pos . y , - 1e5 , 1e5 ) ; fireEvent ( this , 'afterGetPosition' , { pos : pos } ) ; return pos ; } private public getLabelPosition ( x , y , label , horiz , labelOptions , tickmarkOffset , index , step ) { const axis = this . axis , transA = axis . transA , reversed = ( axis . isLinked && axis . linkedParent ? axis . linkedParent . reversed : axis . reversed ) , staggerLines = axis . staggerLines , rotCorr = axis . tickRotCorr || { x : 0 , y : 0 } , labelOffsetCorrection = ( ! horiz && ! axis . reserveSpaceDefault ? - ( axis . labelOffset as any ) * ( axis . labelAlign === 'center' ? 0.5 : 1 ) : 0 ) , pos = { } as PositionObject ; let yOffset = labelOptions . y , line ; if ( ! defined ( yOffset ) ) { if ( axis . side === 0 ) { yOffset = label . rotation ? - 8 : - label . getBBox ( ) . height ; } else if ( axis . side === 2 ) { yOffset = rotCorr . y + 8 ; } else { yOffset = Math . cos ( ( label . rotation as any ) * deg2rad ) * ( rotCorr . y - label . getBBox ( false , 0 ) . height / 2 ) ; } } x = x + labelOptions . x + labelOffsetCorrection + rotCorr . x - ( tickmarkOffset && horiz ? tickmarkOffset * transA * ( reversed ? - 1 : 1 ) : 0 ) ; y = y + yOffset - ( tickmarkOffset && ! horiz ? tickmarkOffset * transA * ( reversed ? 1 : - 1 ) : 0 ) ; if ( staggerLines ) { line = ( index / ( step || 1 ) % staggerLines ) ; if ( axis . opposite ) { line = staggerLines - line - 1 ; } y += line * ( ( axis . labelOffset as any ) / staggerLines ) ; } pos . x = x ; pos . y = Math . round ( y ) ; fireEvent ( this , 'afterGetLabelPosition' , { pos : pos , tickmarkOffset : tickmarkOffset , index : index } ) ; return pos ; } private function public getLabelSize ( ) { return this . label ? this . label . getBBox ( ) [ this . axis . horiz ? 'height' : 'width' ] : 0 ; } private public getMarkPath ( x , y , tickLength , tickWidth , horiz , renderer ) { return renderer . crispLine ( [ [ 'M' , x , y ] , [ 'L' , x + ( horiz ? 0 : - tickLength ) , y + ( horiz ? tickLength : 0 ) ] ] , tickWidth ) ; } private function public handleOverflow ( xy ) { const tick = this , axis = this . axis , labelOptions = axis . options . labels , pxPos = xy . x , chartWidth = axis . chart . chartWidth , spacing = axis . chart . spacing , leftBound = pick ( axis . labelLeft , Math . min ( axis . pos as any , spacing [ 3 ] ) ) , rightBound = pick ( axis . labelRight , Math . max ( ! axis . isRadial ? ( axis . pos as any ) + axis . len : 0 , ( chartWidth as any ) - spacing [ 1 ] ) ) , label = this . label , rotation = this . rotation , factor = ( { left : 0 , center : 0.5 , right : 1 } as Record < string , number > ) [ axis . labelAlign || ( label as any ) . attr ( 'align' ) ] , labelWidth = ( label as any ) . getBBox ( ) . width , slotWidth = axis . getSlotWidth ( tick as any ) , xCorrection = factor , css = { } ; let modifiedSlotWidth = slotWidth , goRight = 1 , leftPos , rightPos , textWidth ; if ( ! rotation && labelOptions . overflow === 'justify' ) { leftPos = pxPos - factor * labelWidth ; rightPos = pxPos + ( 1 - factor ) * labelWidth ; if ( leftPos < leftBound ) { modifiedSlotWidth = xy . x + modifiedSlotWidth * ( 1 - factor ) - leftBound ; } else if ( rightPos > rightBound ) { modifiedSlotWidth = rightBound - xy . x + modifiedSlotWidth * factor ; goRight = - 1 ; } modifiedSlotWidth = Math . min ( slotWidth , modifiedSlotWidth ) ; if ( modifiedSlotWidth < slotWidth && axis . labelAlign === 'center' ) { xy . x += ( goRight * ( slotWidth - modifiedSlotWidth - xCorrection * ( slotWidth - Math . min ( labelWidth , modifiedSlotWidth ) ) ) ) ; } if ( labelWidth > modifiedSlotWidth || ( axis . autoRotation && ( ( label as any ) . styles || { } ) . width ) ) { textWidth = modifiedSlotWidth ; } } else if ( ( rotation as any ) < 0 && pxPos - factor * labelWidth < leftBound ) { textWidth = Math . round ( pxPos / Math . cos ( ( rotation as any ) * deg2rad ) - leftBound ) ; } else if ( ( rotation as any ) > 0 && pxPos + factor * labelWidth > rightBound ) { textWidth = Math . round ( ( ( chartWidth as any ) - pxPos ) / Math . cos ( ( rotation as any ) * deg2rad ) ) ; } if ( textWidth ) { if ( tick . shortenLabel ) { tick . shortenLabel ( ) ; } else { css . width = Math . floor ( textWidth ) + 'px' ; if ( ! ( labelOptions . style || { } ) . textOverflow ) { css . textOverflow = 'ellipsis' ; } ( label as any ) . css ( css ) ; } } } private function public moveLabel ( str , labelOptions ) { const tick = this , label = tick . label , axis = tick . axis , reversed = axis . reversed ; let moved = false , labelPos , xPos , yPos ; if ( label && label . textStr === str ) { tick . movedLabel = label ; moved = true ; delete tick . label ; } else { objectEach ( axis . ticks , function ( currentTick ) : void { if ( ! moved && ! currentTick . isNew && currentTick !== tick && currentTick . label && currentTick . label . textStr === str ) { tick . movedLabel = currentTick . label ; moved = true ; currentTick . labelPos = tick . movedLabel . xy ; delete currentTick . label ; } } ) ; } if ( ! moved && ( tick . labelPos || label ) ) { labelPos = tick . labelPos || ( label as any ) . xy ; xPos = axis . horiz ? ( reversed ? 0 : axis . width + axis . left ) : labelPos . x ; yPos = axis . horiz ? labelPos . y : ( reversed ? ( axis . width + axis . left ) : 0 ) ; tick . movedLabel = tick . createLabel ( { x : xPos , y : yPos } , str , labelOptions ) ; if ( tick . movedLabel ) { tick . movedLabel . attr ( { opacity : 0 } ) ; } } } private param { number } index param { boolean } old param { number } opacity public render ( index , old ? , opacity ? ) { const tick = this , axis = tick . axis , horiz = axis . horiz , pos = tick . pos , tickmarkOffset = pick ( tick . tickmarkOffset , axis . tickmarkOffset ) , xy = tick . getPosition ( horiz , pos , tickmarkOffset , old ) , x = xy . x , y = xy . y , reverseCrisp = ( ( horiz && x === axis . pos + axis . len ) || ( ! horiz && y === axis . pos ) ) ? - 1 : 1 ; const labelOpacity = pick ( opacity , tick . label && tick . label . newOpacity , 1 ) ; opacity = pick ( opacity , 1 ) ; this . isActive = true ; this . renderGridLine ( old , opacity , reverseCrisp ) ; this . renderMark ( xy , opacity , reverseCrisp ) ; this . renderLabel ( xy , old , labelOpacity , index ) ; tick . isNew = false ; fireEvent ( this , 'afterRender' ) ; } private function param { boolean } old param { number } opacity param { number } reverseCrisp public renderGridLine ( old , opacity , reverseCrisp ) { const tick = this , axis = tick . axis , options = axis . options , attribs = { } , pos = tick . pos , type = tick . type , tickmarkOffset = pick ( tick . tickmarkOffset , axis . tickmarkOffset ) , renderer = axis . chart . renderer ; let gridLine = tick . gridLine , gridLinePath , gridLineWidth = options . gridLineWidth , gridLineColor = options . gridLineColor , dashStyle = options . gridLineDashStyle ; if ( tick . type === 'minor' ) { gridLineWidth = options . minorGridLineWidth ; gridLineColor = options . minorGridLineColor ; dashStyle = options . minorGridLineDashStyle ; } if ( ! gridLine ) { if ( ! axis . chart . styledMode ) { attribs . stroke = gridLineColor ; attribs [ 'stroke-width' ] = gridLineWidth || 0 ; attribs . dashstyle = dashStyle ; } if ( ! type ) { attribs . zIndex = 1 ; } if ( old ) { opacity = 0 ; } name type { Highcharts . SVGElement | undefined } tick . gridLine = gridLine = renderer . path ( ) . attr ( attribs ) . addClass ( 'highcharts-' + ( type ? type + '-' : '' ) + 'grid-line' ) . add ( axis . gridGroup ) ; } if ( gridLine ) { gridLinePath = axis . getPlotLinePath ( { value : pos + tickmarkOffset , lineWidth : gridLine . strokeWidth ( ) * reverseCrisp , force : 'pass' , old : old } ) ; if ( gridLinePath ) { gridLine [ old || tick . isNew ? 'attr' : 'animate' ] ( { d : gridLinePath , opacity : opacity } ) ; } } } private function param { Highcharts . PositionObject } xy param { number } opacity param { number } reverseCrisp public renderMark ( xy , opacity , reverseCrisp ) { const tick = this , axis = tick . axis , options = axis . options , renderer = axis . chart . renderer , type = tick . type , tickSize = axis . tickSize ( type ? type + 'Tick' : 'tick' ) , x = xy . x , y = xy . y , tickWidth = pick ( options [ type !== 'minor' ? 'tickWidth' : 'minorTickWidth' ] , ! type && axis . isXAxis ? 1 : 0 ) , tickColor = options [ type !== 'minor' ? 'tickColor' : 'minorTickColor' ] ; let mark = tick . mark ; const isNewMark = ! mark ; if ( tickSize ) { if ( axis . opposite ) { tickSize [ 0 ] = - tickSize [ 0 ] ; } if ( ! mark ) { name type { Highcharts . SVGElement | undefined } tick . mark = mark = renderer . path ( ) . addClass ( 'highcharts-' + ( type ? type + '-' : '' ) + 'tick' ) . add ( axis . axisGroup ) ; if ( ! axis . chart . styledMode ) { mark . attr ( { stroke : tickColor , 'stroke-width' : tickWidth } ) ; } } mark [ isNewMark ? 'attr' : 'animate' ] ( { d : tick . getMarkPath ( x , y , tickSize [ 0 ] , mark . strokeWidth ( ) * reverseCrisp , axis . horiz as any , renderer ) , opacity : opacity } ) ; } } private function param { Highcharts . PositionObject } xy param { boolean } old param { number } opacity param { number } index public renderLabel ( xy , old , opacity , index ) { const tick = this , axis = tick . axis , horiz = axis . horiz , options = axis . options , label = tick . label , labelOptions = options . labels , step = labelOptions . step , tickmarkOffset = pick ( tick . tickmarkOffset , axis . tickmarkOffset ) , x = xy . x , y = xy . y ; let show = true ; if ( label && isNumber ( x ) ) { label . xy = xy = tick . getLabelPosition ( x , y , label , horiz as any , labelOptions as any , tickmarkOffset , index , step ) ; if ( ( tick . isFirst && ! tick . isLast && ! options . showFirstLabel ) || ( tick . isLast && ! tick . isFirst && ! options . showLastLabel ) ) { show = false ; } else if ( horiz && ! labelOptions . step && ! labelOptions . rotation && ! old && opacity !== 0 ) { tick . handleOverflow ( xy ) ; } if ( step && index % step ) { show = false ; } if ( show && isNumber ( xy . y ) ) { xy . opacity = opacity ; label [ tick . isNewLabel ? 'attr' : 'animate' ] ( xy ) ; tick . isNewLabel = false ; } else { label . attr ( 'y' , - 9999 as any ) ; tick . isNewLabel = true ; } } } private function public replaceMovedLabel ( ) { const tick = this , label = tick . label , axis = tick . axis , reversed = axis . reversed ; let x , y ; if ( label && ! tick . isNew ) { x = axis . horiz ? ( reversed ? axis . left : axis . width + axis . left ) : label . xy . x ; y = axis . horiz ? label . xy . y : ( reversed ? axis . width + axis . top : axis . top ) ; label . animate ( { x : x , y : y , opacity : 0 } , void 0 , label . destroy ) ; delete tick . label ; } axis . isDirty = true ; tick . label = tick . movedLabel ; delete tick . movedLabel ; } } interface Tick extends TickLike { } namespace Tick { export interface ParametersObject { category ? : string ; options ? : AnyRecord ; tickmarkOffset ? : number ; } export interface LabelObject extends PositionObject { opacity ? : number ; } } export default Tick ; private interface name type { string | undefined } name type { Highcharts . Dictionary < any > | undefined } name type { number | undefined } interface extends Highcharts . TimeNormalizedObject name type { Array < string > } name type { number } ( '' ) ;