chartjs-chart-sankey 0.17.0 → 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.17.0
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
@@ -461,17 +461,45 @@ function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
461
461
  }
462
462
  return paddings;
463
463
  }
464
- function offsetForNode(state, gap, paddings) {
464
+ function offsetForNode(state, gap, paddings, scale) {
465
465
  const realNodesAbove = state.realCount;
466
- const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, gap.before) : 0;
466
+ const before = gap.before * scale;
467
+ const after = gap.after * scale;
468
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) : 0;
467
469
  const realCumOffset = state.realCumOffset + transitionGap;
468
470
  const virtualLevels = paddings - realNodesAbove;
469
471
  state.realCount = realNodesAbove + 1;
470
472
  state.realCumOffset = realCumOffset;
471
- state.lastAfter = gap.after;
472
- return realCumOffset + virtualLevels * gap.before;
473
+ state.lastAfter = after;
474
+ return realCumOffset + virtualLevels * before;
473
475
  }
474
- function addPadding(nodeArray, gaps) {
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
+ }
475
503
  let maxY = 0;
476
504
  const columnXs = new Map();
477
505
  const grid = [];
@@ -484,7 +512,6 @@ function offsetForNode(state, gap, paddings) {
484
512
  }
485
513
  return columnXs.get(x) ?? 0;
486
514
  };
487
- nodeArray.sort(nodeByXYSize);
488
515
  for (const node of nodeArray){
489
516
  const colIdx = getColIndex(nodeX$1(node));
490
517
  const state = grid[colIdx];
@@ -500,10 +527,10 @@ function offsetForNode(state, gap, paddings) {
500
527
  paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
501
528
  while(state.yHistory.length < paddings)state.yHistory.push(y);
502
529
  }
503
- node.y = y + offsetForNode(state, gap, paddings);
530
+ node.y = y + offsetForNode(state, gap, paddings, scale);
504
531
  } else {
505
532
  state.realCount += 1;
506
- state.lastAfter = gap.after;
533
+ state.lastAfter = gap.after * scale;
507
534
  }
508
535
  maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
509
536
  }
@@ -536,21 +563,14 @@ function sortFlows(nodeArray) {
536
563
  });
537
564
  });
538
565
  }
539
- function layout(nodes, data, { priority, height, nodePadding, modeX }) {
566
+ function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, modeX }) {
540
567
  const nodeArray = [
541
568
  ...nodes.values()
542
569
  ];
543
570
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
544
571
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
545
572
  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);
573
+ const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto');
554
574
  sortFlows(nodeArray);
555
575
  return {
556
576
  maxX,
@@ -705,6 +725,7 @@ class SankeyController extends chart_js.DatasetController {
705
725
  height: orientation === 'vertical' ? this.chart.width : this.chart.height,
706
726
  modeX: this.options.modeX,
707
727
  nodePadding: nodeGaps,
728
+ nodePaddingMode: this.options.nodePaddingMode,
708
729
  priority: !!this.options.priority
709
730
  });
710
731
  this._maxX = maxX;
@@ -889,6 +910,7 @@ SankeyController.defaults = {
889
910
  dataElementType: 'flow',
890
911
  modeX: 'edge',
891
912
  nodePadding: 10,
913
+ nodePaddingMode: 'auto',
892
914
  nodeWidth: 10,
893
915
  orientation: 'horizontal',
894
916
  transitions: {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.17.0
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
@@ -458,17 +458,45 @@ function countCrossColumnPaddings(grid, colIdx, y, ownPaddings) {
458
458
  }
459
459
  return paddings;
460
460
  }
461
- function offsetForNode(state, gap, paddings) {
461
+ function offsetForNode(state, gap, paddings, scale) {
462
462
  const realNodesAbove = state.realCount;
463
- const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, gap.before) : 0;
463
+ const before = gap.before * scale;
464
+ const after = gap.after * scale;
465
+ const transitionGap = realNodesAbove > 0 ? Math.max(state.lastAfter, before) : 0;
464
466
  const realCumOffset = state.realCumOffset + transitionGap;
465
467
  const virtualLevels = paddings - realNodesAbove;
466
468
  state.realCount = realNodesAbove + 1;
467
469
  state.realCumOffset = realCumOffset;
468
- state.lastAfter = gap.after;
469
- return realCumOffset + virtualLevels * gap.before;
470
+ state.lastAfter = after;
471
+ return realCumOffset + virtualLevels * before;
470
472
  }
471
- function addPadding(nodeArray, gaps) {
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
+ }
472
500
  let maxY = 0;
473
501
  const columnXs = new Map();
474
502
  const grid = [];
@@ -481,7 +509,6 @@ function offsetForNode(state, gap, paddings) {
481
509
  }
482
510
  return columnXs.get(x) ?? 0;
483
511
  };
