1
0
Fork 0

gv [ci skip]

This commit is contained in:
hkernbach 2016-07-13 16:39:56 +02:00
parent 08b892f09f
commit 1929b24901
12 changed files with 620 additions and 112 deletions

View File

@ -65,6 +65,9 @@
"frontend/js/lib/sigma.plugins.fullScreen.js", "frontend/js/lib/sigma.plugins.fullScreen.js",
"frontend/js/lib/sigma.layout.fruchtermanReingold.js", "frontend/js/lib/sigma.layout.fruchtermanReingold.js",
"frontend/js/lib/sigma.exporters.svg.js", "frontend/js/lib/sigma.exporters.svg.js",
"frontend/js/lib/sigma.canvas.edges.labels.curve.js",
"frontend/js/lib/sigma.canvas.edges.labels.curvedArrow.js",
"frontend/js/lib/sigma.canvas.edges.labels.def.js",
"frontend/js/lib/worker.js", "frontend/js/lib/worker.js",
"frontend/js/lib/supervisor.js", "frontend/js/lib/supervisor.js",
// END SIGMA LIBRARIES // END SIGMA LIBRARIES

View File

@ -292,10 +292,33 @@ authRouter.get('/graph/:name', function (req, res) {
var graph = gm._graph(name); var graph = gm._graph(name);
var vertexName = graph._vertexCollections()[0].name(); var vertexName = graph._vertexCollections()[0].name();
var startVertex = db[vertexName].any();
var startVertex;
var config;
try {
config = req.queryParams;
} catch (e) {
res.throw('bad request', e.message, {cause: e});
}
if (config.nodeStart) {
try {
startVertex = db._document(config.nodeStart);
} catch (e) {
res.throw('bad request', e.message, {cause: e});
}
console.log(startVertex);
if (!startVertex) {
startVertex = db[vertexName].any();
}
} else {
startVertex = db[vertexName].any();
}
var aqlQuery = var aqlQuery =
'FOR v, e, p IN 1..3 ANY "' + startVertex._id + '" GRAPH "' + name + '"' + 'FOR v, e, p IN 1..' + (config.depth || '2') + ' ANY "' + startVertex._id + '" GRAPH "' + name + '"' +
'RETURN p' 'RETURN p'
; ;
@ -307,29 +330,53 @@ authRouter.get('/graph/:name', function (req, res) {
var edgesArr = []; var edgesArr = [];
_.each(cursor.json, function (obj) { _.each(cursor.json, function (obj) {
var edgeLabel;
_.each(obj.edges, function (edge) { _.each(obj.edges, function (edge) {
if (edge._to && edge._from) { if (edge._to && edge._from) {
if (config.edgeLabel) {
// configure edge labels
edgeLabel = edge[config.edgeLabel];
if (edgeLabel) {
edgeLabel = edgeLabel.toString();
}
if (!edgeLabel) {
edgeLabel = 'attribute ' + config.edgeLabel + ' not found';
}
}
edgesObj[edge._from + edge._to] = { edgesObj[edge._from + edge._to] = {
id: edge._id, id: edge._id,
source: edge._from, source: edge._from,
color: '#cccccc', label: edgeLabel,
color: config.edgeColor || '#cccccc',
target: edge._to target: edge._to
}; };
} }
}); });
var label;
var nodeLabel;
var nodeSize;
_.each(obj.vertices, function (node) { _.each(obj.vertices, function (node) {
if (node.label) { if (config.nodeLabel) {
label = node.label; nodeLabel = node[config.nodeLabel];
} else { }
label = node._id; if (!nodeLabel) {
nodeLabel = node._id;
}
if (config.nodeSize) {
nodeSize = node[config.nodeSize];
} }
nodesObj[node._id] = { nodesObj[node._id] = {
id: node._id, id: node._id,
label: label, label: nodeLabel,
size: Math.random(), size: nodeSize || Math.random(),
color: '#2ecc71', color: config.nodeColor || '#2ecc71',
x: Math.random(), x: Math.random(),
y: Math.random() y: Math.random()
}; };

View File

@ -0,0 +1,112 @@
;(function(undefined) {
'use strict';
if (typeof sigma === 'undefined')
throw 'sigma is not declared';
// Initialize packages:
sigma.utils.pkg('sigma.canvas.edges.labels');
/**
* This label renderer will just display the label on the curve of the edge.
* The label is rendered at half distance of the edge extremities, and is
* always oriented from left to right on the top side of the curve.
*
* @param {object} edge The edge object.
* @param {object} source node The edge source node.
* @param {object} target node The edge target node.
* @param {CanvasRenderingContext2D} context The canvas context.
* @param {configurable} settings The settings function.
*/
sigma.canvas.edges.labels.curve =
function(edge, source, target, context, settings) {
if (typeof edge.label !== 'string')
return;
var prefix = settings('prefix') || '',
size = edge[prefix + 'size'] || 1;
if (size < settings('edgeLabelThreshold'))
return;
var fontSize,
sSize = source[prefix + 'size'],
sX = source[prefix + 'x'],
sY = source[prefix + 'y'],
tX = target[prefix + 'x'],
tY = target[prefix + 'y'],
dX = tX - sX,
dY = tY - sY,
sign = (sX < tX) ? 1 : -1,
cp = {},
c,
angle,
t = 0.5; //length of the curve
if (source.id === target.id) {
cp = sigma.utils.getSelfLoopControlPoints(sX, sY, sSize);
c = sigma.utils.getPointOnBezierCurve(
t, sX, sY, tX, tY, cp.x1, cp.y1, cp.x2, cp.y2
);
angle = Math.atan2(1, 1); // 45°
} else {
cp = sigma.utils.getQuadraticControlPoint(sX, sY, tX, tY);
c = sigma.utils.getPointOnQuadraticCurve(t, sX, sY, tX, tY, cp.x, cp.y);
angle = Math.atan2(dY * sign, dX * sign);
}
// The font size is sublineraly proportional to the edge size, in order to
// avoid very large labels on screen.
// This is achieved by f(x) = x * x^(-1/ a), where 'x' is the size and 'a'
// is the edgeLabelSizePowRatio. Notice that f(1) = 1.
// The final form is:
// f'(x) = b * x * x^(-1 / a), thus f'(1) = b. Application:
// fontSize = defaultEdgeLabelSize if edgeLabelSizePowRatio = 1
fontSize = (settings('edgeLabelSize') === 'fixed') ?
settings('defaultEdgeLabelSize') :
settings('defaultEdgeLabelSize') *
size *
Math.pow(size, -1 / settings('edgeLabelSizePowRatio'));
context.save();
if (edge.active) {
context.font = [
settings('activeFontStyle'),
fontSize + 'px',
settings('activeFont') || settings('font')
].join(' ');
context.fillStyle =
settings('edgeActiveColor') === 'edge' ?
(edge.active_color || settings('defaultEdgeActiveColor')) :
settings('defaultEdgeLabelActiveColor');
}
else {
context.font = [
settings('fontStyle'),
fontSize + 'px',
settings('font')
].join(' ');
context.fillStyle =
(settings('edgeLabelColor') === 'edge') ?
(edge.color || settings('defaultEdgeColor')) :
settings('defaultEdgeLabelColor');
}
context.textAlign = 'center';
context.textBaseline = 'alphabetic';
context.translate(c.x, c.y);
context.rotate(angle);
context.fillText(
edge.label,
0,
(-size / 2) - 3
);
context.restore();
};
}).call(this);

View File

@ -0,0 +1,25 @@
;(function(undefined) {
'use strict';
if (typeof sigma === 'undefined')
throw 'sigma is not declared';
// Initialize packages:
sigma.utils.pkg('sigma.canvas.edges.labels');
/**
* This label renderer will just display the label on the curve of the edge.
* The label is rendered at half distance of the edge extremities, and is
* always oriented from left to right on the top side of the curve.
*
* @param {object} edge The edge object.
* @param {object} source node The edge source node.
* @param {object} target node The edge target node.
* @param {CanvasRenderingContext2D} context The canvas context.
* @param {configurable} settings The settings function.
*/
sigma.canvas.edges.labels.curvedArrow =
function(edge, source, target, context, settings) {
sigma.canvas.edges.labels.curve(edge, source, target, context, settings);
};
}).call(this);

View File

@ -0,0 +1,96 @@
;(function(undefined) {
'use strict';
if (typeof sigma === 'undefined')
throw 'sigma is not declared';
// Initialize packages:
sigma.utils.pkg('sigma.canvas.edges.labels');
/**
* This label renderer will just display the label on the line of the edge.
* The label is rendered at half distance of the edge extremities, and is
* always oriented from left to right on the top side of the line.
*
* @param {object} edge The edge object.
* @param {object} source node The edge source node.
* @param {object} target node The edge target node.
* @param {CanvasRenderingContext2D} context The canvas context.
* @param {configurable} settings The settings function.
*/
sigma.canvas.edges.labels.def =
function(edge, source, target, context, settings) {
if (typeof edge.label !== 'string' || source == target)
return;
var prefix = settings('prefix') || '',
size = edge[prefix + 'size'] || 1;
if (size < settings('edgeLabelThreshold'))
return;
if (0 === settings('edgeLabelSizePowRatio'))
throw '"edgeLabelSizePowRatio" must not be 0.';
var fontSize,
x = (source[prefix + 'x'] + target[prefix + 'x']) / 2,
y = (source[prefix + 'y'] + target[prefix + 'y']) / 2,
dX = target[prefix + 'x'] - source[prefix + 'x'],
dY = target[prefix + 'y'] - source[prefix + 'y'],
sign = (source[prefix + 'x'] < target[prefix + 'x']) ? 1 : -1,
angle = Math.atan2(dY * sign, dX * sign);
// The font size is sublineraly proportional to the edge size, in order to
// avoid very large labels on screen.
// This is achieved by f(x) = x * x^(-1/ a), where 'x' is the size and 'a'
// is the edgeLabelSizePowRatio. Notice that f(1) = 1.
// The final form is:
// f'(x) = b * x * x^(-1 / a), thus f'(1) = b. Application:
// fontSize = defaultEdgeLabelSize if edgeLabelSizePowRatio = 1
fontSize = (settings('edgeLabelSize') === 'fixed') ?
settings('defaultEdgeLabelSize') :
settings('defaultEdgeLabelSize') *
size *
Math.pow(size, -1 / settings('edgeLabelSizePowRatio'));
context.save();
if (edge.active) {
context.font = [
settings('activeFontStyle'),
fontSize + 'px',
settings('activeFont') || settings('font')
].join(' ');
context.fillStyle =
settings('edgeActiveColor') === 'edge' ?
(edge.active_color || settings('defaultEdgeActiveColor')) :
settings('defaultEdgeLabelActiveColor');
}
else {
context.font = [
settings('fontStyle'),
fontSize + 'px',
settings('font')
].join(' ');
context.fillStyle =
(settings('edgeLabelColor') === 'edge') ?
(edge.color || settings('defaultEdgeColor')) :
settings('defaultEdgeLabelColor');
}
context.textAlign = 'center';
context.textBaseline = 'alphabetic';
context.translate(x, y);
context.rotate(angle);
context.fillText(
edge.label,
0,
(-size / 2) - 3
);
context.restore();
};
}).call(this);

View File

@ -42,7 +42,7 @@ window.UserConfig = Backbone.Model.extend({
} }
}, },
error: function () { error: function () {
arangoHelper.arangoNotification('User configuration', 'Could not update user configuration for key: ' + keyName); arangoHelper.arangoError('User configuration', 'Could not update user configuration for key: ' + keyName);
} }
}); });
}, },
@ -61,7 +61,7 @@ window.UserConfig = Backbone.Model.extend({
callback(keyValue); callback(keyValue);
}, },
error: function () { error: function () {
arangoHelper.arangoNotification('User configuration', 'Could not fetch user configuration for key: ' + keyName); arangoHelper.arangoError('User configuration', 'Could not fetch user configuration for key: ' + keyName);
} }
}); });
} }

