chartjs-chart-treemap 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chartjs-chart-treemap.esm.js +555 -370
- package/dist/chartjs-chart-treemap.js +558 -370
- package/dist/chartjs-chart-treemap.min.js +3 -3
- package/package.json +18 -14
- package/types/index.esm.d.ts +19 -10
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-treemap v2.0
|
|
2
|
+
* chartjs-chart-treemap v2.1.0
|
|
3
3
|
* https://chartjs-chart-treemap.pages.dev/
|
|
4
|
-
* (c)
|
|
4
|
+
* (c) 2022 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
6
6
|
*/
|
|
7
7
|
(function (global, factory) {
|
|
@@ -10,6 +10,65 @@ typeof define === 'function' && define.amd ? define(['exports', 'chart.js', 'cha
|
|
|
10
10
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["chartjs-chart-treemap"] = {}, global.Chart, global.Chart.helpers));
|
|
11
11
|
})(this, (function (exports, chart_js, helpers) { 'use strict';
|
|
12
12
|
|
|
13
|
+
const getValue = (item) => helpers.isObject(item) ? item.v : item;
|
|
14
|
+
|
|
15
|
+
const maxValue = (data) => data.reduce(function(m, v) {
|
|
16
|
+
return (m > getValue(v) ? m : getValue(v));
|
|
17
|
+
}, 0);
|
|
18
|
+
|
|
19
|
+
const minValue = (data, mx) => data.reduce(function(m, v) {
|
|
20
|
+
return (m < getValue(v) ? m : getValue(v));
|
|
21
|
+
}, mx);
|
|
22
|
+
|
|
23
|
+
const getGroupKey = (lvl) => '' + lvl;
|
|
24
|
+
|
|
25
|
+
function scanTreeObject(key, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
|
|
26
|
+
const objIndex = lvl - 1;
|
|
27
|
+
if (key in obj && lvl > 0) {
|
|
28
|
+
const record = tree.reduce(function(reduced, item, i) {
|
|
29
|
+
if (i !== objIndex) {
|
|
30
|
+
reduced[getGroupKey(i)] = item;
|
|
31
|
+
}
|
|
32
|
+
return reduced;
|
|
33
|
+
}, {});
|
|
34
|
+
record[treeLeafKey] = tree[objIndex];
|
|
35
|
+
record[key] = obj[key];
|
|
36
|
+
result.push(record);
|
|
37
|
+
} else {
|
|
38
|
+
for (const childKey of Object.keys(obj)) {
|
|
39
|
+
const child = obj[childKey];
|
|
40
|
+
if (helpers.isObject(child)) {
|
|
41
|
+
tree.push(childKey);
|
|
42
|
+
scanTreeObject(key, treeLeafKey, child, tree, lvl + 1, result);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
tree.splice(objIndex, 1);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeTreeToArray(key, treeLeafKey, obj) {
|
|
51
|
+
const data = scanTreeObject(key, treeLeafKey, obj);
|
|
52
|
+
if (!data.length) {
|
|
53
|
+
return data;
|
|
54
|
+
}
|
|
55
|
+
const max = data.reduce(function(maxVal, element) {
|
|
56
|
+
// minus 2 because _leaf and value properties are added
|
|
57
|
+
// on top to groups ones
|
|
58
|
+
const keys = Object.keys(element).length - 2;
|
|
59
|
+
return maxVal > keys ? maxVal : keys;
|
|
60
|
+
});
|
|
61
|
+
data.forEach(function(element) {
|
|
62
|
+
for (let i = 0; i < max; i++) {
|
|
63
|
+
const groupKey = getGroupKey(i);
|
|
64
|
+
if (!element[groupKey]) {
|
|
65
|
+
element[groupKey] = '';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
|
|
13
72
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
|
|
14
73
|
function flatten(input) {
|
|
15
74
|
const stack = [...input];
|
|
@@ -28,36 +87,59 @@ function flatten(input) {
|
|
|
28
87
|
return res.reverse();
|
|
29
88
|
}
|
|
30
89
|
|
|
90
|
+
function getPath(groups, value, defaultValue) {
|
|
91
|
+
if (!groups.length) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const path = [];
|
|
95
|
+
for (const grp of groups) {
|
|
96
|
+
const item = value[grp];
|
|
97
|
+
if (item === '') {
|
|
98
|
+
path.push(defaultValue);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
path.push(item);
|
|
102
|
+
}
|
|
103
|
+
return path.length ? path.join('.') : defaultValue;
|
|
104
|
+
}
|
|
105
|
+
|
|
31
106
|
/**
|
|
32
107
|
* @param {[]} values
|
|
33
108
|
* @param {string} grp
|
|
34
109
|
* @param {string} key
|
|
110
|
+
* @param {string} treeeLeafKey
|
|
35
111
|
* @param {string} [mainGrp]
|
|
36
112
|
* @param {*} [mainValue]
|
|
113
|
+
* @param {[]} groups
|
|
37
114
|
*/
|
|
38
|
-
function group(values, grp, key, mainGrp, mainValue) {
|
|
115
|
+
function group(values, grp, key, treeLeafKey, mainGrp, mainValue, groups = []) {
|
|
39
116
|
const tmp = Object.create(null);
|
|
40
117
|
const data = Object.create(null);
|
|
41
118
|
const ret = [];
|
|
42
|
-
let g, i, n
|
|
119
|
+
let g, i, n;
|
|
43
120
|
for (i = 0, n = values.length; i < n; ++i) {
|
|
44
|
-
v = values[i];
|
|
121
|
+
const v = values[i];
|
|
45
122
|
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
46
123
|
continue;
|
|
47
124
|
}
|
|
48
|
-
g = v[grp] || '';
|
|
125
|
+
g = v[grp] || v[treeLeafKey] || '';
|
|
49
126
|
if (!(g in tmp)) {
|
|
50
|
-
tmp[g] = 0;
|
|
127
|
+
tmp[g] = {value: 0};
|
|
51
128
|
data[g] = [];
|
|
52
129
|
}
|
|
53
|
-
tmp[g] += +v[key];
|
|
130
|
+
tmp[g].value += +v[key];
|
|
131
|
+
tmp[g].label = v[grp] || '';
|
|
132
|
+
tmp[g].path = getPath(groups, v, g);
|
|
54
133
|
data[g].push(v);
|
|
55
134
|
}
|
|
56
135
|
|
|
57
136
|
Object.keys(tmp).forEach((k) => {
|
|
58
|
-
v = {children: data[k]};
|
|
59
|
-
v[key] = +tmp[k];
|
|
60
|
-
v[grp] = k;
|
|
137
|
+
const v = {children: data[k]};
|
|
138
|
+
v[key] = +tmp[k].value;
|
|
139
|
+
v[grp] = tmp[k].label;
|
|
140
|
+
v.label = k;
|
|
141
|
+
v.path = tmp[k].path;
|
|
142
|
+
|
|
61
143
|
if (mainGrp) {
|
|
62
144
|
v[mainGrp] = mainValue;
|
|
63
145
|
}
|
|
@@ -67,11 +149,6 @@ function group(values, grp, key, mainGrp, mainValue) {
|
|
|
67
149
|
return ret;
|
|
68
150
|
}
|
|
69
151
|
|
|
70
|
-
function isObject(obj) {
|
|
71
|
-
const type = typeof obj;
|
|
72
|
-
return type === 'function' || type === 'object' && !!obj;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
152
|
function index(values, key) {
|
|
76
153
|
let n = values.length;
|
|
77
154
|
let i;
|
|
@@ -80,7 +157,7 @@ function index(values, key) {
|
|
|
80
157
|
return key;
|
|
81
158
|
}
|
|
82
159
|
|
|
83
|
-
const obj = isObject(values[0]);
|
|
160
|
+
const obj = helpers.isObject(values[0]);
|
|
84
161
|
key = obj ? key : 'v';
|
|
85
162
|
|
|
86
163
|
for (i = 0, n = values.length; i < n; ++i) {
|
|
@@ -118,6 +195,387 @@ function requireVersion(min, ver) {
|
|
|
118
195
|
}
|
|
119
196
|
}
|
|
120
197
|
|
|
198
|
+
const widthCache = new Map();
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Helper function to get the bounds of the rect
|
|
202
|
+
* @param {TreemapElement} rect the rect
|
|
203
|
+
* @param {boolean} [useFinalPosition]
|
|
204
|
+
* @return {object} bounds of the rect
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
function getBounds(rect, useFinalPosition) {
|
|
208
|
+
const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
209
|
+
return {left: x, top: y, right: x + width, bottom: y + height};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function limit(value, min, max) {
|
|
213
|
+
return Math.max(Math.min(value, max), min);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseBorderWidth(value, maxW, maxH) {
|
|
217
|
+
const o = helpers.toTRBL(value);
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
t: limit(o.top, 0, maxH),
|
|
221
|
+
r: limit(o.right, 0, maxW),
|
|
222
|
+
b: limit(o.bottom, 0, maxH),
|
|
223
|
+
l: limit(o.left, 0, maxW)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function parseBorderRadius(value, maxW, maxH) {
|
|
228
|
+
const o = helpers.toTRBLCorners(value);
|
|
229
|
+
const maxR = Math.min(maxW, maxH);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
topLeft: limit(o.topLeft, 0, maxR),
|
|
233
|
+
topRight: limit(o.topRight, 0, maxR),
|
|
234
|
+
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
235
|
+
bottomRight: limit(o.bottomRight, 0, maxR)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function boundingRects(rect) {
|
|
240
|
+
const bounds = getBounds(rect);
|
|
241
|
+
const width = bounds.right - bounds.left;
|
|
242
|
+
const height = bounds.bottom - bounds.top;
|
|
243
|
+
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
244
|
+
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
outer: {
|
|
248
|
+
x: bounds.left,
|
|
249
|
+
y: bounds.top,
|
|
250
|
+
w: width,
|
|
251
|
+
h: height,
|
|
252
|
+
radius
|
|
253
|
+
},
|
|
254
|
+
inner: {
|
|
255
|
+
x: bounds.left + border.l,
|
|
256
|
+
y: bounds.top + border.t,
|
|
257
|
+
w: width - border.l - border.r,
|
|
258
|
+
h: height - border.t - border.b,
|
|
259
|
+
radius: {
|
|
260
|
+
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
261
|
+
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
|
|
262
|
+
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
263
|
+
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function inRange(rect, x, y, useFinalPosition) {
|
|
270
|
+
const skipX = x === null;
|
|
271
|
+
const skipY = y === null;
|
|
272
|
+
const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
|
|
273
|
+
|
|
274
|
+
return bounds
|
|
275
|
+
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
276
|
+
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function hasRadius(radius) {
|
|
280
|
+
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Add a path of a rectangle to the current sub-path
|
|
285
|
+
* @param {CanvasRenderingContext2D} ctx Context
|
|
286
|
+
* @param {*} rect Bounding rect
|
|
287
|
+
*/
|
|
288
|
+
function addNormalRectPath(ctx, rect) {
|
|
289
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function shouldDrawCaption(rect, options) {
|
|
293
|
+
if (!options || !options.display) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
const {w, h} = rect;
|
|
297
|
+
const font = helpers.toFont(options.font);
|
|
298
|
+
const min = font.lineHeight;
|
|
299
|
+
const padding = limit(helpers.valueOrDefault(options.padding, 3) * 2, 0, Math.min(w, h));
|
|
300
|
+
return (w - padding) > min && (h - padding) > min;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function drawText(ctx, rect, options, item, levels) {
|
|
304
|
+
const {captions, labels} = options;
|
|
305
|
+
ctx.save();
|
|
306
|
+
ctx.beginPath();
|
|
307
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
308
|
+
ctx.clip();
|
|
309
|
+
const isLeaf = (!('l' in item) || item.l === levels);
|
|
310
|
+
if (isLeaf && labels.display) {
|
|
311
|
+
drawLabel(ctx, rect, options);
|
|
312
|
+
} else if (!isLeaf && shouldDrawCaption(rect, captions)) {
|
|
313
|
+
drawCaption(ctx, rect, options, item);
|
|
314
|
+
}
|
|
315
|
+
ctx.restore();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function drawCaption(ctx, rect, options, item) {
|
|
319
|
+
const {captions, spacing, rtl} = options;
|
|
320
|
+
const {color, hoverColor, font, hoverFont, padding, align, formatter} = captions;
|
|
321
|
+
const oColor = (rect.active ? hoverColor : color) || color;
|
|
322
|
+
const oAlign = align || (rtl ? 'right' : 'left');
|
|
323
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
324
|
+
const oFont = helpers.toFont(optFont);
|
|
325
|
+
const lh = oFont.lineHeight / 2;
|
|
326
|
+
const x = calculateX(rect, oAlign, padding);
|
|
327
|
+
ctx.fillStyle = oColor;
|
|
328
|
+
ctx.font = oFont.string;
|
|
329
|
+
ctx.textAlign = oAlign;
|
|
330
|
+
ctx.textBaseline = 'middle';
|
|
331
|
+
ctx.fillText(formatter || item.g, x, rect.y + padding + spacing + lh);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function measureLabelSize(ctx, lines, fonts) {
|
|
335
|
+
const fontsKey = fonts.reduce(function(prev, item) {
|
|
336
|
+
prev += item.string;
|
|
337
|
+
return prev;
|
|
338
|
+
}, '');
|
|
339
|
+
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
340
|
+
if (!widthCache.has(mapKey)) {
|
|
341
|
+
ctx.save();
|
|
342
|
+
const count = lines.length;
|
|
343
|
+
let width = 0;
|
|
344
|
+
let height = 0;
|
|
345
|
+
for (let i = 0; i < count; i++) {
|
|
346
|
+
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
347
|
+
ctx.font = font.string;
|
|
348
|
+
const text = lines[i];
|
|
349
|
+
width = Math.max(width, ctx.measureText(text).width);
|
|
350
|
+
height += font.lineHeight;
|
|
351
|
+
}
|
|
352
|
+
ctx.restore();
|
|
353
|
+
widthCache.set(mapKey, {width, height});
|
|
354
|
+
}
|
|
355
|
+
return widthCache.get(mapKey);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function labelToDraw(ctx, rect, options, labelSize) {
|
|
359
|
+
const {overflow, padding} = options;
|
|
360
|
+
const {width, height} = labelSize;
|
|
361
|
+
if (overflow === 'hidden') {
|
|
362
|
+
return !((width + padding * 2) > rect.w || (height + padding * 2) > rect.h);
|
|
363
|
+
}
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function drawLabel(ctx, rect, options) {
|
|
368
|
+
const labels = options.labels;
|
|
369
|
+
const content = labels.formatter;
|
|
370
|
+
if (!content) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const contents = helpers.isArray(content) ? content : [content];
|
|
374
|
+
const {font, hoverFont} = labels;
|
|
375
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
376
|
+
const fonts = helpers.isArray(optFont) ? optFont.map(f => helpers.toFont(f)) : [helpers.toFont(optFont)];
|
|
377
|
+
const labelSize = measureLabelSize(ctx, contents, fonts);
|
|
378
|
+
if (!labelToDraw(ctx, rect, labels, labelSize)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const {color, hoverColor, align} = labels;
|
|
382
|
+
const optColor = (rect.active ? hoverColor : color) || color;
|
|
383
|
+
const colors = helpers.isArray(optColor) ? optColor : [optColor];
|
|
384
|
+
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
385
|
+
ctx.textAlign = align;
|
|
386
|
+
ctx.textBaseline = 'middle';
|
|
387
|
+
let lhs = 0;
|
|
388
|
+
contents.forEach(function(l, i) {
|
|
389
|
+
const c = colors[Math.min(i, colors.length - 1)];
|
|
390
|
+
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
391
|
+
const lh = f.lineHeight;
|
|
392
|
+
ctx.font = f.string;
|
|
393
|
+
ctx.fillStyle = c;
|
|
394
|
+
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
395
|
+
lhs += lh;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function drawDivider(ctx, rect, options, item) {
|
|
400
|
+
const dividers = options.dividers;
|
|
401
|
+
if (!dividers.display || !item._data.children.length) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const {x, y, w, h} = rect;
|
|
405
|
+
const {lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth} = dividers;
|
|
406
|
+
ctx.save();
|
|
407
|
+
ctx.strokeStyle = lineColor;
|
|
408
|
+
ctx.lineCap = lineCapStyle;
|
|
409
|
+
ctx.setLineDash(lineDash);
|
|
410
|
+
ctx.lineDashOffset = lineDashOffset;
|
|
411
|
+
ctx.lineWidth = lineWidth;
|
|
412
|
+
ctx.beginPath();
|
|
413
|
+
if (w > h) {
|
|
414
|
+
const w2 = w / 2;
|
|
415
|
+
ctx.moveTo(x + w2, y);
|
|
416
|
+
ctx.lineTo(x + w2, y + h);
|
|
417
|
+
} else {
|
|
418
|
+
const h2 = h / 2;
|
|
419
|
+
ctx.moveTo(x, y + h2);
|
|
420
|
+
ctx.lineTo(x + w, y + h2);
|
|
421
|
+
}
|
|
422
|
+
ctx.stroke();
|
|
423
|
+
ctx.restore();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function calculateXYLabel(rect, options, labelSize) {
|
|
427
|
+
const {align, position, padding} = options;
|
|
428
|
+
let x, y;
|
|
429
|
+
x = calculateX(rect, align, padding);
|
|
430
|
+
if (position === 'top') {
|
|
431
|
+
y = rect.y + padding;
|
|
432
|
+
} else if (position === 'bottom') {
|
|
433
|
+
y = rect.y + rect.h - padding - labelSize.height;
|
|
434
|
+
} else {
|
|
435
|
+
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
436
|
+
}
|
|
437
|
+
return {x, y};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function calculateX(rect, align, padding) {
|
|
441
|
+
if (align === 'left') {
|
|
442
|
+
return rect.x + padding;
|
|
443
|
+
} else if (align === 'right') {
|
|
444
|
+
return rect.x + rect.w - padding;
|
|
445
|
+
}
|
|
446
|
+
return rect.x + rect.w / 2;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
class TreemapElement extends chart_js.Element {
|
|
450
|
+
|
|
451
|
+
constructor(cfg) {
|
|
452
|
+
super();
|
|
453
|
+
|
|
454
|
+
this.options = undefined;
|
|
455
|
+
this.width = undefined;
|
|
456
|
+
this.height = undefined;
|
|
457
|
+
|
|
458
|
+
if (cfg) {
|
|
459
|
+
Object.assign(this, cfg);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
draw(ctx, data, levels = 0) {
|
|
464
|
+
if (!data) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const options = this.options;
|
|
468
|
+
const {inner, outer} = boundingRects(this);
|
|
469
|
+
|
|
470
|
+
const addRectPath = hasRadius(outer.radius) ? helpers.addRoundedRectPath : addNormalRectPath;
|
|
471
|
+
|
|
472
|
+
ctx.save();
|
|
473
|
+
|
|
474
|
+
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
475
|
+
ctx.beginPath();
|
|
476
|
+
addRectPath(ctx, outer);
|
|
477
|
+
ctx.clip();
|
|
478
|
+
addRectPath(ctx, inner);
|
|
479
|
+
ctx.fillStyle = options.borderColor;
|
|
480
|
+
ctx.fill('evenodd');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
ctx.beginPath();
|
|
484
|
+
addRectPath(ctx, inner);
|
|
485
|
+
ctx.fillStyle = options.backgroundColor;
|
|
486
|
+
ctx.fill();
|
|
487
|
+
|
|
488
|
+
drawDivider(ctx, inner, options, data);
|
|
489
|
+
drawText(ctx, inner, options, data, levels);
|
|
490
|
+
ctx.restore();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
inRange(mouseX, mouseY, useFinalPosition) {
|
|
494
|
+
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
inXRange(mouseX, useFinalPosition) {
|
|
498
|
+
return inRange(this, mouseX, null, useFinalPosition);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
inYRange(mouseY, useFinalPosition) {
|
|
502
|
+
return inRange(this, null, mouseY, useFinalPosition);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
getCenterPoint(useFinalPosition) {
|
|
506
|
+
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
507
|
+
return {
|
|
508
|
+
x: x + width / 2,
|
|
509
|
+
y: y + height / 2
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
tooltipPosition() {
|
|
514
|
+
return this.getCenterPoint();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
getRange(axis) {
|
|
518
|
+
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
TreemapElement.id = 'treemap';
|
|
523
|
+
|
|
524
|
+
TreemapElement.defaults = {
|
|
525
|
+
label: undefined,
|
|
526
|
+
borderRadius: 0,
|
|
527
|
+
borderWidth: 0,
|
|
528
|
+
captions: {
|
|
529
|
+
align: undefined,
|
|
530
|
+
color: 'black',
|
|
531
|
+
display: true,
|
|
532
|
+
font: {},
|
|
533
|
+
formatter: (ctx) => ctx.raw.g || ctx.raw._data.label || '',
|
|
534
|
+
padding: 3
|
|
535
|
+
},
|
|
536
|
+
dividers: {
|
|
537
|
+
display: false,
|
|
538
|
+
lineCapStyle: 'butt',
|
|
539
|
+
lineColor: 'black',
|
|
540
|
+
lineDash: [],
|
|
541
|
+
lineDashOffset: 0,
|
|
542
|
+
lineWidth: 1,
|
|
543
|
+
},
|
|
544
|
+
labels: {
|
|
545
|
+
align: 'center',
|
|
546
|
+
color: 'black',
|
|
547
|
+
display: false,
|
|
548
|
+
font: {},
|
|
549
|
+
formatter(ctx) {
|
|
550
|
+
if (ctx.raw.g) {
|
|
551
|
+
return [ctx.raw.g, ctx.raw.v + ''];
|
|
552
|
+
}
|
|
553
|
+
return ctx.raw._data.label ? [ctx.raw._data.label, ctx.raw.v + ''] : ctx.raw.v + '';
|
|
554
|
+
},
|
|
555
|
+
overflow: 'cut',
|
|
556
|
+
position: 'middle',
|
|
557
|
+
padding: 3
|
|
558
|
+
},
|
|
559
|
+
rtl: false,
|
|
560
|
+
spacing: 0.5
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
TreemapElement.descriptors = {
|
|
564
|
+
labels: {
|
|
565
|
+
_fallback: true
|
|
566
|
+
},
|
|
567
|
+
captions: {
|
|
568
|
+
_fallback: true
|
|
569
|
+
},
|
|
570
|
+
_scriptable: true,
|
|
571
|
+
_indexable: false
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
TreemapElement.defaultRoutes = {
|
|
575
|
+
backgroundColor: 'backgroundColor',
|
|
576
|
+
borderColor: 'borderColor'
|
|
577
|
+
};
|
|
578
|
+
|
|
121
579
|
function round(v, n) {
|
|
122
580
|
// @ts-ignore
|
|
123
581
|
return (+(Math.round(v + 'e+' + n) + 'e-' + n)) || 0;
|
|
@@ -356,7 +814,7 @@ function squarify(values, rectangle, key, grp, lvl, gsum) {
|
|
|
356
814
|
return flatten(rows);
|
|
357
815
|
}
|
|
358
816
|
|
|
359
|
-
var version = "2.0
|
|
817
|
+
var version = "2.1.0";
|
|
360
818
|
|
|
361
819
|
function rectNotEqual(r1, r2) {
|
|
362
820
|
return !r1 || !r2
|
|
@@ -381,94 +839,38 @@ function arrayNotEqual(a1, a2) {
|
|
|
381
839
|
return false;
|
|
382
840
|
}
|
|
383
841
|
|
|
384
|
-
function
|
|
385
|
-
if (!font) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
const w = rect.width || rect.w;
|
|
389
|
-
const h = rect.height || rect.h;
|
|
390
|
-
const min = font.lineHeight * 2;
|
|
391
|
-
return w > min && h > min;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function drawCaption(ctx, rect, item, opts, levels) {
|
|
395
|
-
ctx.save();
|
|
396
|
-
ctx.beginPath();
|
|
397
|
-
ctx.rect(rect.x, rect.y, rect.width, rect.height);
|
|
398
|
-
ctx.clip();
|
|
399
|
-
if (!('l' in item) || item.l === levels) {
|
|
400
|
-
drawLabels(ctx, item, rect);
|
|
401
|
-
} else if (opts.captions && opts.captions.display) {
|
|
402
|
-
drawCaptionLabel(ctx, item, rect);
|
|
403
|
-
}
|
|
404
|
-
ctx.restore();
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function drawCaptionLabel(ctx, item, rect) {
|
|
408
|
-
const opts = rect.options;
|
|
409
|
-
const captionsOpts = opts.captions || {};
|
|
410
|
-
const borderWidth = opts.borderWidth || 0;
|
|
411
|
-
const spacing = helpers.valueOrDefault(opts.spacing, 0) + borderWidth;
|
|
412
|
-
const color = (rect.active ? captionsOpts.hoverColor : captionsOpts.color) || captionsOpts.color;
|
|
413
|
-
const padding = captionsOpts.padding;
|
|
414
|
-
const align = captionsOpts.align || (opts.rtl ? 'right' : 'left');
|
|
415
|
-
const optFont = (rect.active ? captionsOpts.hoverFont : captionsOpts.font) || captionsOpts.font;
|
|
416
|
-
const font = helpers.toFont(optFont);
|
|
417
|
-
const x = calculateX(rect, align, padding, borderWidth);
|
|
418
|
-
ctx.fillStyle = color;
|
|
419
|
-
ctx.font = font.string;
|
|
420
|
-
ctx.textAlign = align;
|
|
421
|
-
ctx.textBaseline = 'middle';
|
|
422
|
-
ctx.fillText(captionsOpts.formatter || item.g, x, rect.y + padding + spacing + (font.lineHeight / 2));
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function drawDivider(ctx, rect) {
|
|
426
|
-
const opts = rect.options;
|
|
427
|
-
const dividersOpts = opts.dividers || {};
|
|
428
|
-
const w = rect.width || rect.w;
|
|
429
|
-
const h = rect.height || rect.h;
|
|
430
|
-
|
|
431
|
-
ctx.save();
|
|
432
|
-
ctx.strokeStyle = dividersOpts.lineColor || 'black';
|
|
433
|
-
ctx.lineCap = dividersOpts.lineCapStyle;
|
|
434
|
-
ctx.setLineDash(dividersOpts.lineDash || []);
|
|
435
|
-
ctx.lineDashOffset = dividersOpts.lineDashOffset;
|
|
436
|
-
ctx.lineWidth = dividersOpts.lineWidth;
|
|
437
|
-
ctx.beginPath();
|
|
438
|
-
if (w > h) {
|
|
439
|
-
const w2 = w / 2;
|
|
440
|
-
ctx.moveTo(rect.x + w2, rect.y);
|
|
441
|
-
ctx.lineTo(rect.x + w2, rect.y + h);
|
|
442
|
-
} else {
|
|
443
|
-
const h2 = h / 2;
|
|
444
|
-
ctx.moveTo(rect.x, rect.y + h2);
|
|
445
|
-
ctx.lineTo(rect.x + w, rect.y + h2);
|
|
446
|
-
}
|
|
447
|
-
ctx.stroke();
|
|
448
|
-
ctx.restore();
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function buildData(dataset, mainRect, captions) {
|
|
842
|
+
function buildData(dataset, mainRect) {
|
|
452
843
|
const key = dataset.key || '';
|
|
844
|
+
const treeLeafKey = dataset.treeLeafKey || '_leaf';
|
|
453
845
|
let tree = dataset.tree || [];
|
|
846
|
+
if (helpers.isObject(tree)) {
|
|
847
|
+
tree = normalizeTreeToArray(key, treeLeafKey, tree);
|
|
848
|
+
}
|
|
454
849
|
const groups = dataset.groups || [];
|
|
455
850
|
const glen = groups.length;
|
|
456
|
-
const sp = helpers.valueOrDefault(dataset.spacing, 0)
|
|
457
|
-
const
|
|
458
|
-
const font = helpers.toFont(
|
|
851
|
+
const sp = helpers.valueOrDefault(dataset.spacing, 0);
|
|
852
|
+
const captions = dataset.captions || {display: true};
|
|
853
|
+
const font = helpers.toFont(captions.font);
|
|
459
854
|
const padding = helpers.valueOrDefault(captions.padding, 3);
|
|
460
855
|
|
|
461
856
|
function recur(gidx, rect, parent, gs) {
|
|
462
|
-
const g = groups[gidx];
|
|
463
|
-
const pg = (gidx > 0) && groups[gidx - 1];
|
|
464
|
-
const gdata = group(tree, g, key, pg, parent);
|
|
857
|
+
const g = getGroupKey(groups[gidx]);
|
|
858
|
+
const pg = (gidx > 0) && getGroupKey(groups[gidx - 1]);
|
|
859
|
+
const gdata = group(tree, g, key, treeLeafKey, pg, parent, groups.filter((item, index) => index <= gidx));
|
|
465
860
|
const gsq = squarify(gdata, rect, key, g, gidx, gs);
|
|
466
861
|
const ret = gsq.slice();
|
|
467
862
|
let subRect;
|
|
468
863
|
if (gidx < glen - 1) {
|
|
469
864
|
gsq.forEach((sq) => {
|
|
470
|
-
|
|
471
|
-
|
|
865
|
+
const bw = parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
866
|
+
subRect = {
|
|
867
|
+
x: sq.x + sp + bw.l,
|
|
868
|
+
y: sq.y + sp + bw.t,
|
|
869
|
+
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
870
|
+
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
871
|
+
rtl: rect.rtl
|
|
872
|
+
};
|
|
873
|
+
if (shouldDrawCaption(subRect, captions)) {
|
|
472
874
|
subRect.y += font.lineHeight + padding * 2;
|
|
473
875
|
subRect.h -= font.lineHeight + padding * 2;
|
|
474
876
|
}
|
|
@@ -487,51 +889,19 @@ function buildData(dataset, mainRect, captions) {
|
|
|
487
889
|
: squarify(tree, mainRect, key);
|
|
488
890
|
}
|
|
489
891
|
|
|
490
|
-
function
|
|
491
|
-
const
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
const optColor = (rect.active ? labelsOpts.hoverColor : labelsOpts.color) || labelsOpts.color;
|
|
497
|
-
const optFont = (rect.active ? labelsOpts.hoverFont : labelsOpts.font) || labelsOpts.font;
|
|
498
|
-
const font = helpers.toFont(optFont);
|
|
499
|
-
const lh = font.lineHeight;
|
|
500
|
-
const label = labelsOpts.formatter;
|
|
501
|
-
if (label) {
|
|
502
|
-
const labels = helpers.isArray(label) ? label : [label];
|
|
503
|
-
const xyPoint = calculateXYLabel(opts, rect, labels, lh);
|
|
504
|
-
ctx.font = font.string;
|
|
505
|
-
ctx.textAlign = labelsOpts.align;
|
|
506
|
-
ctx.textBaseline = labelsOpts.position;
|
|
507
|
-
ctx.fillStyle = optColor;
|
|
508
|
-
labels.forEach((l, i) => ctx.fillText(l, xyPoint.x, xyPoint.y + i * lh));
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function calculateXYLabel(options, rect, labels, lineHeight) {
|
|
513
|
-
const labelsOpts = options.labels;
|
|
514
|
-
const borderWidth = options.borderWidth || 0;
|
|
515
|
-
const {align, position, padding} = labelsOpts;
|
|
516
|
-
let x, y;
|
|
517
|
-
x = calculateX(rect, align, padding, borderWidth);
|
|
518
|
-
if (position === 'top') {
|
|
519
|
-
y = rect.y + padding + borderWidth;
|
|
520
|
-
} else if (position === 'bottom') {
|
|
521
|
-
y = rect.y + rect.height - padding - borderWidth - (labels.length - 1) * lineHeight;
|
|
522
|
-
} else {
|
|
523
|
-
y = rect.y + rect.height / 2 - labels.length * lineHeight / 4;
|
|
524
|
-
}
|
|
525
|
-
return {x, y};
|
|
892
|
+
function getMinMax(data, useTree) {
|
|
893
|
+
const vMax = useTree ? 1 : maxValue(data);
|
|
894
|
+
const vMin = useTree ? 0 : minValue(data, vMax);
|
|
895
|
+
return {vMin, vMax};
|
|
526
896
|
}
|
|
527
897
|
|
|
528
|
-
function
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return
|
|
898
|
+
function getArea({xScale, yScale}, data, rtl, useTree) {
|
|
899
|
+
const {vMin, vMax} = getMinMax(data, useTree);
|
|
900
|
+
const xMin = xScale.getPixelForValue(0);
|
|
901
|
+
const xMax = xScale.getPixelForValue(1);
|
|
902
|
+
const yMin = yScale.getPixelForValue(vMin);
|
|
903
|
+
const yMax = yScale.getPixelForValue(vMax);
|
|
904
|
+
return {x: xMin, y: yMax, w: xMax - xMin, h: yMin - yMax, rtl};
|
|
535
905
|
}
|
|
536
906
|
|
|
537
907
|
class TreemapController extends chart_js.DatasetController {
|
|
@@ -541,6 +911,7 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
541
911
|
this._rect = undefined;
|
|
542
912
|
this._key = undefined;
|
|
543
913
|
this._groups = undefined;
|
|
914
|
+
this._useTree = undefined;
|
|
544
915
|
}
|
|
545
916
|
|
|
546
917
|
initialize() {
|
|
@@ -548,24 +919,46 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
548
919
|
super.initialize();
|
|
549
920
|
}
|
|
550
921
|
|
|
922
|
+
/**
|
|
923
|
+
* TODO: to be removed when https://github.com/kurkle/chartjs-chart-treemap/issues/137
|
|
924
|
+
* will be implemented
|
|
925
|
+
*/
|
|
926
|
+
updateRangeFromParsed(range, scale) {
|
|
927
|
+
if (range.updated) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
range.updated = true;
|
|
931
|
+
if (scale.axis === 'x') {
|
|
932
|
+
range.min = 0;
|
|
933
|
+
range.max = 1;
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const me = this;
|
|
937
|
+
const dataset = me.getDataset();
|
|
938
|
+
const {vMin, vMax} = getMinMax(dataset.data, me._useTree);
|
|
939
|
+
range.min = vMin;
|
|
940
|
+
range.max = vMax;
|
|
941
|
+
}
|
|
942
|
+
|
|
551
943
|
update(mode) {
|
|
552
944
|
const me = this;
|
|
553
945
|
const meta = me.getMeta();
|
|
554
946
|
const dataset = me.getDataset();
|
|
947
|
+
if (!helpers.defined(me._useTree)) {
|
|
948
|
+
me._useTree = !!dataset.tree;
|
|
949
|
+
}
|
|
555
950
|
const groups = dataset.groups || (dataset.groups = []);
|
|
556
|
-
const captions = dataset.captions ? dataset.captions : {};
|
|
557
|
-
const area = me.chart.chartArea;
|
|
558
951
|
const key = dataset.key || '';
|
|
559
952
|
const rtl = !!dataset.rtl;
|
|
560
953
|
|
|
561
|
-
const mainRect =
|
|
954
|
+
const mainRect = getArea(meta, dataset.data, rtl, me._useTree);
|
|
562
955
|
|
|
563
956
|
if (mode === 'reset' || rectNotEqual(me._rect, mainRect) || me._key !== key || arrayNotEqual(me._groups, groups)) {
|
|
564
957
|
me._rect = mainRect;
|
|
565
958
|
me._groups = groups.slice();
|
|
566
959
|
me._key = key;
|
|
567
960
|
|
|
568
|
-
dataset.data = buildData(dataset, mainRect
|
|
961
|
+
dataset.data = buildData(dataset, mainRect);
|
|
569
962
|
// @ts-ignore using private stuff
|
|
570
963
|
me._dataCheck();
|
|
571
964
|
// @ts-ignore using private stuff
|
|
@@ -575,13 +968,6 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
575
968
|
me.updateElements(meta.data, 0, meta.data.length, mode);
|
|
576
969
|
}
|
|
577
970
|
|
|
578
|
-
resolveDataElementOptions(index, mode) {
|
|
579
|
-
const options = super.resolveDataElementOptions(index, mode);
|
|
580
|
-
const result = Object.isFrozen(options) ? Object.assign({}, options) : options;
|
|
581
|
-
result.font = helpers.toFont(options.captions.font);
|
|
582
|
-
return result;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
971
|
updateElements(rects, start, count, mode) {
|
|
586
972
|
const me = this;
|
|
587
973
|
const reset = mode === 'reset';
|
|
@@ -593,15 +979,14 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
593
979
|
for (let i = start; i < start + count; i++) {
|
|
594
980
|
const sq = dataset.data[i];
|
|
595
981
|
const options = sharedOptions || me.resolveDataElementOptions(i, mode);
|
|
596
|
-
const
|
|
597
|
-
const
|
|
598
|
-
const x = sq.x + options.spacing;
|
|
599
|
-
const y = sq.y + options.spacing;
|
|
982
|
+
const sp = options.spacing;
|
|
983
|
+
const sp2 = sp * 2;
|
|
600
984
|
const properties = {
|
|
601
|
-
x,
|
|
602
|
-
y,
|
|
603
|
-
width,
|
|
604
|
-
height
|
|
985
|
+
x: sq.x + sp,
|
|
986
|
+
y: sq.y + sp,
|
|
987
|
+
width: reset ? 0 : sq.w - sp2,
|
|
988
|
+
height: reset ? 0 : sq.h - sp2,
|
|
989
|
+
hidden: sp2 > sq.w || sp2 > sq.h,
|
|
605
990
|
};
|
|
606
991
|
|
|
607
992
|
if (includeOptions) {
|
|
@@ -613,41 +998,23 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
613
998
|
me.updateSharedOptions(sharedOptions, mode, firstOpts);
|
|
614
999
|
}
|
|
615
1000
|
|
|
616
|
-
_drawDividers(ctx, data, metadata) {
|
|
617
|
-
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
618
|
-
const rect = metadata[i];
|
|
619
|
-
const item = data[i];
|
|
620
|
-
const dividersOpts = rect.options.dividers || {};
|
|
621
|
-
if (dividersOpts.display && item._data.children.length > 1) {
|
|
622
|
-
drawDivider(ctx, rect);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
_drawRects(ctx, data, metadata, levels) {
|
|
628
|
-
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
629
|
-
const rect = metadata[i];
|
|
630
|
-
const item = data[i];
|
|
631
|
-
if (!rect.hidden) {
|
|
632
|
-
rect.draw(ctx);
|
|
633
|
-
const opts = rect.options;
|
|
634
|
-
if (shouldDrawCaption(rect, opts.captions.font)) {
|
|
635
|
-
drawCaption(ctx, rect, item, opts, levels);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
1001
|
draw() {
|
|
642
1002
|
const me = this;
|
|
643
1003
|
const ctx = me.chart.ctx;
|
|
1004
|
+
const area = me.chart.chartArea;
|
|
644
1005
|
const metadata = me.getMeta().data || [];
|
|
645
1006
|
const dataset = me.getDataset();
|
|
646
1007
|
const levels = (dataset.groups || []).length - 1;
|
|
647
|
-
const data = dataset.data
|
|
1008
|
+
const data = dataset.data;
|
|
648
1009
|
|
|
649
|
-
|
|
650
|
-
|
|
1010
|
+
helpers.clipArea(ctx, area);
|
|
1011
|
+
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
1012
|
+
const rect = metadata[i];
|
|
1013
|
+
if (!rect.hidden) {
|
|
1014
|
+
rect.draw(ctx, data[i], levels);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
helpers.unclipArea(ctx);
|
|
651
1018
|
}
|
|
652
1019
|
}
|
|
653
1020
|
|
|
@@ -665,13 +1032,6 @@ TreemapController.defaults = {
|
|
|
665
1032
|
},
|
|
666
1033
|
},
|
|
667
1034
|
|
|
668
|
-
borderWidth: 0,
|
|
669
|
-
spacing: 0.5,
|
|
670
|
-
dividers: {
|
|
671
|
-
display: false,
|
|
672
|
-
lineWidth: 1,
|
|
673
|
-
}
|
|
674
|
-
|
|
675
1035
|
};
|
|
676
1036
|
|
|
677
1037
|
TreemapController.descriptors = {
|
|
@@ -702,7 +1062,7 @@ TreemapController.overrides = {
|
|
|
702
1062
|
label(item) {
|
|
703
1063
|
const dataset = item.dataset;
|
|
704
1064
|
const dataItem = dataset.data[item.dataIndex];
|
|
705
|
-
const label = dataItem.g || dataset.label;
|
|
1065
|
+
const label = dataItem.g || dataItem._data.label || dataset.label;
|
|
706
1066
|
return (label ? label + ': ' : '') + dataItem.v;
|
|
707
1067
|
}
|
|
708
1068
|
}
|
|
@@ -737,6 +1097,8 @@ TreemapController.afterRegister = function() {
|
|
|
737
1097
|
|
|
738
1098
|
return el.tooltipPosition();
|
|
739
1099
|
};
|
|
1100
|
+
} else {
|
|
1101
|
+
console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
|
|
740
1102
|
}
|
|
741
1103
|
};
|
|
742
1104
|
|
|
@@ -747,189 +1109,15 @@ TreemapController.afterUnregister = function() {
|
|
|
747
1109
|
}
|
|
748
1110
|
};
|
|
749
1111
|
|
|
750
|
-
/**
|
|
751
|
-
* Helper function to get the bounds of the rect
|
|
752
|
-
* @param {TreemapElement} rect the rect
|
|
753
|
-
* @param {boolean} [useFinalPosition]
|
|
754
|
-
* @return {object} bounds of the rect
|
|
755
|
-
* @private
|
|
756
|
-
*/
|
|
757
|
-
function getBounds(rect, useFinalPosition) {
|
|
758
|
-
const {x, y, width, height} = rect.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
759
|
-
return {left: x, top: y, right: x + width, bottom: y + height};
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
function limit(value, min, max) {
|
|
763
|
-
return Math.max(Math.min(value, max), min);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
function parseBorderWidth(value, maxW, maxH) {
|
|
767
|
-
let t, r, b, l;
|
|
768
|
-
|
|
769
|
-
if (isObject(value)) {
|
|
770
|
-
t = +value.top || 0;
|
|
771
|
-
r = +value.right || 0;
|
|
772
|
-
b = +value.bottom || 0;
|
|
773
|
-
l = +value.left || 0;
|
|
774
|
-
} else {
|
|
775
|
-
t = r = b = l = +value || 0;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
return {
|
|
779
|
-
t: limit(t, 0, maxH),
|
|
780
|
-
r: limit(r, 0, maxW),
|
|
781
|
-
b: limit(b, 0, maxH),
|
|
782
|
-
l: limit(l, 0, maxW)
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
function boundingRects(rect) {
|
|
787
|
-
const bounds = getBounds(rect);
|
|
788
|
-
const width = bounds.right - bounds.left;
|
|
789
|
-
const height = bounds.bottom - bounds.top;
|
|
790
|
-
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
791
|
-
|
|
792
|
-
return {
|
|
793
|
-
outer: {
|
|
794
|
-
x: bounds.left,
|
|
795
|
-
y: bounds.top,
|
|
796
|
-
w: width,
|
|
797
|
-
h: height
|
|
798
|
-
},
|
|
799
|
-
inner: {
|
|
800
|
-
x: bounds.left + border.l,
|
|
801
|
-
y: bounds.top + border.t,
|
|
802
|
-
w: width - border.l - border.r,
|
|
803
|
-
h: height - border.t - border.b
|
|
804
|
-
}
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function inRange(rect, x, y, useFinalPosition) {
|
|
809
|
-
const skipX = x === null;
|
|
810
|
-
const skipY = y === null;
|
|
811
|
-
const bounds = !rect || (skipX && skipY) ? false : getBounds(rect, useFinalPosition);
|
|
812
|
-
|
|
813
|
-
return bounds
|
|
814
|
-
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
815
|
-
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
class TreemapElement extends chart_js.Element {
|
|
819
|
-
|
|
820
|
-
constructor(cfg) {
|
|
821
|
-
super();
|
|
822
|
-
|
|
823
|
-
this.options = undefined;
|
|
824
|
-
this.width = undefined;
|
|
825
|
-
this.height = undefined;
|
|
826
|
-
|
|
827
|
-
if (cfg) {
|
|
828
|
-
Object.assign(this, cfg);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
draw(ctx) {
|
|
833
|
-
const options = this.options;
|
|
834
|
-
const {inner, outer} = boundingRects(this);
|
|
835
|
-
|
|
836
|
-
ctx.save();
|
|
837
|
-
|
|
838
|
-
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
839
|
-
ctx.beginPath();
|
|
840
|
-
ctx.rect(outer.x, outer.y, outer.w, outer.h);
|
|
841
|
-
ctx.clip();
|
|
842
|
-
ctx.rect(inner.x, inner.y, inner.w, inner.h);
|
|
843
|
-
ctx.fillStyle = options.backgroundColor;
|
|
844
|
-
ctx.fill();
|
|
845
|
-
ctx.fillStyle = options.borderColor;
|
|
846
|
-
ctx.fill('evenodd');
|
|
847
|
-
} else {
|
|
848
|
-
ctx.fillStyle = options.backgroundColor;
|
|
849
|
-
ctx.fillRect(inner.x, inner.y, inner.w, inner.h);
|
|
850
|
-
}
|
|
851
|
-
ctx.restore();
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
inRange(mouseX, mouseY, useFinalPosition) {
|
|
855
|
-
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
inXRange(mouseX, useFinalPosition) {
|
|
859
|
-
return inRange(this, mouseX, null, useFinalPosition);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
inYRange(mouseY, useFinalPosition) {
|
|
863
|
-
return inRange(this, null, mouseY, useFinalPosition);
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
getCenterPoint(useFinalPosition) {
|
|
867
|
-
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
868
|
-
return {
|
|
869
|
-
x: x + width / 2,
|
|
870
|
-
y: y + height / 2
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
tooltipPosition() {
|
|
875
|
-
return this.getCenterPoint();
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
getRange(axis) {
|
|
879
|
-
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
TreemapElement.id = 'treemap';
|
|
884
|
-
|
|
885
|
-
TreemapElement.defaults = {
|
|
886
|
-
borderWidth: undefined,
|
|
887
|
-
spacing: undefined,
|
|
888
|
-
label: undefined,
|
|
889
|
-
rtl: undefined,
|
|
890
|
-
dividers: {
|
|
891
|
-
display: false,
|
|
892
|
-
lineCapStyle: 'butt',
|
|
893
|
-
lineColor: 'black',
|
|
894
|
-
lineDash: undefined,
|
|
895
|
-
lineDashOffset: 0,
|
|
896
|
-
lineWidth: 0,
|
|
897
|
-
},
|
|
898
|
-
captions: {
|
|
899
|
-
align: undefined,
|
|
900
|
-
color: undefined,
|
|
901
|
-
display: true,
|
|
902
|
-
formatter: (ctx) => ctx.raw.g || '',
|
|
903
|
-
font: {},
|
|
904
|
-
padding: 3
|
|
905
|
-
},
|
|
906
|
-
labels: {
|
|
907
|
-
align: 'center',
|
|
908
|
-
color: undefined,
|
|
909
|
-
display: false,
|
|
910
|
-
formatter: (ctx) => ctx.raw.g ? [ctx.raw.g, ctx.raw.v] : ctx.raw.v,
|
|
911
|
-
font: {},
|
|
912
|
-
position: 'middle',
|
|
913
|
-
padding: 3
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
TreemapElement.descriptors = {
|
|
918
|
-
_scriptable: true,
|
|
919
|
-
_indexable: false
|
|
920
|
-
};
|
|
921
|
-
|
|
922
|
-
TreemapElement.defaultRoutes = {
|
|
923
|
-
backgroundColor: 'backgroundColor',
|
|
924
|
-
borderColor: 'borderColor'
|
|
925
|
-
};
|
|
926
|
-
|
|
927
1112
|
chart_js.Chart.register(TreemapController, TreemapElement);
|
|
928
1113
|
|
|
929
1114
|
exports.flatten = flatten;
|
|
1115
|
+
exports.getGroupKey = getGroupKey;
|
|
930
1116
|
exports.group = group;
|
|
931
1117
|
exports.index = index;
|
|
932
|
-
exports.
|
|
1118
|
+
exports.maxValue = maxValue;
|
|
1119
|
+
exports.minValue = minValue;
|
|
1120
|
+
exports.normalizeTreeToArray = normalizeTreeToArray;
|
|
933
1121
|
exports.requireVersion = requireVersion;
|
|
934
1122
|
exports.sort = sort;
|
|
935
1123
|
exports.sum = sum;
|