treemap.src.js 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920
  1. /**
  2. * @license Highcharts JS v7.0.2 (2019-01-17)
  3. *
  4. * (c) 2014-2019 Highsoft AS
  5. * Authors: Jon Arild Nygard / Oystein Moseng
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. factory['default'] = factory;
  13. module.exports = factory;
  14. } else if (typeof define === 'function' && define.amd) {
  15. define(function () {
  16. return factory;
  17. });
  18. } else {
  19. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  20. }
  21. }(function (Highcharts) {
  22. var result = (function (H) {
  23. var extend = H.extend,
  24. isArray = H.isArray,
  25. isBoolean = function (x) {
  26. return typeof x === 'boolean';
  27. },
  28. isFn = function (x) {
  29. return typeof x === 'function';
  30. },
  31. isObject = H.isObject,
  32. isNumber = H.isNumber,
  33. merge = H.merge,
  34. pick = H.pick;
  35. // TODO Combine buildTree and buildNode with setTreeValues
  36. // TODO Remove logic from Treemap and make it utilize this mixin.
  37. var setTreeValues = function setTreeValues(tree, options) {
  38. var before = options.before,
  39. idRoot = options.idRoot,
  40. mapIdToNode = options.mapIdToNode,
  41. nodeRoot = mapIdToNode[idRoot],
  42. levelIsConstant = (
  43. isBoolean(options.levelIsConstant) ?
  44. options.levelIsConstant :
  45. true
  46. ),
  47. points = options.points,
  48. point = points[tree.i],
  49. optionsPoint = point && point.options || {},
  50. childrenTotal = 0,
  51. children = [],
  52. value;
  53. extend(tree, {
  54. levelDynamic: tree.level - (levelIsConstant ? 0 : nodeRoot.level),
  55. name: pick(point && point.name, ''),
  56. visible: (
  57. idRoot === tree.id ||
  58. (isBoolean(options.visible) ? options.visible : false)
  59. )
  60. });
  61. if (isFn(before)) {
  62. tree = before(tree, options);
  63. }
  64. // First give the children some values
  65. tree.children.forEach(function (child, i) {
  66. var newOptions = extend({}, options);
  67. extend(newOptions, {
  68. index: i,
  69. siblings: tree.children.length,
  70. visible: tree.visible
  71. });
  72. child = setTreeValues(child, newOptions);
  73. children.push(child);
  74. if (child.visible) {
  75. childrenTotal += child.val;
  76. }
  77. });
  78. tree.visible = childrenTotal > 0 || tree.visible;
  79. // Set the values
  80. value = pick(optionsPoint.value, childrenTotal);
  81. extend(tree, {
  82. children: children,
  83. childrenTotal: childrenTotal,
  84. isLeaf: tree.visible && !childrenTotal,
  85. val: value
  86. });
  87. return tree;
  88. };
  89. var getColor = function getColor(node, options) {
  90. var index = options.index,
  91. mapOptionsToLevel = options.mapOptionsToLevel,
  92. parentColor = options.parentColor,
  93. parentColorIndex = options.parentColorIndex,
  94. series = options.series,
  95. colors = options.colors,
  96. siblings = options.siblings,
  97. points = series.points,
  98. getColorByPoint,
  99. chartOptionsChart = series.chart.options.chart,
  100. point,
  101. level,
  102. colorByPoint,
  103. colorIndexByPoint,
  104. color,
  105. colorIndex;
  106. function variation(color) {
  107. var colorVariation = level && level.colorVariation;
  108. if (colorVariation) {
  109. if (colorVariation.key === 'brightness') {
  110. return H.color(color).brighten(
  111. colorVariation.to * (index / siblings)
  112. ).get();
  113. }
  114. }
  115. return color;
  116. }
  117. if (node) {
  118. point = points[node.i];
  119. level = mapOptionsToLevel[node.level] || {};
  120. getColorByPoint = point && level.colorByPoint;
  121. if (getColorByPoint) {
  122. colorIndexByPoint = point.index % (colors ?
  123. colors.length :
  124. chartOptionsChart.colorCount
  125. );
  126. colorByPoint = colors && colors[colorIndexByPoint];
  127. }
  128. // Select either point color, level color or inherited color.
  129. if (!series.chart.styledMode) {
  130. color = pick(
  131. point && point.options.color,
  132. level && level.color,
  133. colorByPoint,
  134. parentColor && variation(parentColor),
  135. series.color
  136. );
  137. }
  138. colorIndex = pick(
  139. point && point.options.colorIndex,
  140. level && level.colorIndex,
  141. colorIndexByPoint,
  142. parentColorIndex,
  143. options.colorIndex
  144. );
  145. }
  146. return {
  147. color: color,
  148. colorIndex: colorIndex
  149. };
  150. };
  151. /**
  152. * Creates a map from level number to its given options.
  153. *
  154. * @private
  155. * @function getLevelOptions
  156. *
  157. * @param {object} params
  158. * Object containing parameters.
  159. * - `defaults` Object containing default options. The default options
  160. * are merged with the userOptions to get the final options for a
  161. * specific level.
  162. * - `from` The lowest level number.
  163. * - `levels` User options from series.levels.
  164. * - `to` The highest level number.
  165. *
  166. * @return {Highcharts.Dictionary<object>}
  167. * Returns a map from level number to its given options.
  168. */
  169. var getLevelOptions = function getLevelOptions(params) {
  170. var result = null,
  171. defaults,
  172. converted,
  173. i,
  174. from,
  175. to,
  176. levels;
  177. if (isObject(params)) {
  178. result = {};
  179. from = isNumber(params.from) ? params.from : 1;
  180. levels = params.levels;
  181. converted = {};
  182. defaults = isObject(params.defaults) ? params.defaults : {};
  183. if (isArray(levels)) {
  184. converted = levels.reduce(function (obj, item) {
  185. var level,
  186. levelIsConstant,
  187. options;
  188. if (isObject(item) && isNumber(item.level)) {
  189. options = merge({}, item);
  190. levelIsConstant = (
  191. isBoolean(options.levelIsConstant) ?
  192. options.levelIsConstant :
  193. defaults.levelIsConstant
  194. );
  195. // Delete redundant properties.
  196. delete options.levelIsConstant;
  197. delete options.level;
  198. // Calculate which level these options apply to.
  199. level = item.level + (levelIsConstant ? 0 : from - 1);
  200. if (isObject(obj[level])) {
  201. extend(obj[level], options);
  202. } else {
  203. obj[level] = options;
  204. }
  205. }
  206. return obj;
  207. }, {});
  208. }
  209. to = isNumber(params.to) ? params.to : 1;
  210. for (i = 0; i <= to; i++) {
  211. result[i] = merge(
  212. {},
  213. defaults,
  214. isObject(converted[i]) ? converted[i] : {}
  215. );
  216. }
  217. }
  218. return result;
  219. };
  220. /**
  221. * Update the rootId property on the series. Also makes sure that it is
  222. * accessible to exporting.
  223. *
  224. * @private
  225. * @function updateRootId
  226. *
  227. * @param {object} series
  228. * The series to operate on.
  229. *
  230. * @return {string}
  231. * Returns the resulting rootId after update.
  232. */
  233. var updateRootId = function (series) {
  234. var rootId,
  235. options;
  236. if (isObject(series)) {
  237. // Get the series options.
  238. options = isObject(series.options) ? series.options : {};
  239. // Calculate the rootId.
  240. rootId = pick(series.rootNode, options.rootId, '');
  241. // Set rootId on series.userOptions to pick it up in exporting.
  242. if (isObject(series.userOptions)) {
  243. series.userOptions.rootId = rootId;
  244. }
  245. // Set rootId on series to pick it up on next update.
  246. series.rootNode = rootId;
  247. }
  248. return rootId;
  249. };
  250. var result = {
  251. getColor: getColor,
  252. getLevelOptions: getLevelOptions,
  253. setTreeValues: setTreeValues,
  254. updateRootId: updateRootId
  255. };
  256. return result;
  257. }(Highcharts));
  258. (function (H, mixinTreeSeries) {
  259. /* *
  260. * (c) 2014-2019 Highsoft AS
  261. *
  262. * Authors: Jon Arild Nygard / Oystein Moseng
  263. *
  264. * License: www.highcharts.com/license
  265. */
  266. var seriesType = H.seriesType,
  267. seriesTypes = H.seriesTypes,
  268. merge = H.merge,
  269. extend = H.extend,
  270. noop = H.noop,
  271. getColor = mixinTreeSeries.getColor,
  272. getLevelOptions = mixinTreeSeries.getLevelOptions,
  273. isArray = H.isArray,
  274. isBoolean = function (x) {
  275. return typeof x === 'boolean';
  276. },
  277. isNumber = H.isNumber,
  278. isObject = H.isObject,
  279. isString = H.isString,
  280. pick = H.pick,
  281. Series = H.Series,
  282. stableSort = H.stableSort,
  283. color = H.Color,
  284. eachObject = function (list, func, context) {
  285. context = context || this;
  286. H.objectEach(list, function (val, key) {
  287. func.call(context, val, key, list);
  288. });
  289. },
  290. // @todo find correct name for this function.
  291. // @todo Similar to reduce, this function is likely redundant
  292. recursive = function (item, func, context) {
  293. var next;
  294. context = context || this;
  295. next = func.call(context, item);
  296. if (next !== false) {
  297. recursive(next, func, context);
  298. }
  299. },
  300. updateRootId = mixinTreeSeries.updateRootId;
  301. /**
  302. * @private
  303. * @class
  304. * @name Highcharts.seriesTypes.treemap
  305. *
  306. * @augments Highcharts.Series
  307. */
  308. seriesType(
  309. 'treemap',
  310. 'scatter'
  311. /**
  312. * A treemap displays hierarchical data using nested rectangles. The data
  313. * can be laid out in varying ways depending on options.
  314. *
  315. * @sample highcharts/demo/treemap-large-dataset/
  316. * Treemap
  317. *
  318. * @extends plotOptions.scatter
  319. * @excluding marker, jitter
  320. * @product highcharts
  321. * @optionparent plotOptions.treemap
  322. */
  323. , {
  324. /**
  325. * When enabled the user can click on a point which is a parent and
  326. * zoom in on its children.
  327. *
  328. * @sample {highcharts} highcharts/plotoptions/treemap-allowdrilltonode/
  329. * Enabled
  330. *
  331. * @type {boolean}
  332. * @default false
  333. * @since 4.1.0
  334. * @product highcharts
  335. * @apioption plotOptions.treemap.allowDrillToNode
  336. */
  337. /**
  338. * When the series contains less points than the crop threshold, all
  339. * points are drawn, event if the points fall outside the visible plot
  340. * area at the current zoom. The advantage of drawing all points
  341. * (including markers and columns), is that animation is performed on
  342. * updates. On the other hand, when the series contains more points than
  343. * the crop threshold, the series data is cropped to only contain points
  344. * that fall within the plot area. The advantage of cropping away
  345. * invisible points is to increase performance on large series.
  346. *
  347. * @type {number}
  348. * @default 300
  349. * @since 4.1.0
  350. * @product highcharts
  351. * @apioption plotOptions.treemap.cropThreshold
  352. */
  353. /**
  354. * This option decides if the user can interact with the parent nodes
  355. * or just the leaf nodes. When this option is undefined, it will be
  356. * true by default. However when allowDrillToNode is true, then it will
  357. * be false by default.
  358. *
  359. * @sample {highcharts} highcharts/plotoptions/treemap-interactbyleaf-false/
  360. * False
  361. * @sample {highcharts} highcharts/plotoptions/treemap-interactbyleaf-true-and-allowdrilltonode/
  362. * InteractByLeaf and allowDrillToNode is true
  363. *
  364. * @type {boolean}
  365. * @since 4.1.2
  366. * @product highcharts
  367. * @apioption plotOptions.treemap.interactByLeaf
  368. */
  369. /**
  370. * The sort index of the point inside the treemap level.
  371. *
  372. * @sample {highcharts} highcharts/plotoptions/treemap-sortindex/
  373. * Sort by years
  374. *
  375. * @type {number}
  376. * @since 4.1.10
  377. * @product highcharts
  378. * @apioption plotOptions.treemap.sortIndex
  379. */
  380. /**
  381. * When using automatic point colors pulled from the `options.colors`
  382. * collection, this option determines whether the chart should receive
  383. * one color per series or one color per point.
  384. *
  385. * @see [series colors](#plotOptions.treemap.colors)
  386. *
  387. * @type {boolean}
  388. * @default false
  389. * @since 2.0
  390. * @product highcharts
  391. * @apioption plotOptions.treemap.colorByPoint
  392. */
  393. /**
  394. * A series specific or series type specific color set to apply instead
  395. * of the global [colors](#colors) when
  396. * [colorByPoint](#plotOptions.treemap.colorByPoint) is true.
  397. *
  398. * @type {Array<Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject>}
  399. * @since 3.0
  400. * @product highcharts
  401. * @apioption plotOptions.treemap.colors
  402. */
  403. /**
  404. * Whether to display this series type or specific series item in the
  405. * legend.
  406. */
  407. showInLegend: false,
  408. /**
  409. * @ignore
  410. */
  411. marker: false,
  412. colorByPoint: false,
  413. /**
  414. * @extends plotOptions.heatmap.dataLabels
  415. * @since 4.1.0
  416. */
  417. dataLabels: {
  418. enabled: true,
  419. defer: false,
  420. verticalAlign: 'middle',
  421. formatter: function () {
  422. var point = this && this.point ? this.point : {},
  423. name = isString(point.name) ? point.name : '';
  424. return name;
  425. },
  426. inside: true
  427. },
  428. tooltip: {
  429. headerFormat: '',
  430. pointFormat: '<b>{point.name}</b>: {point.value}<br/>'
  431. },
  432. /**
  433. * Whether to ignore hidden points when the layout algorithm runs.
  434. * If `false`, hidden points will leave open spaces.
  435. *
  436. * @since 5.0.8
  437. */
  438. ignoreHiddenPoint: true,
  439. /**
  440. * This option decides which algorithm is used for setting position
  441. * and dimensions of the points.
  442. *
  443. * @see [How to write your own algorithm](https://www.highcharts.com/docs/chart-and-series-types/treemap)
  444. *
  445. * @sample {highcharts} highcharts/plotoptions/treemap-layoutalgorithm-sliceanddice/
  446. * SliceAndDice by default
  447. * @sample {highcharts} highcharts/plotoptions/treemap-layoutalgorithm-stripes/
  448. * Stripes
  449. * @sample {highcharts} highcharts/plotoptions/treemap-layoutalgorithm-squarified/
  450. * Squarified
  451. * @sample {highcharts} highcharts/plotoptions/treemap-layoutalgorithm-strip/
  452. * Strip
  453. *
  454. * @since 4.1.0
  455. * @validvalue ["sliceAndDice", "stripes", "squarified", "strip"]
  456. */
  457. layoutAlgorithm: 'sliceAndDice',
  458. /**
  459. * Defines which direction the layout algorithm will start drawing.
  460. *
  461. * @since 4.1.0
  462. * @validvalue ["vertical", "horizontal"]
  463. */
  464. layoutStartingDirection: 'vertical',
  465. /**
  466. * Enabling this option will make the treemap alternate the drawing
  467. * direction between vertical and horizontal. The next levels starting
  468. * direction will always be the opposite of the previous.
  469. *
  470. * @sample {highcharts} highcharts/plotoptions/treemap-alternatestartingdirection-true/
  471. * Enabled
  472. *
  473. * @since 4.1.0
  474. */
  475. alternateStartingDirection: false,
  476. /**
  477. * Used together with the levels and allowDrillToNode options. When
  478. * set to false the first level visible when drilling is considered
  479. * to be level one. Otherwise the level will be the same as the tree
  480. * structure.
  481. *
  482. * @since 4.1.0
  483. */
  484. levelIsConstant: true,
  485. /**
  486. * Options for the button appearing when drilling down in a treemap.
  487. */
  488. drillUpButton: {
  489. /**
  490. * The position of the button.
  491. */
  492. position: {
  493. /**
  494. * Vertical alignment of the button.
  495. *
  496. * @default top
  497. * @validvalue ["top", "middle", "bottom"]
  498. * @product highcharts
  499. * @apioption plotOptions.treemap.drillUpButton.position.verticalAlign
  500. */
  501. /**
  502. * Horizontal alignment of the button.
  503. *
  504. * @validvalue ["left", "center", "right"]
  505. */
  506. align: 'right',
  507. /**
  508. * Horizontal offset of the button.
  509. */
  510. x: -10,
  511. /**
  512. * Vertical offset of the button.
  513. */
  514. y: 10
  515. }
  516. },
  517. /**
  518. * Set options on specific levels. Takes precedence over series options,
  519. * but not point options.
  520. *
  521. * @sample {highcharts} highcharts/plotoptions/treemap-levels/
  522. * Styling dataLabels and borders
  523. * @sample {highcharts} highcharts/demo/treemap-with-levels/
  524. * Different layoutAlgorithm
  525. *
  526. * @type {Array<*>}
  527. * @since 4.1.0
  528. * @product highcharts
  529. * @apioption plotOptions.treemap.levels
  530. */
  531. /**
  532. * Can set a `borderColor` on all points which lies on the same level.
  533. *
  534. * @type {Highcharts.ColorString}
  535. * @since 4.1.0
  536. * @product highcharts
  537. * @apioption plotOptions.treemap.levels.borderColor
  538. */
  539. /**
  540. * Set the dash style of the border of all the point which lies on the
  541. * level. See <a href"#plotoptions.scatter.dashstyle">
  542. * plotOptions.scatter.dashStyle</a> for possible options.
  543. *
  544. * @type {string}
  545. * @since 4.1.0
  546. * @product highcharts
  547. * @apioption plotOptions.treemap.levels.borderDashStyle
  548. */
  549. /**
  550. * Can set the borderWidth on all points which lies on the same level.
  551. *
  552. * @type {number}
  553. * @since 4.1.0
  554. * @product highcharts
  555. * @apioption plotOptions.treemap.levels.borderWidth
  556. */
  557. /**
  558. * Can set a color on all points which lies on the same level.
  559. *
  560. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  561. * @since 4.1.0
  562. * @product highcharts
  563. * @apioption plotOptions.treemap.levels.color
  564. */
  565. /**
  566. * A configuration object to define how the color of a child varies from
  567. * the parent's color. The variation is distributed among the children
  568. * of node. For example when setting brightness, the brightness change
  569. * will range from the parent's original brightness on the first child,
  570. * to the amount set in the `to` setting on the last node. This allows a
  571. * gradient-like color scheme that sets children out from each other
  572. * while highlighting the grouping on treemaps and sectors on sunburst
  573. * charts.
  574. *
  575. * @sample highcharts/demo/sunburst/
  576. * Sunburst with color variation
  577. *
  578. * @since 6.0.0
  579. * @product highcharts
  580. * @apioption plotOptions.treemap.levels.colorVariation
  581. */
  582. /**
  583. * The key of a color variation. Currently supports `brightness` only.
  584. *
  585. * @type {string}
  586. * @since 6.0.0
  587. * @product highcharts
  588. * @validvalue ["brightness"]
  589. * @apioption plotOptions.treemap.levels.colorVariation.key
  590. */
  591. /**
  592. * The ending value of a color variation. The last sibling will receive
  593. * this value.
  594. *
  595. * @type {number}
  596. * @since 6.0.0
  597. * @product highcharts
  598. * @apioption plotOptions.treemap.levels.colorVariation.to
  599. */
  600. /**
  601. * Can set the options of dataLabels on each point which lies on the
  602. * level.
  603. * [plotOptions.treemap.dataLabels](#plotOptions.treemap.dataLabels) for
  604. * possible values.
  605. *
  606. * @type {object}
  607. * @since 4.1.0
  608. * @product highcharts
  609. * @apioption plotOptions.treemap.levels.dataLabels
  610. */
  611. /**
  612. * Can set the layoutAlgorithm option on a specific level.
  613. *
  614. * @type {string}
  615. * @since 4.1.0
  616. * @product highcharts
  617. * @validvalue ["sliceAndDice", "stripes", "squarified", "strip"]
  618. * @apioption plotOptions.treemap.levels.layoutAlgorithm
  619. */
  620. /**
  621. * Can set the layoutStartingDirection option on a specific level.
  622. *
  623. * @type {string}
  624. * @since 4.1.0
  625. * @product highcharts
  626. * @validvalue ["vertical", "horizontal"]
  627. * @apioption plotOptions.treemap.levels.layoutStartingDirection
  628. */
  629. /**
  630. * Decides which level takes effect from the options set in the levels
  631. * object.
  632. *
  633. * @sample {highcharts} highcharts/plotoptions/treemap-levels/
  634. * Styling of both levels
  635. *
  636. * @type {number}
  637. * @since 4.1.0
  638. * @product highcharts
  639. * @apioption plotOptions.treemap.levels.level
  640. */
  641. // Presentational options
  642. /**
  643. * The color of the border surrounding each tree map item.
  644. *
  645. * @type {Highcharts.ColorString}
  646. */
  647. borderColor: '#e6e6e6',
  648. /**
  649. * The width of the border surrounding each tree map item.
  650. */
  651. borderWidth: 1,
  652. /**
  653. * The opacity of a point in treemap. When a point has children, the
  654. * visibility of the children is determined by the opacity.
  655. *
  656. * @since 4.2.4
  657. */
  658. opacity: 0.15,
  659. /**
  660. * A wrapper object for all the series options in specific states.
  661. *
  662. * @extends plotOptions.heatmap.states
  663. */
  664. states: {
  665. /**
  666. * Options for the hovered series
  667. *
  668. * @extends plotOptions.heatmap.states.hover
  669. * @excluding halo
  670. */
  671. hover: {
  672. /**
  673. * The border color for the hovered state.
  674. */
  675. borderColor: '#999999',
  676. /**
  677. * Brightness for the hovered point. Defaults to 0 if the
  678. * heatmap series is loaded first, otherwise 0.1.
  679. *
  680. * @type {number}
  681. * @default undefined
  682. */
  683. brightness: seriesTypes.heatmap ? 0 : 0.1,
  684. /**
  685. * @extends plotOptions.heatmap.states.hover.halo
  686. */
  687. halo: false,
  688. /**
  689. * The opacity of a point in treemap. When a point has children,
  690. * the visibility of the children is determined by the opacity.
  691. *
  692. * @since 4.2.4
  693. */
  694. opacity: 0.75,
  695. /**
  696. * The shadow option for hovered state.
  697. */
  698. shadow: false
  699. }
  700. }
  701. // Prototype members
  702. }, {
  703. pointArrayMap: ['value'],
  704. directTouch: true,
  705. optionalAxis: 'colorAxis',
  706. getSymbol: noop,
  707. parallelArrays: ['x', 'y', 'value', 'colorValue'],
  708. colorKey: 'colorValue', // Point color option key
  709. trackerGroups: ['group', 'dataLabelsGroup'],
  710. /**
  711. * Creates an object map from parent id to childrens index.
  712. *
  713. * @private
  714. * @function Highcharts.Series#getListOfParents
  715. *
  716. * @param {Highcharts.SeriesTreemapDataOptions} data
  717. * List of points set in options.
  718. *
  719. * @param {Array<string>} existingIds
  720. * List of all point ids.
  721. *
  722. * @return {object}
  723. * Map from parent id to children index in data.
  724. */
  725. getListOfParents: function (data, existingIds) {
  726. var arr = isArray(data) ? data : [],
  727. ids = isArray(existingIds) ? existingIds : [],
  728. listOfParents = arr.reduce(function (prev, curr, i) {
  729. var parent = pick(curr.parent, '');
  730. if (prev[parent] === undefined) {
  731. prev[parent] = [];
  732. }
  733. prev[parent].push(i);
  734. return prev;
  735. }, {
  736. '': [] // Root of tree
  737. });
  738. // If parent does not exist, hoist parent to root of tree.
  739. eachObject(listOfParents, function (children, parent, list) {
  740. if ((parent !== '') && (ids.indexOf(parent) === -1)) {
  741. children.forEach(function (child) {
  742. list[''].push(child);
  743. });
  744. delete list[parent];
  745. }
  746. });
  747. return listOfParents;
  748. },
  749. // Creates a tree structured object from the series points
  750. getTree: function () {
  751. var series = this,
  752. allIds = this.data.map(function (d) {
  753. return d.id;
  754. }),
  755. parentList = series.getListOfParents(this.data, allIds);
  756. series.nodeMap = [];
  757. return series.buildNode('', -1, 0, parentList, null);
  758. },
  759. init: function (chart, options) {
  760. var series = this,
  761. colorSeriesMixin = H.colorSeriesMixin;
  762. // If color series logic is loaded, add some properties
  763. if (H.colorSeriesMixin) {
  764. this.translateColors = colorSeriesMixin.translateColors;
  765. this.colorAttribs = colorSeriesMixin.colorAttribs;
  766. this.axisTypes = colorSeriesMixin.axisTypes;
  767. }
  768. Series.prototype.init.call(series, chart, options);
  769. if (series.options.allowDrillToNode) {
  770. H.addEvent(series, 'click', series.onClickDrillToNode);
  771. }
  772. },
  773. buildNode: function (id, i, level, list, parent) {
  774. var series = this,
  775. children = [],
  776. point = series.points[i],
  777. height = 0,
  778. node,
  779. child;
  780. // Actions
  781. ((list[id] || [])).forEach(function (i) {
  782. child = series.buildNode(
  783. series.points[i].id,
  784. i,
  785. (level + 1),
  786. list,
  787. id
  788. );
  789. height = Math.max(child.height + 1, height);
  790. children.push(child);
  791. });
  792. node = {
  793. id: id,
  794. i: i,
  795. children: children,
  796. height: height,
  797. level: level,
  798. parent: parent,
  799. visible: false // @todo move this to better location
  800. };
  801. series.nodeMap[node.id] = node;
  802. if (point) {
  803. point.node = node;
  804. }
  805. return node;
  806. },
  807. setTreeValues: function (tree) {
  808. var series = this,
  809. options = series.options,
  810. idRoot = series.rootNode,
  811. mapIdToNode = series.nodeMap,
  812. nodeRoot = mapIdToNode[idRoot],
  813. levelIsConstant = (
  814. isBoolean(options.levelIsConstant) ?
  815. options.levelIsConstant :
  816. true
  817. ),
  818. childrenTotal = 0,
  819. children = [],
  820. val,
  821. point = series.points[tree.i];
  822. // First give the children some values
  823. tree.children.forEach(function (child) {
  824. child = series.setTreeValues(child);
  825. children.push(child);
  826. if (!child.ignore) {
  827. childrenTotal += child.val;
  828. }
  829. });
  830. // Sort the children
  831. stableSort(children, function (a, b) {
  832. return a.sortIndex - b.sortIndex;
  833. });
  834. // Set the values
  835. val = pick(point && point.options.value, childrenTotal);
  836. if (point) {
  837. point.value = val;
  838. }
  839. extend(tree, {
  840. children: children,
  841. childrenTotal: childrenTotal,
  842. // Ignore this node if point is not visible
  843. ignore: !(pick(point && point.visible, true) && (val > 0)),
  844. isLeaf: tree.visible && !childrenTotal,
  845. levelDynamic: (
  846. tree.level - (levelIsConstant ? 0 : nodeRoot.level)
  847. ),
  848. name: pick(point && point.name, ''),
  849. sortIndex: pick(point && point.sortIndex, -val),
  850. val: val
  851. });
  852. return tree;
  853. },
  854. /**
  855. * Recursive function which calculates the area for all children of a
  856. * node.
  857. *
  858. * @private
  859. * @function Highcharts.Series#calculateChildrenAreas
  860. *
  861. * @param {object} node
  862. * The node which is parent to the children.
  863. *
  864. * @param {object} area
  865. * The rectangular area of the parent.
  866. */
  867. calculateChildrenAreas: function (parent, area) {
  868. var series = this,
  869. options = series.options,
  870. mapOptionsToLevel = series.mapOptionsToLevel,
  871. level = mapOptionsToLevel[parent.level + 1],
  872. algorithm = pick(
  873. (
  874. series[level &&
  875. level.layoutAlgorithm] &&
  876. level.layoutAlgorithm
  877. ),
  878. options.layoutAlgorithm
  879. ),
  880. alternate = options.alternateStartingDirection,
  881. childrenValues = [],
  882. children;
  883. // Collect all children which should be included
  884. children = parent.children.filter(function (n) {
  885. return !n.ignore;
  886. });
  887. if (level && level.layoutStartingDirection) {
  888. area.direction = level.layoutStartingDirection === 'vertical' ?
  889. 0 :
  890. 1;
  891. }
  892. childrenValues = series[algorithm](area, children);
  893. children.forEach(function (child, index) {
  894. var values = childrenValues[index];
  895. child.values = merge(values, {
  896. val: child.childrenTotal,
  897. direction: (alternate ? 1 - area.direction : area.direction)
  898. });
  899. child.pointValues = merge(values, {
  900. x: (values.x / series.axisRatio),
  901. width: (values.width / series.axisRatio)
  902. });
  903. // If node has children, then call method recursively
  904. if (child.children.length) {
  905. series.calculateChildrenAreas(child, child.values);
  906. }
  907. });
  908. },
  909. setPointValues: function () {
  910. var series = this,
  911. xAxis = series.xAxis,
  912. yAxis = series.yAxis;
  913. series.points.forEach(function (point) {
  914. var node = point.node,
  915. values = node.pointValues,
  916. x1,
  917. x2,
  918. y1,
  919. y2,
  920. crispCorr = 0;
  921. // Get the crisp correction in classic mode. For this to work in
  922. // styled mode, we would need to first add the shape (without x,
  923. // y, width and height), then read the rendered stroke width
  924. // using point.graphic.strokeWidth(), then modify and apply the
  925. // shapeArgs. This applies also to column series, but the
  926. // downside is performance and code complexity.
  927. if (!series.chart.styledMode) {
  928. crispCorr = (
  929. (series.pointAttribs(point)['stroke-width'] || 0) % 2
  930. ) / 2;
  931. }
  932. // Points which is ignored, have no values.
  933. if (values && node.visible) {
  934. x1 = Math.round(
  935. xAxis.translate(values.x, 0, 0, 0, 1)
  936. ) - crispCorr;
  937. x2 = Math.round(
  938. xAxis.translate(values.x + values.width, 0, 0, 0, 1)
  939. ) - crispCorr;
  940. y1 = Math.round(
  941. yAxis.translate(values.y, 0, 0, 0, 1)
  942. ) - crispCorr;
  943. y2 = Math.round(
  944. yAxis.translate(values.y + values.height, 0, 0, 0, 1)
  945. ) - crispCorr;
  946. // Set point values
  947. point.shapeType = 'rect';
  948. point.shapeArgs = {
  949. x: Math.min(x1, x2),
  950. y: Math.min(y1, y2),
  951. width: Math.abs(x2 - x1),
  952. height: Math.abs(y2 - y1)
  953. };
  954. point.plotX =
  955. point.shapeArgs.x + (point.shapeArgs.width / 2);
  956. point.plotY =
  957. point.shapeArgs.y + (point.shapeArgs.height / 2);
  958. } else {
  959. // Reset visibility
  960. delete point.plotX;
  961. delete point.plotY;
  962. }
  963. });
  964. },
  965. // Set the node's color recursively, from the parent down.
  966. setColorRecursive: function (
  967. node,
  968. parentColor,
  969. colorIndex,
  970. index,
  971. siblings
  972. ) {
  973. var series = this,
  974. chart = series && series.chart,
  975. colors = chart && chart.options && chart.options.colors,
  976. colorInfo,
  977. point;
  978. if (node) {
  979. colorInfo = getColor(node, {
  980. colors: colors,
  981. index: index,
  982. mapOptionsToLevel: series.mapOptionsToLevel,
  983. parentColor: parentColor,
  984. parentColorIndex: colorIndex,
  985. series: series,
  986. siblings: siblings
  987. });
  988. point = series.points[node.i];
  989. if (point) {
  990. point.color = colorInfo.color;
  991. point.colorIndex = colorInfo.colorIndex;
  992. }
  993. // Do it all again with the children
  994. (node.children || []).forEach(function (child, i) {
  995. series.setColorRecursive(
  996. child,
  997. colorInfo.color,
  998. colorInfo.colorIndex,
  999. i,
  1000. node.children.length
  1001. );
  1002. });
  1003. }
  1004. },
  1005. algorithmGroup: function (h, w, d, p) {
  1006. this.height = h;
  1007. this.width = w;
  1008. this.plot = p;
  1009. this.direction = d;
  1010. this.startDirection = d;
  1011. this.total = 0;
  1012. this.nW = 0;
  1013. this.lW = 0;
  1014. this.nH = 0;
  1015. this.lH = 0;
  1016. this.elArr = [];
  1017. this.lP = {
  1018. total: 0,
  1019. lH: 0,
  1020. nH: 0,
  1021. lW: 0,
  1022. nW: 0,
  1023. nR: 0,
  1024. lR: 0,
  1025. aspectRatio: function (w, h) {
  1026. return Math.max((w / h), (h / w));
  1027. }
  1028. };
  1029. this.addElement = function (el) {
  1030. this.lP.total = this.elArr[this.elArr.length - 1];
  1031. this.total = this.total + el;
  1032. if (this.direction === 0) {
  1033. // Calculate last point old aspect ratio
  1034. this.lW = this.nW;
  1035. this.lP.lH = this.lP.total / this.lW;
  1036. this.lP.lR = this.lP.aspectRatio(this.lW, this.lP.lH);
  1037. // Calculate last point new aspect ratio
  1038. this.nW = this.total / this.height;
  1039. this.lP.nH = this.lP.total / this.nW;
  1040. this.lP.nR = this.lP.aspectRatio(this.nW, this.lP.nH);
  1041. } else {
  1042. // Calculate last point old aspect ratio
  1043. this.lH = this.nH;
  1044. this.lP.lW = this.lP.total / this.lH;
  1045. this.lP.lR = this.lP.aspectRatio(this.lP.lW, this.lH);
  1046. // Calculate last point new aspect ratio
  1047. this.nH = this.total / this.width;
  1048. this.lP.nW = this.lP.total / this.nH;
  1049. this.lP.nR = this.lP.aspectRatio(this.lP.nW, this.nH);
  1050. }
  1051. this.elArr.push(el);
  1052. };
  1053. this.reset = function () {
  1054. this.nW = 0;
  1055. this.lW = 0;
  1056. this.elArr = [];
  1057. this.total = 0;
  1058. };
  1059. },
  1060. algorithmCalcPoints: function (
  1061. directionChange, last, group, childrenArea
  1062. ) {
  1063. var pX,
  1064. pY,
  1065. pW,
  1066. pH,
  1067. gW = group.lW,
  1068. gH = group.lH,
  1069. plot = group.plot,
  1070. keep,
  1071. i = 0,
  1072. end = group.elArr.length - 1;
  1073. if (last) {
  1074. gW = group.nW;
  1075. gH = group.nH;
  1076. } else {
  1077. keep = group.elArr[group.elArr.length - 1];
  1078. }
  1079. group.elArr.forEach(function (p) {
  1080. if (last || (i < end)) {
  1081. if (group.direction === 0) {
  1082. pX = plot.x;
  1083. pY = plot.y;
  1084. pW = gW;
  1085. pH = p / pW;
  1086. } else {
  1087. pX = plot.x;
  1088. pY = plot.y;
  1089. pH = gH;
  1090. pW = p / pH;
  1091. }
  1092. childrenArea.push({
  1093. x: pX,
  1094. y: pY,
  1095. width: pW,
  1096. height: pH
  1097. });
  1098. if (group.direction === 0) {
  1099. plot.y = plot.y + pH;
  1100. } else {
  1101. plot.x = plot.x + pW;
  1102. }
  1103. }
  1104. i = i + 1;
  1105. });
  1106. // Reset variables
  1107. group.reset();
  1108. if (group.direction === 0) {
  1109. group.width = group.width - gW;
  1110. } else {
  1111. group.height = group.height - gH;
  1112. }
  1113. plot.y = plot.parent.y + (plot.parent.height - group.height);
  1114. plot.x = plot.parent.x + (plot.parent.width - group.width);
  1115. if (directionChange) {
  1116. group.direction = 1 - group.direction;
  1117. }
  1118. // If not last, then add uncalculated element
  1119. if (!last) {
  1120. group.addElement(keep);
  1121. }
  1122. },
  1123. algorithmLowAspectRatio: function (directionChange, parent, children) {
  1124. var childrenArea = [],
  1125. series = this,
  1126. pTot,
  1127. plot = {
  1128. x: parent.x,
  1129. y: parent.y,
  1130. parent: parent
  1131. },
  1132. direction = parent.direction,
  1133. i = 0,
  1134. end = children.length - 1,
  1135. group = new this.algorithmGroup( // eslint-disable-line new-cap
  1136. parent.height,
  1137. parent.width,
  1138. direction,
  1139. plot
  1140. );
  1141. // Loop through and calculate all areas
  1142. children.forEach(function (child) {
  1143. pTot =
  1144. (parent.width * parent.height) * (child.val / parent.val);
  1145. group.addElement(pTot);
  1146. if (group.lP.nR > group.lP.lR) {
  1147. series.algorithmCalcPoints(
  1148. directionChange,
  1149. false,
  1150. group,
  1151. childrenArea,
  1152. plot
  1153. );
  1154. }
  1155. // If last child, then calculate all remaining areas
  1156. if (i === end) {
  1157. series.algorithmCalcPoints(
  1158. directionChange,
  1159. true,
  1160. group,
  1161. childrenArea,
  1162. plot
  1163. );
  1164. }
  1165. i = i + 1;
  1166. });
  1167. return childrenArea;
  1168. },
  1169. algorithmFill: function (directionChange, parent, children) {
  1170. var childrenArea = [],
  1171. pTot,
  1172. direction = parent.direction,
  1173. x = parent.x,
  1174. y = parent.y,
  1175. width = parent.width,
  1176. height = parent.height,
  1177. pX,
  1178. pY,
  1179. pW,
  1180. pH;
  1181. children.forEach(function (child) {
  1182. pTot =
  1183. (parent.width * parent.height) * (child.val / parent.val);
  1184. pX = x;
  1185. pY = y;
  1186. if (direction === 0) {
  1187. pH = height;
  1188. pW = pTot / pH;
  1189. width = width - pW;
  1190. x = x + pW;
  1191. } else {
  1192. pW = width;
  1193. pH = pTot / pW;
  1194. height = height - pH;
  1195. y = y + pH;
  1196. }
  1197. childrenArea.push({
  1198. x: pX,
  1199. y: pY,
  1200. width: pW,
  1201. height: pH
  1202. });
  1203. if (directionChange) {
  1204. direction = 1 - direction;
  1205. }
  1206. });
  1207. return childrenArea;
  1208. },
  1209. strip: function (parent, children) {
  1210. return this.algorithmLowAspectRatio(false, parent, children);
  1211. },
  1212. squarified: function (parent, children) {
  1213. return this.algorithmLowAspectRatio(true, parent, children);
  1214. },
  1215. sliceAndDice: function (parent, children) {
  1216. return this.algorithmFill(true, parent, children);
  1217. },
  1218. stripes: function (parent, children) {
  1219. return this.algorithmFill(false, parent, children);
  1220. },
  1221. translate: function () {
  1222. var series = this,
  1223. options = series.options,
  1224. // NOTE: updateRootId modifies series.
  1225. rootId = updateRootId(series),
  1226. rootNode,
  1227. pointValues,
  1228. seriesArea,
  1229. tree,
  1230. val;
  1231. // Call prototype function
  1232. Series.prototype.translate.call(series);
  1233. // @todo Only if series.isDirtyData is true
  1234. tree = series.tree = series.getTree();
  1235. rootNode = series.nodeMap[rootId];
  1236. series.mapOptionsToLevel = getLevelOptions({
  1237. from: rootNode.level + 1,
  1238. levels: options.levels,
  1239. to: tree.height,
  1240. defaults: {
  1241. levelIsConstant: series.options.levelIsConstant,
  1242. colorByPoint: options.colorByPoint
  1243. }
  1244. });
  1245. if (
  1246. rootId !== '' &&
  1247. (!rootNode || !rootNode.children.length)
  1248. ) {
  1249. series.drillToNode('', false);
  1250. rootId = series.rootNode;
  1251. rootNode = series.nodeMap[rootId];
  1252. }
  1253. // Parents of the root node is by default visible
  1254. recursive(series.nodeMap[series.rootNode], function (node) {
  1255. var next = false,
  1256. p = node.parent;
  1257. node.visible = true;
  1258. if (p || p === '') {
  1259. next = series.nodeMap[p];
  1260. }
  1261. return next;
  1262. });
  1263. // Children of the root node is by default visible
  1264. recursive(
  1265. series.nodeMap[series.rootNode].children,
  1266. function (children) {
  1267. var next = false;
  1268. children.forEach(function (child) {
  1269. child.visible = true;
  1270. if (child.children.length) {
  1271. next = (next || []).concat(child.children);
  1272. }
  1273. });
  1274. return next;
  1275. }
  1276. );
  1277. series.setTreeValues(tree);
  1278. // Calculate plotting values.
  1279. series.axisRatio = (series.xAxis.len / series.yAxis.len);
  1280. series.nodeMap[''].pointValues = pointValues =
  1281. { x: 0, y: 0, width: 100, height: 100 };
  1282. series.nodeMap[''].values = seriesArea = merge(pointValues, {
  1283. width: (pointValues.width * series.axisRatio),
  1284. direction: (
  1285. options.layoutStartingDirection === 'vertical' ? 0 : 1
  1286. ),
  1287. val: tree.val
  1288. });
  1289. series.calculateChildrenAreas(tree, seriesArea);
  1290. // Logic for point colors
  1291. if (series.colorAxis) {
  1292. series.translateColors();
  1293. } else if (!options.colorByPoint) {
  1294. series.setColorRecursive(series.tree);
  1295. }
  1296. // Update axis extremes according to the root node.
  1297. if (options.allowDrillToNode) {
  1298. val = rootNode.pointValues;
  1299. series.xAxis.setExtremes(val.x, val.x + val.width, false);
  1300. series.yAxis.setExtremes(val.y, val.y + val.height, false);
  1301. series.xAxis.setScale();
  1302. series.yAxis.setScale();
  1303. }
  1304. // Assign values to points.
  1305. series.setPointValues();
  1306. },
  1307. /**
  1308. * Extend drawDataLabels with logic to handle custom options related to
  1309. * the treemap series:
  1310. *
  1311. * - Points which is not a leaf node, has dataLabels disabled by
  1312. * default.
  1313. *
  1314. * - Options set on series.levels is merged in.
  1315. *
  1316. * - Width of the dataLabel is set to match the width of the point
  1317. * shape.
  1318. *
  1319. * @private
  1320. * @function Highcharts.Series#drawDataLabels
  1321. */
  1322. drawDataLabels: function () {
  1323. var series = this,
  1324. mapOptionsToLevel = series.mapOptionsToLevel,
  1325. points = series.points.filter(function (n) {
  1326. return n.node.visible;
  1327. }),
  1328. options,
  1329. level;
  1330. points.forEach(function (point) {
  1331. level = mapOptionsToLevel[point.node.level];
  1332. // Set options to new object to avoid problems with scope
  1333. options = { style: {} };
  1334. // If not a leaf, then label should be disabled as default
  1335. if (!point.node.isLeaf) {
  1336. options.enabled = false;
  1337. }
  1338. // If options for level exists, include them as well
  1339. if (level && level.dataLabels) {
  1340. options = merge(options, level.dataLabels);
  1341. series._hasPointLabels = true;
  1342. }
  1343. // Set dataLabel width to the width of the point shape.
  1344. if (point.shapeArgs) {
  1345. options.style.width = point.shapeArgs.width;
  1346. if (point.dataLabel) {
  1347. point.dataLabel.css({
  1348. width: point.shapeArgs.width + 'px'
  1349. });
  1350. }
  1351. }
  1352. // Merge custom options with point options
  1353. point.dlOptions = merge(options, point.options.dataLabels);
  1354. });
  1355. Series.prototype.drawDataLabels.call(this);
  1356. },
  1357. // Over the alignment method by setting z index
  1358. alignDataLabel: function (point, dataLabel, labelOptions) {
  1359. var style = labelOptions.style;
  1360. // #8160: Prevent the label from exceeding the point's
  1361. // boundaries in treemaps by applying ellipsis overflow.
  1362. // The issue was happening when datalabel's text contained a
  1363. // long sequence of characters without a whitespace.
  1364. if (
  1365. !H.defined(style.textOverflow) &&
  1366. dataLabel.text &&
  1367. dataLabel.getBBox().width > dataLabel.text.textWidth
  1368. ) {
  1369. dataLabel.css({
  1370. textOverflow: 'ellipsis',
  1371. // unit (px) is required when useHTML is true
  1372. width: style.width += 'px'
  1373. });
  1374. }
  1375. seriesTypes.column.prototype.alignDataLabel.apply(this, arguments);
  1376. if (point.dataLabel) {
  1377. // point.node.zIndex could be undefined (#6956)
  1378. point.dataLabel.attr({ zIndex: (point.node.zIndex || 0) + 1 });
  1379. }
  1380. },
  1381. // Get presentational attributes
  1382. pointAttribs: function (point, state) {
  1383. var series = this,
  1384. mapOptionsToLevel = (
  1385. isObject(series.mapOptionsToLevel) ?
  1386. series.mapOptionsToLevel :
  1387. {}
  1388. ),
  1389. level = point && mapOptionsToLevel[point.node.level] || {},
  1390. options = this.options,
  1391. attr,
  1392. stateOptions = (state && options.states[state]) || {},
  1393. className = (point && point.getClassName()) || '',
  1394. opacity;
  1395. // Set attributes by precedence. Point trumps level trumps series.
  1396. // Stroke width uses pick because it can be 0.
  1397. attr = {
  1398. 'stroke':
  1399. (point && point.borderColor) ||
  1400. level.borderColor ||
  1401. stateOptions.borderColor ||
  1402. options.borderColor,
  1403. 'stroke-width': pick(
  1404. point && point.borderWidth,
  1405. level.borderWidth,
  1406. stateOptions.borderWidth,
  1407. options.borderWidth
  1408. ),
  1409. 'dashstyle':
  1410. (point && point.borderDashStyle) ||
  1411. level.borderDashStyle ||
  1412. stateOptions.borderDashStyle ||
  1413. options.borderDashStyle,
  1414. 'fill': (point && point.color) || this.color
  1415. };
  1416. // Hide levels above the current view
  1417. if (className.indexOf('highcharts-above-level') !== -1) {
  1418. attr.fill = 'none';
  1419. attr['stroke-width'] = 0;
  1420. // Nodes with children that accept interaction
  1421. } else if (
  1422. className.indexOf('highcharts-internal-node-interactive') !== -1
  1423. ) {
  1424. opacity = pick(stateOptions.opacity, options.opacity);
  1425. attr.fill = color(attr.fill).setOpacity(opacity).get();
  1426. attr.cursor = 'pointer';
  1427. // Hide nodes that have children
  1428. } else if (className.indexOf('highcharts-internal-node') !== -1) {
  1429. attr.fill = 'none';
  1430. } else if (state) {
  1431. // Brighten and hoist the hover nodes
  1432. attr.fill = color(attr.fill)
  1433. .brighten(stateOptions.brightness)
  1434. .get();
  1435. }
  1436. return attr;
  1437. },
  1438. // Extending ColumnSeries drawPoints
  1439. drawPoints: function () {
  1440. var series = this,
  1441. points = series.points.filter(function (n) {
  1442. return n.node.visible;
  1443. });
  1444. points.forEach(function (point) {
  1445. var groupKey = 'level-group-' + point.node.levelDynamic;
  1446. if (!series[groupKey]) {
  1447. series[groupKey] = series.chart.renderer.g(groupKey)
  1448. .attr({
  1449. // @todo Set the zIndex based upon the number of levels,
  1450. // instead of using 1000
  1451. zIndex: 1000 - point.node.levelDynamic
  1452. })
  1453. .add(series.group);
  1454. }
  1455. point.group = series[groupKey];
  1456. });
  1457. // Call standard drawPoints
  1458. seriesTypes.column.prototype.drawPoints.call(this);
  1459. // In styled mode apply point.color. Use CSS, otherwise the fill
  1460. // used in the style sheet will take precedence over the fill
  1461. // attribute.
  1462. if (this.colorAttribs && series.chart.styledMode) {
  1463. // Heatmap is loaded
  1464. this.points.forEach(function (point) {
  1465. if (point.graphic) {
  1466. point.graphic.css(this.colorAttribs(point));
  1467. }
  1468. }, this);
  1469. }
  1470. // If drillToNode is allowed, set a point cursor on clickables & add
  1471. // drillId to point
  1472. if (series.options.allowDrillToNode) {
  1473. points.forEach(function (point) {
  1474. if (point.graphic) {
  1475. point.drillId = series.options.interactByLeaf ?
  1476. series.drillToByLeaf(point) :
  1477. series.drillToByGroup(point);
  1478. }
  1479. });
  1480. }
  1481. },
  1482. // Add drilling on the suitable points
  1483. onClickDrillToNode: function (event) {
  1484. var series = this,
  1485. point = event.point,
  1486. drillId = point && point.drillId;
  1487. // If a drill id is returned, add click event and cursor.
  1488. if (isString(drillId)) {
  1489. point.setState(''); // Remove hover
  1490. series.drillToNode(drillId);
  1491. }
  1492. },
  1493. /**
  1494. * Finds the drill id for a parent node. Returns false if point should
  1495. * not have a click event.
  1496. *
  1497. * @private
  1498. * @function Highcharts.Series#drillToByGroup
  1499. *
  1500. * @param {object} point
  1501. *
  1502. * @return {boolean|string}
  1503. * Drill to id or false when point should not have a click
  1504. * event.
  1505. */
  1506. drillToByGroup: function (point) {
  1507. var series = this,
  1508. drillId = false;
  1509. if ((point.node.level - series.nodeMap[series.rootNode].level) ===
  1510. 1 &&
  1511. !point.node.isLeaf
  1512. ) {
  1513. drillId = point.id;
  1514. }
  1515. return drillId;
  1516. },
  1517. /**
  1518. * Finds the drill id for a leaf node. Returns false if point should not
  1519. * have a click event
  1520. *
  1521. * @private
  1522. * @function Highcharts.Series#drillToByLeaf
  1523. *
  1524. * @param {object} point
  1525. *
  1526. * @return {boolean|string}
  1527. * Drill to id or false when point should not have a click
  1528. * event.
  1529. */
  1530. drillToByLeaf: function (point) {
  1531. var series = this,
  1532. drillId = false,
  1533. nodeParent;
  1534. if ((point.node.parent !== series.rootNode) &&
  1535. point.node.isLeaf
  1536. ) {
  1537. nodeParent = point.node;
  1538. while (!drillId) {
  1539. nodeParent = series.nodeMap[nodeParent.parent];
  1540. if (nodeParent.parent === series.rootNode) {
  1541. drillId = nodeParent.id;
  1542. }
  1543. }
  1544. }
  1545. return drillId;
  1546. },
  1547. drillUp: function () {
  1548. var series = this,
  1549. node = series.nodeMap[series.rootNode];
  1550. if (node && isString(node.parent)) {
  1551. series.drillToNode(node.parent);
  1552. }
  1553. },
  1554. drillToNode: function (id, redraw) {
  1555. var series = this,
  1556. nodeMap = series.nodeMap,
  1557. node = nodeMap[id];
  1558. series.idPreviousRoot = series.rootNode;
  1559. series.rootNode = id;
  1560. if (id === '') {
  1561. series.drillUpButton = series.drillUpButton.destroy();
  1562. } else {
  1563. series.showDrillUpButton((node && node.name || id));
  1564. }
  1565. this.isDirty = true; // Force redraw
  1566. if (pick(redraw, true)) {
  1567. this.chart.redraw();
  1568. }
  1569. },
  1570. showDrillUpButton: function (name) {
  1571. var series = this,
  1572. backText = (name || '< Back'),
  1573. buttonOptions = series.options.drillUpButton,
  1574. attr,
  1575. states;
  1576. if (buttonOptions.text) {
  1577. backText = buttonOptions.text;
  1578. }
  1579. if (!this.drillUpButton) {
  1580. attr = buttonOptions.theme;
  1581. states = attr && attr.states;
  1582. this.drillUpButton = this.chart.renderer.button(
  1583. backText,
  1584. null,
  1585. null,
  1586. function () {
  1587. series.drillUp();
  1588. },
  1589. attr,
  1590. states && states.hover,
  1591. states && states.select
  1592. )
  1593. .addClass('highcharts-drillup-button')
  1594. .attr({
  1595. align: buttonOptions.position.align,
  1596. zIndex: 7
  1597. })
  1598. .add()
  1599. .align(
  1600. buttonOptions.position,
  1601. false,
  1602. buttonOptions.relativeTo || 'plotBox'
  1603. );
  1604. } else {
  1605. this.drillUpButton.placed = false;
  1606. this.drillUpButton.attr({
  1607. text: backText
  1608. })
  1609. .align();
  1610. }
  1611. },
  1612. buildKDTree: noop,
  1613. drawLegendSymbol: H.LegendSymbolMixin.drawRectangle,
  1614. getExtremes: function () {
  1615. // Get the extremes from the value data
  1616. Series.prototype.getExtremes.call(this, this.colorValueData);
  1617. this.valueMin = this.dataMin;
  1618. this.valueMax = this.dataMax;
  1619. // Get the extremes from the y data
  1620. Series.prototype.getExtremes.call(this);
  1621. },
  1622. getExtremesFromAll: true,
  1623. bindAxes: function () {
  1624. var treeAxis = {
  1625. endOnTick: false,
  1626. gridLineWidth: 0,
  1627. lineWidth: 0,
  1628. min: 0,
  1629. dataMin: 0,
  1630. minPadding: 0,
  1631. max: 100,
  1632. dataMax: 100,
  1633. maxPadding: 0,
  1634. startOnTick: false,
  1635. title: null,
  1636. tickPositions: []
  1637. };
  1638. Series.prototype.bindAxes.call(this);
  1639. H.extend(this.yAxis.options, treeAxis);
  1640. H.extend(this.xAxis.options, treeAxis);
  1641. },
  1642. utils: {
  1643. recursive: recursive
  1644. }
  1645. // Point class
  1646. }, {
  1647. getClassName: function () {
  1648. var className = H.Point.prototype.getClassName.call(this),
  1649. series = this.series,
  1650. options = series.options;
  1651. // Above the current level
  1652. if (this.node.level <= series.nodeMap[series.rootNode].level) {
  1653. className += ' highcharts-above-level';
  1654. } else if (
  1655. !this.node.isLeaf &&
  1656. !pick(options.interactByLeaf, !options.allowDrillToNode)
  1657. ) {
  1658. className += ' highcharts-internal-node-interactive';
  1659. } else if (!this.node.isLeaf) {
  1660. className += ' highcharts-internal-node';
  1661. }
  1662. return className;
  1663. },
  1664. /**
  1665. * A tree point is valid if it has han id too, assume it may be a parent
  1666. * item.
  1667. *
  1668. * @private
  1669. * @function Highcharts.Point#isValid
  1670. */
  1671. isValid: function () {
  1672. return this.id || isNumber(this.value);
  1673. },
  1674. setState: function (state) {
  1675. H.Point.prototype.setState.call(this, state);
  1676. // Graphic does not exist when point is not visible.
  1677. if (this.graphic) {
  1678. this.graphic.attr({
  1679. zIndex: state === 'hover' ? 1 : 0
  1680. });
  1681. }
  1682. },
  1683. setVisible: seriesTypes.pie.prototype.pointClass.prototype.setVisible
  1684. }
  1685. );
  1686. /**
  1687. * A `treemap` series. If the [type](#series.treemap.type) option is
  1688. * not specified, it is inherited from [chart.type](#chart.type).
  1689. *
  1690. * @extends series,plotOptions.treemap
  1691. * @excluding dataParser, dataURL, stack
  1692. * @product highcharts
  1693. * @apioption series.treemap
  1694. */
  1695. /**
  1696. * An array of data points for the series. For the `treemap` series
  1697. * type, points can be given in the following ways:
  1698. *
  1699. * 1. An array of numerical values. In this case, the numerical values
  1700. * will be interpreted as `value` options. Example:
  1701. *
  1702. * ```js
  1703. * data: [0, 5, 3, 5]
  1704. * ```
  1705. *
  1706. * 2. An array of objects with named values. The following snippet shows only a
  1707. * few settings, see the complete options set below. If the total number of data
  1708. * points exceeds the series' [turboThreshold](#series.treemap.turboThreshold),
  1709. * this option is not available.
  1710. *
  1711. * ```js
  1712. * data: [{
  1713. * value: 9,
  1714. * name: "Point2",
  1715. * color: "#00FF00"
  1716. * }, {
  1717. * value: 6,
  1718. * name: "Point1",
  1719. * color: "#FF00FF"
  1720. * }]
  1721. * ```
  1722. *
  1723. * @sample {highcharts} highcharts/chart/reflow-true/
  1724. * Numerical values
  1725. * @sample {highcharts} highcharts/series/data-array-of-objects/
  1726. * Config objects
  1727. *
  1728. * @type {Array<number|*>}
  1729. * @extends series.heatmap.data
  1730. * @excluding x, y
  1731. * @product highcharts
  1732. * @apioption series.treemap.data
  1733. */
  1734. /**
  1735. * The value of the point, resulting in a relative area of the point
  1736. * in the treemap.
  1737. *
  1738. * @type {number}
  1739. * @product highcharts
  1740. * @apioption series.treemap.data.value
  1741. */
  1742. /**
  1743. * Serves a purpose only if a `colorAxis` object is defined in the chart
  1744. * options. This value will decide which color the point gets from the
  1745. * scale of the colorAxis.
  1746. *
  1747. * @type {number}
  1748. * @since 4.1.0
  1749. * @product highcharts
  1750. * @apioption series.treemap.data.colorValue
  1751. */
  1752. /**
  1753. * Only for treemap. Use this option to build a tree structure. The
  1754. * value should be the id of the point which is the parent. If no points
  1755. * has a matching id, or this option is undefined, then the parent will
  1756. * be set to the root.
  1757. *
  1758. * @sample {highcharts} highcharts/point/parent/
  1759. * Point parent
  1760. * @sample {highcharts} highcharts/demo/treemap-with-levels/
  1761. * Example where parent id is not matching
  1762. *
  1763. * @type {string}
  1764. * @since 4.1.0
  1765. * @product highcharts
  1766. * @apioption series.treemap.data.parent
  1767. */
  1768. }(Highcharts, result));
  1769. return (function () {
  1770. }());
  1771. }));