Graph database Analysis of the Steam Network
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.
 
 
 
 

442 lines
12 KiB

;(function(undefined) {
'use strict';
if (typeof sigma === 'undefined')
throw 'sigma is not declared';
if (typeof conrad === 'undefined')
throw 'conrad is not declared';
// Initialize packages:
sigma.utils.pkg('sigma.renderers');
/**
* This function is the constructor of the canvas sigma's renderer.
*
* @param {sigma.classes.graph} graph The graph to render.
* @param {sigma.classes.camera} camera The camera.
* @param {configurable} settings The sigma instance settings
* function.
* @param {object} object The options object.
* @return {sigma.renderers.canvas} The renderer instance.
*/
sigma.renderers.canvas = function(graph, camera, settings, options) {
if (typeof options !== 'object')
throw 'sigma.renderers.canvas: Wrong arguments.';
if (!(options.container instanceof HTMLElement))
throw 'Container not found.';
var k,
i,
l,
a,
fn,
self = this;
sigma.classes.dispatcher.extend(this);
// Initialize main attributes:
Object.defineProperty(this, 'conradId', {
value: sigma.utils.id()
});
this.graph = graph;
this.camera = camera;
this.contexts = {};
this.domElements = {};
this.options = options;
this.container = this.options.container;
this.settings = (
typeof options.settings === 'object' &&
options.settings
) ?
settings.embedObjects(options.settings) :
settings;
// Node indexes:
this.nodesOnScreen = [];
this.edgesOnScreen = [];
// Conrad related attributes:
this.jobs = {};
// Find the prefix:
this.options.prefix = 'renderer' + this.conradId + ':';
// Initialize the DOM elements:
if (
!this.settings('batchEdgesDrawing')
) {
this.initDOM('canvas', 'scene');
this.contexts.edges = this.contexts.scene;
this.contexts.nodes = this.contexts.scene;
this.contexts.labels = this.contexts.scene;
} else {
this.initDOM('canvas', 'edges');
this.initDOM('canvas', 'scene');
this.contexts.nodes = this.contexts.scene;
this.contexts.labels = this.contexts.scene;
}
this.initDOM('canvas', 'mouse');
this.contexts.hover = this.contexts.mouse;
// Initialize captors:
this.captors = [];
a = this.options.captors || [sigma.captors.mouse, sigma.captors.touch];
for (i = 0, l = a.length; i < l; i++) {
fn = typeof a[i] === 'function' ? a[i] : sigma.captors[a[i]];
this.captors.push(
new fn(
this.domElements.mouse,
this.camera,
this.settings
)
);
}
// Deal with sigma events:
sigma.misc.bindEvents.call(this, this.options.prefix);
sigma.misc.drawHovers.call(this, this.options.prefix);
this.resize(false);
};
/**
* This method renders the graph on the canvases.
*
* @param {?object} options Eventually an object of options.
* @return {sigma.renderers.canvas} Returns the instance itself.
*/
sigma.renderers.canvas.prototype.render = function(options) {
options = options || {};
var a,
i,
k,
l,
o,
id,
end,
job,
start,
edges,
renderers,
rendererType,
batchSize,
tempGCO,
index = {},
graph = this.graph,
nodes = this.graph.nodes,
prefix = this.options.prefix || '',
drawEdges = this.settings(options, 'drawEdges'),
drawNodes = this.settings(options, 'drawNodes'),
drawLabels = this.settings(options, 'drawLabels'),
drawEdgeLabels = this.settings(options, 'drawEdgeLabels'),
embedSettings = this.settings.embedObjects(options, {
prefix: this.options.prefix
});
// Call the resize function:
this.resize(false);
// Check the 'hideEdgesOnMove' setting:
if (this.settings(options, 'hideEdgesOnMove'))
if (this.camera.isAnimated || this.camera.isMoving)
drawEdges = false;
// Apply the camera's view:
this.camera.applyView(
undefined,
this.options.prefix,
{
width: this.width,
height: this.height
}
);
// Clear canvases:
this.clear();
// Kill running jobs:
for (k in this.jobs)
if (conrad.hasJob(k))
conrad.killJob(k);
// Find which nodes are on screen:
this.edgesOnScreen = [];
this.nodesOnScreen = this.camera.quadtree.area(
this.camera.getRectangle(this.width, this.height)
);
for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++)
index[a[i].id] = a[i];
// Draw edges:
// - If settings('batchEdgesDrawing') is true, the edges are displayed per
// batches. If not, they are drawn in one frame.
if (drawEdges) {
// First, let's identify which edges to draw. To do this, we just keep
// every edges that have at least one extremity displayed according to
// the quadtree and the "hidden" attribute. We also do not keep hidden
// edges.
for (a = graph.edges(), i = 0, l = a.length; i < l; i++) {
o = a[i];
if (
(index[o.source] || index[o.target]) &&
(!o.hidden && !nodes(o.source).hidden && !nodes(o.target).hidden)
)
this.edgesOnScreen.push(o);
}
// If the "batchEdgesDrawing" settings is true, edges are batched:
if (this.settings(options, 'batchEdgesDrawing')) {
id = 'edges_' + this.conradId;
batchSize = embedSettings('canvasEdgesBatchSize');
edges = this.edgesOnScreen;
l = edges.length;
start = 0;
end = Math.min(edges.length, start + batchSize);
job = function() {
tempGCO = this.contexts.edges.globalCompositeOperation;
this.contexts.edges.globalCompositeOperation = 'destination-over';
renderers = sigma.canvas.edges;
for (i = start; i < end; i++) {
o = edges[i];
(renderers[
o.type || this.settings(options, 'defaultEdgeType')
] || renderers.def)(
o,
graph.nodes(o.source),
graph.nodes(o.target),
this.contexts.edges,
embedSettings
);
}
// Draw edge labels:
if (drawEdgeLabels) {
renderers = sigma.canvas.edges.labels;
for (i = start; i < end; i++) {
o = edges[i];
if (!o.hidden)
(renderers[
o.type || this.settings(options, 'defaultEdgeType')
] || renderers.def)(
o,
graph.nodes(o.source),
graph.nodes(o.target),
this.contexts.labels,
embedSettings
);
}
}
// Restore original globalCompositeOperation:
this.contexts.edges.globalCompositeOperation = tempGCO;
// Catch job's end:
if (end === edges.length) {
delete this.jobs[id];
return false;
}
start = end + 1;
end = Math.min(edges.length, start + batchSize);
return true;
};
this.jobs[id] = job;
conrad.addJob(id, job.bind(this));
// If not, they are drawn in one frame:
} else {
renderers = sigma.canvas.edges;
for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++) {
o = a[i];
(renderers[
o.type || this.settings(options, 'defaultEdgeType')
] || renderers.def)(
o,
graph.nodes(o.source),
graph.nodes(o.target),
this.contexts.edges,
embedSettings
);
}
// Draw edge labels:
// - No batching
if (drawEdgeLabels) {
renderers = sigma.canvas.edges.labels;
for (a = this.edgesOnScreen, i = 0, l = a.length; i < l; i++)
if (!a[i].hidden)
(renderers[
a[i].type || this.settings(options, 'defaultEdgeType')
] || renderers.def)(
a[i],
graph.nodes(a[i].source),
graph.nodes(a[i].target),
this.contexts.labels,
embedSettings
);
}
}
}
// Draw nodes:
// - No batching
if (drawNodes) {
renderers = sigma.canvas.nodes;
for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++)
if (!a[i].hidden)
(renderers[
a[i].type || this.settings(options, 'defaultNodeType')
] || renderers.def)(
a[i],
this.contexts.nodes,
embedSettings
);
}
// Draw labels:
// - No batching
if (drawLabels) {
renderers = sigma.canvas.labels;
for (a = this.nodesOnScreen, i = 0, l = a.length; i < l; i++)
if (!a[i].hidden)
(renderers[
a[i].type || this.settings(options, 'defaultNodeType')
] || renderers.def)(
a[i],
this.contexts.labels,
embedSettings
);
}
this.dispatchEvent('render');
return this;
};
/**
* This method creates a DOM element of the specified type, switches its
* position to "absolute", references it to the domElements attribute, and
* finally appends it to the container.
*
* @param {string} tag The label tag.
* @param {string} id The id of the element (to store it in "domElements").
*/
sigma.renderers.canvas.prototype.initDOM = function(tag, id) {
var dom = document.createElement(tag);
dom.style.position = 'absolute';
dom.setAttribute('class', 'sigma-' + id);
this.domElements[id] = dom;
this.container.appendChild(dom);
if (tag.toLowerCase() === 'canvas')
this.contexts[id] = dom.getContext('2d');
};
/**
* This method resizes each DOM elements in the container and stores the new
* dimensions. Then, it renders the graph.
*
* @param {?number} width The new width of the container.
* @param {?number} height The new height of the container.
* @return {sigma.renderers.canvas} Returns the instance itself.
*/
sigma.renderers.canvas.prototype.resize = function(w, h) {
var k,
oldWidth = this.width,
oldHeight = this.height,
pixelRatio = sigma.utils.getPixelRatio();
if (w !== undefined && h !== undefined) {
this.width = w;
this.height = h;
} else {
this.width = this.container.offsetWidth;
this.height = this.container.offsetHeight;
w = this.width;
h = this.height;
}
if (oldWidth !== this.width || oldHeight !== this.height) {
for (k in this.domElements) {
this.domElements[k].style.width = w + 'px';
this.domElements[k].style.height = h + 'px';
if (this.domElements[k].tagName.toLowerCase() === 'canvas') {
this.domElements[k].setAttribute('width', (w * pixelRatio) + 'px');
this.domElements[k].setAttribute('height', (h * pixelRatio) + 'px');
if (pixelRatio !== 1)
this.contexts[k].scale(pixelRatio, pixelRatio);
}
}
}
return this;
};
/**
* This method clears each canvas.
*
* @return {sigma.renderers.canvas} Returns the instance itself.
*/
sigma.renderers.canvas.prototype.clear = function() {
for (var k in this.contexts) {
this.contexts[k].clearRect(0, 0, this.width, this.height);
}
return this;
};
/**
* This method kills contexts and other attributes.
*/
sigma.renderers.canvas.prototype.kill = function() {
var k,
captor;
// Kill captors:
while ((captor = this.captors.pop()))
captor.kill();
delete this.captors;
// Kill contexts:
for (k in this.domElements) {
this.domElements[k].parentNode.removeChild(this.domElements[k]);
delete this.domElements[k];
delete this.contexts[k];
}
delete this.domElements;
delete this.contexts;
};
/**
* The labels, nodes and edges renderers are stored in the three following
* objects. When an element is drawn, its type will be checked and if a
* renderer with the same name exists, it will be used. If not found, the
* default renderer will be used instead.
*
* They are stored in different files, in the "./canvas" folder.
*/
sigma.utils.pkg('sigma.canvas.nodes');
sigma.utils.pkg('sigma.canvas.edges');
sigma.utils.pkg('sigma.canvas.labels');
}).call(this);