From 993d6f949018fea85f3bf1b32554419fcbb18e9f Mon Sep 17 00:00:00 2001
From: feger <marc.feger@hhu.de>
Date: Tue, 9 Jul 2019 18:29:35 +0200
Subject: [PATCH] Add visualisation with first effort

---
 package-lock.json                  |  76 ++++++++++++
 package.json                       |   3 +-
 src/app.js                         |   5 +-
 src/index.html                     |  44 ++++---
 src/js/graph/graphCreator.js       |  38 ++++--
 src/js/visualizer/visualization.js | 190 +++++++++++++++++++++++++++++
 src/js/visualizer/visualizer.js    |  18 ++-
 7 files changed, 347 insertions(+), 27 deletions(-)
 create mode 100644 src/js/visualizer/visualization.js

diff --git a/package-lock.json b/package-lock.json
index 5d701b8..68987f3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -808,6 +808,12 @@
         "to-fast-properties": "^2.0.0"
       }
     },
+    "@types/d3-selection": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.0.10.tgz",
+      "integrity": "sha1-3PsN3837GtJq6kNRMjdx4a6pboQ=",
+      "dev": true
+    },
     "@types/events": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@@ -2433,6 +2439,76 @@
         "d3-path": "1"
       }
     },
+    "d3-svg-legend": {
+      "version": "2.25.6",
+      "resolved": "https://registry.npmjs.org/d3-svg-legend/-/d3-svg-legend-2.25.6.tgz",
+      "integrity": "sha1-jY3BvWk8N47ki2+CPook5o8uGtI=",
+      "dev": true,
+      "requires": {
+        "@types/d3-selection": "1.0.10",
+        "d3-array": "1.0.1",
+        "d3-dispatch": "1.0.1",
+        "d3-format": "1.0.2",
+        "d3-scale": "1.0.3",
+        "d3-selection": "1.0.2",
+        "d3-transition": "1.0.3"
+      },
+      "dependencies": {
+        "d3-array": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.0.1.tgz",
+          "integrity": "sha1-N1wCh0/NlsFu2fG89bSnvlPzWOc=",
+          "dev": true
+        },
+        "d3-dispatch": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.1.tgz",
+          "integrity": "sha1-S9ZaQ87P9DGN653yRVKqi/KBqEA=",
+          "dev": true
+        },
+        "d3-format": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.0.2.tgz",
+          "integrity": "sha1-E4YYMgtLvrQ7XA/zBRkHn7vXN14=",
+          "dev": true
+        },
+        "d3-scale": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.3.tgz",
+          "integrity": "sha1-T56PDMLqDzkl/wSsJ63AkEX6TJA=",
+          "dev": true,
+          "requires": {
+            "d3-array": "1",
+            "d3-collection": "1",
+            "d3-color": "1",
+            "d3-format": "1",
+            "d3-interpolate": "1",
+            "d3-time": "1",
+            "d3-time-format": "2"
+          }
+        },
+        "d3-selection": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.0.2.tgz",
+          "integrity": "sha1-rmYq/UcCrJxdoDmyEHoXZPockHA=",
+          "dev": true
+        },
+        "d3-transition": {
+          "version": "1.0.3",
+          "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.0.3.tgz",
+          "integrity": "sha1-kdyYa92zCXNjkyCoXbcs5KsaJ7s=",
+          "dev": true,
+          "requires": {
+            "d3-color": "1",
+            "d3-dispatch": "1",
+            "d3-ease": "1",
+            "d3-interpolate": "1",
+            "d3-selection": "1",
+            "d3-timer": "1"
+          }
+        }
+      }
+    },
     "d3-time": {
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz",
diff --git a/package.json b/package.json
index d20274b..a1f8829 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
   "main": "app.js",
   "scripts": {
     "build": "webpack",
-    "start:dev": "webpack-dev-server"
+    "start:dev": "webpack-dev-server --host 0.0.0.0"
   },
   "author": "",
   "license": "ISC",
@@ -15,6 +15,7 @@
     "babel-loader": "^8.0.6",
     "css-loader": "^2.1.1",
     "d3": "^5.9.2",
+    "d3-svg-legend": "^2.25.6",
     "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 49f6a77..2b5ee25 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,3 +1,2 @@
-import {Visualizer} from './js/visualizer/visualizer'
-
-new Visualizer().plotGraph('match (a:Issue{id:4})<-[r*..]-(b) return *');
\ No newline at end of file
+import {Network} from './js/visualizer/visualization'
+new Network().showNetwork('match (a:Issue{id:2})<-[r*..]-(b) return *');
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index 580de53..0b9c23a 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,22 +1,38 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <style>
-    body {
-        background: antiquewhite;
-        font-family: "Inconsolata";
-    }
-    circle {
-        fill: coral;
-        stroke: grey;
+
+    .link {
+        stroke: #ffffff;
         stroke-width: 2px;
+        pointer-events: all;
+    }
+
+    .node circle {
+        pointer-events: all;
+        stroke: #ffffff;
+        stroke-width: 1px;
+        fill: grey;
     }
-    text {
-        fill: slategrey;
-        font-size: 20px;
+
+    div.tooltip {
+        position: absolute;
+        background-color: #ffd899;
+        max-width;
+        200px;
+        height: auto;
+        padding: 2px;
+        border-style: solid;
+        border-radius: 4px;
+        border-width: 1px;
+        box-shadow: 3px 3px 10px rgba(0, 0, 0, .5);
+        pointer-events: none;
     }
-    line {
-        stroke: slategrey;
+
+    svg {
+        background: black;
     }
 </style>
-<svg width="960" height="600">
-</svg>
\ No newline at end of file
+<body>
+<svg width="1400" height="900"></svg>
+</body>
\ No newline at end of file
diff --git a/src/js/graph/graphCreator.js b/src/js/graph/graphCreator.js
index d673a33..a4d60c3 100644
--- a/src/js/graph/graphCreator.js
+++ b/src/js/graph/graphCreator.js
@@ -1,23 +1,45 @@
 import {Neo4JAdapter} from '../neo4j/neo'
 import {v1 as neo4j} from 'neo4j-driver'
-export class GraphCreator{
 
-    async fill(graph, withQuery){
+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};
+                    var newNode = null;
+                    if (element.labels.includes("Issue")){
+                        newNode = {
+                            "id": id,
+                            "labels": element.labels,
+                            "title": element.properties.title
+                        };
+                    }else if (element.labels.includes("Statement")){
+                        newNode = {
+                            "id": id,
+                            "labels": element.labels,
+                            "textversion": element.properties.textversion
+                        };
+                    }else if (element.labels.includes("Argument")){
+                        newNode = {
+                            "id": id,
+                            "labels": element.labels
+                        };
+                    }
                     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);
+                        if (relation instanceof neo4j.types.Relationship) {
+                            var newEdge = {
+                                "source": relation.start.toInt(),
+                                "target": relation.end.toInt(),
+                                "type": relation.type
+                            };
+                            graph.addEdge(newEdge);
+                        }
                     })
                 }
             });
