/* *
|
*
|
* (c) 2010-2019 Torstein Honsi
|
*
|
* License: www.highcharts.com/license
|
*
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
*
|
* */
|
'use strict';
|
|
var _Globals = require('./Globals.js');
|
|
var _Globals2 = _interopRequireDefault(_Globals);
|
|
var _Utilities = require('./Utilities.js');
|
|
var _Utilities2 = _interopRequireDefault(_Utilities);
|
|
require('./Axis.js');
|
|
require('./Chart.js');
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
var defined = _Utilities2.default.defined,
|
isNumber = _Utilities2.default.isNumber,
|
objectEach = _Utilities2.default.objectEach,
|
pInt = _Utilities2.default.pInt,
|
splat = _Utilities2.default.splat;
|
/**
|
* Define the time span for the button
|
*
|
* @typedef {"all"|"day"|"hour"|"millisecond"|"minute"|"month"|"second"|"week"|"year"|"ytd"} Highcharts.RangeSelectorButtonTypeValue
|
*/
|
/**
|
* Callback function to react on button clicks.
|
*
|
* @callback Highcharts.RangeSelectorClickCallbackFunction
|
*
|
* @param {global.Event} e
|
* Event arguments.
|
*
|
* @param {boolean|undefined}
|
* Return false to cancel the default button event.
|
*/
|
/**
|
* Callback function to parse values entered in the input boxes and return a
|
* valid JavaScript time as milliseconds since 1970.
|
*
|
* @callback Highcharts.RangeSelectorParseCallbackFunction
|
*
|
* @param {string} value
|
* Input value to parse.
|
*
|
* @return {number}
|
* Parsed JavaScript time value.
|
*/
|
|
var addEvent = _Globals2.default.addEvent,
|
Axis = _Globals2.default.Axis,
|
Chart = _Globals2.default.Chart,
|
css = _Globals2.default.css,
|
createElement = _Globals2.default.createElement,
|
defaultOptions = _Globals2.default.defaultOptions,
|
destroyObjectProperties = _Globals2.default.destroyObjectProperties,
|
discardElement = _Globals2.default.discardElement,
|
extend = _Globals2.default.extend,
|
fireEvent = _Globals2.default.fireEvent,
|
merge = _Globals2.default.merge,
|
pick = _Globals2.default.pick;
|
/* ************************************************************************** *
|
* Start Range Selector code *
|
* ************************************************************************** */
|
extend(defaultOptions, {
|
/**
|
* The range selector is a tool for selecting ranges to display within
|
* the chart. It provides buttons to select preconfigured ranges in
|
* the chart, like 1 day, 1 week, 1 month etc. It also provides input
|
* boxes where min and max dates can be manually input.
|
*
|
* @product highstock gantt
|
* @optionparent rangeSelector
|
*/
|
rangeSelector: {
|
/**
|
* Whether to enable all buttons from the start. By default buttons are
|
* only enabled if the corresponding time range exists on the X axis,
|
* but enabling all buttons allows for dynamically loading different
|
* time ranges.
|
*
|
* @sample {highstock} stock/rangeselector/allbuttonsenabled-true/
|
* All buttons enabled
|
*
|
* @type {boolean}
|
* @default false
|
* @since 2.0.3
|
* @apioption rangeSelector.allButtonsEnabled
|
*/
|
/**
|
* An array of configuration objects for the buttons.
|
*
|
* Defaults to
|
*
|
* <pre>buttons: [{
|
* type: 'month',
|
* count: 1,
|
* text: '1m'
|
* }, {
|
* type: 'month',
|
* count: 3,
|
* text: '3m'
|
* }, {
|
* type: 'month',
|
* count: 6,
|
* text: '6m'
|
* }, {
|
* type: 'ytd',
|
* text: 'YTD'
|
* }, {
|
* type: 'year',
|
* count: 1,
|
* text: '1y'
|
* }, {
|
* type: 'all',
|
* text: 'All'
|
* }]</pre>
|
*
|
* @sample {highstock} stock/rangeselector/datagrouping/
|
* Data grouping by buttons
|
*
|
* @type {Array<*>}
|
* @apioption rangeSelector.buttons
|
*/
|
/**
|
* How many units of the defined type the button should span. If `type`
|
* is "month" and `count` is 3, the button spans three months.
|
*
|
* @type {number}
|
* @default 1
|
* @apioption rangeSelector.buttons.count
|
*/
|
/**
|
* Fires when clicking on the rangeSelector button. One parameter,
|
* event, is passed to the function, containing common event
|
* information.
|
*
|
* <pre>
|
* click: function(e) {
|
* console.log(this);
|
* }
|
* </pre>
|
*
|
* Return false to stop default button's click action.
|
*
|
* @sample {highstock} stock/rangeselector/button-click/
|
* Click event on the button
|
*
|
* @type {Highcharts.RangeSelectorClickCallbackFunction}
|
* @apioption rangeSelector.buttons.events.click
|
*/
|
/**
|
* Additional range (in milliseconds) added to the end of the calculated
|
* time span.
|
*
|
* @sample {highstock} stock/rangeselector/min-max-offsets/
|
* Button offsets
|
*
|
* @type {number}
|
* @default 0
|
* @since 6.0.0
|
* @apioption rangeSelector.buttons.offsetMax
|
*/
|
/**
|
* Additional range (in milliseconds) added to the start of the
|
* calculated time span.
|
*
|
* @sample {highstock} stock/rangeselector/min-max-offsets/
|
* Button offsets
|
*
|
* @type {number}
|
* @default 0
|
* @since 6.0.0
|
* @apioption rangeSelector.buttons.offsetMin
|
*/
|
/**
|
* When buttons apply dataGrouping on a series, by default zooming
|
* in/out will deselect buttons and unset dataGrouping. Enable this
|
* option to keep buttons selected when extremes change.
|
*
|
* @sample {highstock} stock/rangeselector/preserve-datagrouping/
|
* Different preserveDataGrouping settings
|
*
|
* @type {boolean}
|
* @default false
|
* @since 6.1.2
|
* @apioption rangeSelector.buttons.preserveDataGrouping
|
*/
|
/**
|
* A custom data grouping object for each button.
|
*
|
* @see [series.dataGrouping](#plotOptions.series.dataGrouping)
|
*
|
* @sample {highstock} stock/rangeselector/datagrouping/
|
* Data grouping by range selector buttons
|
*
|
* @type {*}
|
* @extends plotOptions.series.dataGrouping
|
* @apioption rangeSelector.buttons.dataGrouping
|
*/
|
/**
|
* The text for the button itself.
|
*
|
* @type {string}
|
* @apioption rangeSelector.buttons.text
|
*/
|
/**
|
* Defined the time span for the button. Can be one of `millisecond`,
|
* `second`, `minute`, `hour`, `day`, `week`, `month`, `year`, `ytd`,
|
* and `all`.
|
*
|
* @type {Highcharts.RangeSelectorButtonTypeValue}
|
* @apioption rangeSelector.buttons.type
|
*/
|
/**
|
* The space in pixels between the buttons in the range selector.
|
*
|
* @type {number}
|
* @default 0
|
* @apioption rangeSelector.buttonSpacing
|
*/
|
/**
|
* Enable or disable the range selector.
|
*
|
* @sample {highstock} stock/rangeselector/enabled/
|
* Disable the range selector
|
*
|
* @type {boolean}
|
* @default true
|
* @apioption rangeSelector.enabled
|
*/
|
/**
|
* The vertical alignment of the rangeselector box. Allowed properties
|
* are `top`, `middle`, `bottom`.
|
*
|
* @sample {highstock} stock/rangeselector/vertical-align-middle/
|
* Middle
|
* @sample {highstock} stock/rangeselector/vertical-align-bottom/
|
* Bottom
|
*
|
* @type {Highcharts.VerticalAlignValue}
|
* @since 6.0.0
|
*/
|
verticalAlign: 'top',
|
/**
|
* A collection of attributes for the buttons. The object takes SVG
|
* attributes like `fill`, `stroke`, `stroke-width`, as well as `style`,
|
* a collection of CSS properties for the text.
|
*
|
* The object can also be extended with states, so you can set
|
* presentational options for `hover`, `select` or `disabled` button
|
* states.
|
*
|
* CSS styles for the text label.
|
*
|
* In styled mode, the buttons are styled by the
|
* `.highcharts-range-selector-buttons .highcharts-button` rule with its
|
* different states.
|
*
|
* @sample {highstock} stock/rangeselector/styling/
|
* Styling the buttons and inputs
|
*
|
* @type {Highcharts.SVGAttributes}
|
*/
|
buttonTheme: {
|
/** @ignore */
|
width: 28,
|
/** @ignore */
|
height: 18,
|
/** @ignore */
|
padding: 2,
|
/** @ignore */
|
zIndex: 7 // #484, #852
|
},
|
/**
|
* When the rangeselector is floating, the plot area does not reserve
|
* space for it. This opens for positioning anywhere on the chart.
|
*
|
* @sample {highstock} stock/rangeselector/floating/
|
* Placing the range selector between the plot area and the
|
* navigator
|
*
|
* @since 6.0.0
|
*/
|
floating: false,
|
/**
|
* The x offset of the range selector relative to its horizontal
|
* alignment within `chart.spacingLeft` and `chart.spacingRight`.
|
*
|
* @since 6.0.0
|
*/
|
x: 0,
|
/**
|
* The y offset of the range selector relative to its horizontal
|
* alignment within `chart.spacingLeft` and `chart.spacingRight`.
|
*
|
* @since 6.0.0
|
*/
|
y: 0,
|
/**
|
* Deprecated. The height of the range selector. Currently it is
|
* calculated dynamically.
|
*
|
* @deprecated
|
* @type {number|undefined}
|
* @since 2.1.9
|
*/
|
height: undefined,
|
/**
|
* The border color of the date input boxes.
|
*
|
* @sample {highstock} stock/rangeselector/styling/
|
* Styling the buttons and inputs
|
*
|
* @type {Highcharts.ColorString}
|
* @default #cccccc
|
* @since 1.3.7
|
* @apioption rangeSelector.inputBoxBorderColor
|
*/
|
/**
|
* The pixel height of the date input boxes.
|
*
|
* @sample {highstock} stock/rangeselector/styling/
|
* Styling the buttons and inputs
|
*
|
* @type {number}
|
* @default 17
|
* @since 1.3.7
|
* @apioption rangeSelector.inputBoxHeight
|
*/
|
/**
|
* CSS for the container DIV holding the input boxes. Deprecated as
|
* of 1.2.5\. Use [inputPosition](#rangeSelector.inputPosition) instead.
|
*
|
* @sample {highstock} stock/rangeselector/styling/
|
* Styling the buttons and inputs
|
*
|
* @deprecated
|
* @type {Highcharts.CSSObject}
|
* @apioption rangeSelector.inputBoxStyle
|
*/
|
/**
|
* The pixel width of the date input boxes.
|
*
|
* @sample {highstock} stock/rangeselector/styling/
|
* Styling the buttons and inputs
|
*
|
* @type {number}
|
* @default 90
|
* @since 1.3.7
|
* @apioption rangeSelector.inputBoxWidth
|
*/
|
/**
|
* The date format in the input boxes when not selected for editing.
|
* Defaults to `%b %e, %Y`.
|
*
|
* @sample {highstock} stock/rangeselector/input-format/
|
* Milliseconds in the range selector
|
*
|
* @type {string}
|
* @default %b %e, %Y
|
* @apioption rangeSelector.inputDateFormat
|
*/
|
/**
|
* A custom callback function to parse values entered in the input boxes
|
* and return a valid JavaScript time as milliseconds since 1970.
|
*
|
* @sample {highstock} stock/rangeselector/input-format/
|
* Milliseconds in the range selector
|
*
|
* @type {Highcharts.RangeSelectorParseCallbackFunction}
|
* @since 1.3.3
|
* @apioption rangeSelector.inputDateParser
|
*/
|
/**
|
* The date format in the input boxes when they are selected for
|
* editing. This must be a format that is recognized by JavaScript
|
* Date.parse.
|
*
|
* @sample {highstock} stock/rangeselector/input-format/
|
* Milliseconds in the range selector
|
*
|
* @type {string}
|
* @default %Y-%m-%d
|
* @apioption rangeSelector.inputEditDateFormat
|
*/
|
/**
|
* Enable or disable the date input boxes. Defaults to enabled when
|
* there is enough space, disabled if not (typically mobile).
|
*
|
* @sample {highstock} stock/rangeselector/input-datepicker/
|
* Extending the input with a jQuery UI datepicker
|
*
|
* @type {boolean}
|
* @default true
|
* @apioption rangeSelector.inputEnabled
|
*/
|
/**
|
* Positioning for the input boxes. Allowed properties are `align`,
|
* `x` and `y`.
|
*
|
* @since 1.2.4
|
*/
|
inputPosition: {
|
/**
|
* The alignment of the input box. Allowed properties are `left`,
|
* `center`, `right`.
|
*
|
* @sample {highstock} stock/rangeselector/input-button-position/
|
* Alignment
|
*
|
* @type {Highcharts.AlignValue}
|
* @since 6.0.0
|
*/
|
align: 'right',
|
/**
|
* X offset of the input row.
|
*/
|
x: 0,
|
/**
|
* Y offset of the input row.
|
*/
|
y: 0
|
},
|
/**
|
* The index of the button to appear pre-selected.
|
*
|
* @type {number}
|
* @apioption rangeSelector.selected
|
*/
|
/**
|
* Positioning for the button row.
|
*
|
* @since 1.2.4
|
*/
|
buttonPosition: {
|
/**
|
* The alignment of the input box. Allowed properties are `left`,
|
* `center`, `right`.
|
*
|
* @sample {highstock} stock/rangeselector/input-button-position/
|
* Alignment
|
*
|
* @type {Highcharts.AlignValue}
|
* @since 6.0.0
|
*/
|
align: 'left',
|
/**
|
* X offset of the button row.
|
*/
|
x: 0,
|
/**
|
* Y offset of the button row.
|
*/
|
y: 0
|
},
|
/**
|
* CSS for the HTML inputs in the range selector.
|
*
|
* In styled mode, the inputs are styled by the
|
* `.highcharts-range-input text` rule in SVG mode, and
|
* `input.highcharts-range-selector` when active.
|
*
|
* @sample {highstock} stock/rangeselector/styling/
|
* Styling the buttons and inputs
|
*
|
* @type {Highcharts.CSSObject}
|
* @apioption rangeSelector.inputStyle
|
*/
|
/**
|
* CSS styles for the labels - the Zoom, From and To texts.
|
*
|
* In styled mode, the labels are styled by the
|
* `.highcharts-range-label` class.
|
*
|
* @sample {highstock} stock/rangeselector/styling/
|
* Styling the buttons and inputs
|
*
|
* @type {Highcharts.CSSObject}
|
*/
|
labelStyle: {
|
/** @ignore */
|
color: '#666666'
|
}
|
}
|
});
|
defaultOptions.lang = merge(defaultOptions.lang,
|
/**
|
* Language object. The language object is global and it can't be set
|
* on each chart initialization. Instead, use `Highcharts.setOptions` to
|
* set it before any chart is initialized.
|
*
|
* <pre>Highcharts.setOptions({
|
* lang: {
|
* months: [
|
* 'Janvier', 'Février', 'Mars', 'Avril',
|
* 'Mai', 'Juin', 'Juillet', 'Août',
|
* 'Septembre', 'Octobre', 'Novembre', 'Décembre'
|
* ],
|
* weekdays: [
|
* 'Dimanche', 'Lundi', 'Mardi', 'Mercredi',
|
* 'Jeudi', 'Vendredi', 'Samedi'
|
* ]
|
* }
|
* });</pre>
|
*
|
* @optionparent lang
|
*/
|
{
|
/**
|
* The text for the label for the range selector buttons.
|
*
|
* @product highstock gantt
|
*/
|
rangeSelectorZoom: 'Zoom',
|
/**
|
* The text for the label for the "from" input box in the range
|
* selector.
|
*
|
* @product highstock gantt
|
*/
|
rangeSelectorFrom: 'From',
|
/**
|
* The text for the label for the "to" input box in the range selector.
|
*
|
* @product highstock gantt
|
*/
|
rangeSelectorTo: 'To'
|
});
|
/* eslint-disable no-invalid-this, valid-jsdoc */
|
/**
|
* The range selector.
|
*
|
* @private
|
* @class
|
* @name Highcharts.RangeSelector
|
* @param {Highcharts.Chart} chart
|
*/
|
function RangeSelector(chart) {
|
// Run RangeSelector
|
this.init(chart);
|
}
|
RangeSelector.prototype = {
|
/**
|
* The method to run when one of the buttons in the range selectors is
|
* clicked
|
*
|
* @private
|
* @function Highcharts.RangeSelector#clickButton
|
* @param {number} i
|
* The index of the button
|
* @param {boolean} [redraw]
|
* @return {void}
|
*/
|
clickButton: function clickButton(i, redraw) {
|
var 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, pick(dataMax, baseAxis.max))),
|
// #1568
|
type = rangeOptions.type,
|
baseXAxisOptions,
|
range = rangeOptions._range,
|
rangeMin,
|
minSetting,
|
rangeSetting,
|
ctx,
|
ytdExtremes,
|
dataGrouping = rangeOptions.dataGrouping;
|
// chart has no data, base series is removed
|
if (dataMin === null || dataMax === null) {
|
return;
|
}
|
// Set the fixed range before range is altered
|
chart.fixedRange = range;
|
// Apply dataGrouping associated to button
|
if (dataGrouping) {
|
this.forcedDataGrouping = true;
|
Axis.prototype.setDataGrouping.call(baseAxis || { chart: this.chart }, dataGrouping, false);
|
this.frozenStates = rangeOptions.preserveDataGrouping;
|
}
|
// Apply range
|
if (type === 'month' || type === 'year') {
|
if (!baseAxis) {
|
// This is set to the user options and picked up later when the
|
// axis is instantiated so that we know the min and max.
|
range = rangeOptions;
|
} else {
|
ctx = {
|
range: rangeOptions,
|
max: newMax,
|
chart: chart,
|
dataMin: dataMin,
|
dataMax: dataMax
|
};
|
newMin = baseAxis.minFromRange.call(ctx);
|
if (isNumber(ctx.newMax)) {
|
newMax = ctx.newMax;
|
}
|
}
|
// Fixed times like minutes, hours, days
|
} else if (range) {
|
newMin = Math.max(newMax - range, dataMin);
|
newMax = Math.min(newMin + range, dataMax);
|
} else if (type === 'ytd') {
|
// On user clicks on the buttons, or a delayed action running from
|
// the beforeRender event (below), the baseAxis is defined.
|
if (baseAxis) {
|
// When "ytd" is the pre-selected button for the initial view,
|
// its calculation is delayed and rerun in the beforeRender
|
// event (below). When the series are initialized, but before
|
// the chart is rendered, we have access to the xData array
|
// (#942).
|
if (dataMax === undefined) {
|
dataMin = Number.MAX_VALUE;
|
dataMax = Number.MIN_VALUE;
|
chart.series.forEach(function (series) {
|
// reassign it to the last item
|
var xData = series.xData;
|
dataMin = Math.min(xData[0], dataMin);
|
dataMax = Math.max(xData[xData.length - 1], dataMax);
|
});
|
redraw = false;
|
}
|
ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, chart.time.useUTC);
|
newMin = rangeMin = ytdExtremes.min;
|
newMax = ytdExtremes.max;
|
// "ytd" is pre-selected. We don't yet have access to processed
|
// point and extremes data (things like pointStart and pointInterval
|
// are missing), so we delay the process (#942)
|
} else {
|
rangeSelector.deferredYTDClick = i;
|
return;
|
}
|
} else if (type === 'all' && baseAxis) {
|
newMin = dataMin;
|
newMax = dataMax;
|
}
|
newMin += rangeOptions._offsetMin;
|
newMax += rangeOptions._offsetMax;
|
rangeSelector.setSelected(i);
|
// Update the chart
|
if (!baseAxis) {
|
// Axis not yet instanciated. Temporarily set min and range
|
// options and remove them on chart load (#4317).
|
baseXAxisOptions = splat(chart.options.xAxis)[0];
|
rangeSetting = baseXAxisOptions.range;
|
baseXAxisOptions.range = range;
|
minSetting = baseXAxisOptions.min;
|
baseXAxisOptions.min = rangeMin;
|
addEvent(chart, 'load', function resetMinAndRange() {
|
baseXAxisOptions.range = rangeSetting;
|
baseXAxisOptions.min = minSetting;
|
});
|
} else {
|
// Existing axis object. Set extremes after render time.
|
baseAxis.setExtremes(newMin, newMax, pick(redraw, 1), null, // auto animation
|
{
|
trigger: 'rangeSelectorButton',
|
rangeSelectorButton: rangeOptions
|
});
|
}
|
},
|
/**
|
* Set the selected option. This method only sets the internal flag, it
|
* doesn't update the buttons or the actual zoomed range.
|
*
|
* @private
|
* @function Highcharts.RangeSelector#setSelected
|
* @param {number} [selected]
|
* @return {void}
|
*/
|
setSelected: function setSelected(selected) {
|
this.selected = this.options.selected = selected;
|
},
|
/**
|
* The default buttons for pre-selecting time frames
|
*/
|
defaultButtons: [{
|
type: 'month',
|
count: 1,
|
text: '1m'
|
}, {
|
type: 'month',
|
count: 3,
|
text: '3m'
|
}, {
|
type: 'month',
|
count: 6,
|
text: '6m'
|
}, {
|
type: 'ytd',
|
text: 'YTD'
|
}, {
|
type: 'year',
|
count: 1,
|
text: '1y'
|
}, {
|
type: 'all',
|
text: 'All'
|
}],
|
/**
|
* Initialize the range selector
|
*
|
* @private
|
* @function Highcharts.RangeSelector#init
|
* @param {Highcharts.Chart} chart
|
* @return {void}
|
*/
|
init: function init(chart) {
|
var rangeSelector = this,
|
options = chart.options.rangeSelector,
|
buttonOptions = options.buttons || [].concat(rangeSelector.defaultButtons),
|
selectedOption = options.selected,
|
blurInputs = function blurInputs() {
|
var minInput = rangeSelector.minInput,
|
maxInput = rangeSelector.maxInput;
|
// #3274 in some case blur is not defined
|
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.unMouseDown = addEvent(chart.container, 'mousedown', blurInputs);
|
this.unResize = addEvent(chart, 'resize', blurInputs);
|
// Extend the buttonOptions with actual range
|
buttonOptions.forEach(rangeSelector.computeButtonRange);
|
// zoomed range based on a pre-selected button index
|
if (selectedOption !== undefined && buttonOptions[selectedOption]) {
|
this.clickButton(selectedOption, false);
|
}
|
addEvent(chart, 'load', function () {
|
// If a data grouping is applied to the current button, release it
|
// when extremes change
|
if (chart.xAxis && chart.xAxis[0]) {
|
addEvent(chart.xAxis[0], 'setExtremes', function (e) {
|
if (this.max - this.min !== chart.fixedRange && e.trigger !== 'rangeSelectorButton' && e.trigger !== 'updatedData' && rangeSelector.forcedDataGrouping && !rangeSelector.frozenStates) {
|
this.setDataGrouping(false, false);
|
}
|
});
|
}
|
});
|
},
|
/**
|
* Dynamically update the range selector buttons after a new range has been
|
* set
|
*
|
* @private
|
* @function Highcharts.RangeSelector#updateButtonStates
|
* @return {void}
|
*/
|
updateButtonStates: function updateButtonStates() {
|
var rangeSelector = this,
|
chart = this.chart,
|
baseAxis = chart.xAxis[0],
|
actualRange = Math.round(baseAxis.max - baseAxis.min),
|
hasNoData = !baseAxis.hasVisibleSeries,
|
day = 24 * 36e5,
|
// A single day in milliseconds
|
unionExtremes = chart.scroller && chart.scroller.getUnionExtremes() || baseAxis,
|
dataMin = unionExtremes.dataMin,
|
dataMax = unionExtremes.dataMax,
|
ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, 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) {
|
var range = rangeOptions._range,
|
type = rangeOptions.type,
|
count = rangeOptions.count || 1,
|
button = buttons[i],
|
state = 0,
|
disable,
|
select,
|
offsetRange = rangeOptions._offsetMax - rangeOptions._offsetMin,
|
isSelected = i === selected,
|
|
// Disable buttons where the range exceeds what is allowed in
|
// the current view
|
isTooGreatRange = range > dataMax - dataMin,
|
|
// Disable buttons where the range is smaller than the minimum
|
// range
|
isTooSmallRange = range < baseAxis.minRange,
|
|
// Do not select the YTD button if not explicitly told so
|
isYTDButNotSelected = false,
|
|
// Disable the All button if we're already showing all
|
isAllButAlreadyShowingAll = false,
|
isSameRange = range === actualRange;
|
// Months and years have a variable range so we check the extremes
|
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 - baseAxis.min >= dataMax - dataMin;
|
isAllButAlreadyShowingAll = !isSelected && selectedExists && isSameRange;
|
}
|
// The new zoom area happens to match the range for a button - mark
|
// it selected. This happens when scrolling across an ordinal gap.
|
// It can be seen in the intraday demos when selecting 1h and scroll
|
// across the night gap.
|
disable = !allButtonsEnabled && (isTooGreatRange || isTooSmallRange || isAllButAlreadyShowingAll || hasNoData);
|
select = isSelected && isSameRange || isSameRange && !selectedExists && !isYTDButNotSelected || isSelected && rangeSelector.frozenStates;
|
if (disable) {
|
state = 3;
|
} else if (select) {
|
selectedExists = true; // Only one button can be selected
|
state = 2;
|
}
|
// If state has changed, update the button
|
if (button.state !== state) {
|
button.setState(state);
|
// Reset (#9209)
|
if (state === 0 && selected === i) {
|
rangeSelector.setSelected(null);
|
}
|
}
|
});
|
},
|
/**
|
* Compute and cache the range for an individual button
|
*
|
* @private
|
* @function Highcharts.RangeSelector#computeButtonRange
|
* @param {Highcharts.RangeSelectorButtonsOptions} rangeOptions
|
* @return {void}
|
*/
|
computeButtonRange: function computeButtonRange(rangeOptions) {
|
var type = rangeOptions.type,
|
count = rangeOptions.count || 1,
|
|
// these time intervals have a fixed number of milliseconds, as
|
// opposed to month, ytd and year
|
fixedTimes = {
|
millisecond: 1,
|
second: 1000,
|
minute: 60 * 1000,
|
hour: 3600 * 1000,
|
day: 24 * 3600 * 1000,
|
week: 7 * 24 * 3600 * 1000
|
};
|
// Store the range on the button object
|
if (fixedTimes[type]) {
|
rangeOptions._range = fixedTimes[type] * count;
|
} else if (type === 'month' || type === 'year') {
|
rangeOptions._range = {
|
month: 30,
|
year: 365
|
}[type] * 24 * 36e5 * count;
|
}
|
rangeOptions._offsetMin = pick(rangeOptions.offsetMin, 0);
|
rangeOptions._offsetMax = pick(rangeOptions.offsetMax, 0);
|
rangeOptions._range += rangeOptions._offsetMax - rangeOptions._offsetMin;
|
},
|
/**
|
* Set the internal and displayed value of a HTML input for the dates
|
*
|
* @private
|
* @function Highcharts.RangeSelector#setInputValue
|
* @param {string} name
|
* @param {number} [inputTime]
|
* @return {void}
|
*/
|
setInputValue: function setInputValue(name, inputTime) {
|
var options = this.chart.options.rangeSelector,
|
time = this.chart.time,
|
input = this[name + 'Input'];
|
if (defined(inputTime)) {
|
input.previousValue = input.HCTime;
|
input.HCTime = inputTime;
|
}
|
input.value = time.dateFormat(options.inputEditDateFormat || '%Y-%m-%d', input.HCTime);
|
this[name + 'DateBox'].attr({
|
text: time.dateFormat(options.inputDateFormat || '%b %e, %Y', input.HCTime)
|
});
|
},
|
/**
|
* @private
|
* @function Highcharts.RangeSelector#showInput
|
* @param {string} name
|
* @return {void}
|
*/
|
showInput: function showInput(name) {
|
var inputGroup = this.inputGroup,
|
dateBox = this[name + 'DateBox'];
|
css(this[name + 'Input'], {
|
left: inputGroup.translateX + dateBox.x + 'px',
|
top: inputGroup.translateY + 'px',
|
width: dateBox.width - 2 + 'px',
|
height: dateBox.height - 2 + 'px',
|
border: '2px solid silver'
|
});
|
},
|
/**
|
* @private
|
* @function Highcharts.RangeSelector#hideInput
|
* @param {string} name
|
* @return {void}
|
*/
|
hideInput: function hideInput(name) {
|
css(this[name + 'Input'], {
|
border: 0,
|
width: '1px',
|
height: '1px'
|
});
|
this.setInputValue(name);
|
},
|
/**
|
* Draw either the 'from' or the 'to' HTML input box of the range selector
|
*
|
* @private
|
* @function Highcharts.RangeSelector#drawInput
|
* @param {string} name
|
* @return {void}
|
*/
|
drawInput: function drawInput(name) {
|
var rangeSelector = this,
|
chart = rangeSelector.chart,
|
chartStyle = chart.renderer.style || {},
|
renderer = chart.renderer,
|
options = chart.options.rangeSelector,
|
lang = defaultOptions.lang,
|
div = rangeSelector.div,
|
isMin = name === 'min',
|
input,
|
label,
|
dateBox,
|
inputGroup = this.inputGroup;
|
/**
|
* @private
|
*/
|
function updateExtremes() {
|
var inputValue = input.value,
|
value = (options.inputDateParser || Date.parse)(inputValue),
|
chartAxis = chart.xAxis[0],
|
dataAxis = chart.scroller && chart.scroller.xAxis ? chart.scroller.xAxis : chartAxis,
|
dataMin = dataAxis.dataMin,
|
dataMax = dataAxis.dataMax;
|
if (value !== input.previousValue) {
|
input.previousValue = value;
|
// If the value isn't parsed directly to a value by the
|
// browser's Date.parse method, like YYYY-MM-DD in IE, try
|
// parsing it a different way
|
if (!isNumber(value)) {
|
value = inputValue.split('-');
|
value = Date.UTC(pInt(value[0]), pInt(value[1]) - 1, pInt(value[2]));
|
}
|
if (isNumber(value)) {
|
// Correct for timezone offset (#433)
|
if (!chart.time.useUTC) {
|
value = value + new Date().getTimezoneOffset() * 60 * 1000;
|
}
|
// Validate the extremes. If it goes beyound the data min or
|
// max, use the actual data extreme (#2438).
|
if (isMin) {
|
if (value > rangeSelector.maxInput.HCTime) {
|
value = undefined;
|
} else if (value < dataMin) {
|
value = dataMin;
|
}
|
} else {
|
if (value < rangeSelector.minInput.HCTime) {
|
value = undefined;
|
} else if (value > dataMax) {
|
value = dataMax;
|
}
|
}
|
// Set the extremes
|
if (value !== undefined) {
|
// @todo typof undefined
|
chartAxis.setExtremes(isMin ? value : chartAxis.min, isMin ? chartAxis.max : value, undefined, undefined, { trigger: 'rangeSelectorInput' });
|
}
|
}
|
}
|
}
|
// Create the text label
|
this[name + 'Label'] = label = renderer.label(lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo'], this.inputGroup.offset).addClass('highcharts-range-label').attr({
|
padding: 2
|
}).add(inputGroup);
|
inputGroup.offset += label.width + 5;
|
// Create an SVG label that shows updated date ranges and and records
|
// click events that bring in the HTML input.
|
this[name + 'DateBox'] = dateBox = renderer.label('', inputGroup.offset).addClass('highcharts-range-input').attr({
|
padding: 2,
|
width: options.inputBoxWidth || 90,
|
height: options.inputBoxHeight || 17,
|
'text-align': 'center'
|
}).on('click', function () {
|
// If it is already focused, the onfocus event doesn't fire
|
// (#3713)
|
rangeSelector.showInput(name);
|
rangeSelector[name + 'Input'].focus();
|
});
|
if (!chart.styledMode) {
|
dateBox.attr({
|
stroke: options.inputBoxBorderColor || '#cccccc',
|
'stroke-width': 1
|
});
|
}
|
dateBox.add(inputGroup);
|
inputGroup.offset += dateBox.width + (isMin ? 10 : 0);
|
// Create the HTML input element. This is rendered as 1x1 pixel then set
|
// to the right size when focused.
|
this[name + 'Input'] = input = createElement('input', {
|
name: name,
|
className: 'highcharts-range-selector',
|
type: 'text'
|
}, {
|
top: chart.plotTop + 'px' // prevent jump on focus in Firefox
|
}, div);
|
if (!chart.styledMode) {
|
// Styles
|
label.css(merge(chartStyle, options.labelStyle));
|
dateBox.css(merge({
|
color: '#333333'
|
}, chartStyle, options.inputStyle));
|
css(input, extend({
|
position: 'absolute',
|
border: 0,
|
width: '1px',
|
height: '1px',
|
padding: 0,
|
textAlign: 'center',
|
fontSize: chartStyle.fontSize,
|
fontFamily: chartStyle.fontFamily,
|
top: '-9999em' // #4798
|
}, options.inputStyle));
|
}
|
// Blow up the input box
|
input.onfocus = function () {
|
rangeSelector.showInput(name);
|
};
|
// Hide away the input box
|
input.onblur = function () {
|
// update extermes only when inputs are active
|
if (input === _Globals2.default.doc.activeElement) {
|
// Only when focused
|
// Update also when no `change` event is triggered, like when
|
// clicking inside the SVG (#4710)
|
updateExtremes();
|
}
|
// #10404 - move hide and blur outside focus
|
rangeSelector.hideInput(name);
|
input.blur(); // #4606
|
};
|
// handle changes in the input boxes
|
input.onchange = updateExtremes;
|
input.onkeypress = function (event) {
|
// IE does not fire onchange on enter
|
if (event.keyCode === 13) {
|
updateExtremes();
|
}
|
};
|
},
|
/**
|
* Get the position of the range selector buttons and inputs. This can be
|
* overridden from outside for custom positioning.
|
*
|
* @private
|
* @function Highcharts.RangeSelector#getPosition
|
*
|
* @return {Highcharts.Dictionary<number>}
|
*/
|
getPosition: function getPosition() {
|
var chart = this.chart,
|
options = chart.options.rangeSelector,
|
top = options.verticalAlign === 'top' ? chart.plotTop - chart.axisOffset[0] : 0; // set offset only for varticalAlign top
|
return {
|
buttonTop: top + options.buttonPosition.y,
|
inputTop: top + options.inputPosition.y - 10
|
};
|
},
|
/**
|
* Get the extremes of YTD. Will choose dataMax if its value is lower than
|
* the current timestamp. Will choose dataMin if its value is higher than
|
* the timestamp for the start of current year.
|
*
|
* @private
|
* @function Highcharts.RangeSelector#getYTDExtremes
|
*
|
* @param {number} dataMax
|
*
|
* @param {number} dataMin
|
*
|
* @return {*}
|
* Returns min and max for the YTD
|
*/
|
getYTDExtremes: function getYTDExtremes(dataMax, dataMin, useUTC) {
|
var time = this.chart.time,
|
min,
|
now = new time.Date(dataMax),
|
year = time.get('FullYear', now),
|
startOfYear = useUTC ? time.Date.UTC(year, 0, 1) : // eslint-disable-line new-cap
|
+new time.Date(year, 0, 1);
|
min = Math.max(dataMin || 0, startOfYear);
|
now = now.getTime();
|
return {
|
max: Math.min(dataMax || now, now),
|
min: min
|
};
|
},
|
/**
|
* Render the range selector including the buttons and the inputs. The first
|
* time render is called, the elements are created and positioned. On
|
* subsequent calls, they are moved and updated.
|
*
|
* @private
|
* @function Highcharts.RangeSelector#render
|
* @param {number} [min]
|
* X axis minimum
|
* @param {number} [max]
|
* X axis maximum
|
* @return {void}
|
*/
|
render: function render(min, max) {
|
var rangeSelector = this,
|
chart = rangeSelector.chart,
|
renderer = chart.renderer,
|
container = chart.container,
|
chartOptions = chart.options,
|
navButtonOptions = chartOptions.exporting && chartOptions.exporting.enabled !== false && chartOptions.navigation && chartOptions.navigation.buttonOptions,
|
lang = defaultOptions.lang,
|
div = rangeSelector.div,
|
options = chartOptions.rangeSelector,
|
|
// Place inputs above the container
|
inputsZIndex = pick(chartOptions.chart.style && chartOptions.chart.style.zIndex, 0) + 1,
|
floating = options.floating,
|
buttons = rangeSelector.buttons,
|
inputGroup = rangeSelector.inputGroup,
|
buttonTheme = options.buttonTheme,
|
buttonPosition = options.buttonPosition,
|
inputPosition = options.inputPosition,
|
inputEnabled = options.inputEnabled,
|
states = buttonTheme && buttonTheme.states,
|
plotLeft = chart.plotLeft,
|
buttonLeft,
|
buttonGroup = rangeSelector.buttonGroup,
|
group,
|
groupHeight,
|
rendered = rangeSelector.rendered,
|
verticalAlign = rangeSelector.options.verticalAlign,
|
legend = chart.legend,
|
legendOptions = legend && legend.options,
|
buttonPositionY = buttonPosition.y,
|
inputPositionY = inputPosition.y,
|
animate = rendered || false,
|
verb = animate ? 'animate' : 'attr',
|
exportingX = 0,
|
alignTranslateY,
|
legendHeight,
|
minPosition,
|
translateY = 0,
|
translateX;
|
if (options.enabled === false) {
|
return;
|
}
|
// create the elements
|
if (!rendered) {
|
rangeSelector.group = group = renderer.g('range-selector-group').attr({
|
zIndex: 7
|
}).add();
|
rangeSelector.buttonGroup = buttonGroup = renderer.g('range-selector-buttons').add(group);
|
rangeSelector.zoomText = renderer.text(lang.rangeSelectorZoom, 0, 15).add(buttonGroup);
|
if (!chart.styledMode) {
|
rangeSelector.zoomText.css(options.labelStyle);
|
buttonTheme['stroke-width'] = pick(buttonTheme['stroke-width'], 0);
|
}
|
rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
|
buttons[i] = renderer.button(rangeOptions.text, 0, 0, function (e) {
|
// extract events from button object and call
|
var buttonEvents = rangeOptions.events && rangeOptions.events.click,
|
callDefaultEvent;
|
if (buttonEvents) {
|
callDefaultEvent = buttonEvents.call(rangeOptions, e);
|
}
|
if (callDefaultEvent !== false) {
|
rangeSelector.clickButton(i);
|
}
|
rangeSelector.isActive = true;
|
}, buttonTheme, states && states.hover, states && states.select, states && states.disabled).attr({
|
'text-align': 'center'
|
}).add(buttonGroup);
|
});
|
// first create a wrapper outside the container in order to make
|
// the inputs work and make export correct
|
if (inputEnabled !== false) {
|
rangeSelector.div = div = createElement('div', null, {
|
position: 'relative',
|
height: 0,
|
zIndex: inputsZIndex
|
});
|
container.parentNode.insertBefore(div, container);
|
// Create the group to keep the inputs
|
rangeSelector.inputGroup = inputGroup = renderer.g('input-group').add(group);
|
inputGroup.offset = 0;
|
rangeSelector.drawInput('min');
|
rangeSelector.drawInput('max');
|
}
|
}
|
// #8769, allow dynamically updating margins
|
rangeSelector.zoomText[verb]({
|
x: pick(plotLeft + buttonPosition.x, plotLeft)
|
});
|
// button start position
|
buttonLeft = pick(plotLeft + buttonPosition.x, plotLeft) + rangeSelector.zoomText.getBBox().width + 5;
|
rangeSelector.buttonOptions.forEach(function (rangeOptions, i) {
|
buttons[i][verb]({ x: buttonLeft });
|
// increase button position for the next button
|
buttonLeft += buttons[i].width + pick(options.buttonSpacing, 5);
|
});
|
plotLeft = chart.plotLeft - chart.spacing[3];
|
rangeSelector.updateButtonStates();
|
// detect collisiton with exporting
|
if (navButtonOptions && this.titleCollision(chart) && verticalAlign === 'top' && buttonPosition.align === 'right' && buttonPosition.y + buttonGroup.getBBox().height - 12 < (navButtonOptions.y || 0) + navButtonOptions.height) {
|
exportingX = -40;
|
}
|
if (buttonPosition.align === 'left') {
|
translateX = buttonPosition.x - chart.spacing[3];
|
} else if (buttonPosition.align === 'right') {
|
translateX = buttonPosition.x + exportingX - chart.spacing[1];
|
}
|
// align button group
|
buttonGroup.align({
|
y: buttonPosition.y,
|
width: buttonGroup.getBBox().width,
|
align: buttonPosition.align,
|
x: translateX
|
}, true, chart.spacingBox);
|
// skip animation
|
rangeSelector.group.placed = animate;
|
rangeSelector.buttonGroup.placed = animate;
|
if (inputEnabled !== false) {
|
var inputGroupX, inputGroupWidth, buttonGroupX, buttonGroupWidth;
|
// detect collision with exporting
|
if (navButtonOptions && this.titleCollision(chart) && verticalAlign === 'top' && inputPosition.align === 'right' && inputPosition.y - inputGroup.getBBox().height - 12 < (navButtonOptions.y || 0) + navButtonOptions.height + chart.spacing[0]) {
|
exportingX = -40;
|
} else {
|
exportingX = 0;
|
}
|
if (inputPosition.align === 'left') {
|
translateX = plotLeft;
|
} else if (inputPosition.align === 'right') {
|
translateX = -Math.max(chart.axisOffset[1], -exportingX);
|
}
|
// Update the alignment to the updated spacing box
|
inputGroup.align({
|
y: inputPosition.y,
|
width: inputGroup.getBBox().width,
|
align: inputPosition.align,
|
// fix wrong getBBox() value on right align
|
x: inputPosition.x + translateX - 2
|
}, true, chart.spacingBox);
|
// detect collision
|
inputGroupX = inputGroup.alignAttr.translateX + inputGroup.alignOptions.x - exportingX +
|
// getBBox for detecing left margin
|
inputGroup.getBBox().x +
|
// 2px padding to not overlap input and label
|
2;
|
inputGroupWidth = inputGroup.alignOptions.width;
|
buttonGroupX = buttonGroup.alignAttr.translateX + buttonGroup.getBBox().x;
|
// 20 is minimal spacing between elements
|
buttonGroupWidth = buttonGroup.getBBox().width + 20;
|
if (inputPosition.align === buttonPosition.align || buttonGroupX + buttonGroupWidth > inputGroupX && inputGroupX + inputGroupWidth > buttonGroupX && buttonPositionY < inputPositionY + inputGroup.getBBox().height) {
|
inputGroup.attr({
|
translateX: inputGroup.alignAttr.translateX + (chart.axisOffset[1] >= -exportingX ? 0 : -exportingX),
|
translateY: inputGroup.alignAttr.translateY + buttonGroup.getBBox().height + 10
|
});
|
}
|
// Set or reset the input values
|
rangeSelector.setInputValue('min', min);
|
rangeSelector.setInputValue('max', max);
|
// skip animation
|
rangeSelector.inputGroup.placed = animate;
|
}
|
// vertical align
|
rangeSelector.group.align({
|
verticalAlign: verticalAlign
|
}, true, chart.spacingBox);
|
// set position
|
groupHeight = rangeSelector.group.getBBox().height + 20; // # 20 padding
|
alignTranslateY = rangeSelector.group.alignAttr.translateY;
|
// calculate bottom position
|
if (verticalAlign === 'bottom') {
|
legendHeight = legendOptions && legendOptions.verticalAlign === 'bottom' && legendOptions.enabled && !legendOptions.floating ? legend.legendHeight + pick(legendOptions.margin, 10) : 0;
|
groupHeight = groupHeight + legendHeight - 20;
|
translateY = alignTranslateY - groupHeight - (floating ? 0 : options.y) - 10 // 10 spacing
|
;
|
}
|
if (verticalAlign === 'top') {
|
if (floating) {
|
translateY = 0;
|
}
|
if (chart.titleOffset && chart.titleOffset[0]) {
|
translateY = chart.titleOffset[0] + chart.options.title.margin;
|
}
|
translateY += chart.margin[0] - chart.spacing[0] || 0;
|
} else if (verticalAlign === 'middle') {
|
if (inputPositionY === buttonPositionY) {
|
if (inputPositionY < 0) {
|
translateY = alignTranslateY + minPosition;
|
} else {
|
translateY = alignTranslateY;
|
}
|
} else if (inputPositionY || buttonPositionY) {
|
if (inputPositionY < 0 || buttonPositionY < 0) {
|
translateY -= Math.min(inputPositionY, buttonPositionY);
|
} else {
|
translateY = alignTranslateY - groupHeight + minPosition;
|
}
|
}
|
}
|
rangeSelector.group.translate(options.x, options.y + Math.floor(translateY));
|
// translate HTML inputs
|
if (inputEnabled !== false) {
|
rangeSelector.minInput.style.marginTop = rangeSelector.group.translateY + 'px';
|
rangeSelector.maxInput.style.marginTop = rangeSelector.group.translateY + 'px';
|
}
|
rangeSelector.rendered = true;
|
},
|
/**
|
* Extracts height of range selector
|
*
|
* @private
|
* @function Highcharts.RangeSelector#getHeight
|
* @return {number}
|
* Returns rangeSelector height
|
*/
|
getHeight: function getHeight() {
|
var 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;
|
}
|
rangeSelectorHeight = rangeSelectorGroup ?
|
// 13px to keep back compatibility
|
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;
|
},
|
/**
|
* Detect collision with title or subtitle
|
*
|
* @private
|
* @function Highcharts.RangeSelector#titleCollision
|
*
|
* @param {Highcharts.Chart} chart
|
*
|
* @return {boolean}
|
* Returns collision status
|
*/
|
titleCollision: function titleCollision(chart) {
|
return !(chart.options.title.text || chart.options.subtitle.text);
|
},
|
/**
|
* Update the range selector with new options
|
*
|
* @private
|
* @function Highcharts.RangeSelector#update
|
* @param {Highcharts.RangeSelectorOptions} options
|
* @return {void}
|
*/
|
update: function update(options) {
|
var chart = this.chart;
|
merge(true, chart.options.rangeSelector, options);
|
this.destroy();
|
this.init(chart);
|
chart.rangeSelector.render();
|
},
|
/**
|
* Destroys allocated elements.
|
*
|
* @private
|
* @function Highcharts.RangeSelector#destroy
|
*/
|
destroy: function destroy() {
|
var rSelector = this,
|
minInput = rSelector.minInput,
|
maxInput = rSelector.maxInput;
|
rSelector.unMouseDown();
|
rSelector.unResize();
|
// Destroy elements in collections
|
destroyObjectProperties(rSelector.buttons);
|
// Clear input element events
|
if (minInput) {
|
minInput.onfocus = minInput.onblur = minInput.onchange = null;
|
}
|
if (maxInput) {
|
maxInput.onfocus = maxInput.onblur = maxInput.onchange = null;
|
}
|
// Destroy HTML and SVG elements
|
objectEach(rSelector, function (val, key) {
|
if (val && key !== 'chart') {
|
if (val.destroy) {
|
// SVGElement
|
val.destroy();
|
} else if (val.nodeType) {
|
// HTML element
|
discardElement(this[key]);
|
}
|
}
|
if (val !== RangeSelector.prototype[key]) {
|
rSelector[key] = null;
|
}
|
}, this);
|
}
|
};
|
/**
|
* 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.
|
*/
|
Axis.prototype.minFromRange = function () {
|
var rangeOptions = this.range,
|
type = rangeOptions.type,
|
timeName = {
|
month: 'Month',
|
year: 'FullYear'
|
}[type],
|
min,
|
max = this.max,
|
dataMin,
|
range,
|
time = this.chart.time,
|
|
// Get the true range from a start date
|
getTrueRange = function getTrueRange(base, count) {
|
var date = new time.Date(base),
|
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;
|
};
|
if (isNumber(rangeOptions)) {
|
min = max - rangeOptions;
|
range = rangeOptions;
|
} else {
|
min = max + getTrueRange(max, -rangeOptions.count);
|
// Let the fixedRange reflect initial settings (#5930)
|
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 (range === undefined) {
|
// #4501
|
range = getTrueRange(min, rangeOptions.count);
|
}
|
this.newMax = Math.min(min + range, this.dataMax);
|
}
|
if (!isNumber(max)) {
|
min = undefined;
|
}
|
return min;
|
};
|
if (!_Globals2.default.RangeSelector) {
|
// Initialize rangeselector for stock charts
|
addEvent(Chart, 'afterGetContainer', function () {
|
if (this.options.rangeSelector.enabled) {
|
this.rangeSelector = new RangeSelector(this);
|
}
|
});
|
addEvent(Chart, 'beforeRender', function () {
|
var 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) {
|
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) {
|
var 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.enabled = true;
|
this.rangeSelector = new RangeSelector(this);
|
}
|
this.extraBottomMargin = false;
|
this.extraTopMargin = false;
|
if (rangeSelector) {
|
rangeSelector.render();
|
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 () {
|
var 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 () {
|
var rangeSelector = this.rangeSelector,
|
rangeSelectorHeight;
|
if (rangeSelector) {
|
rangeSelectorHeight = rangeSelector.getHeight();
|
if (this.extraTopMargin) {
|
this.plotTop += rangeSelectorHeight;
|
}
|
if (this.extraBottomMargin) {
|
this.marginBottom += rangeSelectorHeight;
|
}
|
}
|
});
|
Chart.prototype.callbacks.push(function (chart) {
|
var extremes,
|
rangeSelector = chart.rangeSelector,
|
unbindRender,
|
unbindSetExtremes;
|
/**
|
* @private
|
*/
|
function renderRangeSelector() {
|
extremes = chart.xAxis[0].getExtremes();
|
if (isNumber(extremes.min)) {
|
rangeSelector.render(extremes.min, extremes.max);
|
}
|
}
|
if (rangeSelector) {
|
// redraw the scroller on setExtremes
|
unbindSetExtremes = addEvent(chart.xAxis[0], 'afterSetExtremes', function (e) {
|
rangeSelector.render(e.min, e.max);
|
});
|
// redraw the scroller chart resize
|
unbindRender = addEvent(chart, 'redraw', renderRangeSelector);
|
// do it now
|
renderRangeSelector();
|
}
|
// Remove resize/afterSetExtremes at chart destroy
|
addEvent(chart, 'destroy', function destroyEvents() {
|
if (rangeSelector) {
|
unbindRender();
|
unbindSetExtremes();
|
}
|
});
|
});
|
_Globals2.default.RangeSelector = RangeSelector;
|
}
|