chartjs-chart-treemap 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chartjs-chart-treemap.cjs +1039 -1058
- package/dist/chartjs-chart-treemap.esm.js +1038 -1059
- package/dist/chartjs-chart-treemap.min.js +3 -3
- package/dist/chartjs-chart-treemap.min.js.map +1 -1
- package/dist/controller.d.ts +19 -0
- package/dist/element.d.ts +27 -0
- package/dist/helpers/index.d.ts +9 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.esm.d.ts +3 -0
- package/dist/rect.d.ts +18 -0
- package/dist/squarify.d.ts +1 -0
- package/dist/statArray.d.ts +18 -0
- package/dist/types.d.ts +113 -0
- package/dist/utils.d.ts +8 -0
- package/package.json +85 -74
- package/types/index.esm.d.ts +0 -159
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-treemap
|
|
2
|
+
* chartjs-chart-treemap v0.0.0-development
|
|
3
3
|
* https://chartjs-chart-treemap.pages.dev/
|
|
4
|
-
* (c)
|
|
4
|
+
* (c) 2026 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
6
6
|
*/
|
|
7
7
|
(function (global, factory) {
|
|
@@ -10,1173 +10,1154 @@ 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
|
-
|
|
14
|
-
|
|
15
|
-
const getGroupKey = (lvl) => '' + lvl;
|
|
16
|
-
|
|
17
|
-
function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
|
|
18
|
-
const objIndex = lvl - 1;
|
|
19
|
-
if (keys[0] in obj && lvl > 0) {
|
|
20
|
-
const record = tree.reduce(function(reduced, item, i) {
|
|
21
|
-
if (i !== objIndex) {
|
|
22
|
-
reduced[getGroupKey(i)] = item;
|
|
23
|
-
}
|
|
24
|
-
return reduced;
|
|
25
|
-
}, {});
|
|
26
|
-
record[treeLeafKey] = tree[objIndex];
|
|
27
|
-
keys.forEach(function(k) {
|
|
28
|
-
record[k] = obj[k];
|
|
29
|
-
});
|
|
30
|
-
result.push(record);
|
|
31
|
-
} else {
|
|
32
|
-
for (const childKey of Object.keys(obj)) {
|
|
33
|
-
const child = obj[childKey];
|
|
34
|
-
if (helpers.isObject(child)) {
|
|
35
|
-
tree.push(childKey);
|
|
36
|
-
scanTreeObject(keys, treeLeafKey, child, tree, lvl + 1, result);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
tree.splice(objIndex, 1);
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function normalizeTreeToArray(keys, treeLeafKey, obj) {
|
|
45
|
-
const data = scanTreeObject(keys, treeLeafKey, obj);
|
|
46
|
-
if (!data.length) {
|
|
47
|
-
return data;
|
|
48
|
-
}
|
|
49
|
-
const max = data.reduce(function(maxVal, element) {
|
|
50
|
-
// minus 2 because _leaf and value properties are added
|
|
51
|
-
// on top to groups ones
|
|
52
|
-
const ikeys = Object.keys(element).length - 2;
|
|
53
|
-
return maxVal > ikeys ? maxVal : ikeys;
|
|
54
|
-
});
|
|
55
|
-
data.forEach(function(element) {
|
|
56
|
-
for (let i = 0; i < max; i++) {
|
|
57
|
-
const groupKey = getGroupKey(i);
|
|
58
|
-
if (!element[groupKey]) {
|
|
59
|
-
element[groupKey] = '';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
return data;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
|
|
67
|
-
function flatten(input) {
|
|
68
|
-
const stack = [...input];
|
|
69
|
-
const res = [];
|
|
70
|
-
while (stack.length) {
|
|
71
|
-
// pop value from stack
|
|
72
|
-
const next = stack.pop();
|
|
73
|
-
if (Array.isArray(next)) {
|
|
74
|
-
// push back array items, won't modify the original input
|
|
75
|
-
stack.push(...next);
|
|
76
|
-
} else {
|
|
77
|
-
res.push(next);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// reverse to restore input order
|
|
81
|
-
return res.reverse();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function getPath(groups, value, defaultValue) {
|
|
85
|
-
if (!groups.length) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const path = [];
|
|
89
|
-
for (const grp of groups) {
|
|
90
|
-
const item = value[grp];
|
|
91
|
-
if (item === '') {
|
|
92
|
-
path.push(defaultValue);
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
path.push(item);
|
|
96
|
-
}
|
|
97
|
-
return path.length ? path.join('.') : defaultValue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* @param {[]} values
|
|
102
|
-
* @param {string} grp
|
|
103
|
-
* @param {[string]} keys
|
|
104
|
-
* @param {string} treeeLeafKey
|
|
105
|
-
* @param {string} [mainGrp]
|
|
106
|
-
* @param {*} [mainValue]
|
|
107
|
-
* @param {[]} groups
|
|
108
|
-
*/
|
|
109
|
-
function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = []) {
|
|
110
|
-
const key = keys[0];
|
|
111
|
-
const addKeys = keys.slice(1);
|
|
112
|
-
const tmp = Object.create(null);
|
|
113
|
-
const data = Object.create(null);
|
|
114
|
-
const ret = [];
|
|
115
|
-
let g, i, n;
|
|
116
|
-
for (i = 0, n = values.length; i < n; ++i) {
|
|
117
|
-
const v = values[i];
|
|
118
|
-
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
g = v[grp] || v[treeLeafKey] || '';
|
|
122
|
-
if (!(g in tmp)) {
|
|
123
|
-
const tmpRef = tmp[g] = {value: 0};
|
|
124
|
-
addKeys.forEach(function(k) {
|
|
125
|
-
tmpRef[k] = 0;
|
|
126
|
-
});
|
|
127
|
-
data[g] = [];
|
|
128
|
-
}
|
|
129
|
-
tmp[g].value += +v[key];
|
|
130
|
-
tmp[g].label = v[grp] || '';
|
|
131
|
-
const tmpRef = tmp[g];
|
|
132
|
-
addKeys.forEach(function(k) {
|
|
133
|
-
tmpRef[k] += v[k];
|
|
134
|
-
});
|
|
135
|
-
tmp[g].path = getPath(groups, v, g);
|
|
136
|
-
data[g].push(v);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
Object.keys(tmp).forEach((k) => {
|
|
140
|
-
const v = {children: data[k]};
|
|
141
|
-
v[key] = +tmp[k].value;
|
|
142
|
-
addKeys.forEach(function(ak) {
|
|
143
|
-
v[ak] = +tmp[k][ak];
|
|
144
|
-
});
|
|
145
|
-
v[grp] = tmp[k].label;
|
|
146
|
-
v.label = k;
|
|
147
|
-
v.path = tmp[k].path;
|
|
148
|
-
|
|
149
|
-
if (mainGrp) {
|
|
150
|
-
v[mainGrp] = mainValue;
|
|
151
|
-
}
|
|
152
|
-
ret.push(v);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
return ret;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function index(values, key) {
|
|
159
|
-
let n = values.length;
|
|
160
|
-
let i;
|
|
161
|
-
|
|
162
|
-
if (!n) {
|
|
163
|
-
return key;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const obj = helpers.isObject(values[0]);
|
|
167
|
-
key = obj ? key : 'v';
|
|
168
|
-
|
|
169
|
-
for (i = 0, n = values.length; i < n; ++i) {
|
|
170
|
-
if (obj) {
|
|
171
|
-
values[i]._idx = i;
|
|
172
|
-
} else {
|
|
173
|
-
values[i] = {v: values[i], _idx: i};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return key;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function sort(values, key) {
|
|
180
|
-
if (key) {
|
|
181
|
-
values.sort((a, b) => +b[key] - +a[key]);
|
|
182
|
-
} else {
|
|
183
|
-
values.sort((a, b) => +b - +a);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function sum(values, key) {
|
|
188
|
-
let s, i, n;
|
|
189
|
-
|
|
190
|
-
for (s = 0, i = 0, n = values.length; i < n; ++i) {
|
|
191
|
-
s += key ? +values[i][key] : +values[i];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return s;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* @param {string} pkg
|
|
199
|
-
* @param {string} min
|
|
200
|
-
* @param {string} ver
|
|
201
|
-
* @param {boolean} [strict=true]
|
|
202
|
-
* @returns {boolean}
|
|
203
|
-
*/
|
|
204
|
-
function requireVersion(pkg, min, ver, strict = true) {
|
|
205
|
-
const parts = ver.split('.');
|
|
206
|
-
let i = 0;
|
|
207
|
-
for (const req of min.split('.')) {
|
|
208
|
-
const act = parts[i++];
|
|
209
|
-
if (parseInt(req, 10) < parseInt(act, 10)) {
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
if (isOlderPart(act, req)) {
|
|
213
|
-
if (strict) {
|
|
214
|
-
throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`);
|
|
215
|
-
} else {
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
13
|
+
var version = "0.0.0-development";
|
|
222
14
|
|
|
223
15
|
const widthCache = new Map();
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Helper function to get the bounds of the rect
|
|
227
|
-
* @param {TreemapElement} rect the rect
|
|
228
|
-
* @param {boolean} [useFinalPosition]
|
|
229
|
-
* @return {object} bounds of the rect
|
|
230
|
-
* @private
|
|
231
|
-
*/
|
|
232
16
|
function getBounds(rect, useFinalPosition) {
|
|
233
|
-
|
|
234
|
-
|
|
17
|
+
const { x, y, width, height } = rect.getProps([
|
|
18
|
+
'x',
|
|
19
|
+
'y',
|
|
20
|
+
'width',
|
|
21
|
+
'height'
|
|
22
|
+
], useFinalPosition);
|
|
23
|
+
return {
|
|
24
|
+
bottom: y + height,
|
|
25
|
+
left: x,
|
|
26
|
+
right: x + width,
|
|
27
|
+
top: y
|
|
28
|
+
};
|
|
235
29
|
}
|
|
236
|
-
|
|
237
30
|
function limit(value, min, max) {
|
|
238
|
-
|
|
31
|
+
return Math.max(Math.min(value, max), min);
|
|
239
32
|
}
|
|
240
|
-
|
|
241
33
|
function parseBorderWidth(value, maxW, maxH) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
};
|
|
34
|
+
const o = helpers.toTRBL(value);
|
|
35
|
+
return {
|
|
36
|
+
b: limit(o.bottom, 0, maxH),
|
|
37
|
+
l: limit(o.left, 0, maxW),
|
|
38
|
+
r: limit(o.right, 0, maxW),
|
|
39
|
+
t: limit(o.top, 0, maxH)
|
|
40
|
+
};
|
|
250
41
|
}
|
|
251
|
-
|
|
252
42
|
function parseBorderRadius(value, maxW, maxH) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
};
|
|
43
|
+
const o = helpers.toTRBLCorners(value);
|
|
44
|
+
const maxR = Math.min(maxW, maxH);
|
|
45
|
+
return {
|
|
46
|
+
bottomLeft: limit(o.bottomLeft, 0, maxR),
|
|
47
|
+
bottomRight: limit(o.bottomRight, 0, maxR),
|
|
48
|
+
topLeft: limit(o.topLeft, 0, maxR),
|
|
49
|
+
topRight: limit(o.topRight, 0, maxR)
|
|
50
|
+
};
|
|
262
51
|
}
|
|
263
|
-
|
|
264
52
|
function boundingRects(rect) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
};
|
|
53
|
+
const bounds = getBounds(rect);
|
|
54
|
+
const width = bounds.right - bounds.left;
|
|
55
|
+
const height = bounds.bottom - bounds.top;
|
|
56
|
+
const border = parseBorderWidth(rect.options.borderWidth, width / 2, height / 2);
|
|
57
|
+
const radius = parseBorderRadius(rect.options.borderRadius, width / 2, height / 2);
|
|
58
|
+
const outer = {
|
|
59
|
+
active: rect.active,
|
|
60
|
+
h: height,
|
|
61
|
+
radius,
|
|
62
|
+
w: width,
|
|
63
|
+
x: bounds.left,
|
|
64
|
+
y: bounds.top
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
inner: {
|
|
68
|
+
active: rect.active,
|
|
69
|
+
h: outer.h - border.t - border.b,
|
|
70
|
+
radius: {
|
|
71
|
+
bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
|
|
72
|
+
bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)),
|
|
73
|
+
topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
|
|
74
|
+
topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r))
|
|
75
|
+
},
|
|
76
|
+
w: outer.w - border.l - border.r,
|
|
77
|
+
x: outer.x + border.l,
|
|
78
|
+
y: outer.y + border.t
|
|
79
|
+
},
|
|
80
|
+
outer
|
|
81
|
+
};
|
|
295
82
|
}
|
|
296
|
-
|
|
297
83
|
function inRange(rect, x, y, useFinalPosition) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return bounds
|
|
303
|
-
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
304
|
-
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
84
|
+
const skipX = x === null;
|
|
85
|
+
const skipY = y === null;
|
|
86
|
+
const bounds = !rect || skipX && skipY ? false : getBounds(rect, useFinalPosition);
|
|
87
|
+
return bounds && (skipX || x >= bounds.left && x <= bounds.right) && (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
305
88
|
}
|
|
306
|
-
|
|
307
89
|
function hasRadius(radius) {
|
|
308
|
-
|
|
90
|
+
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
|
309
91
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
* Add a path of a rectangle to the current sub-path
|
|
313
|
-
* @param {CanvasRenderingContext2D} ctx Context
|
|
314
|
-
* @param {*} rect Bounding rect
|
|
315
|
-
*/
|
|
316
|
-
function addNormalRectPath(ctx, rect) {
|
|
317
|
-
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
92
|
+
function addNormalRectPath(ctx, rect) {
|
|
93
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
318
94
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
95
|
+
function shouldDrawCaption(displayMode, rect, options) {
|
|
96
|
+
if (!options || options.display === false) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (displayMode === 'headerBoxes') {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
const { w, h } = rect;
|
|
103
|
+
const font = helpers.toFont(options.font);
|
|
104
|
+
const min = font.lineHeight;
|
|
105
|
+
const padding = limit(helpers.valueOrDefault(options.padding, 3) * 2, 0, Math.min(w, h));
|
|
106
|
+
return w - padding > min && h - padding > min;
|
|
329
107
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
108
|
+
function getCaptionHeight(displayMode, rect, font, padding) {
|
|
109
|
+
if (displayMode !== 'headerBoxes') {
|
|
110
|
+
return font.lineHeight + padding * 2;
|
|
111
|
+
}
|
|
112
|
+
const captionHeight = font.lineHeight + padding * 2;
|
|
113
|
+
return rect.h < 2 * captionHeight ? rect.h / 3 : captionHeight;
|
|
114
|
+
}
|
|
115
|
+
function drawText(ctx, rect, options, item) {
|
|
116
|
+
const { captions, labels, displayMode } = options;
|
|
117
|
+
ctx.save();
|
|
118
|
+
ctx.beginPath();
|
|
119
|
+
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
120
|
+
ctx.clip();
|
|
121
|
+
const isLeaf = item && (!helpers.defined(item.l) || item.isLeaf);
|
|
122
|
+
if (isLeaf && labels.display) {
|
|
123
|
+
drawLabel(ctx, rect, options);
|
|
124
|
+
} else if (!isLeaf && shouldDrawCaption(displayMode, rect, captions)) {
|
|
125
|
+
drawCaption(ctx, rect, options, item);
|
|
126
|
+
}
|
|
127
|
+
ctx.restore();
|
|
344
128
|
}
|
|
345
|
-
|
|
346
129
|
function drawCaption(ctx, rect, options, item) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
130
|
+
const { captions, spacing, rtl, displayMode } = options;
|
|
131
|
+
const { color, hoverColor, font, hoverFont, padding, align, formatter } = captions;
|
|
132
|
+
const oColor = (rect.active ? hoverColor : color) || color;
|
|
133
|
+
const oAlign = align || (rtl ? 'right' : 'left');
|
|
134
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
135
|
+
const oFont = helpers.toFont(optFont);
|
|
136
|
+
const fonts = [
|
|
137
|
+
oFont
|
|
138
|
+
];
|
|
139
|
+
if (oFont.lineHeight > rect.h) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let text = formatter || item.g;
|
|
143
|
+
const captionSize = measureLabelSize(ctx, [
|
|
144
|
+
formatter
|
|
145
|
+
], fonts);
|
|
146
|
+
if (captionSize.width + 2 * padding > rect.w) {
|
|
147
|
+
text = sliceTextToFitWidth(ctx, text, rect.w - 2 * padding, fonts);
|
|
148
|
+
}
|
|
149
|
+
const lh = oFont.lineHeight / 2;
|
|
150
|
+
const x = calculateX(rect, oAlign, padding);
|
|
151
|
+
ctx.fillStyle = oColor;
|
|
152
|
+
ctx.font = oFont.string;
|
|
153
|
+
ctx.textAlign = oAlign;
|
|
154
|
+
ctx.textBaseline = 'middle';
|
|
155
|
+
const y = displayMode === 'headerBoxes' ? rect.y + rect.h / 2 : rect.y + padding + spacing + lh;
|
|
156
|
+
ctx.fillText(text, x, y);
|
|
157
|
+
}
|
|
158
|
+
function sliceTextToFitWidth(ctx, text, width, fonts) {
|
|
159
|
+
const ellipsis = '...';
|
|
160
|
+
const ellipsisWidth = measureLabelSize(ctx, [
|
|
161
|
+
ellipsis
|
|
162
|
+
], fonts).width;
|
|
163
|
+
if (ellipsisWidth >= width) {
|
|
164
|
+
return '';
|
|
165
|
+
}
|
|
166
|
+
let lowerBoundLen = 1;
|
|
167
|
+
let upperBoundLen = text.length;
|
|
168
|
+
let currentWidth;
|
|
169
|
+
while(lowerBoundLen <= upperBoundLen){
|
|
170
|
+
const currentLen = Math.floor((lowerBoundLen + upperBoundLen) / 2);
|
|
171
|
+
const currentText = text.slice(0, currentLen);
|
|
172
|
+
currentWidth = measureLabelSize(ctx, [
|
|
173
|
+
currentText
|
|
174
|
+
], fonts).width;
|
|
175
|
+
if (currentWidth + ellipsisWidth > width) {
|
|
176
|
+
upperBoundLen = currentLen - 1;
|
|
177
|
+
} else {
|
|
178
|
+
lowerBoundLen = currentLen + 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const slicedText = text.slice(0, Math.max(0, lowerBoundLen - 1));
|
|
182
|
+
return slicedText ? slicedText + ellipsis : '';
|
|
360
183
|
}
|
|
361
|
-
|
|
362
184
|
function measureLabelSize(ctx, lines, fonts) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
185
|
+
const fontsKey = fonts.reduce((prev, item)=>{
|
|
186
|
+
prev += item.string;
|
|
187
|
+
return prev;
|
|
188
|
+
}, '');
|
|
189
|
+
const mapKey = lines.join() + fontsKey + (ctx._measureText ? '-spriting' : '');
|
|
190
|
+
if (!widthCache.has(mapKey)) {
|
|
191
|
+
ctx.save();
|
|
192
|
+
const count = lines.length;
|
|
193
|
+
let width = 0;
|
|
194
|
+
let height = 0;
|
|
195
|
+
for(let i = 0; i < count; i++){
|
|
196
|
+
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
197
|
+
ctx.font = font.string;
|
|
198
|
+
const text = lines[i];
|
|
199
|
+
width = Math.max(width, ctx.measureText(text).width);
|
|
200
|
+
height += font.lineHeight;
|
|
201
|
+
}
|
|
202
|
+
ctx.restore();
|
|
203
|
+
widthCache.set(mapKey, {
|
|
204
|
+
height,
|
|
205
|
+
width
|
|
206
|
+
});
|
|
379
207
|
}
|
|
380
|
-
|
|
381
|
-
widthCache.set(mapKey, {width, height});
|
|
382
|
-
}
|
|
383
|
-
return widthCache.get(mapKey);
|
|
208
|
+
return widthCache.get(mapKey);
|
|
384
209
|
}
|
|
385
|
-
|
|
386
210
|
function toFonts(fonts, fitRatio) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
211
|
+
return fonts.map((f)=>{
|
|
212
|
+
f.size = Math.floor(f.size * fitRatio);
|
|
213
|
+
f.lineHeight = undefined;
|
|
214
|
+
return helpers.toFont(f);
|
|
215
|
+
});
|
|
392
216
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return true;
|
|
217
|
+
function labelToDraw(_ctx, rect, options, labelSize) {
|
|
218
|
+
const { overflow, padding } = options;
|
|
219
|
+
const { width, height } = labelSize;
|
|
220
|
+
if (overflow === 'hidden') {
|
|
221
|
+
return !(width + padding * 2 > rect.w || height + padding * 2 > rect.h);
|
|
222
|
+
} else if (overflow === 'fit') {
|
|
223
|
+
const ratio = Math.min(rect.w / (width + padding * 2), rect.h / (height + padding * 2));
|
|
224
|
+
if (ratio < 1) {
|
|
225
|
+
return ratio;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
406
229
|
}
|
|
407
|
-
|
|
408
230
|
function getFontFromOptions(rect, labels) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
231
|
+
const { font, hoverFont } = labels;
|
|
232
|
+
const optFont = (rect.active ? hoverFont : font) || font;
|
|
233
|
+
return Array.isArray(optFont) ? optFont.map((f)=>helpers.toFont(f)) : [
|
|
234
|
+
helpers.toFont(optFont)
|
|
235
|
+
];
|
|
412
236
|
}
|
|
413
|
-
|
|
414
237
|
function drawLabel(ctx, rect, options) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
ctx.
|
|
443
|
-
ctx.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
238
|
+
const labels = options.labels;
|
|
239
|
+
const content = labels.formatter;
|
|
240
|
+
if (!content) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const contents = helpers.isArray(content) ? content : [
|
|
244
|
+
content
|
|
245
|
+
];
|
|
246
|
+
let fonts = getFontFromOptions(rect, labels);
|
|
247
|
+
let labelSize = measureLabelSize(ctx, contents, fonts);
|
|
248
|
+
const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
|
|
249
|
+
if (!lblToDraw) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (helpers.isNumber(lblToDraw)) {
|
|
253
|
+
labelSize = {
|
|
254
|
+
height: labelSize.height * lblToDraw,
|
|
255
|
+
width: labelSize.width * lblToDraw
|
|
256
|
+
};
|
|
257
|
+
fonts = toFonts(fonts, lblToDraw);
|
|
258
|
+
}
|
|
259
|
+
const { color, hoverColor, align } = labels;
|
|
260
|
+
const optColor = (rect.active ? hoverColor : color) || color;
|
|
261
|
+
const colors = helpers.isArray(optColor) ? optColor : [
|
|
262
|
+
optColor
|
|
263
|
+
];
|
|
264
|
+
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
265
|
+
ctx.textAlign = align;
|
|
266
|
+
ctx.textBaseline = 'middle';
|
|
267
|
+
let lhs = 0;
|
|
268
|
+
contents.forEach((l, i)=>{
|
|
269
|
+
const c = colors[Math.min(i, colors.length - 1)];
|
|
270
|
+
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
271
|
+
const lh = f.lineHeight;
|
|
272
|
+
ctx.font = f.string;
|
|
273
|
+
ctx.fillStyle = c;
|
|
274
|
+
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
275
|
+
lhs += lh;
|
|
276
|
+
});
|
|
447
277
|
}
|
|
448
|
-
|
|
449
278
|
function drawDivider(ctx, rect, options, item) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
279
|
+
const dividers = options.dividers;
|
|
280
|
+
if (!dividers.display || !item._data.children.length) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const { x, y, w, h } = rect;
|
|
284
|
+
const { lineColor, lineCapStyle, lineDash, lineDashOffset, lineWidth } = dividers;
|
|
285
|
+
ctx.save();
|
|
286
|
+
ctx.strokeStyle = lineColor;
|
|
287
|
+
ctx.lineCap = lineCapStyle;
|
|
288
|
+
ctx.setLineDash(lineDash);
|
|
289
|
+
ctx.lineDashOffset = lineDashOffset;
|
|
290
|
+
ctx.lineWidth = lineWidth;
|
|
291
|
+
ctx.beginPath();
|
|
292
|
+
if (w > h) {
|
|
293
|
+
const w2 = w / 2;
|
|
294
|
+
ctx.moveTo(x + w2, y);
|
|
295
|
+
ctx.lineTo(x + w2, y + h);
|
|
296
|
+
} else {
|
|
297
|
+
const h2 = h / 2;
|
|
298
|
+
ctx.moveTo(x, y + h2);
|
|
299
|
+
ctx.lineTo(x + w, y + h2);
|
|
300
|
+
}
|
|
301
|
+
ctx.stroke();
|
|
302
|
+
ctx.restore();
|
|
474
303
|
}
|
|
475
|
-
|
|
476
304
|
function calculateXYLabel(rect, options, labelSize) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
305
|
+
const { align, position, padding } = options;
|
|
306
|
+
const x = calculateX(rect, align, padding);
|
|
307
|
+
let y;
|
|
308
|
+
if (position === 'top') {
|
|
309
|
+
y = rect.y + padding;
|
|
310
|
+
} else if (position === 'bottom') {
|
|
311
|
+
y = rect.y + rect.h - padding - labelSize.height;
|
|
312
|
+
} else {
|
|
313
|
+
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
x,
|
|
317
|
+
y
|
|
318
|
+
};
|
|
488
319
|
}
|
|
489
|
-
|
|
490
320
|
function calculateX(rect, align, padding) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
321
|
+
if (align === 'left') {
|
|
322
|
+
return rect.x + padding;
|
|
323
|
+
} else if (align === 'right') {
|
|
324
|
+
return rect.x + rect.w - padding;
|
|
325
|
+
}
|
|
326
|
+
return rect.x + rect.w / 2;
|
|
497
327
|
}
|
|
498
|
-
|
|
499
328
|
class TreemapElement extends chart_js.Element {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
329
|
+
draw(ctx, data) {
|
|
330
|
+
if (!data) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const options = this.options;
|
|
334
|
+
const { inner, outer } = boundingRects(this);
|
|
335
|
+
const addRectPath = hasRadius(outer.radius) ? helpers.addRoundedRectPath : addNormalRectPath;
|
|
336
|
+
ctx.save();
|
|
337
|
+
if (outer.w !== inner.w || outer.h !== inner.h) {
|
|
338
|
+
ctx.beginPath();
|
|
339
|
+
addRectPath(ctx, outer);
|
|
340
|
+
ctx.clip();
|
|
341
|
+
addRectPath(ctx, inner);
|
|
342
|
+
ctx.fillStyle = options.borderColor;
|
|
343
|
+
ctx.fill('evenodd');
|
|
344
|
+
}
|
|
345
|
+
ctx.beginPath();
|
|
346
|
+
addRectPath(ctx, inner);
|
|
347
|
+
ctx.fillStyle = options.backgroundColor;
|
|
348
|
+
ctx.fill();
|
|
349
|
+
drawDivider(ctx, inner, options, data);
|
|
350
|
+
drawText(ctx, inner, options, data);
|
|
351
|
+
ctx.restore();
|
|
510
352
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
draw(ctx, data, levels = 0) {
|
|
514
|
-
if (!data) {
|
|
515
|
-
return;
|
|
353
|
+
inRange(mouseX, mouseY, useFinalPosition) {
|
|
354
|
+
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
516
355
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
356
|
+
inXRange(mouseX, useFinalPosition) {
|
|
357
|
+
return inRange(this, mouseX, null, useFinalPosition);
|
|
358
|
+
}
|
|
359
|
+
inYRange(mouseY, useFinalPosition) {
|
|
360
|
+
return inRange(this, null, mouseY, useFinalPosition);
|
|
361
|
+
}
|
|
362
|
+
getCenterPoint(useFinalPosition) {
|
|
363
|
+
const { x, y, width, height } = this.getProps([
|
|
364
|
+
'x',
|
|
365
|
+
'y',
|
|
366
|
+
'width',
|
|
367
|
+
'height'
|
|
368
|
+
], useFinalPosition);
|
|
369
|
+
return {
|
|
370
|
+
x: x + width / 2,
|
|
371
|
+
y: y + height / 2
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
tooltipPosition() {
|
|
375
|
+
return this.getCenterPoint();
|
|
376
|
+
}
|
|
377
|
+
constructor(cfg){
|
|
378
|
+
super();
|
|
379
|
+
this.options = undefined;
|
|
380
|
+
this.width = undefined;
|
|
381
|
+
this.height = undefined;
|
|
382
|
+
if (cfg) {
|
|
383
|
+
Object.assign(this, cfg);
|
|
384
|
+
}
|
|
531
385
|
}
|
|
532
|
-
|
|
533
|
-
ctx.beginPath();
|
|
534
|
-
addRectPath(ctx, inner);
|
|
535
|
-
ctx.fillStyle = options.backgroundColor;
|
|
536
|
-
ctx.fill();
|
|
537
|
-
|
|
538
|
-
drawDivider(ctx, inner, options, data);
|
|
539
|
-
drawText(ctx, inner, options, data, levels);
|
|
540
|
-
ctx.restore();
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
inRange(mouseX, mouseY, useFinalPosition) {
|
|
544
|
-
return inRange(this, mouseX, mouseY, useFinalPosition);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
inXRange(mouseX, useFinalPosition) {
|
|
548
|
-
return inRange(this, mouseX, null, useFinalPosition);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
inYRange(mouseY, useFinalPosition) {
|
|
552
|
-
return inRange(this, null, mouseY, useFinalPosition);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
getCenterPoint(useFinalPosition) {
|
|
556
|
-
const {x, y, width, height} = this.getProps(['x', 'y', 'width', 'height'], useFinalPosition);
|
|
557
|
-
return {
|
|
558
|
-
x: x + width / 2,
|
|
559
|
-
y: y + height / 2
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
tooltipPosition() {
|
|
564
|
-
return this.getCenterPoint();
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* @todo: remove this unused function in v3
|
|
569
|
-
*/
|
|
570
|
-
getRange(axis) {
|
|
571
|
-
return axis === 'x' ? this.width / 2 : this.height / 2;
|
|
572
|
-
}
|
|
573
386
|
}
|
|
574
|
-
|
|
575
387
|
TreemapElement.id = 'treemap';
|
|
576
|
-
|
|
577
388
|
TreemapElement.defaults = {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
389
|
+
borderRadius: 0,
|
|
390
|
+
borderWidth: 0,
|
|
391
|
+
captions: {
|
|
392
|
+
align: undefined,
|
|
393
|
+
color: 'black',
|
|
394
|
+
display: true,
|
|
395
|
+
font: {},
|
|
396
|
+
formatter: (ctx)=>ctx.raw.g || ctx.raw._data.label || '',
|
|
397
|
+
padding: 3
|
|
398
|
+
},
|
|
399
|
+
displayMode: 'containerBoxes',
|
|
400
|
+
dividers: {
|
|
401
|
+
display: false,
|
|
402
|
+
lineCapStyle: 'butt',
|
|
403
|
+
lineColor: 'black',
|
|
404
|
+
lineDash: [],
|
|
405
|
+
lineDashOffset: 0,
|
|
406
|
+
lineWidth: 1
|
|
407
|
+
},
|
|
408
|
+
label: undefined,
|
|
409
|
+
labels: {
|
|
410
|
+
align: 'center',
|
|
411
|
+
color: 'black',
|
|
412
|
+
display: false,
|
|
413
|
+
font: {},
|
|
414
|
+
formatter (ctx) {
|
|
415
|
+
if (ctx.raw.g) {
|
|
416
|
+
return [
|
|
417
|
+
ctx.raw.g,
|
|
418
|
+
`${ctx.raw.v}`
|
|
419
|
+
];
|
|
420
|
+
}
|
|
421
|
+
return ctx.raw._data.label ? [
|
|
422
|
+
ctx.raw._data.label,
|
|
423
|
+
`${ctx.raw.v}`
|
|
424
|
+
] : `${ctx.raw.v}`;
|
|
425
|
+
},
|
|
426
|
+
overflow: 'cut',
|
|
427
|
+
padding: 3,
|
|
428
|
+
position: 'middle'
|
|
607
429
|
},
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
},
|
|
612
|
-
rtl: false,
|
|
613
|
-
spacing: 0.5
|
|
430
|
+
rtl: false,
|
|
431
|
+
spacing: 0.5,
|
|
432
|
+
unsorted: false
|
|
614
433
|
};
|
|
615
|
-
|
|
616
434
|
TreemapElement.descriptors = {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
435
|
+
_indexable: false,
|
|
436
|
+
_scriptable: true,
|
|
437
|
+
captions: {
|
|
438
|
+
_fallback: true
|
|
439
|
+
},
|
|
440
|
+
labels: {
|
|
441
|
+
_fallback: true
|
|
442
|
+
}
|
|
625
443
|
};
|
|
626
|
-
|
|
627
444
|
TreemapElement.defaultRoutes = {
|
|
628
|
-
|
|
629
|
-
|
|
445
|
+
backgroundColor: 'backgroundColor',
|
|
446
|
+
borderColor: 'borderColor'
|
|
630
447
|
};
|
|
631
448
|
|
|
632
|
-
function
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
449
|
+
function scaleRect(sq, xScale, yScale, sp) {
|
|
450
|
+
const sp2 = sp * 2;
|
|
451
|
+
const x = xScale.getPixelForValue(sq.x);
|
|
452
|
+
const y = yScale.getPixelForValue(sq.y);
|
|
453
|
+
const w = xScale.getPixelForValue(sq.x + sq.w) - x;
|
|
454
|
+
const h = yScale.getPixelForValue(sq.y + sq.h) - y;
|
|
455
|
+
return {
|
|
456
|
+
height: h - sp2,
|
|
457
|
+
hidden: sp2 > w || sp2 > h,
|
|
458
|
+
width: w - sp2,
|
|
459
|
+
x: x + sp,
|
|
460
|
+
y: y + sp
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function rectNotEqual(r1, r2) {
|
|
464
|
+
return !r1 || !r2 || r1.x !== r2.x || r1.y !== r2.y || r1.w !== r2.w || r1.h !== r2.h || r1.rtl !== r2.rtl || r1.unsorted !== r2.unsorted;
|
|
465
|
+
}
|
|
466
|
+
function arrayNotEqual(a, b) {
|
|
467
|
+
let i;
|
|
468
|
+
let n;
|
|
469
|
+
if (!a || !b) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
if (a === b) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
if (a.length !== b.length) {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
for(i = 0, n = a.length; i < n; ++i){
|
|
479
|
+
if (a[i] !== b[i]) {
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return false;
|
|
641
484
|
}
|
|
642
485
|
|
|
643
|
-
|
|
644
|
-
|
|
486
|
+
function getDims(itm, w2, s2, key) {
|
|
487
|
+
const a = itm._normalized;
|
|
488
|
+
const ar = w2 * a / s2;
|
|
489
|
+
const d1 = Math.sqrt(a * ar);
|
|
490
|
+
const d2 = a / d1;
|
|
491
|
+
const w = key === '_ix' ? d1 : d2;
|
|
492
|
+
const h = key === '_ix' ? d2 : d1;
|
|
493
|
+
return {
|
|
494
|
+
d1,
|
|
495
|
+
d2,
|
|
496
|
+
h,
|
|
497
|
+
w
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const getX = (rect, w)=>rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
|
|
645
501
|
function buildRow(rect, itm, dims, sum) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
502
|
+
const r = {
|
|
503
|
+
_data: itm._data,
|
|
504
|
+
a: itm._normalized,
|
|
505
|
+
h: dims.h,
|
|
506
|
+
s: sum,
|
|
507
|
+
v: itm.value,
|
|
508
|
+
vs: itm.values,
|
|
509
|
+
w: dims.w,
|
|
510
|
+
x: getX(rect, dims.w),
|
|
511
|
+
y: rect.y + rect._iy
|
|
512
|
+
};
|
|
513
|
+
if (itm.group) {
|
|
514
|
+
r.g = itm.group;
|
|
515
|
+
r.l = itm.level;
|
|
516
|
+
r.gs = itm.groupSum;
|
|
517
|
+
}
|
|
518
|
+
return r;
|
|
663
519
|
}
|
|
664
|
-
|
|
665
520
|
class Rect {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
521
|
+
get area() {
|
|
522
|
+
return this.w * this.h;
|
|
523
|
+
}
|
|
524
|
+
get iw() {
|
|
525
|
+
return this.w - this._ix;
|
|
526
|
+
}
|
|
527
|
+
get ih() {
|
|
528
|
+
return this.h - this._iy;
|
|
529
|
+
}
|
|
530
|
+
get dir() {
|
|
531
|
+
const ih = this.ih;
|
|
532
|
+
return ih <= this.iw && ih > 0 ? 'y' : 'x';
|
|
533
|
+
}
|
|
534
|
+
get side() {
|
|
535
|
+
return this.dir === 'x' ? this.iw : this.ih;
|
|
536
|
+
}
|
|
537
|
+
map(arr) {
|
|
538
|
+
const { dir, side } = this;
|
|
539
|
+
const key = dir === 'x' ? '_ix' : '_iy';
|
|
540
|
+
const sum = arr.nsum;
|
|
541
|
+
const row = arr.get();
|
|
542
|
+
const w2 = side * side;
|
|
543
|
+
const s2 = sum * sum;
|
|
544
|
+
const ret = [];
|
|
545
|
+
let maxd2 = 0;
|
|
546
|
+
let totd1 = 0;
|
|
547
|
+
for (const itm of row){
|
|
548
|
+
const dims = getDims(itm, w2, s2, key);
|
|
549
|
+
totd1 += dims.d1;
|
|
550
|
+
maxd2 = Math.max(maxd2, dims.d2);
|
|
551
|
+
ret.push(buildRow(this, itm, dims, arr.sum));
|
|
552
|
+
this[key] += dims.d1;
|
|
553
|
+
}
|
|
554
|
+
this[dir === 'x' ? '_iy' : '_ix'] += maxd2;
|
|
555
|
+
this[key] -= totd1;
|
|
556
|
+
return ret;
|
|
557
|
+
}
|
|
558
|
+
constructor(r){
|
|
559
|
+
r = r || {
|
|
560
|
+
h: 1,
|
|
561
|
+
w: 1
|
|
562
|
+
};
|
|
563
|
+
this.rtl = !!r.rtl;
|
|
564
|
+
this.unsorted = !!r.unsorted;
|
|
565
|
+
this.x = r.x || r.left || 0;
|
|
566
|
+
this.y = r.y || r.top || 0;
|
|
567
|
+
this._ix = 0;
|
|
568
|
+
this._iy = 0;
|
|
569
|
+
this.w = r.w || r.width || r.right - r.left;
|
|
570
|
+
this.h = r.h || r.height || r.bottom - r.top;
|
|
714
571
|
}
|
|
715
|
-
|
|
716
|
-
this[dir === 'x' ? '_iy' : '_ix'] += maxd2;
|
|
717
|
-
this[key] -= totd1;
|
|
718
|
-
return ret;
|
|
719
|
-
}
|
|
720
572
|
}
|
|
721
573
|
|
|
722
574
|
const min = Math.min;
|
|
723
575
|
const max = Math.max;
|
|
724
|
-
|
|
725
576
|
function getStat(sa) {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
577
|
+
return {
|
|
578
|
+
max: sa.max,
|
|
579
|
+
min: sa.min,
|
|
580
|
+
nmax: sa.nmax,
|
|
581
|
+
nmin: sa.nmin,
|
|
582
|
+
nsum: sa.nsum,
|
|
583
|
+
sum: sa.sum
|
|
584
|
+
};
|
|
734
585
|
}
|
|
735
|
-
|
|
736
586
|
function getNewStat(sa, o) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
};
|
|
587
|
+
const v = +o[sa.key];
|
|
588
|
+
const n = v * sa.ratio;
|
|
589
|
+
o._normalized = n;
|
|
590
|
+
return {
|
|
591
|
+
max: max(sa.max, v),
|
|
592
|
+
min: min(sa.min, v),
|
|
593
|
+
nmax: max(sa.nmax, n),
|
|
594
|
+
nmin: min(sa.nmin, n),
|
|
595
|
+
nsum: sa.nsum + n,
|
|
596
|
+
sum: sa.sum + v
|
|
597
|
+
};
|
|
749
598
|
}
|
|
750
|
-
|
|
751
599
|
function setStat(sa, stat) {
|
|
752
|
-
|
|
600
|
+
Object.assign(sa, stat);
|
|
753
601
|
}
|
|
754
|
-
|
|
755
602
|
function push(sa, o, stat) {
|
|
756
|
-
|
|
757
|
-
|
|
603
|
+
sa._arr.push(o);
|
|
604
|
+
setStat(sa, stat);
|
|
758
605
|
}
|
|
759
|
-
|
|
760
606
|
class StatArray {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
607
|
+
get length() {
|
|
608
|
+
return this._arr.length;
|
|
609
|
+
}
|
|
610
|
+
reset() {
|
|
611
|
+
this._arr = [];
|
|
612
|
+
this._hist = [];
|
|
613
|
+
this.sum = 0;
|
|
614
|
+
this.nsum = 0;
|
|
615
|
+
this.min = Infinity;
|
|
616
|
+
this.max = -Infinity;
|
|
617
|
+
this.nmin = Infinity;
|
|
618
|
+
this.nmax = -Infinity;
|
|
619
|
+
}
|
|
620
|
+
push(o) {
|
|
621
|
+
push(this, o, getNewStat(this, o));
|
|
622
|
+
}
|
|
623
|
+
pushIf(o, fn, ...args) {
|
|
624
|
+
const nstat = getNewStat(this, o);
|
|
625
|
+
if (!fn(getStat(this), nstat, args)) {
|
|
626
|
+
return o;
|
|
627
|
+
}
|
|
628
|
+
push(this, o, nstat);
|
|
629
|
+
}
|
|
630
|
+
get() {
|
|
631
|
+
return this._arr;
|
|
632
|
+
}
|
|
633
|
+
constructor(key, ratio){
|
|
634
|
+
this._arr = [];
|
|
635
|
+
this._hist = [];
|
|
636
|
+
this.sum = 0;
|
|
637
|
+
this.nsum = 0;
|
|
638
|
+
this.min = Infinity;
|
|
639
|
+
this.max = -Infinity;
|
|
640
|
+
this.nmin = Infinity;
|
|
641
|
+
this.nmax = -Infinity;
|
|
642
|
+
this.key = key;
|
|
643
|
+
this.ratio = ratio;
|
|
644
|
+
this.reset();
|
|
792
645
|
}
|
|
793
|
-
push(this, o, nstat);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
get() {
|
|
797
|
-
return this._arr;
|
|
798
|
-
}
|
|
799
646
|
}
|
|
800
647
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
648
|
+
const isOlderPart = (act, req)=>req > act || act.length > req.length && act.startsWith(req);
|
|
649
|
+
const getGroupKey = (lvl)=>String(lvl);
|
|
650
|
+
function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
|
|
651
|
+
const objIndex = lvl - 1;
|
|
652
|
+
if (keys[0] in obj && lvl > 0) {
|
|
653
|
+
const record = tree.reduce((reduced, item, i)=>{
|
|
654
|
+
if (i !== objIndex) {
|
|
655
|
+
reduced[getGroupKey(i)] = item;
|
|
656
|
+
}
|
|
657
|
+
return reduced;
|
|
658
|
+
}, {});
|
|
659
|
+
record[treeLeafKey] = tree[objIndex];
|
|
660
|
+
keys.forEach((k)=>{
|
|
661
|
+
record[k] = obj[k];
|
|
662
|
+
});
|
|
663
|
+
result.push(record);
|
|
664
|
+
} else {
|
|
665
|
+
for (const childKey of Object.keys(obj)){
|
|
666
|
+
const child = obj[childKey];
|
|
667
|
+
if (helpers.isObject(child)) {
|
|
668
|
+
tree.push(childKey);
|
|
669
|
+
scanTreeObject(keys, treeLeafKey, child, tree, lvl + 1, result);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
tree.splice(objIndex, 1);
|
|
674
|
+
return result;
|
|
813
675
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
if (!n) {
|
|
834
|
-
return rows;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
const tmp = values.slice();
|
|
838
|
-
let key = index(tmp, keys[0]);
|
|
839
|
-
sort(tmp, key);
|
|
840
|
-
|
|
841
|
-
const val = (idx) => key ? +tmp[idx][key] : +tmp[idx];
|
|
842
|
-
const gval = (idx) => grp && tmp[idx][grp];
|
|
843
|
-
|
|
844
|
-
for (i = 0; i < n; ++i) {
|
|
845
|
-
o = {value: val(i), groupSum: gsum, _data: values[tmp[i]._idx], level: undefined, group: undefined};
|
|
846
|
-
if (grp) {
|
|
847
|
-
o.level = lvl;
|
|
848
|
-
o.group = gval(i);
|
|
849
|
-
const tmpRef = tmp[i];
|
|
850
|
-
o.values = keys.reduce(function(obj, k) {
|
|
851
|
-
obj[k] = +tmpRef[k];
|
|
852
|
-
return obj;
|
|
853
|
-
}, {});
|
|
854
|
-
}
|
|
855
|
-
o = row.pushIf(o, compareAspectRatio, length);
|
|
856
|
-
if (o) {
|
|
857
|
-
rows.push(rect.map(row));
|
|
858
|
-
length = rect.side;
|
|
859
|
-
row.reset();
|
|
860
|
-
row.push(o);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
if (row.length) {
|
|
864
|
-
rows.push(rect.map(row));
|
|
865
|
-
}
|
|
866
|
-
return flatten(rows);
|
|
676
|
+
function normalizeTreeToArray(keys, treeLeafKey, obj) {
|
|
677
|
+
const data = scanTreeObject(keys, treeLeafKey, obj);
|
|
678
|
+
if (!data.length) {
|
|
679
|
+
return data;
|
|
680
|
+
}
|
|
681
|
+
const max = data.reduce((maxVal, element)=>{
|
|
682
|
+
const ikeys = Object.keys(element).length - 2;
|
|
683
|
+
return Math.max(maxVal, ikeys);
|
|
684
|
+
}, 0);
|
|
685
|
+
data.forEach((element)=>{
|
|
686
|
+
for(let i = 0; i < max; i++){
|
|
687
|
+
const groupKey = getGroupKey(i);
|
|
688
|
+
if (!element[groupKey]) {
|
|
689
|
+
element[groupKey] = '';
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
return data;
|
|
867
694
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
hidden: sp2 > w || sp2 > h,
|
|
883
|
-
};
|
|
695
|
+
function flatten(input) {
|
|
696
|
+
const stack = [
|
|
697
|
+
...input
|
|
698
|
+
];
|
|
699
|
+
const res = [];
|
|
700
|
+
while(stack.length){
|
|
701
|
+
const next = stack.pop();
|
|
702
|
+
if (Array.isArray(next)) {
|
|
703
|
+
stack.push(...next);
|
|
704
|
+
} else {
|
|
705
|
+
res.push(next);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return res.reverse();
|
|
884
709
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
710
|
+
function getPath(groups, value, defaultValue) {
|
|
711
|
+
if (!groups.length) {
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
const path = [];
|
|
715
|
+
for (const grp of groups){
|
|
716
|
+
const item = value[grp];
|
|
717
|
+
if (item === '') {
|
|
718
|
+
path.push(defaultValue);
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
path.push(item);
|
|
722
|
+
}
|
|
723
|
+
return path.length ? path.join('.') : defaultValue;
|
|
893
724
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
725
|
+
function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = []) {
|
|
726
|
+
const key = keys[0];
|
|
727
|
+
const addKeys = keys.slice(1);
|
|
728
|
+
const tmp = Object.create(null);
|
|
729
|
+
const data = Object.create(null);
|
|
730
|
+
const ret = [];
|
|
731
|
+
let g;
|
|
732
|
+
let i;
|
|
733
|
+
let n;
|
|
734
|
+
for(i = 0, n = values.length; i < n; ++i){
|
|
735
|
+
const v = values[i];
|
|
736
|
+
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
g = v[grp] || v[treeLeafKey] || '';
|
|
740
|
+
if (!g) {
|
|
741
|
+
return [];
|
|
742
|
+
}
|
|
743
|
+
if (!(g in tmp)) {
|
|
744
|
+
tmp[g] = {
|
|
745
|
+
value: 0
|
|
746
|
+
};
|
|
747
|
+
const tmpRef = tmp[g];
|
|
748
|
+
addKeys.forEach((k)=>{
|
|
749
|
+
tmpRef[k] = 0;
|
|
750
|
+
});
|
|
751
|
+
data[g] = [];
|
|
752
|
+
}
|
|
753
|
+
tmp[g].value += +v[key];
|
|
754
|
+
tmp[g].label = v[grp] || '';
|
|
755
|
+
const tmpRef = tmp[g];
|
|
756
|
+
addKeys.forEach((k)=>{
|
|
757
|
+
tmpRef[k] += v[k];
|
|
758
|
+
});
|
|
759
|
+
tmp[g].path = getPath(groups, v, g);
|
|
760
|
+
data[g].push(v);
|
|
761
|
+
}
|
|
762
|
+
Object.keys(tmp).forEach((k)=>{
|
|
763
|
+
const v = {
|
|
764
|
+
children: data[k]
|
|
765
|
+
};
|
|
766
|
+
v[key] = +tmp[k].value;
|
|
767
|
+
addKeys.forEach((ak)=>{
|
|
768
|
+
v[ak] = +tmp[k][ak];
|
|
769
|
+
});
|
|
770
|
+
v[grp] = tmp[k].label;
|
|
771
|
+
v.label = k;
|
|
772
|
+
v.path = tmp[k].path;
|
|
773
|
+
if (mainGrp) {
|
|
774
|
+
v[mainGrp] = mainValue;
|
|
775
|
+
}
|
|
776
|
+
ret.push(v);
|
|
777
|
+
});
|
|
778
|
+
return ret;
|
|
779
|
+
}
|
|
780
|
+
function index(values, key) {
|
|
781
|
+
let n = values.length;
|
|
782
|
+
let i;
|
|
783
|
+
if (!n) {
|
|
784
|
+
return key;
|
|
785
|
+
}
|
|
786
|
+
const obj = helpers.isObject(values[0]);
|
|
787
|
+
key = obj ? key : 'v';
|
|
788
|
+
for(i = 0, n = values.length; i < n; ++i){
|
|
789
|
+
if (obj) {
|
|
790
|
+
values[i]._idx = i;
|
|
791
|
+
} else {
|
|
792
|
+
values[i] = {
|
|
793
|
+
_idx: i,
|
|
794
|
+
v: values[i]
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return key;
|
|
799
|
+
}
|
|
800
|
+
function sort(values, key) {
|
|
801
|
+
if (key) {
|
|
802
|
+
values.sort((a, b)=>+b[key] - +a[key]);
|
|
803
|
+
} else {
|
|
804
|
+
values.sort((a, b)=>+b - +a);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
function sum(values, key) {
|
|
808
|
+
let s;
|
|
809
|
+
let i;
|
|
810
|
+
let n;
|
|
811
|
+
for(s = 0, i = 0, n = values.length; i < n; ++i){
|
|
812
|
+
s += key ? +values[i][key] : +values[i];
|
|
813
|
+
}
|
|
814
|
+
return s;
|
|
815
|
+
}
|
|
816
|
+
function requireVersion(pkg, min, ver, strict = true) {
|
|
817
|
+
const parts = ver.split('.');
|
|
818
|
+
let i = 0;
|
|
819
|
+
for (const req of min.split('.')){
|
|
820
|
+
const act = parts[i++];
|
|
821
|
+
if (Number.parseInt(req, 10) < Number.parseInt(act, 10)) {
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
if (isOlderPart(act, req)) {
|
|
825
|
+
if (strict) {
|
|
826
|
+
throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`);
|
|
827
|
+
} else {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
907
832
|
return true;
|
|
908
|
-
|
|
833
|
+
}
|
|
909
834
|
|
|
910
|
-
|
|
911
|
-
if (
|
|
912
|
-
|
|
835
|
+
function compareAspectRatio(oldStat, newStat, args) {
|
|
836
|
+
if (oldStat.sum === 0) {
|
|
837
|
+
return true;
|
|
913
838
|
}
|
|
914
|
-
|
|
915
|
-
|
|
839
|
+
const [length] = args;
|
|
840
|
+
const os2 = oldStat.nsum * oldStat.nsum;
|
|
841
|
+
const ns2 = newStat.nsum * newStat.nsum;
|
|
842
|
+
const l2 = length * length;
|
|
843
|
+
const or = Math.max(l2 * oldStat.nmax / os2, os2 / (l2 * oldStat.nmin));
|
|
844
|
+
const nr = Math.max(l2 * newStat.nmax / ns2, ns2 / (l2 * newStat.nmin));
|
|
845
|
+
return nr <= or;
|
|
916
846
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
944
|
-
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
847
|
+
function squarify(values, rectangle, keys = [], grp, lvl, gsum) {
|
|
848
|
+
values = values || [];
|
|
849
|
+
const rows = [];
|
|
850
|
+
const rect = new Rect(rectangle);
|
|
851
|
+
const row = new StatArray('value', rect.area / sum(values, keys[0]));
|
|
852
|
+
let length = rect.side;
|
|
853
|
+
const n = values.length;
|
|
854
|
+
let i;
|
|
855
|
+
let o;
|
|
856
|
+
if (!n) {
|
|
857
|
+
return rows;
|
|
858
|
+
}
|
|
859
|
+
const tmp = values.slice();
|
|
860
|
+
const key = index(tmp, keys[0]);
|
|
861
|
+
if (!rectangle?.unsorted) {
|
|
862
|
+
sort(tmp, key);
|
|
863
|
+
}
|
|
864
|
+
const val = (idx)=>key ? +tmp[idx][key] : +tmp[idx];
|
|
865
|
+
const gval = (idx)=>grp && tmp[idx][grp];
|
|
866
|
+
for(i = 0; i < n; ++i){
|
|
867
|
+
o = {
|
|
868
|
+
_data: values[tmp[i]._idx],
|
|
869
|
+
group: undefined,
|
|
870
|
+
groupSum: gsum,
|
|
871
|
+
level: undefined,
|
|
872
|
+
value: val(i)
|
|
945
873
|
};
|
|
946
|
-
if (
|
|
947
|
-
|
|
948
|
-
|
|
874
|
+
if (grp) {
|
|
875
|
+
o.level = lvl;
|
|
876
|
+
o.group = gval(i);
|
|
877
|
+
const tmpRef = tmp[i];
|
|
878
|
+
o.values = keys.reduce((obj, k)=>{
|
|
879
|
+
obj[k] = +tmpRef[k];
|
|
880
|
+
return obj;
|
|
881
|
+
}, {});
|
|
882
|
+
}
|
|
883
|
+
o = row.pushIf(o, compareAspectRatio, length);
|
|
884
|
+
if (o) {
|
|
885
|
+
rows.push(rect.map(row));
|
|
886
|
+
length = rect.side;
|
|
887
|
+
row.reset();
|
|
888
|
+
row.push(o);
|
|
949
889
|
}
|
|
950
|
-
gdata.forEach((gEl) => {
|
|
951
|
-
ret.push(...recur(gEl.children, gidx + 1, subRect, sq.g, sq.s));
|
|
952
|
-
});
|
|
953
|
-
});
|
|
954
890
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
? recur(tree, 0, mainRect)
|
|
960
|
-
: squarify(tree, mainRect, keys);
|
|
891
|
+
if (row.length) {
|
|
892
|
+
rows.push(rect.map(row));
|
|
893
|
+
}
|
|
894
|
+
return flatten(rows);
|
|
961
895
|
}
|
|
962
896
|
|
|
897
|
+
function buildData(tree, dataset, keys, mainRect) {
|
|
898
|
+
const treeLeafKey = dataset.treeLeafKey || '_leaf';
|
|
899
|
+
if (helpers.isObject(tree)) {
|
|
900
|
+
tree = normalizeTreeToArray(keys, treeLeafKey, tree);
|
|
901
|
+
}
|
|
902
|
+
const groups = dataset.groups || [];
|
|
903
|
+
const glen = groups.length;
|
|
904
|
+
const sp = dataset.displayMode === 'headerBoxes' ? 0 : helpers.valueOrDefault(dataset.spacing, 0);
|
|
905
|
+
const captions = dataset.captions || {};
|
|
906
|
+
const font = helpers.toFont(captions.font);
|
|
907
|
+
const padding = helpers.valueOrDefault(captions.padding, 3);
|
|
908
|
+
function recur(treeElements, gidx, rect, parent, gs) {
|
|
909
|
+
const g = getGroupKey(groups[gidx]);
|
|
910
|
+
const pg = gidx > 0 ? getGroupKey(groups[gidx - 1]) : undefined;
|
|
911
|
+
const gdata = group(treeElements, g, keys, treeLeafKey, pg, parent, groups.filter((_item, index)=>index <= gidx));
|
|
912
|
+
const gsq = squarify(gdata, rect, keys, g, gidx, gs);
|
|
913
|
+
const ret = gsq.slice();
|
|
914
|
+
if (gidx < glen - 1) {
|
|
915
|
+
gsq.forEach((sq)=>{
|
|
916
|
+
const bw = dataset.displayMode === 'headerBoxes' ? {
|
|
917
|
+
b: 0,
|
|
918
|
+
l: 0,
|
|
919
|
+
r: 0,
|
|
920
|
+
t: 0
|
|
921
|
+
} : parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
922
|
+
const subRect = {
|
|
923
|
+
...rect,
|
|
924
|
+
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
925
|
+
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
926
|
+
x: sq.x + sp + bw.l,
|
|
927
|
+
y: sq.y + sp + bw.t
|
|
928
|
+
};
|
|
929
|
+
if (shouldDrawCaption(dataset.displayMode, subRect, captions)) {
|
|
930
|
+
const captionHeight = getCaptionHeight(dataset.displayMode, subRect, font, padding);
|
|
931
|
+
subRect.y += captionHeight;
|
|
932
|
+
subRect.h -= captionHeight;
|
|
933
|
+
}
|
|
934
|
+
const children = [];
|
|
935
|
+
gdata.forEach((gEl)=>{
|
|
936
|
+
children.push(...recur(gEl.children, gidx + 1, subRect, sq.g, sq.s));
|
|
937
|
+
});
|
|
938
|
+
ret.push(...children);
|
|
939
|
+
sq.isLeaf = !children.length;
|
|
940
|
+
});
|
|
941
|
+
} else {
|
|
942
|
+
gsq.forEach((sq)=>{
|
|
943
|
+
sq.isLeaf = true;
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
return ret;
|
|
947
|
+
}
|
|
948
|
+
const result = glen ? recur(tree, 0, mainRect) : squarify(tree, mainRect, keys);
|
|
949
|
+
return result.map((d)=>{
|
|
950
|
+
if (dataset.displayMode !== 'headerBoxes' || d.isLeaf) {
|
|
951
|
+
return d;
|
|
952
|
+
}
|
|
953
|
+
if (!shouldDrawCaption(dataset.displayMode, d, captions)) {
|
|
954
|
+
return undefined;
|
|
955
|
+
}
|
|
956
|
+
const captionHeight = getCaptionHeight(dataset.displayMode, d, font, padding);
|
|
957
|
+
return {
|
|
958
|
+
...d,
|
|
959
|
+
h: captionHeight
|
|
960
|
+
};
|
|
961
|
+
}).filter(Boolean);
|
|
962
|
+
}
|
|
963
963
|
class TreemapController extends chart_js.DatasetController {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
this._groups = undefined;
|
|
968
|
-
this._keys = undefined;
|
|
969
|
-
this._rect = undefined;
|
|
970
|
-
this._rectChanged = true;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
initialize() {
|
|
974
|
-
this.enableOptionSharing = true;
|
|
975
|
-
super.initialize();
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
getMinMax(scale) {
|
|
979
|
-
return {
|
|
980
|
-
min: 0,
|
|
981
|
-
max: scale.axis === 'x' ? scale.right - scale.left : scale.bottom - scale.top
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
configure() {
|
|
986
|
-
super.configure();
|
|
987
|
-
const {xScale, yScale} = this.getMeta();
|
|
988
|
-
if (!xScale || !yScale) {
|
|
989
|
-
// configure is called once before `linkScales`, and at that call we don't have any scales linked yet
|
|
990
|
-
return;
|
|
964
|
+
initialize() {
|
|
965
|
+
this.enableOptionSharing = true;
|
|
966
|
+
super.initialize();
|
|
991
967
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
if (rectNotEqual(this._rect, rect)) {
|
|
998
|
-
this._rect = rect;
|
|
999
|
-
this._rectChanged = true;
|
|
968
|
+
getMinMax(scale) {
|
|
969
|
+
return {
|
|
970
|
+
max: scale.axis === 'x' ? scale.right - scale.left : scale.bottom - scale.top,
|
|
971
|
+
min: 0
|
|
972
|
+
};
|
|
1000
973
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
974
|
+
configure() {
|
|
975
|
+
super.configure();
|
|
976
|
+
const { xScale, yScale } = this.getMeta();
|
|
977
|
+
if (!xScale || !yScale) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const w = xScale.right - xScale.left;
|
|
981
|
+
const h = yScale.bottom - yScale.top;
|
|
982
|
+
const rect = {
|
|
983
|
+
h,
|
|
984
|
+
rtl: !!this.options.rtl,
|
|
985
|
+
unsorted: !!this.options.unsorted,
|
|
986
|
+
w,
|
|
987
|
+
x: 0,
|
|
988
|
+
y: 0
|
|
989
|
+
};
|
|
990
|
+
if (rectNotEqual(this._rect, rect)) {
|
|
991
|
+
this._rect = rect;
|
|
992
|
+
this._rectChanged = true;
|
|
993
|
+
}
|
|
994
|
+
if (this._rectChanged) {
|
|
995
|
+
xScale.max = w;
|
|
996
|
+
xScale.configure();
|
|
997
|
+
yScale.max = h;
|
|
998
|
+
yScale.configure();
|
|
999
|
+
}
|
|
1007
1000
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1001
|
+
update(mode) {
|
|
1002
|
+
const dataset = this.getDataset();
|
|
1003
|
+
const { data } = this.getMeta();
|
|
1004
|
+
const groups = dataset.groups || [];
|
|
1005
|
+
const keys = [
|
|
1006
|
+
dataset.key || ''
|
|
1007
|
+
].concat(dataset.sumKeys || []);
|
|
1008
|
+
dataset.tree = dataset.tree || dataset.data || [];
|
|
1009
|
+
const tree = dataset.tree;
|
|
1010
|
+
if (mode === 'reset') {
|
|
1011
|
+
this.configure();
|
|
1012
|
+
}
|
|
1013
|
+
if (this._rectChanged || arrayNotEqual(this._keys || [], keys) || arrayNotEqual(this._groups || [], groups) || this._prevTree !== tree) {
|
|
1014
|
+
this._groups = groups.slice();
|
|
1015
|
+
this._keys = keys.slice();
|
|
1016
|
+
this._prevTree = tree;
|
|
1017
|
+
this._rectChanged = false;
|
|
1018
|
+
dataset.data = buildData(tree, dataset, this._keys, this._rect);
|
|
1019
|
+
this._dataCheck();
|
|
1020
|
+
this._resyncElements();
|
|
1021
|
+
}
|
|
1022
|
+
this.updateElements(data, 0, data.length, mode);
|
|
1020
1023
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1024
|
+
updateElements(rects, start, count, mode) {
|
|
1025
|
+
const reset = mode === 'reset';
|
|
1026
|
+
const dataset = this.getDataset();
|
|
1027
|
+
const firstOpts = this.resolveDataElementOptions(start, mode);
|
|
1028
|
+
this._rect.options = firstOpts;
|
|
1029
|
+
const sharedOptions = this.getSharedOptions(firstOpts);
|
|
1030
|
+
const includeOptions = this.includeOptions(mode, sharedOptions || {});
|
|
1031
|
+
const { xScale, yScale } = this.getMeta();
|
|
1032
|
+
for(let i = start; i < start + count; i++){
|
|
1033
|
+
const options = sharedOptions || this.resolveDataElementOptions(i, mode);
|
|
1034
|
+
const properties = scaleRect(dataset.data[i], xScale, yScale, options.spacing);
|
|
1035
|
+
if (reset) {
|
|
1036
|
+
properties.width = 0;
|
|
1037
|
+
properties.height = 0;
|
|
1038
|
+
}
|
|
1039
|
+
if (includeOptions) {
|
|
1040
|
+
properties.options = options;
|
|
1041
|
+
}
|
|
1042
|
+
this.updateElement(rects[i], i, properties, mode);
|
|
1043
|
+
}
|
|
1044
|
+
this.updateSharedOptions(sharedOptions || {}, mode, firstOpts);
|
|
1033
1045
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
if (includeOptions) {
|
|
1055
|
-
properties.options = options;
|
|
1056
|
-
}
|
|
1057
|
-
this.updateElement(rects[i], i, properties, mode);
|
|
1046
|
+
draw() {
|
|
1047
|
+
const { ctx, chartArea } = this.chart;
|
|
1048
|
+
const metadata = this.getMeta().data || [];
|
|
1049
|
+
const dataset = this.getDataset();
|
|
1050
|
+
const data = dataset.data;
|
|
1051
|
+
helpers.clipArea(ctx, chartArea);
|
|
1052
|
+
for(let i = 0, ilen = metadata.length; i < ilen; ++i){
|
|
1053
|
+
const rect = metadata[i];
|
|
1054
|
+
if (!rect.hidden) {
|
|
1055
|
+
rect.draw(ctx, data[i]);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
helpers.unclipArea(ctx);
|
|
1059
|
+
}
|
|
1060
|
+
constructor(chart, datasetIndex){
|
|
1061
|
+
super(chart, datasetIndex);
|
|
1062
|
+
this._groups = undefined;
|
|
1063
|
+
this._keys = undefined;
|
|
1064
|
+
this._rect = undefined;
|
|
1065
|
+
this._rectChanged = true;
|
|
1058
1066
|
}
|
|
1059
|
-
|
|
1060
|
-
this.updateSharedOptions(sharedOptions, mode, firstOpts);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
draw() {
|
|
1064
|
-
const {ctx, chartArea} = this.chart;
|
|
1065
|
-
const metadata = this.getMeta().data || [];
|
|
1066
|
-
const dataset = this.getDataset();
|
|
1067
|
-
const levels = (dataset.groups || []).length - 1;
|
|
1068
|
-
const data = dataset.data;
|
|
1069
|
-
|
|
1070
|
-
helpers.clipArea(ctx, chartArea);
|
|
1071
|
-
for (let i = 0, ilen = metadata.length; i < ilen; ++i) {
|
|
1072
|
-
const rect = metadata[i];
|
|
1073
|
-
if (!rect.hidden) {
|
|
1074
|
-
rect.draw(ctx, data[i], levels);
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
helpers.unclipArea(ctx);
|
|
1078
|
-
}
|
|
1079
1067
|
}
|
|
1080
|
-
|
|
1081
1068
|
TreemapController.id = 'treemap';
|
|
1082
|
-
|
|
1083
1069
|
TreemapController.version = version;
|
|
1084
|
-
|
|
1085
1070
|
TreemapController.defaults = {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1071
|
+
animations: {
|
|
1072
|
+
numbers: {
|
|
1073
|
+
properties: [
|
|
1074
|
+
'x',
|
|
1075
|
+
'y',
|
|
1076
|
+
'width',
|
|
1077
|
+
'height'
|
|
1078
|
+
],
|
|
1079
|
+
type: 'number'
|
|
1080
|
+
}
|
|
1092
1081
|
},
|
|
1093
|
-
|
|
1094
|
-
|
|
1082
|
+
dataElementType: 'treemap'
|
|
1095
1083
|
};
|
|
1096
|
-
|
|
1097
1084
|
TreemapController.descriptors = {
|
|
1098
|
-
|
|
1099
|
-
|
|
1085
|
+
_indexable: false,
|
|
1086
|
+
_scriptable: true
|
|
1100
1087
|
};
|
|
1101
|
-
|
|
1102
1088
|
TreemapController.overrides = {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
hover: {},
|
|
1110
|
-
|
|
1111
|
-
plugins: {
|
|
1112
|
-
tooltip: {
|
|
1113
|
-
position: 'treemap',
|
|
1114
|
-
intersect: true,
|
|
1115
|
-
callbacks: {
|
|
1116
|
-
title(items) {
|
|
1117
|
-
if (items.length) {
|
|
1118
|
-
const item = items[0];
|
|
1119
|
-
return item.dataset.key || '';
|
|
1120
|
-
}
|
|
1121
|
-
return '';
|
|
1122
|
-
},
|
|
1123
|
-
label(item) {
|
|
1124
|
-
const dataset = item.dataset;
|
|
1125
|
-
const dataItem = dataset.data[item.dataIndex];
|
|
1126
|
-
const label = dataItem.g || dataItem._data.label || dataset.label;
|
|
1127
|
-
return (label ? label + ': ' : '') + dataItem.v;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1089
|
+
hover: {},
|
|
1090
|
+
interaction: {
|
|
1091
|
+
includeInvisible: true,
|
|
1092
|
+
intersect: true,
|
|
1093
|
+
mode: 'point'
|
|
1130
1094
|
},
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1095
|
+
plugins: {
|
|
1096
|
+
tooltip: {
|
|
1097
|
+
callbacks: {
|
|
1098
|
+
label (item) {
|
|
1099
|
+
const dataset = item.dataset;
|
|
1100
|
+
const dataItem = dataset.data[item.dataIndex];
|
|
1101
|
+
const label = dataItem.g || dataItem._data.label || dataset.label;
|
|
1102
|
+
return (label ? `${label}: ` : '') + dataItem.v;
|
|
1103
|
+
},
|
|
1104
|
+
title (items) {
|
|
1105
|
+
if (items.length) {
|
|
1106
|
+
const item = items[0];
|
|
1107
|
+
return item.dataset.key || '';
|
|
1108
|
+
}
|
|
1109
|
+
return '';
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
intersect: true,
|
|
1113
|
+
position: 'treemap'
|
|
1114
|
+
}
|
|
1138
1115
|
},
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1116
|
+
scales: {
|
|
1117
|
+
x: {
|
|
1118
|
+
alignToPixels: true,
|
|
1119
|
+
bounds: 'data',
|
|
1120
|
+
display: false,
|
|
1121
|
+
type: 'linear'
|
|
1122
|
+
},
|
|
1123
|
+
y: {
|
|
1124
|
+
alignToPixels: true,
|
|
1125
|
+
bounds: 'data',
|
|
1126
|
+
display: false,
|
|
1127
|
+
reverse: true,
|
|
1128
|
+
type: 'linear'
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1147
1131
|
};
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
requireVersion('chart.js', '3.8', chart_js.Chart.version);
|
|
1132
|
+
TreemapController.beforeRegister = ()=>{
|
|
1133
|
+
requireVersion('chart.js', '3.8', chart_js.Chart.version);
|
|
1151
1134
|
};
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}
|
|
1166
|
-
} else {
|
|
1167
|
-
console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
|
|
1168
|
-
}
|
|
1135
|
+
TreemapController.afterRegister = ()=>{
|
|
1136
|
+
const tooltipPlugin = chart_js.registry.plugins.get('tooltip');
|
|
1137
|
+
if (tooltipPlugin) {
|
|
1138
|
+
tooltipPlugin.positioners.treemap = (active)=>{
|
|
1139
|
+
if (!active.length) {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
const item = active.at(-1);
|
|
1143
|
+
const el = item.element;
|
|
1144
|
+
return el.tooltipPosition();
|
|
1145
|
+
};
|
|
1146
|
+
} else {
|
|
1147
|
+
console.warn('Unable to register the treemap positioner because tooltip plugin is not registered');
|
|
1148
|
+
}
|
|
1169
1149
|
};
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
}
|
|
1150
|
+
TreemapController.afterUnregister = ()=>{
|
|
1151
|
+
const tooltipPlugin = chart_js.registry.plugins.get('tooltip');
|
|
1152
|
+
if (tooltipPlugin) {
|
|
1153
|
+
delete tooltipPlugin.positioners.treemap;
|
|
1154
|
+
}
|
|
1176
1155
|
};
|
|
1177
1156
|
|
|
1178
1157
|
chart_js.Chart.register(TreemapController, TreemapElement);
|
|
1179
1158
|
|
|
1159
|
+
exports.TreemapController = TreemapController;
|
|
1160
|
+
exports.TreemapElement = TreemapElement;
|
|
1180
1161
|
exports.flatten = flatten;
|
|
1181
1162
|
exports.getGroupKey = getGroupKey;
|
|
1182
1163
|
exports.group = group;
|