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