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