chartjs-chart-sankey 0.18.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.18.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,44 +470,49 @@ function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
461
470
  }
462
471
  return paddings;
463
472
  }
464
- function offsetForNode(state, gap, paddings, scale) {
473
+ function offsetForNode(state, gap, paddings, scale, extraHalf) {
465
474
  const realNodesAbove = state.realCount;
466
475
  const before = gap.before * scale;
467
476
  const after = gap.after * scale;
468
- const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) : 0;
477
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) + state.lastExtraHalf + extraHalf : 0;
469
478
  const realCumOffset = state.realCumOffset + transitionGap;
470
479
  const virtualLevels = paddings - realNodesAbove;
471
480
  state.realCount = realNodesAbove + 1;
472
481
  state.realCumOffset = realCumOffset;
473
482
  state.lastAfter = after;
483
+ state.lastExtraHalf = extraHalf;
474
484
  return realCumOffset + virtualLevels * before;
475
485
  }
476
- function addEvenPadding(nodeArray, gaps, scale) {
486
+ function addEvenPadding(nodeArray, gaps, scale, minSizes) {
477
487
  let maxY = 0;
478
488
  let columnX;
479
489
  let prev;
480
490
  let prevGap;
491
+ let prevExtraHalf = 0;
481
492
  for (const node of nodeArray){
482
493
  const x = nodeX$1(node);
483
494
  const gap = gaps.get(node.key) ?? {
484
495
  after: 0,
485
496
  before: 0
486
497
  };
498
+ const extraHalf = extraHalfFor(node, minSizes, scale);
487
499
  if (x !== columnX) {
488
500
  columnX = x;
489
501
  } else if (prev && prevGap) {
490
- node.y = nodeY$1(prev) + prev.size + Math.max(prevGap.after, gap.before) * scale;
502
+ node.y = nodeY$1(prev) + prev.size + Math.max(prevGap.after, gap.before) * scale + prevExtraHalf + extraHalf;
491
503
  }
492
504
  prev = node;
493
505
  prevGap = gap;
494
- maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
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);
495
509
  }
496
510
  return maxY;
497
511
  }
498
- function addPadding(nodeArray, gaps, scale = 1, mode = 'auto') {
512
+ function addPadding(nodeArray, gaps, scale = 1, mode = 'auto', minSizes = new Map()) {
499
513
  nodeArray.sort(nodeByXYSize);
500
514
  if (mode === 'even') {
501
- return addEvenPadding(nodeArray, gaps, scale);
515
+ return addEvenPadding(nodeArray, gaps, scale, minSizes);
502
516
  }
503
517
  let maxY = 0;
504
518
  const columnXs = new Map();
@@ -520,6 +534,7 @@ function offsetForNode(state, gap, paddings, scale) {
520
534
  before: 0
521
535
  };
522
536
  const y = nodeY$1(node);
537
+ const extraHalf = extraHalfFor(node, minSizes, scale);
523
538
  if (y) {
524
539
  state.yHistory.push(y);
525
540
  let paddings = state.yHistory.length;
@@ -527,12 +542,14 @@ function offsetForNode(state, gap, paddings, scale) {
527
542
  paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
528
543
  while(state.yHistory.length < paddings)state.yHistory.push(y);
529
544
  }
530
- node.y = y + offsetForNode(state, gap, paddings, scale);
545
+ node.y = y + offsetForNode(state, gap, paddings, scale, extraHalf);
531
546
  } else {
532
547
  state.realCount += 1;
533
548
  state.lastAfter = gap.after * scale;
549
+ state.lastExtraHalf = extraHalf;
534
550
  }
535
- 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);
536
553
  }
537
554
  return maxY;
538
555
  }
@@ -563,14 +580,14 @@ function sortFlows(nodeArray) {
563
580
  });
564
581
  });
565
582
  }
