chartjs-chart-sankey 0.8.1 → 0.9.1
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 +19 -5
- package/dist/chartjs-chart-sankey.esm.js +118 -85
- package/dist/chartjs-chart-sankey.js +117 -84
- package/dist/chartjs-chart-sankey.min.js +2 -2
- package/package.json +14 -14
- package/types/index.esm.d.ts +15 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# chartjs-chart-sankey
|
|
2
2
|
|
|
3
|
-
[Chart.js](https://www.chartjs.org/)
|
|
3
|
+
[Chart.js](https://www.chartjs.org/) **^3.3** module for creating sankey diagrams
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/chartjs-chart-sankey)
|
|
6
6
|
[](https://github.com/kurkle/chartjs-chart-sankey/releases/latest)
|
|
@@ -49,6 +49,15 @@ const chart = new Chart(ctx, {
|
|
|
49
49
|
Example:
|
|
50
50
|
|
|
51
51
|
```js
|
|
52
|
+
const colors = {
|
|
53
|
+
a: 'red',
|
|
54
|
+
b: 'green',
|
|
55
|
+
c: 'blue',
|
|
56
|
+
d: 'gray'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const getColor = (key) => colors[key];
|
|
60
|
+
|
|
52
61
|
const chart = new Chart(ctx, {
|
|
53
62
|
type: 'sankey',
|
|
54
63
|
data: {
|
|
@@ -57,7 +66,8 @@ const chart = new Chart(ctx, {
|
|
|
57
66
|
data: [
|
|
58
67
|
{from: 'a', to: 'b', flow: 10},
|
|
59
68
|
{from: 'a', to: 'c', flow: 5},
|
|
60
|
-
{from: 'b', to: 'c', flow: 10}
|
|
69
|
+
{from: 'b', to: 'c', flow: 10},
|
|
70
|
+
{from: 'd', to: 'c', flow: 7}
|
|
61
71
|
],
|
|
62
72
|
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex].from),
|
|
63
73
|
colorTo: (c) => getColor(c.dataset.data[c.dataIndex].to),
|
|
@@ -66,13 +76,17 @@ const chart = new Chart(ctx, {
|
|
|
66
76
|
labels: {
|
|
67
77
|
a: 'Label A',
|
|
68
78
|
b: 'Label B',
|
|
69
|
-
c: 'Label C'
|
|
79
|
+
c: 'Label C',
|
|
80
|
+
d: 'Label D'
|
|
70
81
|
},
|
|
71
82
|
/* optional priority */
|
|
72
83
|
priority: {
|
|
73
|
-
a: 0,
|
|
74
84
|
b: 1,
|
|
75
|
-
|
|
85
|
+
d: 0
|
|
86
|
+
},
|
|
87
|
+
/* optional column overrides */
|
|
88
|
+
column: {
|
|
89
|
+
d: 1
|
|
76
90
|
},
|
|
77
91
|
size: 'max', // or 'min' if flow overlap is preferred
|
|
78
92
|
}]
|
|
@@ -1,11 +1,50 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.9.1
|
|
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 { valueOrDefault, toFont,
|
|
8
|
+
import { isArray, isNullOrUndef, valueOrDefault, toFont, color } from 'chart.js/helpers';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string | Array<string>} raw
|
|
12
|
+
* @return {Array<string>}
|
|
13
|
+
*/
|
|
14
|
+
function toTextLines(raw) {
|
|
15
|
+
const lines = [];
|
|
16
|
+
const inputs = isArray(raw) ? raw : isNullOrUndef(raw) ? [] : [raw];
|
|
17
|
+
|
|
18
|
+
while (inputs.length) {
|
|
19
|
+
const input = inputs.pop();
|
|
20
|
+
if (typeof input === 'string') {
|
|
21
|
+
lines.unshift.apply(lines, input.split('\n'));
|
|
22
|
+
} else if (Array.isArray(input)) {
|
|
23
|
+
inputs.push.apply(inputs, input);
|
|
24
|
+
} else if (!isNullOrUndef(inputs)) {
|
|
25
|
+
lines.unshift('' + input);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return lines;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {any} size
|
|
34
|
+
* @return {'min' | 'max'}
|
|
35
|
+
*/
|
|
36
|
+
function validateSizeValue(size) {
|
|
37
|
+
if (!size || ['min', 'max'].indexOf(size) === -1) {
|
|
38
|
+
return 'max';
|
|
39
|
+
}
|
|
40
|
+
return size;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param x {any}
|
|
45
|
+
* @return {boolean}
|
|
46
|
+
*/
|
|
47
|
+
const defined = x => x !== undefined;
|
|
9
48
|
|
|
10
49
|
/**
|
|
11
50
|
* @param {Map<string, SankeyNode>} nodes
|
|
@@ -13,15 +52,18 @@ import { valueOrDefault, toFont, isNullOrUndef, color } from 'chart.js/helpers';
|
|
|
13
52
|
* @return {number}
|
|
14
53
|
*/
|
|
15
54
|
function calculateX(nodes, data) {
|
|
16
|
-
const to = new Set(data.map(
|
|
17
|
-
const from = new Set(data.map(
|
|
55
|
+
const to = new Set(data.map(dataPoint => dataPoint.to));
|
|
56
|
+
const from = new Set(data.map(dataPoint => dataPoint.from));
|
|
18
57
|
const keys = new Set([...nodes.keys()]);
|
|
19
58
|
let x = 0;
|
|
20
59
|
while (keys.size) {
|
|
21
60
|
const column = nextColumn([...keys], to);
|
|
22
|
-
for (
|
|
23
|
-
nodes.get(
|
|
24
|
-
|
|
61
|
+
for (const key of column) {
|
|
62
|
+
const node = nodes.get(key);
|
|
63
|
+
if (!defined(node.x)) {
|
|
64
|
+
node.x = x;
|
|
65
|
+
}
|
|
66
|
+
keys.delete(key);
|
|
25
67
|
}
|
|
26
68
|
if (keys.size) {
|
|
27
69
|
to.clear();
|
|
@@ -32,7 +74,11 @@ function calculateX(nodes, data) {
|
|
|
32
74
|
[...nodes.keys()]
|
|
33
75
|
.filter(key => !from.has(key))
|
|
34
76
|
.forEach(key => {
|
|
35
|
-
nodes.get(key)
|
|
77
|
+
const node = nodes.get(key);
|
|
78
|
+
// Only move the node to right edge, if it's column is not defined
|
|
79
|
+
if (!node.column) {
|
|
80
|
+
node.x = x;
|
|
81
|
+
}
|
|
36
82
|
});
|
|
37
83
|
|
|
38
84
|
return x;
|
|
@@ -48,12 +94,6 @@ function nextColumn(keys, to) {
|
|
|
48
94
|
return columnsNotInTo.length ? columnsNotInTo : keys.slice(0, 1);
|
|
49
95
|
}
|
|
50
96
|
|
|
51
|
-
/**
|
|
52
|
-
* @param x {any}
|
|
53
|
-
* @return {boolean}
|
|
54
|
-
*/
|
|
55
|
-
const defined = x => x !== undefined;
|
|
56
|
-
|
|
57
97
|
/**
|
|
58
98
|
* @param {SankeyNode} a
|
|
59
99
|
* @param {SankeyNode} b
|
|
@@ -61,12 +101,28 @@ const defined = x => x !== undefined;
|
|
|
61
101
|
*/
|
|
62
102
|
const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
|
|
63
103
|
|
|
104
|
+
let prevCountId = -1;
|
|
105
|
+
function getCountId() {
|
|
106
|
+
prevCountId = prevCountId < 100 ? prevCountId + 1 : 0;
|
|
107
|
+
return prevCountId;
|
|
108
|
+
}
|
|
109
|
+
|
|
64
110
|
/**
|
|
65
111
|
* @param {Array<FromToElement>} list
|
|
66
112
|
* @param {string} prop
|
|
67
113
|
* @return {number}
|
|
68
114
|
*/
|
|
69
|
-
|
|
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
|
+
}
|
|
70
126
|
|
|
71
127
|
/**
|
|
72
128
|
* @param {string} prop
|
|
@@ -74,26 +130,21 @@ const nodeCount = (list, prop) => list.reduce((acc, cur) => acc + cur.node[prop]
|
|
|
74
130
|
*/
|
|
75
131
|
const flowByNodeCount = (prop) => (a, b) => (nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop)) || (a.node[prop].length - b.node[prop].length);
|
|
76
132
|
|
|
77
|
-
/**
|
|
78
|
-
* @param {Array<SankeyNode>} nodeArray
|
|
79
|
-
* @return {SankeyNode}
|
|
80
|
-
*/
|
|
81
|
-
const findLargestNode = (nodeArray) => nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out))[0];
|
|
82
|
-
|
|
83
133
|
/**
|
|
84
134
|
* @param {SankeyNode} node
|
|
85
135
|
* @param {number} y
|
|
86
136
|
* @return {number}
|
|
87
137
|
*/
|
|
88
138
|
function processFrom(node, y) {
|
|
89
|
-
node.from.sort(flowByNodeCount('from'))
|
|
139
|
+
node.from.sort(flowByNodeCount('from'));
|
|
140
|
+
for (const flow of node.from) {
|
|
90
141
|
const n = flow.node;
|
|
91
142
|
if (!defined(n.y)) {
|
|
92
143
|
n.y = y;
|
|
93
144
|
processFrom(n, y);
|
|
94
145
|
}
|
|
95
146
|
y = Math.max(n.y + n.out, y);
|
|
96
|
-
}
|
|
147
|
+
}
|
|
97
148
|
return y;
|
|
98
149
|
}
|
|
99
150
|
|
|
@@ -103,14 +154,15 @@ function processFrom(node, y) {
|
|
|
103
154
|
* @return {number}
|
|
104
155
|
*/
|
|
105
156
|
function processTo(node, y) {
|
|
106
|
-
node.to.sort(flowByNodeCount('to'))
|
|
157
|
+
node.to.sort(flowByNodeCount('to'));
|
|
158
|
+
for (const flow of node.to) {
|
|
107
159
|
const n = flow.node;
|
|
108
160
|
if (!defined(n.y)) {
|
|
109
161
|
n.y = y;
|
|
110
162
|
processTo(n, y);
|
|
111
163
|
}
|
|
112
164
|
y = Math.max(n.y + n.in, y);
|
|
113
|
-
}
|
|
165
|
+
}
|
|
114
166
|
return y;
|
|
115
167
|
}
|
|
116
168
|
|
|
@@ -182,7 +234,8 @@ function processRest(nodeArray, maxX) {
|
|
|
182
234
|
* @return {number}
|
|
183
235
|
*/
|
|
184
236
|
function calculateY(nodeArray, maxX) {
|
|
185
|
-
|
|
237
|
+
nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out));
|
|
238
|
+
const start = nodeArray[0];
|
|
186
239
|
start.y = 0;
|
|
187
240
|
const left = processFrom(start, 0);
|
|
188
241
|
const right = processTo(start, 0);
|
|
@@ -222,7 +275,8 @@ function addPadding(nodeArray, padding) {
|
|
|
222
275
|
let prev = 0;
|
|
223
276
|
let maxY = 0;
|
|
224
277
|
const rows = [];
|
|
225
|
-
nodeArray.sort(nodeByXY)
|
|
278
|
+
nodeArray.sort(nodeByXY);
|
|
279
|
+
for (const node of nodeArray) {
|
|
226
280
|
if (node.y) {
|
|
227
281
|
if (node.x === 0) {
|
|
228
282
|
rows.push(node.y);
|
|
@@ -243,7 +297,7 @@ function addPadding(nodeArray, padding) {
|
|
|
243
297
|
i++;
|
|
244
298
|
}
|
|
245
299
|
maxY = Math.max(maxY, node.y + Math.max(node.in, node.out));
|
|
246
|
-
}
|
|
300
|
+
}
|
|
247
301
|
return maxY;
|
|
248
302
|
}
|
|
249
303
|
|
|
@@ -365,17 +419,6 @@ function getAddY(arr, key, index) {
|
|
|
365
419
|
return 0;
|
|
366
420
|
}
|
|
367
421
|
|
|
368
|
-
/**
|
|
369
|
-
* @param {any} size
|
|
370
|
-
* @return {'min' | 'max'}
|
|
371
|
-
*/
|
|
372
|
-
function validateSizeValue(size) {
|
|
373
|
-
if (!size || ['min', 'max'].indexOf(size) === -1) {
|
|
374
|
-
return 'max';
|
|
375
|
-
}
|
|
376
|
-
return size;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
422
|
class SankeyController extends DatasetController {
|
|
380
423
|
/**
|
|
381
424
|
* @param {ChartMeta<Flow, Element>} meta
|
|
@@ -394,7 +437,7 @@ class SankeyController extends DatasetController {
|
|
|
394
437
|
const parsed = []; /* Array<SankeyParsedData> */
|
|
395
438
|
const nodes = me._nodes = buildNodesFromRawData(data);
|
|
396
439
|
/* getDataset() => SankeyControllerDatasetOptions */
|
|
397
|
-
const {priority, size} = me.getDataset();
|
|
440
|
+
const {column, priority, size} = me.getDataset();
|
|
398
441
|
if (priority) {
|
|
399
442
|
for (const node of nodes.values()) {
|
|
400
443
|
if (node.key in priority) {
|
|
@@ -402,17 +445,24 @@ class SankeyController extends DatasetController {
|
|
|
402
445
|
}
|
|
403
446
|
}
|
|
404
447
|
}
|
|
448
|
+
if (column) {
|
|
449
|
+
for (const node of nodes.values()) {
|
|
450
|
+
if (node.key in column) {
|
|
451
|
+
node.column = true;
|
|
452
|
+
node.x = column[node.key];
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
405
456
|
|
|
406
457
|
const {maxX, maxY} = layout(nodes, data, !!priority, validateSizeValue(size));
|
|
407
458
|
|
|
408
459
|
me._maxX = maxX;
|
|
409
460
|
me._maxY = maxY;
|
|
410
461
|
|
|
411
|
-
/* loop over raw data elements {SankeyDataPoint} */
|
|
412
462
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
413
|
-
const dataPoint = data[i];
|
|
414
|
-
const from = nodes.get(dataPoint.from);
|
|
415
|
-
const to = nodes.get(dataPoint.to);
|
|
463
|
+
const dataPoint = data[i];
|
|
464
|
+
const from = nodes.get(dataPoint.from);
|
|
465
|
+
const to = nodes.get(dataPoint.to);
|
|
416
466
|
const fromY = from.y + getAddY(from.to, dataPoint.to, i);
|
|
417
467
|
const toY = to.y + getAddY(to.from, dataPoint.from, i);
|
|
418
468
|
parsed.push({
|
|
@@ -531,7 +581,7 @@ class SankeyController extends DatasetController {
|
|
|
531
581
|
_drawLabel(label, y, height, ctx, textX) {
|
|
532
582
|
const me = this;
|
|
533
583
|
const font = toFont(me.options.font, me.chart.options.font);
|
|
534
|
-
const lines = isNullOrUndef(label) ? [] :
|
|
584
|
+
const lines = isNullOrUndef(label) ? [] : toTextLines(label);
|
|
535
585
|
const linesLength = lines.length;
|
|
536
586
|
const middle = y + height / 2;
|
|
537
587
|
const textHeight = font.lineHeight;
|
|
@@ -549,30 +599,6 @@ class SankeyController extends DatasetController {
|
|
|
549
599
|
}
|
|
550
600
|
}
|
|
551
601
|
|
|
552
|
-
/**
|
|
553
|
-
* @param {string | Array<string>} inputs
|
|
554
|
-
* @return {Array<string>}
|
|
555
|
-
* @todo move this in Chart.helpers.toTextLines
|
|
556
|
-
*/
|
|
557
|
-
toTextLines(inputs) {
|
|
558
|
-
let lines = [];
|
|
559
|
-
let input;
|
|
560
|
-
|
|
561
|
-
inputs = [].concat(inputs);
|
|
562
|
-
while (inputs.length) {
|
|
563
|
-
input = inputs.pop();
|
|
564
|
-
if (typeof input === 'string') {
|
|
565
|
-
lines.unshift.apply(lines, input.split('\n'));
|
|
566
|
-
} else if (Array.isArray(input)) {
|
|
567
|
-
inputs.push.apply(inputs, input);
|
|
568
|
-
} else if (!isNullOrUndef(inputs)) {
|
|
569
|
-
lines.unshift('' + input);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
return lines;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
602
|
_drawNodes() {
|
|
577
603
|
const me = this;
|
|
578
604
|
const ctx = me._ctx;
|
|
@@ -752,6 +778,28 @@ const controlPoints = (x, y, x2, y2) => x < x2
|
|
|
752
778
|
*/
|
|
753
779
|
const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
|
|
754
780
|
|
|
781
|
+
/**
|
|
782
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
783
|
+
* @param {Flow} flow
|
|
784
|
+
*/
|
|
785
|
+
function setStyle(ctx, {x, x2, options}) {
|
|
786
|
+
let fill;
|
|
787
|
+
|
|
788
|
+
if (options.colorMode === 'from') {
|
|
789
|
+
fill = color(options.colorFrom).alpha(0.5).rgbString();
|
|
790
|
+
} else if (options.colorMode === 'to') {
|
|
791
|
+
fill = color(options.colorTo).alpha(0.5).rgbString();
|
|
792
|
+
} else {
|
|
793
|
+
fill = ctx.createLinearGradient(x, 0, x2, 0);
|
|
794
|
+
fill.addColorStop(0, color(options.colorFrom).alpha(0.5).rgbString());
|
|
795
|
+
fill.addColorStop(1, color(options.colorTo).alpha(0.5).rgbString());
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
ctx.fillStyle = fill;
|
|
799
|
+
ctx.strokeStyle = fill;
|
|
800
|
+
ctx.lineWidth = 0.5;
|
|
801
|
+
}
|
|
802
|
+
|
|
755
803
|
class Flow extends Element {
|
|
756
804
|
|
|
757
805
|
/**
|
|
@@ -779,7 +827,6 @@ class Flow extends Element {
|
|
|
779
827
|
const me = this;
|
|
780
828
|
const {x, x2, y, y2, height, progress} = me;
|
|
781
829
|
const {cp1, cp2} = controlPoints(x, y, x2, y2);
|
|
782
|
-
const options = me.options;
|
|
783
830
|
|
|
784
831
|
if (progress === 0) {
|
|
785
832
|
return;
|
|
@@ -791,21 +838,7 @@ class Flow extends Element {
|
|
|
791
838
|
ctx.clip();
|
|
792
839
|
}
|
|
793
840
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (options.colorMode === 'from') {
|
|
797
|
-
fill = color(options.colorFrom).alpha(0.5).rgbString();
|
|
798
|
-
} else if (options.colorMode === 'to') {
|
|
799
|
-
fill = color(options.colorTo).alpha(0.5).rgbString();
|
|
800
|
-
} else {
|
|
801
|
-
fill = ctx.createLinearGradient(x, 0, x2, 0);
|
|
802
|
-
fill.addColorStop(0, color(options.colorFrom).alpha(0.5).rgbString());
|
|
803
|
-
fill.addColorStop(1, color(options.colorTo).alpha(0.5).rgbString());
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
ctx.fillStyle = fill;
|
|
807
|
-
ctx.strokeStyle = fill;
|
|
808
|
-
ctx.lineWidth = 0.5;
|
|
841
|
+
setStyle(ctx, me);
|
|
809
842
|
|
|
810
843
|
ctx.beginPath();
|
|
811
844
|
ctx.moveTo(x, y);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.9.1
|
|
3
3
|
* https://github.com/kurkle/chartjs-chart-sankey#readme
|
|
4
4
|
* (c) 2022 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
@@ -10,21 +10,63 @@ typeof define === 'function' && define.amd ? define(['chart.js', 'chart.js/helpe
|
|
|
10
10
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Chart, global.Chart.helpers));
|
|
11
11
|
})(this, (function (chart_js, helpers) { 'use strict';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @param {string | Array<string>} raw
|
|
15
|
+
* @return {Array<string>}
|
|
16
|
+
*/
|
|
17
|
+
function toTextLines(raw) {
|
|
18
|
+
const lines = [];
|
|
19
|
+
const inputs = helpers.isArray(raw) ? raw : helpers.isNullOrUndef(raw) ? [] : [raw];
|
|
20
|
+
|
|
21
|
+
while (inputs.length) {
|
|
22
|
+
const input = inputs.pop();
|
|
23
|
+
if (typeof input === 'string') {
|
|
24
|
+
lines.unshift.apply(lines, input.split('\n'));
|
|
25
|
+
} else if (Array.isArray(input)) {
|
|
26
|
+
inputs.push.apply(inputs, input);
|
|
27
|
+
} else if (!helpers.isNullOrUndef(inputs)) {
|
|
28
|
+
lines.unshift('' + input);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return lines;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {any} size
|
|
37
|
+
* @return {'min' | 'max'}
|
|
38
|
+
*/
|
|
39
|
+
function validateSizeValue(size) {
|
|
40
|
+
if (!size || ['min', 'max'].indexOf(size) === -1) {
|
|
41
|
+
return 'max';
|
|
42
|
+
}
|
|
43
|
+
return size;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param x {any}
|
|
48
|
+
* @return {boolean}
|
|
49
|
+
*/
|
|
50
|
+
const defined = x => x !== undefined;
|
|
51
|
+
|
|
13
52
|
/**
|
|
14
53
|
* @param {Map<string, SankeyNode>} nodes
|
|
15
54
|
* @param {Array<SankeyDataPoint>} data
|
|
16
55
|
* @return {number}
|
|
17
56
|
*/
|
|
18
57
|
function calculateX(nodes, data) {
|
|
19
|
-
const to = new Set(data.map(
|
|
20
|
-
const from = new Set(data.map(
|
|
58
|
+
const to = new Set(data.map(dataPoint => dataPoint.to));
|
|
59
|
+
const from = new Set(data.map(dataPoint => dataPoint.from));
|
|
21
60
|
const keys = new Set([...nodes.keys()]);
|
|
22
61
|
let x = 0;
|
|
23
62
|
while (keys.size) {
|
|
24
63
|
const column = nextColumn([...keys], to);
|
|
25
|
-
for (
|
|
26
|
-
nodes.get(
|
|
27
|
-
|
|
64
|
+
for (const key of column) {
|
|
65
|
+
const node = nodes.get(key);
|
|
66
|
+
if (!defined(node.x)) {
|
|
67
|
+
node.x = x;
|
|
68
|
+
}
|
|
69
|
+
keys.delete(key);
|
|
28
70
|
}
|
|
29
71
|
if (keys.size) {
|
|
30
72
|
to.clear();
|
|
@@ -35,7 +77,11 @@ function calculateX(nodes, data) {
|
|
|
35
77
|
[...nodes.keys()]
|
|
36
78
|
.filter(key => !from.has(key))
|
|
37
79
|
.forEach(key => {
|
|
38
|
-
nodes.get(key)
|
|
80
|
+
const node = nodes.get(key);
|
|
81
|
+
// Only move the node to right edge, if it's column is not defined
|
|
82
|
+
if (!node.column) {
|
|
83
|
+
node.x = x;
|
|
84
|
+
}
|
|
39
85
|
});
|
|
40
86
|
|
|
41
87
|
return x;
|
|
@@ -51,12 +97,6 @@ function nextColumn(keys, to) {
|
|
|
51
97
|
return columnsNotInTo.length ? columnsNotInTo : keys.slice(0, 1);
|
|
52
98
|
}
|
|
53
99
|
|
|
54
|
-
/**
|
|
55
|
-
* @param x {any}
|
|
56
|
-
* @return {boolean}
|
|
57
|
-
*/
|
|
58
|
-
const defined = x => x !== undefined;
|
|
59
|
-
|
|
60
100
|
/**
|
|
61
101
|
* @param {SankeyNode} a
|
|
62
102
|
* @param {SankeyNode} b
|
|
@@ -64,12 +104,28 @@ const defined = x => x !== undefined;
|
|
|
64
104
|
*/
|
|
65
105
|
const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
|
|
66
106
|
|
|
107
|
+
let prevCountId = -1;
|
|
108
|
+
function getCountId() {
|
|
109
|
+
prevCountId = prevCountId < 100 ? prevCountId + 1 : 0;
|
|
110
|
+
return prevCountId;
|
|
111
|
+
}
|
|
112
|
+
|
|
67
113
|
/**
|
|
68
114
|
* @param {Array<FromToElement>} list
|
|
69
115
|
* @param {string} prop
|
|
70
116
|
* @return {number}
|
|
71
117
|
*/
|
|
72
|
-
|
|
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
|
+
}
|
|
73
129
|
|
|
74
130
|
/**
|
|
75
131
|
* @param {string} prop
|
|
@@ -77,26 +133,21 @@ const nodeCount = (list, prop) => list.reduce((acc, cur) => acc + cur.node[prop]
|
|
|
77
133
|
*/
|
|
78
134
|
const flowByNodeCount = (prop) => (a, b) => (nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop)) || (a.node[prop].length - b.node[prop].length);
|
|
79
135
|
|
|
80
|
-
/**
|
|
81
|
-
* @param {Array<SankeyNode>} nodeArray
|
|
82
|
-
* @return {SankeyNode}
|
|
83
|
-
*/
|
|
84
|
-
const findLargestNode = (nodeArray) => nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out))[0];
|
|
85
|
-
|
|
86
136
|
/**
|
|
87
137
|
* @param {SankeyNode} node
|
|
88
138
|
* @param {number} y
|
|
89
139
|
* @return {number}
|
|
90
140
|
*/
|
|
91
141
|
function processFrom(node, y) {
|
|
92
|
-
node.from.sort(flowByNodeCount('from'))
|
|
142
|
+
node.from.sort(flowByNodeCount('from'));
|
|
143
|
+
for (const flow of node.from) {
|
|
93
144
|
const n = flow.node;
|
|
94
145
|
if (!defined(n.y)) {
|
|
95
146
|
n.y = y;
|
|
96
147
|
processFrom(n, y);
|
|
97
148
|
}
|
|
98
149
|
y = Math.max(n.y + n.out, y);
|
|
99
|
-
}
|
|
150
|
+
}
|
|
100
151
|
return y;
|
|
101
152
|
}
|
|
102
153
|
|
|
@@ -106,14 +157,15 @@ function processFrom(node, y) {
|
|
|
106
157
|
* @return {number}
|
|
107
158
|
*/
|
|
108
159
|
function processTo(node, y) {
|
|
109
|
-
node.to.sort(flowByNodeCount('to'))
|
|
160
|
+
node.to.sort(flowByNodeCount('to'));
|
|
161
|
+
for (const flow of node.to) {
|
|
110
162
|
const n = flow.node;
|
|
111
163
|
if (!defined(n.y)) {
|
|
112
164
|
n.y = y;
|
|
113
165
|
processTo(n, y);
|
|
114
166
|
}
|
|
115
167
|
y = Math.max(n.y + n.in, y);
|
|
116
|
-
}
|
|
168
|
+
}
|
|
117
169
|
return y;
|
|
118
170
|
}
|
|
119
171
|
|
|
@@ -185,7 +237,8 @@ function processRest(nodeArray, maxX) {
|
|
|
185
237
|
* @return {number}
|
|
186
238
|
*/
|
|
187
239
|
function calculateY(nodeArray, maxX) {
|
|
188
|
-
|
|
240
|
+
nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out));
|
|
241
|
+
const start = nodeArray[0];
|
|
189
242
|
start.y = 0;
|
|
190
243
|
const left = processFrom(start, 0);
|
|
191
244
|
const right = processTo(start, 0);
|
|
@@ -225,7 +278,8 @@ function addPadding(nodeArray, padding) {
|
|
|
225
278
|
let prev = 0;
|
|
226
279
|
let maxY = 0;
|
|
227
280
|
const rows = [];
|
|
228
|
-
nodeArray.sort(nodeByXY)
|
|
281
|
+
nodeArray.sort(nodeByXY);
|
|
282
|
+
for (const node of nodeArray) {
|
|
229
283
|
if (node.y) {
|
|
230
284
|
if (node.x === 0) {
|
|
231
285
|
rows.push(node.y);
|
|
@@ -246,7 +300,7 @@ function addPadding(nodeArray, padding) {
|
|
|
246
300
|
i++;
|
|
247
301
|
}
|
|
248
302
|
maxY = Math.max(maxY, node.y + Math.max(node.in, node.out));
|
|
249
|
-
}
|
|
303
|
+
}
|
|
250
304
|
return maxY;
|
|
251
305
|
}
|
|
252
306
|
|
|
@@ -368,17 +422,6 @@ function getAddY(arr, key, index) {
|
|
|
368
422
|
return 0;
|
|
369
423
|
}
|
|
370
424
|
|
|
371
|
-
/**
|
|
372
|
-
* @param {any} size
|
|
373
|
-
* @return {'min' | 'max'}
|
|
374
|
-
*/
|
|
375
|
-
function validateSizeValue(size) {
|
|
376
|
-
if (!size || ['min', 'max'].indexOf(size) === -1) {
|
|
377
|
-
return 'max';
|
|
378
|
-
}
|
|
379
|
-
return size;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
425
|
class SankeyController extends chart_js.DatasetController {
|
|
383
426
|
/**
|
|
384
427
|
* @param {ChartMeta<Flow, Element>} meta
|
|
@@ -397,7 +440,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
397
440
|
const parsed = []; /* Array<SankeyParsedData> */
|
|
398
441
|
const nodes = me._nodes = buildNodesFromRawData(data);
|
|
399
442
|
/* getDataset() => SankeyControllerDatasetOptions */
|
|
400
|
-
const {priority, size} = me.getDataset();
|
|
443
|
+
const {column, priority, size} = me.getDataset();
|
|
401
444
|
if (priority) {
|
|
402
445
|
for (const node of nodes.values()) {
|
|
403
446
|
if (node.key in priority) {
|
|
@@ -405,17 +448,24 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
405
448
|
}
|
|
406
449
|
}
|
|
407
450
|
}
|
|
451
|
+
if (column) {
|
|
452
|
+
for (const node of nodes.values()) {
|
|
453
|
+
if (node.key in column) {
|
|
454
|
+
node.column = true;
|
|
455
|
+
node.x = column[node.key];
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
408
459
|
|
|
409
460
|
const {maxX, maxY} = layout(nodes, data, !!priority, validateSizeValue(size));
|
|
410
461
|
|
|
411
462
|
me._maxX = maxX;
|
|
412
463
|
me._maxY = maxY;
|
|
413
464
|
|
|
414
|
-
/* loop over raw data elements {SankeyDataPoint} */
|
|
415
465
|
for (let i = 0, ilen = data.length; i < ilen; ++i) {
|
|
416
|
-
const dataPoint = data[i];
|
|
417
|
-
const from = nodes.get(dataPoint.from);
|
|
418
|
-
const to = nodes.get(dataPoint.to);
|
|
466
|
+
const dataPoint = data[i];
|
|
467
|
+
const from = nodes.get(dataPoint.from);
|
|
468
|
+
const to = nodes.get(dataPoint.to);
|
|
419
469
|
const fromY = from.y + getAddY(from.to, dataPoint.to, i);
|
|
420
470
|
const toY = to.y + getAddY(to.from, dataPoint.from, i);
|
|
421
471
|
parsed.push({
|
|
@@ -534,7 +584,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
534
584
|
_drawLabel(label, y, height, ctx, textX) {
|
|
535
585
|
const me = this;
|
|
536
586
|
const font = helpers.toFont(me.options.font, me.chart.options.font);
|
|
537
|
-
const lines = helpers.isNullOrUndef(label) ? [] :
|
|
587
|
+
const lines = helpers.isNullOrUndef(label) ? [] : toTextLines(label);
|
|
538
588
|
const linesLength = lines.length;
|
|
539
589
|
const middle = y + height / 2;
|
|
540
590
|
const textHeight = font.lineHeight;
|
|
@@ -552,30 +602,6 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
552
602
|
}
|
|
553
603
|
}
|
|
554
604
|
|
|
555
|
-
/**
|
|
556
|
-
* @param {string | Array<string>} inputs
|
|
557
|
-
* @return {Array<string>}
|
|
558
|
-
* @todo move this in Chart.helpers.toTextLines
|
|
559
|
-
*/
|
|
560
|
-
toTextLines(inputs) {
|
|
561
|
-
let lines = [];
|
|
562
|
-
let input;
|
|
563
|
-
|
|
564
|
-
inputs = [].concat(inputs);
|
|
565
|
-
while (inputs.length) {
|
|
566
|
-
input = inputs.pop();
|
|
567
|
-
if (typeof input === 'string') {
|
|
568
|
-
lines.unshift.apply(lines, input.split('\n'));
|
|
569
|
-
} else if (Array.isArray(input)) {
|
|
570
|
-
inputs.push.apply(inputs, input);
|
|
571
|
-
} else if (!helpers.isNullOrUndef(inputs)) {
|
|
572
|
-
lines.unshift('' + input);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
return lines;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
605
|
_drawNodes() {
|
|
580
606
|
const me = this;
|
|
581
607
|
const ctx = me._ctx;
|
|
@@ -755,6 +781,28 @@ const controlPoints = (x, y, x2, y2) => x < x2
|
|
|
755
781
|
*/
|
|
756
782
|
const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
|
|
757
783
|
|
|
784
|
+
/**
|
|
785
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
786
|
+
* @param {Flow} flow
|
|
787
|
+
*/
|
|
788
|
+
function setStyle(ctx, {x, x2, options}) {
|
|
789
|
+
let fill;
|
|
790
|
+
|
|
791
|
+
if (options.colorMode === 'from') {
|
|
792
|
+
fill = helpers.color(options.colorFrom).alpha(0.5).rgbString();
|
|
793
|
+
} else if (options.colorMode === 'to') {
|
|
794
|
+
fill = helpers.color(options.colorTo).alpha(0.5).rgbString();
|
|
795
|
+
} else {
|
|
796
|
+
fill = ctx.createLinearGradient(x, 0, x2, 0);
|
|
797
|
+
fill.addColorStop(0, helpers.color(options.colorFrom).alpha(0.5).rgbString());
|
|
798
|
+
fill.addColorStop(1, helpers.color(options.colorTo).alpha(0.5).rgbString());
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
ctx.fillStyle = fill;
|
|
802
|
+
ctx.strokeStyle = fill;
|
|
803
|
+
ctx.lineWidth = 0.5;
|
|
804
|
+
}
|
|
805
|
+
|
|
758
806
|
class Flow extends chart_js.Element {
|
|
759
807
|
|
|
760
808
|
/**
|
|
@@ -782,7 +830,6 @@ class Flow extends chart_js.Element {
|
|
|
782
830
|
const me = this;
|
|
783
831
|
const {x, x2, y, y2, height, progress} = me;
|
|
784
832
|
const {cp1, cp2} = controlPoints(x, y, x2, y2);
|
|
785
|
-
const options = me.options;
|
|
786
833
|
|
|
787
834
|
if (progress === 0) {
|
|
788
835
|
return;
|
|
@@ -794,21 +841,7 @@ class Flow extends chart_js.Element {
|
|
|
794
841
|
ctx.clip();
|
|
795
842
|
}
|
|
796
843
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
if (options.colorMode === 'from') {
|
|
800
|
-
fill = helpers.color(options.colorFrom).alpha(0.5).rgbString();
|
|
801
|
-
} else if (options.colorMode === 'to') {
|
|
802
|
-
fill = helpers.color(options.colorTo).alpha(0.5).rgbString();
|
|
803
|
-
} else {
|
|
804
|
-
fill = ctx.createLinearGradient(x, 0, x2, 0);
|
|
805
|
-
fill.addColorStop(0, helpers.color(options.colorFrom).alpha(0.5).rgbString());
|
|
806
|
-
fill.addColorStop(1, helpers.color(options.colorTo).alpha(0.5).rgbString());
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
ctx.fillStyle = fill;
|
|
810
|
-
ctx.strokeStyle = fill;
|
|
811
|
-
ctx.lineWidth = 0.5;
|
|
844
|
+
setStyle(ctx, me);
|
|
812
845
|
|
|
813
846
|
ctx.beginPath();
|
|
814
847
|
ctx.moveTo(x, y);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.9.1
|
|
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,e){const o=t.filter((t=>!e.has(t)));return o.length?o:t.slice(0,1)}const r=t=>void 0!==t,a=(t,e)=>t.x!==e.x?t.x-e.x:t.y-e.y,n=(t,e)=>t.reduce(((t,o)=>t+o.node[e].length+n(o.node[e],e)),0),i=t=>(e,o)=>n(e.node[t],t)-n(o.node[t],t)||e.node[t].length-o.node[t].length;function s(t,e){return t.from.sort(i("from")).forEach((t=>{const o=t.node;r(o.y)||(o.y=e,s(o,e)),e=Math.max(o.y+o.out,e)})),e}function l(t,e){return t.to.sort(i("to")).forEach((t=>{const o=t.node;r(o.y)||(o.y=e,l(o,e)),e=Math.max(o.y+o.in,e)})),e}function c(t,e){return r(t.y)?t.y:(t.y=e,e)}function h(t,e){const o=(t=>t.sort(((t,e)=>Math.max(e.in,e.out)-Math.max(t.in,t.out)))[0])(t);o.y=0;const a=s(o,0),n=l(o,0),i=function(t,e){const o=t.filter((t=>0===t.x)),a=t.filter((t=>t.x===e)),n=o.filter((t=>!r(t.y))),i=a.filter((t=>!r(t.y))),h=t.filter((t=>t.x>0&&t.x<e&&!r(t.y)));let d=o.reduce(((t,e)=>Math.max(t,e.y+e.out||0)),0),f=a.reduce(((t,e)=>Math.max(t,e.y+e.in||0)),0),u=0;return d>=f?(n.forEach((t=>{d=c(t,d),d=Math.max(d+t.out,l(t,d))})),i.forEach((t=>{f=c(t,f),f=Math.max(f+t.in,l(t,f))}))):(i.forEach((t=>{f=c(t,f),f=Math.max(f+t.in,l(t,f))})),n.forEach((t=>{d=c(t,d),d=Math.max(d+t.out,l(t,d))}))),h.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=c(e,o),o=Math.max(o+e.in,s(e,o)),o=Math.max(o+e.out,l(e,o)),u=Math.max(u,o)})),Math.max(d,f,u)}(t,e);return Math.max(a,n,i)}function d(t,e,r,n){const i=[...t.values()],s=function(t,e){const r=new Set(e.map((t=>t.to))),a=new Set(e.map((t=>t.from))),n=new Set([...t.keys()]);let i=0;for(;n.size;){const a=o([...n],r);for(let e=0;e<a.length;e++)t.get(a[e]).x=i,n.delete(a[e]);n.size&&(r.clear(),e.filter((t=>n.has(t.from))).forEach((t=>r.add(t.to))),i++)}return[...t.keys()].filter((t=>!a.has(t))).forEach((e=>{t.get(e).x=i})),i}(t,e),l=r?function(t,e){let o=0,r=0;for(let a=0;a<=e;a++){let e=r;const n=t.filter((t=>t.x===a)).sort(((t,e)=>t.priority-e.priority));r=n[0].to.filter((t=>t.node.x>a+1)).reduce(((t,e)=>t+e.flow),0)||0;for(const t of n)t.y=e,e+=Math.max(t.out,t.in);o=Math.max(e,o)}return o}(i,s):h(i,s),c=function(t,e){let o=1,r=0,n=0,i=0;const s=[];return t.sort(a).forEach((t=>{if(t.y){if(0===t.x)s.push(t.y);else{for(r!==t.x&&(r=t.x,n=0),o=n+1;o<s.length&&!(s[o]>t.y);o++);n=o}t.y+=o*e,o++}i=Math.max(i,t.y+Math.max(t.in,t.out))})),i}(i,.03*l);return function(t,e){t.forEach((t=>{const o=Math[e](t.in||t.out,t.out||t.in),r=o<t.in,a=o<t.out;let n=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=n,n+=t.flow)})),n=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)=>{a?t.addY=e*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)}))}))}(i,n),{maxX:s,maxY:c}}function f(t,e,o){for(const r of t)if(r.key===e&&r.index===o)return r.addY;return 0}function u(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}class x extends t.DatasetController{parseObjectData(t,e,o,r){if(0===r)return[];const a=this,{xScale:n,yScale:i}=t,s=[],l=a._nodes=function(t){const e=new Map;for(let o=0;o<t.length;o++){const{from:r,to:a,flow:n}=t[o];if(e.has(r)){const t=e.get(r);t.out+=n,t.to.push({key:a,flow:n,index:o})}else e.set(r,{key:r,in:0,out:n,from:[],to:[{key:a,flow:n,index:o}]});if(e.has(a)){const t=e.get(a);t.in+=n,t.from.push({key:r,flow:n,index:o})}else e.set(a,{key:a,in:n,out:0,from:[{key:r,flow:n,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),{priority:c,size:h}=a.getDataset();if(c)for(const t of l.values())t.key in c&&(t.priority=c[t.key]);const{maxX:x,maxY:y}=d(l,e,!!c,u(h));a._maxX=x,a._maxY=y;for(let t=0,o=e.length;t<o;++t){const o=e[t],r=l.get(o.from),a=l.get(o.to),c=r.y+f(r.to,o.to,t),h=a.y+f(a.from,o.from,t);s.push({x:n.parse(r.x,t),y:i.parse(c,t),_custom:{from:r,to:a,x:n.parse(a.x,t),y:i.parse(h,t),height:i.parse(o.flow,t)}})}return s.slice(o,o+r)}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,a){const n=this,{xScale:i,yScale:s}=n._cachedMeta,l=n.resolveDataElementOptions(o,a),c=n.getSharedOptions(a,t[o],l),h=n.getDataset(),d=e.valueOrDefault(h.borderWidth,1)/2+.5,f=e.valueOrDefault(h.nodeWidth,10);for(let e=o;e<o+r;e++){const o=n.getParsed(e),r=o._custom,l=s.getPixelForValue(o.y);n.updateElement(t[e],e,{x:i.getPixelForValue(o.x)+f+d,y:l,x2:i.getPixelForValue(r.x)-d,y2:s.getPixelForValue(r.y),from:r.from,to:r.to,progress:"reset"===a?0:1,height:Math.abs(s.getPixelForValue(o.y+r.height)-l),options:n.resolveDataElementOptions(e,a)},a)}n.updateSharedOptions(c,a)}_drawLabels(){const t=this,o=t._ctx,r=t._nodes||new Map,a=t.getDataset(),n=u(a.size),i=e.valueOrDefault(a.borderWidth,1),s=e.valueOrDefault(a.nodeWidth,10),l=a.labels,{xScale:c,yScale:h}=t._cachedMeta;o.save();const d=t.chart.chartArea;for(const t of r.values()){const e=c.getPixelForValue(t.x),r=h.getPixelForValue(t.y),f=Math[n](t.in||t.out,t.out||t.in),u=Math.abs(h.getPixelForValue(t.y+f)-r),x=l&&l[t.key]||t.key;let y=e;o.fillStyle=a.color||"black",o.textBaseline="middle",e<d.width/2?(o.textAlign="left",y+=s+i+4):(o.textAlign="right",y-=i+4),this._drawLabel(x,r,u,o,y)}o.restore()}_drawLabel(t,o,r,a,n){const i=this,s=e.toFont(i.options.font,i.chart.options.font),l=e.isNullOrUndef(t)?[]:i.toTextLines(t),c=l.length,h=o+r/2,d=s.lineHeight,f=e.valueOrDefault(i.options.padding,d/2);if(a.font=s.string,c>1){const t=h-d*c/2+f;for(let e=0;e<c;e++)a.fillText(l[e],n,t+e*d)}else a.fillText(t,n,h)}toTextLines(t){let o,r=[];for(t=[].concat(t);t.length;)o=t.pop(),"string"==typeof o?r.unshift.apply(r,o.split("\n")):Array.isArray(o)?t.push.apply(t,o):e.isNullOrUndef(t)||r.unshift(""+o);return r}_drawNodes(){const t=this,o=t._ctx,r=t._nodes||new Map,a=t.getDataset(),n=u(a.size),{xScale:i,yScale:s}=t._cachedMeta,l=e.valueOrDefault(a.borderWidth,1),c=e.valueOrDefault(a.nodeWidth,10);o.save(),o.strokeStyle=a.borderColor||"black",o.lineWidth=l;for(const t of r.values()){o.fillStyle=t.color;const e=i.getPixelForValue(t.x),r=s.getPixelForValue(t.y),a=Math[n](t.in||t.out,t.out||t.in),h=Math.abs(s.getPixelForValue(t.y+a)-r);l&&o.strokeRect(e,r,c,h),o.fillRect(e,r,c,h)}o.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:a,y2:n,height:i,progress:s}=this,{cp1:l,cp2:c}=y(o,a,r,n),h=this.options;if(0===s)return;let d;t.save(),s<1&&(t.beginPath(),t.rect(o,Math.min(a,n),(r-o)*s+1,Math.abs(n-a)+i+1),t.clip()),"from"===h.colorMode?d=e.color(h.colorFrom).alpha(.5).rgbString():"to"===h.colorMode?d=e.color(h.colorTo).alpha(.5).rgbString():(d=t.createLinearGradient(o,0,r,0),d.addColorStop(0,e.color(h.colorFrom).alpha(.5).rgbString()),d.addColorStop(1,e.color(h.colorTo).alpha(.5).rgbString())),t.fillStyle=d,t.strokeStyle=d,t.lineWidth=.5,t.beginPath(),t.moveTo(o,a),t.bezierCurveTo(l.x,l.y,c.x,c.y,r,n),t.lineTo(r,n+i),t.bezierCurveTo(c.x,c.y+i,l.x,l.y+i,o,a+i),t.lineTo(o,a),t.stroke(),t.closePath(),t.fill(),t.restore()}inRange(t,e,o){const{x:r,y:a,x2:n,y2:i,height:s}=this.getProps(["x","y","x2","y2","height"],o);if(t<r||t>n)return!1;const{cp1:l,cp2:c}=y(r,a,n,i),h=(t-r)/(n-r),d={x:n,y:i},f=p({x:r,y:a},l,h),u=p(l,c,h),x=p(c,d,h),g=p(f,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:a}=this.getProps(["y","y2","height"],e),n=Math.min(o,r),i=Math.max(o,r)+a;return t>=n&&t<=i}getCenterPoint(t){const{x:e,y:o,x2:r,y2:a,height:n}=this.getProps(["x","y","x2","y2","height"],t);return{x:(e+r)/2,y:(o+a+n)/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 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)}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chartjs-chart-sankey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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",
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
"autobuild": "rollup -c -w",
|
|
11
11
|
"dev": "karma start --no-single-run --auto-watch --browsers chrome",
|
|
12
12
|
"dev:ff": "karma start --no-single-run --auto-watch --browsers firefox",
|
|
13
|
-
"lint": "concurrently
|
|
13
|
+
"lint": "concurrently --group \"npm:lint-*\"",
|
|
14
14
|
"lint-js": "eslint \"src/**/*.js\" \"test/**/*.js\"",
|
|
15
15
|
"lint-md": "eslint \"**/*.md\"",
|
|
16
16
|
"lint-types": "eslint \"types/**/*.ts\" && tsc -p types/tests/",
|
|
17
|
-
"test": "cross-env NODE_ENV=test concurrently \"npm:test-*\"",
|
|
17
|
+
"test": "cross-env NODE_ENV=test concurrently --group \"npm:test-*\"",
|
|
18
18
|
"test-lint": "npm run lint",
|
|
19
19
|
"test-karma": "karma start --auto-watch --single-run"
|
|
20
20
|
},
|
|
@@ -39,34 +39,34 @@
|
|
|
39
39
|
"homepage": "https://github.com/kurkle/chartjs-chart-sankey#readme",
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@rollup/plugin-node-resolve": "^13.0.0",
|
|
42
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
43
|
-
"@typescript-eslint/parser": "^
|
|
44
|
-
"chart.js": "^3.
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
|
43
|
+
"@typescript-eslint/parser": "^5.10.2",
|
|
44
|
+
"chart.js": "^3.3.0",
|
|
45
45
|
"chartjs-adapter-date-fns": "^2.0.0",
|
|
46
|
-
"chartjs-test-utils": "^0.
|
|
47
|
-
"concurrently": "^
|
|
46
|
+
"chartjs-test-utils": "^0.4.0",
|
|
47
|
+
"concurrently": "^7.0.0",
|
|
48
48
|
"cross-env": "^7.0.3",
|
|
49
49
|
"date-fns": "^2.19.0",
|
|
50
|
-
"eslint": "^
|
|
50
|
+
"eslint": "^8.8.0",
|
|
51
51
|
"eslint-config-chartjs": "^0.3.0",
|
|
52
52
|
"eslint-plugin-es": "^4.1.0",
|
|
53
53
|
"eslint-plugin-html": "^6.1.2",
|
|
54
54
|
"eslint-plugin-markdown": "^2.2.0",
|
|
55
|
-
"jasmine-core": "^
|
|
55
|
+
"jasmine-core": "^4.0.0",
|
|
56
56
|
"karma": "^6.2.0",
|
|
57
57
|
"karma-chrome-launcher": "^3.1.0",
|
|
58
58
|
"karma-coverage": "^2.0.3",
|
|
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": "
|
|
63
|
-
"karma-spec-reporter": "0.0.
|
|
62
|
+
"karma-rollup-preprocessor": "7.0.7",
|
|
63
|
+
"karma-spec-reporter": "^0.0.33",
|
|
64
64
|
"rollup": "^2.42.1",
|
|
65
65
|
"rollup-plugin-istanbul": "^3.0.0",
|
|
66
66
|
"rollup-plugin-terser": "^7.0.2",
|
|
67
|
-
"typescript": "
|
|
67
|
+
"typescript": "^4.5.5"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"chart.js": "^3.
|
|
70
|
+
"chart.js": "^3.3.0"
|
|
71
71
|
}
|
|
72
72
|
}
|
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
|
|
|
@@ -24,6 +26,7 @@ declare module 'chart.js' {
|
|
|
24
26
|
colorMode: 'gradient' | 'from' | 'to';
|
|
25
27
|
/* Map<node.key, priority_value> */
|
|
26
28
|
priority?: Record<string, number>
|
|
29
|
+
column?: Record<string, number>
|
|
27
30
|
/* Map<node.key, label> */
|
|
28
31
|
labels?: Record<string, string>
|
|
29
32
|
|
|
@@ -52,6 +55,8 @@ declare module 'chart.js' {
|
|
|
52
55
|
out: number
|
|
53
56
|
from: Array<FromToElement>
|
|
54
57
|
to: Array<FromToElement>
|
|
58
|
+
/* true if x is defined by SankeyControllerDatasetOptions.column map */
|
|
59
|
+
column?: boolean
|
|
55
60
|
/* priority extracted from the SankeyControllerDatasetOptions.priority map */
|
|
56
61
|
priority?: number
|
|
57
62
|
y?: number
|
|
@@ -75,28 +80,28 @@ declare module 'chart.js' {
|
|
|
75
80
|
datasetOptions: SankeyControllerDatasetOptions;
|
|
76
81
|
defaultDataPoint: SankeyDataPoint;
|
|
77
82
|
parsedDataType: SankeyParsedData;
|
|
78
|
-
metaExtensions:
|
|
83
|
+
metaExtensions: AnyObject
|
|
79
84
|
/* TODO: define sankey chart options */
|
|
80
|
-
chartOptions:
|
|
85
|
+
chartOptions: AnyObject;
|
|
81
86
|
scales: keyof CartesianScaleTypeRegistry;
|
|
82
87
|
};
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
export interface FlowOptions {
|
|
87
92
|
colorMode: 'gradient' | 'from' | 'to';
|
|
88
93
|
colorFrom: string
|
|
89
94
|
colorTo: string
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
|
|
97
|
+
export interface FlowConfig {
|
|
93
98
|
x: number;
|
|
94
99
|
y: number;
|
|
95
100
|
x2: number;
|
|
96
101
|
y2: number;
|
|
97
102
|
height: number;
|
|
98
103
|
options: FlowOptions
|
|
99
|
-
}
|
|
104
|
+
}
|
|
100
105
|
|
|
101
106
|
export type SankeyController = DatasetController
|
|
102
107
|
export const SankeyController: ChartComponent & {
|
|
@@ -104,7 +109,11 @@ export const SankeyController: ChartComponent & {
|
|
|
104
109
|
new(chart: Chart, datasetIndex: number): SankeyController;
|
|
105
110
|
};
|
|
106
111
|
|
|
107
|
-
export
|
|
112
|
+
export interface Flow<
|
|
113
|
+
T extends FlowConfig = FlowConfig,
|
|
114
|
+
O extends FlowOptions = FlowOptions
|
|
115
|
+
> extends Element<T, O>, VisualElement {}
|
|
116
|
+
|
|
108
117
|
export const Flow: ChartComponent & {
|
|
109
118
|
prototype: Flow;
|
|
110
119
|
new(cfg: FlowConfig): Flow;
|