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

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.3
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.3";
14
14
 
15
15
  function scaleRect(sq, xScale, yScale, sp) {
16
16
  const sp2 = sp * 2;
@@ -49,6 +49,11 @@ function arrayNotEqual(a, b) {
49
49
  return false;
50
50
  }
51
51
 
52
+ const layoutDefaults = {
53
+ displayMode: 'containerBoxes',
54
+ rtl: false,
55
+ spacing: 0.5
56
+ };
52
57
  function limit(value, min, max) {
53
58
  return Math.max(Math.min(value, max), min);
54
59
  }
@@ -237,7 +242,7 @@ class StatArray {
237
242
  const isOlderPart = (act, req)=>req > act || act.length > req.length && act.startsWith(req);
238
243
  const getGroupKey = (lvl)=>String(lvl);
239
244
  const hasGroupValue = (value)=>value !== undefined && value !== null && value !== '';
240
- function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = []) {
245
+ function scanTreeObject(keys, leafKey, obj, tree = [], lvl = 0, result = []) {
241
246
  const objIndex = lvl - 1;
242
247
  if (keys[0] in obj && lvl > 0) {
243
248
  const record = tree.reduce((reduced, item, i)=>{
@@ -246,7 +251,7 @@ function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = [])
246
251
  }
247
252
  return reduced;
248
253
  }, {});
249
- record[treeLeafKey] = tree[objIndex];
254
+ record[leafKey] = tree[objIndex];
250
255
  keys.forEach((k)=>{
251
256
  record[k] = obj[k];
252
257
  });
@@ -256,15 +261,15 @@ function scanTreeObject(keys, treeLeafKey, obj, tree = [], lvl = 0, result = [])
256
261
  const child = obj[childKey];
257
262
  if (helpers.isObject(child)) {
258
263
  tree.push(childKey);
259
- scanTreeObject(keys, treeLeafKey, child, tree, lvl + 1, result);
264
+ scanTreeObject(keys, leafKey, child, tree, lvl + 1, result);
260
265
  }
261
266
  }
262
267
  }
263
268
  tree.splice(objIndex, 1);
264
269
  return result;
265
270
  }
