Visualizing Data with Angular and D3

Liran Sharir
Netscape
Published in
6 min readMar 6, 2017

--

Binding the data in the data-driven documents.

D3.js - a JavaScript library for manipulating documents based on data. Angular - a framework that prides itself on its high performant data binding techniques.

Below I’ll review one proper approach to harness all that power. From D3’s simulations and forces to SVG injections and template syntax utilization.

Demo: positive integers under 300 connected to their dividers

The complete code repository is provided below for all the superior developers who will skip reading this article. For all other mediocre developers (definitely not you) note that the code on this page is simplified for readability.

Source: https://github.com/lsharir/angular-d3-graph-example (recently updated to angular 5)

Demo: https://lsharir.github.io/angular-d3-graph-example/

How to easily make cool looking shit like this

Below I will present one approach to harness Angular and D3 together. We will walk through the following steps:

  1. Initializing a project
  2. Interfacing d3 through angular
  3. Generate a simulation
  4. Bind the simulation’s data to the document through angular
  5. Bind user interactions to the graph
  6. Optimize performance by controlling change detection
  7. Go online and complain about angular versioning strategy

So open your terminals, fire up your editors and warm up the clipboard, we are diving into the code.

Application Structure

We will separate d3 related code and svg visual elements code. I will go into detail when the relevant files will be created, but for now, this is the expected structure of our app:

d3
|- models
|- directives
|- d3.service.ts
visuals
|- graph
|- shared

Initializing an Angular application

Start an Angular application project. Angular 5, 4 or 2 the code below was tested on all of those.

If you don’t have one already, use angular-cli to quickly set one up:

npm install -g @angular/cli

Then start a new angular object:

ng new angular-d3-example

Your application got generated at the angular-d3-example folder. Use the ng serve command from its root to start development, the application should be served on localhost:4200 .

Initializing D3

Make sure to install d3 and its relevant types declarations:

npm install --save d3
npm install --save-dev @types/d3

Interfacing D3 through Angular

The correct way to use d3 (or any other library) within a framework is to interact with it through a customized interface, one which we will implement as classes, angular services and directives. By working like that, we separate the core functionalities we are introducing from the components that implement them, making our application structure more flexible and scalable, and isolating bugs in their proper environment.

Our D3 folder will end up looking like this:

d3
|- models
|- directives
|- d3.service.ts

models will provide us typing safety and robust instances of datum. directives will direct elements in how to implement d3 behaviors. d3.service.ts will expose all the methods to be used by either d3 models and directives or external application components.

Let’s start designing the d3 service.

This service will provide us with computational models and behaviors. The getForceDirectedGraph method will return a force directed graph instance. The applyZoomableBehaviour and applyDraggableBehaviour methods will let us bind user interactions with elements to corresponding behaviors.

Force Directed Graph

Let’s start creating our force directed graph class and its relevant models. Our graph consists of nodes and links, let’s define the appropriate models.

After defining the core models for our graph to manipulate, we continue to define the graph model itself.

Once these models are defined, we can update our getForceDirectedGraph method in our D3Service

Creating an instance of ForceDirectedGraph will return an instance

ForceDirectedGraph {
ticker: EventEmitter,
simulation: Object
}

It contains a running simulation with the data we provided, along with a ticker holding an event emitter that fires every time the simulation ticks, which we will use like this:

graph.ticker.subscribe((simulation) => {});

We’ll implement the rest of the methods of the D3Service later, we’re going to try and bind the simulation data into the document.

Binding the simulation

We got a hold of our ForceDirectedGraph instance. It holds an ever changing precious data of nodes and their connecting links. You could bind that data to the document the d3 way (like a savage):

function ticked() {
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}

Luckily, this is the 21st century, humanity has evolved to use efficient data binding tools instead of mindlessly changing attributes on elements. This is where Angular’s .muscles { display: flex }

Intermezzo: SVG and Angular

SVG templating with Angular

SVG delayed implementation resulted in a restricting namespace separation within the html document. Which is why Angular cannot recognize declared SVG elements in our components templates (Unless they are visibly descendants of an <svg> tag).

To compile our svg elements properly, we must either:

  1. Meticulously keep them visibly nested under the <svg> tag.
  2. prefix them with “svg” to let Angular know what’s up <svg:line>

SVG components with Angular

Assigning selectors to components in the SVG namespace will not work the usual way. They must be selected through an attribute selector

Note the svg prefix in the component’s template

End of intermezzo

Binding the simulation — visuals

Equipped with ancient svg knowledge, we can start creating visual components that will display our data. Isolated in a visuals folder, we will create shared components (to be potentially used by other graphs) and our primary graph folder, that will hold all the code required to display our force directed graph.

visuals
|- graph
|- shared

Graph visuals

Let’s create the root component that will generate the graph and bind it to the document. We pass along the nodes and links data as the components input variables.

<graph [nodes]="nodes" [links]="links"></graph>

The component takes the nodes and links and creates an instance of the ForceDirectedGraph

Node visual component

Next, let’s create the node visual component, it will display a circle and the id of the node.

Link visual component

On to the link visual component:

Behaviors

Getting back to the d3 part of the application, let’s start creating the directives and service methods required to add cool behaviors to the graph.

Behavior — pan and zoom

We create hooks for the pan and zoom behavior, so it can be easily used:

<svg #svg>
<g [zoomableOf]="svg">
<!-- nodes and links -->
</g>
</svg>

Behavior — draggable

For a draggable behavior, we want to provide the simulation so it can be paused while dragging is in progress.

<svg #svg>
<g [zoomableOf]="svg">
<!-- links -->
<g [nodeVisual]="node"
*ngFor="let node of nodes"
[draggableNode]="node"
[draggableInGraph]="graph"
>
</g>
</g>
</svg>

Let’s recap, our app can now:

  1. Generate a graph and simulation through D3
  2. Bind the simulation data to the document with Angular
  3. Bind user interactions with elements to d3 behaviors

You are probably now wondering: “My simulation data is constantly changing, angular’s magical change detection is binding that data to the document, but why should I let it, I want to force refresh the graph only after simulation ticks.”

Well, you are sort of right, I compared a custom tests with different change detection strategies and there is a noticeable improvement when we take control of change detection.

Angular, D3 and Change Detection

Set the change detection to onPush (change is detected only when the reference of the objects was completely replaced).

The nodes and links object reference does not change, and no change will be detected. This is great! we can now take control of change detection and mark it for check on every simulation tick (using the ticker event emitter we set up earlier).

import { 
Component,
ChangeDetectorRef,
ChangeDetectionStrategy
} from '@angular/core';
@Component({
selector: 'graph',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<!-- svg, nodes and links visuals -->`
})
export class GraphComponent {
constructor(private ref: ChangeDetectorRef) { }ngOnInit() {
this.graph = this.d3Service.getForceDirectedGraph(...);
this.graph.ticker.subscribe((d) => {
this.ref.markForCheck();
});

}
}

Angular will now refresh the graph elements on every tick, which is exactly what we wanted.

That’s it!

You have successfully survived this article and made a cool and scalable visualization. I hope it was clear, useful, correct and concise. If it wasn’t — let me know!

Thank you for reading!

Liran Sharir

--

--