@lucastho/d3-sankey-circular-ng 0.1.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/LICENSE +27 -0
- package/README.md +199 -0
- package/dist/d3-sankey-circular-ng.js +784 -0
- package/dist/d3-sankey-circular-ng.min.js +2 -0
- package/package.json +59 -0
- package/src/align.js +23 -0
- package/src/constant.js +5 -0
- package/src/index.js +3 -0
- package/src/sankey.js +644 -0
- package/src/sankeyLinkCircular.js +97 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// https://github.com/lucastho/d3-sankey-circular-ng v0.1.0 Copyright 2026 Lucas Tho
|
|
2
|
+
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("d3-array")):"function"==typeof define&&define.amd?define(["exports","d3-array"],n):n((t=t||self).d3=t.d3||{},t.d3)}(this,function(t,n){"use strict";function o(t){return t.target.depth}function e(t,n){return t.sourceLinks.length?t.depth:n-1}function r(t){return function(){return t}}function i(t,n){return s(t.source,n.source)||t.index-n.index}function c(t,n){return s(t.target,n.target)||t.index-n.index}function s(t,n){return t.y0-n.y0}function u(t){return t.value}function f(t){return t.index}function a(t){return t.nodes}function l(t){return t.links}function h(t,n){const o=t.get(n);if(!o)throw new Error("missing: "+n);return o}function y({nodes:t}){for(const n of t){let t=n.y0,o=t;for(const o of n.sourceLinks)o.y0=t+o.width/2,t+=o.width;for(const t of n.targetLinks)t.y1=o+t.width/2,o+=t.width}}const d=4,g=1.5,x=.15,k=3,p=.6,m=2.5,L=2,M=.5,w=1.5;function $(t,{dx:n,dy:o,x0:e,x1:r}){const i=function(t){return Math.max(t,d)}(o),c=[],s=[];for(const n of t.links)n.circular&&("top"===n.circularLinkType?c:s).push(n);function u(t){let n=t.length?i:0;return t.forEach((t,o)=>{t.circularLaneIndex=o,n+=t.width+(o>0?i:0)}),n}c.sort((t,n)=>n.width-t.width),s.sort((t,n)=>n.width-t.width);const f=Math.max(0,...t.links.filter(t=>t.circular).map(t=>t.width/2)),a=Math.min(Math.max(n*g,f+n),(r-e)*x);return{top:u(c),bottom:u(s),gutter:a,topStack:c,bottomStack:s,gap:i}}function b(t,n,o,e,r){const{gap:i}=o,c="top"===n?e:r;let s=i;t.forEach(t=>{const e=t.width,r="top"===n?c-s-e/2:c+s+e/2;s+=e+i;const u=t.source.x1,f=t.target.x0,a=t.y0,l=t.y1,h=Math.max(k*e,i),y=Math.max(h,o.gutter*p),d=u+y,g=f-y,x=t.source===t.target,$="top"===n?x?t.source.y0:Math.min(a,l):x?t.source.y1:Math.max(a,l),b=m*e+i;let S;"top"===n?(S=Math.min(r,$-b),S=Math.min(S,Math.min(a,l)-b)):(S=Math.max(r,$+b),S=Math.max(S,Math.max(a,l)+b));const v=[{x:u,y:a},{x:d,y:a},{x:d,y:S},{x:g,y:S},{x:g,y:l},{x:f,y:l}],E=Math.abs(S-a),P=Math.abs(S-l),j=Math.abs(g-d),z=Math.max(L,Math.min(o.gutter*M,e*w+i,E/2,P/2,j/2,y/2));t.circularPathData={points:v,radius:z,type:n,selfLoop:x,laneY:S,sourceX:u,targetX:f,sourceY:a,targetY:l}})}t.sankey=function(){let t,o,d,g=0,x=0,k=1,p=1,m=24,L=8,M=f,w=e,S=a,v=l,E=6;function P(){const t={nodes:S.apply(null,arguments),links:v.apply(null,arguments)};if(function({nodes:t,links:n}){for(const[n,o]of t.entries())o.index=n,o.sourceLinks=[],o.targetLinks=[];const o=new Map(t.map((n,o)=>[M(n,o,t),n]));for(const[t,e]of n.entries()){e.index=t;let{source:n,target:r}=e;"object"!=typeof n&&(n=e.source=h(o,n)),"object"!=typeof r&&(r=e.target=h(o,r)),n.sourceLinks.push(e),r.targetLinks.push(e)}if(null!=d)for(const{sourceLinks:n,targetLinks:o}of t)n.sort(d),o.sort(d)}(t),function({nodes:t,links:n}){for(const t of n)t.circular=!1;const o=new Set,e=new Set;function r(t){if(!o.has(t)){e.add(t);for(const n of t.sourceLinks)n.target===t?n.circular=!0:e.has(n.target)?n.circular=!0:o.has(n.target)||r(n.target);e.delete(t),o.add(t)}}for(const n of t)o.has(n)||r(n)}(t),function({nodes:t}){for(const o of t)o.value=void 0===o.fixedValue?Math.max(n.sum(o.sourceLinks,u),n.sum(o.targetLinks,u)):o.fixedValue}(t),function({nodes:t}){const n=t.length;let o=new Set(t),e=new Set,r=0;for(;o.size;){for(const t of o){t.depth=r;for(const n of t.sourceLinks)n.circular||e.add(n.target)}if(++r>n)throw new Error("circular link");o=e,e=new Set}}(t),function({nodes:t}){const n=t.length;let o=new Set(t),e=new Set,r=0;for(;o.size;){for(const t of o){t.height=r;for(const n of t.targetLinks)n.circular||e.add(n.source)}if(++r>n)throw new Error("circular link");o=e,e=new Set}}(t),!t.links.some(t=>t.circular))return j(t),y(t),t;j(t),y(t),function({links:t}){const n=(x+p)/2;for(const o of t){if(!o.circular)continue;const t=(o.source.y0+o.source.y1+o.target.y0+o.target.y1)/4;o.circularLinkType=t<n?"top":"bottom"}}(t);const o=$(t,{dx:m,dy:L,x0:g,x1:k});t.circularReservation=o;const e=g,r=k,i=x,c=p;try{g=e+o.gutter,k=r-o.gutter,x=i+o.top,p=c-o.bottom,j(t),y(t)}finally{g=e,k=r,x=i,p=c}return function(t,n){const o=Math.min(...t.nodes.map(t=>t.y0)),e=Math.max(...t.nodes.map(t=>t.y1));b(n.topStack,"top",n,o,e),b(n.bottomStack,"bottom",n,o,e)}(t,o),t}function j(e){const r=function({nodes:t}){const e=n.max(t,t=>t.depth)+1,r=(k-g-m)/(e-1),i=new Array(e);for(const n of t){const t=Math.max(0,Math.min(e-1,Math.floor(w.call(null,n,e))));n.layer=t,n.x0=g+t*r,n.x1=n.x0+m,i[t]?i[t].push(n):i[t]=[n]}if(o)for(const t of i)t.sort(o);return i}(e);t=Math.min(L,(p-x)/(n.max(r,t=>t.length)-1)),function(o){const e=n.min(o,o=>(p-x-(o.length-1)*t)/n.sum(o,u));for(const n of o){let o=x;for(const r of n){r.y0=o,r.y1=o+r.value*e,o=r.y1+t;for(const t of r.sourceLinks)t.width=t.value*e}o=(p-o+t)/(n.length+1);for(let t=0;t<n.length;++t){const e=n[t];e.y0+=o*(t+1),e.y1+=o*(t+1)}R(n)}}(r);for(let t=0;t<E;++t){const n=Math.pow(.99,t),o=Math.max(1-n,(t+1)/E);C(r,n,o),z(r,n,o)}}function z(t,n,e){for(let r=1,i=t.length;r<i;++r){const i=t[r];for(const t of i){let o=0,e=0;for(const{source:n,value:r}of t.targetLinks){let i=r*(t.layer-n.layer);o+=T(n,t)*i,e+=i}if(!(e>0))continue;let r=(o/e-t.y0)*n;t.y0+=r,t.y1+=r,I(t)}void 0===o&&i.sort(s),D(i,e)}}function C(t,n,e){for(let r=t.length-2;r>=0;--r){const i=t[r];for(const t of i){let o=0,e=0;for(const{target:n,value:r}of t.sourceLinks){let i=r*(n.layer-t.layer);o+=V(t,n)*i,e+=i}if(!(e>0))continue;let r=(o/e-t.y0)*n;t.y0+=r,t.y1+=r,I(t)}void 0===o&&i.sort(s),D(i,e)}}function D(n,o){const e=n.length>>1,r=n[e];A(n,r.y0-t,e-1,o),Y(n,r.y1+t,e+1,o),A(n,p,n.length-1,o),Y(n,x,0,o)}function Y(n,o,e,r){for(;e<n.length;++e){const i=n[e],c=(o-i.y0)*r;c>1e-6&&(i.y0+=c,i.y1+=c),o=i.y1+t}}function A(n,o,e,r){for(;e>=0;--e){const i=n[e],c=(i.y1-o)*r;c>1e-6&&(i.y0-=c,i.y1-=c),o=i.y0-t}}function I({sourceLinks:t,targetLinks:n}){if(void 0===d){for(const{source:{sourceLinks:t}}of n)t.sort(c);for(const{target:{targetLinks:n}}of t)n.sort(i)}}function R(t){if(void 0===d)for(const{sourceLinks:n,targetLinks:o}of t)n.sort(c),o.sort(i)}function T(n,o){let e=n.y0-(n.sourceLinks.length-1)*t/2;for(const{target:r,width:i}of n.sourceLinks){if(r===o)break;e+=i+t}for(const{source:t,width:r}of o.targetLinks){if(t===n)break;e-=r}return e}function V(n,o){let e=o.y0-(o.targetLinks.length-1)*t/2;for(const{source:r,width:i}of o.targetLinks){if(r===n)break;e+=i+t}for(const{target:t,width:r}of n.sourceLinks){if(t===o)break;e-=r}return e}return P.nodeId=function(t){return arguments.length?(M="function"==typeof t?t:r(t),P):M},P.nodeAlign=function(t){return arguments.length?(w="function"==typeof t?t:r(t),P):w},P.nodeSort=function(t){return arguments.length?(o=t,P):o},P.nodeWidth=function(t){return arguments.length?(m=+t,P):m},P.nodePadding=function(n){return arguments.length?(L=t=+n,P):L},P.nodes=function(t){return arguments.length?(S="function"==typeof t?t:r(t),P):S},P.links=function(t){return arguments.length?(v="function"==typeof t?t:r(t),P):v},P.linkSort=function(t){return arguments.length?(d=t,P):d},P.size=function(t){return arguments.length?(g=x=0,k=+t[0],p=+t[1],P):[k-g,p-x]},P.extent=function(t){return arguments.length?(g=+t[0][0],k=+t[1][0],x=+t[0][1],p=+t[1][1],P):[[g,x],[k,p]]},P.iterations=function(t){return arguments.length?(E=+t,P):E},P},t.sankeyCenter=function(t){return t.targetLinks.length?t.depth:t.sourceLinks.length?n.min(t.sourceLinks,o)-1:0},t.sankeyJustify=e,t.sankeyLeft=function(t){return t.depth},t.sankeyLinkCircular=function(){let t=!1;function n(t){return t.circular?function(t){const n=t.circularPathData;return function(t,n){if(t.length<2)return"";if(2===t.length)return`M${t[0].x},${t[0].y}L${t[1].x},${t[1].y}`;let o=`M${t[0].x},${t[0].y}`;for(let e=1;e<t.length-1;++e){const r=t[e-1],i=t[e],c=t[e+1],s=r.x-i.x,u=r.y-i.y,f=c.x-i.x,a=c.y-i.y,l=Math.hypot(s,u),h=Math.hypot(f,a);if(l<1e-6||h<1e-6){o+=`L${i.x},${i.y}`;continue}const y=Math.min(n,l/2,h/2),d=i.x+s/l*y,g=i.y+u/l*y,x=i.x+f/h*y,k=i.y+a/h*y;o+=`L${d},${g}`,o+=`Q${i.x},${i.y} ${x},${k}`}const e=t[t.length-1];return o+=`L${e.x},${e.y}`}(n.points,n.radius)}(t):function(t){const n=t.source.x1,o=t.target.x0,e=(n+o)/2;return`M${n},${t.y0}C${e},${t.y0} ${e},${t.y1} ${o},${t.y1}`}(t)}return n.points=function(t){return t.circular?t.circularPathData.points:[{x:t.source.x1,y:t.y0},{x:t.target.x0,y:t.y1}]},n.debug=function(o){return arguments.length?(t=!!o,n):t},n},t.sankeyRight=function(t,n){return n-1-t.height},Object.defineProperty(t,"__esModule",{value:!0})});
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lucastho/d3-sankey-circular-ng",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Fork of d3-sankey adding support for circular links (back-edges and self-loops). Visualize flow between nodes in a directed network that may contain cycles.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"d3",
|
|
7
|
+
"d3-module",
|
|
8
|
+
"sankey",
|
|
9
|
+
"circular",
|
|
10
|
+
"cyclic",
|
|
11
|
+
"flow"
|
|
12
|
+
],
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "Lucas Tho"
|
|
15
|
+
},
|
|
16
|
+
"contributors": [
|
|
17
|
+
{
|
|
18
|
+
"name": "Mike Bostock",
|
|
19
|
+
"url": "https://bost.ocks.org/mike/"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"license": "ISC",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "dist/d3-sankey-circular-ng.js",
|
|
25
|
+
"unpkg": "dist/d3-sankey-circular-ng.min.js",
|
|
26
|
+
"jsdelivr": "dist/d3-sankey-circular-ng.min.js",
|
|
27
|
+
"module": "src/index.js",
|
|
28
|
+
"exports": {
|
|
29
|
+
"umd": "./dist/d3-sankey-circular-ng.min.js",
|
|
30
|
+
"default": "./src/index.js"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/lucastho/d3-sankey-circular-ng",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/lucastho/d3-sankey-circular-ng.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/lucastho/d3-sankey-circular-ng/issues"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist/**/*.js",
|
|
42
|
+
"src/**/*.js"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"pretest": "rollup -c",
|
|
46
|
+
"test": "tape 'test/**/*-test.js' && eslint src",
|
|
47
|
+
"prepublishOnly": "rm -rf dist && yarn test",
|
|
48
|
+
"postpublish": "git push && git push --tags"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"d3-array": "^3.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"eslint": "6",
|
|
55
|
+
"rollup": "1",
|
|
56
|
+
"rollup-plugin-terser": "5",
|
|
57
|
+
"tape": "4"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/align.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {min} from "d3-array";
|
|
2
|
+
|
|
3
|
+
function targetDepth(d) {
|
|
4
|
+
return d.target.depth;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function left(node) {
|
|
8
|
+
return node.depth;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function right(node, n) {
|
|
12
|
+
return n - 1 - node.height;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function justify(node, n) {
|
|
16
|
+
return node.sourceLinks.length ? node.depth : n - 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function center(node) {
|
|
20
|
+
return node.targetLinks.length ? node.depth
|
|
21
|
+
: node.sourceLinks.length ? min(node.sourceLinks, targetDepth) - 1
|
|
22
|
+
: 0;
|
|
23
|
+
}
|
package/src/constant.js
ADDED
package/src/index.js
ADDED