Skip to content
  1. Tutorials

Ogma Angular 9 Tutorial

The following tutorial will show how to create a Ogma component using the Angular framework. This tutorial is based on the Angular 9 framework version.

The final goal of this tutorial is to build a basic Angular web application with Ogma that:

  • Add a node via a button
  • Shows a tooltip with information when hovering a node
  • An advanced expand-like feature with 2 different layouts

Note The project in this tutorial has been bootstrapped using the angular-cli and Angular 9 which may have small differences from previous versions. While these changes amy occur with previous versions, the pattern used in the tutorial should work for any version of Angular.

As first step the OgmaService is has to be defined:

ts
import { Injectable } from '@angular/core';
import Ogma from '@linkurious/ogma';

@Injectable()
export class OgmaService {
  // expose an instance of Ogma from the service
  public ogma: Ogma;

  public initConfig(configuration = {}) {
    this.ogma = new Ogma(configuration);
  }

  public runLayout(): Promise<void> {
    return this.ogma.layouts.force({ locate: true });
  }
}

and associated with the app.module.ts:

ts
...
import { OgmaService } from './ogma.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [OgmaService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now it's time to use the OgmaService in the application:

ts
import {
  OnInit,
  AfterContentInit,
  Component,
  Input,
  ViewChild
} from '@angular/core';
import { OgmaService } from './ogma.service';

