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.
@@ -1,13 +1,13 @@
1
1
  /*!
2
- * chartjs-chart-treemap v5.0.0-next.2
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
6
6
  */
7
- import { DatasetController, Chart, registry, Element } from 'chart.js';
8
- import { toTRBL, toTRBLCorners, isObject, defined, toFont, valueOrDefault, isArray, isNumber, clipArea, unclipArea, addRoundedRectPath } from 'chart.js/helpers';
7
+ import { DatasetController, registry, Chart, Element } from 'chart.js';
8
+ import { toTRBL, toTRBLCorners, isObject, defined, isArray, isNumber, toFont, valueOrDefault, clipArea, unclipArea, addRoundedRectPath } from 'chart.js/helpers';
9
9
 
10
- var version = "5.0.0-next.2";
10
+ var version = "5.0.0-next.4";
11
11
 
12
12
  function scaleRect(sq, xScale, yScale, sp) {
13
13
  const sp2 = sp * 2;
@@ -26,26 +26,12 @@ function scaleRect(sq, xScale, yScale, sp) {
26
26
  function rectNotEqual(r1, r2) {
27
27
  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;
28
28
  }
29
- function arrayNotEqual(a, b) {
30
- let i;
31
- let n;
32
- if (!a || !b) {
33
- return true;
34
- }
35
- if (a === b) {
36
- return false;
37
- }
38
- if (a.length !== b.length) {
39
- return true;
40
- }
41
- for(i = 0, n = a.length; i < n; ++i){
42
- if (a[i] !== b[i]) {
43
- return true;
44
- }
45
- }
46
- return false;
47
- }
48
29
 
30
+ const layoutDefaults = {
31
+ displayMode: 'containerBoxes',
32
+ rtl: false,
33
+ spacing: 0.5
34
+ };
49
35
  function limit(value, min, max) {
50
36
  return Math.max(Math.min(value, max), min);
51
37
  }
@@ -84,24 +70,14 @@ function getDims(itm, w2, s2, key) {
84
70
  };
85
71
  }
86
72
  const getX = (rect, w)=>rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
87
- function buildRow(rect, itm, dims, sum) {
88
- const r = {
89
- _data: itm._data,
90
- a: itm._normalized,
91
- h: dims.h,
92
- s: sum,
93
- v: itm.value,
94
- vs: itm.values,
95
- w: dims.w,
96
- x: getX(rect, dims.w),
97
- y: rect.y + rect._iy
98
- };
99
- if (itm.group) {
100
- r.g = itm.group;
101
- r.l = itm.level;
102
- r.gs = itm.groupSum;
103
- }
104
- return r;
73
+ function assignRow(rect, itm, dims, sum) {
74
+ itm.a = itm._normalized;
75
+ itm.h = dims.h;
76
+ itm.s = sum;
77
+ itm.w = dims.w;
78
+ itm.x = getX(rect, dims.w);
79
+ itm.y = rect.y + rect._iy;
80
+ delete itm._normalized;
105
81
  }
106
82
  class Rect {
107
83
  get area() {
@@ -127,19 +103,17 @@ class Rect {
127
103
  const row = arr.get();
128
104
  const w2 = side * side;
129
105
  const s2 = sum * sum;
130
- const ret = [];
131
106
  let maxd2 = 0;
132
107
  let totd1 = 0;
133
108
  for (const itm of row){
134
109
  const dims = getDims(itm, w2, s2, key);
135
110
  totd1 += dims.d1;
136
111
  maxd2 = Math.max(maxd2, dims.d2);
137
- ret.push(buildRow(this, itm, dims, arr.sum));
112
+ assignRow(this, itm, dims, arr.sum);
138
113
  this[key] += dims.d1;
139
114
  }
140
115
  this[dir === 'x' ? '_iy' : '_ix'] += maxd2;
141
116
  this[key] -= totd1;
142
- return ret;
143
117
  }
144
118
  constructor(r){
145
119
  r = r || {
@@ -234,7 +208,7 @@ class StatArray {
234
208
  const isOlderPart = (act, req)=>req > act || act.length > req.length && act.startsWith(req);
235
209
  const getGroupKey = (lvl)=>String(lvl);
236
210
  const hasGroupValue = (value)=>value !== undefined && value !== null && value !== '';
237
- function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
211
+ function scanTreeObject(keys, leafKey, obj, tree = [], lvl = 0, result = []) {
238
212
  const objIndex = lvl - 1;
239
213
  if (keys[0] in obj && lvl > 0) {
240
214
  const record = tree.reduce((reduced, item, i)=>{
@@ -243,7 +217,7 @@ function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = [])
243
217
  }
244
218
  return reduced;
245
219
  }, {});
246
- record[treeLeafKey] = tree[objIndex];
220
+ record[leafKey] = tree[objIndex];
247
221
  keys.forEach((k)=>{
248
222
  record[k] = obj[k];
249
223
  });
@@ -253,15 +227,18 @@ function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = [])
253
227
  const child = obj[childKey];
254
228
  if (isObject(child)) {
255
229
  tree.push(childKey);
256
- scanTreeObject(keys, treeLeafKey, child, tree, lvl + 1, result);
230
+ scanTreeObject(keys, leafKey, child, tree, lvl + 1, result);
257
231
  }
258
232
  }
259
233
  }
260
234
  tree.splice(objIndex, 1);
261
235
  return result;
262
236
  }
263
- function normalizeTreeToArray(keys, treeLeafKey, obj) {
264
- const data = scanTreeObject(keys, treeLeafKey, obj);
237
+ function flattenTree(tree, keys, leafKey) {
238
+ return normalizeTreeToArray(keys, leafKey, tree);
239
+ }
240
+ function normalizeTreeToArray(keys, leafKey, obj) {
241
+ const data = scanTreeObject(keys, leafKey, obj);
265
242
  if (!data.length) {
266
243
  return data;
267
244
  }
@@ -279,21 +256,6 @@ function normalizeTreeToArray(keys, treeLeafKey, obj) {
279
256
  });
280
257
  return data;
281
258
  }
282
- function flatten(input) {
283
- const stack = [
284
- ...input
285
- ];
286
- const res = [];
287
- while(stack.length){
288
- const next = stack.pop();
289
- if (Array.isArray(next)) {
290
- stack.push(...next);
291
- } else {
292
- res.push(next);
293
- }
294
- }
295
- return res.reverse();
296
- }
297
259
  function getPath(groups, value, defaultValue) {
298
260
  const path = [];
299
261
  for (const grp of groups){
@@ -305,7 +267,7 @@ function getPath(groups, value, defaultValue) {
305
267
  }
306
268
  return path.length ? path.join('.') : defaultValue;
307
269
  }
308
- function resolveGroup(value, treeLeafKey, allGroups, groupIndex) {
270
+ function resolveGroup(value, leafKey, allGroups, groupIndex) {
309
271
  for(let idx = groupIndex; idx < allGroups.length; idx++){
310
272
  const groupKey = getGroupKey(allGroups[idx]);
311
273
  if (hasGroupValue(value[groupKey])) {
@@ -316,11 +278,11 @@ function resolveGroup(value, treeLeafKey, allGroups, groupIndex) {
316
278
  };
317
279
  }
318
280
  }
319
- if (hasGroupValue(value[treeLeafKey])) {
281
+ if (hasGroupValue(value[leafKey])) {
320
282
  return {
321
- group: treeLeafKey,
283
+ group: leafKey,
322
284
  groupIndex,
323
- value: value[treeLeafKey]
285
+ value: value[leafKey]
324
286
  };
325
287
  }
326
288
  return undefined;
@@ -338,7 +300,7 @@ function ensureGroup(tmp, data, groupValue, addKeys) {
338
300
  });
339
301
  data[groupValue] = [];
340
302
  }
341
- function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
303
+ function group(values, grp, keys, leafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
342
304
  const key = keys[0];
343
305
  const addKeys = keys.slice(1);
344
306
  const tmp = Object.create(null);
@@ -352,7 +314,7 @@ function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [],
352
314
  let n;
353
315
  for(i = 0, n = values.length; i < n; ++i){
354
316
  const v = values[i];
355
- const itemGroup = resolveGroup(v, treeLeafKey, resolvedAllGroups, resolvedGroupIndex);
317
+ const itemGroup = resolveGroup(v, leafKey, resolvedAllGroups, resolvedGroupIndex);
356
318
  if (!itemGroup) {
357
319
  return [];
358
320
  }
@@ -407,10 +369,8 @@ function index(values, key) {
407
369
  return key;
408
370
  }
409
371
  function sort(values, key) {
410
- if (key) {
372
+ {
411
373
  values.sort((a, b)=>+b[key] - +a[key]);
412
- } else {
413
- values.sort((a, b)=>+b - +a);
414
374
  }
415
375
  }
416
376
  function sum(values, key) {
@@ -418,7 +378,7 @@ function sum(values, key) {
418
378
  let i;
419
379
  let n;
420
380
  for(s = 0, i = 0, n = values.length; i < n; ++i){
421
- s += key ? +values[i][key] : +values[i];
381
+ s += +values[i][key] ;
422
382
  }
423
383
  return s;
424
384
  }
@@ -453,54 +413,51 @@ function compareAspectRatio(oldStat, newStat, args) {
453
413
  const nr = Math.max(l2 * newStat.nmax / ns2, ns2 / (l2 * newStat.nmin));
454
414
  return nr <= or;
455
415
  }
456
- function squarify(values, rectangle, keys = [], grp, lvl, gsum) {
457
- values = values || [];
458
- const rows = [];
459
- const rect = new Rect(rectangle);
460
- const row = new StatArray('value', rect.area / sum(values, keys[0]));
461
- let length = rect.side;
462
- const n = values.length;
463
- let i;
464
- let o;
465
- if (!n) {
466
- return rows;
467
- }
468
- const tmp = values.slice();
469
- const key = index(tmp, keys[0]);
470
- if (!rectangle?.unsorted) {
471
- sort(tmp, key);
472
- }
473
- const val = (idx)=>key ? +tmp[idx][key] : +tmp[idx];
474
- const gval = (idx)=>grp && tmp[idx][grp];
475
- for(i = 0; i < n; ++i){
476
- o = {
477
- _data: values[tmp[i]._idx],
478
- group: undefined,
479
- groupSum: gsum,
480
- level: undefined,
481
- value: val(i)
416
+ function toNodes(values, keys, grp, lvl) {
417
+ const items = values ? values.slice() : [];
418
+ const key = index(items, keys[0]);
419
+ return items.map((item)=>{
420
+ const node = {
421
+ _data: values[item._idx],
422
+ v: key ? +item[key] : +item,
423
+ vs: undefined
482
424
  };
483
425
  if (grp) {
484
- o.level = tmp[i].groupIndex ?? lvl;
485
- o.group = gval(i);
486
- const tmpRef = tmp[i];
487
- o.values = keys.reduce((obj, k)=>{
488
- obj[k] = +tmpRef[k];
426
+ node.g = item[grp];
427
+ node.gs = undefined;
428
+ node.l = item.groupIndex ?? lvl;
429
+ node.vs = keys.reduce((obj, k)=>{
430
+ obj[k] = +item[k];
489
431
  return obj;
490
432
  }, {});
491
433
  }
492
- o = row.pushIf(o, compareAspectRatio, length);
434
+ return node;
435
+ });
436
+ }
437
+ function sortNodes(nodes) {
438
+ sort(nodes, 'v');
439
+ }
440
+ function packInto(nodes, rectangle) {
441
+ const n = nodes.length;
442
+ if (!n) {
443
+ return;
444
+ }
445
+ const rect = new Rect(rectangle);
446
+ const row = new StatArray('v', rect.area / sum(nodes, 'v'));
447
+ let length = rect.side;
448
+ let o;
449
+ for(let i = 0; i < n; ++i){
450
+ o = row.pushIf(nodes[i], compareAspectRatio, length);
493
451
  if (o) {
494
- rows.push(rect.map(row));
452
+ rect.map(row);
495
453
  length = rect.side;
496
454
  row.reset();
497
455
  row.push(o);
498
456
  }
499
457
  }
500
458
  if (row.length) {
501
- rows.push(rect.map(row));
459
+ rect.map(row);
502
460
  }
503
- return flatten(rows);
504
461
  }
505
462
 
506
463
  const widthCache = new Map();
@@ -524,20 +481,42 @@ function getCaptionHeight(displayMode, rect, font, padding) {
524
481
  const captionHeight = font.lineHeight + padding * 2;
525
482
  return rect.h < 2 * captionHeight ? rect.h / 3 : captionHeight;
526
483
  }
527
- function drawText(ctx, rect, options, item, element) {
528
- const { captions, labels, displayMode } = options;
484
+ function drawText(ctx, rect, options, item, element, layout) {
485
+ const { captions, labels } = options;
486
+ const { displayMode } = layout;
487
+ const isLeaf = item && (!defined(item.l) || item.isLeaf);
488
+ let block;
489
+ if (isLeaf) {
490
+ if (labels.display) {
491
+ block = labelBlock(ctx, rect, options, item, element);
492
+ }
493
+ } else if (shouldDrawCaption(displayMode, rect, captions)) {
494
+ block = captionBlock(ctx, rect, options, item, element, layout);
495
+ }
496
+ if (!block) {
497
+ return;
498
+ }
529
499
  ctx.save();
530
500
  ctx.beginPath();
531
501
  ctx.rect(rect.x, rect.y, rect.w, rect.h);
532
502
  ctx.clip();
533
- const isLeaf = item && (!defined(item.l) || item.isLeaf);
534
- if (isLeaf && labels.display) {
535
- drawLabel(ctx, rect, options, item, element);
536
- } else if (!isLeaf && shouldDrawCaption(displayMode, rect, captions)) {
537
- drawCaption(ctx, rect, options, item, element);
538
- }
503
+ drawTextBlock(ctx, rect, block);
539
504
  ctx.restore();
540
505
  }
506
+ function drawTextBlock(ctx, rect, block) {
507
+ const { align, colors, fonts, lines, padding, top } = block;
508
+ const x = calculateX(rect, align, padding);
509
+ ctx.textAlign = align;
510
+ ctx.textBaseline = 'middle';
511
+ let offset = 0;
512
+ lines.forEach((line, i)=>{
513
+ const font = fonts[Math.min(i, fonts.length - 1)];
514
+ ctx.font = font.string;
515
+ ctx.fillStyle = colors[Math.min(i, colors.length - 1)];
516
+ ctx.fillText(line, x, top + offset + font.lineHeight / 2);
517
+ offset += font.lineHeight;
518
+ });
519
+ }
541
520
  function callbackContext(element, item) {
542
521
  const context = element.$context || {
543
522
  active: !!element.active,
@@ -554,16 +533,12 @@ function resolveOption(option, context) {
554
533
  function resolveCaptionText(captions, element, item) {
555
534
  return resolveOption(captions.formatter, callbackContext(element, item)) || item.g || '';
556
535
  }
557
- function drawCaption(ctx, rect, options, item, element) {
558
- const { captions, spacing, rtl, displayMode } = options;
536
+ function captionBlock(ctx, rect, options, item, element, layout) {
537
+ const { displayMode, rtl, spacing } = layout;
538
+ const { captions } = options;
559
539
  const { color, hoverColor, font, hoverFont, padding, align } = captions;
560
- const oColor = (rect.active ? hoverColor : color) || color;
561
- const oAlign = align || (rtl ? 'right' : 'left');
562
540
  const optFont = (rect.active ? hoverFont : font) || font;
563
541
  const oFont = toFont(optFont);
564
- const fonts = [
565
- oFont
566
- ];
567
542
  if (oFont.lineHeight > rect.h) {
568
543
  return;
569
544
  }
@@ -571,20 +546,62 @@ function drawCaption(ctx, rect, options, item, element) {
571
546
  if (!text) {
572
547
  return;
573
548
  }
574
- const captionSize = measureLabelSize(ctx, [
549
+ const fonts = [
550
+ oFont
551
+ ];
552
+ if (measureLabelSize(ctx, [
575
553
  text
576
- ], fonts);
577
- if (captionSize.width + 2 * padding > rect.w) {
554
+ ], fonts).width + 2 * padding > rect.w) {
578
555
  text = sliceTextToFitWidth(ctx, text, rect.w - 2 * padding, fonts);
579
556
  }
580
- const lh = oFont.lineHeight / 2;
581
- const x = calculateX(rect, oAlign, padding);
582
- ctx.fillStyle = oColor;
583
- ctx.font = oFont.string;
584
- ctx.textAlign = oAlign;
585
- ctx.textBaseline = 'middle';
586
- const y = displayMode === 'headerBoxes' ? rect.y + rect.h / 2 : rect.y + padding + spacing + lh;
587
- ctx.fillText(text, x, y);
557
+ const top = displayMode === 'headerBoxes' ? rect.y + (rect.h - oFont.lineHeight) / 2 : rect.y + padding + spacing;
558
+ return {
559
+ align: align || (rtl ? 'right' : 'left'),
560
+ colors: [
561
+ (rect.active ? hoverColor : color) || color
562
+ ],
563
+ fonts,
564
+ lines: [
565
+ text
566
+ ],
567
+ padding,
568
+ top
569
+ };
570
+ }
571
+ function labelBlock(ctx, rect, options, item, element) {
572
+ const labels = options.labels;
573
+ const content = resolveOption(labels.formatter, callbackContext(element, item));
574
+ if (!content) {
575
+ return;
576
+ }
577
+ const lines = isArray(content) ? content : [
578
+ content
579
+ ];
580
+ let fonts = getFontFromOptions(rect, labels);
581
+ let labelSize = measureLabelSize(ctx, lines, fonts);
582
+ const lblToDraw = labelToDraw(rect, labels, labelSize);
583
+ if (!lblToDraw) {
584
+ return;
585
+ }
586
+ if (isNumber(lblToDraw)) {
587
+ labelSize = {
588
+ height: labelSize.height * lblToDraw,
589
+ width: labelSize.width * lblToDraw
590
+ };
591
+ fonts = toFonts(fonts, lblToDraw);
592
+ }
593
+ const { color, hoverColor, align, padding } = labels;
594
+ const optColor = (rect.active ? hoverColor : color) || color;
595
+ return {
596
+ align,
597
+ colors: isArray(optColor) ? optColor : [
598
+ optColor
599
+ ],
600
+ fonts,
601
+ lines,
602
+ padding,
603
+ top: calculateBlockTop(rect, labels, labelSize)
604
+ };
588
605
  }
589
606
  function sliceTextToFitWidth(ctx, text, width, fonts) {
590
607
  const ellipsis = '...';
@@ -649,7 +666,7 @@ function toFonts(fonts, fitRatio) {
649
666
  });
650
667
  });
651
668
  }
652
- function labelToDraw(_ctx, rect, options, labelSize) {
669
+ function labelToDraw(rect, options, labelSize) {
653
670
  const { overflow, padding } = options;
654
671
  const { width, height } = labelSize;
655
672
  if (overflow === 'hidden') {
@@ -669,62 +686,15 @@ function getFontFromOptions(rect, labels) {
669
686
  toFont(optFont)
670
687
  ];
671
688
  }
672
- function drawLabel(ctx, rect, options, item, element) {
673
- const labels = options.labels;
674
- const content = resolveOption(labels.formatter, callbackContext(element, item));
675
- if (!content) {
676
- return;
677
- }
678
- const contents = isArray(content) ? content : [
679
- content
680
- ];
681
- let fonts = getFontFromOptions(rect, labels);
682
- let labelSize = measureLabelSize(ctx, contents, fonts);
683
- const lblToDraw = labelToDraw(ctx, rect, labels, labelSize);
684
- if (!lblToDraw) {
685
- return;
686
- }
687
- if (isNumber(lblToDraw)) {
688
- labelSize = {
689
- height: labelSize.height * lblToDraw,
690
- width: labelSize.width * lblToDraw
691
- };
692
- fonts = toFonts(fonts, lblToDraw);
693
- }
694
- const { color, hoverColor, align } = labels;
695
- const optColor = (rect.active ? hoverColor : color) || color;
696
- const colors = isArray(optColor) ? optColor : [
697
- optColor
698
- ];
699
- const xyPoint = calculateXYLabel(rect, labels, labelSize);
700
- ctx.textAlign = align;
701
- ctx.textBaseline = 'middle';
702
- let lhs = 0;
703
- contents.forEach((l, i)=>{
704
- const c = colors[Math.min(i, colors.length - 1)];
705
- const f = fonts[Math.min(i, fonts.length - 1)];
706
- const lh = f.lineHeight;
707
- ctx.font = f.string;
708
- ctx.fillStyle = c;
709
- ctx.fillText(l, xyPoint.x, xyPoint.y + lh / 2 + lhs);
710
- lhs += lh;
711
- });
712
- }
713
- function calculateXYLabel(rect, options, labelSize) {
714
- const { align, position, padding } = options;
715
- const x = calculateX(rect, align, padding);
716
- let y;
689
+ function calculateBlockTop(rect, options, labelSize) {
690
+ const { position, padding } = options;
717
691
  if (position === 'top') {
718
- y = rect.y + padding;
719
- } else if (position === 'bottom') {
720
- y = rect.y + rect.h - padding - labelSize.height;
721
- } else {
722
- y = rect.y + (rect.h - labelSize.height) / 2 + padding;
692
+ return rect.y + padding;
723
693
  }
724
- return {
725
- x,
726
- y
727
- };
694
+ if (position === 'bottom') {
695
+ return rect.y + rect.h - padding - labelSize.height;
696
+ }
697
+ return rect.y + (rect.h - labelSize.height) / 2 + padding;
728
698
  }
729
699
  function calculateX(rect, align, padding) {
730
700
  if (align === 'left') {
@@ -735,76 +705,109 @@ function calculateX(rect, align, padding) {
735
705
  return rect.x + rect.w / 2;
736
706
  }
737
707
 
738
- function buildData(tree, dataset, keys, mainRect) {
739
- const treeLeafKey = dataset.treeLeafKey || '_leaf';
740
- if (isObject(tree)) {
741
- tree = normalizeTreeToArray(keys, treeLeafKey, tree);
742
- }
743
- const groups = dataset.groups || [];
708
+ function buildHierarchy(values, gidx, keys, layout, parentGroupValue) {
709
+ const { groups, leafKey, unsorted } = layout;
744
710
  const glen = groups.length;
745
- const sp = dataset.displayMode === 'headerBoxes' ? 0 : valueOrDefault(dataset.spacing, 0);
746
- const captions = dataset.captions || {};
711
+ const g = getGroupKey(groups[gidx]);
712
+ const gdata = group(values, g, keys, leafKey, undefined, parentGroupValue, groups.filter((_item, index)=>index <= gidx), groups, gidx);
713
+ const nodes = toNodes(gdata, keys, g, gidx);
714
+ if (!unsorted) {
715
+ sortNodes(nodes);
716
+ }
717
+ for (const node of nodes){
718
+ const record = node._data;
719
+ const nextGroupIndex = record.groupIndex + 1;
720
+ const children = gidx < glen - 1 && nextGroupIndex < glen ? buildHierarchy(record.children, nextGroupIndex, keys, layout, node.g) : [];
721
+ Object.defineProperty(node, '_children', {
722
+ configurable: true,
723
+ value: children,
724
+ writable: true
725
+ });
726
+ node.isLeaf = !children.length;
727
+ }
728
+ return nodes;
729
+ }
730
+ function flattenNodes(nodes) {
731
+ const flat = nodes.slice();
732
+ for (const node of nodes){
733
+ if (node._children?.length) {
734
+ flat.push(...flattenNodes(node._children));
735
+ }
736
+ }
737
+ return flat;
738
+ }
739
+ function layoutLevel(nodes, rect, layout, parentSum) {
740
+ if (parentSum !== undefined) {
741
+ for (const node of nodes){
742
+ node.gs = parentSum;
743
+ }
744
+ }
745
+ packInto(nodes, rect);
746
+ for (const node of nodes){
747
+ if (node._children?.length) {
748
+ layoutLevel(node._children, getSubRect(node, rect, layout), layout, node.s);
749
+ }
750
+ }
751
+ }
752
+ function layoutNodes(nodes, rect, layout) {
753
+ layoutLevel(nodes, rect, layout);
754
+ applyHeaderBoxes(flattenNodes(nodes), layout);
755
+ }
756
+ function getSubRect(sq, rect, layout) {
757
+ const { borderWidth, captions, displayMode } = layout;
758
+ const sp = displayMode === 'headerBoxes' ? 0 : layout.spacing;
759
+ const bw = displayMode === 'headerBoxes' ? {
760
+ b: 0,
761
+ l: 0,
762
+ r: 0,
763
+ t: 0
764
+ } : parseBorderWidth(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(displayMode, subRect, captions)) {
773
+ const captionHeight = getCaptionHeight(displayMode, subRect, toFont(captions.font), valueOrDefault(captions.padding, 3));
774
+ subRect.y += captionHeight;
775
+ subRect.h -= captionHeight;
776
+ }
777
+ return subRect;
778
+ }
779
+ function buildNodes(tree, keys, layout) {
780
+ const { groups, leafKey, unsorted } = layout;
781
+ const input = isObject(tree) ? normalizeTreeToArray(keys, leafKey, tree) : tree;
782
+ if (groups.length) {
783
+ return buildHierarchy(input, 0, keys, layout);
784
+ }
785
+ const nodes = toNodes(input || [], keys);
786
+ if (!unsorted) {
787
+ sortNodes(nodes);
788
+ }
789
+ return nodes;
790
+ }
791
+ function applyHeaderBoxes(nodes, layout) {
792
+ const { captions, displayMode } = layout;
793
+ if (displayMode !== 'headerBoxes') {
794
+ return;
795
+ }
747
796
  const font = toFont(captions.font);
748
797
  const padding = valueOrDefault(captions.padding, 3);
749
- function getSubRect(sq, rect) {
750
- const bw = dataset.displayMode === 'headerBoxes' ? {
751
- b: 0,
752
- l: 0,
753
- r: 0,
754
- t: 0
755
- } : parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
756
- const subRect = {
757
- ...rect,
758
- h: sq.h - 2 * sp - bw.t - bw.b,
759
- w: sq.w - 2 * sp - bw.l - bw.r,
760
- x: sq.x + sp + bw.l,
761
- y: sq.y + sp + bw.t
762
- };
763
- if (shouldDrawCaption(dataset.displayMode, subRect, captions)) {
764
- const captionHeight = getCaptionHeight(dataset.displayMode, subRect, font, padding);
765
- subRect.y += captionHeight;
766
- subRect.h -= captionHeight;
798
+ for (const node of nodes){
799
+ if (node.isLeaf) {
800
+ continue;
767
801
  }
768
- return subRect;
769
- }
770
- function recur(treeElements, gidx, rect, parent, gs) {
771
- const g = getGroupKey(groups[gidx]);
772
- const gdata = group(treeElements, g, keys, treeLeafKey, undefined, parent, groups.filter((_item, index)=>index <= gidx), groups, gidx);
773
- const gsq = squarify(gdata, rect, keys, g, gidx, gs);
774
- const ret = gsq.slice();
775
- if (gidx < glen - 1) {
776
- gsq.forEach((sq)=>{
777
- const subRect = getSubRect(sq, rect);
778
- const children = [];
779
- const nextGroupIndex = sq._data.groupIndex + 1;
780
- if (nextGroupIndex < glen) {
781
- children.push(...recur(sq._data.children, nextGroupIndex, subRect, sq.g, sq.s));
782
- }
783
- ret.push(...children);
784
- sq.isLeaf = !children.length;
785
- });
802
+ if (shouldDrawCaption(displayMode, node, captions)) {
803
+ node.h = getCaptionHeight(displayMode, node, font, padding);
804
+ node.hidden = false;
786
805
  } else {
787
- gsq.forEach((sq)=>{
788
- sq.isLeaf = true;
789
- });
806
+ node.hidden = true;
790
807
  }
791
- return ret;
792
808
  }
793
- const result = glen ? recur(tree, 0, mainRect) : squarify(tree, mainRect, keys);
794
- return result.map((d)=>{
795
- if (dataset.displayMode !== 'headerBoxes' || d.isLeaf) {
796
- return d;
797
- }
798
- if (!shouldDrawCaption(dataset.displayMode, d, captions)) {
799
- return undefined;
800
- }
801
- const captionHeight = getCaptionHeight(dataset.displayMode, d, font, padding);
802
- return {
803
- ...d,
804
- h: captionHeight
805
- };
806
- }).filter(Boolean);
807
809
  }
810
+
808
811
  function registerTooltipPositioner() {
809
812
  const tooltipPlugin = registry.plugins.get('tooltip');
810
813
  if (!tooltipPlugin || tooltipPlugin.positioners.treemap) {
@@ -857,42 +860,101 @@ class TreemapController extends DatasetController {
857
860
  yScale.configure();
858
861
  }
859
862
  }
860
- update(mode) {
863
+ _layoutOptions() {
861
864
  const dataset = this.getDataset();
862
- const { data } = this.getMeta();
863
- const groups = dataset.groups || [];
865
+ const options = this.options;
866
+ return {
867
+ borderWidth: dataset.borderWidth,
868
+ captions: dataset.captions || {},
869
+ displayMode: options.displayMode,
870
+ groups: options.groups || [],
871
+ leafKey: options.leafKey,
872
+ spacing: valueOrDefault(dataset.spacing, 0),
873
+ unsorted: !!options.unsorted
874
+ };
875
+ }
876
+ buildOrUpdateElements(_resetNewElements) {
877
+ this.parse(0, 0);
878
+ const meta = this.getMeta();
879
+ const count = meta._parsed.length;
880
+ const elements = meta.data;
881
+ if (elements.length > count) {
882
+ elements.splice(count, elements.length - count);
883
+ }
884
+ while(elements.length < count){
885
+ elements.push(new this.dataElementType());
886
+ }
887
+ }
888
+ parse(_start, _count) {
889
+ const dataset = this.getDataset();
890
+ if (dataset.tree !== undefined) {
891
+ throw new Error('chartjs-chart-treemap v5: the "tree" option was renamed to "data". ' + 'See https://chartjs-chart-treemap.pages.dev/usage/');
892
+ }
893
+ const options = this.options;
864
894
  const keys = [
865
- dataset.key || ''
866
- ].concat(dataset.sumKeys || []);
867
- dataset.tree = dataset.tree || dataset.data || [];
868
- const tree = dataset.tree;
869
- const treeVersion = dataset.treeVersion;
895
+ options.key || ''
896
+ ].concat(options.sumKeys || []);
897
+ const nodes = buildNodes(dataset.data || [], keys, this._layoutOptions());
898
+ const flat = flattenNodes(nodes);
899
+ const fingerprint = flat.map((node)=>`${node.v}\u0000${node.g}\u0000${node.l}`).join('\u0001');
900
+ if (this._fingerprint === fingerprint) {
901
+ return;
902
+ }
903
+ this._fingerprint = fingerprint;
904
+ this._nodes = nodes;
905
+ this.getMeta()._parsed = flat;
906
+ this._layoutDirty = true;
907
+ }
908
+ getContext(index, active, mode) {
909
+ const base = DatasetController.prototype.getContext;
910
+ const context = base.call(this, index, active, mode);
911
+ const parsed = typeof index === 'number' ? this.getParsed(index) : undefined;
912
+ if (parsed) {
913
+ context.raw = parsed;
914
+ context.parsed = parsed;
915
+ }
916
+ return context;
917
+ }
918
+ getLabelAndValue(index) {
919
+ const node = this.getParsed(index);
920
+ if (!node) {
921
+ return {
922
+ label: '',
923
+ value: ''
924
+ };
925
+ }
926
+ const dataset = this.getDataset();
927
+ const label = node.g ?? node._data?.label ?? dataset.label;
928
+ return {
929
+ label: label === undefined ? '' : `${label}`,
930
+ value: `${node.v}`
931
+ };
932
+ }
933
+ update(mode) {
934
+ const { data } = this.getMeta();
870
935
  if (mode === 'reset') {
871
936
  this.configure();
872
937
  }
873
- if (this._rectChanged || arrayNotEqual(this._keys || [], keys) || arrayNotEqual(this._groups || [], groups) || this._prevTree !== tree || this._prevTreeVersion !== treeVersion) {
874
- this._groups = groups.slice();
875
- this._keys = keys.slice();
876
- this._prevTree = tree;
877
- this._prevTreeVersion = treeVersion;
938
+ if (this._layoutDirty || this._rectChanged) {
939
+ layoutNodes(this._nodes, this._rect, this._layoutOptions());
940
+ this._layoutDirty = false;
878
941
  this._rectChanged = false;
879
- dataset.data = buildData(tree, dataset, this._keys, this._rect);
880
- this._dataCheck();
881
- this._resyncElements();
882
942
  }
883
943
  this.updateElements(data, 0, data.length, mode);
884
944
  }
885
945
  updateElements(rects, start, count, mode) {
886
946
  const reset = mode === 'reset';
887
- const dataset = this.getDataset();
888
947
  const firstOpts = this.resolveDataElementOptions(start, mode);
889
948
  this._rect.options = firstOpts;
890
949
  const sharedOptions = this.getSharedOptions(firstOpts);
891
950
  const includeOptions = this.includeOptions(mode, sharedOptions || {});
892
951
  const { xScale, yScale } = this.getMeta();
952
+ const spacing = this.options.spacing;
893
953
  for(let i = start; i < start + count; i++){
894
954
  const options = sharedOptions || this.resolveDataElementOptions(i, mode);
895
- const properties = scaleRect(dataset.data[i], xScale, yScale, options.spacing);
955
+ const node = this.getParsed(i);
956
+ const properties = scaleRect(node, xScale, yScale, spacing);
957
+ properties.hidden = properties.hidden || !!node.hidden;
896
958
  if (reset) {
897
959
  properties.width = 0;
898
960
  properties.height = 0;
@@ -907,23 +969,28 @@ class TreemapController extends DatasetController {
907
969
  draw() {
908
970
  const { ctx, chartArea } = this.chart;
909
971
  const metadata = this.getMeta().data || [];
910
- const dataset = this.getDataset();
911
- const data = dataset.data;
972
+ const { displayMode, rtl, spacing } = this.options;
973
+ const layout = {
974
+ displayMode,
975
+ rtl,
976
+ spacing
977
+ };
912
978
  clipArea(ctx, chartArea);
913
979
  for(let i = 0, ilen = metadata.length; i < ilen; ++i){
914
980
  const rect = metadata[i];
915
981
  if (!rect.hidden) {
916
- rect.draw(ctx, data[i]);
982
+ rect.draw(ctx, this.getParsed(i), layout);
917
983
  }
918
984
  }
919
985
  unclipArea(ctx);
920
986
  }
921
987
  constructor(chart, datasetIndex){
922
988
  super(chart, datasetIndex);
923
- this._groups = undefined;
924
- this._keys = undefined;
925
989
  this._rect = undefined;
926
990
  this._rectChanged = true;
991
+ this._nodes = [];
992
+ this._layoutDirty = true;
993
+ this._fingerprint = undefined;
927
994
  }
928
995
  }
929
996
  TreemapController.id = 'treemap';
@@ -940,7 +1007,15 @@ TreemapController.defaults = {
940
1007
  type: 'number'
941
1008
  }
942
1009
  },
943
- dataElementType: 'treemap'
1010
+ dataElementType: 'treemap',
1011
+ displayMode: layoutDefaults.displayMode,
1012
+ groups: [],
1013
+ key: '',
1014
+ leafKey: '_leaf',
1015
+ rtl: layoutDefaults.rtl,
1016
+ spacing: layoutDefaults.spacing,
1017
+ sumKeys: [],
1018
+ unsorted: false
944
1019
  };
945
1020
  TreemapController.descriptors = {
946
1021
  _indexable: false,
@@ -965,10 +1040,9 @@ TreemapController.overrides = {
965
1040
  tooltip: {
966
1041
  callbacks: {
967
1042
  label (item) {
968
- const dataset = item.dataset;
969
- const dataItem = dataset.data[item.dataIndex];
970
- const label = dataItem.g || dataItem._data.label || dataset.label;
971
- return (label ? `${label}: ` : '') + dataItem.v;
1043
+ const node = item.parsed;
1044
+ const label = node.g || node._data?.label || item.dataset.label;
1045
+ return (label ? `${label}: ` : '') + node.v;
972
1046
  },
973
1047
  title (items) {
974
1048
  if (items.length) {
@@ -1070,7 +1144,7 @@ function addNormalRectPath(ctx, rect) {
1070
1144
  }
1071
1145
 
1072
1146
  class TreemapElement extends Element {
1073
- draw(ctx, data) {
1147
+ draw(ctx, data, layout = layoutDefaults) {
1074
1148
  if (!data) {
1075
1149
  return;
1076
1150
  }
@@ -1090,7 +1164,7 @@ class TreemapElement extends Element {
1090
1164
  addRectPath(ctx, inner);
1091
1165
  ctx.fillStyle = options.backgroundColor;
1092
1166
  ctx.fill();
1093
- drawText(ctx, inner, options, data, this);
1167
+ drawText(ctx, inner, options, data, this, layout);
1094
1168
  ctx.restore();
1095
1169
  }
1096
1170
  inRange(mouseX, mouseY, useFinalPosition) {
@@ -1141,8 +1215,6 @@ TreemapElement.defaults = {
1141
1215
  },
1142
1216
  padding: 3
1143
1217
  },
1144
- displayMode: 'containerBoxes',
1145
- label: undefined,
1146
1218
  labels: {
1147
1219
  align: 'center',
1148
1220
  color: 'black',
@@ -1164,10 +1236,7 @@ TreemapElement.defaults = {
1164
1236
  overflow: 'cut',
1165
1237
  padding: 3,
1166
1238
  position: 'middle'
1167
- },
1168
- rtl: false,
1169
- spacing: 0.5,
1170
- unsorted: false
1239
+ }
1171
1240
  };
1172
1241
  TreemapElement.descriptors = {
1173
1242
  _indexable: false,
@@ -1186,4 +1255,4 @@ TreemapElement.defaultRoutes = {
1186
1255
  borderColor: 'borderColor'
1187
1256
  };
1188
1257
 
1189
- export { TreemapController, TreemapElement };
1258
+ export { TreemapController, TreemapElement, flattenTree };