UNPKG

highcharts

Version:
266 lines (265 loc) 8.42 kB
/* * * * (c) 2010-2025 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 } = D; import H from '../../Core/Globals.js'; const { composed } = H; import RangeSelectorDefaults from './RangeSelectorDefaults.js'; import U from '../../Core/Utilities.js'; const { addEvent, defined, extend, isNumber, merge, pick, pushUnique } = U; /* * * * Constants * * */ const chartDestroyEvents = []; /* * * * 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 original = time.toParts(base), modified = original.slice(); if (type === 'year') { modified[0] += count; } else { modified[1] += count; } let d = time.makeTime.apply(time, modified); const numbers = time.toParts(d); // When subtracting a month still places us in the same month, like // subtracting one month from March 31 places us on February 31, // which translates to March 3 (#6537) if (type === 'month' && original[1] === numbers[1] && Math.abs(count) === 1) { modified[0] = original[0]; modified[1] = original[1]; // 0 is the last day of the previous month modified[2] = 0; } d = time.makeTime.apply(time, modified); return d - 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.setFixedRange(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 updateRangeSelectorButtons() { this.rangeSelector?.redrawElements(); } /** * @private */ function compose(AxisClass, ChartClass, RangeSelectorClass) { RangeSelectorConstructor = RangeSelectorClass; if (pushUnique(composed, 'RangeSelector')) { const chartProto = ChartClass.prototype; AxisClass.prototype.minFromRange = axisMinFromRange; addEvent(ChartClass, 'afterGetContainer', createRangeSelector); addEvent(ChartClass, 'beforeRender', onChartBeforeRender); addEvent(ChartClass, 'destroy', onChartDestroy); addEvent(ChartClass, 'getMargins', onChartGetMargins); addEvent(ChartClass, 'redraw', redrawRangeSelector); addEvent(ChartClass, 'update', onChartUpdate); addEvent(ChartClass, 'beforeRedraw', updateRangeSelectorButtons); chartProto.callbacks.push(redrawRangeSelector); extend(defaultOptions, { rangeSelector: RangeSelectorDefaults.rangeSelector }); extend(defaultOptions.lang, RangeSelectorDefaults.lang); } } /** * Initialize rangeselector for stock charts * @private */ function createRangeSelector() { if (this.options.rangeSelector && this.options.rangeSelector.enabled) { this.rangeSelector = new RangeSelectorConstructor(this); } } /** * @private */ function onChartBeforeRender() { const chart = this, rangeSelector = chart.rangeSelector; if (rangeSelector) { if (isNumber(rangeSelector.deferredYTDClick)) { rangeSelector.clickButton(rangeSelector.deferredYTDClick); delete rangeSelector.deferredYTDClick; } const verticalAlign = rangeSelector.options.verticalAlign; if (!rangeSelector.options.floating) { if (verticalAlign === 'bottom') { this.extraBottomMargin = true; } else if (verticalAlign === 'top') { this.extraTopMargin = true; } } } } function redrawRangeSelector() { const chart = this; const rangeSelector = this.rangeSelector; if (!rangeSelector) { return; } let alignTo; const extremes = chart.xAxis[0].getExtremes(); const legend = chart.legend; const 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); } } /** * 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?.options?.enabled) { const rangeSelectorHeight = rangeSelector.getHeight(); const verticalAlign = rangeSelector.options.verticalAlign; if (!rangeSelector.options.floating) { if (verticalAlign === 'bottom') { this.marginBottom += rangeSelectorHeight; } else if (verticalAlign !== 'middle') { this.plotTop += rangeSelectorHeight; } } } } /** * @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) { 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;