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,5 +1,5 @@
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
@@ -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.2";
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 buildRow(rect, itm, dims, sum) {
91
- const r = {
92
- _data: itm._data,
93
- a: itm._normalized,
94
- h: dims.h,
95
- s: sum,
96
- v: itm.value,
97
- vs: itm.values,
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
- ret.push(buildRow(this, itm, dims, arr.sum));
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, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
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[treeLeafKey] = tree[objIndex];
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, treeLeafKey, child, tree, lvl + 1, result);
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 normalizeTreeToArray(keys, treeLeafKey, obj) {
267
- const data = scanTreeObject(keys, treeLeafKey, obj);
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, treeLeafKey, allGroups, groupIndex) {
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[treeLeafKey])) {
299
+ if (hasGroupValue(value[leafKey])) {
323
300
  return {
324
- group: treeLeafKey,
301
+ group: leafKey,
325
302
  groupIndex,
326
- value: value[treeLeafKey]
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, treeLeafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
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, treeLeafKey, resolvedAllGroups, resolvedGroupIndex);
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 squarify(values, rectangle, keys = [], grp, lvl, gsum) {
466
- values = values || [];
467
- const rows = [];
468
- const rect = new Rect(rectangle);
469
- const row = new StatArray('value', rect.area / sum(values, keys[0]));
470
- let length = rect.side;
471
- const n = values.length;
472
- let i;
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
- o.level = tmp[i].groupIndex ?? lvl;
494
- o.group = gval(i);
495
- const tmpRef = tmp[i];
496
- o.values = keys.reduce((obj, k)=>{
497
- obj[k] = +tmpRef[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
- o = row.pushIf(o, compareAspectRatio, length);
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
- rows.push(rect.map(row));
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
- rows.push(rect.map(row));
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, displayMode } = options;
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
- const isLeaf = item && (!helpers.defined(item.l) || item.isLeaf);
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 drawCaption(ctx, rect, options, item, element) {
567
- const { captions, spacing, rtl, displayMode } = options;
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 captionSize = measureLabelSize(ctx, [
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 lh = oFont.lineHeight / 2;
590
- const x = calculateX(rect, oAlign, padding);
591
- ctx.fillStyle = oColor;
592
- ctx.font = oFont.string;
593
- ctx.textAlign = oAlign;
594
- ctx.textBaseline = 'middle';
595
- const y = displayMode === 'headerBoxes' ? rect.y + rect.h / 2 : rect.y + padding + spacing + lh;
596
- ctx.fillText(text, x, y);
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(_ctx, rect, options, labelSize) {
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 drawLabel(ctx, rect, options, item, element) {
682
- const labels = options.labels;
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
- y = rect.y + padding;
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
- return {
734
- x,
735
- y
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 buildData(tree, dataset, keys, mainRect) {
748
- const treeLeafKey = dataset.treeLeafKey || '_leaf';
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 sp = dataset.displayMode === 'headerBoxes' ? 0 : helpers.valueOrDefault(dataset.spacing, 0);
755
- const captions = dataset.captions || {};
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
- function getSubRect(sq, rect) {
759
- const bw = dataset.displayMode === 'headerBoxes' ? {
760
- b: 0,
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
- return subRect;
778
- }
779
- function recur(treeElements, gidx, rect, parent, gs) {
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
- gsq.forEach((sq)=>{
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
- update(mode) {
889
+ _layoutOptions() {
870
890
  const dataset = this.getDataset();
871
- const { data } = this.getMeta();
872
- const groups = dataset.groups || [];
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
- dataset.key || ''
875
- ].concat(dataset.sumKeys || []);
876
- dataset.tree = dataset.tree || dataset.data || [];
877
- const tree = dataset.tree;
878
- const treeVersion = dataset.treeVersion;
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._rectChanged || arrayNotEqual(this._keys || [], keys) || arrayNotEqual(this._groups || [], groups) || this._prevTree !== tree || this._prevTreeVersion !== treeVersion) {
883
- this._groups = groups.slice();
884
- this._keys = keys.slice();
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 properties = scaleRect(dataset.data[i], xScale, yScale, options.spacing);
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 dataset = this.getDataset();
920
- const data = dataset.data;
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, data[i]);
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 dataset = item.dataset;
978
- const dataItem = dataset.data[item.dataIndex];
979
- const label = dataItem.g || dataItem._data.label || dataset.label;
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;