Drilldown.js 42 KB

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