566
- function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, modeX }) {
583
+ function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, nodeMinSize, modeX }) {
567
584
  const nodeArray = [
568
585
  ...nodes.values()
569
586
  ];
570
587
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
571
588
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
572
589
  const scale = maxY / height;
573
- const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto');
590
+ const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto', nodeMinSize ?? new Map());
574
591
  sortFlows(nodeArray);
575
592
  return {
576
593
  maxX,
@@ -666,22 +683,30 @@ function getColumnPadding(nodeWidth, orientation, chart) {
666
683
  const trailingSpace = orientation === 'vertical' ? chart.height - chart.chartArea.bottom : chart.width - chart.chartArea.right;
667
684
  return Math.max(0, nodeWidth + 3 - trailingSpace);
668
685
  }
669
- function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPadding, orientation) {
686
+ function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPadding, orientation, minSize) {
670
687
  if (orientation === 'vertical') {
671
- 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);
672
693
  return {
673
694
  height: nodeWidth,
674
- width: Math.abs(xScale.getPixelForValue(nodeY(node) + size) - x),
675
- x,
695
+ width,
696
+ x: left - (width - naturalWidth) / 2,
676
697
  y: getColumnPixel(yScale, nodeX(node), maxColumn, columnPadding)
677
698
  };
678
699
  }
679
- 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);
680
705
  return {
681
- height: Math.abs(yScale.getPixelForValue(nodeY(node) + size) - y),
706
+ height,
682
707
  width: nodeWidth,
683
708
  x: getColumnPixel(xScale, nodeX(node), maxColumn, columnPadding),
684
- y
709
+ y: top - (height - naturalHeight) / 2
685
710
  };
686
711
  }
687
712
  function resolveNodeGap(option, node) {
@@ -697,6 +722,9 @@ function resolveNodeGap(option, node) {
697
722
  before: resolved.before ?? 10
698
723
  };
699
724
  }
