/* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import D from '../../Core/Defaults.js'; const { defaultOptions, setOptions } = D; import RangeSelectorDefaults from './RangeSelectorDefaults.js'; import U from '../../Core/Utilities.js'; const { addEvent, defined, extend, find, isNumber, merge, pick } = U; /* * * * Constants * * */ const chartDestroyEvents = []; const composedMembers = []; /* * * * Variables * * */ let RangeSelectorConstructor; /* * * * Functions * * */ /** * Get the axis min value based on the range option and the current max. For * stock charts this is extended via the {@link RangeSelector} so that if the * selected range is a multiple of months or years, it is compensated for * various month lengths. * * @private * @function Highcharts.Axis#minFromRange * @return {number|undefined} * The new minimum value. */ function axisMinFromRange() { const rangeOptions = this.range, type = rangeOptions.type, max = this.max, time = this.chart.time, // Get the true range from a start date getTrueRange = function (base, count) { 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); // #6537 } return date.getTime() - base; }; let min, range; if (isNumber(rangeOptions)) { min = max - rangeOptions; range = rangeOptions; } else if (rangeOptions) { min = max + getTrueRange(max, -(rangeOptions.count || 1)); // Let the fixedRange reflect initial settings (#5930) if (this.chart) { this.chart.fixedRange = max - min; } } const dataMin = pick(this.dataMin, Number.MIN_VALUE); if (!isNumber(min)) { min = dataMin; } if (min <= dataMin) { min = dataMin; if (typeof range === 'undefined') { // #4501 range = getTrueRange(min, rangeOptions.count); } this.newMax = Math.min(min + range, pick(this.dataMax, Number.MAX_VALUE)); } if (!isNumber(max)) { min = void 0; } else if (!isNumber(rangeOptions) && rangeOptions && rangeOptions._offsetMin) { min += rangeOptions._offsetMin; } return min; } /** * @private */ function compose(AxisClass, ChartClass, RangeSelectorClass) { RangeSelectorConstructor = RangeSelectorClass; if (U.pushUnique(composedMembers, AxisClass)) { AxisClass.prototype.minFromRange = axisMinFromRange; } if (U.pushUnique(composedMembers, ChartClass)) { addEvent(ChartClass, 'afterGetContainer', onChartAfterGetContainer); addEvent(ChartClass, 'beforeRender', onChartBeforeRender); addEvent(ChartClass, 'destroy', onChartDestroy); addEvent(ChartClass, 'getMargins', onChartGetMargins); addEvent(ChartClass, 'render', onChartRender); addEvent(ChartClass, 'update', onChartUpdate); const chartProto = ChartClass.prototype; chartProto.callbacks.push(onChartCallback); } if (U.pushUnique(composedMembers, setOptions)) { extend(defaultOptions, { rangeSelector: RangeSelectorDefaults.rangeSelector }); extend(defaultOptions.lang, RangeSelectorDefaults.lang); } } /** * Initialize rangeselector for stock charts * @private */ function onChartAfterGetContainer() { if (this.options.rangeSelector && this.options.rangeSelector.enabled) { this.rangeSelector = new RangeSelectorConstructor(this); } } /** * @private */ function onChartBeforeRender() { const chart = this, axes = chart.axes, rangeSelector = chart.rangeSelector; if (rangeSelector) { if (isNumber(rangeSelector.deferredYTDClick)) { rangeSelector.clickButton(rangeSelector.deferredYTDClick); delete rangeSelector.deferredYTDClick; } axes.forEach((axis) => { axis.updateNames(); axis.setScale(); }); chart.getAxisMargins(); rangeSelector.render(); const verticalAlign = rangeSelector.options.verticalAlign; if (!rangeSelector.options.floating) { if (verticalAlign === 'bottom') { this.extraBottomMargin = true; } else if (verticalAlign !== 'middle') { this.extraTopMargin = true; } } } } /** * @private */ function onChartCallback(chart) { let extremes, legend, alignTo, verticalAlign; const rangeSelector = chart.rangeSelector, redraw = () => { 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); } // Re-align the legend so that it's below the rangeselector if (legend.display && verticalAlign === 'top' && verticalAlign === legend.options.verticalAlign) { // Create a new alignment box for the legend. alignTo = merge(chart.spacingBox); if (legend.options.layout === 'vertical') { alignTo.y = chart.plotTop; } else { alignTo.y += rangeSelector.getHeight(); } legend.group.placed = false; // Don't animate the alignment. legend.align(alignTo); } } }; if (rangeSelector) { const events = find(chartDestroyEvents, (e) => e[0] === chart); if (!events) { chartDestroyEvents.push([chart, [ // redraw the scroller on setExtremes addEvent(chart.xAxis[0], 'afterSetExtremes', function (e) { if (rangeSelector) { rangeSelector.render(e.min, e.max); } }), // redraw the scroller chart resize addEvent(chart, 'redraw', redraw) ]]); } // do it now redraw(); } } /** * Remove resize/afterSetExtremes at chart destroy. * @private */ function onChartDestroy() { for (let i = 0, iEnd = chartDestroyEvents.length; i < iEnd; ++i) { const events = chartDestroyEvents[i]; if (events[0] === this) { events[1].forEach((unbind) => unbind()); chartDestroyEvents.splice(i, 1); return; } } } function onChartGetMargins() { const rangeSelector = this.rangeSelector; if (rangeSelector) { const rangeSelectorHeight = rangeSelector.getHeight(); if (this.extraTopMargin) { this.plotTop += rangeSelectorHeight; } if (this.extraBottomMargin) { this.marginBottom += rangeSelectorHeight; } } } /** * @private */ function onChartRender() { const chart = this, rangeSelector = chart.rangeSelector; if (rangeSelector && !rangeSelector.options.floating) { rangeSelector.render(); const verticalAlign = rangeSelector.options.verticalAlign; if (verticalAlign === 'bottom') { this.extraBottomMargin = true; } else if (verticalAlign !== 'middle') { this.extraTopMargin = true; } } } /** * @private */ function onChartUpdate(e) { const chart = this, options = e.options, optionsRangeSelector = options.rangeSelector, extraBottomMarginWas = this.extraBottomMargin, extraTopMarginWas = this.extraTopMargin; let rangeSelector = chart.rangeSelector; if (optionsRangeSelector && optionsRangeSelector.enabled && !defined(rangeSelector) && this.options.rangeSelector) { this.options.rangeSelector.enabled = true; this.rangeSelector = rangeSelector = new RangeSelectorConstructor(this); } this.extraBottomMargin = false; this.extraTopMargin = false; if (rangeSelector) { onChartCallback(this); const 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; } } } /* * * * Default Export * * */ const RangeSelectorComposition = { compose }; export default RangeSelectorComposition;