highcharts-rails 5.0.14 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }));