diff --git a/arangod/Ahuacatl/ahuacatl-functions.cpp b/arangod/Ahuacatl/ahuacatl-functions.cpp index 93d5a5a697..702abbb8b2 100644 --- a/arangod/Ahuacatl/ahuacatl-functions.cpp +++ b/arangod/Ahuacatl/ahuacatl-functions.cpp @@ -664,9 +664,9 @@ TRI_associative_pointer_t* TRI_CreateFunctionsAql (void) { REGISTER_FUNCTION("GRAPH_SHORTEST_PATH", "GENERAL_GRAPH_SHORTEST_PATH", false, false, "s,als,als|a", NULL); REGISTER_FUNCTION("GRAPH_DISTANCE_TO", "GENERAL_GRAPH_DISTANCE_TO", false, false, "s,als,als|a", NULL); REGISTER_FUNCTION("TRAVERSAL", "GRAPH_TRAVERSAL", false, false, "h,h,s,s|a", NULL); - REGISTER_FUNCTION("GRAPH_TRAVERSAL", "GENERAL_GRAPH_TRAVERSAL", false, false, "s,s,s|a", NULL); + REGISTER_FUNCTION("GRAPH_TRAVERSAL", "GENERAL_GRAPH_TRAVERSAL", false, false, "s,als,s|a", NULL); REGISTER_FUNCTION("TRAVERSAL_TREE", "GRAPH_TRAVERSAL_TREE", false, false, "h,h,s,s,s|a", NULL); - REGISTER_FUNCTION("GRAPH_TRAVERSAL_TREE", "GENERAL_GRAPH_TRAVERSAL_TREE", false, false, "s,s,s,s|a", NULL); + REGISTER_FUNCTION("GRAPH_TRAVERSAL_TREE", "GENERAL_GRAPH_TRAVERSAL_TREE", false, false, "s,als,s,s|a", NULL); REGISTER_FUNCTION("EDGES", "GRAPH_EDGES", false, false, "h,s,s|l", NULL); REGISTER_FUNCTION("GRAPH_EDGES", "GENERAL_GRAPH_EDGES", false, false, "s,als|a", NULL); REGISTER_FUNCTION("GRAPH_VERTICES", "GENERAL_GRAPH_VERTICES", false, false, "s,als|a", NULL); diff --git a/js/apps/system/aardvark/frontend/js/graphViewer/graph/eventLibrary.js b/js/apps/system/aardvark/frontend/js/graphViewer/graph/eventLibrary.js index 612950fc69..01c5d9e066 100644 --- a/js/apps/system/aardvark/frontend/js/graphViewer/graph/eventLibrary.js +++ b/js/apps/system/aardvark/frontend/js/graphViewer/graph/eventLibrary.js @@ -161,6 +161,18 @@ function EventLibrary() { }); }; }; + + this.SelectNodeCollection = function(config) { + self.checkNodeEditorConfig(config); + var adapter = config.adapter; + if (!_.isFunction(adapter.useNodeCollection)) { + throw "The adapter has to support collection changes"; + } + return function(name, callback) { + adapter.useNodeCollection(name); + callback(); + }; + }; this.InsertEdge = function (config) { self.checkEdgeEditorConfig(config); diff --git a/js/apps/system/aardvark/frontend/js/graphViewer/graph/gharialAdapter.js b/js/apps/system/aardvark/frontend/js/graphViewer/graph/gharialAdapter.js new file mode 100644 index 0000000000..035e96589e --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/graphViewer/graph/gharialAdapter.js @@ -0,0 +1,532 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ +/*global $, d3, _, console, document*/ +/*global AbstractAdapter*/ +//////////////////////////////////////////////////////////////////////////////// +/// @brief Graph functionality +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2010-2012 triagens GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Michael Hackstein +/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +function GharialAdapter(nodes, edges, viewer, config) { + "use strict"; + + if (nodes === undefined) { + throw "The nodes have to be given."; + } + if (edges === undefined) { + throw "The edges have to be given."; + } + if (viewer === undefined) { + throw "A reference to the graph viewer has to be given."; + } + if (config === undefined) { + throw "A configuration with graphName has to be given."; + } + if (config.graphName === undefined) { + throw "The graphname has to be given."; + } + + var self = this, + absAdapter, + absConfig = {}, + api = {}, + queries = {}, + nodeCollections, + selectedNodeCol, + edgeCollections, + selectedEdgeCol, + graphName, + direction, + + getCollectionsFromGraph = function(name) { + $.ajax({ + cache: false, + type: 'GET', + async: false, + url: api.graph + "/" + name + "/edge", + contentType: "application/json", + success: function(data) { + edgeCollections = data.collections; + selectedEdgeCol = edgeCollections[0]; + } + }); + $.ajax({ + cache: false, + type: 'GET', + async: false, + url: api.graph + "/" + name + "/vertex", + contentType: "application/json", + success: function(data) { + nodeCollections = data.collections; + selectedNodeCol = nodeCollections[0]; + } + }); + }, + + setGraphName = function(name) { + graphName = name; + getCollectionsFromGraph(name); + api.edges = api.graph + "/" + graphName + "/edge/"; + api.vertices = api.graph + "/" + graphName + "/vertex/"; + api.any = api.base + "simple/any"; + }, + + parseConfig = function(config) { + var arangodb = config.baseUrl || ""; + if (config.width !== undefined) { + absAdapter.setWidth(config.width); + } + if (config.height !== undefined) { + absAdapter.setHeight(config.height); + } + if (config.undirected !== undefined) { + if (config.undirected === true) { + direction = "any"; + } else { + direction = "outbound"; + } + } else { + direction = "outbound"; + } + + api.base = arangodb + "_api/"; + api.cursor = api.base + "cursor"; + api.graph = api.base + "gharial"; + + if (config.graphName) { + setGraphName(config.graphName); + } + }, + + sendQuery = function(query, bindVars, onSuccess) { + if (query !== queries.getAllGraphs) { + bindVars.graph = graphName; + if (query !== queries.connectedEdges + && query !== queries.childrenCentrality) { + bindVars.dir = direction; + } + } + var data = { + query: query, + bindVars: bindVars + }; + $.ajax({ + type: "POST", + url: api.cursor, + data: JSON.stringify(data), + contentType: "application/json", + dataType: "json", + processData: false, + success: function(data) { + onSuccess(data.result); + }, + error: function(data) { + try { + console.log(data.statusText); + throw "[" + data.errorNum + "] " + data.errorMessage; + } + catch (e) { + throw "Undefined ERROR"; + } + } + }); + }, + + getNRandom = function(n, callback) { + var list = [], + i = 0, + rand, + onSuccess = function(data) { + list.push(data.document || {}); + if (list.length === n) { + callback(list); + } + }; + for (i = 0; i < n; i++) { + rand = Math.floor(Math.random() * nodeCollections.length); + $.ajax({ + cache: false, + type: 'PUT', + url: api.any, + data: JSON.stringify({ + collection: nodeCollections[rand] + }), + contentType: "application/json", + success: onSuccess + }); + } + }, + + parseResultOfTraversal = function (result, callback) { + if (result.length === 0) { + if (callback) { + callback({ + errorCode: 404 + }); + } + return; + } + result = result[0]; + if (result.length === 0) { + if (callback) { + callback({ + errorCode: 404 + }); + } + return; + } + var inserted = {}, + n = absAdapter.insertNode(result[0].vertex), + oldLength = nodes.length; + + _.each(result, function(visited) { + var node = absAdapter.insertNode(visited.vertex), + path = visited.path; + if (oldLength < nodes.length) { + inserted[node._id] = node; + oldLength = nodes.length; + } + _.each(path.vertices, function(connectedNode) { + var ins = absAdapter.insertNode(connectedNode); + if (oldLength < nodes.length) { + inserted[ins._id] = ins; + oldLength = nodes.length; + } + }); + _.each(path.edges, function(edge) { + absAdapter.insertEdge(edge); + }); + }); + delete inserted[n._id]; + absAdapter.checkSizeOfInserted(inserted); + absAdapter.checkNodeLimit(n); + if (callback) { + callback(n); + } + }, + + insertInitialCallback = function(callback) { + return function (n) { + if (n && n.errorCode) { + callback(n); + return; + } + callback(absAdapter.insertInitialNode(n)); + }; + }; + + + if (config.prioList) { + absConfig.prioList = config.prioList; + } + absAdapter = new AbstractAdapter(nodes, edges, this, viewer, absConfig); + + parseConfig(config); + + queries.getAllGraphs = "FOR g IN _graphs" + + " return g._key"; + queries.traversal = "RETURN GRAPH_TRAVERSAL(" + + "@graph, " + + "@example, " + + "@dir, {" + + "strategy: \"depthfirst\"," + + "maxDepth: 1," + + "paths: true" + + "})"; + queries.childrenCentrality = "RETURN LENGTH(GRAPH_EDGES(@graph, @id, {direction: any}))"; + queries.connectedEdges = "RETURN GRAPH_EDGES(@graph, @id)"; + + self.explore = absAdapter.explore; + + self.loadNode = function(nodeId, callback) { + self.loadNodeFromTreeById(nodeId, callback); + }; + + self.loadRandomNode = function(callback) { + getNRandom(1, function(list) { + var r = list[0]; + if (r._id) { + self.loadInitialNode(r._id, callback); + return; + } + return; + }); + }; + + self.loadInitialNode = function(nodeId, callback) { + absAdapter.cleanUp(); + self.loadNode(nodeId, insertInitialCallback(callback)); + }; + + self.loadNodeFromTreeById = function(nodeId, callback) { + sendQuery(queries.traversal, { + example: nodeId + }, function(res) { + parseResultOfTraversal(res, callback); + }); + }; + + self.loadNodeFromTreeByAttributeValue = function(attribute, value, callback) { + var example = {}; + example[attribute] = value; + sendQuery(queries.traversal, { + example: example + }, function(res) { + parseResultOfTraversal(res, callback); + }); + }; + + self.loadInitialNodeByAttributeValue = function(attribute, value, callback) { + absAdapter.cleanUp(); + self.loadNodeFromTreeByAttributeValue(attribute, value, insertInitialCallback(callback)); + }; + + self.requestCentralityChildren = function(nodeId, callback) { + sendQuery(queries.childrenCentrality,{ + id: nodeId + }, function(res) { + callback(res[0]); + }); + }; + + self.createEdge = function (info, callback) { + var edgeToAdd = {}; + edgeToAdd._from = info.source._id; + edgeToAdd._to = info.target._id; + $.ajax({ + cache: false, + type: "POST", + url: api.edges + selectedEdgeCol, + data: JSON.stringify(edgeToAdd), + dataType: "json", + contentType: "application/json", + processData: false, + success: function(data) { + data._from = edgeToAdd._from; + data._to = edgeToAdd._to; + delete data.error; + var edge = absAdapter.insertEdge(data); + callback(edge); + }, + error: function(data) { + throw data.statusText; + } + }); + }; + + self.deleteEdge = function (edgeToRemove, callback) { + $.ajax({ + cache: false, + type: "DELETE", + url: api.edges + edgeToRemove._id, + contentType: "application/json", + dataType: "json", + processData: false, + success: function() { + absAdapter.removeEdge(edgeToRemove); + if (callback !== undefined && _.isFunction(callback)) { + callback(); + } + }, + error: function(data) { + throw data.statusText; + } + }); + + }; + + self.patchEdge = function (edgeToPatch, patchData, callback) { + $.ajax({ + cache: false, + type: "PUT", + url: api.edges + edgeToPatch._id, + data: JSON.stringify(patchData), + dataType: "json", + contentType: "application/json", + processData: false, + success: function() { + edgeToPatch._data = $.extend(edgeToPatch._data, patchData); + callback(); + }, + error: function(data) { + throw data.statusText; + } + }); + }; + + self.createNode = function (nodeToAdd, callback) { + $.ajax({ + cache: false, + type: "POST", + url: api.vertices + selectedNodeCol, + data: JSON.stringify(nodeToAdd), + dataType: "json", + contentType: "application/json", + processData: false, + success: function(data) { + if (data.error === false) { + nodeToAdd._key = data._key; + nodeToAdd._id = data._id; + nodeToAdd._rev = data._rev; + absAdapter.insertNode(nodeToAdd); + callback(nodeToAdd); + } + }, + error: function(data) { + throw data.statusText; + } + }); + }; + + self.deleteNode = function (nodeToRemove, callback) { + $.ajax({ + cache: false, + type: "DELETE", + url: api.vertices + nodeToRemove._id, + dataType: "json", + contentType: "application/json", + processData: false, + success: function() { + absAdapter.removeEdgesForNode(nodeToRemove); + absAdapter.removeNode(nodeToRemove); + if (callback !== undefined && _.isFunction(callback)) { + callback(); + } + }, + error: function(data) { + throw data.statusText; + } + }); + }; + + self.patchNode = function (nodeToPatch, patchData, callback) { + $.ajax({ + cache: false, + type: "PUT", + url: api.vertices + nodeToPatch._id, + data: JSON.stringify(patchData), + dataType: "json", + contentType: "application/json", + processData: false, + success: function() { + nodeToPatch._data = $.extend(nodeToPatch._data, patchData); + callback(nodeToPatch); + }, + error: function(data) { + throw data.statusText; + } + }); + }; + + self.changeToGraph = function (name, dir) { + absAdapter.cleanUp(); + setGraphName(name); + if (dir !== undefined) { + if (dir === true) { + direction = "any"; + } else { + direction = "outbound"; + } + } + }; + + self.setNodeLimit = function (pLimit, callback) { + absAdapter.setNodeLimit(pLimit, callback); + }; + + self.setChildLimit = function (pLimit) { + absAdapter.setChildLimit(pLimit); + }; + + self.expandCommunity = function (commNode, callback) { + absAdapter.expandCommunity(commNode); + if (callback !== undefined) { + callback(); + } + }; + + self.getGraphs = function(callback) { + if (callback && callback.length >= 1) { + sendQuery( + queries.getAllGraphs, + {}, + callback + ); + } + }; + + self.getAttributeExamples = function(callback) { + if (callback && callback.length >= 1) { + getNRandom(10, function(l) { + var ret = _.sortBy( + _.uniq( + _.flatten( + _.map(l, function(o) { + return _.keys(o); + }) + ) + ), function(e) { + return e.toLowerCase(); + } + ); + callback(ret); + }); + } + }; + + + self.getEdgeCollections = function() { + return edgeCollections; + }; + + self.useEdgeCollection = function(name) { + if (!_.contains(edgeCollections, name)) { + throw "Collection " + name + " is not available in the graph."; + } + selectedEdgeCol = name; + }; + + self.getNodeCollections = function() { + return nodeCollections; + }; + + self.useNodeCollection = function(name) { + if (!_.contains(nodeCollections, name)) { + throw "Collection " + name + " is not available in the graph."; + } + selectedNodeCol = name; + }; + + self.getDirection = function () { + return direction; + }; + + self.getGraphName = function () { + return graphName; + }; + + self.setWidth = absAdapter.setWidth; + self.changeTo = absAdapter.changeTo; + self.getPrioList = absAdapter.getPrioList; +} diff --git a/js/apps/system/aardvark/frontend/js/graphViewer/graphViewer.js b/js/apps/system/aardvark/frontend/js/graphViewer/graphViewer.js index dcadcb296e..02934440cd 100644 --- a/js/apps/system/aardvark/frontend/js/graphViewer/graphViewer.js +++ b/js/apps/system/aardvark/frontend/js/graphViewer/graphViewer.js @@ -1,6 +1,6 @@ /*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ /*global _, $*/ -/*global ArangoAdapter, JSONAdapter, FoxxAdapter, PreviewAdapter */ +/*global ArangoAdapter, JSONAdapter, FoxxAdapter, PreviewAdapter, GharialAdapter*/ /*global ForceLayouter, EdgeShaper, NodeShaper, ZoomManager */ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality @@ -136,6 +136,17 @@ function GraphViewer(svg, width, height, adapterConfig, config) { ); adapter.setChildLimit(10); break; + case "gharial": + adapterConfig.width = width; + adapterConfig.height = height; + adapter = new GharialAdapter( + nodes, + edges, + this, + adapterConfig + ); + adapter.setChildLimit(10); + break; case "foxx": adapterConfig.width = width; adapterConfig.height = height; @@ -197,6 +208,20 @@ function GraphViewer(svg, width, height, adapterConfig, config) { } }); }; + + this.loadGraphWithRandomStart = function(callback) { + adapter.loadRandomNode(function (node) { + if (node.errorCode) { + callback(node); + return; + } + node._expanded = true; + self.start(); + if (_.isFunction(callback)) { + callback(); + } + }); + }; this.loadGraphWithAttributeValue = function(attribute, value, callback) { adapter.loadInitialNodeByAttributeValue(attribute, value, function (node) { diff --git a/js/apps/system/aardvark/frontend/js/graphViewer/ui/gharialAdapterControls.js b/js/apps/system/aardvark/frontend/js/graphViewer/ui/gharialAdapterControls.js new file mode 100644 index 0000000000..6490a56296 --- /dev/null +++ b/js/apps/system/aardvark/frontend/js/graphViewer/ui/gharialAdapterControls.js @@ -0,0 +1,115 @@ +/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ +/*global $, _, d3*/ +/*global document*/ +/*global modalDialogHelper, uiComponentsHelper*/ +//////////////////////////////////////////////////////////////////////////////// +/// @brief Graph functionality +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2010-2012 triagens GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Michael Hackstein +/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// +function GharialAdapterControls(list, adapter) { + "use strict"; + + if (list === undefined) { + throw "A list element has to be given."; + } + if (adapter === undefined) { + throw "The GharialAdapter has to be given."; + } + this.addControlChangeGraph = function(callback) { + var prefix = "control_adapter_graph", + idprefix = prefix + "_"; + + adapter.getGraphs(function(graphs) { + uiComponentsHelper.createButton(list, "Graph", prefix, function() { + modalDialogHelper.createModalDialog("Switch Graph", + idprefix, [{ + type: "list", + id: "graph", + objects: graphs, + text: "Select graph", + selected: adapter.getGraphName() + },{ + type: "checkbox", + text: "Start with random vertex", + id: "random", + selected: true + },{ + type: "checkbox", + id: "undirected", + selected: (adapter.getDirection() === "any") + }], function () { + var graph = $("#" + idprefix + "graph") + .children("option") + .filter(":selected") + .text(), + undirected = !!$("#" + idprefix + "undirected").prop("checked"), + random = !!$("#" + idprefix + "random").prop("checked"); + adapter.changeToGraph(graph, undirected); + if (random) { + adapter.loadRandomNode(callback); + return; + } + if (_.isFunction(callback)) { + callback(); + } + } + ); + }); + }); + }; + + this.addControlChangePriority = function() { + var prefix = "control_adapter_priority", + idprefix = prefix + "_", + label = "Group vertices"; + + uiComponentsHelper.createButton(list, label, prefix, function() { + modalDialogHelper.createModalChangeDialog(label, + idprefix, [{ + type: "extendable", + id: "attribute", + objects: adapter.getPrioList() + }], function () { + var attrList = $("input[id^=" + idprefix + "attribute_]"), + prios = []; + _.each(attrList, function(t) { + var val = $(t).val(); + if (val !== "") { + prios.push(val); + } + }); + adapter.changeTo({ + prioList: prios + }); + } + ); + }); + }; + + this.addAll = function() { + this.addControlChangeGraph(); + this.addControlChangePriority(); + }; +} diff --git a/js/apps/system/aardvark/frontend/js/graphViewer/ui/graphViewerUI.js b/js/apps/system/aardvark/frontend/js/graphViewer/ui/graphViewerUI.js index 6e57d4b030..8a12445340 100644 --- a/js/apps/system/aardvark/frontend/js/graphViewer/ui/graphViewerUI.js +++ b/js/apps/system/aardvark/frontend/js/graphViewer/ui/graphViewerUI.js @@ -1,8 +1,8 @@ /*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ /*global document, $, _ */ /*global EventDispatcherControls, NodeShaperControls, EdgeShaperControls */ -/*global LayouterControls, ArangoAdapterControls*/ -/*global GraphViewer, d3, window*/ +/*global LayouterControls, GharialAdapterControls*/ +/*global GraphViewer, d3, window, alert*/ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality /// @@ -375,7 +375,7 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight, viewerConf configureLists.edges, graphViewer.edgeShaper ); - adapterUI = new ArangoAdapterControls( + adapterUI = new GharialAdapterControls( configureLists.col, graphViewer.adapter ); @@ -402,7 +402,7 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight, viewerConf transparentHeader.appendChild(buttons); transparentHeader.appendChild(title); - adapterUI.addControlChangeCollections(function() { + adapterUI.addControlChangeGraph(function() { updateAttributeExamples(); graphViewer.start(); }); @@ -448,7 +448,15 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight, viewerConf createColourList(); if (startNode) { - graphViewer.loadGraph(startNode); + if (typeof startNode === "string") { + graphViewer.loadGraph(startNode); + } else { + graphViewer.loadGraphWithRandomStart(function(node) { + if (node && node.errorCode) { + alert("Sorry your graph seems to be empty"); + } + }); + } } this.changeWidth = function(w) { diff --git a/js/apps/system/aardvark/frontend/js/routers/router.js b/js/apps/system/aardvark/frontend/js/routers/router.js index 5cbbf072f7..e78668ea82 100644 --- a/js/apps/system/aardvark/frontend/js/routers/router.js +++ b/js/apps/system/aardvark/frontend/js/routers/router.js @@ -233,7 +233,7 @@ new window.GraphManagementView( { collection: new window.GraphCollection(), - collectionCollection: new window.arangoCollections() + collectionCollection: this.arangoCollectionsStore } ); } diff --git a/js/apps/system/aardvark/frontend/js/templates/edgeDefinitionTable.ejs b/js/apps/system/aardvark/frontend/js/templates/edgeDefinitionTable.ejs index 664720ad33..5b40d504b2 100644 --- a/js/apps/system/aardvark/frontend/js/templates/edgeDefinitionTable.ejs +++ b/js/apps/system/aardvark/frontend/js/templates/edgeDefinitionTable.ejs @@ -2,7 +2,7 @@