Commit fc3bc4ee authored by Romain Loth's avatar Romain Loth

WIP port: started twjs port to sigma 1.2

parent c8b06abb
...@@ -523,9 +523,19 @@ ...@@ -523,9 +523,19 @@
--> -->
<!--<script src="geomap/js/countries_iso3166.js" charset="utf-8" ></script>--> <!--<script src="geomap/js/countries_iso3166.js" charset="utf-8" ></script>-->
<!-- new sigma 1.2 imports -->
<script src="tinawebJS/sigma_v1.2/sigma.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/sigma_v1.2/plugins/sigma.layout.forceAtlas2/supervisor.js"></script>
<script src="tinawebJS/sigma_v1.2/plugins/sigma.layout.forceAtlas2/worker.js"></script>
<!-- new sigma (1.0c => 1.2) compatibility imports -->
<script src="tinawebJS/sigma_tools.js"></script>
<!-- classic tinawebJS imports -->
<script type="text/javascript" src="tinawebJS/jLouvain.js"></script> <script type="text/javascript" src="tinawebJS/jLouvain.js"></script>
<script src="tinawebJS/sigma.min.js" type="text/javascript" language="javascript"></script> <!-- <script src="tinawebJS/sigma.v1.customized.js" type="text/javascript" language="javascript"></script> -->
<script src="tinawebJS/sigma.forceatlas2.js" type="text/javascript" language="javascript"></script> <!-- <script src="tinawebJS/sigma.forceatlas2.js" type="text/javascript" language="javascript"></script> -->
<script src="settings_explorerjs.js" type="text/javascript" language="javascript"></script> <script src="settings_explorerjs.js" type="text/javascript" language="javascript"></script>
<script src="tinawebJS/sigma.parseCustom.js" type="text/javascript" language="javascript"></script> <script src="tinawebJS/sigma.parseCustom.js" type="text/javascript" language="javascript"></script>
<script src="extras_explorerjs.js" type="text/javascript" language="javascript"></script> <script src="extras_explorerjs.js" type="text/javascript" language="javascript"></script>
......
explorerjs.html
\ No newline at end of file
...@@ -81,6 +81,8 @@ if(!isUndef(getUrlParam.mode)) { // if {db|api}.json ...@@ -81,6 +81,8 @@ if(!isUndef(getUrlParam.mode)) { // if {db|api}.json
}); });
file = (Array.isArray(TW.mainfile))?TW.mainfile[0]:TW.mainfile; file = (Array.isArray(TW.mainfile))?TW.mainfile[0]:TW.mainfile;
} }
// RES == { OK: true, format: "json", data: Object }
var RES = AjaxSync({ URL: file }); var RES = AjaxSync({ URL: file });
if(RES["OK"]) { if(RES["OK"]) {
...@@ -88,6 +90,8 @@ if(RES["OK"]) { ...@@ -88,6 +90,8 @@ if(RES["OK"]) {
var fileparam;// = { db|api.json , somefile.json|gexf } var fileparam;// = { db|api.json , somefile.json|gexf }
var the_data = RES["data"]; var the_data = RES["data"];
console.log('RES', RES)
var the_file = ""; var the_file = "";
if ( !isUndef(getUrlParam.mode) && getUrlParam.mode=="db.json") { if ( !isUndef(getUrlParam.mode) && getUrlParam.mode=="db.json") {
...@@ -112,13 +116,13 @@ if(RES["OK"]) { ...@@ -112,13 +116,13 @@ if(RES["OK"]) {
var files_selector = '<select onchange="jsActionOnGexfSelector(this.value , true);">' var files_selector = '<select onchange="jsActionOnGexfSelector(this.value , true);">'
for( var path in the_data ) { for( var path in the_data ) {
pr("\t"+path+" has:") console.log("\t"+path+" has:")
pr(the_data[path]) console.log(the_data[path])
var the_gexfs = the_data[path]["gexfs"] var the_gexfs = the_data[path]["gexfs"]
pr("\t\tThese are the available Gexfs:") console.log("\t\tThese are the available Gexfs:")
for(var gexf in the_gexfs) { for(var gexf in the_gexfs) {
var gexfBasename = gexf.replace(/\.gexf$/, "") // more human-readable in the menu var gexfBasename = gexf.replace(/\.gexf$/, "") // more human-readable in the menu
pr("\t\t\t"+gexf+ " -> table:" +the_gexfs[gexf]["semantic"]["table"] ) console.log("\t\t\t"+gexf+ " -> table:" +the_gexfs[gexf]["semantic"]["table"] )
TW.field[path+"/"+gexf] = the_gexfs[gexf]["semantic"]["table"] TW.field[path+"/"+gexf] = the_gexfs[gexf]["semantic"]["table"]
TW.gexfDict[path+"/"+gexf] = gexf TW.gexfDict[path+"/"+gexf] = gexf
...@@ -133,19 +137,19 @@ if(RES["OK"]) { ...@@ -133,19 +137,19 @@ if(RES["OK"]) {
pr("\n============================\n") console.log("\n============================\n")
pr(TW.field) console.log(TW.field)
pr(TW.gexfDict) console.log(TW.gexfDict)
var sub_RES = AjaxSync({ URL: fileparam }); var sub_RES = AjaxSync({ URL: fileparam });
the_data = sub_RES["data"] the_data = sub_RES["data"]
fileparam = sub_RES["format"] fileparam = sub_RES["format"]
pr(the_data.length) console.log(the_data.length)
pr(fileparam) console.log(fileparam)
getUrlParam.file=the_file; getUrlParam.file=the_file;
console.log(" . .. . -. - .- . - -.") console.log(" . .. . -. - .- . - -.")
console.log(getUrlParam.file) console.log(getUrlParam.file)
pr("\n============================\n") console.log("\n============================\n")
...@@ -164,8 +168,8 @@ if(RES["OK"]) { ...@@ -164,8 +168,8 @@ if(RES["OK"]) {
start = new ParseCustom( fileparam , the_data ); start = new ParseCustom( fileparam , the_data );
categories = start.scanFile(); //user should choose the order of categories categories = start.scanFile(); //user should choose the order of categories
pr("Categories: ") console.log("Categories: ")
pr(categories) console.log(categories)
var possibleStates = makeSystemStates( categories ) var possibleStates = makeSystemStates( categories )
var initialState = buildInitialState( categories ) //[true,false]// var initialState = buildInitialState( categories ) //[true,false]//
...@@ -177,8 +181,8 @@ if(RES["OK"]) { ...@@ -177,8 +181,8 @@ if(RES["OK"]) {
TW.nodes1 = dicts.n1;//not used TW.nodes1 = dicts.n1;//not used
var catDict = dicts.catDict var catDict = dicts.catDict
pr("CategoriesDict: ") console.log("CategoriesDict: ")
pr(catDict) console.log(catDict)
TW.categoriesIndex = categories;//to_remove TW.categoriesIndex = categories;//to_remove
TW.catSoc = categories[0];//to_remove TW.catSoc = categories[0];//to_remove
...@@ -200,11 +204,31 @@ if(RES["OK"]) { ...@@ -200,11 +204,31 @@ if(RES["OK"]) {
// [ Poblating the Sigma-Graph ] // [ Poblating the Sigma-Graph ]
var sigma_utils = new SigmaUtils(); var sigma_utils = new SigmaUtils();
TW.partialGraph = sigma.init(document.getElementById('sigma-example')) console.warn("sigma :", sigma)
.drawingProperties(sigmaJsDrawingProperties)
.graphProperties(sigmaJsGraphProperties)
.mouseProperties(sigmaJsMouseProperties); // TW.partialGraph = sigma.init(document.getElementById('sigma-example'))
TW.partialGraph = sigma_utils.FillGraph( initialState , catDict , dicts.nodes , dicts.edges , TW.partialGraph ); // .drawingProperties(sigmaJsDrawingProperties)
// .graphProperties(sigmaJsGraphProperties)
// .mouseProperties(sigmaJsMouseProperties);
TW.graphData = {nodes: [], edges: []}
TW.graphData = sigma_utils.FillGraph( initialState , catDict , dicts.nodes , dicts.edges , TW.graphData );
// ==================================================================
// sigma js library invocation (https://github.com/jacomyal/sigma.js)
// ==================================================================
TW.partialGraph = new sigma({
graph: TW.graphData,
container: 'sigma-example'
});
TW.partialGraph.states = [] TW.partialGraph.states = []
TW.partialGraph.states[0] = false; TW.partialGraph.states[0] = false;
TW.partialGraph.states[1] = TW.SystemStates; TW.partialGraph.states[1] = TW.SystemStates;
...@@ -336,7 +360,16 @@ if(RES["OK"]) { ...@@ -336,7 +360,16 @@ if(RES["OK"]) {
} }
}).index(); }).index();
TW.partialGraph.zoomTo(TW.partialGraph._core.width / 2, TW.partialGraph._core.height / 2, 0.8).draw();
// REFA FIXME
// TW.partialGraph.zoomTo(TW.partialGraph._core.width / 2, TW.partialGraph._core.height / 2, 0.8).draw();
// TW.partialGraph.zoomTo(
// TW.partialGraph._core.width / 2,
// TW.partialGraph._core.height / 2, 0.8
// ).draw();
// fa2enabled=true; TW.partialGraph.zoomTo(TW.partialGraph._core.width / 2, TW.partialGraph._core.height / 2, 0.8).draw(); // fa2enabled=true; TW.partialGraph.zoomTo(TW.partialGraph._core.width / 2, TW.partialGraph._core.height / 2, 0.8).draw();
// $.doTimeout(1,function(){ // $.doTimeout(1,function(){
......
// (transitional package): sigmaTools for functions we had in sam's customized sigma v.1 under sigma.tools
var sigmaTools = {};
sigmaTools = (function(stools) {
stools.rgbToHex = function(R, G, B) {
return stools.toHex(R) + stools.toHex(G) + stools.toHex(B);
};
stools.toHex = function(n) {
n = parseInt(n, 10);
if (isNaN(n)) {
return '00';
}
n = Math.max(0, Math.min(n, 255));
return '0123456789ABCDEF'.charAt((n - n % 16) / 16) +
'0123456789ABCDEF'.charAt(n % 16);
};
return stools
})(sigmaTools);
module.exports = function(grunt) {
// Setting grunt base as sigma's root directory
grunt.file.setBase('../../');
// Registering needed files
var files = ['supervisor.js', 'worker.js'].map(function(p) {
return __dirname + '/' + p;
});
// Project configuration:
grunt.initConfig({
forceAtlas2: {
prod: {
files: {
'build/plugins/sigma.layout.forceAtlas2.min.js': files
}
}
}
});
// Loading tasks
grunt.loadTasks(__dirname + '/tasks');
// By default, we will crush and then minify
grunt.registerTask('default', ['forceAtlas2:prod']);
};
sigma.layout.forceAtlas2
========================
Algorithm by [Mathieu Jacomy](https://github.com/jacomyma).
Plugin by [Guillaume Plique](https://github.com/Yomguithereal).
---
This plugin implements [ForceAtlas2](http://www.plosone.org/article/info%3Adoi%2F10.1371%2Fjournal.pone.0098679), a force-directed layout algorithm.
For optimization purposes, the algorithm's computations are delegated to a web worker.
## Methods
**sigma.startForceAtlas2**
Starts or unpauses the layout. It is possible to pass a configuration if this is the first time you start the layout.
```js
sigmaInstance.startForceAtlas2(config);
```
**sigma.stopForceAtlas2**
Pauses the layout.
```js
sigmaInstance.stopForceAtlas2();
```
**sigma.configForceAtlas2**
Changes the layout's configuration.
```js
sigmaInstance.configForceAtlas2(config);
```
**sigma.killForceAtlas2**
Completely stops the layout and terminates the assiociated worker. You can still restart it later, but a new worker will have to initialize.
```js
sigmaInstance.killForceAtlas2();
```
**sigma.isForceAtlas2Running**
Returns whether ForceAtlas2 is running.
```js
sigmaInstance.isForceAtlas2Running();
```
## Configuration
*Algorithm configuration*
* **linLogMode**: *boolean* `false`
* **outboundAttractionDistribution** *boolean* `false`
* **adjustSizes** *boolean* `false`
* **edgeWeightInfluence** *number* `0`
* **scalingRatio** *number* `1`
* **strongGravityMode** *boolean* `false`
* **gravity** *number* `1`
* **barnesHutOptimize** *boolean* `true`: should we use the algorithm's Barnes-Hut to improve repulsion's scalability (`O(n²)` to `O(nlog(n))`)? This is useful for large graph but harmful to small ones.
* **barnesHutTheta** *number* `0.5`
* **slowDown** *number* `1`
* **startingIterations** *integer* `1`: number of iterations to be run before the first render.
* **iterationsPerRender** *integer* `1`: number of iterations to be run before each render.
*Supervisor configuration*
* **worker** *boolean* `true`: should the layout use a web worker?
* **workerUrl** *string* : path to the worker file if needed because your browser does not support blob workers.
## Notes
1. The layout won't stop by itself, so if you want it to stop, you will have to trigger it explicitly.
;(function(undefined) {
'use strict';
if (typeof sigma === 'undefined')
throw 'sigma is not declared';
/**
* Sigma ForceAtlas2.5 Supervisor
* ===============================
*
* Author: Guillaume Plique (Yomguithereal)
* Version: 0.1
*/
var _root = this;
/**
* Feature detection
* ------------------
*/
var webWorkers = 'Worker' in _root;
/**
* Supervisor Object
* ------------------
*/
function Supervisor(sigInst, options) {
var _this = this,
workerFn = sigInst.getForceAtlas2Worker &&
sigInst.getForceAtlas2Worker();
options = options || {};
// _root URL Polyfill
_root.URL = _root.URL || _root.webkitURL;
// Properties
this.sigInst = sigInst;
this.graph = this.sigInst.graph;
this.ppn = 10;
this.ppe = 3;
this.config = {};
this.shouldUseWorker =
options.worker === false ? false : true && webWorkers;
this.workerUrl = options.workerUrl;
// State
this.started = false;
this.running = false;
// Web worker or classic DOM events?
if (this.shouldUseWorker) {
if (!this.workerUrl) {
var blob = this.makeBlob(workerFn);
this.worker = new Worker(URL.createObjectURL(blob));
}
else {
this.worker = new Worker(this.workerUrl);
}
// Post Message Polyfill
this.worker.postMessage =
this.worker.webkitPostMessage || this.worker.postMessage;
}
else {
eval(workerFn);
}
// Worker message receiver
this.msgName = (this.worker) ? 'message' : 'newCoords';
this.listener = function(e) {
// Retrieving data
_this.nodesByteArray = new Float32Array(e.data.nodes);
// If ForceAtlas2 is running, we act accordingly
if (_this.running) {
// Applying layout
_this.applyLayoutChanges();
// Send data back to worker and loop
_this.sendByteArrayToWorker();
// Rendering graph
_this.sigInst.refresh();
}
};
(this.worker || document).addEventListener(this.msgName, this.listener);
// Filling byteArrays
this.graphToByteArrays();
// Binding on kill to properly terminate layout when parent is killed
sigInst.bind('kill', function() {
sigInst.killForceAtlas2();
});
}
Supervisor.prototype.makeBlob = function(workerFn) {
var blob;
try {
blob = new Blob([workerFn], {type: 'application/javascript'});
}
catch (e) {
_root.BlobBuilder = _root.BlobBuilder ||
_root.WebKitBlobBuilder ||
_root.MozBlobBuilder;
blob = new BlobBuilder();
blob.append(workerFn);
blob = blob.getBlob();
}
return blob;
};
Supervisor.prototype.graphToByteArrays = function() {
var nodes = this.graph.nodes(),
edges = this.graph.edges(),
nbytes = nodes.length * this.ppn,
ebytes = edges.length * this.ppe,
nIndex = {},
i,
j,
l;
// Allocating Byte arrays with correct nb of bytes
this.nodesByteArray = new Float32Array(nbytes);
this.edgesByteArray = new Float32Array(ebytes);
// Iterate through nodes
for (i = j = 0, l = nodes.length; i < l; i++) {
// Populating index
nIndex[nodes[i].id] = j;
// Populating byte array
this.nodesByteArray[j] = nodes[i].x;
this.nodesByteArray[j + 1] = nodes[i].y;
this.nodesByteArray[j + 2] = 0;
this.nodesByteArray[j + 3] = 0;
this.nodesByteArray[j + 4] = 0;
this.nodesByteArray[j + 5] = 0;
this.nodesByteArray[j + 6] = 1 + this.graph.degree(nodes[i].id);
this.nodesByteArray[j + 7] = 1;
this.nodesByteArray[j + 8] = nodes[i].size;
this.nodesByteArray[j + 9] = 0;
j += this.ppn;
}
// Iterate through edges
for (i = j = 0, l = edges.length; i < l; i++) {
this.edgesByteArray[j] = nIndex[edges[i].source];
this.edgesByteArray[j + 1] = nIndex[edges[i].target];
this.edgesByteArray[j + 2] = edges[i].weight || 0;
j += this.ppe;
}
};
// TODO: make a better send function
Supervisor.prototype.applyLayoutChanges = function() {
var nodes = this.graph.nodes(),
j = 0,
realIndex;
// Moving nodes
for (var i = 0, l = this.nodesByteArray.length; i < l; i += this.ppn) {
nodes[j].x = this.nodesByteArray[i];
nodes[j].y = this.nodesByteArray[i + 1];
j++;
}
};
Supervisor.prototype.sendByteArrayToWorker = function(action) {
var content = {
action: action || 'loop',
nodes: this.nodesByteArray.buffer
};
var buffers = [this.nodesByteArray.buffer];
if (action === 'start') {
content.config = this.config || {};
content.edges = this.edgesByteArray.buffer;
buffers.push(this.edgesByteArray.buffer);
}
if (this.shouldUseWorker)
this.worker.postMessage(content, buffers);
else
_root.postMessage(content, '*');
};
Supervisor.prototype.start = function() {
if (this.running)
return;
this.running = true;
// Do not refresh edgequadtree during layout:
var k,
c;
for (k in this.sigInst.cameras) {
c = this.sigInst.cameras[k];
c.edgequadtree._enabled = false;
}
if (!this.started) {
// Sending init message to worker
this.sendByteArrayToWorker('start');
this.started = true;
}
else {
this.sendByteArrayToWorker();
}
};
Supervisor.prototype.stop = function() {
if (!this.running)
return;
// Allow to refresh edgequadtree:
var k,
c,
bounds;
for (k in this.sigInst.cameras) {
c = this.sigInst.cameras[k];
c.edgequadtree._enabled = true;
// Find graph boundaries:
bounds = sigma.utils.getBoundaries(
this.graph,
c.readPrefix
);
// Refresh edgequadtree:
if (c.settings('drawEdges') && c.settings('enableEdgeHovering'))
c.edgequadtree.index(this.sigInst.graph, {
prefix: c.readPrefix,
bounds: {
x: bounds.minX,
y: bounds.minY,
width: bounds.maxX - bounds.minX,
height: bounds.maxY - bounds.minY
}
});
}
this.running = false;
};
Supervisor.prototype.killWorker = function() {
if (this.worker) {
this.worker.terminate();
}
else {
_root.postMessage({action: 'kill'}, '*');
document.removeEventListener(this.msgName, this.listener);
}
};
Supervisor.prototype.configure = function(config) {
// Setting configuration
this.config = config;
if (!this.started)
return;
var data = {action: 'config', config: this.config};
if (this.shouldUseWorker)
this.worker.postMessage(data);
else
_root.postMessage(data, '*');
};
/**
* Interface
* ----------
*/
sigma.prototype.startForceAtlas2 = function(config) {
// Create supervisor if undefined
if (!this.supervisor)
this.supervisor = new Supervisor(this, config);
// Configuration provided?
if (config)
this.supervisor.configure(config);
// Start algorithm
this.supervisor.start();
return this;
};
sigma.prototype.stopForceAtlas2 = function() {
if (!this.supervisor)
return this;
// Pause algorithm
this.supervisor.stop();
return this;
};
sigma.prototype.killForceAtlas2 = function() {
if (!this.supervisor)
return this;
// Stop Algorithm
this.supervisor.stop();
// Kill Worker
this.supervisor.killWorker();
// Kill supervisor
this.supervisor = null;
return this;
};
sigma.prototype.configForceAtlas2 = function(config) {
if (!this.supervisor)
this.supervisor = new Supervisor(this, config);
this.supervisor.configure(config);
return this;
};
sigma.prototype.isForceAtlas2Running = function(config) {
return !!this.supervisor && this.supervisor.running;
};
}).call(this);
/*
* grunt-forceAtlas2
*
* This task crush and minify Force Atlas 2 code.
*/
var uglify = require('uglify-js');
// Shorteners
function minify(string) {
return uglify.minify(string, {fromString: true}).code;
}
// Crushing function
function crush(fnString) {
var pattern,
i,
l;
var np = [
'x',
'y',
'dx',
'dy',
'old_dx',
'old_dy',
'mass',
'convergence',
'size',
'fixed'
];
var ep = [
'source',
'target',
'weight'
];
var rp = [
'node',
'centerX',
'centerY',
'size',
'nextSibling',
'firstChild',
'mass',
'massCenterX',
'massCenterY'
];
// Replacing matrix accessors by incremented indexes
for (i = 0, l = rp.length; i < l; i++) {
pattern = new RegExp('rp\\(([^,]*), \'' + rp[i] + '\'\\)', 'g');
fnString = fnString.replace(
pattern,
(i === 0) ? '$1' : '$1 + ' + i
);
}
for (i = 0, l = np.length; i < l; i++) {
pattern = new RegExp('np\\(([^,]*), \'' + np[i] + '\'\\)', 'g');
fnString = fnString.replace(
pattern,
(i === 0) ? '$1' : '$1 + ' + i
);
}
for (i = 0, l = ep.length; i < l; i++) {
pattern = new RegExp('ep\\(([^,]*), \'' + ep[i] + '\'\\)', 'g');
fnString = fnString.replace(
pattern,
(i === 0) ? '$1' : '$1 + ' + i
);
}
return fnString;
}
// Cleaning function
function clean(string) {
return string.replace(
/function crush\(fnString\)/,
'var crush = null; function no_crush(fnString)'
);
}
module.exports = function(grunt) {
// Force atlas grunt multitask
function multitask() {
// Merge task-specific and/or target-specific options with these defaults.
var options = this.options({});
// Iterate over all specified file groups.
this.files.forEach(function(f) {
// Concat specified files.
var src = f.src.filter(function(filepath) {
// Warn on and remove invalid source files (if nonull was set).
if (!grunt.file.exists(filepath)) {
grunt.log.warn('Source file "' + filepath + '" not found.');
return false;
} else {
return true;
}
}).map(function(filepath) {
// Read file source.
return grunt.file.read(filepath);
}).join('\n');
// Crushing, cleaning and minifying
src = minify(clean(crush(src)));
// Write the destination file.
grunt.file.write(f.dest, src);
// Print a success message.
grunt.log.writeln('File "' + f.dest + '" created.');
});
}
// Registering the task
grunt.registerMultiTask(
'forceAtlas2',
'A grunt task to crush and minify ForceAtlas2.',
multitask
);
};
This diff is collapsed.
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