calculate communiies with a worker

parent e969e845
......@@ -18,4 +18,4 @@ a#pubkey:hover{font-weight: bold;color: black;text-decoration:underline;}
.img-mini{width:25px;height:25px;}
#search{width:230px;}
#toggle-layout{width:130px;}
#loader{color:white;}
\ No newline at end of file
#loader{text-align:center;color:white;}
\ No newline at end of file
......@@ -21,9 +21,11 @@
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="loader">
<div>Veuillez patienter pendant la détection de communautés....</div>
<div>La vitesse de calcul dépend de votre navigateur de de votre matériel.</div>
<div id="loader" class="w3-round">
<h3>Toile de confiance de la monnaie libre Ğ1</h3>
<h5>Détection de communautés</h5>
<div>Calcul en cours....</div><br />
<img src="img/ajax-loader.gif" />
</div>
<div id="myProgress">
<div style="color:white;">Chargement des avatars</div>
......
importScripts('../custom/jLouvain.js');
onmessage = function(e) {
var community = jLouvain().nodes(e.data[0]).edges(e.data[1]);
var community_assignment_result = community();
postMessage(community_assignment_result);
}
\ No newline at end of file
This diff is collapsed.
importScripts('../sigmajs/sigma.core.js');
importScripts('../sigmajs/utils/sigma.utils.js');
importScripts('../sigmajs/classes/sigma.classes.graph.js');
importScripts('../sigmajs/plugins/sigma.statistics.louvain/sigma.statistics.louvain.js');
sigma.utils.pkg('sigma.plugins');
onmessage = function(e) {
console.log('Message received from main script');
e.data.__proto__ = sigma.classes.graph.prototype;
console.log(JSON.stringify(e.data));
var workerResult = sigma.plugins.louvain(e.data, {
setter: function(communityId) { this.my_community = communityId; }
});
console.log('Posting message back to main script');
postMessage(workerResult);
}
\ No newline at end of file
function displayGraph(s){
var filter = new sigma.plugins.filter(s),
avatarsLoaded = false,
maxDegree = 0,
tabIdentities = [],
toKeep = s.graph.nodes(),
selectedNode = "",
cam = s.cameras.cam1,
louvainInstance,
colors = [],
cesium = 'https://g1.duniter.fr' + '/#/app/wot/';
maxDegree = 0,
tabIdentities = [],
tabNodes = [],
tabEdges = s.graph.edges(),
toKeep = s.graph.nodes(),
selectedNode = "",
cam = s.cameras.cam1,
colors = [],
cesium = 'https://g1.duniter.fr' + '/#/app/wot/';
// Detect communities
var divfilters = _.$('menufilters');
louvainInstance = sigma.plugins.louvain(s.graph, {
setter: function(communityId) { this.my_community = communityId; }
});
var partitions = louvainInstance.getPartitions();
var nbPartitions = louvainInstance.countPartitions(partitions);
colors = colorScale(nbPartitions);
// Create filters for community
for (var i = 0; i < nbPartitions; i++) {
var checkCommunity = document.createElement("input"),
labelCommunity = document.createElement("label")
divmain = document.createElement("div");
divmain.classList.add("w3-bar-item", "w3-button", "w3-border-bottom");
divmain.id = 'com'+(i+1);
checkCommunity.type = 'checkbox';
checkCommunity.checked = true;
checkCommunity.id = 'community'+(i+1);
checkCommunity.classList.add("community");
checkCommunity.value = i;
labelCommunity.htmlFor = 'community'+(i+1);
labelCommunity.appendChild(document.createTextNode(' Communauté '+(i+1)));
divmain.appendChild(checkCommunity);
divmain.appendChild(labelCommunity);
divfilters.appendChild(divmain);
checkCommunity.addEventListener('click', function(e) {
var checkboxes = document.getElementsByClassName('community'),
tabchecked = [];
if (checkboxes){
Array.prototype.forEach.call(checkboxes, function(checkboxElem) {
if (checkboxElem.checked){tabchecked.push(+checkboxElem.value);}
});
}
filter
.undo('communities')
.nodesBy(function(n) {
return tabchecked.indexOf(n.my_community) != -1;
}, 'communities')
.apply();
});
}
_.$('loader').style.display = 'none';
// We place the nodes around a circle in order to make the force atlas efficient
s.graph.nodes().forEach(function(node, i, a) {
var tabnode = [node.label, node.url];
......@@ -68,12 +21,71 @@ function displayGraph(s){
node.y = Math.sin(Math.PI * 2 * i / a.length);
maxDegree = Math.max(maxDegree, s.graph.degree(node.id));
tabIdentities.push(tabnode);
tabNodes.push(node.id);
});
// Launch the forces and display the graph
// Launch the forces
s.startForceAtlas2({outboundAttractionDistribution:true,startingIterations:1,iterationsPerRender:5});
s.settings('drawNodes',true);
s.settings('drawLabels',false);
// Detect communities
if (window.Worker) {
var myWorker = new Worker('js/custom/community.worker.js');
myWorker.postMessage([tabNodes,tabEdges]);
myWorker.onmessage = function(e) {
var nbCommunities = 0,
divfilters = _.$('menufilters');
// Attribute the community numbers to nodes
s.graph.nodes().forEach(function(node, i, a) {
node.my_community = e.data[node.id];
nbCommunities = nbCommunities < (e.data[node.id]+1) ? (e.data[node.id]+1) : nbCommunities;
});
colors = colorScale(nbCommunities);
_.$('loader').style.display = 'none';
// Update filters in the menu
for (var i = 0; i < nbCommunities; i++) {
var checkCommunity = document.createElement("input"),
labelCommunity = document.createElement("label")
divmain = document.createElement("div");
divmain.classList.add("w3-bar-item", "w3-button", "w3-border-bottom");
divmain.id = 'com'+(i+1);
checkCommunity.type = 'checkbox';
checkCommunity.checked = true;
checkCommunity.id = 'community'+(i+1);
checkCommunity.classList.add("community");
checkCommunity.value = i;
labelCommunity.htmlFor = 'community'+(i+1);
labelCommunity.appendChild(document.createTextNode(' Communauté '+(i+1)));
divmain.appendChild(checkCommunity);
divmain.appendChild(labelCommunity);
divfilters.appendChild(divmain);
checkCommunity.addEventListener('click', function(e) {
var checkboxes = document.getElementsByClassName('community'),
tabchecked = [];
if (checkboxes){
Array.prototype.forEach.call(checkboxes, function(checkboxElem) {
if (checkboxElem.checked){tabchecked.push(+checkboxElem.value);}
});
}
filter
.undo('communities')
.nodesBy(function(n) {
return tabchecked.indexOf(n.my_community) != -1;
}, 'communities')
.apply();
});
}
// Display the graph
s.settings('drawNodes',true);
s.settings('drawLabels',false);
}
}
// Functions to display/hide avatars
function displayAvatarsAfterLoading(bool){
......@@ -352,25 +364,25 @@ function displayGraph(s){
// Display communities
_.$('run-btn').addEventListener("click", function(e) {
resetFilters();
centerCamera();
s.settings('edgeColor','source');
// Color nodes based on their community
s.graph.nodes().forEach(function(node) {node.color = colors[node.my_community];});
// Reset colors
var p_dom = e.target.parentNode,
oldChild = p_dom.removeChild(e.target),
resetFilters();
centerCamera();
s.settings('edgeColor','source');
// Color nodes based on their community
s.graph.nodes().forEach(function(node) {node.color = colors[node.my_community];});
// Reset colors
var p_dom = e.target.parentNode,
oldChild = p_dom.removeChild(e.target),
a = document.createElement("a");
a.classList.add("w3-bar-item", "w3-button", "w3-border-bottom");
a.id = 'resetColors';
a.href = '#';
a.textContent = 'Réinitialiser les couleurs';
a.classList.add("w3-bar-item", "w3-button", "w3-border-bottom");
a.id = 'resetColors';
a.href = '#';
a.textContent = 'Réinitialiser les couleurs';
p_dom.appendChild(a);
_.$('resetColors').addEventListener("click", function(e) {
_.$('resetColors').addEventListener("click", function(e) {
s.settings('edgeColor','default');
s.graph.nodes().forEach(function(node) {
if (node.attributes.referent){node.color="rgba(0,128,0,1)"}else{node.color="rgba(255,0,0,1)"};
......@@ -378,6 +390,6 @@ function displayGraph(s){
s.refresh({skipIndexation: true});
p_dom.removeChild(a);
p_dom.appendChild(oldChild);
});
});
});
}
\ No newline at end of file
The MIT License (MIT)
Copyright (c) 2015 Corneliu Sugar.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
sigma.statistics.louvain
==================
Plugin developed by Corneliu Sugar as [jLouvain](https://github.com/upphiminn/jLouvain) and ported as a Sigma plugin by [Sébastien Heymann](https://github.com/sheymann) for [Linkurious](https://github.com/Linkurious).
Contact: seb@linkurio.us
---
## General
Formally, a community detection aims to partition a graph’s vertices in subsets, such that there are many edges connecting between vertices of the same sub-set compared to vertices of different sub-sets; in essence, a community has many more ties between each constituent part than with outsiders. There are numerous algorithms present in the literature for solving this problem, a complete survey can be found in [1].
One of the popular community detection algorithms is presented in [2]. This algorithm separates the network in communities by optimizing greedily a modularity score after trying various grouping operations on the network. By using this simple greedy approach the algorithm is computationally very efficient.
[1] Fortunato, Santo. "Community detection in graphs." Physics Reports 486, no. 3-5 (2010).
[2] V.D. Blondel, J.-L. Guillaume, R. Lambiotte, E. Lefebvre. "Fast unfolding of communities in large networks." J. Stat. Mech., 2008: 1008.
![Before](https://github.com/Linkurious/linkurious.js/wiki/media/louvain-before-400.png)
![After](https://github.com/Linkurious/linkurious.js/wiki/media/louvain-after-400.png)
## Usage
See the following [example code](../../examples/plugin-louvain.html) for full usage.
To use, include all .js files under this folder, then execute it as follows.
```js
var louvainInstance = sigma.plugins.louvain(sigmaInstance.graph);
```
You can optionally provide an intermediary community partition assignement as follows:
```js
// Object with ids of nodes as properties and community number assigned as value.
var partitions_init = {'n1':0, 'n2':0, 'n3': 1};
var louvainInstance = sigma.plugins.louvain(sigmaInstance.graph, {
partitions: partitions_init
});
```
### Get and set communities
Communities are denoted by numerical identifiers, which are assigned to the nodes at the `_louvain` key unless an optional setter function is passed:
```js
var louvainInstance = sigma.plugins.louvain(sigmaInstance.graph);
// get community of node 'n0':
var c = sigmaInstance.graph.nodes('n0')._louvain;
```
```js
// run with a setter argument:
var louvainInstance = sigma.plugins.louvain(sigmaInstance.graph, {
setter: function(communityId) { this.my_community = communityId; }
});
// get community of node 'n0':
var c = sigmaInstance.graph.nodes('n0').my_community;
```
### Re-run the algorithm
Execute the algorithm again after calling `sigma.plugins.louvain()` as follows:
```js
louvainInstance.run();
```
You can optionally provide an intermediary community partition assignement as follows:
```js
// Object with ids of nodes as properties and community number assigned as value.
var partitions_init = {'n1':0, 'n2':0, 'n3': 1};
louvainInstance.run({ partitions: partitions_init });
```
### Count levels of hierarchy
The algorithm generates partitions of the graph (i.e. "communities") at multiple levels, with partitions at lower levels being included in partitions of the upper levels to form a dendogram. The higher level in the hierarchy the fewer partitions.
Count levels of hierarchy as follows:
```js
var nbLevels = louvainInstance.countLevels();
```
### Partitions
You may extract partitions at a specified level of hierarchy as follows:
```js
// Get partitions at the highest level:
var partitions = louvainInstance.getPartitions();
// equivalent to louvainInstance.getPartitions(louvainInstance.countLevels())
// Get partitions at the lowest level:
var partitions = louvainInstance.getPartitions({level: 1});
```
It may be useful to count the number of partitions:
```js
var partitions = louvainInstance.getPartitions();
var nbPartitions = louvainInstance.countPartitions(partitions);
```
### Assign communities to the nodes
Communities at the highest level are automatically assigned to the nodes. You may assign communities of a different level as follows:
```js
var nbLevels = louvainInstance.setResults({
level: 1
});
// using a specific setter function:
var nbLevels = louvainInstance.setResults({
level: 1
setter: function(communityId) { this.communityLevel1 = communityId; }
});
// get highest level community of node 'n0':
var c = sigmaInstance.graph.nodes('n0').my_community;
// get level 1 community of node 'n0':
var c = sigmaInstance.graph.nodes('n0').communityLevel1;
```
## Tip: How to color the nodes
The following example shows how to color nodes by community, assuming an array of colors:
```js
var colors = ["#D6C1B0", ..., "#9DDD5A"];
// Color nodes based on their community
sigmaInstance.graph.nodes().forEach(function(node) {
node.color = colors[node.my_community];
});
// refresh sigma renderers:
sigmaInstance.refresh({skipIndexation: true});
```
## Notes
The algorithm takes strength of ties into account by looking for the `weight` key in edges.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment