drilldown.src.js 47 KB

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