725
+ function resolveNodeMinSize(option, node) {
726
+ return resolveNodeOption(option ?? 0, node) ?? 0;
727
+ }
700
728
  function resolveNodeLabelStyle(options, node) {
701
729
  const { backgroundColor, borderRadius = 0, color, display, font, padding = 4, position } = options.nodeLabels ?? {};
702
730
  return {
@@ -709,6 +737,10 @@ function resolveNodeLabelStyle(options, node) {
709
737
  position: resolveNodeOption(position, node) ?? 'auto'
710
738
  };
711
739
  }
740
+ const NODE_SCOPED_OPTIONS = new Set([
741
+ 'nodeMinSize',
742
+ 'nodePadding'
743
+ ]);
712
744
  class SankeyController extends chart_js.DatasetController {
713
745
  parseObjectData(meta, data, start, count) {
714
746
  const sankeyData = getParsedData(data, this.options.parsing);
@@ -718,12 +750,15 @@ class SankeyController extends chart_js.DatasetController {
718
750
  const orientation = this.options.orientation ?? 'horizontal';
719
751
  this._nodes = nodes;
720
752
  const nodeGaps = new Map();
753
+ const nodeMinSizes = new Map();
721
754
  for (const node of nodes.values()){
722
755
  nodeGaps.set(node.key, resolveNodeGap(this.options.nodePadding, node));
756
+ nodeMinSizes.set(node.key, resolveNodeMinSize(this.options.nodeMinSize, node));
723
757
  }
724
758
  const { maxX, maxY } = layout(nodes, sankeyData, {
725
759
  height: orientation === 'vertical' ? this.chart.width : this.chart.height,
726
760
  modeX: this.options.modeX,
761
+ nodeMinSize: nodeMinSizes,
727
762
  nodePadding: nodeGaps,
728
763
  nodePaddingMode: this.options.nodePaddingMode,
729
764
  priority: !!this.options.priority
@@ -789,7 +824,8 @@ class SankeyController extends chart_js.DatasetController {
789
824
  const chartArea = this.chart.chartArea;
790
825
  for (const node of nodes.values()){
791
826
  const max = getNodeSize(node, size);
792
- 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);
793
829
  const label = labels?.[node.key] ?? node.key;
794
830
  const labelStyle = resolveNodeLabelStyle(options, node);
795
831
  if (labelStyle.display) {
@@ -829,7 +865,8 @@ class SankeyController extends chart_js.DatasetController {
829
865
  ctx.fillStyle = node.color ?? 'black';
830
866
  if (!xScale || !yScale) return;
831
867
  const max = Math[sizeMethod](node.in || node.out, node.out || node.in);
832
- 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);
833
870
  if (borderWidth) {
834
871
  ctx.strokeRect(x, y, width, height);
835
872
  }
@@ -872,7 +909,7 @@ class SankeyController extends chart_js.DatasetController {
872
909
  SankeyController.id = 'sankey';
873
910
  SankeyController.descriptors = {
874
911
  _indexable: false,
875
- _scriptable: (name)=>name !== 'nodePadding',
912
+ _scriptable: (name)=>!NODE_SCOPED_OPTIONS.has(name),
876
913
  nodeLabels: {
877
914
  _indexable: false,
878
915
  _scriptable: false
@@ -909,6 +946,7 @@ SankeyController.defaults = {
909
946
  color: 'black',
910
947
  dataElementType: 'flow',
911
948
  modeX: 'edge',
949
+ nodeMinSize: 0,
912
950
  nodePadding: 10,
913
951
  nodePaddingMode: 'auto',
914
952
  nodeWidth: 10,
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.18.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,44 +467,49 @@ function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
458
467
  }
459
468
  return paddings;
460
469
  }
461
- function offsetForNode(state, gap, paddings, scale) {
470
+ function offsetForNode(state, gap, paddings, scale, extraHalf) {
462
471
  const realNodesAbove = state.realCount;
463
472
  const before = gap.before * scale;
464
473
  const after = gap.after * scale;
465
- const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) : 0;
474
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) + state.lastExtraHalf + extraHalf : 0;
466
475
  const realCumOffset = state.realCumOffset + transitionGap;
467
476
  const virtualLevels = paddings - realNodesAbove;
468
477
  state.realCount = realNodesAbove + 1;
469
478
  state.realCumOffset = realCumOffset;
470
479
  state.lastAfter = after;
480
+ state.lastExtraHalf = extraHalf;
471
481
  return realCumOffset + virtualLevels * before;
472
482
  }
473
- function addEvenPadding(nodeArray, gaps, scale) {
483
+ function addEvenPadding(nodeArray, gaps, scale, minSizes) {
474
484
  let maxY = 0;
475
485
  let columnX;
476
486
  let prev;
477
487
  let prevGap;
488
+ let prevExtraHalf = 0;
478
489
  for (const node of nodeArray){
479
490
  const x = nodeX$1(node);
480
491
  const gap = gaps.get(node.key) ?? {
481
492
  after: 0,
482
493
  before: 0
483
494
  };
495
+ const extraHalf = extraHalfFor(node, minSizes, scale);
484
496
  if (x !== columnX) {
485
497
  columnX = x;
486
498
  } else if (prev && prevGap) {
487
- node.y = nodeY$1(prev) + prev.size + Math.max(prevGap.after, gap.before) * scale;
499
+ node.y = nodeY$1(prev) + prev.size + Math.max(prevGap.after, gap.before) * scale + prevExtraHalf + extraHalf;
488
500
  }
489
501
  prev = node;
490
502
  prevGap = gap;
491
- maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
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);
492
506
  }
493
507
  return maxY;
494
508
  }
495
- function addPadding(nodeArray, gaps, scale = 1, mode = 'auto') {
509
+ function addPadding(nodeArray, gaps, scale = 1, mode = 'auto', minSizes = new Map()) {
496
510
  nodeArray.sort(nodeByXYSize);
497
511
  if (mode === 'even') {
498
- return addEvenPadding(nodeArray, gaps, scale);
512
+ return addEvenPadding(nodeArray, gaps, scale, minSizes);
499
513
  }
500
514
  let maxY = 0;
501
515
  const columnXs = new Map();
@@ -517,6 +531,7 @@ function offsetForNode(state, gap, paddings, scale) {
517
531
  before: 0
518
532
  };
519
533
  const y = nodeY$1(node);
534
+ const extraHalf = extraHalfFor(node, minSizes, scale);
520
535
  if (y) {
521
536
  state.yHistory.push(y);
522
537
  let paddings = state.yHistory.length;
@@ -524,12 +539,14 @@ function offsetForNode(state, gap, paddings, scale) {
524
539
  paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
525
540
  while(state.yHistory.length < paddings)state.yHistory.push(y);
526
541
  }
527
- node.y = y + offsetForNode(state, gap, paddings, scale);
542
+ node.y = y + offsetForNode(state, gap, paddings, scale, extraHalf);
528
543
  } else {
529
544
  state.realCount += 1;
530
545
  state.lastAfter = gap.after * scale;
546
+ state.lastExtraHalf = extraHalf;
531
547
  }
532
- 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);
533
550
  }
534
551
  return maxY;
535
552
  }
@@ -560,14 +577,14 @@ function sortFlows(nodeArray) {
560
577
  });
561
578
  });
562
579
  }
563
- function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, modeX }) {
580
+ function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, nodeMinSize, modeX }) {
564
581
  const nodeArray = [
565
582
  ...nodes.values()
566
583
  ];
567
584
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
568
585
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
569
586
  const scale = maxY / height;
570
- const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto');
587
+ const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto', nodeMinSize ?? new Map());
571
588
  sortFlows(nodeArray);
572
589
  return {
573
590
  maxX,
@@ -663,22 +680,30 @@ function getColumnPadding(nodeWidth, orientation, chart) {
663
680
  const trailingSpace = orientation === 'vertical' ? chart.height - chart.chartArea.bottom : chart.width - chart.chartArea.right;
664
681
  return Math.max(0, nodeWidth + 3 - trailingSpace);
665
682
  }
666
- function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPadding, orientation) {
683
+ function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPadding, orientation, minSize) {
667
684
  if (orientation === 'vertical') {
668
- 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);
669
690
  return {
670
691
  height: nodeWidth,
671
- width: Math.abs(xScale.getPixelForValue(nodeY(node) + size) - x),
672
- x,
692
+ width,
693
+ x: left - (width - naturalWidth) / 2,
673
694
  y: getColumnPixel(yScale, nodeX(node), maxColumn, columnPadding)
674
695
  };
675
696
  }
676
- 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);
677
702
  return {
678
- height: Math.abs(yScale.getPixelForValue(nodeY(node) + size) - y),
703
+ height,
679
704
  width: nodeWidth,
680
705
  x: getColumnPixel(xScale, nodeX(node), maxColumn, columnPadding),
681
- y
706
+ y: top - (height - naturalHeight) / 2
682
707
  };
683
708
  }