@Component({
  selector: 'app-root',
  template: `
    <div class="App">
      <div #ogmaContainer style="width: 800px; height: 600px;"></div>
    </div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterContentInit {
  @ViewChild('ogmaContainer', { static: true })
  private container;

  /**
   * @param {OgmaService} ogmaService
   */
  constructor(private ogmaService: OgmaService) {}

  /**
   * Initialize the configuration of Ogma
   */
  ngOnInit() {
    // pass the ogma instance configuration on init
    this.ogmaService.initConfig({
      options: {
        backgroundColor: 'rgb(240, 240, 240)'
      }
    });

    // setup more Ogma stuff here, like event listeners and more
  }

  /**
   * Ogma container must be set when content is initialized
   */
  ngAfterContentInit() {
    // atach the Ogma instance to the DOM
    this.ogmaService.ogma.setContainer(this.container.nativeElement);
    return this.runLayout();
  }
}

Add the data

In this tutorial a mock dataset is used to get started, so we're importing it into the component file:

ts
import {
  OnInit,
  AfterContentInit,
  Component,
  Input,
  ViewChild
} from '@angular/core';
import { OgmaService } from './ogma.service';

// mock data
import initialGraph from './data';

@Component({
  selector: 'app-root',
  template: `
    <div class="App">
      <div #ogmaContainer style="width: 800px; height: 600px;"></div>
    </div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterContentInit {
  @ViewChild('ogmaContainer', { static: true })
  private container;

  /**
   * @param {OgmaService} ogmaService
   */
  constructor(private ogmaService: OgmaService) {}

  /**
   * Initialize the configuration of Ogma
   */
  ngOnInit() {
    // pass the ogma instance configuration on init
    this.ogmaService.initConfig({
      // add the mock data here to start with
      graph: initialGraph,
      options: {
        backgroundColor: 'rgb(240, 240, 240)'
      }
    });

    // setup more Ogma stuff here, like event listeners and more
  }

  /**
   * Ogma container must be set when content is initialized
   */
  ngAfterContentInit() {
    // atach the Ogma instance to the DOM
    this.ogmaService.ogma.setContainer(this.container.nativeElement);
    return this.runLayout();
  }

  /**
   * Runs a layout with the current graph
   */
  public runLayout(): Promise<void> {
    return this.ogmaService.runLayout();
  }
}

A new addNewNode action will be added thus a button on the view:

ts
...

@Component({
  selector: 'app-root',
  template: `<div class="App">
    <div #ogmaContainer style="width: 800px; height: 600px;"></div>
    <div class="text-center">
        Number of nodes: {{ countNodes() }}
        <form class="form" #formRef="ngForm">
        <h3>Action</h3>
        <button class="btn" (click)="addNode()">Add a node</button>
        </form>
    </div>
  </div>
  `,
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit, AfterContentInit {

  ...

  /**
   * Add a new node to the visualization
   */
  public addNode(): Promise<void> {
    this.ogmaService.addNewNode();
    return this.runLayout();
  }

  /**
   * Runs a layout with the current graph
   */
  public runLayout(): Promise<void> {
    return this.ogmaService.runLayout();
  }

  /**
   * Returns the number of nodes in the graph
   */
  public countNodes(): number {
    return this.ogmaService.ogma.getNodes().size;
  }
}

and a new method on the ogmaService:

ts
...

// SOme utility functions
function createNode(id: number): RawNode {
    return {
        id,
        attributes: {
            color: id % 2 ? 'purple' : 'orange'
        }
    };
}

function createEdge(node: RawNode, ids: RawNode['id'][]): RawEdge {
    // pick a random node in the graph
    const randomIndex = Math.floor(Math.random() * ids.length);
    const otherNode = ids[randomIndex];
    return { id: `${otherNode}-${node.id}`, source: otherNode, target: node.id };
}

@Injectable()
export class OgmaService {
    // expose an instance of Ogma from the service
    public ogma: Ogma;


    public initConfig(configuration: OgmaParameters = {}){
        this.ogma = new Ogma(configuration);
    }

    public addNewNode() {
        const idsInGraph = this.ogma.getNodes().getId();
        const newNode = createNode(idsInGraph.length);

        this.ogma.addGraph({nodes: [newNode], edges: [createEdge(newNode, idsInGraph)]});
    }

    public runLayout(): Promise<void> {
        return this.ogma.layouts.force({locate: true});
    }
}

Now the chart can be updated with new nodes!

OgmaService adds new nodes

Add the tooltips

Ogma comes with the whole events namespace already exposing many different event hooks. We can bind the hover and unhover events in the OnInit hook of the app component to trigger a state change:

ts
...
export class AppComponent implements OnInit, AfterContentInit {


  @ViewChild('ogmaContainer', {static: true})
  private container;

  // use these to pass the informations to the tooltips
  hoveredContent: {id: NodeId, type: string};
  hoveredPosition: {x: number, y: number};

  /**
   * Initialize the configuration of Ogma
   */
  ngOnInit(){
    this.ogmaService.initConfig({
        graph: initialGraph,
        options: {
            backgroundColor: 'rgb(240, 240, 240)'
        }
    });

    this.ogmaService.ogma.events.on("mouseover", ({ x, y, target }: HoverEvent) => {
      if (target.isNode) {
        // save the tooltip state (offset by 20px the vertical position)
        this.hoveredContent = {
          id: target.getId(),
          type: target.getAttribute('color')
        };
        this.hoveredPosition = {x, y: y + 20};
      }
    });

    this.ogmaService.ogma.events.on("mouseOut",(_: HoverEvent) => {
      // clear the tooltip state
      this.hoveredContent = null;
    });
  }
  ...

To show the tooltip a new tooltip component will be created that accepts two bit of information: content and position:

ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'tooltip',
  template: `
    <div
      class="tooltip"
      #ngTooltip
      [style.top.px]="position.y"
      [style.left.px]="position.x"
    >
      <div class="tooltip-content">
        <table>
          <tbody>
            <tr>
              <td><strong>ID</strong></td>
              <td>{{ content.id }}</td>
            </tr>
            <tr>
              <td><strong>Type</strong></td>
              <td>
                <div class="circle" [style.background]="content.type"></div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  `,
  styleUrls: ['./tooltip.component.css']
})
export class TooltipComponent {
  @Input() content: { id: string; type: string };
  @Input() position: { x: number; y: number };
}

At this point the tooltip has to be integrated in the App component template:

ts
...

@Component({
  selector: 'app-root',
  template: `
  <div class="App">
    <div #ogmaContainer style="width: 800px; height: 600px;"></div>
  <tooltip *ngIf="hoveredContent" [content]="hoveredContent" [position]="hoveredPosition"></tooltip>
    <div class="text-center">
        Number of nodes: {{ countNodes() }}
        <form class="form" #formRef="ngForm">
        <h3>Action</h3>
        <button class="btn" (click)="addNode()">Add a node</button>
        </form>
    </div>
  </div>
  `,
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit, AfterContentInit {
    ...

Ogma Angular component with tooltip

Layout picker - Advanced

This is an advanced step to complete the tutorial: when a node is added to the graph, the user can decide which layout to apply to the expand-like feature.

Two new layouts are defined as available in the application, and all layout method become parametric now:

ts
...

@Component({
  selector: 'app-root',
  template: `
  <div class="App">
    <div #ogmaContainer style="width: 800px; height: 600px;"></div>
    <tooltip *ngIf="hoveredContent" [content]="hoveredContent" [position]="hoveredPosition"></tooltip>
    <div class="text-center">
        Number of nodes: {{ countNodes() }}
        <form class="form" #formRef="ngForm">
        <h3>Action</h3>
        <button class="btn" (click)="addNode()">Add a node</button>
        <h3>Layout:</h3>
        <div *ngFor="let layout of layouts">
            <input [id]="layout" type="radio" name="layout" [(ngModel)]="currentLayout" [value]="layout"
            (change)="runLayout()" />
            <label [attr.for]="layout">{{layout|titlecase}} layout</label>
        </div>
        </form>
    </div>
    </div>
  `,
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit, AfterContentInit {

  @ViewChild('ogmaContainer', {static: true})
  private container;

  // this holds the current layout
  @Input() currentLayout: string = "force";
  layouts: string[] = ['force', 'hierarchical'];

  hoveredContent: {id: NodeId, type: string};
  hoveredPosition: {x: number, y: number};

  ...

  /**
   * Runs a layout with the current graph
   */
  public runLayout(): Promise<void> {
    return this.ogmaService.runLayout(this.currentLayout);
  }

}

Same in the OgmaService:

ts
...

@Injectable()
export class OgmaService {
    ...

    public runLayout(layout: string = 'force'): Promise<void> {
        return this.ogma.layouts[layout]({locate: true});
    }
}

This is the final result:

Ogma with expand-like layout radio inputs