chartjs-chart-sankey 0.8.0 → 0.8.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.
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.8.
|
|
2
|
+
* chartjs-chart-sankey v0.8.1
|
|
3
3
|
* https://github.com/kurkle/chartjs-chart-sankey#readme
|
|
4
|
-
* (c)
|
|
4
|
+
* (c) 2022 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
6
6
|
*/
|
|
7
7
|
import { DatasetController, Element } from 'chart.js';
|
|
8
8
|
import { valueOrDefault, toFont, isNullOrUndef, color } from 'chart.js/helpers';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
11
|
+
* @param {Map<string, SankeyNode>} nodes
|
|
12
|
+
* @param {Array<SankeyDataPoint>} data
|
|
13
13
|
* @return {number}
|
|
14
14
|
*/
|
|
15
15
|
function calculateX(nodes, data) {
|
|
@@ -39,8 +39,8 @@ function calculateX(nodes, data) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* @param
|
|
43
|
-
* @param
|
|
42
|
+
* @param {Array<string>} keys
|
|
43
|
+
* @param {Set<string>} to
|
|
44
44
|
* @return {Array<string>}
|
|
45
45
|
*/
|
|
46
46
|
function nextColumn(keys, to) {
|
|
@@ -55,34 +55,34 @@ function nextColumn(keys, to) {
|
|
|
55
55
|
const defined = x => x !== undefined;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* @param
|
|
59
|
-
* @param
|
|
58
|
+
* @param {SankeyNode} a
|
|
59
|
+
* @param {SankeyNode} b
|
|
60
60
|
* @return {number}
|
|
61
61
|
*/
|
|
62
62
|
const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
* @param
|
|
66
|
-
* @param
|
|
65
|
+
* @param {Array<FromToElement>} list
|
|
66
|
+
* @param {string} prop
|
|
67
67
|
* @return {number}
|
|
68
68
|
*/
|
|
69
69
|
const nodeCount = (list, prop) => list.reduce((acc, cur) => acc + cur.node[prop].length + nodeCount(cur.node[prop], prop), 0);
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* @param
|
|
72
|
+
* @param {string} prop
|
|
73
73
|
* @return {function(FromToElement, FromToElement): number}
|
|
74
74
|
*/
|
|
75
|
-
const flowByNodeCount = (prop) => (a, b) => nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop);
|
|
75
|
+
const flowByNodeCount = (prop) => (a, b) => (nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop)) || (a.node[prop].length - b.node[prop].length);
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* @param
|
|
78
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
79
79
|
* @return {SankeyNode}
|
|
80
80
|
*/
|
|
81
81
|
const findLargestNode = (nodeArray) => nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out))[0];
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* @param
|
|
85
|
-
* @param
|
|
84
|
+
* @param {SankeyNode} node
|
|
85
|
+
* @param {number} y
|
|
86
86
|
* @return {number}
|
|
87
87
|
*/
|
|
88
88
|
function processFrom(node, y) {
|
|
@@ -90,15 +90,16 @@ function processFrom(node, y) {
|
|
|
90
90
|
const n = flow.node;
|
|
91
91
|
if (!defined(n.y)) {
|
|
92
92
|
n.y = y;
|
|
93
|
-
|
|
93
|
+
processFrom(n, y);
|
|
94
94
|
}
|
|
95
|
+
y = Math.max(n.y + n.out, y);
|
|
95
96
|
});
|
|
96
97
|
return y;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
/**
|
|
100
|
-
* @param
|
|
101
|
-
* @param
|
|
101
|
+
* @param {SankeyNode} node
|
|
102
|
+
* @param {number} y
|
|
102
103
|
* @return {number}
|
|
103
104
|
*/
|
|
104
105
|
function processTo(node, y) {
|
|
@@ -106,15 +107,16 @@ function processTo(node, y) {
|
|
|
106
107
|
const n = flow.node;
|
|
107
108
|
if (!defined(n.y)) {
|
|
108
109
|
n.y = y;
|
|
109
|
-
|
|
110
|
+
processTo(n, y);
|
|
110
111
|
}
|
|
112
|
+
y = Math.max(n.y + n.in, y);
|
|
111
113
|
});
|
|
112
114
|
return y;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
/**
|
|
116
|
-
* @param
|
|
117
|
-
* @param
|
|
118
|
+
* @param {SankeyNode} node
|
|
119
|
+
* @param {number} value
|
|
118
120
|
* @return {number}
|
|
119
121
|
*/
|
|
120
122
|
function setOrGetY(node, value) {
|
|
@@ -126,8 +128,8 @@ function setOrGetY(node, value) {
|
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
/**
|
|
129
|
-
* @param
|
|
130
|
-
* @param
|
|
131
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
132
|
+
* @param {number} maxX
|
|
131
133
|
* @return {number}
|
|
132
134
|
*/
|
|
133
135
|
function processRest(nodeArray, maxX) {
|
|
@@ -135,9 +137,11 @@ function processRest(nodeArray, maxX) {
|
|
|
135
137
|
const rightNodes = nodeArray.filter(node => node.x === maxX);
|
|
136
138
|
const leftToDo = leftNodes.filter(node => !defined(node.y));
|
|
137
139
|
const rightToDo = rightNodes.filter(node => !defined(node.y));
|
|
140
|
+
const centerToDo = nodeArray.filter(node => node.x > 0 && node.x < maxX && !defined(node.y));
|
|
138
141
|
|
|
139
142
|
let leftY = leftNodes.reduce((acc, cur) => Math.max(acc, (cur.y + cur.out) || 0), 0);
|
|
140
143
|
let rightY = rightNodes.reduce((acc, cur) => Math.max(acc, (cur.y + cur.in) || 0), 0);
|
|
144
|
+
let centerY = 0;
|
|
141
145
|
|
|
142
146
|
if (leftY >= rightY) {
|
|
143
147
|
leftToDo.forEach(node => {
|
|
@@ -160,13 +164,21 @@ function processRest(nodeArray, maxX) {
|
|
|
160
164
|
leftY = Math.max(leftY + node.out, processTo(node, leftY));
|
|
161
165
|
});
|
|
162
166
|
}
|
|
167
|
+
centerToDo.forEach(node => {
|
|
168
|
+
let y = nodeArray.filter(n => n.x === node.x && defined(n.y))
|
|
169
|
+
.reduce((acc, cur) => Math.max(acc, cur.y + Math.max(cur.in, cur.out)), 0);
|
|
170
|
+
y = setOrGetY(node, y);
|
|
171
|
+
y = Math.max(y + node.in, processFrom(node, y));
|
|
172
|
+
y = Math.max(y + node.out, processTo(node, y));
|
|
173
|
+
centerY = Math.max(centerY, y);
|
|
174
|
+
});
|
|
163
175
|
|
|
164
|
-
return Math.max(leftY, rightY);
|
|
176
|
+
return Math.max(leftY, rightY, centerY);
|
|
165
177
|
}
|
|
166
178
|
|
|
167
179
|
/**
|
|
168
|
-
* @param
|
|
169
|
-
* @param
|
|
180
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
181
|
+
* @param {number} maxX
|
|
170
182
|
* @return {number}
|
|
171
183
|
*/
|
|
172
184
|
function calculateY(nodeArray, maxX) {
|
|
@@ -179,15 +191,17 @@ function calculateY(nodeArray, maxX) {
|
|
|
179
191
|
}
|
|
180
192
|
|
|
181
193
|
/**
|
|
182
|
-
* @param
|
|
183
|
-
* @param
|
|
194
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
195
|
+
* @param {number} maxX
|
|
184
196
|
* @return {number}
|
|
185
197
|
*/
|
|
186
198
|
function calculateYUsingPriority(nodeArray, maxX) {
|
|
187
199
|
let maxY = 0;
|
|
200
|
+
let nextYStart = 0;
|
|
188
201
|
for (let x = 0; x <= maxX; x++) {
|
|
189
|
-
let y =
|
|
202
|
+
let y = nextYStart;
|
|
190
203
|
const nodes = nodeArray.filter(node => node.x === x).sort((a, b) => a.priority - b.priority);
|
|
204
|
+
nextYStart = nodes[0].to.filter(to => to.node.x > x + 1).reduce((acc, cur) => acc + cur.flow, 0) || 0;
|
|
191
205
|
for (const node of nodes) {
|
|
192
206
|
node.y = y;
|
|
193
207
|
y += Math.max(node.out, node.in);
|
|
@@ -198,8 +212,8 @@ function calculateYUsingPriority(nodeArray, maxX) {
|
|
|
198
212
|
}
|
|
199
213
|
|
|
200
214
|
/**
|
|
201
|
-
* @param
|
|
202
|
-
* @param
|
|
215
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
216
|
+
* @param {number} padding
|
|
203
217
|
* @return {number}
|
|
204
218
|
*/
|
|
205
219
|
function addPadding(nodeArray, padding) {
|
|
@@ -234,8 +248,8 @@ function addPadding(nodeArray, padding) {
|
|
|
234
248
|
}
|
|
235
249
|
|
|
236
250
|
/**
|
|
237
|
-
* @param
|
|
238
|
-
* @param
|
|
251
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
252
|
+
* @param {'min' | 'max'} size
|
|
239
253
|
*/
|
|
240
254
|
function sortFlows(nodeArray, size) {
|
|
241
255
|
nodeArray.forEach((node) => {
|
|
@@ -266,10 +280,10 @@ function sortFlows(nodeArray, size) {
|
|
|
266
280
|
}
|
|
267
281
|
|
|
268
282
|
/**
|
|
269
|
-
* @param
|
|
270
|
-
* @param
|
|
271
|
-
* @param
|
|
272
|
-
* @param
|
|
283
|
+
* @param {Map<string, SankeyNode>} nodes
|
|
284
|
+
* @param {Array<SankeyDataPoint>} data
|
|
285
|
+
* @param {boolean} priority
|
|
286
|
+
* @param {'min' | 'max'} size
|
|
273
287
|
* @return {{maxY: number, maxX: number}}
|
|
274
288
|
*/
|
|
275
289
|
function layout(nodes, data, priority, size) {
|
|
@@ -283,7 +297,7 @@ function layout(nodes, data, priority, size) {
|
|
|
283
297
|
}
|
|
284
298
|
|
|
285
299
|
/**
|
|
286
|
-
* @param
|
|
300
|
+
* @param {Array<SankeyDataPoint>} data Array of raw data elements
|
|
287
301
|
* @return {Map<string, SankeyNode>}
|
|
288
302
|
*/
|
|
289
303
|
function buildNodesFromRawData(data) {
|
|
@@ -297,25 +311,25 @@ function buildNodesFromRawData(data) {
|
|
|
297
311
|
in: 0,
|
|
298
312
|
out: flow,
|
|
299
313
|
from: [],
|
|
300
|
-
to: [{key: to, flow: flow}],
|
|
314
|
+
to: [{key: to, flow: flow, index: i}],
|
|
301
315
|
});
|
|
302
316
|
} else {
|
|
303
317
|
const node = nodes.get(from);
|
|
304
318
|
node.out += flow;
|
|
305
|
-
node.to.push({key: to, flow: flow});
|
|
319
|
+
node.to.push({key: to, flow: flow, index: i});
|
|
306
320
|
}
|
|
307
321
|
if (!nodes.has(to)) {
|
|
308
322
|
nodes.set(to, {
|
|
309
323
|
key: to,
|
|
310
324
|
in: flow,
|
|
311
325
|
out: 0,
|
|
312
|
-
from: [{key: from, flow: flow}],
|
|
326
|
+
from: [{key: from, flow: flow, index: i}],
|
|
313
327
|
to: [],
|
|
314
328
|
});
|
|
315
329
|
} else {
|
|
316
330
|
const node = nodes.get(to);
|
|
317
331
|
node.in += flow;
|
|
318
|
-
node.from.push({key: from, flow: flow});
|
|
332
|
+
node.from.push({key: from, flow: flow, index: i});
|
|
319
333
|
}
|
|
320
334
|
}
|
|
321
335
|
|
|
@@ -337,21 +351,22 @@ function buildNodesFromRawData(data) {
|
|
|
337
351
|
}
|
|
338
352
|
|
|
339
353
|
/**
|
|
340
|
-
* @param
|
|
341
|
-
* @param
|
|
354
|
+
* @param {Array<FromToElement>} arr
|
|
355
|
+
* @param {string} key
|
|
356
|
+
* @param {number} index
|
|
342
357
|
* @return {number}
|
|
343
358
|
*/
|
|
344
|
-
function getAddY(arr, key) {
|
|
345
|
-
for (
|
|
346
|
-
if (
|
|
347
|
-
return
|
|
359
|
+
function getAddY(arr, key, index) {
|
|
360
|
+
for (const item of arr) {
|
|
361
|
+
if (item.key === key && item.index === index) {
|
|
362
|
+
return item.addY;
|
|
348
363
|
}
|
|
349
364
|
}
|
|
350
365
|
return 0;
|
|
351
366
|
}
|
|
352
367
|
|
|
353
368
|
/**
|
|
354
|
-
* @param
|
|
369
|
+
* @param {any} size
|
|
355
370
|
* @return {'min' | 'max'}
|
|
356
371
|
*/
|
|
357
372
|
function validateSizeValue(size) {
|
|
@@ -363,10 +378,10 @@ function validateSizeValue(size) {
|
|
|
363
378
|
|
|
364
379
|
class SankeyController extends DatasetController {
|
|
365
380
|
/**
|
|
366
|
-
* @param
|
|
367
|
-
* @param
|
|
368
|
-
* @param
|
|
369
|
-
* @param
|
|
381
|
+
* @param {ChartMeta<Flow, Element>} meta
|
|
382
|
+
* @param {Array<SankeyDataPoint>} data Array of original data elements
|
|
383
|
+
* @param {number} start
|
|
384
|
+
* @param {number} count
|
|
370
385
|
* @return {Array<SankeyParsedData>}
|
|
371
386
|
*/
|
|
372
387
|
parseObjectData(meta, data, start, count) {
|
|
@@ -398,8 +413,8 @@ class SankeyController extends DatasetController {
|
|
|
398
413
|
const dataPoint = data[i]; /* {SankeyDataPoint} */
|
|
399
414
|
const from = nodes.get(dataPoint.from); /* from {SankeyNode} */
|
|
400
415
|
const to = nodes.get(dataPoint.to); /* to {SankeyNode} */
|
|
401
|
-
const fromY = from.y + getAddY(from.to, dataPoint.to);
|
|
402
|
-
const toY = to.y + getAddY(to.from, dataPoint.from);
|
|
416
|
+
const fromY = from.y + getAddY(from.to, dataPoint.to, i);
|
|
417
|
+
const toY = to.y + getAddY(to.from, dataPoint.from, i);
|
|
403
418
|
parsed.push({
|
|
404
419
|
x: xScale.parse(from.x, i),
|
|
405
420
|
y: yScale.parse(fromY, i),
|
|
@@ -431,10 +446,10 @@ class SankeyController extends DatasetController {
|
|
|
431
446
|
}
|
|
432
447
|
|
|
433
448
|
/**
|
|
434
|
-
* @param
|
|
435
|
-
* @param
|
|
436
|
-
* @param
|
|
437
|
-
* @param
|
|
449
|
+
* @param {Array<Flow>} elems
|
|
450
|
+
* @param {number} start
|
|
451
|
+
* @param {number} count
|
|
452
|
+
* @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
|
|
438
453
|
*/
|
|
439
454
|
updateElements(elems, start, count, mode) {
|
|
440
455
|
const me = this;
|
|
@@ -506,11 +521,11 @@ class SankeyController extends DatasetController {
|
|
|
506
521
|
}
|
|
507
522
|
|
|
508
523
|
/**
|
|
509
|
-
* @param
|
|
510
|
-
* @param
|
|
511
|
-
* @param
|
|
512
|
-
* @param
|
|
513
|
-
* @param
|
|
524
|
+
* @param {string} label
|
|
525
|
+
* @param {number} y
|
|
526
|
+
* @param {number} height
|
|
527
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
528
|
+
* @param {number} textX
|
|
514
529
|
* @private
|
|
515
530
|
*/
|
|
516
531
|
_drawLabel(label, y, height, ctx, textX) {
|
|
@@ -535,7 +550,7 @@ class SankeyController extends DatasetController {
|
|
|
535
550
|
}
|
|
536
551
|
|
|
537
552
|
/**
|
|
538
|
-
* @param
|
|
553
|
+
* @param {string | Array<string>} inputs
|
|
539
554
|
* @return {Array<string>}
|
|
540
555
|
* @todo move this in Chart.helpers.toTextLines
|
|
541
556
|
*/
|
|
@@ -615,6 +630,7 @@ class SankeyController extends DatasetController {
|
|
|
615
630
|
}
|
|
616
631
|
|
|
617
632
|
SankeyController.id = 'sankey';
|
|
633
|
+
|
|
618
634
|
SankeyController.defaults = {
|
|
619
635
|
dataElementType: 'flow',
|
|
620
636
|
animations: {
|
|
@@ -653,6 +669,7 @@ SankeyController.defaults = {
|
|
|
653
669
|
}
|
|
654
670
|
}
|
|
655
671
|
};
|
|
672
|
+
|
|
656
673
|
SankeyController.overrides = {
|
|
657
674
|
interaction: {
|
|
658
675
|
mode: 'nearest',
|
|
@@ -710,10 +727,10 @@ SankeyController.overrides = {
|
|
|
710
727
|
* @typedef {{x: number, y: number}} ControlPoint
|
|
711
728
|
* @typedef {{cp1: ControlPoint, cp2: ControlPoint}} ControlPoints
|
|
712
729
|
*
|
|
713
|
-
* @param
|
|
714
|
-
* @param
|
|
715
|
-
* @param
|
|
716
|
-
* @param
|
|
730
|
+
* @param {number} x
|
|
731
|
+
* @param {number} y
|
|
732
|
+
* @param {number} x2
|
|
733
|
+
* @param {number} y2
|
|
717
734
|
* @return {ControlPoints}
|
|
718
735
|
*/
|
|
719
736
|
const controlPoints = (x, y, x2, y2) => x < x2
|
|
@@ -728,9 +745,9 @@ const controlPoints = (x, y, x2, y2) => x < x2
|
|
|
728
745
|
|
|
729
746
|
/**
|
|
730
747
|
*
|
|
731
|
-
* @param
|
|
732
|
-
* @param
|
|
733
|
-
* @param
|
|
748
|
+
* @param {ControlPoint} p1
|
|
749
|
+
* @param {ControlPoint} p2
|
|
750
|
+
* @param {number} t
|
|
734
751
|
* @return {ControlPoint}
|
|
735
752
|
*/
|
|
736
753
|
const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
|
|
@@ -738,7 +755,7 @@ const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t *
|
|
|
738
755
|
class Flow extends Element {
|
|
739
756
|
|
|
740
757
|
/**
|
|
741
|
-
* @param
|
|
758
|
+
* @param {FlowConfig} cfg
|
|
742
759
|
*/
|
|
743
760
|
constructor(cfg) {
|
|
744
761
|
super();
|
|
@@ -756,7 +773,7 @@ class Flow extends Element {
|
|
|
756
773
|
}
|
|
757
774
|
|
|
758
775
|
/**
|
|
759
|
-
* @param
|
|
776
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
760
777
|
*/
|
|
761
778
|
draw(ctx) {
|
|
762
779
|
const me = this;
|
|
@@ -805,9 +822,9 @@ class Flow extends Element {
|
|
|
805
822
|
}
|
|
806
823
|
|
|
807
824
|
/**
|
|
808
|
-
* @param
|
|
809
|
-
* @param
|
|
810
|
-
* @param
|
|
825
|
+
* @param {number} mouseX
|
|
826
|
+
* @param {number} mouseY
|
|
827
|
+
* @param {boolean} useFinalPosition
|
|
811
828
|
* @return {boolean}
|
|
812
829
|
*/
|
|
813
830
|
inRange(mouseX, mouseY, useFinalPosition) {
|
|
@@ -829,8 +846,8 @@ class Flow extends Element {
|
|
|
829
846
|
}
|
|
830
847
|
|
|
831
848
|
/**
|
|
832
|
-
* @param
|
|
833
|
-
* @param
|
|
849
|
+
* @param {number} mouseX
|
|
850
|
+
* @param {boolean} useFinalPosition
|
|
834
851
|
* @return {boolean}
|
|
835
852
|
*/
|
|
836
853
|
inXRange(mouseX, useFinalPosition) {
|
|
@@ -839,8 +856,8 @@ class Flow extends Element {
|
|
|
839
856
|
}
|
|
840
857
|
|
|
841
858
|
/**
|
|
842
|
-
* @param
|
|
843
|
-
* @param
|
|
859
|
+
* @param {number} mouseY
|
|
860
|
+
* @param {boolean} useFinalPosition
|
|
844
861
|
* @return {boolean}
|
|
845
862
|
*/
|
|
846
863
|
inYRange(mouseY, useFinalPosition) {
|
|
@@ -851,7 +868,7 @@ class Flow extends Element {
|
|
|
851
868
|
}
|
|
852
869
|
|
|
853
870
|
/**
|
|
854
|
-
* @param
|
|
871
|
+
* @param {boolean} useFinalPosition
|
|
855
872
|
* @return {{x: number, y:number}}
|
|
856
873
|
*/
|
|
857
874
|
getCenterPoint(useFinalPosition) {
|
|
@@ -867,7 +884,7 @@ class Flow extends Element {
|
|
|
867
884
|
}
|
|
868
885
|
|
|
869
886
|
/**
|
|
870
|
-
* @param
|
|
887
|
+
* @param {"x" | "y"} axis
|
|
871
888
|
* @return {number}
|
|
872
889
|
*/
|
|
873
890
|
getRange(axis) {
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.8.
|
|
2
|
+
* chartjs-chart-sankey v0.8.1
|
|
3
3
|
* https://github.com/kurkle/chartjs-chart-sankey#readme
|
|
4
|
-
* (c)
|
|
4
|
+
* (c) 2022 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
6
6
|
*/
|
|
7
7
|
(function (global, factory) {
|
|
8
8
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('chart.js'), require('chart.js/helpers')) :
|
|
9
9
|
typeof define === 'function' && define.amd ? define(['chart.js', 'chart.js/helpers'], factory) :
|
|
10
10
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Chart, global.Chart.helpers));
|
|
11
|
-
}(this, (function (chart_js, helpers) { 'use strict';
|
|
11
|
+
})(this, (function (chart_js, helpers) { 'use strict';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
14
|
+
* @param {Map<string, SankeyNode>} nodes
|
|
15
|
+
* @param {Array<SankeyDataPoint>} data
|
|
16
16
|
* @return {number}
|
|
17
17
|
*/
|
|
18
18
|
function calculateX(nodes, data) {
|
|
@@ -42,8 +42,8 @@ function calculateX(nodes, data) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* @param
|
|
46
|
-
* @param
|
|
45
|
+
* @param {Array<string>} keys
|
|
46
|
+
* @param {Set<string>} to
|
|
47
47
|
* @return {Array<string>}
|
|
48
48
|
*/
|
|
49
49
|
function nextColumn(keys, to) {
|
|
@@ -58,34 +58,34 @@ function nextColumn(keys, to) {
|
|
|
58
58
|
const defined = x => x !== undefined;
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* @param
|
|
62
|
-
* @param
|
|
61
|
+
* @param {SankeyNode} a
|
|
62
|
+
* @param {SankeyNode} b
|
|
63
63
|
* @return {number}
|
|
64
64
|
*/
|
|
65
65
|
const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* @param
|
|
69
|
-
* @param
|
|
68
|
+
* @param {Array<FromToElement>} list
|
|
69
|
+
* @param {string} prop
|
|
70
70
|
* @return {number}
|
|
71
71
|
*/
|
|
72
72
|
const nodeCount = (list, prop) => list.reduce((acc, cur) => acc + cur.node[prop].length + nodeCount(cur.node[prop], prop), 0);
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* @param
|
|
75
|
+
* @param {string} prop
|
|
76
76
|
* @return {function(FromToElement, FromToElement): number}
|
|
77
77
|
*/
|
|
78
|
-
const flowByNodeCount = (prop) => (a, b) => nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop);
|
|
78
|
+
const flowByNodeCount = (prop) => (a, b) => (nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop)) || (a.node[prop].length - b.node[prop].length);
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* @param
|
|
81
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
82
82
|
* @return {SankeyNode}
|
|
83
83
|
*/
|
|
84
84
|
const findLargestNode = (nodeArray) => nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out))[0];
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
* @param
|
|
88
|
-
* @param
|
|
87
|
+
* @param {SankeyNode} node
|
|
88
|
+
* @param {number} y
|
|
89
89
|
* @return {number}
|
|
90
90
|
*/
|
|
91
91
|
function processFrom(node, y) {
|
|
@@ -93,15 +93,16 @@ function processFrom(node, y) {
|
|
|
93
93
|
const n = flow.node;
|
|
94
94
|
if (!defined(n.y)) {
|
|
95
95
|
n.y = y;
|
|
96
|
-
|
|
96
|
+
processFrom(n, y);
|
|
97
97
|
}
|
|
98
|
+
y = Math.max(n.y + n.out, y);
|
|
98
99
|
});
|
|
99
100
|
return y;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
/**
|
|
103
|
-
* @param
|
|
104
|
-
* @param
|
|
104
|
+
* @param {SankeyNode} node
|
|
105
|
+
* @param {number} y
|
|
105
106
|
* @return {number}
|
|
106
107
|
*/
|
|
107
108
|
function processTo(node, y) {
|
|
@@ -109,15 +110,16 @@ function processTo(node, y) {
|
|
|
109
110
|
const n = flow.node;
|
|
110
111
|
if (!defined(n.y)) {
|
|
111
112
|
n.y = y;
|
|
112
|
-
|
|
113
|
+
processTo(n, y);
|
|
113
114
|
}
|
|
115
|
+
y = Math.max(n.y + n.in, y);
|
|
114
116
|
});
|
|
115
117
|
return y;
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
/**
|
|
119
|
-
* @param
|
|
120
|
-
* @param
|
|
121
|
+
* @param {SankeyNode} node
|
|
122
|
+
* @param {number} value
|
|
121
123
|
* @return {number}
|
|
122
124
|
*/
|
|
123
125
|
function setOrGetY(node, value) {
|
|
@@ -129,8 +131,8 @@ function setOrGetY(node, value) {
|
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
/**
|
|
132
|
-
* @param
|
|
133
|
-
* @param
|
|
134
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
135
|
+
* @param {number} maxX
|
|
134
136
|
* @return {number}
|
|
135
137
|
*/
|
|
136
138
|
function processRest(nodeArray, maxX) {
|
|
@@ -138,9 +140,11 @@ function processRest(nodeArray, maxX) {
|
|
|
138
140
|
const rightNodes = nodeArray.filter(node => node.x === maxX);
|
|
139
141
|
const leftToDo = leftNodes.filter(node => !defined(node.y));
|
|
140
142
|
const rightToDo = rightNodes.filter(node => !defined(node.y));
|
|
143
|
+
const centerToDo = nodeArray.filter(node => node.x > 0 && node.x < maxX && !defined(node.y));
|
|
141
144
|
|
|
142
145
|
let leftY = leftNodes.reduce((acc, cur) => Math.max(acc, (cur.y + cur.out) || 0), 0);
|
|
143
146
|
let rightY = rightNodes.reduce((acc, cur) => Math.max(acc, (cur.y + cur.in) || 0), 0);
|
|
147
|
+
let centerY = 0;
|
|
144
148
|
|
|
145
149
|
if (leftY >= rightY) {
|
|
146
150
|
leftToDo.forEach(node => {
|
|
@@ -163,13 +167,21 @@ function processRest(nodeArray, maxX) {
|
|
|
163
167
|
leftY = Math.max(leftY + node.out, processTo(node, leftY));
|
|
164
168
|
});
|
|
165
169
|
}
|
|
170
|
+
centerToDo.forEach(node => {
|
|
171
|
+
let y = nodeArray.filter(n => n.x === node.x && defined(n.y))
|
|
172
|
+
.reduce((acc, cur) => Math.max(acc, cur.y + Math.max(cur.in, cur.out)), 0);
|
|
173
|
+
y = setOrGetY(node, y);
|
|
174
|
+
y = Math.max(y + node.in, processFrom(node, y));
|
|
175
|
+
y = Math.max(y + node.out, processTo(node, y));
|
|
176
|
+
centerY = Math.max(centerY, y);
|
|
177
|
+
});
|
|
166
178
|
|
|
167
|
-
return Math.max(leftY, rightY);
|
|
179
|
+
return Math.max(leftY, rightY, centerY);
|
|
168
180
|
}
|
|
169
181
|
|
|
170
182
|
/**
|
|
171
|
-
* @param
|
|
172
|
-
* @param
|
|
183
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
184
|
+
* @param {number} maxX
|
|
173
185
|
* @return {number}
|
|
174
186
|
*/
|
|
175
187
|
function calculateY(nodeArray, maxX) {
|
|
@@ -182,15 +194,17 @@ function calculateY(nodeArray, maxX) {
|
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
/**
|
|
185
|
-
* @param
|
|
186
|
-
* @param
|
|
197
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
198
|
+
* @param {number} maxX
|
|
187
199
|
* @return {number}
|
|
188
200
|
*/
|
|
189
201
|
function calculateYUsingPriority(nodeArray, maxX) {
|
|
190
202
|
let maxY = 0;
|
|
203
|
+
let nextYStart = 0;
|
|
191
204
|
for (let x = 0; x <= maxX; x++) {
|
|
192
|
-
let y =
|
|
205
|
+
let y = nextYStart;
|
|
193
206
|
const nodes = nodeArray.filter(node => node.x === x).sort((a, b) => a.priority - b.priority);
|
|
207
|
+
nextYStart = nodes[0].to.filter(to => to.node.x > x + 1).reduce((acc, cur) => acc + cur.flow, 0) || 0;
|
|
194
208
|
for (const node of nodes) {
|
|
195
209
|
node.y = y;
|
|
196
210
|
y += Math.max(node.out, node.in);
|
|
@@ -201,8 +215,8 @@ function calculateYUsingPriority(nodeArray, maxX) {
|
|
|
201
215
|
}
|
|
202
216
|
|
|
203
217
|
/**
|
|
204
|
-
* @param
|
|
205
|
-
* @param
|
|
218
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
219
|
+
* @param {number} padding
|
|
206
220
|
* @return {number}
|
|
207
221
|
*/
|
|
208
222
|
function addPadding(nodeArray, padding) {
|
|
@@ -237,8 +251,8 @@ function addPadding(nodeArray, padding) {
|
|
|
237
251
|
}
|
|
238
252
|
|
|
239
253
|
/**
|
|
240
|
-
* @param
|
|
241
|
-
* @param
|
|
254
|
+
* @param {Array<SankeyNode>} nodeArray
|
|
255
|
+
* @param {'min' | 'max'} size
|
|
242
256
|
*/
|
|
243
257
|
function sortFlows(nodeArray, size) {
|
|
244
258
|
nodeArray.forEach((node) => {
|
|
@@ -269,10 +283,10 @@ function sortFlows(nodeArray, size) {
|
|
|
269
283
|
}
|
|
270
284
|
|
|
271
285
|
/**
|
|
272
|
-
* @param
|
|
273
|
-
* @param
|
|
274
|
-
* @param
|
|
275
|
-
* @param
|
|
286
|
+
* @param {Map<string, SankeyNode>} nodes
|
|
287
|
+
* @param {Array<SankeyDataPoint>} data
|
|
288
|
+
* @param {boolean} priority
|
|
289
|
+
* @param {'min' | 'max'} size
|
|
276
290
|
* @return {{maxY: number, maxX: number}}
|
|
277
291
|
*/
|
|
278
292
|
function layout(nodes, data, priority, size) {
|
|
@@ -286,7 +300,7 @@ function layout(nodes, data, priority, size) {
|
|
|
286
300
|
}
|
|
287
301
|
|
|
288
302
|
/**
|
|
289
|
-
* @param
|
|
303
|
+
* @param {Array<SankeyDataPoint>} data Array of raw data elements
|
|
290
304
|
* @return {Map<string, SankeyNode>}
|
|
291
305
|
*/
|
|
292
306
|
function buildNodesFromRawData(data) {
|
|
@@ -300,25 +314,25 @@ function buildNodesFromRawData(data) {
|
|
|
300
314
|
in: 0,
|
|
301
315
|
out: flow,
|
|
302
316
|
from: [],
|
|
303
|
-
to: [{key: to, flow: flow}],
|
|
317
|
+
to: [{key: to, flow: flow, index: i}],
|
|
304
318
|
});
|
|
305
319
|
} else {
|
|
306
320
|
const node = nodes.get(from);
|
|
307
321
|
node.out += flow;
|
|
308
|
-
node.to.push({key: to, flow: flow});
|
|
322
|
+
node.to.push({key: to, flow: flow, index: i});
|
|
309
323
|
}
|
|
310
324
|
if (!nodes.has(to)) {
|
|
311
325
|
nodes.set(to, {
|
|
312
326
|
key: to,
|
|
313
327
|
in: flow,
|
|
314
328
|
out: 0,
|
|
315
|
-
from: [{key: from, flow: flow}],
|
|
329
|
+
from: [{key: from, flow: flow, index: i}],
|
|
316
330
|
to: [],
|
|
317
331
|
});
|
|
318
332
|
} else {
|
|
319
333
|
const node = nodes.get(to);
|
|
320
334
|
node.in += flow;
|
|
321
|
-
node.from.push({key: from, flow: flow});
|
|
335
|
+
node.from.push({key: from, flow: flow, index: i});
|
|
322
336
|
}
|
|
323
337
|
}
|
|
324
338
|
|
|
@@ -340,21 +354,22 @@ function buildNodesFromRawData(data) {
|
|
|
340
354
|
}
|
|
341
355
|
|
|
342
356
|
/**
|
|
343
|
-
* @param
|
|
344
|
-
* @param
|
|
357
|
+
* @param {Array<FromToElement>} arr
|
|
358
|
+
* @param {string} key
|
|
359
|
+
* @param {number} index
|
|
345
360
|
* @return {number}
|
|
346
361
|
*/
|
|
347
|
-
function getAddY(arr, key) {
|
|
348
|
-
for (
|
|
349
|
-
if (
|
|
350
|
-
return
|
|
362
|
+
function getAddY(arr, key, index) {
|
|
363
|
+
for (const item of arr) {
|
|
364
|
+
if (item.key === key && item.index === index) {
|
|
365
|
+
return item.addY;
|
|
351
366
|
}
|
|
352
367
|
}
|
|
353
368
|
return 0;
|
|
354
369
|
}
|
|
355
370
|
|
|
356
371
|
/**
|
|
357
|
-
* @param
|
|
372
|
+
* @param {any} size
|
|
358
373
|
* @return {'min' | 'max'}
|
|
359
374
|
*/
|
|
360
375
|
function validateSizeValue(size) {
|
|
@@ -366,10 +381,10 @@ function validateSizeValue(size) {
|
|
|
366
381
|
|
|
367
382
|
class SankeyController extends chart_js.DatasetController {
|
|
368
383
|
/**
|
|
369
|
-
* @param
|
|
370
|
-
* @param
|
|
371
|
-
* @param
|
|
372
|
-
* @param
|
|
384
|
+
* @param {ChartMeta<Flow, Element>} meta
|
|
385
|
+
* @param {Array<SankeyDataPoint>} data Array of original data elements
|
|
386
|
+
* @param {number} start
|
|
387
|
+
* @param {number} count
|
|
373
388
|
* @return {Array<SankeyParsedData>}
|
|
374
389
|
*/
|
|
375
390
|
parseObjectData(meta, data, start, count) {
|
|
@@ -401,8 +416,8 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
401
416
|
const dataPoint = data[i]; /* {SankeyDataPoint} */
|
|
402
417
|
const from = nodes.get(dataPoint.from); /* from {SankeyNode} */
|
|
403
418
|
const to = nodes.get(dataPoint.to); /* to {SankeyNode} */
|
|
404
|
-
const fromY = from.y + getAddY(from.to, dataPoint.to);
|
|
405
|
-
const toY = to.y + getAddY(to.from, dataPoint.from);
|
|
419
|
+
const fromY = from.y + getAddY(from.to, dataPoint.to, i);
|
|
420
|
+
const toY = to.y + getAddY(to.from, dataPoint.from, i);
|
|
406
421
|
parsed.push({
|
|
407
422
|
x: xScale.parse(from.x, i),
|
|
408
423
|
y: yScale.parse(fromY, i),
|
|
@@ -434,10 +449,10 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
434
449
|
}
|
|
435
450
|
|
|
436
451
|
/**
|
|
437
|
-
* @param
|
|
438
|
-
* @param
|
|
439
|
-
* @param
|
|
440
|
-
* @param
|
|
452
|
+
* @param {Array<Flow>} elems
|
|
453
|
+
* @param {number} start
|
|
454
|
+
* @param {number} count
|
|
455
|
+
* @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
|
|
441
456
|
*/
|
|
442
457
|
updateElements(elems, start, count, mode) {
|
|
443
458
|
const me = this;
|
|
@@ -509,11 +524,11 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
509
524
|
}
|
|
510
525
|
|
|
511
526
|
/**
|
|
512
|
-
* @param
|
|
513
|
-
* @param
|
|
514
|
-
* @param
|
|
515
|
-
* @param
|
|
516
|
-
* @param
|
|
527
|
+
* @param {string} label
|
|
528
|
+
* @param {number} y
|
|
529
|
+
* @param {number} height
|
|
530
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
531
|
+
* @param {number} textX
|
|
517
532
|
* @private
|
|
518
533
|
*/
|
|
519
534
|
_drawLabel(label, y, height, ctx, textX) {
|
|
@@ -538,7 +553,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
538
553
|
}
|
|
539
554
|
|
|
540
555
|
/**
|
|
541
|
-
* @param
|
|
556
|
+
* @param {string | Array<string>} inputs
|
|
542
557
|
* @return {Array<string>}
|
|
543
558
|
* @todo move this in Chart.helpers.toTextLines
|
|
544
559
|
*/
|
|
@@ -618,6 +633,7 @@ class SankeyController extends chart_js.DatasetController {
|
|
|
618
633
|
}
|
|
619
634
|
|
|
620
635
|
SankeyController.id = 'sankey';
|
|
636
|
+
|
|
621
637
|
SankeyController.defaults = {
|
|
622
638
|
dataElementType: 'flow',
|
|
623
639
|
animations: {
|
|
@@ -656,6 +672,7 @@ SankeyController.defaults = {
|
|
|
656
672
|
}
|
|
657
673
|
}
|
|
658
674
|
};
|
|
675
|
+
|
|
659
676
|
SankeyController.overrides = {
|
|
660
677
|
interaction: {
|
|
661
678
|
mode: 'nearest',
|
|
@@ -713,10 +730,10 @@ SankeyController.overrides = {
|
|
|
713
730
|
* @typedef {{x: number, y: number}} ControlPoint
|
|
714
731
|
* @typedef {{cp1: ControlPoint, cp2: ControlPoint}} ControlPoints
|
|
715
732
|
*
|
|
716
|
-
* @param
|
|
717
|
-
* @param
|
|
718
|
-
* @param
|
|
719
|
-
* @param
|
|
733
|
+
* @param {number} x
|
|
734
|
+
* @param {number} y
|
|
735
|
+
* @param {number} x2
|
|
736
|
+
* @param {number} y2
|
|
720
737
|
* @return {ControlPoints}
|
|
721
738
|
*/
|
|
722
739
|
const controlPoints = (x, y, x2, y2) => x < x2
|
|
@@ -731,9 +748,9 @@ const controlPoints = (x, y, x2, y2) => x < x2
|
|
|
731
748
|
|
|
732
749
|
/**
|
|
733
750
|
*
|
|
734
|
-
* @param
|
|
735
|
-
* @param
|
|
736
|
-
* @param
|
|
751
|
+
* @param {ControlPoint} p1
|
|
752
|
+
* @param {ControlPoint} p2
|
|
753
|
+
* @param {number} t
|
|
737
754
|
* @return {ControlPoint}
|
|
738
755
|
*/
|
|
739
756
|
const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
|
|
@@ -741,7 +758,7 @@ const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t *
|
|
|
741
758
|
class Flow extends chart_js.Element {
|
|
742
759
|
|
|
743
760
|
/**
|
|
744
|
-
* @param
|
|
761
|
+
* @param {FlowConfig} cfg
|
|
745
762
|
*/
|
|
746
763
|
constructor(cfg) {
|
|
747
764
|
super();
|
|
@@ -759,7 +776,7 @@ class Flow extends chart_js.Element {
|
|
|
759
776
|
}
|
|
760
777
|
|
|
761
778
|
/**
|
|
762
|
-
* @param
|
|
779
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
763
780
|
*/
|
|
764
781
|
draw(ctx) {
|
|
765
782
|
const me = this;
|
|
@@ -808,9 +825,9 @@ class Flow extends chart_js.Element {
|
|
|
808
825
|
}
|
|
809
826
|
|
|
810
827
|
/**
|
|
811
|
-
* @param
|
|
812
|
-
* @param
|
|
813
|
-
* @param
|
|
828
|
+
* @param {number} mouseX
|
|
829
|
+
* @param {number} mouseY
|
|
830
|
+
* @param {boolean} useFinalPosition
|
|
814
831
|
* @return {boolean}
|
|
815
832
|
*/
|
|
816
833
|
inRange(mouseX, mouseY, useFinalPosition) {
|
|
@@ -832,8 +849,8 @@ class Flow extends chart_js.Element {
|
|
|
832
849
|
}
|
|
833
850
|
|
|
834
851
|
/**
|
|
835
|
-
* @param
|
|
836
|
-
* @param
|
|
852
|
+
* @param {number} mouseX
|
|
853
|
+
* @param {boolean} useFinalPosition
|
|
837
854
|
* @return {boolean}
|
|
838
855
|
*/
|
|
839
856
|
inXRange(mouseX, useFinalPosition) {
|
|
@@ -842,8 +859,8 @@ class Flow extends chart_js.Element {
|
|
|
842
859
|
}
|
|
843
860
|
|
|
844
861
|
/**
|
|
845
|
-
* @param
|
|
846
|
-
* @param
|
|
862
|
+
* @param {number} mouseY
|
|
863
|
+
* @param {boolean} useFinalPosition
|
|
847
864
|
* @return {boolean}
|
|
848
865
|
*/
|
|
849
866
|
inYRange(mouseY, useFinalPosition) {
|
|
@@ -854,7 +871,7 @@ class Flow extends chart_js.Element {
|
|
|
854
871
|
}
|
|
855
872
|
|
|
856
873
|
/**
|
|
857
|
-
* @param
|
|
874
|
+
* @param {boolean} useFinalPosition
|
|
858
875
|
* @return {{x: number, y:number}}
|
|
859
876
|
*/
|
|
860
877
|
getCenterPoint(useFinalPosition) {
|
|
@@ -870,7 +887,7 @@ class Flow extends chart_js.Element {
|
|
|
870
887
|
}
|
|
871
888
|
|
|
872
889
|
/**
|
|
873
|
-
* @param
|
|
890
|
+
* @param {"x" | "y"} axis
|
|
874
891
|
* @return {number}
|
|
875
892
|
*/
|
|
876
893
|
getRange(axis) {
|
|
@@ -887,4 +904,4 @@ Flow.defaults = {
|
|
|
887
904
|
|
|
888
905
|
chart_js.Chart.register(SankeyController, Flow);
|
|
889
906
|
|
|
890
|
-
}))
|
|
907
|
+
}));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.8.
|
|
2
|
+
* chartjs-chart-sankey v0.8.1
|
|
3
3
|
* https://github.com/kurkle/chartjs-chart-sankey#readme
|
|
4
|
-
* (c)
|
|
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);function s(t,e){return t.from.sort(i("from")).forEach((t=>{const o=t.node;r(o.y)||(o.y=e,e=Math.max(
|
|
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)}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chartjs-chart-sankey",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"build": "rollup -c",
|
|
10
10
|
"autobuild": "rollup -c -w",
|
|
11
11
|
"dev": "karma start --no-single-run --auto-watch --browsers chrome",
|
|
12
|
+
"dev:ff": "karma start --no-single-run --auto-watch --browsers firefox",
|
|
12
13
|
"lint": "concurrently -r \"npm:lint-*\"",
|
|
13
14
|
"lint-js": "eslint \"src/**/*.js\" \"test/**/*.js\"",
|
|
14
15
|
"lint-md": "eslint \"**/*.md\"",
|