import type { AlignValue } from '../AlignObject' ; import type BBoxObject from '../BBoxObject' ; import type ColorType from '../../Color/ColorType' ; import type CSSObject from '../CSSObject' ; import type ShadowOptionsObject from '../ShadowOptionsObject' ; import type SVGAttributes from './SVGAttributes' ; import type SVGPath from './SVGPath' ; import type SVGRenderer from './SVGRenderer' ; import type { SymbolKey } from './SymbolType' ; import SVGElement from './SVGElement.js' ; import U from '../../Utilities.js' ; const { defined , extend , isNumber , merge , pick , removeEvent } = U ; private class name augments Highcharts . SVGElement class SVGLabel extends SVGElement { public static readonly emptyBBox : BBoxObject = { width : 0 , height : 0 , x : 0 , y : 0 } ; private name type { Array < string > } public static textProps : Array < keyof CSSObject > = [ 'color' , 'direction' , 'fontFamily' , 'fontSize' , 'fontStyle' , 'fontWeight' , 'lineHeight' , 'textAlign' , 'textDecoration' , 'textOutline' , 'textOverflow' , 'width' ] ; public constructor ( renderer , str , x , y ? , shape ? , anchorX ? , anchorY ? , useHTML ? , baseline ? , className ? ) { super ( ) ; this . init ( renderer , 'g' ) ; this . textStr = str ; this . x = x ; this . y = y ; this . anchorX = anchorX ; this . anchorY = anchorY ; this . baseline = baseline ; this . className = className ; this . addClass ( className === 'button' ? 'highcharts-no-tooltip' : 'highcharts-label' ) ; if ( className ) { this . addClass ( 'highcharts-' + className ) ; } this . text = renderer . text ( void 0 , 0 , 0 , useHTML ) . attr ( { zIndex : 1 } ) ; let hasBGImage ; if ( typeof shape === 'string' ) { hasBGImage = /^url\((.*?)\)$/ . test ( shape ) ; if ( hasBGImage || this . renderer . symbols [ shape as SymbolKey ] ) { this . symbolKey = shape ; } } this . bBox = SVGLabel . emptyBBox ; this . padding = 3 ; this . baselineOffset = 0 ; this . needsBox = renderer . styledMode || hasBGImage ; this . deferredAttr = { } ; this . alignFactor = 0 ; } public alignFactor : number ; public baselineOffset : number ; public bBox : BBoxObject ; public box ? : SVGElement ; public deferredAttr : ( SVGAttributes & AnyRecord ) ; public heightSetting ? : number ; public needsBox ? : boolean ; public padding : number ; public paddingLeftSetter = this . paddingSetter ; public paddingRightSetter = this . paddingSetter ; public text : SVGElement ; public textStr : string ; public x : number ; public alignSetter ( value ) { const alignFactor = ( { left : 0 , center : 0.5 , right : 1 } ) [ value ] ; if ( alignFactor !== this . alignFactor ) { this . alignFactor = alignFactor ; if ( this . bBox && isNumber ( this . xSetting ) ) { this . attr ( { x : this . xSetting } ) ; } } } public anchorXSetter ( value , key ) { this . anchorX = value ; this . boxAttr ( key , Math . round ( value ) - this . getCrispAdjust ( ) - this . xSetting ) ; } public anchorYSetter ( value , key ) { this . anchorY = value ; this . boxAttr ( key , value - this . ySetting ) ; } private boxAttr ( key , value ) { if ( this . box ) { this . box . attr ( key , value ) ; } else { this . deferredAttr [ key ] = value ; } } public css ( styles ) { if ( styles ) { const textStyles = { } ; styles = merge ( styles ) ; SVGLabel . textProps . forEach ( ( prop ) : void => { if ( typeof styles [ prop ] !== 'undefined' ) { textStyles [ prop ] = styles [ prop ] ; delete styles [ prop ] ; } } ) ; this . text . css ( textStyles ) ; const isWidth = 'width' in textStyles , isFontStyle = ( 'fontSize' in textStyles || 'fontWeight' in textStyles ) ; if ( isFontStyle ) { this . updateTextPadding ( ) ; } else if ( isWidth ) { this . updateBoxSize ( ) ; } } return SVGElement . prototype . css . call ( this , styles ) as this ; } public destroy ( ) { removeEvent ( this . element , 'mouseenter' ) ; removeEvent ( this . element , 'mouseleave' ) ; if ( this . text ) { this . text . destroy ( ) ; } if ( this . box ) { this . box = this . box . destroy ( ) ; } SVGElement . prototype . destroy . call ( this ) ; return void 0 ; } public fillSetter ( value , key ) { if ( value ) { this . needsBox = true ; } this . fill = value ; this . boxAttr ( key , value ) ; } public getBBox ( ) { if ( this . textStr && this . bBox . width === 0 && this . bBox . height === 0 ) { this . updateBoxSize ( ) ; } const padding = this . padding ; const paddingLeft = pick ( this . paddingLeft , padding ) ; return { width : this . width , height : this . height , x : this . bBox . x - paddingLeft , y : this . bBox . y - padding } ; } private getCrispAdjust ( ) { return this . renderer . styledMode && this . box ? this . box . strokeWidth ( ) % 2 / 2 : ( this [ 'stroke-width' ] ? parseInt ( this [ 'stroke-width' ] , 10 ) : 0 ) % 2 / 2 ; } public heightSetter ( value ) { this . heightSetting = value ; } public onAdd ( ) { const str = this . textStr ; this . text . add ( this ) ; this . attr ( { text : ( defined ( str ) ? str : '' ) , x : this . x , y : this . y } ) ; if ( this . box && defined ( this . anchorX ) ) { this . attr ( { anchorX : this . anchorX , anchorY : this . anchorY } ) ; } } public paddingSetter ( value , key ) { if ( ! isNumber ( value ) ) { this [ key ] = void 0 ; } else if ( value !== this [ key ] ) { this [ key ] = value ; this . updateTextPadding ( ) ; } } public rSetter ( value , key ) { this . boxAttr ( key , value ) ; } public shadow ( b ? ) { if ( b && ! this . renderer . styledMode ) { this . updateBoxSize ( ) ; if ( this . box ) { this . box . shadow ( b ) ; } } return this ; } public strokeSetter ( value , key ) { this . stroke = value ; this . boxAttr ( key , value ) ; } public 'stroke-widthSetter' ( value , key ) { if ( value ) { this . needsBox = true ; } this [ 'stroke-width' ] = value ; this . boxAttr ( key , value ) ; } public 'text-alignSetter' ( value ) { this . textAlign = value ; } public textSetter ( text ? ) { if ( typeof text !== 'undefined' ) { this . text . attr ( { text } ) ; } this . updateTextPadding ( ) ; } private updateBoxSize ( ) { const style = this . text . element . style , attribs = { } , padding = this . padding , bBox = this . bBox = ( ( ( ! isNumber ( this . widthSetting ) || ! isNumber ( this . heightSetting ) || this . textAlign ) && defined ( this . text . textStr ) ) ? this . text . getBBox ( ) : SVGLabel . emptyBBox ) ; let crispAdjust ; this . width = this . getPaddedWidth ( ) ; this . height = ( this . heightSetting || bBox . height || 0 ) + 2 * padding ; const metrics = this . renderer . fontMetrics ( style && style . fontSize , this . text ) ; this . baselineOffset = padding + Math . min ( ( this . text . firstLineMetrics || metrics ) . b , bBox . height || Infinity ) ; if ( this . heightSetting ) { this . baselineOffset += ( this . heightSetting - metrics . h ) / 2 ; } if ( this . needsBox ) { if ( ! this . box ) { const box = this . box = this . symbolKey ? this . renderer . symbol ( this . symbolKey ) : this . renderer . rect ( ) ; box . addClass ( ( this . className === 'button' ? '' : 'highcharts-label-box' ) + ( this . className ? ' highcharts-' + this . className + '-box' : '' ) ) ; box . add ( this ) ; } crispAdjust = this . getCrispAdjust ( ) ; attribs . x = crispAdjust ; attribs . y = ( ( this . baseline ? - this . baselineOffset : 0 ) + crispAdjust ) ; attribs . width = Math . round ( this . width ) ; attribs . height = Math . round ( this . height ) ; this . box . attr ( extend ( attribs , this . deferredAttr ) ) ; this . deferredAttr = { } ; } } public updateTextPadding ( ) { const text = this . text ; this . updateBoxSize ( ) ; const textY = this . baseline ? 0 : this . baselineOffset ; let textX = pick ( this . paddingLeft , this . padding ) ; if ( defined ( this . widthSetting ) && this . bBox && ( this . textAlign === 'center' || this . textAlign === 'right' ) ) { textX += { center : 0.5 , right : 1 } [ this . textAlign as ( 'center' | 'right' ) ] * ( this . widthSetting - this . bBox . width ) ; } if ( textX !== text . x || textY !== text . y ) { text . attr ( 'x' , textX ) ; if ( text . hasBoxWidthChanged ) { this . bBox = text . getBBox ( true ) ; } if ( typeof textY !== 'undefined' ) { text . attr ( 'y' , textY ) ; } } text . x = textX ; text . y = textY ; } public widthSetter ( value ) { this . widthSetting = isNumber ( value ) ? value : void 0 ; } public getPaddedWidth ( ) { const padding = this . padding ; const paddingLeft = pick ( this . paddingLeft , padding ) ; const paddingRight = pick ( this . paddingRight , padding ) ; return ( ( this . widthSetting || this . bBox . width || 0 ) + paddingLeft + paddingRight ) ; } public xSetter ( value ) { this . x = value ; if ( this . alignFactor ) { value -= this . alignFactor * this . getPaddedWidth ( ) ; this [ 'forceAnimate:x' ] = true ; } this . xSetting = Math . round ( value ) ; this . attr ( 'translateX' , this . xSetting ) ; } public ySetter ( value ) { this . ySetting = this . y = Math . round ( value ) ; this . attr ( 'translateY' , this . ySetting ) ; } } export default SVGLabel ;