# diagram-mind-map

> Horizontal mind-map diagram with custom user handles, draggable branches, and add/delete tools.

**Framework:** angular  **Component:** Diagram  **Variant:** mind-map

## Get this item

**If you are an agent, fetch the JSON.** Source is inlined, so one request is enough and no tooling is required:

```
GET https://ai.syncfusion.com/r/angular/diagram-mind-map.json
```

**Install Package(s)**

```bash
npm install @syncfusion/ej2-angular-diagrams
```

**Notes**

- Syncfusion release: 2026 Volume 2 (v34.1.29)
- The Syncfusion package is licensed. The composition in this file is source you own and edit. See https://ai.syncfusion.com/licensing.md

## Source files

### src/components/diagram-mind-map/mind-map.component.ts

```typescript
import { Component, ViewEncapsulation, ViewChild } from '@angular/core';
import { DiagramComponent, Diagram, ConnectorModel, Connector, Node, HierarchicalTree, DataBinding, PointPortModel, randomId, SnapSettingsModel, PortVisibility, MindMap, UserHandleModel, SelectorConstraints, ToolBase, MouseEventArgs,DiagramConstraints, SnapConstraints, NodeModel, ISelectionChangeEventArgs, DiagramTools, NodeConstraints, SelectorModel, MarginModel, VerticalAlignment, HorizontalAlignment, Side, TextModel, ConnectorConstraints, PointPort, DiagramModule, ScrollSettingsModel, UndoRedo } from '@syncfusion/ej2-angular-diagrams';

import { DataManager } from '@syncfusion/ej2-data';
import { mindMap } from '../diagram-data';
// Inject required diagram features
Diagram.Inject(DataBinding, MindMap, HierarchicalTree);

/**
 *  Sample component for Mind Map Tree using Syncfusion Diagram.
 */
@Component({
    selector: 'control-content',
    templateUrl: 'mind-map.html',
    encapsulation: ViewEncapsulation.None,
    standalone: true,
    imports: [ DiagramModule]
})
export class MindMapDiagramComponent {
  @ViewChild('diagram') public diagram!: DiagramComponent;
  // Diagram tool settings
  public tool: DiagramTools = DiagramTools.SingleSelect |
    DiagramTools.MultipleSelect;
  public scrollSettings?: ScrollSettingsModel;
  public constraints!: DiagramConstraints;
  ngOnInit(): void {
    this.constraints= DiagramConstraints.Default &~(DiagramConstraints.UndoRedo)
      // Defines the pageSettings for the diagram
      this.scrollSettings = {
          //Sets the scroll limit
          padding: { right: 50, left: 50 }
      }
  }
  // Data binding setup
  public items: DataManager = new DataManager(mindMap);
  public data: Object = { id: 'id', parentId: 'parentId', dataSource: this.items, root: '1' };
  // Layout configuration
  public layout: Object = {
    type: 'MindMap',orientation:'Horizontal', horizontalSpacing: 50,
    getBranch: (node: NodeModel, nodes: NodeModel[]) => {
      return ((node as Node).data as EmployeeInfo).branch;
    }
  };
  // Snap settings
  public snapSettings: SnapSettingsModel = { constraints: SnapConstraints.None };
  // Lifecycle hook to create the diagram
  public create(args: Object): void {
    this.diagram.fitToPage();
  }

  public getNodeDefaults: Function = this.nodeDefaults.bind(this);
  public getConnDefaults: Function = this.getConnectorDefaults.bind(this);
  public getCustomTool: Function = this.getTool.bind(this);
   // Tool function to retrieve specific tool based on action
  public getTool(action: string): ToolBase {
    let tool: ToolBase = null!;
    if (action === 'leftHandle') {
      let leftTool: LeftExtendTool = new LeftExtendTool(this.diagram.commandHandler);
      leftTool.diagram = this.diagram;
      return leftTool;
    } else if (action === 'rightHandle') {
      let rightTool: RightExtendTool = new RightExtendTool(this.diagram.commandHandler);
      rightTool.diagram = this.diagram;
      return rightTool;
    } else if (action === 'delete') {
      let deleteTool: DeleteClick = new DeleteClick(this.diagram.commandHandler);
      deleteTool.diagram = this.diagram;
      return deleteTool;
    }
    return tool!;
  }
  // Function to define default node properties
  private nodeDefaults(obj: NodeModel): NodeModel {
    obj.constraints = NodeConstraints.Default & ~NodeConstraints.Drag;
    let empInfo: EmployeeInfo = obj.data as EmployeeInfo;
    if (empInfo.branch === 'Root' || empInfo.branch === 'Left' || empInfo.branch === 'Right') {
      obj.shape = { type: 'Basic', shape: 'Ellipse' };
      obj.borderColor = 'black';
      obj.style = { fill: empInfo.branch === 'Root' ? '#E74C3C' : '#F39C12', strokeColor: 'none', strokeWidth: 2 };
      obj.annotations = [
        { content: empInfo.Label, margin: { left: 10, right: 10, top: 10, bottom: 10 }, style: { color: 'white' } }
      ];
    } else {
      let color: string = empInfo.branch === 'subRight' ? '#8E44AD' : '#3498DB';
      obj.shape = { type: 'Basic', shape: 'Rectangle' };
      obj.style = { fill: color, strokeWidth: 0 };
      obj.minWidth = 100;
      obj.height = 4;
      obj.annotations = [{ content: empInfo.Label, offset: { x: 0.5, y: 0 }, verticalAlignment: 'Bottom' }];
      (obj.shape as TextModel).margin = { left: 0, right: 0, top: 0, bottom: 0 };
    }
    // Initialize ports for the node
    let port: PointPortModel[] = this.getPort();
    for (let i: number = 0; i < port.length; i++) {
      obj.ports!.push(new PointPort(obj, 'ports', port[i], true));
    }
    return obj;
  }

  //sets connector default value
  private getConnectorDefaults(connector: ConnectorModel, diagram: Diagram): ConnectorModel {
    let sourceNode: Node = diagram.getObject(connector.sourceID!) as Node;
    let targetNode: Node = diagram.getObject(connector.targetID!) as Node;
    let empInfo: EmployeeInfo = targetNode.data as EmployeeInfo;
    connector.type = 'Bezier';
    connector.targetDecorator = { shape: 'None' };
    connector.constraints! &= ~ConnectorConstraints.Select;
    if (empInfo.branch === 'Right' || empInfo.branch === 'subRight') {
      connector.sourcePortID = sourceNode.ports[0].id;
      connector.targetPortID = targetNode.ports[1].id;
      connector.style = { strokeWidth: 5, strokeColor: '#8E44AD' };
    } else if (empInfo.branch === 'Left' || empInfo.branch === 'subLeft') {
      connector.sourcePortID = sourceNode.ports[1].id;
      connector.targetPortID = targetNode.ports[0].id;
      connector.style = { strokeWidth: 5, strokeColor: '#3498DB' };
    }
    return connector;
  }
  // Initializes user handle
  public handle: UserHandleModel[] = [
    {
      name: 'leftHandle', visible: true, backgroundColor: 'black', offset: 1, side: 'Left', pathColor: 'white',
      pathData: 'M11.924,6.202 L4.633,6.202 L4.633,9.266 L0,4.633 L4.632,0 L4.632,3.551 L11.923,3.551 L11.923,6.202Z',
      margin: { top: 0, bottom: 0, left: 0, right: 10 }, horizontalAlignment: 'Left', verticalAlignment: 'Top'
    },
    {
      name: 'delete', side: 'Top', horizontalAlignment: 'Center', verticalAlignment: 'Center',
      pathData:
        'M 7.04 22.13 L 92.95 22.13 L 92.95 88.8 C 92.95 91.92 91.55 94.58 88.76' +
        '96.74 C 85.97 98.91 82.55 100 78.52 100 L 21.48 100 C 17.45 100 14.03 98.91 11.24 96.74 C 8.45 94.58 7.04' +
        '91.92 7.04 88.8 z M 32.22 0 L 67.78 0 L 75.17 5.47 L 100 5.47 L 100 16.67 L 0 16.67 L 0 5.47 L 24.83 5.47 z',
      margin: { top: 0, bottom: 10, left: 0, right: 0 }, offset: 0.5, pathColor: 'white'
    },
    {
      name: 'rightHandle', offset: 1, horizontalAlignment: 'Right', verticalAlignment: 'Top', pathColor: 'white',
      pathData: 'M0,3.063 L7.292,3.063 L7.292,0 L11.924,4.633 L7.292,9.266 L7.292,5.714 L0.001,5.714 L0.001,3.063Z',
      side: 'Right', backgroundColor: 'black', margin: { top: 0, bottom: 0, left: 10, right: 0 }
    }
  ];

  public selectedItems: SelectorModel = { constraints: SelectorConstraints.UserHandle, userHandles: this.handle };
  public selectionChange(arg: ISelectionChangeEventArgs): void {
    if (arg.state === 'Changing') {
      if (arg.newValue[0] instanceof Node) {
        let empInfo: EmployeeInfo = ((arg.newValue[0] as Node).data as EmployeeInfo);
        for (let handle of this.diagram.selectedItems.userHandles!) {
          handle.visible = true;
        }
        if (empInfo.branch === 'Left' || empInfo.branch === 'subLeft') {
          this.hideUserHandle('leftHandle');
          this.changeUserHandlePosition('leftHandle');
        } else if (empInfo.branch === 'Right' || empInfo.branch === 'subRight') {
          this.hideUserHandle('rightHandle');
          this.changeUserHandlePosition('rightHandle');
        } else if (empInfo.branch === 'Root') {
          this.hideUserHandle('delete');
        }
      } else {
        this.hideUserHandle('leftHandle');
        this.hideUserHandle('rightHandle');
        this.hideUserHandle('delete');
      }
    }
  }

  //hide the require userhandle.
  public hideUserHandle(name: string): void {
    for (let handle of this.diagram.selectedItems.userHandles!) {
      if (handle.name === name) {
        handle.visible = false;
      }
    }
  }

  //Change the Position of the UserHandle.
  private changeUserHandlePosition(change: string): void {
    for (let handle of this.diagram.selectedItems.userHandles!) {
      if (handle.name === 'delete' && change === 'leftHandle') {
        this.applyHandle(handle, 'Left', 1, { top: 0, bottom: 0, left: 0, right: 10 }, 'Left', 'Top');
      } else if (handle.name === 'delete' && change === 'rightHandle') {
        this.applyHandle(handle, 'Right', 1, { top: 0, bottom: 0, left: 10, right: 0 }, 'Right', 'Top');
      }
    }
  }
  //set the value for UserHandle element.
  private applyHandle(
    handle: UserHandleModel, side: Side, offset: number, margin: MarginModel,
    HorizontalAlignment: HorizontalAlignment, VerticalAlignment: VerticalAlignment): void {
    handle.side = side;
    handle.offset = offset;
    handle.margin = margin;
    handle.horizontalAlignment = HorizontalAlignment;
    handle.verticalAlignment = VerticalAlignment;
  }
  //Function to create ports for nodes
  private getPort(): PointPortModel[] {
    let port: PointPortModel[] = [
      { id: 'port1', offset: { x: 0, y: 0.5 }, visibility: PortVisibility.Hidden, style: { fill: 'black' } },
      { id: 'port2', offset: { x: 1, y: 0.5 }, visibility: PortVisibility.Hidden, style: { fill: 'black' } }
    ];
    return port;
  }
}
// Function to add a new node
function addNode(): NodeModel {
  let obj: NodeModel = {};
  obj.id = randomId();
  obj.data = {};
  (obj.data as EmployeeInfo).Label = 'Node';
  return obj;
}
// Function to add a new connector
function addConnector(source: NodeModel, target: NodeModel): ConnectorModel {
  let connector: ConnectorModel = {};
  connector.id = randomId();
  connector.sourceID = source.id;
  connector.targetID = target.id;
  return connector;
}
// Class for handling left extension tool
class LeftExtendTool extends ToolBase {
  public diagram!: Diagram;
  //mouseDown event
  public override mouseDown(args: MouseEventArgs): void {
    super.mouseDown(args);
    this.inAction = true;
  }
  //mouseUp event
  public override mouseUp(args: MouseEventArgs): void {
    if (this.inAction) {
      let selectedObject: any = this.commandHandler.getSelectedObject();
      if (selectedObject[0]) {
        if (selectedObject[0] instanceof Node) {
          let node: NodeModel = addNode();
          let empInfo: EmployeeInfo = (selectedObject[0].data as EmployeeInfo);
          if (empInfo.branch === 'Root') {
            (node.data as EmployeeInfo).branch = 'Right';
          } else if (empInfo.branch === 'Right' || empInfo.branch === 'subRight') {
            (node.data as EmployeeInfo).branch = 'subRight';
          }
          let connector: ConnectorModel = addConnector(selectedObject[0], node);
          this.diagram.clearSelection();
          let newNode: Node = this.diagram.add(node) as Node;
          this.diagram.add(connector);
          this.diagram.doLayout();
          this.diagram.bringIntoView(newNode.wrapper.bounds);
          this.diagram.startTextEdit(newNode);
        }
      }
    }
  }
}
// Class for handling right extension tool
class RightExtendTool extends ToolBase {
  public diagram!: Diagram;
  //mouseDown event
  public override mouseDown(args: MouseEventArgs): void {
    super.mouseDown(args);
    this.inAction = true;
  }
  //mouseUp event
  public override mouseUp(args: MouseEventArgs): void {
    if (this.inAction) {
      let selectedObject: any = this.commandHandler.getSelectedObject();
      if (selectedObject[0]) {
        if (selectedObject[0] instanceof Node) {
          let node: NodeModel = addNode();
          let empInfo: EmployeeInfo = (selectedObject[0].data as EmployeeInfo);
          if (empInfo.branch === 'Root') {
            (node.data as EmployeeInfo).branch = 'Left';
          } else if (empInfo.branch === 'Left' || empInfo.branch === 'subLeft') {
            (node.data as EmployeeInfo).branch = 'subLeft';
          }
          let connector: ConnectorModel = addConnector(selectedObject[0], node);
          this.diagram.clearSelection();
          let newNode: Node = this.diagram.add(node) as Node;
          this.diagram.add(connector);
          this.diagram.doLayout();
          this.diagram.bringIntoView(newNode.wrapper.bounds);
          this.diagram.startTextEdit(newNode);
        }
      }
    }
  }
}
// Class for handling delete tool
class DeleteClick extends ToolBase {
  public diagram!: Diagram;
  //mouseDown event
  public override mouseDown(args: MouseEventArgs): void {
    super.mouseDown(args);
    this.inAction = true;
  }
  //mouseup event
  public override mouseUp(args: MouseEventArgs): void {
    if (this.inAction) {
      let selectedObject: any = this.commandHandler.getSelectedObject();
      if (selectedObject[0]) {
        if (selectedObject[0] instanceof Node) {
          let node: Node = selectedObject[0] as Node;
          this.removeSubChild(node);
        }
        this.diagram.doLayout();
      }
    }
  }
  //Remove the subchild Elements
  private removeSubChild(node: Node): void {
    for (let i: number = node.outEdges.length - 1; i >= 0; i--) {
      let connector: Connector = this.diagram.getObject(node.outEdges[i]) as Connector;
      let childNode: Node = this.diagram.getObject(connector.targetID) as Node;
      if (childNode.outEdges.length > 0) {
        this.removeSubChild(childNode);
      } else {
        this.diagram.remove(childNode);
      }
    }
    this.diagram.remove(node);
  }
}
// Interface for defining employee information
export interface EmployeeInfo {
  branch: string;
  color: string;
  Left: string;
  Right: string;
  Root: string;
  Label: string;
}

```

### src/components/diagram-mind-map/mind-map.html

```html

<div class="col-lg-8 control-section" style="width: 100%;">
    <div class="content-wrapper">
        <ejs-diagram #diagram id="diagram" width="100%" height="700px" [getNodeDefaults]=getNodeDefaults [snapSettings]=snapSettings
            [selectedItems]="selectedItems" [getCustomTool]='getCustomTool' (selectionChange)="selectionChange($event)" (created)="create($event)"
            [tool]=tool [layout]='layout' [dataSourceSettings]='data' [getConnectorDefaults]='getConnDefaults' [scrollSettings]="scrollSettings" [constraints]="constraints">
        </ejs-diagram>
    </div>
</div>

```
