chartjs-chart-sankey 0.9.1 → 0.11.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 +50 -49
- package/dist/chartjs-chart-sankey.js +49 -48
- package/dist/chartjs-chart-sankey.min.js +2 -2
- package/package.json +5 -5
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,11 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.11.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
7
|
import { DatasetController, Element } from 'chart.js';
|
|
8
|
-
import { isArray, isNullOrUndef, valueOrDefault, toFont, color } from 'chart.js/helpers';
|
|
8
|
+
import { isArray, isNullOrUndef, valueOrDefault, toFont, getHoverColor, color } from 'chart.js/helpers';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @param {string | Array<string>} raw
|
|
@@ -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,18 +623,27 @@ 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
|
|
|
629
|
+
// Set node colors
|
|
630
|
+
const active = [];
|
|
639
631
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
640
632
|
const flow = data[i]; /* Flow at index i */
|
|
641
633
|
flow.from.color = flow.options.colorFrom;
|
|
642
634
|
flow.to.color = flow.options.colorTo;
|
|
635
|
+
if (flow.active) {
|
|
636
|
+
active.push(flow);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// Make sure nodes connected to hovered flows are using hover colors.
|
|
640
|
+
for (const flow of active) {
|
|
641
|
+
flow.from.color = flow.options.colorFrom;
|
|
642
|
+
flow.to.color = flow.options.colorTo;
|
|
643
643
|
}
|
|
644
644
|
|
|
645
645
|
/* draw SankeyNodes on the canvas */
|
|
646
|
-
|
|
646
|
+
this._drawNodes();
|
|
647
647
|
|
|
648
648
|
/* draw Flow elements on the canvas */
|
|
649
649
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
@@ -651,7 +651,7 @@ class SankeyController extends DatasetController {
|
|
|
651
651
|
}
|
|
652
652
|
|
|
653
653
|
/* draw labels (for SankeyNodes) on the canvas */
|
|
654
|
-
|
|
654
|
+
this._drawLabels();
|
|
655
655
|
}
|
|
656
656
|
}
|
|
657
657
|
|
|
@@ -702,7 +702,6 @@ SankeyController.overrides = {
|
|
|
702
702
|
intersect: true
|
|
703
703
|
},
|
|
704
704
|
datasets: {
|
|
705
|
-
color: () => '#efefef',
|
|
706
705
|
clip: false,
|
|
707
706
|
parsing: true
|
|
708
707
|
},
|
|
@@ -929,7 +928,9 @@ Flow.id = 'flow';
|
|
|
929
928
|
Flow.defaults = {
|
|
930
929
|
colorFrom: 'red',
|
|
931
930
|
colorTo: 'green',
|
|
932
|
-
colorMode: 'gradient'
|
|
931
|
+
colorMode: 'gradient',
|
|
932
|
+
hoverColorFrom: (ctx, options) => getHoverColor(options.colorFrom),
|
|
933
|
+
hoverColorTo: (ctx, options) => getHoverColor(options.colorTo)
|
|
933
934
|
};
|
|
934
935
|
|
|
935
936
|
export { Flow, SankeyController };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.11.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,18 +626,27 @@ 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
|
|
|
632
|
+
// Set node colors
|
|
633
|
+
const active = [];
|
|
642
634
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
643
635
|
const flow = data[i]; /* Flow at index i */
|
|
644
636
|
flow.from.color = flow.options.colorFrom;
|
|
645
637
|
flow.to.color = flow.options.colorTo;
|
|
638
|
+
if (flow.active) {
|
|
639
|
+
active.push(flow);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// Make sure nodes connected to hovered flows are using hover colors.
|
|
643
|
+
for (const flow of active) {
|
|
644
|
+
flow.from.color = flow.options.colorFrom;
|
|
645
|
+
flow.to.color = flow.options.colorTo;
|
|
646
646
|
}
|
|
647
647
|
|
|
648
648
|
/* draw SankeyNodes on the canvas */
|
|
649
|
-
|
|
649
|
+
this._drawNodes();
|
|
650
650
|
|
|
651
651
|
/* draw Flow elements on the canvas */
|
|
652
652
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
@@ -654,7 +654,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
654
654
|
}
|
|
655
655
|
|
|
656
656
|
/* draw labels (for SankeyNodes) on the canvas */
|
|
657
|
-
|
|
657
|
+
this._drawLabels();
|
|
658
658
|
}
|
|
659
659
|
}
|
|
660
660
|
|
|
@@ -705,7 +705,6 @@ SankeyController.overrides = {
|
|
|
705
705
|
intersect: true
|
|
706
706
|
},
|
|
707
707
|
datasets: {
|
|
708
|
-
color: () => '#efefef',
|
|
709
708
|
clip: false,
|
|
710
709
|
parsing: true
|
|
711
710
|
},
|
|
@@ -932,7 +931,9 @@ Flow.id = 'flow';
|
|
|
932
931
|
Flow.defaults = {
|
|
933
932
|
colorFrom: 'red',
|
|
934
933
|
colorTo: 'green',
|
|
935
|
-
colorMode: 'gradient'
|
|
934
|
+
colorMode: 'gradient',
|
|
935
|
+
hoverColorFrom: (ctx, options) => helpers.getHoverColor(options.colorFrom),
|
|
936
|
+
hoverColorTo: (ctx, options) => helpers.getHoverColor(options.colorTo)
|
|
936
937
|
};
|
|
937
938
|
|
|
938
939
|
chart_js.Chart.register(SankeyController, Flow);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.11.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,o){"object"==typeof exports&&"undefined"!=typeof module?o(require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","chart.js/helpers"],o):o((t="undefined"!=typeof globalThis?globalThis:t||self).Chart,t.Chart.helpers)}(this,(function(t,o){"use strict";function e(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const r=t=>void 0!==t;function n(t,o){const e=t.filter((t=>!o.has(t)));return e.length?e:t.slice(0,1)}const a=(t,o)=>t.x!==o.x?t.x-o.x:t.y-o.y;let i=-1;function s(t,o,e=function(){return i=i<100?i+1:0,i}()){let r=0;for(const n of t)n.node._visited!==e&&(n.node._visited=e,r+=n.node[o].length+s(n.node[o],o,e));return r}const l=t=>(o,e)=>s(o.node[t],t)-s(e.node[t],t)||o.node[t].length-e.node[t].length;function c(t,o){t.from.sort(l("from"));for(const e of t.from){const t=e.node;r(t.y)||(t.y=o,c(t,o)),o=Math.max(t.y+t.out,o)}return o}function h(t,o){t.to.sort(l("to"));for(const e of t.to){const t=e.node;r(t.y)||(t.y=o,h(t,o)),o=Math.max(t.y+t.in,o)}return o}function f(t,o){return r(t.y)?t.y:(t.y=o,o)}function d(t,o){t.sort(((t,o)=>Math.max(o.in,o.out)-Math.max(t.in,t.out)));const e=t[0];e.y=0;const n=c(e,0),a=h(e,0),i=function(t,o){const e=t.filter((t=>0===t.x)),n=t.filter((t=>t.x===o)),a=e.filter((t=>!r(t.y))),i=n.filter((t=>!r(t.y))),s=t.filter((t=>t.x>0&&t.x<o&&!r(t.y)));let l=e.reduce(((t,o)=>Math.max(t,o.y+o.out||0)),0),d=n.reduce(((t,o)=>Math.max(t,o.y+o.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((o=>{let e=t.filter((t=>t.x===o.x&&r(t.y))).reduce(((t,o)=>Math.max(t,o.y+Math.max(o.in,o.out))),0);e=f(o,e),e=Math.max(e+o.in,c(o,e)),e=Math.max(e+o.out,h(o,e)),u=Math.max(u,e)})),Math.max(l,d,u)}(t,o);return Math.max(n,a,i)}function u(t,o,e,i){const s=[...t.values()],l=function(t,o){const e=new Set(o.map((t=>t.to))),a=new Set(o.map((t=>t.from))),i=new Set([...t.keys()]);let s=0;for(;i.size;){const a=n([...i],e);for(const o of a){const e=t.get(o);r(e.x)||(e.x=s),i.delete(o)}i.size&&(e.clear(),o.filter((t=>i.has(t.from))).forEach((t=>e.add(t.to))),s++)}return[...t.keys()].filter((t=>!a.has(t))).forEach((o=>{const e=t.get(o);e.column||(e.x=s)})),s}(t,o),c=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const a=t.filter((t=>t.x===n)).sort(((t,o)=>t.priority-o.priority));r=a[0].to.filter((t=>t.node.x>n+1)).reduce(((t,o)=>t+o.flow),0)||0;for(const t of a)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(s,l):d(s,l),h=function(t,o){let e=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),e=n+1;e<s.length&&!(s[e]>a.y);e++);n=e}a.y+=e*o,e++}i=Math.max(i,a.y+Math.max(a.in,a.out))}return i}(s,.03*c);return function(t,o){t.forEach((t=>{const e=Math[o](t.in||t.out,t.out||t.in),r=e<t.in,n=e<t.out;let a=0,i=t.from.length;t.from.sort(((t,o)=>t.node.y+t.node.out/2-(o.node.y+o.node.out/2))).forEach(((t,o)=>{r?t.addY=o*(e-t.flow)/(i-1):(t.addY=a,a+=t.flow)})),a=0,i=t.to.length,t.to.sort(((t,o)=>t.node.y+t.node.in/2-(o.node.y+o.node.in/2))).forEach(((t,o)=>{n?t.addY=o*(e-t.flow)/(i-1):(t.addY=a,a+=t.flow)}))}))}(s,i),{maxX:l,maxY:h}}function x(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}class y extends t.DatasetController{parseObjectData(t,o,r,n){const{from:a="from",to:i="to",flow:s="flow"}=this.options.parsing,l=o.map((({[a]:t,[i]:o,[s]:e})=>({from:t,to:o,flow:e}))),{xScale:c,yScale:h}=t,f=[],d=this._nodes=function(t){const o=new Map;for(let e=0;e<t.length;e++){const{from:r,to:n,flow:a}=t[e];if(o.has(r)){const t=o.get(r);t.out+=a,t.to.push({key:n,flow:a,index:e})}else o.set(r,{key:r,in:0,out:a,from:[],to:[{key:n,flow:a,index:e}]});if(o.has(n)){const t=o.get(n);t.in+=a,t.from.push({key:r,flow:a,index:e})}else o.set(n,{key:n,in:a,out:0,from:[{key:r,flow:a,index:e}],to:[]})}const e=(t,o)=>o.flow-t.flow;return[...o.values()].forEach((t=>{t.from=t.from.sort(e),t.from.forEach((t=>{t.node=o.get(t.key)})),t.to=t.to.sort(e),t.to.forEach((t=>{t.node=o.get(t.key)}))})),o}(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,e(m));this._maxX=g,this._maxY=M;for(let t=0,o=l.length;t<o;++t){const o=l[t],e=d.get(o.from),r=d.get(o.to),n=e.y+x(e.to,o.to,t),a=r.y+x(r.from,o.from,t);f.push({x:c.parse(e.x,t),y:h.parse(n,t),_custom:{from:e,to:r,x:c.parse(r.x,t),y:h.parse(a,t),height:h.parse(o.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:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,e,r,n){const{xScale:a,yScale:i}=this._cachedMeta,s=this.resolveDataElementOptions(e,n),l=this.getSharedOptions(n,t[e],s),c=this.getDataset(),h=o.valueOrDefault(c.borderWidth,1)/2+.5,f=o.valueOrDefault(c.nodeWidth,10);for(let o=e;o<e+r;o++){const e=this.getParsed(o),r=e._custom,s=i.getPixelForValue(e.y);this.updateElement(t[o],o,{x:a.getPixelForValue(e.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(e.y+r.height)-s),options:this.resolveDataElementOptions(o,n)},n)}this.updateSharedOptions(l,n)}_drawLabels(){const t=this._ctx,r=this._nodes||new Map,n=this.getDataset(),a=e(n.size),i=o.valueOrDefault(n.borderWidth,1),s=o.valueOrDefault(n.nodeWidth,10),l=n.labels,{xScale:c,yScale:h}=this._cachedMeta;t.save();const f=this.chart.chartArea;for(const o of r.values()){const e=c.getPixelForValue(o.x),r=h.getPixelForValue(o.y),d=Math[a](o.in||o.out,o.out||o.in),u=Math.abs(h.getPixelForValue(o.y+d)-r),x=l&&l[o.key]||o.key;let y=e;t.fillStyle=n.color||"black",t.textBaseline="middle",e<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,e,r,n,a){const i=o.toFont(this.options.font,this.chart.options.font),s=o.isNullOrUndef(t)?[]:function(t){const e=[],r=o.isArray(t)?t:o.isNullOrUndef(t)?[]:[t];for(;r.length;){const t=r.pop();"string"==typeof t?e.unshift.apply(e,t.split("\n")):Array.isArray(t)?r.push.apply(r,t):o.isNullOrUndef(r)||e.unshift(""+t)}return e}(t),l=s.length,c=e+r/2,h=i.lineHeight,f=o.valueOrDefault(this.options.padding,h/2);if(n.font=i.string,l>1){const t=c-h*l/2+f;for(let o=0;o<l;o++)n.fillText(s[o],a,t+o*h)}else n.fillText(t,a,c)}_drawNodes(){const t=this._ctx,r=this._nodes||new Map,n=this.getDataset(),a=e(n.size),{xScale:i,yScale:s}=this._cachedMeta,l=o.valueOrDefault(n.borderWidth,1),c=o.valueOrDefault(n.nodeWidth,10);t.save(),t.strokeStyle=n.borderColor||"black",t.lineWidth=l;for(const o of r.values()){t.fillStyle=o.color;const e=i.getPixelForValue(o.x),r=s.getPixelForValue(o.y),n=Math[a](o.in||o.out,o.out||o.in),h=Math.abs(s.getPixelForValue(o.y+n)-r);l&&t.strokeRect(e,r,c,h),t.fillRect(e,r,c,h)}t.restore()}draw(){const t=this._ctx,o=this.getMeta().data||[],e=[];for(let t=0,r=o.length;t<r;++t){const r=o[t];r.from.color=r.options.colorFrom,r.to.color=r.options.colorTo,r.active&&e.push(r)}for(const t of e)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()}}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 o=t.dataset.data[t.dataIndex];return o.from+" -> "+o.to+": "+o.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,o,e,r)=>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}},m=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.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:e,x2:r,y:n,y2:a,height:i,progress:s}=this,{cp1:l,cp2:c}=p(e,n,r,a);0!==s&&(t.save(),s<1&&(t.beginPath(),t.rect(e,Math.min(n,a),(r-e)*s+1,Math.abs(a-n)+i+1),t.clip()),function(t,{x:e,x2:r,options:n}){let a;"from"===n.colorMode?a=o.color(n.colorFrom).alpha(.5).rgbString():"to"===n.colorMode?a=o.color(n.colorTo).alpha(.5).rgbString():(a=t.createLinearGradient(e,0,r,0),a.addColorStop(0,o.color(n.colorFrom).alpha(.5).rgbString()),a.addColorStop(1,o.color(n.colorTo).alpha(.5).rgbString())),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(e,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,e,n+i),t.lineTo(e,n),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,o,e){const{x:r,y:n,x2:a,y2:i,height:s}=this.getProps(["x","y","x2","y2","height"],e);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 o>=M&&o<=M+s}inXRange(t,o){const{x:e,x2:r}=this.getProps(["x","x2"],o);return t>=e&&t<=r}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),a=Math.min(e,r),i=Math.max(e,r)+n;return t>=a&&t<=i}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:a}=this.getProps(["x","y","x2","y2","height"],t);return{x:(o+r)/2,y:(e+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",hoverColorFrom:(t,e)=>o.getHoverColor(e.colorFrom),hoverColorTo:(t,e)=>o.getHoverColor(e.colorTo)},t.Chart.register(y,g)}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chartjs-chart-sankey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Chart.js module for creating sankey diagrams",
|
|
5
5
|
"main": "dist/chartjs-chart-sankey.js",
|
|
6
6
|
"module": "dist/chartjs-chart-sankey.esm.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/kurkle/chartjs-chart-sankey#readme",
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@rollup/plugin-node-resolve": "^
|
|
41
|
+
"@rollup/plugin-node-resolve": "^14.1.0",
|
|
42
42
|
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
|
43
43
|
"@typescript-eslint/parser": "^5.10.2",
|
|
44
44
|
"chart.js": "^3.3.0",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"eslint": "^8.8.0",
|
|
51
51
|
"eslint-config-chartjs": "^0.3.0",
|
|
52
52
|
"eslint-plugin-es": "^4.1.0",
|
|
53
|
-
"eslint-plugin-html": "^
|
|
54
|
-
"eslint-plugin-markdown": "^
|
|
53
|
+
"eslint-plugin-html": "^7.1.0",
|
|
54
|
+
"eslint-plugin-markdown": "^3.0.0",
|
|
55
55
|
"jasmine-core": "^4.0.0",
|
|
56
56
|
"karma": "^6.2.0",
|
|
57
57
|
"karma-chrome-launcher": "^3.1.0",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"karma-jasmine": "^4.0.1",
|
|
61
61
|
"karma-jasmine-html-reporter": "^1.5.4",
|
|
62
62
|
"karma-rollup-preprocessor": "7.0.7",
|
|
63
|
-
"karma-spec-reporter": "^0.0.
|
|
63
|
+
"karma-spec-reporter": "^0.0.34",
|
|
64
64
|
"rollup": "^2.42.1",
|
|
65
65
|
"rollup-plugin-istanbul": "^3.0.0",
|
|
66
66
|
"rollup-plugin-terser": "^7.0.2",
|