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.
 
 
 
 

326 lines
9.8 KiB

/**
* This plugin provides a method to drag & drop nodes. Check the
* sigma.plugins.dragNodes function doc or the examples/basic.html &
* examples/api-candy.html code samples to know more.
*/
(function() {
'use strict';
if (typeof sigma === 'undefined')
throw 'sigma is not declared';
sigma.utils.pkg('sigma.plugins');
/**
* This function will add `mousedown`, `mouseup` & `mousemove` events to the
* nodes in the `overNode`event to perform drag & drop operations. It uses
* `linear interpolation` [http://en.wikipedia.org/wiki/Linear_interpolation]
* and `rotation matrix` [http://en.wikipedia.org/wiki/Rotation_matrix] to
* calculate the X and Y coordinates from the `cam` or `renderer` node
* attributes. These attributes represent the coordinates of the nodes in
* the real container, not in canvas.
*
* Fired events:
* *************
* startdrag Fired at the beginning of the drag.
* drag Fired while the node is dragged.
* drop Fired at the end of the drag if the node has been dragged.
* dragend Fired at the end of the drag.
*
* Recognized parameters:
* **********************
* @param {sigma} s The related sigma instance.
* @param {renderer} renderer The related renderer instance.
*/
function DragNodes(s, renderer) {
sigma.classes.dispatcher.extend(this);
// A quick hardcoded rule to prevent people from using this plugin with the
// WebGL renderer (which is impossible at the moment):
// if (
// sigma.renderers.webgl &&
// renderer instanceof sigma.renderers.webgl
// )
// throw new Error(
// 'The sigma.plugins.dragNodes is not compatible with the WebGL renderer'
// );
// Init variables:
var _self = this,
_s = s,
_body = document.body,
_renderer = renderer,
_mouse = renderer.container.lastChild,
_camera = renderer.camera,
_node = null,
_prefix = '',
_hoverStack = [],
_hoverIndex = {},
_isMouseDown = false,
_isMouseOverCanvas = false,
_drag = false;
if (renderer instanceof sigma.renderers.svg) {
_mouse = renderer.container.firstChild;
}
// It removes the initial substring ('read_') if it's a WegGL renderer.
if (renderer instanceof sigma.renderers.webgl) {
_prefix = renderer.options.prefix.substr(5);
} else {
_prefix = renderer.options.prefix;
}
renderer.bind('overNode', nodeMouseOver);
renderer.bind('outNode', treatOutNode);
renderer.bind('click', click);
_s.bind('kill', function() {
_self.unbindAll();
});
/**
* Unbind all event listeners.
*/
this.unbindAll = function() {
_mouse.removeEventListener('mousedown', nodeMouseDown);
_body.removeEventListener('mousemove', nodeMouseMove);
_body.removeEventListener('mouseup', nodeMouseUp);
_renderer.unbind('overNode', nodeMouseOver);
_renderer.unbind('outNode', treatOutNode);
}
// Calculates the global offset of the given element more accurately than
// element.offsetTop and element.offsetLeft.
function calculateOffset(element) {
var style = window.getComputedStyle(element);
var getCssProperty = function(prop) {
return parseInt(style.getPropertyValue(prop).replace('px', '')) || 0;
};
return {
left: element.getBoundingClientRect().left + getCssProperty('padding-left'),
top: element.getBoundingClientRect().top + getCssProperty('padding-top')
};
};
function click(event) {
// event triggered at the end of the click
_isMouseDown = false;
_body.removeEventListener('mousemove', nodeMouseMove);
_body.removeEventListener('mouseup', nodeMouseUp);
if (!_hoverStack.length) {
_node = null;
}
};
function nodeMouseOver(event) {
// Don't treat the node if it is already registered
if (_hoverIndex[event.data.node.id]) {
return;
}
// Add node to array of current nodes over
_hoverStack.push(event.data.node);
_hoverIndex[event.data.node.id] = true;
if(_hoverStack.length && ! _isMouseDown) {
// Set the current node to be the last one in the array
_node = _hoverStack[_hoverStack.length - 1];
_mouse.addEventListener('mousedown', nodeMouseDown);
}
};
function treatOutNode(event) {
// Remove the node from the array
var indexCheck = _hoverStack.map(function(e) { return e; }).indexOf(event.data.node);
_hoverStack.splice(indexCheck, 1);
delete _hoverIndex[event.data.node.id];
if(_hoverStack.length && ! _isMouseDown) {
// On out, set the current node to be the next stated in array
_node = _hoverStack[_hoverStack.length - 1];
} else {
_mouse.removeEventListener('mousedown', nodeMouseDown);
}
};
function nodeMouseDown(event) {
_isMouseDown = true;
var size = _s.graph.nodes().length;
// when there is only node in the graph, the plugin cannot apply
// linear interpolation. So treat it as if a user is dragging
// the graph
if (_node && size > 1) {
_mouse.removeEventListener('mousedown', nodeMouseDown);
_body.addEventListener('mousemove', nodeMouseMove);
_body.addEventListener('mouseup', nodeMouseUp);
// Do not refresh edgequadtree during drag:
var k,
c;
for (k in _s.cameras) {
c = _s.cameras[k];
if (c.edgequadtree !== undefined) {
c.edgequadtree._enabled = false;
}
}
// Deactivate drag graph.
_renderer.settings({mouseEnabled: false, enableHovering: false});
_s.refresh();
_self.dispatchEvent('startdrag', {
node: _node,
captor: event,
renderer: _renderer
});
}
};
function nodeMouseUp(event) {
_isMouseDown = false;
_mouse.addEventListener('mousedown', nodeMouseDown);
_body.removeEventListener('mousemove', nodeMouseMove);
_body.removeEventListener('mouseup', nodeMouseUp);
// Allow to refresh edgequadtree:
var k,
c;
for (k in _s.cameras) {
c = _s.cameras[k];
if (c.edgequadtree !== undefined) {
c.edgequadtree._enabled = true;
}
}
// Activate drag graph.
_renderer.settings({mouseEnabled: true, enableHovering: true});
_s.refresh();
if (_drag) {
_self.dispatchEvent('drop', {
node: _node,
captor: event,
renderer: _renderer
});
}
_self.dispatchEvent('dragend', {
node: _node,
captor: event,
renderer: _renderer
});
_drag = false;
_node = null;
};
function nodeMouseMove(event) {
if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
clearTimeout(timeOut);
var timeOut = setTimeout(executeNodeMouseMove, 0);
} else {
executeNodeMouseMove();
}
function executeNodeMouseMove() {
var offset = calculateOffset(_renderer.container),
x = event.clientX - offset.left,
y = event.clientY - offset.top,
cos = Math.cos(_camera.angle),
sin = Math.sin(_camera.angle),
nodes = _s.graph.nodes(),
ref = [];
// Getting and derotating the reference coordinates.
for (var i = 0; i < 2; i++) {
var n = nodes[i];
var aux = {
x: n.x * cos + n.y * sin,
y: n.y * cos - n.x * sin,
renX: n[_prefix + 'x'],
renY: n[_prefix + 'y'],
};
ref.push(aux);
}
// Applying linear interpolation.
// if the nodes are on top of each other, we use the camera ratio to interpolate
if (ref[0].x === ref[1].x && ref[0].y === ref[1].y) {
var xRatio = (ref[0].renX === 0) ? 1 : ref[0].renX;
var yRatio = (ref[0].renY === 0) ? 1 : ref[0].renY;
x = (ref[0].x / xRatio) * (x - ref[0].renX) + ref[0].x;
y = (ref[0].y / yRatio) * (y - ref[0].renY) + ref[0].y;
} else {
var xRatio = (ref[1].renX - ref[0].renX) / (ref[1].x - ref[0].x);
var yRatio = (ref[1].renY - ref[0].renY) / (ref[1].y - ref[0].y);
// if the coordinates are the same, we use the other ratio to interpolate
if (ref[1].x === ref[0].x) {
xRatio = yRatio;
}
if (ref[1].y === ref[0].y) {
yRatio = xRatio;
}
x = (x - ref[0].renX) / xRatio + ref[0].x;
y = (y - ref[0].renY) / yRatio + ref[0].y;
}
// Rotating the coordinates.
_node.x = x * cos - y * sin;
_node.y = y * cos + x * sin;
_s.refresh();
_drag = true;
_self.dispatchEvent('drag', {
node: _node,
captor: event,
renderer: _renderer
});
}
};
};
/**
* Interface
* ------------------
*
* > var dragNodesListener = sigma.plugins.dragNodes(s, s.renderers[0]);
*/
var _instance = {};
/**
* @param {sigma} s The related sigma instance.
* @param {renderer} renderer The related renderer instance.
*/
sigma.plugins.dragNodes = function(s, renderer) {
// Create object if undefined
if (!_instance[s.id]) {
_instance[s.id] = new DragNodes(s, renderer);
}
s.bind('kill', function() {
sigma.plugins.killDragNodes(s);
});
return _instance[s.id];
};
/**
* This method removes the event listeners and kills the dragNodes instance.
*
* @param {sigma} s The related sigma instance.
*/
sigma.plugins.killDragNodes = function(s) {
if (_instance[s.id] instanceof DragNodes) {
_instance[s.id].unbindAll();
delete _instance[s.id];
}
};
}).call(window);