chartjs-chart-sankey 0.9.1 → 0.10.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/README.md +31 -0
- package/dist/chartjs-chart-sankey.esm.js +36 -47
- package/dist/chartjs-chart-sankey.js +36 -47
- package/dist/chartjs-chart-sankey.min.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,37 @@ const chart = new Chart(ctx, {
|
|
|
94
94
|
});
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
### Custom data structure
|
|
98
|
+
|
|
99
|
+
Custom data structure can be used by specifying the custom data keys in `options.parsing`.
|
|
100
|
+
For example:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
const chart = new Chart(ctx, {
|
|
104
|
+
type: 'sankey',
|
|
105
|
+
data: {
|
|
106
|
+
datasets: [
|
|
107
|
+
{
|
|
108
|
+
data: [
|
|
109
|
+
{source: 'a', destination: 'b', value: 20},
|
|
110
|
+
{source: 'c', destination: 'd', value: 10},
|
|
111
|
+
{source: 'c', destination: 'e', value: 5},
|
|
112
|
+
],
|
|
113
|
+
colorFrom: 'red',
|
|
114
|
+
colorTo: 'green'
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
options: {
|
|
119
|
+
parsing: {
|
|
120
|
+
from: 'source',
|
|
121
|
+
to: 'destination',
|
|
122
|
+
flow: 'value'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
97
128
|
## Example
|
|
98
129
|
|
|
99
130
|

|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.10.0
|
|
3
3
|
* https://github.com/kurkle/chartjs-chart-sankey#readme
|
|
4
4
|
* (c) 2022 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
@@ -428,16 +428,13 @@ class SankeyController extends DatasetController {
|
|
|
428
428
|
* @return {Array<SankeyParsedData>}
|
|
429
429
|
*/
|
|
430
430
|
parseObjectData(meta, data, start, count) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return [];
|
|
434
|
-
}
|
|
435
|
-
const me = this;
|
|
431
|
+
const {from: fromKey = 'from', to: toKey = 'to', flow: flowKey = 'flow'} = this.options.parsing;
|
|
432
|
+
const sankeyData = data.map(({[fromKey]: from, [toKey]: to, [flowKey]: flow}) => ({from, to, flow}));
|
|
436
433
|
const {xScale, yScale} = meta;
|
|
437
434
|
const parsed = []; /* Array<SankeyParsedData> */
|
|
438
|
-
const nodes =
|
|
435
|
+
const nodes = this._nodes = buildNodesFromRawData(sankeyData);
|
|
439
436
|
/* getDataset() => SankeyControllerDatasetOptions */
|
|
440
|
-
const {column, priority, size} =
|
|
437
|
+
const {column, priority, size} = this.getDataset();
|
|
441
438
|
if (priority) {
|
|
442
439
|
for (const node of nodes.values()) {
|
|
443
440
|
if (node.key in priority) {
|
|
@@ -454,13 +451,13 @@ class SankeyController extends DatasetController {
|
|
|
454
451
|
}
|
|
455
452
|
}
|
|
456
453
|
|
|
457
|
-
const {maxX, maxY} = layout(nodes,
|
|
454
|
+
const {maxX, maxY} = layout(nodes, sankeyData, !!priority, validateSizeValue(size));
|
|
458
455
|
|
|
459
|
-
|
|
460
|
-
|
|
456
|
+
this._maxX = maxX;
|
|
457
|
+
this._maxY = maxY;
|
|
461
458
|
|
|
462
|
-
for (let i = 0, ilen =
|
|
463
|
-
const dataPoint =
|
|
459
|
+
for (let i = 0, ilen = sankeyData.length; i < ilen; ++i) {
|
|
460
|
+
const dataPoint = sankeyData[i];
|
|
464
461
|
const from = nodes.get(dataPoint.from);
|
|
465
462
|
const to = nodes.get(dataPoint.to);
|
|
466
463
|
const fromY = from.y + getAddY(from.to, dataPoint.to, i);
|
|
@@ -481,18 +478,16 @@ class SankeyController extends DatasetController {
|
|
|
481
478
|
}
|
|
482
479
|
|
|
483
480
|
getMinMax(scale) {
|
|
484
|
-
const me = this;
|
|
485
481
|
return {
|
|
486
482
|
min: 0,
|
|
487
|
-
max: scale === this._cachedMeta.xScale ? this._maxX :
|
|
483
|
+
max: scale === this._cachedMeta.xScale ? this._maxX : this._maxY
|
|
488
484
|
};
|
|
489
485
|
}
|
|
490
486
|
|
|
491
487
|
update(mode) {
|
|
492
|
-
const
|
|
493
|
-
const meta = me._cachedMeta;
|
|
488
|
+
const {data} = this._cachedMeta;
|
|
494
489
|
|
|
495
|
-
|
|
490
|
+
this.updateElements(data, 0, data.length, mode);
|
|
496
491
|
}
|
|
497
492
|
|
|
498
493
|
/**
|
|
@@ -502,20 +497,19 @@ class SankeyController extends DatasetController {
|
|
|
502
497
|
* @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
|
|
503
498
|
*/
|
|
504
499
|
updateElements(elems, start, count, mode) {
|
|
505
|
-
const
|
|
506
|
-
const
|
|
507
|
-
const
|
|
508
|
-
const
|
|
509
|
-
const dataset = me.getDataset();
|
|
500
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
501
|
+
const firstOpts = this.resolveDataElementOptions(start, mode);
|
|
502
|
+
const sharedOptions = this.getSharedOptions(mode, elems[start], firstOpts);
|
|
503
|
+
const dataset = this.getDataset();
|
|
510
504
|
const borderWidth = valueOrDefault(dataset.borderWidth, 1) / 2 + 0.5;
|
|
511
505
|
const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
|
|
512
506
|
|
|
513
507
|
for (let i = start; i < start + count; i++) {
|
|
514
508
|
/* getParsed(idx: number) => SankeyParsedData */
|
|
515
|
-
const parsed =
|
|
509
|
+
const parsed = this.getParsed(i);
|
|
516
510
|
const custom = parsed._custom;
|
|
517
511
|
const y = yScale.getPixelForValue(parsed.y);
|
|
518
|
-
|
|
512
|
+
this.updateElement(
|
|
519
513
|
elems[i],
|
|
520
514
|
i,
|
|
521
515
|
{
|
|
@@ -527,27 +521,26 @@ class SankeyController extends DatasetController {
|
|
|
527
521
|
to: custom.to,
|
|
528
522
|
progress: mode === 'reset' ? 0 : 1,
|
|
529
523
|
height: Math.abs(yScale.getPixelForValue(parsed.y + custom.height) - y),
|
|
530
|
-
options:
|
|
524
|
+
options: this.resolveDataElementOptions(i, mode)
|
|
531
525
|
},
|
|
532
526
|
mode);
|
|
533
527
|
}
|
|
534
528
|
|
|
535
|
-
|
|
529
|
+
this.updateSharedOptions(sharedOptions, mode);
|
|
536
530
|
}
|
|
537
531
|
|
|
538
532
|
_drawLabels() {
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
|
|
533
|
+
const ctx = this._ctx;
|
|
534
|
+
const nodes = this._nodes || new Map();
|
|
535
|
+
const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
|
|
543
536
|
const size = validateSizeValue(dataset.size);
|
|
544
537
|
const borderWidth = valueOrDefault(dataset.borderWidth, 1);
|
|
545
538
|
const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
|
|
546
539
|
const labels = dataset.labels;
|
|
547
|
-
const {xScale, yScale} =
|
|
540
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
548
541
|
|
|
549
542
|
ctx.save();
|
|
550
|
-
const chartArea =
|
|
543
|
+
const chartArea = this.chart.chartArea;
|
|
551
544
|
for (const node of nodes.values()) {
|
|
552
545
|
const x = xScale.getPixelForValue(node.x);
|
|
553
546
|
const y = yScale.getPixelForValue(node.y);
|
|
@@ -579,13 +572,12 @@ class SankeyController extends DatasetController {
|
|
|
579
572
|
* @private
|
|
580
573
|
*/
|
|
581
574
|
_drawLabel(label, y, height, ctx, textX) {
|
|
582
|
-
const
|
|
583
|
-
const font = toFont(me.options.font, me.chart.options.font);
|
|
575
|
+
const font = toFont(this.options.font, this.chart.options.font);
|
|
584
576
|
const lines = isNullOrUndef(label) ? [] : toTextLines(label);
|
|
585
577
|
const linesLength = lines.length;
|
|
586
578
|
const middle = y + height / 2;
|
|
587
579
|
const textHeight = font.lineHeight;
|
|
588
|
-
const padding = valueOrDefault(
|
|
580
|
+
const padding = valueOrDefault(this.options.padding, textHeight / 2);
|
|
589
581
|
|
|
590
582
|
ctx.font = font.string;
|
|
591
583
|
|
|
@@ -600,12 +592,11 @@ class SankeyController extends DatasetController {
|
|
|
600
592
|
}
|
|
601
593
|
|
|
602
594
|
_drawNodes() {
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
|
|
595
|
+
const ctx = this._ctx;
|
|
596
|
+
const nodes = this._nodes || new Map();
|
|
597
|
+
const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
|
|
607
598
|
const size = validateSizeValue(dataset.size);
|
|
608
|
-
const {xScale, yScale} =
|
|
599
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
609
600
|
const borderWidth = valueOrDefault(dataset.borderWidth, 1);
|
|
610
601
|
const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
|
|
611
602
|
|
|
@@ -632,9 +623,8 @@ class SankeyController extends DatasetController {
|
|
|
632
623
|
* That's where the drawing process happens
|
|
633
624
|
*/
|
|
634
625
|
draw() {
|
|
635
|
-
const
|
|
636
|
-
const
|
|
637
|
-
const data = me.getMeta().data || []; /* Array<Flow> */
|
|
626
|
+
const ctx = this._ctx;
|
|
627
|
+
const data = this.getMeta().data || []; /* Array<Flow> */
|
|
638
628
|
|
|
639
629
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
640
630
|
const flow = data[i]; /* Flow at index i */
|
|
@@ -643,7 +633,7 @@ class SankeyController extends DatasetController {
|
|
|
643
633
|
}
|
|
644
634
|
|
|
645
635
|
/* draw SankeyNodes on the canvas */
|
|
646
|
-
|
|
636
|
+
this._drawNodes();
|
|
647
637
|
|
|
648
638
|
/* draw Flow elements on the canvas */
|
|
649
639
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
@@ -651,7 +641,7 @@ class SankeyController extends DatasetController {
|
|
|
651
641
|
}
|
|
652
642
|
|
|
653
643
|
/* draw labels (for SankeyNodes) on the canvas */
|
|
654
|
-
|
|
644
|
+
this._drawLabels();
|
|
655
645
|
}
|
|
656
646
|
}
|
|
657
647
|
|
|
@@ -702,7 +692,6 @@ SankeyController.overrides = {
|
|
|
702
692
|
intersect: true
|
|
703
693
|
},
|
|
704
694
|
datasets: {
|
|
705
|
-
color: () => '#efefef',
|
|
706
695
|
clip: false,
|
|
707
696
|
parsing: true
|
|
708
697
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.10.0
|
|
3
3
|
* https://github.com/kurkle/chartjs-chart-sankey#readme
|
|
4
4
|
* (c) 2022 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
@@ -431,16 +431,13 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
431
431
|
* @return {Array<SankeyParsedData>}
|
|
432
432
|
*/
|
|
433
433
|
parseObjectData(meta, data, start, count) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return [];
|
|
437
|
-
}
|
|
438
|
-
const me = this;
|
|
434
|
+
const {from: fromKey = 'from', to: toKey = 'to', flow: flowKey = 'flow'} = this.options.parsing;
|
|
435
|
+
const sankeyData = data.map(({[fromKey]: from, [toKey]: to, [flowKey]: flow}) => ({from, to, flow}));
|
|
439
436
|
const {xScale, yScale} = meta;
|
|
440
437
|
const parsed = []; /* Array<SankeyParsedData> */
|
|
441
|
-
const nodes =
|
|
438
|
+
const nodes = this._nodes = buildNodesFromRawData(sankeyData);
|
|
442
439
|
/* getDataset() => SankeyControllerDatasetOptions */
|
|
443
|
-
const {column, priority, size} =
|
|
440
|
+
const {column, priority, size} = this.getDataset();
|
|
444
441
|
if (priority) {
|
|
445
442
|
for (const node of nodes.values()) {
|
|
446
443
|
if (node.key in priority) {
|
|
@@ -457,13 +454,13 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
457
454
|
}
|
|
458
455
|
}
|
|
459
456
|
|
|
460
|
-
const {maxX, maxY} = layout(nodes,
|
|
457
|
+
const {maxX, maxY} = layout(nodes, sankeyData, !!priority, validateSizeValue(size));
|
|
461
458
|
|
|
462
|
-
|
|
463
|
-
|
|
459
|
+
this._maxX = maxX;
|
|
460
|
+
this._maxY = maxY;
|
|
464
461
|
|
|
465
|
-
for (let i = 0, ilen =
|
|
466
|
-
const dataPoint =
|
|
462
|
+
for (let i = 0, ilen = sankeyData.length; i < ilen; ++i) {
|
|
463
|
+
const dataPoint = sankeyData[i];
|
|
467
464
|
const from = nodes.get(dataPoint.from);
|
|
468
465
|
const to = nodes.get(dataPoint.to);
|
|
469
466
|
const fromY = from.y + getAddY(from.to, dataPoint.to, i);
|
|
@@ -484,18 +481,16 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
484
481
|
}
|
|
485
482
|
|
|
486
483
|
getMinMax(scale) {
|
|
487
|
-
const me = this;
|
|
488
484
|
return {
|
|
489
485
|
min: 0,
|
|
490
|
-
max: scale === this._cachedMeta.xScale ? this._maxX :
|
|
486
|
+
max: scale === this._cachedMeta.xScale ? this._maxX : this._maxY
|
|
491
487
|
};
|
|
492
488
|
}
|
|
493
489
|
|
|
494
490
|
update(mode) {
|
|
495
|
-
const
|
|
496
|
-
const meta = me._cachedMeta;
|
|
491
|
+
const {data} = this._cachedMeta;
|
|
497
492
|
|
|
498
|
-
|
|
493
|
+
this.updateElements(data, 0, data.length, mode);
|
|
499
494
|
}
|
|
500
495
|
|
|
501
496
|
/**
|
|
@@ -505,20 +500,19 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
505
500
|
* @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
|
|
506
501
|
*/
|
|
507
502
|
updateElements(elems, start, count, mode) {
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
const
|
|
511
|
-
const
|
|
512
|
-
const dataset = me.getDataset();
|
|
503
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
504
|
+
const firstOpts = this.resolveDataElementOptions(start, mode);
|
|
505
|
+
const sharedOptions = this.getSharedOptions(mode, elems[start], firstOpts);
|
|
506
|
+
const dataset = this.getDataset();
|
|
513
507
|
const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1) / 2 + 0.5;
|
|
514
508
|
const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
|
|
515
509
|
|
|
516
510
|
for (let i = start; i < start + count; i++) {
|
|
517
511
|
/* getParsed(idx: number) => SankeyParsedData */
|
|
518
|
-
const parsed =
|
|
512
|
+
const parsed = this.getParsed(i);
|
|
519
513
|
const custom = parsed._custom;
|
|
520
514
|
const y = yScale.getPixelForValue(parsed.y);
|
|
521
|
-
|
|
515
|
+
this.updateElement(
|
|
522
516
|
elems[i],
|
|
523
517
|
i,
|
|
524
518
|
{
|
|
@@ -530,27 +524,26 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
530
524
|
to: custom.to,
|
|
531
525
|
progress: mode === 'reset' ? 0 : 1,
|
|
532
526
|
height: Math.abs(yScale.getPixelForValue(parsed.y + custom.height) - y),
|
|
533
|
-
options:
|
|
527
|
+
options: this.resolveDataElementOptions(i, mode)
|
|
534
528
|
},
|
|
535
529
|
mode);
|
|
536
530
|
}
|
|
537
531
|
|
|
538
|
-
|
|
532
|
+
this.updateSharedOptions(sharedOptions, mode);
|
|
539
533
|
}
|
|
540
534
|
|
|
541
535
|
_drawLabels() {
|
|
542
|
-
const
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
|
|
536
|
+
const ctx = this._ctx;
|
|
537
|
+
const nodes = this._nodes || new Map();
|
|
538
|
+
const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
|
|
546
539
|
const size = validateSizeValue(dataset.size);
|
|
547
540
|
const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
|
|
548
541
|
const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
|
|
549
542
|
const labels = dataset.labels;
|
|
550
|
-
const {xScale, yScale} =
|
|
543
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
551
544
|
|
|
552
545
|
ctx.save();
|
|
553
|
-
const chartArea =
|
|
546
|
+
const chartArea = this.chart.chartArea;
|
|
554
547
|
for (const node of nodes.values()) {
|
|
555
548
|
const x = xScale.getPixelForValue(node.x);
|
|
556
549
|
const y = yScale.getPixelForValue(node.y);
|
|
@@ -582,13 +575,12 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
582
575
|
* @private
|
|
583
576
|
*/
|
|
584
577
|
_drawLabel(label, y, height, ctx, textX) {
|
|
585
|
-
const
|
|
586
|
-
const font = helpers.toFont(me.options.font, me.chart.options.font);
|
|
578
|
+
const font = helpers.toFont(this.options.font, this.chart.options.font);
|
|
587
579
|
const lines = helpers.isNullOrUndef(label) ? [] : toTextLines(label);
|
|
588
580
|
const linesLength = lines.length;
|
|
589
581
|
const middle = y + height / 2;
|
|
590
582
|
const textHeight = font.lineHeight;
|
|
591
|
-
const padding = helpers.valueOrDefault(
|
|
583
|
+
const padding = helpers.valueOrDefault(this.options.padding, textHeight / 2);
|
|
592
584
|
|
|
593
585
|
ctx.font = font.string;
|
|
594
586
|
|
|
@@ -603,12 +595,11 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
603
595
|
}
|
|
604
596
|
|
|
605
597
|
_drawNodes() {
|
|
606
|
-
const
|
|
607
|
-
const
|
|
608
|
-
const
|
|
609
|
-
const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
|
|
598
|
+
const ctx = this._ctx;
|
|
599
|
+
const nodes = this._nodes || new Map();
|
|
600
|
+
const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
|
|
610
601
|
const size = validateSizeValue(dataset.size);
|
|
611
|
-
const {xScale, yScale} =
|
|
602
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
612
603
|
const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
|
|
613
604
|
const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
|
|
614
605
|
|
|
@@ -635,9 +626,8 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
635
626
|
* That's where the drawing process happens
|
|
636
627
|
*/
|
|
637
628
|
draw() {
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
const data = me.getMeta().data || []; /* Array<Flow> */
|
|
629
|
+
const ctx = this._ctx;
|
|
630
|
+
const data = this.getMeta().data || []; /* Array<Flow> */
|
|
641
631
|
|
|
642
632
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
643
633
|
const flow = data[i]; /* Flow at index i */
|
|
@@ -646,7 +636,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
646
636
|
}
|
|
647
637
|
|
|
648
638
|
/* draw SankeyNodes on the canvas */
|
|
649
|
-
|
|
639
|
+
this._drawNodes();
|
|
650
640
|
|
|
651
641
|
/* draw Flow elements on the canvas */
|
|
652
642
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
@@ -654,7 +644,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
654
644
|
}
|
|
655
645
|
|
|
656
646
|
/* draw labels (for SankeyNodes) on the canvas */
|
|
657
|
-
|
|
647
|
+
this._drawLabels();
|
|
658
648
|
}
|
|
659
649
|
}
|
|
660
650
|
|
|
@@ -705,7 +695,6 @@ SankeyController.overrides = {
|
|
|
705
695
|
intersect: true
|
|
706
696
|
},
|
|
707
697
|
datasets: {
|
|
708
|
-
color: () => '#efefef',
|
|
709
698
|
clip: false,
|
|
710
699
|
parsing: true
|
|
711
700
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.10.0
|
|
3
3
|
* https://github.com/kurkle/chartjs-chart-sankey#readme
|
|
4
4
|
* (c) 2022 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
6
6
|
*/
|
|
7
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","chart.js/helpers"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Chart,t.Chart.helpers)}(this,(function(t,e){"use strict";function o(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const r=t=>void 0!==t;function n(t,e){const o=t.filter((t=>!e.has(t)));return o.length?o:t.slice(0,1)}const a=(t,e)=>t.x!==e.x?t.x-e.x:t.y-e.y;let i=-1;function s(t,e,o=function(){return i=i<100?i+1:0,i}()){let r=0;for(const n of t)n.node._visited!==o&&(n.node._visited=o,r+=n.node[e].length+s(n.node[e],e,o));return r}const l=t=>(e,o)=>s(e.node[t],t)-s(o.node[t],t)||e.node[t].length-o.node[t].length;function c(t,e){t.from.sort(l("from"));for(const o of t.from){const t=o.node;r(t.y)||(t.y=e,c(t,e)),e=Math.max(t.y+t.out,e)}return e}function f(t,e){t.to.sort(l("to"));for(const o of t.to){const t=o.node;r(t.y)||(t.y=e,f(t,e)),e=Math.max(t.y+t.in,e)}return e}function h(t,e){return r(t.y)?t.y:(t.y=e,e)}function d(t,e){t.sort(((t,e)=>Math.max(e.in,e.out)-Math.max(t.in,t.out)));const o=t[0];o.y=0;const n=c(o,0),a=f(o,0),i=function(t,e){const o=t.filter((t=>0===t.x)),n=t.filter((t=>t.x===e)),a=o.filter((t=>!r(t.y))),i=n.filter((t=>!r(t.y))),s=t.filter((t=>t.x>0&&t.x<e&&!r(t.y)));let l=o.reduce(((t,e)=>Math.max(t,e.y+e.out||0)),0),d=n.reduce(((t,e)=>Math.max(t,e.y+e.in||0)),0),u=0;return l>=d?(a.forEach((t=>{l=h(t,l),l=Math.max(l+t.out,f(t,l))})),i.forEach((t=>{d=h(t,d),d=Math.max(d+t.in,f(t,d))}))):(i.forEach((t=>{d=h(t,d),d=Math.max(d+t.in,f(t,d))})),a.forEach((t=>{l=h(t,l),l=Math.max(l+t.out,f(t,l))}))),s.forEach((e=>{let o=t.filter((t=>t.x===e.x&&r(t.y))).reduce(((t,e)=>Math.max(t,e.y+Math.max(e.in,e.out))),0);o=h(e,o),o=Math.max(o+e.in,c(e,o)),o=Math.max(o+e.out,f(e,o)),u=Math.max(u,o)})),Math.max(l,d,u)}(t,e);return Math.max(n,a,i)}function u(t,e,o,i){const s=[...t.values()],l=function(t,e){const o=new Set(e.map((t=>t.to))),a=new Set(e.map((t=>t.from))),i=new Set([...t.keys()]);let s=0;for(;i.size;){const a=n([...i],o);for(const e of a){const o=t.get(e);r(o.x)||(o.x=s),i.delete(e)}i.size&&(o.clear(),e.filter((t=>i.has(t.from))).forEach((t=>o.add(t.to))),s++)}return[...t.keys()].filter((t=>!a.has(t))).forEach((e=>{const o=t.get(e);o.column||(o.x=s)})),s}(t,e),c=o?function(t,e){let o=0,r=0;for(let n=0;n<=e;n++){let e=r;const a=t.filter((t=>t.x===n)).sort(((t,e)=>t.priority-e.priority));r=a[0].to.filter((t=>t.node.x>n+1)).reduce(((t,e)=>t+e.flow),0)||0;for(const t of a)t.y=e,e+=Math.max(t.out,t.in);o=Math.max(e,o)}return o}(s,l):d(s,l),f=function(t,e){let o=1,r=0,n=0,i=0;const s=[];t.sort(a);for(const a of t){if(a.y){if(0===a.x)s.push(a.y);else{for(r!==a.x&&(r=a.x,n=0),o=n+1;o<s.length&&!(s[o]>a.y);o++);n=o}a.y+=o*e,o++}i=Math.max(i,a.y+Math.max(a.in,a.out))}return i}(s,.03*c);return function(t,e){t.forEach((t=>{const o=Math[e](t.in||t.out,t.out||t.in),r=o<t.in,n=o<t.out;let a=0,i=t.from.length;t.from.sort(((t,e)=>t.node.y+t.node.out/2-(e.node.y+e.node.out/2))).forEach(((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=a,a+=t.flow)})),a=0,i=t.to.length,t.to.sort(((t,e)=>t.node.y+t.node.in/2-(e.node.y+e.node.in/2))).forEach(((t,e)=>{n?t.addY=e*(o-t.flow)/(i-1):(t.addY=a,a+=t.flow)}))}))}(s,i),{maxX:l,maxY:f}}function x(t,e,o){for(const r of t)if(r.key===e&&r.index===o)return r.addY;return 0}class y extends t.DatasetController{parseObjectData(t,e,r,n){if(0===n)return[];const a=this,{xScale:i,yScale:s}=t,l=[],c=a._nodes=function(t){const e=new Map;for(let o=0;o<t.length;o++){const{from:r,to:n,flow:a}=t[o];if(e.has(r)){const t=e.get(r);t.out+=a,t.to.push({key:n,flow:a,index:o})}else e.set(r,{key:r,in:0,out:a,from:[],to:[{key:n,flow:a,index:o}]});if(e.has(n)){const t=e.get(n);t.in+=a,t.from.push({key:r,flow:a,index:o})}else e.set(n,{key:n,in:a,out:0,from:[{key:r,flow:a,index:o}],to:[]})}const o=(t,e)=>e.flow-t.flow;return[...e.values()].forEach((t=>{t.from=t.from.sort(o),t.from.forEach((t=>{t.node=e.get(t.key)})),t.to=t.to.sort(o),t.to.forEach((t=>{t.node=e.get(t.key)}))})),e}(e),{column:f,priority:h,size:d}=a.getDataset();if(h)for(const t of c.values())t.key in h&&(t.priority=h[t.key]);if(f)for(const t of c.values())t.key in f&&(t.column=!0,t.x=f[t.key]);const{maxX:y,maxY:p}=u(c,e,!!h,o(d));a._maxX=y,a._maxY=p;for(let t=0,o=e.length;t<o;++t){const o=e[t],r=c.get(o.from),n=c.get(o.to),a=r.y+x(r.to,o.to,t),f=n.y+x(n.from,o.from,t);l.push({x:i.parse(r.x,t),y:s.parse(a,t),_custom:{from:r,to:n,x:i.parse(n.x,t),y:s.parse(f,t),height:s.parse(o.flow,t)}})}return l.slice(r,r+n)}getMinMax(t){return{min:0,max:t===this._cachedMeta.xScale?this._maxX:this._maxY}}update(t){const e=this._cachedMeta;this.updateElements(e.data,0,e.data.length,t)}updateElements(t,o,r,n){const a=this,{xScale:i,yScale:s}=a._cachedMeta,l=a.resolveDataElementOptions(o,n),c=a.getSharedOptions(n,t[o],l),f=a.getDataset(),h=e.valueOrDefault(f.borderWidth,1)/2+.5,d=e.valueOrDefault(f.nodeWidth,10);for(let e=o;e<o+r;e++){const o=a.getParsed(e),r=o._custom,l=s.getPixelForValue(o.y);a.updateElement(t[e],e,{x:i.getPixelForValue(o.x)+d+h,y:l,x2:i.getPixelForValue(r.x)-h,y2:s.getPixelForValue(r.y),from:r.from,to:r.to,progress:"reset"===n?0:1,height:Math.abs(s.getPixelForValue(o.y+r.height)-l),options:a.resolveDataElementOptions(e,n)},n)}a.updateSharedOptions(c,n)}_drawLabels(){const t=this,r=t._ctx,n=t._nodes||new Map,a=t.getDataset(),i=o(a.size),s=e.valueOrDefault(a.borderWidth,1),l=e.valueOrDefault(a.nodeWidth,10),c=a.labels,{xScale:f,yScale:h}=t._cachedMeta;r.save();const d=t.chart.chartArea;for(const t of n.values()){const e=f.getPixelForValue(t.x),o=h.getPixelForValue(t.y),n=Math[i](t.in||t.out,t.out||t.in),u=Math.abs(h.getPixelForValue(t.y+n)-o),x=c&&c[t.key]||t.key;let y=e;r.fillStyle=a.color||"black",r.textBaseline="middle",e<d.width/2?(r.textAlign="left",y+=l+s+4):(r.textAlign="right",y-=s+4),this._drawLabel(x,o,u,r,y)}r.restore()}_drawLabel(t,o,r,n,a){const i=this,s=e.toFont(i.options.font,i.chart.options.font),l=e.isNullOrUndef(t)?[]:function(t){const o=[],r=e.isArray(t)?t:e.isNullOrUndef(t)?[]:[t];for(;r.length;){const t=r.pop();"string"==typeof t?o.unshift.apply(o,t.split("\n")):Array.isArray(t)?r.push.apply(r,t):e.isNullOrUndef(r)||o.unshift(""+t)}return o}(t),c=l.length,f=o+r/2,h=s.lineHeight,d=e.valueOrDefault(i.options.padding,h/2);if(n.font=s.string,c>1){const t=f-h*c/2+d;for(let e=0;e<c;e++)n.fillText(l[e],a,t+e*h)}else n.fillText(t,a,f)}_drawNodes(){const t=this,r=t._ctx,n=t._nodes||new Map,a=t.getDataset(),i=o(a.size),{xScale:s,yScale:l}=t._cachedMeta,c=e.valueOrDefault(a.borderWidth,1),f=e.valueOrDefault(a.nodeWidth,10);r.save(),r.strokeStyle=a.borderColor||"black",r.lineWidth=c;for(const t of n.values()){r.fillStyle=t.color;const e=s.getPixelForValue(t.x),o=l.getPixelForValue(t.y),n=Math[i](t.in||t.out,t.out||t.in),a=Math.abs(l.getPixelForValue(t.y+n)-o);c&&r.strokeRect(e,o,f,a),r.fillRect(e,o,f,a)}r.restore()}draw(){const t=this,e=t._ctx,o=t.getMeta().data||[];for(let t=0,e=o.length;t<e;++t){const e=o[t];e.from.color=e.options.colorFrom,e.to.color=e.options.colorTo}t._drawNodes();for(let t=0,r=o.length;t<r;++t)o[t].draw(e);t._drawLabels()}}y.id="sankey",y.defaults={dataElementType:"flow",animations:{numbers:{type:"number",properties:["x","y","x2","y2","height"]},progress:{easing:"linear",duration:t=>"data"===t.type?200*(t.parsed._custom.x-t.parsed.x):void 0,delay:t=>"data"===t.type?500*t.parsed.x+20*t.dataIndex:void 0},colors:{type:"color",properties:["colorFrom","colorTo"]}},transitions:{hide:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],to:"transparent"}}},show:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],from:"transparent"}}}}},y.overrides={interaction:{mode:"nearest",intersect:!0},datasets:{color:()=>"#efefef",clip:!1,parsing:!0},plugins:{tooltip:{callbacks:{title:()=>"",label(t){const e=t.dataset.data[t.dataIndex];return e.from+" -> "+e.to+": "+e.flow}}},legend:{display:!1}},scales:{x:{type:"linear",bounds:"data",display:!1,min:0,offset:!1},y:{type:"linear",bounds:"data",display:!1,min:0,reverse:!0,offset:!1}},layout:{padding:{top:3,left:3,right:13,bottom:3}}};const p=(t,e,o,r)=>t<o?{cp1:{x:t+(o-t)/3*2,y:e},cp2:{x:t+(o-t)/3,y:r}}:{cp1:{x:t-(t-o)/3,y:0},cp2:{x:o+(t-o)/3,y:0}},g=(t,e,o)=>({x:t.x+o*(e.x-t.x),y:t.y+o*(e.y-t.y)});class m extends t.Element{constructor(t){super(),this.options=void 0,this.x=void 0,this.y=void 0,this.x2=void 0,this.y2=void 0,this.height=void 0,t&&Object.assign(this,t)}draw(t){const{x:o,x2:r,y:n,y2:a,height:i,progress:s}=this,{cp1:l,cp2:c}=p(o,n,r,a);0!==s&&(t.save(),s<1&&(t.beginPath(),t.rect(o,Math.min(n,a),(r-o)*s+1,Math.abs(a-n)+i+1),t.clip()),function(t,{x:o,x2:r,options:n}){let a;"from"===n.colorMode?a=e.color(n.colorFrom).alpha(.5).rgbString():"to"===n.colorMode?a=e.color(n.colorTo).alpha(.5).rgbString():(a=t.createLinearGradient(o,0,r,0),a.addColorStop(0,e.color(n.colorFrom).alpha(.5).rgbString()),a.addColorStop(1,e.color(n.colorTo).alpha(.5).rgbString())),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(o,n),t.bezierCurveTo(l.x,l.y,c.x,c.y,r,a),t.lineTo(r,a+i),t.bezierCurveTo(c.x,c.y+i,l.x,l.y+i,o,n+i),t.lineTo(o,n),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,e,o){const{x:r,y:n,x2:a,y2:i,height:s}=this.getProps(["x","y","x2","y2","height"],o);if(t<r||t>a)return!1;const{cp1:l,cp2:c}=p(r,n,a,i),f=(t-r)/(a-r),h={x:a,y:i},d=g({x:r,y:n},l,f),u=g(l,c,f),x=g(c,h,f),y=g(d,u,f),m=g(u,x,f),M=g(y,m,f).y;return e>=M&&e<=M+s}inXRange(t,e){const{x:o,x2:r}=this.getProps(["x","x2"],e);return t>=o&&t<=r}inYRange(t,e){const{y:o,y2:r,height:n}=this.getProps(["y","y2","height"],e),a=Math.min(o,r),i=Math.max(o,r)+n;return t>=a&&t<=i}getCenterPoint(t){const{x:e,y:o,x2:r,y2:n,height:a}=this.getProps(["x","y","x2","y2","height"],t);return{x:(e+r)/2,y:(o+n+a)/2}}tooltipPosition(t){return this.getCenterPoint(t)}getRange(t){return"x"===t?this.width/2:this.height/2}}m.id="flow",m.defaults={colorFrom:"red",colorTo:"green",colorMode:"gradient"},t.Chart.register(y,m)}));
|
|
7
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","chart.js/helpers"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Chart,t.Chart.helpers)}(this,(function(t,e){"use strict";function o(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const r=t=>void 0!==t;function n(t,e){const o=t.filter((t=>!e.has(t)));return o.length?o:t.slice(0,1)}const a=(t,e)=>t.x!==e.x?t.x-e.x:t.y-e.y;let i=-1;function s(t,e,o=function(){return i=i<100?i+1:0,i}()){let r=0;for(const n of t)n.node._visited!==o&&(n.node._visited=o,r+=n.node[e].length+s(n.node[e],e,o));return r}const l=t=>(e,o)=>s(e.node[t],t)-s(o.node[t],t)||e.node[t].length-o.node[t].length;function c(t,e){t.from.sort(l("from"));for(const o of t.from){const t=o.node;r(t.y)||(t.y=e,c(t,e)),e=Math.max(t.y+t.out,e)}return e}function h(t,e){t.to.sort(l("to"));for(const o of t.to){const t=o.node;r(t.y)||(t.y=e,h(t,e)),e=Math.max(t.y+t.in,e)}return e}function f(t,e){return r(t.y)?t.y:(t.y=e,e)}function d(t,e){t.sort(((t,e)=>Math.max(e.in,e.out)-Math.max(t.in,t.out)));const o=t[0];o.y=0;const n=c(o,0),a=h(o,0),i=function(t,e){const o=t.filter((t=>0===t.x)),n=t.filter((t=>t.x===e)),a=o.filter((t=>!r(t.y))),i=n.filter((t=>!r(t.y))),s=t.filter((t=>t.x>0&&t.x<e&&!r(t.y)));let l=o.reduce(((t,e)=>Math.max(t,e.y+e.out||0)),0),d=n.reduce(((t,e)=>Math.max(t,e.y+e.in||0)),0),u=0;return l>=d?(a.forEach((t=>{l=f(t,l),l=Math.max(l+t.out,h(t,l))})),i.forEach((t=>{d=f(t,d),d=Math.max(d+t.in,h(t,d))}))):(i.forEach((t=>{d=f(t,d),d=Math.max(d+t.in,h(t,d))})),a.forEach((t=>{l=f(t,l),l=Math.max(l+t.out,h(t,l))}))),s.forEach((e=>{let o=t.filter((t=>t.x===e.x&&r(t.y))).reduce(((t,e)=>Math.max(t,e.y+Math.max(e.in,e.out))),0);o=f(e,o),o=Math.max(o+e.in,c(e,o)),o=Math.max(o+e.out,h(e,o)),u=Math.max(u,o)})),Math.max(l,d,u)}(t,e);return Math.max(n,a,i)}function u(t,e,o,i){const s=[...t.values()],l=function(t,e){const o=new Set(e.map((t=>t.to))),a=new Set(e.map((t=>t.from))),i=new Set([...t.keys()]);let s=0;for(;i.size;){const a=n([...i],o);for(const e of a){const o=t.get(e);r(o.x)||(o.x=s),i.delete(e)}i.size&&(o.clear(),e.filter((t=>i.has(t.from))).forEach((t=>o.add(t.to))),s++)}return[...t.keys()].filter((t=>!a.has(t))).forEach((e=>{const o=t.get(e);o.column||(o.x=s)})),s}(t,e),c=o?function(t,e){let o=0,r=0;for(let n=0;n<=e;n++){let e=r;const a=t.filter((t=>t.x===n)).sort(((t,e)=>t.priority-e.priority));r=a[0].to.filter((t=>t.node.x>n+1)).reduce(((t,e)=>t+e.flow),0)||0;for(const t of a)t.y=e,e+=Math.max(t.out,t.in);o=Math.max(e,o)}return o}(s,l):d(s,l),h=function(t,e){let o=1,r=0,n=0,i=0;const s=[];t.sort(a);for(const a of t){if(a.y){if(0===a.x)s.push(a.y);else{for(r!==a.x&&(r=a.x,n=0),o=n+1;o<s.length&&!(s[o]>a.y);o++);n=o}a.y+=o*e,o++}i=Math.max(i,a.y+Math.max(a.in,a.out))}return i}(s,.03*c);return function(t,e){t.forEach((t=>{const o=Math[e](t.in||t.out,t.out||t.in),r=o<t.in,n=o<t.out;let a=0,i=t.from.length;t.from.sort(((t,e)=>t.node.y+t.node.out/2-(e.node.y+e.node.out/2))).forEach(((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=a,a+=t.flow)})),a=0,i=t.to.length,t.to.sort(((t,e)=>t.node.y+t.node.in/2-(e.node.y+e.node.in/2))).forEach(((t,e)=>{n?t.addY=e*(o-t.flow)/(i-1):(t.addY=a,a+=t.flow)}))}))}(s,i),{maxX:l,maxY:h}}function x(t,e,o){for(const r of t)if(r.key===e&&r.index===o)return r.addY;return 0}class y extends t.DatasetController{parseObjectData(t,e,r,n){const{from:a="from",to:i="to",flow:s="flow"}=this.options.parsing,l=e.map((({[a]:t,[i]:e,[s]:o})=>({from:t,to:e,flow:o}))),{xScale:c,yScale:h}=t,f=[],d=this._nodes=function(t){const e=new Map;for(let o=0;o<t.length;o++){const{from:r,to:n,flow:a}=t[o];if(e.has(r)){const t=e.get(r);t.out+=a,t.to.push({key:n,flow:a,index:o})}else e.set(r,{key:r,in:0,out:a,from:[],to:[{key:n,flow:a,index:o}]});if(e.has(n)){const t=e.get(n);t.in+=a,t.from.push({key:r,flow:a,index:o})}else e.set(n,{key:n,in:a,out:0,from:[{key:r,flow:a,index:o}],to:[]})}const o=(t,e)=>e.flow-t.flow;return[...e.values()].forEach((t=>{t.from=t.from.sort(o),t.from.forEach((t=>{t.node=e.get(t.key)})),t.to=t.to.sort(o),t.to.forEach((t=>{t.node=e.get(t.key)}))})),e}(l),{column:y,priority:p,size:m}=this.getDataset();if(p)for(const t of d.values())t.key in p&&(t.priority=p[t.key]);if(y)for(const t of d.values())t.key in y&&(t.column=!0,t.x=y[t.key]);const{maxX:g,maxY:M}=u(d,l,!!p,o(m));this._maxX=g,this._maxY=M;for(let t=0,e=l.length;t<e;++t){const e=l[t],o=d.get(e.from),r=d.get(e.to),n=o.y+x(o.to,e.to,t),a=r.y+x(r.from,e.from,t);f.push({x:c.parse(o.x,t),y:h.parse(n,t),_custom:{from:o,to:r,x:c.parse(r.x,t),y:h.parse(a,t),height:h.parse(e.flow,t)}})}return f.slice(r,r+n)}getMinMax(t){return{min:0,max:t===this._cachedMeta.xScale?this._maxX:this._maxY}}update(t){const{data:e}=this._cachedMeta;this.updateElements(e,0,e.length,t)}updateElements(t,o,r,n){const{xScale:a,yScale:i}=this._cachedMeta,s=this.resolveDataElementOptions(o,n),l=this.getSharedOptions(n,t[o],s),c=this.getDataset(),h=e.valueOrDefault(c.borderWidth,1)/2+.5,f=e.valueOrDefault(c.nodeWidth,10);for(let e=o;e<o+r;e++){const o=this.getParsed(e),r=o._custom,s=i.getPixelForValue(o.y);this.updateElement(t[e],e,{x:a.getPixelForValue(o.x)+f+h,y:s,x2:a.getPixelForValue(r.x)-h,y2:i.getPixelForValue(r.y),from:r.from,to:r.to,progress:"reset"===n?0:1,height:Math.abs(i.getPixelForValue(o.y+r.height)-s),options:this.resolveDataElementOptions(e,n)},n)}this.updateSharedOptions(l,n)}_drawLabels(){const t=this._ctx,r=this._nodes||new Map,n=this.getDataset(),a=o(n.size),i=e.valueOrDefault(n.borderWidth,1),s=e.valueOrDefault(n.nodeWidth,10),l=n.labels,{xScale:c,yScale:h}=this._cachedMeta;t.save();const f=this.chart.chartArea;for(const e of r.values()){const o=c.getPixelForValue(e.x),r=h.getPixelForValue(e.y),d=Math[a](e.in||e.out,e.out||e.in),u=Math.abs(h.getPixelForValue(e.y+d)-r),x=l&&l[e.key]||e.key;let y=o;t.fillStyle=n.color||"black",t.textBaseline="middle",o<f.width/2?(t.textAlign="left",y+=s+i+4):(t.textAlign="right",y-=i+4),this._drawLabel(x,r,u,t,y)}t.restore()}_drawLabel(t,o,r,n,a){const i=e.toFont(this.options.font,this.chart.options.font),s=e.isNullOrUndef(t)?[]:function(t){const o=[],r=e.isArray(t)?t:e.isNullOrUndef(t)?[]:[t];for(;r.length;){const t=r.pop();"string"==typeof t?o.unshift.apply(o,t.split("\n")):Array.isArray(t)?r.push.apply(r,t):e.isNullOrUndef(r)||o.unshift(""+t)}return o}(t),l=s.length,c=o+r/2,h=i.lineHeight,f=e.valueOrDefault(this.options.padding,h/2);if(n.font=i.string,l>1){const t=c-h*l/2+f;for(let e=0;e<l;e++)n.fillText(s[e],a,t+e*h)}else n.fillText(t,a,c)}_drawNodes(){const t=this._ctx,r=this._nodes||new Map,n=this.getDataset(),a=o(n.size),{xScale:i,yScale:s}=this._cachedMeta,l=e.valueOrDefault(n.borderWidth,1),c=e.valueOrDefault(n.nodeWidth,10);t.save(),t.strokeStyle=n.borderColor||"black",t.lineWidth=l;for(const e of r.values()){t.fillStyle=e.color;const o=i.getPixelForValue(e.x),r=s.getPixelForValue(e.y),n=Math[a](e.in||e.out,e.out||e.in),h=Math.abs(s.getPixelForValue(e.y+n)-r);l&&t.strokeRect(o,r,c,h),t.fillRect(o,r,c,h)}t.restore()}draw(){const t=this._ctx,e=this.getMeta().data||[];for(let t=0,o=e.length;t<o;++t){const o=e[t];o.from.color=o.options.colorFrom,o.to.color=o.options.colorTo}this._drawNodes();for(let o=0,r=e.length;o<r;++o)e[o].draw(t);this._drawLabels()}}y.id="sankey",y.defaults={dataElementType:"flow",animations:{numbers:{type:"number",properties:["x","y","x2","y2","height"]},progress:{easing:"linear",duration:t=>"data"===t.type?200*(t.parsed._custom.x-t.parsed.x):void 0,delay:t=>"data"===t.type?500*t.parsed.x+20*t.dataIndex:void 0},colors:{type:"color",properties:["colorFrom","colorTo"]}},transitions:{hide:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],to:"transparent"}}},show:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],from:"transparent"}}}}},y.overrides={interaction:{mode:"nearest",intersect:!0},datasets:{clip:!1,parsing:!0},plugins:{tooltip:{callbacks:{title:()=>"",label(t){const e=t.dataset.data[t.dataIndex];return e.from+" -> "+e.to+": "+e.flow}}},legend:{display:!1}},scales:{x:{type:"linear",bounds:"data",display:!1,min:0,offset:!1},y:{type:"linear",bounds:"data",display:!1,min:0,reverse:!0,offset:!1}},layout:{padding:{top:3,left:3,right:13,bottom:3}}};const p=(t,e,o,r)=>t<o?{cp1:{x:t+(o-t)/3*2,y:e},cp2:{x:t+(o-t)/3,y:r}}:{cp1:{x:t-(t-o)/3,y:0},cp2:{x:o+(t-o)/3,y:0}},m=(t,e,o)=>({x:t.x+o*(e.x-t.x),y:t.y+o*(e.y-t.y)});class g extends t.Element{constructor(t){super(),this.options=void 0,this.x=void 0,this.y=void 0,this.x2=void 0,this.y2=void 0,this.height=void 0,t&&Object.assign(this,t)}draw(t){const{x:o,x2:r,y:n,y2:a,height:i,progress:s}=this,{cp1:l,cp2:c}=p(o,n,r,a);0!==s&&(t.save(),s<1&&(t.beginPath(),t.rect(o,Math.min(n,a),(r-o)*s+1,Math.abs(a-n)+i+1),t.clip()),function(t,{x:o,x2:r,options:n}){let a;"from"===n.colorMode?a=e.color(n.colorFrom).alpha(.5).rgbString():"to"===n.colorMode?a=e.color(n.colorTo).alpha(.5).rgbString():(a=t.createLinearGradient(o,0,r,0),a.addColorStop(0,e.color(n.colorFrom).alpha(.5).rgbString()),a.addColorStop(1,e.color(n.colorTo).alpha(.5).rgbString())),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(o,n),t.bezierCurveTo(l.x,l.y,c.x,c.y,r,a),t.lineTo(r,a+i),t.bezierCurveTo(c.x,c.y+i,l.x,l.y+i,o,n+i),t.lineTo(o,n),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,e,o){const{x:r,y:n,x2:a,y2:i,height:s}=this.getProps(["x","y","x2","y2","height"],o);if(t<r||t>a)return!1;const{cp1:l,cp2:c}=p(r,n,a,i),h=(t-r)/(a-r),f={x:a,y:i},d=m({x:r,y:n},l,h),u=m(l,c,h),x=m(c,f,h),y=m(d,u,h),g=m(u,x,h),M=m(y,g,h).y;return e>=M&&e<=M+s}inXRange(t,e){const{x:o,x2:r}=this.getProps(["x","x2"],e);return t>=o&&t<=r}inYRange(t,e){const{y:o,y2:r,height:n}=this.getProps(["y","y2","height"],e),a=Math.min(o,r),i=Math.max(o,r)+n;return t>=a&&t<=i}getCenterPoint(t){const{x:e,y:o,x2:r,y2:n,height:a}=this.getProps(["x","y","x2","y2","height"],t);return{x:(e+r)/2,y:(o+n+a)/2}}tooltipPosition(t){return this.getCenterPoint(t)}getRange(t){return"x"===t?this.width/2:this.height/2}}g.id="flow",g.defaults={colorFrom:"red",colorTo:"green",colorMode:"gradient"},t.Chart.register(y,g)}));
|