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