chartjs-chart-sankey 0.9.0 → 0.10.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.
package/README.md CHANGED
@@ -94,6 +94,37 @@ const chart = new Chart(ctx, {
94
94
  });
95
95
  ```
96
96
 
97
+ ### Custom data structure
98
+
99
+ Custom data structure can be used by specifying the custom data keys in `options.parsing`.
100
+ For example:
101
+
102
+ ```js
103
+ const chart = new Chart(ctx, {
104
+ type: 'sankey',
105
+ data: {
106
+ datasets: [
107
+ {
108
+ data: [
109
+ {source: 'a', destination: 'b', value: 20},
110
+ {source: 'c', destination: 'd', value: 10},
111
+ {source: 'c', destination: 'e', value: 5},
112
+ ],
113
+ colorFrom: 'red',
114
+ colorTo: 'green'
115
+ }
116
+ ]
117
+ },
118
+ options: {
119
+ parsing: {
120
+ from: 'source',
121
+ to: 'destination',
122
+ flow: 'value'
123
+ }
124
+ }
125
+ });
126
+ ```
127
+
97
128
  ## Example
98
129
 
99
130
  ![Sankey Example Image](sankey.png)
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.9.0
2
+ * chartjs-chart-sankey v0.10.0
3
3
  * https://github.com/kurkle/chartjs-chart-sankey#readme
4
4
  * (c) 2022 Jukka Kurkela
5
5
  * Released under the MIT license
@@ -101,12 +101,28 @@ function nextColumn(keys, to) {
101
101
  */
102
102
  const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
103
103
 
104
+ let prevCountId = -1;
105
+ function getCountId() {
106
+ prevCountId = prevCountId < 100 ? prevCountId + 1 : 0;
107
+ return prevCountId;
108
+ }
109
+
104
110
  /**
105
111
  * @param {Array<FromToElement>} list
106
112
  * @param {string} prop
107
113
  * @return {number}
108
114
  */
109
- const nodeCount = (list, prop) => list.reduce((acc, cur) => acc + cur.node[prop].length + nodeCount(cur.node[prop], prop), 0);
115
+ function nodeCount(list, prop, countId = getCountId()) {
116
+ let count = 0;
117
+ for (const elem of list) {
118
+ if (elem.node._visited === countId) {
119
+ continue;
120
+ }
121
+ elem.node._visited = countId;
122
+ count += elem.node[prop].length + nodeCount(elem.node[prop], prop, countId);
123
+ }
124
+ return count;
125
+ }
110
126
 
111
127
  /**
112
128
  * @param {string} prop
@@ -412,16 +428,13 @@ class SankeyController extends DatasetController {
412
428
  * @return {Array<SankeyParsedData>}
413
429
  */
414
430
  parseObjectData(meta, data, start, count) {
415
- // https://github.com/chartjs/Chart.js/pull/8379
416
- if (count === 0) {
417
- return [];
418
- }
419
- const me = this;
431
+ const {from: fromKey = 'from', to: toKey = 'to', flow: flowKey = 'flow'} = this.options.parsing;
432
+ const sankeyData = data.map(({[fromKey]: from, [toKey]: to, [flowKey]: flow}) => ({from, to, flow}));
420
433
  const {xScale, yScale} = meta;
421
434
  const parsed = []; /* Array<SankeyParsedData> */
422
- const nodes = me._nodes = buildNodesFromRawData(data);
435
+ const nodes = this._nodes = buildNodesFromRawData(sankeyData);
423
436
  /* getDataset() => SankeyControllerDatasetOptions */
424
- const {column, priority, size} = me.getDataset();
437
+ const {column, priority, size} = this.getDataset();
425
438
  if (priority) {
426
439
  for (const node of nodes.values()) {
427
440
  if (node.key in priority) {
@@ -438,13 +451,13 @@ class SankeyController extends DatasetController {
438
451
  }
439
452
  }
440
453
 
441
- const {maxX, maxY} = layout(nodes, data, !!priority, validateSizeValue(size));
454
+ const {maxX, maxY} = layout(nodes, sankeyData, !!priority, validateSizeValue(size));
442
455
 
443
- me._maxX = maxX;
444
- me._maxY = maxY;
456
+ this._maxX = maxX;
457
+ this._maxY = maxY;
445
458
 
446
- for (let i = 0, ilen = data.length; i < ilen; ++i) {
447
- const dataPoint = data[i];
459
+ for (let i = 0, ilen = sankeyData.length; i < ilen; ++i) {
460
+ const dataPoint = sankeyData[i];
448
461
  const from = nodes.get(dataPoint.from);
449
462
  const to = nodes.get(dataPoint.to);
450
463
  const fromY = from.y + getAddY(from.to, dataPoint.to, i);
@@ -465,18 +478,16 @@ class SankeyController extends DatasetController {
465
478
  }
466
479
 
467
480
  getMinMax(scale) {
468
- const me = this;
469
481
  return {
470
482
  min: 0,
471
- max: scale === this._cachedMeta.xScale ? this._maxX : me._maxY
483
+ max: scale === this._cachedMeta.xScale ? this._maxX : this._maxY
472
484
  };
473
485
  }
474
486
 
475
487
  update(mode) {
476
- const me = this;
477
- const meta = me._cachedMeta;
488
+ const {data} = this._cachedMeta;
478
489
 
479
- me.updateElements(meta.data, 0, meta.data.length, mode);
490
+ this.updateElements(data, 0, data.length, mode);
480
491
  }
481
492
 
482
493
  /**
@@ -486,20 +497,19 @@ class SankeyController extends DatasetController {
486
497
  * @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
487
498
  */
488
499
  updateElements(elems, start, count, mode) {
489
- const me = this;
490
- const {xScale, yScale} = me._cachedMeta;
491
- const firstOpts = me.resolveDataElementOptions(start, mode);
492
- const sharedOptions = me.getSharedOptions(mode, elems[start], firstOpts);
493
- const dataset = me.getDataset();
500
+ const {xScale, yScale} = this._cachedMeta;
501
+ const firstOpts = this.resolveDataElementOptions(start, mode);
502
+ const sharedOptions = this.getSharedOptions(mode, elems[start], firstOpts);
503
+ const dataset = this.getDataset();
494
504
  const borderWidth = valueOrDefault(dataset.borderWidth, 1) / 2 + 0.5;
495
505
  const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
496
506
 
497
507
  for (let i = start; i < start + count; i++) {
498
508
  /* getParsed(idx: number) => SankeyParsedData */
499
- const parsed = me.getParsed(i);
509
+ const parsed = this.getParsed(i);
500
510
  const custom = parsed._custom;
501
511
  const y = yScale.getPixelForValue(parsed.y);
502
- me.updateElement(
512
+ this.updateElement(
503
513
  elems[i],
504
514
  i,
505
515
  {
@@ -511,27 +521,26 @@ class SankeyController extends DatasetController {
511
521
  to: custom.to,
512
522
  progress: mode === 'reset' ? 0 : 1,
513
523
  height: Math.abs(yScale.getPixelForValue(parsed.y + custom.height) - y),
514
- options: me.resolveDataElementOptions(i, mode)
524
+ options: this.resolveDataElementOptions(i, mode)
515
525
  },
516
526
  mode);
517
527
  }
518
528
 
519
- me.updateSharedOptions(sharedOptions, mode);
529
+ this.updateSharedOptions(sharedOptions, mode);
520
530
  }
521
531
 
522
532
  _drawLabels() {
523
- const me = this;
524
- const ctx = me._ctx;
525
- const nodes = me._nodes || new Map();
526
- const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
533
+ const ctx = this._ctx;
534
+ const nodes = this._nodes || new Map();
535
+ const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
527
536
  const size = validateSizeValue(dataset.size);
528
537
  const borderWidth = valueOrDefault(dataset.borderWidth, 1);
529
538
  const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
530
539
  const labels = dataset.labels;
531
- const {xScale, yScale} = me._cachedMeta;
540
+ const {xScale, yScale} = this._cachedMeta;
532
541
 
533
542
  ctx.save();
534
- const chartArea = me.chart.chartArea;
543
+ const chartArea = this.chart.chartArea;
535
544
  for (const node of nodes.values()) {
536
545
  const x = xScale.getPixelForValue(node.x);
537
546
  const y = yScale.getPixelForValue(node.y);
@@ -563,13 +572,12 @@ class SankeyController extends DatasetController {
563
572
  * @private
564
573
  */
565
574
  _drawLabel(label, y, height, ctx, textX) {
566
- const me = this;
567
- const font = toFont(me.options.font, me.chart.options.font);
575
+ const font = toFont(this.options.font, this.chart.options.font);
568
576
  const lines = isNullOrUndef(label) ? [] : toTextLines(label);
569
577
  const linesLength = lines.length;
570
578
  const middle = y + height / 2;
571
579
  const textHeight = font.lineHeight;
572
- const padding = valueOrDefault(me.options.padding, textHeight / 2);
580
+ const padding = valueOrDefault(this.options.padding, textHeight / 2);
573
581
 
574
582
  ctx.font = font.string;
575
583
 
@@ -584,12 +592,11 @@ class SankeyController extends DatasetController {
584
592
  }
585
593
 
586
594
  _drawNodes() {
587
- const me = this;
588
- const ctx = me._ctx;
589
- const nodes = me._nodes || new Map();
590
- const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
595
+ const ctx = this._ctx;
596
+ const nodes = this._nodes || new Map();
597
+ const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
591
598
  const size = validateSizeValue(dataset.size);
592
- const {xScale, yScale} = me._cachedMeta;
599
+ const {xScale, yScale} = this._cachedMeta;
593
600
  const borderWidth = valueOrDefault(dataset.borderWidth, 1);
594
601
  const nodeWidth = valueOrDefault(dataset.nodeWidth, 10);
595
602
 
@@ -616,9 +623,8 @@ class SankeyController extends DatasetController {
616
623
  * That's where the drawing process happens
617
624
  */
618
625
  draw() {
619
- const me = this;
620
- const ctx = me._ctx;
621
- const data = me.getMeta().data || []; /* Array<Flow> */
626
+ const ctx = this._ctx;
627
+ const data = this.getMeta().data || []; /* Array<Flow> */
622
628
 
623
629
  for (let i = 0, ilen = data.length; i < ilen; ++i) {
624
630
  const flow = data[i]; /* Flow at index i */
@@ -627,7 +633,7 @@ class SankeyController extends DatasetController {
627
633
  }
628
634
 
629
635
  /* draw SankeyNodes on the canvas */
630
- me._drawNodes();
636
+ this._drawNodes();
631
637
 
632
638
  /* draw Flow elements on the canvas */
633
639
  for (let i = 0, ilen = data.length; i < ilen; ++i) {
@@ -635,7 +641,7 @@ class SankeyController extends DatasetController {
635
641
  }
636
642
 
637
643
  /* draw labels (for SankeyNodes) on the canvas */
638
- me._drawLabels();
644
+ this._drawLabels();
639
645
  }
640
646
  }
641
647
 
@@ -686,7 +692,6 @@ SankeyController.overrides = {
686
692
  intersect: true
687
693
  },
688
694
  datasets: {
689
- color: () => '#efefef',
690
695
  clip: false,
691
696
  parsing: true
692
697
  },
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.9.0
2
+ * chartjs-chart-sankey v0.10.0
3
3
  * https://github.com/kurkle/chartjs-chart-sankey#readme
4
4
  * (c) 2022 Jukka Kurkela
5
5
  * Released under the MIT license
@@ -104,12 +104,28 @@ function nextColumn(keys, to) {
104
104
  */
105
105
  const nodeByXY = (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y;
106
106
 
107
+ let prevCountId = -1;
108
+ function getCountId() {
109
+ prevCountId = prevCountId < 100 ? prevCountId + 1 : 0;
110
+ return prevCountId;
111
+ }
112
+
107
113
  /**
108
114
  * @param {Array<FromToElement>} list
109
115
  * @param {string} prop
110
116
  * @return {number}
111
117
  */
112
- const nodeCount = (list, prop) => list.reduce((acc, cur) => acc + cur.node[prop].length + nodeCount(cur.node[prop], prop), 0);
118
+ function nodeCount(list, prop, countId = getCountId()) {
119
+ let count = 0;
120
+ for (const elem of list) {
121
+ if (elem.node._visited === countId) {
122
+ continue;
123
+ }
124
+ elem.node._visited = countId;
125
+ count += elem.node[prop].length + nodeCount(elem.node[prop], prop, countId);
126
+ }
127
+ return count;
128
+ }
113
129
 
114
130
  /**
115
131
  * @param {string} prop
@@ -415,16 +431,13 @@ class SankeyController extends chart_js.DatasetController {
415
431
  * @return {Array<SankeyParsedData>}
416
432
  */
417
433
  parseObjectData(meta, data, start, count) {
418
- // https://github.com/chartjs/Chart.js/pull/8379
419
- if (count === 0) {
420
- return [];
421
- }
422
- const me = this;
434
+ const {from: fromKey = 'from', to: toKey = 'to', flow: flowKey = 'flow'} = this.options.parsing;
435
+ const sankeyData = data.map(({[fromKey]: from, [toKey]: to, [flowKey]: flow}) => ({from, to, flow}));
423
436
  const {xScale, yScale} = meta;
424
437
  const parsed = []; /* Array<SankeyParsedData> */
425
- const nodes = me._nodes = buildNodesFromRawData(data);
438
+ const nodes = this._nodes = buildNodesFromRawData(sankeyData);
426
439
  /* getDataset() => SankeyControllerDatasetOptions */
427
- const {column, priority, size} = me.getDataset();
440
+ const {column, priority, size} = this.getDataset();
428
441
  if (priority) {
429
442
  for (const node of nodes.values()) {
430
443
  if (node.key in priority) {
@@ -441,13 +454,13 @@ class SankeyController extends chart_js.DatasetController {
441
454
  }
442
455
  }
443
456
 
444
- const {maxX, maxY} = layout(nodes, data, !!priority, validateSizeValue(size));
457
+ const {maxX, maxY} = layout(nodes, sankeyData, !!priority, validateSizeValue(size));
445
458
 
446
- me._maxX = maxX;
447
- me._maxY = maxY;
459
+ this._maxX = maxX;
460
+ this._maxY = maxY;
448
461
 
449
- for (let i = 0, ilen = data.length; i < ilen; ++i) {
450
- const dataPoint = data[i];
462
+ for (let i = 0, ilen = sankeyData.length; i < ilen; ++i) {
463
+ const dataPoint = sankeyData[i];
451
464
  const from = nodes.get(dataPoint.from);
452
465
  const to = nodes.get(dataPoint.to);
453
466
  const fromY = from.y + getAddY(from.to, dataPoint.to, i);
@@ -468,18 +481,16 @@ class SankeyController extends chart_js.DatasetController {
468
481
  }
469
482
 
470
483
  getMinMax(scale) {
471
- const me = this;
472
484
  return {
473
485
  min: 0,
474
- max: scale === this._cachedMeta.xScale ? this._maxX : me._maxY
486
+ max: scale === this._cachedMeta.xScale ? this._maxX : this._maxY
475
487
  };
476
488
  }
477
489
 
478
490
  update(mode) {
479
- const me = this;
480
- const meta = me._cachedMeta;
491
+ const {data} = this._cachedMeta;
481
492
 
482
- me.updateElements(meta.data, 0, meta.data.length, mode);
493
+ this.updateElements(data, 0, data.length, mode);
483
494
  }
484
495
 
485
496
  /**
@@ -489,20 +500,19 @@ class SankeyController extends chart_js.DatasetController {
489
500
  * @param {"resize" | "reset" | "none" | "hide" | "show" | "normal" | "active"} mode
490
501
  */
491
502
  updateElements(elems, start, count, mode) {
492
- const me = this;
493
- const {xScale, yScale} = me._cachedMeta;
494
- const firstOpts = me.resolveDataElementOptions(start, mode);
495
- const sharedOptions = me.getSharedOptions(mode, elems[start], firstOpts);
496
- const dataset = me.getDataset();
503
+ const {xScale, yScale} = this._cachedMeta;
504
+ const firstOpts = this.resolveDataElementOptions(start, mode);
505
+ const sharedOptions = this.getSharedOptions(mode, elems[start], firstOpts);
506
+ const dataset = this.getDataset();
497
507
  const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1) / 2 + 0.5;
498
508
  const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
499
509
 
500
510
  for (let i = start; i < start + count; i++) {
501
511
  /* getParsed(idx: number) => SankeyParsedData */
502
- const parsed = me.getParsed(i);
512
+ const parsed = this.getParsed(i);
503
513
  const custom = parsed._custom;
504
514
  const y = yScale.getPixelForValue(parsed.y);
505
- me.updateElement(
515
+ this.updateElement(
506
516
  elems[i],
507
517
  i,
508
518
  {
@@ -514,27 +524,26 @@ class SankeyController extends chart_js.DatasetController {
514
524
  to: custom.to,
515
525
  progress: mode === 'reset' ? 0 : 1,
516
526
  height: Math.abs(yScale.getPixelForValue(parsed.y + custom.height) - y),
517
- options: me.resolveDataElementOptions(i, mode)
527
+ options: this.resolveDataElementOptions(i, mode)
518
528
  },
519
529
  mode);
520
530
  }
521
531
 
522
- me.updateSharedOptions(sharedOptions, mode);
532
+ this.updateSharedOptions(sharedOptions, mode);
523
533
  }
524
534
 
525
535
  _drawLabels() {
526
- const me = this;
527
- const ctx = me._ctx;
528
- const nodes = me._nodes || new Map();
529
- const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
536
+ const ctx = this._ctx;
537
+ const nodes = this._nodes || new Map();
538
+ const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
530
539
  const size = validateSizeValue(dataset.size);
531
540
  const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
532
541
  const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
533
542
  const labels = dataset.labels;
534
- const {xScale, yScale} = me._cachedMeta;
543
+ const {xScale, yScale} = this._cachedMeta;
535
544
 
536
545
  ctx.save();
537
- const chartArea = me.chart.chartArea;
546
+ const chartArea = this.chart.chartArea;
538
547
  for (const node of nodes.values()) {
539
548
  const x = xScale.getPixelForValue(node.x);
540
549
  const y = yScale.getPixelForValue(node.y);
@@ -566,13 +575,12 @@ class SankeyController extends chart_js.DatasetController {
566
575
  * @private
567
576
  */
568
577
  _drawLabel(label, y, height, ctx, textX) {
569
- const me = this;
570
- const font = helpers.toFont(me.options.font, me.chart.options.font);
578
+ const font = helpers.toFont(this.options.font, this.chart.options.font);
571
579
  const lines = helpers.isNullOrUndef(label) ? [] : toTextLines(label);
572
580
  const linesLength = lines.length;
573
581
  const middle = y + height / 2;
574
582
  const textHeight = font.lineHeight;
575
- const padding = helpers.valueOrDefault(me.options.padding, textHeight / 2);
583
+ const padding = helpers.valueOrDefault(this.options.padding, textHeight / 2);
576
584
 
577
585
  ctx.font = font.string;
578
586
 
@@ -587,12 +595,11 @@ class SankeyController extends chart_js.DatasetController {
587
595
  }
588
596
 
589
597
  _drawNodes() {
590
- const me = this;
591
- const ctx = me._ctx;
592
- const nodes = me._nodes || new Map();
593
- const dataset = me.getDataset(); /* SankeyControllerDatasetOptions */
598
+ const ctx = this._ctx;
599
+ const nodes = this._nodes || new Map();
600
+ const dataset = this.getDataset(); /* SankeyControllerDatasetOptions */
594
601
  const size = validateSizeValue(dataset.size);
595
- const {xScale, yScale} = me._cachedMeta;
602
+ const {xScale, yScale} = this._cachedMeta;
596
603
  const borderWidth = helpers.valueOrDefault(dataset.borderWidth, 1);
597
604
  const nodeWidth = helpers.valueOrDefault(dataset.nodeWidth, 10);
598
605
 
@@ -619,9 +626,8 @@ class SankeyController extends chart_js.DatasetController {
619
626
  * That's where the drawing process happens
620
627
  */
621
628
  draw() {
622
- const me = this;
623
- const ctx = me._ctx;
624
- const data = me.getMeta().data || []; /* Array<Flow> */
629
+ const ctx = this._ctx;
630
+ const data = this.getMeta().data || []; /* Array<Flow> */
625
631
 
626
632
  for (let i = 0, ilen = data.length; i < ilen; ++i) {
627
633
  const flow = data[i]; /* Flow at index i */
@@ -630,7 +636,7 @@ class SankeyController extends chart_js.DatasetController {
630
636
  }
631
637
 
632
638
  /* draw SankeyNodes on the canvas */
633
- me._drawNodes();
639
+ this._drawNodes();
634
640
 
635
641
  /* draw Flow elements on the canvas */
636
642
  for (let i = 0, ilen = data.length; i < ilen; ++i) {
@@ -638,7 +644,7 @@ class SankeyController extends chart_js.DatasetController {
638
644
  }
639
645
 
640
646
  /* draw labels (for SankeyNodes) on the canvas */
641
- me._drawLabels();
647
+ this._drawLabels();
642
648
  }
643
649
  }
644
650
 
@@ -689,7 +695,6 @@ SankeyController.overrides = {
689
695
  intersect: true
690
696
  },
691
697
  datasets: {
692
- color: () => '#efefef',
693
698
  clip: false,
694
699
  parsing: true
695
700
  },
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.9.0
2
+ * chartjs-chart-sankey v0.10.0
3
3
  * https://github.com/kurkle/chartjs-chart-sankey#readme
4
4
  * (c) 2022 Jukka Kurkela
5
5
  * Released under the MIT license
6
6
  */
7
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","chart.js/helpers"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Chart,t.Chart.helpers)}(this,(function(t,e){"use strict";function o(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const r=t=>void 0!==t;function n(t,e){const o=t.filter((t=>!e.has(t)));return o.length?o:t.slice(0,1)}const a=(t,e)=>t.x!==e.x?t.x-e.x:t.y-e.y,i=(t,e)=>t.reduce(((t,o)=>t+o.node[e].length+i(o.node[e],e)),0),s=t=>(e,o)=>i(e.node[t],t)-i(o.node[t],t)||e.node[t].length-o.node[t].length;function l(t,e){t.from.sort(s("from"));for(const o of t.from){const t=o.node;r(t.y)||(t.y=e,l(t,e)),e=Math.max(t.y+t.out,e)}return e}function c(t,e){t.to.sort(s("to"));for(const o of t.to){const t=o.node;r(t.y)||(t.y=e,c(t,e)),e=Math.max(t.y+t.in,e)}return e}function h(t,e){return r(t.y)?t.y:(t.y=e,e)}function f(t,e){t.sort(((t,e)=>Math.max(e.in,e.out)-Math.max(t.in,t.out)));const o=t[0];o.y=0;const n=l(o,0),a=c(o,0),i=function(t,e){const o=t.filter((t=>0===t.x)),n=t.filter((t=>t.x===e)),a=o.filter((t=>!r(t.y))),i=n.filter((t=>!r(t.y))),s=t.filter((t=>t.x>0&&t.x<e&&!r(t.y)));let f=o.reduce(((t,e)=>Math.max(t,e.y+e.out||0)),0),d=n.reduce(((t,e)=>Math.max(t,e.y+e.in||0)),0),u=0;return f>=d?(a.forEach((t=>{f=h(t,f),f=Math.max(f+t.out,c(t,f))})),i.forEach((t=>{d=h(t,d),d=Math.max(d+t.in,c(t,d))}))):(i.forEach((t=>{d=h(t,d),d=Math.max(d+t.in,c(t,d))})),a.forEach((t=>{f=h(t,f),f=Math.max(f+t.out,c(t,f))}))),s.forEach((e=>{let o=t.filter((t=>t.x===e.x&&r(t.y))).reduce(((t,e)=>Math.max(t,e.y+Math.max(e.in,e.out))),0);o=h(e,o),o=Math.max(o+e.in,l(e,o)),o=Math.max(o+e.out,c(e,o)),u=Math.max(u,o)})),Math.max(f,d,u)}(t,e);return Math.max(n,a,i)}function d(t,e,o,i){const s=[...t.values()],l=function(t,e){const o=new Set(e.map((t=>t.to))),a=new Set(e.map((t=>t.from))),i=new Set([...t.keys()]);let s=0;for(;i.size;){const a=n([...i],o);for(const e of a){const o=t.get(e);r(o.x)||(o.x=s),i.delete(e)}i.size&&(o.clear(),e.filter((t=>i.has(t.from))).forEach((t=>o.add(t.to))),s++)}return[...t.keys()].filter((t=>!a.has(t))).forEach((e=>{const o=t.get(e);o.column||(o.x=s)})),s}(t,e),c=o?function(t,e){let o=0,r=0;for(let n=0;n<=e;n++){let e=r;const a=t.filter((t=>t.x===n)).sort(((t,e)=>t.priority-e.priority));r=a[0].to.filter((t=>t.node.x>n+1)).reduce(((t,e)=>t+e.flow),0)||0;for(const t of a)t.y=e,e+=Math.max(t.out,t.in);o=Math.max(e,o)}return o}(s,l):f(s,l),h=function(t,e){let o=1,r=0,n=0,i=0;const s=[];t.sort(a);for(const a of t){if(a.y){if(0===a.x)s.push(a.y);else{for(r!==a.x&&(r=a.x,n=0),o=n+1;o<s.length&&!(s[o]>a.y);o++);n=o}a.y+=o*e,o++}i=Math.max(i,a.y+Math.max(a.in,a.out))}return i}(s,.03*c);return function(t,e){t.forEach((t=>{const o=Math[e](t.in||t.out,t.out||t.in),r=o<t.in,n=o<t.out;let a=0,i=t.from.length;t.from.sort(((t,e)=>t.node.y+t.node.out/2-(e.node.y+e.node.out/2))).forEach(((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=a,a+=t.flow)})),a=0,i=t.to.length,t.to.sort(((t,e)=>t.node.y+t.node.in/2-(e.node.y+e.node.in/2))).forEach(((t,e)=>{n?t.addY=e*(o-t.flow)/(i-1):(t.addY=a,a+=t.flow)}))}))}(s,i),{maxX:l,maxY:h}}function u(t,e,o){for(const r of t)if(r.key===e&&r.index===o)return r.addY;return 0}class x extends t.DatasetController{parseObjectData(t,e,r,n){if(0===n)return[];const a=this,{xScale:i,yScale:s}=t,l=[],c=a._nodes=function(t){const e=new Map;for(let o=0;o<t.length;o++){const{from:r,to:n,flow:a}=t[o];if(e.has(r)){const t=e.get(r);t.out+=a,t.to.push({key:n,flow:a,index:o})}else e.set(r,{key:r,in:0,out:a,from:[],to:[{key:n,flow:a,index:o}]});if(e.has(n)){const t=e.get(n);t.in+=a,t.from.push({key:r,flow:a,index:o})}else e.set(n,{key:n,in:a,out:0,from:[{key:r,flow:a,index:o}],to:[]})}const o=(t,e)=>e.flow-t.flow;return[...e.values()].forEach((t=>{t.from=t.from.sort(o),t.from.forEach((t=>{t.node=e.get(t.key)})),t.to=t.to.sort(o),t.to.forEach((t=>{t.node=e.get(t.key)}))})),e}(e),{column:h,priority:f,size:x}=a.getDataset();if(f)for(const t of c.values())t.key in f&&(t.priority=f[t.key]);if(h)for(const t of c.values())t.key in h&&(t.column=!0,t.x=h[t.key]);const{maxX:y,maxY:p}=d(c,e,!!f,o(x));a._maxX=y,a._maxY=p;for(let t=0,o=e.length;t<o;++t){const o=e[t],r=c.get(o.from),n=c.get(o.to),a=r.y+u(r.to,o.to,t),h=n.y+u(n.from,o.from,t);l.push({x:i.parse(r.x,t),y:s.parse(a,t),_custom:{from:r,to:n,x:i.parse(n.x,t),y:s.parse(h,t),height:s.parse(o.flow,t)}})}return l.slice(r,r+n)}getMinMax(t){return{min:0,max:t===this._cachedMeta.xScale?this._maxX:this._maxY}}update(t){const e=this._cachedMeta;this.updateElements(e.data,0,e.data.length,t)}updateElements(t,o,r,n){const a=this,{xScale:i,yScale:s}=a._cachedMeta,l=a.resolveDataElementOptions(o,n),c=a.getSharedOptions(n,t[o],l),h=a.getDataset(),f=e.valueOrDefault(h.borderWidth,1)/2+.5,d=e.valueOrDefault(h.nodeWidth,10);for(let e=o;e<o+r;e++){const o=a.getParsed(e),r=o._custom,l=s.getPixelForValue(o.y);a.updateElement(t[e],e,{x:i.getPixelForValue(o.x)+d+f,y:l,x2:i.getPixelForValue(r.x)-f,y2:s.getPixelForValue(r.y),from:r.from,to:r.to,progress:"reset"===n?0:1,height:Math.abs(s.getPixelForValue(o.y+r.height)-l),options:a.resolveDataElementOptions(e,n)},n)}a.updateSharedOptions(c,n)}_drawLabels(){const t=this,r=t._ctx,n=t._nodes||new Map,a=t.getDataset(),i=o(a.size),s=e.valueOrDefault(a.borderWidth,1),l=e.valueOrDefault(a.nodeWidth,10),c=a.labels,{xScale:h,yScale:f}=t._cachedMeta;r.save();const d=t.chart.chartArea;for(const t of n.values()){const e=h.getPixelForValue(t.x),o=f.getPixelForValue(t.y),n=Math[i](t.in||t.out,t.out||t.in),u=Math.abs(f.getPixelForValue(t.y+n)-o),x=c&&c[t.key]||t.key;let y=e;r.fillStyle=a.color||"black",r.textBaseline="middle",e<d.width/2?(r.textAlign="left",y+=l+s+4):(r.textAlign="right",y-=s+4),this._drawLabel(x,o,u,r,y)}r.restore()}_drawLabel(t,o,r,n,a){const i=this,s=e.toFont(i.options.font,i.chart.options.font),l=e.isNullOrUndef(t)?[]:function(t){const o=[],r=e.isArray(t)?t:e.isNullOrUndef(t)?[]:[t];for(;r.length;){const t=r.pop();"string"==typeof t?o.unshift.apply(o,t.split("\n")):Array.isArray(t)?r.push.apply(r,t):e.isNullOrUndef(r)||o.unshift(""+t)}return o}(t),c=l.length,h=o+r/2,f=s.lineHeight,d=e.valueOrDefault(i.options.padding,f/2);if(n.font=s.string,c>1){const t=h-f*c/2+d;for(let e=0;e<c;e++)n.fillText(l[e],a,t+e*f)}else n.fillText(t,a,h)}_drawNodes(){const t=this,r=t._ctx,n=t._nodes||new Map,a=t.getDataset(),i=o(a.size),{xScale:s,yScale:l}=t._cachedMeta,c=e.valueOrDefault(a.borderWidth,1),h=e.valueOrDefault(a.nodeWidth,10);r.save(),r.strokeStyle=a.borderColor||"black",r.lineWidth=c;for(const t of n.values()){r.fillStyle=t.color;const e=s.getPixelForValue(t.x),o=l.getPixelForValue(t.y),n=Math[i](t.in||t.out,t.out||t.in),a=Math.abs(l.getPixelForValue(t.y+n)-o);c&&r.strokeRect(e,o,h,a),r.fillRect(e,o,h,a)}r.restore()}draw(){const t=this,e=t._ctx,o=t.getMeta().data||[];for(let t=0,e=o.length;t<e;++t){const e=o[t];e.from.color=e.options.colorFrom,e.to.color=e.options.colorTo}t._drawNodes();for(let t=0,r=o.length;t<r;++t)o[t].draw(e);t._drawLabels()}}x.id="sankey",x.defaults={dataElementType:"flow",animations:{numbers:{type:"number",properties:["x","y","x2","y2","height"]},progress:{easing:"linear",duration:t=>"data"===t.type?200*(t.parsed._custom.x-t.parsed.x):void 0,delay:t=>"data"===t.type?500*t.parsed.x+20*t.dataIndex:void 0},colors:{type:"color",properties:["colorFrom","colorTo"]}},transitions:{hide:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],to:"transparent"}}},show:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],from:"transparent"}}}}},x.overrides={interaction:{mode:"nearest",intersect:!0},datasets:{color:()=>"#efefef",clip:!1,parsing:!0},plugins:{tooltip:{callbacks:{title:()=>"",label(t){const e=t.dataset.data[t.dataIndex];return e.from+" -> "+e.to+": "+e.flow}}},legend:{display:!1}},scales:{x:{type:"linear",bounds:"data",display:!1,min:0,offset:!1},y:{type:"linear",bounds:"data",display:!1,min:0,reverse:!0,offset:!1}},layout:{padding:{top:3,left:3,right:13,bottom:3}}};const y=(t,e,o,r)=>t<o?{cp1:{x:t+(o-t)/3*2,y:e},cp2:{x:t+(o-t)/3,y:r}}:{cp1:{x:t-(t-o)/3,y:0},cp2:{x:o+(t-o)/3,y:0}},p=(t,e,o)=>({x:t.x+o*(e.x-t.x),y:t.y+o*(e.y-t.y)});class g extends t.Element{constructor(t){super(),this.options=void 0,this.x=void 0,this.y=void 0,this.x2=void 0,this.y2=void 0,this.height=void 0,t&&Object.assign(this,t)}draw(t){const{x:o,x2:r,y:n,y2:a,height:i,progress:s}=this,{cp1:l,cp2:c}=y(o,n,r,a);0!==s&&(t.save(),s<1&&(t.beginPath(),t.rect(o,Math.min(n,a),(r-o)*s+1,Math.abs(a-n)+i+1),t.clip()),function(t,{x:o,x2:r,options:n}){let a;"from"===n.colorMode?a=e.color(n.colorFrom).alpha(.5).rgbString():"to"===n.colorMode?a=e.color(n.colorTo).alpha(.5).rgbString():(a=t.createLinearGradient(o,0,r,0),a.addColorStop(0,e.color(n.colorFrom).alpha(.5).rgbString()),a.addColorStop(1,e.color(n.colorTo).alpha(.5).rgbString())),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(o,n),t.bezierCurveTo(l.x,l.y,c.x,c.y,r,a),t.lineTo(r,a+i),t.bezierCurveTo(c.x,c.y+i,l.x,l.y+i,o,n+i),t.lineTo(o,n),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,e,o){const{x:r,y:n,x2:a,y2:i,height:s}=this.getProps(["x","y","x2","y2","height"],o);if(t<r||t>a)return!1;const{cp1:l,cp2:c}=y(r,n,a,i),h=(t-r)/(a-r),f={x:a,y:i},d=p({x:r,y:n},l,h),u=p(l,c,h),x=p(c,f,h),g=p(d,u,h),m=p(u,x,h),M=p(g,m,h).y;return e>=M&&e<=M+s}inXRange(t,e){const{x:o,x2:r}=this.getProps(["x","x2"],e);return t>=o&&t<=r}inYRange(t,e){const{y:o,y2:r,height:n}=this.getProps(["y","y2","height"],e),a=Math.min(o,r),i=Math.max(o,r)+n;return t>=a&&t<=i}getCenterPoint(t){const{x:e,y:o,x2:r,y2:n,height:a}=this.getProps(["x","y","x2","y2","height"],t);return{x:(e+r)/2,y:(o+n+a)/2}}tooltipPosition(t){return this.getCenterPoint(t)}getRange(t){return"x"===t?this.width/2:this.height/2}}g.id="flow",g.defaults={colorFrom:"red",colorTo:"green",colorMode:"gradient"},t.Chart.register(x,g)}));
7
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","chart.js/helpers"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Chart,t.Chart.helpers)}(this,(function(t,e){"use strict";function o(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const r=t=>void 0!==t;function n(t,e){const o=t.filter((t=>!e.has(t)));return o.length?o:t.slice(0,1)}const a=(t,e)=>t.x!==e.x?t.x-e.x:t.y-e.y;let i=-1;function s(t,e,o=function(){return i=i<100?i+1:0,i}()){let r=0;for(const n of t)n.node._visited!==o&&(n.node._visited=o,r+=n.node[e].length+s(n.node[e],e,o));return r}const l=t=>(e,o)=>s(e.node[t],t)-s(o.node[t],t)||e.node[t].length-o.node[t].length;function c(t,e){t.from.sort(l("from"));for(const o of t.from){const t=o.node;r(t.y)||(t.y=e,c(t,e)),e=Math.max(t.y+t.out,e)}return e}function h(t,e){t.to.sort(l("to"));for(const o of t.to){const t=o.node;r(t.y)||(t.y=e,h(t,e)),e=Math.max(t.y+t.in,e)}return e}function f(t,e){return r(t.y)?t.y:(t.y=e,e)}function d(t,e){t.sort(((t,e)=>Math.max(e.in,e.out)-Math.max(t.in,t.out)));const o=t[0];o.y=0;const n=c(o,0),a=h(o,0),i=function(t,e){const o=t.filter((t=>0===t.x)),n=t.filter((t=>t.x===e)),a=o.filter((t=>!r(t.y))),i=n.filter((t=>!r(t.y))),s=t.filter((t=>t.x>0&&t.x<e&&!r(t.y)));let l=o.reduce(((t,e)=>Math.max(t,e.y+e.out||0)),0),d=n.reduce(((t,e)=>Math.max(t,e.y+e.in||0)),0),u=0;return l>=d?(a.forEach((t=>{l=f(t,l),l=Math.max(l+t.out,h(t,l))})),i.forEach((t=>{d=f(t,d),d=Math.max(d+t.in,h(t,d))}))):(i.forEach((t=>{d=f(t,d),d=Math.max(d+t.in,h(t,d))})),a.forEach((t=>{l=f(t,l),l=Math.max(l+t.out,h(t,l))}))),s.forEach((e=>{let o=t.filter((t=>t.x===e.x&&r(t.y))).reduce(((t,e)=>Math.max(t,e.y+Math.max(e.in,e.out))),0);o=f(e,o),o=Math.max(o+e.in,c(e,o)),o=Math.max(o+e.out,h(e,o)),u=Math.max(u,o)})),Math.max(l,d,u)}(t,e);return Math.max(n,a,i)}function u(t,e,o,i){const s=[...t.values()],l=function(t,e){const o=new Set(e.map((t=>t.to))),a=new Set(e.map((t=>t.from))),i=new Set([...t.keys()]);let s=0;for(;i.size;){const a=n([...i],o);for(const e of a){const o=t.get(e);r(o.x)||(o.x=s),i.delete(e)}i.size&&(o.clear(),e.filter((t=>i.has(t.from))).forEach((t=>o.add(t.to))),s++)}return[...t.keys()].filter((t=>!a.has(t))).forEach((e=>{const o=t.get(e);o.column||(o.x=s)})),s}(t,e),c=o?function(t,e){let o=0,r=0;for(let n=0;n<=e;n++){let e=r;const a=t.filter((t=>t.x===n)).sort(((t,e)=>t.priority-e.priority));r=a[0].to.filter((t=>t.node.x>n+1)).reduce(((t,e)=>t+e.flow),0)||0;for(const t of a)t.y=e,e+=Math.max(t.out,t.in);o=Math.max(e,o)}return o}(s,l):d(s,l),h=function(t,e){let o=1,r=0,n=0,i=0;const s=[];t.sort(a);for(const a of t){if(a.y){if(0===a.x)s.push(a.y);else{for(r!==a.x&&(r=a.x,n=0),o=n+1;o<s.length&&!(s[o]>a.y);o++);n=o}a.y+=o*e,o++}i=Math.max(i,a.y+Math.max(a.in,a.out))}return i}(s,.03*c);return function(t,e){t.forEach((t=>{const o=Math[e](t.in||t.out,t.out||t.in),r=o<t.in,n=o<t.out;let a=0,i=t.from.length;t.from.sort(((t,e)=>t.node.y+t.node.out/2-(e.node.y+e.node.out/2))).forEach(((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=a,a+=t.flow)})),a=0,i=t.to.length,t.to.sort(((t,e)=>t.node.y+t.node.in/2-(e.node.y+e.node.in/2))).forEach(((t,e)=>{n?t.addY=e*(o-t.flow)/(i-1):(t.addY=a,a+=t.flow)}))}))}(s,i),{maxX:l,maxY:h}}function x(t,e,o){for(const r of t)if(r.key===e&&r.index===o)return r.addY;return 0}class y extends t.DatasetController{parseObjectData(t,e,r,n){const{from:a="from",to:i="to",flow:s="flow"}=this.options.parsing,l=e.map((({[a]:t,[i]:e,[s]:o})=>({from:t,to:e,flow:o}))),{xScale:c,yScale:h}=t,f=[],d=this._nodes=function(t){const e=new Map;for(let o=0;o<t.length;o++){const{from:r,to:n,flow:a}=t[o];if(e.has(r)){const t=e.get(r);t.out+=a,t.to.push({key:n,flow:a,index:o})}else e.set(r,{key:r,in:0,out:a,from:[],to:[{key:n,flow:a,index:o}]});if(e.has(n)){const t=e.get(n);t.in+=a,t.from.push({key:r,flow:a,index:o})}else e.set(n,{key:n,in:a,out:0,from:[{key:r,flow:a,index:o}],to:[]})}const o=(t,e)=>e.flow-t.flow;return[...e.values()].forEach((t=>{t.from=t.from.sort(o),t.from.forEach((t=>{t.node=e.get(t.key)})),t.to=t.to.sort(o),t.to.forEach((t=>{t.node=e.get(t.key)}))})),e}(l),{column:y,priority:p,size:m}=this.getDataset();if(p)for(const t of d.values())t.key in p&&(t.priority=p[t.key]);if(y)for(const t of d.values())t.key in y&&(t.column=!0,t.x=y[t.key]);const{maxX:g,maxY:M}=u(d,l,!!p,o(m));this._maxX=g,this._maxY=M;for(let t=0,e=l.length;t<e;++t){const e=l[t],o=d.get(e.from),r=d.get(e.to),n=o.y+x(o.to,e.to,t),a=r.y+x(r.from,e.from,t);f.push({x:c.parse(o.x,t),y:h.parse(n,t),_custom:{from:o,to:r,x:c.parse(r.x,t),y:h.parse(a,t),height:h.parse(e.flow,t)}})}return f.slice(r,r+n)}getMinMax(t){return{min:0,max:t===this._cachedMeta.xScale?this._maxX:this._maxY}}update(t){const{data:e}=this._cachedMeta;this.updateElements(e,0,e.length,t)}updateElements(t,o,r,n){const{xScale:a,yScale:i}=this._cachedMeta,s=this.resolveDataElementOptions(o,n),l=this.getSharedOptions(n,t[o],s),c=this.getDataset(),h=e.valueOrDefault(c.borderWidth,1)/2+.5,f=e.valueOrDefault(c.nodeWidth,10);for(let e=o;e<o+r;e++){const o=this.getParsed(e),r=o._custom,s=i.getPixelForValue(o.y);this.updateElement(t[e],e,{x:a.getPixelForValue(o.x)+f+h,y:s,x2:a.getPixelForValue(r.x)-h,y2:i.getPixelForValue(r.y),from:r.from,to:r.to,progress:"reset"===n?0:1,height:Math.abs(i.getPixelForValue(o.y+r.height)-s),options:this.resolveDataElementOptions(e,n)},n)}this.updateSharedOptions(l,n)}_drawLabels(){const t=this._ctx,r=this._nodes||new Map,n=this.getDataset(),a=o(n.size),i=e.valueOrDefault(n.borderWidth,1),s=e.valueOrDefault(n.nodeWidth,10),l=n.labels,{xScale:c,yScale:h}=this._cachedMeta;t.save();const f=this.chart.chartArea;for(const e of r.values()){const o=c.getPixelForValue(e.x),r=h.getPixelForValue(e.y),d=Math[a](e.in||e.out,e.out||e.in),u=Math.abs(h.getPixelForValue(e.y+d)-r),x=l&&l[e.key]||e.key;let y=o;t.fillStyle=n.color||"black",t.textBaseline="middle",o<f.width/2?(t.textAlign="left",y+=s+i+4):(t.textAlign="right",y-=i+4),this._drawLabel(x,r,u,t,y)}t.restore()}_drawLabel(t,o,r,n,a){const i=e.toFont(this.options.font,this.chart.options.font),s=e.isNullOrUndef(t)?[]:function(t){const o=[],r=e.isArray(t)?t:e.isNullOrUndef(t)?[]:[t];for(;r.length;){const t=r.pop();"string"==typeof t?o.unshift.apply(o,t.split("\n")):Array.isArray(t)?r.push.apply(r,t):e.isNullOrUndef(r)||o.unshift(""+t)}return o}(t),l=s.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 e=0;e<l;e++)n.fillText(s[e],a,t+e*h)}else n.fillText(t,a,c)}_drawNodes(){const t=this._ctx,r=this._nodes||new Map,n=this.getDataset(),a=o(n.size),{xScale:i,yScale:s}=this._cachedMeta,l=e.valueOrDefault(n.borderWidth,1),c=e.valueOrDefault(n.nodeWidth,10);t.save(),t.strokeStyle=n.borderColor||"black",t.lineWidth=l;for(const e of r.values()){t.fillStyle=e.color;const o=i.getPixelForValue(e.x),r=s.getPixelForValue(e.y),n=Math[a](e.in||e.out,e.out||e.in),h=Math.abs(s.getPixelForValue(e.y+n)-r);l&&t.strokeRect(o,r,c,h),t.fillRect(o,r,c,h)}t.restore()}draw(){const t=this._ctx,e=this.getMeta().data||[];for(let t=0,o=e.length;t<o;++t){const o=e[t];o.from.color=o.options.colorFrom,o.to.color=o.options.colorTo}this._drawNodes();for(let o=0,r=e.length;o<r;++o)e[o].draw(t);this._drawLabels()}}y.id="sankey",y.defaults={dataElementType:"flow",animations:{numbers:{type:"number",properties:["x","y","x2","y2","height"]},progress:{easing:"linear",duration:t=>"data"===t.type?200*(t.parsed._custom.x-t.parsed.x):void 0,delay:t=>"data"===t.type?500*t.parsed.x+20*t.dataIndex:void 0},colors:{type:"color",properties:["colorFrom","colorTo"]}},transitions:{hide:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],to:"transparent"}}},show:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],from:"transparent"}}}}},y.overrides={interaction:{mode:"nearest",intersect:!0},datasets:{clip:!1,parsing:!0},plugins:{tooltip:{callbacks:{title:()=>"",label(t){const e=t.dataset.data[t.dataIndex];return e.from+" -> "+e.to+": "+e.flow}}},legend:{display:!1}},scales:{x:{type:"linear",bounds:"data",display:!1,min:0,offset:!1},y:{type:"linear",bounds:"data",display:!1,min:0,reverse:!0,offset:!1}},layout:{padding:{top:3,left:3,right:13,bottom:3}}};const p=(t,e,o,r)=>t<o?{cp1:{x:t+(o-t)/3*2,y:e},cp2:{x:t+(o-t)/3,y:r}}:{cp1:{x:t-(t-o)/3,y:0},cp2:{x:o+(t-o)/3,y:0}},m=(t,e,o)=>({x:t.x+o*(e.x-t.x),y:t.y+o*(e.y-t.y)});class g extends t.Element{constructor(t){super(),this.options=void 0,this.x=void 0,this.y=void 0,this.x2=void 0,this.y2=void 0,this.height=void 0,t&&Object.assign(this,t)}draw(t){const{x:o,x2:r,y:n,y2:a,height:i,progress:s}=this,{cp1:l,cp2:c}=p(o,n,r,a);0!==s&&(t.save(),s<1&&(t.beginPath(),t.rect(o,Math.min(n,a),(r-o)*s+1,Math.abs(a-n)+i+1),t.clip()),function(t,{x:o,x2:r,options:n}){let a;"from"===n.colorMode?a=e.color(n.colorFrom).alpha(.5).rgbString():"to"===n.colorMode?a=e.color(n.colorTo).alpha(.5).rgbString():(a=t.createLinearGradient(o,0,r,0),a.addColorStop(0,e.color(n.colorFrom).alpha(.5).rgbString()),a.addColorStop(1,e.color(n.colorTo).alpha(.5).rgbString())),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(o,n),t.bezierCurveTo(l.x,l.y,c.x,c.y,r,a),t.lineTo(r,a+i),t.bezierCurveTo(c.x,c.y+i,l.x,l.y+i,o,n+i),t.lineTo(o,n),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,e,o){const{x:r,y:n,x2:a,y2:i,height:s}=this.getProps(["x","y","x2","y2","height"],o);if(t<r||t>a)return!1;const{cp1:l,cp2:c}=p(r,n,a,i),h=(t-r)/(a-r),f={x:a,y:i},d=m({x:r,y:n},l,h),u=m(l,c,h),x=m(c,f,h),y=m(d,u,h),g=m(u,x,h),M=m(y,g,h).y;return e>=M&&e<=M+s}inXRange(t,e){const{x:o,x2:r}=this.getProps(["x","x2"],e);return t>=o&&t<=r}inYRange(t,e){const{y:o,y2:r,height:n}=this.getProps(["y","y2","height"],e),a=Math.min(o,r),i=Math.max(o,r)+n;return t>=a&&t<=i}getCenterPoint(t){const{x:e,y:o,x2:r,y2:n,height:a}=this.getProps(["x","y","x2","y2","height"],t);return{x:(e+r)/2,y:(o+n+a)/2}}tooltipPosition(t){return this.getCenterPoint(t)}getRange(t){return"x"===t?this.width/2:this.height/2}}g.id="flow",g.defaults={colorFrom:"red",colorTo:"green",colorMode:"gradient"},t.Chart.register(y,g)}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chartjs-chart-sankey",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Chart.js module for creating sankey diagrams",
5
5
  "main": "dist/chartjs-chart-sankey.js",
6
6
  "module": "dist/chartjs-chart-sankey.esm.js",
@@ -59,7 +59,7 @@
59
59
  "karma-firefox-launcher": "^2.1.0",
60
60
  "karma-jasmine": "^4.0.1",
61
61
  "karma-jasmine-html-reporter": "^1.5.4",
62
- "karma-rollup-preprocessor": "^7.0.7",
62
+ "karma-rollup-preprocessor": "7.0.7",
63
63
  "karma-spec-reporter": "^0.0.33",
64
64
  "rollup": "^2.42.1",
65
65
  "rollup-plugin-istanbul": "^3.0.0",
@@ -3,8 +3,10 @@ import {
3
3
  ChartComponent,
4
4
  DatasetController,
5
5
  Element,
6
+ VisualElement,
6
7
  FontSpec
7
8
  } from 'chart.js';
9
+ import { AnyObject } from 'chart.js/types/basic';
8
10
 
9
11
  declare module 'chart.js' {
10
12
 
@@ -78,28 +80,28 @@ declare module 'chart.js' {
78
80
  datasetOptions: SankeyControllerDatasetOptions;
79
81
  defaultDataPoint: SankeyDataPoint;
80
82
  parsedDataType: SankeyParsedData;
81
- metaExtensions:{}
83
+ metaExtensions: AnyObject
82
84
  /* TODO: define sankey chart options */
83
- chartOptions: any;
85
+ chartOptions: AnyObject;
84
86
  scales: keyof CartesianScaleTypeRegistry;
85
87
  };
86
88
  }
87
89
  }
88
90
 
89
- type FlowOptions = {
91
+ export interface FlowOptions {
90
92
  colorMode: 'gradient' | 'from' | 'to';
91
93
  colorFrom: string
92
94
  colorTo: string
93
95
  }
94
96
 
95
- type FlowConfig = {
97
+ export interface FlowConfig {
96
98
  x: number;
97
99
  y: number;
98
100
  x2: number;
99
101
  y2: number;
100
102
  height: number;
101
103
  options: FlowOptions
102
- };
104
+ }
103
105
 
104
106
  export type SankeyController = DatasetController
105
107
  export const SankeyController: ChartComponent & {
@@ -107,7 +109,11 @@ export const SankeyController: ChartComponent & {
107
109
  new(chart: Chart, datasetIndex: number): SankeyController;
108
110
  };
109
111
 
110
- export type Flow = Element<FlowConfig, FlowOptions>
112
+ export interface Flow<
113
+ T extends FlowConfig = FlowConfig,
114
+ O extends FlowOptions = FlowOptions
115
+ > extends Element<T, O>, VisualElement {}
116
+
111
117
  export const Flow: ChartComponent & {
112
118
  prototype: Flow;
113
119
  new(cfg: FlowConfig): Flow;