Skip to content

jexp/neo4j-3d-force-graph

Repository files navigation

Some experiments using Data in Neo4j to render 3d graphs using three-js via 3d-force-graph which is a really cool repository.

These pages use the latest Neo4j javascript driver to query the graph for some basic data and render it in the 3d-graph.

Please note that the JS driver uses a custom Number object, which we have to turn into JS integers with value.toNumber() and wrap int values we send to the server like the limit in neo4j.int.

The example pages load 5000 relationships from the GameOfThrones graph at https://demo.neo4jlabs.com:7473 You might need to change auth and database (default: database/user/password: gameofthrones)

Basic Loading

basic loading: just using the id’s (fastest)

MATCH (n)-->(m) RETURN id(n) as source, id(m) as target LIMIT $limit
ForceGraph3D()(document.getElementById('3d-graph')).graphData(gData)
basic

Incremental Loading

Incremental loading: each row from the driver result is added to the graph incrementally

result.records.forEach(r => {
   const { nodes, links } = Graph.graphData();
   const link={source:r.get('source').toNumber(), target:r.get('target').toNumber()}
   Graph.graphData({
        nodes: [...nodes, { id:link.source }, { id: link.target}],
        links: [...links, link]
    });
});

Color and Caption

color by label and text caption on hover

MATCH (n)-->(m)
RETURN { id: id(n), label:head(labels(n)), caption:n.name } as source,
       { id: id(m), label:head(labels(m)), caption:m.name } as target
LIMIT $limit
const Graph = ForceGraph3D()(elem)
              .graphData(gData)
              .nodeAutoColorBy('label')
              .nodeLabel(node => `${node.label}: ${node.caption}`)
              .onNodeHover(node => elem.style.cursor = node ? 'pointer' : null);
labels info
Figure 1. Color and Caption on Paradise Papers

Weights for Node and Relationship Sizes

Use weight on node (e.g. pagerank) and on relationship to render different sizes. Also color relationships by type. We use log(weight) for relationships as they would groth too thick otherwise.

MATCH (n)-[r]->(m)
RETURN { id: id(n), label:head(labels(n)), caption:n.name, size:n.pagerank } as source,
       { id: id(m), label:head(labels(m)), caption:m.name, size:m.pagerank } as target,
       { weight:log(r.weight), type:type(r)} as rel
LIMIT $limit
const Graph = ForceGraph3D()(elem)
              .graphData(gData)
              .nodeAutoColorBy('label')
              .nodeVal('size')
              .linkAutoColorBy('type')
              .linkWidth('weight')
              .nodeLabel(node => `${node.label}: ${node.caption}`)
              .onNodeHover(node => elem.style.cursor = node ? 'pointer' : null);
weights got
Figure 2. Weights on Game Of Thrones

Particles & Cluster Coloring

Color nodes and relationships by community/cluster id. Use particles for relationships to render their weight, here we use the original weight as it represents the number of particles traveling.

MATCH (n)-[r]->(m)
RETURN { id: id(n), label:head(labels(n)), community:n.louvain, caption:n.name, size:n.pagerank } as source,
       { id: id(m), label:head(labels(m)), community:n.louvain, caption:m.name, size:m.pagerank } as target,
       { weight:r.weight, type:type(r), community:case when n.community < m.community then n.community else m.community end} as rel
LIMIT $limit
const Graph = ForceGraph3D()(elem)
              .graphData(gData)
              .nodeAutoColorBy('community')
              .nodeVal('size')
              .linkAutoColorBy('community')
              .linkWidth(0)
              .linkDirectionalParticles('weight') // number of particles
              .linkDirectionalParticleSpeed(0.001) // slow down
              .nodeLabel(node => `${node.label}: ${node.caption}`)
              .onNodeHover(node => elem.style.cursor = node ? 'pointer' : null);
particles got
Figure 3. Particles and Clusters on Game Of Thrones