sunburst.src.js 91 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. *
  4. * (c) 2016 Highsoft AS
  5. * Authors: Jon Arild Nygard
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. var draw = (function () {
  18. var isFn = function (x) {
  19. return typeof x === 'function';
  20. };
  21. /**
  22. * draw - Handles the drawing of a point.
  23. * TODO: add type checking.
  24. *
  25. * @param {object} params Parameters.
  26. * @return {undefined} Returns undefined.
  27. */
  28. var draw = function draw(params) {
  29. var point = this,
  30. graphic = point.graphic,
  31. animate = params.animate,
  32. attr = params.attr,
  33. onComplete = params.onComplete,
  34. css = params.css,
  35. group = params.group,
  36. renderer = params.renderer,
  37. shape = params.shapeArgs,
  38. type = params.shapeType;
  39. if (point.shouldDraw()) {
  40. if (!graphic) {
  41. point.graphic = graphic = renderer[type](shape).add(group);
  42. }
  43. graphic.css(css).attr(attr).animate(animate, undefined, onComplete);
  44. } else if (graphic) {
  45. graphic.animate(animate, undefined, function () {
  46. point.graphic = graphic = graphic.destroy();
  47. if (isFn(onComplete)) {
  48. onComplete();
  49. }
  50. });
  51. }
  52. if (graphic) {
  53. graphic.addClass(point.getClassName(), true);
  54. }
  55. };
  56. return draw;
  57. }());
  58. var result = (function (H) {
  59. var each = H.each,
  60. extend = H.extend,
  61. isArray = H.isArray,
  62. isBoolean = function (x) {
  63. return typeof x === 'boolean';
  64. },
  65. isFn = function (x) {
  66. return typeof x === 'function';
  67. },
  68. isObject = H.isObject,
  69. isNumber = H.isNumber,
  70. merge = H.merge,
  71. pick = H.pick,
  72. reduce = H.reduce;
  73. // TODO Combine buildTree and buildNode with setTreeValues
  74. // TODO Remove logic from Treemap and make it utilize this mixin.
  75. var setTreeValues = function setTreeValues(tree, options) {
  76. var before = options.before,
  77. idRoot = options.idRoot,
  78. mapIdToNode = options.mapIdToNode,
  79. nodeRoot = mapIdToNode[idRoot],
  80. levelIsConstant = (
  81. isBoolean(options.levelIsConstant) ?
  82. options.levelIsConstant :
  83. true
  84. ),
  85. points = options.points,
  86. point = points[tree.i],
  87. optionsPoint = point && point.options || {},
  88. childrenTotal = 0,
  89. children = [],
  90. value;
  91. extend(tree, {
  92. levelDynamic: tree.level - (levelIsConstant ? 0 : nodeRoot.level),
  93. name: pick(point && point.name, ''),
  94. visible: (
  95. idRoot === tree.id ||
  96. (isBoolean(options.visible) ? options.visible : false)
  97. )
  98. });
  99. if (isFn(before)) {
  100. tree = before(tree, options);
  101. }
  102. // First give the children some values
  103. each(tree.children, function (child, i) {
  104. var newOptions = extend({}, options);
  105. extend(newOptions, {
  106. index: i,
  107. siblings: tree.children.length,
  108. visible: tree.visible
  109. });
  110. child = setTreeValues(child, newOptions);
  111. children.push(child);
  112. if (child.visible) {
  113. childrenTotal += child.val;
  114. }
  115. });
  116. tree.visible = childrenTotal > 0 || tree.visible;
  117. // Set the values
  118. value = pick(optionsPoint.value, childrenTotal);
  119. extend(tree, {
  120. children: children,
  121. childrenTotal: childrenTotal,
  122. isLeaf: tree.visible && !childrenTotal,
  123. val: value
  124. });
  125. return tree;
  126. };
  127. var getColor = function getColor(node, options) {
  128. var index = options.index,
  129. mapOptionsToLevel = options.mapOptionsToLevel,
  130. parentColor = options.parentColor,
  131. parentColorIndex = options.parentColorIndex,
  132. series = options.series,
  133. colors = options.colors,
  134. siblings = options.siblings,
  135. points = series.points,
  136. getColorByPoint,
  137. point,
  138. level,
  139. colorByPoint,
  140. colorIndexByPoint,
  141. color,
  142. colorIndex;
  143. function variation(color) {
  144. var colorVariation = level && level.colorVariation;
  145. if (colorVariation) {
  146. if (colorVariation.key === 'brightness') {
  147. return H.color(color).brighten(
  148. colorVariation.to * (index / siblings)
  149. ).get();
  150. }
  151. }
  152. return color;
  153. }
  154. if (node) {
  155. point = points[node.i];
  156. level = mapOptionsToLevel[node.level] || {};
  157. getColorByPoint = point && level.colorByPoint;
  158. if (getColorByPoint) {
  159. colorIndexByPoint = point.index % (colors ?
  160. colors.length :
  161. series.chart.options.chart.colorCount
  162. );
  163. colorByPoint = colors && colors[colorIndexByPoint];
  164. }
  165. // Select either point color, level color or inherited color.
  166. color = pick(
  167. point && point.options.color,
  168. level && level.color,
  169. colorByPoint,
  170. parentColor && variation(parentColor),
  171. series.color
  172. );
  173. colorIndex = pick(
  174. point && point.options.colorIndex,
  175. level && level.colorIndex,
  176. colorIndexByPoint,
  177. parentColorIndex,
  178. options.colorIndex
  179. );
  180. }
  181. return {
  182. color: color,
  183. colorIndex: colorIndex
  184. };
  185. };
  186. /**
  187. * getLevelOptions - Creates a map from level number to its given options.
  188. * @param {Object} params Object containing parameters.
  189. * @param {Object} params.defaults Object containing default options. The
  190. * default options are merged with the userOptions to get the final options for
  191. * a specific level.
  192. * @param {Number} params.from The lowest level number.
  193. * @param {Array} params.levels User options from series.levels.
  194. * @param {Number} params.to The highest level number.
  195. * @return {null|Object} Returns a map from level number to its given options.
  196. * Returns null if invalid input parameters.
  197. */
  198. var getLevelOptions = function getLevelOptions(params) {
  199. var result = null,
  200. defaults,
  201. converted,
  202. i,
  203. from,
  204. to,
  205. levels;
  206. if (isObject(params)) {
  207. result = {};
  208. from = isNumber(params.from) ? params.from : 1;
  209. levels = params.levels;
  210. converted = {};
  211. defaults = isObject(params.defaults) ? params.defaults : {};
  212. if (isArray(levels)) {
  213. converted = reduce(levels, function (obj, item) {
  214. var level,
  215. levelIsConstant,
  216. options;
  217. if (isObject(item) && isNumber(item.level)) {
  218. options = merge({}, item);
  219. levelIsConstant = (
  220. isBoolean(options.levelIsConstant) ?
  221. options.levelIsConstant :
  222. defaults.levelIsConstant
  223. );
  224. // Delete redundant properties.
  225. delete options.levelIsConstant;
  226. delete options.level;
  227. // Calculate which level these options apply to.
  228. level = item.level + (levelIsConstant ? 0 : from - 1);
  229. if (isObject(obj[level])) {
  230. extend(obj[level], options);
  231. } else {
  232. obj[level] = options;
  233. }
  234. }
  235. return obj;
  236. }, {});
  237. }
  238. to = isNumber(params.to) ? params.to : 1;
  239. for (i = 0; i <= to; i++) {
  240. result[i] = merge(
  241. {},
  242. defaults,
  243. isObject(converted[i]) ? converted[i] : {}
  244. );
  245. }
  246. }
  247. return result;
  248. };
  249. /**
  250. * Update the rootId property on the series. Also makes sure that it is
  251. * accessible to exporting.
  252. * @param {object} series The series to operate on.
  253. * @returns Returns the resulting rootId after update.
  254. */
  255. var updateRootId = function (series) {
  256. var rootId,
  257. options;
  258. if (isObject(series)) {
  259. // Get the series options.
  260. options = isObject(series.options) ? series.options : {};
  261. // Calculate the rootId.
  262. rootId = pick(series.rootNode, options.rootId, '');
  263. // Set rootId on series.userOptions to pick it up in exporting.
  264. if (isObject(series.userOptions)) {
  265. series.userOptions.rootId = rootId;
  266. }
  267. // Set rootId on series to pick it up on next update.
  268. series.rootNode = rootId;
  269. }
  270. return rootId;
  271. };
  272. var result = {
  273. getColor: getColor,
  274. getLevelOptions: getLevelOptions,
  275. setTreeValues: setTreeValues,
  276. updateRootId: updateRootId
  277. };
  278. return result;
  279. }(Highcharts));
  280. (function (H, mixinTreeSeries) {
  281. /**
  282. * (c) 2014 Highsoft AS
  283. * Authors: Jon Arild Nygard / Oystein Moseng
  284. *
  285. * License: www.highcharts.com/license
  286. */
  287. var seriesType = H.seriesType,
  288. seriesTypes = H.seriesTypes,
  289. map = H.map,
  290. merge = H.merge,
  291. extend = H.extend,
  292. noop = H.noop,
  293. each = H.each,
  294. getColor = mixinTreeSeries.getColor,
  295. getLevelOptions = mixinTreeSeries.getLevelOptions,
  296. grep = H.grep,
  297. isBoolean = function (x) {
  298. return typeof x === 'boolean';
  299. },
  300. isNumber = H.isNumber,
  301. isObject = H.isObject,
  302. isString = H.isString,
  303. pick = H.pick,
  304. Series = H.Series,
  305. stableSort = H.stableSort,
  306. color = H.Color,
  307. eachObject = function (list, func, context) {
  308. context = context || this;
  309. H.objectEach(list, function (val, key) {
  310. func.call(context, val, key, list);
  311. });
  312. },
  313. reduce = H.reduce,
  314. // @todo find correct name for this function.
  315. // @todo Similar to reduce, this function is likely redundant
  316. recursive = function (item, func, context) {
  317. var next;
  318. context = context || this;
  319. next = func.call(context, item);
  320. if (next !== false) {
  321. recursive(next, func, context);
  322. }
  323. },
  324. updateRootId = mixinTreeSeries.updateRootId;
  325. /**
  326. * A treemap displays hierarchical data using nested rectangles. The data can be
  327. * laid out in varying ways depending on options.
  328. *
  329. * @sample highcharts/demo/treemap-large-dataset/ Treemap
  330. *
  331. * @extends {plotOptions.scatter}
  332. * @excluding marker
  333. * @product highcharts
  334. * @optionparent plotOptions.treemap
  335. */
  336. seriesType('treemap', 'scatter', {
  337. /**
  338. * When enabled the user can click on a point which is a parent and
  339. * zoom in on its children.
  340. *
  341. * @type {Boolean}
  342. * @sample {highcharts} highcharts/plotoptions/treemap-allowdrilltonode/ Enabled
  343. * @default false
  344. * @since 4.1.0
  345. * @product highcharts
  346. * @apioption plotOptions.treemap.allowDrillToNode
  347. */
  348. /**
  349. * When the series contains less points than the crop threshold, all
  350. * points are drawn, event if the points fall outside the visible plot
  351. * area at the current zoom. The advantage of drawing all points (including
  352. * markers and columns), is that animation is performed on updates.
  353. * On the other hand, when the series contains more points than the
  354. * crop threshold, the series data is cropped to only contain points
  355. * that fall within the plot area. The advantage of cropping away invisible
  356. * points is to increase performance on large series.
  357. *
  358. * @type {Number}
  359. * @default 300
  360. * @since 4.1.0
  361. * @product highcharts
  362. * @apioption plotOptions.treemap.cropThreshold
  363. */
  364. /**
  365. * This option decides if the user can interact with the parent nodes
  366. * or just the leaf nodes. When this option is undefined, it will be
  367. * true by default. However when allowDrillToNode is true, then it will
  368. * be false by default.
  369. *
  370. * @type {Boolean}
  371. * @sample {highcharts} highcharts/plotoptions/treemap-interactbyleaf-false/ False
  372. * @sample {highcharts} highcharts/plotoptions/treemap-interactbyleaf-true-and-allowdrilltonode/ InteractByLeaf and allowDrillToNode is true
  373. * @since 4.1.2
  374. * @product highcharts
  375. * @apioption plotOptions.treemap.interactByLeaf
  376. */
  377. /**
  378. * The sort index of the point inside the treemap level.
  379. *
  380. * @type {Number}
  381. * @sample {highcharts} highcharts/plotoptions/treemap-sortindex/ Sort by years
  382. * @since 4.1.10
  383. * @product highcharts
  384. * @apioption plotOptions.treemap.sortIndex
  385. */
  386. /**
  387. * When using automatic point colors pulled from the `options.colors`
  388. * collection, this option determines whether the chart should receive
  389. * one color per series or one color per point.
  390. *
  391. * @type {Boolean}
  392. * @see [series colors](#plotOptions.treemap.colors)
  393. * @default false
  394. * @since 2.0
  395. * @apioption plotOptions.treemap.colorByPoint
  396. */
  397. /**
  398. * A series specific or series type specific color set to apply instead
  399. * of the global [colors](#colors) when [colorByPoint](
  400. * #plotOptions.treemap.colorByPoint) is true.
  401. *
  402. * @type {Array<Color>}
  403. * @since 3.0
  404. * @apioption plotOptions.treemap.colors
  405. */
  406. /**
  407. * Whether to display this series type or specific series item in the
  408. * legend.
  409. *
  410. * @type {Boolean}
  411. * @default false
  412. * @product highcharts
  413. */
  414. showInLegend: false,
  415. /**
  416. * @ignore
  417. */
  418. marker: false,
  419. colorByPoint: false,
  420. /**
  421. * @extends plotOptions.heatmap.dataLabels
  422. * @since 4.1.0
  423. * @product highcharts
  424. */
  425. dataLabels: {
  426. enabled: true,
  427. defer: false,
  428. verticalAlign: 'middle',
  429. formatter: function () { // #2945
  430. return this.point.name || this.point.id;
  431. },
  432. inside: true
  433. },
  434. tooltip: {
  435. headerFormat: '',
  436. pointFormat: '<b>{point.name}</b>: {point.value}<br/>'
  437. },
  438. /**
  439. * Whether to ignore hidden points when the layout algorithm runs.
  440. * If `false`, hidden points will leave open spaces.
  441. *
  442. * @type {Boolean}
  443. * @default true
  444. * @since 5.0.8
  445. * @product highcharts
  446. */
  447. ignoreHiddenPoint: true,
  448. /**
  449. * This option decides which algorithm is used for setting position
  450. * and dimensions of the points. Can be one of `sliceAndDice`, `stripes`,
  451. * `squarified` or `strip`.
  452. *
  453. * @validvalue ["sliceAndDice", "stripes", "squarified", "strip"]
  454. * @type {String}
  455. * @see [How to write your own algorithm](http://www.highcharts.com/docs/chart-
  456. * and-series-types/treemap)
  457. * @sample {highcharts} highcharts/plotoptions/treemap-layoutalgorithm-sliceanddice/ SliceAndDice by default
  458. * @sample {highcharts} highcharts/plotoptions/treemap-layoutalgorithm-stripes/ Stripes
  459. * @sample {highcharts} highcharts/plotoptions/treemap-layoutalgorithm-squarified/ Squarified
  460. * @sample {highcharts} highcharts/plotoptions/treemap-layoutalgorithm-strip/ Strip
  461. * @default sliceAndDice
  462. * @since 4.1.0
  463. * @product highcharts
  464. */
  465. layoutAlgorithm: 'sliceAndDice',
  466. /**
  467. * Defines which direction the layout algorithm will start drawing.
  468. * Possible values are "vertical" and "horizontal".
  469. *
  470. * @validvalue ["vertical", "horizontal"]
  471. * @type {String}
  472. * @default vertical
  473. * @since 4.1.0
  474. * @product highcharts
  475. */
  476. layoutStartingDirection: 'vertical',
  477. /**
  478. * Enabling this option will make the treemap alternate the drawing
  479. * direction between vertical and horizontal. The next levels starting
  480. * direction will always be the opposite of the previous.
  481. *
  482. * @type {Boolean}
  483. * @sample {highcharts} highcharts/plotoptions/treemap-alternatestartingdirection-true/ Enabled
  484. * @default false
  485. * @since 4.1.0
  486. * @product highcharts
  487. */
  488. alternateStartingDirection: false,
  489. /**
  490. * Used together with the levels and allowDrillToNode options. When
  491. * set to false the first level visible when drilling is considered
  492. * to be level one. Otherwise the level will be the same as the tree
  493. * structure.
  494. *
  495. * @type {Boolean}
  496. * @default true
  497. * @since 4.1.0
  498. * @product highcharts
  499. */
  500. levelIsConstant: true,
  501. /**
  502. * Options for the button appearing when drilling down in a treemap.
  503. */
  504. drillUpButton: {
  505. /**
  506. * The position of the button.
  507. */
  508. position: {
  509. /**
  510. * Vertical alignment of the button.
  511. *
  512. * @default top
  513. * @validvalue ["top", "middle", "bottom"]
  514. * @apioption plotOptions.treemap.drillUpButton.position.verticalAlign
  515. */
  516. /**
  517. * Horizontal alignment of the button.
  518. * @validvalue ["left", "center", "right"]
  519. */
  520. align: 'right',
  521. /**
  522. * Horizontal offset of the button.
  523. * @default -10
  524. * @type {Number}
  525. */
  526. x: -10,
  527. /**
  528. * Vertical offset of the button.
  529. */
  530. y: 10
  531. }
  532. },
  533. /**
  534. * Set options on specific levels. Takes precedence over series options,
  535. * but not point options.
  536. *
  537. * @type {Array<Object>}
  538. * @sample {highcharts} highcharts/plotoptions/treemap-levels/
  539. * Styling dataLabels and borders
  540. * @sample {highcharts} highcharts/demo/treemap-with-levels/
  541. * Different layoutAlgorithm
  542. * @since 4.1.0
  543. * @product highcharts
  544. * @apioption plotOptions.treemap.levels
  545. */
  546. /**
  547. * Can set a `borderColor` on all points which lies on the same level.
  548. *
  549. * @type {Color}
  550. * @since 4.1.0
  551. * @product highcharts
  552. * @apioption plotOptions.treemap.levels.borderColor
  553. */
  554. /**
  555. * Set the dash style of the border of all the point which lies on the
  556. * level. See <a href"#plotoptions.scatter.dashstyle">
  557. * plotOptions.scatter.dashStyle</a> for possible options.
  558. *
  559. * @type {String}
  560. * @since 4.1.0
  561. * @product highcharts
  562. * @apioption plotOptions.treemap.levels.borderDashStyle
  563. */
  564. /**
  565. * Can set the borderWidth on all points which lies on the same level.
  566. *
  567. * @type {Number}
  568. * @since 4.1.0
  569. * @product highcharts
  570. * @apioption plotOptions.treemap.levels.borderWidth
  571. */
  572. /**
  573. * Can set a color on all points which lies on the same level.
  574. *
  575. * @type {Color}
  576. * @since 4.1.0
  577. * @product highcharts
  578. * @apioption plotOptions.treemap.levels.color
  579. */
  580. /**
  581. * A configuration object to define how the color of a child varies from the
  582. * parent's color. The variation is distributed among the children of node.
  583. * For example when setting brightness, the brightness change will range
  584. * from the parent's original brightness on the first child, to the amount
  585. * set in the `to` setting on the last node. This allows a gradient-like
  586. * color scheme that sets children out from each other while highlighting
  587. * the grouping on treemaps and sectors on sunburst charts.
  588. *
  589. * @type {Object}
  590. * @sample highcharts/demo/sunburst/ Sunburst with color variation
  591. * @since 6.0.0
  592. * @product highcharts
  593. * @apioption plotOptions.treemap.levels.colorVariation
  594. */
  595. /**
  596. * The key of a color variation. Currently supports `brightness` only.
  597. *
  598. * @type {String}
  599. * @validvalue ["brightness"]
  600. * @since 6.0.0
  601. * @product highcharts
  602. * @apioption plotOptions.treemap.levels.colorVariation.key
  603. */
  604. /**
  605. * The ending value of a color variation. The last sibling will receive this
  606. * value.
  607. *
  608. * @type {Number}
  609. * @since 6.0.0
  610. * @product highcharts
  611. * @apioption plotOptions.treemap.levels.colorVariation.to
  612. */
  613. /**
  614. * Can set the options of dataLabels on each point which lies on the
  615. * level. [plotOptions.treemap.dataLabels](#plotOptions.treemap.dataLabels)
  616. * for possible values.
  617. *
  618. * @type {Object}
  619. * @default undefined
  620. * @since 4.1.0
  621. * @product highcharts
  622. * @apioption plotOptions.treemap.levels.dataLabels
  623. */
  624. /**
  625. * Can set the layoutAlgorithm option on a specific level.
  626. *
  627. * @validvalue ["sliceAndDice", "stripes", "squarified", "strip"]
  628. * @type {String}
  629. * @since 4.1.0
  630. * @product highcharts
  631. * @apioption plotOptions.treemap.levels.layoutAlgorithm
  632. */
  633. /**
  634. * Can set the layoutStartingDirection option on a specific level.
  635. *
  636. * @validvalue ["vertical", "horizontal"]
  637. * @type {String}
  638. * @since 4.1.0
  639. * @product highcharts
  640. * @apioption plotOptions.treemap.levels.layoutStartingDirection
  641. */
  642. /**
  643. * Decides which level takes effect from the options set in the levels
  644. * object.
  645. *
  646. * @type {Number}
  647. * @sample {highcharts} highcharts/plotoptions/treemap-levels/
  648. * Styling of both levels
  649. * @since 4.1.0
  650. * @product highcharts
  651. * @apioption plotOptions.treemap.levels.level
  652. */
  653. // Presentational options
  654. /**
  655. * The color of the border surrounding each tree map item.
  656. *
  657. * @type {Color}
  658. * @default #e6e6e6
  659. * @product highcharts
  660. */
  661. borderColor: '#e6e6e6',
  662. /**
  663. * The width of the border surrounding each tree map item.
  664. */
  665. borderWidth: 1,
  666. /**
  667. * The opacity of a point in treemap. When a point has children, the
  668. * visibility of the children is determined by the opacity.
  669. *
  670. * @type {Number}
  671. * @default 0.15
  672. * @since 4.2.4
  673. * @product highcharts
  674. */
  675. opacity: 0.15,
  676. /**
  677. * A wrapper object for all the series options in specific states.
  678. *
  679. * @extends plotOptions.heatmap.states
  680. * @product highcharts
  681. */
  682. states: {
  683. /**
  684. * Options for the hovered series
  685. *
  686. * @extends plotOptions.heatmap.states.hover
  687. * @excluding halo
  688. * @product highcharts
  689. */
  690. hover: {
  691. /**
  692. * The border color for the hovered state.
  693. */
  694. borderColor: '#999999',
  695. /**
  696. * Brightness for the hovered point. Defaults to 0 if the heatmap
  697. * series is loaded, otherwise 0.1.
  698. *
  699. * @default null
  700. * @type {Number}
  701. */
  702. brightness: seriesTypes.heatmap ? 0 : 0.1,
  703. /**
  704. * @extends plotOptions.heatmap.states.hover.halo
  705. */
  706. halo: false,
  707. /**
  708. * The opacity of a point in treemap. When a point has children,
  709. * the visibility of the children is determined by the opacity.
  710. *
  711. * @type {Number}
  712. * @default 0.75
  713. * @since 4.2.4
  714. * @product highcharts
  715. */
  716. opacity: 0.75,
  717. /**
  718. * The shadow option for hovered state.
  719. */
  720. shadow: false
  721. }
  722. }
  723. // Prototype members
  724. }, {
  725. pointArrayMap: ['value'],
  726. axisTypes: seriesTypes.heatmap ?
  727. ['xAxis', 'yAxis', 'colorAxis'] :
  728. ['xAxis', 'yAxis'],
  729. directTouch: true,
  730. optionalAxis: 'colorAxis',
  731. getSymbol: noop,
  732. parallelArrays: ['x', 'y', 'value', 'colorValue'],
  733. colorKey: 'colorValue', // Point color option key
  734. translateColors: (
  735. seriesTypes.heatmap &&
  736. seriesTypes.heatmap.prototype.translateColors
  737. ),
  738. colorAttribs: (
  739. seriesTypes.heatmap &&
  740. seriesTypes.heatmap.prototype.colorAttribs
  741. ),
  742. trackerGroups: ['group', 'dataLabelsGroup'],
  743. /**
  744. * Creates an object map from parent id to childrens index.
  745. * @param {Array} data List of points set in options.
  746. * @param {string} data[].parent Parent id of point.
  747. * @param {Array} ids List of all point ids.
  748. * @return {Object} Map from parent id to children index in data.
  749. */
  750. getListOfParents: function (data, ids) {
  751. var listOfParents = reduce(data || [], function (prev, curr, i) {
  752. var parent = pick(curr.parent, '');
  753. if (prev[parent] === undefined) {
  754. prev[parent] = [];
  755. }
  756. prev[parent].push(i);
  757. return prev;
  758. }, {});
  759. // If parent does not exist, hoist parent to root of tree.
  760. eachObject(listOfParents, function (children, parent, list) {
  761. if ((parent !== '') && (H.inArray(parent, ids) === -1)) {
  762. each(children, function (child) {
  763. list[''].push(child);
  764. });
  765. delete list[parent];
  766. }
  767. });
  768. return listOfParents;
  769. },
  770. /**
  771. * Creates a tree structured object from the series points
  772. */
  773. getTree: function () {
  774. var series = this,
  775. allIds = map(this.data, function (d) {
  776. return d.id;
  777. }),
  778. parentList = series.getListOfParents(this.data, allIds);
  779. series.nodeMap = [];
  780. return series.buildNode('', -1, 0, parentList, null);
  781. },
  782. init: function (chart, options) {
  783. var series = this;
  784. Series.prototype.init.call(series, chart, options);
  785. if (series.options.allowDrillToNode) {
  786. H.addEvent(series, 'click', series.onClickDrillToNode);
  787. }
  788. },
  789. buildNode: function (id, i, level, list, parent) {
  790. var series = this,
  791. children = [],
  792. point = series.points[i],
  793. height = 0,
  794. node,
  795. child;
  796. // Actions
  797. each((list[id] || []), function (i) {
  798. child = series.buildNode(
  799. series.points[i].id,
  800. i,
  801. (level + 1),
  802. list,
  803. id
  804. );
  805. height = Math.max(child.height + 1, height);
  806. children.push(child);
  807. });
  808. node = {
  809. id: id,
  810. i: i,
  811. children: children,
  812. height: height,
  813. level: level,
  814. parent: parent,
  815. visible: false // @todo move this to better location
  816. };
  817. series.nodeMap[node.id] = node;
  818. if (point) {
  819. point.node = node;
  820. }
  821. return node;
  822. },
  823. setTreeValues: function (tree) {
  824. var series = this,
  825. options = series.options,
  826. idRoot = series.rootNode,
  827. mapIdToNode = series.nodeMap,
  828. nodeRoot = mapIdToNode[idRoot],
  829. levelIsConstant = (
  830. isBoolean(options.levelIsConstant) ?
  831. options.levelIsConstant :
  832. true
  833. ),
  834. childrenTotal = 0,
  835. children = [],
  836. val,
  837. point = series.points[tree.i];
  838. // First give the children some values
  839. each(tree.children, function (child) {
  840. child = series.setTreeValues(child);
  841. children.push(child);
  842. if (!child.ignore) {
  843. childrenTotal += child.val;
  844. }
  845. });
  846. // Sort the children
  847. stableSort(children, function (a, b) {
  848. return a.sortIndex - b.sortIndex;
  849. });
  850. // Set the values
  851. val = pick(point && point.options.value, childrenTotal);
  852. if (point) {
  853. point.value = val;
  854. }
  855. extend(tree, {
  856. children: children,
  857. childrenTotal: childrenTotal,
  858. // Ignore this node if point is not visible
  859. ignore: !(pick(point && point.visible, true) && (val > 0)),
  860. isLeaf: tree.visible && !childrenTotal,
  861. levelDynamic: tree.level - (levelIsConstant ? 0 : nodeRoot.level),
  862. name: pick(point && point.name, ''),
  863. sortIndex: pick(point && point.sortIndex, -val),
  864. val: val
  865. });
  866. return tree;
  867. },
  868. /**
  869. * Recursive function which calculates the area for all children of a node.
  870. * @param {Object} node The node which is parent to the children.
  871. * @param {Object} area The rectangular area of the parent.
  872. */
  873. calculateChildrenAreas: function (parent, area) {
  874. var series = this,
  875. options = series.options,
  876. mapOptionsToLevel = series.mapOptionsToLevel,
  877. level = mapOptionsToLevel[parent.level + 1],
  878. algorithm = pick(
  879. (
  880. series[level &&
  881. level.layoutAlgorithm] &&
  882. level.layoutAlgorithm
  883. ),
  884. options.layoutAlgorithm
  885. ),
  886. alternate = options.alternateStartingDirection,
  887. childrenValues = [],
  888. children;
  889. // Collect all children which should be included
  890. children = grep(parent.children, function (n) {
  891. return !n.ignore;
  892. });
  893. if (level && level.layoutStartingDirection) {
  894. area.direction = level.layoutStartingDirection === 'vertical' ?
  895. 0 :
  896. 1;
  897. }
  898. childrenValues = series[algorithm](area, children);
  899. each(children, function (child, index) {
  900. var values = childrenValues[index];
  901. child.values = merge(values, {
  902. val: child.childrenTotal,
  903. direction: (alternate ? 1 - area.direction : area.direction)
  904. });
  905. child.pointValues = merge(values, {
  906. x: (values.x / series.axisRatio),
  907. width: (values.width / series.axisRatio)
  908. });
  909. // If node has children, then call method recursively
  910. if (child.children.length) {
  911. series.calculateChildrenAreas(child, child.values);
  912. }
  913. });
  914. },
  915. setPointValues: function () {
  916. var series = this,
  917. xAxis = series.xAxis,
  918. yAxis = series.yAxis;
  919. each(series.points, function (point) {
  920. var node = point.node,
  921. values = node.pointValues,
  922. x1,
  923. x2,
  924. y1,
  925. y2,
  926. crispCorr = 0;
  927. // Get the crisp correction in classic mode. For this to work in
  928. // styled mode, we would need to first add the shape (without x, y,
  929. // width and height), then read the rendered stroke width using
  930. // point.graphic.strokeWidth(), then modify and apply the shapeArgs.
  931. // This applies also to column series, but the downside is
  932. // performance and code complexity.
  933. crispCorr = (
  934. (series.pointAttribs(point)['stroke-width'] || 0) % 2
  935. ) / 2;
  936. // Points which is ignored, have no values.
  937. if (values && node.visible) {
  938. x1 = Math.round(
  939. xAxis.translate(values.x, 0, 0, 0, 1)
  940. ) - crispCorr;
  941. x2 = Math.round(
  942. xAxis.translate(values.x + values.width, 0, 0, 0, 1)
  943. ) - crispCorr;
  944. y1 = Math.round(
  945. yAxis.translate(values.y, 0, 0, 0, 1)
  946. ) - crispCorr;
  947. y2 = Math.round(
  948. yAxis.translate(values.y + values.height, 0, 0, 0, 1)
  949. ) - crispCorr;
  950. // Set point values
  951. point.shapeType = 'rect';
  952. point.shapeArgs = {
  953. x: Math.min(x1, x2),
  954. y: Math.min(y1, y2),
  955. width: Math.abs(x2 - x1),
  956. height: Math.abs(y2 - y1)
  957. };
  958. point.plotX = point.shapeArgs.x + (point.shapeArgs.width / 2);
  959. point.plotY = point.shapeArgs.y + (point.shapeArgs.height / 2);
  960. } else {
  961. // Reset visibility
  962. delete point.plotX;
  963. delete point.plotY;
  964. }
  965. });
  966. },
  967. /**
  968. * Set the node's color recursively, from the parent down.
  969. */
  970. setColorRecursive: function (
  971. node,
  972. parentColor,
  973. colorIndex,
  974. index,
  975. siblings
  976. ) {
  977. var series = this,
  978. chart = series && series.chart,
  979. colors = chart && chart.options && chart.options.colors,
  980. colorInfo,
  981. point;
  982. if (node) {
  983. colorInfo = getColor(node, {
  984. colors: colors,
  985. index: index,
  986. mapOptionsToLevel: series.mapOptionsToLevel,
  987. parentColor: parentColor,
  988. parentColorIndex: colorIndex,
  989. series: series,
  990. siblings: siblings
  991. });
  992. point = series.points[node.i];
  993. if (point) {
  994. point.color = colorInfo.color;
  995. point.colorIndex = colorInfo.colorIndex;
  996. }
  997. // Do it all again with the children
  998. each(node.children || [], function (child, i) {
  999. series.setColorRecursive(
  1000. child,
  1001. colorInfo.color,
  1002. colorInfo.colorIndex,
  1003. i,
  1004. node.children.length
  1005. );
  1006. });
  1007. }
  1008. },
  1009. algorithmGroup: function (h, w, d, p) {
  1010. this.height = h;
  1011. this.width = w;
  1012. this.plot = p;
  1013. this.direction = d;
  1014. this.startDirection = d;
  1015. this.total = 0;
  1016. this.nW = 0;
  1017. this.lW = 0;
  1018. this.nH = 0;
  1019. this.lH = 0;
  1020. this.elArr = [];
  1021. this.lP = {
  1022. total: 0,
  1023. lH: 0,
  1024. nH: 0,
  1025. lW: 0,
  1026. nW: 0,
  1027. nR: 0,
  1028. lR: 0,
  1029. aspectRatio: function (w, h) {
  1030. return Math.max((w / h), (h / w));
  1031. }
  1032. };
  1033. this.addElement = function (el) {
  1034. this.lP.total = this.elArr[this.elArr.length - 1];
  1035. this.total = this.total + el;
  1036. if (this.direction === 0) {
  1037. // Calculate last point old aspect ratio
  1038. this.lW = this.nW;
  1039. this.lP.lH = this.lP.total / this.lW;
  1040. this.lP.lR = this.lP.aspectRatio(this.lW, this.lP.lH);
  1041. // Calculate last point new aspect ratio
  1042. this.nW = this.total / this.height;
  1043. this.lP.nH = this.lP.total / this.nW;
  1044. this.lP.nR = this.lP.aspectRatio(this.nW, this.lP.nH);
  1045. } else {
  1046. // Calculate last point old aspect ratio
  1047. this.lH = this.nH;
  1048. this.lP.lW = this.lP.total / this.lH;
  1049. this.lP.lR = this.lP.aspectRatio(this.lP.lW, this.lH);
  1050. // Calculate last point new aspect ratio
  1051. this.nH = this.total / this.width;
  1052. this.lP.nW = this.lP.total / this.nH;
  1053. this.lP.nR = this.lP.aspectRatio(this.lP.nW, this.nH);
  1054. }
  1055. this.elArr.push(el);
  1056. };
  1057. this.reset = function () {
  1058. this.nW = 0;
  1059. this.lW = 0;
  1060. this.elArr = [];
  1061. this.total = 0;
  1062. };
  1063. },
  1064. algorithmCalcPoints: function (directionChange, last, group, childrenArea) {
  1065. var pX,
  1066. pY,
  1067. pW,
  1068. pH,
  1069. gW = group.lW,
  1070. gH = group.lH,
  1071. plot = group.plot,
  1072. keep,
  1073. i = 0,
  1074. end = group.elArr.length - 1;
  1075. if (last) {
  1076. gW = group.nW;
  1077. gH = group.nH;
  1078. } else {
  1079. keep = group.elArr[group.elArr.length - 1];
  1080. }
  1081. each(group.elArr, function (p) {
  1082. if (last || (i < end)) {
  1083. if (group.direction === 0) {
  1084. pX = plot.x;
  1085. pY = plot.y;
  1086. pW = gW;
  1087. pH = p / pW;
  1088. } else {
  1089. pX = plot.x;
  1090. pY = plot.y;
  1091. pH = gH;
  1092. pW = p / pH;
  1093. }
  1094. childrenArea.push({
  1095. x: pX,
  1096. y: pY,
  1097. width: pW,
  1098. height: pH
  1099. });
  1100. if (group.direction === 0) {
  1101. plot.y = plot.y + pH;
  1102. } else {
  1103. plot.x = plot.x + pW;
  1104. }
  1105. }
  1106. i = i + 1;
  1107. });
  1108. // Reset variables
  1109. group.reset();
  1110. if (group.direction === 0) {
  1111. group.width = group.width - gW;
  1112. } else {
  1113. group.height = group.height - gH;
  1114. }
  1115. plot.y = plot.parent.y + (plot.parent.height - group.height);
  1116. plot.x = plot.parent.x + (plot.parent.width - group.width);
  1117. if (directionChange) {
  1118. group.direction = 1 - group.direction;
  1119. }
  1120. // If not last, then add uncalculated element
  1121. if (!last) {
  1122. group.addElement(keep);
  1123. }
  1124. },
  1125. algorithmLowAspectRatio: function (directionChange, parent, children) {
  1126. var childrenArea = [],
  1127. series = this,
  1128. pTot,
  1129. plot = {
  1130. x: parent.x,
  1131. y: parent.y,
  1132. parent: parent
  1133. },
  1134. direction = parent.direction,
  1135. i = 0,
  1136. end = children.length - 1,
  1137. group = new this.algorithmGroup( // eslint-disable-line new-cap
  1138. parent.height,
  1139. parent.width,
  1140. direction,
  1141. plot
  1142. );
  1143. // Loop through and calculate all areas
  1144. each(children, function (child) {
  1145. pTot = (parent.width * parent.height) * (child.val / parent.val);
  1146. group.addElement(pTot);
  1147. if (group.lP.nR > group.lP.lR) {
  1148. series.algorithmCalcPoints(
  1149. directionChange,
  1150. false,
  1151. group,
  1152. childrenArea,
  1153. plot
  1154. );
  1155. }
  1156. // If last child, then calculate all remaining areas
  1157. if (i === end) {
  1158. series.algorithmCalcPoints(
  1159. directionChange,
  1160. true,
  1161. group,
  1162. childrenArea,
  1163. plot
  1164. );
  1165. }
  1166. i = i + 1;
  1167. });
  1168. return childrenArea;
  1169. },
  1170. algorithmFill: function (directionChange, parent, children) {
  1171. var childrenArea = [],
  1172. pTot,
  1173. direction = parent.direction,
  1174. x = parent.x,
  1175. y = parent.y,
  1176. width = parent.width,
  1177. height = parent.height,
  1178. pX,
  1179. pY,
  1180. pW,
  1181. pH;
  1182. each(children, function (child) {
  1183. pTot = (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. each(children, 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: (options.layoutStartingDirection === 'vertical' ? 0 : 1),
  1285. val: tree.val
  1286. });
  1287. series.calculateChildrenAreas(tree, seriesArea);
  1288. // Logic for point colors
  1289. if (series.colorAxis) {
  1290. series.translateColors();
  1291. } else if (!options.colorByPoint) {
  1292. series.setColorRecursive(series.tree);
  1293. }
  1294. // Update axis extremes according to the root node.
  1295. if (options.allowDrillToNode) {
  1296. val = rootNode.pointValues;
  1297. series.xAxis.setExtremes(val.x, val.x + val.width, false);
  1298. series.yAxis.setExtremes(val.y, val.y + val.height, false);
  1299. series.xAxis.setScale();
  1300. series.yAxis.setScale();
  1301. }
  1302. // Assign values to points.
  1303. series.setPointValues();
  1304. },
  1305. /**
  1306. * Extend drawDataLabels with logic to handle custom options related to the
  1307. * treemap series:
  1308. * - Points which is not a leaf node, has dataLabels disabled by default.
  1309. * - Options set on series.levels is merged in.
  1310. * - Width of the dataLabel is set to match the width of the point shape.
  1311. */
  1312. drawDataLabels: function () {
  1313. var series = this,
  1314. mapOptionsToLevel = series.mapOptionsToLevel,
  1315. points = grep(series.points, function (n) {
  1316. return n.node.visible;
  1317. }),
  1318. options,
  1319. level;
  1320. each(points, function (point) {
  1321. level = mapOptionsToLevel[point.node.level];
  1322. // Set options to new object to avoid problems with scope
  1323. options = { style: {} };
  1324. // If not a leaf, then label should be disabled as default
  1325. if (!point.node.isLeaf) {
  1326. options.enabled = false;
  1327. }
  1328. // If options for level exists, include them as well
  1329. if (level && level.dataLabels) {
  1330. options = merge(options, level.dataLabels);
  1331. series._hasPointLabels = true;
  1332. }
  1333. // Set dataLabel width to the width of the point shape.
  1334. if (point.shapeArgs) {
  1335. options.style.width = point.shapeArgs.width;
  1336. if (point.dataLabel) {
  1337. point.dataLabel.css({
  1338. width: point.shapeArgs.width + 'px'
  1339. });
  1340. }
  1341. }
  1342. // Merge custom options with point options
  1343. point.dlOptions = merge(options, point.options.dataLabels);
  1344. });
  1345. Series.prototype.drawDataLabels.call(this);
  1346. },
  1347. /**
  1348. * Over the alignment method by setting z index
  1349. */
  1350. alignDataLabel: function (point) {
  1351. seriesTypes.column.prototype.alignDataLabel.apply(this, arguments);
  1352. if (point.dataLabel) {
  1353. // point.node.zIndex could be undefined (#6956)
  1354. point.dataLabel.attr({ zIndex: (point.node.zIndex || 0) + 1 });
  1355. }
  1356. },
  1357. /**
  1358. * Get presentational attributes
  1359. */
  1360. pointAttribs: function (point, state) {
  1361. var series = this,
  1362. mapOptionsToLevel = (
  1363. isObject(series.mapOptionsToLevel) ?
  1364. series.mapOptionsToLevel :
  1365. {}
  1366. ),
  1367. level = point && mapOptionsToLevel[point.node.level] || {},
  1368. options = this.options,
  1369. attr,
  1370. stateOptions = (state && options.states[state]) || {},
  1371. className = (point && point.getClassName()) || '',
  1372. opacity;
  1373. // Set attributes by precedence. Point trumps level trumps series.
  1374. // Stroke width uses pick because it can be 0.
  1375. attr = {
  1376. 'stroke':
  1377. (point && point.borderColor) ||
  1378. level.borderColor ||
  1379. stateOptions.borderColor ||
  1380. options.borderColor,
  1381. 'stroke-width': pick(
  1382. point && point.borderWidth,
  1383. level.borderWidth,
  1384. stateOptions.borderWidth,
  1385. options.borderWidth
  1386. ),
  1387. 'dashstyle':
  1388. (point && point.borderDashStyle) ||
  1389. level.borderDashStyle ||
  1390. stateOptions.borderDashStyle ||
  1391. options.borderDashStyle,
  1392. 'fill': (point && point.color) || this.color
  1393. };
  1394. // Hide levels above the current view
  1395. if (className.indexOf('highcharts-above-level') !== -1) {
  1396. attr.fill = 'none';
  1397. attr['stroke-width'] = 0;
  1398. // Nodes with children that accept interaction
  1399. } else if (
  1400. className.indexOf('highcharts-internal-node-interactive') !== -1
  1401. ) {
  1402. opacity = pick(stateOptions.opacity, options.opacity);
  1403. attr.fill = color(attr.fill).setOpacity(opacity).get();
  1404. attr.cursor = 'pointer';
  1405. // Hide nodes that have children
  1406. } else if (className.indexOf('highcharts-internal-node') !== -1) {
  1407. attr.fill = 'none';
  1408. } else if (state) {
  1409. // Brighten and hoist the hover nodes
  1410. attr.fill = color(attr.fill)
  1411. .brighten(stateOptions.brightness)
  1412. .get();
  1413. }
  1414. return attr;
  1415. },
  1416. /**
  1417. * Extending ColumnSeries drawPoints
  1418. */
  1419. drawPoints: function () {
  1420. var series = this,
  1421. points = grep(series.points, function (n) {
  1422. return n.node.visible;
  1423. });
  1424. each(points, function (point) {
  1425. var groupKey = 'level-group-' + point.node.levelDynamic;
  1426. if (!series[groupKey]) {
  1427. series[groupKey] = series.chart.renderer.g(groupKey)
  1428. .attr({
  1429. // @todo Set the zIndex based upon the number of levels,
  1430. // instead of using 1000
  1431. zIndex: 1000 - point.node.levelDynamic
  1432. })
  1433. .add(series.group);
  1434. }
  1435. point.group = series[groupKey];
  1436. });
  1437. // Call standard drawPoints
  1438. seriesTypes.column.prototype.drawPoints.call(this);
  1439. // If drillToNode is allowed, set a point cursor on clickables & add
  1440. // drillId to point
  1441. if (series.options.allowDrillToNode) {
  1442. each(points, function (point) {
  1443. if (point.graphic) {
  1444. point.drillId = series.options.interactByLeaf ?
  1445. series.drillToByLeaf(point) :
  1446. series.drillToByGroup(point);
  1447. }
  1448. });
  1449. }
  1450. },
  1451. /**
  1452. * Add drilling on the suitable points
  1453. */
  1454. onClickDrillToNode: function (event) {
  1455. var series = this,
  1456. point = event.point,
  1457. drillId = point && point.drillId;
  1458. // If a drill id is returned, add click event and cursor.
  1459. if (isString(drillId)) {
  1460. point.setState(''); // Remove hover
  1461. series.drillToNode(drillId);
  1462. }
  1463. },
  1464. /**
  1465. * Finds the drill id for a parent node.
  1466. * Returns false if point should not have a click event
  1467. * @param {Object} point
  1468. * @return {String|Boolean} Drill to id or false when point should not have a
  1469. * click event
  1470. */
  1471. drillToByGroup: function (point) {
  1472. var series = this,
  1473. drillId = false;
  1474. if (
  1475. (point.node.level - series.nodeMap[series.rootNode].level) === 1 &&
  1476. !point.node.isLeaf
  1477. ) {
  1478. drillId = point.id;
  1479. }
  1480. return drillId;
  1481. },
  1482. /**
  1483. * Finds the drill id for a leaf node.
  1484. * Returns false if point should not have a click event
  1485. * @param {Object} point
  1486. * @return {String|Boolean} Drill to id or false when point should not have a
  1487. * click event
  1488. */
  1489. drillToByLeaf: function (point) {
  1490. var series = this,
  1491. drillId = false,
  1492. nodeParent;
  1493. if ((point.node.parent !== series.rootNode) && (point.node.isLeaf)) {
  1494. nodeParent = point.node;
  1495. while (!drillId) {
  1496. nodeParent = series.nodeMap[nodeParent.parent];
  1497. if (nodeParent.parent === series.rootNode) {
  1498. drillId = nodeParent.id;
  1499. }
  1500. }
  1501. }
  1502. return drillId;
  1503. },
  1504. drillUp: function () {
  1505. var series = this,
  1506. node = series.nodeMap[series.rootNode];
  1507. if (node && isString(node.parent)) {
  1508. series.drillToNode(node.parent);
  1509. }
  1510. },
  1511. drillToNode: function (id, redraw) {
  1512. var series = this,
  1513. nodeMap = series.nodeMap,
  1514. node = nodeMap[id];
  1515. series.idPreviousRoot = series.rootNode;
  1516. series.rootNode = id;
  1517. if (id === '') {
  1518. series.drillUpButton = series.drillUpButton.destroy();
  1519. } else {
  1520. series.showDrillUpButton((node && node.name || id));
  1521. }
  1522. this.isDirty = true; // Force redraw
  1523. if (pick(redraw, true)) {
  1524. this.chart.redraw();
  1525. }
  1526. },
  1527. showDrillUpButton: function (name) {
  1528. var series = this,
  1529. backText = (name || '< Back'),
  1530. buttonOptions = series.options.drillUpButton,
  1531. attr,
  1532. states;
  1533. if (buttonOptions.text) {
  1534. backText = buttonOptions.text;
  1535. }
  1536. if (!this.drillUpButton) {
  1537. attr = buttonOptions.theme;
  1538. states = attr && attr.states;
  1539. this.drillUpButton = this.chart.renderer.button(
  1540. backText,
  1541. null,
  1542. null,
  1543. function () {
  1544. series.drillUp();
  1545. },
  1546. attr,
  1547. states && states.hover,
  1548. states && states.select
  1549. )
  1550. .addClass('highcharts-drillup-button')
  1551. .attr({
  1552. align: buttonOptions.position.align,
  1553. zIndex: 7
  1554. })
  1555. .add()
  1556. .align(
  1557. buttonOptions.position,
  1558. false,
  1559. buttonOptions.relativeTo || 'plotBox'
  1560. );
  1561. } else {
  1562. this.drillUpButton.placed = false;
  1563. this.drillUpButton.attr({
  1564. text: backText
  1565. })
  1566. .align();
  1567. }
  1568. },
  1569. buildKDTree: noop,
  1570. drawLegendSymbol: H.LegendSymbolMixin.drawRectangle,
  1571. getExtremes: function () {
  1572. // Get the extremes from the value data
  1573. Series.prototype.getExtremes.call(this, this.colorValueData);
  1574. this.valueMin = this.dataMin;
  1575. this.valueMax = this.dataMax;
  1576. // Get the extremes from the y data
  1577. Series.prototype.getExtremes.call(this);
  1578. },
  1579. getExtremesFromAll: true,
  1580. bindAxes: function () {
  1581. var treeAxis = {
  1582. endOnTick: false,
  1583. gridLineWidth: 0,
  1584. lineWidth: 0,
  1585. min: 0,
  1586. dataMin: 0,
  1587. minPadding: 0,
  1588. max: 100,
  1589. dataMax: 100,
  1590. maxPadding: 0,
  1591. startOnTick: false,
  1592. title: null,
  1593. tickPositions: []
  1594. };
  1595. Series.prototype.bindAxes.call(this);
  1596. H.extend(this.yAxis.options, treeAxis);
  1597. H.extend(this.xAxis.options, treeAxis);
  1598. },
  1599. utils: {
  1600. recursive: recursive,
  1601. reduce: reduce
  1602. }
  1603. // Point class
  1604. }, {
  1605. getClassName: function () {
  1606. var className = H.Point.prototype.getClassName.call(this),
  1607. series = this.series,
  1608. options = series.options;
  1609. // Above the current level
  1610. if (this.node.level <= series.nodeMap[series.rootNode].level) {
  1611. className += ' highcharts-above-level';
  1612. } else if (
  1613. !this.node.isLeaf &&
  1614. !pick(options.interactByLeaf, !options.allowDrillToNode)
  1615. ) {
  1616. className += ' highcharts-internal-node-interactive';
  1617. } else if (!this.node.isLeaf) {
  1618. className += ' highcharts-internal-node';
  1619. }
  1620. return className;
  1621. },
  1622. /**
  1623. * A tree point is valid if it has han id too, assume it may be a parent
  1624. * item.
  1625. */
  1626. isValid: function () {
  1627. return this.id || isNumber(this.value);
  1628. },
  1629. setState: function (state) {
  1630. H.Point.prototype.setState.call(this, state);
  1631. // Graphic does not exist when point is not visible.
  1632. if (this.graphic) {
  1633. this.graphic.attr({
  1634. zIndex: state === 'hover' ? 1 : 0
  1635. });
  1636. }
  1637. },
  1638. setVisible: seriesTypes.pie.prototype.pointClass.prototype.setVisible
  1639. });
  1640. /**
  1641. * A `treemap` series. If the [type](#series.treemap.type) option is
  1642. * not specified, it is inherited from [chart.type](#chart.type).
  1643. *
  1644. * @type {Object}
  1645. * @extends series,plotOptions.treemap
  1646. * @excluding dataParser,dataURL,stack
  1647. * @product highcharts
  1648. * @apioption series.treemap
  1649. */
  1650. /**
  1651. * An array of data points for the series. For the `treemap` series
  1652. * type, points can be given in the following ways:
  1653. *
  1654. * 1. An array of numerical values. In this case, the numerical values
  1655. * will be interpreted as `value` options. Example:
  1656. *
  1657. * ```js
  1658. * data: [0, 5, 3, 5]
  1659. * ```
  1660. *
  1661. * 2. An array of objects with named values. The objects are point
  1662. * configuration objects as seen below. If the total number of data
  1663. * points exceeds the series' [turboThreshold](#series.treemap.turboThreshold),
  1664. * this option is not available.
  1665. *
  1666. * ```js
  1667. * data: [{
  1668. * value: 9,
  1669. * name: "Point2",
  1670. * color: "#00FF00"
  1671. * }, {
  1672. * value: 6,
  1673. * name: "Point1",
  1674. * color: "#FF00FF"
  1675. * }]
  1676. * ```
  1677. *
  1678. * @type {Array<Object|Number>}
  1679. * @extends series.heatmap.data
  1680. * @excluding x,y
  1681. * @sample {highcharts} highcharts/chart/reflow-true/
  1682. * Numerical values
  1683. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  1684. * Arrays of numeric x and y
  1685. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  1686. * Arrays of datetime x and y
  1687. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  1688. * Arrays of point.name and y
  1689. * @sample {highcharts} highcharts/series/data-array-of-objects/
  1690. * Config objects
  1691. * @product highcharts
  1692. * @apioption series.treemap.data
  1693. */
  1694. /**
  1695. * The value of the point, resulting in a relative area of the point
  1696. * in the treemap.
  1697. *
  1698. * @type {Number}
  1699. * @product highcharts
  1700. * @apioption series.treemap.data.value
  1701. */
  1702. /**
  1703. * Serves a purpose only if a `colorAxis` object is defined in the chart
  1704. * options. This value will decide which color the point gets from the
  1705. * scale of the colorAxis.
  1706. *
  1707. * @type {Number}
  1708. * @default undefined
  1709. * @since 4.1.0
  1710. * @product highcharts
  1711. * @apioption series.treemap.data.colorValue
  1712. */
  1713. /**
  1714. * Only for treemap. Use this option to build a tree structure. The
  1715. * value should be the id of the point which is the parent. If no points
  1716. * has a matching id, or this option is undefined, then the parent will
  1717. * be set to the root.
  1718. *
  1719. * @type {String}
  1720. * @sample {highcharts} highcharts/point/parent/ Point parent
  1721. * @sample {highcharts} highcharts/demo/treemap-with-levels/ Example where parent id is not matching
  1722. * @default undefined
  1723. * @since 4.1.0
  1724. * @product highcharts
  1725. * @apioption series.treemap.data.parent
  1726. */
  1727. }(Highcharts, result));
  1728. (function (H, drawPoint, mixinTreeSeries) {
  1729. /**
  1730. * (c) 2016 Highsoft AS
  1731. * Authors: Jon Arild Nygard
  1732. *
  1733. * License: www.highcharts.com/license
  1734. *
  1735. * This module implements sunburst charts in Highcharts.
  1736. */
  1737. var CenteredSeriesMixin = H.CenteredSeriesMixin,
  1738. Series = H.Series,
  1739. each = H.each,
  1740. extend = H.extend,
  1741. getCenter = CenteredSeriesMixin.getCenter,
  1742. getColor = mixinTreeSeries.getColor,
  1743. getLevelOptions = mixinTreeSeries.getLevelOptions,
  1744. getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians,
  1745. grep = H.grep,
  1746. inArray = H.inArray,
  1747. isBoolean = function (x) {
  1748. return typeof x === 'boolean';
  1749. },
  1750. isNumber = H.isNumber,
  1751. isObject = H.isObject,
  1752. isString = H.isString,
  1753. keys = H.keys,
  1754. merge = H.merge,
  1755. noop = H.noop,
  1756. rad2deg = 180 / Math.PI,
  1757. seriesType = H.seriesType,
  1758. seriesTypes = H.seriesTypes,
  1759. setTreeValues = mixinTreeSeries.setTreeValues,
  1760. reduce = H.reduce,
  1761. updateRootId = mixinTreeSeries.updateRootId;
  1762. // TODO introduce step, which should default to 1.
  1763. var range = function range(from, to) {
  1764. var result = [],
  1765. i;
  1766. if (isNumber(from) && isNumber(to) && from <= to) {
  1767. for (i = from; i <= to; i++) {
  1768. result.push(i);
  1769. }
  1770. }
  1771. return result;
  1772. };
  1773. /**
  1774. * @param {Object} levelOptions Map of level to its options.
  1775. * @param {Object} params Object containing parameters.
  1776. * @param {Number} params.innerRadius
  1777. * @param {Number} params.outerRadius
  1778. * @
  1779. */
  1780. var calculateLevelSizes = function calculateLevelSizes(levelOptions, params) {
  1781. var result,
  1782. p = isObject(params) ? params : {},
  1783. totalWeight = 0,
  1784. diffRadius,
  1785. levels,
  1786. levelsNotIncluded,
  1787. remainingSize,
  1788. from,
  1789. to;
  1790. if (isObject(levelOptions)) {
  1791. result = merge({}, levelOptions); // Copy levelOptions
  1792. from = isNumber(p.from) ? p.from : 0;
  1793. to = isNumber(p.to) ? p.to : 0;
  1794. levels = range(from, to);
  1795. levelsNotIncluded = grep(keys(result), function (k) {
  1796. return inArray(+k, levels) === -1;
  1797. });
  1798. diffRadius = remainingSize = isNumber(p.diffRadius) ? p.diffRadius : 0;
  1799. /**
  1800. * Convert percentage to pixels.
  1801. * Calculate the remaining size to divide between "weight" levels.
  1802. * Calculate total weight to use in convertion from weight to pixels.
  1803. */
  1804. each(levels, function (level) {
  1805. var options = result[level],
  1806. unit = options.levelSize.unit,
  1807. value = options.levelSize.value;
  1808. if (unit === 'weight') {
  1809. totalWeight += value;
  1810. } else if (unit === 'percentage') {
  1811. options.levelSize = {
  1812. unit: 'pixels',
  1813. value: (value / 100) * diffRadius
  1814. };
  1815. remainingSize -= options.levelSize.value;
  1816. } else if (unit === 'pixels') {
  1817. remainingSize -= value;
  1818. }
  1819. });
  1820. // Convert weight to pixels.
  1821. each(levels, function (level) {
  1822. var options = result[level],
  1823. weight;
  1824. if (options.levelSize.unit === 'weight') {
  1825. weight = options.levelSize.value;
  1826. result[level].levelSize = {
  1827. unit: 'pixels',
  1828. value: (weight / totalWeight) * remainingSize
  1829. };
  1830. }
  1831. });
  1832. // Set all levels not included in interval [from,to] to have 0 pixels.
  1833. each(levelsNotIncluded, function (level) {
  1834. result[level].levelSize = {
  1835. value: 0,
  1836. unit: 'pixels'
  1837. };
  1838. });
  1839. }
  1840. return result;
  1841. };
  1842. /**
  1843. * getEndPoint - Find a set of coordinates given a start coordinates, an angle,
  1844. * and a distance.
  1845. *
  1846. * @param {number} x Start coordinate x
  1847. * @param {number} y Start coordinate y
  1848. * @param {number} angle Angle in radians
  1849. * @param {number} distance Distance from start to end coordinates
  1850. * @return {object} Returns the end coordinates, x and y.
  1851. */
  1852. var getEndPoint = function getEndPoint(x, y, angle, distance) {
  1853. return {
  1854. x: x + (Math.cos(angle) * distance),
  1855. y: y + (Math.sin(angle) * distance)
  1856. };
  1857. };
  1858. var layoutAlgorithm = function layoutAlgorithm(parent, children, options) {
  1859. var startAngle = parent.start,
  1860. range = parent.end - startAngle,
  1861. total = parent.val,
  1862. x = parent.x,
  1863. y = parent.y,
  1864. radius = (
  1865. isObject(options.levelSize) && isNumber(options.levelSize.value) ?
  1866. options.levelSize.value :
  1867. 0
  1868. ),
  1869. innerRadius = parent.r,
  1870. outerRadius = innerRadius + radius,
  1871. slicedOffset = isNumber(options.slicedOffset) ?
  1872. options.slicedOffset :
  1873. 0;
  1874. return reduce(children || [], function (arr, child) {
  1875. var percentage = (1 / total) * child.val,
  1876. radians = percentage * range,
  1877. radiansCenter = startAngle + (radians / 2),
  1878. offsetPosition = getEndPoint(x, y, radiansCenter, slicedOffset),
  1879. values = {
  1880. x: child.sliced ? offsetPosition.x : x,
  1881. y: child.sliced ? offsetPosition.y : y,
  1882. innerR: innerRadius,
  1883. r: outerRadius,
  1884. radius: radius,
  1885. start: startAngle,
  1886. end: startAngle + radians
  1887. };
  1888. arr.push(values);
  1889. startAngle = values.end;
  1890. return arr;
  1891. }, []);
  1892. };
  1893. var getDlOptions = function getDlOptions(params) {
  1894. // Set options to new object to avoid problems with scope
  1895. var shape = isObject(params.shapeArgs) ? params.shapeArgs : {},
  1896. optionsPoint = (
  1897. isObject(params.optionsPoint) ?
  1898. params.optionsPoint.dataLabels :
  1899. {}
  1900. ),
  1901. optionsLevel = (
  1902. isObject(params.level) ?
  1903. params.level.dataLabels :
  1904. {}
  1905. ),
  1906. options = merge({
  1907. rotationMode: 'perpendicular',
  1908. style: {
  1909. width: shape.radius
  1910. }
  1911. }, optionsLevel, optionsPoint),
  1912. rotationRad,
  1913. rotation;
  1914. if (!isNumber(options.rotation)) {
  1915. rotationRad = (shape.end - (shape.end - shape.start) / 2);
  1916. rotation = (rotationRad * rad2deg) % 180;
  1917. if (options.rotationMode === 'parallel') {
  1918. rotation -= 90;
  1919. }
  1920. // Data labels should not rotate beyond 90 degrees, for readability.
  1921. if (rotation > 90) {
  1922. rotation -= 180;
  1923. }
  1924. options.rotation = rotation;
  1925. }
  1926. // NOTE: alignDataLabel positions the data label differntly when rotation is
  1927. // 0. Avoiding this by setting rotation to a small number.
  1928. if (options.rotation === 0) {
  1929. options.rotation = 0.001;
  1930. }
  1931. return options;
  1932. };
  1933. var getAnimation = function getAnimation(shape, params) {
  1934. var point = params.point,
  1935. radians = params.radians,
  1936. innerR = params.innerR,
  1937. idRoot = params.idRoot,
  1938. idPreviousRoot = params.idPreviousRoot,
  1939. shapeExisting = params.shapeExisting,
  1940. shapeRoot = params.shapeRoot,
  1941. shapePreviousRoot = params.shapePreviousRoot,
  1942. visible = params.visible,
  1943. from = {},
  1944. to = {
  1945. end: shape.end,
  1946. start: shape.start,
  1947. innerR: shape.innerR,
  1948. r: shape.r,
  1949. x: shape.x,
  1950. y: shape.y
  1951. };
  1952. if (visible) {
  1953. // Animate points in
  1954. if (!point.graphic && shapePreviousRoot) {
  1955. if (idRoot === point.id) {
  1956. from = {
  1957. start: radians.start,
  1958. end: radians.end
  1959. };
  1960. } else {
  1961. from = (shapePreviousRoot.end <= shape.start) ? {
  1962. start: radians.end,
  1963. end: radians.end
  1964. } : {
  1965. start: radians.start,
  1966. end: radians.start
  1967. };
  1968. }
  1969. // Animate from center and outwards.
  1970. from.innerR = from.r = innerR;
  1971. }
  1972. } else {
  1973. // Animate points out
  1974. if (point.graphic) {
  1975. if (idPreviousRoot === point.id) {
  1976. to = {
  1977. innerR: innerR,
  1978. r: innerR
  1979. };
  1980. } else if (shapeRoot) {
  1981. to = (shapeRoot.end <= shapeExisting.start) ?
  1982. {
  1983. innerR: innerR,
  1984. r: innerR,
  1985. start: radians.end,
  1986. end: radians.end
  1987. } : {
  1988. innerR: innerR,
  1989. r: innerR,
  1990. start: radians.start,
  1991. end: radians.start
  1992. };
  1993. }
  1994. }
  1995. }
  1996. return {
  1997. from: from,
  1998. to: to
  1999. };
  2000. };
  2001. var getDrillId = function getDrillId(point, idRoot, mapIdToNode) {
  2002. var drillId,
  2003. node = point.node,
  2004. nodeRoot;
  2005. if (!node.isLeaf) {
  2006. // When it is the root node, the drillId should be set to parent.
  2007. if (idRoot === point.id) {
  2008. nodeRoot = mapIdToNode[idRoot];
  2009. drillId = nodeRoot.parent;
  2010. } else {
  2011. drillId = point.id;
  2012. }
  2013. }
  2014. return drillId;
  2015. };
  2016. var cbSetTreeValuesBefore = function before(node, options) {
  2017. var mapIdToNode = options.mapIdToNode,
  2018. nodeParent = mapIdToNode[node.parent],
  2019. series = options.series,
  2020. chart = series.chart,
  2021. points = series.points,
  2022. point = points[node.i],
  2023. colorInfo = getColor(node, {
  2024. colors: chart && chart.options && chart.options.colors,
  2025. colorIndex: series.colorIndex,
  2026. index: options.index,
  2027. mapOptionsToLevel: options.mapOptionsToLevel,
  2028. parentColor: nodeParent && nodeParent.color,
  2029. parentColorIndex: nodeParent && nodeParent.colorIndex,
  2030. series: options.series,
  2031. siblings: options.siblings
  2032. });
  2033. node.color = colorInfo.color;
  2034. node.colorIndex = colorInfo.colorIndex;
  2035. if (point) {
  2036. point.color = node.color;
  2037. point.colorIndex = node.colorIndex;
  2038. // Set slicing on node, but avoid slicing the top node.
  2039. node.sliced = (node.id !== options.idRoot) ? point.sliced : false;
  2040. }
  2041. return node;
  2042. };
  2043. /**
  2044. * A Sunburst displays hierarchical data, where a level in the hierarchy is
  2045. * represented by a circle. The center represents the root node of the tree.
  2046. * The visualization bears a resemblance to both treemap and pie charts.
  2047. *
  2048. * @extends {plotOptions.pie}
  2049. * @sample highcharts/demo/sunburst Sunburst chart
  2050. * @excluding allAreas, clip, colorAxis, compare, compareBase,
  2051. * dataGrouping, depth, endAngle, gapSize, gapUnit,
  2052. * ignoreHiddenPoint, innerSize, joinBy, legendType, linecap,
  2053. * minSize, navigatorOptions, pointRange
  2054. * @product highcharts
  2055. * @optionparent plotOptions.sunburst
  2056. */
  2057. var sunburstOptions = {
  2058. /**
  2059. * Set options on specific levels. Takes precedence over series options,
  2060. * but not point options.
  2061. *
  2062. * @type {Array<Object>}
  2063. * @sample highcharts/demo/sunburst Sunburst chart
  2064. * @apioption plotOptions.sunburst.levels
  2065. */
  2066. /**
  2067. * Can set a `borderColor` on all points which lies on the same level.
  2068. *
  2069. * @type {Color}
  2070. * @apioption plotOptions.sunburst.levels.borderColor
  2071. */
  2072. /**
  2073. * Can set a `borderWidth` on all points which lies on the same level.
  2074. *
  2075. * @type {Number}
  2076. * @apioption plotOptions.sunburst.levels.borderWidth
  2077. */
  2078. /**
  2079. * Can set a `borderDashStyle` on all points which lies on the same level.
  2080. *
  2081. * @type {String}
  2082. * @apioption plotOptions.sunburst.levels.borderDashStyle
  2083. */
  2084. /**
  2085. * Can set a `color` on all points which lies on the same level.
  2086. *
  2087. * @type {Color}
  2088. * @apioption plotOptions.sunburst.levels.color
  2089. */
  2090. /**
  2091. * Can set a `colorVariation` on all points which lies on the same level.
  2092. *
  2093. * @type {Object}
  2094. * @apioption plotOptions.sunburst.levels.colorVariation
  2095. */
  2096. /**
  2097. * The key of a color variation. Currently supports `brightness` only.
  2098. *
  2099. * @type {String}
  2100. * @apioption plotOptions.sunburst.levels.colorVariation.key
  2101. */
  2102. /**
  2103. * The ending value of a color variation. The last sibling will receive this
  2104. * value.
  2105. *
  2106. * @type {Number}
  2107. * @apioption plotOptions.sunburst.levels.colorVariation.to
  2108. */
  2109. /**
  2110. * Can set a `dataLabels` on all points which lies on the same level.
  2111. *
  2112. * @type {Object}
  2113. * @apioption plotOptions.sunburst.levels.dataLabels
  2114. */
  2115. /**
  2116. * Can set a `levelSize` on all points which lies on the same level.
  2117. *
  2118. * @type {Object}
  2119. * @apioption plotOptions.sunburst.levels.levelSize
  2120. */
  2121. /**
  2122. * Can set a `rotation` on all points which lies on the same level.
  2123. *
  2124. * @type {Number}
  2125. * @apioption plotOptions.sunburst.levels.rotation
  2126. */
  2127. /**
  2128. * Can set a `rotationMode` on all points which lies on the same level.
  2129. *
  2130. * @type {String}
  2131. * @apioption plotOptions.sunburst.levels.rotationMode
  2132. */
  2133. /**
  2134. * When enabled the user can click on a point which is a parent and
  2135. * zoom in on its children.
  2136. *
  2137. * @sample highcharts/demo/sunburst
  2138. * Allow drill to node
  2139. * @type {Boolean}
  2140. * @default false
  2141. * @apioption plotOptions.sunburst.allowDrillToNode
  2142. */
  2143. /**
  2144. * The center of the sunburst chart relative to the plot area. Can be
  2145. * percentages or pixel values.
  2146. *
  2147. * @type {Array<String|Number>}
  2148. * @sample {highcharts} highcharts/plotoptions/pie-center/
  2149. * Centered at 100, 100
  2150. * @product highcharts
  2151. */
  2152. center: ['50%', '50%'],
  2153. colorByPoint: false,
  2154. /**
  2155. * @extends plotOptions.series.dataLabels
  2156. * @excluding align,allowOverlap,staggerLines,step
  2157. */
  2158. dataLabels: {
  2159. defer: true,
  2160. style: {
  2161. textOverflow: 'ellipsis'
  2162. },
  2163. /**
  2164. * Decides how the data label will be rotated according to the perimeter
  2165. * of the sunburst. It can either be parallel or perpendicular to the
  2166. * perimeter.
  2167. * `series.rotation` takes precedence over `rotationMode`.
  2168. * @since 6.0.0
  2169. * @validvalue ["perpendicular", "parallel"]
  2170. */
  2171. rotationMode: 'perpendicular'
  2172. },
  2173. /**
  2174. * Which point to use as a root in the visualization.
  2175. *
  2176. * @type {String|undefined}
  2177. * @default undefined
  2178. */
  2179. rootId: undefined,
  2180. /**
  2181. * Used together with the levels and `allowDrillToNode` options. When
  2182. * set to false the first level visible when drilling is considered
  2183. * to be level one. Otherwise the level will be the same as the tree
  2184. * structure.
  2185. */
  2186. levelIsConstant: true,
  2187. /**
  2188. * Determines the width of the ring per level.
  2189. * @since 6.0.5
  2190. * @sample {highcharts} highcharts/plotoptions/sunburst-levelsize/
  2191. * Sunburst with various sizes per level
  2192. */
  2193. levelSize: {
  2194. /**
  2195. * The value used for calculating the width of the ring. Its' affect is
  2196. * determined by `levelSize.unit`.
  2197. * @sample {highcharts} highcharts/plotoptions/sunburst-levelsize/
  2198. * Sunburst with various sizes per level
  2199. */
  2200. value: 1,
  2201. /**
  2202. * How to interpret `levelSize.value`.
  2203. * `percentage` gives a width relative to result of outer radius minus
  2204. * inner radius.
  2205. * `pixels` gives the ring a fixed width in pixels.
  2206. * `weight` takes the remaining width after percentage and pixels, and
  2207. * distributes it accross all "weighted" levels. The value relative to
  2208. * the sum of all weights determines the width.
  2209. * @validvalue ["percentage", "pixels", "weight"]
  2210. * @sample {highcharts} highcharts/plotoptions/sunburst-levelsize/
  2211. * Sunburst with various sizes per level
  2212. */
  2213. unit: 'weight'
  2214. },
  2215. /**
  2216. * If a point is sliced, moved out from the center, how many pixels
  2217. * should it be moved?.
  2218. *
  2219. * @since 6.0.4
  2220. * @sample highcharts/plotoptions/sunburst-sliced Sliced sunburst
  2221. */
  2222. slicedOffset: 10
  2223. };
  2224. /**
  2225. * Properties of the Sunburst series.
  2226. */
  2227. var sunburstSeries = {
  2228. drawDataLabels: noop, // drawDataLabels is called in drawPoints
  2229. drawPoints: function drawPoints() {
  2230. var series = this,
  2231. mapOptionsToLevel = series.mapOptionsToLevel,
  2232. shapeRoot = series.shapeRoot,
  2233. group = series.group,
  2234. hasRendered = series.hasRendered,
  2235. idRoot = series.rootId,
  2236. idPreviousRoot = series.idPreviousRoot,
  2237. nodeMap = series.nodeMap,
  2238. nodePreviousRoot = nodeMap[idPreviousRoot],
  2239. shapePreviousRoot = nodePreviousRoot && nodePreviousRoot.shapeArgs,
  2240. points = series.points,
  2241. radians = series.startAndEndRadians,
  2242. chart = series.chart,
  2243. optionsChart = chart && chart.options && chart.options.chart || {},
  2244. animation = (
  2245. isBoolean(optionsChart.animation) ?
  2246. optionsChart.animation :
  2247. true
  2248. ),
  2249. positions = series.center,
  2250. center = {
  2251. x: positions[0],
  2252. y: positions[1]
  2253. },
  2254. innerR = positions[3] / 2,
  2255. renderer = series.chart.renderer,
  2256. animateLabels,
  2257. animateLabelsCalled = false,
  2258. addedHack = false,
  2259. hackDataLabelAnimation = !!(
  2260. animation &&
  2261. hasRendered &&
  2262. idRoot !== idPreviousRoot &&
  2263. series.dataLabelsGroup
  2264. );
  2265. if (hackDataLabelAnimation) {
  2266. series.dataLabelsGroup.attr({ opacity: 0 });
  2267. animateLabels = function () {
  2268. var s = series;
  2269. animateLabelsCalled = true;
  2270. if (s.dataLabelsGroup) {
  2271. s.dataLabelsGroup.animate({
  2272. opacity: 1,
  2273. visibility: 'visible'
  2274. });
  2275. }
  2276. };
  2277. }
  2278. each(points, function (point) {
  2279. var node = point.node,
  2280. level = mapOptionsToLevel[node.level],
  2281. shapeExisting = point.shapeExisting || {},
  2282. shape = node.shapeArgs || {},
  2283. animationInfo,
  2284. onComplete,
  2285. visible = !!(node.visible && node.shapeArgs);
  2286. if (hasRendered && animation) {
  2287. animationInfo = getAnimation(shape, {
  2288. center: center,
  2289. point: point,
  2290. radians: radians,
  2291. innerR: innerR,
  2292. idRoot: idRoot,
  2293. idPreviousRoot: idPreviousRoot,
  2294. shapeExisting: shapeExisting,
  2295. shapeRoot: shapeRoot,
  2296. shapePreviousRoot: shapePreviousRoot,
  2297. visible: visible
  2298. });
  2299. } else {
  2300. // When animation is disabled, attr is called from animation.
  2301. animationInfo = {
  2302. to: shape,
  2303. from: {}
  2304. };
  2305. }
  2306. extend(point, {
  2307. shapeExisting: shape, // Store for use in animation
  2308. tooltipPos: [shape.plotX, shape.plotY],
  2309. drillId: getDrillId(point, idRoot, nodeMap),
  2310. name: '' + (point.name || point.id || point.index),
  2311. plotX: shape.plotX, // used for data label position
  2312. plotY: shape.plotY, // used for data label position
  2313. value: node.val,
  2314. isNull: !visible // used for dataLabels & point.draw
  2315. });
  2316. point.dlOptions = getDlOptions({
  2317. level: level,
  2318. optionsPoint: point.options,
  2319. shapeArgs: shape
  2320. });
  2321. if (!addedHack && visible) {
  2322. addedHack = true;
  2323. onComplete = animateLabels;
  2324. }
  2325. point.draw({
  2326. animate: animationInfo.to,
  2327. attr: extend(
  2328. animationInfo.from,
  2329. series.pointAttribs && series.pointAttribs(
  2330. point,
  2331. point.selected && 'select'
  2332. )
  2333. ),
  2334. onComplete: onComplete,
  2335. group: group,
  2336. renderer: renderer,
  2337. shapeType: 'arc',
  2338. shapeArgs: shape
  2339. });
  2340. });
  2341. // Draw data labels after points
  2342. // TODO draw labels one by one to avoid addtional looping
  2343. if (hackDataLabelAnimation && addedHack) {
  2344. series.hasRendered = false;
  2345. series.options.dataLabels.defer = true;
  2346. Series.prototype.drawDataLabels.call(series);
  2347. series.hasRendered = true;
  2348. // If animateLabels is called before labels were hidden, then call
  2349. // it again.
  2350. if (animateLabelsCalled) {
  2351. animateLabels();
  2352. }
  2353. } else {
  2354. Series.prototype.drawDataLabels.call(series);
  2355. }
  2356. },
  2357. pointAttribs: seriesTypes.column.prototype.pointAttribs,
  2358. /*
  2359. * The layout algorithm for the levels
  2360. */
  2361. layoutAlgorithm: layoutAlgorithm,
  2362. /*
  2363. * Set the shape arguments on the nodes. Recursive from root down.
  2364. */
  2365. setShapeArgs: function (parent, parentValues, mapOptionsToLevel) {
  2366. var childrenValues = [],
  2367. level = parent.level + 1,
  2368. options = mapOptionsToLevel[level],
  2369. // Collect all children which should be included
  2370. children = grep(parent.children, function (n) {
  2371. return n.visible;
  2372. }),
  2373. twoPi = 6.28; // Two times Pi.
  2374. childrenValues = this.layoutAlgorithm(parentValues, children, options);
  2375. each(children, function (child, index) {
  2376. var values = childrenValues[index],
  2377. angle = values.start + ((values.end - values.start) / 2),
  2378. radius = values.innerR + ((values.r - values.innerR) / 2),
  2379. radians = (values.end - values.start),
  2380. isCircle = (values.innerR === 0 && radians > twoPi),
  2381. center = (
  2382. isCircle ?
  2383. { x: values.x, y: values.y } :
  2384. getEndPoint(values.x, values.y, angle, radius)
  2385. ),
  2386. val = (
  2387. child.val ?
  2388. (
  2389. child.childrenTotal > child.val ?
  2390. child.childrenTotal :
  2391. child.val
  2392. ) :
  2393. child.childrenTotal
  2394. );
  2395. // The inner arc length is a convenience for data label filters.
  2396. if (this.points[child.i]) {
  2397. this.points[child.i].innerArcLength = radians * values.innerR;
  2398. this.points[child.i].outerArcLength = radians * values.r;
  2399. }
  2400. child.shapeArgs = merge(values, {
  2401. plotX: center.x,
  2402. plotY: center.y
  2403. });
  2404. child.values = merge(values, {
  2405. val: val
  2406. });
  2407. // If node has children, then call method recursively
  2408. if (child.children.length) {
  2409. this.setShapeArgs(child, child.values, mapOptionsToLevel);
  2410. }
  2411. }, this);
  2412. },
  2413. translate: function translate() {
  2414. var series = this,
  2415. options = series.options,
  2416. positions = series.center = getCenter.call(series),
  2417. radians = series.startAndEndRadians = getStartAndEndRadians(
  2418. options.startAngle,
  2419. options.endAngle
  2420. ),
  2421. innerRadius = positions[3] / 2,
  2422. outerRadius = positions[2] / 2,
  2423. diffRadius = outerRadius - innerRadius,
  2424. // NOTE: updateRootId modifies series.
  2425. rootId = updateRootId(series),
  2426. mapIdToNode = series.nodeMap,
  2427. mapOptionsToLevel,
  2428. idTop,
  2429. nodeRoot = mapIdToNode && mapIdToNode[rootId],
  2430. nodeTop,
  2431. tree,
  2432. values;
  2433. series.shapeRoot = nodeRoot && nodeRoot.shapeArgs;
  2434. // Call prototype function
  2435. Series.prototype.translate.call(series);
  2436. // @todo Only if series.isDirtyData is true
  2437. tree = series.tree = series.getTree();
  2438. mapIdToNode = series.nodeMap;
  2439. nodeRoot = mapIdToNode[rootId];
  2440. idTop = isString(nodeRoot.parent) ? nodeRoot.parent : '';
  2441. nodeTop = mapIdToNode[idTop];
  2442. mapOptionsToLevel = getLevelOptions({
  2443. from: nodeRoot.level > 0 ? nodeRoot.level : 1,
  2444. levels: series.options.levels,
  2445. to: tree.height,
  2446. defaults: {
  2447. colorByPoint: options.colorByPoint,
  2448. dataLabels: options.dataLabels,
  2449. levelIsConstant: options.levelIsConstant,
  2450. levelSize: options.levelSize,
  2451. slicedOffset: options.slicedOffset
  2452. }
  2453. });
  2454. // NOTE consider doing calculateLevelSizes in a callback to
  2455. // getLevelOptions
  2456. mapOptionsToLevel = calculateLevelSizes(mapOptionsToLevel, {
  2457. diffRadius: diffRadius,
  2458. from: nodeRoot.level > 0 ? nodeRoot.level : 1,
  2459. to: tree.height
  2460. });
  2461. // TODO Try to combine setTreeValues & setColorRecursive to avoid
  2462. // unnecessary looping.
  2463. setTreeValues(tree, {
  2464. before: cbSetTreeValuesBefore,
  2465. idRoot: rootId,
  2466. levelIsConstant: options.levelIsConstant,
  2467. mapOptionsToLevel: mapOptionsToLevel,
  2468. mapIdToNode: mapIdToNode,
  2469. points: series.points,
  2470. series: series
  2471. });
  2472. values = mapIdToNode[''].shapeArgs = {
  2473. end: radians.end,
  2474. r: innerRadius,
  2475. start: radians.start,
  2476. val: nodeRoot.val,
  2477. x: positions[0],
  2478. y: positions[1]
  2479. };
  2480. this.setShapeArgs(nodeTop, values, mapOptionsToLevel);
  2481. // Set mapOptionsToLevel on series for use in drawPoints.
  2482. series.mapOptionsToLevel = mapOptionsToLevel;
  2483. },
  2484. /**
  2485. * Animate the slices in. Similar to the animation of polar charts.
  2486. */
  2487. animate: function (init) {
  2488. var chart = this.chart,
  2489. center = [
  2490. chart.plotWidth / 2,
  2491. chart.plotHeight / 2
  2492. ],
  2493. plotLeft = chart.plotLeft,
  2494. plotTop = chart.plotTop,
  2495. attribs,
  2496. group = this.group;
  2497. // Initialize the animation
  2498. if (init) {
  2499. // Scale down the group and place it in the center
  2500. attribs = {
  2501. translateX: center[0] + plotLeft,
  2502. translateY: center[1] + plotTop,
  2503. scaleX: 0.001, // #1499
  2504. scaleY: 0.001,
  2505. rotation: 10,
  2506. opacity: 0.01
  2507. };
  2508. group.attr(attribs);
  2509. // Run the animation
  2510. } else {
  2511. attribs = {
  2512. translateX: plotLeft,
  2513. translateY: plotTop,
  2514. scaleX: 1,
  2515. scaleY: 1,
  2516. rotation: 0,
  2517. opacity: 1
  2518. };
  2519. group.animate(attribs, this.options.animation);
  2520. // Delete this function to allow it only once
  2521. this.animate = null;
  2522. }
  2523. },
  2524. utils: {
  2525. calculateLevelSizes: calculateLevelSizes,
  2526. range: range
  2527. }
  2528. };
  2529. /**
  2530. * Properties of the Sunburst series.
  2531. */
  2532. var sunburstPoint = {
  2533. draw: drawPoint,
  2534. shouldDraw: function shouldDraw() {
  2535. var point = this;
  2536. return !point.isNull;
  2537. }
  2538. };
  2539. /**
  2540. * A `sunburst` series. If the [type](#series.sunburst.type) option is
  2541. * not specified, it is inherited from [chart.type](#chart.type).
  2542. *
  2543. * @type {Object}
  2544. * @extends series,plotOptions.sunburst
  2545. * @excluding dataParser,dataURL,stack
  2546. * @product highcharts
  2547. * @apioption series.sunburst
  2548. */
  2549. /**
  2550. * @type {Array<Object|Number>}
  2551. * @extends series.treemap.data
  2552. * @excluding x,y
  2553. * @product highcharts
  2554. * @apioption series.sunburst.data
  2555. */
  2556. /**
  2557. * The value of the point, resulting in a relative area of the point
  2558. * in the sunburst.
  2559. *
  2560. * @type {Number}
  2561. * @default undefined
  2562. * @since 6.0.0
  2563. * @product highcharts
  2564. * @apioption series.sunburst.data.value
  2565. */
  2566. /**
  2567. * Use this option to build a tree structure. The value should be the id of the
  2568. * point which is the parent. If no points has a matching id, or this option is
  2569. * undefined, then the parent will be set to the root.
  2570. *
  2571. * @type {String|undefined}
  2572. * @default undefined
  2573. * @since 6.0.0
  2574. * @product highcharts
  2575. * @apioption series.treemap.data.parent
  2576. */
  2577. /**
  2578. * Whether to display a slice offset from the center. When a sunburst point is
  2579. * sliced, its children are also offset.
  2580. *
  2581. * @type {Boolean}
  2582. * @default false
  2583. * @since 6.0.4
  2584. * @sample highcharts/plotoptions/sunburst-sliced Sliced sunburst
  2585. * @product highcharts
  2586. * @apioption series.sunburst.data.sliced
  2587. */
  2588. seriesType(
  2589. 'sunburst',
  2590. 'treemap',
  2591. sunburstOptions,
  2592. sunburstSeries,
  2593. sunburstPoint
  2594. );
  2595. }(Highcharts, draw, result));
  2596. }));