chartjs-chart-sankey 0.6.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,19 +1,20 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.6.0
2
+ * chartjs-chart-sankey v0.8.1
3
3
  * https://github.com/kurkle/chartjs-chart-sankey#readme
4
- * (c) 2021 Jukka Kurkela
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, helpers) { 'use strict';
12
-
13
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
14
-
15
- var Chart__default = /*#__PURE__*/_interopDefaultLegacy(Chart);
11
+ })(this, (function (chart_js, helpers) { 'use strict';
16
12
 
13
+ /**
14
+ * @param {Map<string, SankeyNode>} nodes
15
+ * @param {Array<SankeyDataPoint>} data
16
+ * @return {number}
17
+ */
17
18
  function calculateX(nodes, data) {
18
19
  const to = new Set(data.map(x => x.to));
19
20
  const from = new Set(data.map(x => x.from));
@@ -40,42 +41,87 @@ function calculateX(nodes, data) {
40
41
  return x;
41
42
  }
42
43
 
44
+ /**
45
+ * @param {Array<string>} keys
46
+ * @param {Set<string>} to
47
+ * @return {Array<string>}
48
+ */
43
49
  function nextColumn(keys, to) {
44
50
  const columnsNotInTo = keys.filter(key => !to.has(key));
45
51
  return columnsNotInTo.length ? columnsNotInTo : keys.slice(0, 1);
46
52
  }
47
53
 
54
+ /**
55
+ * @param x {any}
56
+ * @return {boolean}
57
+ */
48
58
  const defined = x => x !== undefined;
59
+
60
+ /**
61
+ * @param {SankeyNode} a
62
+ * @param {SankeyNode} b
63
+ * @return {number}
64
+ */
49
65
  const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
66
+
67
+ /**
68
+ * @param {Array<FromToElement>} list
69
+ * @param {string} prop
70
+ * @return {number}
71
+ */
50
72
  const nodeCount = (list, prop) => list.reduce((acc, cur) => acc + cur.node[prop].length + nodeCount(cur.node[prop], prop), 0);
51
- const flowByNodeCount = (prop) => (a, b) => nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop);
52
73
 
53
- function findLargestNode(nodeArray) {
54
- return nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out))[0];
55
- }
74
+ /**
75
+ * @param {string} prop
76
+ * @return {function(FromToElement, FromToElement): number}
77
+ */
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
+
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];
56
85
 
86
+ /**
87
+ * @param {SankeyNode} node
88
+ * @param {number} y
89
+ * @return {number}
90
+ */
57
91
  function processFrom(node, y) {
58
92
  node.from.sort(flowByNodeCount('from')).forEach(flow => {
59
93
  const n = flow.node;
60
94
  if (!defined(n.y)) {
61
95
  n.y = y;
62
- y = Math.max(y + n.out, processFrom(n, y));
96
+ processFrom(n, y);
63
97
  }
98
+ y = Math.max(n.y + n.out, y);
64
99
  });
65
100
  return y;
66
101
  }
67
102
 
103
+ /**
104
+ * @param {SankeyNode} node
105
+ * @param {number} y
106
+ * @return {number}
107
+ */
68
108
  function processTo(node, y) {
69
109
  node.to.sort(flowByNodeCount('to')).forEach(flow => {
70
110
  const n = flow.node;
71
111
  if (!defined(n.y)) {
72
112
  n.y = y;
73
- y = Math.max(y + n.in, processTo(n, y));
113
+ processTo(n, y);
74
114
  }
115
+ y = Math.max(n.y + n.in, y);
75
116
  });
76
117
  return y;
77
118
  }
78
119
 
