chartjs-chart-sankey 0.16.3 → 0.18.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.16.3
2
+ * chartjs-chart-sankey v0.18.0
3
3
  * https://chartjs-chart-sankey.pages.dev/
4
4
  * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
@@ -43,7 +43,7 @@ function isPatternOrGradient(value) {
43
43
  const type = Object.prototype.toString.call(value);
44
44
  return type === '[object CanvasPattern]' || type === '[object CanvasGradient]';
45
45
  }
46
- function resolveNodeLabelOption(option, node) {
46
+ function resolveNodeOption(option, node) {
47
47
  if (typeof option === 'function') {
48
48
  return option(node);
49
49
  }
@@ -442,7 +442,64 @@ const nodeByXYSize = (a, b)=>{
442
442
  if (nodeY$1(a) === nodeY$1(b)) return a.size - b.size;
443
443
  return nodeY$1(a) - nodeY$1(b);
444
444
  };
445
- function addPadding(nodeArray, padding) {
445
+ function createColumnGapState() {
446
+ return {
447
+ lastAfter: 0,
448
+ realCount: 0,
449
+ realCumOffset: 0,
450
+ yHistory: []
451
+ };
452
+ }
453
+ function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
454
+ let paddings = ownPaddings;
455
+ for(let col = 0; col < colIdx; col++){
456
+ const otherHistory = grid[col].yHistory;
457
+ for(let row = 0; row < otherHistory.length; row++){
458
+ if (otherHistory[row] > y) break;
459
+ paddings = Math.max(row + 1, paddings);
460
+ }
461
+ }
462
+ return paddings;
463
+ }
464
+ function offsetForNode(state, gap, paddings, scale) {
465
+ const realNodesAbove = state.realCount;
466
+ const before = gap.before * scale;
467
+ const after = gap.after * scale;
468
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) : 0;
469
+ const realCumOffset = state.realCumOffset + transitionGap;
470
+ const virtualLevels = paddings - realNodesAbove;
471
+ state.realCount = realNodesAbove + 1;
472
+ state.realCumOffset = realCumOffset;
473
+ state.lastAfter = after;
474
+ return realCumOffset + virtualLevels * before;
475
+ }
476
+ function addEvenPadding(nodeArray, gaps, scale) {
477
+ let maxY = 0;
478
+ let columnX;
479
+ let prev;
480
+ let prevGap;
481
+ for (const node of nodeArray){
482
+ const x = nodeX$1(node);
483
+ const gap = gaps.get(node.key) ?? {
484
+ after: 0,
485
+ before: 0
486
+ };
487
+ if (x !== columnX) {
488
+ columnX = x;
489
+ } else if (prev && prevGap) {
490
+ node.y = nodeY$1(prev) + prev.size + Math.max(prevGap.after, gap.before) * scale;
491
+ }
492
+ prev = node;
493
+ prevGap = gap;
494
+ maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
495
+ }
496
+ return maxY;
497
+ }
498
+ function addPadding(nodeArray, gaps, scale = 1, mode = 'auto') {
499
+ nodeArray.sort(nodeByXYSize);
500
+ if (mode === 'even') {
501
+ return addEvenPadding(nodeArray, gaps, scale);
502
+ }
446
503
  let maxY = 0;
447
504
  const columnXs = new Map();
448
505
  const grid = [];
@@ -450,29 +507,30 @@ const nodeByXYSize = (a, b)=>{
450
507
  if (!columnXs.has(x)) {
451
508
  const index = grid.length;
452
509
  columnXs.set(x, index);
453
- grid.push([]);
510
+ grid.push(createColumnGapState());
454
511
  return index;
455
512
  }
456
513
  return columnXs.get(x) ?? 0;
457
514
  };
458
- nodeArray.sort(nodeByXYSize);
459
515
  for (const node of nodeArray){
460
516
  const colIdx = getColIndex(nodeX$1(node));
461
- const column = grid[colIdx] ?? [];
462
- if (nodeY$1(node)) {
463
- column.push(nodeY$1(node));
464
- let paddings = column.length;
517
+ const state = grid[colIdx];
518
+ const gap = gaps.get(node.key) ?? {
519
+ after: 0,
520
+ before: 0
521
+ };
522
+ const y = nodeY$1(node);
523
+ if (y) {
524
+ state.yHistory.push(y);
525
+ let paddings = state.yHistory.length;
465
526
  if (node.in) {
466
- for(let col = 0; col < colIdx; col++){
467
- const otherColumn = grid[col] ?? [];
468
- for(let row = 0; row < otherColumn.length; row++){
469
- if (otherColumn[row] > nodeY$1(node)) break;
470
- paddings = Math.max(row + 1, paddings);
471
- }
472
- }
473
- while(column.length < paddings)column.push(nodeY$1(node));
527
+ paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
528
+ while(state.yHistory.length < paddings)state.yHistory.push(y);
474
529
  }
475
- node.y = nodeY$1(node) + paddings * padding;
530
+ node.y = y + offsetForNode(state, gap, paddings, scale);
531
+ } else {
532
+ state.realCount += 1;
533
+ state.lastAfter = gap.after * scale;
476
534
  }
477
535
  maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
478
536
  }
@@ -505,14 +563,14 @@ function sortFlows(nodeArray) {
505
563
  });
506
564
  });
