;(function(undefined) { 'use strict'; if (typeof sigma === 'undefined') throw 'sigma is not declared'; // Initialize packages: sigma.utils.pkg('sigma.misc'); /** * This helper will bind any no-DOM renderer (for instance canvas or WebGL) * to its captors, to properly dispatch the good events to the sigma instance * to manage clicking, hovering etc... * * It has to be called in the scope of the related renderer. */ sigma.misc.bindEvents = function(prefix) { var i, l, mX, mY, captor, self = this; function getNodes(e) { if (e) { mX = 'x' in e.data ? e.data.x : mX; mY = 'y' in e.data ? e.data.y : mY; } var i, j, l, n, x, y, s, inserted, selected = [], modifiedX = mX + self.width / 2, modifiedY = mY + self.height / 2, point = self.camera.cameraPosition( mX, mY ), nodes = self.camera.quadtree.point( point.x, point.y ); if (nodes.length) for (i = 0, l = nodes.length; i < l; i++) { n = nodes[i]; x = n[prefix + 'x']; y = n[prefix + 'y']; s = n[prefix + 'size']; if ( !n.hidden && modifiedX > x - s && modifiedX < x + s && modifiedY > y - s && modifiedY < y + s && Math.sqrt( Math.pow(modifiedX - x, 2) + Math.pow(modifiedY - y, 2) ) < s ) { // Insert the node: inserted = false; for (j = 0; j < selected.length; j++) if (n.size > selected[j].size) { selected.splice(j, 0, n); inserted = true; break; } if (!inserted) selected.push(n); } } return selected; } function getEdges(e) { if (!self.settings('enableEdgeHovering')) { // No event if the setting is off: return []; } var isCanvas = ( sigma.renderers.canvas && self instanceof sigma.renderers.canvas); if (!isCanvas) { // A quick hardcoded rule to prevent people from using this feature // with the WebGL renderer (which is not good enough at the moment): throw new Error( 'The edge events feature is not compatible with the WebGL renderer' ); } if (e) { mX = 'x' in e.data ? e.data.x : mX; mY = 'y' in e.data ? e.data.y : mY; } var i, j, l, a, edge, s, maxEpsilon = self.settings('edgeHoverPrecision'), source, target, cp, nodeIndex = {}, inserted, selected = [], modifiedX = mX + self.width / 2, modifiedY = mY + self.height / 2, point = self.camera.cameraPosition( mX, mY ), edges = []; if (isCanvas) { var nodesOnScreen = self.camera.quadtree.area( self.camera.getRectangle(self.width, self.height) ); for (a = nodesOnScreen, i = 0, l = a.length; i < l; i++) nodeIndex[a[i].id] = a[i]; } if (self.camera.edgequadtree !== undefined) { edges = self.camera.edgequadtree.point( point.x, point.y ); } function insertEdge(selected, edge) { inserted = false; for (j = 0; j < selected.length; j++) if (edge.size > selected[j].size) { selected.splice(j, 0, edge); inserted = true; break; } if (!inserted) selected.push(edge); } if (edges.length) for (i = 0, l = edges.length; i < l; i++) { edge = edges[i]; source = self.graph.nodes(edge.source); target = self.graph.nodes(edge.target); // (HACK) we can't get edge[prefix + 'size'] on WebGL renderer: s = edge[prefix + 'size'] || edge['read_' + prefix + 'size']; // First, let's identify which edges are drawn. To do this, we 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. // Then, let's check if the mouse is on the edge (we suppose that it // is a line segment). if ( !edge.hidden && !source.hidden && !target.hidden && (!isCanvas || (nodeIndex[edge.source] || nodeIndex[edge.target])) && sigma.utils.getDistance( source[prefix + 'x'], source[prefix + 'y'], modifiedX, modifiedY) > source[prefix + 'size'] && sigma.utils.getDistance( target[prefix + 'x'], target[prefix + 'y'], modifiedX, modifiedY) > target[prefix + 'size'] ) { if (edge.type == 'curve' || edge.type == 'curvedArrow') { if (source.id === target.id) { cp = sigma.utils.getSelfLoopControlPoints( source[prefix + 'x'], source[prefix + 'y'], source[prefix + 'size'] ); if ( sigma.utils.isPointOnBezierCurve( modifiedX, modifiedY, source[prefix + 'x'], source[prefix + 'y'], target[prefix + 'x'], target[prefix + 'y'], cp.x1, cp.y1, cp.x2, cp.y2, Math.max(s, maxEpsilon) )) { insertEdge(selected, edge); } } else { cp = sigma.utils.getQuadraticControlPoint( source[prefix + 'x'], source[prefix + 'y'], target[prefix + 'x'], target[prefix + 'y']); if ( sigma.utils.isPointOnQuadraticCurve( modifiedX, modifiedY, source[prefix + 'x'], source[prefix + 'y'], target[prefix + 'x'], target[prefix + 'y'], cp.x, cp.y, Math.max(s, maxEpsilon) )) { insertEdge(selected, edge); } } } else if ( sigma.utils.isPointOnSegment( modifiedX, modifiedY, source[prefix + 'x'], source[prefix + 'y'], target[prefix + 'x'], target[prefix + 'y'], Math.max(s, maxEpsilon) )) { insertEdge(selected, edge); } } } return selected; } function bindCaptor(captor) { var nodes, edges, overNodes = {}, overEdges = {}; function onClick(e) { if (!self.settings('eventsEnabled')) return; self.dispatchEvent('click', e.data); nodes = getNodes(e); edges = getEdges(e); if (nodes.length) { self.dispatchEvent('clickNode', { node: nodes[0], captor: e.data }); self.dispatchEvent('clickNodes', { node: nodes, captor: e.data }); } else if (edges.length) { self.dispatchEvent('clickEdge', { edge: edges[0], captor: e.data }); self.dispatchEvent('clickEdges', { edge: edges, captor: e.data }); } else self.dispatchEvent('clickStage', {captor: e.data}); } function onDoubleClick(e) { if (!self.settings('eventsEnabled')) return; self.dispatchEvent('doubleClick', e.data); nodes = getNodes(e); edges = getEdges(e); if (nodes.length) { self.dispatchEvent('doubleClickNode', { node: nodes[0], captor: e.data }); self.dispatchEvent('doubleClickNodes', { node: nodes, captor: e.data }); } else if (edges.length) { self.dispatchEvent('doubleClickEdge', { edge: edges[0], captor: e.data }); self.dispatchEvent('doubleClickEdges', { edge: edges, captor: e.data }); } else self.dispatchEvent('doubleClickStage', {captor: e.data}); } function onRightClick(e) { if (!self.settings('eventsEnabled')) return; self.dispatchEvent('rightClick', e.data); nodes = getNodes(e); edges = getEdges(e); if (nodes.length) { self.dispatchEvent('rightClickNode', { node: nodes[0], captor: e.data }); self.dispatchEvent('rightClickNodes', { node: nodes, captor: e.data }); } else if (edges.length) { self.dispatchEvent('rightClickEdge', { edge: edges[0], captor: e.data }); self.dispatchEvent('rightClickEdges', { edge: edges, captor: e.data }); } else self.dispatchEvent('rightClickStage', {captor: e.data}); } function onOut(e) { if (!self.settings('eventsEnabled')) return; var k, i, l, le, outNodes = [], outEdges = []; for (k in overNodes) outNodes.push(overNodes[k]); overNodes = {}; // Dispatch both single and multi events: for (i = 0, l = outNodes.length; i < l; i++) self.dispatchEvent('outNode', { node: outNodes[i], captor: e.data }); if (outNodes.length) self.dispatchEvent('outNodes', { nodes: outNodes, captor: e.data }); overEdges = {}; // Dispatch both single and multi events: for (i = 0, le = outEdges.length; i < le; i++) self.dispatchEvent('outEdge', { edge: outEdges[i], captor: e.data }); if (outEdges.length) self.dispatchEvent('outEdges', { edges: outEdges, captor: e.data }); } function onMove(e) { if (!self.settings('eventsEnabled')) return; nodes = getNodes(e); edges = getEdges(e); var i, k, node, edge, newOutNodes = [], newOverNodes = [], currentOverNodes = {}, l = nodes.length, newOutEdges = [], newOverEdges = [], currentOverEdges = {}, le = edges.length; // Check newly overred nodes: for (i = 0; i < l; i++) { node = nodes[i]; currentOverNodes[node.id] = node; if (!overNodes[node.id]) { newOverNodes.push(node); overNodes[node.id] = node; } } // Check no more overred nodes: for (k in overNodes) if (!currentOverNodes[k]) { newOutNodes.push(overNodes[k]); delete overNodes[k]; } // Dispatch both single and multi events: for (i = 0, l = newOverNodes.length; i < l; i++) self.dispatchEvent('overNode', { node: newOverNodes[i], captor: e.data }); for (i = 0, l = newOutNodes.length; i < l; i++) self.dispatchEvent('outNode', { node: newOutNodes[i], captor: e.data }); if (newOverNodes.length) self.dispatchEvent('overNodes', { nodes: newOverNodes, captor: e.data }); if (newOutNodes.length) self.dispatchEvent('outNodes', { nodes: newOutNodes, captor: e.data }); // Check newly overred edges: for (i = 0; i < le; i++) { edge = edges[i]; currentOverEdges[edge.id] = edge; if (!overEdges[edge.id]) { newOverEdges.push(edge); overEdges[edge.id] = edge; } } // Check no more overred edges: for (k in overEdges) if (!currentOverEdges[k]) { newOutEdges.push(overEdges[k]); delete overEdges[k]; } // Dispatch both single and multi events: for (i = 0, le = newOverEdges.length; i < le; i++) self.dispatchEvent('overEdge', { edge: newOverEdges[i], captor: e.data }); for (i = 0, le = newOutEdges.length; i < le; i++) self.dispatchEvent('outEdge', { edge: newOutEdges[i], captor: e.data }); if (newOverEdges.length) self.dispatchEvent('overEdges', { edges: newOverEdges, captor: e.data }); if (newOutEdges.length) self.dispatchEvent('outEdges', { edges: newOutEdges, captor: e.data }); } // Bind events: captor.bind('click', onClick); captor.bind('mousedown', onMove); captor.bind('mouseup', onMove); captor.bind('mousemove', onMove); captor.bind('mouseout', onOut); captor.bind('doubleclick', onDoubleClick); captor.bind('rightclick', onRightClick); self.bind('render', onMove); } for (i = 0, l = this.captors.length; i < l; i++) bindCaptor(this.captors[i]); }; }).call(this);