484
- nodeArray.sort(nodeByXYSize);
485
512
  for (const node of nodeArray){
486
513
  const colIdx = getColIndex(nodeX$1(node));
487
514
  const state = grid[colIdx];
@@ -497,10 +524,10 @@ function offsetForNode(state, gap, paddings) {
497
524
  paddings = countCrossColumnPaddings(grid, colIdx, y, paddings);
498
525
  while(state.yHistory.length < paddings)state.yHistory.push(y);
499
526
  }
500
- node.y = y + offsetForNode(state, gap, paddings);
527
+ node.y = y + offsetForNode(state, gap, paddings, scale);
501
528
  } else {
502
529
  state.realCount += 1;
503
- state.lastAfter = gap.after;
530
+ state.lastAfter = gap.after * scale;
504
531
  }
505
532
  maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
506
533
  }
@@ -533,21 +560,14 @@ function sortFlows(nodeArray) {
533
560
  });
534
561
  });
535
562
  }
536
- function layout(nodes, data, { priority, height, nodePadding, modeX }) {
563
+ function layout(nodes, data, { priority, height, nodePadding, nodePaddingMode, modeX }) {
537
564
  const nodeArray = [
538
565
  ...nodes.values()
539
566
  ];
540
567
  const maxX = calculateX(nodes, data, modeX ?? 'edge');
541
568
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
542
569
  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);
570
+ const maxYWithPadding = addPadding(nodeArray, nodePadding, scale, nodePaddingMode ?? 'auto');
551
571
  sortFlows(nodeArray);
552
572
  return {
553
573
  maxX,
@@ -702,6 +722,7 @@ class SankeyController extends DatasetController {
702
722
  height: orientation === 'vertical' ? this.chart.width : this.chart.height,
703
723
  modeX: this.options.modeX,
704
724
  nodePadding: nodeGaps,
725
+ nodePaddingMode: this.options.nodePaddingMode,
705
726
  priority: !!this.options.priority
706
727
  });
707
728
  this._maxX = maxX;
@@ -886,6 +907,7 @@ SankeyController.defaults = {
886
907
  dataElementType: 'flow',
887
908
  modeX: 'edge',
888
909
  nodePadding: 10,
910
+ nodePaddingMode: 'auto',
889
911
  nodeWidth: 10,
890
912
  orientation: 'horizontal',
891
913
  transitions: {
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.17.0
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,f=function(t,o){return"auto"===t?o:t}(e.position,e.autoPosition),d=Number(l.lineHeight);t.font=l.string;const u=Math.max(...r.map(o=>t.measureText(o).width)),p=d*r.length,y=function(t,o,e){const{borderWidth:r,height:n,padding:i,width:a,x:s,y:l}=o,h={align:"center",x:s+a/2,y:l+n/2};return"left"===t?(h.align="right",h.x=s-r-i):"right"===t?(h.align="left",h.x=s+a+r+i):"top"===t?h.y=l-i-e/2:"bottom"===t&&(h.y=l+n+i+e/2),h}(f,e,p);t.textAlign=y.align,t.textBaseline="middle";const g=u+2*c,x=p+2*c,m=function(t,o,e,r,n){return"left"===t?o-n:"right"===t?o-e-n:o-r/2}(y.align,y.x,u,g,c),b=1===r.length?y.y:y.y-p/2+h,w=b+(r.length-1)*d/2;void 0!==n&&a(t,n,m,w-x/2,g,x,i),t.fillStyle=s;for(let o=0;o<r.length;o++)t.fillText(r[o],y.x,b+o*d)}const l=(t,o)=>o.flow===t.flow?t.index-o.index:o.flow-t.flow;function h(t,{size:o,priority:e,column:r}){const i=new Map;for(let o=0;o<t.length;o++){const{from:e,to:r,flow:n}=t[o],a=i.get(e)??{from:[],in:0,key:e,out:0,size:0,to:[]},s=(e===r?a:i.get(r))??{from:[],in:0,key:r,out:0,size:0,to:[]};a.out+=n,a.to.push({addY:0,flow:n,index:o,key:r,node:s}),1===a.to.length&&i.set(e,a),s.in+=n,s.from.push({addY:0,flow:n,index:o,key:e,node:a}),1===s.from.length&&i.set(r,s)}return((t,o)=>{const e=n(o);for(const o of t.values())o.from.sort(l),o.to.sort(l),o.size=Math[e](o.in||o.out,o.out||o.in)})(i,o),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.priority=o[e.key])})(i,e),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.column=!0,e.x=o[e.key])})(i,r),i}const c=1e-6;function f(t){return t.x??0}function d(t){return t.y??0}const u=(t,o=new Set)=>{const e=[];for(const r of t)o.has(r.key)||(o.add(r.key),e.push(r.key,...u(r.to.map(t=>t.node),o)));return e},p=(t,o)=>{const e=o.filter(t=>0===t.from.length),r=e.map(t=>t.key),n=u(e),i=new Set(n);for(const o of t)i.has(o.from)||i.has(o.to)||(r.push(o.from),i.add(o.from)),i.add(o.to);return r},y=(t,o)=>{const e=new Set(t.filter(t=>o.has(t.from)).map(t=>t.to)),r=[...o],n=r.filter(t=>!e.has(t));return n.length?n:r.slice(0,1)};function g(t,o,e=new Set){let r=0;for(const n of t)e.has(n.node)||(e.add(n.node),r+=n.node[o].length+g(n.node[o],o,e));return r}const x=t=>(o,e)=>g(o.node[t],t)-g(e.node[t],t)||o.node[t].length-e.node[t].length;function m(t,o){if(!t.from.length)return o;t.from.sort(x("from"));for(const e of t.from){const t=e.node;r(t.y)||(t.y=o,m(t,o?o+c:0)),o=Math.max(t.y+t.out,o)}return d(t)+t.size}const b=(t,o)=>Boolean(o&&f(o)<f(t));function w(t,o){if(!t.to.length)return o;t.to.sort(x("to"));for(let e=0;e<t.to.length;e++){const n=t.to[e],i=n.node;r(i.y)||(i.y=o,w(i,o?o+c:0)),b(i,t.to[e+1]?.node)?o+=n.flow:o=Math.max(i.y+Math.max(i.in,i.out),o)}return d(t)+t.size}function M(t,o){return r(t.y)?t.y:(t.y=o,o)}function v(t,o){if(!t.length)return 0;const e=((t,o)=>{const e=[...t].sort((t,o)=>t.size-o.size),r=e[e.length-1].size,n=t.filter(t=>t.size===r),i=n[0];if(1===n.length)return i;if(n.sort((t,o)=>f(t)-f(o)),0===f(i))return i;const a=n[n.length-1];return f(a)===o?a:n[Math.floor(n.length/2)]})(t,o);return e.y=0,m(e,0),w(e,0),function(t,o){const e=t.filter(t=>0===t.x),n=t.filter(t=>t.x===o),i=e.filter(t=>!r(t.y)),a=n.filter(t=>!r(t.y)),s=t.filter(t=>f(t)>0&&f(t)<o&&!r(t.y));let l=e.reduce((t,o)=>Math.max(t,d(o)+o.out||0),0)+c,h=n.reduce((t,o)=>Math.max(t,d(o)+o.in||0),0)+c,u=0;l>=h?(i.forEach(t=>{l=M(t,l),l=Math.max(l+t.out,w(t,l))}),a.forEach(t=>{h=M(t,h),h=Math.max(h+t.in,m(t,h))})):(i.forEach(t=>{l=M(t,l)}),a.forEach(t=>{h=M(t,h),h=Math.max(h+t.in,m(t,h))})),s.forEach(o=>{let e=t.filter(t=>f(t)===f(o)&&r(t.y)).reduce((t,o)=>Math.max(t,d(o)+Math.max(o.in,o.out)),0);e=M(o,e),e=Math.max(e+o.in,m(o,e)),e=Math.max(e+o.out,w(o,e)),u=Math.max(u,e)}),Math.max(l,h,u)}(t,o),((t,o)=>{let e=0;for(let r=0;r<=o;r++){const o=t.filter(t=>f(t)===r).sort((t,o)=>d(t)-d(o));let n=0;for(const t of o)d(t)<n&&(t.y=n),n=d(t)+t.size;e=Math.max(e,n)}return e})(t,o)}const k=(t,o)=>f(t)!==f(o)?f(t)-f(o):d(t)===d(o)?t.size-o.size:d(t)-d(o);function C(t,o,e,r){let n=r;for(let r=0;r<o;r++){const o=t[r].yHistory;for(let t=0;t<o.length&&!(o[t]>e);t++)n=Math.max(t+1,n)}return n}function P(t,o,e){const r=t.realCount,n=r>0?Math.max(t.lastAfter,o.before):0,i=t.realCumOffset+n,a=e-r;return t.realCount=r+1,t.realCumOffset=i,t.lastAfter=o.after,i+a*o.before}function _(t,o,{priority:e,height:n,nodePadding:i,modeX:a}){const s=[...t.values()],l=function(t,o,e){const n=o.filter(t=>t.from!==t.to),i=[...t.keys()],a=[...t.values()],s=new Set(i);let l=0;for(;s.size;){const e=0===l?p(o,a):y(n,s);if(!e.length)throw new Error("Fatal error: Unable to place nodes to columns. Please report this issue.");for(const o of e){const e=t.get(o);e&&!r(e.x)&&(e.x=l),s.delete(o)}s.size&&l++}const h=a.reduce((t,o)=>Math.max(t,f(o)),0);if("edge"===e){const e=new Set(o.map(t=>t.from));i.filter(t=>!e.has(t)).forEach(o=>{const e=t.get(o);e&&!e.column&&(e.x=h)})}return h}(t,o,a??"edge"),h=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const i=t.filter(t=>f(t)===n).sort((t,o)=>(t.priority??0)-(o.priority??0));if(i.length){const o=t.reduce((t,o)=>f(o)>n?Math.min(t,f(o)):t,1/0);r=i[0].to.filter(t=>f(t.node)>o).reduce((t,o)=>t+o.flow,0)||0}for(const t of i)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(s,l):v(s,l),c=h/n,u=new Map;for(const[t,o]of i)u.set(t,{after:o.after*c,before:o.before*c});const g=function(t,o){let e=0;const r=new Map,n=[],i=t=>{if(!r.has(t)){const o=n.length;return r.set(t,o),n.push({lastAfter:0,realCount:0,realCumOffset:0,yHistory:[]}),o}return r.get(t)??0};t.sort(k);for(const r of t){const t=i(f(r)),a=n[t],s=o.get(r.key)??{after:0,before:0},l=d(r);if(l){a.yHistory.push(l);let o=a.yHistory.length;if(r.in)for(o=C(n,t,l,o);a.yHistory.length<o;)a.yHistory.push(l);r.y=l+P(a,s,o)}else a.realCount+=1,a.lastAfter=s.after;e=Math.max(e,d(r)+Math.max(r.in,r.out))}return e}(s,u);return function(t){t.forEach(t=>{const o=t.size,e=o<t.in,r=o<t.out;let n=0,i=t.from.length;t.from.sort((t,o)=>d(t.node)+t.node.out/2-(d(o.node)+o.node.out/2)).forEach((t,r)=>{e?t.addY=r*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)}),n=0,i=t.to.length,t.to.sort((t,o)=>d(t.node)+t.node.in/2-(d(o.node)+o.node.in/2)).forEach((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)})})}(s),{maxX:l,maxY:g}}function T(t){return t.x??0}function S(t){return t.y??0}function z(t,o){return Math[o](t.in||t.out,t.out||t.in)}function F(t,o,e,r){return"vertical"===r?o<(e.top+e.bottom)/2?"bottom":"top":t<(e.left+e.right)/2?"right":"left"}function E(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}function O(t,o,e,r,n,i,a,s,l){return"vertical"===l?{_custom:{flow:n,from:t,height:a.parse(n,i),to:o,x:a.parse(r,i),y:s.parse(T(o),i)},x:a.parse(e,i),y:s.parse(T(t),i)}:{_custom:{flow:n,from:t,height:s.parse(n,i),to:o,x:a.parse(T(o),i),y:s.parse(r,i)},x:a.parse(T(t),i),y:s.parse(e,i)}}function R(t,o,e,r,n,i,a,s){const l=t._custom,h=o.getPixelForValue(t.x),c=e.getPixelForValue(t.y);return"vertical"===s?{flow:l.flow,from:l.from,height:0,to:l.to,width:Math.abs(o.getPixelForValue(t.x+l.height)-h),x:h,x2:o.getPixelForValue(l.x),y:W(e,t.y,r,i)+n+a,y2:W(e,l.y,r,i)-a}:{flow:l.flow,from:l.from,height:Math.abs(e.getPixelForValue(t.y+l.height)-c),to:l.to,width:0,x:W(o,t.x,r,i)+n+a,x2:W(o,l.x,r,i)-a,y:c,y2:e.getPixelForValue(l.y)}}function W(t,o,e,r){const n=t.getPixelForValue(o);return e?n-o/e*r:n}function X(t,o,e){const r="vertical"===o?e.height-e.chartArea.bottom:e.width-e.chartArea.right;return Math.max(0,t+3-r)}function Y(t,o,e,r,n,i,a,s){if("vertical"===s){const s=e.getPixelForValue(S(t));return{height:i,width:Math.abs(e.getPixelForValue(S(t)+o)-s),x:s,y:W(r,T(t),n,a)}}const l=r.getPixelForValue(S(t));return{height:Math.abs(r.getPixelForValue(S(t)+o)-l),width:i,x:W(e,T(t),n,a),y:l}}function j(t,o){const e=i(t??10,o)??10;return"number"==typeof e?{after:e,before:e}:{after:e.after??10,before:e.before??10}}function A(t,o){const{backgroundColor:e,borderRadius:r=0,color:n,display:a,font:s,padding:l=4,position:h}=t.nodeLabels??{};return{backgroundColor:i(e,o),borderRadius:r,color:i(n,o)??t.color??"black",display:i(a,o)??!0,font:s,padding:l,position:i(h,o)??"auto"}}class V extends o.DatasetController{parseObjectData(t,o,e,r){const n=((t,o)=>{const{from:e="from",to:r="to",flow:n="flow"}=o;return t.map(({[e]:t,[r]:o,[n]:i})=>({flow:i,from:t,to:o}))})(o,this.options.parsing),{xScale:i,yScale:a}=t,s=[],l=h(n,this.options),c=this.options.orientation??"horizontal";this._nodes=l;const f=new Map;for(const t of l.values())f.set(t.key,j(this.options.nodePadding,t));const{maxX:d,maxY:u}=_(l,n,{height:"vertical"===c?this.chart.width:this.chart.height,modeX:this.options.modeX,nodePadding:f,priority:!!this.options.priority});if(this._maxX=d,this._maxY=u,!i||!a)return[];for(let t=0,o=n.length;t<o;++t){const o=n[t],e=l.get(o.from),r=l.get(o.to);if(!e||!r)continue;const h=S(e)+E(e.to,o.to,t),f=S(r)+E(r.from,o.from,t);s.push(O(e,r,h,f,o.flow,t,i,a,c))}return s.slice(e,e+r)}getMinMax(t){return{max:t===("vertical"===this.options.orientation?this._cachedMeta.yScale:this._cachedMeta.xScale)?this._maxX:this._maxY,min:0}}update(t){const{data:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,o,e,r){const{xScale:n,yScale:i}=this._cachedMeta;if(!n||!i)return;const a=this.resolveDataElementOptions(o,r),s=this.getSharedOptions(a),{borderWidth:l,nodeWidth:h=10,orientation:c="horizontal"}=this.options,f=X(h,c,this.chart),d=l?l/2+.5:0;for(let a=o;a<o+e;a++){const o=this.getParsed(a);this.updateElement(t[a],a,{options:this.resolveDataElementOptions(a,r),progress:"reset"===r?0:1,...R(o,n,i,this._maxX,h,f,d,c)},r)}s&&this.updateSharedOptions(s,r,a)}_drawLabels(){const t=this.chart.ctx,r=this.options,i=this._nodes||new Map,a=n(r.size),l=r.labels,{borderWidth:h=1,nodeWidth:c=10,orientation:f="horizontal"}=r,d=X(c,f,this.chart),u=r.font??this.chart.options.font??o.Chart.defaults.font,{xScale:p,yScale:y}=this._cachedMeta;if(!p||!y)return;t.save();const g=this.chart.chartArea;for(const o of i.values()){const n=z(o,a),{height:i,width:x,x:m,y:b}=Y(o,n,p,y,this._maxX,c,d,f),w=l?.[o.key]??o.key,M=A(r,o);if(M.display){const o=e.toFont(M.font??u);s(t,w,{autoPosition:F(m,b,g,f),backgroundColor:M.backgroundColor,borderRadius:M.borderRadius,borderWidth:h,color:M.color,font:o,height:i,lineOffset:e.valueOrDefault(r.padding,o.lineHeight/2),padding:M.padding,position:M.position,width:x,x:m,y:b})}}t.restore()}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:r=0,nodeWidth:i=10,orientation:a="horizontal",size:s}=this.options,l=X(i,a,this.chart),h=n(s),{xScale:c,yScale:f}=this._cachedMeta;t.save(),e&&r&&(t.strokeStyle=e,t.lineWidth=r);for(const e of o.values()){if(t.fillStyle=e.color??"black",!c||!f)return;const o=Math[h](e.in||e.out,e.out||e.in),{height:n,width:s,x:d,y:u}=Y(e,o,c,f,this._maxX,i,l,a);r&&t.strokeRect(d,u,s,n),t.fillRect(d,u,s,n)}t.restore()}draw(){const t=this.chart.ctx,o=this.getMeta().data??[],e=[];for(let t=0,r=o.length;t<r;++t){const r=o[t];r.from&&r.to&&(r.from.color=r.options.colorFrom,r.to.color=r.options.colorTo,r.active&&e.push(r))}for(const t of e)t.from&&t.to&&(t.from.color=t.options.colorFrom,t.to.color=t.options.colorTo);this._drawNodes();for(let e=0,r=o.length;e<r;++e)o[e].draw(t);this._drawLabels()}constructor(...t){super(...t),this._nodes=new Map,this._maxX=0,this._maxY=0}}V.id="sankey",V.descriptors={_indexable:!1,_scriptable:t=>"nodePadding"!==t,nodeLabels:{_indexable:!1,_scriptable:!1}},V.defaults={animations:{colors:{properties:["colorFrom","colorTo"],type:"color"},numbers:{properties:["x","y","x2","y2","height","width"],type:"number"},progress:{delay:t=>"data"===t.type?500*t.parsed["vertical"===t.dataset.orientation?"y":"x"]+20*t.dataIndex:void 0,duration:t=>"data"===t.type?200*(t.parsed._custom["vertical"===t.dataset.orientation?"y":"x"]-t.parsed["vertical"===t.dataset.orientation?"y":"x"]):void 0,easing:"linear"}},borderColor:"black",borderWidth:1,color:"black",dataElementType:"flow",modeX:"edge",nodePadding:10,nodeWidth:10,orientation:"horizontal",transitions:{hide:{animations:{colors:{properties:["colorFrom","colorTo"],to:"transparent",type:"color"}}},resize:{animations:{progress:{delay:0,duration:0}}},show:{animations:{colors:{from:"transparent",properties:["colorFrom","colorTo"],type:"color"}}}}},V.overrides={datasets:{clip:!1,parsing:{flow:"flow",from:"from",to:"to"}},interaction:{intersect:!0,mode:"nearest"},layout:{padding:{bottom:3,left:3,right:13,top:3}},plugins:{legend:{display:!1},tooltip:{callbacks:{label(t){const o=t.parsed._custom;return`${o.from.key} -> ${o.to.key}: ${o.flow}`},title:()=>""}}},scales:{x:{bounds:"data",display:!1,min:0,offset:!1,type:"linear"},y:{bounds:"data",display:!1,min:0,offset:!1,reverse:!0,type:"linear"}}};const H=(t,o,e,r,n)=>"vertical"===n?o<r?{cp1:{x:t,y:o+(r-o)/3*2},cp2:{x:e,y:o+(r-o)/3}}:{cp1:{x:0,y:o-(o-r)/3},cp2:{x:0,y:r+(o-r)/3}}:t<e?{cp1:{x:t+(e-t)/3*2,y:o},cp2:{x:t+(e-t)/3,y:r}}:{cp1:{x:t-(t-e)/3,y:0},cp2:{x:e+(t-e)/3,y:0}},L=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),q=(t,o)=>e.color(t).alpha(o).rgbString(),D=(t,o)=>"string"==typeof t?q(t,o):t,$=t=>"string"==typeof t?e.getHoverColor(t):t;class G extends o.Element{draw(t){const{x:r,x2:n,y:i,y2:a,height:l,progress:h,width:c}=this,f=this.options.orientation,d=H(r,i,n,a,f),u={height:l,width:c,x:r,x2:n,y:i,y2:a};if(0===h)return;t.save(),h<1&&function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},s,l){t.beginPath(),"vertical"===l?t.rect(Math.min(r,n),i,Math.abs(n-r)+e+1,(a-i)*s+1):t.rect(r,Math.min(i,a),(n-r)*s+1,Math.abs(a-i)+o+1),t.clip()}(t,u,h,f),function(t,{x:o,x2:e,y:r,y2:n,options:i}){let a="black";null!==i.flowColor?a=i.flowColor:"from"===i.colorMode?a=D(i.colorFrom,i.alpha):"to"===i.colorMode?a=D(i.colorTo,i.alpha):"string"==typeof i.colorFrom&&"string"==typeof i.colorTo&&(a="vertical"===i.orientation?t.createLinearGradient(0,r,0,n):t.createLinearGradient(o,0,e,0),a.addColorStop(0,q(i.colorFrom,i.alpha)),a.addColorStop(1,q(i.colorTo,i.alpha))),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},{cp1:s,cp2:l},h){t.beginPath(),t.moveTo(r,i),t.bezierCurveTo(s.x,s.y,l.x,l.y,n,a),"vertical"===h?(t.lineTo(n+e,a),t.bezierCurveTo(l.x+e,l.y,s.x+e,s.y,r+e,i)):(t.lineTo(n,a+o),t.bezierCurveTo(l.x,l.y+o,s.x,s.y+o,r,i+o)),t.lineTo(r,i),t.stroke(),t.closePath(),t.fill()}(t,u,d,f);const p=this.options.flowLabels;if(p.display){const r=e.toFont(p.font??o.Chart.defaults.font),n=function({height:t,width:o,x:e,x2:r,y:n,y2:i},a){return"vertical"===a?{height:i-n,width:Math.abs(r-e)+o,x:Math.min(e,r),y:n}:{height:Math.abs(i-n)+t,width:r-e,x:e,y:Math.min(n,i)}}(u,f);s(t,`${this.flow}`,{autoPosition:"center",backgroundColor:p.backgroundColor,borderRadius:p.borderRadius,borderWidth:0,color:p.color,font:r,height:n.height,lineOffset:r.lineHeight/2,padding:p.padding,position:p.position,width:n.width,x:n.x,y:n.y})}t.restore()}inRange(t,o,e){const{x:r,y:n,x2:i,y2:a,height:s,width:l}=this.getProps(["x","y","x2","y2","height","width"],e),h="vertical"===this.options.orientation;if(h?o<n||o>a:t<r||t>i)return!1;const{cp1:c,cp2:f}=H(r,n,i,a,this.options.orientation),d=h?(o-n)/(a-n):(t-r)/(i-r),u={x:i,y:a},p=L({x:r,y:n},c,d),y=L(c,f,d),g=L(f,u,d),x=L(p,y,d),m=L(y,g,d),b=L(x,m,d);return h?t>=b.x&&t<=b.x+l:o>=b.y&&o<=b.y+s}inXRange(t,o){const{x:e,x2:r,width:n}=this.getProps(["x","x2","width"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?n:0);return t>=i&&t<=a}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?0:n);return t>=i&&t<=a}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:i,width:a}=this.getProps(["x","y","x2","y2","height","width"],t),s="vertical"===this.options.orientation;return{x:(o+r+(s?a:0))/2,y:(e+n+(s?0:i))/2}}tooltipPosition(t=!1){return this.getCenterPoint(t)}getRange(t){const o="vertical"===this.options.orientation;return"x"===t?o?this.width/2:0:o?0:this.height/2}constructor(t){super(),this.flow=0,this.x2=0,this.y2=0,this.width=0,this.height=0,this.progress=1,t&&Object.assign(this,t)}}G.id="flow",G.defaults={alpha:.5,colorFrom:"red",colorMode:"gradient",colorTo:"green",flowColor:null,flowLabels:{borderRadius:0,color:"black",display:!1,padding:4,position:"center"},hoverColorFrom:(t,o)=>$(o.colorFrom),hoverColorTo:(t,o)=>$(o.colorTo),orientation:"horizontal"},G.descriptors={_scriptable:!0,flowLabels:{_scriptable:!0}},o.Chart.register(V,G),t.Flow=G,t.SankeyController=V});
7
+ !function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["exports","chart.js","chart.js/helpers"],o):o((t="undefined"!=typeof globalThis?globalThis:t||self)["chartjs-chart-sankey"]={},t.Chart,t.Chart.helpers)}(this,function(t,o,e){"use strict";const 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});
@@ -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: {
@@ -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: {
@@ -17,10 +17,11 @@ export declare function calculateY(nodeArray: SankeyNode[], maxX: number): numbe
17
17
  export declare function calculateYUsingPriority(nodeArray: SankeyNode[], maxX: number): number;
18
18
  type PaddableNode = Pick<SankeyNode, 'in' | 'key' | 'out' | 'size' | 'x' | 'y'>;
19
19
  type NodeGap = Required<SankeyNodeGap>;
20
+ export type NodePaddingMode = 'auto' | 'even';
20
21
  /**
21
22
  * @return {number} maxY
22
23
  */
23
- export declare function addPadding(nodeArray: PaddableNode[], gaps: Map<string, NodeGap>): number;
24
+ export declare function addPadding(nodeArray: PaddableNode[], gaps: Map<string, NodeGap>, scale?: number, mode?: NodePaddingMode): number;
24
25
  export declare function sortFlows(nodeArray: SankeyNode[]): void;
25
26
  interface LayoutOptions {
26
27
  /** use node priority when sorting nodes vertically */
@@ -29,10 +30,12 @@ interface LayoutOptions {
29
30
  height: number;
30
31
  /** vertical before/after gap per node, in CSS pixels */
31
32
  nodePadding: Map<string, NodeGap>;
33
+ /** how nodePadding gaps are distributed within a column, defaults to 'auto' */
34
+ nodePaddingMode: SankeyControllerDatasetOptions['nodePaddingMode'];
32
35
  /** layout mode in x-direction */
33
36
  modeX: SankeyControllerDatasetOptions['modeX'];
34
37
  }
35
- 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): {
36
39
  maxY: number;
37
40
  maxX: number;
38
41
  };
package/dist/types.d.cts CHANGED
@@ -62,6 +62,7 @@ export interface SankeyControllerDatasetOptions extends Omit<ControllerDatasetOp
62
62
  modeX?: 'edge' | 'even';
63
63
  nodeLabels?: SankeyControllerDatasetNodeLabelsOptions;
64
64
  nodePadding?: SankeyNodeOption<number | SankeyNodeGap>;
65
+ nodePaddingMode?: 'auto' | 'even';
65
66
  nodeWidth?: number;
66
67
  orientation?: SankeyOrientation;
67
68
  padding?: number;
package/dist/types.d.ts CHANGED
@@ -61,6 +61,7 @@ export interface SankeyControllerDatasetOptions extends Omit<ControllerDatasetOp
61
61
  modeX?: 'edge' | 'even';
62
62
  nodeLabels?: SankeyControllerDatasetNodeLabelsOptions;
63
63
  nodePadding?: SankeyNodeOption<number | SankeyNodeGap>;
64
+ nodePaddingMode?: 'auto' | 'even';
64
65
  nodeWidth?: number;
65
66
  orientation?: SankeyOrientation;
66
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.17.0"
107
+ "version": "0.18.0"
108
108
  }