javascript - Obtener datos para d3 de ArangoDB usando AQL(o arangojs)
d3.js (1)
perdón por la respuesta tardía, estábamos ocupados construyendo v2.8;) Sugeriría hacer tantas cosas como sea posible en el lado de la base de datos, ya que copiar y serializar / deserializar JSON a través de la red suele ser costoso, transfiriendo tan pocos datos como posible debe ser un buen objetivo.
En primer lugar, he utilizado su consulta y la he ejecutado en un conjunto de datos de muestra que he creado (~ 800 vértices y 800 bordes en mi conjunto de datos). Como referencia utilicé el tiempo de ejecución de su consulta, que en mi caso era ~ 5.0s
Así que traté de crear exactamente el mismo resultado que necesita en AQL solamente. He encontrado algunas mejoras en su consulta: 1. GRAPH_NEIGHBORS
es un poco más rápido que GRAPH_EDGES
. 2. Si es posible, evite {includeData: true}
si no necesita los datos Especialmente si necesita / desde vértices._id solo GRAPH_NEIGHBORS
con {includeData: false}
supera a GRAPH_EDGES
en un orden de magnitud. 3. GRAPH_NEIGHBORS está deduplicado, GRAPH_EDGES no lo es. Que en tu caso parece ser deseado. 3. Puede deshacerse de un par de subconsultas allí.
Así que aquí está la consulta AQL pura que podría surgir:
LET docId = "ExampleDocClass/1234567"
LET edges = GRAPH_EDGES(''EdgeClass'',docId,{direction:''any'',maxDepth:1,includeData:true})
LET verticesTmp = (FOR v IN GRAPH_NEIGHBORS(''EdgeClass'', docId, {direction: ''any'', maxDepth: 1, includeData: true})
RETURN {
vertexData: v,
outEdges: GRAPH_NEIGHBORS(''EdgeClass'', v, {direction: ''outbound'', maxDepth: 1, includeData: false}),
inEdges: GRAPH_NEIGHBORS(''EdgeClass'', v, {direction: ''inbound'', maxDepth: 1, includeData: false})
})
LET vertices = PUSH(verticesTmp, {
vertexData: DOCUMENT(docId),
outEdges: GRAPH_NEIGHBORS(''EdgeClass'', docId, {direction: ''outbound'', maxDepth: 1, includeData: false}),
inEdges: GRAPH_NEIGHBORS(''EdgeClass'', docId, {direction: ''inbound'', maxDepth: 1, includeData: false})
})
RETURN { edges, vertices }
Esto produce el mismo formato de resultado que su consulta y tiene la ventaja de que cada vértice conectado a docId se almacena exactamente una vez en vértices. También docId se almacena exactamente una vez en vértices. No se requiere deduplicación en el lado del cliente. Pero, en outEdges / inEdges de cada vértice, todos los vértices conectados también son exactamente una vez, no sé si necesitas saber si hay múltiples bordes entre vértices en esta lista también.
Esta consulta usa ~ 0.06s en mi conjunto de datos.
Sin embargo, si le dedicas más esfuerzo, también podrías considerar utilizar un recorrido transversal hecho a mano dentro de una aplicación Foxx. Esto es un poco más complicado, pero podría ser más rápido en su caso, ya que realiza menos subconsultas. El código para esto podría verse así:
var traversal = require("org/arangodb/graph/traversal");
var result = {
edges: [],
vertices: {}
}
var myVisitor = function (config, result, vertex, path, connected) {
switch (path.edges.length) {
case 0:
if (! result.vertices.hasOwnProperty(vertex._id)) {
// If we visit a vertex, we store it''s data and prepare out/in
result.vertices[vertex._id] = {
vertexData: vertex,
outEdges: [],
inEdges: []
};
}
// No further action
break;
case 1:
if (! result.vertices.hasOwnProperty(vertex._id)) {
// If we visit a vertex, we store it''s data and prepare out/in
result.vertices[vertex._id] = {
vertexData: vertex,
outEdges: [],
inEdges: []
};
}
// First Depth, we need EdgeData
var e = path.edges[0];
result.edges.push(e);
// We fill from / to for both vertices
result.vertices[e._from].outEdges.push(e._to);
result.vertices[e._to].inEdges.push(e._from);
break;
case 2:
// Second Depth, we do not need EdgeData
var e = path.edges[1];
// We fill from / to for all vertices that exist
if (result.vertices.hasOwnProperty(e._from)) {
result.vertices[e._from].outEdges.push(e._to);
}
if (result.vertices.hasOwnProperty(e._to)) {
result.vertices[e._to].inEdges.push(e._from);
}
break;
}
};
var config = {
datasource: traversal.generalGraphDatasourceFactory("EdgeClass"),
strategy: "depthfirst",
order: "preorder",
visitor: myVisitor,
expander: traversal.anyExpander,
minDepth: 0,
maxDepth: 2
};
var traverser = new traversal.Traverser(config);
traverser.traverse(result, {_id: "ExampleDocClass/1234567"});
return {
edges: result.edges,
vertices: Object.keys(result.vertices).map(function (key) {
return result.vertices[key];
})
};
La idea de este recorrido es visitar todos los vértices desde el vértice inicial hasta dos bordes de distancia. Todos los vértices en 0 - 1 profundidad se agregarán con datos en el objeto vértices. Todos los bordes que se originan desde el vértice de inicio se agregarán con los datos en la lista de bordes. Todos los vértices en profundidad 2 solo establecerán los outEdges / inEdges en el resultado.
Esto tiene la ventaja de que los vertices
se deduplican. y outEdges / inEdges contienen todos los vértices conectados varias veces, si hay múltiples bordes entre ellos.
Este cruce se ejecuta en mi conjunto de datos en ~ 0.025s, por lo que es dos veces más rápido que la solución AQL.
espero que esto todavía ayude;)
Estoy construyendo una aplicación basada en un gráfico d3 force-directed con ArangoDB en el back-end, y quiero poder cargar datos de nodo y enlaces dinámicamente desde Arango de la manera más eficiente posible.
No soy un experto en d3, pero en general el diseño de fuerza parece querer sus datos como una matriz de nodos y una matriz de enlaces que tienen los objetos de nodo reales como sus fuentes y objetivos, de esta manera:
var nodes = [
{id: 0, reflexive: false},
{id: 1, reflexive: true },
{id: 2, reflexive: false}
],
links = [
{source: nodes[0], target: nodes[1], left: false, right: true },
{source: nodes[1], target: nodes[2], left: false, right: true }
];
Actualmente estoy usando la siguiente consulta AQL para obtener nodos vecinos, pero es bastante engorroso. Parte de la dificultad es que quiero incluir información de borde para nodos, incluso cuando esos bordes no están atravesados (para mostrar la cantidad de enlaces que tiene un nodo antes de cargar esos enlaces desde la base de datos).
LET docId = "ExampleDocClass/1234567"
// get data for all the edges
LET es = GRAPH_EDGES(''EdgeClass'',docId,{direction:''any'',maxDepth:1,includeData:true})
// create an array of all the neighbor nodes
LET vArray = (
FOR v IN GRAPH_TRAVERSAL(''EdgeClass'',docId[0],''any'',{ maxDepth:1})
FOR v1 IN v RETURN v1.vertex
)
// using node array, return inbound and outbound for each node
LET vs = (
FOR v IN vArray
// inbound and outbound are separate queries because I couldn''t figure out
// how to get Arango to differentiate inbout and outbound in the query results
LET oe = (FOR oe1 IN GRAPH_EDGES(''EdgeClass'',v,{direction:''outbound'',maxDepth:1,includeData:true}) RETURN oe1._to)
LET ie = (FOR ie1 IN GRAPH_EDGES(''EdgeClass'',v,{direction:''inbound'',maxDepth:1,includeData:true}) RETURN ie1._from)
RETURN {''vertexData'': v, ''outEdges'': oe, ''inEdges'': ie}
)
RETURN {''edges'':es,''vertices'':vs}
La salida final se ve así: http://pastebin.com/raw.php?i=B7uzaWxs ... que se puede leer casi directamente en d3 (solo tengo que deduplicar un poco).
Mis nodos gráficos tienen una gran cantidad de enlaces, por lo que el rendimiento es importante (tanto en términos de carga en el servidor y el cliente, y el tamaño del archivo para la comunicación entre los dos). También estoy planeando crear una variedad de comandos para interactuar con el gráfico aparte de simplemente expandir los nodos vecinos. ¿Hay alguna manera de estructurar mejor esta consulta AQL (por ejemplo, evitando cuatro consultas de gráficos separadas) o evitar AQL por completo utilizando las funciones arangojs o una aplicación FOXX, mientras sigue estructurando la respuesta en el formato que necesito para d3 (incluyendo datos de enlace con cada nodo )?