mirror of https://gitee.com/bigwinds/arangodb
This commit is contained in:
parent
a0ff5e9dc3
commit
604637abdd
|
@ -61,8 +61,10 @@
|
|||
"frontend/js/lib/sigma.min.js",
|
||||
"frontend/js/lib/sigma.plugins.animate.js",
|
||||
"frontend/js/lib/sigma.plugins.dragNodes.js",
|
||||
"frontend/js/lib/sigma.layout.noverlap.js",
|
||||
"frontend/js/lib/sigma.plugins.fullScreen.js",
|
||||
"frontend/js/lib/sigma.plugins.filter.js",
|
||||
"frontend/js/lib/sigma.plugins.lasso.js",
|
||||
"frontend/js/lib/sigma.layout.noverlap.js",
|
||||
"frontend/js/lib/sigma.layout.fruchtermanReingold.js",
|
||||
"frontend/js/lib/sigma.exporters.svg.js",
|
||||
"frontend/js/lib/sigma.canvas.edges.labels.curve.js",
|
||||
|
|
|
@ -92,7 +92,7 @@ window.ArangoDocument = Backbone.Collection.extend({
|
|||
}
|
||||
},
|
||||
error: function (data) {
|
||||
callback(true, data._id);
|
||||
callback(true, data._id, data.responseJSON);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -0,0 +1,504 @@
|
|||
;(function(undefined) {
|
||||
'use strict';
|
||||
|
||||
if (typeof sigma === 'undefined')
|
||||
throw 'sigma is not declared';
|
||||
|
||||
// Initialize package:
|
||||
sigma.utils.pkg('sigma.plugins');
|
||||
|
||||
// Add custom graph methods:
|
||||
/**
|
||||
* This methods returns an array of nodes that are adjacent to a node.
|
||||
*
|
||||
* @param {string} id The node id.
|
||||
* @return {array} The array of adjacent nodes.
|
||||
*/
|
||||
if (!sigma.classes.graph.hasMethod('adjacentNodes'))
|
||||
sigma.classes.graph.addMethod('adjacentNodes', function(id) {
|
||||
if (typeof id !== 'string')
|
||||
throw 'adjacentNodes: the node id must be a string.';
|
||||
|
||||
var target,
|
||||
nodes = [];
|
||||
for(target in this.allNeighborsIndex[id]) {
|
||||
nodes.push(this.nodesIndex[target]);
|
||||
}
|
||||
return nodes;
|
||||
});
|
||||
|
||||
/**
|
||||
* This methods returns an array of edges that are adjacent to a node.
|
||||
*
|
||||
* @param {string} id The node id.
|
||||
* @return {array} The array of adjacent edges.
|
||||
*/
|
||||
if (!sigma.classes.graph.hasMethod('adjacentEdges'))
|
||||
sigma.classes.graph.addMethod('adjacentEdges', function(id) {
|
||||
if (typeof id !== 'string')
|
||||
throw 'adjacentEdges: the node id must be a string.';
|
||||
|
||||
var a = this.allNeighborsIndex[id],
|
||||
eid,
|
||||
target,
|
||||
edges = [];
|
||||
for(target in a) {
|
||||
for(eid in a[target]) {
|
||||
edges.push(a[target][eid]);
|
||||
}
|
||||
}
|
||||
return edges;
|
||||
});
|
||||
|
||||
/**
|
||||
* Sigma Filter
|
||||
* =============================
|
||||
*
|
||||
* @author Sébastien Heymann <seb@linkurio.us> (Linkurious)
|
||||
* @version 0.1
|
||||
*/
|
||||
|
||||
var _g = undefined,
|
||||
_s = undefined,
|
||||
_chain = [], // chain of wrapped filters
|
||||
_keysIndex = Object.create(null),
|
||||
Processors = {}; // available predicate processors
|
||||
|
||||
|
||||
/**
|
||||
* Library of processors
|
||||
* ------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {function} fn The predicate.
|
||||
*/
|
||||
Processors.nodes = function nodes(fn) {
|
||||
var n = _g.nodes(),
|
||||
ln = n.length,
|
||||
e = _g.edges(),
|
||||
le = e.length;
|
||||
|
||||
// hide node, or keep former value
|
||||
while(ln--)
|
||||
n[ln].hidden = !fn.call(_g, n[ln]) || n[ln].hidden;
|
||||
|
||||
while(le--)
|
||||
if (_g.nodes(e[le].source).hidden || _g.nodes(e[le].target).hidden)
|
||||
e[le].hidden = true;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {function} fn The predicate.
|
||||
*/
|
||||
Processors.edges = function edges(fn) {
|
||||
var e = _g.edges(),
|
||||
le = e.length;
|
||||
|
||||
// hide edge, or keep former value
|
||||
while(le--)
|
||||
e[le].hidden = !fn.call(_g, e[le]) || e[le].hidden;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} id The center node.
|
||||
*/
|
||||
Processors.neighbors = function neighbors(id) {
|
||||
var n = _g.nodes(),
|
||||
ln = n.length,
|
||||
e = _g.edges(),
|
||||
le = e.length,
|
||||
neighbors = _g.adjacentNodes(id),
|
||||
nn = neighbors.length,
|
||||
no = {};
|
||||
|
||||
while(nn--)
|
||||
no[neighbors[nn].id] = true;
|
||||
|
||||
while(ln--)
|
||||
if (n[ln].id !== id && !(n[ln].id in no))
|
||||
n[ln].hidden = true;
|
||||
|
||||
while(le--)
|
||||
if (_g.nodes(e[le].source).hidden || _g.nodes(e[le].target).hidden)
|
||||
e[le].hidden = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This function adds a filter to the chain of filters.
|
||||
*
|
||||
* @param {function} fn The filter (i.e. predicate processor).
|
||||
* @param {function} p The predicate.
|
||||
* @param {?string} key The key to identify the filter.
|
||||
*/
|
||||
function register(fn, p, key) {
|
||||
if (key != undefined && typeof key !== 'string')
|
||||
throw 'The filter key "'+ key.toString() +'" must be a string.';
|
||||
|
||||
if (key != undefined && !key.length)
|
||||
throw 'The filter key must be a non-empty string.';
|
||||
|
||||
if (typeof fn !== 'function')
|
||||
throw 'The predicate of key "'+ key +'" must be a function.';
|
||||
|
||||
if ('undo' === key)
|
||||
throw '"undo" is a reserved key.';
|
||||
|
||||
if (_keysIndex[key])
|
||||
throw 'The filter "' + key + '" already exists.';
|
||||
|
||||
if (key)
|
||||
_keysIndex[key] = true;
|
||||
|
||||
_chain.push({
|
||||
'key': key,
|
||||
'processor': fn,
|
||||
'predicate': p
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This function removes a set of filters from the chain.
|
||||
*
|
||||
* @param {object} o The filter keys.
|
||||
*/
|
||||
function unregister (o) {
|
||||
_chain = _chain.filter(function(a) {
|
||||
return !(a.key in o);
|
||||
});
|
||||
|
||||
for(var key in o)
|
||||
delete _keysIndex[key];
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Filter Object
|
||||
* ------------------
|
||||
* @param {sigma} s The related sigma instance.
|
||||
*/
|
||||
function Filter(s) {
|
||||
_s = s;
|
||||
_g = s.graph;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This method is used to filter the nodes. The method must be called with
|
||||
* the predicate, which is a function that takes a node as argument and
|
||||
* returns a boolean. It may take an identifier as argument to undo the
|
||||
* filter later. The method wraps the predicate into an anonymous function
|
||||
* that looks through each node in the graph. When executed, the anonymous
|
||||
* function hides the nodes that fail a truth test (predicate). The method
|
||||
* adds the anonymous function to the chain of filters. The filter is not
|
||||
* executed until the apply() method is called.
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
* > filter.nodesBy(function(n) {
|
||||
* > return this.degree(n.id) > 0;
|
||||
* > }, 'degreeNotNull');
|
||||
*
|
||||
* @param {function} fn The filter predicate.
|
||||
* @param {?string} key The key to identify the filter.
|
||||
* @return {sigma.plugins.filter} Returns the instance.
|
||||
*/
|
||||
Filter.prototype.nodesBy = function(fn, key) {
|
||||
// Wrap the predicate to be applied on the graph and add it to the chain.
|
||||
register(Processors.nodes, fn, key);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to filter the edges. The method must be called with
|
||||
* the predicate, which is a function that takes a node as argument and
|
||||
* returns a boolean. It may take an identifier as argument to undo the
|
||||
* filter later. The method wraps the predicate into an anonymous function
|
||||
* that looks through each edge in the graph. When executed, the anonymous
|
||||
* function hides the edges that fail a truth test (predicate). The method
|
||||
* adds the anonymous function to the chain of filters. The filter is not
|
||||
* executed until the apply() method is called.
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
* > filter.edgesBy(function(e) {
|
||||
* > return e.size > 1;
|
||||
* > }, 'edgeSize');
|
||||
*
|
||||
* @param {function} fn The filter predicate.
|
||||
* @param {?string} key The key to identify the filter.
|
||||
* @return {sigma.plugins.filter} Returns the instance.
|
||||
*/
|
||||
Filter.prototype.edgesBy = function(fn, key) {
|
||||
// Wrap the predicate to be applied on the graph and add it to the chain.
|
||||
register(Processors.edges, fn, key);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to filter the nodes which are not direct connections
|
||||
* of a given node. The method must be called with the node identifier. It
|
||||
* may take an identifier as argument to undo the filter later. The filter
|
||||
* is not executed until the apply() method is called.
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
* > filter.neighborsOf('n0');
|
||||
*
|
||||
* @param {string} id The node id.
|
||||
* @param {?string} key The key to identify the filter.
|
||||
* @return {sigma.plugins.filter} Returns the instance.
|
||||
*/
|
||||
Filter.prototype.neighborsOf = function(id, key) {
|
||||
if (typeof id !== 'string')
|
||||
throw 'The node id "'+ id.toString() +'" must be a string.';
|
||||
if (!id.length)
|
||||
throw 'The node id must be a non-empty string.';
|
||||
|
||||
// Wrap the predicate to be applied on the graph and add it to the chain.
|
||||
register(Processors.neighbors, id, key);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to execute the chain of filters and to refresh the
|
||||
* display.
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
* > filter
|
||||
* > .nodesBy(function(n) {
|
||||
* > return this.degree(n.id) > 0;
|
||||
* > }, 'degreeNotNull')
|
||||
* > .apply();
|
||||
*
|
||||
* @return {sigma.plugins.filter} Returns the instance.
|
||||
*/
|
||||
Filter.prototype.apply = function() {
|
||||
for (var i = 0, len = _chain.length; i < len; ++i) {
|
||||
_chain[i].processor(_chain[i].predicate);
|
||||
};
|
||||
|
||||
if (_chain[0] && 'undo' === _chain[0].key) {
|
||||
_chain.shift();
|
||||
}
|
||||
|
||||
_s.refresh();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method undoes one or several filters, depending on how it is called.
|
||||
*
|
||||
* To undo all filters, call "undo" without argument. To undo a specific
|
||||
* filter, call it with the key of the filter. To undo multiple filters, call
|
||||
* it with an array of keys or multiple arguments, and it will undo each
|
||||
* filter, in the same order. The undo is not executed until the apply()
|
||||
* method is called. For instance:
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
* > filter
|
||||
* > .nodesBy(function(n) {
|
||||
* > return this.degree(n.id) > 0;
|
||||
* > }, 'degreeNotNull');
|
||||
* > .edgesBy(function(e) {
|
||||
* > return e.size > 1;
|
||||
* > }, 'edgeSize')
|
||||
* > .undo();
|
||||
*
|
||||
* Other examples:
|
||||
* > filter.undo();
|
||||
* > filter.undo('myfilter');
|
||||
* > filter.undo(['myfilter1', 'myfilter2']);
|
||||
* > filter.undo('myfilter1', 'myfilter2');
|
||||
*
|
||||
* @param {?(string|array|*string))} v Eventually one key, an array of keys.
|
||||
* @return {sigma.plugins.filter} Returns the instance.
|
||||
*/
|
||||
Filter.prototype.undo = function(v) {
|
||||
var q = Object.create(null),
|
||||
la = arguments.length;
|
||||
|
||||
// find removable filters
|
||||
if (la === 1) {
|
||||
if (Object.prototype.toString.call(v) === '[object Array]')
|
||||
for (var i = 0, len = v.length; i < len; i++)
|
||||
q[v[i]] = true;
|
||||
|
||||
else // 1 filter key
|
||||
q[v] = true;
|
||||
|
||||
} else if (la > 1) {
|
||||
for (var i = 0; i < la; i++)
|
||||
q[arguments[i]] = true;
|
||||
}
|
||||
else
|
||||
this.clear();
|
||||
|
||||
unregister(q);
|
||||
|
||||
function processor() {
|
||||
var n = _g.nodes(),
|
||||
ln = n.length,
|
||||
e = _g.edges(),
|
||||
le = e.length;
|
||||
|
||||
while(ln--)
|
||||
n[ln].hidden = false;
|
||||
|
||||
while(le--)
|
||||
e[le].hidden = false;
|
||||
};
|
||||
|
||||
_chain.unshift({
|
||||
'key': 'undo',
|
||||
'processor': processor
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// fast deep copy function
|
||||
function deepCopy(o) {
|
||||
var copy = Object.create(null);
|
||||
for (var i in o) {
|
||||
if (typeof o[i] === "object" && o[i] !== null) {
|
||||
copy[i] = deepCopy(o[i]);
|
||||
}
|
||||
else if (typeof o[i] === "function" && o[i] !== null) {
|
||||
// clone function:
|
||||
eval(" copy[i] = " + o[i].toString());
|
||||
//copy[i] = o[i].bind(_g);
|
||||
}
|
||||
|
||||
else
|
||||
copy[i] = o[i];
|
||||
}
|
||||
return copy;
|
||||
};
|
||||
|
||||
function cloneChain(chain) {
|
||||
// Clone the array of filters:
|
||||
var copy = chain.slice(0);
|
||||
for (var i = 0, len = copy.length; i < len; i++) {
|
||||
copy[i] = deepCopy(copy[i]);
|
||||
if (typeof copy[i].processor === "function")
|
||||
copy[i].processor = 'filter.processors.' + copy[i].processor.name;
|
||||
};
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to empty the chain of filters.
|
||||
* Prefer the undo() method to reset filters.
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
* > filter.clear();
|
||||
*
|
||||
* @return {sigma.plugins.filter} Returns the instance.
|
||||
*/
|
||||
Filter.prototype.clear = function() {
|
||||
_chain.length = 0; // clear the array
|
||||
_keysIndex = Object.create(null);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method clones the filter chain and return the copy.
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
* > var chain = filter.export();
|
||||
*
|
||||
* @return {object} The cloned chain of filters.
|
||||
*/
|
||||
Filter.prototype.export = function() {
|
||||
var c = cloneChain(_chain);
|
||||
return c;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method sets the chain of filters with the specified chain.
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
* > var chain = [
|
||||
* > {
|
||||
* > key: 'my-filter',
|
||||
* > predicate: function(n) {...},
|
||||
* > processor: 'filter.processors.nodes'
|
||||
* > }, ...
|
||||
* > ];
|
||||
* > filter.import(chain);
|
||||
*
|
||||
* @param {array} chain The chain of filters.
|
||||
* @return {sigma.plugins.filter} Returns the instance.
|
||||
*/
|
||||
Filter.prototype.import = function(chain) {
|
||||
if (chain === undefined)
|
||||
throw 'Wrong arguments.';
|
||||
|
||||
if (Object.prototype.toString.call(chain) !== '[object Array]')
|
||||
throw 'The chain" must be an array.';
|
||||
|
||||
var copy = cloneChain(chain);
|
||||
|
||||
for (var i = 0, len = copy.length; i < len; i++) {
|
||||
if (copy[i].predicate === undefined || copy[i].processor === undefined)
|
||||
throw 'Wrong arguments.';
|
||||
|
||||
if (copy[i].key != undefined && typeof copy[i].key !== 'string')
|
||||
throw 'The filter key "'+ copy[i].key.toString() +'" must be a string.';
|
||||
|
||||
if (typeof copy[i].predicate !== 'function')
|
||||
throw 'The predicate of key "'+ copy[i].key +'" must be a function.';
|
||||
|
||||
if (typeof copy[i].processor !== 'string')
|
||||
throw 'The processor of key "'+ copy[i].key +'" must be a string.';
|
||||
|
||||
// Replace the processor name by the corresponding function:
|
||||
switch(copy[i].processor) {
|
||||
case 'filter.processors.nodes':
|
||||
copy[i].processor = Processors.nodes;
|
||||
break;
|
||||
case 'filter.processors.edges':
|
||||
copy[i].processor = Processors.edges;
|
||||
break;
|
||||
case 'filter.processors.neighbors':
|
||||
copy[i].processor = Processors.neighbors;
|
||||
break;
|
||||
default:
|
||||
throw 'Unknown processor ' + copy[i].processor;
|
||||
}
|
||||
};
|
||||
|
||||
_chain = copy;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Interface
|
||||
* ------------------
|
||||
*
|
||||
* > var filter = new sigma.plugins.filter(s);
|
||||
*/
|
||||
var filter = null;
|
||||
|
||||
/**
|
||||
* @param {sigma} s The related sigma instance.
|
||||
*/
|
||||
sigma.plugins.filter = function(s) {
|
||||
// Create filter if undefined
|
||||
if (!filter) {
|
||||
filter = new Filter(s);
|
||||
}
|
||||
return filter;
|
||||
};
|
||||
|
||||
}).call(this);
|
|
@ -0,0 +1,337 @@
|
|||
/**
|
||||
* Sigma Lasso
|
||||
* =============================
|
||||
*
|
||||
* @author Florent Schildknecht <florent.schildknecht@gmail.com> (Florent Schildknecht)
|
||||
* @version 0.0.2
|
||||
*/
|
||||
;(function (undefined) {
|
||||
'use strict';
|
||||
|
||||
if (typeof sigma === 'undefined')
|
||||
throw 'sigma is not declared';
|
||||
|
||||
// Initialize package:
|
||||
sigma.utils.pkg('sigma.plugins');
|
||||
|
||||
var _body = undefined,
|
||||
_instances = {};
|
||||
|
||||
/**
|
||||
* Lasso Object
|
||||
* ------------------
|
||||
* @param {sigma} sigmaInstance The related sigma instance.
|
||||
* @param {renderer} renderer The sigma instance renderer.
|
||||
* @param {sigma.classes.configurable} settings A settings class
|
||||
*/
|
||||
function Lasso (sigmaInstance, renderer, settings) {
|
||||
// Lasso is also an event dispatcher
|
||||
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.lasso is not compatible with the WebGL renderer'
|
||||
);
|
||||
|
||||
this.sigmaInstance = sigmaInstance;
|
||||
this.renderer = renderer;
|
||||
this.drawingCanvas = undefined;
|
||||
this.drawingContext = undefined;
|
||||
this.drewPoints = [];
|
||||
this.selectedNodes = [];
|
||||
this.isActive = false;
|
||||
this.isDrawing = false;
|
||||
|
||||
_body = document.body;
|
||||
|
||||
// Extends default settings
|
||||
this.settings = new sigma.classes.configurable({
|
||||
'strokeStyle': 'black',
|
||||
'lineWidth': 2,
|
||||
'fillWhileDrawing': false,
|
||||
'fillStyle': 'rgba(200, 200, 200, 0.25)',
|
||||
'cursor': 'crosshair'
|
||||
}, settings || {});
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to destroy the lasso.
|
||||
*
|
||||
* > var lasso = new sigma.plugins.lasso(sigmaInstance);
|
||||
* > lasso.clear();
|
||||
*
|
||||
* @return {sigma.plugins.lasso} Returns the instance.
|
||||
*/
|
||||
Lasso.prototype.clear = function () {
|
||||
this.deactivate();
|
||||
|
||||
this.sigmaInstance = undefined;
|
||||
this.renderer = undefined;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Lasso.prototype.getSigmaInstance = function () {
|
||||
// return this.sigmaInstance;
|
||||
// }
|
||||
|
||||
/**
|
||||
* This method is used to activate the lasso mode.
|
||||
*
|
||||
* > var lasso = new sigma.plugins.lasso(sigmaInstance);
|
||||
* > lasso.activate();
|
||||
*
|
||||
* @return {sigma.plugins.lasso} Returns the instance.
|
||||
*/
|
||||
Lasso.prototype.activate = function () {
|
||||
if (this.sigmaInstance && !this.isActive) {
|
||||
this.isActive = true;
|
||||
|
||||
// Add a new background layout canvas to draw the path on
|
||||
if (!this.renderer.domElements['lasso']) {
|
||||
this.renderer.initDOM('canvas', 'lasso');
|
||||
this.drawingCanvas = this.renderer.domElements['lasso'];
|
||||
|
||||
this.drawingCanvas.width = this.renderer.container.offsetWidth;
|
||||
this.drawingCanvas.height = this.renderer.container.offsetHeight;
|
||||
this.renderer.container.appendChild(this.drawingCanvas);
|
||||
this.drawingContext = this.drawingCanvas.getContext('2d');
|
||||
this.drawingCanvas.style.cursor = this.settings('cursor');
|
||||
}
|
||||
|
||||
_bindAll.apply(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to deactivate the lasso mode.
|
||||
*
|
||||
* > var lasso = new sigma.plugins.lasso(sigmaInstance);
|
||||
* > lasso.deactivate();
|
||||
*
|
||||
* @return {sigma.plugins.lasso} Returns the instance.
|
||||
*/
|
||||
Lasso.prototype.deactivate = function () {
|
||||
if (this.sigmaInstance && this.isActive) {
|
||||
this.isActive = false;
|
||||
this.isDrawing = false;
|
||||
|
||||
_unbindAll.apply(this);
|
||||
|
||||
if (this.renderer.domElements['lasso']) {
|
||||
this.renderer.container.removeChild(this.renderer.domElements['lasso']);
|
||||
delete this.renderer.domElements['lasso'];
|
||||
this.drawingCanvas.style.cursor = '';
|
||||
this.drawingCanvas = undefined;
|
||||
this.drawingContext = undefined;
|
||||
this.drewPoints = [];
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to bind all events of the lasso mode.
|
||||
*
|
||||
* > var lasso = new sigma.plugins.lasso(sigmaInstance);
|
||||
* > lasso.activate();
|
||||
*
|
||||
* @return {sigma.plugins.lasso} Returns the instance.
|
||||
*/
|
||||
var _bindAll = function () {
|
||||
// Mouse events
|
||||
this.drawingCanvas.addEventListener('mousedown', onDrawingStart.bind(this));
|
||||
_body.addEventListener('mousemove', onDrawing.bind(this));
|
||||
_body.addEventListener('mouseup', onDrawingEnd.bind(this));
|
||||
// Touch events
|
||||
this.drawingCanvas.addEventListener('touchstart', onDrawingStart.bind(this));
|
||||
_body.addEventListener('touchmove', onDrawing.bind(this));
|
||||
_body.addEventListener('touchcancel', onDrawingEnd.bind(this));
|
||||
_body.addEventListener('touchleave', onDrawingEnd.bind(this));
|
||||
_body.addEventListener('touchend', onDrawingEnd.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to unbind all events of the lasso mode.
|
||||
*
|
||||
* > var lasso = new sigma.plugins.lasso(sigmaInstance);
|
||||
* > lasso.activate();
|
||||
*
|
||||
* @return {sigma.plugins.lasso} Returns the instance.
|
||||
*/
|
||||
var _unbindAll = function () {
|
||||
// Mouse events
|
||||
this.drawingCanvas.removeEventListener('mousedown', onDrawingStart.bind(this));
|
||||
_body.removeEventListener('mousemove', onDrawing.bind(this));
|
||||
_body.removeEventListener('mouseup', onDrawingEnd.bind(this));
|
||||
// Touch events
|
||||
this.drawingCanvas.removeEventListener('touchstart', onDrawingStart.bind(this));
|
||||
this.drawingCanvas.removeEventListener('touchmove', onDrawing.bind(this));
|
||||
_body.removeEventListener('touchcancel', onDrawingEnd.bind(this));
|
||||
_body.removeEventListener('touchleave', onDrawingEnd.bind(this));
|
||||
_body.removeEventListener('touchend', onDrawingEnd.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to retrieve the previously selected nodes
|
||||
*
|
||||
* > var lasso = new sigma.plugins.lasso(sigmaInstance);
|
||||
* > lasso.getSelectedNodes();
|
||||
*
|
||||
* @return {array} Returns an array of nodes.
|
||||
*/
|
||||
Lasso.prototype.getSelectedNodes = function () {
|
||||
return this.selectedNodes;
|
||||
};
|
||||
|
||||
function onDrawingStart (event) {
|
||||
var drawingRectangle = this.drawingCanvas.getBoundingClientRect();
|
||||
|
||||
if (this.isActive) {
|
||||
this.isDrawing = true;
|
||||
this.drewPoints = [];
|
||||
this.selectedNodes = [];
|
||||
|
||||
this.sigmaInstance.refresh();
|
||||
|
||||
this.drewPoints.push({
|
||||
x: event.clientX - drawingRectangle.left,
|
||||
y: event.clientY - drawingRectangle.top
|
||||
});
|
||||
|
||||
this.drawingCanvas.style.cursor = this.settings('cursor');
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function onDrawing (event) {
|
||||
if (this.isActive && this.isDrawing) {
|
||||
var x = 0,
|
||||
y = 0,
|
||||
drawingRectangle = this.drawingCanvas.getBoundingClientRect();
|
||||
switch (event.type) {
|
||||
case 'touchmove':
|
||||
x = event.touches[0].clientX;
|
||||
y = event.touches[0].clientY;
|
||||
break;
|
||||
default:
|
||||
x = event.clientX;
|
||||
y = event.clientY;
|
||||
break;
|
||||
}
|
||||
this.drewPoints.push({
|
||||
x: x - drawingRectangle.left,
|
||||
y: y - drawingRectangle.top
|
||||
});
|
||||
|
||||
// Drawing styles
|
||||
this.drawingContext.lineWidth = this.settings('lineWidth');
|
||||
this.drawingContext.strokeStyle = this.settings('strokeStyle');
|
||||
this.drawingContext.fillStyle = this.settings('fillStyle');
|
||||
this.drawingContext.lineJoin = 'round';
|
||||
this.drawingContext.lineCap = 'round';
|
||||
|
||||
// Clear the canvas
|
||||
this.drawingContext.clearRect(0, 0, this.drawingContext.canvas.width, this.drawingContext.canvas.height);
|
||||
|
||||
// Redraw the complete path for a smoother effect
|
||||
// Even smoother with quadratic curves
|
||||
var sourcePoint = this.drewPoints[0],
|
||||
destinationPoint = this.drewPoints[1],
|
||||
pointsLength = this.drewPoints.length,
|
||||
getMiddlePointCoordinates = function (firstPoint, secondPoint) {
|
||||
return {
|
||||
x: firstPoint.x + (secondPoint.x - firstPoint.x) / 2,
|
||||
y: firstPoint.y + (secondPoint.y - firstPoint.y) / 2
|
||||
};
|
||||
};
|
||||
|
||||
this.drawingContext.beginPath();
|
||||
this.drawingContext.moveTo(sourcePoint.x, sourcePoint.y);
|
||||
|
||||
for (var i = 1; i < pointsLength; i++) {
|
||||
var middlePoint = getMiddlePointCoordinates(sourcePoint, destinationPoint);
|
||||
// this.drawingContext.lineTo(this.drewPoints[i].x, this.drewPoints[i].y);
|
||||
this.drawingContext.quadraticCurveTo(sourcePoint.x, sourcePoint.y, middlePoint.x, middlePoint.y);
|
||||
sourcePoint = this.drewPoints[i];
|
||||
destinationPoint = this.drewPoints[i+1];
|
||||
}
|
||||
|
||||
this.drawingContext.lineTo(sourcePoint.x, sourcePoint.y);
|
||||
this.drawingContext.stroke();
|
||||
|
||||
if (this.settings('fillWhileDrawing')) {
|
||||
this.drawingContext.fill();
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function onDrawingEnd (event) {
|
||||
if (this.isActive && this.isDrawing) {
|
||||
this.isDrawing = false;
|
||||
|
||||
// Select the nodes inside the path
|
||||
var nodes = this.renderer.nodesOnScreen,
|
||||
nodesLength = nodes.length,
|
||||
i = 0,
|
||||
prefix = this.renderer.options.prefix || '';
|
||||
|
||||
// Loop on all nodes and check if they are in the path
|
||||
while (nodesLength--) {
|
||||
var node = nodes[nodesLength],
|
||||
x = node[prefix + 'x'],
|
||||
y = node[prefix + 'y'];
|
||||
|
||||
if (this.drawingContext.isPointInPath(x, y) && !node.hidden) {
|
||||
this.selectedNodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch event with selected nodes
|
||||
this.dispatchEvent('selectedNodes', this.selectedNodes);
|
||||
|
||||
// Clear the drawing canvas
|
||||
this.drawingContext.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
|
||||
|
||||
this.drawingCanvas.style.cursor = this.settings('cursor');
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {sigma} sigmaInstance The related sigma instance.
|
||||
* @param {renderer} renderer The sigma instance renderer.
|
||||
* @param {sigma.classes.configurable} settings A settings class
|
||||
*
|
||||
* @return {sigma.plugins.lasso} Returns the instance
|
||||
*/
|
||||
sigma.plugins.lasso = function (sigmaInstance, renderer, settings) {
|
||||
// Create lasso if undefined
|
||||
if (!_instances[sigmaInstance.id]) {
|
||||
_instances[sigmaInstance.id] = new Lasso(sigmaInstance, renderer, settings);
|
||||
}
|
||||
|
||||
// Listen for sigmaInstance kill event, and remove the lasso isntance
|
||||
sigmaInstance.bind('kill', function () {
|
||||
if (_instances[sigmaInstance.id] instanceof Lasso) {
|
||||
_instances[sigmaInstance.id].clear();
|
||||
delete _instances[sigmaInstance.id];
|
||||
}
|
||||
});
|
||||
|
||||
return _instances[sigmaInstance.id];
|
||||
};
|
||||
|
||||
}).call(this);
|
|
@ -53,6 +53,10 @@
|
|||
if (callback) {
|
||||
callback.apply(this, args);
|
||||
}
|
||||
|
||||
if (this.graphViewer2) {
|
||||
this.graphViewer2.graphSettingsView.hide();
|
||||
}
|
||||
},
|
||||
|
||||
checkUser: function () {
|
||||
|
|
|
@ -11,23 +11,58 @@
|
|||
<div id="graphSettingsView" class="innerContent">
|
||||
|
||||
<div class="pure-g" style="margin-top: -15px">
|
||||
<div class="pure-u-1-1 pure-u-md-1-1 pure-u-lg-1-1 pure-u-xl-1-1">
|
||||
|
||||
<div class="pure-g pure-table pure-table-body">
|
||||
|
||||
<% _.each(general, function(val, key) { %>
|
||||
<% if (val.type === 'divider') { %>
|
||||
<div class="pure-u-1-1 left heading"><%=val.name%></div>
|
||||
<% } else { %>
|
||||
<div class="<%= genClass %> left"><%=val.name%></div>
|
||||
<div class="<%= genClass2 %> left">
|
||||
|
||||
<% if (val.type === 'select') { %>
|
||||
<select id="g_<%=key%>">
|
||||
<% _.each(val, function(option, optKey) { %>
|
||||
<% if (option.name) { %>
|
||||
<option value="<%=option.val%>"> <%=option.name%> </option>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</select>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'string') { %>
|
||||
<input id="g_<%=key%>" type="text" placeholder="string"></input>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'number') { %>
|
||||
<input id="g_<%=key%>" type="text" id="<%=val %>" value="<%=val.value %>" placeholder=""></input>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'range') { %>
|
||||
<input id="g_<%=key%>" type='range' min="0" max="50" val="<%=VALUE%>"/>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'color') { %>
|
||||
<input id="g_<%=key%>" type='color' name='color' value="<%=VALUE%>"/>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% }); %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1-1 pure-u-md-1-1 pure-u-lg-1-1 pure-u-xl-1-1">
|
||||
|
||||
<div class="sectionHeader pure-g">
|
||||
<div class="pure-u-1-1">
|
||||
<div class="title">
|
||||
Graph specific
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pure-g pure-table pure-table-body">
|
||||
<% _.each(specific, function(val, key) { %>
|
||||
|
||||
<% if (val.type === 'divider') { %>
|
||||
<div class="heading <%= genClass %> left"><%=val.name%></div>
|
||||
<div class="<%= genClass2 %> left"></div>
|
||||
<div class="pure-u-1-1 left heading"><%=val.name%></div>
|
||||
<% } else { %>
|
||||
|
||||
<div class="<%= genClass %> left"><%=val.name%></div>
|
||||
|
@ -53,6 +88,10 @@
|
|||
<input id="g_<%=key%>" type='color' name='color' value="<%=VALUE%>"/>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'range') { %>
|
||||
<input id="g_<%=key%>" type='range' min="0" max="100" val="<%=VALUE%>"/>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'select') { %>
|
||||
<select id="g_<%=key%>">
|
||||
<% _.each(val, function(option, optKey) { %>
|
||||
|
@ -70,50 +109,8 @@
|
|||
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1-1 pure-u-md-1-1 pure-u-lg-1-1 pure-u-xl-1-1">
|
||||
|
||||
<div class="sectionHeader pure-g">
|
||||
<div class="pure-u-1-1">
|
||||
<div class="title">
|
||||
General
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pure-g pure-table pure-table-body">
|
||||
|
||||
<% _.each(general, function(val, key) { %>
|
||||
<% if (val.type === 'divider') { %>
|
||||
<div class="heading <%= genClass %> left"><%=val.name%></div>
|
||||
<div class="<%= genClass2 %> left"></div>
|
||||
<% } else { %>
|
||||
<div class="<%= genClass %> left"><%=val.name%></div>
|
||||
|
||||
<% if (val.type === 'select') { %>
|
||||
<select id="g_<%=key%>">
|
||||
<% _.each(val, function(option, optKey) { %>
|
||||
<% if (option.name) { %>
|
||||
<option value="<%=option.val%>"> <%=option.name%> </option>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</select>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'string') { %>
|
||||
<input id="g_<%=key%>" type="text" placeholder="string"></input>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'number') { %>
|
||||
<input id="g_<%=key%>" type="text" id="<%=val %>" value="<%=val.value %>" placeholder=""></input>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
<% }); %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<button id="saveGraphSettings" style="margin-top: 20px;" class="button-success pull-right">Save</button>
|
||||
<button id="saveGraphSettings" style="margin-top: 20px; margin-right: 10px;" class="button-success pull-right">Save</button>
|
||||
<button id="restoreGraphSettings" style="margin-top: 20px;" class="button-success pull-right">Restore defaults</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -67,7 +67,13 @@
|
|||
'nodeLabel': {
|
||||
type: 'string',
|
||||
name: 'Label',
|
||||
desc: 'Default node color. RGB or HEX value.',
|
||||
desc: 'Node label. Please choose a valid and available node attribute.',
|
||||
default: '_key'
|
||||
},
|
||||
'nodeLabelThreshold': {
|
||||
type: 'range',
|
||||
name: 'Node label threshold',
|
||||
desc: 'The minimum size a node must have on screen to see its label displayed. This does not affect hovering behavior.',
|
||||
default: '_key'
|
||||
},
|
||||
'nodeColor': {
|
||||
|
@ -90,6 +96,12 @@
|
|||
name: 'Label',
|
||||
desc: 'Default edge label.'
|
||||
},
|
||||
'edgeLabelThreshold': {
|
||||
type: 'range',
|
||||
name: 'Edge label threshold',
|
||||
desc: 'The minimum size an edge must have on screen to see its label displayed. This does not affect hovering behavior.',
|
||||
default: '_key'
|
||||
},
|
||||
'edgeColor': {
|
||||
type: 'color',
|
||||
name: 'Color',
|
||||
|
@ -112,7 +124,8 @@
|
|||
curve: {
|
||||
name: 'Curve',
|
||||
val: 'curve'
|
||||
},
|
||||
}
|
||||
/*
|
||||
arrow: {
|
||||
name: 'Arrow',
|
||||
val: 'arrow'
|
||||
|
@ -121,6 +134,7 @@
|
|||
name: 'Curved Arrow',
|
||||
val: 'curvedArrow'
|
||||
}
|
||||
*/
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -137,13 +151,14 @@
|
|||
'click #restoreGraphSettings': 'restoreGraphSettings',
|
||||
'keyup #graphSettingsView input': 'checkEnterKey',
|
||||
'keyup #graphSettingsView select': 'checkEnterKey',
|
||||
'change input[type="range"]': 'saveGraphSettings',
|
||||
'change input[type="color"]': 'checkColor',
|
||||
'change select': 'saveGraphSettings',
|
||||
'focus #graphSettingsView input': 'lastFocus',
|
||||
'focus #graphSettingsView select': 'lastFocus'
|
||||
},
|
||||
|
||||
lastFocus: function (e) {
|
||||
console.log(e.currentTarget.id);
|
||||
console.log(e.currentTarget);
|
||||
this.lastFocussed = e.currentTarget.id;
|
||||
},
|
||||
|
||||
|
@ -167,17 +182,25 @@
|
|||
});
|
||||
},
|
||||
|
||||
saveGraphSettings: function () {
|
||||
checkColor: function () {
|
||||
this.saveGraphSettings(true);
|
||||
},
|
||||
|
||||
saveGraphSettings: function (color, nodeStart) {
|
||||
var self = this;
|
||||
console.log('CLICK');
|
||||
var combinedName = window.App.currentDB.toJSON().name + '_' + this.name;
|
||||
|
||||
var config = {};
|
||||
|
||||
config[combinedName] = {
|
||||
layout: $('#g_layout').val(),
|
||||
renderer: $('#g_renderer').val(),
|
||||
depth: $('#g_depth').val(),
|
||||
nodeColor: $('#g_nodeColor').val(),
|
||||
nodeLabelThreshold: $('#g_nodeLabelThreshold').val(),
|
||||
edgeColor: $('#g_edgeColor').val(),
|
||||
edgeLabelThreshold: $('#g_edgeLabelThreshold').val(),
|
||||
nodeLabel: $('#g_nodeLabel').val(),
|
||||
edgeLabel: $('#g_edgeLabel').val(),
|
||||
edgeType: $('#g_edgeType').val(),
|
||||
|
@ -186,9 +209,17 @@
|
|||
nodeStart: $('#g_nodeStart').val()
|
||||
};
|
||||
|
||||
if (nodeStart) {
|
||||
config[combinedName].nodeStart = nodeStart;
|
||||
}
|
||||
|
||||
var callback = function () {
|
||||
if (window.App.graphViewer2) {
|
||||
window.App.graphViewer2.render(self.lastFocussed);
|
||||
if (color) {
|
||||
window.App.graphViewer2.updateColors();
|
||||
} else {
|
||||
window.App.graphViewer2.render(self.lastFocussed);
|
||||
}
|
||||
} else {
|
||||
arangoHelper.arangoNotification('Graph ' + this.name, 'Configuration saved.');
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
events: {
|
||||
'click #downloadPNG': 'downloadSVG',
|
||||
'click #reloadGraph': 'reloadGraph',
|
||||
'click #settingsMenu': 'toggleSettings'
|
||||
'click #settingsMenu': 'toggleSettings',
|
||||
'click #noGraphToggle': 'toggleSettings'
|
||||
},
|
||||
|
||||
cursorX: 0,
|
||||
|
@ -62,6 +63,18 @@
|
|||
}
|
||||
return neighbors;
|
||||
});
|
||||
|
||||
sigma.classes.graph.addMethod('getNodeEdges', function (nodeId) {
|
||||
var edges = this.edges();
|
||||
var edgesToReturn = [];
|
||||
|
||||
_.each(edges, function (edge) {
|
||||
if (edge.source === nodeId || edge.target === nodeId) {
|
||||
edgesToReturn.push(edge.id);
|
||||
}
|
||||
});
|
||||
return edgesToReturn;
|
||||
});
|
||||
} catch (ignore) {}
|
||||
},
|
||||
|
||||
|
@ -220,19 +233,96 @@
|
|||
this.cursorY = e.y;
|
||||
},
|
||||
|
||||
deleteNode: function () {
|
||||
var self = this;
|
||||
var documentKey = $('#delete-node-attr-id').text();
|
||||
var collectionId = documentKey.split('/')[0];
|
||||
var documentId = documentKey.split('/')[1];
|
||||
|
||||
if ($('#delete-node-edges-attr').val() === 'yes') {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
type: 'DELETE',
|
||||
contentType: 'application/json',
|
||||
url: arangoHelper.databaseUrl(
|
||||
'/_api/gharial/' + encodeURIComponent(self.name) + '/vertex/' + encodeURIComponent(documentKey.split('/')[0]) + '/' + encodeURIComponent(documentKey.split('/')[1])
|
||||
),
|
||||
success: function (data) {
|
||||
self.currentGraph.graph.dropNode(documentKey);
|
||||
self.currentGraph.refresh();
|
||||
},
|
||||
error: function () {
|
||||
arangoHelper.arangoError('Graph', 'Could not delete node.');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var callback = function (error) {
|
||||
if (!error) {
|
||||
self.currentGraph.graph.dropNode(documentKey);
|
||||
|
||||
// rerender graph
|
||||
self.currentGraph.refresh();
|
||||
} else {
|
||||
arangoHelper.arangoError('Graph', 'Could not delete node.');
|
||||
}
|
||||
};
|
||||
|
||||
this.documentStore.deleteDocument(collectionId, documentId, callback);
|
||||
}
|
||||
window.modalView.hide();
|
||||
},
|
||||
|
||||
deleteNodeModal: function (nodeId) {
|
||||
var buttons = []; var tableContent = [];
|
||||
|
||||
tableContent.push(
|
||||
window.modalView.createReadOnlyEntry('delete-node-attr-id', 'Really delete node', nodeId)
|
||||
);
|
||||
|
||||
tableContent.push(
|
||||
window.modalView.createSelectEntry(
|
||||
'delete-node-edges-attr',
|
||||
'Also delete edges?',
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
value: 'yes',
|
||||
label: 'Yes'
|
||||
},
|
||||
{
|
||||
value: 'no',
|
||||
label: 'No'
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
window.modalView.createDeleteButton('Delete', this.deleteNode.bind(this))
|
||||
);
|
||||
|
||||
window.modalView.show(
|
||||
'modalTable.ejs',
|
||||
'Delete node',
|
||||
buttons,
|
||||
tableContent
|
||||
);
|
||||
},
|
||||
|
||||
addNode: function () {
|
||||
var self = this;
|
||||
|
||||
var collectionId = $('.modal-body #new-node-collection-attr').val();
|
||||
var key = $('.modal-body #new-node-key-attr').last().val();
|
||||
|
||||
var callback = function (error, id) {
|
||||
var callback = function (error, id, msg) {
|
||||
if (error) {
|
||||
arangoHelper.arangoError('Error', 'Could not create node');
|
||||
arangoHelper.arangoError('Could not create node', msg.errorMessage);
|
||||
} else {
|
||||
self.currentGraph.graph.addNode({
|
||||
id: id,
|
||||
label: self.graphConfig.nodeLabel,
|
||||
label: self.graphConfig.nodeLabel || '',
|
||||
size: self.graphConfig.nodeSize || Math.random(),
|
||||
color: self.graphConfig.nodeColor || '#2ecc71',
|
||||
x: self.cursorX,
|
||||
|
@ -412,6 +502,26 @@
|
|||
}
|
||||
},
|
||||
|
||||
updateColors: function () {
|
||||
var combinedName = window.App.currentDB.toJSON().name + '_' + this.name;
|
||||
var self = this;
|
||||
|
||||
this.userConfig.fetch({
|
||||
success: function (data) {
|
||||
self.graphConfig = data.toJSON().graphs[combinedName];
|
||||
self.currentGraph.graph.nodes().forEach(function (n) {
|
||||
n.color = self.graphConfig.nodeColor;
|
||||
});
|
||||
|
||||
self.currentGraph.graph.edges().forEach(function (e) {
|
||||
e.color = self.graphConfig.edgeColor;
|
||||
});
|
||||
|
||||
self.currentGraph.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// right click background context menu
|
||||
createContextMenu: function (e) {
|
||||
var self = this;
|
||||
|
@ -498,7 +608,7 @@
|
|||
wheel.multiSelect = true;
|
||||
wheel.clickModeRotate = false;
|
||||
wheel.slicePathFunction = slicePath().DonutSlice;
|
||||
wheel.createWheel([icon.edit, icon.trash, icon.arrowleft2, icon.connect]);
|
||||
wheel.createWheel([icon.edit, icon.trash, icon.play, icon.connect]);
|
||||
|
||||
wheel.navItems[0].selected = false;
|
||||
wheel.navItems[0].hovered = false;
|
||||
|
@ -513,7 +623,7 @@
|
|||
// function 1: delete
|
||||
wheel.navItems[1].navigateFunction = function (e) {
|
||||
self.clearOldContextMenu();
|
||||
self.deleteNode(nodeId);
|
||||
self.deleteNodeModal(nodeId);
|
||||
};
|
||||
|
||||
// function 2: mark as start node
|
||||
|
@ -599,6 +709,11 @@
|
|||
});
|
||||
},
|
||||
|
||||
setStartNode: function (id) {
|
||||
this.graphConfig.nodeStart = id;
|
||||
this.graphSettingsView.saveGraphSettings(null, id);
|
||||
},
|
||||
|
||||
editNode: function (id) {
|
||||
var callback = function () {};
|
||||
|
||||
|
@ -627,6 +742,44 @@
|
|||
return array;
|
||||
},
|
||||
|
||||
initializeGraph: function (sigmaInstance, graph) {
|
||||
var self = this;
|
||||
// sigmaInstance.graph.read(graph);
|
||||
sigmaInstance.refresh();
|
||||
|
||||
this.Sigma.plugins.Lasso = sigma.plugins.lasso;
|
||||
|
||||
var lasso = new this.Sigma.plugins.Lasso(sigmaInstance, sigmaInstance.renderers[0], {
|
||||
'strokeStyle': 'black',
|
||||
'lineWidth': 1,
|
||||
'fillWhileDrawing': true,
|
||||
'fillStyle': 'rgba(41, 41, 41, 0.2)',
|
||||
'cursor': 'crosshair'
|
||||
});
|
||||
|
||||
// Listen for selectedNodes event
|
||||
lasso.bind('selectedNodes', function (event) {
|
||||
// Do something with the selected nodes
|
||||
var nodes = event.data;
|
||||
|
||||
console.log('nodes', nodes);
|
||||
|
||||
// For instance, reset all node size as their initial size
|
||||
sigmaInstance.graph.nodes().forEach(function (node) {
|
||||
node.color = self.graphConfig.nodeColor;
|
||||
});
|
||||
|
||||
// Then increase the size of selected nodes...
|
||||
nodes.forEach(function (node) {
|
||||
node.color = 'red';
|
||||
});
|
||||
|
||||
sigmaInstance.refresh();
|
||||
});
|
||||
|
||||
return lasso;
|
||||
},
|
||||
|
||||
renderGraph: function (graph, toFocus) {
|
||||
var self = this;
|
||||
|
||||
|
@ -634,16 +787,22 @@
|
|||
|
||||
if (graph.edges.length === 0) {
|
||||
var string = 'No edges found for starting point: <span style="font-weight: 400">' + self.graphSettings.startVertex._id + '</span>';
|
||||
arangoHelper.arangoError('Graph', string);
|
||||
$('#calculatingGraph').html(
|
||||
'<div style="font-weight: 300; font-size: 10.5pt"><span style="font-weight: 400">Stopped. </span></br></br>' +
|
||||
string +
|
||||
'. Please <a style="color: #3498db" href="' + window.location.href +
|
||||
'/settings">choose a different start node </a>or try to reload the graph. ' +
|
||||
'. Please <span id="noGraphToggle" style="cursor: pointer; color: #3498db">choose a different start node </span>or try to reload the graph. ' +
|
||||
'<i id="reloadGraph" class="fa fa-refresh" style="cursor: pointer"></i></div>'
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
$('#content').append(
|
||||
'<div style="position: absolute; right: 25px; bottom: 45px;">' +
|
||||
'<span style="margin-right: 10px" class="arangoState">' + graph.nodes.length + ' nodes</span>' +
|
||||
'<span class="arangoState">' + graph.edges.length + ' edges</span>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
|
||||
this.Sigma = sigma;
|
||||
|
||||
// defaults
|
||||
|
@ -674,15 +833,15 @@
|
|||
defaultEdgeHoverColor: '#000',
|
||||
defaultEdgeType: 'line',
|
||||
edgeHoverSizeRatio: 2,
|
||||
edgeHoverExtremities: true
|
||||
edgeHoverExtremities: true,
|
||||
// lasso settings
|
||||
autoRescale: true,
|
||||
mouseEnabled: true,
|
||||
touchEnabled: true,
|
||||
nodesPowRatio: 1,
|
||||
edgesPowRatio: 1
|
||||
};
|
||||
|
||||
if (this.graphConfig) {
|
||||
if (this.graphConfig.edgeType) {
|
||||
settings.defaultEdgeType = this.graphConfig.edgeType;
|
||||
}
|
||||
}
|
||||
|
||||
// adjust display settings for big graphs
|
||||
if (graph.nodes.length > 500) {
|
||||
// show node label if size is 15
|
||||
|
@ -690,6 +849,20 @@
|
|||
settings.hideEdgesOnMove = true;
|
||||
}
|
||||
|
||||
if (this.graphConfig) {
|
||||
if (this.graphConfig.edgeType) {
|
||||
settings.defaultEdgeType = this.graphConfig.edgeType;
|
||||
}
|
||||
|
||||
if (this.graphConfig.nodeLabelThreshold) {
|
||||
settings.labelThreshold = this.graphConfig.nodeLabelThreshold;
|
||||
}
|
||||
|
||||
if (this.graphConfig.edgeLabelThreshold) {
|
||||
settings.edgeLabelThreshold = this.graphConfig.edgeLabelThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
// adjust display settings for webgl renderer
|
||||
if (renderer === 'webgl') {
|
||||
settings.enableEdgeHovering = false;
|
||||
|
@ -751,6 +924,54 @@
|
|||
self.clearMouseCanvas();
|
||||
});
|
||||
|
||||
s.bind('overNode', function (e) {
|
||||
$('.nodeInfoDiv').remove();
|
||||
if (self.contextState.createEdge === false) {
|
||||
var callback = function (error, data) {
|
||||
if (!error) {
|
||||
var obj = {};
|
||||
var counter = 0;
|
||||
var more = false;
|
||||
|
||||
_.each(data, function (val, key) {
|
||||
if (counter < 15) {
|
||||
if (typeof val === 'string') {
|
||||
if (val.length > 10) {
|
||||
obj[key] = val.substr(0, 10) + ' ...';
|
||||
} else {
|
||||
obj[key] = val;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
more = true;
|
||||
}
|
||||
counter++;
|
||||
});
|
||||
|
||||
var string = '<div id="nodeInfoDiv" class="nodeInfoDiv">' +
|
||||
'<pre>' + JSON.stringify(obj, null, 2);
|
||||
|
||||
if (more) {
|
||||
string = string.substr(0, string.length - 2);
|
||||
string += ' \n\n ... \n\n } </pre></div>';
|
||||
} else {
|
||||
string += '</pre></div>';
|
||||
}
|
||||
|
||||
$('#content').append(string);
|
||||
}
|
||||
};
|
||||
|
||||
self.documentStore.getDocument(e.data.node.id.split('/')[0], e.data.node.id.split('/')[1], callback);
|
||||
}
|
||||
});
|
||||
|
||||
s.bind('outNode', function (e) {
|
||||
if (self.contextState.createEdge === false) {
|
||||
$('.nodeInfoDiv').remove();
|
||||
}
|
||||
});
|
||||
|
||||
s.bind('clickNode', function (e) {
|
||||
if (self.contextState.createEdge === true) {
|
||||
// create the edge
|
||||
|
@ -831,6 +1052,7 @@
|
|||
window.setTimeout(function () {
|
||||
s.stopForceAtlas2();
|
||||
dragListener = sigma.plugins.dragNodes(s, s.renderers[0]);
|
||||
console.log(dragListener);
|
||||
}, duration);
|
||||
} else if (algorithm === 'fruchtermann') {
|
||||
// Start the Fruchterman-Reingold algorithm:
|
||||
|
@ -839,7 +1061,6 @@
|
|||
} else {
|
||||
dragListener = sigma.plugins.dragNodes(s, s.renderers[0]);
|
||||
}
|
||||
console.log(dragListener);
|
||||
|
||||
// add listener to keep track of cursor position
|
||||
var c = document.getElementsByClassName('sigma-mouse')[0];
|
||||
|
@ -849,6 +1070,28 @@
|
|||
if (toFocus) {
|
||||
$('#' + toFocus).focus();
|
||||
}
|
||||
|
||||
// init graph lasso
|
||||
self.graphLasso = self.initializeGraph(s, graph);
|
||||
self.graphLasso.activate();
|
||||
self.graphLasso.deactivate();
|
||||
|
||||
// add lasso event
|
||||
// Toggle lasso activation on Alt + l
|
||||
document.addEventListener('keyup', function (event) {
|
||||
switch (event.keyCode) {
|
||||
case 76:
|
||||
if (event.altKey) {
|
||||
if (self.graphLasso.isActive) {
|
||||
self.graphLasso.deactivate();
|
||||
} else {
|
||||
self.graphLasso.activate();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// clear up info div
|
||||
$('#calculatingGraph').remove();
|
||||
}
|
||||
|
|
|
@ -1473,15 +1473,6 @@
|
|||
);
|
||||
}
|
||||
}
|
||||
if (data.extra.stats.scannedFull > 0) {
|
||||
appendSpan(
|
||||
'full collection scan', 'fa-exclamation-circle warning', 'additional'
|
||||
);
|
||||
} else {
|
||||
appendSpan(
|
||||
'no full collection scan', 'fa-check-circle positive', 'additional'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
.graphContent {
|
||||
|
||||
margin-top: 3px;
|
||||
|
||||
#graph-container {
|
||||
background-color: $c-white;
|
||||
z-index: 5;
|
||||
|
@ -40,6 +42,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
.nodeInfoDiv {
|
||||
left: 25px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 130px;
|
||||
|
||||
pre {
|
||||
background-color: rgba(64, 74, 83, .9);
|
||||
border-radius: 2px;
|
||||
color: $c-white;
|
||||
max-height: 400px;
|
||||
max-width: 330px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.nodeContextMenu {
|
||||
|
||||
position: fixed;
|
||||
|
@ -67,6 +85,11 @@
|
|||
border: 0;
|
||||
border-radius: 0;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
|
||||
button {
|
||||
margin-bottom: 125px;
|
||||
}
|
||||
}
|
||||
|
||||
#graphSettingsContent {
|
||||
|
@ -79,13 +102,29 @@
|
|||
width: 400px;
|
||||
|
||||
.pure-g {
|
||||
|
||||
font-size: 10pt;
|
||||
|
||||
input,
|
||||
select {
|
||||
color: $c-black;
|
||||
}
|
||||
|
||||
.left {
|
||||
color: $c-white;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.pure-u-2-3 {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.heading {
|
||||
border-bottom: 1px solid $c-white;
|
||||
height: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.pure-table {
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
border: 1px solid $c-white;
|
||||
border-radius: 3px;
|
||||
margin-top: 10px;
|
||||
width: 218px;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
background: $c-tab-bottom-border;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
height: 5px;
|
||||
width: 218px;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
background: $c-positive;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
height: 16px;
|
||||
margin-top: -5px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: $c-accordion-heading;
|
||||
}
|
||||
|
||||
input[type=range]::-moz-range-track {
|
||||
background: $c-e1grey;
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
height: 5px;
|
||||
width: 218px;
|
||||
}
|
||||
|
||||
input[type=range]::-moz-range-thumb {
|
||||
background: $c-positive;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
input[type=range]:-moz-focusring {
|
||||
outline: 1px solid $c-white;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-track {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
border-width: 6px 0;
|
||||
color: transparent;
|
||||
height: 5px;
|
||||
width: 218px;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-fill-lower {
|
||||
background: $c-darker-grey;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-fill-upper {
|
||||
background: $c-tab-bottom-border;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-thumb {
|
||||
background: $c-positive;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
input[type=range]:focus::-ms-fill-lower {
|
||||
background: $c-dark-grey;
|
||||
}
|
||||
|
||||
input[type=range]:focus::-ms-fill-upper {
|
||||
background: $c-accordion-heading;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
.arangoState {
|
||||
background-color: $c-bluegrey-dark;
|
||||
border-radius: 3px;
|
||||
color: $c-white;
|
||||
font-size: 10pt;
|
||||
font-weight: 100;
|
||||
padding: 5px 8px;
|
||||
}
|
|
@ -86,6 +86,11 @@
|
|||
// screen shards
|
||||
@import 'shards';
|
||||
|
||||
// input type range
|
||||
@import 'range';
|
||||
// state
|
||||
@import 'state';
|
||||
|
||||
//arangoTable Template
|
||||
@import 'arangoTable';
|
||||
//arangoTabbar Template
|
||||
|
|
Loading…
Reference in New Issue