Let’s look at how to integrate the javascript graph visualisation library with Angular 18.x

Introduction

A graph is a data structure commonly used in computer science. The easiest way to think about it is as a data type you use to store cities and roads which connect them. In this case roads are known as edges, and cities are known as nodes. An edge may be directed meaning it can only go in one direction, and it may be weighted which in the case of connected cities the weight of an edge might represent the distance between the two connected cities. There are many well-known algorithms to find the shortest path between two cities, or to find if there is a path connecting two cities.

Another place this data structure is used would be to represent a maze where you’d use one of those algorithms I just mentioned to find the exit path.

workflow

In my project I needed to build a graph of document tags where a documnt would sometimes have more than one tag. What I wanted to do was visually see which tags were related to each other, and when clicking such a tag I wanted my application to bring up a list of documents using that tag.

Implementation

I decided to go with vis.js because I had used it previously and found it easy to use. The documentation is also very good.

Installation

So the first thing I needed to do was install it.

npm i vis @types/vis

This command installs the vis libray and the types library.

Setup

The documentation has this snippet

content.js

var container = document.getElementById("mynetwork");
var network = new vis.Network(container, data, options);

content.html

<div id="mynetwork"></div>

content.css

#mynetwork {
  width: 600px;
  height: 400px;
  border: 1px solid lightgray;
}

We can see here that the first thing you do is create a div that is styled in the dimensions of your graphing area. Then in your code you obtain a reference to that div, and use that reference in the constructor of the Network object.

Angular solution

Here I create a reference to the div by specifying #visnetwork

component.html

<span
  #visnetwork
  style="width:640px; height:400px; background: rgb(164, 133, 173);"></span>

Next I need to refer to that element in my code, now I have a reference to the #visnetwork element.

component.ts

@ViewChild('visnetwork') visnetwork!: ElementRef;

In the same component.ts file I have the following code to get the list of tags via an observable in a service.


interface Node
{
  id: number,
  label: string
}
interface Edge
{
  from: number,
  to: number
}
//....
this.documentService.getDocuments().pipe(takeUntil(this._destroy$)).subscribe((data) =>
    {
      let j = 0;
      let options = {}
      let tagDictionary = new Map();

      this.container = this.visnetwork.nativeElement;
      for (var tag of this.allTags)
      {
        if (!tagDictionary.get(tag))
        {
          j++;
          tagDictionary.set(tag, j);
          this.nodes.push({ id: j, label: tag })
        }
      }
      for (var document of data.folderContents)
      {
        for (var i = 0; i < document.tags.length; i++)
        {
          if (i + 1 < document.tags.length)
          {
            this.documentEdges.push({ from: tagDictionary.get(document.tags[i].tag), to: tagDictionary.get(document.tags[i + 1].tag) })
          }
        }
      }
      let network!: Network;
      network = new Network(this.container, { nodes: this.nodes, edges: this.documentEdges }, options);

Basically, the variable this.allTags is a string[] containing a list of all tags defined in the application. A node in the graph is basically a type of {id:number, label:string} so I could have used a map I guess but I used a loop to loop through the tag list and and populate a nodes collection.

Next I looped through all the documents associated with this user. Each document has a list of tags associated to it. I then create an edge between the nodes (tags) of a document.

Finally, when I have a collection of all the this.documentEdges and this.nodes, I use the reference to the div element that’s going to be where visjs draws the graph and instantiate a Network object.

Making it event-driven

The last thing I want to do is register an event when the user clicks a node so that I load a list of documents using that node.

network = new Network(this.container, { nodes: this.nodes, edges: this.documentEdges }, options);


network.on("click", (params: any) =>
      {
        this.lastClickedTag = this.nodes.find((x: any) => x.id == params.nodes[0])?.label || '';
        this.documentService.getDocumentsByTags([this.lastClickedTag]).pipe(takeUntil(this._destroy$)).subscribe(data =>
            {
              this.documents = data;
            });
      }

The variable this.documents forms the basis of a list that is generated based on the results of whatever node the user clicked.

Final product

Here is a screenshot of the implementation in action.

graph

I will write another post about how I supplemented my tag generation to use an AI to label each document when its uploaded.