| 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));
|