force-graph 1.42.3

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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -0
  3. package/dist/force-graph.common.js +1754 -0
  4. package/dist/force-graph.d.ts +195 -0
  5. package/dist/force-graph.js +12168 -0
  6. package/dist/force-graph.js.map +1 -0
  7. package/dist/force-graph.min.js +5 -0
  8. package/dist/force-graph.module.js +1743 -0
  9. package/example/auto-colored/index.html +34 -0
  10. package/example/basic/index.html +29 -0
  11. package/example/build-a-graph/index.html +108 -0
  12. package/example/click-to-focus/index.html +28 -0
  13. package/example/collision-detection/index.html +50 -0
  14. package/example/curved-links/index.html +37 -0
  15. package/example/curved-links-computed-curvature/index.html +76 -0
  16. package/example/custom-node-shape/index.html +44 -0
  17. package/example/dag-yarn/index.html +96 -0
  18. package/example/dagre/index.html +119 -0
  19. package/example/dash-odd-links/index.html +47 -0
  20. package/example/datasets/blocks.json +1 -0
  21. package/example/datasets/d3-dependencies.csv +464 -0
  22. package/example/datasets/miserables.json +337 -0
  23. package/example/datasets/mplate.mtx +74090 -0
  24. package/example/directional-links-arrows/index.html +29 -0
  25. package/example/directional-links-particles/index.html +22 -0
  26. package/example/dynamic/index.html +42 -0
  27. package/example/emit-particles/index.html +50 -0
  28. package/example/expandable-nodes/index.html +66 -0
  29. package/example/expandable-tree/index.html +85 -0
  30. package/example/fit-to-canvas/index.html +34 -0
  31. package/example/fix-dragged-nodes/index.html +24 -0
  32. package/example/highlight/index.html +84 -0
  33. package/example/huge-1M/index.html +37 -0
  34. package/example/img-nodes/imgs/cat.jpg +0 -0
  35. package/example/img-nodes/imgs/dog.jpg +0 -0
  36. package/example/img-nodes/imgs/eagle.jpg +0 -0
  37. package/example/img-nodes/imgs/elephant.jpg +0 -0
  38. package/example/img-nodes/imgs/grasshopper.jpg +0 -0
  39. package/example/img-nodes/imgs/octopus.jpg +0 -0
  40. package/example/img-nodes/imgs/owl.jpg +0 -0
  41. package/example/img-nodes/imgs/panda.jpg +0 -0
  42. package/example/img-nodes/imgs/squirrel.jpg +0 -0
  43. package/example/img-nodes/imgs/tiger.jpg +0 -0
  44. package/example/img-nodes/imgs/whale.jpg +0 -0
  45. package/example/img-nodes/index.html +43 -0
  46. package/example/large-graph/index.html +41 -0
  47. package/example/load-json/index.html +24 -0
  48. package/example/medium-graph/index.html +26 -0
  49. package/example/medium-graph/preview.png +0 -0
  50. package/example/move-viewport/index.html +42 -0
  51. package/example/multi-selection/index.html +57 -0
  52. package/example/responsive/index.html +37 -0
  53. package/example/text-links/index.html +69 -0
  54. package/example/text-nodes/index.html +42 -0
  55. package/example/tree/index.html +71 -0
  56. package/package.json +72 -0
  57. package/src/canvas-force-graph.js +544 -0
  58. package/src/color-utils.js +17 -0
  59. package/src/dagDepths.js +51 -0
  60. package/src/force-graph.css +35 -0
  61. package/src/force-graph.js +644 -0
  62. package/src/index.d.ts +195 -0
  63. package/src/index.js +3 -0
  64. package/src/kapsule-link.js +34 -0
