chartjs-chart-sankey 0.17.0 → 0.19.0

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-sankey v0.17.0
2
+ * chartjs-chart-sankey v0.19.0
3
3
  * https://chartjs-chart-sankey.pages.dev/
4
4
  * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
@@ -445,10 +445,19 @@ const nodeByXYSize = (a, b)=>{
445
445
  function createColumnGapState() {
446
446
  return {
447
447
  lastAfter: 0,
448
+ lastExtraHalf: 0,
448
449
  realCount: 0,
449
450
  realCumOffset: 0,
450
451
  yHistory: []
451
452
  };
453
+ }
454
+ function extraHalfFor(node, minSizes, scale) {
455
+ const minSizeUnits = (minSizes.get(node.key) ?? 0) * scale;
456
+ return Math.max(0, minSizeUnits - node.size) / 2;
457
+ }
458
+ function stretchedSize(node, minSizes, scale) {
459
+ const minSizeUnits = (minSizes.get(node.key) ?? 0) * scale;
460
+ return Math.max(node.size, minSizeUnits);
452
461
  }
453
462
  function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
454
463
  let paddings = ownPaddings;
@@ -461,17 +470,50 @@ function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
461
470
  }
462
471
  return paddings;
463
472
  }
464
- function offsetForNode(state, gap, paddings) {
473
+ function offsetForNode(state, gap, paddings, scale, extraHalf) {
465
474
  const realNodesAbove = state.realCount;
466
- const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, gap.before) : 0;
475
+ const before = gap.before * scale;
476
+ const after = gap.after * scale;
477
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) + state.lastExtraHalf + extraHalf : 0;
467
478
  const realCumOffset = state.realCumOffset + transitionGap;
468
479
  const virtualLevels = paddings - realNodesAbove;
469
480
  state.realCount = realNodesAbove + 1;
470
481
  state.realCumOffset = realCumOffset;
471
- state.lastAfter = gap.after;
472
- return realCumOffset + virtualLevels * gap.before;
482
+ state.lastAfter = after;
483
+ state.lastExtraHalf = extraHalf;
484
+ return realCumOffset + virtualLevels * before;
485
+ }
486
+ function addEvenPadding(nodeArray, gaps, scale, minSizes) {
487
+ let maxY = 0;
488
+ let columnX;
489
+ let prev;
490
+ let prevGap;
491
+ let prevExtraHalf = 0;
492
+ for (const node of nodeArray){
493
+ const x = nodeX$1(node);
494
+ const gap = gaps.get(node.key) ?? {
495
+ after: 0,
496
+ before: 0
497
+ };
498
+ const extraHalf = extraHalfFor(node, minSizes, scale);
499
+ if (x !== columnX) {
500
+ columnX = x;
501
+ } else if (prev && prevGap) {
502
+ node.y = nodeY$1(prev) + prev.size + Math.max(prevGap.after, gap.before) * scale + prevExtraHalf + extraHalf;
503
+ }
504
+ prev = node;
505
+ prevGap = gap;
506
+ prevExtraHalf = extraHalf;
507
+ const H = stretchedSize(node, minSizes, scale);
508
+ maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out), nodeY$1(node) + (node.size + H) / 2);
509
+ }
510
+ return maxY;
473
511
  }
474
- function addPadding(nodeArray, gaps) {
512
+ function addPadding(nodeArray, gaps, scale = 1, mode = 'auto', minSizes = new Map()) {
513
+ nodeArray.sort(nodeByXYSize);
514
+ if (mode === 'even') {
515
+ return addEvenPadding(nodeArray, gaps, scale, minSizes);
516
+ }
475
517
  let maxY = 0;
476
518
  const columnXs = new Map();
477
519
  const grid = [];
@@ -484,7 +526,6 @@ function offsetForNode(state, gap, paddings) {
484
526
  }
485
527
  return columnXs.get(x) ?? 0;
486
528
  };
487
- nodeArray.sort(nodeByXYSize);
488
529
  for (const node of nodeArray){
489
530
  const colIdx = getColIndex(nodeX$1(node));
490
531
  const state = grid[colIdx];
@@ -493,6 +534,7 @@ function offsetForNode(state, gap, paddings) {
493
534
  before: 0
494
535
  };
495
536
  const y = nodeY$1(node);
537
+ const extraHalf = extraHalfFor(node, minSizes, scale);
496
538
  if (y) {
497
539
  state.yHistory.push(y);
498
540
  let paddings = state.yHistory.length;
@@ -500,12 +542,14 @@ function offsetForNode(state, gap, paddings) {
500
542
  paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
501
543
  while(state.yHistory.length < paddings)state.yHistory.push(y);
502
544
  }
503
- node.y = y + offsetForNode(state, gap, paddings);
545
+ node.y = y + offsetForNode(state, gap, paddings, scale, extraHalf);
504
546
  } else {
505
547
  state.realCount += 1;
506
- state.lastAfter = gap.after;
548
+ state.lastAfter = gap.after * scale;
549
+ state.lastExtraHalf = extraHalf;
507
550
  }
508
- maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
551
+ const H = stretchedSize(node, minSizes, scale);
552
+ maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out), nodeY$1(node) + (node.size + H) / 2);
509
553
  }
510
554
  return maxY;
511
555
  }
@@ -536,21 +580,14 @@ function sortFlows(nodeArray) {
536
580
  });
537
581
  });
538
582
  }
