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