highcharts-rails 5.0.14 → 6.0.0

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