123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058 |
- /**
- * (c) 2010-2019 Torstein Honsi
- *
- * License: www.highcharts.com/license
- */
- /**
- * 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.
- */
- 'use strict';
- import H from './Globals.js';
- import './Axis.js';
- import './Chart.js';
- var addEvent = H.addEvent,
- Axis = H.Axis,
- Chart = H.Chart,
- css = H.css,
- createElement = H.createElement,
- defaultOptions = H.defaultOptions,
- defined = H.defined,
- destroyObjectProperties = H.destroyObjectProperties,
- discardElement = H.discardElement,
- extend = H.extend,
- fireEvent = H.fireEvent,
- isNumber = H.isNumber,
- merge = H.merge,
- pick = H.pick,
- pInt = H.pInt,
- splat = H.splat;
- /* ****************************************************************************
- * 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
- * @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`, `ytd`, `all`.
- *
- * @type {string}
- * @validvalue ["millisecond", "second", "minute", "day", "week", "month", "ytd", "all"]
- * @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.VerticalAlignType}
- * @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.CSSObject}
- */
- 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, // reserved space for buttons and input
- /**
- * 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.AlignType}
- * @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}
- * @product highstock
- * @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
- *
- * @since 6.0.0
- * @validvalue ["left", "center", "right"]
- */
- 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 initiation. 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
- */
- rangeSelectorZoom: 'Zoom',
- /**
- * The text for the label for the "from" input box in the range
- * selector.
- *
- * @product highstock
- */
- rangeSelectorFrom: 'From',
- /**
- * The text for the label for the "to" input box in the range selector.
- *
- * @product highstock
- */
- rangeSelectorTo: 'To'
- }
- );
- /**
- * 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
- */
- clickButton: function (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 {boolean} selected
- */
- setSelected: function (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
- */
- init: function (chart) {
- var rangeSelector = this,
- options = chart.options.rangeSelector,
- buttonOptions = options.buttons ||
- [].concat(rangeSelector.defaultButtons),
- selectedOption = options.selected,
- blurInputs = function () {
- 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 = [];
- chart.extraTopMargin = options.height;
- 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
- */
- updateButtonStates: function () {
- 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);
- }
- });
- },
- /**
- * Compute and cache the range for an individual button
- *
- * @private
- * @function Highcharts.RangeSelector#computeButtonRange
- *
- * @param {Highcharts.RangeSelectorOptions} rangeOptions
- */
- computeButtonRange: function (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
- */
- setInputValue: function (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
- */
- showInput: function (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
- */
- hideInput: function (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
- */
- drawInput: function (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;
- 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) {
- 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', // Chrome needs a pixel to see it
- 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 () {
- if (input === H.doc.activeElement) { // Only when focused
- // Update also when no `change` event is triggered, like when
- // clicking inside the SVG (#4710)
- updateExtremes();
- rangeSelector.hideInput(name);
- }
- };
- // 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 () {
- 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 (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
- */
- render: function (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 () {
- // extract events from button object and call
- var buttonEvents = (
- rangeOptions.events &&
- rangeOptions.events.click
- ),
- callDefaultEvent;
- if (buttonEvents) {
- callDefaultEvent =
- buttonEvents.call(rangeOptions);
- }
- 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) {
- translateY = chart.titleOffset + 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 () {
- 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;
- 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 (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
- */
- update: function (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 () {
- 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
- H.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);
- }
- };
- /**
- * Add logic to normalize the zoomed range in order to preserve the pressed
- * state of range selector buttons
- *
- * @private
- * @function Highcharts.Axis#toFixedRange
- *
- * @param {number} pxMin
- *
- * @param {number} pxMax
- *
- * @param {number} fixedMin
- *
- * @param {number} fixedMax
- *
- * @return {*}
- */
- Axis.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) {
- var fixedRange = this.chart && this.chart.fixedRange,
- newMin = pick(fixedMin, this.translate(pxMin, true, !this.horiz)),
- newMax = pick(fixedMax, this.translate(pxMax, true, !this.horiz)),
- changeRatio = fixedRange && (newMax - newMin) / fixedRange;
- // If the difference between the fixed range and the actual requested range
- // is too great, the user is dragging across an ordinal gap, and we need to
- // release the range selector button.
- if (changeRatio > 0.7 && changeRatio < 1.3) {
- if (fixedMax) {
- newMin = newMax - fixedRange;
- } else {
- newMax = newMin + fixedRange;
- }
- }
- if (!isNumber(newMin) || !isNumber(newMax)) { // #1195, #7411
- newMin = newMax = undefined;
- }
- return {
- min: newMin,
- max: newMax
- };
- };
- /**
- * 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}
- * 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,
- // Get the true range from a start date
- getTrueRange = function (base, count) {
- var date = new Date(base),
- basePeriod = date['get' + timeName]();
- date['set' + timeName](basePeriod + count);
- if (basePeriod === date['get' + timeName]()) {
- date.setDate(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;
- };
- // 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;
- 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();
- }
- });
- });
- H.RangeSelector = RangeSelector;
- /* ****************************************************************************
- * End Range Selector code *
- *****************************************************************************/
|