'use strict' ; import type { AlignValue } from '../../Core/Renderer/AlignObject' ; import type BBoxObject from '../../Core/Renderer/BBoxObject' ; import type BubbleSeries from './BubbleSeries' ; import type { BubbleSizeByValue } from './BubbleSeriesOptions' ; import type ColorType from '../../Core/Color/ColorType' ; import type CSSObject from '../../Core/Renderer/CSSObject' ; import type FontMetricsObject from '../../Core/Renderer/FontMetricsObject' ; import type FormatUtilities from '../../Core/FormatUtilities' ; import type LegendItemObject from '../../Core/Legend/LegendItemObject' ; import type Point from '../../Core/Series/Point' ; import type SVGAttributes from '../../Core/Renderer/SVG/SVGAttributes' ; import type SVGElement from '../../Core/Renderer/SVG/SVGElement' ; import Chart from '../../Core/Chart/Chart.js' ; import Color from '../../Core/Color/Color.js' ; const { parse : color } = Color ; import F from '../../Core/FormatUtilities.js' ; import H from '../../Core/Globals.js' ; const { noop } = H ; import Legend from '../../Core/Legend/Legend.js' ; import Series from '../../Core/Series/Series.js' ; import U from '../../Core/Utilities.js' ; const { arrayMax , arrayMin , isNumber , merge , pick , stableSort } = U ; declare module '../../Core/Legend/LegendLike' { interface LegendLike { bubbleLegend ? : BubbleLegendItem ; } } declare module '../../Core/Series/PointLike' { interface PointLike { isBubble ? : boolean ; } } declare module '../../Core/Series/SeriesLike' { interface SeriesLike { ignoreSeries ? : boolean ; isBubble ? : boolean ; } } declare module '../../Core/Legend/LegendOptions' { interface LegendOptions { bubbleLegend ? : BubbleLegendItem . Options ; } } interface name type { number } name type { number } name type { number } '' ; private class name param { Highcharts . LegendBubbleLegendOptions } options param { Highcharts . Legend } legend class BubbleLegendItem { public constructor ( options , legend ) { this . init ( options , legend ) ; } public chart : Chart = void 0 as any ; public fontMetrics : FontMetricsObject = void 0 as any ; public legend : Legend = void 0 as any ; public legendGroup : SVGElement = void 0 as any ; public legendItem : SVGElement = void 0 as any ; public legendItemHeight : number = void 0 as any ; public legendItemWidth : number = void 0 as any ; public legendSymbol : SVGElement = void 0 as any ; public maxLabel : BBoxObject = void 0 as any ; public movementX : number = void 0 as any ; public ranges : Array < BubbleLegendItem . RangesOptions > = void 0 as any ; public selected : undefined = void 0 as any ; public visible : boolean = void 0 as any ; public symbols : Record < string , Array < SVGElement > > = void 0 as any ; public options : BubbleLegendItem . Options = void 0 as any ; private function param { Highcharts . LegendBubbleLegendOptions } options param { Highcharts . Legend } legend public init ( options , legend ) { this . options = options ; this . visible = true ; this . chart = legend . chart ; this . legend = legend ; } public setState = noop ; private function param { Array < ( Highcharts . Point | Highcharts . Series ) > } items public addToLegend ( items ) { items . splice ( this . options . legendIndex as any , 0 , this as any ) ; } private function param { Highcharts . Legend } legend public drawLegendSymbol ( legend ) { const chart = this . chart , options = this . options , itemDistance = pick ( legend . options . itemDistance , 20 ) , ranges = options . ranges as Array < BubbleLegendItem . RangesOptions > , connectorDistance = options . connectorDistance ; let connectorSpace ; this . fontMetrics = chart . renderer . fontMetrics ( ( options . labels as any ) . style . fontSize ) ; if ( ! ranges || ! ranges . length || ! isNumber ( ranges [ 0 ] . value ) ) { ( legend . options . bubbleLegend as any ) . autoRanges = true ; return ; } stableSort ( ranges , function ( a , b ) : number { return b . value - a . value ; } ) ; this . ranges = ranges ; this . setOptions ( ) ; this . render ( ) ; const maxLabel = this . getMaxLabelSize ( ) , radius = this . ranges [ 0 ] . radius , size = ( radius as any ) * 2 ; connectorSpace = ( connectorDistance as any ) - ( radius as any ) + maxLabel . width ; connectorSpace = connectorSpace > 0 ? connectorSpace : 0 ; this . maxLabel = maxLabel ; this . movementX = ( options . labels as any ) . align === 'left' ? connectorSpace : 0 ; this . legendItemWidth = size + connectorSpace + itemDistance ; this . legendItemHeight = size + this . fontMetrics . h / 2 ; } private function public setOptions ( ) { const ranges = this . ranges , options = this . options , series = this . chart . series [ options . seriesIndex as any ] , baseline = this . legend . baseline , bubbleAttribs = { zIndex : options . zIndex , 'stroke-width' : options . borderWidth } , connectorAttribs = { zIndex : options . zIndex , 'stroke-width' : options . connectorWidth } , labelAttribs = { align : ( this . legend . options . rtl || ( options . labels as any ) . align === 'left' ) ? 'right' : 'left' , zIndex : options . zIndex } , fillOpacity = ( series . options . marker as any ) . fillOpacity , styledMode = this . chart . styledMode ; ranges . forEach ( function ( range , i ) : void { if ( ! styledMode ) { bubbleAttribs . stroke = pick ( range . borderColor , options . borderColor , series . color ) ; bubbleAttribs . fill = pick ( range . color , options . color , fillOpacity !== 1 ? color ( series . color ) . setOpacity ( fillOpacity ) . get ( 'rgba' ) : series . color ) ; connectorAttribs . stroke = pick ( range . connectorColor , options . connectorColor , series . color ) ; } ranges [ i ] . radius = this . getRangeRadius ( range . value ) ; ranges [ i ] = merge ( ranges [ i ] , { center : ( ( ranges [ 0 ] . radius as any ) - ( ranges [ i ] . radius as any ) + ( baseline as any ) ) } ) ; if ( ! styledMode ) { merge ( true , ranges [ i ] , { bubbleAttribs : merge ( bubbleAttribs ) , connectorAttribs : merge ( connectorAttribs ) , labelAttribs : labelAttribs } ) ; } } , this ) ; } private function param { number } value return { number | null } public getRangeRadius ( value ) { const options = this . options , seriesIndex = this . options . seriesIndex , bubbleSeries = this . chart . series [ seriesIndex as any ] as any , zMax = ( options . ranges as any ) [ 0 ] . value , zMin = ( options . ranges as any ) [ ( options . ranges as any ) . length - 1 ] . value , minSize = options . minSize , maxSize = options . maxSize ; return bubbleSeries . getRadius . call ( this , zMin , zMax , minSize as any , maxSize as any , value ) ; } private function public render ( ) { const renderer = this . chart . renderer , zThreshold = this . options . zThreshold ; if ( ! this . symbols ) { this . symbols = { connectors : [ ] , bubbleItems : [ ] , labels : [ ] } ; } this . legendSymbol = renderer . g ( 'bubble-legend' ) ; this . legendItem = renderer . g ( 'bubble-legend-item' ) ; this . legendSymbol . translateX = 0 ; this . legendSymbol . translateY = 0 ; this . ranges . forEach ( function ( range ) : void { if ( range . value >= ( zThreshold as any ) ) { this . renderRange ( range ) ; } } , this ) ; this . legendSymbol . add ( this . legendItem ) ; this . legendItem . add ( this . legendGroup ) ; this . hideOverlappingLabels ( ) ; } private function param { Highcharts . LegendBubbleLegendRangesOptions } range public renderRange ( range ) { const mainRange = this . ranges [ 0 ] , legend = this . legend , options = this . options , labelsOptions = options . labels as any , chart = this . chart , bubbleSeries = chart . series [ options . seriesIndex as any ] as any , renderer = chart . renderer , symbols = this . symbols , labels = symbols . labels , elementCenter = range . center , absoluteRadius = Math . abs ( range . radius as any ) , connectorDistance = options . connectorDistance || 0 , labelsAlign = ( labelsOptions as any ) . align , rtl = legend . options . rtl , borderWidth = options . borderWidth , connectorWidth = options . connectorWidth , posX = mainRange . radius || 0 , posY = ( elementCenter as any ) - absoluteRadius - ( borderWidth as any ) / 2 + ( connectorWidth as any ) / 2 , fontMetrics = this . fontMetrics , labelMovement = fontMetrics . f / 2 - ( fontMetrics . h - fontMetrics . f ) / 2 , crispMovement = ( posY % 1 ? 1 : 0.5 ) - ( ( connectorWidth as any ) % 2 ? 0 : 0.5 ) , styledMode = renderer . styledMode ; let connectorLength = rtl || labelsAlign === 'left' ? - connectorDistance : connectorDistance ; if ( labelsAlign === 'center' ) { connectorLength = 0 ; options . connectorDistance = 0 ; ( range . labelAttribs as any ) . align = 'center' ; } const labelY = posY + ( options . labels as any ) . y , labelX = posX + connectorLength + ( options . labels as any ) . x ; symbols . bubbleItems . push ( renderer . circle ( posX , ( elementCenter as any ) + crispMovement , absoluteRadius ) . attr ( styledMode ? { } : range . bubbleAttribs ) . addClass ( ( styledMode ? 'highcharts-color-' + bubbleSeries . colorIndex + ' ' : '' ) + 'highcharts-bubble-legend-symbol ' + ( options . className || '' ) ) . add ( this . legendSymbol ) ) ; symbols . connectors . push ( renderer . path ( renderer . crispLine ( [ [ 'M' , posX , posY ] , [ 'L' , posX + connectorLength , posY ] ] , options . connectorWidth as any ) ) . attr ( ( styledMode ? { } : range . connectorAttribs ) ) . addClass ( ( styledMode ? 'highcharts-color-' + this . options . seriesIndex + ' ' : '' ) + 'highcharts-bubble-legend-connectors ' + ( options . connectorClassName || '' ) ) . add ( this . legendSymbol ) ) ; const label = renderer . text ( this . formatLabel ( range ) , labelX , labelY + labelMovement ) . attr ( ( styledMode ? { } : range . labelAttribs ) ) . css ( styledMode ? { } : ( labelsOptions as any ) . style ) . addClass ( 'highcharts-bubble-legend-labels ' + ( ( options . labels as any ) . className || '' ) ) . add ( this . legendSymbol ) ; labels . push ( label ) ; label . placed = true ; label . alignAttr = { x : labelX , y : labelY + labelMovement } ; } private function public getMaxLabelSize ( ) { const labels = this . symbols . labels ; let maxLabel , labelSize ; labels . forEach ( function ( label ) : void { labelSize = label . getBBox ( true ) ; if ( maxLabel ) { maxLabel = labelSize . width > maxLabel . width ? labelSize : maxLabel ; } else { maxLabel = labelSize ; } } ) ; return maxLabel || ( { } as any ) ; } private function param { Highcharts . LegendBubbleLegendRangesOptions } range return { string } public formatLabel ( range ) { const options = this . options , formatter = ( options . labels as any ) . formatter , format = ( options . labels as any ) . format ; const { numberFormatter } = this . chart ; return format ? F . format ( format , range ) : formatter ? formatter . call ( range ) : numberFormatter ( range . value , 1 ) ; } private function public hideOverlappingLabels ( ) { const chart = this . chart , allowOverlap = ( this . options . labels as any ) . allowOverlap , symbols = this . symbols ; if ( ! allowOverlap && symbols ) { chart . hideOverlappingLabels ( symbols . labels ) ; symbols . labels . forEach ( function ( label , index ) : void { if ( ! label . newOpacity ) { symbols . connectors [ index ] . hide ( ) ; } else if ( label . newOpacity !== label . oldOpacity ) { symbols . connectors [ index ] . show ( ) ; } } ) ; } } private function return { Array < Highcharts . LegendBubbleLegendRangesOptions > } public getRanges ( ) { const bubbleLegend = this . legend . bubbleLegend , series = ( bubbleLegend as any ) . chart . series , rangesOptions = ( bubbleLegend as any ) . options . ranges ; let ranges , zData , minZ = Number . MAX_VALUE , maxZ = - Number . MAX_VALUE ; series . forEach ( function ( s ) : void { if ( s . isBubble && ! s . ignoreSeries ) { zData = s . zData . filter ( isNumber ) ; if ( zData . length ) { minZ = pick ( s . options . zMin , Math . min ( minZ , Math . max ( arrayMin ( zData ) , s . options . displayNegative === false ? ( s . options . zThreshold as any ) : - Number . MAX_VALUE ) ) ) ; maxZ = pick ( s . options . zMax , Math . max ( maxZ , arrayMax ( zData ) ) ) ; } } } ) ; if ( minZ === maxZ ) { ranges = [ { value : maxZ } ] ; } else { ranges = [ { value : minZ } , { value : ( minZ + maxZ ) / 2 } , { value : maxZ , autoRanges : true } ] ; } if ( rangesOptions . length && rangesOptions [ 0 ] . radius ) { ranges . reverse ( ) ; } ranges . forEach ( function ( range , i ) : void { if ( rangesOptions && rangesOptions [ i ] ) { ranges [ i ] = merge ( rangesOptions [ i ] , range ) ; } } ) ; return ranges ; } private function return { Array < number , number > } public predictBubbleSizes ( ) { const chart = this . chart , fontMetrics = this . fontMetrics , legendOptions = chart . legend . options , floating = legendOptions . floating , horizontal = legendOptions . layout === 'horizontal' , lastLineHeight = horizontal ? chart . legend . lastLineHeight : 0 , plotSizeX = chart . plotSizeX , plotSizeY = chart . plotSizeY , bubbleSeries = chart . series [ this . options . seriesIndex as any ] as any , pxSizes = bubbleSeries . getPxExtremes ( ) , minSize = Math . ceil ( pxSizes . minPxSize ) , maxPxSize = Math . ceil ( pxSizes . maxPxSize ) , plotSize = Math . min ( plotSizeY as any , plotSizeX as any ) ; let calculatedSize , maxSize = bubbleSeries . options . maxSize ; if ( floating || ! ( /%$/ . test ( maxSize as any ) ) ) { calculatedSize = maxPxSize ; } else { maxSize = parseFloat ( maxSize as any ) ; calculatedSize = ( ( plotSize + lastLineHeight - fontMetrics . h / 2 ) * maxSize / 100 ) / ( maxSize / 100 + 1 ) ; if ( ( horizontal && ( plotSizeY as any ) - calculatedSize >= ( plotSizeX as any ) ) || ( ! horizontal && ( plotSizeX as any ) - calculatedSize >= ( plotSizeY as any ) ) ) { calculatedSize = maxPxSize ; } } return [ minSize , Math . ceil ( calculatedSize ) ] ; } private function param { number } min param { number } max public updateRanges ( min , max ) { const bubbleLegendOptions = this . legend . options . bubbleLegend ; ( bubbleLegendOptions as any ) . minSize = min ; ( bubbleLegendOptions as any ) . maxSize = max ; ( bubbleLegendOptions as any ) . ranges = this . getRanges ( ) ; } private function public correctSizes ( ) { const legend = this . legend , chart = this . chart , bubbleSeries = chart . series [ this . options . seriesIndex as any ] as any , pxSizes = bubbleSeries . getPxExtremes ( ) , bubbleSeriesSize = pxSizes . maxPxSize , bubbleLegendSize = this . options . maxSize ; if ( Math . abs ( Math . ceil ( bubbleSeriesSize ) - ( bubbleLegendSize as any ) ) > 1 ) { this . updateRanges ( this . options . minSize as any , pxSizes . maxPxSize ) ; legend . render ( ) ; } } } interface BubbleLegendItem extends LegendItemObject { } namespace BubbleLegendItem { export interface FormatterContextObject { center : number ; radius : ( number | null ) ; value : number ; } export interface LabelsOptions { align ? : AlignValue ; allowOverlap ? : boolean ; className ? : string ; format ? : string ; formatter ? : ( FormatUtilities . FormatterCallback < FormatterContextObject > ) ; style ? : CSSObject ; x ? : number ; y ? : number ; } export interface Options { borderColor ? : ColorType ; borderWidth ? : number ; className ? : string ; color ? : ColorType ; connectorClassName ? : string ; connectorColor ? : ColorType ; connectorDistance ? : number ; connectorWidth ? : number ; enabled ? : boolean ; labels ? : LabelsOptions ; legendIndex ? : number ; maxSize ? : number ; minSize ? : number ; placed ? : boolean ; ranges ? : Array < RangesOptions > ; seriesIndex ? : number ; sizeBy ? : BubbleSizeByValue ; sizeByAbsoluteValue ? : boolean ; zIndex ? : number ; zThreshold ? : number ; } export interface RangesOptions extends Partial < FormatterContextObject > { autoRanges ? : boolean ; borderColor ? : ColorType ; color ? : ColorType ; connectorColor ? : ColorType ; bubbleAttribs ? : SVGAttributes ; connectorAttribs ? : SVGAttributes ; labelAttribs ? : SVGAttributes ; value ? : any ; } } export default BubbleLegendItem ;