highcharts-rails 4.2.7 → 5.0.0

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