chartjs-chart-treemap 5.0.0-next.2 → 5.0.0-next.4
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 +354 -267
- package/dist/chartjs-chart-treemap.esm.js +358 -289
- package/dist/chartjs-chart-treemap.min.js +2 -2
- package/dist/chartjs-chart-treemap.min.js.map +1 -1
- package/dist/controller.d.ts +25 -4
- package/dist/element.d.ts +2 -7
- package/dist/index.esm.d.ts +1 -0
- package/dist/layout.d.ts +27 -0
- package/dist/options.d.ts +7 -1
- package/dist/rect.d.ts +1 -1
- package/dist/squarify.d.ts +15 -0
- package/dist/text.d.ts +2 -2
- package/dist/types.d.ts +18 -9
- package/dist/utils.d.ts +11 -2
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-treemap v5.0.0-next.
|
|
2
|
+
* chartjs-chart-treemap v5.0.0-next.4
|
|
3
3
|
* https://chartjs-chart-treemap.pages.dev/
|
|
4
4
|
* (c) 2026 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
@@ -10,7 +10,7 @@ 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
|
-
var version = "5.0.0-next.
|
|
13
|
+
var version = "5.0.0-next.4";
|
|
14
14
|
|
|
15
15
|
function scaleRect(sq, xScale, yScale, sp) {
|
|
16
16
|
const sp2 = sp * 2;
|
|
@@ -29,26 +29,12 @@ function scaleRect(sq, xScale, yScale, sp) {
|
|
|
29
29
|
function rectNotEqual(r1, r2) {
|
|
30
30
|
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;
|
|
31
31
|
}
|
|
32
|
-
function arrayNotEqual(a, b) {
|
|
33
|
-
let i;
|
|
34
|
-
let n;
|
|
35
|
-
if (!a || !b) {
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
if (a === b) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
if (a.length !== b.length) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
for(i = 0, n = a.length; i < n; ++i){
|
|
45
|
-
if (a[i] !== b[i]) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
32
|
|
|
33
|
+
const layoutDefaults = {
|
|
34
|
+
displayMode: 'containerBoxes',
|
|
35
|
+
rtl: false,
|
|
36
|
+
spacing: 0.5
|
|
37
|
+
};
|
|
52
38
|
function limit(value, min, max) {
|
|
53
39
|
return Math.max(Math.min(value, max), min);
|
|
54
40
|
}
|
|
@@ -87,24 +73,14 @@ function getDims(itm, w2, s2, key) {
|
|
|
87
73
|
};
|
|
88
74
|
}
|
|
89
75
|
const getX = (rect, w)=>rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
w: dims.w,
|
|
99
|
-
x: getX(rect, dims.w),
|
|
100
|
-
y: rect.y + rect._iy
|
|
101
|
-
};
|
|
102
|
-
if (itm.group) {
|
|
103
|
-
r.g = itm.group;
|
|
104
|
-
r.l = itm.level;
|
|
105
|
-
r.gs = itm.groupSum;
|
|
106
|
-
}
|
|
107
|
-
return r;
|
|
76
|
+
function assignRow(rect, itm, dims, sum) {
|
|
77
|
+
itm.a = itm._normalized;
|
|
78
|
+
itm.h = dims.h;
|
|
79
|
+
itm.s = sum;
|
|
80
|
+
itm.w = dims.w;
|
|
81
|
+
itm.x = getX(rect, dims.w);
|
|
82
|
+
itm.y = rect.y + rect._iy;
|
|
83
|
+
delete itm._normalized;
|
|
108
84
|
}
|
|
109
85
|
class Rect {
|
|
110
86
|
get area() {
|
|
@@ -130,19 +106,17 @@ class Rect {
|
|
|
130
106
|
const row = arr.get();
|
|
131
107
|
const w2 = side * side;
|
|
132
108
|
const s2 = sum * sum;
|
|
133
|
-
const ret = [];
|
|
134
109
|
let maxd2 = 0;
|
|
135
110
|
let totd1 = 0;
|
|
136
111
|
for (const itm of row){
|
|
137
112
|
const dims = getDims(itm, w2, s2, key);
|
|
138
113
|
totd1 += dims.d1;
|
|
139
114
|
maxd2 = Math.max(maxd2, dims.d2);
|
|
140
|
-
|
|
115
|
+
assignRow(this, itm, dims, arr.sum);
|
|
141
116
|
this[key] += dims.d1;
|
|
142
117
|
}
|
|
143
118
|
this[dir === 'x' ? '_iy' : '_ix'] += maxd2;
|
|
144
119
|
this[key] -= totd1;
|
|
145
|
-
return ret;
|
|
146
120
|
}
|
|
147
121
|
constructor(r){
|
|
148
122
|
r = r || {
|
|
@@ -237,7 +211,7 @@ class StatArray {
|
|
|
237
211
|
const isOlderPart = (act, req)=>req > act || act.length > req.length && act.startsWith(req);
|
|
238
212
|
const getGroupKey = (lvl)=>String(lvl);
|
|
239
213
|
const hasGroupValue = (value)=>value !== undefined && value !== null && value !== '';
|
|
240
|
-
function scanTreeObject(keys,
|
|
214
|
+
function scanTreeObject(keys, leafKey, obj, tree = [], lvl = 0, result = []) {
|
|
241
215
|
const objIndex = lvl - 1;
|
|
242
216
|
if (keys[0] in obj && lvl > 0) {
|
|
243
217
|
const record = tree.reduce((reduced, item, i)=>{
|
|
@@ -246,7 +220,7 @@ function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = [])
|
|
|
246
220
|
}
|
|
247
221
|
return reduced;
|
|
248
222
|
}, {});
|
|
249
|
-
record[
|
|
223
|
+
record[leafKey] = tree[objIndex];
|
|
250
224
|
keys.forEach((k)=>{
|
|
251
225
|
record[k] = obj[k];
|
|
252
226
|
});
|
|
@@ -256,15 +230,18 @@ function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = [])
|
|
|
256
230
|
const child = obj[childKey];
|
|
257
231
|
if (helpers.isObject(child)) {
|
|
258
232
|
tree.push(childKey);
|
|
259
|
-
scanTreeObject(keys,
|
|
233
|
+
scanTreeObject(keys, leafKey, child, tree, lvl + 1, result);
|
|
260
234
|
}
|
|
261
235
|
}
|
|
262
236
|
}
|
|
263
237
|
tree.splice(objIndex, 1);
|
|
264
238
|
return result;
|
|
265
239
|
}
|
|
266
|
-
function
|
|
267
|
-
|
|
240
|
+
function flattenTree(tree, keys, leafKey) {
|
|
241
|
+
return normalizeTreeToArray(keys, leafKey, tree);
|
|
242
|
+
}
|
|
243
|
+
function normalizeTreeToArray(keys, leafKey, obj) {
|
|
244
|
+
const data = scanTreeObject(keys, leafKey, obj);
|
|
268
245
|
if (!data.length) {
|
|
269
246
|
return data;
|
|
270
247
|
}
|
|
@@ -308,7 +285,7 @@ function getPath(groups, value, defaultValue) {
|
|
|
308
285
|
}
|
|
309
286
|
return path.length ? path.join('.') : defaultValue;
|
|
310
287
|
}
|
|
311
|
-
function resolveGroup(value,
|
|
288
|
+
function resolveGroup(value, leafKey, allGroups, groupIndex) {
|
|
312
289
|
for(let idx = groupIndex; idx < allGroups.length; idx++){
|
|
313
290
|
const groupKey = getGroupKey(allGroups[idx]);
|
|
314
291
|
if (hasGroupValue(value[groupKey])) {
|
|
@@ -319,11 +296,11 @@ function resolveGroup(value, treeLeafKey, allGroups, groupIndex) {
|
|
|
319
296
|
};
|
|
320
297
|
}
|
|
321
298
|
}
|
|
322
|
-
if (hasGroupValue(value[
|
|
299
|
+
if (hasGroupValue(value[leafKey])) {
|
|
323
300
|
return {
|
|
324
|
-
group:
|
|
301
|
+
group: leafKey,
|
|
325
302
|
groupIndex,
|
|
326
|
-
value: value[
|
|
303
|
+
value: value[leafKey]
|
|
327
304
|
};
|
|
328
305
|
}
|
|
329
306
|
return undefined;
|
|
@@ -341,7 +318,7 @@ function ensureGroup(tmp, data, groupValue, addKeys) {
|
|
|
341
318
|
});
|
|
342
319
|
data[groupValue] = [];
|
|
343
320
|
}
|
|
344
|
-
function group(values, grp, keys,
|
|
321
|
+
function group(values, grp, keys, leafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
|
|
345
322
|
const key = keys[0];
|
|
346
323
|
const addKeys = keys.slice(1);
|
|
347
324
|
const tmp = Object.create(null);
|
|
@@ -358,7 +335,7 @@ function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [],
|
|
|
358
335
|
if (mainGrp && v[mainGrp] !== mainValue) {
|
|
359
336
|
continue;
|
|
360
337
|
}
|
|
361
|
-
const itemGroup = resolveGroup(v,
|
|
338
|
+
const itemGroup = resolveGroup(v, leafKey, resolvedAllGroups, resolvedGroupIndex);
|
|
362
339
|
if (!itemGroup) {
|
|
363
340
|
return [];
|
|
364
341
|
}
|
|
@@ -462,54 +439,51 @@ function compareAspectRatio(oldStat, newStat, args) {
|
|
|
462
439
|
const nr = Math.max(l2 * newStat.nmax / ns2, ns2 / (l2 * newStat.nmin));
|
|
463
440
|
return nr <= or;
|
|
464
441
|
}
|
|
465
|
-
function
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
let o;
|
|
474
|
-
if (!n) {
|
|
475
|
-
return rows;
|
|
476
|
-
}
|
|
477
|
-
const tmp = values.slice();
|
|
478
|
-
const key = index(tmp, keys[0]);
|
|
479
|
-
if (!rectangle?.unsorted) {
|
|
480
|
-
sort(tmp, key);
|
|
481
|
-
}
|
|
482
|
-
const val = (idx)=>key ? +tmp[idx][key] : +tmp[idx];
|
|
483
|
-
const gval = (idx)=>grp && tmp[idx][grp];
|
|
484
|
-
for(i = 0; i < n; ++i){
|
|
485
|
-
o = {
|
|
486
|
-
_data: values[tmp[i]._idx],
|
|
487
|
-
group: undefined,
|
|
488
|
-
groupSum: gsum,
|
|
489
|
-
level: undefined,
|
|
490
|
-
value: val(i)
|
|
442
|
+
function toNodes(values, keys, grp, lvl) {
|
|
443
|
+
const items = values ? values.slice() : [];
|
|
444
|
+
const key = index(items, keys[0]);
|
|
445
|
+
return items.map((item)=>{
|
|
446
|
+
const node = {
|
|
447
|
+
_data: values[item._idx],
|
|
448
|
+
v: key ? +item[key] : +item,
|
|
449
|
+
vs: undefined
|
|
491
450
|
};
|
|
492
451
|
if (grp) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
obj[k] = +
|
|
452
|
+
node.g = item[grp];
|
|
453
|
+
node.gs = undefined;
|
|
454
|
+
node.l = item.groupIndex ?? lvl;
|
|
455
|
+
node.vs = keys.reduce((obj, k)=>{
|
|
456
|
+
obj[k] = +item[k];
|
|
498
457
|
return obj;
|
|
499
458
|
}, {});
|
|
500
459
|
}
|
|
501
|
-
|
|
460
|
+
return node;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function sortNodes(nodes) {
|
|
464
|
+
sort(nodes, 'v');
|
|
465
|
+
}
|
|
466
|
+
function packInto(nodes, rectangle) {
|
|
467
|
+
const n = nodes.length;
|
|
468
|
+
if (!n) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const rect = new Rect(rectangle);
|
|
472
|
+
const row = new StatArray('v', rect.area / sum(nodes, 'v'));
|
|
473
|
+
let length = rect.side;
|
|
474
|
+
let o;
|
|
475
|
+
for(let i = 0; i < n; ++i){
|
|
476
|
+
o = row.pushIf(nodes[i], compareAspectRatio, length);
|
|
502
477
|
if (o) {
|
|
503
|
-
|
|
478
|
+
rect.map(row);
|
|
504
479
|
length = rect.side;
|
|
505
480
|
row.reset();
|
|
506
481
|
row.push(o);
|
|
507
482
|
}
|
|
508
483
|
}
|
|
509
484
|
if (row.length) {
|
|
510
|
-
|
|
485
|
+
rect.map(row);
|
|
511
486
|
}
|
|
512
|
-
return flatten(rows);
|
|
513
487
|
}
|
|
514
488
|
|
|
515
489
|
const widthCache = new Map();
|
|
@@ -533,20 +507,42 @@ function getCaptionHeight(displayMode, rect, font, padding) {
|
|
|
533
507
|
const captionHeight = font.lineHeight + padding * 2;
|
|
534
508
|
return rect.h < 2 * captionHeight ? rect.h / 3 : captionHeight;
|
|
535
509
|
}
|
|
536
|
-
function drawText(ctx, rect, options, item, element) {
|
|
537
|
-
const { captions, labels
|
|
510
|
+
function drawText(ctx, rect, options, item, element, layout) {
|
|
511
|
+
const { captions, labels } = options;
|
|
512
|
+
const { displayMode } = layout;
|
|
513
|
+
const isLeaf = item && (!helpers.defined(item.l) || item.isLeaf);
|
|
514
|
+
let block;
|
|
515
|
+
if (isLeaf) {
|
|
516
|
+
if (labels.display) {
|
|
517
|
+
block = labelBlock(ctx, rect, options, item, element);
|
|
518
|
+
}
|
|
519
|
+
} else if (shouldDrawCaption(displayMode, rect, captions)) {
|
|
520
|
+
block = captionBlock(ctx, rect, options, item, element, layout);
|
|
521
|
+
}
|
|
522
|
+
if (!block) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
538
525
|
ctx.save();
|
|
539
526
|
ctx.beginPath();
|
|
540
527
|
ctx.rect(rect.x, rect.y, rect.w, rect.h);
|
|
541
528
|
ctx.clip();
|
|
542
|
-
|
|
543
|
-
if (isLeaf && labels.display) {
|
|
544
|
-
drawLabel(ctx, rect, options, item, element);
|
|
545
|
-
} else if (!isLeaf && shouldDrawCaption(displayMode, rect, captions)) {
|
|
546
|
-
drawCaption(ctx, rect, options, item, element);
|
|
547
|
-
}
|
|
529
|
+
drawTextBlock(ctx, rect, block);
|
|
548
530
|
ctx.restore();
|
|
549
531
|
}
|
|
532
|
+
function drawTextBlock(ctx, rect, block) {
|
|
533
|
+
const { align, colors, fonts, lines, padding, top } = block;
|
|
534
|
+
const x = calculateX(rect, align, padding);
|
|
535
|
+
ctx.textAlign = align;
|
|
536
|
+
ctx.textBaseline = 'middle';
|
|
537
|
+
let offset = 0;
|
|
538
|
+
lines.forEach((line, i)=>{
|
|
539
|
+
const font = fonts[Math.min(i, fonts.length - 1)];
|
|
540
|
+
ctx.font = font.string;
|
|
541
|
+
ctx.fillStyle = colors[Math.min(i, colors.length - 1)];
|
|
542
|
+
ctx.fillText(line, x, top + offset + font.lineHeight / 2);
|
|
543
|
+
offset += font.lineHeight;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
550
546
|
function callbackContext(element, item) {
|
|
551
547
|
const context = element.$context || {
|
|
552
548
|
active: !!element.active,
|
|
@@ -563,16 +559,12 @@ function resolveOption(option, context) {
|
|
|
563
559
|
function resolveCaptionText(captions, element, item) {
|
|
564
560
|
return resolveOption(captions.formatter, callbackContext(element, item)) || item.g || '';
|
|
565
561
|
}
|
|
566
|
-
function
|
|
567
|
-
const {
|
|
562
|
+
function captionBlock(ctx, rect, options, item, element, layout) {
|
|
563
|
+
const { displayMode, rtl, spacing } = layout;
|
|
564
|
+
const { captions } = options;
|
|
568
565
|
const { color, hoverColor, font, hoverFont, padding, align } = captions;
|
|
569
|
-
const oColor = (rect.active ? hoverColor : color) || color;
|
|
570
|
-
const oAlign = align || (rtl ? 'right' : 'left');
|
|
571
566
|
const optFont = (rect.active ? hoverFont : font) || font;
|
|
572
567
|
const oFont = helpers.toFont(optFont);
|
|
573
|
-
const fonts = [
|
|
574
|
-
oFont
|
|
575
|
-
];
|
|
576
568
|
if (oFont.lineHeight > rect.h) {
|
|
577
569
|
return;
|
|
578
570
|
}
|
|
@@ -580,20 +572,62 @@ function drawCaption(ctx, rect, options, item, element) {
|
|
|
580
572
|
if (!text) {
|
|
581
573
|
return;
|
|
582
574
|
}
|
|
583
|
-
const
|
|
575
|
+
const fonts = [
|
|
576
|
+
oFont
|
|
577
|
+
];
|
|
578
|
+
if (measureLabelSize(ctx, [
|
|
584
579
|
text
|
|
585
|
-
], fonts)
|
|
586
|
-
if (captionSize.width + 2 * padding > rect.w) {
|
|
580
|
+
], fonts).width + 2 * padding > rect.w) {
|
|
587
581
|
text = sliceTextToFitWidth(ctx, text, rect.w - 2 * padding, fonts);
|
|
588
582
|
}
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
583
|
+
const top = displayMode === 'headerBoxes' ? rect.y + (rect.h - oFont.lineHeight) / 2 : rect.y + padding + spacing;
|
|
584
|
+
return {
|
|
585
|
+
align: align || (rtl ? 'right' : 'left'),
|
|
586
|
+
colors: [
|
|
587
|
+
(rect.active ? hoverColor : color) || color
|
|
588
|
+
],
|
|
589
|
+
fonts,
|
|
590
|
+
lines: [
|
|
591
|
+
text
|
|
592
|
+
],
|
|
593
|
+
padding,
|
|
594
|
+
top
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function labelBlock(ctx, rect, options, item, element) {
|
|
598
|
+
const labels = options.labels;
|
|
599
|
+
const content = resolveOption(labels.formatter, callbackContext(element, item));
|
|
600
|
+
if (!content) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const lines = helpers.isArray(content) ? content : [
|
|
604
|
+
content
|
|
605
|
+
];
|
|
606
|
+
let fonts = getFontFromOptions(rect, labels);
|
|
607
|
+
let labelSize = measureLabelSize(ctx, lines, fonts);
|
|
608
|
+
const lblToDraw = labelToDraw(rect, labels, labelSize);
|
|
609
|
+
if (!lblToDraw) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (helpers.isNumber(lblToDraw)) {
|
|
613
|
+
labelSize = {
|
|
614
|
+
height: labelSize.height * lblToDraw,
|
|
615
|
+
width: labelSize.width * lblToDraw
|
|
616
|
+
};
|
|
617
|
+
fonts = toFonts(fonts, lblToDraw);
|
|
618
|
+
}
|
|
619
|
+
const { color, hoverColor, align, padding } = labels;
|
|
620
|
+
const optColor = (rect.active ? hoverColor : color) || color;
|
|
621
|
+
return {
|
|
622
|
+
align,
|
|
623
|
+
colors: helpers.isArray(optColor) ? optColor : [
|
|
624
|
+
optColor
|
|
625
|
+
],
|
|
626
|
+
fonts,
|
|
627
|
+
lines,
|
|
628
|
+
padding,
|
|
629
|
+
top: calculateBlockTop(rect, labels, labelSize)
|
|
630
|
+
};
|
|
597
631
|
}
|
|
598
632
|
function sliceTextToFitWidth(ctx, text, width, fonts) {
|
|
599
633
|
const ellipsis = '...';
|
|
@@ -658,7 +692,7 @@ function toFonts(fonts, fitRatio) {
|
|
|
658
692
|
});
|
|
659
693
|
});
|
|
660
694
|
}
|
|
661
|
-
function labelToDraw(
|
|
695
|
+
function labelToDraw(rect, options, labelSize) {
|
|
662
696
|
const { overflow, padding } = options;
|
|
663
697
|
const { width, height } = labelSize;
|
|
664
698
|
if (overflow === 'hidden') {
|
|
@@ -678,62 +712,15 @@ function getFontFromOptions(rect, labels) {
|
|
|
678
712
|
helpers.toFont(optFont)
|
|
679
713
|
];
|
|
680
714
|
}
|
|
681
|
-
function
|
|
682
|
-
const
|
|
683
|
-
const content = resolveOption(labels.formatter, callbackContext(element, item));
|
|
684
|
-
if (!content) {
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
const contents = helpers.isArray(content) ? content : [
|
|
688
|
-
content
|
|
689
|
-
];
|
|
690
|
-
let fonts = getFontFromOptions(rect, labels);
|
|
691
|
-
let labelSize = measureLabelSize(ctx, contents, fonts);
|
|
692
|
-
const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
|
|
693
|
-
if (!lblToDraw) {
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
if (helpers.isNumber(lblToDraw)) {
|
|
697
|
-
labelSize = {
|
|
698
|
-
height: labelSize.height * lblToDraw,
|
|
699
|
-
width: labelSize.width * lblToDraw
|
|
700
|
-
};
|
|
701
|
-
fonts = toFonts(fonts, lblToDraw);
|
|
702
|
-
}
|
|
703
|
-
const { color, hoverColor, align } = labels;
|
|
704
|
-
const optColor = (rect.active ? hoverColor : color) || color;
|
|
705
|
-
const colors = helpers.isArray(optColor) ? optColor : [
|
|
706
|
-
optColor
|
|
707
|
-
];
|
|
708
|
-
const xyPoint = calculateXYLabel(rect, labels, labelSize);
|
|
709
|
-
ctx.textAlign = align;
|
|
710
|
-
ctx.textBaseline = 'middle';
|
|
711
|
-
let lhs = 0;
|
|
712
|
-
contents.forEach((l, i)=>{
|
|
713
|
-
const c = colors[Math.min(i, colors.length - 1)];
|
|
714
|
-
const f = fonts[Math.min(i, fonts.length - 1)];
|
|
715
|
-
const lh = f.lineHeight;
|
|
716
|
-
ctx.font = f.string;
|
|
717
|
-
ctx.fillStyle = c;
|
|
718
|
-
ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
|
|
719
|
-
lhs += lh;
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
function calculateXYLabel(rect, options, labelSize) {
|
|
723
|
-
const { align, position, padding } = options;
|
|
724
|
-
const x = calculateX(rect, align, padding);
|
|
725
|
-
let y;
|
|
715
|
+
function calculateBlockTop(rect, options, labelSize) {
|
|
716
|
+
const { position, padding } = options;
|
|
726
717
|
if (position === 'top') {
|
|
727
|
-
|
|
728
|
-
} else if (position === 'bottom') {
|
|
729
|
-
y = rect.y + rect.h - padding - labelSize.height;
|
|
730
|
-
} else {
|
|
731
|
-
y = rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
718
|
+
return rect.y + padding;
|
|
732
719
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
720
|
+
if (position === 'bottom') {
|
|
721
|
+
return rect.y + rect.h - padding - labelSize.height;
|
|
722
|
+
}
|
|
723
|
+
return rect.y + (rect.h - labelSize.height) / 2 + padding;
|
|
737
724
|
}
|
|
738
725
|
function calculateX(rect, align, padding) {
|
|
739
726
|
if (align === 'left') {
|
|
@@ -744,76 +731,109 @@ function calculateX(rect, align, padding) {
|
|
|
744
731
|
return rect.x + rect.w / 2;
|
|
745
732
|
}
|
|
746
733
|
|
|
747
|
-
function
|
|
748
|
-
const
|
|
749
|
-
if (helpers.isObject(tree)) {
|
|
750
|
-
tree = normalizeTreeToArray(keys, treeLeafKey, tree);
|
|
751
|
-
}
|
|
752
|
-
const groups = dataset.groups || [];
|
|
734
|
+
function buildHierarchy(values, gidx, keys, layout, parentGroupValue) {
|
|
735
|
+
const { groups, leafKey, unsorted } = layout;
|
|
753
736
|
const glen = groups.length;
|
|
754
|
-
const
|
|
755
|
-
const
|
|
737
|
+
const g = getGroupKey(groups[gidx]);
|
|
738
|
+
const gdata = group(values, g, keys, leafKey, undefined, parentGroupValue, groups.filter((_item, index)=>index <= gidx), groups, gidx);
|
|
739
|
+
const nodes = toNodes(gdata, keys, g, gidx);
|
|
740
|
+
if (!unsorted) {
|
|
741
|
+
sortNodes(nodes);
|
|
742
|
+
}
|
|
743
|
+
for (const node of nodes){
|
|
744
|
+
const record = node._data;
|
|
745
|
+
const nextGroupIndex = record.groupIndex + 1;
|
|
746
|
+
const children = gidx < glen - 1 && nextGroupIndex < glen ? buildHierarchy(record.children, nextGroupIndex, keys, layout, node.g) : [];
|
|
747
|
+
Object.defineProperty(node, '_children', {
|
|
748
|
+
configurable: true,
|
|
749
|
+
value: children,
|
|
750
|
+
writable: true
|
|
751
|
+
});
|
|
752
|
+
node.isLeaf = !children.length;
|
|
753
|
+
}
|
|
754
|
+
return nodes;
|
|
755
|
+
}
|
|
756
|
+
function flattenNodes(nodes) {
|
|
757
|
+
const flat = nodes.slice();
|
|
758
|
+
for (const node of nodes){
|
|
759
|
+
if (node._children?.length) {
|
|
760
|
+
flat.push(...flattenNodes(node._children));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return flat;
|
|
764
|
+
}
|
|
765
|
+
function layoutLevel(nodes, rect, layout, parentSum) {
|
|
766
|
+
if (parentSum !== undefined) {
|
|
767
|
+
for (const node of nodes){
|
|
768
|
+
node.gs = parentSum;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
packInto(nodes, rect);
|
|
772
|
+
for (const node of nodes){
|
|
773
|
+
if (node._children?.length) {
|
|
774
|
+
layoutLevel(node._children, getSubRect(node, rect, layout), layout, node.s);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function layoutNodes(nodes, rect, layout) {
|
|
779
|
+
layoutLevel(nodes, rect, layout);
|
|
780
|
+
applyHeaderBoxes(flattenNodes(nodes), layout);
|
|
781
|
+
}
|
|
782
|
+
function getSubRect(sq, rect, layout) {
|
|
783
|
+
const { borderWidth, captions, displayMode } = layout;
|
|
784
|
+
const sp = displayMode === 'headerBoxes' ? 0 : layout.spacing;
|
|
785
|
+
const bw = displayMode === 'headerBoxes' ? {
|
|
786
|
+
b: 0,
|
|
787
|
+
l: 0,
|
|
788
|
+
r: 0,
|
|
789
|
+
t: 0
|
|
790
|
+
} : parseBorderWidth(borderWidth, sq.w / 2, sq.h / 2);
|
|
791
|
+
const subRect = {
|
|
792
|
+
...rect,
|
|
793
|
+
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
794
|
+
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
795
|
+
x: sq.x + sp + bw.l,
|
|
796
|
+
y: sq.y + sp + bw.t
|
|
797
|
+
};
|
|
798
|
+
if (shouldDrawCaption(displayMode, subRect, captions)) {
|
|
799
|
+
const captionHeight = getCaptionHeight(displayMode, subRect, helpers.toFont(captions.font), helpers.valueOrDefault(captions.padding, 3));
|
|
800
|
+
subRect.y += captionHeight;
|
|
801
|
+
subRect.h -= captionHeight;
|
|
802
|
+
}
|
|
803
|
+
return subRect;
|
|
804
|
+
}
|
|
805
|
+
function buildNodes(tree, keys, layout) {
|
|
806
|
+
const { groups, leafKey, unsorted } = layout;
|
|
807
|
+
const input = helpers.isObject(tree) ? normalizeTreeToArray(keys, leafKey, tree) : tree;
|
|
808
|
+
if (groups.length) {
|
|
809
|
+
return buildHierarchy(input, 0, keys, layout);
|
|
810
|
+
}
|
|
811
|
+
const nodes = toNodes(input || [], keys);
|
|
812
|
+
if (!unsorted) {
|
|
813
|
+
sortNodes(nodes);
|
|
814
|
+
}
|
|
815
|
+
return nodes;
|
|
816
|
+
}
|
|
817
|
+
function applyHeaderBoxes(nodes, layout) {
|
|
818
|
+
const { captions, displayMode } = layout;
|
|
819
|
+
if (displayMode !== 'headerBoxes') {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
756
822
|
const font = helpers.toFont(captions.font);
|
|
757
823
|
const padding = helpers.valueOrDefault(captions.padding, 3);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
l: 0,
|
|
762
|
-
r: 0,
|
|
763
|
-
t: 0
|
|
764
|
-
} : parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
|
|
765
|
-
const subRect = {
|
|
766
|
-
...rect,
|
|
767
|
-
h: sq.h - 2 * sp - bw.t - bw.b,
|
|
768
|
-
w: sq.w - 2 * sp - bw.l - bw.r,
|
|
769
|
-
x: sq.x + sp + bw.l,
|
|
770
|
-
y: sq.y + sp + bw.t
|
|
771
|
-
};
|
|
772
|
-
if (shouldDrawCaption(dataset.displayMode, subRect, captions)) {
|
|
773
|
-
const captionHeight = getCaptionHeight(dataset.displayMode, subRect, font, padding);
|
|
774
|
-
subRect.y += captionHeight;
|
|
775
|
-
subRect.h -= captionHeight;
|
|
824
|
+
for (const node of nodes){
|
|
825
|
+
if (node.isLeaf) {
|
|
826
|
+
continue;
|
|
776
827
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const g = getGroupKey(groups[gidx]);
|
|
781
|
-
const gdata = group(treeElements, g, keys, treeLeafKey, undefined, parent, groups.filter((_item, index)=>index <= gidx), groups, gidx);
|
|
782
|
-
const gsq = squarify(gdata, rect, keys, g, gidx, gs);
|
|
783
|
-
const ret = gsq.slice();
|
|
784
|
-
if (gidx < glen - 1) {
|
|
785
|
-
gsq.forEach((sq)=>{
|
|
786
|
-
const subRect = getSubRect(sq, rect);
|
|
787
|
-
const children = [];
|
|
788
|
-
const nextGroupIndex = sq._data.groupIndex + 1;
|
|
789
|
-
if (nextGroupIndex < glen) {
|
|
790
|
-
children.push(...recur(sq._data.children, nextGroupIndex, subRect, sq.g, sq.s));
|
|
791
|
-
}
|
|
792
|
-
ret.push(...children);
|
|
793
|
-
sq.isLeaf = !children.length;
|
|
794
|
-
});
|
|
828
|
+
if (shouldDrawCaption(displayMode, node, captions)) {
|
|
829
|
+
node.h = getCaptionHeight(displayMode, node, font, padding);
|
|
830
|
+
node.hidden = false;
|
|
795
831
|
} else {
|
|
796
|
-
|
|
797
|
-
sq.isLeaf = true;
|
|
798
|
-
});
|
|
832
|
+
node.hidden = true;
|
|
799
833
|
}
|
|
800
|
-
return ret;
|
|
801
834
|
}
|
|
802
|
-
const result = glen ? recur(tree, 0, mainRect) : squarify(tree, mainRect, keys);
|
|
803
|
-
return result.map((d)=>{
|
|
804
|
-
if (dataset.displayMode !== 'headerBoxes' || d.isLeaf) {
|
|
805
|
-
return d;
|
|
806
|
-
}
|
|
807
|
-
if (!shouldDrawCaption(dataset.displayMode, d, captions)) {
|
|
808
|
-
return undefined;
|
|
809
|
-
}
|
|
810
|
-
const captionHeight = getCaptionHeight(dataset.displayMode, d, font, padding);
|
|
811
|
-
return {
|
|
812
|
-
...d,
|
|
813
|
-
h: captionHeight
|
|
814
|
-
};
|
|
815
|
-
}).filter(Boolean);
|
|
816
835
|
}
|
|
836
|
+
|
|
817
837
|
function registerTooltipPositioner() {
|
|
818
838
|
const tooltipPlugin = chart_js.registry.plugins.get('tooltip');
|
|
819
839
|
if (!tooltipPlugin || tooltipPlugin.positioners.treemap) {
|
|
@@ -866,42 +886,101 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
866
886
|
yScale.configure();
|
|
867
887
|
}
|
|
868
888
|
}
|
|
869
|
-
|
|
889
|
+
_layoutOptions() {
|
|
870
890
|
const dataset = this.getDataset();
|
|
871
|
-
const
|
|
872
|
-
|
|
891
|
+
const options = this.options;
|
|
892
|
+
return {
|
|
893
|
+
borderWidth: dataset.borderWidth,
|
|
894
|
+
captions: dataset.captions || {},
|
|
895
|
+
displayMode: options.displayMode,
|
|
896
|
+
groups: options.groups || [],
|
|
897
|
+
leafKey: options.leafKey,
|
|
898
|
+
spacing: helpers.valueOrDefault(dataset.spacing, 0),
|
|
899
|
+
unsorted: !!options.unsorted
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
buildOrUpdateElements(_resetNewElements) {
|
|
903
|
+
this.parse(0, 0);
|
|
904
|
+
const meta = this.getMeta();
|
|
905
|
+
const count = meta._parsed.length;
|
|
906
|
+
const elements = meta.data;
|
|
907
|
+
if (elements.length > count) {
|
|
908
|
+
elements.splice(count, elements.length - count);
|
|
909
|
+
}
|
|
910
|
+
while(elements.length < count){
|
|
911
|
+
elements.push(new this.dataElementType());
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
parse(_start, _count) {
|
|
915
|
+
const dataset = this.getDataset();
|
|
916
|
+
if (dataset.tree !== undefined) {
|
|
917
|
+
throw new Error('chartjs-chart-treemap v5: the "tree" option was renamed to "data". ' + 'See https://chartjs-chart-treemap.pages.dev/usage/');
|
|
918
|
+
}
|
|
919
|
+
const options = this.options;
|
|
873
920
|
const keys = [
|
|
874
|
-
|
|
875
|
-
].concat(
|
|
876
|
-
|
|
877
|
-
const
|
|
878
|
-
const
|
|
921
|
+
options.key || ''
|
|
922
|
+
].concat(options.sumKeys || []);
|
|
923
|
+
const nodes = buildNodes(dataset.data || [], keys, this._layoutOptions());
|
|
924
|
+
const flat = flattenNodes(nodes);
|
|
925
|
+
const fingerprint = flat.map((node)=>`${node.v}\u0000${node.g}\u0000${node.l}`).join('\u0001');
|
|
926
|
+
if (this._fingerprint === fingerprint) {
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
this._fingerprint = fingerprint;
|
|
930
|
+
this._nodes = nodes;
|
|
931
|
+
this.getMeta()._parsed = flat;
|
|
932
|
+
this._layoutDirty = true;
|
|
933
|
+
}
|
|
934
|
+
getContext(index, active, mode) {
|
|
935
|
+
const base = chart_js.DatasetController.prototype.getContext;
|
|
936
|
+
const context = base.call(this, index, active, mode);
|
|
937
|
+
const parsed = typeof index === 'number' ? this.getParsed(index) : undefined;
|
|
938
|
+
if (parsed) {
|
|
939
|
+
context.raw = parsed;
|
|
940
|
+
context.parsed = parsed;
|
|
941
|
+
}
|
|
942
|
+
return context;
|
|
943
|
+
}
|
|
944
|
+
getLabelAndValue(index) {
|
|
945
|
+
const node = this.getParsed(index);
|
|
946
|
+
if (!node) {
|
|
947
|
+
return {
|
|
948
|
+
label: '',
|
|
949
|
+
value: ''
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
const dataset = this.getDataset();
|
|
953
|
+
const label = node.g ?? node._data?.label ?? dataset.label;
|
|
954
|
+
return {
|
|
955
|
+
label: label === undefined ? '' : `${label}`,
|
|
956
|
+
value: `${node.v}`
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
update(mode) {
|
|
960
|
+
const { data } = this.getMeta();
|
|
879
961
|
if (mode === 'reset') {
|
|
880
962
|
this.configure();
|
|
881
963
|
}
|
|
882
|
-
if (this.
|
|
883
|
-
this.
|
|
884
|
-
this.
|
|
885
|
-
this._prevTree = tree;
|
|
886
|
-
this._prevTreeVersion = treeVersion;
|
|
964
|
+
if (this._layoutDirty || this._rectChanged) {
|
|
965
|
+
layoutNodes(this._nodes, this._rect, this._layoutOptions());
|
|
966
|
+
this._layoutDirty = false;
|
|
887
967
|
this._rectChanged = false;
|
|
888
|
-
dataset.data = buildData(tree, dataset, this._keys, this._rect);
|
|
889
|
-
this._dataCheck();
|
|
890
|
-
this._resyncElements();
|
|
891
968
|
}
|
|
892
969
|
this.updateElements(data, 0, data.length, mode);
|
|
893
970
|
}
|
|
894
971
|
updateElements(rects, start, count, mode) {
|
|
895
972
|
const reset = mode === 'reset';
|
|
896
|
-
const dataset = this.getDataset();
|
|
897
973
|
const firstOpts = this.resolveDataElementOptions(start, mode);
|
|
898
974
|
this._rect.options = firstOpts;
|
|
899
975
|
const sharedOptions = this.getSharedOptions(firstOpts);
|
|
900
976
|
const includeOptions = this.includeOptions(mode, sharedOptions || {});
|
|
901
977
|
const { xScale, yScale } = this.getMeta();
|
|
978
|
+
const spacing = this.options.spacing;
|
|
902
979
|
for(let i = start; i < start + count; i++){
|
|
903
980
|
const options = sharedOptions || this.resolveDataElementOptions(i, mode);
|
|
904
|
-
const
|
|
981
|
+
const node = this.getParsed(i);
|
|
982
|
+
const properties = scaleRect(node, xScale, yScale, spacing);
|
|
983
|
+
properties.hidden = properties.hidden || !!node.hidden;
|
|
905
984
|
if (reset) {
|
|
906
985
|
properties.width = 0;
|
|
907
986
|
properties.height = 0;
|
|
@@ -916,23 +995,28 @@ class TreemapController extends chart_js.DatasetController {
|
|
|
916
995
|
draw() {
|
|
917
996
|
const { ctx, chartArea } = this.chart;
|
|
918
997
|
const metadata = this.getMeta().data || [];
|
|
919
|
-
const
|
|
920
|
-
const
|
|
998
|
+
const { displayMode, rtl, spacing } = this.options;
|
|
999
|
+
const layout = {
|
|
1000
|
+
displayMode,
|
|
1001
|
+
rtl,
|
|
1002
|
+
spacing
|
|
1003
|
+
};
|
|
921
1004
|
helpers.clipArea(ctx, chartArea);
|
|
922
1005
|
for(let i = 0, ilen = metadata.length; i < ilen; ++i){
|
|
923
1006
|
const rect = metadata[i];
|
|
924
1007
|
if (!rect.hidden) {
|
|
925
|
-
rect.draw(ctx,
|
|
1008
|
+
rect.draw(ctx, this.getParsed(i), layout);
|
|
926
1009
|
}
|
|
927
1010
|
}
|
|
928
1011
|
helpers.unclipArea(ctx);
|
|
929
1012
|
}
|
|
930
1013
|
constructor(chart, datasetIndex){
|
|
931
1014
|
super(chart, datasetIndex);
|
|
932
|
-
this._groups = undefined;
|
|
933
|
-
this._keys = undefined;
|
|
934
1015
|
this._rect = undefined;
|
|
935
1016
|
this._rectChanged = true;
|
|
1017
|
+
this._nodes = [];
|
|
1018
|
+
this._layoutDirty = true;
|
|
1019
|
+
this._fingerprint = undefined;
|
|
936
1020
|
}
|
|
937
1021
|
}
|
|
938
1022
|
TreemapController.id = 'treemap';
|
|
@@ -949,7 +1033,15 @@ TreemapController.defaults = {
|
|
|
949
1033
|
type: 'number'
|
|
950
1034
|
}
|
|
951
1035
|
},
|
|
952
|
-
dataElementType: 'treemap'
|
|
1036
|
+
dataElementType: 'treemap',
|
|
1037
|
+
displayMode: layoutDefaults.displayMode,
|
|
1038
|
+
groups: [],
|
|
1039
|
+
key: '',
|
|
1040
|
+
leafKey: '_leaf',
|
|
1041
|
+
rtl: layoutDefaults.rtl,
|
|
1042
|
+
spacing: layoutDefaults.spacing,
|
|
1043
|
+
sumKeys: [],
|
|
1044
|
+
unsorted: false
|
|
953
1045
|
};
|
|
954
1046
|
TreemapController.descriptors = {
|
|
955
1047
|
_indexable: false,
|
|
@@ -974,10 +1066,9 @@ TreemapController.overrides = {
|
|
|
974
1066
|
tooltip: {
|
|
975
1067
|
callbacks: {
|
|
976
1068
|
label (item) {
|
|
977
|
-
const
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
return (label ? `${label}: ` : '') + dataItem.v;
|
|
1069
|
+
const node = item.parsed;
|
|
1070
|
+
const label = node.g || node._data?.label || item.dataset.label;
|
|
1071
|
+
return (label ? `${label}: ` : '') + node.v;
|
|
981
1072
|
},
|
|
982
1073
|
title (items) {
|
|
983
1074
|
if (items.length) {
|
|
@@ -1079,7 +1170,7 @@ function addNormalRectPath(ctx, rect) {
|
|
|
1079
1170
|
}
|
|
1080
1171
|
|
|
1081
1172
|
class TreemapElement extends chart_js.Element {
|
|
1082
|
-
draw(ctx, data) {
|
|
1173
|
+
draw(ctx, data, layout = layoutDefaults) {
|
|
1083
1174
|
if (!data) {
|
|
1084
1175
|
return;
|
|
1085
1176
|
}
|
|
@@ -1099,7 +1190,7 @@ class TreemapElement extends chart_js.Element {
|
|
|
1099
1190
|
addRectPath(ctx, inner);
|
|
1100
1191
|
ctx.fillStyle = options.backgroundColor;
|
|
1101
1192
|
ctx.fill();
|
|
1102
|
-
drawText(ctx, inner, options, data, this);
|
|
1193
|
+
drawText(ctx, inner, options, data, this, layout);
|
|
1103
1194
|
ctx.restore();
|
|
1104
1195
|
}
|
|
1105
1196
|
inRange(mouseX, mouseY, useFinalPosition) {
|
|
@@ -1150,8 +1241,6 @@ TreemapElement.defaults = {
|
|
|
1150
1241
|
},
|
|
1151
1242
|
padding: 3
|
|
1152
1243
|
},
|
|
1153
|
-
displayMode: 'containerBoxes',
|
|
1154
|
-
label: undefined,
|
|
1155
1244
|
labels: {
|
|
1156
1245
|
align: 'center',
|
|
1157
1246
|
color: 'black',
|
|
@@ -1173,10 +1262,7 @@ TreemapElement.defaults = {
|
|
|
1173
1262
|
overflow: 'cut',
|
|
1174
1263
|
padding: 3,
|
|
1175
1264
|
position: 'middle'
|
|
1176
|
-
}
|
|
1177
|
-
rtl: false,
|
|
1178
|
-
spacing: 0.5,
|
|
1179
|
-
unsorted: false
|
|
1265
|
+
}
|
|
1180
1266
|
};
|
|
1181
1267
|
TreemapElement.descriptors = {
|
|
1182
1268
|
_indexable: false,
|
|
@@ -1200,6 +1286,7 @@ chart_js.Chart.register(TreemapController, TreemapElement);
|
|
|
1200
1286
|
exports.TreemapController = TreemapController;
|
|
1201
1287
|
exports.TreemapElement = TreemapElement;
|
|
1202
1288
|
exports.flatten = flatten;
|
|
1289
|
+
exports.flattenTree = flattenTree;
|
|
1203
1290
|
exports.getGroupKey = getGroupKey;
|
|
1204
1291
|
exports.group = group;
|
|
1205
1292
|
exports.index = index;
|