chartjs-chart-sankey 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,30 +1,72 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.7.1
2
+ * chartjs-chart-sankey v0.9.0
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_js, helpers) { 'use strict';
11
+ })(this, (function (chart_js, helpers) { 'use strict';
12
12
 
13
13
  /**
14
- * @param nodes {Map<string, SankeyNode>}
15
- * @param data {Array<SankeyDataPoint>}
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
+
52
+ /**
53
+ * @param {Map<string, SankeyNode>} nodes
54
+ * @param {Array<SankeyDataPoint>} data
16
55
  * @return {number}
17
56
  */
18
57
  function calculateX(nodes, data) {
19
- const to = new Set(data.map(x => x.to));
20
- const from = new Set(data.map(x => x.from));
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 (let i = 0; i < column.length; i++) {
26
- nodes.get(column[i]).x = x;
27
- keys.delete(column[i]);
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,15 +77,19 @@ function calculateX(nodes, data) {
35
77
  [...nodes.keys()]
36
78
  .filter(key => !from.has(key))
37
79
  .forEach(key => {
38
- nodes.get(key).x = x;
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;
42
88
  }
43
89
 
44
90
  /**
45
- * @param keys {Array<string>}
46
- * @param to {Set<string>}
91
+ * @param {Array<string>} keys
92
+ * @param {Set<string>} to
47
93
  * @return {Array<string>}
48
94
  */
49
95
  function nextColumn(keys, to) {
@@ -52,74 +98,64 @@ function nextColumn(keys, to) {
52
98
  }
53
99
 
54
100
  /**
55
- * @param x {any}
56
- * @return {boolean}
57
- */
58
- const defined = x => x !== undefined;
59
-
60
- /**
61
- * @param a {SankeyNode}
62
- * @param b {SankeyNode}
101
+ * @param {SankeyNode} a
102
+ * @param {SankeyNode} b
63
103
  * @return {number}
64
104
  */
65
105
  const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
66
106
 
67
107
  /**
68
- * @param list {Array<FromToElement>}
69
- * @param prop {string}
108
+ * @param {Array<FromToElement>} list
109
+ * @param {string} prop
70
110
  * @return {number}
71
111
  */
72
112
  const nodeCount = (list, prop) => list.reduce((acc, cur) => acc + cur.node[prop].length + nodeCount(cur.node[prop], prop), 0);
73
113
 
74
114
  /**
75
- * @param prop {string}
115
+ * @param {string} prop
76
116
  * @return {function(FromToElement, FromToElement): number}
77
117
  */
78
- const flowByNodeCount = (prop) => (a, b) => nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop);
118
+ const flowByNodeCount = (prop) => (a, b) => (nodeCount(a.node[prop], prop) - nodeCount(b.node[prop], prop)) || (a.node[prop].length - b.node[prop].length);
79
119
 
80
120
  /**
81
- * @param nodeArray {Array<SankeyNode>}
82
- * @return {SankeyNode}
83
- */
84
- function findLargestNode(nodeArray) {
85
- return nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out))[0];
86
- }
87
-
88
- /**
89
- * @param node {SankeyNode}
90
- * @param y {number}
121
+ * @param {SankeyNode} node
122
+ * @param {number} y
91
123
  * @return {number}
92
124
  */
93
125
  function processFrom(node, y) {
94
- node.from.sort(flowByNodeCount('from')).forEach(flow => {
126
+ node.from.sort(flowByNodeCount('from'));
127
+ for (const flow of node.from) {
95
128
  const n = flow.node;
96
129
  if (!defined(n.y)) {
97
130
  n.y = y;
98
- y = Math.max(y + n.out, processFrom(n, y));
131
+ processFrom(n, y);
99
132
  }
100
- });
133
+ y = Math.max(n.y + n.out, y);
134
+ }
101
135
  return y;
102
136
  }
103
137
 
104
138
  /**
105
- * @param node {SankeyNode}
106
- * @param y {number}
139
+ * @param {SankeyNode} node
140
+ * @param {number} y
107
141
  * @return {number}
108
142
  */
109
143
  function processTo(node, y) {
110
- node.to.sort(flowByNodeCount('to')).forEach(flow => {
144
+ node.to.sort(flowByNodeCount('to'));
145
+ for (const flow of node.to) {
111
146
  const n = flow.node;
112
147
  if (!defined(n.y)) {
113
148
  n.y = y;
114
- y = Math.max(y + n.in, processTo(n, y));
149
+ processTo(n, y);
115
150
  }
116
- });
151
+ y = Math.max(n.y + n.in, y);
152
+ }
117
153
  return y;
118
154
  }
119
155
 
120
156
  /**
121
- * @param node {SankeyNode}
122
- * @param value {number}
157
+ * @param {SankeyNode} node
158
+ * @param {number} value
123
159
  * @return {number}
124
160
  */
125
161
  function setOrGetY(node, value) {
@@ -131,8 +167,8 @@ function setOrGetY(node, value) {
131
167
  }
132
168
 
133
169
  /**
134
- * @param nodeArray {Array<SankeyNode>}
135
- * @param maxX {number}
170
+ * @param {Array<SankeyNode>} nodeArray
171
+ * @param {number} maxX
136
172
  * @return {number}
137
173
  */
138
174
  function processRest(nodeArray, maxX) {
@@ -140,9 +176,11 @@ function processRest(nodeArray, maxX) {
140
176
  const rightNodes = nodeArray.filter(node => node.x === maxX);
141
177
  const leftToDo = leftNodes.filter(node => !defined(node.y));
142
178
  const rightToDo = rightNodes.filter(node => !defined(node.y));
179
+ const centerToDo = nodeArray.filter(node => node.x > 0 && node.x < maxX && !defined(node.y));
143
180
 
144
181
  let leftY = leftNodes.reduce((acc, cur) => Math.max(acc, (cur.y + cur.out) || 0), 0);
145
182
  let rightY = rightNodes.reduce((acc, cur) => Math.max(acc, (cur.y + cur.in) || 0), 0);
183
+ let centerY = 0;
146
184
 
147
185
  if (leftY >= rightY) {
148
186
  leftToDo.forEach(node => {
@@ -165,17 +203,26 @@ function processRest(nodeArray, maxX) {
165
203
  leftY = Math.max(leftY + node.out, processTo(node, leftY));
166
204
  });
167
205
  }
206
+ centerToDo.forEach(node => {
207
+ let y = nodeArray.filter(n => n.x === node.x && defined(n.y))
208
+ .reduce((acc, cur) => Math.max(acc, cur.y + Math.max(cur.in, cur.out)), 0);
209
+ y = setOrGetY(node, y);
210
+ y = Math.max(y + node.in, processFrom(node, y));
211
+ y = Math.max(y + node.out, processTo(node, y));
212
+ centerY = Math.max(centerY, y);
213
+ });
168
214
 
169
- return Math.max(leftY, rightY);
215
+ return Math.max(leftY, rightY, centerY);
170
216
  }
171
217
 
172
218
  /**
173
- * @param nodeArray {Array<SankeyNode>}
174
- * @param maxX {number}
219
+ * @param {Array<SankeyNode>} nodeArray
220
+ * @param {number} maxX
175
221
  * @return {number}
176
222
  */
177
223
  function calculateY(nodeArray, maxX) {
178
- const start = findLargestNode(nodeArray);
224
+ nodeArray.sort((a, b) => Math.max(b.in, b.out) - Math.max(a.in, a.out));
225
+ const start = nodeArray[0];
179
226
  start.y = 0;
180
227
  const left = processFrom(start, 0);
181
228
  const right = processTo(start, 0);
@@ -184,15 +231,17 @@ function calculateY(nodeArray, maxX) {
184
231
  }
185
232
 
186
233
  /**
187
- * @param nodeArray {Array<SankeyNode>}
188
- * @param maxX {number}
234
+ * @param {Array<SankeyNode>} nodeArray
235
+ * @param {number} maxX
189
236
  * @return {number}
190
237
  */
191
238
  function calculateYUsingPriority(nodeArray, maxX) {
192
239
  let maxY = 0;
240
+ let nextYStart = 0;
193
241
  for (let x = 0; x <= maxX; x++) {
194
- let y = 0;
242
+ let y = nextYStart;
195
243
  const nodes = nodeArray.filter(node => node.x === x).sort((a, b) => a.priority - b.priority);
244
+ nextYStart = nodes[0].to.filter(to => to.node.x > x + 1).reduce((acc, cur) => acc + cur.flow, 0) || 0;
196
245
  for (const node of nodes) {
197
246
  node.y = y;
198
247
  y += Math.max(node.out, node.in);
@@ -203,8 +252,8 @@ function calculateYUsingPriority(nodeArray, maxX) {
203
252
  }
204
253
 
205
254
  /**
206
- * @param nodeArray {Array<SankeyNode>}
207
- * @param padding {number}
255
+ * @param {Array<SankeyNode>} nodeArray
256
+ * @param {number} padding
208
257
  * @return {number}
209
258
  */
210
259
  function addPadding(nodeArray, padding) {
@@ -213,7 +262,8 @@ function addPadding(nodeArray, padding) {
213
262
  let prev = 0;
214
263
  let maxY = 0;
215
264
  const rows = [];
216
- nodeArray.sort(nodeByXY).forEach(node => {
265
+ nodeArray.sort(nodeByXY);
266
+ for (const node of nodeArray) {
217
267
  if (node.y) {
218
268
  if (node.x === 0) {
219
269
  rows.push(node.y);
@@ -234,49 +284,61 @@ function addPadding(nodeArray, padding) {
234
284
  i++;
235
285
  }
236
286
  maxY = Math.max(maxY, node.y + Math.max(node.in, node.out));
237
- });
238
-
287
+ }
239
288
  return maxY;
240
289
  }
241
290
 
242
291
  /**
243
- * @param nodeArray {Array<SankeyNode>}
292
+ * @param {Array<SankeyNode>} nodeArray
293
+ * @param {'min' | 'max'} size
244
294
  */
245
- function sortFlows(nodeArray) {
246
- nodeArray.forEach(node => {
295
+ function sortFlows(nodeArray, size) {
296
+ nodeArray.forEach((node) => {
297
+ const nodeSize = Math[size](node.in || node.out, node.out || node.in);
298
+ const overlapFrom = nodeSize < node.in;
299
+ const overlapTo = nodeSize < node.out;
247
300
  let addY = 0;
248
- node.from.sort((a, b) => (a.node.y + a.node.out / 2) - (b.node.y + b.node.out / 2)).forEach(flow => {
249
- flow.addY = addY;
250
- addY += flow.flow;
301
+ let len = node.from.length;
302
+ node.from.sort((a, b) => (a.node.y + a.node.out / 2) - (b.node.y + b.node.out / 2)).forEach((flow, idx) => {
303
+ if (overlapFrom) {
304
+ flow.addY = idx * (nodeSize - flow.flow) / (len - 1);
305
+ } else {
306
+ flow.addY = addY;
307
+ addY += flow.flow;
308
+ }
251
309
  });
252
310
  addY = 0;
253
- node.to.sort((a, b) => (a.node.y + a.node.in / 2) - (b.node.y + b.node.in / 2)).forEach(flow => {
254
- flow.addY = addY;
255
- addY += flow.flow;
311
+ len = node.to.length;
312
+ node.to.sort((a, b) => (a.node.y + a.node.in / 2) - (b.node.y + b.node.in / 2)).forEach((flow, idx) => {
313
+ if (overlapTo) {
314
+ flow.addY = idx * (nodeSize - flow.flow) / (len - 1);
315
+ } else {
316
+ flow.addY = addY;
317
+ addY += flow.flow;
318
+ }
256
319
  });
257
320
  });
258
321
  }
259
322
 
260
323
  /**
261
- * @param nodes {Map<string, SankeyNode>}
262
- * @param data {Array<SankeyDataPoint>}
263
- * @param priority {boolean}
324
+ * @param {Map<string, SankeyNode>} nodes
325
+ * @param {Array<SankeyDataPoint>} data
326
+ * @param {boolean} priority
327
+ * @param {'min' | 'max'} size
264
328
  * @return {{maxY: number, maxX: number}}
265
329
  */
266
- function layout(nodes, data, priority) {
330
+ function layout(nodes, data, priority, size) {
267
331
  const nodeArray = [...nodes.values()];
268
332
  const maxX = calculateX(nodes, data);
269
333
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
270
334
  const padding = maxY * 0.03; // rows;
271
-
272
335
  const maxYWithPadding = addPadding(nodeArray, padding);
273
- sortFlows(nodeArray);
274
-
336
+ sortFlows(nodeArray, size);
275
337
  return {maxX, maxY: maxYWithPadding};
276
338
  }
277
339
 
278
340
  /**
279
- * @param data {Array<SankeyDataPoint>} Array of raw data elements
341
+ * @param {Array<SankeyDataPoint>} data Array of raw data elements
280
342
  * @return {Map<string, SankeyNode>}
281
343
  */
282
344
  function buildNodesFromRawData(data) {
@@ -290,45 +352,39 @@ function buildNodesFromRawData(data) {
290
352
  in: 0,
291
353
  out: flow,
292
354
  from: [],
293
- to: [{key: to, flow: flow}],
355
+ to: [{key: to, flow: flow, index: i}],
294
356
  });
295
357
  } else {
296
358
  const node = nodes.get(from);
297
359
  node.out += flow;
298
- node.to.push({key: to, flow: flow});
360
+ node.to.push({key: to, flow: flow, index: i});
299
361
  }
300
362
  if (!nodes.has(to)) {
301
363
  nodes.set(to, {
302
364
  key: to,
303
365
  in: flow,
304
366
  out: 0,
305
- from: [{key: from, flow: flow}],
367
+ from: [{key: from, flow: flow, index: i}],
306
368
  to: [],
307
369
  });
308
370
  } else {
309
371
  const node = nodes.get(to);
310
372
  node.in += flow;
311
- node.from.push({key: from, flow: flow});
373
+ node.from.push({key: from, flow: flow, index: i});
312
374
  }
313
375
  }
314
376
 
315
377
  const flowSort = (a, b) => b.flow - a.flow;
316
378
 
317
379
  [...nodes.values()].forEach(node => {
318
- let tmp = 0;
319
380
  node.from = node.from.sort(flowSort);
320
381
  node.from.forEach(x => {
321
382
  x.node = nodes.get(x.key);
322
- x.addY = tmp;
323
- tmp += x.flow;
324
383
  });
325
384
 
326
- tmp = 0;
327
385
  node.to = node.to.sort(flowSort);
328
386
  node.to.forEach(x => {
329
387
  x.node = nodes.get(x.key);
330
- x.addY = tmp;
331
- tmp += x.flow;
332
388
  });
333
389
  });
334
390
 
@@ -336,14 +392,15 @@ function buildNodesFromRawData(data) {
336
392
  }
337
393
 
338
394
  /**
339
- * @param arr {Array<FromToElement>}
340
- * @param key {string}
395
+ * @param {Array<FromToElement>} arr
396
+ * @param {string} key
397
+ * @param {number} index
341
398
  * @return {number}
342
399
  */
343
- function getAddY(arr, key) {
344
- for (let i = 0; i < arr.length; i++) {
345
- if (arr[i].key === key) {
346
- return arr[i].addY;
400
+ function getAddY(arr, key, index) {
401
+ for (const item of arr) {
402
+ if (item.key === key && item.index === index) {
403
+ return item.addY;
347
404
  }
348
405
  }
349
406
  return 0;
@@ -351,10 +408,10 @@ function getAddY(arr, key) {
351
408
 
352
409
  class SankeyController extends chart_js.DatasetController {
353
410
  /**
354
- * @param meta {ChartMeta<Flow, Element>}
355
- * @param data {Array<SankeyDataPoint>} Array of original data elements
356
- * @param start {number}
357
- * @param count {number}
411
+ * @param {ChartMeta<Flow, Element>} meta
412
+ * @param {Array<SankeyDataPoint>} data Array of original data elements
413
+ * @param {number} start
414
+ * @param {number} count
358
415
  * @return {Array<SankeyParsedData>}
359
416
  */
360
417
  parseObjectData(meta, data, start, count) {
@@ -367,7 +424,7 @@ class SankeyController extends chart_js.DatasetController {
367
424
  const parsed = []; /* Array<SankeyParsedData> */
368
425
  const nodes = me._nodes = buildNodesFromRawData(data);
369
426
  /* getDataset() => SankeyControllerDatasetOptions */
370
- const priority = me.getDataset().priority;
427
+ const {column, priority, size} = me.getDataset();
371
428
  if (priority) {
372
429
  for (const node of nodes.values()) {
373
430
  if (node.key in priority) {
@@ -375,18 +432,26 @@ class SankeyController extends chart_js.DatasetController {
375
432
  }
376
433
  }
377
434
  }
435
+ if (column) {
436
+ for (const node of nodes.values()) {
437
+ if (node.key in column) {
438
+ node.column = true;
439
+ node.x = column[node.key];
440
+ }
441
+ }
442
+ }
378
443
 
379
- const {maxX, maxY} = layout(nodes, data, !!priority);
444
+ const {maxX, maxY} = layout(nodes, data, !!priority, validateSizeValue(size));
380
445
 
381
446
  me._maxX = maxX;
382
447
  me._maxY = maxY;
383
448
 
384
449
  for (let i = 0, ilen = data.length; i < ilen; ++i) {
385
- const flow = data[i];
386
- const from = nodes.get(flow.from);
387
- const to = nodes.get(flow.to);
388
- const fromY = from.y + getAddY(from.to, flow.to);
389
- const toY = to.y + getAddY(to.from, flow.from);
450
+ const dataPoint = data[i];
451
+ const from = nodes.get(dataPoint.from);
452
+ const to = nodes.get(dataPoint.to);
453
+ const fromY = from.y + getAddY(from.to, dataPoint.to, i);
454
+ const toY = to.y + getAddY(to.from, dataPoint.from, i);
390
455
  parsed.push({
391
456
  x: xScale.parse(from.x, i),
392
457
  y: yScale.parse(fromY, i),
@@ -395,7 +460,7 @@ class SankeyController extends chart_js.DatasetController {
395
460
  to,
396
461
  x: xScale.parse(to.x, i),
397
462
  y: yScale.parse(toY, i),
398
- height: yScale.parse(flow.flow, i),
463
+ height: yScale.parse(dataPoint.flow, i),
399
464
  }
400
465
  });
401
466
  }
@@ -418,10 +483,10 @@ class SankeyController extends chart_js.DatasetController {
418
483
  }
419
484
 
420
485
  /**
421
- * @param elems {Array<Flow>}
422
- * @param start {number}
423
- * @param count {number}
424
- * @param mode {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"}
486
+ * @param {Array<Flow>} elems
487
+ * @param {number} start
488
+ * @param {number} count
489
+ * @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
425
490
  */
426
491
  updateElements(elems, start, count, mode) {
427
492
  const me = this;
@@ -462,6 +527,7 @@ class SankeyController extends chart_js.DatasetController {
462
527
  const ctx = me._ctx;
463
528
  const nodes = me._nodes || new Map();
464
529
  const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
530
+ const size = validateSizeValue(dataset.size);
465
531
  const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
466
532
  const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
467
533
  const labels = dataset.labels;
@@ -472,7 +538,8 @@ class SankeyController extends chart_js.DatasetController {
472
538
  for (const node of nodes.values()) {
473
539
  const x = xScale.getPixelForValue(node.x);
474
540
  const y = yScale.getPixelForValue(node.y);
475
- const max = Math.max(node.in, node.out);
541
+
542
+ const max = Math[size](node.in || node.out, node.out || node.in);
476
543
  const height = Math.abs(yScale.getPixelForValue(node.y + max) - y);
477
544
  const label = labels && labels[node.key] || node.key;
478
545
  let textX = x;
@@ -491,19 +558,23 @@ class SankeyController extends chart_js.DatasetController {
491
558
  }
492
559
 
493
560
  /**
494
- * @param label {string}
495
- * @param y {number}
496
- * @param height {number}
497
- * @param ctx {CanvasRenderingContext2D}
498
- * @param textX {number}
561
+ * @param {string} label
562
+ * @param {number} y
563
+ * @param {number} height
564
+ * @param {CanvasRenderingContext2D} ctx
565
+ * @param {number} textX
499
566
  * @private
500
567
  */
501
568
  _drawLabel(label, y, height, ctx, textX) {
502
- const lines = helpers.isNullOrUndef(label) ? [] : this.toTextLines(label);
569
+ const me = this;
570
+ const font = helpers.toFont(me.options.font, me.chart.options.font);
571
+ const lines = helpers.isNullOrUndef(label) ? [] : toTextLines(label);
503
572
  const linesLength = lines.length;
504
573
  const middle = y + height / 2;
505
- const padding = 7.5;
506
- const textHeight = 15;
574
+ const textHeight = font.lineHeight;
575
+ const padding = helpers.valueOrDefault(me.options.padding, textHeight / 2);
576
+
577
+ ctx.font = font.string;
507
578
 
508
579
  if (linesLength > 1) {
509
580
  const top = middle - (textHeight * linesLength / 2) + padding;
@@ -515,35 +586,12 @@ class SankeyController extends chart_js.DatasetController {
515
586
  }
516
587
  }
517
588
 
518
- /**
519
- * @param inputs {string | Array<string>}
520
- * @return {Array<string>}
521
- * @todo move this in Chart.helpers.toTextLines
522
- */
523
- toTextLines(inputs) {
524
- let lines = [];
525
- let input;
526
-
527
- inputs = [].concat(inputs);
528
- while (inputs.length) {
529
- input = inputs.pop();
530
- if (typeof input === 'string') {
531
- lines.unshift.apply(lines, input.split('\n'));
532
- } else if (Array.isArray(input)) {
533
- inputs.push.apply(inputs, input);
534
- } else if (!helpers.isNullOrUndef(inputs)) {
535
- lines.unshift('' + input);
536
- }
537
- }
538
-
539
- return lines;
540
- }
541
-
542
589
  _drawNodes() {
543
590
  const me = this;
544
591
  const ctx = me._ctx;
545
592
  const nodes = me._nodes || new Map();
546
593
  const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
594
+ const size = validateSizeValue(dataset.size);
547
595
  const {xScale, yScale} = me._cachedMeta;
548
596
  const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
549
597
  const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
@@ -556,7 +604,8 @@ class SankeyController extends chart_js.DatasetController {
556
604
  ctx.fillStyle = node.color;
557
605
  const x = xScale.getPixelForValue(node.x);
558
606
  const y = yScale.getPixelForValue(node.y);
559
- const max = Math.max(node.in, node.out);
607
+
608
+ const max = Math[size](node.in || node.out, node.out || node.in);
560
609
  const height = Math.abs(yScale.getPixelForValue(node.y + max) - y);
561
610
  if (borderWidth) {
562
611
  ctx.strokeRect(x, y, nodeWidth, height);
@@ -594,6 +643,7 @@ class SankeyController extends chart_js.DatasetController {
594
643
  }
595
644
 
596
645
  SankeyController.id = 'sankey';
646
+
597
647
  SankeyController.defaults = {
598
648
  dataElementType: 'flow',
599
649
  animations: {
@@ -629,9 +679,10 @@ SankeyController.defaults = {
629
679
  from: 'transparent'
630
680
  }
631
681
  }
632
- },
682
+ }
633
683
  }
634
684
  };
685
+
635
686
  SankeyController.overrides = {
636
687
  interaction: {
637
688
  mode: 'nearest',
@@ -652,10 +703,10 @@ SankeyController.overrides = {
652
703
  const item = context.dataset.data[context.dataIndex];
653
704
  return item.from + ' -> ' + item.to + ': ' + item.flow;
654
705
  }
655
- }
706
+ },
656
707
  },
657
708
  legend: {
658
- display: false
709
+ display: false,
659
710
  },
660
711
  },
661
712
  scales: {
@@ -664,7 +715,7 @@ SankeyController.overrides = {
664
715
  bounds: 'data',
665
716
  display: false,
666
717
  min: 0,
667
- offset: false
718
+ offset: false,
668
719
  },
669
720
  y: {
670
721
  type: 'linear',
@@ -672,27 +723,27 @@ SankeyController.overrides = {
672
723
  display: false,
673
724
  min: 0,
674
725
  reverse: true,
675
- offset: false
676
- }
726
+ offset: false,
727
+ },
677
728
  },
678
729
  layout: {
679
730
  padding: {
680
731
  top: 3,
681
732
  left: 3,
682
733
  right: 13,
683
- bottom: 3
684
- }
685
- }
734
+ bottom: 3,
735
+ },
736
+ },
686
737
  };
687
738
 
688
739
  /**
689
740
  * @typedef {{x: number, y: number}} ControlPoint
690
741
  * @typedef {{cp1: ControlPoint, cp2: ControlPoint}} ControlPoints
691
742
  *
692
- * @param x {number}
693
- * @param y {number}
694
- * @param x2 {number}
695
- * @param y2 {number}
743
+ * @param {number} x
744
+ * @param {number} y
745
+ * @param {number} x2
746
+ * @param {number} y2
696
747
  * @return {ControlPoints}
697
748
  */
698
749
  const controlPoints = (x, y, x2, y2) => x < x2
@@ -707,17 +758,39 @@ const controlPoints = (x, y, x2, y2) => x < x2
707
758
 
708
759
  /**
709
760
  *
710
- * @param p1 {ControlPoint}
711
- * @param p2 {ControlPoint}
712
- * @param t {number}
761
+ * @param {ControlPoint} p1
762
+ * @param {ControlPoint} p2
763
+ * @param {number} t
713
764
  * @return {ControlPoint}
714
765
  */
715
766
  const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)});
716
767
 
768
+ /**
769
+ * @param {CanvasRenderingContext2D} ctx
770
+ * @param {Flow} flow
771
+ */
772
+ function setStyle(ctx, {x, x2, options}) {
773
+ let fill;
774
+
775
+ if (options.colorMode === 'from') {
776
+ fill = helpers.color(options.colorFrom).alpha(0.5).rgbString();
777
+ } else if (options.colorMode === 'to') {
778
+ fill = helpers.color(options.colorTo).alpha(0.5).rgbString();
779
+ } else {
780
+ fill = ctx.createLinearGradient(x, 0, x2, 0);
781
+ fill.addColorStop(0, helpers.color(options.colorFrom).alpha(0.5).rgbString());
782
+ fill.addColorStop(1, helpers.color(options.colorTo).alpha(0.5).rgbString());
783
+ }
784
+
785
+ ctx.fillStyle = fill;
786
+ ctx.strokeStyle = fill;
787
+ ctx.lineWidth = 0.5;
788
+ }
789
+
717
790
  class Flow extends chart_js.Element {
718
791
 
719
792
  /**
720
- * @param cfg {FlowConfig}
793
+ * @param {FlowConfig} cfg
721
794
  */
722
795
  constructor(cfg) {
723
796
  super();
@@ -735,13 +808,12 @@ class Flow extends chart_js.Element {
735
808
  }
736
809
 
737
810
  /**
738
- * @param ctx {CanvasRenderingContext2D}
811
+ * @param {CanvasRenderingContext2D} ctx
739
812
  */
740
813
  draw(ctx) {
741
814
  const me = this;
742
815
  const {x, x2, y, y2, height, progress} = me;
743
816
  const {cp1, cp2} = controlPoints(x, y, x2, y2);
744
- const options = me.options;
745
817
 
746
818
  if (progress === 0) {
747
819
  return;
@@ -753,21 +825,7 @@ class Flow extends chart_js.Element {
753
825
  ctx.clip();
754
826
  }
755
827
 
756
- let fill;
757
-
758
- if (options.colorMode === 'from') {
759
- fill = helpers.color(options.colorFrom).alpha(0.5).rgbString();
760
- } else if (options.colorMode === 'to') {
761
- fill = helpers.color(options.colorTo).alpha(0.5).rgbString();
762
- } else {
763
- fill = ctx.createLinearGradient(x, 0, x2, 0);
764
- fill.addColorStop(0, helpers.color(options.colorFrom).alpha(0.5).rgbString());
765
- fill.addColorStop(1, helpers.color(options.colorTo).alpha(0.5).rgbString());
766
- }
767
-
768
- ctx.fillStyle = fill;
769
- ctx.strokeStyle = fill;
770
- ctx.lineWidth = 0.5;
828
+ setStyle(ctx, me);
771
829
 
772
830
  ctx.beginPath();
773
831
  ctx.moveTo(x, y);
@@ -784,9 +842,9 @@ class Flow extends chart_js.Element {
784
842
  }
785
843
 
786
844
  /**
787
- * @param mouseX {number}
788
- * @param mouseY {number}
789
- * @param useFinalPosition {boolean}
845
+ * @param {number} mouseX
846
+ * @param {number} mouseY
847
+ * @param {boolean} useFinalPosition
790
848
  * @return {boolean}
791
849
  */
792
850
  inRange(mouseX, mouseY, useFinalPosition) {
@@ -808,8 +866,8 @@ class Flow extends chart_js.Element {
808
866
  }
809
867
 
810
868
  /**
811
- * @param mouseX {number}
812
- * @param useFinalPosition {boolean}
869
+ * @param {number} mouseX
870
+ * @param {boolean} useFinalPosition
813
871
  * @return {boolean}
814
872
  */
815
873
  inXRange(mouseX, useFinalPosition) {
@@ -818,8 +876,8 @@ class Flow extends chart_js.Element {
818
876
  }
819
877
 
820
878
  /**
821
- * @param mouseY {number}
822
- * @param useFinalPosition {boolean}
879
+ * @param {number} mouseY
880
+ * @param {boolean} useFinalPosition
823
881
  * @return {boolean}
824
882
  */
825
883
  inYRange(mouseY, useFinalPosition) {
@@ -830,7 +888,7 @@ class Flow extends chart_js.Element {
830
888
  }
831
889
 
832
890
  /**
833
- * @param useFinalPosition {boolean}
891
+ * @param {boolean} useFinalPosition
834
892
  * @return {{x: number, y:number}}
835
893
  */
836
894
  getCenterPoint(useFinalPosition) {
@@ -846,7 +904,7 @@ class Flow extends chart_js.Element {
846
904
  }
847
905
 
848
906
  /**
849
- * @param axis {"x" | "y"}
907
+ * @param {"x" | "y"} axis
850
908
  * @return {number}
851
909
  */
852
910
  getRange(axis) {
@@ -863,4 +921,4 @@ Flow.defaults = {
863
921
 
864
922
  chart_js.Chart.register(SankeyController, Flow);
865
923
 
866
- })));
924
+ }));