Rendering a Direct Acyclic Graph with force-graph

Colin Wren
3 min readSep 1, 2024

--

I’ve been toying around with an idea recently that involves exploring the relationship between personas and the interaction paths they take to complete tasks in software.

When defining the data model I needed the persona and interaction points to have distinct objects but also have a means of linking them together. I landed on a Directed Acyclic Graph (DAG) as I wanted to ensure that I could model repeated steps flatly.

In the past I had used something like Neo4J to create this model but this would have been a bit overkill for what I was looking to do as I just wanted to visualise the links and query anything too complex.

I also wanted a force graph to for the visualisation, as I wanted the end user to be able to play around with the graph and have the nodes not cluster together too much.

So I did a bit of searching for graph visualisation libraries and found force-graph, a library for building a force graph that has libraries for a multitude of JavaScript frameworks (React, Vue etc) and presentational needs (2D, 3D, AR, VR etc).

As I’m using React and wanted a simple 2D visualisation I settled on using react-force-graph-2d.

Visualising a DAG with react-force-graph-2d

The first thing you’ll need to visualise a DAG in force-graph is, well a DAG. The basics of the DAG data structure are nodes and edges, force-graph calls these nodes and links but they serve the same purpose.

DAG data shape that force-graph uses

With your DAG object you can then pass that to the graphDataprop of the force-graph component and it will render you a set of nodes and edges.

rendering force-graph in React
The rendered graph

However a set of circles and lines doesn’t mean much so to have a graph that’s somewhat useful you’ll need to consult the force-graph documentation to figure out how to add things like labels to, onClick events and more.

The force graph documentation unfortunately isn’t the easiest thing in the world to make sense of but I found that the many examples that have been created in the repositories helped massively as I could see how the props the documentation mentioned actually worked.

Don’t be confused by the nodeLabeland linkLabelprops though, these aren’t going to render labels on the graph itself but instead set the labels shown when the user hovers over a node or link in the graph.

For it’s 2D presentation force-graph uses a canvas so labels and node presentation callbacks give you a canvas context and the node/link data and give you a lot of control over how you want to display them.

For rendering a label for the node you use the nodeCanvasObjectprop which gives you the node object and the canvas context. You can then use normal canvas painting code to create a text object on the canvas and plot it at node’s coordinates on the canvas.

In order to ensure that your click events work with the new node’s shape you also need to define the nodePointerPaintArea so that it knows what the dimensions of the node are.

Rendering a custom node with the name of the node
Nodes with labels

If you want to show the direction of the link then you can set the linkDirectionalArrowprop which will let you define the thickness of the link, the length of the arrow head and more.

Also you can control how the graph acts when it renders, such as once the force engine has finished calculating you may want to show the entire graph.

zooming the graph to fill the canvas after rending

Summary

force-graph is a great little library if you need to create a graph visualisation quickly, I was able to get something reasonably presentable going in an hour and then being able to take the 2D version and expand it to other presentations like 3D is really useful.

--

--

Colin Wren

Currently building reciprocal.dev. Interested in building shared understanding, Automated Testing, Dev practises, Metal, Chiptune. All views my own.