@@ -0,0 +1,42 @@
1
+ <head>
2
+ <style> body { margin: 0; } </style>
3
+
4
+ <script src="//unpkg.com/force-graph"></script>
5
+ <!--<script src="../../dist/force-graph.js"></script>-->
6
+ </head>
7
+
8
+ <body>
9
+ <div id="graph"></div>
10
+
11
+ <script>
12
+ // Random tree
13
+ const N = 300;
14
+ const gData = {
15
+ nodes: [...Array(N).keys()].map(i => ({ id: i })),
16
+ links: [...Array(N).keys()]
17
+ .filter(id => id)
18
+ .map(id => ({
19
+ source: id,
20
+ target: Math.round(Math.random() * (id-1))
21
+ }))
22
+ };
23
+
24
+ const Graph = ForceGraph()
25
+ (document.getElementById('graph'))
26
+ .graphData(gData);
27
+
28
+ let k = 0, angle = 0, radius = 300;
29
+ setInterval(() => {
30
+ // zoom in
31
+ Graph.zoom(k);
32
+ k += 0.001;
33
+
34
+ // pan around
35
+ Graph.centerAt(
36
+ radius * Math.cos(angle),
37
+ radius * Math.sin(angle)
38
+ );
39
+ angle += Math.PI / 300;
40
+ }, 10);
41
+ </script>
42
+ </body>
@@ -0,0 +1,57 @@
1
+ <head>
2
+ <style> body { margin: 0; } </style>
3
+
4
+ <script src="//unpkg.com/force-graph"></script>
5
+ <!-- <script src="../../dist/force-graph.js"></script>-->
6
+ </head>
7
+
8
+ <body>
9
+ <div id="graph"></div>
10
+
11
+ <script>
12
+ // Random tree
13
+ const N = 30;
14
+ const gData = {
15
+ nodes: [...Array(N).keys()].map(i => ({ id: i })),
16
+ links: [...Array(N).keys()]
17
+ .filter(id => id)
18
+ .map(id => ({
19
+ source: id,
20
+ target: Math.round(Math.random() * (id-1))
21
+ }))
22
+ };
23
+
24
+ let selectedNodes = new Set();
25
+
26
+ const Graph = ForceGraph()
27
+ (document.getElementById('graph'))
28
+ .graphData(gData)
29
+ .nodeRelSize(7)
30
+ .nodeColor(node => selectedNodes.has(node) ? 'darkorange' : 'grey')
31
+ .onNodeClick((node, event) => {
32
+ if (event.ctrlKey || event.shiftKey || event.altKey) { // multi-selection
33
+ selectedNodes.has(node) ? selectedNodes.delete(node) : selectedNodes.add(node);
34
+ } else { // single-selection
35
+ const untoggle = selectedNodes.has(node) && selectedNodes.size === 1;
36
+ selectedNodes.clear();
37
+ !untoggle && selectedNodes.add(node);
38
+ }
39
+
40
+ Graph.nodeColor(Graph.nodeColor()); // update color of selected nodes
41
+ })
42
+ .onNodeDrag((node, translate) => {
43
+ if (selectedNodes.has(node)) { // moving a selected node
44
+ [...selectedNodes]
45
+ .filter(selNode => selNode !== node) // don't touch node being dragged
46
+ .forEach(node => ['x', 'y'].forEach(coord => node[`f${coord}`] = node[coord] + translate[coord])); // translate other nodes by same amount
47
+ }
48
+ })
49
+ .onNodeDragEnd(node => {
50
+ if (selectedNodes.has(node)) { // finished moving a selected node
51
+ [...selectedNodes]
52
+ .filter(selNode => selNode !== node) // don't touch node being dragged
53
+ .forEach(node => ['x', 'y'].forEach(coord => node[`f${coord}`] = undefined)); // unfix controlled nodes
54
+ }
55
+ });
56
+ </script>
57
+ </body>
@@ -0,0 +1,37 @@
1
+ <head>
2
+ <style> body { margin: 30px; } </style>
3
+
4
+ <script src="//unpkg.com/element-resize-detector/dist/element-resize-detector.min.js"></script>
5
+
6
+ <script src="//unpkg.com/force-graph"></script>
7
+ <!--<script src="../../dist/force-graph.js"></script>-->
8
+ </head>
9
+
10
+ <body>
11
+ <div id="graph"></div>
12
+
13
+ <script>
14
+ // Random tree
15
+ const N = 200;
16
+ const gData = {
17
+ nodes: [...Array(N).keys()].map(i => ({ id: i })),
18
+ links: [...Array(N).keys()]
19
+ .filter(id => id)
20
+ .map(id => ({
21
+ source: id,
22
+ target: Math.round(Math.random() * (id-1))
23
+ }))
24
+ };
25
+
26
+ const Graph = ForceGraph()
27
+ (document.getElementById('graph'))
28
+ .backgroundColor('#F5F5FF')
29
+ .height(window.innerHeight - 60)
30
+ .graphData(gData);
31
+
32
+ elementResizeDetectorMaker().listenTo(
33
+ document.getElementById('graph'),
34
+ el => Graph.width(el.offsetWidth)
35
+ );
36
+ </script>
37
+ </body>
@@ -0,0 +1,69 @@
1
+ <head>
2
+ <style> body { margin: 0; } </style>
3
+
4
+ <script src="//unpkg.com/force-graph"></script>
5
+ <!--<script src="../../dist/force-graph.js"></script>-->
6
+ </head>
7
+
8
+ <body>
9
+ <div id="graph"></div>
10
+
11
+ <script>
12
+ fetch('../datasets/miserables.json').then(res => res.json()).then(data => {
13
+ const Graph = ForceGraph()
14
+ (document.getElementById('graph'))
15
+ .graphData(data)
16
+ .nodeId('id')
17
+ .nodeLabel('id')
18
+ .nodeAutoColorBy('group')
19
+ .linkCanvasObjectMode(() => 'after')
20
+ .linkCanvasObject((link, ctx) => {
21
+ const MAX_FONT_SIZE = 4;
22
+ const LABEL_NODE_MARGIN = Graph.nodeRelSize() * 1.5;
23
+
24
+ const start = link.source;
25
+ const end = link.target;
26
+
27
+ // ignore unbound links
28
+ if (typeof start !== 'object' || typeof end !== 'object') return;
29
+
30
+ // calculate label positioning
31
+ const textPos = Object.assign(...['x', 'y'].map(c => ({
32
+ [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point
33
+ })));
34
+
35
+ const relLink = { x: end.x - start.x, y: end.y - start.y };
36
+
37
+ const maxTextLength = Math.sqrt(Math.pow(relLink.x, 2) + Math.pow(relLink.y, 2)) - LABEL_NODE_MARGIN * 2;
38
+
39
+ let textAngle = Math.atan2(relLink.y, relLink.x);
40
+ // maintain label vertical orientation for legibility
41
+ if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle);
42
+ if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle);
43
+
44
+ const label = `${link.source.id} > ${link.target.id}`;
45
+
46
+ // estimate fontSize to fit in link length
47
+ ctx.font = '1px Sans-Serif';
48
+ const fontSize = Math.min(MAX_FONT_SIZE, maxTextLength / ctx.measureText(label).width);
49
+ ctx.font = `${fontSize}px Sans-Serif`;
50
+ const textWidth = ctx.measureText(label).width;
51
+ const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding
52
+
53
+ // draw text label (with background rect)
54
+ ctx.save();
55
+ ctx.translate(textPos.x, textPos.y);
56
+ ctx.rotate(textAngle);
57
+
58
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
59
+ ctx.fillRect(- bckgDimensions[0] / 2, - bckgDimensions[1] / 2, ...bckgDimensions);
60
+
61
+ ctx.textAlign = 'center';
62
+ ctx.textBaseline = 'middle';
63
+ ctx.fillStyle = 'darkgrey';
64
+ ctx.fillText(label, 0, 0);
65
+ ctx.restore();
66
+ });
67
+ });
68
+ </script>
69
+ </body>
@@ -0,0 +1,42 @@
1
+ <head>
2
+ <style> body { margin: 0; } </style>
3
+
4
+ <script src="//unpkg.com/force-graph"></script>
5
+ <!--<script src="../../dist/force-graph.js"></script>-->
6
+ </head>
7
+
8
+ <body>
9
+ <div id="graph"></div>
10
+
11
+ <script>
12
+ fetch('../datasets/miserables.json').then(res => res.json()).then(data => {
13
+ const Graph = ForceGraph()
14
+ (document.getElementById('graph'))
15
+ .graphData(data)
16
+ .nodeId('id')
17
+ .nodeAutoColorBy('group')
18
+ .nodeCanvasObject((node, ctx, globalScale) => {
19
+ const label = node.id;
20
+ const fontSize = 12/globalScale;
21
+ ctx.font = `${fontSize}px Sans-Serif`;
22
+ const textWidth = ctx.measureText(label).width;
23
+ const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding
24
+
25
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
26
+ ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2, ...bckgDimensions);
27
+
28
+ ctx.textAlign = 'center';
29
+ ctx.textBaseline = 'middle';
30
+ ctx.fillStyle = node.color;
31
+ ctx.fillText(label, node.x, node.y);
32
+
33
+ node.__bckgDimensions = bckgDimensions; // to re-use in nodePointerAreaPaint
34
+ })
35
+ .nodePointerAreaPaint((node, color, ctx) => {
36
+ ctx.fillStyle = color;
37
+ const bckgDimensions = node.__bckgDimensions;
38
+ bckgDimensions && ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2, ...bckgDimensions);
39
+ });
40
+ });
41
+ </script>
42
+ </body>
@@ -0,0 +1,71 @@
1
+ <head>
2
+ <style> body { margin: 0; } </style>
3
+
4
+ <script src="//unpkg.com/d3-dsv"></script>
5
+ <script src="//unpkg.com/dat.gui"></script>
6
+ <script src="//unpkg.com/d3-quadtree"></script>
7
+ <script src="//unpkg.com/d3-force"></script>
8
+
9
+ <script src="//unpkg.com/force-graph"></script>
10
+ <!--<script src="../../dist/force-graph.js"></script>-->
11
+ </head>
12
+
13
+ <body>
14
+ <div id="graph"></div>
15
+
16
+ <script>
17
+ // controls
18
+ const controls = { 'DAG Orientation': 'td'};
19
+ const gui = new dat.GUI();
20
+ gui.add(controls, 'DAG Orientation', ['td', 'bu', 'lr', 'rl', 'radialout', 'radialin', null])
21
+ .onChange(orientation => graph && graph.dagMode(orientation));
22
+
23
+ // graph config
24
+ const NODE_REL_SIZE = 1;
25
+ const graph = ForceGraph()
26
+ .dagMode('td')
27
+ .dagLevelDistance(300)
28
+ .backgroundColor('#101020')
29
+ .linkColor(() => 'rgba(255,255,255,0.2)')
30
+ .nodeRelSize(NODE_REL_SIZE)
31
+ .nodeId('path')
32
+ .nodeVal(node => 100 / (node.level + 1))
33
+ .nodeLabel('path')
34
+ .nodeAutoColorBy('module')
35
+ .linkDirectionalParticles(2)
36
+ .linkDirectionalParticleWidth(2)
37
+ .d3Force('collision', d3.forceCollide(node => Math.sqrt(100 / (node.level + 1)) * NODE_REL_SIZE))
38
+ .d3VelocityDecay(0.3);
39
+
40
+ fetch('../datasets/d3-dependencies.csv')
41
+ .then(r => r.text())
42
+ .then(d3.csvParse)
43
+ .then(data => {
44
+ const nodes = [], links = [];
45
+ data.forEach(({ size, path }) => {
46
+ const levels = path.split('/'),
47
+ level = levels.length - 1,
48
+ module = level > 0 ? levels[1] : null,
49
+ leaf = levels.pop(),
50
+ parent = levels.join('/');
51
+
52
+ const node = {
53
+ path,
54
+ leaf,
55
+ module,
56
+ size: +size || 20,
57
+ level
58
+ };
59
+
60
+ nodes.push(node);
61
+
62
+ if (parent) {
63
+ links.push({source: parent, target: path, targetNode: node});
64
+ }
65
+ });
66
+
67
+ graph(document.getElementById('graph'))
68
+ .graphData({ nodes, links });
69
+ });
70
+ </script>
71
+ </body>
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "force-graph",
3
+ "version": "1.42.3",
4
+ "description": "2D force-directed graph rendered on HTML5 canvas",
5
+ "unpkg": "dist/force-graph.min.js",
6
+ "main": "dist/force-graph.common.js",
7
+ "module": "dist/force-graph.module.js",
8
+ "types": "src/index.d.ts",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/vasturiano/force-graph.git"
12
+ },
13
+ "homepage": "https://github.com/vasturiano/force-graph",
14
+ "keywords": [
15
+ "2d",
16
+ "force",
17
+ "simulation",
18
+ "graph",
19
+ "canvas",
20
+ "d3"
21
+ ],
22
+ "author": {
23
+ "name": "Vasco Asturiano",
24
+ "url": "http://bl.ocks.org/vasturiano"
25
+ },
26
+ "license": "MIT",
27
+ "bugs": {
28
+ "url": "https://github.com/vasturiano/force-graph/issues"
29
+ },
30
+ "scripts": {
31
+ "build": "rimraf dist && rollup -c",
32
+ "dev": "rollup -w -c rollup.config.dev.js",
33
+ "prepare": "npm run build"
34
+ },
35
+ "files": [
36
+ "dist/**/*",
37
+ "example/**/*",
38
+ "src/**/*"
39
+ ],
40
+ "dependencies": {
41
+ "@tweenjs/tween.js": "18",
42
+ "accessor-fn": "1",
43
+ "bezier-js": "3 - 4",
44
+ "canvas-color-tracker": "1",
45
+ "d3-array": "1 - 3",
46
+ "d3-drag": "2 - 3",
47
+ "d3-force-3d": "2 - 3",
48
+ "d3-scale": "1 - 4",
49
+ "d3-scale-chromatic": "1 - 3",
50
+ "d3-selection": "2 - 3",
51
+ "d3-zoom": "2 - 3",
52
+ "index-array-by": "1",
53
+ "kapsule": "^1.13",
54
+ "lodash.throttle": "4"
55
+ },
56
+ "devDependencies": {
57
+ "@babel/core": "^7.15.5",
58
+ "@babel/plugin-proposal-class-properties": "^7.14.5",
59
+ "@babel/plugin-proposal-object-rest-spread": "^7.15.6",
60
+ "@babel/preset-env": "^7.15.6",
61
+ "@rollup/plugin-babel": "^5.3.0",
62
+ "@rollup/plugin-commonjs": "^21.0.0",
63
+ "@rollup/plugin-node-resolve": "^13.0.5",
64
+ "postcss": "^8.3.8",
65
+ "rimraf": "^3.0.2",
66
+ "rollup": "^2.58.0",
67
+ "rollup-plugin-dts": "^4.0.0",
68
+ "rollup-plugin-postcss": "^4.0.1",
69
+ "rollup-plugin-terser": "^7.0.2",
70
+ "typescript": "^4.4.3"
71
+ }
72
+ }