View File

@ -671,6 +671,9 @@
this.waitForInit(this.graph2.bind(this), name); this.waitForInit(this.graph2.bind(this), name);
return; return;
} }
if (this.graphViewer2) {
this.graphViewer2.remove();
}
this.graphViewer2 = new window.GraphViewer2({ this.graphViewer2 = new window.GraphViewer2({
name: name, name: name,
userConfig: this.userConfig userConfig: this.userConfig
@ -684,6 +687,9 @@
this.waitForInit(this.graph2settings.bind(this), name); this.waitForInit(this.graph2settings.bind(this), name);
return; return;
} }
if (this.graphSettingsView) {
this.graphSettingsView.remove();
}
this.graphSettingsView = new window.GraphSettingsView({ this.graphSettingsView = new window.GraphSettingsView({
name: name, name: name,
userConfig: this.userConfig userConfig: this.userConfig

View File

@ -7,7 +7,7 @@
<% return name.charAt(0).toUpperCase() + string.slice(1);%> <% return name.charAt(0).toUpperCase() + string.slice(1);%>
<% }; %> <% }; %>
<div id="shardsContent" class="innerContent"> <div id="graphSettingsView" class="innerContent">
<div class="pure-g" style="margin-top: -15px"> <div class="pure-g" style="margin-top: -15px">
@ -21,15 +21,14 @@
</div> </div>
</div> </div>
<div class="pure-g pure-table pure-table-header pure-title">
<div class="pure-table-row">
<div class="<%= genClass %> left">Name</div>
<div class="<%= genClass %> left">Property</div>
</div>
</div>
<div class="pure-g pure-table pure-table-body"> <div class="pure-g pure-table pure-table-body">
<% _.each(specific, function(val, key) { %> <% _.each(specific, function(val, key) { %>
<% if (val.type === 'divider') { %>
<div class="heading <%= genClass %> left"><%=val.name%></div>
<div class="<%= genClass %> left"></div>
<% } else { %>
<div class="<%= genClass %> left"><%=val.name%></div> <div class="<%= genClass %> left"><%=val.name%></div>
<div class="<%= genClass %> left"> <div class="<%= genClass %> left">
@ -42,19 +41,23 @@
<% } %> <% } %>
<% if (val.type === 'string') { %> <% if (val.type === 'string') { %>
<input type="text" value="<%=VALUE%>" placeholder=""></input> <input id="g_<%=key%>" type="text" placeholder="string"></input>
<% } %>
<% if (val.type === 'number') { %>
<input id="g_<%=key%>" type="text" placeholder="number"></input>
<% } %> <% } %>
<% if (val.type === 'color') { %> <% if (val.type === 'color') { %>
<input type='color' name='color' value="<%=VALUE%>"/> <input id="g_<%=key%>" type='color' name='color' value="<%=VALUE%>"/>
<% } %> <% } %>
<% if (val.type === 'select') { %> <% if (val.type === 'select') { %>
<div class="<%= genClass %> left"> <div class="<%= genClass %> left">
<select> <select id="g_<%=key%>">
<% _.each(val, function(option, optKey) { %> <% _.each(val, function(option, optKey) { %>
<% if (option.name) { %> <% if (option.name) { %>
<option> <%=option.name%> </option> <option value="<%=option.val%>"> <%=option.name%> </option>
<% } %> <% } %>
<% }); %> <% }); %>
</select> </select>
@ -62,6 +65,7 @@
<% } %> <% } %>
</div> </div>
<% } %>
<% }); %> <% }); %>
</div> </div>
@ -77,16 +81,13 @@
</div> </div>
</div> </div>
<div class="pure-g pure-table pure-table-header pure-title">
<div class="pure-table-row">
<div class="<%= genClass %> left">Name</div>
<div class="<%= genClass %> left">Property</div>
</div>
</div>
<div class="pure-g pure-table pure-table-body"> <div class="pure-g pure-table pure-table-body">
<% _.each(general, function(val, key) { %> <% _.each(general, function(val, key) { %>
<% if (val.type === 'divider') { %>
<div class="heading <%= genClass %> left"><%=val.name%></div>
<div class="<%= genClass %> left"></div>
<% } else { %>
<div class="<%= genClass %> left"><%=val.name%></div> <div class="<%= genClass %> left"><%=val.name%></div>
<% if (val.type === 'select') { %> <% if (val.type === 'select') { %>
@ -101,9 +102,14 @@
</div> </div>
<% } %> <% } %>
<% if (val.type === 'numeric') { %> <% 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> <input id="g_<%=key%>" type="text" id="<%=val %>" value="<%=val.value %>" placeholder=""></input>
<% } %> <% } %>
<% } %>
<% }); %> <% }); %>
</div> </div>

View File

@ -7,7 +7,23 @@
window.GraphSettingsView = Backbone.View.extend({ window.GraphSettingsView = Backbone.View.extend({
el: '#content', el: '#content',
remove: function () {
this.$el.empty().off(); /* off to unbind the events */
this.stopListening();
return this;
},
general: { general: {
'graph': {
type: 'divider',
name: 'Graph'
},
'nodeStart': {
type: 'string',
name: 'Starting node',
desc: 'A valid node id. If empty, a random node will be chosen.',
value: 2
},
'layout': { 'layout': {
type: 'select', type: 'select',
name: 'Layout algorithm', name: 'Layout algorithm',
@ -37,58 +53,73 @@
} }
}, },
'depth': { 'depth': {
type: 'numeric', type: 'number',
name: 'Search depth', name: 'Search depth',
value: 2 value: 2
} }
}, },
specific: { specific: {
'nodes': {
type: 'divider',
name: 'Nodes'
},
'nodeLabel': { 'nodeLabel': {
type: 'string', type: 'string',
name: 'Node label', name: 'Label',
desc: 'Default node color. RGB or HEX value.', desc: 'Default node color. RGB or HEX value.',
default: '_key' default: '_key'
}, },
'nodeColor': { 'nodeColor': {
type: 'color', type: 'color',
name: 'Node color', name: 'Color',
desc: 'Default node color. RGB or HEX value.', desc: 'Default node color. RGB or HEX value.',
default: '#2ecc71' default: '#2ecc71'
}, },
'nodeSize': { 'nodeSize': {
type: 'string', type: 'string',
name: 'Node size', name: 'Sizing attribute',
desc: 'Default node size. Numeric value > 0.', desc: 'Default node size. Numeric value > 0.'
value: undefined },
'edges': {
type: 'divider',
name: 'Edges'
}, },
'edgeLabel': { 'edgeLabel': {
type: 'string', type: 'string',
name: 'Edge label', name: 'Label',
desc: 'Default edge label.', desc: 'Default edge label.'
value: undefined
}, },
'edgeColor': { 'edgeColor': {
type: 'color', type: 'color',
name: 'Edge color', name: 'Color',
desc: 'Default edge color. RGB or HEX value.', desc: 'Default edge color. RGB or HEX value.',
default: '#cccccc' default: '#cccccc'
}, },
'edgeSize': { 'edgeSize': {
type: 'string', type: 'number',
name: 'Edge thickness', name: 'Sizing',
desc: 'Default edge thickness. Numeric value > 0.', desc: 'Default edge thickness. Numeric value > 0.'
value: undefined
}, },
'edgeType': { 'edgeType': {
type: 'select', type: 'select',
name: 'Edge type', name: 'Type',
desc: 'The type of the edge', desc: 'The type of the edge',
canvas: { line: {
name: 'Straight' name: 'Line',
val: 'line'
}, },
webgl: { curve: {
name: 'Curved' name: 'Curve',
val: 'curve'
},
arrow: {
name: 'Arrow',
val: 'arrow'
},
curvedArrow: {
name: 'Curved Arrow',
val: 'curvedArrow'
} }
} }
}, },
@ -112,7 +143,6 @@
this.userConfig.fetch({ this.userConfig.fetch({
success: function (data) { success: function (data) {
self.graphConfig = data.toJSON().graphs[combinedName]; self.graphConfig = data.toJSON().graphs[combinedName];
if (render) { if (render) {
self.continueRender(); self.continueRender();
} }
@ -127,7 +157,15 @@
config[combinedName] = { config[combinedName] = {
layout: $('#g_layout').val(), layout: $('#g_layout').val(),
renderer: $('#g_renderer').val(), renderer: $('#g_renderer').val(),
depth: $('#g_depth').val() depth: $('#g_depth').val(),
nodeColor: $('#g_nodeColor').val(),
edgeColor: $('#g_edgeColor').val(),
nodeLabel: $('#g_nodeLabel').val(),
edgeLabel: $('#g_edgeLabel').val(),
edgeType: $('#g_edgeType').val(),
nodeSize: $('#g_nodeSize').val(),
edgeSize: $('#g_edgeSize').val(),
nodeStart: $('#g_nodeStart').val()
}; };
var callback = function () { var callback = function () {

View File

@ -1,12 +1,18 @@
/* jshint browser: true */ /* jshint browser: true */
/* jshint unused: false */ /* jshint unused: false */
/* global arangoHelper, slicePath, icon, wheelnav, document, sigma, Backbone, templateEngine, $, window*/ /* global arangoHelper, _, slicePath, icon, wheelnav, document, sigma, Backbone, templateEngine, $, window*/
(function () { (function () {
'use strict'; 'use strict';
window.GraphViewer2 = Backbone.View.extend({ window.GraphViewer2 = Backbone.View.extend({
el: '#content', el: '#content',
remove: function () {
this.$el.empty().off(); /* off to unbind the events */
this.stopListening();
return this;
},
template: templateEngine.createTemplate('graphViewer2.ejs'), template: templateEngine.createTemplate('graphViewer2.ejs'),
initialize: function (options) { initialize: function (options) {
@ -64,30 +70,104 @@
$('#content').append( $('#content').append(
'<div id="calculatingGraph" style="position: absolute; left: 25px; top: 130px;">' + '<div id="calculatingGraph" style="position: absolute; left: 25px; top: 130px;">' +
'<i class="fa fa-circle-o-notch fa-spin" style="margin-right: 10px;"></i>' + '<i class="fa fa-circle-o-notch fa-spin" style="margin-right: 10px;"></i>' +
'Calculating layout. Please wait ... </div>' '<span id="calcText">Fetching graph data. Please wait ... </span></div>'
); );
var fetchGraph = function () { var continueFetchGraph = function () {
var ajaxData = {};
if (this.graphConfig) {
ajaxData = _.clone(this.graphConfig);
// remove not needed params
delete ajaxData.layout;
delete ajaxData.edgeType;
delete ajaxData.renderer;
}
this.setupSigma();
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: arangoHelper.databaseUrl('/_admin/aardvark/graph/' + encodeURIComponent(this.name)), url: arangoHelper.databaseUrl('/_admin/aardvark/graph/' + encodeURIComponent(this.name)),
contentType: 'application/json', contentType: 'application/json',
data: ajaxData,
success: function (data) { success: function (data) {
$('#calcText').html('Calculating layout. Please wait ... ');
arangoHelper.buildGraphSubNav(self.name, 'Content'); arangoHelper.buildGraphSubNav(self.name, 'Content');
self.renderGraph(data); self.renderGraph(data);
}, },
error: function () { error: function (e) {
console.log(e);
try {
arangoHelper.arangoError('Graph', e.responseJSON.exception);
} catch (ignore) {}
$('#calculatingGraph').html('Failed to fetch graph information.'); $('#calculatingGraph').html('Failed to fetch graph information.');
} }
}); });
}.bind(this); }.bind(this);
// TODO LOAD GRAPH SETTINGS // load graph configuration
this.getGraphSettings(fetchGraph); this.getGraphSettings(continueFetchGraph);
}, },
clearOldContextMenu: function () { setupSigma: function () {
if (this.graphConfig) {
if (this.graphConfig.edgeLabel) {
// Initialize package:
sigma.utils.pkg('sigma.settings');
var settings = {
defaultEdgeLabelColor: '#000',
defaultEdgeLabelActiveColor: '#000',
defaultEdgeLabelSize: 10,
edgeLabelSize: 'fixed',
edgeLabelSizePowRatio: 1,
edgeLabelThreshold: 1
};
// Export the previously designed settings:
sigma.settings = sigma.utils.extend(sigma.settings || {}, settings);
// Override default settings:
sigma.settings.drawEdgeLabels = true;
}
}
},
contextState: {
createEdge: false,
_from: false,
_to: false,
fromX: false,
fromY: false
},
clearOldContextMenu: function (states) {
var self = this;
// clear dom
$('#nodeContextMenu').remove(); $('#nodeContextMenu').remove();
var string = '<div id="nodeContextMenu" class="nodeContextMenu"></div>';
$('#graph-container').append(string);
// clear state
if (states) {
_.each(this.contextState, function (val, key) {
self.contextState[key] = false;
});
}
// clear info div
},
createContextMenu: function (e) {
var x = e.data.captor.clientX;
var y = e.data.captor.clientX;
console.log('Context menu');
console.log(x);
console.log(y);
this.clearOldContextMenu();
}, },
createNodeContextMenu: function (nodeId, e) { createNodeContextMenu: function (nodeId, e) {
@ -97,8 +177,6 @@
var y = e.data.node['renderer1:y']; var y = e.data.node['renderer1:y'];
this.clearOldContextMenu(); this.clearOldContextMenu();
var string = '<div id="nodeContextMenu" class="nodeContextMenu"></div>';
$('#graph-container').append(string);
var generateMenu = function (e, nodeId) { var generateMenu = function (e, nodeId) {
var hotaru = ['#364C4A', '#497C7F', '#92C5C0', '#858168', '#CCBCA5']; var hotaru = ['#364C4A', '#497C7F', '#92C5C0', '#858168', '#CCBCA5'];
@ -110,35 +188,49 @@
wheel.wheelRadius = 50; wheel.wheelRadius = 50;
wheel.clockwise = false; wheel.clockwise = false;
wheel.colors = hotaru; wheel.colors = hotaru;
wheel.multiSelect = true;
wheel.clickModeRotate = false; wheel.clickModeRotate = false;
wheel.slicePathFunction = slicePath().DonutSlice; wheel.slicePathFunction = slicePath().DonutSlice;
wheel.createWheel([icon.edit, icon.trash, icon.smallgear, icon.smallgear]); wheel.createWheel([icon.edit, icon.trash, icon.arrowleft2, icon.connect]);
wheel.navItems[0].selected = false;
wheel.navItems[0].hovered = false;
// add menu events // add menu events
// function 0: edit // function 0: edit
wheel.navItems[0].navigateFunction = function () { wheel.navItems[0].navigateFunction = function (e) {
self.clearOldContextMenu(); self.clearOldContextMenu();
self.editNode(nodeId); self.editNode(nodeId);
}; };
// function 1: // function 1: delete
wheel.navItems[1].navigateFunction = function () { wheel.navItems[1].navigateFunction = function (e) {
self.clearOldContextMenu(); self.clearOldContextMenu();
self.editNode(nodeId); self.deleteNode(nodeId);
}; };
// function 2: // function 2: mark as start node
wheel.navItems[2].navigateFunction = function () { wheel.navItems[2].navigateFunction = function (e) {
self.clearOldContextMenu(); self.clearOldContextMenu();
self.editNode(nodeId); self.setStartNode(nodeId);
}; };
// function 3: delete // function 3: create edge
wheel.navItems[3].navigateFunction = function () { wheel.navItems[3].navigateFunction = function (e) {
self.contextState.createEdge = true;
self.contextState._from = nodeId;
self.contextState.fromX = x;
self.contextState.fromY = y;
var c = document.getElementsByClassName('sigma-mouse')[0];
c.addEventListener('mousemove', self.drawLine.bind(this), false);
self.clearOldContextMenu(); self.clearOldContextMenu();
self.editNode(nodeId);
}; };
// deselect active default entry
wheel.navItems[0].selected = false;
wheel.navItems[0].hovered = false;
}; };
$('#nodeContextMenu').css('left', x + 115); $('#nodeContextMenu').css('left', x + 115);
@ -149,6 +241,31 @@
generateMenu(e, nodeId); generateMenu(e, nodeId);
}, },
clearMouseCanvas: function () {
var c = document.getElementsByClassName('sigma-mouse')[0];
var ctx = c.getContext('2d');
ctx.clearRect(0, 0, $(c).width(), $(c).height());
},
drawLine: function (e) {
var context = window.App.graphViewer2.contextState;
if (context.createEdge) {
var fromX = context.fromX;
var fromY = context.fromY;
var toX = e.offsetX;
var toY = e.offsetY;
var c = document.getElementsByClassName('sigma-mouse')[0];
var ctx = c.getContext('2d');
ctx.clearRect(0, 0, $(c).width(), $(c).height());
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.stroke();
}
},
getGraphSettings: function (callback) { getGraphSettings: function (callback) {
var self = this; var self = this;
var combinedName = window.App.currentDB.toJSON().name + '_' + this.name; var combinedName = window.App.currentDB.toJSON().name + '_' + this.name;
@ -158,7 +275,7 @@
self.graphConfig = data.toJSON().graphs[combinedName]; self.graphConfig = data.toJSON().graphs[combinedName];
if (callback) { if (callback) {
callback(); callback(self.graphConfig);
} }
} }
}); });
@ -184,8 +301,6 @@
var renderer = 'canvas'; var renderer = 'canvas';
if (this.graphConfig) { if (this.graphConfig) {
console.log(this.graphConfig);
if (this.graphConfig.layout) { if (this.graphConfig.layout) {
algorithm = this.graphConfig.layout; algorithm = this.graphConfig.layout;
} }
@ -201,20 +316,28 @@
var settings = { var settings = {
doubleClickEnabled: false, doubleClickEnabled: false,
minEdgeSize: 0.5, minNodeSize: 3.5,
minEdgeSize: 1,
maxEdgeSize: 4, maxEdgeSize: 4,
enableEdgeHovering: true, enableEdgeHovering: true,
// edgeHoverColor: 'edge', edgeHoverColor: '#000',
// defaultEdgeHoverColor: '#000', defaultEdgeHoverColor: '#000',
// defaultEdgeType: 'curve', defaultEdgeType: 'line',
edgeHoverSizeRatio: 1, edgeHoverSizeRatio: 2,
edgeHoverExtremities: true edgeHoverExtremities: true
}; };
if (this.graphConfig) {
if (this.graphConfig.edgeType) {
settings.defaultEdgeType = this.graphConfig.edgeType;
}
}
// adjust display settings for big graphs // adjust display settings for big graphs
if (graph.nodes.length > 500) { if (graph.nodes.length > 500) {
// show node label if size is 20 // show node label if size is 15
settings.labelThreshold = 20; settings.labelThreshold = 15;
settings.hideEdgesOnMove = true;
} }
// adjust display settings for webgl renderer // adjust display settings for webgl renderer
@ -271,7 +394,33 @@
e.originalColor = e.color; e.originalColor = e.color;
}); });
if (renderer !== 'webgl') { // for canvas renderer allow graph editing
if (renderer === 'canvas') {
s.bind('rightClickStage', function (e) {
self.createContextMenu(e);
self.clearMouseCanvas();
});
s.bind('clickNode', function (e) {
if (self.contextState.createEdge === true) {
// create the edge
self.contextState._to = e.data.node.id;
self.currentGraph.graph.addEdge({
source: self.contextState._from,
target: self.contextState._to,
id: Math.random(),
color: self.graphConfig.edgeColor
});
// rerender graph
self.currentGraph.refresh();
// then clear states
self.clearOldContextMenu(true);
}
});
s.bind('rightClickNode', function (e) { s.bind('rightClickNode', function (e) {
var nodeId = e.data.node.id; var nodeId = e.data.node.id;
self.createNodeContextMenu(nodeId, e); self.createNodeContextMenu(nodeId, e);
@ -314,7 +463,8 @@
}); });
s.bind('clickStage', function () { s.bind('clickStage', function () {
self.clearOldContextMenu(); self.clearOldContextMenu(true);
self.clearMouseCanvas();
}); });
} }
@ -327,11 +477,18 @@
} else if (algorithm === 'force') { } else if (algorithm === 'force') {
s.startForceAtlas2({worker: true, barnesHutOptimize: false}); s.startForceAtlas2({worker: true, barnesHutOptimize: false});
var duration = 3000;
if (graph.nodes.length > 2500) {
duration = 5000;
} else if (graph.nodes.length < 50) {
duration = 500;
}
window.setTimeout(function () { window.setTimeout(function () {
s.stopForceAtlas2(); s.stopForceAtlas2();
dragListener = sigma.plugins.dragNodes(s, s.renderers[0]); dragListener = sigma.plugins.dragNodes(s, s.renderers[0]);
console.log('stopped force'); }, duration);
}, 3000);
} else if (algorithm === 'fruchtermann') { } else if (algorithm === 'fruchtermann') {
// Start the Fruchterman-Reingold algorithm: // Start the Fruchterman-Reingold algorithm:
sigma.layouts.fruchtermanReingold.start(s); sigma.layouts.fruchtermanReingold.start(s);

View File

@ -45,8 +45,19 @@
position: fixed; position: fixed;
svg { svg {
path { #wheelnav-nodeContextMenu-title-0 {
font-size: 2pt !important; transform: translate(24px, 14px) scale(.7) !important;
}
#wheelnav-nodeContextMenu-title-0,
#wheelnav-nodeContextMenu-title-1,
#wheelnav-nodeContextMenu-title-2,
#wheelnav-nodeContextMenu-title-3 {
fill: $c-white;
&:hover {
fill: $c-positive;
}
} }
} }
} }

View File

@ -9,6 +9,13 @@
.dataTables_empty { .dataTables_empty {
padding-left: 10px; padding-left: 10px;
} }
.heading {
font-weight: 600;
height: 40px;
padding-bottom: 10px;
padding-top: 10px;
}
} }
.pure-table { .pure-table {