2015-11-22 12 views
10

Sto costruendo un'app basata su un grafico d3 force-directed con ArangoDB sul backend, e voglio essere in grado di caricare i dati di nodo e collegamento dinamicamente da Arango nel modo più efficiente possibile.Ottenere dati per d3 da ArangoDB usando AQL (o arangojs)

Non sono un esperto in d3, ma in generale il layout della forza sembra voler i suoi dati come una serie di nodi e una serie di collegamenti che hanno gli oggetti nodo reali come sorgenti e destinazioni, in questo modo:

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 } 
    ]; 

Attualmente sto usando la seguente query AQL per ottenere nodi vicini, ma è abbastanza ingombrante. Parte della difficoltà è che voglio includere informazioni sui bordi per i nodi anche quando questi bordi non sono attraversati (per visualizzare il numero di collegamenti che un nodo ha prima di caricare quei collegamenti dal database).

uscita
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 fine assomiglia a questo: http://pastebin.com/raw.php?i=B7uzaWxs ... che può essere letto quasi direttamente in d3 (non mi resta che deduplicare un po ').

I miei nodi grafici hanno una grande quantità di collegamenti, quindi le prestazioni sono importanti (sia in termini di carico sul server e client, sia di dimensioni del file per la comunicazione tra i due). Sto anche progettando di creare una varietà di comandi per interagire con il grafico oltre a espandere semplicemente i nodi vicini. C'è un modo per strutturare meglio questa query AQL (ad esempio evitando quattro query di grafici separati) o evitare completamente AQL utilizzando le funzioni arangojs o un'app FOXX, mentre ancora strutturando la risposta nel formato che mi serve per d3 (inclusi i dati di collegamento con ciascun nodo)?

risposta

14

scusa per la risposta tardiva, eravamo occupati a costruire v2.8;) Vorrei suggerire di fare quante più cose possibili sul lato del database, poiché la copia e la serializzazione/deserializzazione di JSON in rete è in genere costosa, quindi trasferire il minor numero possibile di dati dovrebbe essere un buon obiettivo.

Prima di tutto ho utilizzato la query e l'ho eseguita su un set di dati campione creato (~ 800 vertici e 800 bordi sono stati colpiti nel mio set di dati) Come linea di base ho utilizzato il tempo di esecuzione della query che nel mio caso era ~ 5.0s

Così ho provato a creare esattamente lo stesso risultato di cui hai bisogno solo in AQL. Ho trovato alcuni miglioramenti nella tua query: 1. GRAPH_NEIGHBORS è un po 'più veloce di GRAPH_EDGES. 2. Se possibile evitare {includeData: true} se non sono necessari i dati Soprattutto se è necessario/da vertici._id solo GRAPH_NEIGHBORS con {includeData: false} prestazioni superiori a GRAPH_EDGES di un ordine di grandezza. 3. GRAPH_NEIGHBORS è deduplicato, GRAPH_EDGES no. Che nel tuo caso sembra essere desiderato. 3. È possibile sbarazzarsi di un paio di subquery lì.

ecco la domanda pura AQL ho potuto venire con:

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 } 

Ciò produce lo stesso formato di risultato come query e ha il vantaggio che ogni vertice connesso a DocID viene memorizzato una sola volta in vertici. Anche docId stesso è memorizzato esattamente una volta nei vertici. Nessuna deduplicazione richiesta sul lato client. Ma, in outEdges/inEdges di ogni vertice, anche tutti i vertici connessi sono esattamente una volta, non so se è necessario sapere se ci sono più spigoli tra i vertici in questo elenco.

Questa query utilizza ~ 0.06s sul mio set di dati.

Tuttavia, se ci si impegna di più, si potrebbe anche considerare l'utilizzo di una traversata artigianale all'interno di un'applicazione Foxx. Questo è un po 'più complicato ma potrebbe essere più veloce nel tuo caso, poiché fai meno sottoquery. Il codice per questo potrebbe apparire come il seguente:

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]; 
      }) 
}; 

L'idea di questo attraversamento è quello di visitare tutti i vertici dal vertice inizio per un massimo di due bordi di distanza. Verranno aggiunti tutti i vertici in 0 - 1 con i dati nell'oggetto vertici. Tutti i bordi provenienti dal vertice di partenza verranno aggiunti con i dati nell'elenco dei bordi. Tutti i vertici in profondità 2 imposteranno solo i bordi esterni/angoli nel risultato.

Questo ha il vantaggio che, vertices viene deduplicato. e outEdges/inEdges contengono tutti i vertici collegati più volte, se vi sono più spigoli tra loro.

Questo attraversamento viene eseguito sul set di dati in ~ 0.025s quindi è due volte più veloce della soluzione solo AQL.

spero che questo aiuti ancora;)

+0

Questo è incredibilmente utile - grazie! Ho dovuto cambiare 'vertexData: DOCUMENT (docId),' a 'vertexData: DOCUMENT (docId) [0]', per farlo funzionare con i miei dati in d3 (poiché altrimenti le proprietà del documento del vertice finale erano contenute in una matrice di lunghezza 1 invece di semplicemente attaccato direttamente al vertice). Altrimenti funziona come un fascino. Tornerò al codice FOXX quando avrò un po 'più di tempo per imparare FOXX, ma sembra molto utile. – ropeladder

+0

siete i benvenuti;) – mchacki

Problemi correlati