Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

340 lines
7.9 KiB

;(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);