chartjs-chart-sankey 0.9.0 → 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 +53 -48
- package/dist/chartjs-chart-sankey.js +53 -48
- package/dist/chartjs-chart-sankey.min.js +2 -2
- package/package.json +2 -2
- package/types/index.esm.d.ts +12 -6
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
|
|
@@ -101,12 +101,28 @@ function nextColumn(keys, to) {
|
|
|
101
101
|
*/
|
|
102
102
|
const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
|
|
103
103
|
|
|
104
|
+
let prevCountId = -1;
|
|
105
|
+
function getCountId() {
|
|
106
|
+
prevCountId = prevCountId < 100 ? prevCountId + 1 : 0;
|
|
107
|
+
return prevCountId;
|
|
108
|
+
}
|
|
109
|
+
|
|
104
110
|
/**
|
|
105
111
|
* @param {Array<FromToElement>} list
|
|
106
112
|
* @param {string} prop
|
|
107
113
|
* @return {number}
|
|
108
114
|
*/
|
|
109
|
-
|
|
115
|
+
function nodeCount(list, prop, countId = getCountId()) {
|
|
116
|
+
let count = 0;
|
|
117
|
+
for (const elem of list) {
|
|
118
|
+
if (elem.node._visited === countId) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
elem.node._visited = countId;
|
|
122
|
+
count += elem.node[prop].length + nodeCount(elem.node[prop], prop, countId);
|
|
123
|
+
}
|
|
124
|
+
return count;
|
|
125
|
+
}
|
|
110
126
|
|
|
111
127
|
/**
|
|
112
128
|
* @param {string} prop
|
|
@@ -412,16 +428,13 @@ class SankeyController extends DatasetController {
|
|
|
412
428
|
* @return {Array<SankeyParsedData>}
|
|
413
429
|
*/
|
|
414
430
|
parseObjectData(meta, data, start, count) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
return [];
|
|
418
|
-
}
|
|
419
|
-
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}));
|
|
420
433
|
const {xScale, yScale} = meta;
|
|
421
434
|
const parsed = []; /* Array<SankeyParsedData> */
|
|
422
|
-
const nodes =
|
|
435
|
+
const nodes = this._nodes = buildNodesFromRawData(sankeyData);
|
|
423
436
|
/* getDataset() => SankeyControllerDatasetOptions */
|
|
424
|
-
const {column, priority, size} =
|
|
437
|
+
const {column, priority, size} = this.getDataset();
|
|
425
438
|
if (priority) {
|
|
426
439
|
for (const node of nodes.values()) {
|
|
427
440
|
if (node.key in priority) {
|
|
@@ -438,13 +451,13 @@ class SankeyController extends DatasetController {
|
|
|
438
451
|
}
|
|
439
452
|
}
|
|
440
453
|
|
|
441
|
-
const {maxX, maxY} = layout(nodes,
|
|
454
|
+
const {maxX, maxY} = layout(nodes, sankeyData, !!priority, validateSizeValue(size));
|
|
442
455
|
|
|
443
|
-
|
|
444
|
-
|
|
456
|
+
this._maxX = maxX;
|
|
457
|
+
this._maxY = maxY;
|
|
445
458
|
|
|
446
|
-
for (let i = 0, ilen =
|
|
447
|
-
const dataPoint =
|
|
459
|
+
for (let i = 0, ilen = sankeyData.length; i < ilen; ++i) {
|
|
460
|
+
const dataPoint = sankeyData[i];
|
|
448
461
|
const from = nodes.get(dataPoint.from);
|
|
449
462
|
const to = nodes.get(dataPoint.to);
|
|
450
463
|
const fromY = from.y + getAddY(from.to, dataPoint.to, i);
|
|
@@ -465,18 +478,16 @@ class SankeyController extends DatasetController {
|
|
|
465
478
|
}
|
|
466
479
|
|
|
467
480
|
getMinMax(scale) {
|
|
468
|
-
const me = this;
|
|
469
481
|
return {
|
|
470
482
|
min: 0,
|
|
471
|
-
max: scale === this._cachedMeta.xScale ? this._maxX :
|
|
483
|
+
max: scale === this._cachedMeta.xScale ? this._maxX : this._maxY
|
|
472
484
|
};
|
|
473
485
|
}
|
|
474
486
|
|
|
475
487
|
update(mode) {
|
|
476
|
-
const
|
|
477
|
-
const meta = me._cachedMeta;
|
|
488
|
+
const {data} = this._cachedMeta;
|
|
478
489
|
|
|
479
|
-
|
|
490
|
+
this.updateElements(data, 0, data.length, mode);
|
|
480
491
|
}
|
|
481
492
|
|
|
482
493
|
/**
|
|
@@ -486,20 +497,19 @@ class SankeyController extends DatasetController {
|
|
|
486
497
|
* @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
|
|
487
498
|
*/
|
|
488
499
|
updateElements(elems, start, count, mode) {
|
|
489
|
-
const
|
|
490
|
-
const
|
|
491
|
-
const
|
|
492
|
-
const
|
|
493
|
-
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();
|
|
494
504
|
const borderWidth = valueOrDefault(dataset.borderWidth, 1) / 2 + 0.5;
|
|
495
505
|
const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
|
|
496
506
|
|
|
497
507
|
for (let i = start; i < start + count; i++) {
|
|
498
508
|
/* getParsed(idx: number) => SankeyParsedData */
|
|
499
|
-
const parsed =
|
|
509
|
+
const parsed = this.getParsed(i);
|
|
500
510
|
const custom = parsed._custom;
|
|
501
511
|
const y = yScale.getPixelForValue(parsed.y);
|
|
502
|
-
|
|
512
|
+
this.updateElement(
|
|
503
513
|
elems[i],
|
|
504
514
|
i,
|
|
505
515
|
{
|
|
@@ -511,27 +521,26 @@ class SankeyController extends DatasetController {
|
|
|
511
521
|
to: custom.to,
|
|
512
522
|
progress: mode === 'reset' ? 0 : 1,
|
|
513
523
|
height: Math.abs(yScale.getPixelForValue(parsed.y + custom.height) - y),
|
|
514
|
-
options:
|
|
524
|
+
options: this.resolveDataElementOptions(i, mode)
|
|
515
525
|
},
|
|
516
526
|
mode);
|
|
517
527
|
}
|
|
518
528
|
|
|
519
|
-
|
|
529
|
+
this.updateSharedOptions(sharedOptions, mode);
|
|
520
530
|
}
|
|
521
531
|
|
|
522
532
|
_drawLabels() {
|
|
523
|
-
const
|
|
524
|
-
const
|
|
525
|
-
const
|
|
526
|
-
const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
|
|
533
|
+
const ctx = this._ctx;
|
|
534
|
+
const nodes = this._nodes || new Map();
|
|
535
|
+
const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
|
|
527
536
|
const size = validateSizeValue(dataset.size);
|
|
528
537
|
const borderWidth = valueOrDefault(dataset.borderWidth, 1);
|
|
529
538
|
const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
|
|
530
539
|
const labels = dataset.labels;
|
|
531
|
-
const {xScale, yScale} =
|
|
540
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
532
541
|
|
|
533
542
|
ctx.save();
|
|
534
|
-
const chartArea =
|
|
543
|
+
const chartArea = this.chart.chartArea;
|
|
535
544
|
for (const node of nodes.values()) {
|
|
536
545
|
const x = xScale.getPixelForValue(node.x);
|
|
537
546
|
const y = yScale.getPixelForValue(node.y);
|
|
@@ -563,13 +572,12 @@ class SankeyController extends DatasetController {
|
|
|
563
572
|
* @private
|
|
564
573
|
*/
|
|
565
574
|
_drawLabel(label, y, height, ctx, textX) {
|
|
566
|
-
const
|
|
567
|
-
const font = toFont(me.options.font, me.chart.options.font);
|
|
575
|
+
const font = toFont(this.options.font, this.chart.options.font);
|
|
568
576
|
const lines = isNullOrUndef(label) ? [] : toTextLines(label);
|
|
569
577
|
const linesLength = lines.length;
|
|
570
578
|
const middle = y + height / 2;
|
|
571
579
|
const textHeight = font.lineHeight;
|
|
572
|
-
const padding = valueOrDefault(
|
|
580
|
+
const padding = valueOrDefault(this.options.padding, textHeight / 2);
|
|
573
581
|
|
|
574
582
|
ctx.font = font.string;
|
|
575
583
|
|
|
@@ -584,12 +592,11 @@ class SankeyController extends DatasetController {
|
|
|
584
592
|
}
|
|
585
593
|
|
|
586
594
|
_drawNodes() {
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
const
|
|
590
|
-
const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
|
|
595
|
+
const ctx = this._ctx;
|
|
596
|
+
const nodes = this._nodes || new Map();
|
|
597
|
+
const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
|
|
591
598
|
const size = validateSizeValue(dataset.size);
|
|
592
|
-
const {xScale, yScale} =
|
|
599
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
593
600
|
const borderWidth = valueOrDefault(dataset.borderWidth, 1);
|
|
594
601
|
const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
|
|
595
602
|
|
|
@@ -616,9 +623,8 @@ class SankeyController extends DatasetController {
|
|
|
616
623
|
* That's where the drawing process happens
|
|
617
624
|
*/
|
|
618
625
|
draw() {
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
const data = me.getMeta().data || []; /* Array<Flow> */
|
|
626
|
+
const ctx = this._ctx;
|
|
627
|
+
const data = this.getMeta().data || []; /* Array<Flow> */
|
|
622
628
|
|
|
623
629
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
624
630
|
const flow = data[i]; /* Flow at index i */
|
|
@@ -627,7 +633,7 @@ class SankeyController extends DatasetController {
|
|
|
627
633
|
}
|
|
628
634
|
|
|
629
635
|
/* draw SankeyNodes on the canvas */
|
|
630
|
-
|
|
636
|
+
this._drawNodes();
|
|
631
637
|
|
|
632
638
|
/* draw Flow elements on the canvas */
|
|
633
639
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
@@ -635,7 +641,7 @@ class SankeyController extends DatasetController {
|
|
|
635
641
|
}
|
|
636
642
|
|
|
637
643
|
/* draw labels (for SankeyNodes) on the canvas */
|
|
638
|
-
|
|
644
|
+
this._drawLabels();
|
|
639
645
|
}
|
|
640
646
|
}
|
|
641
647
|
|
|
@@ -686,7 +692,6 @@ SankeyController.overrides = {
|
|
|
686
692
|
intersect: true
|
|
687
693
|
},
|
|
688
694
|
datasets: {
|
|
689
|
-
color: () => '#efefef',
|
|
690
695
|
clip: false,
|
|
691
696
|
parsing: true
|
|
692
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
|
|
@@ -104,12 +104,28 @@ function nextColumn(keys, to) {
|
|
|
104
104
|
*/
|
|
105
105
|
const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
|
|
106
106
|
|
|
107
|
+
let prevCountId = -1;
|
|
108
|
+
function getCountId() {
|
|
109
|
+
prevCountId = prevCountId < 100 ? prevCountId + 1 : 0;
|
|
110
|
+
return prevCountId;
|
|
111
|
+
}
|
|
112
|
+
|
|
107
113
|
/**
|
|
108
114
|
* @param {Array<FromToElement>} list
|
|
109
115
|
* @param {string} prop
|
|
110
116
|
* @return {number}
|
|
111
117
|
*/
|
|
112
|
-
|
|
118
|
+
function nodeCount(list, prop, countId = getCountId()) {
|
|
119
|
+
let count = 0;
|
|
120
|
+
for (const elem of list) {
|
|
121
|
+
if (elem.node._visited === countId) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
elem.node._visited = countId;
|
|
125
|
+
count += elem.node[prop].length + nodeCount(elem.node[prop], prop, countId);
|
|
126
|
+
}
|
|
127
|
+
return count;
|
|
128
|
+
}
|
|
113
129
|
|
|
114
130
|
/**
|
|
115
131
|
* @param {string} prop
|
|
@@ -415,16 +431,13 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
415
431
|
* @return {Array<SankeyParsedData>}
|
|
416
432
|
*/
|
|
417
433
|
parseObjectData(meta, data, start, count) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return [];
|
|
421
|
-
}
|
|
422
|
-
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}));
|
|
423
436
|
const {xScale, yScale} = meta;
|
|
424
437
|
const parsed = []; /* Array<SankeyParsedData> */
|
|
425
|
-
const nodes =
|
|
438
|
+
const nodes = this._nodes = buildNodesFromRawData(sankeyData);
|
|
426
439
|
/* getDataset() => SankeyControllerDatasetOptions */
|
|
427
|
-
const {column, priority, size} =
|
|
440
|
+
const {column, priority, size} = this.getDataset();
|
|
428
441
|
if (priority) {
|
|
429
442
|
for (const node of nodes.values()) {
|
|
430
443
|
if (node.key in priority) {
|
|
@@ -441,13 +454,13 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
441
454
|
}
|
|
442
455
|
}
|
|
443
456
|
|
|
444
|
-
const {maxX, maxY} = layout(nodes,
|
|
457
|
+
const {maxX, maxY} = layout(nodes, sankeyData, !!priority, validateSizeValue(size));
|
|
445
458
|
|
|
446
|
-
|
|
447
|
-
|
|
459
|
+
this._maxX = maxX;
|
|
460
|
+
this._maxY = maxY;
|
|
448
461
|
|
|
449
|
-
for (let i = 0, ilen =
|
|
450
|
-
const dataPoint =
|
|
462
|
+
for (let i = 0, ilen = sankeyData.length; i < ilen; ++i) {
|
|
463
|
+
const dataPoint = sankeyData[i];
|
|
451
464
|
const from = nodes.get(dataPoint.from);
|
|
452
465
|
const to = nodes.get(dataPoint.to);
|
|
453
466
|
const fromY = from.y + getAddY(from.to, dataPoint.to, i);
|
|
@@ -468,18 +481,16 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
468
481
|
}
|
|
469
482
|
|
|
470
483
|
getMinMax(scale) {
|
|
471
|
-
const me = this;
|
|
472
484
|
return {
|
|
473
485
|
min: 0,
|
|
474
|
-
max: scale === this._cachedMeta.xScale ? this._maxX :
|
|
486
|
+
max: scale === this._cachedMeta.xScale ? this._maxX : this._maxY
|
|
475
487
|
};
|
|
476
488
|
}
|
|
477
489
|
|
|
478
490
|
update(mode) {
|
|
479
|
-
const
|
|
480
|
-
const meta = me._cachedMeta;
|
|
491
|
+
const {data} = this._cachedMeta;
|
|
481
492
|
|
|
482
|
-
|
|
493
|
+
this.updateElements(data, 0, data.length, mode);
|
|
483
494
|
}
|
|
484
495
|
|
|
485
496
|
/**
|
|
@@ -489,20 +500,19 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
489
500
|
* @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
|
|
490
501
|
*/
|
|
491
502
|
updateElements(elems, start, count, mode) {
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
const
|
|
495
|
-
const
|
|
496
|
-
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();
|
|
497
507
|
const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1) / 2 + 0.5;
|
|
498
508
|
const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
|
|
499
509
|
|
|
500
510
|
for (let i = start; i < start + count; i++) {
|
|
501
511
|
/* getParsed(idx: number) => SankeyParsedData */
|
|
502
|
-
const parsed =
|
|
512
|
+
const parsed = this.getParsed(i);
|
|
503
513
|
const custom = parsed._custom;
|
|
504
514
|
const y = yScale.getPixelForValue(parsed.y);
|
|
505
|
-
|
|
515
|
+
this.updateElement(
|
|
506
516
|
elems[i],
|
|
507
517
|
i,
|
|
508
518
|
{
|
|
@@ -514,27 +524,26 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
514
524
|
to: custom.to,
|
|
515
525
|
progress: mode === 'reset' ? 0 : 1,
|
|
516
526
|
height: Math.abs(yScale.getPixelForValue(parsed.y + custom.height) - y),
|
|
517
|
-
options:
|
|
527
|
+
options: this.resolveDataElementOptions(i, mode)
|
|
518
528
|
},
|
|
519
529
|
mode);
|
|
520
530
|
}
|
|
521
531
|
|
|
522
|
-
|
|
532
|
+
this.updateSharedOptions(sharedOptions, mode);
|
|
523
533
|
}
|
|
524
534
|
|
|
525
535
|
_drawLabels() {
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
const
|
|
529
|
-
const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
|
|
536
|
+
const ctx = this._ctx;
|
|
537
|
+
const nodes = this._nodes || new Map();
|
|
538
|
+
const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
|
|
530
539
|
const size = validateSizeValue(dataset.size);
|
|
531
540
|
const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
|
|
532
541
|
const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
|
|
533
542
|
const labels = dataset.labels;
|
|
534
|
-
const {xScale, yScale} =
|
|
543
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
535
544
|
|
|
536
545
|
ctx.save();
|
|
537
|
-
const chartArea =
|
|
546
|
+
const chartArea = this.chart.chartArea;
|
|
538
547
|
for (const node of nodes.values()) {
|
|
539
548
|
const x = xScale.getPixelForValue(node.x);
|
|
540
549
|
const y = yScale.getPixelForValue(node.y);
|
|
@@ -566,13 +575,12 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
566
575
|
* @private
|
|
567
576
|
*/
|
|
568
577
|
_drawLabel(label, y, height, ctx, textX) {
|
|
569
|
-
const
|
|
570
|
-
const font = helpers.toFont(me.options.font, me.chart.options.font);
|
|
578
|
+
const font = helpers.toFont(this.options.font, this.chart.options.font);
|
|
571
579
|
const lines = helpers.isNullOrUndef(label) ? [] : toTextLines(label);
|
|
572
580
|
const linesLength = lines.length;
|
|
573
581
|
const middle = y + height / 2;
|
|
574
582
|
const textHeight = font.lineHeight;
|
|
575
|
-
const padding = helpers.valueOrDefault(
|
|
583
|
+
const padding = helpers.valueOrDefault(this.options.padding, textHeight / 2);
|
|
576
584
|
|
|
577
585
|
ctx.font = font.string;
|
|
578
586
|
|
|
@@ -587,12 +595,11 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
587
595
|
}
|
|
588
596
|
|
|
589
597
|
_drawNodes() {
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
|
|
598
|
+
const ctx = this._ctx;
|
|
599
|
+
const nodes = this._nodes || new Map();
|
|
600
|
+
const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
|
|
594
601
|
const size = validateSizeValue(dataset.size);
|
|
595
|
-
const {xScale, yScale} =
|
|
602
|
+
const {xScale, yScale} = this._cachedMeta;
|
|
596
603
|
const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
|
|
597
604
|
const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
|
|
598
605
|
|
|
@@ -619,9 +626,8 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
619
626
|
* That's where the drawing process happens
|
|
620
627
|
*/
|
|
621
628
|
draw() {
|
|
622
|
-
const
|
|
623
|
-
const
|
|
624
|
-
const data = me.getMeta().data || []; /* Array<Flow> */
|
|
629
|
+
const ctx = this._ctx;
|
|
630
|
+
const data = this.getMeta().data || []; /* Array<Flow> */
|
|
625
631
|
|
|
626
632
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
627
633
|
const flow = data[i]; /* Flow at index i */
|
|
@@ -630,7 +636,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
630
636
|
}
|
|
631
637
|
|
|
632
638
|
/* draw SankeyNodes on the canvas */
|
|
633
|
-
|
|
639
|
+
this._drawNodes();
|
|
634
640
|
|
|
635
641
|
/* draw Flow elements on the canvas */
|
|
636
642
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
@@ -638,7 +644,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
638
644
|
}
|
|
639
645
|
|
|
640
646
|
/* draw labels (for SankeyNodes) on the canvas */
|
|
641
|
-
|
|
647
|
+
this._drawLabels();
|
|
642
648
|
}
|
|
643
649
|
}
|
|
644
650
|
|
|
@@ -689,7 +695,6 @@ SankeyController.overrides = {
|
|
|
689
695
|
intersect: true
|
|
690
696
|
},
|
|
691
697
|
datasets: {
|
|
692
|
-
color: () => '#efefef',
|
|
693
698
|
clip: false,
|
|
694
699
|
parsing: true
|
|
695
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,i=(t,e)=>t.reduce(((t,o)=>t+o.node[e].length+i(o.node[e],e)),0),s=t=>(e,o)=>i(e.node[t],t)-i(o.node[t],t)||e.node[t].length-o.node[t].length;function l(t,e){t.from.sort(s("from"));for(const o of t.from){const t=o.node;r(t.y)||(t.y=e,l(t,e)),e=Math.max(t.y+t.out,e)}return e}function c(t,e){t.to.sort(s("to"));for(const o of t.to){const t=o.node;r(t.y)||(t.y=e,c(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 f(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=l(o,0),a=c(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 f=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 f>=d?(a.forEach((t=>{f=h(t,f),f=Math.max(f+t.out,c(t,f))})),i.forEach((t=>{d=h(t,d),d=Math.max(d+t.in,c(t,d))}))):(i.forEach((t=>{d=h(t,d),d=Math.max(d+t.in,c(t,d))})),a.forEach((t=>{f=h(t,f),f=Math.max(f+t.out,c(t,f))}))),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,l(e,o)),o=Math.max(o+e.out,c(e,o)),u=Math.max(u,o)})),Math.max(f,d,u)}(t,e);return Math.max(n,a,i)}function d(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):f(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 u(t,e,o){for(const r of t)if(r.key===e&&r.index===o)return r.addY;return 0}class x 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:h,priority:f,size:x}=a.getDataset();if(f)for(const t of c.values())t.key in f&&(t.priority=f[t.key]);if(h)for(const t of c.values())t.key in h&&(t.column=!0,t.x=h[t.key]);const{maxX:y,maxY:p}=d(c,e,!!f,o(x));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+u(r.to,o.to,t),h=n.y+u(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(h,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),h=a.getDataset(),f=e.valueOrDefault(h.borderWidth,1)/2+.5,d=e.valueOrDefault(h.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+f,y:l,x2:i.getPixelForValue(r.x)-f,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:h,yScale:f}=t._cachedMeta;r.save();const d=t.chart.chartArea;for(const t of n.values()){const e=h.getPixelForValue(t.x),o=f.getPixelForValue(t.y),n=Math[i](t.in||t.out,t.out||t.in),u=Math.abs(f.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,h=o+r/2,f=s.lineHeight,d=e.valueOrDefault(i.options.padding,f/2);if(n.font=s.string,c>1){const t=h-f*c/2+d;for(let e=0;e<c;e++)n.fillText(l[e],a,t+e*f)}else n.fillText(t,a,h)}_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),h=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,h,a),r.fillRect(e,o,h,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()}}x.id="sankey",x.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"}}}}},x.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 y=(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}},p=(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}=y(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}=y(r,n,a,i),h=(t-r)/(a-r),f={x:a,y:i},d=p({x:r,y:n},l,h),u=p(l,c,h),x=p(c,f,h),g=p(d,u,h),m=p(u,x,h),M=p(g,m,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(x,g)}));
|
|
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)}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chartjs-chart-sankey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"karma-firefox-launcher": "^2.1.0",
|
|
60
60
|
"karma-jasmine": "^4.0.1",
|
|
61
61
|
"karma-jasmine-html-reporter": "^1.5.4",
|
|
62
|
-
"karma-rollup-preprocessor": "
|
|
62
|
+
"karma-rollup-preprocessor": "7.0.7",
|
|
63
63
|
"karma-spec-reporter": "^0.0.33",
|
|
64
64
|
"rollup": "^2.42.1",
|
|
65
65
|
"rollup-plugin-istanbul": "^3.0.0",
|
package/types/index.esm.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
ChartComponent,
|
|
4
4
|
DatasetController,
|
|
5
5
|
Element,
|
|
6
|
+
VisualElement,
|
|
6
7
|
FontSpec
|
|
7
8
|
} from 'chart.js';
|
|
9
|
+
import { AnyObject } from 'chart.js/types/basic';
|
|
8
10
|
|
|
9
11
|
declare module 'chart.js' {
|
|
10
12
|
|
|
@@ -78,28 +80,28 @@ declare module 'chart.js' {
|
|
|
78
80
|
datasetOptions: SankeyControllerDatasetOptions;
|
|
79
81
|
defaultDataPoint: SankeyDataPoint;
|
|
80
82
|
parsedDataType: SankeyParsedData;
|
|
81
|
-
metaExtensions:
|
|
83
|
+
metaExtensions: AnyObject
|
|
82
84
|
/* TODO: define sankey chart options */
|
|
83
|
-
chartOptions:
|
|
85
|
+
chartOptions: AnyObject;
|
|
84
86
|
scales: keyof CartesianScaleTypeRegistry;
|
|
85
87
|
};
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
export interface FlowOptions {
|
|
90
92
|
colorMode: 'gradient' | 'from' | 'to';
|
|
91
93
|
colorFrom: string
|
|
92
94
|
colorTo: string
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
|
|
97
|
+
export interface FlowConfig {
|
|
96
98
|
x: number;
|
|
97
99
|
y: number;
|
|
98
100
|
x2: number;
|
|
99
101
|
y2: number;
|
|
100
102
|
height: number;
|
|
101
103
|
options: FlowOptions
|
|
102
|
-
}
|
|
104
|
+
}
|
|
103
105
|
|
|
104
106
|
export type SankeyController = DatasetController
|
|
105
107
|
export const SankeyController: ChartComponent & {
|
|
@@ -107,7 +109,11 @@ export const SankeyController: ChartComponent & {
|
|
|
107
109
|
new(chart: Chart, datasetIndex: number): SankeyController;
|
|
108
110
|
};
|
|
109
111
|
|
|
110
|
-
export
|
|
112
|
+
export interface Flow<
|
|
113
|
+
T extends FlowConfig = FlowConfig,
|
|
114
|
+
O extends FlowOptions = FlowOptions
|
|
115
|
+
> extends Element<T, O>, VisualElement {}
|
|
116
|
+
|
|
111
117
|
export const Flow: ChartComponent & {
|
|
112
118
|
prototype: Flow;
|
|
113
119
|
new(cfg: FlowConfig): Flow;
|