-
Notifications
You must be signed in to change notification settings - Fork 248
/
index.html
119 lines (105 loc) · 3.43 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<head>
<style> body { margin: 0; } </style>
<script src="//bundle.run/@yarnpkg/[email protected]"></script>
<script src="//unpkg.com/force-graph"></script>
<!-- <script src="../../dist/force-graph.js"></script>-->
</head>
<body>
<div id="graph"></div>
<script type="module">
import dagre from 'https://esm.sh/dagre';
import accessorFn from 'https://esm.sh/accessor-fn';
const Graph = new ForceGraph(document.getElementById('graph'))
.nodeId('id')
.nodeLabel('id')
.cooldownTicks(0) // pre-defined layout, cancel force engine iterations
.linkDirectionalArrowLength(3)
.linkDirectionalArrowRelPos(1)
.linkCurvature(d =>
0.07 * // max curvature
// curve outwards from source, using gradual straightening within a margin of a few px
Math.max(-1, Math.min(1, (d.source.x - d.target.x) / 5)) *
Math.max(-1, Math.min(1, (d.target.y - d.source.y) / 5))
);
fetch('../../yarn.lock')
.then(r => r.text())
.then(text => {
const yarnlock = _yarnpkg_lockfile.parse(text);
if (yarnlock.type !== 'success') throw new Error('invalid yarn.lock');
return yarnlock.object;
})
.then(yarnlock => {
const nodes = [];
const links = [];
Object.entries(yarnlock).forEach(([pkg, details]) => {
nodes.push({ id: pkg });
if (details.dependencies) {
Object.entries(details.dependencies).forEach(([dep, version]) => {
links.push({source: pkg, target: `${dep}@${version}`});
});
}
});
return { nodes, links };
}).then(data => {
const nodeDiameter = Graph.nodeRelSize() * 2;
const layoutData = getLayout(data.nodes, data.links, {
nodeWidth: nodeDiameter,
nodeHeight: nodeDiameter,
nodesep: nodeDiameter * 0.5,
ranksep: nodeDiameter * Math.sqrt(data.nodes.length) * 0.6,
// root nodes aligned on top
rankDir: 'BT',
ranker: 'longest-path',
linkSource: 'target',
linkTarget: 'source'
});
layoutData.nodes.forEach(node => { node.fx = node.x; node.fy = node.y; }); // fix nodes
Graph.graphData(layoutData);
Graph.zoomToFit();
});
//
function getLayout(nodes, links, {
nodeId = 'id',
linkSource = 'source',
linkTarget = 'target',
nodeWidth = 0,
nodeHeight = 0,
...graphCfg
} = {}) {
const getNodeWidth = accessorFn(nodeWidth);
const getNodeHeight = accessorFn(nodeHeight);
const g = new dagre.graphlib.Graph();
g.setGraph({
// rankDir: 'LR',
// ranker: 'network-simplex' // 'tight-tree', 'longest-path'
// acyclicer: 'greedy'
nodesep: 5,
edgesep: 1,
ranksep: 20,
...graphCfg
});
nodes.forEach(node =>
g.setNode(
node[nodeId],
Object.assign({}, node, {
width: getNodeWidth(node),
height: getNodeHeight(node)
})
)
);
links.forEach(link =>
g.setEdge(link[linkSource], link[linkTarget], Object.assign({}, link))
);
dagre.layout(g);
return {
nodes: g.nodes().map(n => {
const node = g.node(n);
delete node.width;
delete node.height;
return node;
}),
links: g.edges().map(e => g.edge(e))
};
}
</script>
</body>