diff --git a/src/js/visualizer/visualization.js b/src/js/visualizer/visualization.js
new file mode 100644
index 0000000..4108444
--- /dev/null
+++ b/src/js/visualizer/visualization.js
@@ -0,0 +1,190 @@
+import * as d3 from 'd3'
+import {legendColor} from 'd3-svg-legend'
+import {Neo4JAdapter} from '../neo4j/neo'
+import {Graph} from '../graph/graph'
+import {GraphCreator} from '../graph/graphCreator'
+import {v1 as neo4j} from 'neo4j-driver'
+
+export class Network {
+    async showNetwork(query) {
+        const graph = await new GraphCreator().fill(new Graph(), query);
+
+        var color = d3.scaleOrdinal(d3.schemeSet3);
+
+        var tooltip = d3.select("body")
+            .append("div")
+            .attr("class", "tooltip")
+            .style("opacity", 0);
+
+        const svg = d3.select('svg'),
+            width = +svg.attr('width'),
+            height = +svg.attr('height');
+
+        const simulation = d3.forceSimulation()
+            .nodes(graph.nodes)
+            .force('link', d3.forceLink().id(d => d.id).distance(20))
+            .force('charge', d3.forceManyBody())
+            .force('center', d3.forceCenter(width / 2, height / 2))
+            .on('tick', ticked);
+
+        simulation.force('link')
+            .links(graph.edges);
+
+        const R = 6;
+        let link = svg.selectAll('line')
+            .data(graph.edges)
+            .enter().append('line');
+
+        link
+            .attr('class', 'link')
+            .on('mouseover.tooltip', function (d) {
+                tooltip.transition()
+                    .duration(300)
+                    .style("opacity", 0.8);
+                tooltip.html("Source:" + d.source.id +
+                    "<p/>Target:" + d.target.id +
+                    "<p/>Type:" + d.type)
+                    .style("left", (d3.event.pageX) + "px")
+                    .style("top", (d3.event.pageY + 10) + "px");
+            })
+            .on("mouseout.tooltip", function () {
+                tooltip.transition()
+                    .duration(100)
+                    .style("opacity", 0);
+            })
+            .on('mouseout.fade', fade(1))
+            .on("mousemove", function () {
+                tooltip.style("left", (d3.event.pageX) + "px")
+                    .style("top", (d3.event.pageY + 10) + "px");
+            });
+        ;
+        let node = svg.selectAll('.node')
+            .data(graph.nodes)
+            .enter().append('g')
+            .attr('class', 'node')
+            .call(d3.drag()
+                .on("start", dragstarted)
+                .on("drag", dragged)
+                .on("end", dragended));
+        ;
+        node.append('circle')
+            .attr('r', R)
+            .attr("fill", function (d) {
+                return color(d.labels);
+            })
+            .on('mouseover.tooltip', function (d) {
+                tooltip.transition()
+                    .duration(300)
+                    .style("opacity", .8);
+                let info = "";
+                if (d.labels.includes("Issue")) {
+                    info = "id:" + d.id
+                        + "<p/>Labels:" + d.labels
+                        + "<p/>Title:" + d.title;
+                }else if (d.labels.includes("Statement")){
+                    info = "id:" + d.id
+                        + "<p/>Labels:" + d.labels
+                        + "<p/>Textversion:" + d.textversion;
+                }
+                else if (d.labels.includes("Argument")){
+                    info = "id:" + d.id
+                        + "<p/>Labels:" + d.labels;
+                }
+                tooltip.html(info)
+                    .style("left", (d3.event.pageX) + "px")
+                    .style("top", (d3.event.pageY + 10) + "px");
+            })
+            .on('mouseover.fade', fade(0.1))
+            .on("mouseout.tooltip", function () {
+                tooltip.transition()
+                    .duration(100)
+                    .style("opacity", 0);
+            })
+            .on('mouseout.fade', fade(1))
+            .on("mousemove", function () {
+                tooltip.style("left", (d3.event.pageX) + "px")
+                    .style("top", (d3.event.pageY + 10) + "px");
+            })
+            .on('dblclick', releasenode)
+
+        node.append('text')
+            .attr('x', 0)
+            .attr('dy', '.35em')
+            .text(d => d.name);
+
+        function ticked() {
+            link
+                .attr('x1', d => d.source.x)
+                .attr('y1', d => d.source.y)
+                .attr('x2', d => d.target.x)
+                .attr('y2', d => d.target.y);
+
+            node
+                .attr('transform', d => `translate(${d.x},${d.y})`);
+        }
+
+        function dragstarted(d) {
+            if (!d3.event.active) simulation.alphaTarget(0.3).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;
+        }
+
+        function releasenode(d) {
+            d.fx = null;
+            d.fy = null;
+        }
+
+        const linkedByIndex = {};
+        graph.edges.forEach(d => {
+            linkedByIndex[`${d.source.index},${d.target.index}`] = 1;
+        });
+
+        function isConnected(a, b) {
+            return linkedByIndex[`${a.index},${b.index}`] || linkedByIndex[`${b.index},${a.index}`] || a.index === b.index;
+        }
+
+        function fade(opacity) {
+            return d => {
+                node.style('stroke-opacity', function (o) {
+                    const thisOpacity = isConnected(d, o) ? 1 : opacity;
+                    this.setAttribute('fill-opacity', thisOpacity);
+                    return thisOpacity;
+                });
+
+                link.style('stroke-opacity', o => (o.source === d || o.target === d ? 1 : opacity));
+
+            };
+        }
+
+        var sequentialScale = d3.scaleOrdinal(d3.schemeSet3)
+            .domain([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
+
+        svg.append("g")
+            .attr("class", "legendSequential")
+            .attr("transform", "translate(" + (width - 100) + "," + (height - 300) + ")");
+
+
+        var legendSequential = legendColor()
+            .shapeWidth(30)
+            .cells(11)
+            .orient("vertical")
+            .title("Group number by color:")
+            .titleWidth(100)
+            .scale(sequentialScale);
+
+        svg.select(".legendSequential")
+            .call(legendSequential);
+
+    }
+}
\ No newline at end of file
diff --git a/src/js/visualizer/visualizer.js b/src/js/visualizer/visualizer.js
index e51434d..793e66c 100644
--- a/src/js/visualizer/visualizer.js
+++ b/src/js/visualizer/visualizer.js
@@ -22,7 +22,7 @@ export class Visualizer {
             .attr("markerWidth", 13)
             .attr("markerHeight", 13)
             .attr("orient", "auto")
-            .attr('xoverflow','visible')
+            .attr('xoverflow', 'visible')
             .append("svg:path")
             .attr("d", "M0,-5L10,0L0,5");
         var simulation = d3.forceSimulation()
@@ -52,6 +52,20 @@ export class Visualizer {
             d.fy = d.y;
         }
 
+        function click() {
+            d3.select(this).classed("fixed", this.fixed = true).transition()
+                .duration(1000)
+                .attr("r", 15)
+                .style("fill", "lightsteelblue");
+        }
+
+        function dbclick() {
+            d3.select(this).classed("fixed", this.fixed = false).transition()
+                .duration(1000)
+                .attr("r", 5)
+                .style("fill", "#ccc");
+        }
+
         function dragged(d) {
             d.fx = d3.event.x;
             d.fy = d3.event.y;
@@ -76,6 +90,8 @@ export class Visualizer {
             .data(graph.nodes)
             .enter()
             .append("circle")
+            .on("click", click)
+            .on("dblick", dbclick)
             .call(d3.drag()
                 .on("start", dragstarted)
                 .on("drag", dragged)
-- 
GitLab