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