chartjs-chart-sankey 0.16.2 → 0.17.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.2
2
+ * chartjs-chart-sankey v0.17.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,36 @@ 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) {
465
+ const realNodesAbove = state.realCount;
466
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, gap.before) : 0;
467
+ const realCumOffset = state.realCumOffset + transitionGap;
468
+ const virtualLevels = paddings - realNodesAbove;
469
+ state.realCount = realNodesAbove + 1;
470
+ state.realCumOffset = realCumOffset;
471
+ state.lastAfter = gap.after;
472
+ return realCumOffset + virtualLevels * gap.before;
473
+ }
474
+ function addPadding(nodeArray, gaps) {
446
475
  let maxY = 0;
447
476
  const columnXs = new Map();
448
477
  const grid = [];
@@ -450,7 +479,7 @@ const nodeByXYSize = (a, b)=>{
450
479
  if (!columnXs.has(x)) {
451
480
  const index = grid.length;
452
481
  columnXs.set(x, index);
453
- grid.push([]);
482
+ grid.push(createColumnGapState());
454
483
  return index;
455
484
  }
456
485
  return columnXs.get(x) ?? 0;
@@ -458,21 +487,23 @@ const nodeByXYSize = (a, b)=>{
458
487
  nodeArray.sort(nodeByXYSize);
459
488
  for (const node of nodeArray){
460
489
  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;
490
+ const state = grid[colIdx];
491
+ const gap = gaps.get(node.key) ?? {
492
+ after: 0,
493
+ before: 0
494
+ };
495
+ const y = nodeY$1(node);
496
+ if (y) {
497
+ state.yHistory.push(y);
498
+ let paddings = state.yHistory.length;
465
499
  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));
500
+ paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
501
+ while(state.yHistory.length < paddings)state.yHistory.push(y);
474
502
  }
475
- node.y = nodeY$1(node) + paddings * padding;
503
+ node.y = y + offsetForNode(state, gap, paddings);
504
+ } else {
505
+ state.realCount += 1;
506
+ state.lastAfter = gap.after;
476
507
  }
477
508
  maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
478
509
  }
@@ -511,8 +542,15 @@ function layout(nodes, data, { priority, height, nodePadding, modeX }) {
511
542
  ];
512
543
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
513
544
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
514
- const padding = maxY / height * nodePadding;
515
- const maxYWithPadding = addPadding(nodeArray, padding);
545
+ const scale = maxY / height;
546
+ const scaledGaps = new Map();
547
+ for (const [key, gap] of nodePadding){
548
+ scaledGaps.set(key, {
549
+ after: gap.after * scale,
550
+ before: gap.before * scale
551
+ });
552
+ }
553
+ const maxYWithPadding = addPadding(nodeArray, scaledGaps);
516
554
  sortFlows(nodeArray);
517
555
  return {
518
556
  maxX,
@@ -626,16 +664,29 @@ function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPad
626
664
  y
627
665
  };
628
666
  }
667
+ function resolveNodeGap(option, node) {
668
+ const resolved = resolveNodeOption(option ?? 10, node) ?? 10;
669
+ if (typeof resolved === 'number') {
670
+ return {
671
+ after: resolved,
672
+ before: resolved
673
+ };
674
+ }
675
+ return {
676
+ after: resolved.after ?? 10,
677
+ before: resolved.before ?? 10
678
+ };
679
+ }
629
680
  function resolveNodeLabelStyle(options, node) {
630
681
  const { backgroundColor, borderRadius = 0, color, display, font, padding = 4, position } = options.nodeLabels ?? {};
631
682
  return {
632
- backgroundColor: resolveNodeLabelOption(backgroundColor, node),
683
+ backgroundColor: resolveNodeOption(backgroundColor, node),
633
684
  borderRadius,
634
- color: resolveNodeLabelOption(color, node) ?? options.color ?? 'black',
635
- display: resolveNodeLabelOption(display, node) ?? true,
685
+ color: resolveNodeOption(color, node) ?? options.color ?? 'black',
686
+ display: resolveNodeOption(display, node) ?? true,
636
687
  font,
637
688
  padding,
638
- position: resolveNodeLabelOption(position, node) ?? 'auto'
689
+ position: resolveNodeOption(position, node) ?? 'auto'
639
690
  };
640
691
  }
