Complex hierarchical tree layout in Angular Diagram control

23 Aug 202523 minutes to read

Complex hierarchical tree layout arranges nodes in a tree-like structure where child nodes can have multiple parent nodes, creating interconnected relationships beyond traditional single-parent hierarchies. This layout type is ideal for organizational charts with dotted-line relationships, project dependencies, or any structure where entities report to multiple authorities. This layout extends the standard hierarchical tree layout to support these complex relationships.

To create a complex hierarchical tree, set the type property of layout to ComplexHierarchicalTree.

Complex hierarchical tree layout with nodes and connectors

This example demonstrates how to create a complex hierarchical tree layout by manually defining nodes and connectors. The layout automatically positions nodes based on their hierarchical relationships while handling multiple parent-child connections.

import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramModule, DiagramComponent, NodeModel, ConnectorModel, Diagram, DataBinding,
  ComplexHierarchicalTree, LayoutModel } from '@syncfusion/ej2-angular-diagrams';

Diagram.Inject(DataBinding, ComplexHierarchicalTree);

@Component({
  imports: [ DiagramModule ],
  providers: [],
  standalone: true,
  selector: 'app-container',
  template: `<ejs-diagram #diagram id="diagram" width="100%" height="1000px" [nodes]='nodes' [connectors]='connectors'
  [getConnectorDefaults]='connectorDefaults' [getNodeDefaults]='nodeDefaults' [layout]='layout'></ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild('diagram')
  public diagram?: DiagramComponent;

  //Initialize nodes for diagram
  public nodes: NodeModel[] = [
    { id: 'node1' },
    { id: 'node2' },
    { id: 'node3' },
    { id: 'node4' },
    { id: 'node5' },
    { id: 'node6' },
    { id: 'node7' },
  ];

  //Initialize connectors for diagram
  public connectors: ConnectorModel[] = [
    { id: 'node1-node4', sourceID: 'node1', targetID: 'node4' },
    { id: 'node2-node4', sourceID: 'node2', targetID: 'node4' },
    { id: 'node3-node4', sourceID: 'node3', targetID: 'node4' },
    { id: 'node4-node5', sourceID: 'node4', targetID: 'node5' },
    { id: 'node4-node6', sourceID: 'node4', targetID: 'node6' },
    { id: 'node5-node6', sourceID: 'node6', targetID: 'node7' },
  ];

  //Uses layout to auto-arrange nodes on the Diagram page
  public layout: LayoutModel = {
    //Sets layout type
    type: 'ComplexHierarchicalTree',
  };

  //Sets the default properties for all the Nodes
  public nodeDefaults(node: NodeModel): NodeModel {
    node.width = 70; node.height = 70;
    node.annotations = [{ content: node.id }];
    return node;
  };

  //Sets the default properties for all the connectors
  public connectorDefaults(connector: ConnectorModel): ConnectorModel {
    connector.type = 'Orthogonal';
    return connector;
  };
   
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Complex hierarchical tree layout with DataSource

When working with large datasets, binding the layout to a data source provides better maintainability and dynamic content management. The following example shows how to create a complex hierarchical tree using a data source configuration.

import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramModule, HierarchicalTreeService, DataBindingService,  NodeModel, ConnectorModel, Diagram,
  DataBinding, ComplexHierarchicalTree, LayoutModel, DiagramComponent } from '@syncfusion/ej2-angular-diagrams'
import { DataManager } from '@syncfusion/ej2-data';

Diagram.Inject(DataBinding, ComplexHierarchicalTree);

@Component({
  imports: [DiagramModule],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: 'app-container',
  template: `<ejs-diagram #diagram id="diagram" width="100%" height="1000px" [getNodeDefaults]='nodeDefaults'
  [getConnectorDefaults]='connectorDefaults' [layout]='layout' [dataSourceSettings]='data'></ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild('diagram')
  public diagram?: DiagramComponent;

  //Initializes data source
  public data: Object = {
    id: 'Name', parentId: 'ReportingPerson',
    dataSource: new DataManager([
      { "Name": "node11" },
      { "Name": "node12", "ReportingPerson": ["node114"] },
      { "Name": "node13", "ReportingPerson": ["node12"] },
      { "Name": "node14", "ReportingPerson": ["node12"] },
      { "Name": "node15", "ReportingPerson": ["node12"] },
      { "Name": "node16", "ReportingPerson": [] },
      { "Name": "node17", "ReportingPerson": ["node13", "node14", "node15"] },
      { "Name": "node18", "ReportingPerson": [] },
      { "Name": "node19", "ReportingPerson": ["node16", "node17", "node18"] },
      { "Name": "node110", "ReportingPerson": ["node16", "node17", "node18"] },
      { "Name": "node111", "ReportingPerson": ["node16", "node17", "node18", "node116"] },
      { "Name": "node21" },
      { "Name": "node22", "ReportingPerson": ["node114"] },
      { "Name": "node23", "ReportingPerson": ["node22"] },
      { "Name": "node24", "ReportingPerson": ["node22"] },
      { "Name": "node25", "ReportingPerson": ["node22"] },
      { "Name": "node26", "ReportingPerson": [] },
      { "Name": "node27", "ReportingPerson": ["node23", "node24", "node25"] },
      { "Name": "node28", "ReportingPerson": [] },
      { "Name": "node29", "ReportingPerson": ["node26", "node27", "node28", "node116"] },
      { "Name": "node210", "ReportingPerson": ["node26", "node27", "node28"] },
      { "Name": "node211", "ReportingPerson": ["node26", "node27", "node28"] },
      { "Name": "node31" },
      { "Name": "node114", "ReportingPerson": ["node11", "node21", "node31"] },
      { "Name": "node116", "ReportingPerson": ["node12", "node22"], }
    ],)
  };

  //Sets the default properties for all the Nodes
  public nodeDefaults(node: NodeModel): NodeModel {
    node.width = 70; node.height = 70;
    return node;
  };

  //Sets the default properties for all the connectors
  public connectorDefaults(connector: ConnectorModel): ConnectorModel {
    connector.type = 'Orthogonal';
    return connector;
  };
 
  //Uses layout to auto-arrange nodes on the Diagram page
  public layout: LayoutModel = {
    //Sets layout type
    type: 'ComplexHierarchicalTree',
  };
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Complex hierarchical tree layout

Note: In Diagram layouts, all root nodes will always render at the same level. This default behavior cannot be changed to render different trees at distinct levels.

Line Distribution

Line distribution prevents connector overlap by controlling how multiple connectors from a single parent node are positioned. Without line distribution, connectors may overlap and create visual confusion in complex layouts.

The connectionPointOrigin property controls this behavior:

  • SamePoint (default): All connectors from a parent originate from the same point
  • DifferentPoint: Each connector originates from a different point, distributing connections around the node
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { NodeModel, ConnectorModel, Diagram, DataBinding, ComplexHierarchicalTree, LayoutModel, LineDistribution,
  ConnectionPointOrigin, DiagramModule, HierarchicalTreeService, DataBindingService, DiagramComponent } from '@syncfusion/ej2-angular-diagrams';
import { DataManager, Query } from '@syncfusion/ej2-data';

Diagram.Inject(DataBinding, ComplexHierarchicalTree, LineDistribution);


@Component({
  imports: [DiagramModule],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: 'app-container',
  template: `<ejs-diagram #diagram id="diagram" width="100%" height="590px"
    [getNodeDefaults]='nodeDefaults' [getConnectorDefaults]='connectorDefaults'
    [layout]='layout' [dataSourceSettings]='source'></ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild('diagram')
  public diagram?: DiagramComponent;

  //Initializes data source
  public data: Object[] = [
    { "Name": "node11" },
    { "Name": "node12", "ReportingPerson": ["node114"] },
    { "Name": "node13", "ReportingPerson": ["node12"] },
    { "Name": "node14", "ReportingPerson": ["node12"] },
    { "Name": "node15", "ReportingPerson": ["node12"] },
    { "Name": "node116", "ReportingPerson": ["node22", "node12"] },
    { "Name": "node16", "ReportingPerson": [] },
    { "Name": "node18", "ReportingPerson": [] },
    { "Name": "node21" },
    { "Name": "node22", "ReportingPerson": ["node114"] },
    { "Name": "node23", "ReportingPerson": ["node22"] },
    { "Name": "node24", "ReportingPerson": ["node22"] },
    { "Name": "node25", "ReportingPerson": ["node22"] },
    { "Name": "node26", "ReportingPerson": [] },
    { "Name": "node28", "ReportingPerson": [] },
    { "Name": "node31" },
    { "Name": "node114", "ReportingPerson": ["node11", "node21", "node31"] }
  ];

  //Configures data source for diagram
  public source: Object = {
    id: 'Name', parentId: 'ReportingPerson',
    dataSource: new DataManager(this.data as JSON[], new Query().take(7))
  };

  //Sets the default properties for all the Nodes
  public nodeDefaults(node: NodeModel): NodeModel {
    node.width = 40; node.height = 40;
    return node;
  };

  //Sets the default properties for all the connectors
  public connectorDefaults(connector: ConnectorModel): ConnectorModel {
    connector.type = 'Orthogonal';
    connector.cornerRadius = 7;
    return connector;
  };

  //Uses layout to auto-arrange nodes on the Diagram page
  public layout: LayoutModel = {
    //Sets layout type
    type: 'ComplexHierarchicalTree',
    connectionPointOrigin: ConnectionPointOrigin.DifferentPoint,
  };

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Note: Line distribution requires the LineDistribution module to be injected in the diagram.

Different point

Linear Arrangement

Linear arrangement positions child nodes in a straight line with their parent node centered relative to the children. This creates a more organized appearance when dealing with nodes that have multiple children.

When line distribution is enabled, linear arrangement activates automatically. The arrangement property provides control over this feature:

  • Nonlinear (default): Child nodes are arranged based on available space
  • Linear: Child nodes are arranged in a straight line with the parent centered
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramComponent, DiagramModule, HierarchicalTreeService, DataBindingService, NodeModel, ConnectorModel,
  Diagram, DataBinding, ComplexHierarchicalTree, LayoutModel, LineDistribution, ChildArrangement
} from '@syncfusion/ej2-angular-diagrams';
import { DataManager, Query } from '@syncfusion/ej2-data';

Diagram.Inject(DataBinding, ComplexHierarchicalTree, LineDistribution);


@Component({
  imports: [DiagramModule],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: 'app-container',
  template: `<ejs-diagram #diagram id="diagram" width="100%" height="590px" [getNodeDefaults]='nodeDefaults'
  [getConnectorDefaults]='connectorDefaults' [layout]='layout' [dataSourceSettings]='source' ></ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild('diagram')
  public diagram?: DiagramComponent;

  //Initializes data source
  public data: Object[] = [
    { "Name": "node11" },
    { "Name": "node12", "ReportingPerson": ["node114"] },
    { "Name": "node13", "ReportingPerson": ["node12"] },
    { "Name": "node14", "ReportingPerson": ["node12"] },
    { "Name": "node15", "ReportingPerson": ["node12"] },
    { "Name": "node116", "ReportingPerson": ["node22", "node12"] },
    { "Name": "node16", "ReportingPerson": [] },
    { "Name": "node18", "ReportingPerson": [] },
    { "Name": "node21" },
    { "Name": "node22", "ReportingPerson": ["node114"] },
    { "Name": "node23", "ReportingPerson": ["node22"] },
    { "Name": "node24", "ReportingPerson": ["node22"] },
    { "Name": "node25", "ReportingPerson": ["node22"] },
    { "Name": "node26", "ReportingPerson": [] },
    { "Name": "node28", "ReportingPerson": [] },
    { "Name": "node31" },
    { "Name": "node114", "ReportingPerson": ["node11", "node21", "node31"] }
  ];

  //Configures data source for diagram
  public source: Object = {
    id: 'Name', parentId: 'ReportingPerson',
    dataSource: new DataManager(this.data as JSON[], new Query().take(7))
  };

  //Sets the default properties for all the Nodes
  public nodeDefaults(node: NodeModel): NodeModel {
    node.width = 40; node.height = 40;
    return node;
  };

  //Sets the default properties for all the connectors
  public connectorDefaults(connector: ConnectorModel): ConnectorModel {
    connector.type = 'Orthogonal';
    connector.cornerRadius = 7;
    return connector;
  };

  //Uses layout to auto-arrange nodes on the Diagram page
  public layout: LayoutModel = {
    //Sets layout type
    type: 'ComplexHierarchicalTree',
    arrangement: ChildArrangement.Linear
  };

}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Note: Linear arrangement requires the LineDistribution module and is applicable only for complex hierarchical tree layouts.

Enable routing for layout

In complex diagrams with intricate parent-child relationships, connectors may pass through or overlap with nodes, making the diagram difficult to read. Routing functionality automatically calculates connector paths that avoid intersecting with nodes and other obstacles.

Set the enableRouting property to true to activate intelligent connector routing.

import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramModule, HierarchicalTreeService, DataBindingService, DiagramComponent, NodeModel, ConnectorModel,
  Diagram, DataBinding, ComplexHierarchicalTree, LayoutModel, LineDistribution } from '@syncfusion/ej2-angular-diagrams';
import { DataManager, Query } from '@syncfusion/ej2-data';

Diagram.Inject(DataBinding, ComplexHierarchicalTree, LineDistribution );


@Component({
  imports: [ DiagramModule ],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: 'app-container',
  template: `<ejs-diagram #diagram id="diagram" width="100%" height="590px" [getNodeDefaults]='nodeDefaults'
  [getConnectorDefaults]='connectorDefaults' [layout]='layout' [dataSourceSettings]='source' ></ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild('diagram')
  public diagram?: DiagramComponent;

  //Initializes data source
  public data: Object[] =[
      { Name: 'node11' },
      { Name: 'node12', ReportingPerson: ['node114'] },
      { Name: 'node13', ReportingPerson: ['node12'] },
      { Name: 'node14', ReportingPerson: ['node12'] },
      { Name: 'node15', ReportingPerson: ['node12'] },
      { Name: 'node16', ReportingPerson: ['node12'] },
      { Name: 'node116', ReportingPerson: ['node22', 'node12', 'node114'] },
      { Name: 'node21' },
      { Name: 'node22', ReportingPerson: ['node114'] },
      { Name: 'node222', ReportingPerson: ['node114'] },
      { Name: 'node2222', ReportingPerson: ['node114'] },
      { Name: 'node23', ReportingPerson: ['node22'] },
      { Name: 'node31' },
      { Name: 'node114', ReportingPerson: ['node11', 'node21', 'node31'] },
    ];

  //Configures data source for diagram
  public source: Object = {
    id: 'Name', parentId: 'ReportingPerson',
    dataSource: new DataManager(this.data as JSON[], new Query().take(7))
  };


    //Sets the default properties for all the Nodes
    public nodeDefaults(node: NodeModel): NodeModel {
      node.width = 40; node.height = 40;
      return node;
    };
  
    //Sets the default properties for all the connectors
    public connectorDefaults(connector: ConnectorModel): ConnectorModel {
      connector.type = 'Orthogonal';
      connector.cornerRadius = 7;
      return connector;
    };
  
    //Uses layout to auto-arrange nodes on the Diagram page
    public layout: LayoutModel = {
      //Sets layout type
      type: 'ComplexHierarchicalTree',
      enableRouting: true
    };
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Best Practices

  • Use data source binding for dynamic content and better maintainability
  • Enable line distribution when dealing with nodes that have multiple connections
  • Consider enabling routing for complex layouts to improve visual clarity
  • Test layout performance with large datasets and optimize as needed
  • Ensure proper module injection for advanced features like line distribution