Neo4j 3D Force Graph

repository·master·Indexed 43 days ago

https://github.com/jexp/neo4j-3d-force-graph

A demonstration project for connecting Neo4j databases to React-based 2D and 3D force-directed graph visualizations. It features the CypherViz component for executing Cypher queries and rendering results using react-force-graph-2d and 3d-force-graph, including support for incremental loading, node coloring, and relationship weights.

Tokens
2.3K
Snippets
10
Records
11
Agent score
56%

What's inside Neo4j 3D Force Graph

  1. Integrate Neo4j data with ForceGraph3D

    master

    This project demonstrates how to use the Neo4j JavaScript driver to query graph data and render it using 3d-force-graph.

    Important Driver Notes:

    • The Neo4j JS driver uses a custom Number object. You must convert these to standard JavaScript integers using .toNumber() when accessing values (e.g., r.get('source').toNumber()).
    • When sending integer values to the server (like a LIMIT clause), wrap them in neo4j.int() to ensure compatibility.
    // Example of converting Neo4j numbers to JS integers
    const link = { 
      source: r.get('source').toNumber(), 
      target: r.get('target').toNumber() 
    };
    
    // Example of wrapping an integer for a Neo4j query
    const params = { limit: neo4j.int(5000) };
  2. Data format for CypherViz

    master

    The CypherViz component expects the Cypher query results to be mapped into a specific JSON structure for the graph visualization.

    To work correctly with the component's internal loadData logic, the Cypher query must return columns named source and target. The component then transforms these records into a data object with the following shape:

    • nodes: An array of objects, where each object contains a name property (e.g., { name: 'Joe' }).
    • links: An array of objects containing source and target properties matching the names in the nodes array (e.g., { source: 'Joe', target: 'Jane' }).
    {
      "nodes": [{ "name": "Joe" }, { "name": "Jane" }],
      "links": [{ "source": "Joe", "target": "Jane" }]
    }
  3. Perform incremental loading of graph data

    master

    Instead of loading the entire dataset at once, you can iterate through the Neo4j driver result records and update the graph incrementally. This is useful for large datasets.

    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]
        });
    });        
  4. Configure node and relationship sizes (weights)

    master

    Use properties from your Neo4j data to drive the visual scale of the graph elements. For example, use a size property for nodes and a weight property for links.

    Tip: When using weights for relationship thickness, consider using log(weight) in your Cypher query to prevent links from becoming excessively thick.

    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);
  5. Use particles and cluster coloring

    master

    To visualize community structures or flow, you can color both nodes and links by a cluster ID and use linkDirectionalParticles to represent relationship weights as moving particles.

    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);
  6. Configure node colors, labels, and hover effects

    master

    You can enhance the visualization by automatically coloring nodes based on a property (like a label) and showing custom text captions when hovering over nodes.

    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);
  7. Perform basic loading of graph data

    master

    The fastest way to load data is to query only the IDs of nodes and relationships. This minimizes the payload size from Neo4j.

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

    master

    The CypherViz component is a React component designed to visualize Neo4j graph data by executing Cypher queries. It requires a Neo4j driver instance passed via the driver prop.

    Internally, it manages a Cypher query string and a graph data object. It provides a way to execute the query against a specific database and transform the resulting records into a node-link format compatible with react-force-graph-2d.

    import CypherViz from './CypherViz';
    
    // Usage: <CypherViz driver={driver}/>
    // Note: The driver must be a valid Neo4j driver instance.