mirror of https://gitee.com/bigwinds/arangodb
gv [ci skip]
This commit is contained in:
parent
08b892f09f
commit
1929b24901
|
@ -65,6 +65,9 @@
|
|||
"frontend/js/lib/sigma.plugins.fullScreen.js",
|
||||
"frontend/js/lib/sigma.layout.fruchtermanReingold.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/supervisor.js",
|
||||
// END SIGMA LIBRARIES
|
||||
|
|
|
@ -292,10 +292,33 @@ authRouter.get('/graph/:name', function (req, res) {
|
|||
|
||||
var graph = gm._graph(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 =
|
||||
'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'
|
||||
;
|
||||
|
||||
|
@ -307,29 +330,53 @@ authRouter.get('/graph/:name', function (req, res) {
|
|||
var edgesArr = [];
|
||||
|
||||
_.each(cursor.json, function (obj) {
|
||||
var edgeLabel;
|
||||
|
||||
_.each(obj.edges, function (edge) {
|
||||
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] = {
|
||||
id: edge._id,
|
||||
source: edge._from,
|
||||
color: '#cccccc',
|
||||
label: edgeLabel,
|
||||
color: config.edgeColor || '#cccccc',
|
||||
target: edge._to
|
||||
};
|
||||
}
|
||||
});
|
||||
var label;
|
||||
|
||||
var nodeLabel;
|
||||
var nodeSize;
|
||||
|
||||
_.each(obj.vertices, function (node) {
|
||||
if (node.label) {
|
||||
label = node.label;
|
||||
} else {
|
||||
label = node._id;
|
||||
if (config.nodeLabel) {
|
||||
nodeLabel = node[config.nodeLabel];
|
||||
}
|
||||
if (!nodeLabel) {
|
||||
nodeLabel = node._id;
|
||||
}
|
||||
|
||||
if (config.nodeSize) {
|
||||
nodeSize = node[config.nodeSize];
|
||||
}
|
||||
|
||||
nodesObj[node._id] = {
|
||||
id: node._id,
|
||||
label: label,
|
||||
size: Math.random(),
|
||||
color: '#2ecc71',
|
||||
label: nodeLabel,
|
||||
size: nodeSize || Math.random(),
|
||||
color: config.nodeColor || '#2ecc71',
|
||||
x: Math.random(),
|
||||
y: Math.random()
|
||||
};
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -42,7 +42,7 @@ window.UserConfig = Backbone.Model.extend({
|
|||
}
|
||||
},
|
||||
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);
|
||||
},
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -671,6 +671,9 @@
|
|||
this.waitForInit(this.graph2.bind(this), name);
|
||||
return;
|
||||
}
|
||||
if (this.graphViewer2) {
|
||||
this.graphViewer2.remove();
|
||||
}
|
||||
this.graphViewer2 = new window.GraphViewer2({
|
||||
name: name,
|
||||
userConfig: this.userConfig
|
||||
|
@ -684,6 +687,9 @@
|
|||
this.waitForInit(this.graph2settings.bind(this), name);
|
||||
return;
|
||||
}
|
||||
if (this.graphSettingsView) {
|
||||
this.graphSettingsView.remove();
|
||||
}
|
||||
this.graphSettingsView = new window.GraphSettingsView({
|
||||
name: name,
|
||||
userConfig: this.userConfig
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<% 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">
|
||||
|
||||
|
@ -21,47 +21,51 @@
|
|||
</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">
|
||||
<% _.each(specific, function(val, key) { %>
|
||||
<div class="<%= genClass %> left"><%=val.name%></div>
|
||||
<div class="<%= genClass %> left">
|
||||
|
||||
<% 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">
|
||||
|
||||
|
||||
<% var VALUE; %>
|
||||
<% if (val.value) { %>
|
||||
<% VALUE = val.value %>
|
||||
<% } else { %>
|
||||
<% VALUE = val.default %>
|
||||
<% } %>
|
||||
<% var VALUE; %>
|
||||
<% if (val.value) { %>
|
||||
<% VALUE = val.value %>
|
||||
<% } else { %>
|
||||
<% VALUE = val.default %>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'string') { %>
|
||||
<input type="text" value="<%=VALUE%>" placeholder=""></input>
|
||||
<% } %>
|
||||
<% if (val.type === 'string') { %>
|
||||
<input id="g_<%=key%>" type="text" placeholder="string"></input>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'color') { %>
|
||||
<input type='color' name='color' value="<%=VALUE%>"/>
|
||||
<% } %>
|
||||
<% if (val.type === 'number') { %>
|
||||
<input id="g_<%=key%>" type="text" placeholder="number"></input>
|
||||
<% } %>
|
||||
|
||||
<% if (val.type === 'select') { %>
|
||||
<div class="<%= genClass %> left">
|
||||
<select>
|
||||
<% _.each(val, function(option, optKey) { %>
|
||||
<% if (option.name) { %>
|
||||
<option> <%=option.name%> </option>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (val.type === 'color') { %>
|
||||
<input id="g_<%=key%>" type='color' name='color' value="<%=VALUE%>"/>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
<% if (val.type === 'select') { %>
|
||||
<div class="<%= genClass %> left">
|
||||
<select id="g_<%=key%>">
|
||||
<% _.each(val, function(option, optKey) { %>
|
||||
<% if (option.name) { %>
|
||||
<option value="<%=option.val%>"> <%=option.name%> </option>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</div>
|
||||
|
||||
|
@ -77,16 +81,13 @@
|
|||
</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">
|
||||
|
||||
<% _.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>
|
||||
|
||||
<% if (val.type === 'select') { %>
|
||||
|
@ -101,9 +102,14 @@
|
|||
</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>
|
||||
<% } %>
|
||||
<% } %>
|
||||
|
||||
<% }); %>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,23 @@
|
|||
window.GraphSettingsView = Backbone.View.extend({
|
||||
el: '#content',
|
||||
|
||||
remove: function () {
|
||||
this.$el.empty().off(); /* off to unbind the events */
|
||||
this.stopListening();
|
||||
return this;
|
||||
},
|
||||
|
||||
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': {
|
||||
type: 'select',
|
||||
name: 'Layout algorithm',
|
||||
|
@ -37,58 +53,73 @@
|
|||
}
|
||||
},
|
||||
'depth': {
|
||||
type: 'numeric',
|
||||
type: 'number',
|
||||
name: 'Search depth',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
|
||||
specific: {
|
||||
'nodes': {
|
||||
type: 'divider',
|
||||
name: 'Nodes'
|
||||
},
|
||||
'nodeLabel': {
|
||||
type: 'string',
|
||||
name: 'Node label',
|
||||
name: 'Label',
|
||||
desc: 'Default node color. RGB or HEX value.',
|
||||
default: '_key'
|
||||
},
|
||||
'nodeColor': {
|
||||
type: 'color',
|
||||
name: 'Node color',
|
||||
name: 'Color',
|
||||
desc: 'Default node color. RGB or HEX value.',
|
||||
default: '#2ecc71'
|
||||
},
|
||||
'nodeSize': {
|
||||
type: 'string',
|
||||
name: 'Node size',
|
||||
desc: 'Default node size. Numeric value > 0.',
|
||||
value: undefined
|
||||
name: 'Sizing attribute',
|
||||
desc: 'Default node size. Numeric value > 0.'
|
||||
},
|
||||
'edges': {
|
||||
type: 'divider',
|
||||
name: 'Edges'
|
||||
},
|
||||
'edgeLabel': {
|
||||
type: 'string',
|
||||
name: 'Edge label',
|
||||
desc: 'Default edge label.',
|
||||
value: undefined
|
||||
name: 'Label',
|
||||
desc: 'Default edge label.'
|
||||
},
|
||||
'edgeColor': {
|
||||
type: 'color',
|
||||
name: 'Edge color',
|
||||
name: 'Color',
|
||||
desc: 'Default edge color. RGB or HEX value.',
|
||||
default: '#cccccc'
|
||||
},
|
||||
'edgeSize': {
|
||||
type: 'string',
|
||||
name: 'Edge thickness',
|
||||
desc: 'Default edge thickness. Numeric value > 0.',
|
||||
value: undefined
|
||||
type: 'number',
|
||||
name: 'Sizing',
|
||||
desc: 'Default edge thickness. Numeric value > 0.'
|
||||
},
|
||||
'edgeType': {
|
||||
type: 'select',
|
||||
name: 'Edge type',
|
||||
name: 'Type',
|
||||
desc: 'The type of the edge',
|
||||
canvas: {
|
||||
name: 'Straight'
|
||||
line: {
|
||||
name: 'Line',
|
||||
val: 'line'
|
||||
},
|
||||
webgl: {
|
||||
name: 'Curved'
|
||||
curve: {
|
||||
name: 'Curve',
|
||||
val: 'curve'
|
||||
},
|
||||
arrow: {
|
||||
name: 'Arrow',
|
||||
val: 'arrow'
|
||||
},
|
||||
curvedArrow: {
|
||||
name: 'Curved Arrow',
|
||||
val: 'curvedArrow'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -112,7 +143,6 @@
|
|||
this.userConfig.fetch({
|
||||
success: function (data) {
|
||||
self.graphConfig = data.toJSON().graphs[combinedName];
|
||||
|
||||
if (render) {
|
||||
self.continueRender();
|
||||
}
|
||||
|
@ -127,7 +157,15 @@
|
|||
config[combinedName] = {
|
||||
layout: $('#g_layout').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 () {
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
/* jshint browser: true */
|
||||
/* jshint unused: false */
|
||||
/* global arangoHelper, slicePath, icon, wheelnav, document, sigma, Backbone, templateEngine, $, window*/
|
||||
/* global arangoHelper, _, slicePath, icon, wheelnav, document, sigma, Backbone, templateEngine, $, window*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
window.GraphViewer2 = Backbone.View.extend({
|
||||
el: '#content',
|
||||
|
||||
remove: function () {
|
||||
this.$el.empty().off(); /* off to unbind the events */
|
||||
this.stopListening();
|
||||
return this;
|
||||
},
|
||||
|
||||
template: templateEngine.createTemplate('graphViewer2.ejs'),
|
||||
|
||||
initialize: function (options) {
|
||||
|
@ -64,30 +70,104 @@
|
|||
$('#content').append(
|
||||
'<div id="calculatingGraph" style="position: absolute; left: 25px; top: 130px;">' +
|
||||
'<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({
|
||||
type: 'GET',
|
||||
url: arangoHelper.databaseUrl('/_admin/aardvark/graph/' + encodeURIComponent(this.name)),
|
||||
contentType: 'application/json',
|
||||
data: ajaxData,
|
||||
success: function (data) {
|
||||
$('#calcText').html('Calculating layout. Please wait ... ');
|
||||
arangoHelper.buildGraphSubNav(self.name, 'Content');
|
||||
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.');
|
||||
}
|
||||
});
|
||||
}.bind(this);
|
||||
|
||||
// TODO LOAD GRAPH SETTINGS
|
||||
this.getGraphSettings(fetchGraph);
|
||||
// load graph configuration
|
||||
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();
|
||||
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) {
|
||||
|
@ -97,8 +177,6 @@
|
|||
var y = e.data.node['renderer1:y'];
|
||||
|
||||
this.clearOldContextMenu();
|
||||
var string = '<div id="nodeContextMenu" class="nodeContextMenu"></div>';
|
||||
$('#graph-container').append(string);
|
||||
|
||||
var generateMenu = function (e, nodeId) {
|
||||
var hotaru = ['#364C4A', '#497C7F', '#92C5C0', '#858168', '#CCBCA5'];
|
||||
|
@ -110,35 +188,49 @@
|
|||
wheel.wheelRadius = 50;
|
||||
wheel.clockwise = false;
|
||||
wheel.colors = hotaru;
|
||||
wheel.multiSelect = true;
|
||||
wheel.clickModeRotate = false;
|
||||
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
|
||||
|
||||
// function 0: edit
|
||||
wheel.navItems[0].navigateFunction = function () {
|
||||
wheel.navItems[0].navigateFunction = function (e) {
|
||||
self.clearOldContextMenu();
|
||||
self.editNode(nodeId);
|
||||
};
|
||||
|
||||
// function 1:
|
||||
wheel.navItems[1].navigateFunction = function () {
|
||||
// function 1: delete
|
||||
wheel.navItems[1].navigateFunction = function (e) {
|
||||
self.clearOldContextMenu();
|
||||
self.editNode(nodeId);
|
||||
self.deleteNode(nodeId);
|
||||
};
|
||||
|
||||
// function 2:
|
||||
wheel.navItems[2].navigateFunction = function () {
|
||||
// function 2: mark as start node
|
||||
wheel.navItems[2].navigateFunction = function (e) {
|
||||
self.clearOldContextMenu();
|
||||
self.editNode(nodeId);
|
||||
self.setStartNode(nodeId);
|
||||
};
|
||||
|
||||
// function 3: delete
|
||||
wheel.navItems[3].navigateFunction = function () {
|
||||
// function 3: create edge
|
||||
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.editNode(nodeId);
|
||||
};
|
||||
|
||||
// deselect active default entry
|
||||
wheel.navItems[0].selected = false;
|
||||
wheel.navItems[0].hovered = false;
|
||||
};
|
||||
|
||||
$('#nodeContextMenu').css('left', x + 115);
|
||||
|
@ -149,6 +241,31 @@
|
|||
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) {
|
||||
var self = this;
|
||||
var combinedName = window.App.currentDB.toJSON().name + '_' + this.name;
|
||||
|
@ -158,7 +275,7 @@
|
|||
self.graphConfig = data.toJSON().graphs[combinedName];
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
callback(self.graphConfig);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -184,8 +301,6 @@
|
|||
var renderer = 'canvas';
|
||||
|
||||
if (this.graphConfig) {
|
||||
console.log(this.graphConfig);
|
||||
|
||||
if (this.graphConfig.layout) {
|
||||
algorithm = this.graphConfig.layout;
|
||||
}
|
||||
|
@ -201,20 +316,28 @@
|
|||
|
||||
var settings = {
|
||||
doubleClickEnabled: false,
|
||||
minEdgeSize: 0.5,
|
||||
minNodeSize: 3.5,
|
||||
minEdgeSize: 1,
|
||||
maxEdgeSize: 4,
|
||||
enableEdgeHovering: true,
|
||||
// edgeHoverColor: 'edge',
|
||||
// defaultEdgeHoverColor: '#000',
|
||||
// defaultEdgeType: 'curve',
|
||||
edgeHoverSizeRatio: 1,
|
||||
edgeHoverColor: '#000',
|
||||
defaultEdgeHoverColor: '#000',
|
||||
defaultEdgeType: 'line',
|
||||
edgeHoverSizeRatio: 2,
|
||||
edgeHoverExtremities: true
|
||||
};
|
||||
|
||||
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 20
|
||||
settings.labelThreshold = 20;
|
||||
// show node label if size is 15
|
||||
settings.labelThreshold = 15;
|
||||
settings.hideEdgesOnMove = true;
|
||||
}
|
||||
|
||||
// adjust display settings for webgl renderer
|
||||
|
@ -271,7 +394,33 @@
|
|||
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) {
|
||||
var nodeId = e.data.node.id;
|
||||
self.createNodeContextMenu(nodeId, e);
|
||||
|
@ -314,7 +463,8 @@
|
|||
});
|
||||
|
||||
s.bind('clickStage', function () {
|
||||
self.clearOldContextMenu();
|
||||
self.clearOldContextMenu(true);
|
||||
self.clearMouseCanvas();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -327,11 +477,18 @@
|
|||
} else if (algorithm === 'force') {
|
||||
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 () {
|
||||
s.stopForceAtlas2();
|
||||
dragListener = sigma.plugins.dragNodes(s, s.renderers[0]);
|
||||
console.log('stopped force');
|
||||
}, 3000);
|
||||
}, duration);
|
||||
} else if (algorithm === 'fruchtermann') {
|
||||
// Start the Fruchterman-Reingold algorithm:
|
||||
sigma.layouts.fruchtermanReingold.start(s);
|
||||
|
|
|
@ -45,8 +45,19 @@
|
|||
position: fixed;
|
||||
|
||||
svg {
|
||||
path {
|
||||
font-size: 2pt !important;
|
||||
#wheelnav-nodeContextMenu-title-0 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,13 @@
|
|||
.dataTables_empty {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.pure-table {
|
||||
|
|
Loading…
Reference in New Issue