Data Visualization in Mind-map using D3.js
Visualizing information about any industry’s entities and assets through graphs gives a deeper insight and understanding of all its resources. Deciding the right kind of chart or graph for data analysis depends on factors considered for generating ideas like timeline, monetary funds, assets used, resources, etc.
Mind Map is a way stakeholders can visualize professional business visions and ideas. We have innumerable libraries and frameworks that help developers integrate them with their applications in front-end frameworks. One such library is D3.js which uses HTML, Canvas, and SVG to manipulate the DOM. D3.js uses basic HTML and Canvas, making it easier for developers to customize it how they want, and its graphs are interactive. D3.js is one of the standard libraries for generating charts and graphs and its appropriate user experience.
Recently, my team and I had to develop a graph with many entities and more than 100 resources involved. Thus, to create a chart with a huge dataset and straightforward user interface, we decided to use D3.js. We have articulated a few significant changes required in any application while adapting it. In this article, we will be using Angular.
Let’s look at how we integrated D3.js into our Angular application.
Installation
D3.js consists of multiple independent modules, such as arrays, contours, forces, polygons, etc., which developers can use independently in any application. Also, D3.js itself comes in various versions. In its latest version (7.6.1), you can either download the latest version from npm or download the standalone libraries as per requirements such that for arrays, you can download d3-array. We have installed the whole library from npm and its type declaration for mindmap.
npm install d3
npm install --save @types/d3
Graph Component
Create a shared graph component and render it from its parent component. In this example, we are directly calling it from app.component.ts as follows:
<graph *ngIf="data" data=[data]></graph>
Create a method to generate the necessary data in the app.component.ts file, then call that function in the component’s ngOnInit state. To generate data in that function, create nodes and edges with the help of a loop and add the code in the constructor method itself, as shown below. In our case, we fetched the data from the API, but for now, we will generate the same with a static dataset.
Nodes and Edges Components
The D3 library has implemented a module named “d3-force”. D3 Force is used to generate the force-directed graph. Developers can position the visual elements using a physics-based simulator in D3’s force layout. In a force layout consisting of nodes and links, all the nodes are attracted to one another by a collision function, forming a circle. Each iteration will produce SVG and position modifications are based on node collision detection and force intensity.
Here is a small example of generating the data set:
- Create a Node Component
We create class components for the Node class, which implements the d3 simulator node interface, and declare class variables and their respective data type:
export class Node implements d3.SimulationNodeDatum {
id: string;
name: string;
type: string;
size: number;
index?: number;
x?: number;
y?: number;
vx?: number;
vy?: number;
fx?: number | null;
fy?: number | null;
link: number = 0;
constructor(id, name, type, size = 3.5) {
this.id = id;
this.name = name;
this.type = type;
this.size = size;
}
}
2. Create a Link Component
Create a Link class that implements d3’s simulator Link interface. Declare the variables and default assignments in its constructor:
export class Link implements d3.SimulationLinkDatum<Node> {
index?: number;
source: Node | string | number;
target: Node | string | number;
color: string;
constructor(source, target, color="black") {
this.source = source;
this.target = target;
this.color = color;
}
}
3. Import Node and Link
Import above created Node and Link Component into App Component for static data set generation:
export class AppComponent {
nodes: Node[] = nodes;
links: Link[] = links;
constructor() {
const limit = 10;
const getIndex = number => number - 1;
for(let i==1; i<=limit; i++){
this.nodes.push(new Node(i, "Root", 'root'))
}
for(let i=1; i<= limit; i++){
for(let j=2; i*m <= limit; j++){
this.nodes[getIndex(i)].link++;
this.nodes[getIndex(i+1)].link++;
this.links.push(new Link(i, i+1));
}
}
}
}
Generating Graph
We used a force simulator for generating graphs. We have also added the simulator to an Angular service so that all the relevant class components can access it. We pass the array of objects (nodes and links) to this function.
There are four inbuild functions of D3 force layout which we will be using to generate our graph:
forceSimulator(options) {
if(!this.simulation) {
const ticker = this.ticker;
this.simulation = d3.forceSimulation(this.nodes)
.force("link", d3.forceLink(this.links).id(d => d['id']).distance(10))
.force("x", d3.forceX(options.width/3).strength(0.08))
.force("y", d3.forceY(options.height/2).strength(0.08))
.force("collide", d3.forceCollide().radius(50).iterations(2));
this.simulation.on('tick', function() {
ticker.emit(this);
});
}
this.simulation.force('centers',
d3.forceCenter(options.width/2, options.height/2));
this.simulation.restart();
}
In above example, we saw forceLink function helps with creating a link between nodes by specifying the distance. We have taken default distance as 10 as mentioned in above funciton. Similarly, forceX and forceY helps in specifying the dimension of the links and forceCollide helps in specifying the distance between each node to avoid collision between any two nodes.
This way, we can also add functionalities like dragging and zooming. To give a better user experience, we can also create custom menus or filters in the graph, such as adding a tooltip to a node.
Conclusion
When creating graphs with D3.js, developers will have more room for user-friendly visualizations. Users benefit from modules like disconnected graphs, force simulation, axis, scale, etc.; In D3.js, developers can design bespoke bundles. So for more extensive datasets, it takes a few seconds to render them completely. But, in some cases, if the data records are more than thousands, D3 can provide smooth, clear, and accurate visualizations. Overall, D3.js has been a classic choice in data analytics, irrespective of the frameworks used in the application.
Special mention to Mukund and Priyadarshini for helping me with this article.