highcharts-rails 4.2.7 → 5.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.markdown +34 -0
  3. data/Gemfile +4 -0
  4. data/Rakefile +53 -32
  5. data/app/assets/javascripts/highcharts.js +18775 -17176
  6. data/app/assets/javascripts/highcharts/highcharts-3d.js +1849 -1563
  7. data/app/assets/javascripts/highcharts/highcharts-more.js +2162 -1988
  8. data/app/assets/javascripts/highcharts/modules/accessibility.js +1005 -0
  9. data/app/assets/javascripts/highcharts/modules/annotations.js +408 -401
  10. data/app/assets/javascripts/highcharts/modules/boost.js +561 -546
  11. data/app/assets/javascripts/highcharts/modules/broken-axis.js +330 -324
  12. data/app/assets/javascripts/highcharts/modules/data.js +973 -965
  13. data/app/assets/javascripts/highcharts/modules/drilldown.js +783 -723
  14. data/app/assets/javascripts/highcharts/modules/exporting.js +864 -785
  15. data/app/assets/javascripts/highcharts/modules/funnel.js +290 -306
  16. data/app/assets/javascripts/highcharts/modules/heatmap.js +701 -645
  17. data/app/assets/javascripts/highcharts/modules/no-data-to-display.js +150 -132
  18. data/app/assets/javascripts/highcharts/modules/offline-exporting.js +414 -355
  19. data/app/assets/javascripts/highcharts/modules/overlapping-datalabels.js +164 -0
  20. data/app/assets/javascripts/highcharts/modules/series-label.js +473 -448
  21. data/app/assets/javascripts/highcharts/modules/solid-gauge.js +279 -271
  22. data/app/assets/javascripts/highcharts/modules/treemap.js +921 -886
  23. data/app/assets/javascripts/highcharts/themes/dark-blue.js +307 -244
  24. data/app/assets/javascripts/highcharts/themes/dark-green.js +303 -244
  25. data/app/assets/javascripts/highcharts/themes/dark-unica.js +231 -201
  26. data/app/assets/javascripts/highcharts/themes/gray.js +314 -245
  27. data/app/assets/javascripts/highcharts/themes/grid-light.js +91 -66
  28. data/app/assets/javascripts/highcharts/themes/grid.js +124 -96
  29. data/app/assets/javascripts/highcharts/themes/sand-signika.js +119 -94
  30. data/app/assets/javascripts/highcharts/themes/skies.js +108 -85
  31. data/lib/highcharts/version.rb +1 -1
  32. metadata +13 -14
  33. data/app/assets/javascripts/highcharts/adapters/standalone-framework.js +0 -1
  34. data/app/assets/javascripts/highcharts/modules/canvas-tools.js +0 -3115
  35. data/app/assets/javascripts/highcharts/modules/map.js +0 -2117
@@ -1,895 +1,930 @@
1
1
  /**
2
- * @license Highcharts JS v4.2.7 (2016-09-21)
2
+ * @license Highcharts JS v5.0.0 (2016-09-29)
3
3
  *
4
4
  * (c) 2014 Highsoft AS
5
5
  * Authors: Jon Arild Nygard / Oystein Moseng
6
6
  *
7
7
  * License: www.highcharts.com/license
8
8
  */