539
- function layout(nodes, data, { priority, height, nodePadding, modeX }) {
583
+ function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, nodeMinSize, modeX }) {
540
584
  const nodeArray = [
541
585
  ...nodes.values()
542
586
  ];
543
587
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
544
588
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
545
589
  const scale = maxY / height;
546
- const scaledGaps = new Map();
547
- for (const [key, gap] of nodePadding){
548
- scaledGaps.set(key, {
549
- after: gap.after * scale,
550
- before: gap.before * scale
551
- });
552
- }
553
- const maxYWithPadding = addPadding(nodeArray, scaledGaps);
590
+ const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto', nodeMinSize ?? new Map());
554
591
  sortFlows(nodeArray);
555
592
  return {
556
593
  maxX,
@@ -646,22 +683,30 @@ function getColumnPadding(nodeWidth, orientation, chart) {
646
683
  const trailingSpace = orientation === 'vertical' ? chart.height - chart.chartArea.bottom : chart.width - chart.chartArea.right;
647
684
  return Math.max(0, nodeWidth + 3 - trailingSpace);
648
685
  }
649
- function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPadding, orientation) {
686
+ function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPadding, orientation, minSize) {
650
687
  if (orientation === 'vertical') {
651
- const x = xScale.getPixelForValue(nodeY(node));
688
+ const x1 = xScale.getPixelForValue(nodeY(node));
689
+ const x2 = xScale.getPixelForValue(nodeY(node) + size);
690
+ const left = Math.min(x1, x2);
691
+ const naturalWidth = Math.abs(x2 - x1);
692
+ const width = Math.max(naturalWidth, minSize);
652
693
  return {
653
694
  height: nodeWidth,
654
- width: Math.abs(xScale.getPixelForValue(nodeY(node) + size) - x),
655
- x,
695
+ width,
696
+ x: left - (width - naturalWidth) / 2,
656
697
  y: getColumnPixel(yScale, nodeX(node), maxColumn, columnPadding)
657
698
  };
658
699
  }
659
- const y = yScale.getPixelForValue(nodeY(node));
700
+ const y1 = yScale.getPixelForValue(nodeY(node));
701
+ const y2 = yScale.getPixelForValue(nodeY(node) + size);
702
+ const top = Math.min(y1, y2);
703
+ const naturalHeight = Math.abs(y2 - y1);
704
+ const height = Math.max(naturalHeight, minSize);
660
705
  return {
661
- height: Math.abs(yScale.getPixelForValue(nodeY(node) + size) - y),
706
+ height,
662
707
  width: nodeWidth,
663
708
  x: getColumnPixel(xScale, nodeX(node), maxColumn, columnPadding),
664
- y
709
+ y: top - (height - naturalHeight) / 2
665
710
  };
666
711
  }
667
712
  function resolveNodeGap(option, node) {
@@ -677,6 +722,9 @@ function resolveNodeGap(option, node) {
677
722
  before: resolved.before ?? 10
678
723
  };
679
724
  }
725
+ function resolveNodeMinSize(option, node) {
726
+ return resolveNodeOption(option ?? 0, node) ?? 0;
727
+ }
680
728
  function resolveNodeLabelStyle(options, node) {
681
729
  const { backgroundColor, borderRadius = 0, color, display, font, padding = 4, position } = options.nodeLabels ?? {};
682
730
  return {
@@ -689,6 +737,10 @@ function resolveNodeLabelStyle(options, node) {
689
737
  position: resolveNodeOption(position, node) ?? 'auto'
690
738
  };
691
739
  }
740
+ const NODE_SCOPED_OPTIONS = new Set([
741
+ 'nodeMinSize',
742
+ 'nodePadding'
743
+ ]);
692
744
  class SankeyController extends chart_js.DatasetController {
693
745
  parseObjectData(meta, data, start, count) {
694
746
  const sankeyData = getParsedData(data, this.options.parsing);
@@ -698,13 +750,17 @@ class SankeyController extends chart_js.DatasetController {
698
750
  const orientation = this.options.orientation ?? 'horizontal';
699
751
  this._nodes = nodes;
700
752
  const nodeGaps = new Map();
753
+ const nodeMinSizes = new Map();
701
754
  for (const node of nodes.values()){
702
755
  nodeGaps.set(node.key, resolveNodeGap(this.options.nodePadding, node));
756
+ nodeMinSizes.set(node.key, resolveNodeMinSize(this.options.nodeMinSize, node));
703
757
  }
704
758
  const { maxX, maxY } = layout(nodes, sankeyData, {
705
759
  height: orientation === 'vertical' ? this.chart.width : this.chart.height,
706
760
  modeX: this.options.modeX,
761
+ nodeMinSize: nodeMinSizes,
707
762
  nodePadding: nodeGaps,
763
+ nodePaddingMode: this.options.nodePaddingMode,
708
764
  priority: !!this.options.priority
709
765
  });
710
766
  this._maxX = maxX;
@@ -768,7 +824,8 @@ class SankeyController extends chart_js.DatasetController {
768
824
  const chartArea = this.chart.chartArea;
769
825
  for (const node of nodes.values()){
770
826
  const max = getNodeSize(node, size);
771
- const { height, width, x, y } = getNodeRect(node, max, xScale, yScale, this._maxX, nodeWidth, columnPadding, orientation);
827
+ const minSize = resolveNodeMinSize(options.nodeMinSize, node);
828
+ const { height, width, x, y } = getNodeRect(node, max, xScale, yScale, this._maxX, nodeWidth, columnPadding, orientation, minSize);
772
829
  const label = labels?.[node.key] ?? node.key;
773
830
  const labelStyle = resolveNodeLabelStyle(options, node);
774
831
  if (labelStyle.display) {
@@ -808,7 +865,8 @@ class SankeyController extends chart_js.DatasetController {
808
865
  ctx.fillStyle = node.color ?? 'black';
809
866
  if (!xScale || !yScale) return;
810
867
  const max = Math[sizeMethod](node.in || node.out, node.out || node.in);
811
- const { height, width, x, y } = getNodeRect(node, max, xScale, yScale, this._maxX, nodeWidth, columnPadding, orientation);
868
+ const minSize = resolveNodeMinSize(this.options.nodeMinSize, node);
869
+ const { height, width, x, y } = getNodeRect(node, max, xScale, yScale, this._maxX, nodeWidth, columnPadding, orientation, minSize);
812
870
  if (borderWidth) {
813
871
  ctx.strokeRect(x, y, width, height);
814
872
  }
@@ -851,7 +909,7 @@ class SankeyController extends chart_js.DatasetController {
851
909
  SankeyController.id = 'sankey';
852
910
  SankeyController.descriptors = {
853
911
  _indexable: false,
854
- _scriptable: (name)=>name !== 'nodePadding',
912
+ _scriptable: (name)=>!NODE_SCOPED_OPTIONS.has(name),
855
913
  nodeLabels: {
856
914
  _indexable: false,
857
915
  _scriptable: false
@@ -888,7 +946,9 @@ SankeyController.defaults = {
888
946
  color: 'black',
889
947
  dataElementType: 'flow',
890
948
  modeX: 'edge',
949
+ nodeMinSize: 0,
891
950
  nodePadding: 10,
951
+ nodePaddingMode: 'auto',
892
952
  nodeWidth: 10,
893
953
  orientation: 'horizontal',
894
954
  transitions: {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.17.0
2
+ * chartjs-chart-sankey v0.19.0
3
3
  * https://chartjs-chart-sankey.pages.dev/
4
4
  * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
@@ -442,10 +442,19 @@ const nodeByXYSize = (a, b)=>{
442
442
  function createColumnGapState() {
443
443
  return {
444
444
  lastAfter: 0,
445
+ lastExtraHalf: 0,
445
446
  realCount: 0,
446
447
  realCumOffset: 0,
447
448
  yHistory: []
448
449
  };
450
+ }
451
+ function extraHalfFor(node, minSizes, scale) {
452
+ const minSizeUnits = (minSizes.get(node.key) ?? 0) * scale;
453
+ return Math.max(0, minSizeUnits - node.size) / 2;
454
+ }
455
+ function stretchedSize(node, minSizes, scale) {
456
+ const minSizeUnits = (minSizes.get(node.key) ?? 0) * scale;
457
+ return Math.max(node.size, minSizeUnits);
449
458
  }
450
459
  function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
451
460
  let paddings = ownPaddings;
@@ -458,17 +467,50 @@ function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
458
467
  }
459
468
  return paddings;
460
469
  }
461
- function offsetForNode(state, gap, paddings) {
470
+ function offsetForNode(state, gap, paddings, scale, extraHalf) {
462
471
  const realNodesAbove = state.realCount;
463
- const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, gap.before) : 0;
472
+ const before = gap.before * scale;
473
+ const after = gap.after * scale;
474
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) + state.lastExtraHalf + extraHalf : 0;
464
475
  const realCumOffset = state.realCumOffset + transitionGap;
465
476
  const virtualLevels = paddings - realNodesAbove;
466
477
  state.realCount = realNodesAbove + 1;
467
478
  state.realCumOffset = realCumOffset;
468
- state.lastAfter = gap.after;
469
- return realCumOffset + virtualLevels * gap.before;
479
+ state.lastAfter = after;
480
+ state.lastExtraHalf = extraHalf;
481
+ return realCumOffset + virtualLevels * before;
482
+ }
483
+ function addEvenPadding(nodeArray, gaps, scale, minSizes) {
484
+ let maxY = 0;
485
+ let columnX;
486
+ let prev;
487
+ let prevGap;
488
+ let prevExtraHalf = 0;
489
+ for (const node of nodeArray){
490
+ const x = nodeX$1(node);
491
+ const gap = gaps.get(node.key) ?? {
492
+ after: 0,
493
+ before: 0
494
+ };
495
+ const extraHalf = extraHalfFor(node, minSizes, scale);
496
+ if (x !== columnX) {
497
+ columnX = x;
498
+ } else if (prev && prevGap) {
499
+ node.y = nodeY$1(prev) + prev.size + Math.max(prevGap.after, gap.before) * scale + prevExtraHalf + extraHalf;
500
+ }
501
+ prev = node;
502
+ prevGap = gap;
503
+ prevExtraHalf = extraHalf;
504
+ const H = stretchedSize(node, minSizes, scale);
505
+ maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out), nodeY$1(node) + (node.size + H) / 2);
506
+ }
507
+ return maxY;
470
508
  }
471
- function addPadding(nodeArray, gaps) {
509
+ function addPadding(nodeArray, gaps, scale = 1, mode = 'auto', minSizes = new Map()) {
510
+ nodeArray.sort(nodeByXYSize);
511
+ if (mode === 'even') {
512
+ return addEvenPadding(nodeArray, gaps, scale, minSizes);
513
+ }
472
514
  let maxY = 0;
473
515
  const columnXs = new Map();
474
516
  const grid = [];
@@ -481,7 +523,6 @@ function offsetForNode(state, gap, paddings) {
481
523
  }
482
524
  return columnXs.get(x) ?? 0;
483
525
  };
484
- nodeArray.sort(nodeByXYSize);
485
526
  for (const node of nodeArray){
486
527
  const colIdx = getColIndex(nodeX$1(node));
487
528
  const state = grid[colIdx];
@@ -490,6 +531,7 @@ function offsetForNode(state, gap, paddings) {
490
531
  before: 0
491
532
  };
492
533
  const y = nodeY$1(node);
534
+ const extraHalf = extraHalfFor(node, minSizes, scale);
493
535
  if (y) {
494
536
  state.yHistory.push(y);
495
537
  let paddings = state.yHistory.length;
@@ -497,12 +539,14 @@ function offsetForNode(state, gap, paddings) {
497
539
  paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
498
540
  while(state.yHistory.length < paddings)state.yHistory.push(y);
499
541
  }
500
- node.y = y + offsetForNode(state, gap, paddings);
542
+ node.y = y + offsetForNode(state, gap, paddings, scale, extraHalf);
501
543
  } else {
502
544
  state.realCount += 1;
503
- state.lastAfter = gap.after;
545
+ state.lastAfter = gap.after * scale;
546
+ state.lastExtraHalf = extraHalf;
504
547
  }
505
- maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
548
+ const H = stretchedSize(node, minSizes, scale);
549
+ maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out), nodeY$1(node) + (node.size + H) / 2);
506
550
  }
507
551
  return maxY;
508
552
  }
@@ -533,21 +577,14 @@ function sortFlows(nodeArray) {
533
577
  });
534
578
  });
535
579
  }
536
- function layout(nodes, data, { priority, height, nodePadding, modeX }) {
580
+ function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, nodeMinSize, modeX }) {
537
581
  const nodeArray = [
538
582
  ...nodes.values()
539
583
  ];
540
584
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
541
585
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
542
586
  const scale = maxY / height;
543
- const scaledGaps = new Map();
544
- for (const [key, gap] of nodePadding){
545
- scaledGaps.set(key, {
546
- after: gap.after * scale,
547
- before: gap.before * scale
548
- });
549
- }
550
- const maxYWithPadding = addPadding(nodeArray, scaledGaps);
587
+ const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto', nodeMinSize ?? new Map());
551
588
  sortFlows(nodeArray);
552
589
  return {
553
590
  maxX,
@@ -643,22 +680,30 @@ function getColumnPadding(nodeWidth, orientation, chart) {
643
680
  const trailingSpace = orientation === 'vertical' ? chart.height - chart.chartArea.bottom : chart.width - chart.chartArea.right;
644
681
  return Math.max(0, nodeWidth + 3 - trailingSpace);
645
682
  }
646
- function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPadding, orientation) {
683
+ function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPadding, orientation, minSize) {
647
684
  if (orientation === 'vertical') {
648
- const x = xScale.getPixelForValue(nodeY(node));
685
+ const x1 = xScale.getPixelForValue(nodeY(node));
686
+ const x2 = xScale.getPixelForValue(nodeY(node) + size);
687
+ const left = Math.min(x1, x2);
688
+ const naturalWidth = Math.abs(x2 - x1);
689
+ const width = Math.max(naturalWidth, minSize);
649
690
  return {
650
691
  height: nodeWidth,
651
- width: Math.abs(xScale.getPixelForValue(nodeY(node) + size) - x),
652
- x,
692
+ width,
693
+ x: left - (width - naturalWidth) / 2,
653
694
  y: getColumnPixel(yScale, nodeX(node), maxColumn, columnPadding)
654
695
  };
655
696
  }
656
- const y = yScale.getPixelForValue(nodeY(node));
697
+ const y1 = yScale.getPixelForValue(nodeY(node));
698
+ const y2 = yScale.getPixelForValue(nodeY(node) + size);
699
+ const top = Math.min(y1, y2);
700
+ const naturalHeight = Math.abs(y2 - y1);
701
+ const height = Math.max(naturalHeight, minSize);
657
702
  return {
658
- height: Math.abs(yScale.getPixelForValue(nodeY(node) + size) - y),
703
+ height,
659
704
  width: nodeWidth,
660
705
  x: getColumnPixel(xScale, nodeX(node), maxColumn, columnPadding),
661
- y
706
+ y: top - (height - naturalHeight) / 2
662
707
  };
663
708
  }
664
709
  function resolveNodeGap(option, node) {
@@ -674,6 +719,9 @@ function resolveNodeGap(option, node) {
674
719
  before: resolved.before ?? 10
675
720
  };
676
721
  }
722
+ function resolveNodeMinSize(option, node) {
723
+ return resolveNodeOption(option ?? 0, node) ?? 0;
724
+ }
677
725
  function resolveNodeLabelStyle(options, node) {
678
726
  const { backgroundColor, borderRadius = 0, color, display, font, padding = 4, position } = options.nodeLabels ?? {};
679
727
  return {
@@ -686,6 +734,10 @@ function resolveNodeLabelStyle(options, node) {
686
734
  position: resolveNodeOption(position, node) ?? 'auto'
687
735
  };
688
736
  }
737
+ const NODE_SCOPED_OPTIONS = new Set([
738
+ 'nodeMinSize',
739
+ 'nodePadding'
740
+ ]);
689
741
  class SankeyController extends DatasetController {
690
742
  parseObjectData(meta, data, start, count) {
691
743
  const sankeyData = getParsedData(data, this.options.parsing);
@@ -695,13 +747,17 @@ class SankeyController extends DatasetController {
695
747
  const orientation = this.options.orientation ?? 'horizontal';
696
748
  this._nodes = nodes;
697
749
  const nodeGaps = new Map();
750
+ const nodeMinSizes = new Map();
698
751
  for (const node of nodes.values()){
699
752
  nodeGaps.set(node.key, resolveNodeGap(this.options.nodePadding, node));
753
+ nodeMinSizes.set(node.key, resolveNodeMinSize(this.options.nodeMinSize, node));
700
754
  }
701
755
  const { maxX, maxY } = layout(nodes, sankeyData, {
702
756
  height: orientation === 'vertical' ? this.chart.width : this.chart.height,
703
757
  modeX: this.options.modeX,
758
+ nodeMinSize: nodeMinSizes,
704
759
  nodePadding: nodeGaps,
760
+ nodePaddingMode: this.options.nodePaddingMode,
705
761
  priority: !!this.options.priority
706
762
  });
707
763
  this._maxX = maxX;
@@ -765,7 +821,8 @@ class SankeyController extends DatasetController {
765
821
  const chartArea = this.chart.chartArea;
766
822
  for (const node of nodes.values()){
767
823
  const max = getNodeSize(node, size);
768
- const { height, width, x, y } = getNodeRect(node, max, xScale, yScale, this._maxX, nodeWidth, columnPadding, orientation);
824
+ const minSize = resolveNodeMinSize(options.nodeMinSize, node);
825
+ const { height, width, x, y } = getNodeRect(node, max, xScale, yScale, this._maxX, nodeWidth, columnPadding, orientation, minSize);
769
826
  const label = labels?.[node.key] ?? node.key;
770
827
  const labelStyle = resolveNodeLabelStyle(options, node);
771
828
  if (labelStyle.display) {
@@ -805,7 +862,8 @@ class SankeyController extends DatasetController {
805
862
  ctx.fillStyle = node.color ?? 'black';
806
863
  if (!xScale || !yScale) return;
807
864
  const max = Math[sizeMethod](node.in || node.out, node.out || node.in);
808
- const { height, width, x, y } = getNodeRect(node, max, xScale, yScale, this._maxX, nodeWidth, columnPadding, orientation);
865
+ const minSize = resolveNodeMinSize(this.options.nodeMinSize, node);
866
+ const { height, width, x, y } = getNodeRect(node, max, xScale, yScale, this._maxX, nodeWidth, columnPadding, orientation, minSize);
809
867
  if (borderWidth) {
810
868
  ctx.strokeRect(x, y, width, height);
811
869
  }
@@ -848,7 +906,7 @@ class SankeyController extends DatasetController {
848
906
  SankeyController.id = 'sankey';
849
907
  SankeyController.descriptors = {
850
908
  _indexable: false,
851
- _scriptable: (name)=>name !== 'nodePadding',
909
+ _scriptable: (name)=>!NODE_SCOPED_OPTIONS.has(name),
852
910
  nodeLabels: {
853
911
  _indexable: false,
854
912
  _scriptable: false
@@ -885,7 +943,9 @@ SankeyController.defaults = {
885
943
  color: 'black',
886
944
  dataElementType: 'flow',
887
945
  modeX: 'edge',
946
+ nodeMinSize: 0,
888
947
  nodePadding: 10,
948
+ nodePaddingMode: 'auto',
889
949
  nodeWidth: 10,
890
950
  orientation: 'horizontal',
891
951
  transitions: {
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.17.0
2
+ * chartjs-chart-sankey v0.19.0
3
3
  * https://chartjs-chart-sankey.pages.dev/
4
4
  * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
6
6
  */
7
- !function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["exports","chart.js","chart.js/helpers"],o):o((t="undefined"!=typeof globalThis?globalThis:t||self)["chartjs-chart-sankey"]={},t.Chart,t.Chart.helpers)}(this,function(t,o,e){"use strict";const r=t=>void 0!==t;function n(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}function i(t,o){return"function"==typeof t?t(o):t&&"object"==typeof t&&!function(t){const o=Object.prototype.toString.call(t);return"[object CanvasPattern]"===o||"[object CanvasGradient]"===o}(t)?t[o.key]:t}function a(t,o,e,r,n,i,a){t.save(),t.fillStyle=o,a>0?(!function(t,o,e,r,n,i){const a=Math.max(0,Math.min(i,r/2,n/2));t.beginPath(),t.moveTo(o+a,e),t.lineTo(o+r-a,e),t.quadraticCurveTo(o+r,e,o+r,e+a),t.lineTo(o+r,e+n-a),t.quadraticCurveTo(o+r,e+n,o+r-a,e+n),t.lineTo(o+a,e+n),t.quadraticCurveTo(o,e+n,o,e+n-a),t.lineTo(o,e+a),t.quadraticCurveTo(o,e,o+a,e),t.closePath()}(t,e,r,n,i,a),t.fill()):t.fillRect(e,r,n,i),t.restore()}function s(t,o,e){const r=function(t){if(!t)return[];const o=[],e=Array.isArray(t)?t:[t];for(;e.length;){const t=e.pop();"string"==typeof t?o.unshift(...t.split("\n")):Array.isArray(t)?e.push(...t):t&&o.unshift(`${t}`)}return o}(o);if(!r.length)return;const{backgroundColor:n,borderRadius:i,color:s,font:l,lineOffset:h,padding:c}=e,f=function(t,o){return"auto"===t?o:t}(e.position,e.autoPosition),d=Number(l.lineHeight);t.font=l.string;const u=Math.max(...r.map(o=>t.measureText(o).width)),p=d*r.length,y=function(t,o,e){const{borderWidth:r,height:n,padding:i,width:a,x:s,y:l}=o,h={align:"center",x:s+a/2,y:l+n/2};return"left"===t?(h.align="right",h.x=s-r-i):"right"===t?(h.align="left",h.x=s+a+r+i):"top"===t?h.y=l-i-e/2:"bottom"===t&&(h.y=l+n+i+e/2),h}(f,e,p);t.textAlign=y.align,t.textBaseline="middle";const g=u+2*c,x=p+2*c,m=function(t,o,e,r,n){return"left"===t?o-n:"right"===t?o-e-n:o-r/2}(y.align,y.x,u,g,c),b=1===r.length?y.y:y.y-p/2+h,w=b+(r.length-1)*d/2;void 0!==n&&a(t,n,m,w-x/2,g,x,i),t.fillStyle=s;for(let o=0;o<r.length;o++)t.fillText(r[o],y.x,b+o*d)}const l=(t,o)=>o.flow===t.flow?t.index-o.index:o.flow-t.flow;function h(t,{size:o,priority:e,column:r}){const i=new Map;for(let o=0;o<t.length;o++){const{from:e,to:r,flow:n}=t[o],a=i.get(e)??{from:[],in:0,key:e,out:0,size:0,to:[]},s=(e===r?a:i.get(r))??{from:[],in:0,key:r,out:0,size:0,to:[]};a.out+=n,a.to.push({addY:0,flow:n,index:o,key:r,node:s}),1===a.to.length&&i.set(e,a),s.in+=n,s.from.push({addY:0,flow:n,index:o,key:e,node:a}),1===s.from.length&&i.set(r,s)}return((t,o)=>{const e=n(o);for(const o of t.values())o.from.sort(l),o.to.sort(l),o.size=Math[e](o.in||o.out,o.out||o.in)})(i,o),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.priority=o[e.key])})(i,e),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.column=!0,e.x=o[e.key])})(i,r),i}const c=1e-6;function f(t){return t.x??0}function d(t){return t.y??0}const u=(t,o=new Set)=>{const e=[];for(const r of t)o.has(r.key)||(o.add(r.key),e.push(r.key,...u(r.to.map(t=>t.node),o)));return e},p=(t,o)=>{const e=o.filter(t=>0===t.from.length),r=e.map(t=>t.key),n=u(e),i=new Set(n);for(const o of t)i.has(o.from)||i.has(o.to)||(r.push(o.from),i.add(o.from)),i.add(o.to);return r},y=(t,o)=>{const e=new Set(t.filter(t=>o.has(t.from)).map(t=>t.to)),r=[...o],n=r.filter(t=>!e.has(t));return n.length?n:r.slice(0,1)};function g(t,o,e=new Set){let r=0;for(const n of t)e.has(n.node)||(e.add(n.node),r+=n.node[o].length+g(n.node[o],o,e));return r}const x=t=>(o,e)=>g(o.node[t],t)-g(e.node[t],t)||o.node[t].length-e.node[t].length;function m(t,o){if(!t.from.length)return o;t.from.sort(x("from"));for(const e of t.from){const t=e.node;r(t.y)||(t.y=o,m(t,o?o+c:0)),o=Math.max(t.y+t.out,o)}return d(t)+t.size}const b=(t,o)=>Boolean(o&&f(o)<f(t));function w(t,o){if(!t.to.length)return o;t.to.sort(x("to"));for(let e=0;e<t.to.length;e++){const n=t.to[e],i=n.node;r(i.y)||(i.y=o,w(i,o?o+c:0)),b(i,t.to[e+1]?.node)?o+=n.flow:o=Math.max(i.y+Math.max(i.in,i.out),o)}return d(t)+t.size}function M(t,o){return r(t.y)?t.y:(t.y=o,o)}function v(t,o){if(!t.length)return 0;const e=((t,o)=>{const e=[...t].sort((t,o)=>t.size-o.size),r=e[e.length-1].size,n=t.filter(t=>t.size===r),i=n[0];if(1===n.length)return i;if(n.sort((t,o)=>f(t)-f(o)),0===f(i))return i;const a=n[n.length-1];return f(a)===o?a:n[Math.floor(n.length/2)]})(t,o);return e.y=0,m(e,0),w(e,0),function(t,o){const e=t.filter(t=>0===t.x),n=t.filter(t=>t.x===o),i=e.filter(t=>!r(t.y)),a=n.filter(t=>!r(t.y)),s=t.filter(t=>f(t)>0&&f(t)<o&&!r(t.y));let l=e.reduce((t,o)=>Math.max(t,d(o)+o.out||0),0)+c,h=n.reduce((t,o)=>Math.max(t,d(o)+o.in||0),0)+c,u=0;l>=h?(i.forEach(t=>{l=M(t,l),l=Math.max(l+t.out,w(t,l))}),a.forEach(t=>{h=M(t,h),h=Math.max(h+t.in,m(t,h))})):(i.forEach(t=>{l=M(t,l)}),a.forEach(t=>{h=M(t,h),h=Math.max(h+t.in,m(t,h))})),s.forEach(o=>{let e=t.filter(t=>f(t)===f(o)&&r(t.y)).reduce((t,o)=>Math.max(t,d(o)+Math.max(o.in,o.out)),0);e=M(o,e),e=Math.max(e+o.in,m(o,e)),e=Math.max(e+o.out,w(o,e)),u=Math.max(u,e)}),Math.max(l,h,u)}(t,o),((t,o)=>{let e=0;for(let r=0;r<=o;r++){const o=t.filter(t=>f(t)===r).sort((t,o)=>d(t)-d(o));let n=0;for(const t of o)d(t)<n&&(t.y=n),n=d(t)+t.size;e=Math.max(e,n)}return e})(t,o)}const k=(t,o)=>f(t)!==f(o)?f(t)-f(o):d(t)===d(o)?t.size-o.size:d(t)-d(o);function C(t,o,e,r){let n=r;for(let r=0;r<o;r++){const o=t[r].yHistory;for(let t=0;t<o.length&&!(o[t]>e);t++)n=Math.max(t+1,n)}return n}function P(t,o,e){const r=t.realCount,n=r>0?Math.max(t.lastAfter,o.before):0,i=t.realCumOffset+n,a=e-r;return t.realCount=r+1,t.realCumOffset=i,t.lastAfter=o.after,i+a*o.before}function _(t,o,{priority:e,height:n,nodePadding:i,modeX:a}){const s=[...t.values()],l=function(t,o,e){const n=o.filter(t=>t.from!==t.to),i=[...t.keys()],a=[...t.values()],s=new Set(i);let l=0;for(;s.size;){const e=0===l?p(o,a):y(n,s);if(!e.length)throw new Error("Fatal error: Unable to place nodes to columns. Please report this issue.");for(const o of e){const e=t.get(o);e&&!r(e.x)&&(e.x=l),s.delete(o)}s.size&&l++}const h=a.reduce((t,o)=>Math.max(t,f(o)),0);if("edge"===e){const e=new Set(o.map(t=>t.from));i.filter(t=>!e.has(t)).forEach(o=>{const e=t.get(o);e&&!e.column&&(e.x=h)})}return h}(t,o,a??"edge"),h=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const i=t.filter(t=>f(t)===n).sort((t,o)=>(t.priority??0)-(o.priority??0));if(i.length){const o=t.reduce((t,o)=>f(o)>n?Math.min(t,f(o)):t,1/0);r=i[0].to.filter(t=>f(t.node)>o).reduce((t,o)=>t+o.flow,0)||0}for(const t of i)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(s,l):v(s,l),c=h/n,u=new Map;for(const[t,o]of i)u.set(t,{after:o.after*c,before:o.before*c});const g=function(t,o){let e=0;const r=new Map,n=[],i=t=>{if(!r.has(t)){const o=n.length;return r.set(t,o),n.push({lastAfter:0,realCount:0,realCumOffset:0,yHistory:[]}),o}return r.get(t)??0};t.sort(k);for(const r of t){const t=i(f(r)),a=n[t],s=o.get(r.key)??{after:0,before:0},l=d(r);if(l){a.yHistory.push(l);let o=a.yHistory.length;if(r.in)for(o=C(n,t,l,o);a.yHistory.length<o;)a.yHistory.push(l);r.y=l+P(a,s,o)}else a.realCount+=1,a.lastAfter=s.after;e=Math.max(e,d(r)+Math.max(r.in,r.out))}return e}(s,u);return function(t){t.forEach(t=>{const o=t.size,e=o<t.in,r=o<t.out;let n=0,i=t.from.length;t.from.sort((t,o)=>d(t.node)+t.node.out/2-(d(o.node)+o.node.out/2)).forEach((t,r)=>{e?t.addY=r*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)}),n=0,i=t.to.length,t.to.sort((t,o)=>d(t.node)+t.node.in/2-(d(o.node)+o.node.in/2)).forEach((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)})})}(s),{maxX:l,maxY:g}}function T(t){return t.x??0}function S(t){return t.y??0}function z(t,o){return Math[o](t.in||t.out,t.out||t.in)}function F(t,o,e,r){return"vertical"===r?o<(e.top+e.bottom)/2?"bottom":"top":t<(e.left+e.right)/2?"right":"left"}function E(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}function O(t,o,e,r,n,i,a,s,l){return"vertical"===l?{_custom:{flow:n,from:t,height:a.parse(n,i),to:o,x:a.parse(r,i),y:s.parse(T(o),i)},x:a.parse(e,i),y:s.parse(T(t),i)}:{_custom:{flow:n,from:t,height:s.parse(n,i),to:o,x:a.parse(T(o),i),y:s.parse(r,i)},x:a.parse(T(t),i),y:s.parse(e,i)}}function R(t,o,e,r,n,i,a,s){const l=t._custom,h=o.getPixelForValue(t.x),c=e.getPixelForValue(t.y);return"vertical"===s?{flow:l.flow,from:l.from,height:0,to:l.to,width:Math.abs(o.getPixelForValue(t.x+l.height)-h),x:h,x2:o.getPixelForValue(l.x),y:W(e,t.y,r,i)+n+a,y2:W(e,l.y,r,i)-a}:{flow:l.flow,from:l.from,height:Math.abs(e.getPixelForValue(t.y+l.height)-c),to:l.to,width:0,x:W(o,t.x,r,i)+n+a,x2:W(o,l.x,r,i)-a,y:c,y2:e.getPixelForValue(l.y)}}function W(t,o,e,r){const n=t.getPixelForValue(o);return e?n-o/e*r:n}function X(t,o,e){const r="vertical"===o?e.height-e.chartArea.bottom:e.width-e.chartArea.right;return Math.max(0,t+3-r)}function Y(t,o,e,r,n,i,a,s){if("vertical"===s){const s=e.getPixelForValue(S(t));return{height:i,width:Math.abs(e.getPixelForValue(S(t)+o)-s),x:s,y:W(r,T(t),n,a)}}const l=r.getPixelForValue(S(t));return{height:Math.abs(r.getPixelForValue(S(t)+o)-l),width:i,x:W(e,T(t),n,a),y:l}}function j(t,o){const e=i(t??10,o)??10;return"number"==typeof e?{after:e,before:e}:{after:e.after??10,before:e.before??10}}function A(t,o){const{backgroundColor:e,borderRadius:r=0,color:n,display:a,font:s,padding:l=4,position:h}=t.nodeLabels??{};return{backgroundColor:i(e,o),borderRadius:r,color:i(n,o)??t.color??"black",display:i(a,o)??!0,font:s,padding:l,position:i(h,o)??"auto"}}class V extends o.DatasetController{parseObjectData(t,o,e,r){const n=((t,o)=>{const{from:e="from",to:r="to",flow:n="flow"}=o;return t.map(({[e]:t,[r]:o,[n]:i})=>({flow:i,from:t,to:o}))})(o,this.options.parsing),{xScale:i,yScale:a}=t,s=[],l=h(n,this.options),c=this.options.orientation??"horizontal";this._nodes=l;const f=new Map;for(const t of l.values())f.set(t.key,j(this.options.nodePadding,t));const{maxX:d,maxY:u}=_(l,n,{height:"vertical"===c?this.chart.width:this.chart.height,modeX:this.options.modeX,nodePadding:f,priority:!!this.options.priority});if(this._maxX=d,this._maxY=u,!i||!a)return[];for(let t=0,o=n.length;t<o;++t){const o=n[t],e=l.get(o.from),r=l.get(o.to);if(!e||!r)continue;const h=S(e)+E(e.to,o.to,t),f=S(r)+E(r.from,o.from,t);s.push(O(e,r,h,f,o.flow,t,i,a,c))}return s.slice(e,e+r)}getMinMax(t){return{max:t===("vertical"===this.options.orientation?this._cachedMeta.yScale:this._cachedMeta.xScale)?this._maxX:this._maxY,min:0}}update(t){const{data:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,o,e,r){const{xScale:n,yScale:i}=this._cachedMeta;if(!n||!i)return;const a=this.resolveDataElementOptions(o,r),s=this.getSharedOptions(a),{borderWidth:l,nodeWidth:h=10,orientation:c="horizontal"}=this.options,f=X(h,c,this.chart),d=l?l/2+.5:0;for(let a=o;a<o+e;a++){const o=this.getParsed(a);this.updateElement(t[a],a,{options:this.resolveDataElementOptions(a,r),progress:"reset"===r?0:1,...R(o,n,i,this._maxX,h,f,d,c)},r)}s&&this.updateSharedOptions(s,r,a)}_drawLabels(){const t=this.chart.ctx,r=this.options,i=this._nodes||new Map,a=n(r.size),l=r.labels,{borderWidth:h=1,nodeWidth:c=10,orientation:f="horizontal"}=r,d=X(c,f,this.chart),u=r.font??this.chart.options.font??o.Chart.defaults.font,{xScale:p,yScale:y}=this._cachedMeta;if(!p||!y)return;t.save();const g=this.chart.chartArea;for(const o of i.values()){const n=z(o,a),{height:i,width:x,x:m,y:b}=Y(o,n,p,y,this._maxX,c,d,f),w=l?.[o.key]??o.key,M=A(r,o);if(M.display){const o=e.toFont(M.font??u);s(t,w,{autoPosition:F(m,b,g,f),backgroundColor:M.backgroundColor,borderRadius:M.borderRadius,borderWidth:h,color:M.color,font:o,height:i,lineOffset:e.valueOrDefault(r.padding,o.lineHeight/2),padding:M.padding,position:M.position,width:x,x:m,y:b})}}t.restore()}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:r=0,nodeWidth:i=10,orientation:a="horizontal",size:s}=this.options,l=X(i,a,this.chart),h=n(s),{xScale:c,yScale:f}=this._cachedMeta;t.save(),e&&r&&(t.strokeStyle=e,t.lineWidth=r);for(const e of o.values()){if(t.fillStyle=e.color??"black",!c||!f)return;const o=Math[h](e.in||e.out,e.out||e.in),{height:n,width:s,x:d,y:u}=Y(e,o,c,f,this._maxX,i,l,a);r&&t.strokeRect(d,u,s,n),t.fillRect(d,u,s,n)}t.restore()}draw(){const t=this.chart.ctx,o=this.getMeta().data??[],e=[];for(let t=0,r=o.length;t<r;++t){const r=o[t];r.from&&r.to&&(r.from.color=r.options.colorFrom,r.to.color=r.options.colorTo,r.active&&e.push(r))}for(const t of e)t.from&&t.to&&(t.from.color=t.options.colorFrom,t.to.color=t.options.colorTo);this._drawNodes();for(let e=0,r=o.length;e<r;++e)o[e].draw(t);this._drawLabels()}constructor(...t){super(...t),this._nodes=new Map,this._maxX=0,this._maxY=0}}V.id="sankey",V.descriptors={_indexable:!1,_scriptable:t=>"nodePadding"!==t,nodeLabels:{_indexable:!1,_scriptable:!1}},V.defaults={animations:{colors:{properties:["colorFrom","colorTo"],type:"color"},numbers:{properties:["x","y","x2","y2","height","width"],type:"number"},progress:{delay:t=>"data"===t.type?500*t.parsed["vertical"===t.dataset.orientation?"y":"x"]+20*t.dataIndex:void 0,duration:t=>"data"===t.type?200*(t.parsed._custom["vertical"===t.dataset.orientation?"y":"x"]-t.parsed["vertical"===t.dataset.orientation?"y":"x"]):void 0,easing:"linear"}},borderColor:"black",borderWidth:1,color:"black",dataElementType:"flow",modeX:"edge",nodePadding:10,nodeWidth:10,orientation:"horizontal",transitions:{hide:{animations:{colors:{properties:["colorFrom","colorTo"],to:"transparent",type:"color"}}},resize:{animations:{progress:{delay:0,duration:0}}},show:{animations:{colors:{from:"transparent",properties:["colorFrom","colorTo"],type:"color"}}}}},V.overrides={datasets:{clip:!1,parsing:{flow:"flow",from:"from",to:"to"}},interaction:{intersect:!0,mode:"nearest"},layout:{padding:{bottom:3,left:3,right:13,top:3}},plugins:{legend:{display:!1},tooltip:{callbacks:{label(t){const o=t.parsed._custom;return`${o.from.key} -> ${o.to.key}: ${o.flow}`},title:()=>""}}},scales:{x:{bounds:"data",display:!1,min:0,offset:!1,type:"linear"},y:{bounds:"data",display:!1,min:0,offset:!1,reverse:!0,type:"linear"}}};const H=(t,o,e,r,n)=>"vertical"===n?o<r?{cp1:{x:t,y:o+(r-o)/3*2},cp2:{x:e,y:o+(r-o)/3}}:{cp1:{x:0,y:o-(o-r)/3},cp2:{x:0,y:r+(o-r)/3}}:t<e?{cp1:{x:t+(e-t)/3*2,y:o},cp2:{x:t+(e-t)/3,y:r}}:{cp1:{x:t-(t-e)/3,y:0},cp2:{x:e+(t-e)/3,y:0}},L=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),q=(t,o)=>e.color(t).alpha(o).rgbString(),D=(t,o)=>"string"==typeof t?q(t,o):t,$=t=>"string"==typeof t?e.getHoverColor(t):t;class G extends o.Element{draw(t){const{x:r,x2:n,y:i,y2:a,height:l,progress:h,width:c}=this,f=this.options.orientation,d=H(r,i,n,a,f),u={height:l,width:c,x:r,x2:n,y:i,y2:a};if(0===h)return;t.save(),h<1&&function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},s,l){t.beginPath(),"vertical"===l?t.rect(Math.min(r,n),i,Math.abs(n-r)+e+1,(a-i)*s+1):t.rect(r,Math.min(i,a),(n-r)*s+1,Math.abs(a-i)+o+1),t.clip()}(t,u,h,f),function(t,{x:o,x2:e,y:r,y2:n,options:i}){let a="black";null!==i.flowColor?a=i.flowColor:"from"===i.colorMode?a=D(i.colorFrom,i.alpha):"to"===i.colorMode?a=D(i.colorTo,i.alpha):"string"==typeof i.colorFrom&&"string"==typeof i.colorTo&&(a="vertical"===i.orientation?t.createLinearGradient(0,r,0,n):t.createLinearGradient(o,0,e,0),a.addColorStop(0,q(i.colorFrom,i.alpha)),a.addColorStop(1,q(i.colorTo,i.alpha))),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},{cp1:s,cp2:l},h){t.beginPath(),t.moveTo(r,i),t.bezierCurveTo(s.x,s.y,l.x,l.y,n,a),"vertical"===h?(t.lineTo(n+e,a),t.bezierCurveTo(l.x+e,l.y,s.x+e,s.y,r+e,i)):(t.lineTo(n,a+o),t.bezierCurveTo(l.x,l.y+o,s.x,s.y+o,r,i+o)),t.lineTo(r,i),t.stroke(),t.closePath(),t.fill()}(t,u,d,f);const p=this.options.flowLabels;if(p.display){const r=e.toFont(p.font??o.Chart.defaults.font),n=function({height:t,width:o,x:e,x2:r,y:n,y2:i},a){return"vertical"===a?{height:i-n,width:Math.abs(r-e)+o,x:Math.min(e,r),y:n}:{height:Math.abs(i-n)+t,width:r-e,x:e,y:Math.min(n,i)}}(u,f);s(t,`${this.flow}`,{autoPosition:"center",backgroundColor:p.backgroundColor,borderRadius:p.borderRadius,borderWidth:0,color:p.color,font:r,height:n.height,lineOffset:r.lineHeight/2,padding:p.padding,position:p.position,width:n.width,x:n.x,y:n.y})}t.restore()}inRange(t,o,e){const{x:r,y:n,x2:i,y2:a,height:s,width:l}=this.getProps(["x","y","x2","y2","height","width"],e),h="vertical"===this.options.orientation;if(h?o<n||o>a:t<r||t>i)return!1;const{cp1:c,cp2:f}=H(r,n,i,a,this.options.orientation),d=h?(o-n)/(a-n):(t-r)/(i-r),u={x:i,y:a},p=L({x:r,y:n},c,d),y=L(c,f,d),g=L(f,u,d),x=L(p,y,d),m=L(y,g,d),b=L(x,m,d);return h?t>=b.x&&t<=b.x+l:o>=b.y&&o<=b.y+s}inXRange(t,o){const{x:e,x2:r,width:n}=this.getProps(["x","x2","width"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?n:0);return t>=i&&t<=a}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?0:n);return t>=i&&t<=a}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:i,width:a}=this.getProps(["x","y","x2","y2","height","width"],t),s="vertical"===this.options.orientation;return{x:(o+r+(s?a:0))/2,y:(e+n+(s?0:i))/2}}tooltipPosition(t=!1){return this.getCenterPoint(t)}getRange(t){const o="vertical"===this.options.orientation;return"x"===t?o?this.width/2:0:o?0:this.height/2}constructor(t){super(),this.flow=0,this.x2=0,this.y2=0,this.width=0,this.height=0,this.progress=1,t&&Object.assign(this,t)}}G.id="flow",G.defaults={alpha:.5,colorFrom:"red",colorMode:"gradient",colorTo:"green",flowColor:null,flowLabels:{borderRadius:0,color:"black",display:!1,padding:4,position:"center"},hoverColorFrom:(t,o)=>$(o.colorFrom),hoverColorTo:(t,o)=>$(o.colorTo),orientation:"horizontal"},G.descriptors={_scriptable:!0,flowLabels:{_scriptable:!0}},o.Chart.register(V,G),t.Flow=G,t.SankeyController=V});
7
+ !function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["exports","chart.js","chart.js/helpers"],o):o((t="undefined"!=typeof globalThis?globalThis:t||self)["chartjs-chart-sankey"]={},t.Chart,t.Chart.helpers)}(this,function(t,o,e){"use strict";const n=t=>void 0!==t;function r(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}function i(t,o){return"function"==typeof t?t(o):t&&"object"==typeof t&&!function(t){const o=Object.prototype.toString.call(t);return"[object CanvasPattern]"===o||"[object CanvasGradient]"===o}(t)?t[o.key]:t}function a(t,o,e,n,r,i,a){t.save(),t.fillStyle=o,a>0?(!function(t,o,e,n,r,i){const a=Math.max(0,Math.min(i,n/2,r/2));t.beginPath(),t.moveTo(o+a,e),t.lineTo(o+n-a,e),t.quadraticCurveTo(o+n,e,o+n,e+a),t.lineTo(o+n,e+r-a),t.quadraticCurveTo(o+n,e+r,o+n-a,e+r),t.lineTo(o+a,e+r),t.quadraticCurveTo(o,e+r,o,e+r-a),t.lineTo(o,e+a),t.quadraticCurveTo(o,e,o+a,e),t.closePath()}(t,e,n,r,i,a),t.fill()):t.fillRect(e,n,r,i),t.restore()}function s(t,o,e){const n=function(t){if(!t)return[];const o=[],e=Array.isArray(t)?t:[t];for(;e.length;){const t=e.pop();"string"==typeof t?o.unshift(...t.split("\n")):Array.isArray(t)?e.push(...t):t&&o.unshift(`${t}`)}return o}(o);if(!n.length)return;const{backgroundColor:r,borderRadius:i,color:s,font:l,lineOffset:h,padding:c}=e,f=function(t,o){return"auto"===t?o:t}(e.position,e.autoPosition),d=Number(l.lineHeight);t.font=l.string;const u=Math.max(...n.map(o=>t.measureText(o).width)),p=d*n.length,y=function(t,o,e){const{borderWidth:n,height:r,padding:i,width:a,x:s,y:l}=o,h={align:"center",x:s+a/2,y:l+r/2};return"left"===t?(h.align="right",h.x=s-n-i):"right"===t?(h.align="left",h.x=s+a+n+i):"top"===t?h.y=l-i-e/2:"bottom"===t&&(h.y=l+r+i+e/2),h}(f,e,p);t.textAlign=y.align,t.textBaseline="middle";const x=u+2*c,g=p+2*c,m=function(t,o,e,n,r){return"left"===t?o-r:"right"===t?o-e-r:o-n/2}(y.align,y.x,u,x,c),w=1===n.length?y.y:y.y-p/2+h,b=w+(n.length-1)*d/2;void 0!==r&&a(t,r,m,b-g/2,x,g,i),t.fillStyle=s;for(let o=0;o<n.length;o++)t.fillText(n[o],y.x,w+o*d)}const l=(t,o)=>o.flow===t.flow?t.index-o.index:o.flow-t.flow;function h(t,{size:o,priority:e,column:n}){const i=new Map;for(let o=0;o<t.length;o++){const{from:e,to:n,flow:r}=t[o],a=i.get(e)??{from:[],in:0,key:e,out:0,size:0,to:[]},s=(e===n?a:i.get(n))??{from:[],in:0,key:n,out:0,size:0,to:[]};a.out+=r,a.to.push({addY:0,flow:r,index:o,key:n,node:s}),1===a.to.length&&i.set(e,a),s.in+=r,s.from.push({addY:0,flow:r,index:o,key:e,node:a}),1===s.from.length&&i.set(n,s)}return((t,o)=>{const e=r(o);for(const o of t.values())o.from.sort(l),o.to.sort(l),o.size=Math[e](o.in||o.out,o.out||o.in)})(i,o),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.priority=o[e.key])})(i,e),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.column=!0,e.x=o[e.key])})(i,n),i}const c=1e-6;function f(t){return t.x??0}function d(t){return t.y??0}const u=(t,o=new Set)=>{const e=[];for(const n of t)o.has(n.key)||(o.add(n.key),e.push(n.key,...u(n.to.map(t=>t.node),o)));return e},p=(t,o)=>{const e=o.filter(t=>0===t.from.length),n=e.map(t=>t.key),r=u(e),i=new Set(r);for(const o of t)i.has(o.from)||i.has(o.to)||(n.push(o.from),i.add(o.from)),i.add(o.to);return n},y=(t,o)=>{const e=new Set(t.filter(t=>o.has(t.from)).map(t=>t.to)),n=[...o],r=n.filter(t=>!e.has(t));return r.length?r:n.slice(0,1)};function x(t,o,e=new Set){let n=0;for(const r of t)e.has(r.node)||(e.add(r.node),n+=r.node[o].length+x(r.node[o],o,e));return n}const g=t=>(o,e)=>x(o.node[t],t)-x(e.node[t],t)||o.node[t].length-e.node[t].length;function m(t,o){if(!t.from.length)return o;t.from.sort(g("from"));for(const e of t.from){const t=e.node;n(t.y)||(t.y=o,m(t,o?o+c:0)),o=Math.max(t.y+t.out,o)}return d(t)+t.size}const w=(t,o)=>Boolean(o&&f(o)<f(t));function b(t,o){if(!t.to.length)return o;t.to.sort(g("to"));for(let e=0;e<t.to.length;e++){const r=t.to[e],i=r.node;n(i.y)||(i.y=o,b(i,o?o+c:0)),w(i,t.to[e+1]?.node)?o+=r.flow:o=Math.max(i.y+Math.max(i.in,i.out),o)}return d(t)+t.size}function M(t,o){return n(t.y)?t.y:(t.y=o,o)}function v(t,o){if(!t.length)return 0;const e=((t,o)=>{const e=[...t].sort((t,o)=>t.size-o.size),n=e[e.length-1].size,r=t.filter(t=>t.size===n),i=r[0];if(1===r.length)return i;if(r.sort((t,o)=>f(t)-f(o)),0===f(i))return i;const a=r[r.length-1];return f(a)===o?a:r[Math.floor(r.length/2)]})(t,o);return e.y=0,m(e,0),b(e,0),function(t,o){const e=t.filter(t=>0===t.x),r=t.filter(t=>t.x===o),i=e.filter(t=>!n(t.y)),a=r.filter(t=>!n(t.y)),s=t.filter(t=>f(t)>0&&f(t)<o&&!n(t.y));let l=e.reduce((t,o)=>Math.max(t,d(o)+o.out||0),0)+c,h=r.reduce((t,o)=>Math.max(t,d(o)+o.in||0),0)+c,u=0;l>=h?(i.forEach(t=>{l=M(t,l),l=Math.max(l+t.out,b(t,l))}),a.forEach(t=>{h=M(t,h),h=Math.max(h+t.in,m(t,h))})):(i.forEach(t=>{l=M(t,l)}),a.forEach(t=>{h=M(t,h),h=Math.max(h+t.in,m(t,h))})),s.forEach(o=>{let e=t.filter(t=>f(t)===f(o)&&n(t.y)).reduce((t,o)=>Math.max(t,d(o)+Math.max(o.in,o.out)),0);e=M(o,e),e=Math.max(e+o.in,m(o,e)),e=Math.max(e+o.out,b(o,e)),u=Math.max(u,e)}),Math.max(l,h,u)}(t,o),((t,o)=>{let e=0;for(let n=0;n<=o;n++){const o=t.filter(t=>f(t)===n).sort((t,o)=>d(t)-d(o));let r=0;for(const t of o)d(t)<r&&(t.y=r),r=d(t)+t.size;e=Math.max(e,r)}return e})(t,o)}const k=(t,o)=>f(t)!==f(o)?f(t)-f(o):d(t)===d(o)?t.size-o.size:d(t)-d(o);function C(t,o,e){const n=(o.get(t.key)??0)*e;return Math.max(0,n-t.size)/2}function z(t,o,e){const n=(o.get(t.key)??0)*e;return Math.max(t.size,n)}function P(t,o,e,n){let r=n;for(let n=0;n<o;n++){const o=t[n].yHistory;for(let t=0;t<o.length&&!(o[t]>e);t++)r=Math.max(t+1,r)}return r}function S(t,o,e,n,r){const i=t.realCount,a=o.before*n,s=o.after*n,l=i>0?Math.max(t.lastAfter,a)+t.lastExtraHalf+r:0,h=t.realCumOffset+l,c=e-i;return t.realCount=i+1,t.realCumOffset=h,t.lastAfter=s,t.lastExtraHalf=r,h+c*a}function _(t,o,e=1,n="auto",r=new Map){if(t.sort(k),"even"===n)return function(t,o,e,n){let r,i,a,s=0,l=0;for(const h of t){const t=f(h),c=o.get(h.key)??{after:0,before:0},u=C(h,n,e);t!==r?r=t:i&&a&&(h.y=d(i)+i.size+Math.max(a.after,c.before)*e+l+u),i=h,a=c,l=u;const p=z(h,n,e);s=Math.max(s,d(h)+Math.max(h.in,h.out),d(h)+(h.size+p)/2)}return s}(t,o,e,r);let i=0;const a=new Map,s=[],l=t=>{if(!a.has(t)){const o=s.length;return a.set(t,o),s.push({lastAfter:0,lastExtraHalf:0,realCount:0,realCumOffset:0,yHistory:[]}),o}return a.get(t)??0};for(const n of t){const t=l(f(n)),a=s[t],h=o.get(n.key)??{after:0,before:0},c=d(n),u=C(n,r,e);if(c){a.yHistory.push(c);let o=a.yHistory.length;if(n.in)for(o=P(s,t,c,o);a.yHistory.length<o;)a.yHistory.push(c);n.y=c+S(a,h,o,e,u)}else a.realCount+=1,a.lastAfter=h.after*e,a.lastExtraHalf=u;const p=z(n,r,e);i=Math.max(i,d(n)+Math.max(n.in,n.out),d(n)+(n.size+p)/2)}return i}function T(t,o,{priority:e,height:r,nodePadding:i,nodePaddingMode:a,nodeMinSize:s,modeX:l}){const h=[...t.values()],c=function(t,o,e){const r=o.filter(t=>t.from!==t.to),i=[...t.keys()],a=[...t.values()],s=new Set(i);let l=0;for(;s.size;){const e=0===l?p(o,a):y(r,s);if(!e.length)throw new Error("Fatal error: Unable to place nodes to columns. Please report this issue.");for(const o of e){const e=t.get(o);e&&!n(e.x)&&(e.x=l),s.delete(o)}s.size&&l++}const h=a.reduce((t,o)=>Math.max(t,f(o)),0);if("edge"===e){const e=new Set(o.map(t=>t.from));i.filter(t=>!e.has(t)).forEach(o=>{const e=t.get(o);e&&!e.column&&(e.x=h)})}return h}(t,o,l??"edge"),u=e?function(t,o){let e=0,n=0;for(let r=0;r<=o;r++){let o=n;const i=t.filter(t=>f(t)===r).sort((t,o)=>(t.priority??0)-(o.priority??0));if(i.length){const o=t.reduce((t,o)=>f(o)>r?Math.min(t,f(o)):t,1/0);n=i[0].to.filter(t=>f(t.node)>o).reduce((t,o)=>t+o.flow,0)||0}for(const t of i)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(h,c):v(h,c),x=_(h,i,u/r,a??"auto",s??new Map);return function(t){t.forEach(t=>{const o=t.size,e=o<t.in,n=o<t.out;let r=0,i=t.from.length;t.from.sort((t,o)=>d(t.node)+t.node.out/2-(d(o.node)+o.node.out/2)).forEach((t,n)=>{e?t.addY=n*(o-t.flow)/(i-1):(t.addY=r,r+=t.flow)}),r=0,i=t.to.length,t.to.sort((t,o)=>d(t.node)+t.node.in/2-(d(o.node)+o.node.in/2)).forEach((t,e)=>{n?t.addY=e*(o-t.flow)/(i-1):(t.addY=r,r+=t.flow)})})}(h),{maxX:c,maxY:x}}function F(t){return t.x??0}function E(t){return t.y??0}function O(t,o){return Math[o](t.in||t.out,t.out||t.in)}function R(t,o,e,n){return"vertical"===n?o<(e.top+e.bottom)/2?"bottom":"top":t<(e.left+e.right)/2?"right":"left"}function H(t,o,e){for(const n of t)if(n.key===o&&n.index===e)return n.addY;return 0}function W(t,o,e,n,r,i,a,s,l){return"vertical"===l?{_custom:{flow:r,from:t,height:a.parse(r,i),to:o,x:a.parse(n,i),y:s.parse(F(o),i)},x:a.parse(e,i),y:s.parse(F(t),i)}:{_custom:{flow:r,from:t,height:s.parse(r,i),to:o,x:a.parse(F(o),i),y:s.parse(n,i)},x:a.parse(F(t),i),y:s.parse(e,i)}}function X(t,o,e,n,r,i,a,s){const l=t._custom,h=o.getPixelForValue(t.x),c=e.getPixelForValue(t.y);return"vertical"===s?{flow:l.flow,from:l.from,height:0,to:l.to,width:Math.abs(o.getPixelForValue(t.x+l.height)-h),x:h,x2:o.getPixelForValue(l.x),y:Y(e,t.y,n,i)+r+a,y2:Y(e,l.y,n,i)-a}:{flow:l.flow,from:l.from,height:Math.abs(e.getPixelForValue(t.y+l.height)-c),to:l.to,width:0,x:Y(o,t.x,n,i)+r+a,x2:Y(o,l.x,n,i)-a,y:c,y2:e.getPixelForValue(l.y)}}function Y(t,o,e,n){const r=t.getPixelForValue(o);return e?r-o/e*n:r}function j(t,o,e){const n="vertical"===o?e.height-e.chartArea.bottom:e.width-e.chartArea.right;return Math.max(0,t+3-n)}function A(t,o,e,n,r,i,a,s,l){if("vertical"===s){const s=e.getPixelForValue(E(t)),h=e.getPixelForValue(E(t)+o),c=Math.min(s,h),f=Math.abs(h-s),d=Math.max(f,l);return{height:i,width:d,x:c-(d-f)/2,y:Y(n,F(t),r,a)}}const h=n.getPixelForValue(E(t)),c=n.getPixelForValue(E(t)+o),f=Math.min(h,c),d=Math.abs(c-h),u=Math.max(d,l);return{height:u,width:i,x:Y(e,F(t),r,a),y:f-(u-d)/2}}function V(t,o){const e=i(t??10,o)??10;return"number"==typeof e?{after:e,before:e}:{after:e.after??10,before:e.before??10}}function L(t,o){return i(t??0,o)??0}function q(t,o){const{backgroundColor:e,borderRadius:n=0,color:r,display:a,font:s,padding:l=4,position:h}=t.nodeLabels??{};return{backgroundColor:i(e,o),borderRadius:n,color:i(r,o)??t.color??"black",display:i(a,o)??!0,font:s,padding:l,position:i(h,o)??"auto"}}const D=new Set(["nodeMinSize","nodePadding"]);class $ extends o.DatasetController{parseObjectData(t,o,e,n){const r=((t,o)=>{const{from:e="from",to:n="to",flow:r="flow"}=o;return t.map(({[e]:t,[n]:o,[r]:i})=>({flow:i,from:t,to:o}))})(o,this.options.parsing),{xScale:i,yScale:a}=t,s=[],l=h(r,this.options),c=this.options.orientation??"horizontal";this._nodes=l;const f=new Map,d=new Map;for(const t of l.values())f.set(t.key,V(this.options.nodePadding,t)),d.set(t.key,L(this.options.nodeMinSize,t));const{maxX:u,maxY:p}=T(l,r,{height:"vertical"===c?this.chart.width:this.chart.height,modeX:this.options.modeX,nodeMinSize:d,nodePadding:f,nodePaddingMode:this.options.nodePaddingMode,priority:!!this.options.priority});if(this._maxX=u,this._maxY=p,!i||!a)return[];for(let t=0,o=r.length;t<o;++t){const o=r[t],e=l.get(o.from),n=l.get(o.to);if(!e||!n)continue;const h=E(e)+H(e.to,o.to,t),f=E(n)+H(n.from,o.from,t);s.push(W(e,n,h,f,o.flow,t,i,a,c))}return s.slice(e,e+n)}getMinMax(t){return{max:t===("vertical"===this.options.orientation?this._cachedMeta.yScale:this._cachedMeta.xScale)?this._maxX:this._maxY,min:0}}update(t){const{data:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,o,e,n){const{xScale:r,yScale:i}=this._cachedMeta;if(!r||!i)return;const a=this.resolveDataElementOptions(o,n),s=this.getSharedOptions(a),{borderWidth:l,nodeWidth:h=10,orientation:c="horizontal"}=this.options,f=j(h,c,this.chart),d=l?l/2+.5:0;for(let a=o;a<o+e;a++){const o=this.getParsed(a);this.updateElement(t[a],a,{options:this.resolveDataElementOptions(a,n),progress:"reset"===n?0:1,...X(o,r,i,this._maxX,h,f,d,c)},n)}s&&this.updateSharedOptions(s,n,a)}_drawLabels(){const t=this.chart.ctx,n=this.options,i=this._nodes||new Map,a=r(n.size),l=n.labels,{borderWidth:h=1,nodeWidth:c=10,orientation:f="horizontal"}=n,d=j(c,f,this.chart),u=n.font??this.chart.options.font??o.Chart.defaults.font,{xScale:p,yScale:y}=this._cachedMeta;if(!p||!y)return;t.save();const x=this.chart.chartArea;for(const o of i.values()){const r=O(o,a),i=L(n.nodeMinSize,o),{height:g,width:m,x:w,y:b}=A(o,r,p,y,this._maxX,c,d,f,i),M=l?.[o.key]??o.key,v=q(n,o);if(v.display){const o=e.toFont(v.font??u);s(t,M,{autoPosition:R(w,b,x,f),backgroundColor:v.backgroundColor,borderRadius:v.borderRadius,borderWidth:h,color:v.color,font:o,height:g,lineOffset:e.valueOrDefault(n.padding,o.lineHeight/2),padding:v.padding,position:v.position,width:m,x:w,y:b})}}t.restore()}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:n=0,nodeWidth:i=10,orientation:a="horizontal",size:s}=this.options,l=j(i,a,this.chart),h=r(s),{xScale:c,yScale:f}=this._cachedMeta;t.save(),e&&n&&(t.strokeStyle=e,t.lineWidth=n);for(const e of o.values()){if(t.fillStyle=e.color??"black",!c||!f)return;const o=Math[h](e.in||e.out,e.out||e.in),r=L(this.options.nodeMinSize,e),{height:s,width:d,x:u,y:p}=A(e,o,c,f,this._maxX,i,l,a,r);n&&t.strokeRect(u,p,d,s),t.fillRect(u,p,d,s)}t.restore()}draw(){const t=this.chart.ctx,o=this.getMeta().data??[],e=[];for(let t=0,n=o.length;t<n;++t){const n=o[t];n.from&&n.to&&(n.from.color=n.options.colorFrom,n.to.color=n.options.colorTo,n.active&&e.push(n))}for(const t of e)t.from&&t.to&&(t.from.color=t.options.colorFrom,t.to.color=t.options.colorTo);this._drawNodes();for(let e=0,n=o.length;e<n;++e)o[e].draw(t);this._drawLabels()}constructor(...t){super(...t),this._nodes=new Map,this._maxX=0,this._maxY=0}}$.id="sankey",$.descriptors={_indexable:!1,_scriptable:t=>!D.has(t),nodeLabels:{_indexable:!1,_scriptable:!1}},$.defaults={animations:{colors:{properties:["colorFrom","colorTo"],type:"color"},numbers:{properties:["x","y","x2","y2","height","width"],type:"number"},progress:{delay:t=>"data"===t.type?500*t.parsed["vertical"===t.dataset.orientation?"y":"x"]+20*t.dataIndex:void 0,duration:t=>"data"===t.type?200*(t.parsed._custom["vertical"===t.dataset.orientation?"y":"x"]-t.parsed["vertical"===t.dataset.orientation?"y":"x"]):void 0,easing:"linear"}},borderColor:"black",borderWidth:1,color:"black",dataElementType:"flow",modeX:"edge",nodeMinSize:0,nodePadding:10,nodePaddingMode:"auto",nodeWidth:10,orientation:"horizontal",transitions:{hide:{animations:{colors:{properties:["colorFrom","colorTo"],to:"transparent",type:"color"}}},resize:{animations:{progress:{delay:0,duration:0}}},show:{animations:{colors:{from:"transparent",properties:["colorFrom","colorTo"],type:"color"}}}}},$.overrides={datasets:{clip:!1,parsing:{flow:"flow",from:"from",to:"to"}},interaction:{intersect:!0,mode:"nearest"},layout:{padding:{bottom:3,left:3,right:13,top:3}},plugins:{legend:{display:!1},tooltip:{callbacks:{label(t){const o=t.parsed._custom;return`${o.from.key} -> ${o.to.key}: ${o.flow}`},title:()=>""}}},scales:{x:{bounds:"data",display:!1,min:0,offset:!1,type:"linear"},y:{bounds:"data",display:!1,min:0,offset:!1,reverse:!0,type:"linear"}}};const G=(t,o,e,n,r)=>"vertical"===r?o<n?{cp1:{x:t,y:o+(n-o)/3*2},cp2:{x:e,y:o+(n-o)/3}}:{cp1:{x:0,y:o-(o-n)/3},cp2:{x:0,y:n+(o-n)/3}}:t<e?{cp1:{x:t+(e-t)/3*2,y:o},cp2:{x:t+(e-t)/3,y:n}}:{cp1:{x:t-(t-e)/3,y:0},cp2:{x:e+(t-e)/3,y:0}},N=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),B=(t,o)=>e.color(t).alpha(o).rgbString(),I=(t,o)=>"string"==typeof t?B(t,o):t,U=t=>"string"==typeof t?e.getHoverColor(t):t;class J extends o.Element{draw(t){const{x:n,x2:r,y:i,y2:a,height:l,progress:h,width:c}=this,f=this.options.orientation,d=G(n,i,r,a,f),u={height:l,width:c,x:n,x2:r,y:i,y2:a};if(0===h)return;t.save(),h<1&&function(t,{height:o,width:e,x:n,x2:r,y:i,y2:a},s,l){t.beginPath(),"vertical"===l?t.rect(Math.min(n,r),i,Math.abs(r-n)+e+1,(a-i)*s+1):t.rect(n,Math.min(i,a),(r-n)*s+1,Math.abs(a-i)+o+1),t.clip()}(t,u,h,f),function(t,{x:o,x2:e,y:n,y2:r,options:i}){let a="black";null!==i.flowColor?a=i.flowColor:"from"===i.colorMode?a=I(i.colorFrom,i.alpha):"to"===i.colorMode?a=I(i.colorTo,i.alpha):"string"==typeof i.colorFrom&&"string"==typeof i.colorTo&&(a="vertical"===i.orientation?t.createLinearGradient(0,n,0,r):t.createLinearGradient(o,0,e,0),a.addColorStop(0,B(i.colorFrom,i.alpha)),a.addColorStop(1,B(i.colorTo,i.alpha))),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),function(t,{height:o,width:e,x:n,x2:r,y:i,y2:a},{cp1:s,cp2:l},h){t.beginPath(),t.moveTo(n,i),t.bezierCurveTo(s.x,s.y,l.x,l.y,r,a),"vertical"===h?(t.lineTo(r+e,a),t.bezierCurveTo(l.x+e,l.y,s.x+e,s.y,n+e,i)):(t.lineTo(r,a+o),t.bezierCurveTo(l.x,l.y+o,s.x,s.y+o,n,i+o)),t.lineTo(n,i),t.stroke(),t.closePath(),t.fill()}(t,u,d,f);const p=this.options.flowLabels;if(p.display){const n=e.toFont(p.font??o.Chart.defaults.font),r=function({height:t,width:o,x:e,x2:n,y:r,y2:i},a){return"vertical"===a?{height:i-r,width:Math.abs(n-e)+o,x:Math.min(e,n),y:r}:{height:Math.abs(i-r)+t,width:n-e,x:e,y:Math.min(r,i)}}(u,f);s(t,`${this.flow}`,{autoPosition:"center",backgroundColor:p.backgroundColor,borderRadius:p.borderRadius,borderWidth:0,color:p.color,font:n,height:r.height,lineOffset:n.lineHeight/2,padding:p.padding,position:p.position,width:r.width,x:r.x,y:r.y})}t.restore()}inRange(t,o,e){const{x:n,y:r,x2:i,y2:a,height:s,width:l}=this.getProps(["x","y","x2","y2","height","width"],e),h="vertical"===this.options.orientation;if(h?o<r||o>a:t<n||t>i)return!1;const{cp1:c,cp2:f}=G(n,r,i,a,this.options.orientation),d=h?(o-r)/(a-r):(t-n)/(i-n),u={x:i,y:a},p=N({x:n,y:r},c,d),y=N(c,f,d),x=N(f,u,d),g=N(p,y,d),m=N(y,x,d),w=N(g,m,d);return h?t>=w.x&&t<=w.x+l:o>=w.y&&o<=w.y+s}inXRange(t,o){const{x:e,x2:n,width:r}=this.getProps(["x","x2","width"],o),i=Math.min(e,n),a=Math.max(e,n)+("vertical"===this.options.orientation?r:0);return t>=i&&t<=a}inYRange(t,o){const{y:e,y2:n,height:r}=this.getProps(["y","y2","height"],o),i=Math.min(e,n),a=Math.max(e,n)+("vertical"===this.options.orientation?0:r);return t>=i&&t<=a}getCenterPoint(t){const{x:o,y:e,x2:n,y2:r,height:i,width:a}=this.getProps(["x","y","x2","y2","height","width"],t),s="vertical"===this.options.orientation;return{x:(o+n+(s?a:0))/2,y:(e+r+(s?0:i))/2}}tooltipPosition(t=!1){return this.getCenterPoint(t)}getRange(t){const o="vertical"===this.options.orientation;return"x"===t?o?this.width/2:0:o?0:this.height/2}constructor(t){super(),this.flow=0,this.x2=0,this.y2=0,this.width=0,this.height=0,this.progress=1,t&&Object.assign(this,t)}}J.id="flow",J.defaults={alpha:.5,colorFrom:"red",colorMode:"gradient",colorTo:"green",flowColor:null,flowLabels:{borderRadius:0,color:"black",display:!1,padding:4,position:"center"},hoverColorFrom:(t,o)=>U(o.colorFrom),hoverColorTo:(t,o)=>U(o.colorTo),orientation:"horizontal"},J.descriptors={_scriptable:!0,flowLabels:{_scriptable:!0}},o.Chart.register($,J),t.Flow=J,t.SankeyController=$});
@@ -33,7 +33,9 @@ export default class SankeyController extends DatasetController {
33
33
  color: string;
34
34
  dataElementType: string;
35
35
  modeX: string;
36
+ nodeMinSize: number;
36
37
  nodePadding: number;
38
+ nodePaddingMode: string;
37
39
  nodeWidth: number;
38
40
  orientation: string;
39
41
  transitions: {
@@ -33,7 +33,9 @@ export default class SankeyController extends DatasetController {
33
33
  color: string;
34
34
  dataElementType: string;
35
35
  modeX: string;
36
+ nodeMinSize: number;
36
37
  nodePadding: number;
38
+ nodePaddingMode: string;
37
39
  nodeWidth: number;
38
40
  orientation: string;
39
41
  transitions: {
@@ -17,10 +17,11 @@ export declare function calculateY(nodeArray: SankeyNode[], maxX: number): numbe
17
17
  export declare function calculateYUsingPriority(nodeArray: SankeyNode[], maxX: number): number;
18
18
  type PaddableNode = Pick<SankeyNode, 'in' | 'key' | 'out' | 'size' | 'x' | 'y'>;
19
19
  type NodeGap = Required<SankeyNodeGap>;
20
+ export type NodePaddingMode = 'auto' | 'even';
20
21
  /**
21
22
  * @return {number} maxY
22
23
  */
23
- export declare function addPadding(nodeArray: PaddableNode[], gaps: Map<string, NodeGap>): number;
24
+ export declare function addPadding(nodeArray: PaddableNode[], gaps: Map<string, NodeGap>, scale?: number, mode?: NodePaddingMode, minSizes?: Map<string, number>): number;
24
25
  export declare function sortFlows(nodeArray: SankeyNode[]): void;
25
26
  interface LayoutOptions {
26
27
  /** use node priority when sorting nodes vertically */
@@ -29,10 +30,14 @@ interface LayoutOptions {
29
30
  height: number;
30
31
  /** vertical before/after gap per node, in CSS pixels */
31
32
  nodePadding: Map<string, NodeGap>;
33
+ /** how nodePadding gaps are distributed within a column, defaults to 'auto' */
34
+ nodePaddingMode: SankeyControllerDatasetOptions['nodePaddingMode'];
35
+ /** minimum drawn node size per node, in CSS pixels */
36
+ nodeMinSize?: Map<string, number>;
32
37
  /** layout mode in x-direction */
33
38
  modeX: SankeyControllerDatasetOptions['modeX'];
34
39
  }
35
- export declare function layout(nodes: Map<string, SankeyNode>, data: SankeyDataPoint[], { priority, height, nodePadding, modeX }: LayoutOptions): {
40
+ export declare function layout(nodes: Map<string, SankeyNode>, data: SankeyDataPoint[], { priority, height, nodePadding, nodePaddingMode, nodeMinSize, modeX }: LayoutOptions): {
36
41
  maxY: number;
37
42
  maxX: number;
38
43
  };
package/dist/types.d.cts CHANGED
@@ -61,7 +61,9 @@ export interface SankeyControllerDatasetOptions extends Omit<ControllerDatasetOp
61
61
  flowColor?: ScriptableAndArray<Color, SankeyScriptableContext>;
62
62
  modeX?: 'edge' | 'even';
63
63
  nodeLabels?: SankeyControllerDatasetNodeLabelsOptions;
64
+ nodeMinSize?: SankeyNodeOption<number>;
64
65
  nodePadding?: SankeyNodeOption<number | SankeyNodeGap>;
66
+ nodePaddingMode?: 'auto' | 'even';
65
67
  nodeWidth?: number;
66
68
  orientation?: SankeyOrientation;
67
69
  padding?: number;
package/dist/types.d.ts CHANGED
@@ -60,7 +60,9 @@ export interface SankeyControllerDatasetOptions extends Omit<ControllerDatasetOp
60
60
  flowColor?: ScriptableAndArray<Color, SankeyScriptableContext>;
61
61
  modeX?: 'edge' | 'even';
62
62
  nodeLabels?: SankeyControllerDatasetNodeLabelsOptions;
63
+ nodeMinSize?: SankeyNodeOption<number>;
63
64
  nodePadding?: SankeyNodeOption<number | SankeyNodeGap>;
65
+ nodePaddingMode?: 'auto' | 'even';
64
66
  nodeWidth?: number;
65
67
  orientation?: SankeyOrientation;
66
68
  padding?: number;
package/package.json CHANGED
@@ -104,5 +104,5 @@
104
104
  "type": "module",
105
105
  "types": "dist/index.esm.d.ts",
106
106
  "unpkg": "dist/chartjs-chart-sankey.min.js",
107
- "version": "0.17.0"
107
+ "version": "0.19.0"
108
108
  }