266
- function normalizeTreeToArray(keys, treeLeafKey, obj) {
267
- const data = scanTreeObject(keys, treeLeafKey, obj);
271
+ function normalizeTreeToArray(keys, leafKey, obj) {
272
+ const data = scanTreeObject(keys, leafKey, obj);
268
273
  if (!data.length) {
269
274
  return data;
270
275
  }
@@ -308,7 +313,7 @@ function getPath(groups, value, defaultValue) {
308
313
  }
309
314
  return path.length ? path.join('.') : defaultValue;
310
315
  }
311
- function resolveGroup(value, treeLeafKey, allGroups, groupIndex) {
316
+ function resolveGroup(value, leafKey, allGroups, groupIndex) {
312
317
  for(let idx = groupIndex; idx < allGroups.length; idx++){
313
318
  const groupKey = getGroupKey(allGroups[idx]);
314
319
  if (hasGroupValue(value[groupKey])) {
@@ -319,11 +324,11 @@ function resolveGroup(value, treeLeafKey, allGroups, groupIndex) {
319
324
  };
320
325
  }
321
326
  }
322
- if (hasGroupValue(value[treeLeafKey])) {
327
+ if (hasGroupValue(value[leafKey])) {
323
328
  return {
324
- group: treeLeafKey,
329
+ group: leafKey,
325
330
  groupIndex,
326
- value: value[treeLeafKey]
331
+ value: value[leafKey]
327
332
  };
328
333
  }
329
334
  return undefined;
@@ -341,7 +346,7 @@ function ensureGroup(tmp, data, groupValue, addKeys) {
341
346
  });
342
347
  data[groupValue] = [];
343
348
  }
344
- function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
349
+ function group(values, grp, keys, leafKey, mainGrp, mainValue, groups = [], allGroups = groups, groupIndex = groups.length - 1) {
345
350
  const key = keys[0];
346
351
  const addKeys = keys.slice(1);
347
352
  const tmp = Object.create(null);
@@ -358,7 +363,7 @@ function group(values, grp, keys, treeLeafKey, mainGrp, mainValue, groups = [],
358
363
  if (mainGrp && v[mainGrp] !== mainValue) {
359
364
  continue;
360
365
  }
361
- const itemGroup = resolveGroup(v, treeLeafKey, resolvedAllGroups, resolvedGroupIndex);
366
+ const itemGroup = resolveGroup(v, leafKey, resolvedAllGroups, resolvedGroupIndex);
362
367
  if (!itemGroup) {
363
368
  return [];
364
369
  }
@@ -533,20 +538,41 @@ function getCaptionHeight(displayMode, rect, font, padding) {
533
538
  const captionHeight = font.lineHeight + padding * 2;
534
539
  return rect.h < 2 * captionHeight ? rect.h / 3 : captionHeight;
535
540
  }
536
- function drawText(ctx, rect, options, item, element) {
537
- const { captions, labels, displayMode } = options;
541
+ function drawText(ctx, rect, options, item, element, layout) {
542
+ const { captions, labels } = options;
543
+ const { displayMode } = layout;
538
544
  ctx.save();
539
545
  ctx.beginPath();
540
546
  ctx.rect(rect.x, rect.y, rect.w, rect.h);
541
547
  ctx.clip();
542
548
  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);
549
+ let block;
550
+ if (isLeaf) {
551
+ if (labels.display) {
552
+ block = labelBlock(ctx, rect, options, item, element);
553
+ }
554
+ } else if (shouldDrawCaption(displayMode, rect, captions)) {
555
+ block = captionBlock(ctx, rect, options, item, element, layout);
556
+ }
557
+ if (block) {
558
+ drawTextBlock(ctx, rect, block);
547
559
  }
548
560
  ctx.restore();
549
561
  }
562
+ function drawTextBlock(ctx, rect, block) {
563
+ const { align, colors, fonts, lines, padding, top } = block;
564
+ const x = calculateX(rect, align, padding);
565
+ ctx.textAlign = align;
566
+ ctx.textBaseline = 'middle';
567
+ let offset = 0;
568
+ lines.forEach((line, i)=>{
569
+ const font = fonts[Math.min(i, fonts.length - 1)];
570
+ ctx.font = font.string;
571
+ ctx.fillStyle = colors[Math.min(i, colors.length - 1)];
572
+ ctx.fillText(line, x, top + offset + font.lineHeight / 2);
573
+ offset += font.lineHeight;
574
+ });
575
+ }
550
576
  function callbackContext(element, item) {
551
577
  const context = element.$context || {
552
578
  active: !!element.active,
@@ -563,16 +589,12 @@ function resolveOption(option, context) {
563
589
  function resolveCaptionText(captions, element, item) {
564
590
  return resolveOption(captions.formatter, callbackContext(element, item)) || item.g || '';
565
591
  }
566
- function drawCaption(ctx, rect, options, item, element) {
567
- const { captions, spacing, rtl, displayMode } = options;
592
+ function captionBlock(ctx, rect, options, item, element, layout) {
593
+ const { displayMode, rtl, spacing } = layout;
594
+ const { captions } = options;
568
595
  const { color, hoverColor, font, hoverFont, padding, align } = captions;
569
- const oColor = (rect.active ? hoverColor : color) || color;
570
- const oAlign = align || (rtl ? 'right' : 'left');
571
596
  const optFont = (rect.active ? hoverFont : font) || font;
572
597
  const oFont = helpers.toFont(optFont);
573
- const fonts = [
574
- oFont
575
- ];
576
598
  if (oFont.lineHeight > rect.h) {
577
599
  return;
578
600
  }
@@ -580,20 +602,62 @@ function drawCaption(ctx, rect, options, item, element) {
580
602
  if (!text) {
581
603
  return;
582
604
  }
583
- const captionSize = measureLabelSize(ctx, [
605
+ const fonts = [
606
+ oFont
607
+ ];
608
+ if (measureLabelSize(ctx, [
584
609
  text
585
- ], fonts);
586
- if (captionSize.width + 2 * padding > rect.w) {
610
+ ], fonts).width + 2 * padding > rect.w) {
587
611
  text = sliceTextToFitWidth(ctx, text, rect.w - 2 * padding, fonts);
588
612
  }
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);
613
+ const top = displayMode === 'headerBoxes' ? rect.y + (rect.h - oFont.lineHeight) / 2 : rect.y + padding + spacing;
614
+ return {
615
+ align: align || (rtl ? 'right' : 'left'),
616
+ colors: [
617
+ (rect.active ? hoverColor : color) || color
618
+ ],
619
+ fonts,
620
+ lines: [
621
+ text
622
+ ],
623
+ padding,
624
+ top
625
+ };
626
+ }
627
+ function labelBlock(ctx, rect, options, item, element) {
628
+ const labels = options.labels;
629
+ const content = resolveOption(labels.formatter, callbackContext(element, item));
630
+ if (!content) {
631
+ return;
632
+ }
633
+ const lines = helpers.isArray(content) ? content : [
634
+ content
635
+ ];
636
+ let fonts = getFontFromOptions(rect, labels);
637
+ let labelSize = measureLabelSize(ctx, lines, fonts);
638
+ const lblToDraw = labelToDraw(rect, labels, labelSize);
639
+ if (!lblToDraw) {
640
+ return;
641
+ }
642
+ if (helpers.isNumber(lblToDraw)) {
643
+ labelSize = {
644
+ height: labelSize.height * lblToDraw,
645
+ width: labelSize.width * lblToDraw
646
+ };
647
+ fonts = toFonts(fonts, lblToDraw);
648
+ }
649
+ const { color, hoverColor, align, padding } = labels;
650
+ const optColor = (rect.active ? hoverColor : color) || color;
651
+ return {
652
+ align,
653
+ colors: helpers.isArray(optColor) ? optColor : [
654
+ optColor
655
+ ],
656
+ fonts,
657
+ lines,
658
+ padding,
659
+ top: calculateBlockTop(rect, labels, labelSize)
660
+ };
597
661
  }
598
662
  function sliceTextToFitWidth(ctx, text, width, fonts) {
599
663
  const ellipsis = '...';
@@ -658,7 +722,7 @@ function toFonts(fonts, fitRatio) {
658
722
  });
659
723
  });
660
724
  }
661
- function labelToDraw(_ctx, rect, options, labelSize) {
725
+ function labelToDraw(rect, options, labelSize) {
662
726
  const { overflow, padding } = options;
663
727
  const { width, height } = labelSize;
664
728
  if (overflow === 'hidden') {
@@ -678,62 +742,15 @@ function getFontFromOptions(rect, labels) {
678
742
  helpers.toFont(optFont)
679
743
  ];
680
744
  }
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;
745
+ function calculateBlockTop(rect, options, labelSize) {
746
+ const { position, padding } = options;
726
747
  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;
748
+ return rect.y + padding;
732
749
  }
733
- return {
734
- x,
735
- y
736
- };
750
+ if (position === 'bottom') {
751
+ return rect.y + rect.h - padding - labelSize.height;
752
+ }
753
+ return rect.y + (rect.h - labelSize.height) / 2 + padding;
737
754
  }
738
755
  function calculateX(rect, align, padding) {
739
756
  if (align === 'left') {
@@ -744,24 +761,22 @@ function calculateX(rect, align, padding) {
744
761
  return rect.x + rect.w / 2;
745
762
  }
746
763
 
747
- function buildData(tree, dataset, keys, mainRect) {
748
- const treeLeafKey = dataset.treeLeafKey || '_leaf';
764
+ function buildData(tree, keys, mainRect, layout) {
765
+ const { borderWidth, captions, displayMode, groups, leafKey } = layout;
749
766
  if (helpers.isObject(tree)) {
750
- tree = normalizeTreeToArray(keys, treeLeafKey, tree);
767
+ tree = normalizeTreeToArray(keys, leafKey, tree);
751
768
  }
752
- const groups = dataset.groups || [];
753
769
  const glen = groups.length;
754
- const sp = dataset.displayMode === 'headerBoxes' ? 0 : helpers.valueOrDefault(dataset.spacing, 0);
755
- const captions = dataset.captions || {};
770
+ const sp = displayMode === 'headerBoxes' ? 0 : layout.spacing;
756
771
  const font = helpers.toFont(captions.font);
757
772
  const padding = helpers.valueOrDefault(captions.padding, 3);
758
773
  function getSubRect(sq, rect) {
759
- const bw = dataset.displayMode === 'headerBoxes' ? {
774
+ const bw = displayMode === 'headerBoxes' ? {
760
775
  b: 0,
761
776
  l: 0,
762
777
  r: 0,
763
778
  t: 0
764
- } : parseBorderWidth(dataset.borderWidth, sq.w / 2, sq.h / 2);
779
+ } : parseBorderWidth(borderWidth, sq.w / 2, sq.h / 2);
765
780
  const subRect = {
766
781
  ...rect,
767
782
  h: sq.h - 2 * sp - bw.t - bw.b,
@@ -769,8 +784,8 @@ function buildData(tree, dataset, keys, mainRect) {
769
784
  x: sq.x + sp + bw.l,
770
785
  y: sq.y + sp + bw.t
771
786
  };
772
- if (shouldDrawCaption(dataset.displayMode, subRect, captions)) {
773
- const captionHeight = getCaptionHeight(dataset.displayMode, subRect, font, padding);
787
+ if (shouldDrawCaption(displayMode, subRect, captions)) {
788
+ const captionHeight = getCaptionHeight(displayMode, subRect, font, padding);
774
789
  subRect.y += captionHeight;
775
790
  subRect.h -= captionHeight;
776
791
  }
@@ -778,7 +793,7 @@ function buildData(tree, dataset, keys, mainRect) {
778
793
  }
779
794
  function recur(treeElements, gidx, rect, parent, gs) {
780
795
  const g = getGroupKey(groups[gidx]);
781
- const gdata = group(treeElements, g, keys, treeLeafKey, undefined, parent, groups.filter((_item, index)=>index <= gidx), groups, gidx);
796
+ const gdata = group(treeElements, g, keys, leafKey, undefined, parent, groups.filter((_item, index)=>index <= gidx), groups, gidx);
782
797
  const gsq = squarify(gdata, rect, keys, g, gidx, gs);
783
798
  const ret = gsq.slice();
784
799
  if (gidx < glen - 1) {
@@ -801,13 +816,13 @@ function buildData(tree, dataset, keys, mainRect) {
801
816
  }
802
817
  const result = glen ? recur(tree, 0, mainRect) : squarify(tree, mainRect, keys);
803
818
  return result.map((d)=>{
804
- if (dataset.displayMode !== 'headerBoxes' || d.isLeaf) {
819
+ if (displayMode !== 'headerBoxes' || d.isLeaf) {
805
820
  return d;
806
821
  }
807
- if (!shouldDrawCaption(dataset.displayMode, d, captions)) {
822
+ if (!shouldDrawCaption(displayMode, d, captions)) {
808
823
  return undefined;
809
824
  }
810
- const captionHeight = getCaptionHeight(dataset.displayMode, d, font, padding);
825
+ const captionHeight = getCaptionHeight(displayMode, d, font, padding);
811
826
  return {
812
827
  ...d,
813
828
  h: captionHeight
@@ -866,13 +881,26 @@ class TreemapController extends chart_js.DatasetController {
866
881
  yScale.configure();
867
882
  }
868
883
  }
884
+ _layoutOptions() {
885
+ const dataset = this.getDataset();
886
+ const options = this.options;
887
+ return {
888
+ borderWidth: dataset.borderWidth,
889
+ captions: dataset.captions || {},
890
+ displayMode: options.displayMode,
891
+ groups: options.groups || [],
892
+ leafKey: options.leafKey,
893
+ spacing: helpers.valueOrDefault(dataset.spacing, 0)
894
+ };
895
+ }
869
896
  update(mode) {
870
897
  const dataset = this.getDataset();
871
898
  const { data } = this.getMeta();
872
- const groups = dataset.groups || [];
899
+ const options = this.options;
900
+ const groups = options.groups || [];
873
901
  const keys = [
874
- dataset.key || ''
875
- ].concat(dataset.sumKeys || []);
902
+ options.key || ''
903
+ ].concat(options.sumKeys || []);
876
904
  dataset.tree = dataset.tree || dataset.data || [];
877
905
  const tree = dataset.tree;
878
906
  const treeVersion = dataset.treeVersion;
@@ -885,7 +913,7 @@ class TreemapController extends chart_js.DatasetController {
885
913
  this._prevTree = tree;
886
914
  this._prevTreeVersion = treeVersion;
887
915
  this._rectChanged = false;
888
- dataset.data = buildData(tree, dataset, this._keys, this._rect);
916
+ dataset.data = buildData(tree, this._keys, this._rect, this._layoutOptions());
889
917
  this._dataCheck();
890
918
  this._resyncElements();
891
919
  }
@@ -899,9 +927,10 @@ class TreemapController extends chart_js.DatasetController {
899
927
  const sharedOptions = this.getSharedOptions(firstOpts);
900
928
  const includeOptions = this.includeOptions(mode, sharedOptions || {});
901
929
  const { xScale, yScale } = this.getMeta();
930
+ const spacing = this.options.spacing;
902
931
  for(let i = start; i < start + count; i++){
903
932
  const options = sharedOptions || this.resolveDataElementOptions(i, mode);
904
- const properties = scaleRect(dataset.data[i], xScale, yScale, options.spacing);
933
+ const properties = scaleRect(dataset.data[i], xScale, yScale, spacing);
905
934
  if (reset) {
906
935
  properties.width = 0;
907
936
  properties.height = 0;
@@ -918,11 +947,17 @@ class TreemapController extends chart_js.DatasetController {
918
947
  const metadata = this.getMeta().data || [];
919
948
  const dataset = this.getDataset();
920
949
  const data = dataset.data;
950
+ const { displayMode, rtl, spacing } = this.options;
951
+ const layout = {
952
+ displayMode,
953
+ rtl,
954
+ spacing
955
+ };
921
956
  helpers.clipArea(ctx, chartArea);
922
957
  for(let i = 0, ilen = metadata.length; i < ilen; ++i){
923
958
  const rect = metadata[i];
924
959
  if (!rect.hidden) {
925
- rect.draw(ctx, data[i]);
960
+ rect.draw(ctx, data[i], layout);
926
961
  }
927
962
  }
928
963
  helpers.unclipArea(ctx);
@@ -949,7 +984,15 @@ TreemapController.defaults = {
949
984
  type: 'number'
950
985
  }
951
986
  },
952
- dataElementType: 'treemap'
987
+ dataElementType: 'treemap',
988
+ displayMode: layoutDefaults.displayMode,
989
+ groups: [],
990
+ key: '',
991
+ leafKey: '_leaf',
992
+ rtl: layoutDefaults.rtl,
993
+ spacing: layoutDefaults.spacing,
994
+ sumKeys: [],
995
+ unsorted: false
953
996
  };
954
997
  TreemapController.descriptors = {
955
998
  _indexable: false,
@@ -1079,7 +1122,7 @@ function addNormalRectPath(ctx, rect) {
1079
1122
  }
1080
1123
 
1081
1124
  class TreemapElement extends chart_js.Element {
1082
- draw(ctx, data) {
1125
+ draw(ctx, data, layout = layoutDefaults) {
1083
1126
  if (!data) {
1084
1127
  return;
1085
1128
  }
@@ -1099,7 +1142,7 @@ class TreemapElement extends chart_js.Element {
1099
1142
  addRectPath(ctx, inner);
1100
1143
  ctx.fillStyle = options.backgroundColor;
1101
1144
  ctx.fill();
1102
- drawText(ctx, inner, options, data, this);
1145
+ drawText(ctx, inner, options, data, this, layout);
1103
1146
  ctx.restore();
1104
1147
  }
1105
1148
  inRange(mouseX, mouseY, useFinalPosition) {
@@ -1150,8 +1193,6 @@ TreemapElement.defaults = {
1150
1193
  },
1151
1194
  padding: 3
1152
1195
  },
1153
- displayMode: 'containerBoxes',
1154
- label: undefined,
1155
1196
  labels: {
1156
1197
  align: 'center',
1157
1198
  color: 'black',
@@ -1173,10 +1214,7 @@ TreemapElement.defaults = {
1173
1214
  overflow: 'cut',
1174
1215
  padding: 3,
1175
1216
  position: 'middle'
1176
- },
1177
- rtl: false,
1178
- spacing: 0.5,
1179
- unsorted: false
1217
+ }
1180
1218
  };
1181
1219
  TreemapElement.descriptors = {
1182
1220
  _indexable: false,