chartjs-chart-treemap 5.0.0-next.3 → 5.0.0-next.5

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.3
2
+ * chartjs-chart-treemap v5.0.0-next.5
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.3";
13
+ var version = "5.0.0-next.5";
14
14
 
15
15
  function scaleRect(sq, xScale, yScale, sp) {
16
16
  const sp2 = sp * 2;
@@ -29,25 +29,6 @@ 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
 
52
33
  const layoutDefaults = {
53
34
  displayMode: 'containerBoxes',
@@ -92,24 +73,14 @@ function getDims(itm, w2, s2, key) {
92
73
  };
93
74
  }
94
75
  const getX = (rect, w)=>rect.rtl ? rect.x + rect.iw - w : rect.x + rect._ix;
95
- function buildRow(rect, itm, dims, sum) {
96
- const r = {
97
- _data: itm._data,
98
- a: itm._normalized,
99
- h: dims.h,
100
- s: sum,
101
- v: itm.value,
102
- vs: itm.values,
103
- w: dims.w,
104
- x: getX(rect, dims.w),
105
- y: rect.y + rect._iy
106
- };
107
- if (itm.group) {
108
- r.g = itm.group;
109
- r.l = itm.level;
110
- r.gs = itm.groupSum;
111
- }
112
- 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;
113
84
  }
114
85
  class Rect {
115
86
  get area() {
@@ -135,19 +106,17 @@ class Rect {
135
106
  const row = arr.get();
136
107
  const w2 = side * side;
137
108
  const s2 = sum * sum;
138
- const ret = [];
139
109
  let maxd2 = 0;
140
110
  let totd1 = 0;
141
111
  for (const itm of row){
142
112
  const dims = getDims(itm, w2, s2, key);
143
113
  totd1 += dims.d1;
144
114
  maxd2 = Math.max(maxd2, dims.d2);
145
- ret.push(buildRow(this, itm, dims, arr.sum));
115
+ assignRow(this, itm, dims, arr.sum);
146
116
  this[key] += dims.d1;
147
117
  }
148
118
  this[dir === 'x' ? '_iy' : '_ix'] += maxd2;
149
119
  this[key] -= totd1;
150
- return ret;
151
120
  }
152
121
  constructor(r){
153
122
  r = r || {
@@ -267,6 +236,9 @@ function scanTreeObject(keys, leafKey, obj, tree = [], lvl = 0, result = []) {
267
236
  }
268
237
  tree.splice(objIndex, 1);
269
238
  return result;
239
+ }
240
+ function flattenTree(tree, keys, leafKey) {
241
+ return normalizeTreeToArray(keys, leafKey, tree);
270
242
  }
271
243
  function normalizeTreeToArray(keys, leafKey, obj) {
272
244
  const data = scanTreeObject(keys, leafKey, obj);
@@ -467,54 +439,51 @@ function compareAspectRatio(oldStat, newStat, args) {
467
439
  const nr = Math.max(l2 * newStat.nmax / ns2, ns2 / (l2 * newStat.nmin));
468
440
  return nr <= or;
469
441
  }
470
- function squarify(values, rectangle, keys = [], grp, lvl, gsum) {
471
- values = values || [];
472
- const rows = [];
473
- const rect = new Rect(rectangle);
474
- const row = new StatArray('value', rect.area / sum(values, keys[0]));
475
- let length = rect.side;
476
- const n = values.length;
477
- let i;
478
- let o;
479
- if (!n) {
480
- return rows;
481
- }
482
- const tmp = values.slice();
483
- const key = index(tmp, keys[0]);
484
- if (!rectangle?.unsorted) {
485
- sort(tmp, key);
486
- }
487
- const val = (idx)=>key ? +tmp[idx][key] : +tmp[idx];
488
- const gval = (idx)=>grp && tmp[idx][grp];
489
- for(i = 0; i < n; ++i){
490
- o = {
491
- _data: values[tmp[i]._idx],
492
- group: undefined,
493
- groupSum: gsum,
494
- level: undefined,
495
- 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
496
450
  };
497
451
  if (grp) {
498
- o.level = tmp[i].groupIndex ?? lvl;
499
- o.group = gval(i);
500
- const tmpRef = tmp[i];
501
- o.values = keys.reduce((obj, k)=>{
502
- 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];
503
457
  return obj;
504
458
  }, {});
505
459
  }
506
- 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);
507
477
  if (o) {
508
- rows.push(rect.map(row));
478
+ rect.map(row);
509
479
  length = rect.side;
510
480
  row.reset();
511
481
  row.push(o);
512
482
  }
513
483
  }
514
484
  if (row.length) {
515
- rows.push(rect.map(row));
485
+ rect.map(row);
516
486
  }
517
- return flatten(rows);
518
487
  }
519
488
 
520
489
  const widthCache = new Map();
@@ -541,10 +510,6 @@ function getCaptionHeight(displayMode, rect, font, padding) {
541
510
  function drawText(ctx, rect, options, item, element, layout) {
542
511
  const { captions, labels } = options;
543
512
  const { displayMode } = layout;
544
- ctx.save();
545
- ctx.beginPath();
546
- ctx.rect(rect.x, rect.y, rect.w, rect.h);
547
- ctx.clip();
548
513
  const isLeaf = item && (!helpers.defined(item.l) || item.isLeaf);
549
514
  let block;
550
515
  if (isLeaf) {
@@ -554,9 +519,14 @@ function drawText(ctx, rect, options, item, element, layout) {
554
519
  } else if (shouldDrawCaption(displayMode, rect, captions)) {
555
520
  block = captionBlock(ctx, rect, options, item, element, layout);
556
521
  }
557
- if (block) {
558
- drawTextBlock(ctx, rect, block);
522
+ if (!block) {
523
+ return;
559
524
  }
525
+ ctx.save();
526
+ ctx.beginPath();
527
+ ctx.rect(rect.x, rect.y, rect.w, rect.h);
528
+ ctx.clip();
529
+ drawTextBlock(ctx, rect, block);
560
530
  ctx.restore();
561
531
  }
562
532
  function drawTextBlock(ctx, rect, block) {
@@ -761,74 +731,109 @@ function calculateX(rect, align, padding) {
761
731
  return rect.x + rect.w / 2;
762
732
  }
763
733
 
764
- function buildData(tree, keys, mainRect, layout) {
765
- const { borderWidth, captions, displayMode, groups, leafKey } = layout;
766
- if (helpers.isObject(tree)) {
767
- tree = normalizeTreeToArray(keys, leafKey, tree);
768
- }
734
+ function buildHierarchy(values, gidx, keys, layout, parentGroupValue) {
735
+ const { groups, leafKey, unsorted } = layout;
769
736
  const glen = groups.length;
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;
770
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
+ }
771
822
  const font = helpers.toFont(captions.font);
772
823
  const padding = helpers.valueOrDefault(captions.padding, 3);
773
- function getSubRect(sq, rect) {
774
- const bw = displayMode === 'headerBoxes' ? {
775
- b: 0,
776
- l: 0,
777
- r: 0,
778
- t: 0
779
- } : parseBorderWidth(borderWidth, sq.w / 2, sq.h / 2);
780
- const subRect = {
781
- ...rect,
782
- h: sq.h - 2 * sp - bw.t - bw.b,
783
- w: sq.w - 2 * sp - bw.l - bw.r,
784
- x: sq.x + sp + bw.l,
785
- y: sq.y + sp + bw.t
786
- };
787
- if (shouldDrawCaption(displayMode, subRect, captions)) {
788
- const captionHeight = getCaptionHeight(displayMode, subRect, font, padding);
789
- subRect.y += captionHeight;
790
- subRect.h -= captionHeight;
824
+ for (const node of nodes){
825
+ if (node.isLeaf) {
826
+ continue;
791
827
  }
792
- return subRect;
793
- }
794
- function recur(treeElements, gidx, rect, parent, gs) {
795
- const g = getGroupKey(groups[gidx]);
796
- const gdata = group(treeElements, g, keys, leafKey, undefined, parent, groups.filter((_item, index)=>index <= gidx), groups, gidx);
797
- const gsq = squarify(gdata, rect, keys, g, gidx, gs);
798
- const ret = gsq.slice();
799
- if (gidx < glen - 1) {
800
- gsq.forEach((sq)=>{
801
- const subRect = getSubRect(sq, rect);
802
- const children = [];
803
- const nextGroupIndex = sq._data.groupIndex + 1;
804
- if (nextGroupIndex < glen) {
805
- children.push(...recur(sq._data.children, nextGroupIndex, subRect, sq.g, sq.s));
806
- }
807
- ret.push(...children);
808
- sq.isLeaf = !children.length;
809
- });
828
+ if (shouldDrawCaption(displayMode, node, captions)) {
829
+ node.h = getCaptionHeight(displayMode, node, font, padding);
830
+ node.hidden = false;
810
831
  } else {
811
- gsq.forEach((sq)=>{
812
- sq.isLeaf = true;
813
- });
832
+ node.hidden = true;
814
833
  }
815
- return ret;
816
834
  }
817
- const result = glen ? recur(tree, 0, mainRect) : squarify(tree, mainRect, keys);
818
- return result.map((d)=>{
819
- if (displayMode !== 'headerBoxes' || d.isLeaf) {
820
- return d;
821
- }
822
- if (!shouldDrawCaption(displayMode, d, captions)) {
823
- return undefined;
824
- }
825
- const captionHeight = getCaptionHeight(displayMode, d, font, padding);
826
- return {
827
- ...d,
828
- h: captionHeight
829
- };
830
- }).filter(Boolean);
831
835
  }
836
+
832
837
  function registerTooltipPositioner() {
833
838
  const tooltipPlugin = chart_js.registry.plugins.get('tooltip');
834
839
  if (!tooltipPlugin || tooltipPlugin.positioners.treemap) {
@@ -890,38 +895,81 @@ class TreemapController extends chart_js.DatasetController {
890
895
  displayMode: options.displayMode,
891
896
  groups: options.groups || [],
892
897
  leafKey: options.leafKey,
893
- spacing: helpers.valueOrDefault(dataset.spacing, 0)
898
+ spacing: helpers.valueOrDefault(dataset.spacing, 0),
899
+ unsorted: !!options.unsorted
894
900
  };
895
901
  }
896
- update(mode) {
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) {
897
915
  const dataset = this.getDataset();
898
- const { data } = this.getMeta();
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
+ }
899
919
  const options = this.options;
900
- const groups = options.groups || [];
901
920
  const keys = [
902
921
  options.key || ''
903
922
  ].concat(options.sumKeys || []);
904
- dataset.tree = dataset.tree || dataset.data || [];
905
- const tree = dataset.tree;
906
- const treeVersion = dataset.treeVersion;
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();
907
961
  if (mode === 'reset') {
908
962
  this.configure();
909
963
  }
910
- if (this._rectChanged || arrayNotEqual(this._keys || [], keys) || arrayNotEqual(this._groups || [], groups) || this._prevTree !== tree || this._prevTreeVersion !== treeVersion) {
911
- this._groups = groups.slice();
912
- this._keys = keys.slice();
913
- this._prevTree = tree;
914
- this._prevTreeVersion = treeVersion;
964
+ if (this._layoutDirty || this._rectChanged) {
965
+ layoutNodes(this._nodes, this._rect, this._layoutOptions());
966
+ this._layoutDirty = false;
915
967
  this._rectChanged = false;
916
- dataset.data = buildData(tree, this._keys, this._rect, this._layoutOptions());
917
- this._dataCheck();
918
- this._resyncElements();
919
968
  }
920
969
  this.updateElements(data, 0, data.length, mode);
921
970
  }
922
971
  updateElements(rects, start, count, mode) {
923
972
  const reset = mode === 'reset';
924
- const dataset = this.getDataset();
925
973
  const firstOpts = this.resolveDataElementOptions(start, mode);
926
974
  this._rect.options = firstOpts;
927
975
  const sharedOptions = this.getSharedOptions(firstOpts);
@@ -930,7 +978,9 @@ class TreemapController extends chart_js.DatasetController {
930
978
  const spacing = this.options.spacing;
931
979
  for(let i = start; i < start + count; i++){
932
980
  const options = sharedOptions || this.resolveDataElementOptions(i, mode);
933
- const properties = scaleRect(dataset.data[i], xScale, yScale, spacing);
981
+ const node = this.getParsed(i);
982
+ const properties = scaleRect(node, xScale, yScale, spacing);
983
+ properties.hidden = properties.hidden || !!node.hidden;
934
984
  if (reset) {
935
985
  properties.width = 0;
936
986
  properties.height = 0;
@@ -945,8 +995,6 @@ class TreemapController extends chart_js.DatasetController {
945
995
  draw() {
946
996
  const { ctx, chartArea } = this.chart;
947
997
  const metadata = this.getMeta().data || [];
948
- const dataset = this.getDataset();
949
- const data = dataset.data;
950
998
  const { displayMode, rtl, spacing } = this.options;
951
999
  const layout = {
952
1000
  displayMode,
@@ -957,17 +1005,18 @@ class TreemapController extends chart_js.DatasetController {
957
1005
  for(let i = 0, ilen = metadata.length; i < ilen; ++i){
958
1006
  const rect = metadata[i];
959
1007
  if (!rect.hidden) {
960
- rect.draw(ctx, data[i], layout);
1008
+ rect.draw(ctx, this.getParsed(i), layout);
961
1009
  }
962
1010
  }
963
1011
  helpers.unclipArea(ctx);
964
1012
  }
965
1013
  constructor(chart, datasetIndex){
966
1014
  super(chart, datasetIndex);
967
- this._groups = undefined;
968
- this._keys = undefined;
969
1015
  this._rect = undefined;
970
1016
  this._rectChanged = true;
1017
+ this._nodes = [];
1018
+ this._layoutDirty = true;
1019
+ this._fingerprint = undefined;
971
1020
  }
972
1021
  }
973
1022
  TreemapController.id = 'treemap';
@@ -1017,10 +1066,9 @@ TreemapController.overrides = {
1017
1066
  tooltip: {
1018
1067
  callbacks: {
1019
1068
  label (item) {
1020
- const dataset = item.dataset;
1021
- const dataItem = dataset.data[item.dataIndex];
1022
- const label = dataItem.g || dataItem._data.label || dataset.label;
1023
- 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;
1024
1072
  },
1025
1073
  title (items) {
1026
1074
  if (items.length) {
@@ -1238,6 +1286,7 @@ chart_js.Chart.register(TreemapController, TreemapElement);
1238
1286
  exports.TreemapController = TreemapController;
1239
1287
  exports.TreemapElement = TreemapElement;
1240
1288
  exports.flatten = flatten;
1289
+ exports.flattenTree = flattenTree;
1241
1290
  exports.getGroupKey = getGroupKey;
1242
1291
  exports.group = group;
1243
1292
  exports.index = index;