9
+ (function(factory) {
10
+ if (typeof module === 'object' && module.exports) {
11
+ module.exports = factory;
12
+ } else {
13
+ factory(Highcharts);
14
+ }
15
+ }(function(Highcharts) {
16
+ (function(H) {
17
+ /**
18
+ * (c) 2014 Highsoft AS
19
+ * Authors: Jon Arild Nygard / Oystein Moseng
20
+ *
21
+ * License: www.highcharts.com/license
22
+ */
23
+ 'use strict';
24
+ var seriesType = H.seriesType,
25
+ seriesTypes = H.seriesTypes,
26
+ map = H.map,
27
+ merge = H.merge,
28
+ extend = H.extend,
29
+ noop = H.noop,
30
+ each = H.each,
31
+ grep = H.grep,
32
+ pick = H.pick,
33
+ Series = H.Series,
34
+ stableSort = H.stableSort,
35
+ color = H.Color,
36
+ eachObject = function(list, func, context) {
37
+ var key;
38
+ context = context || this;
39
+ for (key in list) {
40
+ if (list.hasOwnProperty(key)) {
41
+ func.call(context, list[key], key, list);
42
+ }
43
+ }
44
+ },
45
+ reduce = function(arr, func, previous, context) {
46
+ context = context || this;
47
+ arr = arr || []; // @note should each be able to handle empty values automatically?
48
+ each(arr, function(current, i) {
49
+ previous = func.call(context, previous, current, i, arr);
50
+ });
51
+ return previous;
52
+ },
53
+ // @todo find correct name for this function.
54
+ // @todo Similar to reduce, this function is likely redundant
55
+ recursive = function(item, func, context) {
56
+ var next;
57
+ context = context || this;
58
+ next = func.call(context, item);
59
+ if (next !== false) {
60
+ recursive(next, func, context);
61
+ }
62
+ };
9
63
 
10
- (function (factory) {
11
- if (typeof module === 'object' && module.exports) {
12
- module.exports = factory;
13
- } else {
14
- factory(Highcharts);
15
- }
16
- }(function (H) {
17
- var seriesTypes = H.seriesTypes,
18
- map = H.map,
19
- merge = H.merge,
20
- extend = H.extend,
21
- extendClass = H.extendClass,
22
- defaultOptions = H.getOptions(),
23
- plotOptions = defaultOptions.plotOptions,
24
- noop = function () {
25
- },
26
- each = H.each,
27
- grep = H.grep,
28
- pick = H.pick,
29
- Series = H.Series,
30
- stableSort = H.stableSort,
31
- Color = H.Color,
32
- eachObject = function (list, func, context) {
33
- var key;
34
- context = context || this;
35
- for (key in list) {
36
- if (list.hasOwnProperty(key)) {
37
- func.call(context, list[key], key, list);
38
- }
39
- }
40
- },
41
- reduce = function (arr, func, previous, context) {
42
- context = context || this;
43
- arr = arr || []; // @note should each be able to handle empty values automatically?
44
- each(arr, function (current, i) {
45
- previous = func.call(context, previous, current, i, arr);
46
- });
47
- return previous;
48
- },
49
- // @todo find correct name for this function.
50
- // @todo Similar to reduce, this function is likely redundant
51
- recursive = function (item, func, context) {
52
- var next;
53
- context = context || this;
54
- next = func.call(context, item);
55
- if (next !== false) {
56
- recursive(next, func, context);
57
- }
58
- };
59
-
60
- // Define default options
61
- plotOptions.treemap = merge(plotOptions.scatter, {
62
- showInLegend: false,
63
- marker: false,
64
- borderColor: '#E0E0E0',
65
- borderWidth: 1,
66
- dataLabels: {
67
- enabled: true,
68
- defer: false,
69
- verticalAlign: 'middle',
70
- formatter: function () { // #2945
71
- return this.point.name || this.point.id;
72
- },
73
- inside: true
74
- },
75
- tooltip: {
76
- headerFormat: '',
77
- pointFormat: '<b>{point.name}</b>: {point.value}</b><br/>'
78
- },
79
- layoutAlgorithm: 'sliceAndDice',
80
- layoutStartingDirection: 'vertical',
81
- alternateStartingDirection: false,
82
- levelIsConstant: true,
83
- opacity: 0.15,
84
- states: {
85
- hover: {
86
- borderColor: '#A0A0A0',
87
- brightness: seriesTypes.heatmap ? 0 : 0.1,
88
- opacity: 0.75,
89
- shadow: false
90
- }
91
- },
92
- drillUpButton: {
93
- position: {
94
- align: 'right',
95
- x: -10,
96
- y: 10
97
- }
98
- }
99
- });
100
-
101
- // Stolen from heatmap
102
- var colorSeriesMixin = {
103
- // mapping between SVG attributes and the corresponding options
104
- pointAttrToOptions: {},
105
- pointArrayMap: ['value'],
106
- axisTypes: seriesTypes.heatmap ? ['xAxis', 'yAxis', 'colorAxis'] : ['xAxis', 'yAxis'],
107
- optionalAxis: 'colorAxis',
108
- getSymbol: noop,
109
- parallelArrays: ['x', 'y', 'value', 'colorValue'],
110
- colorKey: 'colorValue', // Point color option key
111
- translateColors: seriesTypes.heatmap && seriesTypes.heatmap.prototype.translateColors
112
- };
113
-
114
- // The Treemap series type
115
- seriesTypes.treemap = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, {
116
- type: 'treemap',
117
- trackerGroups: ['group', 'dataLabelsGroup'],
118
- pointClass: extendClass(H.Point, {
119
- setVisible: seriesTypes.pie.prototype.pointClass.prototype.setVisible
120
- }),
121
- /**
122
- * Creates an object map from parent id to childrens index.
123
- * @param {Array} data List of points set in options.
124
- * @param {string} data[].parent Parent id of point.
125
- * @param {Array} ids List of all point ids.
126
- * @return {Object} Map from parent id to children index in data.
127
- */
128
- getListOfParents: function (data, ids) {
129
- var listOfParents = reduce(data, function (prev, curr, i) {
130
- var parent = pick(curr.parent, '');
131
- if (prev[parent] === undefined) {
132
- prev[parent] = [];
133
- }
134
- prev[parent].push(i);
135
- return prev;
136
- }, {});
137
-
138
- // If parent does not exist, hoist parent to root of tree.
139
- eachObject(listOfParents, function (children, parent, list) {
140
- if ((parent !== '') && (H.inArray(parent, ids) === -1)) {
141
- each(children, function (child) {
142
- list[''].push(child);
143
- });
144
- delete list[parent];
145
- }
146
- });
147
- return listOfParents;
148
- },
149
- /**
150
- * Creates a tree structured object from the series points
151
- */
152
- getTree: function () {
153
- var tree,
154
- series = this,
155
- allIds = map(this.data, function (d) {
156
- return d.id;
157
- }),
158
- parentList = series.getListOfParents(this.data, allIds);
159
-
160
- series.nodeMap = [];
161
- tree = series.buildNode('', -1, 0, parentList, null);
162
- // Parents of the root node is by default visible
163
- recursive(this.nodeMap[this.rootNode], function (node) {
164
- var next = false,
165
- p = node.parent;
166
- node.visible = true;
167
- if (p || p === '') {
168
- next = series.nodeMap[p];
169
- }
170
- return next;
171
- });
172
- // Children of the root node is by default visible
173
- recursive(this.nodeMap[this.rootNode].children, function (children) {
174
- var next = false;
175
- each(children, function (child) {
176
- child.visible = true;
177
- if (child.children.length) {
178
- next = (next || []).concat(child.children);
179
- }
180
- });
181
- return next;
182
- });
183
- this.setTreeValues(tree);
184
- return tree;
185
- },
186
- init: function (chart, options) {
187
- var series = this;
188
- Series.prototype.init.call(series, chart, options);
189
- if (series.options.allowDrillToNode) {
190
- series.drillTo();
191
- }
192
- },
193
- buildNode: function (id, i, level, list, parent) {
194
- var series = this,
195
- children = [],
196
- point = series.points[i],
197
- node,
198
- child;
199
-
200
- // Actions
201
- each((list[id] || []), function (i) {
202
- child = series.buildNode(series.points[i].id, i, (level + 1), list, id);
203
- children.push(child);
204
- });
205
- node = {
206
- id: id,
207
- i: i,
208
- children: children,
209
- level: level,
210
- parent: parent,
211
- visible: false // @todo move this to better location
212
- };
213
- series.nodeMap[node.id] = node;
214
- if (point) {
215
- point.node = node;
216
- }
217
- return node;
218
- },
219
- setTreeValues: function (tree) {
220
- var series = this,
221
- options = series.options,
222
- childrenTotal = 0,
223
- children = [],
224
- val,
225
- point = series.points[tree.i];
226
-
227
- // First give the children some values
228
- each(tree.children, function (child) {
229
- child = series.setTreeValues(child);
230
- children.push(child);
231
-
232
- if (!child.ignore) {
233
- childrenTotal += child.val;
234
- } else {
235
- // @todo Add predicate to avoid looping already ignored children
236
- recursive(child.children, function (children) {
237
- var next = false;
238
- each(children, function (node) {
239
- extend(node, {
240
- ignore: true,
241
- isLeaf: false,
242
- visible: false
243
- });
244
- if (node.children.length) {
245
- next = (next || []).concat(node.children);
246
- }
247
- });
248
- return next;
249
- });
250
- }
251
- });
252
- // Sort the children
253
- stableSort(children, function (a, b) {
254
- return a.sortIndex - b.sortIndex;
255
- });
256
- // Set the values
257
- val = pick(point && point.options.value, childrenTotal);
258
- if (point) {
259
- point.value = val;
260
- }
261
- extend(tree, {
262
- children: children,
263
- childrenTotal: childrenTotal,
264
- // Ignore this node if point is not visible
265
- ignore: !(pick(point && point.visible, true) && (val > 0)),
266
- isLeaf: tree.visible && !childrenTotal,
267
- levelDynamic: (options.levelIsConstant ? tree.level : (tree.level - series.nodeMap[series.rootNode].level)),
268
- name: pick(point && point.name, ''),
269
- sortIndex: pick(point && point.sortIndex, -val),
270
- val: val
271
- });
272
- return tree;
273
- },
274
- /**
275
- * Recursive function which calculates the area for all children of a node.
276
- * @param {Object} node The node which is parent to the children.
277
- * @param {Object} area The rectangular area of the parent.
278
- */
279
- calculateChildrenAreas: function (parent, area) {
280
- var series = this,
281
- options = series.options,
282
- level = this.levelMap[parent.levelDynamic + 1],
283
- algorithm = pick((series[level && level.layoutAlgorithm] && level.layoutAlgorithm), options.layoutAlgorithm),
284
- alternate = options.alternateStartingDirection,
285
- childrenValues = [],
286
- children;
287
-
288
- // Collect all children which should be included
289
- children = grep(parent.children, function (n) {
290
- return !n.ignore;
291
- });
292
-
293
- if (level && level.layoutStartingDirection) {
294
- area.direction = level.layoutStartingDirection === 'vertical' ? 0 : 1;
295
- }
296
- childrenValues = series[algorithm](area, children);
297
- each(children, function (child, index) {
298
- var values = childrenValues[index];
299
- child.values = merge(values, {
300
- val: child.childrenTotal,
301
- direction: (alternate ? 1 - area.direction : area.direction)
302
- });
303
- child.pointValues = merge(values, {
304
- x: (values.x / series.axisRatio),
305
- width: (values.width / series.axisRatio)
306
- });
307
- // If node has children, then call method recursively
308
- if (child.children.length) {
309
- series.calculateChildrenAreas(child, child.values);
310
- }
311
- });
312
- },
313
- setPointValues: function () {
314
- var series = this,
315
- xAxis = series.xAxis,
316
- yAxis = series.yAxis;
317
- each(series.points, function (point) {
318
- var node = point.node,
319
- values = node.pointValues,
320
- x1,
321
- x2,
322
- y1,
323
- y2;
324
- // Points which is ignored, have no values.
325
- if (values && node.visible) {
326
- x1 = Math.round(xAxis.translate(values.x, 0, 0, 0, 1));
327
- x2 = Math.round(xAxis.translate(values.x + values.width, 0, 0, 0, 1));
328
- y1 = Math.round(yAxis.translate(values.y, 0, 0, 0, 1));
329
- y2 = Math.round(yAxis.translate(values.y + values.height, 0, 0, 0, 1));
330
- // Set point values
331
- point.shapeType = 'rect';
332
- point.shapeArgs = {
333
- x: Math.min(x1, x2),
334
- y: Math.min(y1, y2),
335
- width: Math.abs(x2 - x1),
336
- height: Math.abs(y2 - y1)
337
- };
338
- point.plotX = point.shapeArgs.x + (point.shapeArgs.width / 2);
339
- point.plotY = point.shapeArgs.y + (point.shapeArgs.height / 2);
340
- } else {
341
- // Reset visibility
342
- delete point.plotX;
343
- delete point.plotY;
344
- }
345
- });
346
- },
347
- setColorRecursive: function (node, color) {
348
- var series = this,
349
- point,
350
- level;
351
- if (node) {
352
- point = series.points[node.i];
353
- level = series.levelMap[node.levelDynamic];
354
- // Select either point color, level color or inherited color.
355
- color = pick(point && point.options.color, level && level.color, color);
356
- if (point) {
357
- point.color = color;
358
- }
359
- // Do it all again with the children
360
- if (node.children.length) {
361
- each(node.children, function (child) {
362
- series.setColorRecursive(child, color);
363
- });
364
- }
365
- }
366
- },
367
- algorithmGroup: function (h, w, d, p) {
368
- this.height = h;
369
- this.width = w;
370
- this.plot = p;
371
- this.direction = d;
372
- this.startDirection = d;
373
- this.total = 0;
374
- this.nW = 0;
375
- this.lW = 0;
376
- this.nH = 0;
377
- this.lH = 0;
378
- this.elArr = [];
379
- this.lP = {
380
- total: 0,
381
- lH: 0,
382
- nH: 0,
383
- lW: 0,
384
- nW: 0,
385
- nR: 0,
386
- lR: 0,
387
- aspectRatio: function (w, h) {
388
- return Math.max((w / h), (h / w));
389
- }
390
- };
391
- this.addElement = function (el) {
392
- this.lP.total = this.elArr[this.elArr.length - 1];
393
- this.total = this.total + el;
394
- if (this.direction === 0) {
395
- // Calculate last point old aspect ratio
396
- this.lW = this.nW;
397
- this.lP.lH = this.lP.total / this.lW;
398
- this.lP.lR = this.lP.aspectRatio(this.lW, this.lP.lH);
399
- // Calculate last point new aspect ratio
400
- this.nW = this.total / this.height;
401
- this.lP.nH = this.lP.total / this.nW;
402
- this.lP.nR = this.lP.aspectRatio(this.nW, this.lP.nH);
403
- } else {
404
- // Calculate last point old aspect ratio
405
- this.lH = this.nH;
406
- this.lP.lW = this.lP.total / this.lH;
407
- this.lP.lR = this.lP.aspectRatio(this.lP.lW, this.lH);
408
- // Calculate last point new aspect ratio
409
- this.nH = this.total / this.width;
410
- this.lP.nW = this.lP.total / this.nH;
411
- this.lP.nR = this.lP.aspectRatio(this.lP.nW, this.nH);
412
- }
413
- this.elArr.push(el);
414
- };
415
- this.reset = function () {
416
- this.nW = 0;
417
- this.lW = 0;
418
- this.elArr = [];
419
- this.total = 0;
420
- };
421
- },
422
- algorithmCalcPoints: function (directionChange, last, group, childrenArea) {
423
- var pX,
424
- pY,
425
- pW,
426
- pH,
427
- gW = group.lW,
428
- gH = group.lH,
429
- plot = group.plot,
430
- keep,
431
- i = 0,
432
- end = group.elArr.length - 1;
433
- if (last) {
434
- gW = group.nW;
435
- gH = group.nH;
436
- } else {
437
- keep = group.elArr[group.elArr.length - 1];
438
- }
439
- each(group.elArr, function (p) {
440
- if (last || (i < end)) {
441
- if (group.direction === 0) {
442
- pX = plot.x;
443
- pY = plot.y;
444
- pW = gW;
445
- pH = p / pW;
446
- } else {
447
- pX = plot.x;
448
- pY = plot.y;
449
- pH = gH;
450
- pW = p / pH;
451
- }
452
- childrenArea.push({
453
- x: pX,
454
- y: pY,
455
- width: pW,
456
- height: pH
457
- });
458
- if (group.direction === 0) {
459
- plot.y = plot.y + pH;
460
- } else {
461
- plot.x = plot.x + pW;
462
- }
463
- }
464
- i = i + 1;
465
- });
466
- // Reset variables
467
- group.reset();
468
- if (group.direction === 0) {
469
- group.width = group.width - gW;
470
- } else {
471
- group.height = group.height - gH;
472
- }
473
- plot.y = plot.parent.y + (plot.parent.height - group.height);
474
- plot.x = plot.parent.x + (plot.parent.width - group.width);
475
- if (directionChange) {
476
- group.direction = 1 - group.direction;
477
- }
478
- // If not last, then add uncalculated element
479
- if (!last) {
480
- group.addElement(keep);
481
- }
482
- },
483
- algorithmLowAspectRatio: function (directionChange, parent, children) {
484
- var childrenArea = [],
485
- series = this,
486
- pTot,
487
- plot = {
488
- x: parent.x,
489
- y: parent.y,
490
- parent: parent
491
- },
492
- direction = parent.direction,
493
- i = 0,
494
- end = children.length - 1,
495
- group = new this.algorithmGroup(parent.height, parent.width, direction, plot);
496
- // Loop through and calculate all areas
497
- each(children, function (child) {
498
- pTot = (parent.width * parent.height) * (child.val / parent.val);
499
- group.addElement(pTot);
500
- if (group.lP.nR > group.lP.lR) {
501
- series.algorithmCalcPoints(directionChange, false, group, childrenArea, plot);
502
- }
503
- // If last child, then calculate all remaining areas
504
- if (i === end) {
505
- series.algorithmCalcPoints(directionChange, true, group, childrenArea, plot);
506
- }
507
- i = i + 1;
508
- });
509
- return childrenArea;
510
- },
511
- algorithmFill: function (directionChange, parent, children) {
512
- var childrenArea = [],
513
- pTot,
514
- direction = parent.direction,
515
- x = parent.x,
516
- y = parent.y,
517
- width = parent.width,
518
- height = parent.height,
519
- pX,
520
- pY,
521
- pW,
522
- pH;
523
- each(children, function (child) {
524
- pTot = (parent.width * parent.height) * (child.val / parent.val);
525
- pX = x;
526
- pY = y;
527
- if (direction === 0) {
528
- pH = height;
529
- pW = pTot / pH;
530
- width = width - pW;
531
- x = x + pW;
532
- } else {
533
- pW = width;
534
- pH = pTot / pW;
535
- height = height - pH;
536
- y = y + pH;
537
- }
538
- childrenArea.push({
539
- x: pX,
540
- y: pY,
541
- width: pW,
542
- height: pH
543
- });
544
- if (directionChange) {
545
- direction = 1 - direction;
546
- }
547
- });
548
- return childrenArea;
549
- },
550
- strip: function (parent, children) {
551
- return this.algorithmLowAspectRatio(false, parent, children);
552
- },
553
- squarified: function (parent, children) {
554
- return this.algorithmLowAspectRatio(true, parent, children);
555
- },
556
- sliceAndDice: function (parent, children) {
557
- return this.algorithmFill(true, parent, children);
558
- },
559
- stripes: function (parent, children) {
560
- return this.algorithmFill(false, parent, children);
561
- },
562
- translate: function () {
563
- var pointValues,
564
- seriesArea,
565
- tree,
566
- val;
567
-
568
- // Call prototype function
569
- Series.prototype.translate.call(this);
570
-
571
- // Assign variables
572
- this.rootNode = pick(this.options.rootId, '');
573
- // Create a object map from level to options
574
- this.levelMap = reduce(this.options.levels, function (arr, item) {
575
- arr[item.level] = item;
576
- return arr;
577
- }, {});
578
- tree = this.tree = this.getTree(); // @todo Only if series.isDirtyData is true
579
-
580
- // Calculate plotting values.
581
- this.axisRatio = (this.xAxis.len / this.yAxis.len);
582
- this.nodeMap[''].pointValues = pointValues = { x: 0, y: 0, width: 100, height: 100 };
583
- this.nodeMap[''].values = seriesArea = merge(pointValues, {
584
- width: (pointValues.width * this.axisRatio),
585
- direction: (this.options.layoutStartingDirection === 'vertical' ? 0 : 1),
586
- val: tree.val
587
- });
588
- this.calculateChildrenAreas(tree, seriesArea);
589
-
590
- // Logic for point colors
591
- if (this.colorAxis) {
592
- this.translateColors();
593
- } else if (!this.options.colorByPoint) {
594
- this.setColorRecursive(this.tree, undefined);
595
- }
596
-
597
- // Update axis extremes according to the root node.
598
- if (this.options.allowDrillToNode) {
599
- val = this.nodeMap[this.rootNode].pointValues;
600
- this.xAxis.setExtremes(val.x, val.x + val.width, false);
601
- this.yAxis.setExtremes(val.y, val.y + val.height, false);
602
- this.xAxis.setScale();
603
- this.yAxis.setScale();
604
- }
605
-
606
- // Assign values to points.
607
- this.setPointValues();
608
- },
609
- /**
610
- * Extend drawDataLabels with logic to handle custom options related to the treemap series:
611
- * - Points which is not a leaf node, has dataLabels disabled by default.
612
- * - Options set on series.levels is merged in.
613
- * - Width of the dataLabel is set to match the width of the point shape.
614
- */
615
- drawDataLabels: function () {
616
- var series = this,
617
- points = grep(series.points, function (n) {
618
- return n.node.visible;
619
- }),
620
- options,
621
- level;
622
- each(points, function (point) {
623
- level = series.levelMap[point.node.levelDynamic];
624
- // Set options to new object to avoid problems with scope
625
- options = { style: {} };
626
-
627
- // If not a leaf, then label should be disabled as default
628
- if (!point.node.isLeaf) {
629
- options.enabled = false;
630
- }
631
-
632
- // If options for level exists, include them as well
633
- if (level && level.dataLabels) {
634
- options = merge(options, level.dataLabels);
635
- series._hasPointLabels = true;
636
- }
637
-
638
- // Set dataLabel width to the width of the point shape.
639
- if (point.shapeArgs) {
640
- options.style.width = point.shapeArgs.width;
641
- if (point.dataLabel) {
642
- point.dataLabel.css({ width: point.shapeArgs.width + 'px' });
643
- }
644
- }
645
-
646
- // Merge custom options with point options
647
- point.dlOptions = merge(options, point.options.dataLabels);
648
- });
649
- Series.prototype.drawDataLabels.call(this);
650
- },
651
- alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
652
-
653
- /**
654
- * Get presentational attributes
655
- */
656
- pointAttribs: function (point, state) {
657
- var level = this.levelMap[point.node.levelDynamic] || {},
658
- options = this.options,
659
- attr,
660
- stateOptions = (state && options.states[state]) || {},
661
- opacity;
662
-
663
- // Set attributes by precedence. Point trumps level trumps series. Stroke width uses pick
664
- // because it can be 0.
665
- attr = {
666
- 'stroke': point.borderColor || level.borderColor || stateOptions.borderColor || options.borderColor,
667
- 'stroke-width': pick(point.borderWidth, level.borderWidth, stateOptions.borderWidth, options.borderWidth),
668
- 'dashstyle': point.borderDashStyle || level.borderDashStyle || stateOptions.borderDashStyle || options.borderDashStyle,
669
- 'fill': point.color || this.color,
670
- 'zIndex': state === 'hover' ? 1 : 0
671
- };
672
-
673
- if (point.node.level <= this.nodeMap[this.rootNode].level) {
674
- // Hide levels above the current view
675
- attr.fill = 'none';
676
- attr['stroke-width'] = 0;
677
- } else if (!point.node.isLeaf) {
678
- // If not a leaf, either set opacity or remove fill
679
- if (pick(options.interactByLeaf, !options.allowDrillToNode)) {
680
- attr.fill = 'none';
681
- } else {
682
- opacity = pick(stateOptions.opacity, options.opacity);
683
- attr.fill = Color(attr.fill).setOpacity(opacity).get();
684
- }
685
- } else if (state) {
686
- // Brighten and hoist the hover nodes
687
- attr.fill = Color(attr.fill).brighten(stateOptions.brightness).get();
688
- }
689
- return attr;
690
- },
691
-
692
- /**
693
- * Extending ColumnSeries drawPoints
694
- */
695
- drawPoints: function () {
696
- var series = this,
697
- points = grep(series.points, function (n) {
698
- return n.node.visible;
699
- });
700
-
701
- each(points, function (point) {
702
- var groupKey = 'levelGroup-' + point.node.levelDynamic,
703
- pointAttribs,
704
- crispCorr;
705
- if (!series[groupKey]) {
706
- series[groupKey] = series.chart.renderer.g(groupKey)
707
- .attr({
708
- zIndex: 1000 - point.node.levelDynamic // @todo Set the zIndex based upon the number of levels, instead of using 1000
709
- })
710
- .add(series.group);
711
- }
712
- point.group = series[groupKey];
713
- // Preliminary code in prepraration for HC5 that uses pointAttribs for all series
714
- pointAttribs = series.pointAttribs(point);
715
- point.pointAttr = {
716
- '': pointAttribs,
717
- 'hover': series.pointAttribs(point, 'hover'),
718
- 'select': {}
719
- };
720
-
721
- // Crisp correction
722
- if (point.shapeArgs) {
723
- crispCorr = parseInt(pointAttribs['stroke-width'], 10) % 2 / 2;
724
- point.shapeArgs.x -= crispCorr;
725
- point.shapeArgs.y -= crispCorr;
726
- }
727
- });
728
- // Call standard drawPoints
729
- seriesTypes.column.prototype.drawPoints.call(this);
730
-
731
- // If drillToNode is allowed, set a point cursor on clickables & add drillId to point
732
- if (series.options.allowDrillToNode) {
733
- each(points, function (point) {
734
- var cursor,
735
- drillId;
736
- if (point.graphic) {
737
- drillId = point.drillId = series.options.interactByLeaf ? series.drillToByLeaf(point) : series.drillToByGroup(point);
738
- cursor = drillId ? 'pointer' : 'default';
739
- point.graphic.css({ cursor: cursor });
740
- }
741
- });
742
- }
743
- },
744
- /**
745
- * Add drilling on the suitable points
746
- */
747
- drillTo: function () {
748
- var series = this;
749
- H.addEvent(series, 'click', function (event) {
750
- var point = event.point,
751
- drillId = point.drillId,
752
- drillName;
753
- // If a drill id is returned, add click event and cursor.
754
- if (drillId) {
755
- drillName = series.nodeMap[series.rootNode].name || series.rootNode;
756
- point.setState(''); // Remove hover
757
- series.drillToNode(drillId);
758
- series.showDrillUpButton(drillName);
759
- }
760
- });
761
- },
762
- /**
763
- * Finds the drill id for a parent node.
764
- * Returns false if point should not have a click event
765
- * @param {Object} point
766
- * @return {string || boolean} Drill to id or false when point should not have a click event
767
- */
768
- drillToByGroup: function (point) {
769
- var series = this,
770
- drillId = false;
771
- if ((point.node.level - series.nodeMap[series.rootNode].level) === 1 && !point.node.isLeaf) {
772
- drillId = point.id;
773
- }
774
- return drillId;
775
- },
776
- /**
777
- * Finds the drill id for a leaf node.
778
- * Returns false if point should not have a click event
779
- * @param {Object} point
780
- * @return {string || boolean} Drill to id or false when point should not have a click event
781
- */
782
- drillToByLeaf: function (point) {
783
- var series = this,
784
- drillId = false,
785
- nodeParent;
786
- if ((point.node.parent !== series.rootNode) && (point.node.isLeaf)) {
787
- nodeParent = point.node;
788
- while (!drillId) {
789
- nodeParent = series.nodeMap[nodeParent.parent];
790
- if (nodeParent.parent === series.rootNode) {
791
- drillId = nodeParent.id;
792
- }
793
- }
794
- }
795
- return drillId;
796
- },
797
- drillUp: function () {
798
- var drillPoint = null,
799
- node,
800
- parent;
801
- if (this.rootNode) {
802
- node = this.nodeMap[this.rootNode];
803
- if (node.parent !== null) {
804
- drillPoint = this.nodeMap[node.parent];
805
- } else {
806
- drillPoint = this.nodeMap[''];
807
- }
808
- }
809
-
810
- if (drillPoint !== null) {
811
- this.drillToNode(drillPoint.id);
812
- if (drillPoint.id === '') {
813
- this.drillUpButton = this.drillUpButton.destroy();
814
- } else {
815
- parent = this.nodeMap[drillPoint.parent];
816
- this.showDrillUpButton((parent.name || parent.id));
817
- }
818
- }
819
- },
820
- drillToNode: function (id) {
821
- this.options.rootId = id;
822
- this.isDirty = true; // Force redraw
823
- this.chart.redraw();
824
- },
825
- showDrillUpButton: function (name) {
826
- var series = this,
827
- backText = (name || '< Back'),
828
- buttonOptions = series.options.drillUpButton,
829
- attr,
830
- states;
831
-
832
- if (buttonOptions.text) {
833
- backText = buttonOptions.text;
834
- }
835
- if (!this.drillUpButton) {
836
- attr = buttonOptions.theme;
837
- states = attr && attr.states;
838
-
839
- this.drillUpButton = this.chart.renderer.button(
840
- backText,
841
- null,
842
- null,
843
- function () {
844
- series.drillUp();
845
- },
846
- attr,
847
- states && states.hover,
848
- states && states.select
849
- )
850
- .attr({
851
- align: buttonOptions.position.align,
852
- zIndex: 9
853
- })
854
- .add()
855
- .align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');
856
- } else {
857
- this.drillUpButton.attr({
858
- text: backText
859
- })
860
- .align();
861
- }
862
- },
863
- buildKDTree: noop,
864
- drawLegendSymbol: H.LegendSymbolMixin.drawRectangle,
865
- getExtremes: function () {
866
- // Get the extremes from the value data
867
- Series.prototype.getExtremes.call(this, this.colorValueData);
868
- this.valueMin = this.dataMin;
869
- this.valueMax = this.dataMax;
870
-
871
- // Get the extremes from the y data
872
- Series.prototype.getExtremes.call(this);
873
- },
874
- getExtremesFromAll: true,
875
- bindAxes: function () {
876
- var treeAxis = {
877
- endOnTick: false,
878
- gridLineWidth: 0,
879
- lineWidth: 0,
880
- min: 0,
881
- dataMin: 0,
882
- minPadding: 0,
883
- max: 100,
884
- dataMax: 100,
885
- maxPadding: 0,
886
- startOnTick: false,
887
- title: null,
888
- tickPositions: []
889
- };
890
- Series.prototype.bindAxes.call(this);
891
- H.extend(this.yAxis.options, treeAxis);
892
- H.extend(this.xAxis.options, treeAxis);
893
- }
894
- }));
64
+ // The Treemap series type
65
+ seriesType('treemap', 'scatter', {
66
+ showInLegend: false,
67
+ marker: false,
68
+ dataLabels: {
69
+ enabled: true,
70
+ defer: false,
71
+ verticalAlign: 'middle',
72
+ formatter: function() { // #2945
73
+ return this.point.name || this.point.id;
74
+ },
75
+ inside: true
76
+ },
77
+ tooltip: {
78
+ headerFormat: '',
79
+ pointFormat: '<b>{point.name}</b>: {point.value}</b><br/>'
80
+ },
81
+ layoutAlgorithm: 'sliceAndDice',
82
+ layoutStartingDirection: 'vertical',
83
+ alternateStartingDirection: false,
84
+ levelIsConstant: true,
85
+ drillUpButton: {
86
+ position: {
87
+ align: 'right',
88
+ x: -10,
89
+ y: 10
90
+ }
91
+ },
92
+
93
+ // Presentational options
94
+ borderColor: '#e6e6e6',
95
+ borderWidth: 1,
96
+ opacity: 0.15,
97
+ states: {
98
+ hover: {
99
+ borderColor: '#999999',
100
+ brightness: seriesTypes.heatmap ? 0 : 0.1,
101
+ opacity: 0.75,
102
+ shadow: false
103
+ }
104
+ }
105
+
106
+
107
+ // Prototype members
108
+ }, {
109
+ pointArrayMap: ['value'],
110
+ axisTypes: seriesTypes.heatmap ? ['xAxis', 'yAxis', 'colorAxis'] : ['xAxis', 'yAxis'],
111
+ optionalAxis: 'colorAxis',
112
+ getSymbol: noop,
113
+ parallelArrays: ['x', 'y', 'value', 'colorValue'],
114
+ colorKey: 'colorValue', // Point color option key
115
+ translateColors: seriesTypes.heatmap && seriesTypes.heatmap.prototype.translateColors,
116
+ trackerGroups: ['group', 'dataLabelsGroup'],
117
+ /**
118
+ * Creates an object map from parent id to childrens index.
119
+ * @param {Array} data List of points set in options.
120
+ * @param {string} data[].parent Parent id of point.
121
+ * @param {Array} ids List of all point ids.
122
+ * @return {Object} Map from parent id to children index in data.
123
+ */
124
+ getListOfParents: function(data, ids) {
125
+ var listOfParents = reduce(data, function(prev, curr, i) {
126
+ var parent = pick(curr.parent, '');
127
+ if (prev[parent] === undefined) {
128
+ prev[parent] = [];
129
+ }
130
+ prev[parent].push(i);
131
+ return prev;
132
+ }, {});
133
+
134
+ // If parent does not exist, hoist parent to root of tree.
135
+ eachObject(listOfParents, function(children, parent, list) {
136
+ if ((parent !== '') && (H.inArray(parent, ids) === -1)) {
137
+ each(children, function(child) {
138
+ list[''].push(child);
139
+ });
140
+ delete list[parent];
141
+ }
142
+ });
143
+ return listOfParents;
144
+ },
145
+ /**
146
+ * Creates a tree structured object from the series points
147
+ */
148
+ getTree: function() {
149
+ var tree,
150
+ series = this,
151
+ allIds = map(this.data, function(d) {
152
+ return d.id;
153
+ }),
154
+ parentList = series.getListOfParents(this.data, allIds);
155
+
156
+ series.nodeMap = [];
157
+ tree = series.buildNode('', -1, 0, parentList, null);
158
+ // Parents of the root node is by default visible
159
+ recursive(this.nodeMap[this.rootNode], function(node) {
160
+ var next = false,
161
+ p = node.parent;
162
+ node.visible = true;
163
+ if (p || p === '') {
164
+ next = series.nodeMap[p];
165
+ }
166
+ return next;
167
+ });
168
+ // Children of the root node is by default visible
169
+ recursive(this.nodeMap[this.rootNode].children, function(children) {
170
+ var next = false;
171
+ each(children, function(child) {
172
+ child.visible = true;
173
+ if (child.children.length) {
174
+ next = (next || []).concat(child.children);
175
+ }
176
+ });
177
+ return next;
178
+ });
179
+ this.setTreeValues(tree);
180
+ return tree;
181
+ },
182
+ init: function(chart, options) {
183
+ var series = this;
184
+ Series.prototype.init.call(series, chart, options);
185
+ if (series.options.allowDrillToNode) {
186
+ series.drillTo();
187
+ }
188
+ },
189
+ buildNode: function(id, i, level, list, parent) {
190
+ var series = this,
191
+ children = [],
192
+ point = series.points[i],
193
+ node,
194
+ child;
195
+
196
+ // Actions
197
+ each((list[id] || []), function(i) {
198
+ child = series.buildNode(series.points[i].id, i, (level + 1), list, id);
199
+ children.push(child);
200
+ });
201
+ node = {
202
+ id: id,
203
+ i: i,
204
+ children: children,
205
+ level: level,
206
+ parent: parent,
207
+ visible: false // @todo move this to better location
208
+ };
209
+ series.nodeMap[node.id] = node;
210
+ if (point) {
211
+ point.node = node;
212
+ }
213
+ return node;
214
+ },
215
+ setTreeValues: function(tree) {
216
+ var series = this,
217
+ options = series.options,
218
+ childrenTotal = 0,
219
+ children = [],
220
+ val,
221
+ point = series.points[tree.i];
222
+
223
+ // First give the children some values
224
+ each(tree.children, function(child) {
225
+ child = series.setTreeValues(child);
226
+ children.push(child);
227
+
228
+ if (!child.ignore) {
229
+ childrenTotal += child.val;
230
+ } else {
231
+ // @todo Add predicate to avoid looping already ignored children
232
+ recursive(child.children, function(children) {
233
+ var next = false;
234
+ each(children, function(node) {
235
+ extend(node, {
236
+ ignore: true,
237
+ isLeaf: false,
238
+ visible: false
239
+ });
240
+ if (node.children.length) {
241
+ next = (next || []).concat(node.children);
242
+ }
243
+ });
244
+ return next;
245
+ });
246
+ }
247
+ });
248
+ // Sort the children
249
+ stableSort(children, function(a, b) {
250
+ return a.sortIndex - b.sortIndex;
251
+ });
252
+ // Set the values
253
+ val = pick(point && point.options.value, childrenTotal);
254
+ if (point) {
255
+ point.value = val;
256
+ }
257
+ extend(tree, {
258
+ children: children,
259
+ childrenTotal: childrenTotal,
260
+ // Ignore this node if point is not visible
261
+ ignore: !(pick(point && point.visible, true) && (val > 0)),
262
+ isLeaf: tree.visible && !childrenTotal,
263
+ levelDynamic: (options.levelIsConstant ? tree.level : (tree.level - series.nodeMap[series.rootNode].level)),
264
+ name: pick(point && point.name, ''),
265
+ sortIndex: pick(point && point.sortIndex, -val),
266
+ val: val
267
+ });
268
+ return tree;
269
+ },
270
+ /**
271
+ * Recursive function which calculates the area for all children of a node.
272
+ * @param {Object} node The node which is parent to the children.
273
+ * @param {Object} area The rectangular area of the parent.
274
+ */
275
+ calculateChildrenAreas: function(parent, area) {
276
+ var series = this,
277
+ options = series.options,
278
+ level = this.levelMap[parent.levelDynamic + 1],
279
+ algorithm = pick((series[level && level.layoutAlgorithm] && level.layoutAlgorithm), options.layoutAlgorithm),
280
+ alternate = options.alternateStartingDirection,
281
+ childrenValues = [],
282
+ children;
283
+
284
+ // Collect all children which should be included
285
+ children = grep(parent.children, function(n) {
286
+ return !n.ignore;
287
+ });
288
+
289
+ if (level && level.layoutStartingDirection) {
290
+ area.direction = level.layoutStartingDirection === 'vertical' ? 0 : 1;
291
+ }
292
+ childrenValues = series[algorithm](area, children);
293
+ each(children, function(child, index) {
294
+ var values = childrenValues[index];
295
+ child.values = merge(values, {
296
+ val: child.childrenTotal,
297
+ direction: (alternate ? 1 - area.direction : area.direction)
298
+ });
299
+ child.pointValues = merge(values, {
300
+ x: (values.x / series.axisRatio),
301
+ width: (values.width / series.axisRatio)
302
+ });
303
+ // If node has children, then call method recursively
304
+ if (child.children.length) {
305
+ series.calculateChildrenAreas(child, child.values);
306
+ }
307
+ });
308
+ },
309
+ setPointValues: function() {
310
+ var series = this,
311
+ xAxis = series.xAxis,
312
+ yAxis = series.yAxis;
313
+ each(series.points, function(point) {
314
+ var node = point.node,
315
+ values = node.pointValues,
316
+ x1,
317
+ x2,
318
+ y1,
319
+ y2,
320
+ crispCorr = 0.5; // Assume 1px borderWidth for simplicity
321
+
322
+ // Points which is ignored, have no values.
323
+ if (values && node.visible) {
324
+ x1 = Math.round(xAxis.translate(values.x, 0, 0, 0, 1)) - crispCorr;
325
+ x2 = Math.round(xAxis.translate(values.x + values.width, 0, 0, 0, 1)) - crispCorr;
326
+ y1 = Math.round(yAxis.translate(values.y, 0, 0, 0, 1)) - crispCorr;
327
+ y2 = Math.round(yAxis.translate(values.y + values.height, 0, 0, 0, 1)) - crispCorr;
328
+ // Set point values
329
+ point.shapeType = 'rect';
330
+ point.shapeArgs = {
331
+ x: Math.min(x1, x2),
332
+ y: Math.min(y1, y2),
333
+ width: Math.abs(x2 - x1),
334
+ height: Math.abs(y2 - y1)
335
+ };
336
+ point.plotX = point.shapeArgs.x + (point.shapeArgs.width / 2);
337
+ point.plotY = point.shapeArgs.y + (point.shapeArgs.height / 2);
338
+ } else {
339
+ // Reset visibility
340
+ delete point.plotX;
341
+ delete point.plotY;
342
+ }
343
+ });
344
+ },
345
+ setColorRecursive: function(node, color, colorIndex) {
346
+ var series = this,
347
+ point,
348
+ level;
349
+ if (node) {
350
+ point = series.points[node.i];
351
+ level = series.levelMap[node.levelDynamic];
352
+ // Select either point color, level color or inherited color.
353
+ color = pick(point && point.options.color, level && level.color, color);
354
+ colorIndex = pick(point && point.options.colorIndex, level && level.colorIndex, colorIndex);
355
+ if (point) {
356
+ point.color = color;
357
+ point.colorIndex = colorIndex;
358
+ }
359
+
360
+ // Do it all again with the children
361
+ if (node.children.length) {
362
+ each(node.children, function(child) {
363
+ series.setColorRecursive(child, color, colorIndex);
364
+ });
365
+ }
366
+ }
367
+ },
368
+ algorithmGroup: function(h, w, d, p) {
369
+ this.height = h;
370
+ this.width = w;
371
+ this.plot = p;
372
+ this.direction = d;
373
+ this.startDirection = d;
374
+ this.total = 0;
375
+ this.nW = 0;
376
+ this.lW = 0;
377
+ this.nH = 0;
378
+ this.lH = 0;
379
+ this.elArr = [];
380
+ this.lP = {
381
+ total: 0,
382
+ lH: 0,
383
+ nH: 0,
384
+ lW: 0,
385
+ nW: 0,
386
+ nR: 0,
387
+ lR: 0,
388
+ aspectRatio: function(w, h) {
389
+ return Math.max((w / h), (h / w));
390
+ }
391
+ };
392
+ this.addElement = function(el) {
393
+ this.lP.total = this.elArr[this.elArr.length - 1];
394
+ this.total = this.total + el;
395
+ if (this.direction === 0) {
396
+ // Calculate last point old aspect ratio
397
+ this.lW = this.nW;
398
+ this.lP.lH = this.lP.total / this.lW;
399
+ this.lP.lR = this.lP.aspectRatio(this.lW, this.lP.lH);
400
+ // Calculate last point new aspect ratio
401
+ this.nW = this.total / this.height;
402
+ this.lP.nH = this.lP.total / this.nW;
403
+ this.lP.nR = this.lP.aspectRatio(this.nW, this.lP.nH);
404
+ } else {
405
+ // Calculate last point old aspect ratio
406
+ this.lH = this.nH;
407
+ this.lP.lW = this.lP.total / this.lH;
408
+ this.lP.lR = this.lP.aspectRatio(this.lP.lW, this.lH);
409
+ // Calculate last point new aspect ratio
410
+ this.nH = this.total / this.width;
411
+ this.lP.nW = this.lP.total / this.nH;
412
+ this.lP.nR = this.lP.aspectRatio(this.lP.nW, this.nH);
413
+ }
414
+ this.elArr.push(el);
415
+ };
416
+ this.reset = function() {
417
+ this.nW = 0;
418
+ this.lW = 0;
419
+ this.elArr = [];
420
+ this.total = 0;
421
+ };
422
+ },
423
+ algorithmCalcPoints: function(directionChange, last, group, childrenArea) {
424
+ var pX,
425
+ pY,
426
+ pW,
427
+ pH,
428
+ gW = group.lW,
429
+ gH = group.lH,
430
+ plot = group.plot,
431
+ keep,
432
+ i = 0,
433
+ end = group.elArr.length - 1;
434
+ if (last) {
435
+ gW = group.nW;
436
+ gH = group.nH;
437
+ } else {
438
+ keep = group.elArr[group.elArr.length - 1];
439
+ }
440
+ each(group.elArr, function(p) {
441
+ if (last || (i < end)) {
442
+ if (group.direction === 0) {
443
+ pX = plot.x;
444
+ pY = plot.y;
445
+ pW = gW;
446
+ pH = p / pW;
447
+ } else {
448
+ pX = plot.x;
449
+ pY = plot.y;
450
+ pH = gH;
451
+ pW = p / pH;
452
+ }
453
+ childrenArea.push({
454
+ x: pX,
455
+ y: pY,
456
+ width: pW,
457
+ height: pH
458
+ });
459
+ if (group.direction === 0) {
460
+ plot.y = plot.y + pH;
461
+ } else {
462
+ plot.x = plot.x + pW;
463
+ }
464
+ }
465
+ i = i + 1;
466
+ });
467
+ // Reset variables
468
+ group.reset();
469
+ if (group.direction === 0) {
470
+ group.width = group.width - gW;
471
+ } else {
472
+ group.height = group.height - gH;
473
+ }
474
+ plot.y = plot.parent.y + (plot.parent.height - group.height);
475
+ plot.x = plot.parent.x + (plot.parent.width - group.width);
476
+ if (directionChange) {
477
+ group.direction = 1 - group.direction;
478
+ }
479
+ // If not last, then add uncalculated element
480
+ if (!last) {
481
+ group.addElement(keep);
482
+ }
483
+ },
484
+ algorithmLowAspectRatio: function(directionChange, parent, children) {
485
+ var childrenArea = [],
486
+ series = this,
487
+ pTot,
488
+ plot = {
489
+ x: parent.x,
490
+ y: parent.y,
491
+ parent: parent
492
+ },
493
+ direction = parent.direction,
494
+ i = 0,
495
+ end = children.length - 1,
496
+ group = new this.algorithmGroup(parent.height, parent.width, direction, plot); // eslint-disable-line new-cap
497
+ // Loop through and calculate all areas
498
+ each(children, function(child) {
499
+ pTot = (parent.width * parent.height) * (child.val / parent.val);
500
+ group.addElement(pTot);
501
+ if (group.lP.nR > group.lP.lR) {
502
+ series.algorithmCalcPoints(directionChange, false, group, childrenArea, plot);
503
+ }
504
+ // If last child, then calculate all remaining areas
505
+ if (i === end) {
506
+ series.algorithmCalcPoints(directionChange, true, group, childrenArea, plot);
507
+ }
508
+ i = i + 1;
509
+ });
510
+ return childrenArea;
511
+ },
512
+ algorithmFill: function(directionChange, parent, children) {
513
+ var childrenArea = [],
514
+ pTot,
515
+ direction = parent.direction,
516
+ x = parent.x,
517
+ y = parent.y,
518
+ width = parent.width,
519
+ height = parent.height,
520
+ pX,
521
+ pY,
522
+ pW,
523
+ pH;
524
+ each(children, function(child) {
525
+ pTot = (parent.width * parent.height) * (child.val / parent.val);
526
+ pX = x;
527
+ pY = y;
528
+ if (direction === 0) {
529
+ pH = height;
530
+ pW = pTot / pH;
531
+ width = width - pW;
532
+ x = x + pW;
533
+ } else {
534
+ pW = width;
535
+ pH = pTot / pW;
536
+ height = height - pH;
537
+ y = y + pH;
538
+ }
539
+ childrenArea.push({
540
+ x: pX,
541
+ y: pY,
542
+ width: pW,
543
+ height: pH
544
+ });
545
+ if (directionChange) {
546
+ direction = 1 - direction;
547
+ }
548
+ });
549
+ return childrenArea;
550
+ },
551
+ strip: function(parent, children) {
552
+ return this.algorithmLowAspectRatio(false, parent, children);
553
+ },
554
+ squarified: function(parent, children) {
555
+ return this.algorithmLowAspectRatio(true, parent, children);
556
+ },
557
+ sliceAndDice: function(parent, children) {
558
+ return this.algorithmFill(true, parent, children);
559
+ },
560
+ stripes: function(parent, children) {
561
+ return this.algorithmFill(false, parent, children);
562
+ },
563
+ translate: function() {
564
+ var pointValues,
565
+ seriesArea,
566
+ tree,
567
+ val;
568
+
569
+ // Call prototype function
570
+ Series.prototype.translate.call(this);
571
+
572
+ // Assign variables
573
+ this.rootNode = pick(this.options.rootId, '');
574
+ // Create a object map from level to options
575
+ this.levelMap = reduce(this.options.levels, function(arr, item) {
576
+ arr[item.level] = item;
577
+ return arr;
578
+ }, {});
579
+ tree = this.tree = this.getTree(); // @todo Only if series.isDirtyData is true
580
+
581
+ // Calculate plotting values.
582
+ this.axisRatio = (this.xAxis.len / this.yAxis.len);
583
+ this.nodeMap[''].pointValues = pointValues = {
584
+ x: 0,
585
+ y: 0,
586
+ width: 100,
587
+ height: 100
588
+ };
589
+ this.nodeMap[''].values = seriesArea = merge(pointValues, {
590
+ width: (pointValues.width * this.axisRatio),
591
+ direction: (this.options.layoutStartingDirection === 'vertical' ? 0 : 1),
592
+ val: tree.val
593
+ });
594
+ this.calculateChildrenAreas(tree, seriesArea);
595
+
596
+ // Logic for point colors
597
+ if (this.colorAxis) {
598
+ this.translateColors();
599
+ } else if (!this.options.colorByPoint) {
600
+ this.setColorRecursive(this.tree);
601
+ }
602
+
603
+ // Update axis extremes according to the root node.
604
+ if (this.options.allowDrillToNode) {
605
+ val = this.nodeMap[this.rootNode].pointValues;
606
+ this.xAxis.setExtremes(val.x, val.x + val.width, false);
607
+ this.yAxis.setExtremes(val.y, val.y + val.height, false);
608
+ this.xAxis.setScale();
609
+ this.yAxis.setScale();
610
+ }
611
+
612
+ // Assign values to points.
613
+ this.setPointValues();
614
+ },
615
+ /**
616
+ * Extend drawDataLabels with logic to handle custom options related to the treemap series:
617
+ * - Points which is not a leaf node, has dataLabels disabled by default.
618
+ * - Options set on series.levels is merged in.
619
+ * - Width of the dataLabel is set to match the width of the point shape.
620
+ */
621
+ drawDataLabels: function() {
622
+ var series = this,
623
+ points = grep(series.points, function(n) {
624
+ return n.node.visible;
625
+ }),
626
+ options,
627
+ level;
628
+ each(points, function(point) {
629
+ level = series.levelMap[point.node.levelDynamic];
630
+ // Set options to new object to avoid problems with scope
631
+ options = {
632
+ style: {}
633
+ };
634
+
635
+ // If not a leaf, then label should be disabled as default
636
+ if (!point.node.isLeaf) {
637
+ options.enabled = false;
638
+ }
639
+
640
+ // If options for level exists, include them as well
641
+ if (level && level.dataLabels) {
642
+ options = merge(options, level.dataLabels);
643
+ series._hasPointLabels = true;
644
+ }
645
+
646
+ // Set dataLabel width to the width of the point shape.
647
+ if (point.shapeArgs) {
648
+ options.style.width = point.shapeArgs.width;
649
+ if (point.dataLabel) {
650
+ point.dataLabel.css({
651
+ width: point.shapeArgs.width + 'px'
652
+ });
653
+ }
654
+ }
655
+
656
+ // Merge custom options with point options
657
+ point.dlOptions = merge(options, point.options.dataLabels);
658
+ });
659
+ Series.prototype.drawDataLabels.call(this);
660
+ },
661
+
662
+ /**
663
+ * Over the alignment method by setting z index
664
+ */
665
+ alignDataLabel: function(point) {
666
+ seriesTypes.column.prototype.alignDataLabel.apply(this, arguments);
667
+ if (point.dataLabel) {
668
+ point.dataLabel.attr({
669
+ zIndex: point.node.zIndex + 1
670
+ });
671
+ }
672
+ },
673
+
674
+
675
+ /**
676
+ * Get presentational attributes
677
+ */
678
+ pointAttribs: function(point, state) {
679
+ var level = this.levelMap[point.node.levelDynamic] || {},
680
+ options = this.options,
681
+ attr,
682
+ stateOptions = (state && options.states[state]) || {},
683
+ className = point.getClassName(),
684
+ opacity;
685
+
686
+ // Set attributes by precedence. Point trumps level trumps series. Stroke width uses pick
687
+ // because it can be 0.
688
+ attr = {
689
+ 'stroke': point.borderColor || level.borderColor || stateOptions.borderColor || options.borderColor,
690
+ 'stroke-width': pick(point.borderWidth, level.borderWidth, stateOptions.borderWidth, options.borderWidth),
691
+ 'dashstyle': point.borderDashStyle || level.borderDashStyle || stateOptions.borderDashStyle || options.borderDashStyle,
692
+ 'fill': point.color || this.color
693
+ };
694
+
695
+ // Hide levels above the current view
696
+ if (className.indexOf('highcharts-above-level') !== -1) {
697
+ attr.fill = 'none';
698
+ attr['stroke-width'] = 0;
699
+
700
+ // Nodes with children that accept interaction
701
+ } else if (className.indexOf('highcharts-internal-node-interactive') !== -1) {
702
+ opacity = pick(stateOptions.opacity, options.opacity);
703
+ attr.fill = color(attr.fill).setOpacity(opacity).get();
704
+ attr.cursor = 'pointer';
705
+ // Hide nodes that have children
706
+ } else if (className.indexOf('highcharts-internal-node') !== -1) {
707
+ attr.fill = 'none';
708
+
709
+ } else if (state) {
710
+ // Brighten and hoist the hover nodes
711
+ attr.fill = color(attr.fill).brighten(stateOptions.brightness).get();
712
+ }
713
+ return attr;
714
+ },
715
+
716
+
717
+ /**
718
+ * Extending ColumnSeries drawPoints
719
+ */
720
+ drawPoints: function() {
721
+ var series = this,
722
+ points = grep(series.points, function(n) {
723
+ return n.node.visible;
724
+ });
725
+
726
+ each(points, function(point) {
727
+ var groupKey = 'levelGroup-' + point.node.levelDynamic;
728
+ if (!series[groupKey]) {
729
+ series[groupKey] = series.chart.renderer.g(groupKey)
730
+ .attr({
731
+ zIndex: 1000 - point.node.levelDynamic // @todo Set the zIndex based upon the number of levels, instead of using 1000
732
+ })
733
+ .add(series.group);
734
+ }
735
+ point.group = series[groupKey];
736
+
737
+ });
738
+ // Call standard drawPoints
739
+ seriesTypes.column.prototype.drawPoints.call(this);
740
+
741
+ // If drillToNode is allowed, set a point cursor on clickables & add drillId to point
742
+ if (series.options.allowDrillToNode) {
743
+ each(points, function(point) {
744
+ if (point.graphic) {
745
+ point.drillId = series.options.interactByLeaf ? series.drillToByLeaf(point) : series.drillToByGroup(point);
746
+ }
747
+ });
748
+ }
749
+ },
750
+ /**
751
+ * Add drilling on the suitable points
752
+ */
753
+ drillTo: function() {
754
+ var series = this;
755
+ H.addEvent(series, 'click', function(event) {
756
+ var point = event.point,
757
+ drillId = point.drillId,
758
+ drillName;
759
+ // If a drill id is returned, add click event and cursor.
760
+ if (drillId) {
761
+ drillName = series.nodeMap[series.rootNode].name || series.rootNode;
762
+ point.setState(''); // Remove hover
763
+ series.drillToNode(drillId);
764
+ series.showDrillUpButton(drillName);
765
+ }
766
+ });
767
+ },
768
+ /**
769
+ * Finds the drill id for a parent node.
770
+ * Returns false if point should not have a click event
771
+ * @param {Object} point
772
+ * @return {string || boolean} Drill to id or false when point should not have a click event
773
+ */
774
+ drillToByGroup: function(point) {
775
+ var series = this,
776
+ drillId = false;
777
+ if ((point.node.level - series.nodeMap[series.rootNode].level) === 1 && !point.node.isLeaf) {
778
+ drillId = point.id;
779
+ }
780
+ return drillId;
781
+ },
782
+ /**
783
+ * Finds the drill id for a leaf node.
784
+ * Returns false if point should not have a click event
785
+ * @param {Object} point
786
+ * @return {string || boolean} Drill to id or false when point should not have a click event
787
+ */
788
+ drillToByLeaf: function(point) {
789
+ var series = this,
790
+ drillId = false,
791
+ nodeParent;
792
+ if ((point.node.parent !== series.rootNode) && (point.node.isLeaf)) {
793
+ nodeParent = point.node;
794
+ while (!drillId) {
795
+ nodeParent = series.nodeMap[nodeParent.parent];
796
+ if (nodeParent.parent === series.rootNode) {
797
+ drillId = nodeParent.id;
798
+ }
799
+ }
800
+ }
801
+ return drillId;
802
+ },
803
+ drillUp: function() {
804
+ var drillPoint = null,
805
+ node,
806
+ parent;
807
+ if (this.rootNode) {
808
+ node = this.nodeMap[this.rootNode];
809
+ if (node.parent !== null) {
810
+ drillPoint = this.nodeMap[node.parent];
811
+ } else {
812
+ drillPoint = this.nodeMap[''];
813
+ }
814
+ }
815
+
816
+ if (drillPoint !== null) {
817
+ this.drillToNode(drillPoint.id);
818
+ if (drillPoint.id === '') {
819
+ this.drillUpButton = this.drillUpButton.destroy();
820
+ } else {
821
+ parent = this.nodeMap[drillPoint.parent];
822
+ this.showDrillUpButton((parent.name || parent.id));
823
+ }
824
+ }
825
+ },
826
+ drillToNode: function(id) {
827
+ this.options.rootId = id;
828
+ this.isDirty = true; // Force redraw
829
+ this.chart.redraw();
830
+ },
831
+ showDrillUpButton: function(name) {
832
+ var series = this,
833
+ backText = (name || '< Back'),
834
+ buttonOptions = series.options.drillUpButton,
835
+ attr,
836
+ states;
837
+
838
+ if (buttonOptions.text) {
839
+ backText = buttonOptions.text;
840
+ }
841
+ if (!this.drillUpButton) {
842
+ attr = buttonOptions.theme;
843
+ states = attr && attr.states;
844
+
845
+ this.drillUpButton = this.chart.renderer.button(
846
+ backText,
847
+ null,
848
+ null,
849
+ function() {
850
+ series.drillUp();
851
+ },
852
+ attr,
853
+ states && states.hover,
854
+ states && states.select
855
+ )
856
+ .attr({
857
+ align: buttonOptions.position.align,
858
+ zIndex: 7
859
+ })
860
+ .add()
861
+ .align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');
862
+ } else {
863
+ this.drillUpButton.attr({
864
+ text: backText
865
+ })
866
+ .align();
867
+ }
868
+ },
869
+ buildKDTree: noop,
870
+ drawLegendSymbol: H.LegendSymbolMixin.drawRectangle,
871
+ getExtremes: function() {
872
+ // Get the extremes from the value data
873
+ Series.prototype.getExtremes.call(this, this.colorValueData);
874
+ this.valueMin = this.dataMin;
875
+ this.valueMax = this.dataMax;
876
+
877
+ // Get the extremes from the y data
878
+ Series.prototype.getExtremes.call(this);
879
+ },
880
+ getExtremesFromAll: true,
881
+ bindAxes: function() {
882
+ var treeAxis = {
883
+ endOnTick: false,
884
+ gridLineWidth: 0,
885
+ lineWidth: 0,
886
+ min: 0,
887
+ dataMin: 0,
888
+ minPadding: 0,
889
+ max: 100,
890
+ dataMax: 100,
891
+ maxPadding: 0,
892
+ startOnTick: false,
893
+ title: null,
894
+ tickPositions: []
895
+ };
896
+ Series.prototype.bindAxes.call(this);
897
+ H.extend(this.yAxis.options, treeAxis);
898
+ H.extend(this.xAxis.options, treeAxis);
899
+ }
900
+
901
+ // Point class
902
+ }, {
903
+ getClassName: function() {
904
+ var className = H.Point.prototype.getClassName.call(this),
905
+ series = this.series,
906
+ options = series.options;
907
+
908
+ // Above the current level
909
+ if (this.node.level <= series.nodeMap[series.rootNode].level) {
910
+ className += ' highcharts-above-level';
911
+
912
+ } else if (!this.node.isLeaf && !pick(options.interactByLeaf, !options.allowDrillToNode)) {
913
+ className += ' highcharts-internal-node-interactive';
914
+
915
+ } else if (!this.node.isLeaf) {
916
+ className += ' highcharts-internal-node';
917
+ }
918
+ return className;
919
+ },
920
+ setState: function(state) {
921
+ H.Point.prototype.setState.call(this, state);
922
+ this.graphic.attr({
923
+ zIndex: state === 'hover' ? 1 : 0
924
+ });
925
+ },
926
+ setVisible: seriesTypes.pie.prototype.pointClass.prototype.setVisible
927
+ });
928
+
929
+ }(Highcharts));
895
930
  }));