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