DEV Community

Son Pham
Son Pham

Posted on

Build a hierarchy force graph by D3v7 and React + Vite

In recent features, I was involved to building graph visualization which built used D3js(v7). In this post, i'll show you how to create simple hierarchy force graph using D3 for visualizing connections in your data.

Creating The App

We'll start by creating a new React app using Vite and TypeScript

yarn create vite d3-hierarchy-force-graph --template react-ts
Enter fullscreen mode Exit fullscreen mode

This command will generate a new Vite and React Typescript project. After the project was created, get into the app folder and add D3 to it by using the following command:

yarn add d3 @types/d3
Enter fullscreen mode Exit fullscreen mode

Now that all the libraries we need are in the project, it’s time to move forward

Building Graph

Here is the code for the Graph component container:

export const HierarchyForceGraph = () => {
const containerRef = useRef(null)
useEffect(() => {
let _destroy = null;
if(containerRef.current) {
const {destroy} = runGraph({container: containerRef.current})
_destroy = destroy
}
return () => _destroy.()
}, []);
return <div ref={containerRef} style={{height: 500, width: 600}}/>
}

We will add the D3 to generate the graph

const rect = container.getBoundingClientRect();
const {width, height} = rect;
const svg = d3.select(container).append("svg").attr("width", "100%").attr("height", "100%").attr("viewBox", `0 0 ${width} ${height}`)
const simulation = d3
.forceSimulation()
.force(
"collide",
d3
.forceCollide()
.radius(() => 50)
.strength(0.2)
)
.force(
"x",
d3.forceX().x(() => width / 2)
)
.force("charge", d3.forceManyBody().strength(100));
const drawLinks = (links: any) => {
svg.append("g").attr("class", "links");
let link = svg
.select(".links")
.selectAll(".link")
.data(links, (d) => {
return d.index as number;
});
link.exit().remove();
const linkEnter = link
.enter()
.append("line")
.attr("class", "link")
.attr("stroke", "#9D9D9D")
.attr("stroke-dasharray", () => "4, 6")
.attr("stroke-width", () => "0.85px");
link = linkEnter.merge(link);
};
const drawNodes = (nodes: any) => {
svg.append("g").attr("class", "nodes");
let nodeContainer = svg
.select(".nodes")
.selectAll(".node")
.data(nodes, (d) => d.index as number);
nodeContainer.exit().remove();
const nodeEnter = nodeContainer
.enter()
.append("g").attr("class", "node")
;
nodeEnter
.append("circle")
.attr("r", () => NODE_RADIUS)
.attr("fill", "#E9E9E9");
nodeEnter
.append("text")
.attr("y", 5)
.attr("text-anchor", "middle")
.attr("font-size", "16px")
.attr("font-weight", "bold")
.text((d: any) => {
return d.data.influencer_name
?.split(" ")
.slice(0, 2)
.map((str: string) => str.charAt(0))
.join("")
.toUpperCase();
});
// prevent link overlap title
nodeEnter
.append("text")
.attr("y", () => NODE_RADIUS * 1.5 + 3.5)
.attr("text-anchor", "middle")
.attr("font-size", "13px")
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", "11px")
.text((d: any) => {
return d.data.influencer_name;
});
nodeEnter
.append("text")
.attr("y", () => NODE_RADIUS * 1.5)
.attr("text-anchor", "middle")
.attr("font-size", "13px")
.text((d: any) => d.data.influencer_name);
nodeContainer = nodeEnter.merge(nodeContainer);
};
view raw runGraph.tsx hosted with ❤ by GitHub

When the graph is ready we will add nodes and links to simulation then add a few event handlers to handle what is going to happen when tick is happening

const {links, nodes} = flattenNode(data);
simulation.stop();
simulation.nodes(nodes);
const link = d3
.forceLink()
.id((d) => {
return d.index as number;
})
.distance(120);
link.links(links);
simulation.force("link", link);
drawLinks(links);
drawNodes(nodes);
simulation.on("tick", () => {
const linkElems = svg.select(".links").selectAll(".link");
const nodeElems = svg.select(".nodes").selectAll(".node");
nodes.forEach((d: SimulationNodeDatum & {depth: number}) => {
d.y = d.depth < 0 ? -80 : (d.depth) * 120 + 60;
});
nodeElems.attr("transform", (d: any) => {
return `translate(${d.x}, ${d.y})`;
});
linkElems
.attr("x1", (d: any) => {
return d.source.x
})
.attr("y1", (d: any) => {
return d.source.y + NODE_RADIUS + 10;
})
.attr("x2", (d: any) => d.target.x)
.attr("y2", (d: any) => d.target.y);
});
simulation.restart().alpha(1).alphaTarget(0);

Now that everything is set in place you can run the app and look at your fancy force graph.

Hierarchy force graph

Summary
In the post I showed how to create a hierarchy force graph using React + Vite and D3. You can find the graph code here.

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Retry later
👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay