Commit eabebce8 authored by born's avatar born
Browse files

Added path finding functionality; computationally, graph gets treated as undirected for that

parent 6061288a
Loading
Loading
Loading
Loading
+105 −3
Original line number Diff line number Diff line
angular
    .module('kg-app')
    .run(function($rootScope) {
        $rootScope.vissettings = {settingsview: true, visview: false, query: "", ownfile: true, paperdatafile: false, otherdatafile: false, fileIsRead: false, addEdgeValue: false, addCentrality: false, degreeCentrality: true, eigenvectorCentrality: false, addEventYears: false, showUnconnectedNodes: false, groupByGender: true, degree: 1, minDegree: true, equalDegree: false, maxDegree: false, count: 1, minCount: true, equalCount: false, maxCount: false, selectAllIssuers: false, physicsEnabled: true, networkLocked: false};
        $rootScope.vissettings = {settingsview: true, visview: false, query: "", ownfile: true, paperdatafile: false, otherdatafile: false, fileIsRead: false, addEdgeValue: false, addCentrality: false, degreeCentrality: true, eigenvectorCentrality: false, addEventYears: false, showUnconnectedNodes: false, groupByGender: true, degree: 1, minDegree: true, equalDegree: false, maxDegree: false, count: 1, minCount: true, equalCount: false, maxCount: false, selectAllIssuers: false, physicsEnabled: true, allPaths: false, networkLocked: false};
        $rootScope.search = {highlighted:[]};
    })
    .controller('VissuiteController', function($rootScope, $scope, $sce, $filter, $http, $timeout, $q, GraphUtils, GeneralUtils, FileSaver, FileUploader) {
@@ -492,10 +492,12 @@ angular
            }

            var eigenvector = [];
            if( $scope.vissettings.addCentrality ){
            //if( $scope.vissettings.addCentrality ){
            var adjacencymatrix = GraphUtils.createAdjacencyMatrix(updatedNodes, edges);
            eigenvector = GraphUtils.getEigenvectorPowerIteration(adjacencymatrix, 1000);
            }
            //}
            var adjacencylist = GraphUtils.getAdjacencyList(adjacencymatrix);

            angular.forEach(updatedNodes, function(value1, key1){
                var count = edgeArr.reduce(function(n, val) {
                    return n + (val.from === value1.id || val.to === value1.id);
@@ -597,6 +599,106 @@ angular
            }
            console.log(options);

            $scope.findShortestPath = function(){
                const idxStart = presentNodes.map(function(e) { return e.id; }).indexOf($scope.person.info.id);
                const idxEnd = presentNodes.map(function(e) { return e.id; }).indexOf($scope.path.selected);
                if( $scope.vissettings.allPaths == true ){
                    var allpathids = GraphUtils.bfsFromAToB(adjacencylist, idxStart, idxEnd, true);
                    if( allpathids.length > 0 ){
                        $("#pathfinder").modal('hide');
                        var allnodeids = highlightpaths(allpathids);
                        network.fit({nodes:allnodeids, animation: {duration: 400}});
                        $scope.path.selected = -1;
                        $scope.results = $scope.allnodes;
                        $scope.person.query = "";
                        $scope.vissettings.allPaths = false;
                    }
                    else{
                        alert("No path found!");
                    }
                }
                else{
                    var pathids = GraphUtils.bfsFromAToB(adjacencylist, idxStart, idxEnd);
                    if( pathids.length > 0 ){
                        $("#pathfinder").modal('hide');
                        var allnodeids = highlightpath(pathids);
                        network.fit({nodes:allnodeids, animation: {duration: 400}});
                        $scope.path.selected = -1;
                        $scope.results = $scope.allnodes;
                        $scope.person.query = "";
                        $scope.vissettings.allPaths = false;
                    }
                    else{
                        alert("No path found!");
                    }
                }
            }

            function highlightpath(pathids){
                var allnodeids = getAndSelectAllPathNodeIDs(pathids);
                var edgesToSelect = [];
                angular.forEach(edges.get(), function(value, key){
                    var toIndex = presentNodes.map(function(e) { return e.id; }).indexOf(value.to);
                    var fromIndex = presentNodes.map(function(e) { return e.id; }).indexOf(value.from);
                    if( pathids.indexOf(fromIndex) != -1 && pathids.indexOf(toIndex) != -1 ){
                        edgesToSelect.push(value.id);
                    }
                    else{
                        value.color.color = "#cccccc";
                    }
                });
                network.selectEdges(edgesToSelect);
                edges.update(edges.get());
                return allnodeids;
            }

            function highlightpaths(allpathids){
                var allids = GraphUtils.flatten(allpathids);
                var allnodeids = getAndSelectAllPathNodeIDs(allids);
                var edgesToSelect = [];
                angular.forEach(allpathids, function(pathids, key){
                    angular.forEach(edges.get(), function(value, key){
                        var toIndex = presentNodes.map(function(e) { return e.id; }).indexOf(value.to);
                        var fromIndex = presentNodes.map(function(e) { return e.id; }).indexOf(value.from);
                        if( pathids.indexOf(fromIndex) != -1 && pathids.indexOf(toIndex) != -1 ){
                            edgesToSelect.push(value.id);
                        }
                        else{
                            value.color.color = "#cccccc";
                        }
                    });
                });
                network.selectEdges(edgesToSelect);
                edges.update(edges.get());
                return allnodeids;
            }

            function getAndSelectAllPathNodeIDs(allids){
                var allnodeids = [];
                angular.forEach(nodes.get(), function(nodeVal, nodeKey){
                    if( presentNodes.map(function(e) { return e.id; }).indexOf(nodeVal.id) == -1 || allids.indexOf(presentNodes.map(function(e) { return e.id; }).indexOf(nodeVal.id)) == -1 ){
                        nodeVal.color.background = "#cccccc";
                    }
                    else{
                        if( nodeVal.nodetype == "issuer" ){
                            nodeVal.color.background = "#990500";
                        }
                        else{
                            nodeVal.color.background = "#006aff";
                        }
                        allnodeids.push(nodeVal.id);
                    }
                });
                nodes.update(nodes.get());
                return allnodeids;
            }

            $scope.path = {selected: -1};
            $scope.togglePersonForPath = function(){
                if( $scope.path.selected == this.item.id ){ $scope.path.selected = -1; }
                else{ $scope.path.selected = this.item.id; }
            }

            if( $scope.addCentrality ){ options.nodes.scaling = { max: 50 }; }

            var network = new vis.Network(container, data, options);
+100 −2
Original line number Diff line number Diff line
@@ -2,8 +2,7 @@ angular
    .module('kg-app')
    .service('GraphUtils', [function() {
        return {
            // Maybe add function to app to export adjacency matrix?
            // Maybe in some standardized format such as GraphMD?
            /*
            createAdjacencyMatrix: function(nodes, edges) {
                var edgeHash = {};
                edges.forEach(function(x){
@@ -27,6 +26,38 @@ angular
                });
                return matrix;
            },
            */
            createAdjacencyMatrix: function(nodes, edges) {
                const edgeHash = {};
                const matrix = [];
                edges.forEach(function(x){
                    const id = x.from + "-" + x.to;
                    edgeHash[id] = x;
                });
                //create all possible edges
                nodes.forEach(function(a){
                    const adjacencyvector = [];
                    nodes.forEach(function(b){
                        const gridid = a.id + "-" + b.id;
                        const gridid2 = b.id + "-" + a.id; // because we can justify treating our graph as undirected (since relation directions are irrelevant for social dimension of graph)
                        if( edgeHash[gridid] || edgeHash[gridid2] ){ adjacencyvector.push(1); }
                        else{ adjacencyvector.push(0); }
                    });
                    matrix.push(adjacencyvector);
                });
                return matrix;
            },
            getAdjacencyList: function(adjacencymatrix) {
                const adjacencylist = {};
                angular.forEach(adjacencymatrix, function(value, key){
                    const adjacentNodes = [];
                    angular.forEach(value, function(value2, key2){
                        if( value2 != 0 ){ adjacentNodes.push(key2); }
                    });
                    adjacencylist[key] = adjacentNodes;
                });
                return adjacencylist;
            },
            getEigenvectorPowerIteration: function(adjacencymatrix, num_simulations){
                console.log(adjacencymatrix);
                // create random vector of length of A
@@ -67,6 +98,73 @@ angular
                }
                */
                return b_k;
            },
            // Adapted from: https://stackoverflow.com/questions/41789767/finding-the-shortest-path-nodes-with-breadth-first-search/48260217#48260217
            bfsFromAToB: function(adjacencylist, startidx, endidx, allPaths){
                var queue = [];
                var seen = [];
                var shortestdistance = 0;
                var allpathids = [];

                var pathToFirstNode = [];
                pathToFirstNode.push(startidx);
                queue.push(pathToFirstNode);
                while( queue.length != 0 ){
                    var pathToNode = queue.shift();
                    var node = pathToNode[pathToNode.length - 1];
                    if( node == endidx ){
                        // This is the case for the first shortest path to be found
                        if( shortestdistance == 0 ){
                            var pathids = this.flatten(pathToNode);
                            if( allPaths == true ){
                                allpathids.push(pathids);
                                shortestdistance = pathids.length;
                            }
                            else{
                                return pathids; // return shortest path if only first one is wanted
                            }
                        }
                        else if( this.flatten(pathToNode).length <= shortestdistance ){
                            var pathids = this.flatten(pathToNode);
                            allpathids.push(pathids);
                        }
                    }
                    var neighbours = adjacencylist[node];
                    angular.forEach(neighbours, function(neighbour, key){
                        if( seen.indexOf(neighbour) == -1 ){
                            var pathToNeighbourNode = [pathToNode];
                            pathToNeighbourNode.push(neighbour);
                            queue.push(pathToNeighbourNode);
                            if( allPaths == true ){
                                if( neighbour != endidx ){
                                    seen.push(neighbour);
                                }
                            }
                            else{
                                seen.push(neighbour);
                            }
                        }
                    });
                }
                if( allPaths == true ){
                    return allpathids;  // return all shortest paths
                }
                else{
                    return []; // if no path is found
                }
            },
            // After: https://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript/39000004#39000004
            flatten: function(arr, result = []){
                for( let i = 0, length = arr.length; i < length; i++ ){
                    const value = arr[i];
                    if( Array.isArray(value) ){
                        this.flatten(value, result);
                    }
                    else{
                        result.push(value);
                    }
                }
                return result;
            }
        }
    }]);
+39 −0
Original line number Diff line number Diff line
@@ -139,6 +139,45 @@
                    </div>
                </div>
            </div>
            <div id="pathfinder" class="modal fade" role="dialog">
                <div class="modal-dialog vis-modal-dialog">
                    <div class="modal-content">
                        <div class="modal-header">
                            <button type="button" class="close" data-dismiss="modal">&times;</button>
                            <h4 class="modal-title">Find shortest path from {{person.info.id}}</h4>
                        </div>
                        <div class="modal-body vis-modal text-center">
                            <div>
                                <input type="text" spellcheck="false" class="form-control glyphicon glyphicon-search" style="font-size:14px;word-spacing:-0.5em;" placeholder="Search..." ng-model="person.query" ng-change="updateFilteredPersons()"></input>
                            </div>
                            <div>
                                <div class="vis-small-search">
                                    <table class="table table-bordered" style="margin-bottom:0px;" at-table at-list="results">
                                        <tbody class="text-left">
                                            <tr ng-click="togglePersonForPath();" ng-mouseover="hoverActive=true" ng-mouseleave="hoverActive=false" ng-style="hoverActive ? {'background-color':'#c7c9c9'} : {}">
                                                <td ng-if="person.info.id != item.id">{{item.id}} / {{item.id}} <span ng-if="path.selected == item.id" class="pull-right"><span class="glyphicon glyphicon-ok"></span></span></td>
                                            </tr>
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                            <br>
                            <div class="group">
                                <div class="col span_a_third">
                                    <label class="checkbox-inline pull-left"><input type="checkbox" ng-model="vissettings.allPaths" ng-true-value="true" ng-false-value="false"> Show all paths</label>
                                </div>
                                <div class="col span_a_third">
                                    <div style="display:flex;justify-content:center;">
                                        <button type="submit" ng-click="findShortestPath()" ng-disabled="path.selected == -1" class="btn btn-sm btn-info pull-right">Find</button>
                                    </div>
                                </div>
                                <div class="col span_a_third">
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div ng-show="vissettings.settingsview" class="cont text-center">
                <h2 style="color:#990500">WELCOME</h2>
                <div class="vis-apple">