Next.js 3D Node Chart with React-Force-Graph

Overview

This is a how-to for working with React-Force-Charts. React-Force-Charts is an amazing open source chart library that can only be used as a client side rendered component. This post will go over how to dynamically import a CSR library and run the graphs.

Source Code

Please get the source files for this demo. The article only goes over one of the three charts that are available.

GitHub Repo

Prerequisites

New Next.js App

Create a new Next.js application

npx create-next-app@latest

React-Force-Chart

Install the React-Force-Graph library. This package is a wrapper for both React-Force-Graph-2d and React-Force-Graph-3d.

npm i react-force-graph

Material – (Optional)

MUI – Material UI v5.4.3

Material is only used for the layout of the demo.

npm install @mui/material @mui/lab @emotion/react @emotion/styled

Running the Next.JS app

npm run dev

Data for the example

Create the Subfolder

Create a file labeled miserables.json subfolder under ./public labeled data.

You can get the JSON data here

The JSON data

The charts read the following JSON data. There are two sections that the charts read. Nodes and Links
Nodes have an id and group fields.
Links connect the nodes and have a source, target, and value fields.

Static JSON Data

{
    "nodes": [
        {
            "id": "Myriel",
            "group": 1
        },
        {
            "id": "Napoleon",
            "group": 1
        },

   ...
   ],
    "links": [
        {
            "source": "Napoleon",
            "target": "Myriel",
            "value": 1
        },
        {
            "source": "Mlle.Baptistine",
            "target": "Myriel",
            "value": 8
        },
     
     ...
     ]
}

Randomly Generated Chart Data

function genRandomTree(N = 300, reverse = false) {
  return {
      nodes: [...Array(N).keys()].map(i => ({ id: i })),
      links: [...Array(N).keys()]
          .filter(id => id)
          .map(id => ({
              [reverse ? 'target' : 'source']: id,
              [reverse ? 'source' : 'target']: Math.round(Math.random() * (id - 1))
          }))
  };
}

Create the Chart Component

Under the component folder create a new file “Graphs\BasicNodeChart.js“.

Copy & paste the code below.

import React from 'react'

import {  ForceGraph3D  } from 'react-force-graph';


// Random data
const N = 30;
const gData = {
  nodes: [...Array(N).keys()].map((i) => ({ id: i })),
  links: [...Array(N).keys()]
    .filter((id) => id)
    .map((id) => ({
      source: id,
      target: Math.round(Math.random() * (id - 1))
    }))
};

export default function App() {
  return (
    <ForceGraph3D
      backgroundColor={"rgba(0,0,0,0)"}
      nodeColor={() => "red"}
      linkColor={() => "blue"}
      graphData={gData}
    />
  );
}

Dynamically Import a Library

Under pages create a folder labeled basic-node-chart

Using React.lazy(()=> import(”)) we can lazy load a client side rendered only library after the component has been mounted.

In addition to React.lazy. a state variable isLoaded along with <Suspense /> only renders after the component has been rendered. This library only works as client side rendered.

For more information about Next.js Dynamic Imports click here

Code for basic-node-chart\index.js

import React, {useEffect, useState, Suspense} from 'react'

const NodeChartDynamic = React.lazy(()=> import('@/components/Graph/NodeChartC') );

export default function index() {
  const [isLoaded, setIsLoaded]  = useState(false);
  
  useEffect(async () => {
    // Client-side-only code

    setIsLoaded(true)
  })

  return (
    <>
    {isLoaded &&
     <Suspense fallback={<div>Loading...</div>}>
       <NodeChartDynamic />
     </Suspense>
    }
    </>
  )
}

NOTE: Without lazy loading the component and using <Suspense/>, you will get the window is not defined error below.

Running the Demo

Run the application with npm run dev.

When running the application navigate to “http://localhost:3000/