641
692
  class SankeyController extends chart_js.DatasetController {
@@ -646,10 +697,14 @@ class SankeyController extends chart_js.DatasetController {
646
697
  const nodes = buildNodesFromData(sankeyData, this.options);
647
698
  const orientation = this.options.orientation ?? 'horizontal';
648
699
  this._nodes = nodes;
700
+ const nodeGaps = new Map();
701
+ for (const node of nodes.values()){
702
+ nodeGaps.set(node.key, resolveNodeGap(this.options.nodePadding, node));
703
+ }
649
704
  const { maxX, maxY } = layout(nodes, sankeyData, {
650
- height: orientation === 'vertical' ? this.chart.canvas.width : this.chart.canvas.height,
705
+ height: orientation === 'vertical' ? this.chart.width : this.chart.height,
651
706
  modeX: this.options.modeX,
652
- nodePadding: this.options.nodePadding ?? 10,
707
+ nodePadding: nodeGaps,
653
708
  priority: !!this.options.priority
654
709
  });
655
710
  this._maxX = maxX;
@@ -796,7 +851,7 @@ class SankeyController extends chart_js.DatasetController {
796
851
  SankeyController.id = 'sankey';
797
852
  SankeyController.descriptors = {
798
853
  _indexable: false,
799
- _scriptable: true,
854
+ _scriptable: (name)=>name !== 'nodePadding',
800
855
  nodeLabels: {
801
856
  _indexable: false,
802
857
  _scriptable: false
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.16.2
2
+ * chartjs-chart-sankey v0.17.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,36 @@ 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) {
462
+ const realNodesAbove = state.realCount;
463
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, gap.before) : 0;
464
+ const realCumOffset = state.realCumOffset + transitionGap;
465
+ const virtualLevels = paddings - realNodesAbove;
466
+ state.realCount = realNodesAbove + 1;
467
+ state.realCumOffset = realCumOffset;
468
+ state.lastAfter = gap.after;
469
+ return realCumOffset + virtualLevels * gap.before;
470
+ }
471
+ function addPadding(nodeArray, gaps) {
443
472
  let maxY = 0;
444
473
  const columnXs = new Map();
445
474
  const grid = [];
@@ -447,7 +476,7 @@ const nodeByXYSize = (a, b)=>{
447
476
  if (!columnXs.has(x)) {
448
477
  const index = grid.length;
449
478
  columnXs.set(x, index);
450
- grid.push([]);
479
+ grid.push(createColumnGapState());
451
480
  return index;
452
481
  }
453
482
  return columnXs.get(x) ?? 0;
@@ -455,21 +484,23 @@ const nodeByXYSize = (a, b)=>{
455
484
  nodeArray.sort(nodeByXYSize);
456
485
  for (const node of nodeArray){
457
486
  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;
487
+ const state = grid[colIdx];
488
+ const gap = gaps.get(node.key) ?? {
489
+ after: 0,
490
+ before: 0
491
+ };
492
+ const y = nodeY$1(node);
493
+ if (y) {
494
+ state.yHistory.push(y);
495
+ let paddings = state.yHistory.length;
462
496
  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));
497
+ paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
498
+ while(state.yHistory.length < paddings)state.yHistory.push(y);
471
499
  }
472
- node.y = nodeY$1(node) + paddings * padding;
500
+ node.y = y + offsetForNode(state, gap, paddings);
501
+ } else {
502
+ state.realCount += 1;
503
+ state.lastAfter = gap.after;
473
504
  }
474
505
  maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
475
506
  }
@@ -508,8 +539,15 @@ function layout(nodes, data, { priority, height, nodePadding, modeX }) {
508
539
  ];
509
540
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
510
541
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
511
- const padding = maxY / height * nodePadding;
512
- const maxYWithPadding = addPadding(nodeArray, padding);
542
+ const scale = maxY / height;
543
+ const scaledGaps = new Map();
544
+ for (const [key, gap] of nodePadding){
545
+ scaledGaps.set(key, {
546
+ after: gap.after * scale,
547
+ before: gap.before * scale
548
+ });
549
+ }
550
+ const maxYWithPadding = addPadding(nodeArray, scaledGaps);
513
551
  sortFlows(nodeArray);
514
552
  return {
515
553
  maxX,
@@ -623,16 +661,29 @@ function getNodeRect(node, size, xScale, yScale, maxColumn, nodeWidth, columnPad
623
661
  y
624
662
  };
625
663
  }
664
+ function resolveNodeGap(option, node) {
665
+ const resolved = resolveNodeOption(option ?? 10, node) ?? 10;
666
+ if (typeof resolved === 'number') {
667
+ return {
668
+ after: resolved,
669
+ before: resolved
670
+ };
671
+ }
672
+ return {
673
+ after: resolved.after ?? 10,
674
+ before: resolved.before ?? 10
675
+ };
676
+ }
626
677
  function resolveNodeLabelStyle(options, node) {
627
678
  const { backgroundColor, borderRadius = 0, color, display, font, padding = 4, position } = options.nodeLabels ?? {};
628
679
  return {
629
- backgroundColor: resolveNodeLabelOption(backgroundColor, node),
680
+ backgroundColor: resolveNodeOption(backgroundColor, node),
630
681
  borderRadius,
631
- color: resolveNodeLabelOption(color, node) ?? options.color ?? 'black',
632
- display: resolveNodeLabelOption(display, node) ?? true,
682
+ color: resolveNodeOption(color, node) ?? options.color ?? 'black',
683
+ display: resolveNodeOption(display, node) ?? true,
633
684
  font,
634
685
  padding,
635
- position: resolveNodeLabelOption(position, node) ?? 'auto'
686
+ position: resolveNodeOption(position, node) ?? 'auto'
636
687
  };
637
688
  }
638
689
  class SankeyController extends DatasetController {
@@ -643,10 +694,14 @@ class SankeyController extends DatasetController {
643
694
  const nodes = buildNodesFromData(sankeyData, this.options);
644
695
  const orientation = this.options.orientation ?? 'horizontal';
645
696
  this._nodes = nodes;
697
+ const nodeGaps = new Map();
698
+ for (const node of nodes.values()){
699
+ nodeGaps.set(node.key, resolveNodeGap(this.options.nodePadding, node));
700
+ }
646
701
  const { maxX, maxY } = layout(nodes, sankeyData, {
647
- height: orientation === 'vertical' ? this.chart.canvas.width : this.chart.canvas.height,
702
+ height: orientation === 'vertical' ? this.chart.width : this.chart.height,
648
703
  modeX: this.options.modeX,
649
- nodePadding: this.options.nodePadding ?? 10,
704
+ nodePadding: nodeGaps,
650
705
  priority: !!this.options.priority
651
706
  });
652
707
  this._maxX = maxX;
@@ -793,7 +848,7 @@ class SankeyController extends DatasetController {
793
848
  SankeyController.id = 'sankey';
794
849
  SankeyController.descriptors = {
795
850
  _indexable: false,
796
- _scriptable: true,
851
+ _scriptable: (name)=>name !== 'nodePadding',
797
852
  nodeLabels: {
798
853
  _indexable: false,
799
854
  _scriptable: false
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.16.2
2
+ * chartjs-chart-sankey v0.17.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.canvas.width:this.chart.canvas.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){const r=t.realCount,n=r>0?Math.max(t.lastAfter,o.before):0,i=t.realCumOffset+n,a=e-r;return t.realCount=r+1,t.realCumOffset=i,t.lastAfter=o.after,i+a*o.before}function _(t,o,{priority:e,height:n,nodePadding:i,modeX:a}){const s=[...t.values()],l=function(t,o,e){const n=o.filter(t=>t.from!==t.to),i=[...t.keys()],a=[...t.values()],s=new Set(i);let l=0;for(;s.size;){const e=0===l?p(o,a):y(n,s);if(!e.length)throw new Error("Fatal error: Unable to place nodes to columns. Please report this issue.");for(const o of e){const e=t.get(o);e&&!r(e.x)&&(e.x=l),s.delete(o)}s.size&&l++}const h=a.reduce((t,o)=>Math.max(t,f(o)),0);if("edge"===e){const e=new Set(o.map(t=>t.from));i.filter(t=>!e.has(t)).forEach(o=>{const e=t.get(o);e&&!e.column&&(e.x=h)})}return h}(t,o,a??"edge"),h=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const i=t.filter(t=>f(t)===n).sort((t,o)=>(t.priority??0)-(o.priority??0));if(i.length){const o=t.reduce((t,o)=>f(o)>n?Math.min(t,f(o)):t,1/0);r=i[0].to.filter(t=>f(t.node)>o).reduce((t,o)=>t+o.flow,0)||0}for(const t of i)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(s,l):v(s,l),c=h/n,u=new Map;for(const[t,o]of i)u.set(t,{after:o.after*c,before:o.before*c});const g=function(t,o){let e=0;const r=new Map,n=[],i=t=>{if(!r.has(t)){const o=n.length;return r.set(t,o),n.push({lastAfter:0,realCount:0,realCumOffset:0,yHistory:[]}),o}return r.get(t)??0};t.sort(k);for(const r of t){const t=i(f(r)),a=n[t],s=o.get(r.key)??{after:0,before:0},l=d(r);if(l){a.yHistory.push(l);let o=a.yHistory.length;if(r.in)for(o=C(n,t,l,o);a.yHistory.length<o;)a.yHistory.push(l);r.y=l+P(a,s,o)}else a.realCount+=1,a.lastAfter=s.after;e=Math.max(e,d(r)+Math.max(r.in,r.out))}return e}(s,u);return function(t){t.forEach(t=>{const o=t.size,e=o<t.in,r=o<t.out;let n=0,i=t.from.length;t.from.sort((t,o)=>d(t.node)+t.node.out/2-(d(o.node)+o.node.out/2)).forEach((t,r)=>{e?t.addY=r*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)}),n=0,i=t.to.length,t.to.sort((t,o)=>d(t.node)+t.node.in/2-(d(o.node)+o.node.in/2)).forEach((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)})})}(s),{maxX:l,maxY:g}}function T(t){return t.x??0}function S(t){return t.y??0}function z(t,o){return Math[o](t.in||t.out,t.out||t.in)}function F(t,o,e,r){return"vertical"===r?o<(e.top+e.bottom)/2?"bottom":"top":t<(e.left+e.right)/2?"right":"left"}function E(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}function O(t,o,e,r,n,i,a,s,l){return"vertical"===l?{_custom:{flow:n,from:t,height:a.parse(n,i),to:o,x:a.parse(r,i),y:s.parse(T(o),i)},x:a.parse(e,i),y:s.parse(T(t),i)}:{_custom:{flow:n,from:t,height:s.parse(n,i),to:o,x:a.parse(T(o),i),y:s.parse(r,i)},x:a.parse(T(t),i),y:s.parse(e,i)}}function R(t,o,e,r,n,i,a,s){const l=t._custom,h=o.getPixelForValue(t.x),c=e.getPixelForValue(t.y);return"vertical"===s?{flow:l.flow,from:l.from,height:0,to:l.to,width:Math.abs(o.getPixelForValue(t.x+l.height)-h),x:h,x2:o.getPixelForValue(l.x),y:W(e,t.y,r,i)+n+a,y2:W(e,l.y,r,i)-a}:{flow:l.flow,from:l.from,height:Math.abs(e.getPixelForValue(t.y+l.height)-c),to:l.to,width:0,x:W(o,t.x,r,i)+n+a,x2:W(o,l.x,r,i)-a,y:c,y2:e.getPixelForValue(l.y)}}function W(t,o,e,r){const n=t.getPixelForValue(o);return e?n-o/e*r:n}function X(t,o,e){const r="vertical"===o?e.height-e.chartArea.bottom:e.width-e.chartArea.right;return Math.max(0,t+3-r)}function Y(t,o,e,r,n,i,a,s){if("vertical"===s){const s=e.getPixelForValue(S(t));return{height:i,width:Math.abs(e.getPixelForValue(S(t)+o)-s),x:s,y:W(r,T(t),n,a)}}const l=r.getPixelForValue(S(t));return{height:Math.abs(r.getPixelForValue(S(t)+o)-l),width:i,x:W(e,T(t),n,a),y:l}}function j(t,o){const e=i(t??10,o)??10;return"number"==typeof e?{after:e,before:e}:{after:e.after??10,before:e.before??10}}function A(t,o){const{backgroundColor:e,borderRadius:r=0,color:n,display:a,font:s,padding:l=4,position:h}=t.nodeLabels??{};return{backgroundColor:i(e,o),borderRadius:r,color:i(n,o)??t.color??"black",display:i(a,o)??!0,font:s,padding:l,position:i(h,o)??"auto"}}class V extends o.DatasetController{parseObjectData(t,o,e,r){const n=((t,o)=>{const{from:e="from",to:r="to",flow:n="flow"}=o;return t.map(({[e]:t,[r]:o,[n]:i})=>({flow:i,from:t,to:o}))})(o,this.options.parsing),{xScale:i,yScale:a}=t,s=[],l=h(n,this.options),c=this.options.orientation??"horizontal";this._nodes=l;const f=new Map;for(const t of l.values())f.set(t.key,j(this.options.nodePadding,t));const{maxX:d,maxY:u}=_(l,n,{height:"vertical"===c?this.chart.width:this.chart.height,modeX:this.options.modeX,nodePadding:f,priority:!!this.options.priority});if(this._maxX=d,this._maxY=u,!i||!a)return[];for(let t=0,o=n.length;t<o;++t){const o=n[t],e=l.get(o.from),r=l.get(o.to);if(!e||!r)continue;const h=S(e)+E(e.to,o.to,t),f=S(r)+E(r.from,o.from,t);s.push(O(e,r,h,f,o.flow,t,i,a,c))}return s.slice(e,e+r)}getMinMax(t){return{max:t===("vertical"===this.options.orientation?this._cachedMeta.yScale:this._cachedMeta.xScale)?this._maxX:this._maxY,min:0}}update(t){const{data:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,o,e,r){const{xScale:n,yScale:i}=this._cachedMeta;if(!n||!i)return;const a=this.resolveDataElementOptions(o,r),s=this.getSharedOptions(a),{borderWidth:l,nodeWidth:h=10,orientation:c="horizontal"}=this.options,f=X(h,c,this.chart),d=l?l/2+.5:0;for(let a=o;a<o+e;a++){const o=this.getParsed(a);this.updateElement(t[a],a,{options:this.resolveDataElementOptions(a,r),progress:"reset"===r?0:1,...R(o,n,i,this._maxX,h,f,d,c)},r)}s&&this.updateSharedOptions(s,r,a)}_drawLabels(){const t=this.chart.ctx,r=this.options,i=this._nodes||new Map,a=n(r.size),l=r.labels,{borderWidth:h=1,nodeWidth:c=10,orientation:f="horizontal"}=r,d=X(c,f,this.chart),u=r.font??this.chart.options.font??o.Chart.defaults.font,{xScale:p,yScale:y}=this._cachedMeta;if(!p||!y)return;t.save();const g=this.chart.chartArea;for(const o of i.values()){const n=z(o,a),{height:i,width:x,x:m,y:b}=Y(o,n,p,y,this._maxX,c,d,f),w=l?.[o.key]??o.key,M=A(r,o);if(M.display){const o=e.toFont(M.font??u);s(t,w,{autoPosition:F(m,b,g,f),backgroundColor:M.backgroundColor,borderRadius:M.borderRadius,borderWidth:h,color:M.color,font:o,height:i,lineOffset:e.valueOrDefault(r.padding,o.lineHeight/2),padding:M.padding,position:M.position,width:x,x:m,y:b})}}t.restore()}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:r=0,nodeWidth:i=10,orientation:a="horizontal",size:s}=this.options,l=X(i,a,this.chart),h=n(s),{xScale:c,yScale:f}=this._cachedMeta;t.save(),e&&r&&(t.strokeStyle=e,t.lineWidth=r);for(const e of o.values()){if(t.fillStyle=e.color??"black",!c||!f)return;const o=Math[h](e.in||e.out,e.out||e.in),{height:n,width:s,x:d,y:u}=Y(e,o,c,f,this._maxX,i,l,a);r&&t.strokeRect(d,u,s,n),t.fillRect(d,u,s,n)}t.restore()}draw(){const t=this.chart.ctx,o=this.getMeta().data??[],e=[];for(let t=0,r=o.length;t<r;++t){const r=o[t];r.from&&r.to&&(r.from.color=r.options.colorFrom,r.to.color=r.options.colorTo,r.active&&e.push(r))}for(const t of e)t.from&&t.to&&(t.from.color=t.options.colorFrom,t.to.color=t.options.colorTo);this._drawNodes();for(let e=0,r=o.length;e<r;++e)o[e].draw(t);this._drawLabels()}constructor(...t){super(...t),this._nodes=new Map,this._maxX=0,this._maxY=0}}V.id="sankey",V.descriptors={_indexable:!1,_scriptable:t=>"nodePadding"!==t,nodeLabels:{_indexable:!1,_scriptable:!1}},V.defaults={animations:{colors:{properties:["colorFrom","colorTo"],type:"color"},numbers:{properties:["x","y","x2","y2","height","width"],type:"number"},progress:{delay:t=>"data"===t.type?500*t.parsed["vertical"===t.dataset.orientation?"y":"x"]+20*t.dataIndex:void 0,duration:t=>"data"===t.type?200*(t.parsed._custom["vertical"===t.dataset.orientation?"y":"x"]-t.parsed["vertical"===t.dataset.orientation?"y":"x"]):void 0,easing:"linear"}},borderColor:"black",borderWidth:1,color:"black",dataElementType:"flow",modeX:"edge",nodePadding:10,nodeWidth:10,orientation:"horizontal",transitions:{hide:{animations:{colors:{properties:["colorFrom","colorTo"],to:"transparent",type:"color"}}},resize:{animations:{progress:{delay:0,duration:0}}},show:{animations:{colors:{from:"transparent",properties:["colorFrom","colorTo"],type:"color"}}}}},V.overrides={datasets:{clip:!1,parsing:{flow:"flow",from:"from",to:"to"}},interaction:{intersect:!0,mode:"nearest"},layout:{padding:{bottom:3,left:3,right:13,top:3}},plugins:{legend:{display:!1},tooltip:{callbacks:{label(t){const o=t.parsed._custom;return`${o.from.key} -> ${o.to.key}: ${o.flow}`},title:()=>""}}},scales:{x:{bounds:"data",display:!1,min:0,offset:!1,type:"linear"},y:{bounds:"data",display:!1,min:0,offset:!1,reverse:!0,type:"linear"}}};const H=(t,o,e,r,n)=>"vertical"===n?o<r?{cp1:{x:t,y:o+(r-o)/3*2},cp2:{x:e,y:o+(r-o)/3}}:{cp1:{x:0,y:o-(o-r)/3},cp2:{x:0,y:r+(o-r)/3}}:t<e?{cp1:{x:t+(e-t)/3*2,y:o},cp2:{x:t+(e-t)/3,y:r}}:{cp1:{x:t-(t-e)/3,y:0},cp2:{x:e+(t-e)/3,y:0}},L=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),q=(t,o)=>e.color(t).alpha(o).rgbString(),D=(t,o)=>"string"==typeof t?q(t,o):t,$=t=>"string"==typeof t?e.getHoverColor(t):t;class G extends o.Element{draw(t){const{x:r,x2:n,y:i,y2:a,height:l,progress:h,width:c}=this,f=this.options.orientation,d=H(r,i,n,a,f),u={height:l,width:c,x:r,x2:n,y:i,y2:a};if(0===h)return;t.save(),h<1&&function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},s,l){t.beginPath(),"vertical"===l?t.rect(Math.min(r,n),i,Math.abs(n-r)+e+1,(a-i)*s+1):t.rect(r,Math.min(i,a),(n-r)*s+1,Math.abs(a-i)+o+1),t.clip()}(t,u,h,f),function(t,{x:o,x2:e,y:r,y2:n,options:i}){let a="black";null!==i.flowColor?a=i.flowColor:"from"===i.colorMode?a=D(i.colorFrom,i.alpha):"to"===i.colorMode?a=D(i.colorTo,i.alpha):"string"==typeof i.colorFrom&&"string"==typeof i.colorTo&&(a="vertical"===i.orientation?t.createLinearGradient(0,r,0,n):t.createLinearGradient(o,0,e,0),a.addColorStop(0,q(i.colorFrom,i.alpha)),a.addColorStop(1,q(i.colorTo,i.alpha))),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},{cp1:s,cp2:l},h){t.beginPath(),t.moveTo(r,i),t.bezierCurveTo(s.x,s.y,l.x,l.y,n,a),"vertical"===h?(t.lineTo(n+e,a),t.bezierCurveTo(l.x+e,l.y,s.x+e,s.y,r+e,i)):(t.lineTo(n,a+o),t.bezierCurveTo(l.x,l.y+o,s.x,s.y+o,r,i+o)),t.lineTo(r,i),t.stroke(),t.closePath(),t.fill()}(t,u,d,f);const p=this.options.flowLabels;if(p.display){const r=e.toFont(p.font??o.Chart.defaults.font),n=function({height:t,width:o,x:e,x2:r,y:n,y2:i},a){return"vertical"===a?{height:i-n,width:Math.abs(r-e)+o,x:Math.min(e,r),y:n}:{height:Math.abs(i-n)+t,width:r-e,x:e,y:Math.min(n,i)}}(u,f);s(t,`${this.flow}`,{autoPosition:"center",backgroundColor:p.backgroundColor,borderRadius:p.borderRadius,borderWidth:0,color:p.color,font:r,height:n.height,lineOffset:r.lineHeight/2,padding:p.padding,position:p.position,width:n.width,x:n.x,y:n.y})}t.restore()}inRange(t,o,e){const{x:r,y:n,x2:i,y2:a,height:s,width:l}=this.getProps(["x","y","x2","y2","height","width"],e),h="vertical"===this.options.orientation;if(h?o<n||o>a:t<r||t>i)return!1;const{cp1:c,cp2:f}=H(r,n,i,a,this.options.orientation),d=h?(o-n)/(a-n):(t-r)/(i-r),u={x:i,y:a},p=L({x:r,y:n},c,d),y=L(c,f,d),g=L(f,u,d),x=L(p,y,d),m=L(y,g,d),b=L(x,m,d);return h?t>=b.x&&t<=b.x+l:o>=b.y&&o<=b.y+s}inXRange(t,o){const{x:e,x2:r,width:n}=this.getProps(["x","x2","width"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?n:0);return t>=i&&t<=a}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?0:n);return t>=i&&t<=a}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:i,width:a}=this.getProps(["x","y","x2","y2","height","width"],t),s="vertical"===this.options.orientation;return{x:(o+r+(s?a:0))/2,y:(e+n+(s?0:i))/2}}tooltipPosition(t=!1){return this.getCenterPoint(t)}getRange(t){const o="vertical"===this.options.orientation;return"x"===t?o?this.width/2:0:o?0:this.height/2}constructor(t){super(),this.flow=0,this.x2=0,this.y2=0,this.width=0,this.height=0,this.progress=1,t&&Object.assign(this,t)}}G.id="flow",G.defaults={alpha:.5,colorFrom:"red",colorMode:"gradient",colorTo:"green",flowColor:null,flowLabels:{borderRadius:0,color:"black",display:!1,padding:4,position:"center"},hoverColorFrom:(t,o)=>$(o.colorFrom),hoverColorTo:(t,o)=>$(o.colorTo),orientation:"horizontal"},G.descriptors={_scriptable:!0,flowLabels:{_scriptable:!0}},o.Chart.register(V,G),t.Flow=G,t.SankeyController=V});
@@ -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;
@@ -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;
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,18 +15,20 @@ 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>;
18
20
  /**
19
21
  * @return {number} maxY
20
22
  */
21
- export declare function addPadding(nodeArray: Pick<SankeyNode, 'x' | 'y' | 'in' | 'out' | 'size'>[], padding: number): number;
23
+ export declare function addPadding(nodeArray: PaddableNode[], gaps: Map<string, NodeGap>): number;
22
24
  export declare function sortFlows(nodeArray: SankeyNode[]): void;
23
25
  interface LayoutOptions {
24
26
  /** use node priority when sorting nodes vertically */
25
27
  priority: boolean;
26
- /** canvas height (in pixels) */
28
+ /** chart height in CSS pixels */
27
29
  height: number;
28
- /** vertical padding between nodes (in pixels) */
29
- nodePadding: number;
30
+ /** vertical before/after gap per node, in CSS pixels */
31
+ nodePadding: Map<string, NodeGap>;
30
32
  /** layout mode in x-direction */
31
33
  modeX: SankeyControllerDatasetOptions['modeX'];
32
34
  }
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,7 @@ 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>;
59
65
  nodeWidth?: number;
60
66
  orientation?: SankeyOrientation;
61
67
  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,7 @@ 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>;
58
64
  nodeWidth?: number;
59
65
  orientation?: SankeyOrientation;
60
66
  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.2"
107
+ "version": "0.17.0"
108
108
  }