507
565
  }
508
- function layout(nodes, data, { priority, height, nodePadding, modeX }) {
566
+ function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, modeX }) {
509
567
  const nodeArray = [
510
568
  ...nodes.values()
511
569
  ];
512
570
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
513
571
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
514
- const padding = maxY / height * nodePadding;
515
- const maxYWithPadding = addPadding(nodeArray, padding);
572
+ const scale = maxY / height;
573
+ const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto');
516
574
  sortFlows(nodeArray);
517
575
  return {
518
576
  maxX,
@@ -626,16 +684,29 @@ function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPad
626
684
  y
627
685
  };
628
686
  }
687
+ function resolveNodeGap(option, node) {
688
+ const resolved = resolveNodeOption(option ?? 10, node) ?? 10;
689
+ if (typeof resolved === 'number') {
690
+ return {
691
+ after: resolved,
692
+ before: resolved
693
+ };
694
+ }
695
+ return {
696
+ after: resolved.after ?? 10,
697
+ before: resolved.before ?? 10
698
+ };
699
+ }
629
700
  function resolveNodeLabelStyle(options, node) {
630
701
  const { backgroundColor, borderRadius = 0, color, display, font, padding = 4, position } = options.nodeLabels ?? {};
631
702
  return {
632
- backgroundColor: resolveNodeLabelOption(backgroundColor, node),
703
+ backgroundColor: resolveNodeOption(backgroundColor, node),
633
704
  borderRadius,
634
- color: resolveNodeLabelOption(color, node) ?? options.color ?? 'black',
635
- display: resolveNodeLabelOption(display, node) ?? true,
705
+ color: resolveNodeOption(color, node) ?? options.color ?? 'black',
706
+ display: resolveNodeOption(display, node) ?? true,
636
707
  font,
637
708
  padding,
638
- position: resolveNodeLabelOption(position, node) ?? 'auto'
709
+ position: resolveNodeOption(position, node) ?? 'auto'
639
710
  };
640
711
  }