684
709
  function resolveNodeGap(option, node) {
@@ -694,6 +719,9 @@ function resolveNodeGap(option, node) {
694
719
  before: resolved.before ?? 10
695
720
  };
696
721
  }
722
+ function resolveNodeMinSize(option, node) {
723
+ return resolveNodeOption(option ?? 0, node) ?? 0;
724
+ }
697
725
  function resolveNodeLabelStyle(options, node) {
698
726
  const { backgroundColor, borderRadius = 0, color, display, font, padding = 4, position } = options.nodeLabels ?? {};
699
727
  return {
@@ -706,6 +734,10 @@ function resolveNodeLabelStyle(options, node) {
706
734
  position: resolveNodeOption(position, node) ?? 'auto'
707
735
  };
708
736
  }
737
+ const NODE_SCOPED_OPTIONS = new Set([
738
+ 'nodeMinSize',
739
+ 'nodePadding'
740
+ ]);
709
741
  class SankeyController extends DatasetController {
710
742
  parseObjectData(meta, data, start, count) {
711
743
  const sankeyData = getParsedData(data, this.options.parsing);
@@ -715,12 +747,15 @@ class SankeyController extends DatasetController {
715
747
  const orientation = this.options.orientation ?? 'horizontal';
716
748
  this._nodes = nodes;
717
749
  const nodeGaps = new Map();
750
+ const nodeMinSizes = new Map();
718
751
  for (const node of nodes.values()){
719
752
  nodeGaps.set(node.key, resolveNodeGap(this.options.nodePadding, node));
753
+ nodeMinSizes.set(node.key, resolveNodeMinSize(this.options.nodeMinSize, node));
720
754
  }
721
755
  const { maxX, maxY } = layout(nodes, sankeyData, {
722
756
  height: orientation === 'vertical' ? this.chart.width : this.chart.height,
723
757
  modeX: this.options.modeX,
758
+ nodeMinSize: nodeMinSizes,
724
759
  nodePadding: nodeGaps,
725
760
  nodePaddingMode: this.options.nodePaddingMode,
726
761
  priority: !!this.options.priority
@@ -786,7 +821,8 @@ class SankeyController extends DatasetController {
786
821
  const chartArea = this.chart.chartArea;
787
822
  for (const node of nodes.values()){
788
823
  const max = getNodeSize(node, size);
789
- 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);
790
826
  const label = labels?.[node.key] ?? node.key;
791
827
  const labelStyle = resolveNodeLabelStyle(options, node);
792
828
  if (labelStyle.display) {
@@ -826,7 +862,8 @@ class SankeyController extends DatasetController {
826
862
  ctx.fillStyle = node.color ?? 'black';
827
863
  if (!xScale || !yScale) return;
828
864
  const max = Math[sizeMethod](node.in || node.out, node.out || node.in);
829
- 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);
830
867
  if (borderWidth) {
831
868
  ctx.strokeRect(x, y, width, height);
832
869
  }
@@ -869,7 +906,7 @@ class SankeyController extends DatasetController {
869
906
  SankeyController.id = 'sankey';
870
907
  SankeyController.descriptors = {
871
908
  _indexable: false,
872
- _scriptable: (name)=>name !== 'nodePadding',
909
+ _scriptable: (name)=>!NODE_SCOPED_OPTIONS.has(name),
873
910
  nodeLabels: {
874
911
  _indexable: false,
875
912
  _scriptable: false
@@ -906,6 +943,7 @@ SankeyController.defaults = {
906
943
  color: 'black',
907
944
  dataElementType: 'flow',
908
945
  modeX: 'edge',
946
+ nodeMinSize: 0,
909
947
  nodePadding: 10,
910
948
  nodePaddingMode: 'auto',
911
949
  nodeWidth: 10,
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.18.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,r){const n=t.realCount,i=o.before*r,a=o.after*r,s=n>0?Math.max(t.lastAfter,i):0,l=t.realCumOffset+s,h=e-n;return t.realCount=n+1,t.realCumOffset=l,t.lastAfter=a,l+h*i}function _(t,o,e=1,r="auto"){if(t.sort(k),"even"===r)return function(t,o,e){let r,n,i,a=0;for(const s of t){const t=f(s),l=o.get(s.key)??{after:0,before:0};t!==r?r=t:n&&i&&(s.y=d(n)+n.size+Math.max(i.after,l.before)*e),n=s,i=l,a=Math.max(a,d(s)+Math.max(s.in,s.out))}return a}(t,o,e);let n=0;const i=new Map,a=[],s=t=>{if(!i.has(t)){const o=a.length;return i.set(t,o),a.push({lastAfter:0,realCount:0,realCumOffset:0,yHistory:[]}),o}return i.get(t)??0};for(const r of t){const t=s(f(r)),i=a[t],l=o.get(r.key)??{after:0,before:0},h=d(r);if(h){i.yHistory.push(h);let o=i.yHistory.length;if(r.in)for(o=C(a,t,h,o);i.yHistory.length<o;)i.yHistory.push(h);r.y=h+P(i,l,o,e)}else i.realCount+=1,i.lastAfter=l.after*e;n=Math.max(n,d(r)+Math.max(r.in,r.out))}return n}function T(t,o,{priority:e,height:n,nodePadding:i,nodePaddingMode:a,modeX:s}){const l=[...t.values()],h=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,s??"edge"),c=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}(l,h):v(l,h),u=_(l,i,c/n,a??"auto");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)})})}(l),{maxX:h,maxY:u}}function z(t){return t.x??0}function S(t){return t.y??0}function F(t,o){return Math[o](t.in||t.out,t.out||t.in)}function E(t,o,e,r){return"vertical"===r?o<(e.top+e.bottom)/2?"bottom":"top":t<(e.left+e.right)/2?"right":"left"}function O(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}function R(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(z(o),i)},x:a.parse(e,i),y:s.parse(z(t),i)}:{_custom:{flow:n,from:t,height:s.parse(n,i),to:o,x:a.parse(z(o),i),y:s.parse(r,i)},x:a.parse(z(t),i),y:s.parse(e,i)}}function W(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:X(e,t.y,r,i)+n+a,y2:X(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:X(o,t.x,r,i)+n+a,x2:X(o,l.x,r,i)-a,y:c,y2:e.getPixelForValue(l.y)}}function X(t,o,e,r){const n=t.getPixelForValue(o);return e?n-o/e*r:n}function Y(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 j(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:X(r,z(t),n,a)}}const l=r.getPixelForValue(S(t));return{height:Math.abs(r.getPixelForValue(S(t)+o)-l),width:i,x:X(e,z(t),n,a),y:l}}function A(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 V(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 H 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,A(this.options.nodePadding,t));const{maxX:d,maxY:u}=T(l,n,{height:"vertical"===c?this.chart.width:this.chart.height,modeX:this.options.modeX,nodePadding:f,nodePaddingMode:this.options.nodePaddingMode,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)+O(e.to,o.to,t),f=S(r)+O(r.from,o.from,t);s.push(R(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=Y(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,...W(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=Y(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=F(o,a),{height:i,width:x,x:m,y:b}=j(o,n,p,y,this._maxX,c,d,f),w=l?.[o.key]??o.key,M=V(r,o);if(M.display){const o=e.toFont(M.font??u);s(t,w,{autoPosition:E(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=Y(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}=j(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}}H.id="sankey",H.descriptors={_indexable:!1,_scriptable:t=>"nodePadding"!==t,nodeLabels:{_indexable:!1,_scriptable:!1}},H.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,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"}}}}},H.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 L=(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}},q=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),D=(t,o)=>e.color(t).alpha(o).rgbString(),$=(t,o)=>"string"==typeof t?D(t,o):t,G=t=>"string"==typeof t?e.getHoverColor(t):t;class N 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=L(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=$(i.colorFrom,i.alpha):"to"===i.colorMode?a=$(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,D(i.colorFrom,i.alpha)),a.addColorStop(1,D(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}=L(r,n,i,a,this.options.orientation),d=h?(o-n)/(a-n):(t-r)/(i-r),u={x:i,y:a},p=q({x:r,y:n},c,d),y=q(c,f,d),g=q(f,u,d),x=q(p,y,d),m=q(y,g,d),b=q(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)}}N.id="flow",N.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)=>G(o.colorFrom),hoverColorTo:(t,o)=>G(o.colorTo),orientation:"horizontal"},N.descriptors={_scriptable:!0,flowLabels:{_scriptable:!0}},o.Chart.register(H,N),t.Flow=N,t.SankeyController=H});
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,6 +33,7 @@ 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;
37
38
  nodePaddingMode: string;
38
39
  nodeWidth: number;
@@ -33,6 +33,7 @@ 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;
37
38
  nodePaddingMode: string;
38
39
  nodeWidth: number;
@@ -21,7 +21,7 @@ export type NodePaddingMode = 'auto' | 'even';
21
21
  /**
22
22
  * @return {number} maxY
23
23
  */
24
- export declare function addPadding(nodeArray: PaddableNode[], gaps: Map<string, NodeGap>, scale?: number, mode?: NodePaddingMode): number;
24
+ export declare function addPadding(nodeArray: PaddableNode[], gaps: Map<string, NodeGap>, scale?: number, mode?: NodePaddingMode, minSizes?: Map<string, number>): number;
25
25
  export declare function sortFlows(nodeArray: SankeyNode[]): void;
26
26
  interface LayoutOptions {
27
27
  /** use node priority when sorting nodes vertically */
@@ -32,10 +32,12 @@ interface LayoutOptions {
32
32
  nodePadding: Map<string, NodeGap>;
33
33
  /** how nodePadding gaps are distributed within a column, defaults to 'auto' */
34
34
  nodePaddingMode: SankeyControllerDatasetOptions['nodePaddingMode'];
35
+ /** minimum drawn node size per node, in CSS pixels */
36
+ nodeMinSize?: Map<string, number>;
35
37
  /** layout mode in x-direction */
36
38
  modeX: SankeyControllerDatasetOptions['modeX'];
37
39
  }
38
- export declare function layout(nodes: Map<string, SankeyNode>, data: SankeyDataPoint[], { priority, height, nodePadding, nodePaddingMode, modeX }: LayoutOptions): {
40
+ export declare function layout(nodes: Map<string, SankeyNode>, data: SankeyDataPoint[], { priority, height, nodePadding, nodePaddingMode, nodeMinSize, modeX }: LayoutOptions): {
39
41
  maxY: number;
40
42
  maxX: number;
41
43
  };
package/dist/types.d.cts CHANGED
@@ -61,6 +61,7 @@ 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>;
65
66
  nodePaddingMode?: 'auto' | 'even';
66
67
  nodeWidth?: number;
package/dist/types.d.ts CHANGED
@@ -60,6 +60,7 @@ 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>;
64
65
  nodePaddingMode?: 'auto' | 'even';
65
66
  nodeWidth?: 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.18.0"
107
+ "version": "0.19.0"
108
108
  }