120
+ /**
121
+ * @param {SankeyNode} node
122
+ * @param {number} value
123
+ * @return {number}
124
+ */
79
125
  function setOrGetY(node, value) {
80
126
  if (defined(node.y)) {
81
127
  return node.y;
@@ -84,14 +130,21 @@ function setOrGetY(node, value) {
84
130
  return value;
85
131
  }
86
132
 
133
+ /**
134
+ * @param {Array<SankeyNode>} nodeArray
135
+ * @param {number} maxX
136
+ * @return {number}
137
+ */
87
138
  function processRest(nodeArray, maxX) {
88
139
  const leftNodes = nodeArray.filter(node => node.x === 0);
89
140
  const rightNodes = nodeArray.filter(node => node.x === maxX);
90
141
  const leftToDo = leftNodes.filter(node => !defined(node.y));
91
142
  const rightToDo = rightNodes.filter(node => !defined(node.y));
143
+ const centerToDo = nodeArray.filter(node => node.x > 0 && node.x < maxX && !defined(node.y));
92
144
 
93
145
  let leftY = leftNodes.reduce((acc, cur) => Math.max(acc, (cur.y + cur.out) || 0), 0);
94
146
  let rightY = rightNodes.reduce((acc, cur) => Math.max(acc, (cur.y + cur.in) || 0), 0);
147
+ let centerY = 0;
95
148
 
96
149
  if (leftY >= rightY) {
97
150
  leftToDo.forEach(node => {
@@ -114,10 +167,23 @@ function processRest(nodeArray, maxX) {
114
167
  leftY = Math.max(leftY + node.out, processTo(node, leftY));
115
168
  });
116
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
+ });
117
178
 
118
- return Math.max(leftY, rightY);
179
+ return Math.max(leftY, rightY, centerY);
119
180
  }
120
181
 
182
+ /**
183
+ * @param {Array<SankeyNode>} nodeArray
184
+ * @param {number} maxX
185
+ * @return {number}
186
+ */
121
187
  function calculateY(nodeArray, maxX) {
122
188
  const start = findLargestNode(nodeArray);
123
189
  start.y = 0;
@@ -127,11 +193,18 @@ function calculateY(nodeArray, maxX) {
127
193
  return Math.max(left, right, rest);
128
194
  }
129
195
 
196
+ /**
197
+ * @param {Array<SankeyNode>} nodeArray
198
+ * @param {number} maxX
199
+ * @return {number}
200
+ */
130
201
  function calculateYUsingPriority(nodeArray, maxX) {
131
202
  let maxY = 0;
203
+ let nextYStart = 0;
132
204
  for (let x = 0; x <= maxX; x++) {
133
- let y = 0;
205
+ let y = nextYStart;
134
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;
135
208
  for (const node of nodes) {
136
209
  node.y = y;
137
210
  y += Math.max(node.out, node.in);
@@ -141,6 +214,11 @@ function calculateYUsingPriority(nodeArray, maxX) {
141
214
  return maxY;
142
215
  }
143
216
 
217
+ /**
218
+ * @param {Array<SankeyNode>} nodeArray
219
+ * @param {number} padding
220
+ * @return {number}
221
+ */
144
222
  function addPadding(nodeArray, padding) {
145
223
  let i = 1;
146
224
  let x = 0;
@@ -165,95 +243,150 @@ function addPadding(nodeArray, padding) {
165
243
  prev = i;
166
244
  }
167
245
  node.y += i * padding;
168
- maxY = Math.max(maxY, node.y + Math.max(node.in, node.out));
169
246
  i++;
170
247
  }
248
+ maxY = Math.max(maxY, node.y + Math.max(node.in, node.out));
171
249
  });
172
250
  return maxY;
173
251
  }
174
252
 
175
- function sortFlows(nodeArray) {
176
- nodeArray.forEach(node => {
253
+ /**
254
+ * @param {Array<SankeyNode>} nodeArray
255
+ * @param {'min' | 'max'} size
256
+ */
257
+ function sortFlows(nodeArray, size) {
258
+ nodeArray.forEach((node) => {
259
+ const nodeSize = Math[size](node.in || node.out, node.out || node.in);
260
+ const overlapFrom = nodeSize < node.in;
261
+ const overlapTo = nodeSize < node.out;
177
262
  let addY = 0;
178
- node.from.sort((a, b) => (a.node.y + a.node.out / 2) - (b.node.y + b.node.out / 2)).forEach(flow => {
179
- flow.addY = addY;
180
- addY += flow.flow;
263
+ let len = node.from.length;
264
+ node.from.sort((a, b) => (a.node.y + a.node.out / 2) - (b.node.y + b.node.out / 2)).forEach((flow, idx) => {
265
+ if (overlapFrom) {
266
+ flow.addY = idx * (nodeSize - flow.flow) / (len - 1);
267
+ } else {
268
+ flow.addY = addY;
269
+ addY += flow.flow;
270
+ }
181
271
  });
182
272
  addY = 0;
183
- node.to.sort((a, b) => (a.node.y + a.node.in / 2) - (b.node.y + b.node.in / 2)).forEach(flow => {
184
- flow.addY = addY;
185
- addY += flow.flow;
273
+ len = node.to.length;
274
+ node.to.sort((a, b) => (a.node.y + a.node.in / 2) - (b.node.y + b.node.in / 2)).forEach((flow, idx) => {
275
+ if (overlapTo) {
276
+ flow.addY = idx * (nodeSize - flow.flow) / (len - 1);
277
+ } else {
278
+ flow.addY = addY;
279
+ addY += flow.flow;
280
+ }
186
281
  });
187
282
  });
188
283
  }
189
284
 
190
- function layout(nodes, data, priority) {
285
+ /**
286
+ * @param {Map<string, SankeyNode>} nodes
287
+ * @param {Array<SankeyDataPoint>} data
288
+ * @param {boolean} priority
289
+ * @param {'min' | 'max'} size
290
+ * @return {{maxY: number, maxX: number}}
291
+ */
292
+ function layout(nodes, data, priority, size) {
191
293
  const nodeArray = [...nodes.values()];
192
294
  const maxX = calculateX(nodes, data);
193
295
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
194
296
  const padding = maxY * 0.03; // rows;
195
-
196
297
  const maxYWithPadding = addPadding(nodeArray, padding);
197
- sortFlows(nodeArray);
198
-
298
+ sortFlows(nodeArray, size);
199
299
  return {maxX, maxY: maxYWithPadding};
200
300
  }
201
301
 
202
- function buildNodesFromFlows(data) {
302
+ /**
303
+ * @param {Array<SankeyDataPoint>} data Array of raw data elements
304
+ * @return {Map<string, SankeyNode>}
305
+ */
306
+ function buildNodesFromRawData(data) {
203
307
  const nodes = new Map();
204
308
  for (let i = 0; i < data.length; i++) {
205
- const d = data[i];
206
- if (!nodes.has(d.from)) {
207
- nodes.set(d.from, {key: d.from, in: 0, out: d.flow, from: [], to: [{key: d.to, flow: d.flow}]});
309
+ const {from, to, flow} = data[i];
310
+
311
+ if (!nodes.has(from)) {
312
+ nodes.set(from, {
313
+ key: from,
314
+ in: 0,
315
+ out: flow,
316
+ from: [],
317
+ to: [{key: to, flow: flow, index: i}],
318
+ });
208
319
  } else {
209
- const node = nodes.get(d.from);
210
- node.out += d.flow;
211
- node.to.push({key: d.to, flow: d.flow});
320
+ const node = nodes.get(from);
321
+ node.out += flow;
322
+ node.to.push({key: to, flow: flow, index: i});
212
323
  }
213
- if (!nodes.has(d.to)) {
214
- nodes.set(d.to, {key: d.to, in: d.flow, out: 0, from: [{key: d.from, flow: d.flow}], to: []});
324
+ if (!nodes.has(to)) {
325
+ nodes.set(to, {
326
+ key: to,
327
+ in: flow,
328
+ out: 0,
329
+ from: [{key: from, flow: flow, index: i}],
330
+ to: [],
331
+ });
215
332
  } else {
216
- const node = nodes.get(d.to);
217
- node.in += d.flow;
218
- node.from.push({key: d.from, flow: d.flow});
333
+ const node = nodes.get(to);
334
+ node.in += flow;
335
+ node.from.push({key: from, flow: flow, index: i});
219
336
  }
220
337
  }
221
338
 
222
339
  const flowSort = (a, b) => b.flow - a.flow;
223
340
 
224
341
  [...nodes.values()].forEach(node => {
225
- let tmp = 0;
226
342
  node.from = node.from.sort(flowSort);
227
343
  node.from.forEach(x => {
228
344
  x.node = nodes.get(x.key);
229
- x.addY = tmp;
230
- tmp += x.flow;
231
345
  });
232
346
 
233
- tmp = 0;
234
347
  node.to = node.to.sort(flowSort);
235
348
  node.to.forEach(x => {
236
349
  x.node = nodes.get(x.key);
237
- x.addY = tmp;
238
- tmp += x.flow;
239
350
  });
240
351
  });
241
352
 
242
353
  return nodes;
243
354
  }
244
355
 
245
-
246
- function getAddY(arr, key) {
247
- for (let i = 0; i < arr.length; i++) {
248
- if (arr[i].key === key) {
249
- return arr[i].addY;
356
+ /**
357
+ * @param {Array<FromToElement>} arr
358
+ * @param {string} key
359
+ * @param {number} index
360
+ * @return {number}
361
+ */
362
+ function getAddY(arr, key, index) {
363
+ for (const item of arr) {
364
+ if (item.key === key && item.index === index) {
365
+ return item.addY;
250
366
  }
251
367
  }
252
368
  return 0;
253
369
  }
254
370
 
255
- class SankeyController extends Chart.DatasetController {
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
+ }
256
381
 
382
+ class SankeyController extends chart_js.DatasetController {
383
+ /**
384
+ * @param {ChartMeta<Flow, Element>} meta
385
+ * @param {Array<SankeyDataPoint>} data Array of original data elements
386
+ * @param {number} start
387
+ * @param {number} count
388
+ * @return {Array<SankeyParsedData>}
389
+ */
257
390
  parseObjectData(meta, data, start, count) {
258
391
  // https://github.com/chartjs/Chart.js/pull/8379
259
392
  if (count === 0) {
@@ -261,9 +394,10 @@ class SankeyController extends Chart.DatasetController {
261
394
  }
262
395
  const me = this;
263
396
  const {xScale, yScale} = meta;
264
- const parsed = [];
265
- const nodes = me._nodes = buildNodesFromFlows(data);
266
- const priority = me.getDataset().priority;
397
+ const parsed = []; /* Array<SankeyParsedData> */
398
+ const nodes = me._nodes = buildNodesFromRawData(data);
399
+ /* getDataset() => SankeyControllerDatasetOptions */
400
+ const {priority, size} = me.getDataset();
267
401
  if (priority) {
268
402
  for (const node of nodes.values()) {
269
403
  if (node.key in priority) {
@@ -272,17 +406,18 @@ class SankeyController extends Chart.DatasetController {
272
406
  }
273
407
  }
274
408
 
275
- const {maxX, maxY} = layout(nodes, data, !!priority);
409
+ const {maxX, maxY} = layout(nodes, data, !!priority, validateSizeValue(size));
276
410
 
277
411
  me._maxX = maxX;
278
412
  me._maxY = maxY;
279
413
 
414
+ /* loop over raw data elements {SankeyDataPoint} */
280
415
  for (let i = 0, ilen = data.length; i < ilen; ++i) {
281
- const flow = data[i];
282
- const from = nodes.get(flow.from);
283
- const to = nodes.get(flow.to);
284
- const fromY = from.y + getAddY(from.to, flow.to);
285
- const toY = to.y + getAddY(to.from, flow.from);
416
+ const dataPoint = data[i]; /* {SankeyDataPoint} */
417
+ const from = nodes.get(dataPoint.from); /* from {SankeyNode} */
418
+ const to = nodes.get(dataPoint.to); /* to {SankeyNode} */
419
+ const fromY = from.y + getAddY(from.to, dataPoint.to, i);
420
+ const toY = to.y + getAddY(to.from, dataPoint.from, i);
286
421
  parsed.push({
287
422
  x: xScale.parse(from.x, i),
288
423
  y: yScale.parse(fromY, i),
@@ -291,7 +426,7 @@ class SankeyController extends Chart.DatasetController {
291
426
  to,
292
427
  x: xScale.parse(to.x, i),
293
428
  y: yScale.parse(toY, i),
294
- height: yScale.parse(flow.flow, i),
429
+ height: yScale.parse(dataPoint.flow, i),
295
430
  }
296
431
  });
297
432
  }
@@ -313,6 +448,12 @@ class SankeyController extends Chart.DatasetController {
313
448
  me.updateElements(meta.data, 0, meta.data.length, mode);
314
449
  }
315
450
 
451
+ /**
452
+ * @param {Array<Flow>} elems
453
+ * @param {number} start
454
+ * @param {number} count
455
+ * @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
456
+ */
316
457
  updateElements(elems, start, count, mode) {
317
458
  const me = this;
318
459
  const {xScale, yScale} = me._cachedMeta;
@@ -323,6 +464,7 @@ class SankeyController extends Chart.DatasetController {
323
464
  const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
324
465
 
325
466
  for (let i = start; i < start + count; i++) {
467
+ /* getParsed(idx: number) => SankeyParsedData */
326
468
  const parsed = me.getParsed(i);
327
469
  const custom = parsed._custom;
328
470
  const y = yScale.getPixelForValue(parsed.y);
@@ -350,7 +492,8 @@ class SankeyController extends Chart.DatasetController {
350
492
  const me = this;
351
493
  const ctx = me._ctx;
352
494
  const nodes = me._nodes || new Map();
353
- const dataset = me.getDataset();
495
+ const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
496
+ const size = validateSizeValue(dataset.size);
354
497
  const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
355
498
  const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
356
499
  const labels = dataset.labels;
@@ -358,11 +501,11 @@ class SankeyController extends Chart.DatasetController {
358
501
 
359
502
  ctx.save();
360
503
  const chartArea = me.chart.chartArea;
361
-
362
504
  for (const node of nodes.values()) {
363
505
  const x = xScale.getPixelForValue(node.x);
364
506
  const y = yScale.getPixelForValue(node.y);
365
- const max = Math.max(node.in, node.out);
507
+
508
+ const max = Math[size](node.in || node.out, node.out || node.in);
366
509
  const height = Math.abs(yScale.getPixelForValue(node.y + max) - y);
367
510
  const label = labels && labels[node.key] || node.key;
368
511
  let textX = x;
@@ -375,16 +518,70 @@ class SankeyController extends Chart.DatasetController {
375
518
  ctx.textAlign = 'right';
376
519
  textX -= borderWidth + 4;
377
520
  }
378
- ctx.fillText(label, textX, y + height / 2);
521
+ this._drawLabel(label, y, height, ctx, textX);
379
522
  }
380
523
  ctx.restore();
381
524
  }
382
525
 
526
+ /**
527
+ * @param {string} label
528
+ * @param {number} y
529
+ * @param {number} height
530
+ * @param {CanvasRenderingContext2D} ctx
531
+ * @param {number} textX
532
+ * @private
533
+ */
534
+ _drawLabel(label, y, height, ctx, textX) {
535
+ const me = this;
536
+ const font = helpers.toFont(me.options.font, me.chart.options.font);
537
+ const lines = helpers.isNullOrUndef(label) ? [] : me.toTextLines(label);
538
+ const linesLength = lines.length;
539
+ const middle = y + height / 2;
540
+ const textHeight = font.lineHeight;
541
+ const padding = helpers.valueOrDefault(me.options.padding, textHeight / 2);
542
+
543
+ ctx.font = font.string;
544
+
545
+ if (linesLength > 1) {
546
+ const top = middle - (textHeight * linesLength / 2) + padding;
547
+ for (let i = 0; i < linesLength; i++) {
548
+ ctx.fillText(lines[i], textX, top + (i * textHeight));
549
+ }
550
+ } else {
551
+ ctx.fillText(label, textX, middle);
552
+ }
553
+ }
554
+
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
+
383
579
  _drawNodes() {
384
580
  const me = this;
385
581
  const ctx = me._ctx;
386
582
  const nodes = me._nodes || new Map();
387
- const dataset = me.getDataset();
583
+ const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
584
+ const size = validateSizeValue(dataset.size);
388
585
  const {xScale, yScale} = me._cachedMeta;
389
586
  const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
390
587
  const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
@@ -397,7 +594,8 @@ class SankeyController extends Chart.DatasetController {
397
594
  ctx.fillStyle = node.color;
398
595
  const x = xScale.getPixelForValue(node.x);
399
596
  const y = yScale.getPixelForValue(node.y);
400
- const max = Math.max(node.in, node.out);
597
+
598
+ const max = Math[size](node.in || node.out, node.out || node.in);
401
599
  const height = Math.abs(yScale.getPixelForValue(node.y + max) - y);
402
600
  if (borderWidth) {
403
601
  ctx.strokeRect(x, y, nodeWidth, height);
@@ -407,28 +605,35 @@ class SankeyController extends Chart.DatasetController {
407
605
  ctx.restore();
408
606
  }
409
607
 
608
+ /**
609
+ * That's where the drawing process happens
610
+ */
410
611
  draw() {
411
612
  const me = this;
412
613
  const ctx = me._ctx;
413
- const data = me.getMeta().data || [];
614
+ const data = me.getMeta().data || []; /* Array<Flow> */
414
615
 
415
616
  for (let i = 0, ilen = data.length; i < ilen; ++i) {
416
- const flow = data[i];
617
+ const flow = data[i]; /* Flow at index i */
417
618
  flow.from.color = flow.options.colorFrom;
418
619
  flow.to.color = flow.options.colorTo;
419
620
  }
420
621
 
622
+ /* draw SankeyNodes on the canvas */
421
623
  me._drawNodes();
422
624
 
625
+ /* draw Flow elements on the canvas */
423
626
  for (let i = 0, ilen = data.length; i < ilen; ++i) {
424
627
  data[i].draw(ctx);
425
628
  }
426
629
 
630
+ /* draw labels (for SankeyNodes) on the canvas */
427
631
  me._drawLabels();
428
632
  }
429
633
  }
430
634
 
431
635
  SankeyController.id = 'sankey';
636
+
432
637
  SankeyController.defaults = {
433
638
  dataElementType: 'flow',
434
639
  animations: {
@@ -464,9 +669,10 @@ SankeyController.defaults = {
464
669
  from: 'transparent'
465
670
  }
466
671
  }
467
- },
672
+ }
468
673
  }
469
674
  };
675
+
470
676
  SankeyController.overrides = {
471
677
  interaction: {
472
678
  mode: 'nearest',
@@ -487,10 +693,10 @@ SankeyController.overrides = {
487
693
  const item = context.dataset.data[context.dataIndex];
488
694
  return item.from + ' -> ' + item.to + ': ' + item.flow;
489
695
  }
490
- }
696
+ },
491
697
  },
492
698
  legend: {
493
- display: false
699
+ display: false,
494
700
  },
495
701
  },
496
702
  scales: {
@@ -499,7 +705,7 @@ SankeyController.overrides = {
499
705
  bounds: 'data',
500
706
  display: false,
501
707
  min: 0,
502
- offset: false
708
+ offset: false,
503
709
  },
504
710
  y: {
505
711
  type: 'linear',
@@ -507,19 +713,29 @@ SankeyController.overrides = {
507
713
  display: false,
508
714
  min: 0,
509
715
  reverse: true,
510
- offset: false
511
- }
716
+ offset: false,
717
+ },
512
718
  },
513
719
  layout: {
514
720
  padding: {
515
721
  top: 3,
516
722
  left: 3,
517
723
  right: 13,
518
- bottom: 3
519
- }
520
- }
724
+ bottom: 3,
725
+ },
726
+ },
521
727
  };
522
728
 
729
+ /**
730
+ * @typedef {{x: number, y: number}} ControlPoint
731
+ * @typedef {{cp1: ControlPoint, cp2: ControlPoint}} ControlPoints
732
+ *
733
+ * @param {number} x
734
+ * @param {number} y
735
+ * @param {number} x2
736
+ * @param {number} y2
737
+ * @return {ControlPoints}
738
+ */
523
739
  const controlPoints = (x, y, x2, y2) => x < x2
524
740
  ? {
525
741
  cp1: {x: x + (x2 - x) / 3 * 2, y},
@@ -530,9 +746,20 @@ const controlPoints = (x, y, x2, y2) => x < x2
530
746
  cp2: {x: x2 + (x - x2) / 3, y: 0}
531
747
  };
532
748
 
749
+ /**
750
+ *
751
+ * @param {ControlPoint} p1
752
+ * @param {ControlPoint} p2
753
+ * @param {number} t
754
+ * @return {ControlPoint}
755
+ */
533
756
  const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
534
757
 
535
- class Flow extends Chart.Element {
758
+ class Flow extends chart_js.Element {
759
+
760
+ /**
761
+ * @param {FlowConfig} cfg
762
+ */
536
763
  constructor(cfg) {
537
764
  super();
538
765
 
@@ -548,6 +775,9 @@ class Flow extends Chart.Element {
548
775
  }
549
776
  }
550
777
 
778
+ /**
779
+ * @param {CanvasRenderingContext2D} ctx
780
+ */
551
781
  draw(ctx) {
552
782
  const me = this;
553
783
  const {x, x2, y, y2, height, progress} = me;
@@ -594,6 +824,12 @@ class Flow extends Chart.Element {
594
824
  ctx.restore();
595
825
  }
596
826
 
827
+ /**
828
+ * @param {number} mouseX
829
+ * @param {number} mouseY
830
+ * @param {boolean} useFinalPosition
831
+ * @return {boolean}
832
+ */
597
833
  inRange(mouseX, mouseY, useFinalPosition) {
598
834
  const {x, y, x2, y2, height} = this.getProps(['x', 'y', 'x2', 'y2', 'height'], useFinalPosition);
599
835
  if (mouseX < x || mouseX > x2) {
@@ -612,11 +848,21 @@ class Flow extends Chart.Element {
612
848
  return mouseY >= topY && mouseY <= topY + height;
613
849
  }
614
850
 
851
+ /**
852
+ * @param {number} mouseX
853
+ * @param {boolean} useFinalPosition
854
+ * @return {boolean}
855
+ */
615
856
  inXRange(mouseX, useFinalPosition) {
616
857
  const {x, x2} = this.getProps(['x', 'x2'], useFinalPosition);
617
858
  return mouseX >= x && mouseX <= x2;
618
859
  }
619
860
 
861
+ /**
862
+ * @param {number} mouseY
863
+ * @param {boolean} useFinalPosition
864
+ * @return {boolean}
865
+ */
620
866
  inYRange(mouseY, useFinalPosition) {
621
867
  const {y, y2, height} = this.getProps(['y', 'y2', 'height'], useFinalPosition);
622
868
  const minY = Math.min(y, y2);
@@ -624,6 +870,10 @@ class Flow extends Chart.Element {
624
870
  return mouseY >= minY && mouseY <= maxY;
625
871
  }
626
872
 
873
+ /**
874
+ * @param {boolean} useFinalPosition
875
+ * @return {{x: number, y:number}}
876
+ */
627
877
  getCenterPoint(useFinalPosition) {
628
878
  const {x, y, x2, y2, height} = this.getProps(['x', 'y', 'x2', 'y2', 'height'], useFinalPosition);
629
879
  return {
@@ -632,10 +882,14 @@ class Flow extends Chart.Element {
632
882
  };
633
883
  }
634
884
 
635
- tooltipPosition() {
636
- return this.getCenterPoint();
885
+ tooltipPosition(useFinalPosition) {
886
+ return this.getCenterPoint(useFinalPosition);
637
887
  }
638
888
 
889
+ /**
890
+ * @param {"x" | "y"} axis
891
+ * @return {number}
892
+ */
639
893
  getRange(axis) {
640
894
  return axis === 'x' ? this.width / 2 : this.height / 2;
641
895
  }
@@ -648,6 +902,6 @@ Flow.defaults = {
648
902
  colorMode: 'gradient'
649
903
  };
650
904
 
651
- Chart__default['default'].register(SankeyController, Flow);
905
+ chart_js.Chart.register(SankeyController, Flow);
652
906
 
653
- })));
907
+ }));