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