|
|
- ;(function(undefined) {
- 'use strict';
-
- if (typeof sigma === 'undefined')
- throw 'sigma 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.webgl = function(graph, camera, settings, options) {
- if (typeof options !== 'object')
- throw 'sigma.renderers.webgl: Wrong arguments.';
-
- if (!(options.container instanceof HTMLElement))
- throw 'Container not found.';
-
- var k,
- i,
- l,
- a,
- fn,
- _self = this;
-
- sigma.classes.dispatcher.extend(this);
-
- // Conrad related attributes:
- this.jobs = {};
-
- Object.defineProperty(this, 'conradId', {
- value: sigma.utils.id()
- });
-
- // Initialize main attributes:
- 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;
-
- // Find the prefix:
- this.options.prefix = this.camera.readPrefix;
-
- // Initialize programs hash
- Object.defineProperty(this, 'nodePrograms', {
- value: {}
- });
- Object.defineProperty(this, 'edgePrograms', {
- value: {}
- });
- Object.defineProperty(this, 'nodeFloatArrays', {
- value: {}
- });
- Object.defineProperty(this, 'edgeFloatArrays', {
- value: {}
- });
- Object.defineProperty(this, 'edgeIndicesArrays', {
- value: {}
- });
-
- // Initialize the DOM elements:
- if (this.settings(options, 'batchEdgesDrawing')) {
- this.initDOM('canvas', 'edges', true);
- this.initDOM('canvas', 'nodes', true);
- } else {
- this.initDOM('canvas', 'scene', true);
- this.contexts.nodes = this.contexts.scene;
- this.contexts.edges = this.contexts.scene;
- }
-
- this.initDOM('canvas', 'labels');
- 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.camera.prefix);
- sigma.misc.drawHovers.call(this, this.camera.prefix);
-
- this.resize();
- };
-
-
-
-
- /**
- * This method will generate the nodes and edges float arrays. This step is
- * separated from the "render" method, because to keep WebGL efficient, since
- * all the camera and middlewares are modelised as matrices and they do not
- * require the float arrays to be regenerated.
- *
- * Basically, when the user moves the camera or applies some specific linear
- * transformations, this process step will be skipped, and the "render"
- * method will efficiently refresh the rendering.
- *
- * And when the user modifies the graph colors or positions (applying a new
- * layout or filtering the colors, for instance), this "process" step will be
- * required to regenerate the float arrays.
- *
- * @return {sigma.renderers.webgl} Returns the instance itself.
- */
- sigma.renderers.webgl.prototype.process = function() {
- var a,
- i,
- l,
- k,
- type,
- renderer,
- graph = this.graph,
- options = sigma.utils.extend(options, this.options),
- defaultEdgeType = this.settings(options, 'defaultEdgeType'),
- defaultNodeType = this.settings(options, 'defaultNodeType');
-
- // Empty float arrays:
- for (k in this.nodeFloatArrays)
- delete this.nodeFloatArrays[k];
-
- for (k in this.edgeFloatArrays)
- delete this.edgeFloatArrays[k];
-
- for (k in this.edgeIndicesArrays)
- delete this.edgeIndicesArrays[k];
-
- // Sort edges and nodes per types:
- for (a = graph.edges(), i = 0, l = a.length; i < l; i++) {
- type = a[i].type || defaultEdgeType;
- k = (type && sigma.webgl.edges[type]) ? type : 'def';
-
- if (!this.edgeFloatArrays[k])
- this.edgeFloatArrays[k] = {
- edges: []
- };
-
- this.edgeFloatArrays[k].edges.push(a[i]);
- }
-
- for (a = graph.nodes(), i = 0, l = a.length; i < l; i++) {
- type = a[i].type || defaultNodeType;
- k = (type && sigma.webgl.nodes[type]) ? type : 'def';
-
- if (!this.nodeFloatArrays[k])
- this.nodeFloatArrays[k] = {
- nodes: []
- };
-
- this.nodeFloatArrays[k].nodes.push(a[i]);
- }
-
- // Push edges:
- for (k in this.edgeFloatArrays) {
- renderer = sigma.webgl.edges[k];
- a = this.edgeFloatArrays[k].edges;
-
- // Creating the necessary arrays
- this.edgeFloatArrays[k].array = new Float32Array(
- a.length * renderer.POINTS * renderer.ATTRIBUTES
- );
-
- for (i = 0, l = a.length; i < l; i++) {
-
- // Just check that the edge and both its extremities are visible:
- if (
- !a[i].hidden &&
- !graph.nodes(a[i].source).hidden &&
- !graph.nodes(a[i].target).hidden
- )
- renderer.addEdge(
- a[i],
- graph.nodes(a[i].source),
- graph.nodes(a[i].target),
- this.edgeFloatArrays[k].array,
- i * renderer.POINTS * renderer.ATTRIBUTES,
- options.prefix,
- this.settings
- );
- }
-
- if (typeof renderer.computeIndices === 'function')
- this.edgeIndicesArrays[k] = renderer.computeIndices(
- this.edgeFloatArrays[k].array
- );
- }
-
- // Push nodes:
- for (k in this.nodeFloatArrays) {
- renderer = sigma.webgl.nodes[k];
- a = this.nodeFloatArrays[k].nodes;
-
- // Creating the necessary arrays
- this.nodeFloatArrays[k].array = new Float32Array(
- a.length * renderer.POINTS * renderer.ATTRIBUTES
- );
-
- for (i = 0, l = a.length; i < l; i++) {
- if (!this.nodeFloatArrays[k].array)
- this.nodeFloatArrays[k].array = new Float32Array(
- a.length * renderer.POINTS * renderer.ATTRIBUTES
- );
-
- // Just check that the edge and both its extremities are visible:
- if (
- !a[i].hidden
- )
- renderer.addNode(
- a[i],
- this.nodeFloatArrays[k].array,
- i * renderer.POINTS * renderer.ATTRIBUTES,
- options.prefix,
- this.settings
- );
- }
- }
-
- return this;
- };
-
-
-
-
- /**
- * This method renders the graph. It basically calls each program (and
- * generate them if they do not exist yet) to render nodes and edges, batched
- * per renderer.
- *
- * As in the canvas renderer, it is possible to display edges, nodes and / or
- * labels in batches, to make the whole thing way more scalable.
- *
- * @param {?object} params Eventually an object of options.
- * @return {sigma.renderers.webgl} Returns the instance itself.
- */
- sigma.renderers.webgl.prototype.render = function(params) {
- var a,
- i,
- l,
- k,
- o,
- program,
- renderer,
- self = this,
- graph = this.graph,
- nodesGl = this.contexts.nodes,
- edgesGl = this.contexts.edges,
- matrix = this.camera.getMatrix(),
- options = sigma.utils.extend(params, this.options),
- drawLabels = this.settings(options, 'drawLabels'),
- drawEdges = this.settings(options, 'drawEdges'),
- drawNodes = this.settings(options, 'drawNodes');
-
- // 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;
-
- // Clear canvases:
- this.clear();
-
- // Translate matrix to [width/2, height/2]:
- matrix = sigma.utils.matrices.multiply(
- matrix,
- sigma.utils.matrices.translation(this.width / 2, this.height / 2)
- );
-
- // Kill running jobs:
- for (k in this.jobs)
- if (conrad.hasJob(k))
- conrad.killJob(k);
-
- if (drawEdges) {
- if (this.settings(options, 'batchEdgesDrawing'))
- (function() {
- var a,
- k,
- i,
- id,
- job,
- arr,
- end,
- start,
- indices,
- renderer,
- batchSize,
- currentProgram;
-
- id = 'edges_' + this.conradId;
- batchSize = this.settings(options, 'webglEdgesBatchSize');
- a = Object.keys(this.edgeFloatArrays);
-
- if (!a.length)
- return;
- i = 0;
- renderer = sigma.webgl.edges[a[i]];
- arr = this.edgeFloatArrays[a[i]].array;
- indices = this.edgeIndicesArrays[a[i]];
- start = 0;
- end = Math.min(
- start + batchSize * renderer.POINTS,
- arr.length / renderer.ATTRIBUTES
- );
-
- job = function() {
- // Check program:
- if (!this.edgePrograms[a[i]])
- this.edgePrograms[a[i]] = renderer.initProgram(edgesGl);
-
- if (start < end) {
- edgesGl.useProgram(this.edgePrograms[a[i]]);
- renderer.render(
- edgesGl,
- this.edgePrograms[a[i]],
- arr,
- {
- settings: this.settings,
- matrix: matrix,
- width: this.width,
- height: this.height,
- ratio: this.camera.ratio,
- scalingRatio: this.settings(
- options,
- 'webglOversamplingRatio'
- ),
- start: start,
- count: end - start,
- indicesData: indices
- }
- );
- }
-
- // Catch job's end:
- if (
- end >= arr.length / renderer.ATTRIBUTES &&
- i === a.length - 1
- ) {
- delete this.jobs[id];
- return false;
- }
-
- if (end >= arr.length / renderer.ATTRIBUTES) {
- i++;
- arr = this.edgeFloatArrays[a[i]].array;
- renderer = sigma.webgl.edges[a[i]];
- start = 0;
- end = Math.min(
- start + batchSize * renderer.POINTS,
- arr.length / renderer.ATTRIBUTES
- );
- } else {
- start = end;
- end = Math.min(
- start + batchSize * renderer.POINTS,
- arr.length / renderer.ATTRIBUTES
- );
- }
-
- return true;
- };
-
- this.jobs[id] = job;
- conrad.addJob(id, job.bind(this));
- }).call(this);
- else {
- for (k in this.edgeFloatArrays) {
- renderer = sigma.webgl.edges[k];
-
- // Check program:
- if (!this.edgePrograms[k])
- this.edgePrograms[k] = renderer.initProgram(edgesGl);
-
- // Render
- if (this.edgeFloatArrays[k]) {
- edgesGl.useProgram(this.edgePrograms[k]);
- renderer.render(
- edgesGl,
- this.edgePrograms[k],
- this.edgeFloatArrays[k].array,
- {
- settings: this.settings,
- matrix: matrix,
- width: this.width,
- height: this.height,
- ratio: this.camera.ratio,
- scalingRatio: this.settings(options, 'webglOversamplingRatio'),
- indicesData: this.edgeIndicesArrays[k]
- }
- );
- }
- }
- }
- }
-
- if (drawNodes) {
- // Enable blending:
- nodesGl.blendFunc(nodesGl.SRC_ALPHA, nodesGl.ONE_MINUS_SRC_ALPHA);
- nodesGl.enable(nodesGl.BLEND);
-
- for (k in this.nodeFloatArrays) {
- renderer = sigma.webgl.nodes[k];
-
- // Check program:
- if (!this.nodePrograms[k])
- this.nodePrograms[k] = renderer.initProgram(nodesGl);
-
- // Render
- if (this.nodeFloatArrays[k]) {
- nodesGl.useProgram(this.nodePrograms[k]);
- renderer.render(
- nodesGl,
- this.nodePrograms[k],
- this.nodeFloatArrays[k].array,
- {
- settings: this.settings,
- matrix: matrix,
- width: this.width,
- height: this.height,
- ratio: this.camera.ratio,
- scalingRatio: this.settings(options, 'webglOversamplingRatio')
- }
- );
- }
- }
- }
-
- if (drawLabels) {
- a = this.camera.quadtree.area(
- this.camera.getRectangle(this.width, this.height)
- );
-
- // Apply camera view to these nodes:
- this.camera.applyView(
- undefined,
- undefined,
- {
- nodes: a,
- edges: [],
- width: this.width,
- height: this.height
- }
- );
-
- o = function(key) {
- return self.settings({
- prefix: self.camera.prefix
- }, key);
- };
-
- for (i = 0, l = a.length; i < l; i++)
- if (!a[i].hidden)
- (
- sigma.canvas.labels[
- a[i].type ||
- this.settings(options, 'defaultNodeType')
- ] || sigma.canvas.labels.def
- )(a[i], this.contexts.labels, o);
- }
-
- 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").
- * @param {?boolean} webgl Will init the WebGL context if true.
- */
- sigma.renderers.webgl.prototype.initDOM = function(tag, id, webgl) {
- var gl,
- dom = document.createElement(tag),
- self = this;
-
- 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(webgl ? 'experimental-webgl' : '2d', {
- preserveDrawingBuffer: true
- });
-
- // Adding webgl context loss listeners
- if (webgl) {
- dom.addEventListener('webglcontextlost', function(e) {
- e.preventDefault();
- }, false);
-
- dom.addEventListener('webglcontextrestored', function(e) {
- self.render();
- }, false);
- }
- }
- };
-
- /**
- * 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.webgl} Returns the instance itself.
- */
- sigma.renderers.webgl.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') {
- // If simple 2D canvas:
- if (this.contexts[k] && this.contexts[k].scale) {
- 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);
- } else {
- this.domElements[k].setAttribute(
- 'width',
- (w * this.settings('webglOversamplingRatio')) + 'px'
- );
- this.domElements[k].setAttribute(
- 'height',
- (h * this.settings('webglOversamplingRatio')) + 'px'
- );
- }
- }
- }
- }
-
- // Scale:
- for (k in this.contexts)
- if (this.contexts[k] && this.contexts[k].viewport)
- this.contexts[k].viewport(
- 0,
- 0,
- this.width * this.settings('webglOversamplingRatio'),
- this.height * this.settings('webglOversamplingRatio')
- );
-
- return this;
- };
-
- /**
- * This method clears each canvas.
- *
- * @return {sigma.renderers.webgl} Returns the instance itself.
- */
- sigma.renderers.webgl.prototype.clear = function() {
- this.contexts.labels.clearRect(0, 0, this.width, this.height);
- this.contexts.nodes.clear(this.contexts.nodes.COLOR_BUFFER_BIT);
- this.contexts.edges.clear(this.contexts.edges.COLOR_BUFFER_BIT);
-
- return this;
- };
-
- /**
- * This method kills contexts and other attributes.
- */
- sigma.renderers.webgl.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 object "sigma.webgl.nodes" contains the different WebGL node
- * renderers. The default one draw nodes as discs. Here are the attributes
- * any node renderer must have:
- *
- * {number} POINTS The number of points required to draw a node.
- * {number} ATTRIBUTES The number of attributes needed to draw one point.
- * {function} addNode A function that adds a node to the data stack that
- * will be given to the buffer. Here is the arguments:
- * > {object} node
- * > {number} index The node index in the
- * nodes array.
- * > {Float32Array} data The stack.
- * > {object} options Some options.
- * {function} render The function that will effectively render the nodes
- * into the buffer.
- * > {WebGLRenderingContext} gl
- * > {WebGLProgram} program
- * > {Float32Array} data The stack to give to the
- * buffer.
- * > {object} params An object containing some
- * options, like width,
- * height, the camera ratio.
- * {function} initProgram The function that will initiate the program, with
- * the relevant shaders and parameters. It must return
- * the newly created program.
- *
- * Check sigma.webgl.nodes.def or sigma.webgl.nodes.fast to see how it
- * works more precisely.
- */
- sigma.utils.pkg('sigma.webgl.nodes');
-
-
-
-
- /**
- * The object "sigma.webgl.edges" contains the different WebGL edge
- * renderers. The default one draw edges as direct lines. Here are the
- * attributes any edge renderer must have:
- *
- * {number} POINTS The number of points required to draw an edge.
- * {number} ATTRIBUTES The number of attributes needed to draw one point.
- * {function} addEdge A function that adds an edge to the data stack that
- * will be given to the buffer. Here is the arguments:
- * > {object} edge
- * > {object} source
- * > {object} target
- * > {Float32Array} data The stack.
- * > {object} options Some options.
- * {function} render The function that will effectively render the edges
- * into the buffer.
- * > {WebGLRenderingContext} gl
- * > {WebGLProgram} program
- * > {Float32Array} data The stack to give to the
- * buffer.
- * > {object} params An object containing some
- * options, like width,
- * height, the camera ratio.
- * {function} initProgram The function that will initiate the program, with
- * the relevant shaders and parameters. It must return
- * the newly created program.
- *
- * Check sigma.webgl.edges.def or sigma.webgl.edges.fast to see how it
- * works more precisely.
- */
- sigma.utils.pkg('sigma.webgl.edges');
-
-
-
-
- /**
- * The object "sigma.canvas.labels" contains the different
- * label renderers for the WebGL renderer. Since displaying texts in WebGL is
- * definitely painful and since there a way less labels to display than nodes
- * or edges, the default renderer simply renders them in a canvas.
- *
- * A labels renderer is a simple function, taking as arguments the related
- * node, the renderer and a settings function.
- */
- sigma.utils.pkg('sigma.canvas.labels');
- }).call(this);
|