带有标签和加权边缘的Javascript网络

时间:2017-09-02 10:49:38

标签: javascript graph visualization

我一直在寻找相当长的一段时间,而且我找不到一个简单的JS库来建立一个网络:

  • 节点有标签
  • 边缘厚度与重量成比例
  • 能够传播节点,使其不会变得混乱。

我在bl.ocks.org上找到了一个符合我需求的脚本,但是当我使用很多节点时,它变得非常混乱:
enter image description here
我用它作为输入:

target,source,weight
woooooow,notgood,70.0
woooooow,gainzz,32.004950495049506
woooooow,test,33.86138613861386
woooooow,peanutss,20.866336633663366
nicecode,woooooow,22.103960396039604
woooooow,oreo,20.742574257425744
bread,woooooow,37.945544554455445
jam,nutella,20.123762376237625
bread,nutsarelol,20.866336633663366
etc

问题
我可以使用哪些代码/库创建一个图形,如上图,但节点的扩散更多?

JS代码

/*
Author: Corneliu S. (github.com/upphiminn)

This is a javascript implementation of the Louvain
community detection algorithm (http://arxiv.org/abs/0803.0476)
Based on https://bitbucket.org/taynaud/python-louvain/overview

*/
(function(){
    jLouvain = function(){
        //Constants
        var __PASS_MAX = -1
        var __MIN    = 0.0000001

        //Local vars
        var original_graph_nodes;
        var original_graph_edges;
        var original_graph = {};
        var partition_init;

        //Helpers
        function make_set(array){
            var set = {};
            array.forEach(function(d,i){
                set[d] = true;
            });
            return Object.keys(set);
        };

        function obj_values(obj){
             var vals = [];
             for( var key in obj ) {
                 if ( obj.hasOwnProperty(key) ) {
                     vals.push(obj[key]);
                 }
             }
             return vals;
        };

        function get_degree_for_node(graph, node){
            var neighbours = graph._assoc_mat[node] ? Object.keys(graph._assoc_mat[node]) : [];
            var weight = 0;
            neighbours.forEach(function(neighbour,i){
                var value = graph._assoc_mat[node][neighbour] || 1;
                if(node == neighbour)
                    value *= 2;
                weight += value;
            });
            return weight;
        };

        function get_neighbours_of_node(graph, node){
            if(typeof graph._assoc_mat[node] == 'undefined')
                return [];

            var neighbours = Object.keys(graph._assoc_mat[node]);
            return neighbours;
        }

        function get_edge_weight(graph, node1, node2){
            return graph._assoc_mat[node1] ? graph._assoc_mat[node1][node2] : undefined;
        }

        function get_graph_size(graph){
            var size = 0;
            graph.edges.forEach(function(edge){
                size += edge.weight;
            });
            return size;
        }

        function add_edge_to_graph(graph, edge){
            update_assoc_mat(graph, edge);

            var edge_index = graph.edges.map(function(d){
                return d.source+'_'+d.target;
            }).indexOf(edge.source+'_'+edge.target);

            if(edge_index != -1)
                graph.edges[edge_index].weight = edge.weight;
            else
                graph.edges.push(edge);
        }

        function make_assoc_mat(edge_list){
            var mat = {};
            edge_list.forEach(function(edge, i){
                mat[edge.source] = mat[edge.source] || {};
                mat[edge.source][edge.target] = edge.weight;
                mat[edge.target] = mat[edge.target] || {};
                mat[edge.target][edge.source] = edge.weight;
            });

            return mat;
        }

        function update_assoc_mat(graph, edge){
            graph._assoc_mat[edge.source] = graph._assoc_mat[edge.source] || {};
            graph._assoc_mat[edge.source][edge.target] = edge.weight;
            graph._assoc_mat[edge.target] = graph._assoc_mat[edge.target] || {};
            graph._assoc_mat[edge.target][edge.source] = edge.weight;
        }

        function clone(obj){
            if(obj == null || typeof(obj) != 'object')
                return obj;

            var temp = obj.constructor();

            for(var key in obj)
                temp[key] = clone(obj[key]);
            return temp;
        }

        //Core-Algorithm Related
        function init_status(graph, status, part){
            status['nodes_to_com'] = {};
            status['total_weight'] = 0;
            status['internals'] = {};
            status['degrees'] = {};
            status['gdegrees'] = {};
            status['loops'] = {};
            status['total_weight'] = get_graph_size(graph);

            if(typeof part == 'undefined'){
                graph.nodes.forEach(function(node,i){
                    status.nodes_to_com[node] = i;
                    var deg = get_degree_for_node(graph, node);
                    if (deg < 0)
                        throw 'Bad graph type, use positive weights!';
                    status.degrees[i] = deg;
                    status.gdegrees[node] = deg;
                    status.loops[node] = get_edge_weight(graph, node, node) || 0;
                    status.internals[i] = status.loops[node];
                });
            }else{
                graph.nodes.forEach(function(node,i){
                    var com = part[node];
                    status.nodes_to_com[node] = com;
                    var deg = get_degree_for_node(graph, node);
                    status.degrees[com] = (status.degrees[com] || 0) + deg;
                    status.gdegrees[node] = deg;
                    var inc = 0.0;

                    var neighbours  = get_neighbours_of_node(graph, node);
                    neighbours.forEach(function(neighbour, i){
                        var weight = graph._assoc_mat[node][neighbour];
                        if (weight <= 0){
                            throw "Bad graph type, use positive weights";
                        }

                        if(part[neighbour] == com){
                            if (neighbour == node){
                                inc += weight;
                            }else{
                                inc += weight/2.0;
                            }
                        }
                    });
                    status.internals[com] = (status.internals[com] || 0) + inc;
                });
            }
        }

        function __modularity(status){
            var links = status.total_weight;
            var result = 0.0;
            var communities = make_set(obj_values(status.nodes_to_com));

            communities.forEach(function(com,i){
                var in_degree = status.internals[com] || 0 ;
                var degree = status.degrees[com] || 0 ;
                if(links > 0){
                    result = result + in_degree / links - Math.pow((degree / (2.0*links)), 2);
                }
            });
            return result;
        }

        function __neighcom(node, graph, status){
            // compute the communities in the neighb. of the node, with the graph given by
            // node_to_com

            var weights = {};
            var neighboorhood = get_neighbours_of_node(graph, node);//make iterable;

            neighboorhood.forEach(function(neighbour, i){
                if(neighbour != node){
                    var weight = graph._assoc_mat[node][neighbour] || 1; 
                    var neighbourcom = status.nodes_to_com[neighbour];
                    weights[neighbourcom] = (weights[neighbourcom] || 0) + weight;
                }   
            });

            return weights;
        }

        function __insert(node, com, weight, status){
            //insert node into com and modify status
            status.nodes_to_com[node] = +com;
            status.degrees[com] = (status.degrees[com] || 0) + (status.gdegrees[node]||0);
            status.internals[com] = (status.internals[com] || 0) + weight + (status.loops[node]||0);
        }

        function __remove(node, com, weight, status){
            //remove node from com and modify status
            status.degrees[com] = ((status.degrees[com] || 0) - (status.gdegrees[node] || 0));
            status.internals[com] = ((status.internals[com] || 0) - weight -(status.loops[node] ||0));
            status.nodes_to_com[node] = -1;
        }

        function __renumber(dict){
            var count = 0;
            var ret = clone(dict); //deep copy :)
            var new_values = {};
            var dict_keys = Object.keys(dict);
            dict_keys.forEach(function(key){
                var value = dict[key];
                var new_value =  typeof new_values[value] =='undefined' ? -1 : new_values[value];
                if(new_value == -1){
                    new_values[value] = count;
                    new_value = count;
                    count = count + 1;
                }
                ret[key] = new_value;
            });
            return ret;
        }

        function __one_level(graph, status){
            //Compute one level of the Communities Dendogram.
            var modif = true,
                nb_pass_done = 0,
                cur_mod = __modularity(status),
                new_mod = cur_mod;

            while (modif && nb_pass_done != __PASS_MAX){
                cur_mod = new_mod;
                modif = false;
                nb_pass_done += 1

                graph.nodes.forEach(function(node,i){
                    var com_node = status.nodes_to_com[node];
                    var degc_totw = (status.gdegrees[node] || 0) / (status.total_weight * 2.0);
                    var neigh_communities = __neighcom(node, graph, status);
                    __remove(node, com_node, (neigh_communities[com_node] || 0.0), status);
                    var best_com = com_node;
                    var best_increase = 0;
                    var neigh_communities_entries = Object.keys(neigh_communities);//make iterable;

                    neigh_communities_entries.forEach(function(com,i){
                        var incr = neigh_communities[com] - (status.degrees[com] || 0.0) * degc_totw;
                        if (incr > best_increase){
                            best_increase = incr;
                            best_com = com;
                        }
                    });

                    __insert(node, best_com, neigh_communities[best_com] || 0, status);

                    if(best_com != com_node)
                        modif = true;
                });
                new_mod = __modularity(status);
                if(new_mod - cur_mod < __MIN)
                    break;
            }
        }

        function induced_graph(partition, graph){
            var ret = {nodes:[], edges:[], _assoc_mat: {}};
            var w_prec, weight;
            //add nodes from partition values
            var partition_values = obj_values(partition);
            ret.nodes = ret.nodes.concat(make_set(partition_values)); //make set
            graph.edges.forEach(function(edge,i){
                weight = edge.weight || 1;
                var com1 = partition[edge.source];
                var com2 = partition[edge.target];
                w_prec = (get_edge_weight(ret, com1, com2) || 0); 
                var new_weight = (w_prec + weight);
                add_edge_to_graph(ret, {'source': com1, 'target': com2, 'weight': new_weight});
            });
            return ret;
        }

        function partition_at_level(dendogram, level){
            var partition = clone(dendogram[0]);
            for(var i = 1; i < level + 1; i++ )
                Object.keys(partition).forEach(function(key,j){
                    var node = key;
                    var com  = partition[key];
                    partition[node] = dendogram[i][com];
                });
            return partition;
        }


        function generate_dendogram(graph, part_init){

            if(graph.edges.length == 0){
                var part = {};
                graph.nodes.forEach(function(node,i){
                    part[node] = node;
                });
                return part;
            }
            var status = {};

            init_status(original_graph, status, part_init);
            var mod = __modularity(status);
            var status_list = [];
            __one_level(original_graph, status);
            var new_mod = __modularity(status);
            var partition = __renumber(status.nodes_to_com);
            status_list.push(partition);
            mod = new_mod;
            var current_graph = induced_graph(partition, original_graph);
            init_status(current_graph, status);

            while (true){
                __one_level(current_graph, status);
                new_mod = __modularity(status);
                if(new_mod - mod < __MIN)
                    break;

                partition = __renumber(status.nodes_to_com);
                status_list.push(partition); 

                mod = new_mod;
                current_graph = induced_graph(partition, current_graph);
                init_status(current_graph, status);
            }

            return status_list; 
        }

        var core = function(){
            var status = {};
            var dendogram = generate_dendogram(original_graph, partition_init);
            return partition_at_level(dendogram, dendogram.length - 1);
        };

        core.nodes = function(nds){
            if(arguments.length > 0){
                original_graph_nodes = nds;
            }
            return core;
        };

        core.edges = function(edgs){
            if(typeof original_graph_nodes == 'undefined')
                throw 'Please provide the graph nodes first!';

            if(arguments.length > 0){
                original_graph_edges = edgs;
                var assoc_mat = make_assoc_mat(edgs);
                original_graph = { 'nodes': original_graph_nodes,
                                   'edges': original_graph_edges,
                                   '_assoc_mat': assoc_mat };
            }
            return core;

        };

        core.partition_init = function(prttn){
            if(arguments.length > 0){
                partition_init = prttn;
            }
            return core;
        };

        return core;
    }
})();

0 个答案:

没有答案