chartjs-chart-sankey 0.15.3 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -134
- package/dist/chartjs-chart-sankey.cjs +9 -1
- package/dist/chartjs-chart-sankey.esm.js +9 -1
- package/dist/chartjs-chart-sankey.min.js +2 -2
- package/dist/controller.d.cts +135 -0
- package/dist/controller.d.ts +8 -0
- package/dist/flow.d.cts +78 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.esm.d.cts +3 -0
- package/dist/labels.d.cts +22 -0
- package/dist/types.d.cts +140 -0
- package/package.json +32 -32
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# chartjs-chart-sankey
|
|
2
2
|
|
|
3
|
-
[Chart.js](https://www.chartjs.org/) **^3.3** module for creating sankey diagrams
|
|
4
|
-
|
|
5
3
|
[](https://www.npmjs.com/package/chartjs-chart-sankey)
|
|
6
4
|
[](https://github.com/kurkle/chartjs-chart-sankey/releases/latest)
|
|
7
5
|

|
|
@@ -10,165 +8,60 @@
|
|
|
10
8
|
[](https://chartjs-chart-sankey.pages.dev)
|
|
11
9
|

|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
All modern and up-to-date browsers are supported, including, but not limited to:
|
|
16
|
-
|
|
17
|
-
- Chrome
|
|
18
|
-
- Edge
|
|
19
|
-
- Firefox
|
|
20
|
-
- Safari
|
|
21
|
-
|
|
22
|
-
Internet Explorer 11 is not supported.
|
|
23
|
-
|
|
24
|
-
## Typescript
|
|
25
|
-
|
|
26
|
-
Typescript 3.x and higher is supported.
|
|
11
|
+
[Chart.js](https://www.chartjs.org/) **v3.3+, v4+** module that adds a sankey chart type, drawing flows between named nodes as directional bands whose width is proportional to the flow value — useful for visualizing energy transfers, budgets, funnels, and other flow-style data, for anyone already charting with Chart.js.
|
|
27
12
|
|
|
28
|
-
##
|
|
29
|
-
|
|
30
|
-
You can find documentation for chartjs-chart-sankey at [https://chartjs-chart-sankey.pages.dev/](https://chartjs-chart-sankey.pages.dev/).
|
|
31
|
-
|
|
32
|
-
## Integration
|
|
13
|
+
## Example
|
|
33
14
|
|
|
34
|
-
|
|
15
|
+

|
|
35
16
|
|
|
36
|
-
|
|
37
|
-
import {Chart} from 'chart.js';
|
|
38
|
-
import {SankeyController, Flow} from 'chartjs-chart-sankey';
|
|
17
|
+
## Installation
|
|
39
18
|
|
|
40
|
-
|
|
19
|
+
```bash
|
|
20
|
+
npm install chartjs-chart-sankey
|
|
41
21
|
```
|
|
42
22
|
|
|
43
|
-
|
|
44
|
-
and flow element automatically.
|
|
23
|
+
Or via CDN:
|
|
45
24
|
|
|
46
25
|
```html
|
|
47
26
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
48
27
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-chart-sankey"></script>
|
|
49
28
|
```
|
|
50
29
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```html
|
|
54
|
-
<script src="https://unpkg.com/chart.js"></script>
|
|
55
|
-
<script src="https://unpkg.com/chartjs-chart-sankey"></script>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
If a CDN does not use the package metadata for its default file, reference the browser bundle directly:
|
|
59
|
-
|
|
60
|
-
```html
|
|
61
|
-
<script src="https://cdn.jsdelivr.net/npm/chartjs-chart-sankey/dist/chartjs-chart-sankey.min.js"></script>
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
To create a sankey chart, include chartjs-chart-sankey.js after chart.js and then create the chart by setting the `type`
|
|
65
|
-
attribute to `'sankey'`
|
|
30
|
+
## Quickstart
|
|
66
31
|
|
|
67
32
|
```js
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
data: dataObject
|
|
71
|
-
});
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Configuration
|
|
75
|
-
|
|
76
|
-
Example:
|
|
77
|
-
|
|
78
|
-
```js
|
|
79
|
-
const colors = {
|
|
80
|
-
a: 'red',
|
|
81
|
-
b: 'green',
|
|
82
|
-
c: 'blue',
|
|
83
|
-
d: 'gray'
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const getHover = (key) => colors[key];
|
|
87
|
-
const getColor = (key) => colors[key];
|
|
88
|
-
|
|
89
|
-
const chart = new Chart(ctx, {
|
|
90
|
-
type: 'sankey',
|
|
91
|
-
data: {
|
|
92
|
-
datasets: [{
|
|
93
|
-
label: 'My sankey',
|
|
94
|
-
data: [
|
|
95
|
-
{from: 'a', to: 'b', flow: 10},
|
|
96
|
-
{from: 'a', to: 'c', flow: 5},
|
|
97
|
-
{from: 'b', to: 'c', flow: 10},
|
|
98
|
-
{from: 'd', to: 'c', flow: 7}
|
|
99
|
-
],
|
|
100
|
-
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex].from),
|
|
101
|
-
colorTo: (c) => getColor(c.dataset.data[c.dataIndex].to),
|
|
102
|
-
hoverColorFrom: (c) => getHover(c.dataset.data[c.dataIndex].from),
|
|
103
|
-
hoverColorTo: (c) => getHover(c.dataset.data[c.dataIndex].to),
|
|
104
|
-
colorMode: 'gradient', // or 'from' or 'to'
|
|
105
|
-
/* optionally override default alpha (0.5) applied to colorFrom and colorTo */
|
|
106
|
-
alpha: 1,
|
|
107
|
-
/* optional labels */
|
|
108
|
-
labels: {
|
|
109
|
-
a: 'Label A',
|
|
110
|
-
b: 'Label B',
|
|
111
|
-
c: 'Label C',
|
|
112
|
-
d: 'Label D'
|
|
113
|
-
},
|
|
114
|
-
/* optional priority */
|
|
115
|
-
priority: {
|
|
116
|
-
b: 1,
|
|
117
|
-
d: 0
|
|
118
|
-
},
|
|
119
|
-
/* optional column overrides */
|
|
120
|
-
column: {
|
|
121
|
-
d: 1
|
|
122
|
-
},
|
|
123
|
-
size: 'max', // or 'min' if flow overlap is preferred
|
|
124
|
-
}]
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Custom data structure
|
|
33
|
+
import { Chart, LinearScale } from 'chart.js';
|
|
34
|
+
import { Flow, SankeyController } from 'chartjs-chart-sankey';
|
|
130
35
|
|
|
131
|
-
|
|
132
|
-
For example:
|
|
36
|
+
Chart.register(LinearScale, SankeyController, Flow);
|
|
133
37
|
|
|
134
|
-
|
|
135
|
-
const chart = new Chart(ctx, {
|
|
38
|
+
new Chart(document.getElementById('chart'), {
|
|
136
39
|
type: 'sankey',
|
|
137
40
|
data: {
|
|
138
41
|
datasets: [
|
|
139
42
|
{
|
|
43
|
+
label: 'My sankey',
|
|
140
44
|
data: [
|
|
141
|
-
{
|
|
142
|
-
{
|
|
143
|
-
{
|
|
45
|
+
{from: 'a', to: 'b', flow: 10},
|
|
46
|
+
{from: 'a', to: 'c', flow: 5},
|
|
47
|
+
{from: 'b', to: 'd', flow: 6},
|
|
48
|
+
{from: 'c', to: 'd', flow: 4},
|
|
144
49
|
],
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
]
|
|
50
|
+
},
|
|
51
|
+
],
|
|
149
52
|
},
|
|
150
|
-
options: {
|
|
151
|
-
parsing: {
|
|
152
|
-
from: 'source',
|
|
153
|
-
to: 'destination',
|
|
154
|
-
flow: 'value'
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
53
|
});
|
|
158
54
|
```
|
|
159
55
|
|
|
160
|
-
|
|
56
|
+
See more integration options (script tag, other module loaders) in the [documentation](https://chartjs-chart-sankey.pages.dev/integration/).
|
|
161
57
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
## Online examples
|
|
58
|
+
## Documentation
|
|
165
59
|
|
|
166
|
-
[
|
|
167
|
-
[Vue.js 2](https://codesandbox.io/s/reverent-boyd-od2fr?file=/src/App.vue)
|
|
60
|
+
You can find documentation for chartjs-chart-sankey at [https://chartjs-chart-sankey.pages.dev/](https://chartjs-chart-sankey.pages.dev/). The full dataset and options reference lives there, not in this README — this file stays a quickstart.
|
|
168
61
|
|
|
169
62
|
## Development
|
|
170
63
|
|
|
171
|
-
You first need to install node dependencies
|
|
64
|
+
You first need to install node dependencies (requires [Node.js](https://nodejs.org/)):
|
|
172
65
|
|
|
173
66
|
```bash
|
|
174
67
|
> npm install
|
|
@@ -177,12 +70,10 @@ You first need to install node dependencies (requires [Node.js](https://nodejs.
|
|
|
177
70
|
The following commands will then be available from the repository root:
|
|
178
71
|
|
|
179
72
|
```bash
|
|
180
|
-
> npm run build
|
|
73
|
+
> npm run build // build dist files
|
|
181
74
|
> npm run autobuild // build and watch for changes
|
|
182
75
|
> npm test // run all tests
|
|
183
|
-
> npm
|
|
184
|
-
> npm lint // perform code linting
|
|
185
|
-
> npm package // create an archive with dist files and samples
|
|
76
|
+
> npm run lint // perform code linting
|
|
186
77
|
```
|
|
187
78
|
|
|
188
79
|
## License
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.16.1
|
|
3
3
|
* https://chartjs-chart-sankey.pages.dev/
|
|
4
4
|
* (c) 2026 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
@@ -854,6 +854,14 @@ SankeyController.defaults = {
|
|
|
854
854
|
}
|
|
855
855
|
}
|
|
856
856
|
},
|
|
857
|
+
resize: {
|
|
858
|
+
animations: {
|
|
859
|
+
progress: {
|
|
860
|
+
delay: 0,
|
|
861
|
+
duration: 0
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
},
|
|
857
865
|
show: {
|
|
858
866
|
animations: {
|
|
859
867
|
colors: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.16.1
|
|
3
3
|
* https://chartjs-chart-sankey.pages.dev/
|
|
4
4
|
* (c) 2026 Jukka Kurkela
|
|
5
5
|
* Released under the MIT license
|
|
@@ -851,6 +851,14 @@ SankeyController.defaults = {
|
|
|
851
851
|
}
|
|
852
852
|
}
|
|
853
853
|
},
|
|
854
|
+
resize: {
|
|
855
|
+
animations: {
|
|
856
|
+
progress: {
|
|
857
|
+
delay: 0,
|
|
858
|
+
duration: 0
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
},
|
|
854
862
|
show: {
|
|
855
863
|
animations: {
|
|
856
864
|
colors: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* chartjs-chart-sankey v0.
|
|
2
|
+
* chartjs-chart-sankey v0.16.1
|
|
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"}function i(t,o){return"function"==typeof t?t(o):t&&"object"==typeof t&&!function(t){const o=Object.prototype.toString.call(t);return"[object CanvasPattern]"===o||"[object CanvasGradient]"===o}(t)?t[o.key]:t}function a(t,o,e,r,n,i,a){t.save(),t.fillStyle=o,a>0?(!function(t,o,e,r,n,i){const a=Math.max(0,Math.min(i,r/2,n/2));t.beginPath(),t.moveTo(o+a,e),t.lineTo(o+r-a,e),t.quadraticCurveTo(o+r,e,o+r,e+a),t.lineTo(o+r,e+n-a),t.quadraticCurveTo(o+r,e+n,o+r-a,e+n),t.lineTo(o+a,e+n),t.quadraticCurveTo(o,e+n,o,e+n-a),t.lineTo(o,e+a),t.quadraticCurveTo(o,e,o+a,e),t.closePath()}(t,e,r,n,i,a),t.fill()):t.fillRect(e,r,n,i),t.restore()}function s(t,o,e){const r=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}(o);if(!r.length)return;const{backgroundColor:n,borderRadius:i,color:s,font:l,lineOffset:h,padding:c}=e,d=function(t,o){return"auto"===t?o:t}(e.position,e.autoPosition),f=Number(l.lineHeight);t.font=l.string;const u=Math.max(...r.map(o=>t.measureText(o).width)),p=f*r.length,y=function(t,o,e){const{borderWidth:r,height:n,padding:i,width:a,x:s,y:l}=o,h={align:"center",x:s+a/2,y:l+n/2};return"left"===t?(h.align="right",h.x=s-r-i):"right"===t?(h.align="left",h.x=s+a+r+i):"top"===t?h.y=l-i-e/2:"bottom"===t&&(h.y=l+n+i+e/2),h}(d,e,p);t.textAlign=y.align,t.textBaseline="middle";const x=u+2*c,g=p+2*c,m=function(t,o,e,r,n){return"left"===t?o-n:"right"===t?o-e-n:o-r/2}(y.align,y.x,u,x,c),w=1===r.length?y.y:y.y-p/2+h,b=w+(r.length-1)*f/2;void 0!==n&&a(t,n,m,b-g/2,x,g,i),t.fillStyle=s;for(let o=0;o<r.length;o++)t.fillText(r[o],y.x,w+o*f)}const l=(t,o)=>o.flow===t.flow?t.index-o.index:o.flow-t.flow;function h(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],a=i.get(e)??{from:[],in:0,key:e,out:0,size:0,to:[]},s=(e===r?a:i.get(r))??{from:[],in:0,key:r,out:0,size:0,to:[]};a.out+=n,a.to.push({addY:0,flow:n,index:o,key:r,node:s}),1===a.to.length&&i.set(e,a),s.in+=n,s.from.push({addY:0,flow:n,index:o,key:e,node:a}),1===s.from.length&&i.set(r,s)}return((t,o)=>{const e=n(o);for(const o of t.values())o.from.sort(l),o.to.sort(l),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 c=1e-6;function d(t){return t.x??0}function f(t){return t.y??0}const u=(t,o=new Set)=>{const e=[];for(const r of t)o.has(r.key)||(o.add(r.key),e.push(r.key,...u(r.to.map(t=>t.node),o)));return e},p=(t,o)=>{const e=o.filter(t=>0===t.from.length),r=e.map(t=>t.key),n=u(e),i=new Set(n);for(const o of t)i.has(o.from)||i.has(o.to)||(r.push(o.from),i.add(o.from)),i.add(o.to);return r},y=(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 x=-1;function g(t,o,e=function(){return x=x<100?x+1:0,x}()){let r=0;for(const n of t)n.node._visited!==e&&(n.node._visited=e,r+=n.node[o].length+g(n.node[o],o,e));return r}const m=t=>(o,e)=>g(o.node[t],t)-g(e.node[t],t)||o.node[t].length-e.node[t].length;function w(t,o){if(!t.from.length)return o;t.from.sort(m("from"));for(const e of t.from){const t=e.node;r(t.y)||(t.y=o,w(t,o?o+c:0)),o=Math.max(t.y+t.out,o)}return f(t)+t.size}const b=(t,o)=>Boolean(o&&d(o)<d(t));function M(t,o){if(!t.to.length)return o;t.to.sort(m("to"));for(let e=0;e<t.to.length;e++){const n=t.to[e],i=n.node;r(i.y)||(i.y=o,M(i,o?o+c:0)),b(i,t.to[e+1]?.node)?o+=n.flow:o=Math.max(i.y+Math.max(i.in,i.out),o)}return f(t)+t.size}function v(t,o){return r(t.y)?t.y:(t.y=o,o)}function k(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),i=n[0];if(1===n.length)return i;if(n.sort((t,o)=>d(t)-d(o)),0===d(i))return i;const a=n[n.length-1];return d(a)===o?a:n[Math.floor(n.length/2)]})(t,o);return e.y=0,w(e,0),M(e,0),function(t,o){const e=t.filter(t=>0===t.x),n=t.filter(t=>t.x===o),i=e.filter(t=>!r(t.y)),a=n.filter(t=>!r(t.y)),s=t.filter(t=>d(t)>0&&d(t)<o&&!r(t.y));let l=e.reduce((t,o)=>Math.max(t,f(o)+o.out||0),0)+c,h=n.reduce((t,o)=>Math.max(t,f(o)+o.in||0),0)+c,u=0;l>=h?(i.forEach(t=>{l=v(t,l),l=Math.max(l+t.out,M(t,l))}),a.forEach(t=>{h=v(t,h),h=Math.max(h+t.in,w(t,h))})):(i.forEach(t=>{l=v(t,l)}),a.forEach(t=>{h=v(t,h),h=Math.max(h+t.in,w(t,h))})),s.forEach(o=>{let e=t.filter(t=>d(t)===d(o)&&r(t.y)).reduce((t,o)=>Math.max(t,f(o)+Math.max(o.in,o.out)),0);e=v(o,e),e=Math.max(e+o.in,w(o,e)),e=Math.max(e+o.out,M(o,e)),u=Math.max(u,e)}),Math.max(l,h,u)}(t,o),((t,o)=>{let e=0;for(let r=0;r<=o;r++){const o=t.filter(t=>d(t)===r).sort((t,o)=>f(t)-f(o));let n=0;for(const t of o)f(t)<n&&(t.y=n),n=f(t)+t.size;e=Math.max(e,n)}return e})(t,o)}const _=(t,o)=>d(t)!==d(o)?d(t)-d(o):f(t)===f(o)?t.size-o.size:f(t)-f(o);function C(t,o,{priority:e,height:n,nodePadding:i,modeX:a}){const s=[...t.values()],l=function(t,o,e){const n=o.filter(t=>t.from!==t.to),i=[...t.keys()],a=[...t.values()],s=new Set(i);let l=0;for(;s.size;){const e=0===l?p(o,a):y(n,s);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),s.delete(o)}s.size&&l++}const h=a.reduce((t,o)=>Math.max(t,d(o)),0);if("edge"===e){const e=new Set(o.map(t=>t.from));i.filter(t=>!e.has(t)).forEach(o=>{const e=t.get(o);e&&!e.column&&(e.x=h)})}return h}(t,o,a??"edge"),h=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const i=t.filter(t=>d(t)===n).sort((t,o)=>(t.priority??0)-(o.priority??0));if(i.length){const o=t.reduce((t,o)=>d(o)>n?Math.min(t,d(o)):t,1/0);r=i[0].to.filter(t=>d(t.node)>o).reduce((t,o)=>t+o.flow,0)||0}for(const t of i)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(s,l):k(s,l),c=function(t,o){let e=0;const r=new Map,n=[],i=t=>{if(!r.has(t)){const o=n.length;return r.set(t,o),n.push([]),o}return r.get(t)??0};t.sort(_);for(const r of t){const t=i(d(r)),a=n[t]??[];if(f(r)){a.push(f(r));let e=a.length;if(r.in){for(let o=0;o<t;o++){const t=n[o]??[];for(let o=0;o<t.length&&!(t[o]>f(r));o++)e=Math.max(o+1,e)}for(;a.length<e;)a.push(f(r))}r.y=f(r)+e*o}e=Math.max(e,f(r)+Math.max(r.in,r.out))}return e}(s,h/n*i);return function(t){t.forEach(t=>{const o=t.size,e=o<t.in,r=o<t.out;let n=0,i=t.from.length;t.from.sort((t,o)=>f(t.node)+t.node.out/2-(f(o.node)+o.node.out/2)).forEach((t,r)=>{e?t.addY=r*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)}),n=0,i=t.to.length,t.to.sort((t,o)=>f(t.node)+t.node.in/2-(f(o.node)+o.node.in/2)).forEach((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)})})}(s),{maxX:l,maxY:c}}function P(t){return t.x??0}function T(t){return t.y??0}function S(t,o){return Math[o](t.in||t.out,t.out||t.in)}function z(t,o,e,r){return"vertical"===r?o<(e.top+e.bottom)/2?"bottom":"top":t<(e.left+e.right)/2?"right":"left"}function F(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}function E(t,o,e,r,n,i,a,s,l){return"vertical"===l?{_custom:{flow:n,from:t,height:a.parse(n,i),to:o,x:a.parse(r,i),y:s.parse(P(o),i)},x:a.parse(e,i),y:s.parse(P(t),i)}:{_custom:{flow:n,from:t,height:s.parse(n,i),to:o,x:a.parse(P(o),i),y:s.parse(r,i)},x:a.parse(P(t),i),y:s.parse(e,i)}}function R(t,o,e,r,n,i,a,s){const l=t._custom,h=o.getPixelForValue(t.x),c=e.getPixelForValue(t.y);return"vertical"===s?{flow:l.flow,from:l.from,height:0,to:l.to,width:Math.abs(o.getPixelForValue(t.x+l.height)-h),x:h,x2:o.getPixelForValue(l.x),y:W(e,t.y,r,i)+n+a,y2:W(e,l.y,r,i)-a}:{flow:l.flow,from:l.from,height:Math.abs(e.getPixelForValue(t.y+l.height)-c),to:l.to,width:0,x:W(o,t.x,r,i)+n+a,x2:W(o,l.x,r,i)-a,y:c,y2:e.getPixelForValue(l.y)}}function W(t,o,e,r){const n=t.getPixelForValue(o);return e?n-o/e*r:n}function X(t,o,e){const r="vertical"===o?e.height-e.chartArea.bottom:e.width-e.chartArea.right;return Math.max(0,t+3-r)}function Y(t,o,e,r,n,i,a,s){if("vertical"===s){const s=e.getPixelForValue(T(t));return{height:i,width:Math.abs(e.getPixelForValue(T(t)+o)-s),x:s,y:W(r,P(t),n,a)}}const l=r.getPixelForValue(T(t));return{height:Math.abs(r.getPixelForValue(T(t)+o)-l),width:i,x:W(e,P(t),n,a),y:l}}function j(t,o){const{backgroundColor:e,borderRadius:r=0,color:n,display:a,font:s,padding:l=4,position:h}=t.nodeLabels??{};return{backgroundColor:i(e,o),borderRadius:r,color:i(n,o)??t.color??"black",display:i(a,o)??!0,font:s,padding:l,position:i(h,o)??"auto"}}class O 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]:i})=>({flow:i,from:t,to:o}))})(o,this.options.parsing),{xScale:i,yScale:a}=t,s=[],l=h(n,this.options),c=this.options.orientation??"horizontal";this._nodes=l;const{maxX:d,maxY:f}=C(l,n,{height:"vertical"===c?this.chart.canvas.width:this.chart.canvas.height,modeX:this.options.modeX,nodePadding:this.options.nodePadding??10,priority:!!this.options.priority});if(this._maxX=d,this._maxY=f,!i||!a)return[];for(let t=0,o=n.length;t<o;++t){const o=n[t],e=l.get(o.from),r=l.get(o.to);if(!e||!r)continue;const h=T(e)+F(e.to,o.to,t),d=T(r)+F(r.from,o.from,t);s.push(E(e,r,h,d,o.flow,t,i,a,c))}return s.slice(e,e+r)}getMinMax(t){return{max:t===("vertical"===this.options.orientation?this._cachedMeta.yScale: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:i}=this._cachedMeta;if(!n||!i)return;const a=this.resolveDataElementOptions(o,r),s=this.getSharedOptions(a),{borderWidth:l,nodeWidth:h=10,orientation:c="horizontal"}=this.options,d=X(h,c,this.chart),f=l?l/2+.5:0;for(let a=o;a<o+e;a++){const o=this.getParsed(a);this.updateElement(t[a],a,{options:this.resolveDataElementOptions(a,r),progress:"reset"===r?0:1,...R(o,n,i,this._maxX,h,d,f,c)},r)}s&&this.updateSharedOptions(s,r,a)}_drawLabels(){const t=this.chart.ctx,r=this.options,i=this._nodes||new Map,a=n(r.size),l=r.labels,{borderWidth:h=1,nodeWidth:c=10,orientation:d="horizontal"}=r,f=X(c,d,this.chart),u=r.font??this.chart.options.font??o.Chart.defaults.font,{xScale:p,yScale:y}=this._cachedMeta;if(!p||!y)return;t.save();const x=this.chart.chartArea;for(const o of i.values()){const n=S(o,a),{height:i,width:g,x:m,y:w}=Y(o,n,p,y,this._maxX,c,f,d),b=l?.[o.key]??o.key,M=j(r,o);if(M.display){const o=e.toFont(M.font??u);s(t,b,{autoPosition:z(m,w,x,d),backgroundColor:M.backgroundColor,borderRadius:M.borderRadius,borderWidth:h,color:M.color,font:o,height:i,lineOffset:e.valueOrDefault(r.padding,o.lineHeight/2),padding:M.padding,position:M.position,width:g,x:m,y:w})}}t.restore()}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:r=0,nodeWidth:i=10,orientation:a="horizontal",size:s}=this.options,l=X(i,a,this.chart),h=n(s),{xScale:c,yScale:d}=this._cachedMeta;t.save(),e&&r&&(t.strokeStyle=e,t.lineWidth=r);for(const e of o.values()){if(t.fillStyle=e.color??"black",!c||!d)return;const o=Math[h](e.in||e.out,e.out||e.in),{height:n,width:s,x:f,y:u}=Y(e,o,c,d,this._maxX,i,l,a);r&&t.strokeRect(f,u,s,n),t.fillRect(f,u,s,n)}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}}O.id="sankey",O.descriptors={_indexable:!1,_scriptable:!0,nodeLabels:{_indexable:!1,_scriptable:!1}},O.defaults={animations:{colors:{properties:["colorFrom","colorTo"],type:"color"},numbers:{properties:["x","y","x2","y2","height","width"],type:"number"},progress:{delay:t=>"data"===t.type?500*t.parsed["vertical"===t.dataset.orientation?"y":"x"]+20*t.dataIndex:void 0,duration:t=>"data"===t.type?200*(t.parsed._custom["vertical"===t.dataset.orientation?"y":"x"]-t.parsed["vertical"===t.dataset.orientation?"y":"x"]):void 0,easing:"linear"}},borderColor:"black",borderWidth:1,color:"black",dataElementType:"flow",modeX:"edge",nodePadding:10,nodeWidth:10,orientation:"horizontal",transitions:{hide:{animations:{colors:{properties:["colorFrom","colorTo"],to:"transparent",type:"color"}}},show:{animations:{colors:{from:"transparent",properties:["colorFrom","colorTo"],type:"color"}}}}},O.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 V=(t,o,e,r,n)=>"vertical"===n?o<r?{cp1:{x:t,y:o+(r-o)/3*2},cp2:{x:e,y:o+(r-o)/3}}:{cp1:{x:0,y:o-(o-r)/3},cp2:{x:0,y:r+(o-r)/3}}: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}},L=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),A=(t,o)=>e.color(t).alpha(o).rgbString(),q=(t,o)=>"string"==typeof t?A(t,o):t,D=t=>"string"==typeof t?e.getHoverColor(t):t;class $ extends o.Element{draw(t){const{x:r,x2:n,y:i,y2:a,height:l,progress:h,width:c}=this,d=this.options.orientation,f=V(r,i,n,a,d),u={height:l,width:c,x:r,x2:n,y:i,y2:a};if(0===h)return;t.save(),h<1&&function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},s,l){t.beginPath(),"vertical"===l?t.rect(Math.min(r,n),i,Math.abs(n-r)+e+1,(a-i)*s+1):t.rect(r,Math.min(i,a),(n-r)*s+1,Math.abs(a-i)+o+1),t.clip()}(t,u,h,d),function(t,{x:o,x2:e,y:r,y2:n,options:i}){let a="black";null!==i.flowColor?a=i.flowColor:"from"===i.colorMode?a=q(i.colorFrom,i.alpha):"to"===i.colorMode?a=q(i.colorTo,i.alpha):"string"==typeof i.colorFrom&&"string"==typeof i.colorTo&&(a="vertical"===i.orientation?t.createLinearGradient(0,r,0,n):t.createLinearGradient(o,0,e,0),a.addColorStop(0,A(i.colorFrom,i.alpha)),a.addColorStop(1,A(i.colorTo,i.alpha))),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},{cp1:s,cp2:l},h){t.beginPath(),t.moveTo(r,i),t.bezierCurveTo(s.x,s.y,l.x,l.y,n,a),"vertical"===h?(t.lineTo(n+e,a),t.bezierCurveTo(l.x+e,l.y,s.x+e,s.y,r+e,i)):(t.lineTo(n,a+o),t.bezierCurveTo(l.x,l.y+o,s.x,s.y+o,r,i+o)),t.lineTo(r,i),t.stroke(),t.closePath(),t.fill()}(t,u,f,d);const p=this.options.flowLabels;if(p.display){const r=e.toFont(p.font??o.Chart.defaults.font),n=function({height:t,width:o,x:e,x2:r,y:n,y2:i},a){return"vertical"===a?{height:i-n,width:Math.abs(r-e)+o,x:Math.min(e,r),y:n}:{height:Math.abs(i-n)+t,width:r-e,x:e,y:Math.min(n,i)}}(u,d);s(t,`${this.flow}`,{autoPosition:"center",backgroundColor:p.backgroundColor,borderRadius:p.borderRadius,borderWidth:0,color:p.color,font:r,height:n.height,lineOffset:r.lineHeight/2,padding:p.padding,position:p.position,width:n.width,x:n.x,y:n.y})}t.restore()}inRange(t,o,e){const{x:r,y:n,x2:i,y2:a,height:s,width:l}=this.getProps(["x","y","x2","y2","height","width"],e),h="vertical"===this.options.orientation;if(h?o<n||o>a:t<r||t>i)return!1;const{cp1:c,cp2:d}=V(r,n,i,a,this.options.orientation),f=h?(o-n)/(a-n):(t-r)/(i-r),u={x:i,y:a},p=L({x:r,y:n},c,f),y=L(c,d,f),x=L(d,u,f),g=L(p,y,f),m=L(y,x,f),w=L(g,m,f);return h?t>=w.x&&t<=w.x+l:o>=w.y&&o<=w.y+s}inXRange(t,o){const{x:e,x2:r,width:n}=this.getProps(["x","x2","width"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?n:0);return t>=i&&t<=a}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?0:n);return t>=i&&t<=a}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:i,width:a}=this.getProps(["x","y","x2","y2","height","width"],t),s="vertical"===this.options.orientation;return{x:(o+r+(s?a:0))/2,y:(e+n+(s?0:i))/2}}tooltipPosition(t=!1){return this.getCenterPoint(t)}getRange(t){const o="vertical"===this.options.orientation;return"x"===t?o?this.width/2:0:o?0:this.height/2}constructor(t){super(),this.flow=0,this.x2=0,this.y2=0,this.width=0,this.height=0,this.progress=1,t&&Object.assign(this,t)}}$.id="flow",$.defaults={alpha:.5,colorFrom:"red",colorMode:"gradient",colorTo:"green",flowColor:null,flowLabels:{borderRadius:0,color:"black",display:!1,padding:4,position:"center"},hoverColorFrom:(t,o)=>D(o.colorFrom),hoverColorTo:(t,o)=>D(o.colorTo),orientation:"horizontal"},$.descriptors={_scriptable:!0,flowLabels:{_scriptable:!0}},o.Chart.register(O,$),t.Flow=$,t.SankeyController=O});
|
|
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"}function i(t,o){return"function"==typeof t?t(o):t&&"object"==typeof t&&!function(t){const o=Object.prototype.toString.call(t);return"[object CanvasPattern]"===o||"[object CanvasGradient]"===o}(t)?t[o.key]:t}function a(t,o,e,r,n,i,a){t.save(),t.fillStyle=o,a>0?(!function(t,o,e,r,n,i){const a=Math.max(0,Math.min(i,r/2,n/2));t.beginPath(),t.moveTo(o+a,e),t.lineTo(o+r-a,e),t.quadraticCurveTo(o+r,e,o+r,e+a),t.lineTo(o+r,e+n-a),t.quadraticCurveTo(o+r,e+n,o+r-a,e+n),t.lineTo(o+a,e+n),t.quadraticCurveTo(o,e+n,o,e+n-a),t.lineTo(o,e+a),t.quadraticCurveTo(o,e,o+a,e),t.closePath()}(t,e,r,n,i,a),t.fill()):t.fillRect(e,r,n,i),t.restore()}function s(t,o,e){const r=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}(o);if(!r.length)return;const{backgroundColor:n,borderRadius:i,color:s,font:l,lineOffset:h,padding:c}=e,d=function(t,o){return"auto"===t?o:t}(e.position,e.autoPosition),f=Number(l.lineHeight);t.font=l.string;const u=Math.max(...r.map(o=>t.measureText(o).width)),p=f*r.length,y=function(t,o,e){const{borderWidth:r,height:n,padding:i,width:a,x:s,y:l}=o,h={align:"center",x:s+a/2,y:l+n/2};return"left"===t?(h.align="right",h.x=s-r-i):"right"===t?(h.align="left",h.x=s+a+r+i):"top"===t?h.y=l-i-e/2:"bottom"===t&&(h.y=l+n+i+e/2),h}(d,e,p);t.textAlign=y.align,t.textBaseline="middle";const g=u+2*c,x=p+2*c,m=function(t,o,e,r,n){return"left"===t?o-n:"right"===t?o-e-n:o-r/2}(y.align,y.x,u,g,c),w=1===r.length?y.y:y.y-p/2+h,b=w+(r.length-1)*f/2;void 0!==n&&a(t,n,m,b-x/2,g,x,i),t.fillStyle=s;for(let o=0;o<r.length;o++)t.fillText(r[o],y.x,w+o*f)}const l=(t,o)=>o.flow===t.flow?t.index-o.index:o.flow-t.flow;function h(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],a=i.get(e)??{from:[],in:0,key:e,out:0,size:0,to:[]},s=(e===r?a:i.get(r))??{from:[],in:0,key:r,out:0,size:0,to:[]};a.out+=n,a.to.push({addY:0,flow:n,index:o,key:r,node:s}),1===a.to.length&&i.set(e,a),s.in+=n,s.from.push({addY:0,flow:n,index:o,key:e,node:a}),1===s.from.length&&i.set(r,s)}return((t,o)=>{const e=n(o);for(const o of t.values())o.from.sort(l),o.to.sort(l),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 c=1e-6;function d(t){return t.x??0}function f(t){return t.y??0}const u=(t,o=new Set)=>{const e=[];for(const r of t)o.has(r.key)||(o.add(r.key),e.push(r.key,...u(r.to.map(t=>t.node),o)));return e},p=(t,o)=>{const e=o.filter(t=>0===t.from.length),r=e.map(t=>t.key),n=u(e),i=new Set(n);for(const o of t)i.has(o.from)||i.has(o.to)||(r.push(o.from),i.add(o.from)),i.add(o.to);return r},y=(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 g=-1;function x(t,o,e=function(){return g=g<100?g+1:0,g}()){let r=0;for(const n of t)n.node._visited!==e&&(n.node._visited=e,r+=n.node[o].length+x(n.node[o],o,e));return r}const m=t=>(o,e)=>x(o.node[t],t)-x(e.node[t],t)||o.node[t].length-e.node[t].length;function w(t,o){if(!t.from.length)return o;t.from.sort(m("from"));for(const e of t.from){const t=e.node;r(t.y)||(t.y=o,w(t,o?o+c:0)),o=Math.max(t.y+t.out,o)}return f(t)+t.size}const b=(t,o)=>Boolean(o&&d(o)<d(t));function M(t,o){if(!t.to.length)return o;t.to.sort(m("to"));for(let e=0;e<t.to.length;e++){const n=t.to[e],i=n.node;r(i.y)||(i.y=o,M(i,o?o+c:0)),b(i,t.to[e+1]?.node)?o+=n.flow:o=Math.max(i.y+Math.max(i.in,i.out),o)}return f(t)+t.size}function v(t,o){return r(t.y)?t.y:(t.y=o,o)}function k(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),i=n[0];if(1===n.length)return i;if(n.sort((t,o)=>d(t)-d(o)),0===d(i))return i;const a=n[n.length-1];return d(a)===o?a:n[Math.floor(n.length/2)]})(t,o);return e.y=0,w(e,0),M(e,0),function(t,o){const e=t.filter(t=>0===t.x),n=t.filter(t=>t.x===o),i=e.filter(t=>!r(t.y)),a=n.filter(t=>!r(t.y)),s=t.filter(t=>d(t)>0&&d(t)<o&&!r(t.y));let l=e.reduce((t,o)=>Math.max(t,f(o)+o.out||0),0)+c,h=n.reduce((t,o)=>Math.max(t,f(o)+o.in||0),0)+c,u=0;l>=h?(i.forEach(t=>{l=v(t,l),l=Math.max(l+t.out,M(t,l))}),a.forEach(t=>{h=v(t,h),h=Math.max(h+t.in,w(t,h))})):(i.forEach(t=>{l=v(t,l)}),a.forEach(t=>{h=v(t,h),h=Math.max(h+t.in,w(t,h))})),s.forEach(o=>{let e=t.filter(t=>d(t)===d(o)&&r(t.y)).reduce((t,o)=>Math.max(t,f(o)+Math.max(o.in,o.out)),0);e=v(o,e),e=Math.max(e+o.in,w(o,e)),e=Math.max(e+o.out,M(o,e)),u=Math.max(u,e)}),Math.max(l,h,u)}(t,o),((t,o)=>{let e=0;for(let r=0;r<=o;r++){const o=t.filter(t=>d(t)===r).sort((t,o)=>f(t)-f(o));let n=0;for(const t of o)f(t)<n&&(t.y=n),n=f(t)+t.size;e=Math.max(e,n)}return e})(t,o)}const _=(t,o)=>d(t)!==d(o)?d(t)-d(o):f(t)===f(o)?t.size-o.size:f(t)-f(o);function C(t,o,{priority:e,height:n,nodePadding:i,modeX:a}){const s=[...t.values()],l=function(t,o,e){const n=o.filter(t=>t.from!==t.to),i=[...t.keys()],a=[...t.values()],s=new Set(i);let l=0;for(;s.size;){const e=0===l?p(o,a):y(n,s);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),s.delete(o)}s.size&&l++}const h=a.reduce((t,o)=>Math.max(t,d(o)),0);if("edge"===e){const e=new Set(o.map(t=>t.from));i.filter(t=>!e.has(t)).forEach(o=>{const e=t.get(o);e&&!e.column&&(e.x=h)})}return h}(t,o,a??"edge"),h=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const i=t.filter(t=>d(t)===n).sort((t,o)=>(t.priority??0)-(o.priority??0));if(i.length){const o=t.reduce((t,o)=>d(o)>n?Math.min(t,d(o)):t,1/0);r=i[0].to.filter(t=>d(t.node)>o).reduce((t,o)=>t+o.flow,0)||0}for(const t of i)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(s,l):k(s,l),c=function(t,o){let e=0;const r=new Map,n=[],i=t=>{if(!r.has(t)){const o=n.length;return r.set(t,o),n.push([]),o}return r.get(t)??0};t.sort(_);for(const r of t){const t=i(d(r)),a=n[t]??[];if(f(r)){a.push(f(r));let e=a.length;if(r.in){for(let o=0;o<t;o++){const t=n[o]??[];for(let o=0;o<t.length&&!(t[o]>f(r));o++)e=Math.max(o+1,e)}for(;a.length<e;)a.push(f(r))}r.y=f(r)+e*o}e=Math.max(e,f(r)+Math.max(r.in,r.out))}return e}(s,h/n*i);return function(t){t.forEach(t=>{const o=t.size,e=o<t.in,r=o<t.out;let n=0,i=t.from.length;t.from.sort((t,o)=>f(t.node)+t.node.out/2-(f(o.node)+o.node.out/2)).forEach((t,r)=>{e?t.addY=r*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)}),n=0,i=t.to.length,t.to.sort((t,o)=>f(t.node)+t.node.in/2-(f(o.node)+o.node.in/2)).forEach((t,e)=>{r?t.addY=e*(o-t.flow)/(i-1):(t.addY=n,n+=t.flow)})})}(s),{maxX:l,maxY:c}}function P(t){return t.x??0}function T(t){return t.y??0}function z(t,o){return Math[o](t.in||t.out,t.out||t.in)}function S(t,o,e,r){return"vertical"===r?o<(e.top+e.bottom)/2?"bottom":"top":t<(e.left+e.right)/2?"right":"left"}function F(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}function E(t,o,e,r,n,i,a,s,l){return"vertical"===l?{_custom:{flow:n,from:t,height:a.parse(n,i),to:o,x:a.parse(r,i),y:s.parse(P(o),i)},x:a.parse(e,i),y:s.parse(P(t),i)}:{_custom:{flow:n,from:t,height:s.parse(n,i),to:o,x:a.parse(P(o),i),y:s.parse(r,i)},x:a.parse(P(t),i),y:s.parse(e,i)}}function R(t,o,e,r,n,i,a,s){const l=t._custom,h=o.getPixelForValue(t.x),c=e.getPixelForValue(t.y);return"vertical"===s?{flow:l.flow,from:l.from,height:0,to:l.to,width:Math.abs(o.getPixelForValue(t.x+l.height)-h),x:h,x2:o.getPixelForValue(l.x),y:W(e,t.y,r,i)+n+a,y2:W(e,l.y,r,i)-a}:{flow:l.flow,from:l.from,height:Math.abs(e.getPixelForValue(t.y+l.height)-c),to:l.to,width:0,x:W(o,t.x,r,i)+n+a,x2:W(o,l.x,r,i)-a,y:c,y2:e.getPixelForValue(l.y)}}function W(t,o,e,r){const n=t.getPixelForValue(o);return e?n-o/e*r:n}function X(t,o,e){const r="vertical"===o?e.height-e.chartArea.bottom:e.width-e.chartArea.right;return Math.max(0,t+3-r)}function Y(t,o,e,r,n,i,a,s){if("vertical"===s){const s=e.getPixelForValue(T(t));return{height:i,width:Math.abs(e.getPixelForValue(T(t)+o)-s),x:s,y:W(r,P(t),n,a)}}const l=r.getPixelForValue(T(t));return{height:Math.abs(r.getPixelForValue(T(t)+o)-l),width:i,x:W(e,P(t),n,a),y:l}}function j(t,o){const{backgroundColor:e,borderRadius:r=0,color:n,display:a,font:s,padding:l=4,position:h}=t.nodeLabels??{};return{backgroundColor:i(e,o),borderRadius:r,color:i(n,o)??t.color??"black",display:i(a,o)??!0,font:s,padding:l,position:i(h,o)??"auto"}}class O 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]:i})=>({flow:i,from:t,to:o}))})(o,this.options.parsing),{xScale:i,yScale:a}=t,s=[],l=h(n,this.options),c=this.options.orientation??"horizontal";this._nodes=l;const{maxX:d,maxY:f}=C(l,n,{height:"vertical"===c?this.chart.canvas.width:this.chart.canvas.height,modeX:this.options.modeX,nodePadding:this.options.nodePadding??10,priority:!!this.options.priority});if(this._maxX=d,this._maxY=f,!i||!a)return[];for(let t=0,o=n.length;t<o;++t){const o=n[t],e=l.get(o.from),r=l.get(o.to);if(!e||!r)continue;const h=T(e)+F(e.to,o.to,t),d=T(r)+F(r.from,o.from,t);s.push(E(e,r,h,d,o.flow,t,i,a,c))}return s.slice(e,e+r)}getMinMax(t){return{max:t===("vertical"===this.options.orientation?this._cachedMeta.yScale: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:i}=this._cachedMeta;if(!n||!i)return;const a=this.resolveDataElementOptions(o,r),s=this.getSharedOptions(a),{borderWidth:l,nodeWidth:h=10,orientation:c="horizontal"}=this.options,d=X(h,c,this.chart),f=l?l/2+.5:0;for(let a=o;a<o+e;a++){const o=this.getParsed(a);this.updateElement(t[a],a,{options:this.resolveDataElementOptions(a,r),progress:"reset"===r?0:1,...R(o,n,i,this._maxX,h,d,f,c)},r)}s&&this.updateSharedOptions(s,r,a)}_drawLabels(){const t=this.chart.ctx,r=this.options,i=this._nodes||new Map,a=n(r.size),l=r.labels,{borderWidth:h=1,nodeWidth:c=10,orientation:d="horizontal"}=r,f=X(c,d,this.chart),u=r.font??this.chart.options.font??o.Chart.defaults.font,{xScale:p,yScale:y}=this._cachedMeta;if(!p||!y)return;t.save();const g=this.chart.chartArea;for(const o of i.values()){const n=z(o,a),{height:i,width:x,x:m,y:w}=Y(o,n,p,y,this._maxX,c,f,d),b=l?.[o.key]??o.key,M=j(r,o);if(M.display){const o=e.toFont(M.font??u);s(t,b,{autoPosition:S(m,w,g,d),backgroundColor:M.backgroundColor,borderRadius:M.borderRadius,borderWidth:h,color:M.color,font:o,height:i,lineOffset:e.valueOrDefault(r.padding,o.lineHeight/2),padding:M.padding,position:M.position,width:x,x:m,y:w})}}t.restore()}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:r=0,nodeWidth:i=10,orientation:a="horizontal",size:s}=this.options,l=X(i,a,this.chart),h=n(s),{xScale:c,yScale:d}=this._cachedMeta;t.save(),e&&r&&(t.strokeStyle=e,t.lineWidth=r);for(const e of o.values()){if(t.fillStyle=e.color??"black",!c||!d)return;const o=Math[h](e.in||e.out,e.out||e.in),{height:n,width:s,x:f,y:u}=Y(e,o,c,d,this._maxX,i,l,a);r&&t.strokeRect(f,u,s,n),t.fillRect(f,u,s,n)}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}}O.id="sankey",O.descriptors={_indexable:!1,_scriptable:!0,nodeLabels:{_indexable:!1,_scriptable:!1}},O.defaults={animations:{colors:{properties:["colorFrom","colorTo"],type:"color"},numbers:{properties:["x","y","x2","y2","height","width"],type:"number"},progress:{delay:t=>"data"===t.type?500*t.parsed["vertical"===t.dataset.orientation?"y":"x"]+20*t.dataIndex:void 0,duration:t=>"data"===t.type?200*(t.parsed._custom["vertical"===t.dataset.orientation?"y":"x"]-t.parsed["vertical"===t.dataset.orientation?"y":"x"]):void 0,easing:"linear"}},borderColor:"black",borderWidth:1,color:"black",dataElementType:"flow",modeX:"edge",nodePadding:10,nodeWidth:10,orientation:"horizontal",transitions:{hide:{animations:{colors:{properties:["colorFrom","colorTo"],to:"transparent",type:"color"}}},resize:{animations:{progress:{delay:0,duration:0}}},show:{animations:{colors:{from:"transparent",properties:["colorFrom","colorTo"],type:"color"}}}}},O.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 V=(t,o,e,r,n)=>"vertical"===n?o<r?{cp1:{x:t,y:o+(r-o)/3*2},cp2:{x:e,y:o+(r-o)/3}}:{cp1:{x:0,y:o-(o-r)/3},cp2:{x:0,y:r+(o-r)/3}}: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}},L=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),A=(t,o)=>e.color(t).alpha(o).rgbString(),q=(t,o)=>"string"==typeof t?A(t,o):t,D=t=>"string"==typeof t?e.getHoverColor(t):t;class $ extends o.Element{draw(t){const{x:r,x2:n,y:i,y2:a,height:l,progress:h,width:c}=this,d=this.options.orientation,f=V(r,i,n,a,d),u={height:l,width:c,x:r,x2:n,y:i,y2:a};if(0===h)return;t.save(),h<1&&function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},s,l){t.beginPath(),"vertical"===l?t.rect(Math.min(r,n),i,Math.abs(n-r)+e+1,(a-i)*s+1):t.rect(r,Math.min(i,a),(n-r)*s+1,Math.abs(a-i)+o+1),t.clip()}(t,u,h,d),function(t,{x:o,x2:e,y:r,y2:n,options:i}){let a="black";null!==i.flowColor?a=i.flowColor:"from"===i.colorMode?a=q(i.colorFrom,i.alpha):"to"===i.colorMode?a=q(i.colorTo,i.alpha):"string"==typeof i.colorFrom&&"string"==typeof i.colorTo&&(a="vertical"===i.orientation?t.createLinearGradient(0,r,0,n):t.createLinearGradient(o,0,e,0),a.addColorStop(0,A(i.colorFrom,i.alpha)),a.addColorStop(1,A(i.colorTo,i.alpha))),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),function(t,{height:o,width:e,x:r,x2:n,y:i,y2:a},{cp1:s,cp2:l},h){t.beginPath(),t.moveTo(r,i),t.bezierCurveTo(s.x,s.y,l.x,l.y,n,a),"vertical"===h?(t.lineTo(n+e,a),t.bezierCurveTo(l.x+e,l.y,s.x+e,s.y,r+e,i)):(t.lineTo(n,a+o),t.bezierCurveTo(l.x,l.y+o,s.x,s.y+o,r,i+o)),t.lineTo(r,i),t.stroke(),t.closePath(),t.fill()}(t,u,f,d);const p=this.options.flowLabels;if(p.display){const r=e.toFont(p.font??o.Chart.defaults.font),n=function({height:t,width:o,x:e,x2:r,y:n,y2:i},a){return"vertical"===a?{height:i-n,width:Math.abs(r-e)+o,x:Math.min(e,r),y:n}:{height:Math.abs(i-n)+t,width:r-e,x:e,y:Math.min(n,i)}}(u,d);s(t,`${this.flow}`,{autoPosition:"center",backgroundColor:p.backgroundColor,borderRadius:p.borderRadius,borderWidth:0,color:p.color,font:r,height:n.height,lineOffset:r.lineHeight/2,padding:p.padding,position:p.position,width:n.width,x:n.x,y:n.y})}t.restore()}inRange(t,o,e){const{x:r,y:n,x2:i,y2:a,height:s,width:l}=this.getProps(["x","y","x2","y2","height","width"],e),h="vertical"===this.options.orientation;if(h?o<n||o>a:t<r||t>i)return!1;const{cp1:c,cp2:d}=V(r,n,i,a,this.options.orientation),f=h?(o-n)/(a-n):(t-r)/(i-r),u={x:i,y:a},p=L({x:r,y:n},c,f),y=L(c,d,f),g=L(d,u,f),x=L(p,y,f),m=L(y,g,f),w=L(x,m,f);return h?t>=w.x&&t<=w.x+l:o>=w.y&&o<=w.y+s}inXRange(t,o){const{x:e,x2:r,width:n}=this.getProps(["x","x2","width"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?n:0);return t>=i&&t<=a}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),i=Math.min(e,r),a=Math.max(e,r)+("vertical"===this.options.orientation?0:n);return t>=i&&t<=a}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:i,width:a}=this.getProps(["x","y","x2","y2","height","width"],t),s="vertical"===this.options.orientation;return{x:(o+r+(s?a:0))/2,y:(e+n+(s?0:i))/2}}tooltipPosition(t=!1){return this.getCenterPoint(t)}getRange(t){const o="vertical"===this.options.orientation;return"x"===t?o?this.width/2:0:o?0:this.height/2}constructor(t){super(),this.flow=0,this.x2=0,this.y2=0,this.width=0,this.height=0,this.progress=1,t&&Object.assign(this,t)}}$.id="flow",$.defaults={alpha:.5,colorFrom:"red",colorMode:"gradient",colorTo:"green",flowColor:null,flowLabels:{borderRadius:0,color:"black",display:!1,padding:4,position:"center"},hoverColorFrom:(t,o)=>D(o.colorFrom),hoverColorTo:(t,o)=>D(o.colorTo),orientation:"horizontal"},$.descriptors={_scriptable:!0,flowLabels:{_scriptable:!0}},o.Chart.register(O,$),t.Flow=$,t.SankeyController=O});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ChartMeta } from 'chart.js' with { 'resolution-mode': 'import' };
|
|
2
|
+
import type Flow from './flow.cjs';
|
|
3
|
+
import type { AnyObject, SankeyControllerDatasetOptions, SankeyParsedData } from './types.cjs';
|
|
4
|
+
import type { DatasetController } from 'chart.js' with { 'resolution-mode': 'import' };
|
|
5
|
+
export default class SankeyController extends DatasetController {
|
|
6
|
+
static readonly id = "sankey";
|
|
7
|
+
static readonly descriptors: {
|
|
8
|
+
_indexable: boolean;
|
|
9
|
+
_scriptable: boolean;
|
|
10
|
+
nodeLabels: {
|
|
11
|
+
_indexable: boolean;
|
|
12
|
+
_scriptable: boolean;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
static readonly defaults: {
|
|
16
|
+
animations: {
|
|
17
|
+
colors: {
|
|
18
|
+
properties: string[];
|
|
19
|
+
type: string;
|
|
20
|
+
};
|
|
21
|
+
numbers: {
|
|
22
|
+
properties: string[];
|
|
23
|
+
type: string;
|
|
24
|
+
};
|
|
25
|
+
progress: {
|
|
26
|
+
delay: (ctx: any) => number | undefined;
|
|
27
|
+
duration: (ctx: any) => number | undefined;
|
|
28
|
+
easing: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
borderColor: string;
|
|
32
|
+
borderWidth: number;
|
|
33
|
+
color: string;
|
|
34
|
+
dataElementType: string;
|
|
35
|
+
modeX: string;
|
|
36
|
+
nodePadding: number;
|
|
37
|
+
nodeWidth: number;
|
|
38
|
+
orientation: string;
|
|
39
|
+
transitions: {
|
|
40
|
+
hide: {
|
|
41
|
+
animations: {
|
|
42
|
+
colors: {
|
|
43
|
+
properties: string[];
|
|
44
|
+
to: string;
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
resize: {
|
|
50
|
+
animations: {
|
|
51
|
+
progress: {
|
|
52
|
+
delay: number;
|
|
53
|
+
duration: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
show: {
|
|
58
|
+
animations: {
|
|
59
|
+
colors: {
|
|
60
|
+
from: string;
|
|
61
|
+
properties: string[];
|
|
62
|
+
type: string;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
static readonly overrides: {
|
|
69
|
+
datasets: {
|
|
70
|
+
clip: boolean;
|
|
71
|
+
parsing: {
|
|
72
|
+
flow: string;
|
|
73
|
+
from: string;
|
|
74
|
+
to: string;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
interaction: {
|
|
78
|
+
intersect: boolean;
|
|
79
|
+
mode: string;
|
|
80
|
+
};
|
|
81
|
+
layout: {
|
|
82
|
+
padding: {
|
|
83
|
+
bottom: number;
|
|
84
|
+
left: number;
|
|
85
|
+
right: number;
|
|
86
|
+
top: number;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
plugins: {
|
|
90
|
+
legend: {
|
|
91
|
+
display: boolean;
|
|
92
|
+
};
|
|
93
|
+
tooltip: {
|
|
94
|
+
callbacks: {
|
|
95
|
+
label(context: any): string;
|
|
96
|
+
title(): string;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
scales: {
|
|
101
|
+
x: {
|
|
102
|
+
bounds: string;
|
|
103
|
+
display: boolean;
|
|
104
|
+
min: number;
|
|
105
|
+
offset: boolean;
|
|
106
|
+
type: string;
|
|
107
|
+
};
|
|
108
|
+
y: {
|
|
109
|
+
bounds: string;
|
|
110
|
+
display: boolean;
|
|
111
|
+
min: number;
|
|
112
|
+
offset: boolean;
|
|
113
|
+
reverse: boolean;
|
|
114
|
+
type: string;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
options: SankeyControllerDatasetOptions;
|
|
119
|
+
private _nodes;
|
|
120
|
+
private _maxX;
|
|
121
|
+
private _maxY;
|
|
122
|
+
parseObjectData(meta: ChartMeta<'sankey', Flow>, data: AnyObject[], start: number, count: number): SankeyParsedData[];
|
|
123
|
+
getMinMax(scale: any): {
|
|
124
|
+
max: number;
|
|
125
|
+
min: number;
|
|
126
|
+
};
|
|
127
|
+
update(mode: any): void;
|
|
128
|
+
updateElements(elems: Flow[], start: number, count: number, mode: 'default' | 'resize' | 'reset' | 'none' | 'hide' | 'show' | 'active'): void;
|
|
129
|
+
private _drawLabels;
|
|
130
|
+
private _drawNodes;
|
|
131
|
+
/**
|
|
132
|
+
* That's where the drawing process happens
|
|
133
|
+
*/
|
|
134
|
+
draw(): void;
|
|
135
|
+
}
|
package/dist/controller.d.ts
CHANGED
package/dist/flow.d.cts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Color } from 'chart.js' with { 'resolution-mode': 'import' };
|
|
2
|
+
import type { FlowConfig, FlowOptions, FlowProps, SankeyNode } from './types.cjs';
|
|
3
|
+
import type { Element } from 'chart.js' with { 'resolution-mode': 'import' };
|
|
4
|
+
export default class Flow extends Element<FlowProps, FlowOptions> {
|
|
5
|
+
static readonly id = "flow";
|
|
6
|
+
static readonly defaults: {
|
|
7
|
+
alpha: number;
|
|
8
|
+
colorFrom: string;
|
|
9
|
+
colorMode: string;
|
|
10
|
+
colorTo: string;
|
|
11
|
+
flowColor: null;
|
|
12
|
+
flowLabels: {
|
|
13
|
+
borderRadius: number;
|
|
14
|
+
color: string;
|
|
15
|
+
display: boolean;
|
|
16
|
+
padding: number;
|
|
17
|
+
position: string;
|
|
18
|
+
};
|
|
19
|
+
hoverColorFrom: (_ctx: unknown, options: FlowOptions) => Color;
|
|
20
|
+
hoverColorTo: (_ctx: unknown, options: FlowOptions) => Color;
|
|
21
|
+
orientation: string;
|
|
22
|
+
};
|
|
23
|
+
static readonly descriptors: {
|
|
24
|
+
_scriptable: boolean;
|
|
25
|
+
flowLabels: {
|
|
26
|
+
_scriptable: boolean;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
flow: number;
|
|
30
|
+
x2: number;
|
|
31
|
+
y2: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
progress: number;
|
|
35
|
+
from?: SankeyNode;
|
|
36
|
+
to?: SankeyNode;
|
|
37
|
+
constructor(cfg: FlowConfig);
|
|
38
|
+
/**
|
|
39
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
40
|
+
*/
|
|
41
|
+
draw(ctx: CanvasRenderingContext2D): void;
|
|
42
|
+
/**
|
|
43
|
+
* @param {number} mouseX
|
|
44
|
+
* @param {number} mouseY
|
|
45
|
+
* @param {boolean} useFinalPosition
|
|
46
|
+
* @return {boolean}
|
|
47
|
+
*/
|
|
48
|
+
inRange(mouseX: number, mouseY: number, useFinalPosition: boolean): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* @param {number} mouseX
|
|
51
|
+
* @param {boolean} useFinalPosition
|
|
52
|
+
* @return {boolean}
|
|
53
|
+
*/
|
|
54
|
+
inXRange(mouseX: number, useFinalPosition: boolean): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* @param {number} mouseY
|
|
57
|
+
* @param {boolean} useFinalPosition
|
|
58
|
+
* @return {boolean}
|
|
59
|
+
*/
|
|
60
|
+
inYRange(mouseY: number, useFinalPosition: boolean): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* @param {boolean} useFinalPosition
|
|
63
|
+
* @return {{x: number, y:number}}
|
|
64
|
+
*/
|
|
65
|
+
getCenterPoint(useFinalPosition: boolean): {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
};
|
|
69
|
+
tooltipPosition(useFinalPosition?: boolean): {
|
|
70
|
+
x: number;
|
|
71
|
+
y: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* @param {"x" | "y"} axis
|
|
75
|
+
* @return {number}
|
|
76
|
+
*/
|
|
77
|
+
getRange(axis: 'x' | 'y'): number;
|
|
78
|
+
}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import Sankey from './controller.cjs';
|
|
2
|
+
import Flow from './flow.cjs';
|
|
3
|
+
export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.cjs';
|
|
4
|
+
export { Flow, Sankey as SankeyController };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { FlowConfig, FlowOptions, FlowProps, SankeyControllerDatasetFlowLabelsOptions, SankeyControllerDatasetNodeLabelsOptions, SankeyControllerDatasetOptions, SankeyDataPoint, SankeyLabelPosition, SankeyNodeLabelOption, SankeyNodeLabelPosition, SankeyOrientation, SankeyParsedData, SankeyParsingOptions, SankeyScriptableContext, } from './types.cjs';
|
|
2
|
+
export { default as SankeyController } from './controller.cjs';
|
|
3
|
+
export { default as Flow } from './flow.cjs';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CanvasFontSpec, Color } from 'chart.js' with { 'resolution-mode': 'import' };
|
|
2
|
+
import type { SankeyLabelPosition, SankeyNode, SankeyNodeLabelOption } from './types.cjs';
|
|
3
|
+
type ResolvableNodeLabelValue = boolean | Color | SankeyLabelPosition;
|
|
4
|
+
type ResolvedLabelPosition = Exclude<SankeyLabelPosition, 'auto'>;
|
|
5
|
+
export interface DrawLabelOptions {
|
|
6
|
+
autoPosition: ResolvedLabelPosition;
|
|
7
|
+
backgroundColor?: Color;
|
|
8
|
+
borderRadius: number;
|
|
9
|
+
borderWidth: number;
|
|
10
|
+
color: Color;
|
|
11
|
+
font: CanvasFontSpec;
|
|
12
|
+
height: number;
|
|
13
|
+
lineOffset: number;
|
|
14
|
+
padding: number;
|
|
15
|
+
position: SankeyLabelPosition;
|
|
16
|
+
width: number;
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function resolveNodeLabelOption<T extends ResolvableNodeLabelValue>(option: SankeyNodeLabelOption<T> | undefined, node: SankeyNode): T | undefined;
|
|
21
|
+
export declare function drawLabel(ctx: CanvasRenderingContext2D, label: string, options: DrawLabelOptions): void;
|
|
22
|
+
export {};
|
package/dist/types.d.cts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type {} from './types.js' with { 'resolution-mode': 'import' };
|
|
2
|
+
import type { CartesianScaleTypeRegistry, Color, ControllerDatasetOptions, CoreChartOptions, FontSpec, Scriptable, ScriptableAndArray, ScriptableContext } from 'chart.js' with { 'resolution-mode': 'import' };
|
|
3
|
+
export type AnyObject = Record<string, unknown>;
|
|
4
|
+
export interface SankeyDataPoint {
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
flow: number;
|
|
8
|
+
}
|
|
9
|
+
export type SankeyScriptableContext = ScriptableContext<'sankey'> & {
|
|
10
|
+
raw: SankeyDataPoint;
|
|
11
|
+
};
|
|
12
|
+
export type SankeyLabelPosition = 'auto' | 'bottom' | 'center' | 'left' | 'right' | 'top';
|
|
13
|
+
export type SankeyNodeLabelPosition = SankeyLabelPosition;
|
|
14
|
+
export type SankeyOrientation = 'horizontal' | 'vertical';
|
|
15
|
+
export type SankeyNodeLabelOption<T> = T | Record<string, T> | ((node: SankeyNode) => T | undefined);
|
|
16
|
+
export interface SankeyControllerDatasetNodeLabelsOptions {
|
|
17
|
+
backgroundColor?: SankeyNodeLabelOption<Color>;
|
|
18
|
+
borderRadius?: number;
|
|
19
|
+
color?: SankeyNodeLabelOption<Color>;
|
|
20
|
+
display?: SankeyNodeLabelOption<boolean>;
|
|
21
|
+
font?: Partial<FontSpec>;
|
|
22
|
+
padding?: number;
|
|
23
|
+
position?: SankeyNodeLabelOption<SankeyLabelPosition>;
|
|
24
|
+
}
|
|
25
|
+
export interface SankeyControllerDatasetFlowLabelsOptions {
|
|
26
|
+
backgroundColor?: Scriptable<Color, SankeyScriptableContext>;
|
|
27
|
+
borderRadius?: Scriptable<number, SankeyScriptableContext>;
|
|
28
|
+
color?: Scriptable<Color, SankeyScriptableContext>;
|
|
29
|
+
display?: Scriptable<boolean, SankeyScriptableContext>;
|
|
30
|
+
font?: Scriptable<Partial<FontSpec>, SankeyScriptableContext>;
|
|
31
|
+
padding?: Scriptable<number, SankeyScriptableContext>;
|
|
32
|
+
position?: Scriptable<SankeyLabelPosition, SankeyScriptableContext>;
|
|
33
|
+
}
|
|
34
|
+
export interface SankeyParsingOptions {
|
|
35
|
+
from: string;
|
|
36
|
+
to: string;
|
|
37
|
+
flow: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SankeyControllerDatasetOptions extends Omit<ControllerDatasetOptions, 'parsing'> {
|
|
40
|
+
alpha?: number;
|
|
41
|
+
backgroundColor?: never;
|
|
42
|
+
borderColor?: Color;
|
|
43
|
+
borderWidth?: number;
|
|
44
|
+
color?: Color;
|
|
45
|
+
colorFrom?: ScriptableAndArray<Color, SankeyScriptableContext>;
|
|
46
|
+
colorMode?: 'gradient' | 'from' | 'to';
|
|
47
|
+
colorTo?: ScriptableAndArray<Color, SankeyScriptableContext>;
|
|
48
|
+
column?: Record<string, number>;
|
|
49
|
+
font?: Partial<FontSpec>;
|
|
50
|
+
flowLabels?: SankeyControllerDatasetFlowLabelsOptions;
|
|
51
|
+
hoverColorFrom?: ScriptableAndArray<Color, SankeyScriptableContext>;
|
|
52
|
+
hoverColorTo?: ScriptableAndArray<Color, SankeyScriptableContext>;
|
|
53
|
+
hoverFlowColor?: ScriptableAndArray<Color, SankeyScriptableContext>;
|
|
54
|
+
labels?: Record<string, string>;
|
|
55
|
+
flowColor?: ScriptableAndArray<Color, SankeyScriptableContext>;
|
|
56
|
+
modeX?: 'edge' | 'even';
|
|
57
|
+
nodeLabels?: SankeyControllerDatasetNodeLabelsOptions;
|
|
58
|
+
nodePadding?: number;
|
|
59
|
+
nodeWidth?: number;
|
|
60
|
+
orientation?: SankeyOrientation;
|
|
61
|
+
padding?: number;
|
|
62
|
+
parsing: SankeyParsingOptions;
|
|
63
|
+
priority?: Record<string, number>;
|
|
64
|
+
size?: 'min' | 'max';
|
|
65
|
+
}
|
|
66
|
+
export interface FromToElement {
|
|
67
|
+
addY: number;
|
|
68
|
+
flow: number;
|
|
69
|
+
key: string;
|
|
70
|
+
node: SankeyNode;
|
|
71
|
+
index: number;
|
|
72
|
+
}
|
|
73
|
+
export interface SankeyNode {
|
|
74
|
+
key: string;
|
|
75
|
+
in: number;
|
|
76
|
+
out: number;
|
|
77
|
+
size: number;
|
|
78
|
+
from: FromToElement[];
|
|
79
|
+
to: FromToElement[];
|
|
80
|
+
column?: boolean;
|
|
81
|
+
priority?: number;
|
|
82
|
+
y?: number;
|
|
83
|
+
x?: number;
|
|
84
|
+
color?: Color;
|
|
85
|
+
_visited?: number;
|
|
86
|
+
}
|
|
87
|
+
export interface SankeyParsedData {
|
|
88
|
+
x: number;
|
|
89
|
+
y: number;
|
|
90
|
+
_custom: {
|
|
91
|
+
from: SankeyNode;
|
|
92
|
+
to: SankeyNode;
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
height: number;
|
|
96
|
+
flow: number;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface FlowProps {
|
|
101
|
+
flow: number;
|
|
102
|
+
x: number;
|
|
103
|
+
y: number;
|
|
104
|
+
x2: number;
|
|
105
|
+
y2: number;
|
|
106
|
+
height: number;
|
|
107
|
+
width: number;
|
|
108
|
+
}
|
|
109
|
+
export interface FlowOptions {
|
|
110
|
+
alpha: number;
|
|
111
|
+
colorMode: 'gradient' | 'from' | 'to';
|
|
112
|
+
colorFrom: Color;
|
|
113
|
+
colorTo: Color;
|
|
114
|
+
hoverColorFrom: Color;
|
|
115
|
+
hoverColorTo: Color;
|
|
116
|
+
flowColor: Color | null;
|
|
117
|
+
orientation: SankeyOrientation;
|
|
118
|
+
flowLabels: {
|
|
119
|
+
backgroundColor?: Color;
|
|
120
|
+
borderRadius: number;
|
|
121
|
+
color: Color;
|
|
122
|
+
display: boolean;
|
|
123
|
+
font?: Partial<FontSpec>;
|
|
124
|
+
padding: number;
|
|
125
|
+
position: SankeyLabelPosition;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export interface FlowConfig {
|
|
129
|
+
flow?: number;
|
|
130
|
+
x: number;
|
|
131
|
+
y: number;
|
|
132
|
+
x2: number;
|
|
133
|
+
y2: number;
|
|
134
|
+
height: number;
|
|
135
|
+
width?: number;
|
|
136
|
+
progress?: number;
|
|
137
|
+
from?: SankeyNode;
|
|
138
|
+
to?: SankeyNode;
|
|
139
|
+
options: FlowOptions;
|
|
140
|
+
}
|
package/package.json
CHANGED
|
@@ -12,43 +12,43 @@
|
|
|
12
12
|
"@emnapi/core": "^1.11.2",
|
|
13
13
|
"@emnapi/runtime": "^1.11.2",
|
|
14
14
|
"@kurkle/astro-chartjs-editor": "^1.0.0",
|
|
15
|
-
"@kurkle/configs": "^1.
|
|
15
|
+
"@kurkle/configs": "^1.5.1",
|
|
16
16
|
"@napi-rs/canvas": "^1.0.0",
|
|
17
17
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
18
18
|
"@rollup/plugin-swc": "^0.4.0",
|
|
19
19
|
"@rollup/plugin-terser": "^1.0.0",
|
|
20
|
-
"@swc/cli": "^0.8.1",
|
|
21
20
|
"@swc/core": "^1.7.40",
|
|
22
|
-
"@types/jasmine": "^6.0.0",
|
|
23
21
|
"@types/node": "^26.1.0",
|
|
22
|
+
"@vitest/browser": "^5.0.0",
|
|
23
|
+
"@vitest/browser-playwright": "^5.0.0",
|
|
24
|
+
"@vitest/coverage-istanbul": "^5.0.0",
|
|
25
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
24
26
|
"astro": "^7.0.6",
|
|
25
|
-
"c8": "^12.0.0",
|
|
26
27
|
"chart.js": "^4.4.5",
|
|
27
|
-
"chartjs-test-utils": "^0.5.0",
|
|
28
|
-
"cross-env": "^10.1.0",
|
|
29
28
|
"globals": "^17.0.0",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"karma-chrome-launcher": "^3.1.0",
|
|
33
|
-
"karma-coverage": "^2.0.3",
|
|
34
|
-
"karma-firefox-launcher": "^2.1.3",
|
|
35
|
-
"karma-jasmine": "^5.0.1",
|
|
36
|
-
"karma-jasmine-html-reporter": "^2.0.0",
|
|
37
|
-
"karma-rollup-preprocessor": "^7.0.8",
|
|
38
|
-
"karma-spec-reporter": "^0.0.36",
|
|
29
|
+
"pixelmatch": "^7.1.0",
|
|
30
|
+
"playwright": "^1.63.0",
|
|
39
31
|
"png-to-ico": "^3.0.2",
|
|
40
32
|
"rollup": "^4.24.0",
|
|
41
33
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
42
|
-
"rollup-plugin-istanbul": "^5.0.0",
|
|
43
34
|
"semantic-release": "^25.0.3",
|
|
44
35
|
"sharp": "^0.35.4",
|
|
45
|
-
"typescript": "^6.0.3"
|
|
36
|
+
"typescript": "^6.0.3",
|
|
37
|
+
"vitest": "^5.0.0"
|
|
46
38
|
},
|
|
47
39
|
"exports": {
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
".": {
|
|
41
|
+
"import": {
|
|
42
|
+
"types": "./dist/index.esm.d.ts",
|
|
43
|
+
"default": "./dist/chartjs-chart-sankey.esm.js"
|
|
44
|
+
},
|
|
45
|
+
"require": {
|
|
46
|
+
"types": "./dist/index.d.cts",
|
|
47
|
+
"default": "./dist/chartjs-chart-sankey.cjs"
|
|
48
|
+
},
|
|
49
|
+
"script": "./dist/chartjs-chart-sankey.min.js"
|
|
50
|
+
},
|
|
51
|
+
"./package.json": "./package.json"
|
|
52
52
|
},
|
|
53
53
|
"files": [
|
|
54
54
|
"dist/*",
|
|
@@ -74,27 +74,27 @@
|
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"autobuild": "rollup -c -w",
|
|
77
|
-
"build": "rollup -c && tsc -p tsconfig.json --emitDeclarationOnly",
|
|
78
|
-
"dev": "
|
|
79
|
-
"dev:ff": "karma start ./karma.conf.cjs --no-single-run --auto-watch --browsers firefox",
|
|
77
|
+
"build": "rollup -c && tsc -p tsconfig.json --emitDeclarationOnly && kurkle-build-cjs-types",
|
|
78
|
+
"dev": "vitest --config vitest.browser.config.ts",
|
|
80
79
|
"docs": "npm run build && astro build",
|
|
81
80
|
"docs:dev": "npm run build && astro dev",
|
|
82
81
|
"docs:version": "node ./docs/scripts/write-docs-version.cjs",
|
|
82
|
+
"fixtures:update": "node scripts/update-fixtures.mjs",
|
|
83
83
|
"format": "biome check --write",
|
|
84
|
-
"icons": "
|
|
84
|
+
"icons": "kurkle-generate-icons",
|
|
85
85
|
"lint": "biome check",
|
|
86
86
|
"predocs": "npm run docs:version",
|
|
87
87
|
"predocs:dev": "npm run docs:version",
|
|
88
88
|
"prepack": "npm run build",
|
|
89
89
|
"prepare": "git config core.hooksPath .githooks || true",
|
|
90
|
-
"
|
|
91
|
-
"test": "
|
|
92
|
-
"test:
|
|
93
|
-
"test:integration:browser-module": "karma start ./karma.integration.cjs --single-run",
|
|
90
|
+
"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 && npm run test:pack",
|
|
91
|
+
"test:fixture": "vitest run --config vitest.browser.config.ts --coverage",
|
|
92
|
+
"test:integration:browser-module": "vitest run --config vitest.integration.config.ts",
|
|
94
93
|
"test:integration:node-commonjs": "node test/integration/node-commonjs/test.cjs",
|
|
95
94
|
"test:integration:node-module": "node test/integration/node-module/test.mjs",
|
|
96
|
-
"test:
|
|
97
|
-
"
|
|
95
|
+
"test:pack": "kurkle-check-package",
|
|
96
|
+
"test:unit": "vitest run --config vitest.config.ts --coverage",
|
|
97
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json && tsc -p tsconfig.json --emitDeclarationOnly && tsc --noEmit -p test/types/ && tsc --noEmit -p tsconfig.tooling.json",
|
|
98
98
|
"typecheck:docs": "astro check"
|
|
99
99
|
},
|
|
100
100
|
"sideEffects": [
|
|
@@ -104,5 +104,5 @@
|
|
|
104
104
|
"type": "module",
|
|
105
105
|
"types": "dist/index.esm.d.ts",
|
|
106
106
|
"unpkg": "dist/chartjs-chart-sankey.min.js",
|
|
107
|
-
"version": "0.
|
|
107
|
+
"version": "0.16.1"
|
|
108
108
|
}
|