drilldown.src.js 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. /**
  2. * @license Highcharts JS v8.2.0 (2020-08-20)
  3. *
  4. * Highcharts Drilldown module
  5. *
  6. * Author: Torstein Honsi
  7. * License: www.highcharts.com/license
  8. *
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/modules/drilldown', ['highcharts'], function (Highcharts) {
  17. factory(Highcharts);
  18. factory.Highcharts = Highcharts;
  19. return factory;
  20. });
  21. } else {
  22. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  23. }
  24. }(function (Highcharts) {
  25. var _modules = Highcharts ? Highcharts._modules : {};
  26. function _registerModule(obj, path, args, fn) {
  27. if (!obj.hasOwnProperty(path)) {
  28. obj[path] = fn.apply(null, args);
  29. }
  30. }
  31. _registerModule(_modules, 'Extensions/Drilldown.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Color.js'], _modules['Core/Globals.js'], _modules['Core/Options.js'], _modules['Core/Series/Point.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Axis/Tick.js'], _modules['Core/Utilities.js']], function (Chart, Color, H, O, Point, SVGRenderer, Tick, U) {
  32. /* *
  33. *
  34. * Highcharts Drilldown module
  35. *
  36. * Author: Torstein Honsi
  37. *
  38. * License: www.highcharts.com/license
  39. *
  40. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  41. *
  42. * */
  43. var defaultOptions = O.defaultOptions;
  44. var addEvent = U.addEvent,
  45. removeEvent = U.removeEvent,
  46. animObject = U.animObject,
  47. extend = U.extend,
  48. fireEvent = U.fireEvent,
  49. format = U.format,
  50. merge = U.merge,
  51. objectEach = U.objectEach,
  52. pick = U.pick,
  53. syncTimeout = U.syncTimeout;
  54. /**
  55. * Gets fired when a drilldown point is clicked, before the new series is added.
  56. * Note that when clicking a category label to trigger multiple series
  57. * drilldown, one `drilldown` event is triggered per point in the category.
  58. *
  59. * @callback Highcharts.DrilldownCallbackFunction
  60. *
  61. * @param {Highcharts.Chart} this
  62. * The chart where the event occurs.
  63. *
  64. * @param {Highcharts.DrilldownEventObject} e
  65. * The drilldown event.
  66. */
  67. /**
  68. * The event arguments when a drilldown point is clicked.
  69. *
  70. * @interface Highcharts.DrilldownEventObject
  71. */ /**
  72. * If a category label was clicked, which index.
  73. * @name Highcharts.DrilldownEventObject#category
  74. * @type {number|undefined}
  75. */ /**
  76. * The original browser event (usually click) that triggered the drilldown.
  77. * @name Highcharts.DrilldownEventObject#originalEvent
  78. * @type {global.Event|undefined}
  79. */ /**
  80. * Prevents the default behaviour of the event.
  81. * @name Highcharts.DrilldownEventObject#preventDefault
  82. * @type {Function}
  83. */ /**
  84. * The originating point.
  85. * @name Highcharts.DrilldownEventObject#point
  86. * @type {Highcharts.Point}
  87. */ /**
  88. * If a category label was clicked, this array holds all points corresponding to
  89. * the category. Otherwise it is set to false.
  90. * @name Highcharts.DrilldownEventObject#points
  91. * @type {boolean|Array<Highcharts.Point>|undefined}
  92. */ /**
  93. * Options for the new series. If the event is utilized for async drilldown, the
  94. * seriesOptions are not added, but rather loaded async.
  95. * @name Highcharts.DrilldownEventObject#seriesOptions
  96. * @type {Highcharts.SeriesOptionsType|undefined}
  97. */ /**
  98. * The event target.
  99. * @name Highcharts.DrilldownEventObject#target
  100. * @type {Highcharts.Chart}
  101. */ /**
  102. * The event type.
  103. * @name Highcharts.DrilldownEventObject#type
  104. * @type {"drilldown"}
  105. */
  106. /**
  107. * This gets fired after all the series have been drilled up. This is especially
  108. * usefull in a chart with multiple drilldown series.
  109. *
  110. * @callback Highcharts.DrillupAllCallbackFunction
  111. *
  112. * @param {Highcharts.Chart} this
  113. * The chart where the event occurs.
  114. *
  115. * @param {Highcharts.DrillupAllEventObject} e
  116. * The final drillup event.
  117. */
  118. /**
  119. * The event arguments when all the series have been drilled up.
  120. *
  121. * @interface Highcharts.DrillupAllEventObject
  122. */ /**
  123. * Prevents the default behaviour of the event.
  124. * @name Highcharts.DrillupAllEventObject#preventDefault
  125. * @type {Function}
  126. */ /**
  127. * The event target.
  128. * @name Highcharts.DrillupAllEventObject#target
  129. * @type {Highcharts.Chart}
  130. */ /**
  131. * The event type.
  132. * @name Highcharts.DrillupAllEventObject#type
  133. * @type {"drillupall"}
  134. */
  135. /**
  136. * Gets fired when drilling up from a drilldown series.
  137. *
  138. * @callback Highcharts.DrillupCallbackFunction
  139. *
  140. * @param {Highcharts.Chart} this
  141. * The chart where the event occurs.
  142. *
  143. * @param {Highcharts.DrillupEventObject} e
  144. * The drillup event.
  145. */
  146. /**
  147. * The event arguments when drilling up from a drilldown series.
  148. *
  149. * @interface Highcharts.DrillupEventObject
  150. */ /**
  151. * Prevents the default behaviour of the event.
  152. * @name Highcharts.DrillupEventObject#preventDefault
  153. * @type {Function}
  154. */ /**
  155. * Options for the new series.
  156. * @name Highcharts.DrillupEventObject#seriesOptions
  157. * @type {Highcharts.SeriesOptionsType|undefined}
  158. */ /**
  159. * The event target.
  160. * @name Highcharts.DrillupEventObject#target
  161. * @type {Highcharts.Chart}
  162. */ /**
  163. * The event type.
  164. * @name Highcharts.DrillupEventObject#type
  165. * @type {"drillup"}
  166. */
  167. var noop = H.noop,
  168. seriesTypes = H.seriesTypes,
  169. PieSeries = seriesTypes.pie,
  170. ColumnSeries = seriesTypes.column,
  171. ddSeriesId = 1;
  172. // Add language
  173. extend(defaultOptions.lang,
  174. /**
  175. * @optionparent lang
  176. */
  177. {
  178. /**
  179. * The text for the button that appears when drilling down, linking back
  180. * to the parent series. The parent series' name is inserted for
  181. * `{series.name}`.
  182. *
  183. * @since 3.0.8
  184. * @product highcharts highmaps
  185. * @requires modules/drilldown
  186. *
  187. * @private
  188. */
  189. drillUpText: '◁ Back to {series.name}'
  190. });
  191. /**
  192. * Options for drill down, the concept of inspecting increasingly high
  193. * resolution data through clicking on chart items like columns or pie slices.
  194. *
  195. * The drilldown feature requires the drilldown.js file to be loaded,
  196. * found in the modules directory of the download package, or online at
  197. * [code.highcharts.com/modules/drilldown.js
  198. * ](https://code.highcharts.com/modules/drilldown.js).
  199. *
  200. * @product highcharts highmaps
  201. * @requires modules/drilldown
  202. * @optionparent drilldown
  203. */
  204. defaultOptions.drilldown = {
  205. /**
  206. * When this option is false, clicking a single point will drill down
  207. * all points in the same category, equivalent to clicking the X axis
  208. * label.
  209. *
  210. * @sample {highcharts} highcharts/drilldown/allowpointdrilldown-false/
  211. * Don't allow point drilldown
  212. *
  213. * @type {boolean}
  214. * @default true
  215. * @since 4.1.7
  216. * @product highcharts
  217. * @apioption drilldown.allowPointDrilldown
  218. */
  219. /**
  220. * An array of series configurations for the drill down. Each series
  221. * configuration uses the same syntax as the [series](#series) option set.
  222. * These drilldown series are hidden by default. The drilldown series is
  223. * linked to the parent series' point by its `id`.
  224. *
  225. * @type {Array<Highcharts.SeriesOptionsType>}
  226. * @since 3.0.8
  227. * @product highcharts highmaps
  228. * @apioption drilldown.series
  229. */
  230. /**
  231. * Additional styles to apply to the X axis label for a point that
  232. * has drilldown data. By default it is underlined and blue to invite
  233. * to interaction.
  234. *
  235. * In styled mode, active label styles can be set with the
  236. * `.highcharts-drilldown-axis-label` class.
  237. *
  238. * @sample {highcharts} highcharts/drilldown/labels/
  239. * Label styles
  240. *
  241. * @type {Highcharts.CSSObject}
  242. * @default { "cursor": "pointer", "color": "#003399", "fontWeight": "bold", "textDecoration": "underline" }
  243. * @since 3.0.8
  244. * @product highcharts highmaps
  245. */
  246. activeAxisLabelStyle: {
  247. /** @ignore-option */
  248. cursor: 'pointer',
  249. /** @ignore-option */
  250. color: '#003399',
  251. /** @ignore-option */
  252. fontWeight: 'bold',
  253. /** @ignore-option */
  254. textDecoration: 'underline'
  255. },
  256. /**
  257. * Additional styles to apply to the data label of a point that has
  258. * drilldown data. By default it is underlined and blue to invite to
  259. * interaction.
  260. *
  261. * In styled mode, active data label styles can be applied with the
  262. * `.highcharts-drilldown-data-label` class.
  263. *
  264. * @sample {highcharts} highcharts/drilldown/labels/
  265. * Label styles
  266. *
  267. * @type {Highcharts.CSSObject}
  268. * @default { "cursor": "pointer", "color": "#003399", "fontWeight": "bold", "textDecoration": "underline" }
  269. * @since 3.0.8
  270. * @product highcharts highmaps
  271. */
  272. activeDataLabelStyle: {
  273. cursor: 'pointer',
  274. color: '#003399',
  275. fontWeight: 'bold',
  276. textDecoration: 'underline'
  277. },
  278. /**
  279. * Set the animation for all drilldown animations. Animation of a drilldown
  280. * occurs when drilling between a column point and a column series,
  281. * or a pie slice and a full pie series. Drilldown can still be used
  282. * between series and points of different types, but animation will
  283. * not occur.
  284. *
  285. * The animation can either be set as a boolean or a configuration
  286. * object. If `true`, it will use the 'swing' jQuery easing and a duration
  287. * of 500 ms. If used as a configuration object, the following properties
  288. * are supported:
  289. *
  290. * - `duration`: The duration of the animation in milliseconds.
  291. *
  292. * - `easing`: A string reference to an easing function set on the `Math`
  293. * object. See
  294. * [the easing demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-animation-easing/).
  295. *
  296. * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
  297. * @since 3.0.8
  298. * @product highcharts highmaps
  299. */
  300. animation: {
  301. /** @internal */
  302. duration: 500
  303. },
  304. /**
  305. * Options for the drill up button that appears when drilling down on a
  306. * series. The text for the button is defined in
  307. * [lang.drillUpText](#lang.drillUpText).
  308. *
  309. * @sample {highcharts} highcharts/drilldown/drillupbutton/
  310. * Drill up button
  311. * @sample {highmaps} highcharts/drilldown/drillupbutton/
  312. * Drill up button
  313. *
  314. * @since 3.0.8
  315. * @product highcharts highmaps
  316. */
  317. drillUpButton: {
  318. /**
  319. * What box to align the button to. Can be either `plotBox` or
  320. * `spacingBox`.
  321. *
  322. * @type {Highcharts.ButtonRelativeToValue}
  323. * @default plotBox
  324. * @since 3.0.8
  325. * @product highcharts highmaps
  326. * @apioption drilldown.drillUpButton.relativeTo
  327. */
  328. /**
  329. * A collection of attributes for the button. The object takes SVG
  330. * attributes like `fill`, `stroke`, `stroke-width` or `r`, the border
  331. * radius. The theme also supports `style`, a collection of CSS
  332. * properties for the text. Equivalent attributes for the hover state
  333. * are given in `theme.states.hover`.
  334. *
  335. * In styled mode, drill-up button styles can be applied with the
  336. * `.highcharts-drillup-button` class.
  337. *
  338. * @sample {highcharts} highcharts/drilldown/drillupbutton/
  339. * Button theming
  340. * @sample {highmaps} highcharts/drilldown/drillupbutton/
  341. * Button theming
  342. *
  343. * @type {object}
  344. * @since 3.0.8
  345. * @product highcharts highmaps
  346. * @apioption drilldown.drillUpButton.theme
  347. */
  348. /**
  349. * Positioning options for the button within the `relativeTo` box.
  350. * Available properties are `x`, `y`, `align` and `verticalAlign`.
  351. *
  352. * @type {Highcharts.AlignObject}
  353. * @since 3.0.8
  354. * @product highcharts highmaps
  355. */
  356. position: {
  357. /**
  358. * Vertical alignment of the button.
  359. *
  360. * @type {Highcharts.VerticalAlignValue}
  361. * @default top
  362. * @product highcharts highmaps
  363. * @apioption drilldown.drillUpButton.position.verticalAlign
  364. */
  365. /**
  366. * Horizontal alignment.
  367. *
  368. * @type {Highcharts.AlignValue}
  369. */
  370. align: 'right',
  371. /**
  372. * The X offset of the button.
  373. */
  374. x: -10,
  375. /**
  376. * The Y offset of the button.
  377. */
  378. y: 10
  379. }
  380. }
  381. };
  382. /**
  383. * Fires when a drilldown point is clicked, before the new series is added. This
  384. * event is also utilized for async drilldown, where the seriesOptions are not
  385. * added by option, but rather loaded async. Note that when clicking a category
  386. * label to trigger multiple series drilldown, one `drilldown` event is
  387. * triggered per point in the category.
  388. *
  389. * Event arguments:
  390. *
  391. * - `category`: If a category label was clicked, which index.
  392. *
  393. * - `originalEvent`: The original browser event (usually click) that triggered
  394. * the drilldown.
  395. *
  396. * - `point`: The originating point.
  397. *
  398. * - `points`: If a category label was clicked, this array holds all points
  399. * corresponding to the category.
  400. *
  401. * - `seriesOptions`: Options for the new series.
  402. *
  403. * @sample {highcharts} highcharts/drilldown/async/
  404. * Async drilldown
  405. *
  406. * @type {Highcharts.DrilldownCallbackFunction}
  407. * @since 3.0.8
  408. * @product highcharts highmaps
  409. * @context Highcharts.Chart
  410. * @requires modules/drilldown
  411. * @apioption chart.events.drilldown
  412. */
  413. /**
  414. * Fires when drilling up from a drilldown series.
  415. *
  416. * @type {Highcharts.DrillupCallbackFunction}
  417. * @since 3.0.8
  418. * @product highcharts highmaps
  419. * @context Highcharts.Chart
  420. * @requires modules/drilldown
  421. * @apioption chart.events.drillup
  422. */
  423. /**
  424. * In a chart with multiple drilldown series, this event fires after all the
  425. * series have been drilled up.
  426. *
  427. * @type {Highcharts.DrillupAllCallbackFunction}
  428. * @since 4.2.4
  429. * @product highcharts highmaps
  430. * @context Highcharts.Chart
  431. * @requires modules/drilldown
  432. * @apioption chart.events.drillupall
  433. */
  434. /**
  435. * The `id` of a series in the [drilldown.series](#drilldown.series) array to
  436. * use for a drilldown for this point.
  437. *
  438. * @sample {highcharts} highcharts/drilldown/basic/
  439. * Basic drilldown
  440. *
  441. * @type {string}
  442. * @since 3.0.8
  443. * @product highcharts
  444. * @requires modules/drilldown
  445. * @apioption series.line.data.drilldown
  446. */
  447. /**
  448. * A general fadeIn method.
  449. *
  450. * @requires module:modules/drilldown
  451. *
  452. * @function Highcharts.SVGElement#fadeIn
  453. *
  454. * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
  455. * The animation options for the element fade.
  456. */
  457. SVGRenderer.prototype.Element.prototype.fadeIn = function (animation) {
  458. this
  459. .attr({
  460. opacity: 0.1,
  461. visibility: 'inherit'
  462. })
  463. .animate({
  464. opacity: pick(this.newOpacity, 1) // newOpacity used in maps
  465. }, animation || {
  466. duration: 250
  467. });
  468. };
  469. /**
  470. * Add a series to the chart as drilldown from a specific point in the parent
  471. * series. This method is used for async drilldown, when clicking a point in a
  472. * series should result in loading and displaying a more high-resolution series.
  473. * When not async, the setup is simpler using the
  474. * [drilldown.series](https://api.highcharts.com/highcharts/drilldown.series)
  475. * options structure.
  476. *
  477. * @sample highcharts/drilldown/async/
  478. * Async drilldown
  479. *
  480. * @function Highcharts.Chart#addSeriesAsDrilldown
  481. *
  482. * @param {Highcharts.Point} point
  483. * The point from which the drilldown will start.
  484. *
  485. * @param {Highcharts.SeriesOptionsType} options
  486. * The series options for the new, detailed series.
  487. */
  488. Chart.prototype.addSeriesAsDrilldown = function (point, options) {
  489. this.addSingleSeriesAsDrilldown(point, options);
  490. this.applyDrilldown();
  491. };
  492. Chart.prototype.addSingleSeriesAsDrilldown = function (point, ddOptions) {
  493. var oldSeries = point.series,
  494. xAxis = oldSeries.xAxis,
  495. yAxis = oldSeries.yAxis,
  496. newSeries,
  497. pointIndex,
  498. levelSeries = [],
  499. levelSeriesOptions = [],
  500. level,
  501. levelNumber,
  502. last,
  503. colorProp;
  504. colorProp = this.styledMode ?
  505. { colorIndex: pick(point.colorIndex, oldSeries.colorIndex) } :
  506. { color: point.color || oldSeries.color };
  507. if (!this.drilldownLevels) {
  508. this.drilldownLevels = [];
  509. }
  510. levelNumber = oldSeries.options._levelNumber || 0;
  511. // See if we can reuse the registered series from last run
  512. last = this.drilldownLevels[this.drilldownLevels.length - 1];
  513. if (last && last.levelNumber !== levelNumber) {
  514. last = void 0;
  515. }
  516. ddOptions = extend(extend({
  517. _ddSeriesId: ddSeriesId++
  518. }, colorProp), ddOptions);
  519. pointIndex = oldSeries.points.indexOf(point);
  520. // Record options for all current series
  521. oldSeries.chart.series.forEach(function (series) {
  522. if (series.xAxis === xAxis && !series.isDrilling) {
  523. series.options._ddSeriesId =
  524. series.options._ddSeriesId || ddSeriesId++;
  525. series.options._colorIndex = series.userOptions._colorIndex;
  526. series.options._levelNumber =
  527. series.options._levelNumber || levelNumber; // #3182
  528. if (last) {
  529. levelSeries = last.levelSeries;
  530. levelSeriesOptions = last.levelSeriesOptions;
  531. }
  532. else {
  533. levelSeries.push(series);
  534. // (#10597)
  535. series.purgedOptions = merge({
  536. _ddSeriesId: series.options._ddSeriesId,
  537. _levelNumber: series.options._levelNumber,
  538. selected: series.options.selected
  539. }, series.userOptions);
  540. levelSeriesOptions.push(series.purgedOptions);
  541. }
  542. }
  543. });
  544. // Add a record of properties for each drilldown level
  545. level = extend({
  546. levelNumber: levelNumber,
  547. seriesOptions: oldSeries.options,
  548. seriesPurgedOptions: oldSeries.purgedOptions,
  549. levelSeriesOptions: levelSeriesOptions,
  550. levelSeries: levelSeries,
  551. shapeArgs: point.shapeArgs,
  552. // no graphic in line series with markers disabled
  553. bBox: point.graphic ? point.graphic.getBBox() : {},
  554. color: point.isNull ?
  555. new Color(colorProp.color).setOpacity(0).get() :
  556. colorProp.color,
  557. lowerSeriesOptions: ddOptions,
  558. pointOptions: oldSeries.options.data[pointIndex],
  559. pointIndex: pointIndex,
  560. oldExtremes: {
  561. xMin: xAxis && xAxis.userMin,
  562. xMax: xAxis && xAxis.userMax,
  563. yMin: yAxis && yAxis.userMin,
  564. yMax: yAxis && yAxis.userMax
  565. },
  566. resetZoomButton: this.resetZoomButton
  567. }, colorProp);
  568. // Push it to the lookup array
  569. this.drilldownLevels.push(level);
  570. // Reset names to prevent extending (#6704)
  571. if (xAxis && xAxis.names) {
  572. xAxis.names.length = 0;
  573. }
  574. newSeries = level.lowerSeries = this.addSeries(ddOptions, false);
  575. newSeries.options._levelNumber = levelNumber + 1;
  576. if (xAxis) {
  577. xAxis.oldPos = xAxis.pos;
  578. xAxis.userMin = xAxis.userMax = null;
  579. yAxis.userMin = yAxis.userMax = null;
  580. }
  581. // Run fancy cross-animation on supported and equal types
  582. if (oldSeries.type === newSeries.type) {
  583. newSeries.animate = newSeries.animateDrilldown || noop;
  584. newSeries.options.animation = true;
  585. }
  586. };
  587. Chart.prototype.applyDrilldown = function () {
  588. var drilldownLevels = this.drilldownLevels,
  589. levelToRemove;
  590. if (drilldownLevels && drilldownLevels.length > 0) { // #3352, async loading
  591. levelToRemove = drilldownLevels[drilldownLevels.length - 1].levelNumber;
  592. this.drilldownLevels.forEach(function (level) {
  593. if (level.levelNumber === levelToRemove) {
  594. level.levelSeries.forEach(function (series) {
  595. // Not removed, not added as part of a multi-series
  596. // drilldown
  597. if (series.options &&
  598. series.options._levelNumber === levelToRemove) {
  599. series.remove(false);
  600. }
  601. });
  602. }
  603. });
  604. }
  605. // We have a reset zoom button. Hide it and detatch it from the chart. It
  606. // is preserved to the layer config above.
  607. if (this.resetZoomButton) {
  608. this.resetZoomButton.hide();
  609. delete this.resetZoomButton;
  610. }
  611. this.pointer.reset();
  612. this.redraw();
  613. this.showDrillUpButton();
  614. fireEvent(this, 'afterDrilldown');
  615. };
  616. Chart.prototype.getDrilldownBackText = function () {
  617. var drilldownLevels = this.drilldownLevels,
  618. lastLevel;
  619. if (drilldownLevels && drilldownLevels.length > 0) { // #3352, async loading
  620. lastLevel = drilldownLevels[drilldownLevels.length - 1];
  621. lastLevel.series = lastLevel.seriesOptions;
  622. return format(this.options.lang.drillUpText, lastLevel);
  623. }
  624. };
  625. Chart.prototype.showDrillUpButton = function () {
  626. var chart = this,
  627. backText = this.getDrilldownBackText(),
  628. buttonOptions = chart.options.drilldown.drillUpButton,
  629. attr,
  630. states;
  631. if (!this.drillUpButton) {
  632. attr = buttonOptions.theme;
  633. states = attr && attr.states;
  634. this.drillUpButton = this.renderer.button(backText, null, null, function () {
  635. chart.drillUp();
  636. }, attr, states && states.hover, states && states.select)
  637. .addClass('highcharts-drillup-button')
  638. .attr({
  639. align: buttonOptions.position.align,
  640. zIndex: 7
  641. })
  642. .add()
  643. .align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');
  644. }
  645. else {
  646. this.drillUpButton.attr({
  647. text: backText
  648. })
  649. .align();
  650. }
  651. };
  652. /**
  653. * When the chart is drilled down to a child series, calling `chart.drillUp()`
  654. * will drill up to the parent series.
  655. *
  656. * @requires modules/drilldown
  657. *
  658. * @function Highcharts.Chart#drillUp
  659. */
  660. Chart.prototype.drillUp = function () {
  661. if (!this.drilldownLevels || this.drilldownLevels.length === 0) {
  662. return;
  663. }
  664. var chart = this,
  665. drilldownLevels = chart.drilldownLevels,
  666. levelNumber = drilldownLevels[drilldownLevels.length - 1].levelNumber,
  667. i = drilldownLevels.length,
  668. chartSeries = chart.series,
  669. seriesI,
  670. level,
  671. oldSeries,
  672. newSeries,
  673. oldExtremes,
  674. addSeries = function (seriesOptions) {
  675. var addedSeries;
  676. chartSeries.forEach(function (series) {
  677. if (series.options._ddSeriesId === seriesOptions._ddSeriesId) {
  678. addedSeries = series;
  679. }
  680. });
  681. addedSeries = addedSeries || chart.addSeries(seriesOptions, false);
  682. if (addedSeries.type === oldSeries.type &&
  683. addedSeries.animateDrillupTo) {
  684. addedSeries.animate = addedSeries.animateDrillupTo;
  685. }
  686. if (seriesOptions === level.seriesPurgedOptions) {
  687. newSeries = addedSeries;
  688. }
  689. };
  690. while (i--) {
  691. level = drilldownLevels[i];
  692. if (level.levelNumber === levelNumber) {
  693. drilldownLevels.pop();
  694. // Get the lower series by reference or id
  695. oldSeries = level.lowerSeries;
  696. if (!oldSeries.chart) { // #2786
  697. seriesI = chartSeries.length; // #2919
  698. while (seriesI--) {
  699. if (chartSeries[seriesI].options.id ===
  700. level.lowerSeriesOptions.id &&
  701. chartSeries[seriesI].options._levelNumber ===
  702. levelNumber + 1) { // #3867
  703. oldSeries = chartSeries[seriesI];
  704. break;
  705. }
  706. }
  707. }
  708. oldSeries.xData = []; // Overcome problems with minRange (#2898)
  709. level.levelSeriesOptions.forEach(addSeries);
  710. fireEvent(chart, 'drillup', {
  711. seriesOptions: level.seriesPurgedOptions ||
  712. level.seriesOptions
  713. });
  714. if (newSeries.type === oldSeries.type) {
  715. newSeries.drilldownLevel = level;
  716. newSeries.options.animation =
  717. chart.options.drilldown.animation;
  718. if (oldSeries.animateDrillupFrom && oldSeries.chart) { // #2919
  719. oldSeries.animateDrillupFrom(level);
  720. }
  721. }
  722. newSeries.options._levelNumber = levelNumber;
  723. oldSeries.remove(false);
  724. // Reset the zoom level of the upper series
  725. if (newSeries.xAxis) {
  726. oldExtremes = level.oldExtremes;
  727. newSeries.xAxis.setExtremes(oldExtremes.xMin, oldExtremes.xMax, false);
  728. newSeries.yAxis.setExtremes(oldExtremes.yMin, oldExtremes.yMax, false);
  729. }
  730. // We have a resetZoomButton tucked away for this level. Attatch
  731. // it to the chart and show it.
  732. if (level.resetZoomButton) {
  733. chart.resetZoomButton = level.resetZoomButton;
  734. chart.resetZoomButton.show();
  735. }
  736. }
  737. }
  738. this.redraw();
  739. if (this.drilldownLevels.length === 0) {
  740. this.drillUpButton = this.drillUpButton.destroy();
  741. }
  742. else {
  743. this.drillUpButton.attr({
  744. text: this.getDrilldownBackText()
  745. })
  746. .align();
  747. }
  748. this.ddDupes.length = []; // #3315
  749. // Fire a once-off event after all series have been drilled up (#5158)
  750. fireEvent(chart, 'drillupall');
  751. };
  752. /* eslint-disable no-invalid-this */
  753. // Add update function to be called internally from Chart.update
  754. // (#7600, #12855)
  755. addEvent(Chart, 'afterInit', function () {
  756. var chart = this;
  757. chart.drilldown = {
  758. update: function (options, redraw) {
  759. merge(true, chart.options.drilldown, options);
  760. if (pick(redraw, true)) {
  761. chart.redraw();
  762. }
  763. }
  764. };
  765. });
  766. // Don't show the reset button if we already are displaying the drillUp button.
  767. addEvent(Chart, 'beforeShowResetZoom', function () {
  768. if (this.drillUpButton) {
  769. return false;
  770. }
  771. });
  772. addEvent(Chart, 'render', function () {
  773. (this.xAxis || []).forEach(function (axis) {
  774. axis.ddPoints = {};
  775. axis.series.forEach(function (series) {
  776. var i,
  777. xData = series.xData || [],
  778. points = series.points,
  779. p;
  780. for (i = 0; i < xData.length; i++) {
  781. p = series.options.data[i];
  782. // The `drilldown` property can only be set on an array or an
  783. // object
  784. if (typeof p !== 'number') {
  785. // Convert array to object (#8008)
  786. p = series.pointClass.prototype.optionsToObject
  787. .call({ series: series }, p);
  788. if (p.drilldown) {
  789. if (!axis.ddPoints[xData[i]]) {
  790. axis.ddPoints[xData[i]] = [];
  791. }
  792. axis.ddPoints[xData[i]].push(points ? points[i] : true);
  793. }
  794. }
  795. }
  796. });
  797. // Add drillability to ticks, and always keep it drillability updated
  798. // (#3951)
  799. objectEach(axis.ticks, Tick.prototype.drillable);
  800. });
  801. });
  802. /**
  803. * When drilling up, keep the upper series invisible until the lower series has
  804. * moved into place.
  805. *
  806. * @private
  807. * @function Highcharts.ColumnSeries#animateDrillupTo
  808. * @param {boolean} [init=false]
  809. * Whether to initialize animation
  810. */
  811. ColumnSeries.prototype.animateDrillupTo = function (init) {
  812. if (!init) {
  813. var newSeries = this,
  814. level = newSeries.drilldownLevel;
  815. // First hide all items before animating in again
  816. this.points.forEach(function (point) {
  817. var dataLabel = point.dataLabel;
  818. if (point.graphic) { // #3407
  819. point.graphic.hide();
  820. }
  821. if (dataLabel) {
  822. // The data label is initially hidden, make sure it is not faded
  823. // in (#6127)
  824. dataLabel.hidden = dataLabel.attr('visibility') === 'hidden';
  825. if (!dataLabel.hidden) {
  826. dataLabel.hide();
  827. if (point.connector) {
  828. point.connector.hide();
  829. }
  830. }
  831. }
  832. });
  833. // Do dummy animation on first point to get to complete
  834. syncTimeout(function () {
  835. if (newSeries.points) { // May be destroyed in the meantime, #3389
  836. newSeries.points.forEach(function (point, i) {
  837. // Fade in other points
  838. var verb = i === (level && level.pointIndex) ? 'show' : 'fadeIn', inherit = verb === 'show' ? true : void 0, dataLabel = point.dataLabel;
  839. if (point.graphic) { // #3407
  840. point.graphic[verb](inherit);
  841. }
  842. if (dataLabel && !dataLabel.hidden) { // #6127
  843. dataLabel.fadeIn(); // #7384
  844. if (point.connector) {
  845. point.connector.fadeIn();
  846. }
  847. }
  848. });
  849. }
  850. }, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));
  851. // Reset to prototype
  852. delete this.animate;
  853. }
  854. };
  855. ColumnSeries.prototype.animateDrilldown = function (init) {
  856. var series = this,
  857. chart = this.chart,
  858. drilldownLevels = chart.drilldownLevels,
  859. animateFrom,
  860. animationOptions = animObject(chart.options.drilldown.animation),
  861. xAxis = this.xAxis,
  862. styledMode = chart.styledMode;
  863. if (!init) {
  864. drilldownLevels.forEach(function (level) {
  865. if (series.options._ddSeriesId ===
  866. level.lowerSeriesOptions._ddSeriesId) {
  867. animateFrom = level.shapeArgs;
  868. if (!styledMode) {
  869. // Add the point colors to animate from
  870. animateFrom.fill = level.color;
  871. }
  872. }
  873. });
  874. animateFrom.x += pick(xAxis.oldPos, xAxis.pos) - xAxis.pos;
  875. this.points.forEach(function (point) {
  876. var animateTo = point.shapeArgs;
  877. if (!styledMode) {
  878. // Add the point colors to animate to
  879. animateTo.fill = point.color;
  880. }
  881. if (point.graphic) {
  882. point.graphic
  883. .attr(animateFrom)
  884. .animate(extend(point.shapeArgs, { fill: point.color || series.color }), animationOptions);
  885. }
  886. if (point.dataLabel) {
  887. point.dataLabel.fadeIn(animationOptions);
  888. }
  889. });
  890. // Reset to prototype
  891. delete this.animate;
  892. }
  893. };
  894. /**
  895. * When drilling up, pull out the individual point graphics from the lower
  896. * series and animate them into the origin point in the upper series.
  897. *
  898. * @private
  899. * @function Highcharts.ColumnSeries#animateDrillupFrom
  900. * @param {Highcharts.DrilldownLevelObject} level
  901. * Level container
  902. * @return {void}
  903. */
  904. ColumnSeries.prototype.animateDrillupFrom = function (level) {
  905. var animationOptions = animObject(this.chart.options.drilldown.animation),
  906. group = this.group,
  907. // For 3d column series all columns are added to one group
  908. // so we should not delete the whole group. #5297
  909. removeGroup = group !== this.chart.columnGroup,
  910. series = this;
  911. // Cancel mouse events on the series group (#2787)
  912. series.trackerGroups.forEach(function (key) {
  913. if (series[key]) { // we don't always have dataLabelsGroup
  914. series[key].on('mouseover');
  915. }
  916. });
  917. if (removeGroup) {
  918. delete this.group;
  919. }
  920. this.points.forEach(function (point) {
  921. var graphic = point.graphic,
  922. animateTo = level.shapeArgs,
  923. complete = function () {
  924. graphic.destroy();
  925. if (group && removeGroup) {
  926. group = group.destroy();
  927. }
  928. };
  929. if (graphic && animateTo) {
  930. delete point.graphic;
  931. if (!series.chart.styledMode) {
  932. animateTo.fill = level.color;
  933. }
  934. if (animationOptions.duration) {
  935. graphic.animate(animateTo, merge(animationOptions, { complete: complete }));
  936. }
  937. else {
  938. graphic.attr(animateTo);
  939. complete();
  940. }
  941. }
  942. });
  943. };
  944. if (PieSeries) {
  945. extend(PieSeries.prototype, {
  946. animateDrillupTo: ColumnSeries.prototype.animateDrillupTo,
  947. animateDrillupFrom: ColumnSeries.prototype.animateDrillupFrom,
  948. animateDrilldown: function (init) {
  949. var level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],
  950. animationOptions = this.chart.options.drilldown.animation;
  951. if (this.is('item')) {
  952. animationOptions.duration = 0;
  953. }
  954. // Unable to drill down in the horizontal item series #13372
  955. if (this.center) {
  956. var animateFrom = level.shapeArgs,
  957. start = animateFrom.start,
  958. angle = animateFrom.end - start,
  959. startAngle = angle / this.points.length,
  960. styledMode = this.chart.styledMode;
  961. if (!init) {
  962. this.points.forEach(function (point, i) {
  963. var animateTo = point.shapeArgs;
  964. if (!styledMode) {
  965. animateFrom.fill = level.color;
  966. animateTo.fill = point.color;
  967. }
  968. if (point.graphic) {
  969. point.graphic
  970. .attr(merge(animateFrom, {
  971. start: start + i * startAngle,
  972. end: start + (i + 1) * startAngle
  973. }))[animationOptions ? 'animate' : 'attr'](animateTo, animationOptions);
  974. }
  975. });
  976. // Reset to prototype
  977. delete this.animate;
  978. }
  979. }
  980. }
  981. });
  982. }
  983. Point.prototype.doDrilldown = function (_holdRedraw, category, originalEvent) {
  984. var series = this.series,
  985. chart = series.chart,
  986. drilldown = chart.options.drilldown,
  987. i = (drilldown.series || []).length,
  988. seriesOptions;
  989. if (!chart.ddDupes) {
  990. chart.ddDupes = [];
  991. }
  992. while (i-- && !seriesOptions) {
  993. if (drilldown.series[i].id === this.drilldown &&
  994. chart.ddDupes.indexOf(this.drilldown) === -1) {
  995. seriesOptions = drilldown.series[i];
  996. chart.ddDupes.push(this.drilldown);
  997. }
  998. }
  999. // Fire the event. If seriesOptions is undefined, the implementer can check
  1000. // for seriesOptions, and call addSeriesAsDrilldown async if necessary.
  1001. fireEvent(chart, 'drilldown', {
  1002. point: this,
  1003. seriesOptions: seriesOptions,
  1004. category: category,
  1005. originalEvent: originalEvent,
  1006. points: (typeof category !== 'undefined' &&
  1007. this.series.xAxis.getDDPoints(category).slice(0))
  1008. }, function (e) {
  1009. var chart = e.point.series && e.point.series.chart,
  1010. seriesOptions = e.seriesOptions;
  1011. if (chart && seriesOptions) {
  1012. if (_holdRedraw) {
  1013. chart.addSingleSeriesAsDrilldown(e.point, seriesOptions);
  1014. }
  1015. else {
  1016. chart.addSeriesAsDrilldown(e.point, seriesOptions);
  1017. }
  1018. }
  1019. });
  1020. };
  1021. /**
  1022. * Drill down to a given category. This is the same as clicking on an axis
  1023. * label.
  1024. *
  1025. * @private
  1026. * @function Highcharts.Axis#drilldownCategory
  1027. * @param {number} x
  1028. * Tick position
  1029. * @param {global.MouseEvent} e
  1030. * Click event
  1031. */
  1032. H.Axis.prototype.drilldownCategory = function (x, e) {
  1033. objectEach(this.getDDPoints(x), function (point) {
  1034. if (point &&
  1035. point.series &&
  1036. point.series.visible &&
  1037. point.doDrilldown) { // #3197
  1038. point.doDrilldown(true, x, e);
  1039. }
  1040. });
  1041. this.chart.applyDrilldown();
  1042. };
  1043. /**
  1044. * Return drillable points for this specific X value.
  1045. *
  1046. * @private
  1047. * @function Highcharts.Axis#getDDPoints
  1048. * @param {number} x
  1049. * Tick position
  1050. * @return {Array<(boolean|Highcharts.Point)>|undefined}
  1051. * Drillable points
  1052. */
  1053. H.Axis.prototype.getDDPoints = function (x) {
  1054. return this.ddPoints && this.ddPoints[x];
  1055. };
  1056. /**
  1057. * Make a tick label drillable, or remove drilling on update.
  1058. *
  1059. * @private
  1060. * @function Highcharts.Axis#drillable
  1061. */
  1062. Tick.prototype.drillable = function () {
  1063. var pos = this.pos,
  1064. label = this.label,
  1065. axis = this.axis,
  1066. isDrillable = axis.coll === 'xAxis' && axis.getDDPoints,
  1067. ddPointsX = isDrillable && axis.getDDPoints(pos),
  1068. styledMode = axis.chart.styledMode;
  1069. if (isDrillable) {
  1070. if (label && ddPointsX && ddPointsX.length) {
  1071. label.drillable = true;
  1072. if (!label.basicStyles && !styledMode) {
  1073. label.basicStyles = merge(label.styles);
  1074. }
  1075. label.addClass('highcharts-drilldown-axis-label');
  1076. // #12656 - avoid duplicate of attach event
  1077. if (label.removeOnDrillableClick) {
  1078. removeEvent(label.element, 'click');
  1079. }
  1080. label.removeOnDrillableClick = addEvent(label.element, 'click', function (e) {
  1081. e.preventDefault();
  1082. axis.drilldownCategory(pos, e);
  1083. });
  1084. if (!styledMode) {
  1085. label.css(axis.chart.options.drilldown.activeAxisLabelStyle);
  1086. }
  1087. }
  1088. else if (label && label.drillable && label.removeOnDrillableClick) {
  1089. if (!styledMode) {
  1090. label.styles = {}; // reset for full overwrite of styles
  1091. label.css(label.basicStyles);
  1092. }
  1093. label.removeOnDrillableClick(); // #3806
  1094. label.removeClass('highcharts-drilldown-axis-label');
  1095. }
  1096. }
  1097. };
  1098. // On initialization of each point, identify its label and make it clickable.
  1099. // Also, provide a list of points associated to that label.
  1100. addEvent(Point, 'afterInit', function () {
  1101. var point = this,
  1102. series = point.series;
  1103. if (point.drilldown) {
  1104. // Add the click event to the point
  1105. addEvent(point, 'click', function (e) {
  1106. if (series.xAxis &&
  1107. series.chart.options.drilldown.allowPointDrilldown ===
  1108. false) {
  1109. // #5822, x changed
  1110. series.xAxis.drilldownCategory(point.x, e);
  1111. }
  1112. else {
  1113. point.doDrilldown(void 0, void 0, e);
  1114. }
  1115. });
  1116. }
  1117. return point;
  1118. });
  1119. addEvent(H.Series, 'afterDrawDataLabels', function () {
  1120. var css = this.chart.options.drilldown.activeDataLabelStyle,
  1121. renderer = this.chart.renderer,
  1122. styledMode = this.chart.styledMode;
  1123. this.points.forEach(function (point) {
  1124. var dataLabelsOptions = point.options.dataLabels,
  1125. pointCSS = pick(point.dlOptions,
  1126. dataLabelsOptions && dataLabelsOptions.style, {});
  1127. if (point.drilldown && point.dataLabel) {
  1128. if (css.color === 'contrast' && !styledMode) {
  1129. pointCSS.color = renderer.getContrast(point.color || this.color);
  1130. }
  1131. if (dataLabelsOptions && dataLabelsOptions.color) {
  1132. pointCSS.color = dataLabelsOptions.color;
  1133. }
  1134. point.dataLabel
  1135. .addClass('highcharts-drilldown-data-label');
  1136. if (!styledMode) {
  1137. point.dataLabel
  1138. .css(css)
  1139. .css(pointCSS);
  1140. }
  1141. }
  1142. }, this);
  1143. });
  1144. var applyCursorCSS = function (element,
  1145. cursor,
  1146. addClass,
  1147. styledMode) {
  1148. element[addClass ? 'addClass' : 'removeClass']('highcharts-drilldown-point');
  1149. if (!styledMode) {
  1150. element.css({ cursor: cursor });
  1151. }
  1152. };
  1153. // Mark the trackers with a pointer
  1154. addEvent(H.Series, 'afterDrawTracker', function () {
  1155. var styledMode = this.chart.styledMode;
  1156. this.points.forEach(function (point) {
  1157. if (point.drilldown && point.graphic) {
  1158. applyCursorCSS(point.graphic, 'pointer', true, styledMode);
  1159. }
  1160. });
  1161. });
  1162. addEvent(Point, 'afterSetState', function () {
  1163. var styledMode = this.series.chart.styledMode;
  1164. if (this.drilldown && this.series.halo && this.state === 'hover') {
  1165. applyCursorCSS(this.series.halo, 'pointer', true, styledMode);
  1166. }
  1167. else if (this.series.halo) {
  1168. applyCursorCSS(this.series.halo, 'auto', false, styledMode);
  1169. }
  1170. });
  1171. });
  1172. _registerModule(_modules, 'masters/modules/drilldown.src.js', [], function () {
  1173. });
  1174. }));