Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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