chartjs-chart-sankey 0.14.3 → 0.14.4

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,5 +1,5 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.14.3
2
+ * chartjs-chart-sankey v0.14.4
3
3
  * https://chartjs-chart-sankey.pages.dev/
4
4
  * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
@@ -126,6 +126,12 @@ function buildNodesFromData(data, { size, priority, column }) {
126
126
  }
127
127
 
128
128
  const SMALL_VALUE = 1e-6;
129
+ function nodeX$1(node) {
130
+ return node.x ?? 0;
131
+ }
132
+ function nodeY$1(node) {
133
+ return node.y ?? 0;
134
+ }
129
135
  const getAllKeysForward = (nodes, visited = new Set())=>{
130
136
  const keys = [];
131
137
  for (const node of nodes){
@@ -183,7 +189,7 @@ function calculateX(nodeMap, data, mode) {
183
189
  x++;
184
190
  }
185
191
  }
186
- const maxX = allNodes.reduce((max, node)=>Math.max(max, node.x), 0);
192
+ const maxX = allNodes.reduce((max, node)=>Math.max(max, nodeX$1(node)), 0);
187
193
  if (mode === 'edge') {
188
194
  const from = new Set(data.map((dataPoint)=>dataPoint.from));
189
195
  allKeys.filter((key)=>!from.has(key)).forEach((key)=>{
@@ -223,7 +229,7 @@ function processFrom(node, y) {
223
229
  }
224
230
  y = Math.max(n.y + n.out, y);
225
231
  }
226
- return node.y + node.size;
232
+ return nodeY$1(node) + node.size;
227
233
  }
228
234
  function processTo(node, y) {
229
235
  if (!node.to.length) return y;
@@ -236,7 +242,7 @@ function processTo(node, y) {
236
242
  }
237
243
  y = Math.max(n.y + Math.max(n.in, n.out), y);
238
244
  }
239
- return node.y + node.size;
245
+ return nodeY$1(node) + node.size;
240
246
  }
241
247
  function setOrGetY(node, value) {
242
248
  if (defined(node.y)) {
@@ -250,9 +256,9 @@ function processRest(nodeArray, maxX) {
250
256
  const rightNodes = nodeArray.filter((node)=>node.x === maxX);
251
257
  const leftToDo = leftNodes.filter((node)=>!defined(node.y));
252
258
  const rightToDo = rightNodes.filter((node)=>!defined(node.y));
253
- const centerToDo = nodeArray.filter((node)=>node.x > 0 && node.x < maxX && !defined(node.y));
254
- let leftY = leftNodes.reduce((acc, cur)=>Math.max(acc, cur.y + cur.out || 0), 0) + SMALL_VALUE;
255
- let rightY = rightNodes.reduce((acc, cur)=>Math.max(acc, cur.y + cur.in || 0), 0) + SMALL_VALUE;
259
+ const centerToDo = nodeArray.filter((node)=>nodeX$1(node) > 0 && nodeX$1(node) < maxX && !defined(node.y));
260
+ let leftY = leftNodes.reduce((acc, cur)=>Math.max(acc, nodeY$1(cur) + cur.out || 0), 0) + SMALL_VALUE;
261
+ let rightY = rightNodes.reduce((acc, cur)=>Math.max(acc, nodeY$1(cur) + cur.in || 0), 0) + SMALL_VALUE;
256
262
  let centerY = 0;
257
263
  if (leftY >= rightY) {
258
264
  leftToDo.forEach((node)=>{
@@ -273,7 +279,7 @@ function processRest(nodeArray, maxX) {
273
279
  });
274
280
  }
275
281
  centerToDo.forEach((node)=>{
276
- let y = nodeArray.filter((n)=>n.x === node.x && defined(n.y)).reduce((acc, cur)=>Math.max(acc, cur.y + Math.max(cur.in, cur.out)), 0);
282
+ let y = nodeArray.filter((n)=>nodeX$1(n) === nodeX$1(node) && defined(n.y)).reduce((acc, cur)=>Math.max(acc, nodeY$1(cur) + Math.max(cur.in, cur.out)), 0);
277
283
  y = setOrGetY(node, y);
278
284
  y = Math.max(y + node.in, processFrom(node, y));
279
285
  y = Math.max(y + node.out, processTo(node, y));
@@ -284,25 +290,29 @@ function processRest(nodeArray, maxX) {
284
290
  const fixTop = (nodeArray, maxX)=>{
285
291
  let maxY = 0;
286
292
  for(let x = 0; x <= maxX; x++){
287
- const nodes = nodeArray.filter((n)=>n.x === x).sort((a, b)=>a.y - b.y);
293
+ const nodes = nodeArray.filter((n)=>nodeX$1(n) === x).sort((a, b)=>nodeY$1(a) - nodeY$1(b));
288
294
  let minY = 0;
289
295
  for (const node of nodes){
290
- if (node.y < minY) node.y = minY;
291
- minY = node.y + node.size;
296
+ if (nodeY$1(node) < minY) node.y = minY;
297
+ minY = nodeY$1(node) + node.size;
292
298
  }
293
299
  maxY = Math.max(maxY, minY);
294
300
  }
295
301
  return maxY;
296
302
  };
297
303
  const findStartNode = (nodeArray, maxX)=>{
298
- const size = [
304
+ const sorted = [
299
305
  ...nodeArray
300
- ].sort((a, b)=>a.size - b.size).pop().size;
306
+ ].sort((a, b)=>a.size - b.size);
307
+ const largest = sorted[sorted.length - 1];
308
+ const size = largest.size;
301
309
  const biggest = nodeArray.filter((n)=>n.size === size);
302
- if (biggest.length === 1) return biggest[0];
303
- biggest.sort((a, b)=>a.x - b.x);
304
- if (biggest[0].x === 0) return biggest[0];
305
- if (biggest[biggest.length - 1].x === maxX) return biggest.pop();
310
+ const first = biggest[0];
311
+ if (biggest.length === 1) return first;
312
+ biggest.sort((a, b)=>nodeX$1(a) - nodeX$1(b));
313
+ if (nodeX$1(first) === 0) return first;
314
+ const last = biggest[biggest.length - 1];
315
+ if (nodeX$1(last) === maxX) return last;
306
316
  const mid = Math.floor(biggest.length / 2);
307
317
  return biggest[mid];
308
318
  };
@@ -320,8 +330,8 @@ function calculateYUsingPriority(nodeArray, maxX) {
320
330
  let nextYStart = 0;
321
331
  for(let x = 0; x <= maxX; x++){
322
332
  let y = nextYStart;
323
- const nodes = nodeArray.filter((node)=>node.x === x).sort((a, b)=>(a.priority ?? 0) - (b.priority ?? 0));
324
- nextYStart = nodes.length ? nodes[0].to.filter((to)=>to.node.x > x + 1).reduce((acc, cur)=>acc + cur.flow, 0) || 0 : 0;
333
+ const nodes = nodeArray.filter((node)=>nodeX$1(node) === x).sort((a, b)=>(a.priority ?? 0) - (b.priority ?? 0));
334
+ nextYStart = nodes.length ? nodes[0].to.filter((to)=>nodeX$1(to.node) > x + 1).reduce((acc, cur)=>acc + cur.flow, 0) || 0 : 0;
325
335
  for (const node of nodes){
326
336
  node.y = y;
327
337
  y += Math.max(node.out, node.in);
@@ -331,9 +341,9 @@ function calculateYUsingPriority(nodeArray, maxX) {
331
341
  return maxY;
332
342
  }
333
343
  const nodeByXYSize = (a, b)=>{
334
- if (a.x !== b.x) return a.x - b.x;
335
- if (a.y === b.y) return a.size - b.size;
336
- return a.y - b.y;
344
+ if (nodeX$1(a) !== nodeX$1(b)) return nodeX$1(a) - nodeX$1(b);
345
+ if (nodeY$1(a) === nodeY$1(b)) return a.size - b.size;
346
+ return nodeY$1(a) - nodeY$1(b);
337
347
  };
338
348
  function addPadding(nodeArray, padding) {
339
349
  let maxY = 0;
@@ -341,31 +351,33 @@ const nodeByXYSize = (a, b)=>{
341
351
  const grid = [];
342
352
  const getColIndex = (x)=>{
343
353
  if (!columnXs.has(x)) {
344
- columnXs.set(x, grid.length);
354
+ const index = grid.length;
355
+ columnXs.set(x, index);
345
356
  grid.push([]);
357
+ return index;
346
358
  }
347
- return columnXs.get(x);
359
+ return columnXs.get(x) ?? 0;
348
360
  };
349
361
  nodeArray.sort(nodeByXYSize);
350
362
  for (const node of nodeArray){
351
- const colIdx = getColIndex(node.x);
352
- const column = grid[colIdx];
353
- if (node.y) {
354
- column.push(node.y);
363
+ const colIdx = getColIndex(nodeX$1(node));
364
+ const column = grid[colIdx] ?? [];
365
+ if (nodeY$1(node)) {
366
+ column.push(nodeY$1(node));
355
367
  let paddings = column.length;
356
368
  if (node.in) {
357
369
  for(let col = 0; col < colIdx; col++){
358
- const otherColumn = grid[col];
370
+ const otherColumn = grid[col] ?? [];
359
371
  for(let row = 0; row < otherColumn.length; row++){
360
- if (otherColumn[row] > node.y) break;
372
+ if (otherColumn[row] > nodeY$1(node)) break;
361
373
  paddings = Math.max(row + 1, paddings);
362
374
  }
363
375
  }
364
- while(column.length < paddings)column.push(node.y);
376
+ while(column.length < paddings)column.push(nodeY$1(node));
365
377
  }
366
- node.y += paddings * padding;
378
+ node.y = nodeY$1(node) + paddings * padding;
367
379
  }
368
- maxY = Math.max(maxY, node.y + Math.max(node.in, node.out));
380
+ maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
369
381
  }
370
382
  return maxY;
371
383
  }
@@ -376,7 +388,7 @@ function sortFlows(nodeArray) {
376
388
  const overlapTo = nodeSize < node.out;
377
389
  let addY = 0;
378
390
  let len = node.from.length;
379
- node.from.sort((a, b)=>a.node.y + a.node.out / 2 - (b.node.y + b.node.out / 2)).forEach((flow, idx)=>{
391
+ node.from.sort((a, b)=>nodeY$1(a.node) + a.node.out / 2 - (nodeY$1(b.node) + b.node.out / 2)).forEach((flow, idx)=>{
380
392
  if (overlapFrom) {
381
393
  flow.addY = idx * (nodeSize - flow.flow) / (len - 1);
382
394
  } else {
@@ -386,7 +398,7 @@ function sortFlows(nodeArray) {
386
398
  });
387
399
  addY = 0;
388
400
  len = node.to.length;
389
- node.to.sort((a, b)=>a.node.y + a.node.in / 2 - (b.node.y + b.node.in / 2)).forEach((flow, idx)=>{
401
+ node.to.sort((a, b)=>nodeY$1(a.node) + a.node.in / 2 - (nodeY$1(b.node) + b.node.in / 2)).forEach((flow, idx)=>{
390
402
  if (overlapTo) {
391
403
  flow.addY = idx * (nodeSize - flow.flow) / (len - 1);
392
404
  } else {
@@ -400,7 +412,7 @@ function layout(nodes, data, { priority, height, nodePadding, modeX }) {
400
412
  const nodeArray = [
401
413
  ...nodes.values()
402
414
  ];
403
- const maxX = calculateX(nodes, data, modeX);
415
+ const maxX = calculateX(nodes, data, modeX ?? 'edge');
404
416
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
405
417
  const padding = maxY / height * nodePadding;
406
418
  const maxYWithPadding = addPadding(nodeArray, padding);
@@ -411,6 +423,12 @@ function layout(nodes, data, { priority, height, nodePadding, modeX }) {
411
423
  };
412
424
  }
413
425
 
426
+ function nodeX(node) {
427
+ return node.x ?? 0;
428
+ }
429
+ function nodeY(node) {
430
+ return node.y ?? 0;
431
+ }
414
432
  function getAddY(arr, key, index) {
415
433
  for (const item of arr){
416
434
  if (item.key === key && item.index === index) {
@@ -429,7 +447,7 @@ class SankeyController extends chart_js.DatasetController {
429
447
  const { maxX, maxY } = layout(nodes, sankeyData, {
430
448
  height: this.chart.canvas.height,
431
449
  modeX: this.options.modeX,
432
- nodePadding: this.options.nodePadding,
450
+ nodePadding: this.options.nodePadding ?? 10,
433
451
  priority: !!this.options.priority
434
452
  });
435
453
  this._maxX = maxX;
@@ -440,18 +458,18 @@ class SankeyController extends chart_js.DatasetController {
440
458
  const from = nodes.get(dataPoint.from);
441
459
  const to = nodes.get(dataPoint.to);
442
460
  if (!from || !to) continue;
443
- const fromY = (from.y ?? 0) + getAddY(from.to, dataPoint.to, i);
444
- const toY = (to.y ?? 0) + getAddY(to.from, dataPoint.from, i);
461
+ const fromY = nodeY(from) + getAddY(from.to, dataPoint.to, i);
462
+ const toY = nodeY(to) + getAddY(to.from, dataPoint.from, i);
445
463
  parsed.push({
446
464
  _custom: {
447
465
  flow: dataPoint.flow,
448
466
  from,
449
467
  height: yScale.parse(dataPoint.flow, i),
450
468
  to,
451
- x: xScale.parse(to.x, i),
469
+ x: xScale.parse(nodeX(to), i),
452
470
  y: yScale.parse(toY, i)
453
471
  },
454
- x: xScale.parse(from.x, i),
472
+ x: xScale.parse(nodeX(from), i),
455
473
  y: yScale.parse(fromY, i)
456
474
  });
457
475
  }
@@ -490,7 +508,9 @@ class SankeyController extends chart_js.DatasetController {
490
508
  y2: yScale.getPixelForValue(custom.y)
491
509
  }, mode);
492
510
  }
493
- this.updateSharedOptions(sharedOptions, mode, firstOpts);
511
+ if (sharedOptions) {
512
+ this.updateSharedOptions(sharedOptions, mode, firstOpts);
513
+ }
494
514
  }
495
515
  _drawLabels() {
496
516
  const ctx = this.chart.ctx;
@@ -505,10 +525,10 @@ class SankeyController extends chart_js.DatasetController {
505
525
  ctx.save();
506
526
  const chartArea = this.chart.chartArea;
507
527
  for (const node of nodes.values()){
508
- const x = xScale.getPixelForValue(node.x);
509
- const y = yScale.getPixelForValue(node.y);
528
+ const x = xScale.getPixelForValue(nodeX(node));
529
+ const y = yScale.getPixelForValue(nodeY(node));
510
530
  const max = Math[size](node.in || node.out, node.out || node.in);
511
- const height = Math.abs(yScale.getPixelForValue(node.y + max) - y);
531
+ const height = Math.abs(yScale.getPixelForValue(nodeY(node) + max) - y);
512
532
  const label = labels?.[node.key] ?? node.key;
513
533
  let textX = x;
514
534
  ctx.fillStyle = options.color ?? 'black';
@@ -525,7 +545,7 @@ class SankeyController extends chart_js.DatasetController {
525
545
  ctx.restore();
526
546
  }
527
547
  _drawLabel(label, y, height, ctx, textX) {
528
- const font = helpers.toFont(this.options.font, this.chart.options.font);
548
+ const font = helpers.toFont(this.options.font ?? this.chart.options.font ?? chart_js.Chart.defaults.font);
529
549
  const lines = toTextLines(label);
530
550
  const lineCount = lines.length;
531
551
  const middle = y + height / 2;
@@ -554,10 +574,11 @@ class SankeyController extends chart_js.DatasetController {
554
574
  }
555
575
  for (const node of nodes.values()){
556
576
  ctx.fillStyle = node.color ?? 'black';
557
- const x = xScale.getPixelForValue(node.x);
558
- const y = yScale.getPixelForValue(node.y);
577
+ if (!xScale || !yScale) return;
578
+ const x = xScale.getPixelForValue(nodeX(node));
579
+ const y = yScale.getPixelForValue(nodeY(node));
559
580
  const max = Math[sizeMethod](node.in || node.out, node.out || node.in);
560
- const height = Math.abs(yScale.getPixelForValue(node.y + max) - y);
581
+ const height = Math.abs(yScale.getPixelForValue(nodeY(node) + max) - y);
561
582
  if (borderWidth) {
562
583
  ctx.strokeRect(x, y, nodeWidth, height);
563
584
  }
@@ -571,13 +592,19 @@ class SankeyController extends chart_js.DatasetController {
571
592
  const active = [];
572
593
  for(let i = 0, ilen = data.length; i < ilen; ++i){
573
594
  const flow = data[i];
574
- flow.from.color = flow.options.colorFrom;
595
+ if (!flow.from || !flow.to) {
596
+ continue;
597
+ }
598
+ flow.from.color = flow.options.colorFrom;
575
599
  flow.to.color = flow.options.colorTo;
576
600
  if (flow.active) {
577
601
  active.push(flow);
578
602
  }
579
603
  }
580
604
  for (const flow of active){
605
+ if (!flow.from || !flow.to) {
606
+ continue;
607
+ }
581
608
  flow.from.color = flow.options.colorFrom;
582
609
  flow.to.color = flow.options.colorTo;
583
610
  }
@@ -587,6 +614,9 @@ class SankeyController extends chart_js.DatasetController {
587
614
  }
588
615
  this._drawLabels();
589
616
  }
617
+ constructor(...args){
618
+ super(...args), this._nodes = new Map(), this._maxX = 0, this._maxY = 0;
619
+ }
590
620
  }
591
621
  SankeyController.id = 'sankey';
592
622
  SankeyController.defaults = {
@@ -677,7 +707,7 @@ SankeyController.overrides = {
677
707
  callbacks: {
678
708
  label (context) {
679
709
  const parsedCustom = context.parsed._custom;
680
- return parsedCustom.from.key + ' -> ' + parsedCustom.to.key + ': ' + parsedCustom.flow;
710
+ return `${parsedCustom.from.key} -> ${parsedCustom.to.key}: ${parsedCustom.flow}`;
681
711
  },
682
712
  title () {
683
713
  return '';
@@ -729,6 +759,7 @@ const pointInLine = (p1, p2, t)=>({
729
759
  });
730
760
  const applyAlpha = (original, alpha)=>helpers.color(original).alpha(alpha).rgbString();
731
761
  const getColorOption = (option, alpha)=>typeof option === 'string' ? applyAlpha(option, alpha) : option;
762
+ const getHoverColorOption = (option)=>typeof option === 'string' ? helpers.getHoverColor(option) : option;
732
763
  function setStyle(ctx, { x, x2, options }) {
733
764
  let fill = 'black';
734
765
  if (options.colorMode === 'from') {
@@ -828,14 +859,14 @@ class Flow extends chart_js.Element {
828
859
  y: (y + y2 + height) / 2
829
860
  };
830
861
  }
831
- tooltipPosition(useFinalPosition) {
862
+ tooltipPosition(useFinalPosition = false) {
832
863
  return this.getCenterPoint(useFinalPosition);
833
864
  }
834
865
  getRange(axis) {
835
866
  return axis === 'x' ? this.width / 2 : this.height / 2;
836
867
  }
837
868
  constructor(cfg){
838
- super();
869
+ super(), this.x2 = 0, this.y2 = 0, this.width = 0, this.height = 0, this.progress = 1;
839
870
  if (cfg) {
840
871
  Object.assign(this, cfg);
841
872
  }
@@ -847,8 +878,8 @@ Flow.defaults = {
847
878
  colorFrom: 'red',
848
879
  colorMode: 'gradient',
849
880
  colorTo: 'green',
850
- hoverColorFrom: (_ctx, options)=>helpers.getHoverColor(options.colorFrom),
851
- hoverColorTo: (_ctx, options)=>helpers.getHoverColor(options.colorTo)
881
+ hoverColorFrom: (_ctx, options)=>getHoverColorOption(options.colorFrom),
882
+ hoverColorTo: (_ctx, options)=>getHoverColorOption(options.colorTo)
852
883
  };
853
884
  Flow.descriptors = {
854
885
  _scriptable: true
@@ -1,10 +1,10 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.14.3
2
+ * chartjs-chart-sankey v0.14.4
3
3
  * https://chartjs-chart-sankey.pages.dev/
4
4
  * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
6
6
  */
7
- import { DatasetController, Element } from 'chart.js';
7
+ import { DatasetController, Chart, Element } from 'chart.js';
8
8
  import { toFont, valueOrDefault, getHoverColor, color } from 'chart.js/helpers';
9
9
 
10
10
  const defined = (x)=>x !== undefined;
@@ -123,6 +123,12 @@ function buildNodesFromData(data, { size, priority, column }) {
123
123
  }
124
124
 
125
125
  const SMALL_VALUE = 1e-6;
126
+ function nodeX$1(node) {
127
+ return node.x ?? 0;
128
+ }
129
+ function nodeY$1(node) {
130
+ return node.y ?? 0;
131
+ }
126
132
  const getAllKeysForward = (nodes, visited = new Set())=>{
127
133
  const keys = [];
128
134
  for (const node of nodes){
@@ -180,7 +186,7 @@ function calculateX(nodeMap, data, mode) {
180
186
  x++;
181
187
  }
182
188
  }
183
- const maxX = allNodes.reduce((max, node)=>Math.max(max, node.x), 0);
189
+ const maxX = allNodes.reduce((max, node)=>Math.max(max, nodeX$1(node)), 0);
184
190
  if (mode === 'edge') {
185
191
  const from = new Set(data.map((dataPoint)=>dataPoint.from));
186
192
  allKeys.filter((key)=>!from.has(key)).forEach((key)=>{
@@ -220,7 +226,7 @@ function processFrom(node, y) {
220
226
  }
221
227
  y = Math.max(n.y + n.out, y);
222
228
  }
223
- return node.y + node.size;
229
+ return nodeY$1(node) + node.size;
224
230
  }
225
231
  function processTo(node, y) {
226
232
  if (!node.to.length) return y;
@@ -233,7 +239,7 @@ function processTo(node, y) {
233
239
  }
234
240
  y = Math.max(n.y + Math.max(n.in, n.out), y);
235
241
  }
236
- return node.y + node.size;
242
+ return nodeY$1(node) + node.size;
237
243
  }
238
244
  function setOrGetY(node, value) {
239
245
  if (defined(node.y)) {
@@ -247,9 +253,9 @@ function processRest(nodeArray, maxX) {
247
253
  const rightNodes = nodeArray.filter((node)=>node.x === maxX);
248
254
  const leftToDo = leftNodes.filter((node)=>!defined(node.y));
249
255
  const rightToDo = rightNodes.filter((node)=>!defined(node.y));
250
- const centerToDo = nodeArray.filter((node)=>node.x > 0 && node.x < maxX && !defined(node.y));
251
- let leftY = leftNodes.reduce((acc, cur)=>Math.max(acc, cur.y + cur.out || 0), 0) + SMALL_VALUE;
252
- let rightY = rightNodes.reduce((acc, cur)=>Math.max(acc, cur.y + cur.in || 0), 0) + SMALL_VALUE;
256
+ const centerToDo = nodeArray.filter((node)=>nodeX$1(node) > 0 && nodeX$1(node) < maxX && !defined(node.y));
257
+ let leftY = leftNodes.reduce((acc, cur)=>Math.max(acc, nodeY$1(cur) + cur.out || 0), 0) + SMALL_VALUE;
258
+ let rightY = rightNodes.reduce((acc, cur)=>Math.max(acc, nodeY$1(cur) + cur.in || 0), 0) + SMALL_VALUE;
253
259
  let centerY = 0;
254
260
  if (leftY >= rightY) {
255
261
  leftToDo.forEach((node)=>{
@@ -270,7 +276,7 @@ function processRest(nodeArray, maxX) {
270
276
  });
271
277
  }
272
278
  centerToDo.forEach((node)=>{
273
- let y = nodeArray.filter((n)=>n.x === node.x && defined(n.y)).reduce((acc, cur)=>Math.max(acc, cur.y + Math.max(cur.in, cur.out)), 0);
279
+ let y = nodeArray.filter((n)=>nodeX$1(n) === nodeX$1(node) && defined(n.y)).reduce((acc, cur)=>Math.max(acc, nodeY$1(cur) + Math.max(cur.in, cur.out)), 0);
274
280
  y = setOrGetY(node, y);
275
281
  y = Math.max(y + node.in, processFrom(node, y));
276
282
  y = Math.max(y + node.out, processTo(node, y));
@@ -281,25 +287,29 @@ function processRest(nodeArray, maxX) {
281
287
  const fixTop = (nodeArray, maxX)=>{
282
288
  let maxY = 0;
283
289
  for(let x = 0; x <= maxX; x++){
284
- const nodes = nodeArray.filter((n)=>n.x === x).sort((a, b)=>a.y - b.y);
290
+ const nodes = nodeArray.filter((n)=>nodeX$1(n) === x).sort((a, b)=>nodeY$1(a) - nodeY$1(b));
285
291
  let minY = 0;
286
292
  for (const node of nodes){
287
- if (node.y < minY) node.y = minY;
288
- minY = node.y + node.size;
293
+ if (nodeY$1(node) < minY) node.y = minY;
294
+ minY = nodeY$1(node) + node.size;
289
295
  }
290
296
  maxY = Math.max(maxY, minY);
291
297
  }
292
298
  return maxY;
293
299
  };
294
300
  const findStartNode = (nodeArray, maxX)=>{
295
- const size = [
301
+ const sorted = [
296
302
  ...nodeArray
297
- ].sort((a, b)=>a.size - b.size).pop().size;
303
+ ].sort((a, b)=>a.size - b.size);
304
+ const largest = sorted[sorted.length - 1];
305
+ const size = largest.size;
298
306
  const biggest = nodeArray.filter((n)=>n.size === size);
299
- if (biggest.length === 1) return biggest[0];
300
- biggest.sort((a, b)=>a.x - b.x);
301
- if (biggest[0].x === 0) return biggest[0];
302
- if (biggest[biggest.length - 1].x === maxX) return biggest.pop();
307
+ const first = biggest[0];
308
+ if (biggest.length === 1) return first;
309
+ biggest.sort((a, b)=>nodeX$1(a) - nodeX$1(b));
310
+ if (nodeX$1(first) === 0) return first;
311
+ const last = biggest[biggest.length - 1];
312
+ if (nodeX$1(last) === maxX) return last;
303
313
  const mid = Math.floor(biggest.length / 2);
304
314
  return biggest[mid];
305
315
  };
@@ -317,8 +327,8 @@ function calculateYUsingPriority(nodeArray, maxX) {
317
327
  let nextYStart = 0;
318
328
  for(let x = 0; x <= maxX; x++){
319
329
  let y = nextYStart;
320
- const nodes = nodeArray.filter((node)=>node.x === x).sort((a, b)=>(a.priority ?? 0) - (b.priority ?? 0));
321
- nextYStart = nodes.length ? nodes[0].to.filter((to)=>to.node.x > x + 1).reduce((acc, cur)=>acc + cur.flow, 0) || 0 : 0;
330
+ const nodes = nodeArray.filter((node)=>nodeX$1(node) === x).sort((a, b)=>(a.priority ?? 0) - (b.priority ?? 0));
331
+ nextYStart = nodes.length ? nodes[0].to.filter((to)=>nodeX$1(to.node) > x + 1).reduce((acc, cur)=>acc + cur.flow, 0) || 0 : 0;
322
332
  for (const node of nodes){
323
333
  node.y = y;
324
334
  y += Math.max(node.out, node.in);
@@ -328,9 +338,9 @@ function calculateYUsingPriority(nodeArray, maxX) {
328
338
  return maxY;
329
339
  }
330
340
  const nodeByXYSize = (a, b)=>{
331
- if (a.x !== b.x) return a.x - b.x;
332
- if (a.y === b.y) return a.size - b.size;
333
- return a.y - b.y;
341
+ if (nodeX$1(a) !== nodeX$1(b)) return nodeX$1(a) - nodeX$1(b);
342
+ if (nodeY$1(a) === nodeY$1(b)) return a.size - b.size;
343
+ return nodeY$1(a) - nodeY$1(b);
334
344
  };
335
345
  function addPadding(nodeArray, padding) {
336
346
  let maxY = 0;
@@ -338,31 +348,33 @@ const nodeByXYSize = (a, b)=>{
338
348
  const grid = [];
339
349
  const getColIndex = (x)=>{
340
350
  if (!columnXs.has(x)) {
341
- columnXs.set(x, grid.length);
351
+ const index = grid.length;
352
+ columnXs.set(x, index);
342
353
  grid.push([]);
354
+ return index;
343
355
  }
344
- return columnXs.get(x);
356
+ return columnXs.get(x) ?? 0;
345
357
  };
346
358
  nodeArray.sort(nodeByXYSize);
347
359
  for (const node of nodeArray){
348
- const colIdx = getColIndex(node.x);
349
- const column = grid[colIdx];
350
- if (node.y) {
351
- column.push(node.y);
360
+ const colIdx = getColIndex(nodeX$1(node));
361
+ const column = grid[colIdx] ?? [];
362
+ if (nodeY$1(node)) {
363
+ column.push(nodeY$1(node));
352
364
  let paddings = column.length;
353
365
  if (node.in) {
354
366
  for(let col = 0; col < colIdx; col++){
355
- const otherColumn = grid[col];
367
+ const otherColumn = grid[col] ?? [];
356
368
  for(let row = 0; row < otherColumn.length; row++){
357
- if (otherColumn[row] > node.y) break;
369
+ if (otherColumn[row] > nodeY$1(node)) break;
358
370
  paddings = Math.max(row + 1, paddings);
359
371
  }
360
372
  }
361
- while(column.length < paddings)column.push(node.y);
373
+ while(column.length < paddings)column.push(nodeY$1(node));
362
374
  }
363
- node.y += paddings * padding;
375
+ node.y = nodeY$1(node) + paddings * padding;
364
376
  }
365
- maxY = Math.max(maxY, node.y + Math.max(node.in, node.out));
377
+ maxY = Math.max(maxY, nodeY$1(node) + Math.max(node.in, node.out));
366
378
  }
367
379
  return maxY;
368
380
  }
@@ -373,7 +385,7 @@ function sortFlows(nodeArray) {
373
385
  const overlapTo = nodeSize < node.out;
374
386
  let addY = 0;
375
387
  let len = node.from.length;
376
- node.from.sort((a, b)=>a.node.y + a.node.out / 2 - (b.node.y + b.node.out / 2)).forEach((flow, idx)=>{
388
+ node.from.sort((a, b)=>nodeY$1(a.node) + a.node.out / 2 - (nodeY$1(b.node) + b.node.out / 2)).forEach((flow, idx)=>{
377
389
  if (overlapFrom) {
378
390
  flow.addY = idx * (nodeSize - flow.flow) / (len - 1);
379
391
  } else {
@@ -383,7 +395,7 @@ function sortFlows(nodeArray) {
383
395
  });
384
396
  addY = 0;
385
397
  len = node.to.length;
386
- node.to.sort((a, b)=>a.node.y + a.node.in / 2 - (b.node.y + b.node.in / 2)).forEach((flow, idx)=>{
398
+ node.to.sort((a, b)=>nodeY$1(a.node) + a.node.in / 2 - (nodeY$1(b.node) + b.node.in / 2)).forEach((flow, idx)=>{
387
399
  if (overlapTo) {
388
400
  flow.addY = idx * (nodeSize - flow.flow) / (len - 1);
389
401
  } else {
@@ -397,7 +409,7 @@ function layout(nodes, data, { priority, height, nodePadding, modeX }) {
397
409
  const nodeArray = [
398
410
  ...nodes.values()
399
411
  ];
400
- const maxX = calculateX(nodes, data, modeX);
412
+ const maxX = calculateX(nodes, data, modeX ?? 'edge');
401
413
  const maxY = priority ? calculateYUsingPriority(nodeArray, maxX) : calculateY(nodeArray, maxX);
402
414
  const padding = maxY / height * nodePadding;
403
415
  const maxYWithPadding = addPadding(nodeArray, padding);
@@ -408,6 +420,12 @@ function layout(nodes, data, { priority, height, nodePadding, modeX }) {
408
420
  };
409
421
  }
410
422
 
423
+ function nodeX(node) {
424
+ return node.x ?? 0;
425
+ }
426
+ function nodeY(node) {
427
+ return node.y ?? 0;
428
+ }
411
429
  function getAddY(arr, key, index) {
412
430
  for (const item of arr){
413
431
  if (item.key === key && item.index === index) {
@@ -426,7 +444,7 @@ class SankeyController extends DatasetController {
426
444
  const { maxX, maxY } = layout(nodes, sankeyData, {
427
445
  height: this.chart.canvas.height,
428
446
  modeX: this.options.modeX,
429
- nodePadding: this.options.nodePadding,
447
+ nodePadding: this.options.nodePadding ?? 10,
430
448
  priority: !!this.options.priority
431
449
  });
432
450
  this._maxX = maxX;
@@ -437,18 +455,18 @@ class SankeyController extends DatasetController {
437
455
  const from = nodes.get(dataPoint.from);
438
456
  const to = nodes.get(dataPoint.to);
439
457
  if (!from || !to) continue;
440
- const fromY = (from.y ?? 0) + getAddY(from.to, dataPoint.to, i);
441
- const toY = (to.y ?? 0) + getAddY(to.from, dataPoint.from, i);
458
+ const fromY = nodeY(from) + getAddY(from.to, dataPoint.to, i);
459
+ const toY = nodeY(to) + getAddY(to.from, dataPoint.from, i);
442
460
  parsed.push({
443
461
  _custom: {
444
462
  flow: dataPoint.flow,
445
463
  from,
446
464
  height: yScale.parse(dataPoint.flow, i),
447
465
  to,
448
- x: xScale.parse(to.x, i),
466
+ x: xScale.parse(nodeX(to), i),
449
467
  y: yScale.parse(toY, i)
450
468
  },
451
- x: xScale.parse(from.x, i),
469
+ x: xScale.parse(nodeX(from), i),
452
470
  y: yScale.parse(fromY, i)
453
471
  });
454
472
  }
@@ -487,7 +505,9 @@ class SankeyController extends DatasetController {
487
505
  y2: yScale.getPixelForValue(custom.y)
488
506
  }, mode);
489
507
  }
490
- this.updateSharedOptions(sharedOptions, mode, firstOpts);
508
+ if (sharedOptions) {
509
+ this.updateSharedOptions(sharedOptions, mode, firstOpts);
510
+ }
491
511
  }
492
512
  _drawLabels() {
493
513
  const ctx = this.chart.ctx;
@@ -502,10 +522,10 @@ class SankeyController extends DatasetController {
502
522
  ctx.save();
503
523
  const chartArea = this.chart.chartArea;
504
524
  for (const node of nodes.values()){
505
- const x = xScale.getPixelForValue(node.x);
506
- const y = yScale.getPixelForValue(node.y);
525
+ const x = xScale.getPixelForValue(nodeX(node));
526
+ const y = yScale.getPixelForValue(nodeY(node));
507
527
  const max = Math[size](node.in || node.out, node.out || node.in);
508
- const height = Math.abs(yScale.getPixelForValue(node.y + max) - y);
528
+ const height = Math.abs(yScale.getPixelForValue(nodeY(node) + max) - y);
509
529
  const label = labels?.[node.key] ?? node.key;
510
530
  let textX = x;
511
531
  ctx.fillStyle = options.color ?? 'black';
@@ -522,7 +542,7 @@ class SankeyController extends DatasetController {
522
542
  ctx.restore();
523
543
  }
524
544
  _drawLabel(label, y, height, ctx, textX) {
525
- const font = toFont(this.options.font, this.chart.options.font);
545
+ const font = toFont(this.options.font ?? this.chart.options.font ?? Chart.defaults.font);
526
546
  const lines = toTextLines(label);
527
547
  const lineCount = lines.length;
528
548
  const middle = y + height / 2;
@@ -551,10 +571,11 @@ class SankeyController extends DatasetController {
551
571
  }
552
572
  for (const node of nodes.values()){
553
573
  ctx.fillStyle = node.color ?? 'black';
554
- const x = xScale.getPixelForValue(node.x);
555
- const y = yScale.getPixelForValue(node.y);
574
+ if (!xScale || !yScale) return;
575
+ const x = xScale.getPixelForValue(nodeX(node));
576
+ const y = yScale.getPixelForValue(nodeY(node));
556
577
  const max = Math[sizeMethod](node.in || node.out, node.out || node.in);
557
- const height = Math.abs(yScale.getPixelForValue(node.y + max) - y);
578
+ const height = Math.abs(yScale.getPixelForValue(nodeY(node) + max) - y);
558
579
  if (borderWidth) {
559
580
  ctx.strokeRect(x, y, nodeWidth, height);
560
581
  }
@@ -568,13 +589,19 @@ class SankeyController extends DatasetController {
568
589
  const active = [];
569
590
  for(let i = 0, ilen = data.length; i < ilen; ++i){
570
591
  const flow = data[i];
571
- flow.from.color = flow.options.colorFrom;
592
+ if (!flow.from || !flow.to) {
593
+ continue;
594
+ }
595
+ flow.from.color = flow.options.colorFrom;
572
596
  flow.to.color = flow.options.colorTo;
573
597
  if (flow.active) {
574
598
  active.push(flow);
575
599
  }
576
600
  }
577
601
  for (const flow of active){
602
+ if (!flow.from || !flow.to) {
603
+ continue;
604
+ }
578
605
  flow.from.color = flow.options.colorFrom;
579
606
  flow.to.color = flow.options.colorTo;
580
607
  }
@@ -584,6 +611,9 @@ class SankeyController extends DatasetController {
584
611
  }
585
612
  this._drawLabels();
586
613
  }
614
+ constructor(...args){
615
+ super(...args), this._nodes = new Map(), this._maxX = 0, this._maxY = 0;
616
+ }
587
617
  }
588
618
  SankeyController.id = 'sankey';
589
619
  SankeyController.defaults = {
@@ -674,7 +704,7 @@ SankeyController.overrides = {
674
704
  callbacks: {
675
705
  label (context) {
676
706
  const parsedCustom = context.parsed._custom;
677
- return parsedCustom.from.key + ' -> ' + parsedCustom.to.key + ': ' + parsedCustom.flow;
707
+ return `${parsedCustom.from.key} -> ${parsedCustom.to.key}: ${parsedCustom.flow}`;
678
708
  },
679
709
  title () {
680
710
  return '';
@@ -726,6 +756,7 @@ const pointInLine = (p1, p2, t)=>({
726
756
  });
727
757
  const applyAlpha = (original, alpha)=>color(original).alpha(alpha).rgbString();
728
758
  const getColorOption = (option, alpha)=>typeof option === 'string' ? applyAlpha(option, alpha) : option;
759
+ const getHoverColorOption = (option)=>typeof option === 'string' ? getHoverColor(option) : option;
729
760
  function setStyle(ctx, { x, x2, options }) {
730
761
  let fill = 'black';
731
762
  if (options.colorMode === 'from') {
@@ -825,14 +856,14 @@ class Flow extends Element {
825
856
  y: (y + y2 + height) / 2
826
857
  };
827
858
  }
828
- tooltipPosition(useFinalPosition) {
859
+ tooltipPosition(useFinalPosition = false) {
829
860
  return this.getCenterPoint(useFinalPosition);
830
861
  }
831
862
  getRange(axis) {
832
863
  return axis === 'x' ? this.width / 2 : this.height / 2;
833
864
  }
834
865
  constructor(cfg){
835
- super();
866
+ super(), this.x2 = 0, this.y2 = 0, this.width = 0, this.height = 0, this.progress = 1;
836
867
  if (cfg) {
837
868
  Object.assign(this, cfg);
838
869
  }
@@ -844,8 +875,8 @@ Flow.defaults = {
844
875
  colorFrom: 'red',
845
876
  colorMode: 'gradient',
846
877
  colorTo: 'green',
847
- hoverColorFrom: (_ctx, options)=>getHoverColor(options.colorFrom),
848
- hoverColorTo: (_ctx, options)=>getHoverColor(options.colorTo)
878
+ hoverColorFrom: (_ctx, options)=>getHoverColorOption(options.colorFrom),
879
+ hoverColorTo: (_ctx, options)=>getHoverColorOption(options.colorTo)
849
880
  };
850
881
  Flow.descriptors = {
851
882
  _scriptable: true
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.14.3
2
+ * chartjs-chart-sankey v0.14.4
3
3
  * https://chartjs-chart-sankey.pages.dev/
4
4
  * (c) 2026 Jukka Kurkela
5
5
  * Released under the MIT license
6
6
  */
7
- !function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["exports","chart.js","chart.js/helpers"],o):o((t="undefined"!=typeof globalThis?globalThis:t||self)["chartjs-chart-sankey"]={},t.Chart,t.Chart.helpers)}(this,function(t,o,e){"use strict";const r=t=>void 0!==t;function n(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const s=(t,o)=>o.flow===t.flow?t.index-o.index:o.flow-t.flow;function i(t,{size:o,priority:e,column:r}){const i=new Map;for(let o=0;o<t.length;o++){const{from:e,to:r,flow:n}=t[o],s=i.get(e)??{from:[],in:0,key:e,out:0,size:0,to:[]},a=(e===r?s:i.get(r))??{from:[],in:0,key:r,out:0,size:0,to:[]};s.out+=n,s.to.push({addY:0,flow:n,index:o,key:r,node:a}),1===s.to.length&&i.set(e,s),a.in+=n,a.from.push({addY:0,flow:n,index:o,key:e,node:s}),1===a.from.length&&i.set(r,a)}return((t,o)=>{const e=n(o);for(const o of t.values())o.from.sort(s),o.to.sort(s),o.size=Math[e](o.in||o.out,o.out||o.in)})(i,o),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.priority=o[e.key])})(i,e),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.column=!0,e.x=o[e.key])})(i,r),i}const a=1e-6,l=(t,o=new Set)=>{const e=[];for(const r of t)o.has(r.key)||(o.add(r.key),e.push(r.key,...l(r.to.map(t=>t.node),o)));return e},c=(t,o)=>{const e=o.filter(t=>0===t.from.length),r=e.map(t=>t.key),n=l(e),s=new Set(n);for(const o of t)s.has(o.from)||s.has(o.to)||(r.push(o.from),s.add(o.from)),s.add(o.to);return r},h=(t,o)=>{const e=new Set(t.filter(t=>o.has(t.from)).map(t=>t.to)),r=[...o],n=r.filter(t=>!e.has(t));return n.length?n:r.slice(0,1)};let f=-1;function d(t,o,e=function(){return f=f<100?f+1:0,f}()){let r=0;for(const n of t)n.node._visited!==e&&(n.node._visited=e,r+=n.node[o].length+d(n.node[o],o,e));return r}const u=t=>(o,e)=>d(o.node[t],t)-d(e.node[t],t)||o.node[t].length-e.node[t].length;function y(t,o){if(!t.from.length)return o;t.from.sort(u("from"));for(const e of t.from){const t=e.node;r(t.y)||(t.y=o,y(t,o?o+a:0)),o=Math.max(t.y+t.out,o)}return t.y+t.size}function p(t,o){if(!t.to.length)return o;t.to.sort(u("to"));for(const e of t.to){const t=e.node;r(t.y)||(t.y=o,p(t,o?o+a:0)),o=Math.max(t.y+Math.max(t.in,t.out),o)}return t.y+t.size}function x(t,o){return r(t.y)?t.y:(t.y=o,o)}function m(t,o){if(!t.length)return 0;const e=((t,o)=>{const e=[...t].sort((t,o)=>t.size-o.size).pop().size,r=t.filter(t=>t.size===e);return 1===r.length?r[0]:(r.sort((t,o)=>t.x-o.x),0===r[0].x?r[0]:r[r.length-1].x===o?r.pop():r[Math.floor(r.length/2)])})(t,o);return e.y=0,y(e,0),p(e,0),function(t,o){const e=t.filter(t=>0===t.x),n=t.filter(t=>t.x===o),s=e.filter(t=>!r(t.y)),i=n.filter(t=>!r(t.y)),l=t.filter(t=>t.x>0&&t.x<o&&!r(t.y));let c=e.reduce((t,o)=>Math.max(t,o.y+o.out||0),0)+a,h=n.reduce((t,o)=>Math.max(t,o.y+o.in||0),0)+a,f=0;c>=h?(s.forEach(t=>{c=x(t,c),c=Math.max(c+t.out,p(t,c))}),i.forEach(t=>{h=x(t,h),h=Math.max(h+t.in,y(t,h))})):(s.forEach(t=>{c=x(t,c)}),i.forEach(t=>{h=x(t,h),h=Math.max(h+t.in,y(t,h))})),l.forEach(o=>{let e=t.filter(t=>t.x===o.x&&r(t.y)).reduce((t,o)=>Math.max(t,o.y+Math.max(o.in,o.out)),0);e=x(o,e),e=Math.max(e+o.in,y(o,e)),e=Math.max(e+o.out,p(o,e)),f=Math.max(f,e)}),Math.max(c,h,f)}(t,o),((t,o)=>{let e=0;for(let r=0;r<=o;r++){const o=t.filter(t=>t.x===r).sort((t,o)=>t.y-o.y);let n=0;for(const t of o)t.y<n&&(t.y=n),n=t.y+t.size;e=Math.max(e,n)}return e})(t,o)}const g=(t,o)=>t.x!==o.x?t.x-o.x:t.y===o.y?t.size-o.size:t.y-o.y;function w(t,o,{priority:e,height:n,nodePadding:s,modeX:i}){const a=[...t.values()],l=function(t,o,e){const n=o.filter(t=>t.from!==t.to),s=[...t.keys()],i=[...t.values()],a=new Set(s);let l=0;for(;a.size;){const e=0===l?c(o,i):h(n,a);if(!e.length)throw new Error("Fatal error: Unable to place nodes to columns. Please report this issue.");for(const o of e){const e=t.get(o);e&&!r(e.x)&&(e.x=l),a.delete(o)}a.size&&l++}const f=i.reduce((t,o)=>Math.max(t,o.x),0);if("edge"===e){const e=new Set(o.map(t=>t.from));s.filter(t=>!e.has(t)).forEach(o=>{const e=t.get(o);e&&!e.column&&(e.x=f)})}return f}(t,o,i),f=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const s=t.filter(t=>t.x===n).sort((t,o)=>(t.priority??0)-(o.priority??0));r=s.length&&s[0].to.filter(t=>t.node.x>n+1).reduce((t,o)=>t+o.flow,0)||0;for(const t of s)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(a,l):m(a,l),d=function(t,o){let e=0;const r=new Map,n=[],s=t=>(r.has(t)||(r.set(t,n.length),n.push([])),r.get(t));t.sort(g);for(const r of t){const t=s(r.x),i=n[t];if(r.y){i.push(r.y);let e=i.length;if(r.in){for(let o=0;o<t;o++){const t=n[o];for(let o=0;o<t.length&&!(t[o]>r.y);o++)e=Math.max(o+1,e)}for(;i.length<e;)i.push(r.y)}r.y+=e*o}e=Math.max(e,r.y+Math.max(r.in,r.out))}return e}(a,f/n*s);return function(t){t.forEach(t=>{const o=t.size,e=o<t.in,r=o<t.out;let n=0,s=t.from.length;t.from.sort((t,o)=>t.node.y+t.node.out/2-(o.node.y+o.node.out/2)).forEach((t,r)=>{e?t.addY=r*(o-t.flow)/(s-1):(t.addY=n,n+=t.flow)}),n=0,s=t.to.length,t.to.sort((t,o)=>t.node.y+t.node.in/2-(o.node.y+o.node.in/2)).forEach((t,e)=>{r?t.addY=e*(o-t.flow)/(s-1):(t.addY=n,n+=t.flow)})})}(a),{maxX:l,maxY:d}}function M(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}class b extends o.DatasetController{parseObjectData(t,o,e,r){const n=((t,o)=>{const{from:e="from",to:r="to",flow:n="flow"}=o;return t.map(({[e]:t,[r]:o,[n]:s})=>({flow:s,from:t,to:o}))})(o,this.options.parsing),{xScale:s,yScale:a}=t,l=[],c=i(n,this.options);this._nodes=c;const{maxX:h,maxY:f}=w(c,n,{height:this.chart.canvas.height,modeX:this.options.modeX,nodePadding:this.options.nodePadding,priority:!!this.options.priority});if(this._maxX=h,this._maxY=f,!s||!a)return[];for(let t=0,o=n.length;t<o;++t){const o=n[t],e=c.get(o.from),r=c.get(o.to);if(!e||!r)continue;const i=(e.y??0)+M(e.to,o.to,t),h=(r.y??0)+M(r.from,o.from,t);l.push({_custom:{flow:o.flow,from:e,height:a.parse(o.flow,t),to:r,x:s.parse(r.x,t),y:a.parse(h,t)},x:s.parse(e.x,t),y:a.parse(i,t)})}return l.slice(e,e+r)}getMinMax(t){return{max:t===this._cachedMeta.xScale?this._maxX:this._maxY,min:0}}update(t){const{data:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,o,e,r){const{xScale:n,yScale:s}=this._cachedMeta;if(!n||!s)return;const i=this.resolveDataElementOptions(o,r),a=this.getSharedOptions(i),{borderWidth:l,nodeWidth:c=10}=this.options,h=l?l/2+.5:0;for(let i=o;i<o+e;i++){const o=this.getParsed(i),e=o._custom,a=s.getPixelForValue(o.y);this.updateElement(t[i],i,{from:e.from,height:Math.abs(s.getPixelForValue(o.y+e.height)-a),options:this.resolveDataElementOptions(i,r),progress:"reset"===r?0:1,to:e.to,x:n.getPixelForValue(o.x)+c+h,x2:n.getPixelForValue(e.x)-h,y:a,y2:s.getPixelForValue(e.y)},r)}this.updateSharedOptions(a,r,i)}_drawLabels(){const t=this.chart.ctx,o=this.options,e=this._nodes||new Map,r=n(o.size),s=o.borderWidth??1,i=o.nodeWidth??10,a=o.labels,{xScale:l,yScale:c}=this._cachedMeta;if(!l||!c)return;t.save();const h=this.chart.chartArea;for(const n of e.values()){const e=l.getPixelForValue(n.x),f=c.getPixelForValue(n.y),d=Math[r](n.in||n.out,n.out||n.in),u=Math.abs(c.getPixelForValue(n.y+d)-f),y=a?.[n.key]??n.key;let p=e;t.fillStyle=o.color??"black",t.textBaseline="middle",e<h.width/2?(t.textAlign="left",p+=i+s+4):(t.textAlign="right",p-=s+4),this._drawLabel(y,f,u,t,p)}t.restore()}_drawLabel(t,o,r,n,s){const i=e.toFont(this.options.font,this.chart.options.font),a=function(t){if(!t)return[];const o=[],e=Array.isArray(t)?t:[t];for(;e.length;){const t=e.pop();"string"==typeof t?o.unshift(...t.split("\n")):Array.isArray(t)?e.push(...t):t&&o.unshift(`${t}`)}return o}(t),l=a.length,c=o+r/2,h=i.lineHeight,f=e.valueOrDefault(this.options.padding,h/2);if(n.font=i.string,l>1){const t=c-h*l/2+f;for(let o=0;o<l;o++)n.fillText(a[o],s,t+o*h)}else n.fillText(t,s,c)}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:r=0,nodeWidth:s=10,size:i}=this.options,a=n(i),{xScale:l,yScale:c}=this._cachedMeta;t.save(),e&&r&&(t.strokeStyle=e,t.lineWidth=r);for(const e of o.values()){t.fillStyle=e.color??"black";const o=l.getPixelForValue(e.x),n=c.getPixelForValue(e.y),i=Math[a](e.in||e.out,e.out||e.in),h=Math.abs(c.getPixelForValue(e.y+i)-n);r&&t.strokeRect(o,n,s,h),t.fillRect(o,n,s,h)}t.restore()}draw(){const t=this.chart.ctx,o=this.getMeta().data??[],e=[];for(let t=0,r=o.length;t<r;++t){const r=o[t];r.from.color=r.options.colorFrom,r.to.color=r.options.colorTo,r.active&&e.push(r)}for(const t of e)t.from.color=t.options.colorFrom,t.to.color=t.options.colorTo;this._drawNodes();for(let e=0,r=o.length;e<r;++e)o[e].draw(t);this._drawLabels()}}b.id="sankey",b.defaults={animations:{colors:{properties:["colorFrom","colorTo"],type:"color"},numbers:{properties:["x","y","x2","y2","height"],type:"number"},progress:{delay:t=>"data"===t.type?500*t.parsed.x+20*t.dataIndex:void 0,duration:t=>"data"===t.type?200*(t.parsed._custom.x-t.parsed.x):void 0,easing:"linear"}},borderColor:"black",borderWidth:1,color:"black",dataElementType:"flow",modeX:"edge",nodePadding:10,nodeWidth:10,transitions:{hide:{animations:{colors:{properties:["colorFrom","colorTo"],to:"transparent",type:"color"}}},show:{animations:{colors:{from:"transparent",properties:["colorFrom","colorTo"],type:"color"}}}}},b.overrides={datasets:{clip:!1,parsing:{flow:"flow",from:"from",to:"to"}},interaction:{intersect:!0,mode:"nearest"},layout:{padding:{bottom:3,left:3,right:13,top:3}},plugins:{legend:{display:!1},tooltip:{callbacks:{label(t){const o=t.parsed._custom;return o.from.key+" -> "+o.to.key+": "+o.flow},title:()=>""}}},scales:{x:{bounds:"data",display:!1,min:0,offset:!1,type:"linear"},y:{bounds:"data",display:!1,min:0,offset:!1,reverse:!0,type:"linear"}}};const k=(t,o,e,r)=>t<e?{cp1:{x:t+(e-t)/3*2,y:o},cp2:{x:t+(e-t)/3,y:r}}:{cp1:{x:t-(t-e)/3,y:0},cp2:{x:e+(t-e)/3,y:0}},v=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),P=(t,o)=>e.color(t).alpha(o).rgbString(),F=(t,o)=>"string"==typeof t?P(t,o):t;class S extends o.Element{draw(t){const{x:o,x2:e,y:r,y2:n,height:s,progress:i}=this,{cp1:a,cp2:l}=k(o,r,e,n);0!==i&&(t.save(),i<1&&(t.beginPath(),t.rect(o,Math.min(r,n),(e-o)*i+1,Math.abs(n-r)+s+1),t.clip()),function(t,{x:o,x2:e,options:r}){let n="black";"from"===r.colorMode?n=F(r.colorFrom,r.alpha):"to"===r.colorMode?n=F(r.colorTo,r.alpha):"string"==typeof r.colorFrom&&"string"==typeof r.colorTo&&(n=t.createLinearGradient(o,0,e,0),n.addColorStop(0,P(r.colorFrom,r.alpha)),n.addColorStop(1,P(r.colorTo,r.alpha))),t.fillStyle=n,t.strokeStyle=n,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(o,r),t.bezierCurveTo(a.x,a.y,l.x,l.y,e,n),t.lineTo(e,n+s),t.bezierCurveTo(l.x,l.y+s,a.x,a.y+s,o,r+s),t.lineTo(o,r),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,o,e){const{x:r,y:n,x2:s,y2:i,height:a}=this.getProps(["x","y","x2","y2","height"],e);if(t<r||t>s)return!1;const{cp1:l,cp2:c}=k(r,n,s,i),h=(t-r)/(s-r),f={x:s,y:i},d=v({x:r,y:n},l,h),u=v(l,c,h),y=v(c,f,h),p=v(d,u,h),x=v(u,y,h),m=v(p,x,h).y;return o>=m&&o<=m+a}inXRange(t,o){const{x:e,x2:r}=this.getProps(["x","x2"],o);return t>=e&&t<=r}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),s=Math.min(e,r),i=Math.max(e,r)+n;return t>=s&&t<=i}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:s}=this.getProps(["x","y","x2","y2","height"],t);return{x:(o+r)/2,y:(e+n+s)/2}}tooltipPosition(t){return this.getCenterPoint(t)}getRange(t){return"x"===t?this.width/2:this.height/2}constructor(t){super(),t&&Object.assign(this,t)}}S.id="flow",S.defaults={alpha:.5,colorFrom:"red",colorMode:"gradient",colorTo:"green",hoverColorFrom:(t,o)=>e.getHoverColor(o.colorFrom),hoverColorTo:(t,o)=>e.getHoverColor(o.colorTo)},S.descriptors={_scriptable:!0},o.Chart.register(b,S),t.Flow=S,t.SankeyController=b});
7
+ !function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(exports,require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["exports","chart.js","chart.js/helpers"],o):o((t="undefined"!=typeof globalThis?globalThis:t||self)["chartjs-chart-sankey"]={},t.Chart,t.Chart.helpers)}(this,function(t,o,e){"use strict";const r=t=>void 0!==t;function n(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const s=(t,o)=>o.flow===t.flow?t.index-o.index:o.flow-t.flow;function i(t,{size:o,priority:e,column:r}){const i=new Map;for(let o=0;o<t.length;o++){const{from:e,to:r,flow:n}=t[o],s=i.get(e)??{from:[],in:0,key:e,out:0,size:0,to:[]},a=(e===r?s:i.get(r))??{from:[],in:0,key:r,out:0,size:0,to:[]};s.out+=n,s.to.push({addY:0,flow:n,index:o,key:r,node:a}),1===s.to.length&&i.set(e,s),a.in+=n,a.from.push({addY:0,flow:n,index:o,key:e,node:s}),1===a.from.length&&i.set(r,a)}return((t,o)=>{const e=n(o);for(const o of t.values())o.from.sort(s),o.to.sort(s),o.size=Math[e](o.in||o.out,o.out||o.in)})(i,o),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.priority=o[e.key])})(i,e),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.column=!0,e.x=o[e.key])})(i,r),i}const a=1e-6;function l(t){return t.x??0}function h(t){return t.y??0}const c=(t,o=new Set)=>{const e=[];for(const r of t)o.has(r.key)||(o.add(r.key),e.push(r.key,...c(r.to.map(t=>t.node),o)));return e},f=(t,o)=>{const e=o.filter(t=>0===t.from.length),r=e.map(t=>t.key),n=c(e),s=new Set(n);for(const o of t)s.has(o.from)||s.has(o.to)||(r.push(o.from),s.add(o.from)),s.add(o.to);return r},d=(t,o)=>{const e=new Set(t.filter(t=>o.has(t.from)).map(t=>t.to)),r=[...o],n=r.filter(t=>!e.has(t));return n.length?n:r.slice(0,1)};let u=-1;function p(t,o,e=function(){return u=u<100?u+1:0,u}()){let r=0;for(const n of t)n.node._visited!==e&&(n.node._visited=e,r+=n.node[o].length+p(n.node[o],o,e));return r}const x=t=>(o,e)=>p(o.node[t],t)-p(e.node[t],t)||o.node[t].length-e.node[t].length;function m(t,o){if(!t.from.length)return o;t.from.sort(x("from"));for(const e of t.from){const t=e.node;r(t.y)||(t.y=o,m(t,o?o+a:0)),o=Math.max(t.y+t.out,o)}return h(t)+t.size}function y(t,o){if(!t.to.length)return o;t.to.sort(x("to"));for(const e of t.to){const t=e.node;r(t.y)||(t.y=o,y(t,o?o+a:0)),o=Math.max(t.y+Math.max(t.in,t.out),o)}return h(t)+t.size}function g(t,o){return r(t.y)?t.y:(t.y=o,o)}function w(t,o){if(!t.length)return 0;const e=((t,o)=>{const e=[...t].sort((t,o)=>t.size-o.size),r=e[e.length-1].size,n=t.filter(t=>t.size===r),s=n[0];if(1===n.length)return s;if(n.sort((t,o)=>l(t)-l(o)),0===l(s))return s;const i=n[n.length-1];return l(i)===o?i:n[Math.floor(n.length/2)]})(t,o);return e.y=0,m(e,0),y(e,0),function(t,o){const e=t.filter(t=>0===t.x),n=t.filter(t=>t.x===o),s=e.filter(t=>!r(t.y)),i=n.filter(t=>!r(t.y)),c=t.filter(t=>l(t)>0&&l(t)<o&&!r(t.y));let f=e.reduce((t,o)=>Math.max(t,h(o)+o.out||0),0)+a,d=n.reduce((t,o)=>Math.max(t,h(o)+o.in||0),0)+a,u=0;f>=d?(s.forEach(t=>{f=g(t,f),f=Math.max(f+t.out,y(t,f))}),i.forEach(t=>{d=g(t,d),d=Math.max(d+t.in,m(t,d))})):(s.forEach(t=>{f=g(t,f)}),i.forEach(t=>{d=g(t,d),d=Math.max(d+t.in,m(t,d))})),c.forEach(o=>{let e=t.filter(t=>l(t)===l(o)&&r(t.y)).reduce((t,o)=>Math.max(t,h(o)+Math.max(o.in,o.out)),0);e=g(o,e),e=Math.max(e+o.in,m(o,e)),e=Math.max(e+o.out,y(o,e)),u=Math.max(u,e)}),Math.max(f,d,u)}(t,o),((t,o)=>{let e=0;for(let r=0;r<=o;r++){const o=t.filter(t=>l(t)===r).sort((t,o)=>h(t)-h(o));let n=0;for(const t of o)h(t)<n&&(t.y=n),n=h(t)+t.size;e=Math.max(e,n)}return e})(t,o)}const M=(t,o)=>l(t)!==l(o)?l(t)-l(o):h(t)===h(o)?t.size-o.size:h(t)-h(o);function b(t,o,{priority:e,height:n,nodePadding:s,modeX:i}){const a=[...t.values()],c=function(t,o,e){const n=o.filter(t=>t.from!==t.to),s=[...t.keys()],i=[...t.values()],a=new Set(s);let h=0;for(;a.size;){const e=0===h?f(o,i):d(n,a);if(!e.length)throw new Error("Fatal error: Unable to place nodes to columns. Please report this issue.");for(const o of e){const e=t.get(o);e&&!r(e.x)&&(e.x=h),a.delete(o)}a.size&&h++}const c=i.reduce((t,o)=>Math.max(t,l(o)),0);if("edge"===e){const e=new Set(o.map(t=>t.from));s.filter(t=>!e.has(t)).forEach(o=>{const e=t.get(o);e&&!e.column&&(e.x=c)})}return c}(t,o,i??"edge"),u=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const s=t.filter(t=>l(t)===n).sort((t,o)=>(t.priority??0)-(o.priority??0));r=s.length&&s[0].to.filter(t=>l(t.node)>n+1).reduce((t,o)=>t+o.flow,0)||0;for(const t of s)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(a,c):w(a,c),p=function(t,o){let e=0;const r=new Map,n=[],s=t=>{if(!r.has(t)){const o=n.length;return r.set(t,o),n.push([]),o}return r.get(t)??0};t.sort(M);for(const r of t){const t=s(l(r)),i=n[t]??[];if(h(r)){i.push(h(r));let e=i.length;if(r.in){for(let o=0;o<t;o++){const t=n[o]??[];for(let o=0;o<t.length&&!(t[o]>h(r));o++)e=Math.max(o+1,e)}for(;i.length<e;)i.push(h(r))}r.y=h(r)+e*o}e=Math.max(e,h(r)+Math.max(r.in,r.out))}return e}(a,u/n*s);return function(t){t.forEach(t=>{const o=t.size,e=o<t.in,r=o<t.out;let n=0,s=t.from.length;t.from.sort((t,o)=>h(t.node)+t.node.out/2-(h(o.node)+o.node.out/2)).forEach((t,r)=>{e?t.addY=r*(o-t.flow)/(s-1):(t.addY=n,n+=t.flow)}),n=0,s=t.to.length,t.to.sort((t,o)=>h(t.node)+t.node.in/2-(h(o.node)+o.node.in/2)).forEach((t,e)=>{r?t.addY=e*(o-t.flow)/(s-1):(t.addY=n,n+=t.flow)})})}(a),{maxX:c,maxY:p}}function k(t){return t.x??0}function v(t){return t.y??0}function _(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}class P extends o.DatasetController{parseObjectData(t,o,e,r){const n=((t,o)=>{const{from:e="from",to:r="to",flow:n="flow"}=o;return t.map(({[e]:t,[r]:o,[n]:s})=>({flow:s,from:t,to:o}))})(o,this.options.parsing),{xScale:s,yScale:a}=t,l=[],h=i(n,this.options);this._nodes=h;const{maxX:c,maxY:f}=b(h,n,{height:this.chart.canvas.height,modeX:this.options.modeX,nodePadding:this.options.nodePadding??10,priority:!!this.options.priority});if(this._maxX=c,this._maxY=f,!s||!a)return[];for(let t=0,o=n.length;t<o;++t){const o=n[t],e=h.get(o.from),r=h.get(o.to);if(!e||!r)continue;const i=v(e)+_(e.to,o.to,t),c=v(r)+_(r.from,o.from,t);l.push({_custom:{flow:o.flow,from:e,height:a.parse(o.flow,t),to:r,x:s.parse(k(r),t),y:a.parse(c,t)},x:s.parse(k(e),t),y:a.parse(i,t)})}return l.slice(e,e+r)}getMinMax(t){return{max:t===this._cachedMeta.xScale?this._maxX:this._maxY,min:0}}update(t){const{data:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,o,e,r){const{xScale:n,yScale:s}=this._cachedMeta;if(!n||!s)return;const i=this.resolveDataElementOptions(o,r),a=this.getSharedOptions(i),{borderWidth:l,nodeWidth:h=10}=this.options,c=l?l/2+.5:0;for(let i=o;i<o+e;i++){const o=this.getParsed(i),e=o._custom,a=s.getPixelForValue(o.y);this.updateElement(t[i],i,{from:e.from,height:Math.abs(s.getPixelForValue(o.y+e.height)-a),options:this.resolveDataElementOptions(i,r),progress:"reset"===r?0:1,to:e.to,x:n.getPixelForValue(o.x)+h+c,x2:n.getPixelForValue(e.x)-c,y:a,y2:s.getPixelForValue(e.y)},r)}a&&this.updateSharedOptions(a,r,i)}_drawLabels(){const t=this.chart.ctx,o=this.options,e=this._nodes||new Map,r=n(o.size),s=o.borderWidth??1,i=o.nodeWidth??10,a=o.labels,{xScale:l,yScale:h}=this._cachedMeta;if(!l||!h)return;t.save();const c=this.chart.chartArea;for(const n of e.values()){const e=l.getPixelForValue(k(n)),f=h.getPixelForValue(v(n)),d=Math[r](n.in||n.out,n.out||n.in),u=Math.abs(h.getPixelForValue(v(n)+d)-f),p=a?.[n.key]??n.key;let x=e;t.fillStyle=o.color??"black",t.textBaseline="middle",e<c.width/2?(t.textAlign="left",x+=i+s+4):(t.textAlign="right",x-=s+4),this._drawLabel(p,f,u,t,x)}t.restore()}_drawLabel(t,r,n,s,i){const a=e.toFont(this.options.font??this.chart.options.font??o.Chart.defaults.font),l=function(t){if(!t)return[];const o=[],e=Array.isArray(t)?t:[t];for(;e.length;){const t=e.pop();"string"==typeof t?o.unshift(...t.split("\n")):Array.isArray(t)?e.push(...t):t&&o.unshift(`${t}`)}return o}(t),h=l.length,c=r+n/2,f=a.lineHeight,d=e.valueOrDefault(this.options.padding,f/2);if(s.font=a.string,h>1){const t=c-f*h/2+d;for(let o=0;o<h;o++)s.fillText(l[o],i,t+o*f)}else s.fillText(t,i,c)}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:r=0,nodeWidth:s=10,size:i}=this.options,a=n(i),{xScale:l,yScale:h}=this._cachedMeta;t.save(),e&&r&&(t.strokeStyle=e,t.lineWidth=r);for(const e of o.values()){if(t.fillStyle=e.color??"black",!l||!h)return;const o=l.getPixelForValue(k(e)),n=h.getPixelForValue(v(e)),i=Math[a](e.in||e.out,e.out||e.in),c=Math.abs(h.getPixelForValue(v(e)+i)-n);r&&t.strokeRect(o,n,s,c),t.fillRect(o,n,s,c)}t.restore()}draw(){const t=this.chart.ctx,o=this.getMeta().data??[],e=[];for(let t=0,r=o.length;t<r;++t){const r=o[t];r.from&&r.to&&(r.from.color=r.options.colorFrom,r.to.color=r.options.colorTo,r.active&&e.push(r))}for(const t of e)t.from&&t.to&&(t.from.color=t.options.colorFrom,t.to.color=t.options.colorTo);this._drawNodes();for(let e=0,r=o.length;e<r;++e)o[e].draw(t);this._drawLabels()}constructor(...t){super(...t),this._nodes=new Map,this._maxX=0,this._maxY=0}}P.id="sankey",P.defaults={animations:{colors:{properties:["colorFrom","colorTo"],type:"color"},numbers:{properties:["x","y","x2","y2","height"],type:"number"},progress:{delay:t=>"data"===t.type?500*t.parsed.x+20*t.dataIndex:void 0,duration:t=>"data"===t.type?200*(t.parsed._custom.x-t.parsed.x):void 0,easing:"linear"}},borderColor:"black",borderWidth:1,color:"black",dataElementType:"flow",modeX:"edge",nodePadding:10,nodeWidth:10,transitions:{hide:{animations:{colors:{properties:["colorFrom","colorTo"],to:"transparent",type:"color"}}},show:{animations:{colors:{from:"transparent",properties:["colorFrom","colorTo"],type:"color"}}}}},P.overrides={datasets:{clip:!1,parsing:{flow:"flow",from:"from",to:"to"}},interaction:{intersect:!0,mode:"nearest"},layout:{padding:{bottom:3,left:3,right:13,top:3}},plugins:{legend:{display:!1},tooltip:{callbacks:{label(t){const o=t.parsed._custom;return`${o.from.key} -> ${o.to.key}: ${o.flow}`},title:()=>""}}},scales:{x:{bounds:"data",display:!1,min:0,offset:!1,type:"linear"},y:{bounds:"data",display:!1,min:0,offset:!1,reverse:!0,type:"linear"}}};const F=(t,o,e,r)=>t<e?{cp1:{x:t+(e-t)/3*2,y:o},cp2:{x:t+(e-t)/3,y:r}}:{cp1:{x:t-(t-e)/3,y:0},cp2:{x:e+(t-e)/3,y:0}},S=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),T=(t,o)=>e.color(t).alpha(o).rgbString(),z=(t,o)=>"string"==typeof t?T(t,o):t,C=t=>"string"==typeof t?e.getHoverColor(t):t;class E extends o.Element{draw(t){const{x:o,x2:e,y:r,y2:n,height:s,progress:i}=this,{cp1:a,cp2:l}=F(o,r,e,n);0!==i&&(t.save(),i<1&&(t.beginPath(),t.rect(o,Math.min(r,n),(e-o)*i+1,Math.abs(n-r)+s+1),t.clip()),function(t,{x:o,x2:e,options:r}){let n="black";"from"===r.colorMode?n=z(r.colorFrom,r.alpha):"to"===r.colorMode?n=z(r.colorTo,r.alpha):"string"==typeof r.colorFrom&&"string"==typeof r.colorTo&&(n=t.createLinearGradient(o,0,e,0),n.addColorStop(0,T(r.colorFrom,r.alpha)),n.addColorStop(1,T(r.colorTo,r.alpha))),t.fillStyle=n,t.strokeStyle=n,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(o,r),t.bezierCurveTo(a.x,a.y,l.x,l.y,e,n),t.lineTo(e,n+s),t.bezierCurveTo(l.x,l.y+s,a.x,a.y+s,o,r+s),t.lineTo(o,r),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,o,e){const{x:r,y:n,x2:s,y2:i,height:a}=this.getProps(["x","y","x2","y2","height"],e);if(t<r||t>s)return!1;const{cp1:l,cp2:h}=F(r,n,s,i),c=(t-r)/(s-r),f={x:s,y:i},d=S({x:r,y:n},l,c),u=S(l,h,c),p=S(h,f,c),x=S(d,u,c),m=S(u,p,c),y=S(x,m,c).y;return o>=y&&o<=y+a}inXRange(t,o){const{x:e,x2:r}=this.getProps(["x","x2"],o);return t>=e&&t<=r}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),s=Math.min(e,r),i=Math.max(e,r)+n;return t>=s&&t<=i}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:s}=this.getProps(["x","y","x2","y2","height"],t);return{x:(o+r)/2,y:(e+n+s)/2}}tooltipPosition(t=!1){return this.getCenterPoint(t)}getRange(t){return"x"===t?this.width/2:this.height/2}constructor(t){super(),this.x2=0,this.y2=0,this.width=0,this.height=0,this.progress=1,t&&Object.assign(this,t)}}E.id="flow",E.defaults={alpha:.5,colorFrom:"red",colorMode:"gradient",colorTo:"green",hoverColorFrom:(t,o)=>C(o.colorFrom),hoverColorTo:(t,o)=>C(o.colorTo)},E.descriptors={_scriptable:!0},o.Chart.register(P,E),t.Flow=E,t.SankeyController=P});
@@ -15,8 +15,8 @@ export default class SankeyController extends DatasetController {
15
15
  type: string;
16
16
  };
17
17
  progress: {
18
- delay: (ctx: any) => number;
19
- duration: (ctx: any) => number;
18
+ delay: (ctx: any) => number | undefined;
19
+ duration: (ctx: any) => number | undefined;
20
20
  easing: string;
21
21
  };
22
22
  };
package/dist/flow.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { SankeyNode } from 'chart.js';
1
+ import type { Color, SankeyNode } from 'chart.js';
2
2
  import type { FlowConfig, FlowOptions, FlowProps } from './types.js';
3
3
  import { Element } from 'chart.js';
4
4
  export default class Flow extends Element<FlowProps, FlowOptions> {
@@ -8,8 +8,8 @@ export default class Flow extends Element<FlowProps, FlowOptions> {
8
8
  colorFrom: string;
9
9
  colorMode: string;
10
10
  colorTo: string;
11
- hoverColorFrom: (_ctx: any, options: any) => CanvasGradient;
12
- hoverColorTo: (_ctx: any, options: any) => CanvasGradient;
11
+ hoverColorFrom: (_ctx: unknown, options: FlowOptions) => Color;
12
+ hoverColorTo: (_ctx: unknown, options: FlowOptions) => Color;
13
13
  };
14
14
  static readonly descriptors: {
15
15
  _scriptable: boolean;
@@ -19,8 +19,8 @@ export default class Flow extends Element<FlowProps, FlowOptions> {
19
19
  width: number;
20
20
  height: number;
21
21
  progress: number;
22
- from: SankeyNode;
23
- to: SankeyNode;
22
+ from?: SankeyNode;
23
+ to?: SankeyNode;
24
24
  constructor(cfg: FlowConfig);
25
25
  /**
26
26
  * @param {CanvasRenderingContext2D} ctx
@@ -53,7 +53,7 @@ export default class Flow extends Element<FlowProps, FlowOptions> {
53
53
  x: number;
54
54
  y: number;
55
55
  };
56
- tooltipPosition(useFinalPosition: any): {
56
+ tooltipPosition(useFinalPosition?: boolean): {
57
57
  x: number;
58
58
  y: number;
59
59
  };
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CartesianScaleTypeRegistry, Color, FontSpec, Scriptable, ScriptableContext } from 'chart.js';
1
+ import type { CartesianScaleTypeRegistry, Color, FontSpec, SankeyNode, Scriptable, ScriptableContext } from 'chart.js';
2
2
  export type AnyObject = Record<string, unknown>;
3
3
  declare module 'chart.js' {
4
4
  interface SankeyDataPoint {
@@ -98,5 +98,9 @@ export interface FlowConfig {
98
98
  x2: number;
99
99
  y2: number;
100
100
  height: number;
101
+ width?: number;
102
+ progress?: number;
103
+ from?: SankeyNode;
104
+ to?: SankeyNode;
101
105
  options: FlowOptions;
102
106
  }
package/package.json CHANGED
@@ -6,41 +6,40 @@
6
6
  "description": "Chart.js module for creating sankey diagrams",
7
7
  "devDependencies": {
8
8
  "@astrojs/check": "^0.9.9",
9
- "@astrojs/mdx": "^5.0.6",
10
- "@astrojs/starlight": "^0.38.5",
9
+ "@astrojs/mdx": "^7.0.2",
10
+ "@astrojs/starlight": "^0.41.3",
11
11
  "@biomejs/biome": "^2.4.14",
12
- "@emnapi/core": "^1.10.0",
13
- "@emnapi/runtime": "^1.10.0",
14
- "@jest/globals": "^29.7.0",
12
+ "@emnapi/core": "^1.11.2",
13
+ "@emnapi/runtime": "^1.11.2",
15
14
  "@kurkle/configs": "^1.0.1",
16
15
  "@napi-rs/canvas": "^1.0.0",
17
16
  "@rollup/plugin-node-resolve": "^16.0.0",
18
17
  "@rollup/plugin-swc": "^0.4.0",
19
- "@rollup/plugin-terser": "^0.4.0",
18
+ "@rollup/plugin-terser": "^1.0.0",
19
+ "@swc/cli": "^0.8.1",
20
20
  "@swc/core": "^1.7.40",
21
- "@types/jest": "^30.0.0",
22
- "@types/node": "^25.0.3",
23
- "astro": "^6.4.2",
21
+ "@types/jasmine": "^6.0.0",
22
+ "@types/node": "^26.1.0",
23
+ "astro": "^7.0.6",
24
+ "c8": "^11.0.0",
24
25
  "chart.js": "^4.4.5",
25
26
  "chartjs-test-utils": "^0.5.0",
26
27
  "cross-env": "^10.1.0",
27
- "jest": "^30.2.0",
28
- "jest-util": "^30.2.0",
28
+ "globals": "^17.0.0",
29
+ "jasmine": "^6.2.0",
29
30
  "karma": "^6.3.2",
30
31
  "karma-chrome-launcher": "^3.1.0",
31
32
  "karma-coverage": "^2.0.3",
32
33
  "karma-firefox-launcher": "^2.1.3",
33
34
  "karma-jasmine": "^5.0.1",
34
35
  "karma-jasmine-html-reporter": "^2.0.0",
35
- "karma-rollup-preprocessor": "7.0.7",
36
+ "karma-rollup-preprocessor": "^7.0.8",
36
37
  "karma-spec-reporter": "^0.0.36",
37
38
  "rollup": "^4.24.0",
38
39
  "rollup-plugin-cleanup": "^3.2.1",
39
40
  "rollup-plugin-istanbul": "^5.0.0",
40
41
  "semantic-release": "^25.0.3",
41
- "ts-jest": "^29.2.5",
42
- "ts-node": "^10.9.2",
43
- "typescript": "^5.0.4"
42
+ "typescript": "^6.0.3"
44
43
  },
45
44
  "exports": {
46
45
  "import": "./dist/chartjs-chart-sankey.esm.js",
@@ -83,17 +82,18 @@
83
82
  "predocs": "npm run docs:version",
84
83
  "predocs:dev": "npm run docs:version",
85
84
  "prepack": "npm run build",
85
+ "pretest:unit": "swc --config-file .swcrc-spec src -d build",
86
86
  "test": "npm run test:unit && npm run test:fixture && npm run test:integration:browser-module && npm run test:integration:node-commonjs && npm run test:integration:node-module",
87
87
  "test:fixture": "cross-env NODE_ENV=test karma start ./karma.conf.cjs --no-auto-watch --single-run",
88
88
  "test:integration:browser-module": "karma start ./karma.integration.cjs --single-run",
89
89
  "test:integration:node-commonjs": "node test/integration/node-commonjs/test.cjs",
90
90
  "test:integration:node-module": "node test/integration/node-module/test.mjs",
91
- "test:unit": "jest",
92
- "typecheck": "tsc --noEmit && tsc -p tsconfig.json --emitDeclarationOnly && tsc --noEmit -p test/types/",
91
+ "test:unit": "cross-env JASMINE_CONFIG_PATH=jasmine.json c8 --src=src --reporter=text --reporter=lcov -o=coverage/unit jasmine",
92
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && tsc -p tsconfig.json --emitDeclarationOnly && tsc --noEmit -p test/types/",
93
93
  "typecheck:docs": "astro check"
94
94
  },
95
95
  "type": "module",
96
96
  "types": "dist/index.esm.d.ts",
97
97
  "unpkg": "dist/chartjs-chart-sankey.min.js",
98
- "version": "0.14.3"
98
+ "version": "0.14.4"
99
99
  }