Customizing layout in Angular Diagram control

23 Aug 202524 minutes to read

The Angular Diagram component provides extensive customization options for automatic layouts, allowing developers to control orientation, spacing, alignment, bounds, and visual behavior. These properties enable fine-tuned positioning and appearance of nodes within hierarchical, organizational, and tree-based diagrams.

To explore all available layout properties, refer to Layout Properties.

Layout bounds

The diagram supports aligning layouts within custom rectangular areas using layout bounds. This feature constrains the layout to a specific region of the canvas, providing precise control over where the layout appears.

Layout bounds define a rectangular area where the entire layout will be positioned. This is particularly useful when integrating diagrams into dashboards or when multiple layouts need to coexist on the same canvas.

The following example shows how to align the layout within specified layout bounds:

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

Diagram.Inject(DataBinding, HierarchicalTree);

@Component({
  imports: [DiagramModule],

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

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;

  //Initialize data source
  public data: object[] = [
    { Name: "Steve-Ceo" },
    { Name: "Kevin-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "Peter-Manager", ReportingPerson: "Kevin-Manager" },
    { Name: "John-Manager", ReportingPerson: "Peter-Manager" },
    { Name: "Mary-CSE ", ReportingPerson: "Peter-Manager" },
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel): NodeModel {
    node.annotations = [{ content: (node.data as { Name: 'string' }).Name }];
    node.width = 100; node.height = 40;
    return node;
  }

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

  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
      //set bounds for layout
      bounds: new Rect(0, 0, 500, 700)
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

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

For more information about bounds configuration, refer to bounds.

Layout alignment

The layout can be positioned anywhere within the layout bounds using the horizontalAlignment and verticalAlignment properties. These properties determine how the layout is positioned relative to its container.

Available alignment options include Left, Right, Center for horizontal alignment, and Top, Bottom, Center for vertical alignment. These settings work independently, allowing for precise positioning control.

The following code illustrates how to configure layout alignment and modify alignment properties at runtime:

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

Diagram.Inject(DataBinding, HierarchicalTree);

@Component({
  imports: [DiagramModule],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: "app-container",
  template: `
  <label for="horizontalAlignment">Horizontal Alignment : </label>
  <select id="horizontalAlignment" (change)="onHorizontalChange($event)">
      <option value="Left">Left</option>
      <option value="Center">Center</option>
      <option value="Right">Right</option>
    </select>
  <label for="verticalAlignment">Vertical Alignment : </label>
    <select id="verticalAlignment" (change)="onVerticalChange($event)">
      <option value="Top">Top</option>
      <option value="Center">Center</option>
      <option value="Bottom">Bottom</option>
    </select>
  <ejs-diagram #diagram id="diagram" width="100%" height="550px" [getNodeDefaults]="getNodeDefaults"
  [getConnectorDefaults]="getConnectorDefaults" [layout]="layout" [dataSourceSettings]="dataSourceSettings"> </ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;

  //Initialize data source
  public data: object[] = [
    { Name: "Steve-Ceo" },
    { Name: "Kevin-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "Peter-Manager", ReportingPerson: "Kevin-Manager" },
    { Name: "John-Manager", ReportingPerson: "Peter-Manager" },
    { Name: "Mary-CSE ", ReportingPerson: "Peter-Manager" },
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel, diagram: Diagram): NodeModel {
    node.annotations = [{ content: (node.data as { Name: 'string' }).Name }];
    node.width = 100; node.height = 40;
    return node;
  }

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

  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
      //set layout alignments
      horizontalAlignment: 'Left',
      verticalAlignment: 'Top'
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

  onHorizontalChange(event: Event) {
    const horizontalAlignment = (event.target as HTMLSelectElement).value as HorizontalAlignment;
    this.diagram.layout.horizontalAlignment = horizontalAlignment;
    this.diagram.dataBind();
  }

  onVerticalChange(event: Event) {
    const verticalAlignment = (event.target as HTMLSelectElement).value as VerticalAlignment;
    this.diagram.layout.verticalAlignment = verticalAlignment;
    this.diagram.dataBind();
  }

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

Layout spacing

Layout spacing controls the distance between nodes in the layout. The horizontalSpacing and verticalSpacing properties define the gaps between nodes horizontally and vertically respectively.

Proper spacing ensures visual clarity and prevents node overlap. Spacing values are measured in pixels and can be adjusted based on node sizes and content density requirements.

The following code illustrates how to set initial horizontal and vertical spacing for the layout and modify these values at runtime:

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

Diagram.Inject(DataBinding, HierarchicalTree);

@Component({
  imports: [DiagramModule],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: "app-container",
  template: `
  <label for="horizontalSpacing">Horizontal Spacing : </label>
  <input type="number" id="horizontalSpacing" value="30" (change)="onHorizontalChange($event)">
  <label for="verticalSpacing">Vertical Spacing : </label>
  <input type="number" id="verticalSpacing" value="30" (change)="onVerticalChange($event)">
  <ejs-diagram #diagram id="diagram" width="100%" height="550px" [getNodeDefaults]="getNodeDefaults" [getConnectorDefaults]="getConnectorDefaults" 
  [layout]="layout" [dataSourceSettings]="dataSourceSettings"> </ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;

  //Initialize data source
  public data: object[] = [
    { Name: "Steve-Ceo" },
    { Name: "Kevin-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "Peter-Manager", ReportingPerson: "Kevin-Manager" },
    { Name: "John-Manager", ReportingPerson: "Peter-Manager" },
    { Name: "Mary-CSE ", ReportingPerson: "Peter-Manager" },
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel): NodeModel {
    node.annotations = [{ content: (node.data as { Name: 'string' }).Name }];
    node.width = 100; node.height = 40;
    return node;
  }

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

  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
      //set layout spacing
      horizontalSpacing: 30,
      verticalSpacing: 30,
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

  onHorizontalChange(event: Event) {
    let horizontalSpacing = Number((event.target as HTMLSelectElement).value);
    if (horizontalSpacing < 20) {
      horizontalSpacing = 20;
    }
    if (horizontalSpacing > 100) {
      horizontalSpacing = 100;
    }
    this.diagram.layout.horizontalSpacing = horizontalSpacing;
    this.diagram.dataBind();
  }

  onVerticalChange(event: Event) {
    let verticalSpacing = Number((event.target as HTMLSelectElement).value);
    if (verticalSpacing < 20) {
      verticalSpacing = 20;
    }
    if (verticalSpacing > 100) {
      verticalSpacing = 100;
    }
    this.diagram.layout.verticalSpacing = verticalSpacing;
    this.diagram.dataBind();
  }

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

Layout margin

Layout margin creates blank space between the layout bounds and the actual layout content. The margin property adds padding around the entire layout, ensuring adequate separation from container edges.

Margins are particularly useful when layouts are displayed within panels, cards, or other UI containers where visual separation is important for clarity and aesthetics.

The following code demonstrates how to set initial layout margin and modify margin values dynamically at runtime:

import { Component, OnInit, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramComponent, Diagram, NodeModel, ConnectorModel, LayoutModel, DataSourceModel, DecoratorModel, ShapeStyleModel, HierarchicalTree, TextModel,
  DiagramModule, HierarchicalTreeService, DataBindingService, DataBinding, Rect, MarginModel } from '@syncfusion/ej2-angular-diagrams';
import { DataManager, Query } from '@syncfusion/ej2-data';

Diagram.Inject(DataBinding, HierarchicalTree);


@Component({
  imports: [DiagramModule],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: "app-container",
  template: `
  <label for="marginLeft">Margin Left : </label>
  <input type="number" id="marginLeft" value="100" (change)="onLeftChange($event)">
  <label for="marginTop">Margin Top : </label>
  <input type="number" id="marginTop" value="100" (change)="onTopChange($event)">
  <ejs-diagram #diagram id="diagram" width="100%" height="550px" [getNodeDefaults]="getNodeDefaults" [getConnectorDefaults]="getConnectorDefaults" 
  [layout]="layout" [dataSourceSettings]="dataSourceSettings"> </ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;

  //Initialize data source
  public data: object[] = [
    { Name: "Steve-Ceo" },
    { Name: "Kevin-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "Peter-Manager", ReportingPerson: "Kevin-Manager" },
    { Name: "John-Manager", ReportingPerson: "Peter-Manager" },
    { Name: "Mary-CSE ", ReportingPerson: "Peter-Manager" },
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel): NodeModel {
    node.annotations = [{ content: (node.data as { Name: 'string' }).Name }];
    node.width = 100; node.height = 40;
    return node;
  }

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

  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
      //Sets layout alignment
      horizontalAlignment: 'Left',
      verticalAlignment: 'Top',
      margin: { left: 100, top: 100 },
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

  onLeftChange(event: Event) {
    let leftValue = Number((event.target as HTMLSelectElement).value);
    if (leftValue < 20) {
      leftValue = 20;
    }
    if (leftValue > 500) {
      leftValue = 500;
    }
    (this.diagram.layout.margin as MarginModel).left = leftValue;
    this.diagram.dataBind();
  }

  onTopChange(event: Event) {
    let topValue = Number((event.target as HTMLSelectElement).value);
    if (topValue < 20) {
      topValue = 20;
    }
    if (topValue > 500) {
      topValue = 500;
    }
    (this.diagram.layout.margin as MarginModel).top = topValue;
    this.diagram.dataBind();
  }

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

Layout orientation

The layout orientation determines the primary direction in which the layout flows. Different orientations are suitable for various organizational structures and display requirements.

Orientation Description
TopToBottom Aligns the layout from top to bottom. All root nodes are placed at the top of the diagram.
LeftToRight Aligns the layout from left to right. All root nodes are placed at the left of the diagram.
BottomToTop Aligns the layout from bottom to top. All root nodes are placed at the bottom of the diagram.
RightToLeft Aligns the layout from right to left. All root nodes are placed at the right of the diagram.

The orientation property can be customized to match specific design requirements or cultural reading patterns.

NOTE

The default orientation in diagram is TopToBottom.

The following code demonstrates how to set the initial orientation for the layout and change it dynamically at runtime:

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

Diagram.Inject(DataBinding, HierarchicalTree);


@Component({
  imports: [DiagramModule],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: "app-container",
  template: `
  <label for="orientation">Orientation : </label>
  <select id="orientation" (change)="onOrientationChange($event)">
      <option value="TopToBottom">TopToBottom</option>
      <option value="BottomToTop">BottomToTop</option>
      <option value="LeftToRight">LeftToRight</option>
      <option value="RightToLeft">RightToLeft</option>
    </select>

  <ejs-diagram #diagram id="diagram" width="100%" height="550px" [getNodeDefaults]="getNodeDefaults" [getConnectorDefaults]="getConnectorDefaults" 
  [layout]="layout" [dataSourceSettings]="dataSourceSettings"> </ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;

  //Initialize data source
  public data: object[] = [
    { Name: "Steve-Ceo" },
    { Name: "Kevin-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "Peter-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "John-Manager", ReportingPerson: "Peter-Manager" },
    { Name: "Mary-CSE", ReportingPerson: "Peter-Manager" },
    { Name: "Jim-CSE", ReportingPerson: "Kevin-Manager" },
    { Name: "Martin-CSE ", ReportingPerson: "Kevin-Manager" },
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel): NodeModel {
    node.annotations = [{ content: (node.data as { Name: 'string' }).Name }];
    node.width = 100; node.height = 40;
    return node;
  }

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

  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
      //Sets layout alignment
      horizontalAlignment: 'Left',
      verticalAlignment: 'Top',
      orientation: 'TopToBottom',
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

  onOrientationChange(event: Event) {
    let orientation = (event.target as HTMLSelectElement).value as LayoutOrientation;
    this.diagram.layout.orientation = orientation;
    this.diagram.dataBind();
  }

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

Exclude from layout

In certain scenarios, specific nodes may need manual positioning rather than automatic arrangement by the layout algorithm. These nodes can be excluded from layout calculations by setting the excludeFromLayout property to true.

This feature is useful for annotation nodes, floating panels, or special elements that require fixed positioning regardless of the overall layout structure.

The following code example demonstrates how to exclude a node from the layout and position it manually:

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

Diagram.Inject(DataBinding, HierarchicalTree);

@Component({
  imports: [DiagramModule],

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

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout: LayoutModel = {};
  public dataSourceSettings: DataSourceModel = {};

  //Initialize data source
  public data: object[] = [
    {
      Name: 'Steve-Ceo',
    },
    {
      Name: 'Kevin-Manager',
      ReportingPerson: 'Steve-Ceo',
    },
    {
      Name: 'Robert',
      ReportingPerson: 'Steve-Ceo',
    },
    {
      Name: 'Peter-Manager',
      ReportingPerson: 'Steve-Ceo',
    },
    {
      Name: 'John- Manager',
      ReportingPerson: 'Peter-Manager',
    },
    {
      Name: 'Mary-CSE ',
      ReportingPerson: 'Peter-Manager',
    },
    {
      Name: 'Jim-CSE ',
      ReportingPerson: 'Kevin-Manager',
    },
    {
      Name: 'Martin-CSE',
      ReportingPerson: 'Kevin-Manager',
    },
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel): NodeModel {
    node.width = 100;
    node.height = 50;
    node.annotations = [{ content: (node.data as { Name: 'string'; }).Name }];

    if ((node.data as any).Name === 'Robert') {
      //Excludes node from layout
      node.excludeFromLayout = true;
      node.offsetX = 150;
      node.offsetY = 75;
    }

    return node;
  }

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

  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

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

Fixed node

Layout arrangement can be anchored relative to a specific node by setting the fixedNode property. This ensures that the specified node maintains its position while other nodes are arranged around it.

This feature is particularly beneficial during expand/collapse operations, where maintaining the position of the interacted node provides better user experience and visual stability.

import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramComponent, Diagram, NodeModel, ConnectorModel, SnapSettingsModel, LayoutModel,
  DataSourceModel, DiagramModule, DataBindingService, DataBinding, FlowchartLayout, FlowchartLayoutService } from '@syncfusion/ej2-angular-diagrams';
import { DataManager, Query } from '@syncfusion/ej2-data';
Diagram.Inject(DataBinding, FlowchartLayout);

@Component({
  imports: [DiagramModule],

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

export class AppComponent {
  @ViewChild("diagram")
  public diagram?: DiagramComponent;
  public snapSettings?: SnapSettingsModel;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;

  //Initializes data source
  public data: object[] = [
    {
      Name: "Steve-Ceo",
    },
    {
      Name: "Kevin-Manager",
      ReportingPerson: "Steve-Ceo"
    },
    {
      Name: "Peter-Manager",
      ReportingPerson: "Steve-Ceo"
    },
    {
      Name: "John- Manager",
      ReportingPerson: "Peter-Manager"
    },
    {
      Name: "Mary-CSE ",
      ReportingPerson: "Peter-Manager"
    },
    {
      Name: "Jim-CSE ",
      ReportingPerson: "Kevin-Manager"
    },
    {
      Name: "Martin-CSE",
      ReportingPerson: "Kevin-Manager"
    }
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel, diagram: Diagram): NodeModel {
    node.annotations = [{ content: (node.data as { Name: 'string' }).Name }];
    node.width = 100; node.height = 40;
    return node;
  }

  //Sets the default properties for all the connectors
  public getConnectorDefaults(connector: ConnectorModel, diagram: Diagram): ConnectorModel {
    connector.type = 'Orthogonal';
    return connector;
  }

  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      type: 'Flowchart',
      fixedNode:'Steve-Ceo'
    }
    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }
  }
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

Expand and collapse

The diagram supports expanding and collapsing subtrees within layouts. The node’s isExpanded property controls the visibility of child nodes, allowing users to focus on specific portions of large hierarchical structures.

This functionality is essential for managing complex organizational charts, decision trees, and other hierarchical data where progressive disclosure improves usability.

The following code example shows how to expand/collapse the children of a node:

import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramComponent, Diagram, NodeModel, ConnectorModel, LayoutModel, DataSourceModel, DecoratorModel, DiagramModule, DataBindingService, SnapSettingsModel,
  SelectorModel, SelectorConstraints, TreeInfo, DataBinding, HierarchicalTree, HierarchicalTreeService } from '@syncfusion/ej2-angular-diagrams';
import { DataManager, Query } from '@syncfusion/ej2-data';

Diagram.Inject(DataBinding, HierarchicalTree);

@Component({
  imports: [
    DiagramModule
  ],

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

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout: LayoutModel = {};
  public dataSourceSettings: DataSourceModel = {};
  public snapSettings: SnapSettingsModel = {};
  public selectedItems: SelectorModel = {};

  //Initialize data source
  public data: object[] = [
    {
      'Id': 'parent1',
      'Name': 'Maria ',
      'Designation': 'Managing Director',
      'RatingColor': '#C34444'
    },
    {
      'Id': 'parent',
      'Name': ' sam',
      'Designation': 'Managing Director',
      'ReportingPerson': 'parent1',
      'RatingColor': '#C34444'
    },
    {
      'Id': 'parent3',
      'Name': ' sam geo',
      'Designation': 'Managing Director',
      'ReportingPerson': 'parent1',
      'RatingColor': '#C34444'
    },
    {
      'Id': '80',
      'Name': ' david',
      'Designation': 'Managing Director',
      'ReportingPerson': 'parent3',
      'RatingColor': '#C34444'
    },
    {
      'Id': '82',
      'Name': ' pirlo',
      'Designation': 'Managing Director',
      'ReportingPerson': 'parent',
      'RatingColor': '#C34444'
    }
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel): NodeModel {
    node.expandIcon = {
      height: 15,
      width: 15,
      shape: "Plus",
      fill: 'lightgray',
      offset: { x: .5, y: .85 }
    }
    node.collapseIcon = {
      height: 15,
      width: 15,
      shape: "Minus",
      offset: { x: .5, y: .85 },
    }
    node.height = 50; node.width = 50; 
    return node;
  }

  //Sets the default properties for all the connectors
  public getConnectorDefaults(connector: ConnectorModel): ConnectorModel {
    connector.type = 'Orthogonal';
    (connector.targetDecorator as DecoratorModel).shape = "None";
    return connector;
  }

  ngOnInit(): void {

    this.selectedItems = { constraints: ~SelectorConstraints.ResizeAll }
    this.snapSettings = { constraints: 0 }
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'OrganizationalChart',
      // define the getLayoutInfo
      getLayoutInfo: (tree: TreeInfo | any) => {
        if (!tree.hasSubTree) {
          tree.orientation = 'vertical';
          tree.type = 'alternate';
        }
      }
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Id',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

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

For more details about customizing the expand and collapse icons refer expand Collapse

Layout animation

Expand and collapse operations can be animated by applying transitions during layout changes. The enableAnimation property controls this behavior, enhancing the visual experience during structural changes.

Animation provides visual continuity and helps users track changes in the layout structure. By default, enableAnimation is set to true.

The following example demonstrates how layout animation enhances the visual experience during expand and collapse operations:

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

Diagram.Inject(DataBinding, HierarchicalTree, LayoutAnimation);


@Component({
  imports: [DiagramModule],

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

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;

  //Initialize data source
  public data: object[] = [
    { Name: "Steve-Ceo" },
    { Name: "Kevin-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "Robert-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "Peter-Manager", ReportingPerson: "Kevin-Manager" },
    { Name: "Hary-Manager", ReportingPerson: "Robert-Manager" },
    { Name: "John-Manager", ReportingPerson: "Hary-Manager" },
    { Name: "Mary-CSE", ReportingPerson: "Hary-Manager" },
    { Name: "John-Manager", ReportingPerson: "Peter-Manager" },
    { Name: "Mary-CSE ", ReportingPerson: "Peter-Manager" },
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel): NodeModel {
    node.annotations = [{ content: (node.data as { Name: 'string' }).Name }];
    node.expandIcon = { shape: "Minus" };
      node.collapseIcon = { shape: "Plus" };
      node.width = 100;
    node.height = 40;
    return node;
  }

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

  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
      enableAnimation: true,
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

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

NOTE

To enable layout animation, inject the LayoutAnimation module in the diagram.

Parent - child relation with dropped nodes from symbol palette

Layouts can be dynamically extended by creating parent-child relationships between existing nodes and items dropped from the symbol palette. The drop event provides the mechanism to establish these connections programmatically.

This functionality enables interactive diagram building, where users can expand existing structures by dragging and dropping new elements from a predefined set of symbols.

The following code example creates parent-child relationships between source and target nodes in the drop event:

import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramComponent, Diagram, NodeModel, ConnectorModel, LayoutModel, DataSourceModel, HierarchicalTree, PaletteModel, NodeConstraints,
  IDropEventArgs, randomId, DiagramModule, HierarchicalTreeService, DataBindingService, DataBinding, SymbolPaletteModule } from '@syncfusion/ej2-angular-diagrams';
import { DataManager, Query } from '@syncfusion/ej2-data';
import { ExpandMode } from '@syncfusion/ej2-navigations';

Diagram.Inject(DataBinding, HierarchicalTree);


@Component({
  imports: [DiagramModule, SymbolPaletteModule],

  providers: [HierarchicalTreeService, DataBindingService],
  standalone: true,
  selector: "app-container",
  template: `<div style="height: 600px; width: 150px; float: left">
  <ejs-symbolpalette id="symbolpalette" width="100%" height="600px" [expandMode]="expandMode" [palettes]="palettes" [symbolHeight]=50 [symbolWidth]=100>
  </ejs-symbolpalette></div>
  <ejs-diagram #diagram id="diagram" width="80%" height="600px" [getNodeDefaults]="getNodeDefaults" [getConnectorDefaults]="getConnectorDefaults" 
  [layout]="layout" [dataSourceSettings]="dataSourceSettings" (drop)="drop($event)"> </ejs-diagram>`,
  encapsulation: ViewEncapsulation.None
})

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;
  public expandMode: ExpandMode = 'Multiple';

  //Initialize data source
  public data: object[] = [
    { Name: "Steve-Ceo" },
    { Name: "Kevin-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "Peter-Manager", ReportingPerson: "Steve-Ceo" },
    { Name: "John-Manager", ReportingPerson: "Peter-Manager" },
    { Name: "Mary-CSE", ReportingPerson: "Peter-Manager" },
    { Name: "Jim-CSE", ReportingPerson: "Kevin-Manager" },
    { Name: "Martin-CSE ", ReportingPerson: "Kevin-Manager" },
  ];

  //Initialize shapes for symbol palette
  public palettes: PaletteModel[] = [
    {
      title: 'Basic shapes',
      id: 'basicShapes',
      symbols: [
        {
          id: 'Node',
          width: 100,
          height: 50,
          data: { Name: 'New Node' },
        },
      ],
    },
  ];

  //Sets the default properties for all the Nodes
  public getNodeDefaults(node: NodeModel): NodeModel {
    node.width = 100; node.height = 40;
    node.constraints = NodeConstraints.Default | NodeConstraints.AllowDrop;
    return node;
  }

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

  // Handle drop event that create a connection between the source and target item
  public drop(args: IDropEventArgs) {
    setTimeout(() => {
      //Argument element is used to get the dropped node.
      let node: NodeModel = args.element as NodeModel;
      let bottomNode: NodeModel = args.target as NodeModel;
      //Gets the connector that connected to dropped node
      let edges: string[] = this.diagram.getEdges(node);
      if (edges && edges.length > 0) {
        let connector: ConnectorModel = this.diagram.getObject(edges[0]);
        //Argument target is used to get the hovered node
        connector.sourceID = (args.target as NodeModel).id;
        this.diagram.dataBind();
      } else {
        let newCon: ConnectorModel = {
          id: 'newcon' + randomId(),
          sourceID: (args.target as NodeModel).id,
          targetID: (args.element as NodeModel).id,
        };
        if (newCon.sourceID === undefined) {
          newCon.sourceID = this.diagram.nodes[0].id;
        }
        this.diagram.dataBind();
        this.diagram.add(newCon);
      }
      this.diagram.doLayout();
    }, 100);
  }

  ngOnInit(): void {

    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

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

Layout drag and drop

setNodeTemplate

The setNodeTemplate function enables comprehensive customization of node visual representation and behavior within diagrams. This function is invoked during node initialization, allowing developers to define styling, properties, and data binding for each node.

The function typically accepts container elements such as StackPanel or Grid to organize visual components within nodes. The StackPanel can contain various elements including ImageElement, PathElement, NativeElement, DiagramElement, and HtmlElement.

Additional customization options include cornerRadius for rounded appearances, horizontalAlignment and verticalAlignment for positioning control, and orientation for child element arrangement.

import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramComponent, Diagram, NodeModel, ConnectorModel, LayoutModel, DataSourceModel, DecoratorModel, DiagramModule, HierarchicalTreeService,
  DataBindingService, DataBinding, ImageElement, StackPanel, TextElement, Container, HierarchicalTree } from '@syncfusion/ej2-angular-diagrams';
import { DataManager, Query } from '@syncfusion/ej2-data';

Diagram.Inject(DataBinding, HierarchicalTree);


@Component({
  imports: [DiagramModule],

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

export class AppComponent {
  @ViewChild("diagram")
  public diagram!: DiagramComponent;
  public items?: DataManager;
  public layout?: LayoutModel;
  public dataSourceSettings?: DataSourceModel;

  //Initialize data source
  public data: object[] = [
    { Name: "Steve-Ceo" },
    { Name: "Kevin-Manager", ReportingPerson: "Steve-Ceo", color: 'darkcyan' },
    { Name: "Peter-Manager", ReportingPerson: "Steve-Ceo", color: 'white' },
    { Name: "John-Manager", ReportingPerson: "Peter-Manager", color: 'darkcyan' },
    { Name: "Mary-CSE", ReportingPerson: "Peter-Manager", color: 'white' },
    { Name: "Jim-CSE", ReportingPerson: "Kevin-Manager", color: 'darkcyan' },
    { Name: "Martin-CSE ", ReportingPerson: "Kevin-Manager", color: 'white' },
  ];

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

  //Sets the default properties for all the connectors
  public getConnectorDefaults(connector: ConnectorModel): ConnectorModel {
    connector.type = 'Orthogonal';
    (connector.targetDecorator as DecoratorModel).shape = 'None';
    return connector;
  }

  //Function to add the Template of the Node.
  public setNodeTemplate(node: NodeModel): Container {

    // Create an outer StackPanel as container to contain image and text elements
    let container = new StackPanel();
    container.width = 200;
    container.height = 60;
    container.cornerRadius = 10;
    container.style.fill = 'skyblue';
    container.horizontalAlignment = 'Left';
    container.orientation = 'Horizontal';
    container.id = (node.data as any).Name + '_StackContainter';

    // Create an inner image element to displaying image
    let innerContent = new ImageElement();
    innerContent.id = (node.data as any).Name + '_innerContent';
    innerContent.width = 40;
    innerContent.height = 40;
    innerContent.margin.left = 20;
    innerContent.style.fill = 'lightgrey';

    // Create a inner text element for displaying employee details
    let text = new TextElement();
    text.content = 'Name: ' + (node.data as any).Name;
    text.margin = { left: 10, top: 5 };
    text.id = (node.data as any).Name + '_textContent';
    text.style.fill = 'green';
    text.style.color = 'white';
    if ((node.data as any).Name === 'Steve-Ceo') {
      text.style.fill = 'black';
      text.style.color = 'white';
    }

    // Add inner image and text element to the outer StackPanel
    container.children = [innerContent, text];
    return container;
  }


  ngOnInit(): void {
    this.items = new DataManager(this.data as JSON[], new Query().take(7));

    //Uses layout to auto-arrange nodes on the Diagram page
    this.layout = {
      //Sets layout type
      type: 'HierarchicalTree',
    }

    //Configures data source for Diagram
    this.dataSourceSettings = {
      id: 'Name',
      parentId: 'ReportingPerson',
      dataSource: this.items
    }

  }

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

Refresh layout

The diagram supports refreshing layouts at runtime to reflect structural or data changes. The doLayout method recalculates and redraws the entire layout based on current data and configuration.

This functionality is essential when nodes are added, removed, or modified programmatically, ensuring the layout remains consistent with the updated structure.

//To refresh layout
this.diagram.doLayout();