diff --git a/package-lock.json b/package-lock.json index 97c3e70bc3842ffdf9cb366fd9e788651ee9ded9..5d701b8d239b2d9db1d84a5aed8d5597a2e375f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2191,6 +2191,302 @@ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "dev": true }, + "d3": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.9.2.tgz", + "integrity": "sha512-ydrPot6Lm3nTWH+gJ/Cxf3FcwuvesYQ5uk+j/kXEH/xbuYWYWTMAHTJQkyeuG8Y5WM5RSEYB41EctUrXQQytRQ==", + "dev": true, + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "dev": true + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==", + "dev": true + }, + "d3-brush": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.6.tgz", + "integrity": "sha512-lGSiF5SoSqO5/mYGD5FAeGKKS62JdA1EV7HPrU2b5rTX4qEJJtpjaGLJngjnkewQy7UnGstnFd3168wpf5z76w==", + "dev": true, + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "dev": true, + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==", + "dev": true + }, + "d3-color": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.2.3.tgz", + "integrity": "sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw==", + "dev": true + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "dev": true, + "requires": { + "d3-array": "^1.1.1" + } + }, + "d3-dispatch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", + "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==", + "dev": true + }, + "d3-drag": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.3.tgz", + "integrity": "sha512-8S3HWCAg+ilzjJsNtWW1Mutl74Nmzhb9yU6igspilaJzeZVFktmY6oO9xOh5TDk+BM2KrNFjttZNoJJmDnkjkg==", + "dev": true, + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", + "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", + "dev": true, + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", + "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==", + "dev": true + }, + "d3-fetch": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", + "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "dev": true, + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "dev": true, + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.3.2.tgz", + "integrity": "sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==", + "dev": true + }, + "d3-geo": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.3.tgz", + "integrity": "sha512-n30yN9qSKREvV2fxcrhmHUdXP9TNH7ZZj3C/qnaoU0cVf/Ea85+yT7HY7i8ySPwkwjCNYtmKqQFTvLFngfkItQ==", + "dev": true, + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==", + "dev": true + }, + "d3-interpolate": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", + "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "dev": true, + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.7.tgz", + "integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA==", + "dev": true + }, + "d3-polygon": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", + "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==", + "dev": true + }, + "d3-quadtree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", + "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==", + "dev": true + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==", + "dev": true + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "dev": true, + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz", + "integrity": "sha512-BWTipif1CimXcYfT02LKjAyItX5gKiwxuPRgr4xM58JwlLocWbjPLI7aMEjkcoOQXMkYsmNsvv3d2yl/OKuHHw==", + "dev": true, + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "d3-selection": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", + "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==", + "dev": true + }, + "d3-shape": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", + "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", + "dev": true, + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz", + "integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==", + "dev": true + }, + "d3-time-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", + "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "dev": true, + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", + "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==", + "dev": true + }, + "d3-transition": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", + "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "dev": true, + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==", + "dev": true + }, + "d3-zoom": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.7.3.tgz", + "integrity": "sha512-xEBSwFx5Z9T3/VrwDkMt+mr0HCzv7XjpGURJ8lWmIC8wxe32L39eWHIasEe/e7Ox8MPU4p1hvH8PKN2olLzIBg==", + "dev": true, + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -6520,6 +6816,12 @@ "aproba": "^1.1.1" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=", + "dev": true + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", diff --git a/package.json b/package.json index e4da75a48b6fc5f482ac32991b85652e4b9196fc..d20274be287f44bac542b41955d6dce8b2d0d231 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@babel/preset-env": "^7.4.5", "babel-loader": "^8.0.6", "css-loader": "^2.1.1", + "d3": "^5.9.2", "file-loader": "^3.0.1", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", diff --git a/src/app.js b/src/app.js index cddfe220d4574e359546f2c324a7f5220c52a9d5..49f6a7740b0a2c866dade27b57fe2798e3bc23e6 100644 --- a/src/app.js +++ b/src/app.js @@ -1,12 +1,3 @@ -import {Graph} from "./js/graph/graph"; -import {Edge} from "./js/graph/edge"; -import {Node} from "./js/graph/node"; -import {Neo4JAdapter} from "./js/neo4j/neo" +import {Visualizer} from './js/visualizer/visualizer' -var graph = new Graph(); - -var neo = new Neo4JAdapter('neo4j', 'W7uFSy$ywR3M3ck'); - -graph = neo.getGraph(); - -console.log(graph); \ No newline at end of file +new Visualizer().plotGraph('match (a:Issue{id:4})<-[r*..]-(b) return *'); \ No newline at end of file diff --git a/src/index.html b/src/index.html index 8231315c293c3e412900d4166cb2a67921e15d67..6d3a0bbd7052430dddd2d1ac6cd1fbde799d87b6 100644 --- a/src/index.html +++ b/src/index.html @@ -1,9 +1,14 @@ <!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <title>Foo</title> -</head> -<body> -</body> -</html> \ No newline at end of file +<meta charset="utf-8"> +<style> + body { + font-family: "Inconsolata"; + } + circle { + fill: aquamarine; + } + line { + stroke: blueviolet; + } +</style> +<svg width="960" height="600"></svg> \ No newline at end of file diff --git a/src/js/graph/edge.js b/src/js/graph/edge.js deleted file mode 100644 index c6911f0ae24acd03482077d80c61abd992dee397..0000000000000000000000000000000000000000 --- a/src/js/graph/edge.js +++ /dev/null @@ -1,10 +0,0 @@ -export class Edge { - - constructor(id, properties, type, start, end) { - this.id = id; - this.properties = properties; - this.type = type; - this.start = start; - this.end = end; - } -} \ No newline at end of file diff --git a/src/js/graph/graph.js b/src/js/graph/graph.js index af8dde920a28e139fb9b2401b6f9176fc3ac8cae..7c174e220d46f7ab32e75b2399d7240f5e02cdf3 100644 --- a/src/js/graph/graph.js +++ b/src/js/graph/graph.js @@ -1,42 +1,27 @@ -import {Edge} from './edge' - export class Graph { - constructor() { - this.edges = {}; - this.nodes = {}; + constructor(){ + this.nodes = new Array(); + this.edges = new Array(); } - addEdge(edge) { - if (!(edge.id in this.edges)) { - this.edges[edge.id] = edge; - console.log("Added: " + JSON.stringify(edge) + " to edges."); - } - }; - - addNode(node) { - if (!(node.id in this.nodes)) { - this.nodes[node.id] = node; - console.log("Added: " + JSON.stringify(node) + " to nodes."); - } - }; - - _asList(dict) { - var list = []; - for (var key in dict) { - if (dict.hasOwnProperty(key)) { - list.push(dict[key]); - } - } - return list; + addNode(newNode){ + this.nodes.push(newNode); + this.nodes = this.getUnique(this.nodes, "id"); } - get edgesAsList() { - return this._asList(this.edges); + addEdge(newEdge){ + this.edges.push(newEdge); } - get nodesAsList() { - return this._asList(this.nodes); + getUnique(arr, comp) { + const unique = arr + .map(e => e[comp]) + // store the keys of the unique objects + .map((e, i, final) => final.indexOf(e) === i && i) + // eliminate the dead keys & store unique objects + .filter(e => arr[e]).map(e => arr[e]); + return unique; } } \ No newline at end of file diff --git a/src/js/graph/graphCreator.js b/src/js/graph/graphCreator.js new file mode 100644 index 0000000000000000000000000000000000000000..d673a3338388f327c51aa203947207d950a2879c --- /dev/null +++ b/src/js/graph/graphCreator.js @@ -0,0 +1,27 @@ +import {Neo4JAdapter} from '../neo4j/neo' +import {v1 as neo4j} from 'neo4j-driver' +export class GraphCreator{ + + async fill(graph, withQuery){ + const queryResult = await new Neo4JAdapter('neo4j', 'W7uFSy$ywR3M3ck').ask(withQuery); + var records = queryResult.records; + records.forEach(function (record) { + record.forEach(function (element) { + if (element instanceof neo4j.types.Node) { + var id = element.identity.toInt(); + var newNode = {"id": id}; + graph.addNode(newNode); + } else if (element instanceof neo4j.types.Relationship) { + var newEdge = {"source": element.start.toInt(), "target": element.end.toInt()}; + graph.addEdge(newEdge); + } else if (element instanceof Array) { + element.forEach(function (relation) { + var newEdge = {"source": relation.start.toInt(), "target": relation.end.toInt()}; + graph.addEdge(newEdge); + }) + } + }); + }); + return graph; + } +} \ No newline at end of file diff --git a/src/js/graph/node.js b/src/js/graph/node.js deleted file mode 100644 index 30f0ed5b1911c0bd2a19b085f156c845c05834fc..0000000000000000000000000000000000000000 --- a/src/js/graph/node.js +++ /dev/null @@ -1,8 +0,0 @@ -export class Node { - - constructor(id, properties, labels) { - this.id = id; - this.properties = properties; - this.labels = labels - } -} \ No newline at end of file diff --git a/src/js/neo4j/neo.js b/src/js/neo4j/neo.js index 995c7d9af0dcf6d811fe749581ee5b892c359412..bce85427e25f526b9c5651011be4565a8154756b 100644 --- a/src/js/neo4j/neo.js +++ b/src/js/neo4j/neo.js @@ -1,7 +1,4 @@ import {v1 as neo4j} from 'neo4j-driver' -import {Edge} from '../graph/edge' -import {Node} from '../graph/node' -import {Graph} from '../graph/graph' export class Neo4JAdapter { constructor(username, password) { @@ -10,33 +7,12 @@ export class Neo4JAdapter { this.authToken = neo4j.auth.basic(this.username, this.password); } - getGraph(graph) { - - const driver = neo4j.driver('bolt://localhost:7687', this.authToken, { - encrypted: false - }); - + async ask(query){ + const driver = neo4j.driver('bolt://localhost:7687', this.authToken); const session = driver.session(); - - - const resultPromise = session.run( - 'match (a:User)<-[r]-() return *' - ); - - var graph = new Graph(); - resultPromise.then(result => { - session.close(); - result.records.forEach(function (element) { - element.forEach(function (subelement) { - if (subelement instanceof neo4j.types.Node) { - graph.addNode(new Node(subelement.identity.toInt(), subelement.properties, subelement.labels)); - } else if (subelement instanceof neo4j.types.Relationship) { - graph.addEdge(new Edge(subelement.identity.toInt(), subelement.properties, subelement.type, subelement.start.toInt(), subelement.end.toInt())); - } - }); - }); - driver.close(); - }); - return graph + const result = await session.run(query); + session.close(); + driver.close(); + return result; } } diff --git a/src/js/visualizer/visualizer.js b/src/js/visualizer/visualizer.js new file mode 100644 index 0000000000000000000000000000000000000000..f39a73ec28f58929f3b1853da2056743e65a17d6 --- /dev/null +++ b/src/js/visualizer/visualizer.js @@ -0,0 +1,126 @@ +import * as d3 from 'd3' +import {Neo4JAdapter} from '../neo4j/neo' +import {Graph} from '../graph/graph' +import {GraphCreator} from '../graph/graphCreator' +import {v1 as neo4j} from 'neo4j-driver' + +export class Visualizer { + + async plotGraph(query) { + const graph = await new GraphCreator().fill(new Graph(), query); + + var svg = d3.select("svg"); + var width = +svg.attr("width"); + var height = +svg.attr("height"); + + var simulation = d3.forceSimulation() + .force("link", d3.forceLink().id(function (d) { + return d.id; + })) + .force('charge', d3.forceManyBody() + .strength(0) + .theta(1) + .distanceMax(10) + ) + .force('collide', d3.forceCollide().radius(d => 40) + .iterations(2) + ) + .force("center", d3.forceCenter(width / 2, height / 2)); + + + this.run(svg, simulation, graph); + } + + + async run(svg, simulation, graph) { + + function dragstarted(d) { + if (!d3.event.active) simulation.alphaTarget(0.2).restart() + d.fx = d.x; + d.fy = d.y; + } + + function dragged(d) { + d.fx = d3.event.x; + d.fy = d3.event.y; + } + + function dragended(d) { + if (!d3.event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + } + + var link = svg.append("g") + .selectAll("line") + .data(graph.edges) + .enter() + .append("line"); + + var node = svg.append("g") + .attr("class", "nodes") + .selectAll("circle") + .data(graph.nodes) + .enter() + .append("circle") + .call(d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended)); + + var label = svg.append("g") + .attr("class", "labels") + .selectAll("text") + .data(graph.nodes) + .enter() + .append("text") + .attr("class", "label") + .text(function (d) { + return d.id; + }); + + simulation + .nodes(graph.nodes) + .on("tick", ticked); + + simulation.force("link") + .links(graph.edges); + + function ticked() { + link + .attr("x1", function (d) { + return d.source.x; + }) + .attr("y1", function (d) { + return d.source.y; + }) + .attr("x2", function (d) { + return d.target.x; + }) + .attr("y2", function (d) { + return d.target.y; + }); + + node + .attr("r", 10) + .style("stroke", "#424242") + .style("stroke-width", "2px") + .attr("cx", function (d) { + return d.x + 3; + }) + .attr("cy", function (d) { + return d.y - 3; + }); + + label + .attr("x", function (d) { + return d.x; + }) + .attr("y", function (d) { + return d.y; + }) + .style("font-size", "10px").style("fill", "#333"); + } + } + +} \ No newline at end of file