'use strict' ; import type { AlignValue , VerticalAlignValue } from '../Core/Renderer/AlignObject' ; import type AxisOptions from '../Core/Axis/AxisOptions' ; import type ButtonThemeObject from '../Core/Renderer/SVG/ButtonThemeObject' ; import type ColorString from '../Core/Color/ColorString' ; import type CSSObject from '../Core/Renderer/CSSObject' ; import type { HTMLDOMElement } from '../Core/Renderer/DOMElementType' ; import type SVGAttributes from '../Core/Renderer/SVG/SVGAttributes' ; import type Time from '../Core/Time' ; import Axis from '../Core/Axis/Axis.js' ; import Chart from '../Core/Chart/Chart.js' ; import H from '../Core/Globals.js' ; import D from '../Core/DefaultOptions.js' ; const { defaultOptions } = D ; import { Palette } from '../Core/Color/Palettes.js' ; import SVGElement from '../Core/Renderer/SVG/SVGElement.js' ; import U from '../Core/Utilities.js' ; const { addEvent , createElement , css , defined , destroyObjectProperties , discardElement , extend , find , fireEvent , isNumber , merge , objectEach , pad , pick , pInt , splat } = U ; declare module '../Core/Axis/AxisLike' { interface AxisLike { newMax ? : number ; range ? : ( null | number | Highcharts . RangeSelectorButtonsOptions ) ; } } declare module '../Core/Chart/ChartLike' { interface ChartLike { extraBottomMargin ? : boolean ; extraTopMargin ? : boolean ; fixedRange ? : number ; rangeSelector ? : Highcharts . RangeSelector ; } } declare module '../Core/LangOptions' { interface LangOptions { rangeSelectorFrom ? : string ; rangeSelectorTo ? : string ; rangeSelectorZoom ? : string ; } } declare module '../Core/Options' { interface Options { rangeSelector ? : DeepPartial < Highcharts . RangeSelectorOptions > ; } } private declare global { namespace Highcharts { type RangeSelectorButtonTypeValue = ( 'all' | 'day' | 'hour' | 'millisecond' | 'minute' | 'month' | 'second' | 'week' | 'year' | 'ytd' ) ; interface RangeSelectorClickCallbackFunction { ( e ) : ( boolean | undefined ) ; } interface RangeSelectorParseCallbackFunction { ( value , useUTC , time ? ) : number ; } interface RangeSelectorButtonsEventsOptions { click ? : RangeSelectorClickCallbackFunction ; } interface RangeSelectorButtonsOptions { _offsetMax ? : number ; _offsetMin ? : number ; _range ? : number ; count ? : number ; dataGrouping ? : DataGroupingOptionsObject ; title ? : string ; events ? : RangeSelectorButtonsEventsOptions ; offsetMax ? : number ; offsetMin ? : number ; preserveDataGrouping ? : boolean ; text : string ; type ? : RangeSelectorButtonTypeValue ; } interface RangeSelectorInputElements { dateBox : SVGElement ; input : HTMLInputElement ; label : SVGElement ; } interface RangeSelectorOptions { allButtonsEnabled : boolean ; buttonPosition : RangeSelectorPositionOptions ; buttons ? : Array < RangeSelectorButtonsOptions > ; buttonSpacing : number ; buttonTheme : ButtonThemeObject ; dropdown : 'always' | 'never' | 'responsive' ; enabled ? : boolean ; floating : boolean ; height ? : number ; inputBoxBorderColor : ColorString ; inputBoxHeight : number ; inputBoxWidth ? : number ; inputDateFormat : string ; inputDateParser ? : RangeSelectorParseCallbackFunction ; inputEditDateFormat : string ; inputEnabled : boolean ; inputPosition : RangeSelectorPositionOptions ; inputSpacing : number ; inputStyle : CSSObject ; labelStyle : CSSObject ; selected ? : number ; verticalAlign : VerticalAlignValue ; x : number ; y : number ; } interface RangeSelectorPositionOptions { align : AlignValue ; x : number ; y : number ; } class RangeSelector { public constructor ( chart ) ; public buttons : Array < SVGElement > ; public buttonGroup ? : SVGElement ; public buttonOptions : Array < RangeSelectorButtonsOptions > ; public chart : Chart ; public defaultButtons : Array < RangeSelectorButtonsOptions > ; public deferredYTDClick ? : number ; public div ? : HTMLDOMElement ; public dropdown ? : HTMLSelectElement ; public eventsToUnbind ? : Array < Function > ; public forcedDataGrouping ? : boolean ; public frozenStates ? : boolean ; public group ? : SVGElement ; public hasVisibleDropdown ? : boolean ; public inputGroup ? : SVGElement ; public isActive ? : boolean ; public maxDateBox ? : SVGElement ; public maxInput ? : HTMLInputElement ; public minDateBox ? : SVGElement ; public minInput ? : HTMLInputElement ; public options : RangeSelectorOptions ; public rendered ? : boolean ; public selected ? : number ; public zoomText ? : SVGElement ; public clickButton ( i , redraw ? ) ; public computeButtonRange ( rangeOptions ) ; public destroy ( ) ; public drawInput ( name ) ; public getHeight ( ) ; public getInputValue ( name ) ; public getPosition ( ) ; public getYTDExtremes ( dataMax , dataMin , useUTC ? ) ; public hideInput ( name ) ; public init ( chart ) ; public render ( min ? , max ? ) ; public setInputExtremes ( name , min , max ) ; public setInputValue ( name , inputTime ? ) ; public setSelected ( selected ) ; public showInput ( name ) ; public titleCollision ( chart ) ; public update ( options ) ; public updateButtonStates ( ) ; } } } typedef { "all" | "day" | "hour" | "millisecond" | "minute" | "month" | "second" | "week" | "year" | "ytd" } Highcharts . RangeSelectorButtonTypeValue callback Highcharts . RangeSelectorClickCallbackFunction param { global . Event } e param { boolean | undefined } Return callback Highcharts . RangeSelectorParseCallbackFunction param { string } value return { number } extend ( defaultOptions , { product optionparent rangeSelector : { sample since allButtonsEnabled : false , sample type { Array < * > } buttons : void 0 , type { number } default apioption sample type { Highcharts . RangeSelectorClickCallbackFunction } apioption sample type { number } default since apioption sample type { number } default since apioption sample type { boolean } default since apioption see sample type { * } extends plotOptions . series . dataGrouping apioption type { string } apioption type { string } apioption type { Highcharts . RangeSelectorButtonTypeValue } apioption /**
         * The space in pixels between the buttons in the range selector.
         */ buttonSpacing : 5 , sample validvalue since dropdown : 'responsive' , sample type { boolean | undefined } default enabled : void 0 , sample sample type { Highcharts . VerticalAlignValue } since verticalAlign : 'top' , sample type { Highcharts . SVGAttributes } buttonTheme : { ignore width : 28 , ignore height : 18 , ignore padding : 2 , ignore zIndex : 7 } , sample since floating : false , since x : 0 , since y : 0 , deprecated type { number | undefined } since height : void 0 , sample type { Highcharts . ColorString } since inputBoxBorderColor : 'none' , sample since inputBoxHeight : 17 , sample type { number | undefined } since inputBoxWidth : void 0 , sample sample inputDateFormat : '%b %e, %Y' , sample type { Highcharts . RangeSelectorParseCallbackFunction } since inputDateParser : void 0 , sample inputEditDateFormat : '%Y-%m-%d' , /**
         * Enable or disable the date input boxes.
         */ inputEnabled : true , since inputPosition : { sample type { Highcharts . AlignValue } since align : 'right' , /**
             * X offset of the input row.
             */ x : 0 , /**
             * Y offset of the input row.
             */ y : 0 } , since inputSpacing : 5 , type { number } selected : void 0 , since buttonPosition : { sample type { Highcharts . AlignValue } since align : 'left' , /**
             * X offset of the button row.
             */ x : 0 , /**
             * Y offset of the button row.
             */ y : 0 } , sample type { Highcharts . CSSObject } apioption inputStyle : { ignore color : Palette . highlightColor80 , ignore cursor : 'pointer' } , sample type { Highcharts . CSSObject } labelStyle : { ignore color : Palette . neutralColor60 } } as Highcharts . RangeSelectorOptions } ) ; extend ( defaultOptions . lang , { product rangeSelectorZoom : 'Zoom' , product rangeSelectorFrom : '' , product rangeSelectorTo : '→' } ) ; private class name param { Highcharts . Chart } chart class RangeSelector { public constructor ( chart ) { this . chart = chart ; this . init ( chart ) ; } public buttons : Array < SVGElement > = void 0 as any ; public buttonGroup ? : SVGElement ; public buttonOptions : Array < Highcharts . RangeSelectorButtonsOptions > = RangeSelector . prototype . defaultButtons ; public chart : Chart ; public deferredYTDClick ? : number ; public div ? : HTMLDOMElement ; public dropdown ? : HTMLSelectElement ; public eventsToUnbind ? : Array < Function > ; public forcedDataGrouping ? : boolean ; public frozenStates ? : boolean ; public group ? : SVGElement ; public hasVisibleDropdown ? : boolean ; public initialButtonGroupWidth = 0 ; public inputGroup ? : SVGElement ; public isActive ? : boolean ; public maxDateBox ? : SVGElement ; public maxInput ? : HTMLInputElement ; public maxLabel ? : SVGElement ; public minDateBox ? : SVGElement ; public minInput ? : HTMLInputElement ; public minLabel ? : SVGElement ; public options : Highcharts . RangeSelectorOptions = void 0 as any ; public rendered ? : boolean ; public selected ? : number ; public zoomText ? : SVGElement ; private function param { number } i param { boolean } redraw public clickButton ( i , redraw ? ) { let rangeSelector = this , chart = rangeSelector . chart , rangeOptions = rangeSelector . buttonOptions [ i ] , baseAxis = chart . xAxis [ 0 ] , unionExtremes = ( chart . scroller && chart . scroller . getUnionExtremes ( ) ) || baseAxis || { } , dataMin = unionExtremes . dataMin , dataMax = unionExtremes . dataMax , newMin , newMax = baseAxis && Math . round ( Math . min ( baseAxis . max as any , pick ( dataMax , baseAxis . max as any ) ) ) , type = rangeOptions . type , baseXAxisOptions , range = rangeOptions . _range , rangeMin , minSetting , rangeSetting , ctx , ytdExtremes , dataGrouping = rangeOptions . dataGrouping ; if ( dataMin === null || dataMax === null ) { return ; } chart . fixedRange = range ; rangeSelector . setSelected ( i ) ; if ( dataGrouping ) { this . forcedDataGrouping = true ; Axis . prototype . setDataGrouping . call ( baseAxis || { chart : this . chart } , dataGrouping , false ) ; this . frozenStates = rangeOptions . preserveDataGrouping ; } if ( type === 'month' || type === 'year' ) { if ( ! baseAxis ) { range = rangeOptions as any ; } else { ctx = { range : rangeOptions , max : newMax , chart : chart , dataMin : dataMin , dataMax : dataMax } as any ; newMin = baseAxis . minFromRange . call ( ctx ) ; if ( isNumber ( ctx . newMax ) ) { newMax = ctx . newMax as any ; } } } else if ( range ) { newMin = Math . max ( newMax - range , dataMin as any ) ; newMax = Math . min ( newMin + range , dataMax as any ) ; } else if ( type === 'ytd' ) { if ( baseAxis ) { if ( typeof dataMax === 'undefined' ) { dataMin = Number . MAX_VALUE ; dataMax = Number . MIN_VALUE ; chart . series . forEach ( function ( series ) : void { const xData = series . xData ; dataMin = Math . min ( ( xData as any ) [ 0 ] , dataMin as any ) ; dataMax = Math . max ( ( xData as any ) [ ( xData as any ) . length - 1 ] , dataMax as any ) ; } ) ; redraw = false ; } ytdExtremes = rangeSelector . getYTDExtremes ( dataMax , dataMin as any , chart . time . useUTC ) ; newMin = rangeMin = ytdExtremes . min ; newMax = ytdExtremes . max ; } else { rangeSelector . deferredYTDClick = i ; return ; } } else if ( type === 'all' && baseAxis ) { if ( chart . navigator && chart . navigator . baseSeries [ 0 ] ) { chart . navigator . baseSeries [ 0 ] . xAxis . options . range = void 0 as any ; } newMin = dataMin ; newMax = dataMax as any ; } if ( defined ( newMin ) ) { newMin += rangeOptions . _offsetMin as any ; } if ( defined ( newMax ) ) { newMax += rangeOptions . _offsetMax as any ; } if ( this . dropdown ) { this . dropdown . selectedIndex = i + 1 ; } if ( ! baseAxis ) { baseXAxisOptions = splat ( chart . options . xAxis as any ) [ 0 ] ; rangeSetting = baseXAxisOptions . range ; baseXAxisOptions . range = range ; minSetting = baseXAxisOptions . min ; baseXAxisOptions . min = rangeMin ; addEvent ( chart , 'load' , function resetMinAndRange ( ) : void { baseXAxisOptions . range = rangeSetting ; baseXAxisOptions . min = minSetting ; } ) ; } else { baseAxis . setExtremes ( newMin , newMax , pick ( redraw , true ) , void 0 , { trigger : 'rangeSelectorButton' , rangeSelectorButton : rangeOptions } ) ; } fireEvent ( this , 'afterBtnClick' ) ; } private function param { number } selected public setSelected ( selected ? ) { this . selected = this . options . selected = selected ; } private function param { Highcharts . Chart } chart public init ( chart ) { const rangeSelector = this , options = ( chart . options . rangeSelector as Highcharts . RangeSelectorOptions ) , buttonOptions = ( options . buttons || rangeSelector . defaultButtons . slice ( ) ) , selectedOption = options . selected , blurInputs = function ( ) : void { const minInput = rangeSelector . minInput , maxInput = rangeSelector . maxInput ; if ( minInput && minInput . blur ) { fireEvent ( minInput , 'blur' ) ; } if ( maxInput && maxInput . blur ) { fireEvent ( maxInput , 'blur' ) ; } } ; rangeSelector . chart = chart ; rangeSelector . options = options ; rangeSelector . buttons = [ ] ; rangeSelector . buttonOptions = buttonOptions ; this . eventsToUnbind = [ ] ; this . eventsToUnbind . push ( addEvent ( chart . container , 'mousedown' , blurInputs ) ) ; this . eventsToUnbind . push ( addEvent ( chart , 'resize' , blurInputs ) ) ; buttonOptions . forEach ( rangeSelector . computeButtonRange ) ; if ( typeof selectedOption !== 'undefined' && buttonOptions [ selectedOption ] ) { this . clickButton ( selectedOption , false ) ; } this . eventsToUnbind . push ( addEvent ( chart , 'load' , function ( ) : void { if ( chart . xAxis && chart . xAxis [ 0 ] ) { addEvent ( chart . xAxis [ 0 ] , 'setExtremes' , function ( e ) : void { if ( ( this . max as any ) - ( this . min as any ) !== chart . fixedRange && e . trigger !== 'rangeSelectorButton' && e . trigger !== 'updatedData' && rangeSelector . forcedDataGrouping && ! rangeSelector . frozenStates ) { this . setDataGrouping ( false , false ) ; } } ) ; } } ) ) ; } private function public updateButtonStates ( ) { let rangeSelector = this , chart = this . chart , dropdown = this . dropdown , baseAxis = chart . xAxis [ 0 ] , actualRange = Math . round ( ( baseAxis . max as any ) - ( baseAxis . min as any ) ) , hasNoData = ! baseAxis . hasVisibleSeries , day = 24 * 36e5 , unionExtremes = ( chart . scroller && chart . scroller . getUnionExtremes ( ) ) || baseAxis , dataMin = unionExtremes . dataMin , dataMax = unionExtremes . dataMax , ytdExtremes = rangeSelector . getYTDExtremes ( dataMax as any , dataMin as any , chart . time . useUTC ) , ytdMin = ytdExtremes . min , ytdMax = ytdExtremes . max , selected = rangeSelector . selected , selectedExists = isNumber ( selected ) , allButtonsEnabled = rangeSelector . options . allButtonsEnabled , buttons = rangeSelector . buttons ; rangeSelector . buttonOptions . forEach ( function ( rangeOptions , i ) : void { let range = rangeOptions . _range , type = rangeOptions . type , count = rangeOptions . count || 1 , button = buttons [ i ] , state = 0 , disable , select , offsetRange = ( rangeOptions . _offsetMax as any ) - ( rangeOptions . _offsetMin as any ) , isSelected = i === selected , isTooGreatRange = ( range as any ) > ( dataMax as any ) - ( dataMin as any ) , isTooSmallRange = ( range as any ) < ( baseAxis . minRange as any ) , isYTDButNotSelected = false , isAllButAlreadyShowingAll = false , isSameRange = range === actualRange ; if ( ( type === 'month' || type === 'year' ) && ( actualRange + 36e5 >= { month : 28 , year : 365 } [ type ] * day * count - offsetRange ) && ( actualRange - 36e5 <= { month : 31 , year : 366 } [ type ] * day * count + offsetRange ) ) { isSameRange = true ; } else if ( type === 'ytd' ) { isSameRange = ( ytdMax - ytdMin + offsetRange ) === actualRange ; isYTDButNotSelected = ! isSelected ; } else if ( type === 'all' ) { isSameRange = ( ( baseAxis . max as any ) - ( baseAxis . min as any ) >= ( dataMax as any ) - ( dataMin as any ) ) ; isAllButAlreadyShowingAll = ( ! isSelected && selectedExists && isSameRange ) ; } disable = ( ! allButtonsEnabled && ( isTooGreatRange || isTooSmallRange || isAllButAlreadyShowingAll || hasNoData ) ) ; select = ( ( isSelected && isSameRange ) || ( isSameRange && ! selectedExists && ! isYTDButNotSelected ) || ( isSelected && rangeSelector . frozenStates ) ) ; if ( disable ) { state = 3 ; } else if ( select ) { selectedExists = true ; state = 2 ; } if ( button . state !== state ) { button . setState ( state ) ; if ( dropdown ) { dropdown . options [ i + 1 ] . disabled = disable ; if ( state === 2 ) { dropdown . selectedIndex = i + 1 ; } } if ( state === 0 && selected === i ) { rangeSelector . setSelected ( ) ; } } } ) ; } private function param { Highcharts . RangeSelectorButtonsOptions } rangeOptions public computeButtonRange ( rangeOptions ) { const type = rangeOptions . type as string , count = rangeOptions . count || 1 , fixedTimes = ( { millisecond : 1 , second : 1000 , minute : 60 * 1000 , hour : 3600 * 1000 , day : 24 * 3600 * 1000 , week : 7 * 24 * 3600 * 1000 } as Record < string , number > ) ; if ( fixedTimes [ type ] ) { rangeOptions . _range = fixedTimes [ type ] * count ; } else if ( type === 'month' || type === 'year' ) { rangeOptions . _range = ( { month : 30 , year : 365 } as Record < string , number > ) [ type ] * 24 * 36e5 * count ; } rangeOptions . _offsetMin = pick ( rangeOptions . offsetMin , 0 ) ; rangeOptions . _offsetMax = pick ( rangeOptions . offsetMax , 0 ) ; ( rangeOptions . _range as any ) += ( rangeOptions . _offsetMax as any ) - ( rangeOptions . _offsetMin as any ) ; } private function public getInputValue ( name ) { const input = name === 'min' ? this . minInput : this . maxInput ; const options = this . chart . options . rangeSelector as Highcharts . RangeSelectorOptions ; const time = this . chart . time ; if ( input ) { return ( ( input . type === 'text' && options . inputDateParser ) || this . defaultInputDateParser ) ( input . value , time . useUTC , time ) ; } return 0 ; } private function public setInputValue ( name , inputTime ? ) { const options = this . options , time = this . chart . time , input = name === 'min' ? this . minInput : this . maxInput , dateBox = name === 'min' ? this . minDateBox : this . maxDateBox ; if ( input ) { const hcTimeAttr = input . getAttribute ( 'data-hc-time' ) ; let updatedTime = defined ( hcTimeAttr ) ? Number ( hcTimeAttr ) : void 0 ; if ( defined ( inputTime ) ) { const previousTime = updatedTime ; if ( defined ( previousTime ) ) { input . setAttribute ( 'data-hc-time-previous' , previousTime ) ; } input . setAttribute ( 'data-hc-time' , inputTime ) ; updatedTime = inputTime ; } input . value = time . dateFormat ( ( this . inputTypeFormats [ input . type ] || options . inputEditDateFormat ) , updatedTime ) ; if ( dateBox ) { dateBox . attr ( { text : time . dateFormat ( options . inputDateFormat , updatedTime ) } ) ; } } } private function public setInputExtremes ( name , min , max ) { const input = name === 'min' ? this . minInput : this . maxInput ; if ( input ) { const format = this . inputTypeFormats [ input . type ] ; const time = this . chart . time ; if ( format ) { const newMin = time . dateFormat ( format , min ) ; if ( input . min !== newMin ) { input . min = newMin ; } const newMax = time . dateFormat ( format , max ) ; if ( input . max !== newMax ) { input . max = newMax ; } } } } private function param { string } name public showInput ( name ) { const dateBox = name === 'min' ? this . minDateBox : this . maxDateBox ; const input = name === 'min' ? this . minInput : this . maxInput ; if ( input && dateBox && this . inputGroup ) { const isTextInput = input . type === 'text' ; const { translateX , translateY } = this . inputGroup ; const { inputBoxWidth } = this . options ; css ( input , { width : isTextInput ? ( ( dateBox . width + ( inputBoxWidth ? - 2 : 20 ) ) + 'px' ) : 'auto' , height : isTextInput ? ( ( dateBox . height - 2 ) + 'px' ) : 'auto' , border : '2px solid silver' } ) ; if ( isTextInput && inputBoxWidth ) { css ( input , { left : ( translateX + dateBox . x ) + 'px' , top : translateY + 'px' } ) ; } else { css ( input , { left : Math . min ( Math . round ( dateBox . x + translateX - ( input . offsetWidth - dateBox . width ) / 2 ) , this . chart . chartWidth - input . offsetWidth ) + 'px' , top : ( translateY - ( input . offsetHeight - dateBox . height ) / 2 ) + 'px' } ) ; } } } private function param { string } name public hideInput ( name ) { const input = name === 'min' ? this . minInput : this . maxInput ; if ( input ) { css ( input , { top : '-9999em' , border : 0 , width : '1px' , height : '1px' } ) ; } } private function public defaultInputDateParser ( inputDate , useUTC , time ? ) { const hasTimezone = ( str ) : boolean => str . length > 6 && ( str . lastIndexOf ( '-' ) === str . length - 6 || str . lastIndexOf ( '+' ) === str . length - 6 ) ; let input = inputDate . split ( '/' ) . join ( '-' ) . split ( ' ' ) . join ( 'T' ) ; if ( input . indexOf ( 'T' ) === - 1 ) { input += 'T00:00' ; } if ( useUTC ) { input += 'Z' ; } else if ( H . isSafari && ! hasTimezone ( input ) ) { const offset = new Date ( input ) . getTimezoneOffset ( ) / 60 ; input += offset <= 0 ? `+${ pad ( - offset ) }:00` : `-${ pad ( offset ) }:00` ; } let date = Date . parse ( input ) ; if ( ! isNumber ( date ) ) { const parts = inputDate . split ( '-' ) ; date = Date . UTC ( pInt ( parts [ 0 ] ) , pInt ( parts [ 1 ] ) - 1 , pInt ( parts [ 2 ] ) ) ; } if ( time && useUTC && isNumber ( date ) ) { date += time . getTimezoneOffset ( date ) ; } return date ; } private function public drawInput ( name ) { const { chart , div , inputGroup } = this ; const rangeSelector = this , chartStyle = chart . renderer . style || { } , renderer = chart . renderer , options = chart . options . rangeSelector as Highcharts . RangeSelectorOptions , lang = defaultOptions . lang , isMin = name === 'min' ; private function updateExtremes ( ) { let value = rangeSelector . getInputValue ( name ) , chartAxis = chart . xAxis [ 0 ] , dataAxis = chart . scroller && chart . scroller . xAxis ? chart . scroller . xAxis : chartAxis , dataMin = dataAxis . dataMin , dataMax = dataAxis . dataMax ; const { maxInput , minInput } = rangeSelector ; if ( value !== Number ( input . getAttribute ( 'data-hc-time-previous' ) ) && isNumber ( value ) ) { input . setAttribute ( 'data-hc-time-previous' , value ) ; if ( isMin && maxInput && isNumber ( dataMin ) ) { if ( value > Number ( maxInput . getAttribute ( 'data-hc-time' ) ) ) { value = void 0 ; } else if ( value < dataMin ) { value = dataMin ; } } else if ( minInput && isNumber ( dataMax ) ) { if ( value < Number ( minInput . getAttribute ( 'data-hc-time' ) ) ) { value = void 0 ; } else if ( value > dataMax ) { value = dataMax ; } } if ( typeof value !== 'undefined' ) { chartAxis . setExtremes ( isMin ? value : ( chartAxis . min as any ) , isMin ? ( chartAxis . max as any ) : value , void 0 , void 0 , { trigger : 'rangeSelectorInput' } ) ; } } } const text = lang [ isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo' ] || '' ; const label = renderer . label ( text , 0 ) . addClass ( 'highcharts-range-label' ) . attr ( { padding : text ? 2 : 0 , height : text ? options . inputBoxHeight : 0 } ) . add ( inputGroup ) ; const dateBox = renderer . label ( '' , 0 ) . addClass ( 'highcharts-range-input' ) . attr ( { padding : 2 , width : options . inputBoxWidth , height : options . inputBoxHeight , 'text-align' : 'center' } ) . on ( 'click' , function ( ) : void { rangeSelector . showInput ( name ) ; ( rangeSelector as any ) [ name + 'Input' ] . focus ( ) ; } ) ; if ( ! chart . styledMode ) { dateBox . attr ( { stroke : options . inputBoxBorderColor , 'stroke-width' : 1 } ) ; } dateBox . add ( inputGroup ) ; const input = createElement ( 'input' , { name : name , className : 'highcharts-range-selector' } , void 0 , div ) as HTMLInputElement ; input . setAttribute ( 'type' , preferredInputType ( options . inputDateFormat || '%b %e, %Y' ) ) ; if ( ! chart . styledMode ) { label . css ( merge ( chartStyle , options . labelStyle ) ) ; dateBox . css ( merge ( { color : Palette . neutralColor80 } , chartStyle , options . inputStyle ) ) ; css ( input , extend < CSSObject > ( { position : 'absolute' , border : 0 , boxShadow : '0 0 15px rgba(0,0,0,0.3)' , width : '1px' , height : '1px' , padding : 0 , textAlign : 'center' , fontSize : chartStyle . fontSize , fontFamily : chartStyle . fontFamily , top : '-9999em' } , options . inputStyle ) ) ; } input . onfocus = ( ) : void => { rangeSelector . showInput ( name ) ; } ; input . onblur = ( ) : void => { if ( input === H . doc . activeElement ) { updateExtremes ( ) ; } rangeSelector . hideInput ( name ) ; rangeSelector . setInputValue ( name ) ; input . blur ( ) ; } ; let keyDown = false ; input . onchange = ( ) : void => { if ( ! keyDown ) { updateExtremes ( ) ; rangeSelector . hideInput ( name ) ; input . blur ( ) ; } } ; input . onkeypress = ( event ) : void => { if ( event . keyCode === 13 ) { updateExtremes ( ) ; } } ; input . onkeydown = ( event ) : void => { keyDown = true ; if ( event . keyCode === 38 || event . keyCode === 40 ) { updateExtremes ( ) ; } } ; input . onkeyup = ( ) : void => { keyDown = false ; } ; return { dateBox , input , label } ; } private function public getPosition ( ) { const chart = this . chart , options = chart . options . rangeSelector as Highcharts . RangeSelectorOptions , top = options . verticalAlign === 'top' ? chart . plotTop - chart . axisOffset [ 0 ] : 0 ; return { buttonTop : top + options . buttonPosition . y , inputTop : top + options . inputPosition . y - 10 } ; } private function return { * } public getYTDExtremes ( dataMax , dataMin , useUTC ? ) { let time = this . chart . time , min , now = new time . Date ( dataMax ) , year = time . get ( 'FullYear' , now ) , startOfYear = useUTC ? time . Date . UTC ( year , 0 , 1 ) : + new time . Date ( year , 0 , 1 ) ; min = Math . max ( dataMin , startOfYear ) ; const ts = now . getTime ( ) ; return { max : Math . min ( dataMax || ts , ts ) , min : min } ; } private function param { number } min param { number } max public render ( min ? , max ? ) { const chart = this . chart , renderer = chart . renderer , container = chart . container , chartOptions = chart . options , options = chartOptions . rangeSelector as Highcharts . RangeSelectorOptions , inputsZIndex = pick ( ( chartOptions . chart as any ) . style && ( chartOptions . chart as any ) . style . zIndex , 0 ) + 1 , inputEnabled = options . inputEnabled , rendered = this . rendered ; if ( options . enabled === false ) { return ; } if ( ! rendered ) { this . group = renderer . g ( 'range-selector-group' ) . attr ( { zIndex : 7 } ) . add ( ) ; this . div = createElement ( 'div' , void 0 , { position : 'relative' , height : 0 , zIndex : inputsZIndex } ) ; if ( this . buttonOptions . length ) { this . renderButtons ( ) ; } if ( container . parentNode ) { container . parentNode . insertBefore ( this . div , container ) ; } if ( inputEnabled ) { this . inputGroup = renderer . g ( 'input-group' ) . add ( this . group ) ; const minElems = this . drawInput ( 'min' ) ; this . minDateBox = minElems . dateBox ; this . minLabel = minElems . label ; this . minInput = minElems . input ; const maxElems = this . drawInput ( 'max' ) ; this . maxDateBox = maxElems . dateBox ; this . maxLabel = maxElems . label ; this . maxInput = maxElems . input ; } } if ( inputEnabled ) { this . setInputValue ( 'min' , min ) ; this . setInputValue ( 'max' , max ) ; const unionExtremes = ( chart . scroller && chart . scroller . getUnionExtremes ( ) ) || chart . xAxis [ 0 ] || { } ; if ( defined ( unionExtremes . dataMin ) && defined ( unionExtremes . dataMax ) ) { const minRange = chart . xAxis [ 0 ] . minRange || 0 ; this . setInputExtremes ( 'min' , unionExtremes . dataMin , Math . min ( unionExtremes . dataMax , this . getInputValue ( 'max' ) ) - minRange ) ; this . setInputExtremes ( 'max' , Math . max ( unionExtremes . dataMin , this . getInputValue ( 'min' ) ) + minRange , unionExtremes . dataMax ) ; } if ( this . inputGroup ) { let x = 0 ; [ this . minLabel , this . minDateBox , this . maxLabel , this . maxDateBox ] . forEach ( ( label ) : void => { if ( label ) { const { width } = label . getBBox ( ) ; if ( width ) { label . attr ( { x } ) ; x += width + options . inputSpacing ; } } } ) ; } } this . alignElements ( ) ; this . rendered = true ; } private function public renderButtons ( ) { const { buttons , chart , options } = this ; const lang = defaultOptions . lang ; const renderer = chart . renderer ; const buttonTheme = merge ( options . buttonTheme ) ; const states = buttonTheme && buttonTheme . states ; const width = buttonTheme . width || 28 ; delete buttonTheme . width ; delete buttonTheme . states ; this . buttonGroup = renderer . g ( 'range-selector-buttons' ) . add ( this . group ) ; const dropdown = this . dropdown = createElement ( 'select' , void 0 , { position : 'absolute' , width : '1px' , height : '1px' , padding : 0 , border : 0 , top : '-9999em' , cursor : 'pointer' , opacity : 0.0001 } , this . div ) as HTMLSelectElement ; addEvent ( dropdown , 'touchstart' , ( ) : void => { dropdown . style . fontSize = '16px' ; } ) ; [ [ H . isMS ? 'mouseover' : 'mouseenter' ] , [ H . isMS ? 'mouseout' : 'mouseleave' ] , [ 'change' , 'click' ] ] . forEach ( ( [ from , to ] ) : void => { addEvent ( dropdown , from , ( ) : void => { const button = buttons [ this . currentButtonIndex ( ) ] ; if ( button ) { fireEvent ( button . element , to || from ) ; } } ) ; } ) ; this . zoomText = renderer . label ( ( lang && lang . rangeSelectorZoom ) || '' , 0 ) . attr ( { padding : options . buttonTheme . padding , height : options . buttonTheme . height , paddingLeft : 0 , paddingRight : 0 } ) . add ( this . buttonGroup ) ; if ( ! this . chart . styledMode ) { this . zoomText . css ( options . labelStyle ) ; buttonTheme [ 'stroke-width' ] = pick ( buttonTheme [ 'stroke-width' ] , 0 ) ; } createElement ( 'option' , { textContent : this . zoomText . textStr , disabled : true } , void 0 , dropdown ) ; this . buttonOptions . forEach ( ( rangeOptions , i ) : void => { createElement ( 'option' , { textContent : rangeOptions . title || rangeOptions . text } , void 0 , dropdown ) ; buttons [ i ] = renderer . button ( rangeOptions . text , 0 , 0 , ( e ) : void => { let buttonEvents = ( rangeOptions . events && rangeOptions . events . click ) , callDefaultEvent ; if ( buttonEvents ) { callDefaultEvent = buttonEvents . call ( rangeOptions , e as any ) ; } if ( callDefaultEvent !== false ) { this . clickButton ( i ) ; } this . isActive = true ; } , buttonTheme , states && states . hover , states && states . select , states && states . disabled ) . attr ( { 'text-align' : 'center' , width } ) . add ( this . buttonGroup ) ; if ( rangeOptions . title ) { buttons [ i ] . attr ( 'title' , rangeOptions . title ) ; } } ) ; } private function public alignElements ( ) { const { buttonGroup , buttons , chart , group , inputGroup , options , zoomText } = this ; const chartOptions = chart . options ; const navButtonOptions = ( chartOptions . exporting && chartOptions . exporting . enabled !== false && chartOptions . navigation && chartOptions . navigation . buttonOptions ) ; const { buttonPosition , inputPosition , verticalAlign } = options ; const getXOffsetForExportButton = ( group , position ) : number => { if ( navButtonOptions && this . titleCollision ( chart ) && verticalAlign === 'top' && position . align === 'right' && ( ( position . y - group . getBBox ( ) . height - 12 ) < ( ( navButtonOptions . y || 0 ) + ( navButtonOptions . height || 0 ) + chart . spacing [ 0 ] ) ) ) { return - 40 ; } return 0 ; } ; let plotLeft = chart . plotLeft ; if ( group && buttonPosition && inputPosition ) { let translateX = buttonPosition . x - chart . spacing [ 3 ] ; if ( buttonGroup ) { this . positionButtons ( ) ; if ( ! this . initialButtonGroupWidth ) { let width = 0 ; if ( zoomText ) { width += zoomText . getBBox ( ) . width + 5 ; } buttons . forEach ( ( button , i ) : void => { width += button . width ; if ( i !== buttons . length - 1 ) { width += options . buttonSpacing ; } } ) ; this . initialButtonGroupWidth = width ; } plotLeft -= chart . spacing [ 3 ] ; this . updateButtonStates ( ) ; const xOffsetForExportButton = getXOffsetForExportButton ( buttonGroup , buttonPosition ) ; this . alignButtonGroup ( xOffsetForExportButton ) ; group . placed = buttonGroup . placed = chart . hasLoaded ; } let xOffsetForExportButton = 0 ; if ( inputGroup ) { xOffsetForExportButton = getXOffsetForExportButton ( inputGroup , inputPosition ) ; if ( inputPosition . align === 'left' ) { translateX = plotLeft ; } else if ( inputPosition . align === 'right' ) { translateX = - Math . max ( chart . axisOffset [ 1 ] , - xOffsetForExportButton ) ; } inputGroup . align ( { y : inputPosition . y , width : inputGroup . getBBox ( ) . width , align : inputPosition . align , x : inputPosition . x + translateX - 2 } , true , chart . spacingBox ) ; inputGroup . placed = chart . hasLoaded ; } this . handleCollision ( xOffsetForExportButton ) ; group . align ( { verticalAlign } , true , chart . spacingBox ) ; const alignTranslateY = group . alignAttr . translateY ; let groupHeight = group . getBBox ( ) . height + 20 ; let translateY = 0 ; if ( verticalAlign === 'bottom' ) { const legendOptions = chart . legend && chart . legend . options ; const legendHeight = ( legendOptions && legendOptions . verticalAlign === 'bottom' && legendOptions . enabled && ! legendOptions . floating ? ( chart . legend . legendHeight + pick ( legendOptions . margin , 10 ) ) : 0 ) ; groupHeight = groupHeight + legendHeight - 20 ; translateY = ( alignTranslateY - groupHeight - ( options . floating ? 0 : options . y ) - ( chart . titleOffset ? chart . titleOffset [ 2 ] : 0 ) - 10 ) ; } if ( verticalAlign === 'top' ) { if ( options . floating ) { translateY = 0 ; } if ( chart . titleOffset && chart . titleOffset [ 0 ] ) { translateY = chart . titleOffset [ 0 ] ; } translateY += ( ( chart . margin [ 0 ] - chart . spacing [ 0 ] ) || 0 ) ; } else if ( verticalAlign === 'middle' ) { if ( inputPosition . y === buttonPosition . y ) { translateY = alignTranslateY ; } else if ( inputPosition . y || buttonPosition . y ) { if ( inputPosition . y < 0 || buttonPosition . y < 0 ) { translateY -= Math . min ( inputPosition . y , buttonPosition . y ) ; } else { translateY = alignTranslateY - groupHeight ; } } } group . translate ( options . x , options . y + Math . floor ( translateY ) ) ; const { minInput , maxInput , dropdown } = this ; if ( options . inputEnabled && minInput && maxInput ) { minInput . style . marginTop = group . translateY + 'px' ; maxInput . style . marginTop = group . translateY + 'px' ; } if ( dropdown ) { dropdown . style . marginTop = group . translateY + 'px' ; } } } private function param { number } xOffsetForExportButton param { number } width public alignButtonGroup ( xOffsetForExportButton , width ? ) { const { chart , options , buttonGroup , buttons } = this ; const { buttonPosition } = options ; const plotLeft = chart . plotLeft - chart . spacing [ 3 ] ; let translateX = buttonPosition . x - chart . spacing [ 3 ] ; if ( buttonPosition . align === 'right' ) { translateX += xOffsetForExportButton - plotLeft ; } else if ( buttonPosition . align === 'center' ) { translateX -= plotLeft / 2 ; } if ( buttonGroup ) { buttonGroup . align ( { y : buttonPosition . y , width : pick ( width , this . initialButtonGroupWidth ) , align : buttonPosition . align , x : translateX } , true , chart . spacingBox ) ; } } private function public positionButtons ( ) { const { buttons , chart , options , zoomText } = this ; const verb = chart . hasLoaded ? 'animate' : 'attr' ; const { buttonPosition } = options ; const plotLeft = chart . plotLeft ; let buttonLeft = plotLeft ; if ( zoomText && zoomText . visibility !== 'hidden' ) { zoomText [ verb ] ( { x : pick ( plotLeft + buttonPosition . x , plotLeft ) } ) ; buttonLeft += buttonPosition . x + zoomText . getBBox ( ) . width + 5 ; } this . buttonOptions . forEach ( function ( rangeOptions , i ) : void { if ( buttons [ i ] . visibility !== 'hidden' ) { buttons [ i ] [ verb ] ( { x : buttonLeft } ) ; buttonLeft += buttons [ i ] . width + options . buttonSpacing ; } else { buttons [ i ] [ verb ] ( { x : plotLeft } ) ; } } ) ; } private function param { number } xOffsetForExportButton public handleCollision ( xOffsetForExportButton ) { const { chart , buttonGroup , inputGroup } = this ; const { buttonPosition , dropdown , inputPosition } = this . options ; const maxButtonWidth = ( ) : number => { let buttonWidth = 0 ; this . buttons . forEach ( ( button ) : void => { const bBox = button . getBBox ( ) ; if ( bBox . width > buttonWidth ) { buttonWidth = bBox . width ; } } ) ; return buttonWidth ; } ; const groupsOverlap = ( buttonGroupWidth ) : boolean => { if ( inputGroup && buttonGroup ) { const inputGroupX = ( inputGroup . alignAttr . translateX + inputGroup . alignOptions . x - xOffsetForExportButton + inputGroup . getBBox ( ) . x + 2 ) ; const inputGroupWidth = inputGroup . alignOptions . width ; const buttonGroupX = buttonGroup . alignAttr . translateX + buttonGroup . getBBox ( ) . x ; return ( buttonGroupX + buttonGroupWidth > inputGroupX ) && ( inputGroupX + inputGroupWidth > buttonGroupX ) && ( buttonPosition . y < ( inputPosition . y + inputGroup . getBBox ( ) . height ) ) ; } return false ; } ; const moveInputsDown = ( ) : void => { if ( inputGroup && buttonGroup ) { inputGroup . attr ( { translateX : inputGroup . alignAttr . translateX + ( chart . axisOffset [ 1 ] >= - xOffsetForExportButton ? 0 : - xOffsetForExportButton ) , translateY : inputGroup . alignAttr . translateY + buttonGroup . getBBox ( ) . height + 10 } ) ; } } ; if ( buttonGroup ) { if ( dropdown === 'always' ) { this . collapseButtons ( xOffsetForExportButton ) ; if ( groupsOverlap ( maxButtonWidth ( ) ) ) { moveInputsDown ( ) ; } return ; } if ( dropdown === 'never' ) { this . expandButtons ( ) ; } } if ( inputGroup && buttonGroup ) { if ( ( inputPosition . align === buttonPosition . align ) || groupsOverlap ( this . initialButtonGroupWidth + 20 ) ) { if ( dropdown === 'responsive' ) { this . collapseButtons ( xOffsetForExportButton ) ; if ( groupsOverlap ( maxButtonWidth ( ) ) ) { moveInputsDown ( ) ; } } else { moveInputsDown ( ) ; } } else if ( dropdown === 'responsive' ) { this . expandButtons ( ) ; } } else if ( buttonGroup && dropdown === 'responsive' ) { if ( this . initialButtonGroupWidth > chart . plotWidth ) { this . collapseButtons ( xOffsetForExportButton ) ; } else { this . expandButtons ( ) ; } } } private function param { number } xOffsetForExportButton public collapseButtons ( xOffsetForExportButton ) { const { buttons , buttonOptions , chart , dropdown , options , zoomText } = this ; const userButtonTheme = ( chart . userOptions . rangeSelector && chart . userOptions . rangeSelector . buttonTheme ) || { } ; const getAttribs = ( text ? ) : SVGAttributes => ( { text : text ? `${ text } ▾` : '▾' , width : 'auto' , paddingLeft : pick ( options . buttonTheme . paddingLeft , userButtonTheme . padding , 8 ) , paddingRight : pick ( options . buttonTheme . paddingRight , userButtonTheme . padding , 8 ) } as unknown as SVGAttributes ) ; if ( zoomText ) { zoomText . hide ( ) ; } let hasActiveButton = false ; buttonOptions . forEach ( ( rangeOptions , i ) : void => { const button = buttons [ i ] ; if ( button . state !== 2 ) { button . hide ( ) ; } else { button . show ( ) ; button . attr ( getAttribs ( rangeOptions . text ) ) ; hasActiveButton = true ; } } ) ; if ( ! hasActiveButton ) { if ( dropdown ) { dropdown . selectedIndex = 0 ; } buttons [ 0 ] . show ( ) ; buttons [ 0 ] . attr ( getAttribs ( this . zoomText && this . zoomText . textStr ) ) ; } const { align } = options . buttonPosition ; this . positionButtons ( ) ; if ( align === 'right' || align === 'center' ) { this . alignButtonGroup ( xOffsetForExportButton , buttons [ this . currentButtonIndex ( ) ] . getBBox ( ) . width ) ; } this . showDropdown ( ) ; } private function public expandButtons ( ) { const { buttons , buttonOptions , options , zoomText } = this ; this . hideDropdown ( ) ; if ( zoomText ) { zoomText . show ( ) ; } buttonOptions . forEach ( ( rangeOptions , i ) : void => { const button = buttons [ i ] ; button . show ( ) ; button . attr ( { text : rangeOptions . text , width : options . buttonTheme . width || 28 , paddingLeft : pick ( options . buttonTheme . paddingLeft , 'unset' ) , paddingRight : pick ( options . buttonTheme . paddingRight , 'unset' ) } as SVGAttributes ) ; if ( button . state < 2 ) { button . setState ( 0 ) ; } } ) ; this . positionButtons ( ) ; } private function public currentButtonIndex ( ) { const { dropdown } = this ; if ( dropdown && dropdown . selectedIndex > 0 ) { return dropdown . selectedIndex - 1 ; } return 0 ; } private function public showDropdown ( ) { const { buttonGroup , buttons , chart , dropdown } = this ; if ( buttonGroup && dropdown ) { const { translateX , translateY } = buttonGroup ; const bBox = buttons [ this . currentButtonIndex ( ) ] . getBBox ( ) ; css ( dropdown , { left : ( chart . plotLeft + translateX ) + 'px' , top : ( translateY + 0.5 ) + 'px' , width : bBox . width + 'px' , height : bBox . height + 'px' } ) ; this . hasVisibleDropdown = true ; } } private function public hideDropdown ( ) { const { dropdown } = this ; if ( dropdown ) { css ( dropdown , { top : '-9999em' , width : '1px' , height : '1px' } ) ; this . hasVisibleDropdown = false ; } } private function return { number } public getHeight ( ) { let rangeSelector = this , options = rangeSelector . options , rangeSelectorGroup = rangeSelector . group , inputPosition = options . inputPosition , buttonPosition = options . buttonPosition , yPosition = options . y , buttonPositionY = buttonPosition . y , inputPositionY = inputPosition . y , rangeSelectorHeight = 0 , minPosition ; if ( options . height ) { return options . height ; } this . alignElements ( ) ; rangeSelectorHeight = rangeSelectorGroup ? ( rangeSelectorGroup . getBBox ( true ) . height ) + 13 + yPosition : 0 ; minPosition = Math . min ( inputPositionY , buttonPositionY ) ; if ( ( inputPositionY < 0 && buttonPositionY < 0 ) || ( inputPositionY > 0 && buttonPositionY > 0 ) ) { rangeSelectorHeight += Math . abs ( minPosition ) ; } return rangeSelectorHeight ; } private function return { boolean } public titleCollision ( chart ) { return ! ( ( chart . options . title as any ) . text || ( chart . options . subtitle as any ) . text ) ; } private function param { Highcharts . RangeSelectorOptions } options public update ( this , options ) { const chart = this . chart ; merge ( true , chart . options . rangeSelector , options ) ; this . destroy ( ) ; this . init ( chart ) ; this . render ( ) ; } private function public destroy ( ) { const rSelector = this , minInput = rSelector . minInput , maxInput = rSelector . maxInput ; if ( rSelector . eventsToUnbind ) { rSelector . eventsToUnbind . forEach ( ( unbind ) : void => unbind ( ) ) ; rSelector . eventsToUnbind = void 0 ; } destroyObjectProperties ( rSelector . buttons ) ; if ( minInput ) { minInput . onfocus = minInput . onblur = minInput . onchange = null ; } if ( maxInput ) { maxInput . onfocus = maxInput . onblur = maxInput . onchange = null ; } objectEach ( rSelector , function ( val , key ) : void { if ( val && key !== 'chart' ) { if ( val instanceof SVGElement ) { val . destroy ( ) ; } else if ( val instanceof window . HTMLElement ) { discardElement ( val ) ; } } if ( val !== RangeSelector . prototype [ key ] ) { ( rSelector as any ) [ key ] = null ; } } , this ) ; } } private interface RangeSelector { defaultButtons : Array < Highcharts . RangeSelectorButtonsOptions > ; inputTypeFormats : Record < string , string > ; } /**
 * The default buttons for pre-selecting time frames
 */ RangeSelector . prototype . defaultButtons = [ { type : 'month' , count : 1 , text : '1m' , title : 'View 1 month' } , { type : 'month' , count : 3 , text : '3m' , title : 'View 3 months' } , { type : 'month' , count : 6 , text : '6m' , title : 'View 6 months' } , { type : 'ytd' , text : 'YTD' , title : 'View year to date' } , { type : 'year' , count : 1 , text : '1y' , title : 'View 1 year' } , { type : 'all' , text : 'All' , title : 'View all' } ] ; /**
 * The date formats to use when setting min, max and value on date inputs
 */ RangeSelector . prototype . inputTypeFormats = { 'datetime-local' : '%Y-%m-%dT%H:%M:%S' , 'date' : '%Y-%m-%d' , 'time' : '%H:%M:%S' } ; private function function preferredInputType ( format ) { const ms = format . indexOf ( '%L' ) !== - 1 ; if ( ms ) { return 'text' ; } const date = [ 'a' , 'A' , 'd' , 'e' , 'w' , 'b' , 'B' , 'm' , 'o' , 'y' , 'Y' ] . some ( ( char ) : boolean => format . indexOf ( '%' + char ) !== - 1 ) ; const time = [ 'H' , 'k' , 'I' , 'l' , 'M' , 'S' ] . some ( ( char ) : boolean => format . indexOf ( '%' + char ) !== - 1 ) ; if ( date && time ) { return 'datetime-local' ; } if ( date ) { return 'date' ; } if ( time ) { return 'time' ; } return 'text' ; } /**
 * Get the axis min value based on the range option and the current max. For
 * stock charts this is extended via the  RangeSelector so that if the
 * selected range is a multiple of months or years, it is compensated for
 * various month lengths.
 *
 *  private function return { number | undefined } Axis . prototype . minFromRange = function ( ) : ( number | undefined ) { let rangeOptions = this . range , type = ( rangeOptions as any ) . type , min , max = this . max as any , dataMin , range , time = this . chart . time , getTrueRange = function ( base , count ) : number { const timeName = type === 'year' ? 'FullYear' : 'Month' ; const date = new time . Date ( base ) ; const basePeriod = time . get ( timeName , date ) ; time . set ( timeName , date , basePeriod + count ) ; if ( basePeriod === time . get ( timeName , date ) ) { time . set ( 'Date' , date , 0 ) ; } return date . getTime ( ) - base ; } ; if ( isNumber ( rangeOptions ) ) { min = max - ( rangeOptions as any ) ; range = rangeOptions ; } else { min = max + getTrueRange ( max , - ( rangeOptions as any ) . count ) ; if ( this . chart ) { this . chart . fixedRange = max - min ; } } dataMin = pick ( this . dataMin , Number . MIN_VALUE ) ; if ( ! isNumber ( min ) ) { min = dataMin ; } if ( min <= dataMin ) { min = dataMin ; if ( typeof range === 'undefined' ) { range = getTrueRange ( min , ( rangeOptions as any ) . count ) ; } this . newMax = Math . min ( min + range , this . dataMax as any ) ; } if ( ! isNumber ( max ) ) { min = void 0 ; } return min ; } ; if ( ! H . RangeSelector ) { const chartDestroyEvents = [ ] ; const initRangeSelector = ( chart ) : void => { let extremes , rangeSelector = chart . rangeSelector , legend , alignTo , verticalAlign ; private function render ( ) { if ( rangeSelector ) { extremes = chart . xAxis [ 0 ] . getExtremes ( ) ; legend = chart . legend ; verticalAlign = ( rangeSelector && rangeSelector . options . verticalAlign ) ; if ( isNumber ( extremes . min ) ) { rangeSelector . render ( extremes . min , extremes . max ) ; } if ( legend . display && verticalAlign === 'top' && verticalAlign === legend . options . verticalAlign ) { alignTo = merge ( chart . spacingBox ) ; if ( legend . options . layout === 'vertical' ) { alignTo . y = chart . plotTop ; } else { alignTo . y += rangeSelector . getHeight ( ) ; } legend . group . placed = false ; legend . align ( alignTo ) ; } } } if ( rangeSelector ) { const events = find ( chartDestroyEvents , ( e ) : boolean => e [ 0 ] === chart ) ; if ( ! events ) { chartDestroyEvents . push ( [ chart , [ addEvent ( chart . xAxis [ 0 ] , 'afterSetExtremes' , function ( e ) : void { if ( rangeSelector ) { rangeSelector . render ( e . min , e . max ) ; } } ) , addEvent ( chart , 'redraw' , render ) ] ] ) ; } render ( ) ; } } ; addEvent ( Chart , 'afterGetContainer' , function ( ) : void { if ( this . options . rangeSelector && this . options . rangeSelector . enabled ) { this . rangeSelector = new RangeSelector ( this ) ; } } ) ; addEvent ( Chart , 'beforeRender' , function ( ) : void { let chart = this , axes = chart . axes , rangeSelector = chart . rangeSelector , verticalAlign ; if ( rangeSelector ) { if ( isNumber ( rangeSelector . deferredYTDClick ) ) { rangeSelector . clickButton ( rangeSelector . deferredYTDClick ) ; delete rangeSelector . deferredYTDClick ; } axes . forEach ( function ( axis ) : void { axis . updateNames ( ) ; axis . setScale ( ) ; } ) ; chart . getAxisMargins ( ) ; rangeSelector . render ( ) ; verticalAlign = rangeSelector . options . verticalAlign ; if ( ! rangeSelector . options . floating ) { if ( verticalAlign === 'bottom' ) { this . extraBottomMargin = true ; } else if ( verticalAlign !== 'middle' ) { this . extraTopMargin = true ; } } } } ) ; addEvent ( Chart , 'update' , function ( e ) : void { let chart = this , options = e . options , optionsRangeSelector = options . rangeSelector , rangeSelector = chart . rangeSelector , verticalAlign , extraBottomMarginWas = this . extraBottomMargin , extraTopMarginWas = this . extraTopMargin ; if ( optionsRangeSelector && optionsRangeSelector . enabled && ! defined ( rangeSelector ) && this . options . rangeSelector ) { this . options . rangeSelector . enabled = true ; this . rangeSelector = rangeSelector = new RangeSelector ( this ) ; } this . extraBottomMargin = false ; this . extraTopMargin = false ; if ( rangeSelector ) { initRangeSelector ( this ) ; verticalAlign = ( optionsRangeSelector && optionsRangeSelector . verticalAlign ) || ( rangeSelector . options && rangeSelector . options . verticalAlign ) ; if ( ! rangeSelector . options . floating ) { if ( verticalAlign === 'bottom' ) { this . extraBottomMargin = true ; } else if ( verticalAlign !== 'middle' ) { this . extraTopMargin = true ; } } if ( this . extraBottomMargin !== extraBottomMarginWas || this . extraTopMargin !== extraTopMarginWas ) { this . isDirtyBox = true ; } } } ) ; addEvent ( Chart , 'render' , function ( ) : void { let chart = this , rangeSelector = chart . rangeSelector , verticalAlign ; if ( rangeSelector && ! rangeSelector . options . floating ) { rangeSelector . render ( ) ; verticalAlign = rangeSelector . options . verticalAlign ; if ( verticalAlign === 'bottom' ) { this . extraBottomMargin = true ; } else if ( verticalAlign !== 'middle' ) { this . extraTopMargin = true ; } } } ) ; addEvent ( Chart , 'getMargins' , function ( ) : void { let rangeSelector = this . rangeSelector , rangeSelectorHeight ; if ( rangeSelector ) { rangeSelectorHeight = rangeSelector . getHeight ( ) ; if ( this . extraTopMargin ) { this . plotTop += rangeSelectorHeight ; } if ( this . extraBottomMargin ) { ( this . marginBottom as any ) += rangeSelectorHeight ; } } } ) ; Chart . prototype . callbacks . push ( initRangeSelector ) ; addEvent ( Chart , 'destroy' , function destroyEvents ( ) : void { for ( let i = 0 ; i < chartDestroyEvents . length ; i ++ ) { const events = chartDestroyEvents [ i ] ; if ( events [ 0 ] === this ) { events [ 1 ] . forEach ( ( unbind ) : void => unbind ( ) ) ; chartDestroyEvents . splice ( i , 1 ) ; return ; } } } ) ; H . RangeSelector = RangeSelector ; } namespace RangeSelector { export interface RangeObject { max : number ; min : number ; } } export default RangeSelector ;