641
712
  class SankeyController extends chart_js.DatasetController {
@@ -646,10 +717,15 @@ class SankeyController extends chart_js.DatasetController {
646
717
  const nodes = buildNodesFromData(sankeyData, this.options);
647
718
  const orientation = this.options.orientation ?? 'horizontal';
648
719
  this._nodes = nodes;
720
+ const nodeGaps = new Map();
721
+ for (const node of nodes.values()){
722
+ nodeGaps.set(node.key, resolveNodeGap(this.options.nodePadding, node));
723
+ }
649
724
  const { maxX, maxY } = layout(nodes, sankeyData, {
650
725
  height: orientation === 'vertical' ? this.chart.width : this.chart.height,
651
726
  modeX: this.options.modeX,
652
- nodePadding: this.options.nodePadding ?? 10,
727
+ nodePadding: nodeGaps,
728
+ nodePaddingMode: this.options.nodePaddingMode,
653
729
  priority: !!this.options.priority
654
730
  });
655
731
  this._maxX = maxX;
@@ -796,7 +872,7 @@ class SankeyController extends chart_js.DatasetController {
796
872
  SankeyController.id = 'sankey';
797
873
  SankeyController.descriptors = {
798
874
  _indexable: false,
799
- _scriptable: true,
875
+ _scriptable: (name)=>name !== 'nodePadding',
800
876
  nodeLabels: {
801
877
  _indexable: false,
802
878
  _scriptable: false
@@ -834,6 +910,7 @@ SankeyController.defaults = {
834
910
  dataElementType: 'flow',
835
911
  modeX: 'edge',
836
912
  nodePadding: 10,
913
+ nodePaddingMode: 'auto',
837
914
  nodeWidth: 10,
838
915
  orientation: 'horizontal',
839
916
  transitions: {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.16.3
2
+ * chartjs-chart-sankey v0.18.0
3
3
  * https://chartjs-chart-sankey.pages.dev/
4
4
  * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
@@ -40,7 +40,7 @@ function isPatternOrGradient(value) {
40
40
  const type = Object.prototype.toString.call(value);
41
41
  return type === '[object CanvasPattern]' || type === '[object CanvasGradient]';
42
42
  }
43
- function resolveNodeLabelOption(option, node) {
43
+ function resolveNodeOption(option, node) {
44
44
  if (typeof option === 'function') {
45
45
  return option(node);
46
46
  }
@@ -439,7 +439,64 @@ const nodeByXYSize = (a, b)=>{
439
439
  if (nodeY$1(a) === nodeY$1(b)) return a.size - b.size;
440
440
  return nodeY$1(a) - nodeY$1(b);
441
441
  };
442
- function addPadding(nodeArray, padding) {
442
+ function createColumnGapState() {
443
+ return {
444
+ lastAfter: 0,
445
+ realCount: 0,
446
+ realCumOffset: 0,
447
+ yHistory: []
448
+ };
449
+ }
450
+ function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
451
+ let paddings = ownPaddings;
452
+ for(let col = 0; col < colIdx; col++){
453
+ const otherHistory = grid[col].yHistory;
454
+ for(let row = 0; row < otherHistory.length; row++){
455
+ if (otherHistory[row] > y) break;
456
+ paddings = Math.max(row + 1, paddings);
457
+ }
458
+ }
459
+ return paddings;
460
+ }
461
+ function offsetForNode(state, gap, paddings, scale) {
462
+ const realNodesAbove = state.realCount;
463
+ const before = gap.before * scale;
464
+ const after = gap.after * scale;
465
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) : 0;
466
+ const realCumOffset = state.realCumOffset + transitionGap;
467
+ const virtualLevels = paddings - realNodesAbove;
468
+ state.realCount = realNodesAbove + 1;
469
+ state.realCumOffset = realCumOffset;
470
+ state.lastAfter = after;
471
+ return realCumOffset + virtualLevels * before;
472
+ }
473
+ function addEvenPadding(nodeArray, gaps, scale) {
474
+ let maxY = 0;
475
+ let columnX;
476
+ let prev;
477
+ let prevGap;
478
+ for (const node of nodeArray){
479
+ const x = nodeX$1(node);
480
+ const gap = gaps.get(node.key) ?? {
481
+ after: 0,
482
+ before: 0
483
+ };
484
+ if (x !== columnX) {
485
+ columnX = x;
486
+ } else if (prev && prevGap) {
487
+ node.y = nodeY$1(prev) + prev.size + Math.max(prevGap.after, gap.before) * scale;
488
+ }
489
+ prev = node;
490
+ prevGap = gap;
491
+ maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
492
+ }
493
+ return maxY;
494
+ }
495
+ function addPadding(nodeArray, gaps, scale = 1, mode = 'auto') {
496
+ nodeArray.sort(nodeByXYSize);
497
+ if (mode === 'even') {
498
+ return addEvenPadding(nodeArray, gaps, scale);
499
+ }
443
500
  let maxY = 0;
444
501
  const columnXs = new Map();
445
502
  const grid = [];
@@ -447,29 +504,30 @@ const nodeByXYSize = (a, b)=>{
447
504
  if (!columnXs.has(x)) {
448
505
  const index = grid.length;
449
506
  columnXs.set(x, index);
450
- grid.push([]);
507
+ grid.push(createColumnGapState());
451
508
  return index;
452
509
  }
453
510
  return columnXs.get(x) ?? 0;
454
511
  };
455
- nodeArray.sort(nodeByXYSize);
456
512
  for (const node of nodeArray){
457
513
  const colIdx = getColIndex(nodeX$1(node));
458
- const column = grid[colIdx] ?? [];
459
- if (nodeY$1(node)) {
460
- column.push(nodeY$1(node));
461
- let paddings = column.length;
514
+ const state = grid[colIdx];
515
+ const gap = gaps.get(node.key) ?? {
516
+ after: 0,
517
+ before: 0
518
+ };
519
+ const y = nodeY$1(node);
520
+ if (y) {
521
+ state.yHistory.push(y);
522
+ let paddings = state.yHistory.length;
462
523
  if (node.in) {
463
- for(let col = 0; col < colIdx; col++){
464
- const otherColumn = grid[col] ?? [];
465
- for(let row = 0; row < otherColumn.length; row++){
466
- if (otherColumn[row] > nodeY$1(node)) break;
467
- paddings = Math.max(row + 1, paddings);
468
- }
469
- }
470
- while(column.length < paddings)column.push(nodeY$1(node));
524
+ paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
525
+ while(state.yHistory.length < paddings)state.yHistory.push(y);
471
526
  }
472
- node.y = nodeY$1(node) + paddings * padding;
527
+ node.y = y + offsetForNode(state, gap, paddings, scale);
528
+ } else {
529
+ state.realCount += 1;
530
+ state.lastAfter = gap.after * scale;
473
531
  }
474
532
  maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
475
533
  }
@@ -502,14 +560,14 @@ function sortFlows(nodeArray) {
502
560
  });
503
561
  });
504
562
  }
505
- function layout(nodes, data, { priority, height, nodePadding, modeX }) {
563
+ function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, modeX }) {
506
564
  const nodeArray = [
507
565
  ...nodes.values()
508
566
  ];
509
567
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
510
568
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
511
- const padding = maxY / height * nodePadding;
512
- const maxYWithPadding = addPadding(nodeArray, padding);
569
+ const scale = maxY / height;
570
+ const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto');
513
571
  sortFlows(nodeArray);
514
572
  return {
515
573
  maxX,
@@ -623,16 +681,29 @@ function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPad
623
681
  y
624
682
  };
625
683
  }
684
+ function resolveNodeGap(option, node) {
685
+ const resolved = resolveNodeOption(option ?? 10, node) ?? 10;
686
+ if (typeof resolved === 'number') {
687
+ return {
688
+ after: resolved,
689
+ before: resolved
690
+ };
691
+ }
692
+ return {
693
+ after: resolved.after ?? 10,
694
+ before: resolved.before ?? 10
695
+ };
696
+ }
626
697
  function resolveNodeLabelStyle(options, node) {
627
698
  const { backgroundColor, borderRadius = 0, color, display, font, padding = 4, position } = options.nodeLabels ?? {};
628
699
  return {
629
- backgroundColor: resolveNodeLabelOption(backgroundColor, node),
700
+ backgroundColor: resolveNodeOption(backgroundColor, node),
630
701
  borderRadius,
631
- color: resolveNodeLabelOption(color, node) ?? options.color ?? 'black',
632
- display: resolveNodeLabelOption(display, node) ?? true,
702
+ color: resolveNodeOption(color, node) ?? options.color ?? 'black',
703
+ display: resolveNodeOption(display, node) ?? true,
633
704
  font,
634
705
  padding,
635
- position: resolveNodeLabelOption(position, node) ?? 'auto'
706
+ position: resolveNodeOption(position, node) ?? 'auto'
636
707
  };
637
708
  }
638
709
  class SankeyController extends DatasetController {
@@ -643,10 +714,15 @@ class SankeyController extends DatasetController {
643
714
  const nodes = buildNodesFromData(sankeyData, this.options);
644
715
  const orientation = this.options.orientation ?? 'horizontal';
645
716
  this._nodes = nodes;
717
+ const nodeGaps = new Map();
718
+ for (const node of nodes.values()){
719
+ nodeGaps.set(node.key, resolveNodeGap(this.options.nodePadding, node));
720
+ }
646
721
  const { maxX, maxY } = layout(nodes, sankeyData, {
647
722
  height: orientation === 'vertical' ? this.chart.width : this.chart.height,
648
723
  modeX: this.options.modeX,
649
- nodePadding: this.options.nodePadding ?? 10,
724
+ nodePadding: nodeGaps,
725
+ nodePaddingMode: this.options.nodePaddingMode,
650
726
  priority: !!this.options.priority
651
727
  });
652
728
  this._maxX = maxX;
@@ -793,7 +869,7 @@ class SankeyController extends DatasetController {
793
869
  SankeyController.id = 'sankey';
794
870
  SankeyController.descriptors = {
795
871
  _indexable: false,
796
- _scriptable: true,
872
+ _scriptable: (name)=>name !== 'nodePadding',
797
873
  nodeLabels: {
798
874
  _indexable: false,
799
875
  _scriptable: false
@@ -831,6 +907,7 @@ SankeyController.defaults = {
831
907
  dataElementType: 'flow',
832
908
  modeX: 'edge',
833
909
  nodePadding: 10,
910
+ nodePaddingMode: 'auto',
834
911
  nodeWidth: 10,
835
912
  orientation: 'horizontal',
836
913
  transitions: {
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.16.3
2
+ * chartjs-chart-sankey v0.18.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,d=function(t,o){return"auto"===t?o:t}(e.position,e.autoPosition),f=Number(l.lineHeight);t.font=l.string;const u=Math.max(...r.map(o=>t.measureText(o).width)),p=f*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}(d,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),w=1===r.length?y.y:y.y-p/2+h,b=w+(r.length-1)*f/2;void 0!==n&&a(t,n,m,b-x/2,g,x,i),t.fillStyle=s;for(let o=0;o<r.length;o++)t.fillText(r[o],y.x,w+o*f)}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 d(t){return t.x??0}function f(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 f(t)+t.size}const w=(t,o)=>Boolean(o&&d(o)<d(t));function b(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,b(i,o?o+c:0)),w(i,t.to[e+1]?.node)?o+=n.flow:o=Math.max(i.y+Math.max(i.in,i.out),o)}return f(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)=>d(t)-d(o)),0===d(i))return i;const a=n[n.length-1];return d(a)===o?a:n[Math.floor(n.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),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=>d(t)>0&&d(t)<o&&!r(t.y));let l=e.reduce((t,o)=>Math.max(t,f(o)+o.out||0),0)+c,h=n.reduce((t,o)=>Math.max(t,f(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=>d(t)===d(o)&&r(t.y)).reduce((t,o)=>Math.max(t,f(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 r=0;r<=o;r++){const o=t.filter(t=>d(t)===r).sort((t,o)=>f(t)-f(o));let n=0;for(const t of o)f(t)<n&&(t.y=n),n=f(t)+t.size;e=Math.max(e,n)}return e})(t,o)}const k=(t,o)=>d(t)!==d(o)?d(t)-d(o):f(t)===f(o)?t.size-o.size:f(t)-f(o);function C(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,d(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=>d(t)===n).sort((t,o)=>(t.priority??0)-(o.priority??0));if(i.length){const o=t.reduce((t,o)=>d(o)>n?Math.min(t,d(o)):t,1/0);r=i[0].to.filter(t=>d(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=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([]),o}return r.get(t)??0};t.sort(k);for(const r of t){const t=i(d(r)),a=n[t]??[];if(f(r)){a.push(f(r));let e=a.length;if(r.in){for(let o=0;o<t;o++){const t=n[o]??[];for(let o=0;o<t.length&&!(t[o]>f(r));o++)e=Math.max(o+1,e)}for(;a.length<e;)a.push(f(r))}r.y=f(r)+e*o}e=Math.max(e,f(r)+Math.max(r.in,r.out))}return e}(s,h/n*i);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)=>f(t.node)+t.node.out/2-(f(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)=>f(t.node)+t.node.in/2-(f(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:c}}function _(t){return t.x??0}function P(t){return t.y??0}function T(t,o){return Math[o](t.in||t.out,t.out||t.in)}function S(t,o,e,r){return"vertical"===r?o<(e.top+e.bottom)/2?"bottom":"top":t<(e.left+e.right)/2?"right":"left"}function z(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}function F(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(_(o),i)},x:a.parse(e,i),y:s.parse(_(t),i)}:{_custom:{flow:n,from:t,height:s.parse(n,i),to:o,x:a.parse(_(o),i),y:s.parse(r,i)},x:a.parse(_(t),i),y:s.parse(e,i)}}function E(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:R(e,t.y,r,i)+n+a,y2:R(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:R(o,t.x,r,i)+n+a,x2:R(o,l.x,r,i)-a,y:c,y2:e.getPixelForValue(l.y)}}function R(t,o,e,r){const n=t.getPixelForValue(o);return e?n-o/e*r:n}function W(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 X(t,o,e,r,n,i,a,s){if("vertical"===s){const s=e.getPixelForValue(P(t));return{height:i,width:Math.abs(e.getPixelForValue(P(t)+o)-s),x:s,y:R(r,_(t),n,a)}}const l=r.getPixelForValue(P(t));return{height:Math.abs(r.getPixelForValue(P(t)+o)-l),width:i,x:R(e,_(t),n,a),y:l}}function Y(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 j 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{maxX:d,maxY:f}=C(l,n,{height:"vertical"===c?this.chart.width:this.chart.height,modeX:this.options.modeX,nodePadding:this.options.nodePadding??10,priority:!!this.options.priority});if(this._maxX=d,this._maxY=f,!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=P(e)+z(e.to,o.to,t),d=P(r)+z(r.from,o.from,t);s.push(F(e,r,h,d,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,d=W(h,c,this.chart),f=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,...E(o,n,i,this._maxX,h,d,f,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:d="horizontal"}=r,f=W(c,d,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=T(o,a),{height:i,width:x,x:m,y:w}=X(o,n,p,y,this._maxX,c,f,d),b=l?.[o.key]??o.key,M=Y(r,o);if(M.display){const o=e.toFont(M.font??u);s(t,b,{autoPosition:S(m,w,g,d),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:w})}}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=W(i,a,this.chart),h=n(s),{xScale:c,yScale:d}=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||!d)return;const o=Math[h](e.in||e.out,e.out||e.in),{height:n,width:s,x:f,y:u}=X(e,o,c,d,this._maxX,i,l,a);r&&t.strokeRect(f,u,s,n),t.fillRect(f,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}}j.id="sankey",j.descriptors={_indexable:!1,_scriptable:!0,nodeLabels:{_indexable:!1,_scriptable:!1}},j.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"}}}}},j.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 O=(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}},V=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),L=(t,o)=>e.color(t).alpha(o).rgbString(),A=(t,o)=>"string"==typeof t?L(t,o):t,q=t=>"string"==typeof t?e.getHoverColor(t):t;class D extends o.Element{draw(t){const{x:r,x2:n,y:i,y2:a,height:l,progress:h,width:c}=this,d=this.options.orientation,f=O(r,i,n,a,d),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,d),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=A(i.colorFrom,i.alpha):"to"===i.colorMode?a=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,L(i.colorFrom,i.alpha)),a.addColorStop(1,L(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,f,d);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,d);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:d}=O(r,n,i,a,this.options.orientation),f=h?(o-n)/(a-n):(t-r)/(i-r),u={x:i,y:a},p=V({x:r,y:n},c,f),y=V(c,d,f),g=V(d,u,f),x=V(p,y,f),m=V(y,g,f),w=V(x,m,f);return h?t>=w.x&&t<=w.x+l:o>=w.y&&o<=w.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)}}D.id="flow",D.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)=>q(o.colorFrom),hoverColorTo:(t,o)=>q(o.colorTo),orientation:"horizontal"},D.descriptors={_scriptable:!0,flowLabels:{_scriptable:!0}},o.Chart.register(j,D),t.Flow=D,t.SankeyController=j});
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});
@@ -6,7 +6,7 @@ export default class SankeyController extends DatasetController {
6
6
  static readonly id = "sankey";
7
7
  static readonly descriptors: {
8
8
  _indexable: boolean;
9
- _scriptable: boolean;
9
+ _scriptable: (name: string) => boolean;
10
10
  nodeLabels: {
11
11
  _indexable: boolean;
12
12
  _scriptable: boolean;
@@ -34,6 +34,7 @@ export default class SankeyController extends DatasetController {
34
34
  dataElementType: string;
35
35
  modeX: string;
36
36
  nodePadding: number;
37
+ nodePaddingMode: string;
37
38
  nodeWidth: number;
38
39
  orientation: string;
39
40
  transitions: {
@@ -6,7 +6,7 @@ export default class SankeyController extends DatasetController {
6
6
  static readonly id = "sankey";
7
7
  static readonly descriptors: {
8
8
  _indexable: boolean;
9
- _scriptable: boolean;
9
+ _scriptable: (name: string) => boolean;
10
10
  nodeLabels: {
11
11
  _indexable: boolean;
12
12
  _scriptable: boolean;
@@ -34,6 +34,7 @@ export default class SankeyController extends DatasetController {
34
34
  dataElementType: string;
35
35
  modeX: string;
36
36
  nodePadding: number;
37
+ nodePaddingMode: string;
37
38
  nodeWidth: number;
38
39
  orientation: string;
39
40
  transitions: {
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
1
  import Sankey from './controller.cjs';
2
2
  import Flow from './flow.cjs';
3
- export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.cjs';
3
+ export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeGap, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyNodeOption, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.cjs';
4
4
  export { Flow, Sankey as SankeyController };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import Sankey from './controller.js';
2
2
  import Flow from './flow.js';
3
- export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.js';
3
+ export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeGap, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyNodeOption, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.js';
4
4
  export { Flow, Sankey as SankeyController };
@@ -1,3 +1,3 @@
1
- export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.cjs';
1
+ export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeGap, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyNodeOption, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.cjs';
2
2
  export { default as SankeyController } from './controller.cjs';
3
3
  export { default as Flow } from './flow.cjs';
@@ -1,3 +1,3 @@
1
- export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.js';
1
+ export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeGap, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyNodeOption, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.js';
2
2
  export { default as SankeyController } from './controller.js';
3
3
  export { default as Flow } from './flow.js';
package/dist/labels.d.cts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { CanvasFontSpec, Color } from 'chart.js' with { 'resolution-mode': 'import' };
2
- import type { SankeyLabelPosition, SankeyNode, SankeyNodeLabelOption } from './types.cjs';
3
- type ResolvableNodeLabelValue = boolean | Color | SankeyLabelPosition;
2
+ import type { SankeyLabelPosition, SankeyNode, SankeyNodeOption } from './types.cjs';
4
3
  type ResolvedLabelPosition = Exclude<SankeyLabelPosition, 'auto'>;
5
4
  export interface DrawLabelOptions {
6
5
  autoPosition: ResolvedLabelPosition;
@@ -17,6 +16,6 @@ export interface DrawLabelOptions {
17
16
  x: number;
18
17
  y: number;
19
18
  }
20
- export declare function resolveNodeLabelOption<T extends ResolvableNodeLabelValue>(option: SankeyNodeLabelOption<T> | undefined, node: SankeyNode): T | undefined;
19
+ export declare function resolveNodeOption<T>(option: SankeyNodeOption<T> | undefined, node: SankeyNode): T | undefined;
21
20
  export declare function drawLabel(ctx: CanvasRenderingContext2D, label: string, options: DrawLabelOptions): void;
22
21
  export {};
package/dist/labels.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { CanvasFontSpec, Color } from 'chart.js';
2
- import type { SankeyLabelPosition, SankeyNode, SankeyNodeLabelOption } from './types.js';
3
- type ResolvableNodeLabelValue = boolean | Color | SankeyLabelPosition;
2
+ import type { SankeyLabelPosition, SankeyNode, SankeyNodeOption } from './types.js';
4
3
  type ResolvedLabelPosition = Exclude<SankeyLabelPosition, 'auto'>;
5
4
  export interface DrawLabelOptions {
6
5
  autoPosition: ResolvedLabelPosition;
@@ -17,6 +16,6 @@ export interface DrawLabelOptions {
17
16
  x: number;
18
17
  y: number;
19
18
  }
20
- export declare function resolveNodeLabelOption<T extends ResolvableNodeLabelValue>(option: SankeyNodeLabelOption<T> | undefined, node: SankeyNode): T | undefined;
19
+ export declare function resolveNodeOption<T>(option: SankeyNodeOption<T> | undefined, node: SankeyNode): T | undefined;
21
20
  export declare function drawLabel(ctx: CanvasRenderingContext2D, label: string, options: DrawLabelOptions): void;
22
21
  export {};
@@ -1,4 +1,4 @@
1
- import type { FromToElement, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyNode } from '../types.js';
1
+ import type { FromToElement, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyNode, SankeyNodeGap } from '../types.js';
2
2
  export type SankeyMode = 'edge' | 'even';
3
3
  /**
4
4
  * Get all keys the input nodes flow to, including keys of the input nodes
@@ -15,22 +15,27 @@ export declare function nodeCount(list: Array<FromToElement>, prop: FlowDirectio
15
15
  export declare const returnsToNearerColumn: (current: SankeyNode, next?: SankeyNode) => boolean;
16
16
  export declare function calculateY(nodeArray: SankeyNode[], maxX: number): number;
17
17
  export declare function calculateYUsingPriority(nodeArray: SankeyNode[], maxX: number): number;
18
+ type PaddableNode = Pick<SankeyNode, 'in' | 'key' | 'out' | 'size' | 'x' | 'y'>;
19
+ type NodeGap = Required<SankeyNodeGap>;
20
+ export type NodePaddingMode = 'auto' | 'even';
18
21
  /**
19
22
  * @return {number} maxY
20
23
  */
21
- export declare function addPadding(nodeArray: Pick<SankeyNode, 'x' | 'y' | 'in' | 'out' | 'size'>[], padding: number): number;
24
+ export declare function addPadding(nodeArray: PaddableNode[], gaps: Map<string, NodeGap>, scale?: number, mode?: NodePaddingMode): number;
22
25
  export declare function sortFlows(nodeArray: SankeyNode[]): void;
23
26
  interface LayoutOptions {
24
27
  /** use node priority when sorting nodes vertically */
25
28
  priority: boolean;
26
29
  /** chart height in CSS pixels */
27
30
  height: number;
28
- /** vertical padding between nodes (in pixels) */
29
- nodePadding: number;
31
+ /** vertical before/after gap per node, in CSS pixels */
32
+ nodePadding: Map<string, NodeGap>;
33
+ /** how nodePadding gaps are distributed within a column, defaults to 'auto' */
34
+ nodePaddingMode: SankeyControllerDatasetOptions['nodePaddingMode'];
30
35
  /** layout mode in x-direction */
31
36
  modeX: SankeyControllerDatasetOptions['modeX'];
32
37
  }
33
- export declare function layout(nodes: Map<string, SankeyNode>, data: SankeyDataPoint[], { priority, height, nodePadding, modeX }: LayoutOptions): {
38
+ export declare function layout(nodes: Map<string, SankeyNode>, data: SankeyDataPoint[], { priority, height, nodePadding, nodePaddingMode, modeX }: LayoutOptions): {
34
39
  maxY: number;
35
40
  maxX: number;
36
41
  };
package/dist/types.d.cts CHANGED
@@ -12,15 +12,21 @@ export type SankeyScriptableContext = ScriptableContext<'sankey'> & {
12
12
  export type SankeyLabelPosition = 'auto' | 'bottom' | 'center' | 'left' | 'right' | 'top';
13
13
  export type SankeyNodeLabelPosition = SankeyLabelPosition;
14
14
  export type SankeyOrientation = 'horizontal' | 'vertical';
15
- export type SankeyNodeLabelOption<T> = T | Record<string, T> | ((node: SankeyNode) => T | undefined);
15
+ export type SankeyNodeOption<T> = T | Record<string, T> | ((node: SankeyNode) => T | undefined);
16
+ /** @deprecated use SankeyNodeOption */
17
+ export type SankeyNodeLabelOption<T> = SankeyNodeOption<T>;
18
+ export interface SankeyNodeGap {
19
+ after?: number;
20
+ before?: number;
21
+ }
16
22
  export interface SankeyControllerDatasetNodeLabelsOptions {
17
- backgroundColor?: SankeyNodeLabelOption<Color>;
23
+ backgroundColor?: SankeyNodeOption<Color>;
18
24
  borderRadius?: number;
19
- color?: SankeyNodeLabelOption<Color>;
20
- display?: SankeyNodeLabelOption<boolean>;
25
+ color?: SankeyNodeOption<Color>;
26
+ display?: SankeyNodeOption<boolean>;
21
27
  font?: Partial<FontSpec>;
22
28
  padding?: number;
23
- position?: SankeyNodeLabelOption<SankeyLabelPosition>;
29
+ position?: SankeyNodeOption<SankeyLabelPosition>;
24
30
  }
25
31
  export interface SankeyControllerDatasetFlowLabelsOptions {
26
32
  backgroundColor?: Scriptable<Color, SankeyScriptableContext>;
@@ -55,7 +61,8 @@ export interface SankeyControllerDatasetOptions extends Omit<ControllerDatasetOp
55
61
  flowColor?: ScriptableAndArray<Color, SankeyScriptableContext>;
56
62
  modeX?: 'edge' | 'even';
57
63
  nodeLabels?: SankeyControllerDatasetNodeLabelsOptions;
58
- nodePadding?: number;
64
+ nodePadding?: SankeyNodeOption<number | SankeyNodeGap>;
65
+ nodePaddingMode?: 'auto' | 'even';
59
66
  nodeWidth?: number;
60
67
  orientation?: SankeyOrientation;
61
68
  padding?: number;
package/dist/types.d.ts CHANGED
@@ -11,15 +11,21 @@ export type SankeyScriptableContext = ScriptableContext<'sankey'> & {
11
11
  export type SankeyLabelPosition = 'auto' | 'bottom' | 'center' | 'left' | 'right' | 'top';
12
12
  export type SankeyNodeLabelPosition = SankeyLabelPosition;
13
13
  export type SankeyOrientation = 'horizontal' | 'vertical';
14
- export type SankeyNodeLabelOption<T> = T | Record<string, T> | ((node: SankeyNode) => T | undefined);
14
+ export type SankeyNodeOption<T> = T | Record<string, T> | ((node: SankeyNode) => T | undefined);
15
+ /** @deprecated use SankeyNodeOption */
16
+ export type SankeyNodeLabelOption<T> = SankeyNodeOption<T>;
17
+ export interface SankeyNodeGap {
18
+ after?: number;
19
+ before?: number;
20
+ }
15
21
  export interface SankeyControllerDatasetNodeLabelsOptions {
16
- backgroundColor?: SankeyNodeLabelOption<Color>;
22
+ backgroundColor?: SankeyNodeOption<Color>;
17
23
  borderRadius?: number;
18
- color?: SankeyNodeLabelOption<Color>;
19
- display?: SankeyNodeLabelOption<boolean>;
24
+ color?: SankeyNodeOption<Color>;
25
+ display?: SankeyNodeOption<boolean>;
20
26
  font?: Partial<FontSpec>;
21
27
  padding?: number;
22
- position?: SankeyNodeLabelOption<SankeyLabelPosition>;
28
+ position?: SankeyNodeOption<SankeyLabelPosition>;
23
29
  }
24
30
  export interface SankeyControllerDatasetFlowLabelsOptions {
25
31
  backgroundColor?: Scriptable<Color, SankeyScriptableContext>;
@@ -54,7 +60,8 @@ export interface SankeyControllerDatasetOptions extends Omit<ControllerDatasetOp
54
60
  flowColor?: ScriptableAndArray<Color, SankeyScriptableContext>;
55
61
  modeX?: 'edge' | 'even';
56
62
  nodeLabels?: SankeyControllerDatasetNodeLabelsOptions;
57
- nodePadding?: number;
63
+ nodePadding?: SankeyNodeOption<number | SankeyNodeGap>;
64
+ nodePaddingMode?: 'auto' | 'even';
58
65
  nodeWidth?: number;
59
66
  orientation?: SankeyOrientation;
60
67
  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.16.3"
107
+ "version": "0.18.0"
108
108
  }