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.
 
 
 
 

509 lines
14 KiB

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