1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482 |
- /**
- * (c) 2010-2019 Torstein Honsi
- *
- * License: www.highcharts.com/license
- */
- 'use strict';
- import H from './Globals.js';
- import './Utilities.js';
- import './Color.js';
- import './Axis.js';
- import './Chart.js';
- import './Series.js';
- import './Options.js';
- import './Scrollbar.js';
- var addEvent = H.addEvent,
- Axis = H.Axis,
- Chart = H.Chart,
- color = H.color,
- defaultDataGroupingUnits = H.defaultDataGroupingUnits,
- defaultOptions = H.defaultOptions,
- defined = H.defined,
- destroyObjectProperties = H.destroyObjectProperties,
- erase = H.erase,
- extend = H.extend,
- hasTouch = H.hasTouch,
- isArray = H.isArray,
- isNumber = H.isNumber,
- isTouchDevice = H.isTouchDevice,
- merge = H.merge,
- pick = H.pick,
- removeEvent = H.removeEvent,
- Scrollbar = H.Scrollbar,
- Series = H.Series,
- seriesTypes = H.seriesTypes,
- units = [].concat(defaultDataGroupingUnits), // copy
- defaultSeriesType,
- // Finding the min or max of a set of variables where we don't know if they
- // are defined, is a pattern that is repeated several places in Highcharts.
- // Consider making this a global utility method.
- numExt = function (extreme) {
- var numbers = [].filter.call(arguments, isNumber);
- if (numbers.length) {
- return Math[extreme].apply(0, numbers);
- }
- };
- // add more resolution to units
- units[4] = ['day', [1, 2, 3, 4]]; // allow more days
- units[5] = ['week', [1, 2, 3]]; // allow more weeks
- defaultSeriesType = seriesTypes.areaspline === undefined ?
- 'line' :
- 'areaspline';
- extend(defaultOptions, {
- /**
- * Maximum range which can be set using the navigator's handles.
- * Opposite of [xAxis.minRange](#xAxis.minRange).
- *
- * @sample {highstock} stock/navigator/maxrange/
- * Defined max and min range
- *
- * @type {number}
- * @since 6.0.0
- * @product highstock
- * @apioption xAxis.maxRange
- */
- /**
- * The navigator is a small series below the main series, displaying
- * a view of the entire data set. It provides tools to zoom in and
- * out on parts of the data as well as panning across the dataset.
- *
- * @product highstock
- * @optionparent navigator
- */
- navigator: {
- /**
- * Whether the navigator and scrollbar should adapt to updated data
- * in the base X axis. When loading data async, as in the demo below,
- * this should be `false`. Otherwise new data will trigger navigator
- * redraw, which will cause unwanted looping. In the demo below, the
- * data in the navigator is set only once. On navigating, only the main
- * chart content is updated.
- *
- * @sample {highstock} stock/demo/lazy-loading/
- * Set to false with async data loading
- *
- * @type {boolean}
- * @default true
- * @product highstock
- * @apioption navigator.adaptToUpdatedData
- */
- /**
- * An integer identifying the index to use for the base series, or a
- * string representing the id of the series.
- *
- * **Note**: As of Highcharts 5.0, this is now a deprecated option.
- * Prefer [series.showInNavigator](#plotOptions.series.showInNavigator).
- *
- * @see [series.showInNavigator](#plotOptions.series.showInNavigator)
- *
- * @deprecated
- * @type {*}
- * @default 0
- * @product highstock
- * @apioption navigator.baseSeries
- */
- /**
- * Enable or disable the navigator.
- *
- * @sample {highstock} stock/navigator/enabled/
- * Disable the navigator
- *
- * @type {boolean}
- * @default true
- * @product highstock
- * @apioption navigator.enabled
- */
- /**
- * When the chart is inverted, whether to draw the navigator on the
- * opposite side.
- *
- * @type {boolean}
- * @default false
- * @since 5.0.8
- * @product highstock
- * @apioption navigator.opposite
- */
- /**
- * The height of the navigator.
- *
- * @sample {highstock} stock/navigator/height/
- * A higher navigator
- *
- * @product highstock
- */
- height: 40,
- /**
- * The distance from the nearest element, the X axis or X axis labels.
- *
- * @sample {highstock} stock/navigator/margin/
- * A margin of 2 draws the navigator closer to the X axis labels
- *
- * @product highstock
- */
- margin: 25,
- /**
- * Whether the mask should be inside the range marking the zoomed
- * range, or outside. In Highstock 1.x it was always `false`.
- *
- * @sample {highstock} stock/navigator/maskinside-false/
- * False, mask outside
- *
- * @since 2.0
- * @product highstock
- */
- maskInside: true,
- /**
- * Options for the handles for dragging the zoomed area.
- *
- * @sample {highstock} stock/navigator/handles/
- * Colored handles
- *
- * @product highstock
- */
- handles: {
- /**
- * Width for handles.
- *
- * @sample {highstock} stock/navigator/styled-handles/
- * Styled handles
- *
- * @since 6.0.0
- * @product highstock
- */
- width: 7,
- /**
- * Height for handles.
- *
- * @sample {highstock} stock/navigator/styled-handles/
- * Styled handles
- *
- * @since 6.0.0
- * @product highstock
- */
- height: 15,
- /**
- * Array to define shapes of handles. 0-index for left, 1-index for
- * right.
- *
- * Additionally, the URL to a graphic can be given on this form:
- * `url(graphic.png)`. Note that for the image to be applied to
- * exported charts, its URL needs to be accessible by the export
- * server.
- *
- * Custom callbacks for symbol path generation can also be added to
- * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
- * used by its method name, as shown in the demo.
- *
- * @sample {highstock} stock/navigator/styled-handles/
- * Styled handles
- *
- * @type {Array<string>}
- * @default ["navigator-handle", "navigator-handle"]
- * @since 6.0.0
- * @product highstock
- */
- symbols: ['navigator-handle', 'navigator-handle'],
- /**
- * Allows to enable/disable handles.
- *
- * @since 6.0.0
- * @product highstock
- */
- enabled: true,
- /**
- * The width for the handle border and the stripes inside.
- *
- * @sample {highstock} stock/navigator/styled-handles/
- * Styled handles
- *
- * @since 6.0.0
- * @product highstock
- * @apioption navigator.handles.lineWidth
- */
- lineWidth: 1,
- /**
- * The fill for the handle.
- *
- * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
- * @product highstock
- */
- backgroundColor: '#f2f2f2',
- /**
- * The stroke for the handle border and the stripes inside.
- *
- * @type {Highcharts.ColorString}
- * @product highstock
- */
- borderColor: '#999999'
- },
- /**
- * The color of the mask covering the areas of the navigator series
- * that are currently not visible in the main series. The default
- * color is bluish with an opacity of 0.3 to see the series below.
- *
- * @see In styled mode, the mask is styled with the
- * `.highcharts-navigator-mask` and
- * `.highcharts-navigator-mask-inside` classes.
- *
- * @sample {highstock} stock/navigator/maskfill/
- * Blue, semi transparent mask
- *
- * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
- * @default rgba(102,133,194,0.3)
- * @product highstock
- */
- maskFill: color('#6685c2').setOpacity(0.3).get(),
- /**
- * The color of the line marking the currently zoomed area in the
- * navigator.
- *
- * @sample {highstock} stock/navigator/outline/
- * 2px blue outline
- *
- * @type {Highcharts.ColorString}
- * @default #cccccc
- * @product highstock
- */
- outlineColor: '#cccccc',
- /**
- * The width of the line marking the currently zoomed area in the
- * navigator.
- *
- * @see In styled mode, the outline stroke width is set with the
- * `.highcharts-navigator-outline` class.
- *
- * @sample {highstock} stock/navigator/outline/
- * 2px blue outline
- *
- * @type {number}
- * @product highstock
- */
- outlineWidth: 1,
- /**
- * Options for the navigator series. Available options are the same
- * as any series, documented at [plotOptions](#plotOptions.series)
- * and [series](#series).
- *
- * Unless data is explicitly defined on navigator.series, the data
- * is borrowed from the first series in the chart.
- *
- * Default series options for the navigator series are:
- *
- * <pre>series: {
- * type: 'areaspline',
- * fillOpacity: 0.05,
- * dataGrouping: {
- * smoothed: true
- * },
- * lineWidth: 1,
- * marker: {
- * enabled: false
- * }
- * }</pre>
- *
- * @see In styled mode, the navigator series is styled with the
- * `.highcharts-navigator-series` class.
- *
- * @sample {highstock} stock/navigator/series-data/
- * Using a separate data set for the navigator
- * @sample {highstock} stock/navigator/series/
- * A green navigator series
- *
- * @product highstock
- */
- series: {
- /**
- * The type of the navigator series. Defaults to `areaspline` if
- * defined, otherwise `line`.
- *
- * @type {string}
- * @default areaspline
- */
- type: defaultSeriesType,
- /**
- * The fill opacity of the navigator series.
- */
- fillOpacity: 0.05,
- /**
- * The pixel line width of the navigator series.
- */
- lineWidth: 1,
- /**
- * @ignore-option
- */
- compare: null,
- /**
- * Data grouping options for the navigator series.
- *
- * @extends plotOptions.series.dataGrouping
- */
- dataGrouping: {
- approximation: 'average',
- enabled: true,
- groupPixelWidth: 2,
- smoothed: true,
- units: units
- },
- /**
- * Data label options for the navigator series. Data labels are
- * disabled by default on the navigator series.
- *
- * @extends plotOptions.series.dataLabels
- */
- dataLabels: {
- enabled: false,
- zIndex: 2 // #1839
- },
- id: 'highcharts-navigator-series',
- className: 'highcharts-navigator-series',
- /**
- * Line color for the navigator series. Allows setting the color
- * while disallowing the default candlestick setting.
- *
- * @type {Highcharts.ColorString|null}
- */
- lineColor: null, // #4602
- marker: {
- enabled: false
- },
- pointRange: 0,
- /**
- * The threshold option. Setting it to 0 will make the default
- * navigator area series draw its area from the 0 value and up.
- *
- * @type {number|null}
- */
- threshold: null
- },
- /**
- * Options for the navigator X axis. Default series options
- * for the navigator xAxis are:
- *
- * <pre>xAxis: {
- * tickWidth: 0,
- * lineWidth: 0,
- * gridLineWidth: 1,
- * tickPixelInterval: 200,
- * labels: {
- * align: 'left',
- * style: {
- * color: '#888'
- * },
- * x: 3,
- * y: -4
- * }
- * }</pre>
- *
- * @extends xAxis
- * @excluding linkedTo, maxZoom, minRange, opposite, range, scrollbar,
- * showEmpty, maxRange
- * @product highstock
- */
- xAxis: {
- /**
- * Additional range on the right side of the xAxis. Works similar to
- * xAxis.maxPadding, but value is set in milliseconds.
- * Can be set for both, main xAxis and navigator's xAxis.
- *
- * @since 6.0.0
- * @product highstock
- */
- overscroll: 0,
- className: 'highcharts-navigator-xaxis',
- tickLength: 0,
- lineWidth: 0,
- gridLineColor: '#e6e6e6',
- gridLineWidth: 1,
- tickPixelInterval: 200,
- labels: {
- align: 'left',
- /**
- * @type {Highcharts.CSSObject}
- */
- style: {
- /** @ignore */
- color: '#999999'
- },
- x: 3,
- y: -4
- },
- crosshair: false
- },
- /**
- * Options for the navigator Y axis. Default series options
- * for the navigator yAxis are:
- *
- * <pre>yAxis: {
- * gridLineWidth: 0,
- * startOnTick: false,
- * endOnTick: false,
- * minPadding: 0.1,
- * maxPadding: 0.1,
- * labels: {
- * enabled: false
- * },
- * title: {
- * text: null
- * },
- * tickWidth: 0
- * }</pre>
- *
- * @extends yAxis
- * @excluding height, linkedTo, maxZoom, minRange, ordinal, range,
- * showEmpty, scrollbar, top, units, maxRange, minLength,
- * maxLength, resize
- * @product highstock
- */
- yAxis: {
- className: 'highcharts-navigator-yaxis',
- gridLineWidth: 0,
- startOnTick: false,
- endOnTick: false,
- minPadding: 0.1,
- maxPadding: 0.1,
- labels: {
- enabled: false
- },
- crosshair: false,
- title: {
- text: null
- },
- tickLength: 0,
- tickWidth: 0
- }
- }
- });
- /**
- * Draw one of the handles on the side of the zoomed range in the navigator
- *
- * @function Highcharts.Renderer#symbols.navigator-handle
- *
- * @param {boolean} inverted
- * flag for chart.inverted
- *
- * @return {Highcharts.SVGPathArray}
- * Path to be used in a handle
- */
- H.Renderer.prototype.symbols['navigator-handle'] = function (
- x,
- y,
- w,
- h,
- options
- ) {
- var halfWidth = options.width / 2,
- markerPosition = Math.round(halfWidth / 3) + 0.5,
- height = options.height;
- return [
- 'M',
- -halfWidth - 1, 0.5,
- 'L',
- halfWidth, 0.5,
- 'L',
- halfWidth, height + 0.5,
- 'L',
- -halfWidth - 1, height + 0.5,
- 'L',
- -halfWidth - 1, 0.5,
- 'M',
- -markerPosition, 4,
- 'L',
- -markerPosition, height - 3,
- 'M',
- markerPosition - 1, 4,
- 'L',
- markerPosition - 1, height - 3
- ];
- };
- /**
- * The Navigator class
- *
- * @private
- * @class
- * @name Highcharts.Navigator
- *
- * @param {Highcharts.Chart} chart
- * Chart object
- */
- function Navigator(chart) {
- this.init(chart);
- }
- Navigator.prototype = {
- /**
- * Draw one of the handles on the side of the zoomed range in the navigator
- *
- * @private
- * @function Highcharts.Navigator#drawHandle
- *
- * @param {number} x
- * The x center for the handle
- *
- * @param {number} index
- * 0 for left and 1 for right
- *
- * @param {boolean} inverted
- * flag for chart.inverted
- *
- * @param {string} verb
- * use 'animate' or 'attr'
- */
- drawHandle: function (x, index, inverted, verb) {
- var navigator = this,
- height = navigator.navigatorOptions.handles.height;
- // Place it
- navigator.handles[index][verb](inverted ? {
- translateX: Math.round(navigator.left + navigator.height / 2),
- translateY: Math.round(
- navigator.top + parseInt(x, 10) + 0.5 - height
- )
- } : {
- translateX: Math.round(navigator.left + parseInt(x, 10)),
- translateY: Math.round(
- navigator.top + navigator.height / 2 - height / 2 - 1
- )
- });
- },
- /**
- * Render outline around the zoomed range
- *
- * @private
- * @function Highcharts.Navigator#drawOutline
- *
- * @param {number} zoomedMin
- * in pixels position where zoomed range starts
- *
- * @param {number} zoomedMax
- * in pixels position where zoomed range ends
- *
- * @param {boolean} inverted
- * flag if chart is inverted
- *
- * @param {string} verb
- * use 'animate' or 'attr'
- */
- drawOutline: function (zoomedMin, zoomedMax, inverted, verb) {
- var navigator = this,
- maskInside = navigator.navigatorOptions.maskInside,
- outlineWidth = navigator.outline.strokeWidth(),
- halfOutline = outlineWidth / 2,
- outlineCorrection = (outlineWidth % 2) / 2, // #5800
- outlineHeight = navigator.outlineHeight,
- scrollbarHeight = navigator.scrollbarHeight,
- navigatorSize = navigator.size,
- left = navigator.left - scrollbarHeight,
- navigatorTop = navigator.top,
- verticalMin,
- path;
- if (inverted) {
- left -= halfOutline;
- verticalMin = navigatorTop + zoomedMax + outlineCorrection;
- zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
- path = [
- 'M',
- left + outlineHeight,
- navigatorTop - scrollbarHeight - outlineCorrection, // top edge
- 'L',
- left + outlineHeight,
- verticalMin, // top right of zoomed range
- 'L',
- left,
- verticalMin, // top left of z.r.
- 'L',
- left,
- zoomedMax, // bottom left of z.r.
- 'L',
- left + outlineHeight,
- zoomedMax, // bottom right of z.r.
- 'L',
- left + outlineHeight,
- navigatorTop + navigatorSize + scrollbarHeight // bottom edge
- ].concat(maskInside ? [
- 'M',
- left + outlineHeight,
- verticalMin - halfOutline, // upper left of zoomed range
- 'L',
- left + outlineHeight,
- zoomedMax + halfOutline // upper right of z.r.
- ] : []);
- } else {
- zoomedMin += left + scrollbarHeight - outlineCorrection;
- zoomedMax += left + scrollbarHeight - outlineCorrection;
- navigatorTop += halfOutline;
- path = [
- 'M',
- left,
- navigatorTop, // left
- 'L',
- zoomedMin,
- navigatorTop, // upper left of zoomed range
- 'L',
- zoomedMin,
- navigatorTop + outlineHeight, // lower left of z.r.
- 'L',
- zoomedMax,
- navigatorTop + outlineHeight, // lower right of z.r.
- 'L',
- zoomedMax,
- navigatorTop, // upper right of z.r.
- 'L',
- left + navigatorSize + scrollbarHeight * 2,
- navigatorTop // right
- ].concat(maskInside ? [
- 'M',
- zoomedMin - halfOutline,
- navigatorTop, // upper left of zoomed range
- 'L',
- zoomedMax + halfOutline,
- navigatorTop // upper right of z.r.
- ] : []);
- }
- navigator.outline[verb]({
- d: path
- });
- },
- /**
- * Render outline around the zoomed range
- *
- * @private
- * @function Highcharts.Navigator#drawMasks
- *
- * @param {number} zoomedMin
- * in pixels position where zoomed range starts
- *
- * @param {number} zoomedMax
- * in pixels position where zoomed range ends
- *
- * @param {boolean} inverted
- * flag if chart is inverted
- *
- * @param {string} verb
- * use 'animate' or 'attr'
- */
- drawMasks: function (zoomedMin, zoomedMax, inverted, verb) {
- var navigator = this,
- left = navigator.left,
- top = navigator.top,
- navigatorHeight = navigator.height,
- height,
- width,
- x,
- y;
- // Determine rectangle position & size
- // According to (non)inverted position:
- if (inverted) {
- x = [left, left, left];
- y = [top, top + zoomedMin, top + zoomedMax];
- width = [navigatorHeight, navigatorHeight, navigatorHeight];
- height = [
- zoomedMin,
- zoomedMax - zoomedMin,
- navigator.size - zoomedMax
- ];
- } else {
- x = [left, left + zoomedMin, left + zoomedMax];
- y = [top, top, top];
- width = [
- zoomedMin,
- zoomedMax - zoomedMin,
- navigator.size - zoomedMax
- ];
- height = [navigatorHeight, navigatorHeight, navigatorHeight];
- }
- navigator.shades.forEach(function (shade, i) {
- shade[verb]({
- x: x[i],
- y: y[i],
- width: width[i],
- height: height[i]
- });
- });
- },
- /**
- * Generate DOM elements for a navigator:
- *
- * - main navigator group
- *
- * - all shades
- *
- * - outline
- *
- * - handles
- *
- * @private
- * @function Highcharts.Navigator#renderElements
- */
- renderElements: function () {
- var navigator = this,
- navigatorOptions = navigator.navigatorOptions,
- maskInside = navigatorOptions.maskInside,
- chart = navigator.chart,
- inverted = chart.inverted,
- renderer = chart.renderer,
- navigatorGroup,
- mouseCursor = {
- cursor: inverted ? 'ns-resize' : 'ew-resize'
- };
- // Create the main navigator group
- navigator.navigatorGroup = navigatorGroup = renderer.g('navigator')
- .attr({
- zIndex: 8,
- visibility: 'hidden'
- })
- .add();
- // Create masks, each mask will get events and fill:
- [
- !maskInside,
- maskInside,
- !maskInside
- ].forEach(function (hasMask, index) {
- navigator.shades[index] = renderer.rect()
- .addClass('highcharts-navigator-mask' +
- (index === 1 ? '-inside' : '-outside'))
- .add(navigatorGroup);
- if (!chart.styledMode) {
- navigator.shades[index]
- .attr({
- fill: hasMask ?
- navigatorOptions.maskFill :
- 'rgba(0,0,0,0)'
- })
- .css(index === 1 && mouseCursor);
- }
- });
- // Create the outline:
- navigator.outline = renderer.path()
- .addClass('highcharts-navigator-outline')
- .add(navigatorGroup);
- if (!chart.styledMode) {
- navigator.outline.attr({
- 'stroke-width': navigatorOptions.outlineWidth,
- stroke: navigatorOptions.outlineColor
- });
- }
- // Create the handlers:
- if (navigatorOptions.handles.enabled) {
- [0, 1].forEach(function (index) {
- navigatorOptions.handles.inverted = chart.inverted;
- navigator.handles[index] = renderer.symbol(
- navigatorOptions.handles.symbols[index],
- -navigatorOptions.handles.width / 2 - 1,
- 0,
- navigatorOptions.handles.width,
- navigatorOptions.handles.height,
- navigatorOptions.handles
- );
- // zIndex = 6 for right handle, 7 for left.
- // Can't be 10, because of the tooltip in inverted chart #2908
- navigator.handles[index].attr({ zIndex: 7 - index })
- .addClass(
- 'highcharts-navigator-handle ' +
- 'highcharts-navigator-handle-' +
- ['left', 'right'][index]
- ).add(navigatorGroup);
- if (!chart.styledMode) {
- var handlesOptions = navigatorOptions.handles;
- navigator.handles[index]
- .attr({
- fill: handlesOptions.backgroundColor,
- stroke: handlesOptions.borderColor,
- 'stroke-width': handlesOptions.lineWidth
- })
- .css(mouseCursor);
- }
- });
- }
- },
- /**
- * Update navigator
- *
- * @private
- * @function Highcharts.Navigator#update
- *
- * @param {Highcharts.NavigatorOptions} options
- * Options to merge in when updating navigator
- */
- update: function (options) {
- // Remove references to old navigator series in base series
- (this.series || []).forEach(function (series) {
- if (series.baseSeries) {
- delete series.baseSeries.navigatorSeries;
- }
- });
- // Destroy and rebuild navigator
- this.destroy();
- var chartOptions = this.chart.options;
- merge(true, chartOptions.navigator, this.options, options);
- this.init(this.chart);
- },
- /**
- * Render the navigator
- *
- * @private
- * @function Highcharts.Navigator#render
- *
- * @param {number} min
- * X axis value minimum
- *
- * @param {number} max
- * X axis value maximum
- *
- * @param {number} pxMin
- * Pixel value minimum
- *
- * @param {number} pxMax
- * Pixel value maximum
- */
- render: function (min, max, pxMin, pxMax) {
- var navigator = this,
- chart = navigator.chart,
- navigatorWidth,
- scrollbarLeft,
- scrollbarTop,
- scrollbarHeight = navigator.scrollbarHeight,
- navigatorSize,
- xAxis = navigator.xAxis,
- scrollbarXAxis = xAxis.fake ? chart.xAxis[0] : xAxis,
- navigatorEnabled = navigator.navigatorEnabled,
- zoomedMin,
- zoomedMax,
- rendered = navigator.rendered,
- inverted = chart.inverted,
- verb,
- newMin,
- newMax,
- currentRange,
- minRange = chart.xAxis[0].minRange,
- maxRange = chart.xAxis[0].options.maxRange;
- // Don't redraw while moving the handles (#4703).
- if (this.hasDragged && !defined(pxMin)) {
- return;
- }
- // Don't render the navigator until we have data (#486, #4202, #5172).
- if (!isNumber(min) || !isNumber(max)) {
- // However, if navigator was already rendered, we may need to resize
- // it. For example hidden series, but visible navigator (#6022).
- if (rendered) {
- pxMin = 0;
- pxMax = pick(xAxis.width, scrollbarXAxis.width);
- } else {
- return;
- }
- }
- navigator.left = pick(
- xAxis.left,
- // in case of scrollbar only, without navigator
- chart.plotLeft + scrollbarHeight + (inverted ? chart.plotWidth : 0)
- );
- navigator.size = zoomedMax = navigatorSize = pick(
- xAxis.len,
- (inverted ? chart.plotHeight : chart.plotWidth) -
- 2 * scrollbarHeight
- );
- if (inverted) {
- navigatorWidth = scrollbarHeight;
- } else {
- navigatorWidth = navigatorSize + 2 * scrollbarHeight;
- }
- // Get the pixel position of the handles
- pxMin = pick(pxMin, xAxis.toPixels(min, true));
- pxMax = pick(pxMax, xAxis.toPixels(max, true));
- // Verify (#1851, #2238)
- if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) {
- pxMin = 0;
- pxMax = navigatorWidth;
- }
- // Are we below the minRange? (#2618, #6191)
- newMin = xAxis.toValue(pxMin, true);
- newMax = xAxis.toValue(pxMax, true);
- currentRange = Math.abs(H.correctFloat(newMax - newMin));
- if (currentRange < minRange) {
- if (this.grabbedLeft) {
- pxMin = xAxis.toPixels(newMax - minRange, true);
- } else if (this.grabbedRight) {
- pxMax = xAxis.toPixels(newMin + minRange, true);
- }
- } else if (defined(maxRange) && currentRange > maxRange) {
- if (this.grabbedLeft) {
- pxMin = xAxis.toPixels(newMax - maxRange, true);
- } else if (this.grabbedRight) {
- pxMax = xAxis.toPixels(newMin + maxRange, true);
- }
- }
- // Handles are allowed to cross, but never exceed the plot area
- navigator.zoomedMax = Math.min(Math.max(pxMin, pxMax, 0), zoomedMax);
- navigator.zoomedMin = Math.min(
- Math.max(
- navigator.fixedWidth ?
- navigator.zoomedMax - navigator.fixedWidth :
- Math.min(pxMin, pxMax),
- 0
- ),
- zoomedMax
- );
- navigator.range = navigator.zoomedMax - navigator.zoomedMin;
- zoomedMax = Math.round(navigator.zoomedMax);
- zoomedMin = Math.round(navigator.zoomedMin);
- if (navigatorEnabled) {
- navigator.navigatorGroup.attr({
- visibility: 'visible'
- });
- // Place elements
- verb = rendered && !navigator.hasDragged ? 'animate' : 'attr';
- navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
- navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
- if (navigator.navigatorOptions.handles.enabled) {
- navigator.drawHandle(zoomedMin, 0, inverted, verb);
- navigator.drawHandle(zoomedMax, 1, inverted, verb);
- }
- }
- if (navigator.scrollbar) {
- if (inverted) {
- scrollbarTop = navigator.top - scrollbarHeight;
- scrollbarLeft = navigator.left - scrollbarHeight +
- (navigatorEnabled || !scrollbarXAxis.opposite ? 0 :
- // Multiple axes has offsets:
- (scrollbarXAxis.titleOffset || 0) +
- // Self margin from the axis.title
- scrollbarXAxis.axisTitleMargin
- );
- scrollbarHeight = navigatorSize + 2 * scrollbarHeight;
- } else {
- scrollbarTop = navigator.top +
- (navigatorEnabled ? navigator.height : -scrollbarHeight);
- scrollbarLeft = navigator.left - scrollbarHeight;
- }
- // Reposition scrollbar
- navigator.scrollbar.position(
- scrollbarLeft,
- scrollbarTop,
- navigatorWidth,
- scrollbarHeight
- );
- // Keep scale 0-1
- navigator.scrollbar.setRange(
- // Use real value, not rounded because range can be very small
- // (#1716)
- navigator.zoomedMin / (navigatorSize || 1),
- navigator.zoomedMax / (navigatorSize || 1)
- );
- }
- navigator.rendered = true;
- },
- /**
- * Set up the mouse and touch events for the navigator
- *
- * @private
- * @function Highcharts.Navigator#addMouseEvents
- */
- addMouseEvents: function () {
- var navigator = this,
- chart = navigator.chart,
- container = chart.container,
- eventsToUnbind = [],
- mouseMoveHandler,
- mouseUpHandler;
- /**
- * Create mouse events' handlers.
- * Make them as separate functions to enable wrapping them:
- */
- navigator.mouseMoveHandler = mouseMoveHandler = function (e) {
- navigator.onMouseMove(e);
- };
- navigator.mouseUpHandler = mouseUpHandler = function (e) {
- navigator.onMouseUp(e);
- };
- // Add shades and handles mousedown events
- eventsToUnbind = navigator.getPartsEvents('mousedown');
- // Add mouse move and mouseup events. These are bind to doc/container,
- // because Navigator.grabbedSomething flags are stored in mousedown
- // events
- eventsToUnbind.push(
- addEvent(container, 'mousemove', mouseMoveHandler),
- addEvent(container.ownerDocument, 'mouseup', mouseUpHandler)
- );
- // Touch events
- if (hasTouch) {
- eventsToUnbind.push(
- addEvent(container, 'touchmove', mouseMoveHandler),
- addEvent(container.ownerDocument, 'touchend', mouseUpHandler)
- );
- eventsToUnbind.concat(navigator.getPartsEvents('touchstart'));
- }
- navigator.eventsToUnbind = eventsToUnbind;
- // Data events
- if (navigator.series && navigator.series[0]) {
- eventsToUnbind.push(
- addEvent(
- navigator.series[0].xAxis,
- 'foundExtremes',
- function () {
- chart.navigator.modifyNavigatorAxisExtremes();
- }
- )
- );
- }
- },
- /**
- * Generate events for handles and masks
- *
- * @private
- * @function Highcharts.Navigator#getPartsEvents
- *
- * @param {string} eventName
- * Event name handler, 'mousedown' or 'touchstart'
- *
- * @return {Array<Function>}
- * An array of functions to remove navigator functions from the
- * events again.
- */
- getPartsEvents: function (eventName) {
- var navigator = this,
- events = [];
- ['shades', 'handles'].forEach(function (name) {
- navigator[name].forEach(function (navigatorItem, index) {
- events.push(
- addEvent(
- navigatorItem.element,
- eventName,
- function (e) {
- navigator[name + 'Mousedown'](e, index);
- }
- )
- );
- });
- });
- return events;
- },
- /**
- * Mousedown on a shaded mask, either:
- *
- * - will be stored for future drag&drop
- *
- * - will directly shift to a new range
- *
- * @private
- * @function Highcharts.Navigator#shadesMousedown
- *
- * @param {global.PointerEventObject} e
- * Mouse event
- *
- * @param {number} index
- * Index of a mask in Navigator.shades array
- */
- shadesMousedown: function (e, index) {
- e = this.chart.pointer.normalize(e);
- var navigator = this,
- chart = navigator.chart,
- xAxis = navigator.xAxis,
- zoomedMin = navigator.zoomedMin,
- navigatorPosition = navigator.left,
- navigatorSize = navigator.size,
- range = navigator.range,
- chartX = e.chartX,
- fixedMax,
- fixedMin,
- ext,
- left;
- // For inverted chart, swap some options:
- if (chart.inverted) {
- chartX = e.chartY;
- navigatorPosition = navigator.top;
- }
- if (index === 1) {
- // Store information for drag&drop
- navigator.grabbedCenter = chartX;
- navigator.fixedWidth = range;
- navigator.dragOffset = chartX - zoomedMin;
- } else {
- // Shift the range by clicking on shaded areas
- left = chartX - navigatorPosition - range / 2;
- if (index === 0) {
- left = Math.max(0, left);
- } else if (index === 2 && left + range >= navigatorSize) {
- left = navigatorSize - range;
- if (navigator.reversedExtremes) {
- // #7713
- left -= range;
- fixedMin = navigator.getUnionExtremes().dataMin;
- } else {
- // #2293, #3543
- fixedMax = navigator.getUnionExtremes().dataMax;
- }
- }
- if (left !== zoomedMin) { // it has actually moved
- navigator.fixedWidth = range; // #1370
- ext = xAxis.toFixedRange(
- left,
- left + range,
- fixedMin,
- fixedMax
- );
- if (defined(ext.min)) { // #7411
- chart.xAxis[0].setExtremes(
- Math.min(ext.min, ext.max),
- Math.max(ext.min, ext.max),
- true,
- null, // auto animation
- { trigger: 'navigator' }
- );
- }
- }
- }
- },
- /**
- * Mousedown on a handle mask.
- * Will store necessary information for drag&drop.
- *
- * @private
- * @function Highcharts.Navigator#handlesMousedown
- *
- * @param {Highcharts.PointerEventObject} e
- * Mouse event
- *
- * @param {number} index
- * Index of a handle in Navigator.handles array
- */
- handlesMousedown: function (e, index) {
- e = this.chart.pointer.normalize(e);
- var navigator = this,
- chart = navigator.chart,
- baseXAxis = chart.xAxis[0],
- // For reversed axes, min and max are changed,
- // so the other extreme should be stored
- reverse = navigator.reversedExtremes;
- if (index === 0) {
- // Grab the left handle
- navigator.grabbedLeft = true;
- navigator.otherHandlePos = navigator.zoomedMax;
- navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max;
- } else {
- // Grab the right handle
- navigator.grabbedRight = true;
- navigator.otherHandlePos = navigator.zoomedMin;
- navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min;
- }
- chart.fixedRange = null;
- },
- /**
- * Mouse move event based on x/y mouse position.
- *
- * @private
- * @function Highcharts.Navigator#onMouseMove
- *
- * @param {Highcharts.PointerEventObject} e
- * Mouse event
- */
- onMouseMove: function (e) {
- var navigator = this,
- chart = navigator.chart,
- left = navigator.left,
- navigatorSize = navigator.navigatorSize,
- range = navigator.range,
- dragOffset = navigator.dragOffset,
- inverted = chart.inverted,
- chartX;
- // In iOS, a mousemove event with e.pageX === 0 is fired when holding
- // the finger down in the center of the scrollbar. This should be
- // ignored.
- if (!e.touches || e.touches[0].pageX !== 0) { // #4696
- e = chart.pointer.normalize(e);
- chartX = e.chartX;
- // Swap some options for inverted chart
- if (inverted) {
- left = navigator.top;
- chartX = e.chartY;
- }
- // Drag left handle or top handle
- if (navigator.grabbedLeft) {
- navigator.hasDragged = true;
- navigator.render(
- 0,
- 0,
- chartX - left,
- navigator.otherHandlePos
- );
- // Drag right handle or bottom handle
- } else if (navigator.grabbedRight) {
- navigator.hasDragged = true;
- navigator.render(
- 0,
- 0,
- navigator.otherHandlePos,
- chartX - left
- );
- // Drag scrollbar or open area in navigator
- } else if (navigator.grabbedCenter) {
- navigator.hasDragged = true;
- if (chartX < dragOffset) { // outside left
- chartX = dragOffset;
- // outside right
- } else if (chartX > navigatorSize + dragOffset - range) {
- chartX = navigatorSize + dragOffset - range;
- }
- navigator.render(
- 0,
- 0,
- chartX - dragOffset,
- chartX - dragOffset + range
- );
- }
- if (
- navigator.hasDragged &&
- navigator.scrollbar &&
- pick(
- navigator.scrollbar.options.liveRedraw,
- // By default, don't run live redraw on VML, on touch
- // devices or if the chart is in boost.
- H.svg && !isTouchDevice && !this.chart.isBoosting
- )
- ) {
- e.DOMType = e.type; // DOMType is for IE8
- setTimeout(function () {
- navigator.onMouseUp(e);
- }, 0);
- }
- }
- },
- /**
- * Mouse up event based on x/y mouse position.
- *
- * @private
- * @function Highcharts.Navigator#onMouseUp
- *
- * @param {Highcharts.PointerEventObject} e
- * Mouse event
- */
- onMouseUp: function (e) {
- var navigator = this,
- chart = navigator.chart,
- xAxis = navigator.xAxis,
- scrollbar = navigator.scrollbar,
- unionExtremes,
- fixedMin,
- fixedMax,
- ext,
- DOMEvent = e.DOMEvent || e;
- if (
- // MouseUp is called for both, navigator and scrollbar (that order),
- // which causes calling afterSetExtremes twice. Prevent first call
- // by checking if scrollbar is going to set new extremes (#6334)
- (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) ||
- e.trigger === 'scrollbar'
- ) {
- unionExtremes = navigator.getUnionExtremes();
- // When dragging one handle, make sure the other one doesn't change
- if (navigator.zoomedMin === navigator.otherHandlePos) {
- fixedMin = navigator.fixedExtreme;
- } else if (navigator.zoomedMax === navigator.otherHandlePos) {
- fixedMax = navigator.fixedExtreme;
- }
- // Snap to right edge (#4076)
- if (navigator.zoomedMax === navigator.size) {
- fixedMax = navigator.reversedExtremes ?
- unionExtremes.dataMin : unionExtremes.dataMax;
- }
- // Snap to left edge (#7576)
- if (navigator.zoomedMin === 0) {
- fixedMin = navigator.reversedExtremes ?
- unionExtremes.dataMax : unionExtremes.dataMin;
- }
- ext = xAxis.toFixedRange(
- navigator.zoomedMin,
- navigator.zoomedMax,
- fixedMin,
- fixedMax
- );
- if (defined(ext.min)) {
- chart.xAxis[0].setExtremes(
- Math.min(ext.min, ext.max),
- Math.max(ext.min, ext.max),
- true,
- // Run animation when clicking buttons, scrollbar track etc,
- // but not when dragging handles or scrollbar
- navigator.hasDragged ? false : null,
- {
- trigger: 'navigator',
- triggerOp: 'navigator-drag',
- DOMEvent: DOMEvent // #1838
- }
- );
- }
- }
- if (e.DOMType !== 'mousemove') {
- navigator.grabbedLeft = navigator.grabbedRight =
- navigator.grabbedCenter = navigator.fixedWidth =
- navigator.fixedExtreme = navigator.otherHandlePos =
- navigator.hasDragged = navigator.dragOffset = null;
- }
- },
- /**
- * Removes the event handlers attached previously with addEvents.
- *
- * @private
- * @function Highcharts.Navigator#removeEvents
- */
- removeEvents: function () {
- if (this.eventsToUnbind) {
- this.eventsToUnbind.forEach(function (unbind) {
- unbind();
- });
- this.eventsToUnbind = undefined;
- }
- this.removeBaseSeriesEvents();
- },
- /**
- * Remove data events.
- *
- * @private
- * @function Highcharts.Navigator#removeBaseSeriesEvents
- */
- removeBaseSeriesEvents: function () {
- var baseSeries = this.baseSeries || [];
- if (this.navigatorEnabled && baseSeries[0]) {
- if (this.navigatorOptions.adaptToUpdatedData !== false) {
- baseSeries.forEach(function (series) {
- removeEvent(series, 'updatedData', this.updatedDataHandler);
- }, this);
- }
- // We only listen for extremes-events on the first baseSeries
- if (baseSeries[0].xAxis) {
- removeEvent(
- baseSeries[0].xAxis,
- 'foundExtremes',
- this.modifyBaseAxisExtremes
- );
- }
- }
- },
- /**
- * Initiate the Navigator object
- *
- * @private
- * @function Highcharts.Navigator#init
- *
- * @param {Highcharts.Chart} chart
- */
- init: function (chart) {
- var chartOptions = chart.options,
- navigatorOptions = chartOptions.navigator,
- navigatorEnabled = navigatorOptions.enabled,
- scrollbarOptions = chartOptions.scrollbar,
- scrollbarEnabled = scrollbarOptions.enabled,
- height = navigatorEnabled ? navigatorOptions.height : 0,
- scrollbarHeight = scrollbarEnabled ? scrollbarOptions.height : 0;
- this.handles = [];
- this.shades = [];
- this.chart = chart;
- this.setBaseSeries();
- this.height = height;
- this.scrollbarHeight = scrollbarHeight;
- this.scrollbarEnabled = scrollbarEnabled;
- this.navigatorEnabled = navigatorEnabled;
- this.navigatorOptions = navigatorOptions;
- this.scrollbarOptions = scrollbarOptions;
- this.outlineHeight = height + scrollbarHeight;
- this.opposite = pick(
- navigatorOptions.opposite,
- !navigatorEnabled && chart.inverted
- ); // #6262
- var navigator = this,
- baseSeries = navigator.baseSeries,
- xAxisIndex = chart.xAxis.length,
- yAxisIndex = chart.yAxis.length,
- baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis ||
- chart.xAxis[0] || { options: {} };
- chart.isDirtyBox = true;
- if (navigator.navigatorEnabled) {
- // an x axis is required for scrollbar also
- navigator.xAxis = new Axis(chart, merge({
- // inherit base xAxis' break and ordinal options
- breaks: baseXaxis.options.breaks,
- ordinal: baseXaxis.options.ordinal
- }, navigatorOptions.xAxis, {
- id: 'navigator-x-axis',
- yAxis: 'navigator-y-axis',
- isX: true,
- type: 'datetime',
- index: xAxisIndex,
- isInternal: true,
- offset: 0,
- keepOrdinalPadding: true, // #2436
- startOnTick: false,
- endOnTick: false,
- minPadding: 0,
- maxPadding: 0,
- zoomEnabled: false
- }, chart.inverted ? {
- offsets: [scrollbarHeight, 0, -scrollbarHeight, 0],
- width: height
- } : {
- offsets: [0, -scrollbarHeight, 0, scrollbarHeight],
- height: height
- }));
- navigator.yAxis = new Axis(chart, merge(navigatorOptions.yAxis, {
- id: 'navigator-y-axis',
- alignTicks: false,
- offset: 0,
- index: yAxisIndex,
- isInternal: true,
- zoomEnabled: false
- }, chart.inverted ? {
- width: height
- } : {
- height: height
- }));
- // If we have a base series, initialize the navigator series
- if (baseSeries || navigatorOptions.series.data) {
- navigator.updateNavigatorSeries(false);
- // If not, set up an event to listen for added series
- } else if (chart.series.length === 0) {
- navigator.unbindRedraw = addEvent(
- chart,
- 'beforeRedraw',
- function () {
- // We've got one, now add it as base
- if (chart.series.length > 0 && !navigator.series) {
- navigator.setBaseSeries();
- navigator.unbindRedraw(); // reset
- }
- }
- );
- }
- navigator.reversedExtremes = (
- chart.inverted && !navigator.xAxis.reversed
- ) || (
- !chart.inverted && navigator.xAxis.reversed
- );
- // Render items, so we can bind events to them:
- navigator.renderElements();
- // Add mouse events
- navigator.addMouseEvents();
- // in case of scrollbar only, fake an x axis to get translation
- } else {
- navigator.xAxis = {
- translate: function (value, reverse) {
- var axis = chart.xAxis[0],
- ext = axis.getExtremes(),
- scrollTrackWidth = axis.len - 2 * scrollbarHeight,
- min = numExt('min', axis.options.min, ext.dataMin),
- valueRange = numExt(
- 'max',
- axis.options.max,
- ext.dataMax
- ) - min;
- return reverse ?
- // from pixel to value
- (value * valueRange / scrollTrackWidth) + min :
- // from value to pixel
- scrollTrackWidth * (value - min) / valueRange;
- },
- toPixels: function (value) {
- return this.translate(value);
- },
- toValue: function (value) {
- return this.translate(value, true);
- },
- toFixedRange: Axis.prototype.toFixedRange,
- fake: true
- };
- }
- // Initialize the scrollbar
- if (chart.options.scrollbar.enabled) {
- chart.scrollbar = navigator.scrollbar = new Scrollbar(
- chart.renderer,
- merge(chart.options.scrollbar, {
- margin: navigator.navigatorEnabled ? 0 : 10,
- vertical: chart.inverted
- }),
- chart
- );
- addEvent(navigator.scrollbar, 'changed', function (e) {
- var range = navigator.size,
- to = range * this.to,
- from = range * this.from;
- navigator.hasDragged = navigator.scrollbar.hasDragged;
- navigator.render(0, 0, from, to);
- if (
- chart.options.scrollbar.liveRedraw ||
- (
- e.DOMType !== 'mousemove' &&
- e.DOMType !== 'touchmove'
- )
- ) {
- setTimeout(function () {
- navigator.onMouseUp(e);
- });
- }
- });
- }
- // Add data events
- navigator.addBaseSeriesEvents();
- // Add redraw events
- navigator.addChartEvents();
- },
- /**
- * Get the union data extremes of the chart - the outer data extremes of the
- * base X axis and the navigator axis.
- *
- * @private
- * @function Highcharts.Navigator#getUnionExtremes
- *
- * @param {boolean} returnFalseOnNoBaseSeries
- * as the param says.
- *
- * @return {*}
- */
- getUnionExtremes: function (returnFalseOnNoBaseSeries) {
- var baseAxis = this.chart.xAxis[0],
- navAxis = this.xAxis,
- navAxisOptions = navAxis.options,
- baseAxisOptions = baseAxis.options,
- ret;
- if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) {
- ret = {
- dataMin: pick( // #4053
- navAxisOptions && navAxisOptions.min,
- numExt(
- 'min',
- baseAxisOptions.min,
- baseAxis.dataMin,
- navAxis.dataMin,
- navAxis.min
- )
- ),
- dataMax: pick(
- navAxisOptions && navAxisOptions.max,
- numExt(
- 'max',
- baseAxisOptions.max,
- baseAxis.dataMax,
- navAxis.dataMax,
- navAxis.max
- )
- )
- };
- }
- return ret;
- },
- /**
- * Set the base series and update the navigator series from this. With a bit
- * of modification we should be able to make this an API method to be called
- * from the outside
- *
- * @private
- * @function Highcharts.Navigator#setBaseSeries
- *
- * @param {*} baseSeriesOptions
- * Additional series options for a navigator
- *
- * @param {boolean} [redraw]
- * Whether to redraw after update.
- */
- setBaseSeries: function (baseSeriesOptions, redraw) {
- var chart = this.chart,
- baseSeries = this.baseSeries = [];
- baseSeriesOptions = (
- baseSeriesOptions ||
- chart.options && chart.options.navigator.baseSeries ||
- 0
- );
- // Iterate through series and add the ones that should be shown in
- // navigator.
- (chart.series || []).forEach(function (series, i) {
- if (
- // Don't include existing nav series
- !series.options.isInternal &&
- (
- series.options.showInNavigator ||
- (
- i === baseSeriesOptions ||
- series.options.id === baseSeriesOptions
- ) &&
- series.options.showInNavigator !== false
- )
- ) {
- baseSeries.push(series);
- }
- });
- // When run after render, this.xAxis already exists
- if (this.xAxis && !this.xAxis.fake) {
- this.updateNavigatorSeries(true, redraw);
- }
- },
- /**
- * Update series in the navigator from baseSeries, adding new if does not
- * exist.
- *
- * @private
- * @function Highcharts.Navigator.updateNavigatorSeries
- *
- * @param {boolean} addEvents
- *
- * @param {boolean} redraw
- */
- updateNavigatorSeries: function (addEvents, redraw) {
- var navigator = this,
- chart = navigator.chart,
- baseSeries = navigator.baseSeries,
- baseOptions,
- mergedNavSeriesOptions,
- chartNavigatorSeriesOptions = navigator.navigatorOptions.series,
- baseNavigatorOptions,
- navSeriesMixin = {
- enableMouseTracking: false,
- index: null, // #6162
- linkedTo: null, // #6734
- group: 'nav', // for columns
- padXAxis: false,
- xAxis: 'navigator-x-axis',
- yAxis: 'navigator-y-axis',
- showInLegend: false,
- stacking: false, // #4823
- isInternal: true
- },
- // Remove navigator series that are no longer in the baseSeries
- navigatorSeries = navigator.series =
- (navigator.series || []).filter(function (navSeries) {
- var base = navSeries.baseSeries;
- if (baseSeries.indexOf(base) < 0) { // Not in array
- // If there is still a base series connected to this
- // series, remove event handler and reference.
- if (base) {
- removeEvent(
- base,
- 'updatedData',
- navigator.updatedDataHandler
- );
- delete base.navigatorSeries;
- }
- // Kill the nav series. It may already have been
- // destroyed (#8715).
- if (navSeries.chart) {
- navSeries.destroy();
- }
- return false;
- }
- return true;
- });
- // Go through each base series and merge the options to create new
- // series
- if (baseSeries && baseSeries.length) {
- baseSeries.forEach(function eachBaseSeries(base) {
- var linkedNavSeries = base.navigatorSeries,
- userNavOptions = extend(
- // Grab color and visibility from base as default
- {
- color: base.color,
- visible: base.visible
- },
- !isArray(chartNavigatorSeriesOptions) ?
- chartNavigatorSeriesOptions :
- defaultOptions.navigator.series
- );
- // Don't update if the series exists in nav and we have disabled
- // adaptToUpdatedData.
- if (
- linkedNavSeries &&
- navigator.navigatorOptions.adaptToUpdatedData === false
- ) {
- return;
- }
- navSeriesMixin.name = 'Navigator ' + baseSeries.length;
- baseOptions = base.options || {};
- baseNavigatorOptions = baseOptions.navigatorOptions || {};
- mergedNavSeriesOptions = merge(
- baseOptions,
- navSeriesMixin,
- userNavOptions,
- baseNavigatorOptions
- );
- // Merge data separately. Do a slice to avoid mutating the
- // navigator options from base series (#4923).
- var navigatorSeriesData =
- baseNavigatorOptions.data || userNavOptions.data;
- navigator.hasNavigatorData =
- navigator.hasNavigatorData || !!navigatorSeriesData;
- mergedNavSeriesOptions.data =
- navigatorSeriesData ||
- baseOptions.data && baseOptions.data.slice(0);
- // Update or add the series
- if (linkedNavSeries && linkedNavSeries.options) {
- linkedNavSeries.update(mergedNavSeriesOptions, redraw);
- } else {
- base.navigatorSeries = chart.initSeries(
- mergedNavSeriesOptions
- );
- base.navigatorSeries.baseSeries = base; // Store ref
- navigatorSeries.push(base.navigatorSeries);
- }
- });
- }
- // If user has defined data (and no base series) or explicitly defined
- // navigator.series as an array, we create these series on top of any
- // base series.
- if (
- chartNavigatorSeriesOptions.data &&
- !(baseSeries && baseSeries.length) ||
- isArray(chartNavigatorSeriesOptions)
- ) {
- navigator.hasNavigatorData = false;
- // Allow navigator.series to be an array
- chartNavigatorSeriesOptions = H.splat(chartNavigatorSeriesOptions);
- chartNavigatorSeriesOptions
- .forEach(function (userSeriesOptions, i) {
- navSeriesMixin.name =
- 'Navigator ' + (navigatorSeries.length + 1);
- mergedNavSeriesOptions = merge(
- defaultOptions.navigator.series,
- {
- // Since we don't have a base series to pull color from,
- // try to fake it by using color from series with same
- // index. Otherwise pull from the colors array. We need
- // an explicit color as otherwise updates will increment
- // color counter and we'll get a new color for each
- // update of the nav series.
- color: chart.series[i] &&
- !chart.series[i].options.isInternal &&
- chart.series[i].color ||
- chart.options.colors[i] ||
- chart.options.colors[0]
- },
- navSeriesMixin,
- userSeriesOptions
- );
- mergedNavSeriesOptions.data = userSeriesOptions.data;
- if (mergedNavSeriesOptions.data) {
- navigator.hasNavigatorData = true;
- navigatorSeries.push(
- chart.initSeries(mergedNavSeriesOptions)
- );
- }
- });
- }
- if (addEvents) {
- this.addBaseSeriesEvents();
- }
- },
- /**
- * Add data events.
- * For example when main series is updated we need to recalculate extremes
- *
- * @private
- * @function Highcharts.Navigator#addBaseSeriesEvent
- */
- addBaseSeriesEvents: function () {
- var navigator = this,
- baseSeries = navigator.baseSeries || [];
- // Bind modified extremes event to first base's xAxis only.
- // In event of > 1 base-xAxes, the navigator will ignore those.
- // Adding this multiple times to the same axis is no problem, as
- // duplicates should be discarded by the browser.
- if (baseSeries[0] && baseSeries[0].xAxis) {
- addEvent(
- baseSeries[0].xAxis,
- 'foundExtremes',
- this.modifyBaseAxisExtremes
- );
- }
- baseSeries.forEach(function (base) {
- // Link base series show/hide to navigator series visibility
- addEvent(base, 'show', function () {
- if (this.navigatorSeries) {
- this.navigatorSeries.setVisible(true, false);
- }
- });
- addEvent(base, 'hide', function () {
- if (this.navigatorSeries) {
- this.navigatorSeries.setVisible(false, false);
- }
- });
- // Respond to updated data in the base series, unless explicitily
- // not adapting to data changes.
- if (this.navigatorOptions.adaptToUpdatedData !== false) {
- if (base.xAxis) {
- addEvent(base, 'updatedData', this.updatedDataHandler);
- }
- }
- // Handle series removal
- addEvent(base, 'remove', function () {
- if (this.navigatorSeries) {
- erase(navigator.series, this.navigatorSeries);
- if (defined(this.navigatorSeries.options)) {
- this.navigatorSeries.remove(false);
- }
- delete this.navigatorSeries;
- }
- });
- }, this);
- },
- /**
- * Get minimum from all base series connected to the navigator
- *
- * @param {number} currentSeriesMin
- * Minium from the current series
- *
- * @return {number} Minimum from all series
- */
- getBaseSeriesMin: function (currentSeriesMin) {
- return this.baseSeries.reduce(
- function (min, series) {
- return Math.min(min, series.xData[0]);
- },
- currentSeriesMin
- );
- },
- /**
- * Set the navigator x axis extremes to reflect the total. The navigator
- * extremes should always be the extremes of the union of all series in the
- * chart as well as the navigator series.
- *
- * @private
- * @function Highcharts.Navigator#modifyNavigatorAxisExtremes
- */
- modifyNavigatorAxisExtremes: function () {
- var xAxis = this.xAxis,
- unionExtremes;
- if (xAxis.getExtremes) {
- unionExtremes = this.getUnionExtremes(true);
- if (
- unionExtremes &&
- (
- unionExtremes.dataMin !== xAxis.min ||
- unionExtremes.dataMax !== xAxis.max
- )
- ) {
- xAxis.min = unionExtremes.dataMin;
- xAxis.max = unionExtremes.dataMax;
- }
- }
- },
- /**
- * Hook to modify the base axis extremes with information from the Navigator
- *
- * @private
- * @function Highcharts.Navigator#modifyBaseAxisExtremes
- */
- modifyBaseAxisExtremes: function () {
- var baseXAxis = this,
- navigator = baseXAxis.chart.navigator,
- baseExtremes = baseXAxis.getExtremes(),
- baseMin = baseExtremes.min,
- baseMax = baseExtremes.max,
- baseDataMin = baseExtremes.dataMin,
- baseDataMax = baseExtremes.dataMax,
- range = baseMax - baseMin,
- stickToMin = navigator.stickToMin,
- stickToMax = navigator.stickToMax,
- overscroll = pick(baseXAxis.options.overscroll, 0),
- newMax,
- newMin,
- navigatorSeries = navigator.series && navigator.series[0],
- hasSetExtremes = !!baseXAxis.setExtremes,
- // When the extremes have been set by range selector button, don't
- // stick to min or max. The range selector buttons will handle the
- // extremes. (#5489)
- unmutable = baseXAxis.eventArgs &&
- baseXAxis.eventArgs.trigger === 'rangeSelectorButton';
- if (!unmutable) {
- // If the zoomed range is already at the min, move it to the right
- // as new data comes in
- if (stickToMin) {
- newMin = baseDataMin;
- newMax = newMin + range;
- }
- // If the zoomed range is already at the max, move it to the right
- // as new data comes in
- if (stickToMax) {
- newMax = baseDataMax + overscroll;
- // if stickToMin is true, the new min value is set above
- if (!stickToMin) {
- newMin = Math.max(
- newMax - range,
- navigator.getBaseSeriesMin(
- navigatorSeries && navigatorSeries.xData ?
- navigatorSeries.xData[0] :
- -Number.MAX_VALUE
- )
- );
- }
- }
- // Update the extremes
- if (hasSetExtremes && (stickToMin || stickToMax)) {
- if (isNumber(newMin)) {
- baseXAxis.min = baseXAxis.userMin = newMin;
- baseXAxis.max = baseXAxis.userMax = newMax;
- }
- }
- }
- // Reset
- navigator.stickToMin = navigator.stickToMax = null;
- },
- /**
- * Handler for updated data on the base series. When data is modified, the
- * navigator series must reflect it. This is called from the Chart.redraw
- * function before axis and series extremes are computed.
- *
- * @private
- * @function Highcharts.Navigator#updateDataHandler
- */
- updatedDataHandler: function () {
- var navigator = this.chart.navigator,
- baseSeries = this,
- navigatorSeries = this.navigatorSeries,
- xDataMin = navigator.getBaseSeriesMin(baseSeries.xData[0]);
- // If the scrollbar is scrolled all the way to the right, keep right as
- // new data comes in.
- navigator.stickToMax = navigator.reversedExtremes ?
- Math.round(navigator.zoomedMin) === 0 :
- Math.round(navigator.zoomedMax) >= Math.round(navigator.size);
- // Detect whether the zoomed area should stick to the minimum or
- // maximum. If the current axis minimum falls outside the new updated
- // dataset, we must adjust.
- navigator.stickToMin = isNumber(baseSeries.xAxis.min) &&
- (baseSeries.xAxis.min <= xDataMin) &&
- (!this.chart.fixedRange || !navigator.stickToMax);
- // Set the navigator series data to the new data of the base series
- if (navigatorSeries && !navigator.hasNavigatorData) {
- navigatorSeries.options.pointStart = baseSeries.xData[0];
- navigatorSeries.setData(
- baseSeries.options.data,
- false,
- null,
- false
- ); // #5414
- }
- },
- /**
- * Add chart events, like redrawing navigator, when chart requires that.
- *
- * @private
- * @function Highcharts.Navigator#addChartEvents
- */
- addChartEvents: function () {
- if (!this.eventsToUnbind) {
- this.eventsToUnbind = [];
- }
- this.eventsToUnbind.push(
- // Move the scrollbar after redraw, like after data updata even if
- // axes don't redraw
- addEvent(
- this.chart,
- 'redraw',
- function () {
- var navigator = this.navigator,
- xAxis = navigator && (
- navigator.baseSeries &&
- navigator.baseSeries[0] &&
- navigator.baseSeries[0].xAxis ||
- navigator.scrollbar && this.xAxis[0]
- ); // #5709
- if (xAxis) {
- navigator.render(xAxis.min, xAxis.max);
- }
- }
- ),
- // Make room for the navigator, can be placed around the chart:
- addEvent(
- this.chart,
- 'getMargins',
- function () {
- var chart = this,
- navigator = chart.navigator,
- marginName = navigator.opposite ?
- 'plotTop' : 'marginBottom';
- if (chart.inverted) {
- marginName = navigator.opposite ?
- 'marginRight' : 'plotLeft';
- }
- chart[marginName] = (chart[marginName] || 0) + (
- navigator.navigatorEnabled || !chart.inverted ?
- navigator.outlineHeight :
- 0
- ) + navigator.navigatorOptions.margin;
- }
- )
- );
- },
- /**
- * Destroys allocated elements.
- *
- * @private
- * @function Highcharts.Navigator#destroy
- */
- destroy: function () {
- // Disconnect events added in addEvents
- this.removeEvents();
- if (this.xAxis) {
- erase(this.chart.xAxis, this.xAxis);
- erase(this.chart.axes, this.xAxis);
- }
- if (this.yAxis) {
- erase(this.chart.yAxis, this.yAxis);
- erase(this.chart.axes, this.yAxis);
- }
- // Destroy series
- (this.series || []).forEach(function (s) {
- if (s.destroy) {
- s.destroy();
- }
- });
- // Destroy properties
- [
- 'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack',
- 'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup',
- 'rendered'
- ].forEach(function (prop) {
- if (this[prop] && this[prop].destroy) {
- this[prop].destroy();
- }
- this[prop] = null;
- }, this);
- // Destroy elements in collection
- [this.handles].forEach(function (coll) {
- destroyObjectProperties(coll);
- }, this);
- }
- };
- H.Navigator = Navigator;
- /*
- * For Stock charts, override selection zooming with some special features
- * because X axis zooming is already allowed by the Navigator and Range
- * selector.
- */
- addEvent(Axis, 'zoom', function (e) {
- var chart = this.chart,
- chartOptions = chart.options,
- zoomType = chartOptions.chart.zoomType,
- pinchType = chartOptions.chart.pinchType,
- previousZoom,
- navigator = chartOptions.navigator,
- rangeSelector = chartOptions.rangeSelector;
- if (this.isXAxis && ((navigator && navigator.enabled) ||
- (rangeSelector && rangeSelector.enabled))) {
- // For y only zooming, ignore the X axis completely
- if (zoomType === 'y') {
- e.zoomed = false;
- // For xy zooming, record the state of the zoom before zoom selection,
- // then when the reset button is pressed, revert to this state. This
- // should apply only if the chart is initialized with a range (#6612),
- // otherwise zoom all the way out.
- } else if (
- (
- (!isTouchDevice && zoomType === 'xy') ||
- (isTouchDevice && pinchType === 'xy')
- ) &&
- this.options.range
- ) {
- previousZoom = this.previousZoom;
- if (defined(e.newMin)) {
- this.previousZoom = [this.min, this.max];
- } else if (previousZoom) {
- e.newMin = previousZoom[0];
- e.newMax = previousZoom[1];
- delete this.previousZoom;
- }
- }
- }
- if (e.zoomed !== undefined) {
- e.preventDefault();
- }
- });
- /**
- * For Stock charts. For x only zooming, do not to create the zoom button
- * because X axis zooming is already allowed by the Navigator and Range
- * selector. (#9285)
- */
- addEvent(Chart, 'beforeShowResetZoom', function () {
- var chartOptions = this.options,
- navigator = chartOptions.navigator,
- rangeSelector = chartOptions.rangeSelector;
- if (
- (
- (navigator && navigator.enabled) ||
- (rangeSelector && rangeSelector.enabled)
- ) && (
- (!isTouchDevice && chartOptions.chart.zoomType === 'x') ||
- (isTouchDevice && chartOptions.chart.pinchType === 'x')
- )
- ) {
- return false;
- }
- });
- // Initialize navigator for stock charts
- addEvent(Chart, 'beforeRender', function () {
- var options = this.options;
- if (options.navigator.enabled || options.scrollbar.enabled) {
- this.scroller = this.navigator = new Navigator(this);
- }
- });
- /*
- * For stock charts, extend the Chart.setChartSize method so that we can set the
- * final top position of the navigator once the height of the chart, including
- * the legend, is determined. #367. We can't use Chart.getMargins, because
- * labels offsets are not calculated yet.
- */
- addEvent(Chart, 'afterSetChartSize', function () {
- var legend = this.legend,
- navigator = this.navigator,
- scrollbarHeight,
- legendOptions,
- xAxis,
- yAxis;
- if (navigator) {
- legendOptions = legend && legend.options;
- xAxis = navigator.xAxis;
- yAxis = navigator.yAxis;
- scrollbarHeight = navigator.scrollbarHeight;
- // Compute the top position
- if (this.inverted) {
- navigator.left = navigator.opposite ?
- this.chartWidth - scrollbarHeight - navigator.height :
- this.spacing[3] + scrollbarHeight;
- navigator.top = this.plotTop + scrollbarHeight;
- } else {
- navigator.left = this.plotLeft + scrollbarHeight;
- navigator.top = navigator.navigatorOptions.top ||
- this.chartHeight -
- navigator.height -
- scrollbarHeight -
- this.spacing[2] -
- (
- this.rangeSelector && this.extraBottomMargin ?
- this.rangeSelector.getHeight() :
- 0
- ) -
- (
- (
- legendOptions &&
- legendOptions.verticalAlign === 'bottom' &&
- legendOptions.enabled &&
- !legendOptions.floating
- ) ?
- legend.legendHeight + pick(legendOptions.margin, 10) :
- 0
- );
- }
- if (xAxis && yAxis) { // false if navigator is disabled (#904)
- if (this.inverted) {
- xAxis.options.left = yAxis.options.left = navigator.left;
- } else {
- xAxis.options.top = yAxis.options.top = navigator.top;
- }
- xAxis.setAxisSize();
- yAxis.setAxisSize();
- }
- }
- });
- // Merge options, if no scrolling exists yet
- addEvent(Chart, 'update', function (e) {
- var navigatorOptions = (e.options.navigator || {}),
- scrollbarOptions = (e.options.scrollbar || {});
- if (!this.navigator && !this.scroller &&
- (navigatorOptions.enabled || scrollbarOptions.enabled)
- ) {
- merge(true, this.options.navigator, navigatorOptions);
- merge(true, this.options.scrollbar, scrollbarOptions);
- delete e.options.navigator;
- delete e.options.scrollbar;
- }
- });
- // Initiate navigator, if no scrolling exists yet
- addEvent(Chart, 'afterUpdate', function () {
- if (!this.navigator && !this.scroller &&
- (this.options.navigator.enabled || this.options.scrollbar.enabled)
- ) {
- this.scroller = this.navigator = new Navigator(this);
- }
- });
- // Handle adding new series
- addEvent(Chart, 'afterAddSeries', function () {
- if (this.navigator) {
- // Recompute which series should be shown in navigator, and add them
- this.navigator.setBaseSeries(null, false);
- }
- });
- // Handle updating series
- addEvent(Series, 'afterUpdate', function () {
- if (this.chart.navigator && !this.options.isInternal) {
- this.chart.navigator.setBaseSeries(null, false);
- }
- });
- Chart.prototype.callbacks.push(function (chart) {
- var extremes,
- navigator = chart.navigator;
- // Initiate the navigator
- if (navigator && chart.xAxis[0]) {
- extremes = chart.xAxis[0].getExtremes();
- navigator.render(extremes.min, extremes.max);
- }
- });
|