# diagram-mind-map

> Mind-map diagram with custom user handles to add, branch, and delete nodes.

**Framework:** vue  **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/vue/diagram-mind-map.json
```

**Install Package(s)**

```bash
npm install @syncfusion/ej2-data @syncfusion/ej2-vue-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/Diagram.vue

```vue
<template>
<div class="control-section">
    <div class="diagram-control">
        <ejs-diagram style='display:block' ref='diagramObj' id="diagram" :width='width' :height='height' :constraints= 'constraints' :snapSettings='snapSettings' :tool='tool' :layout='layout' :selectionChange='selectionChange' :getNodeDefaults='getNodeDefaults' :selectedItems='selectedItems' :getConnectorDefaults='getConnectorDefaults' :dataSourceSettings='dataSourceSettings'
        :getCustomTool='getCustomTool'></ejs-diagram>
        <input id="palette" style="visibility: hidden;position: absolute" type="color" name="favcolor" value="#000000">
    </div>

</div>
</template>

<style scoped>
</style>

<script>
import {
  DiagramComponent,
  Diagram,
  Node,
  ConnectorConstraints,
  DiagramConstraints,
  DataBinding,
  PointPort,
  randomId,
  PortVisibility,
  SelectorConstraints,
  ToolBase,
  SnapConstraints,
  MindMap,
  HierarchicalTree,
  DiagramTools,
  NodeConstraints,
  
} from "@syncfusion/ej2-vue-diagrams";
import { DataManager, Query } from "@syncfusion/ej2-data";

const mindMap = [
    { id: 1, Label: "Creativity", fill: "red", branch: "Root" },
    { id: 3, Label: "Brainstorming", parentId: 1, branch: "Right", fill: "red" },
    { id: 4, Label: "Complementing", parentId: 1, branch: "Left", fill: "red" },
    { id: 22, Label: "Sessions", parentId: 3, branch: "subRight", fill: "red" },
    { id: 23, Label: "Generate", parentId: 3, branch: "subRight", fill: "red" },
    { id: 25, Label: "Local", parentId: 22, branch: "subRight" },
    { id: 26, Label: "Remote", parentId: 22, branch: "subRight" },
    { id: 27, Label: "Individual", parentId: 22, branch: "subRight" },
    { id: 28, Label: "Teams", parentId: 22, branch: "subRight" },
    { id: 29, Label: "Ideas", parentId: 23, branch: "subRight" },
    { id: 30, Label: "Engagement", parentId: 23, branch: "subRight" },
    { id: 31, Label: "Product", parentId: 29, branch: "subRight" },
    { id: 32, Label: "Service", parentId: 29, branch: "subRight" },
    { id: 33, Label: "Business Direction", parentId: 29, branch: "subRight" },
    { id: 34, Label: "Empowering", parentId: 30, branch: "subRight" },
    { id: 35, Label: "Ownership", parentId: 30, branch: "subRight" },
    { id: 50, Label: "Information", parentId: 4, branch: "subLeft" },
    { id: 51, Label: "Expectations", parentId: 4, branch: "subLeft" },
    { id: 53, Label: "Competetors", parentId: 50, branch: "subLeft" },
    { id: 54, Label: "Products", parentId: 50, branch: "subLeft" },
    { id: 55, Label: "Features", parentId: 50, branch: "subLeft" },
    { id: 56, Label: "Other Data", parentId: 50, branch: "subLeft" },
    { id: 59, Label: "Organization", parentId: 51, branch: "subLeft" },
    { id: 60, Label: "Customer", parentId: 51, branch: "subLeft" },
    { id: 61, Label: "Staff", parentId: 51, branch: "subLeft" },
    { id: 62, Label: "Stakeholders", parentId: 51, branch: "subLeft" }
];

// Variables for diagram instance and data manager
let diagramInstance;
let items = new DataManager(
  mindMap,
  new Query().take(7)
);
export default {
  components: {
    'ejs-diagram': DiagramComponent
  },
  data: function() {
    return {
      width: "100%",
      height: "550px",
      constraints: DiagramConstraints.Default & ~DiagramConstraints.UndoRedo,
      snapSettings: { constraints: SnapConstraints.None },
      tool: DiagramTools.SingleSelect,
      layout: {
        type: "MindMap",
         orientation:"Horizontal",
        getBranch: (node, nodes) => {
          return ((node).data).branch;
        },
        horizontalSpacing: 50
      },
      //Selectionchange event for Node and connector
      selectionChange: (arg) => {
        if (arg.state === "Changing") {
          if (
            arg.newValue[0] instanceof Node &&
            diagramInstance.selectedItems.userHandles
          ) {
            for (let handle of diagramInstance.selectedItems.userHandles) {
              handle.visible = true;
            }
            if (
              ((arg.newValue[0]).data).branch ===
                "Left" ||
              ((arg.newValue[0]).data).branch ===
                "subLeft"
            ) {
              hideUserHandle("leftHandle");
              changeUserHandlePosition("leftHandle");
            } else if (
              ((arg.newValue[0]).data).branch ===
                "Right" ||
              ((arg.newValue[0]).data).branch ===
                "subRight"
            ) {
              hideUserHandle("rightHandle");
              changeUserHandlePosition("rightHandle");
            } else if (
              ((arg.newValue[0]).data).branch === "Root"
            ) {
              hideUserHandle("delete");
            }
          } else {
            hideUserHandle("leftHandle");
            hideUserHandle("rightHandle");
            hideUserHandle("delete");
          }
        }
      },
      selectedItems: {
        constraints: SelectorConstraints.UserHandle,
        userHandles: handle
      },
      dataSourceSettings: {
        id: "id",
        parentId: "parentId",
        dataSource: items,
        root: String(1)
      },
      //sets node default value
      getNodeDefaults: (obj) => {
        obj.constraints = NodeConstraints.Default & ~NodeConstraints.Drag;
        if (
          (obj.data).branch === "Left" ||
          (obj.data).branch === "Right" ||
          (obj.data).branch === "Root"
        ) {
          obj.shape = { type: "Basic", shape: "Ellipse" };
          obj.borderColor = "black"; /* tslint:disable:no-string-literal */
          obj.style = {
            fill:
              (obj.data).branch === "Root"
                ? "#E74C3C"
                : "#F39C12",
            strokeColor: "none",
            strokeWidth: 2
          };
          obj.annotations = [
            {
              content: (obj.data).Label,
              margin: { left: 10, right: 10, top: 10, bottom: 10 },
              style: { color: "white" }
            }
          ];
          let port = getPort();
          for (let i = 0; i < port.length; i++) {
            obj.ports.push(new PointPort(obj, "ports", port[i], true));
          }
        } else {
          let color; /* tslint:disable:no-string-literal */
          if (
            (obj.data).branch === "Right" ||
            (obj.data).branch === "subRight"
          ) {
            color = "#8E44AD";
          } else {
            color = "#3498DB";
          }
          obj.shape = { type: "Basic", shape: "Rectangle" };
          obj.style = { fill: color, strokeWidth: 0 };
          obj.minWidth = 100;
          obj.height = 4;
          let port = getPort();
          for (let i = 0; i < port.length; i++) {
            obj.ports.push(new PointPort(obj, "ports", port[i], true));
          }
          obj.annotations = [
            {
              content: (obj.data).Label,
              offset: { x: 0.5, y: 0 },
              verticalAlignment: "Bottom"
            }
          ];
          (obj.shape).margin = {
            left: 0,
            right: 0,
            top: 0,
            bottom: 0
          };
        }
        return obj;
      },
      //sets connector default value
      getConnectorDefaults: (connector,diagram) => {
        connector.type = "Bezier";
        connector.targetDecorator = { shape: "None" };
        if (connector.sourceID && connector.targetID && connector.constraints) {
          let sourceNode = diagram.getObject(connector.sourceID);
          let targetNode = diagram.getObject(connector.targetID);
          if (
            (targetNode.data).branch === "Right" ||
            (targetNode.data).branch === "subRight"
          ) {
            connector.sourcePortID = sourceNode.ports[0].id;
            connector.targetPortID = targetNode.ports[1].id;
            connector.style = { strokeWidth: 5, strokeColor: "#8E44AD" };
          } else if (
            (targetNode.data).branch === "Left" ||
            (targetNode.data).branch === "subLeft"
          ) {
            connector.sourcePortID = sourceNode.ports[1].id;
            connector.targetPortID = targetNode.ports[0].id;
            connector.style = { strokeWidth: 5, strokeColor: "#3498DB" };
          }
          connector.constraints &= ~ConnectorConstraints.Select;
        }
        return connector;
      },
      getCustomTool: getTool
    };
  },
  provide: {
    diagram: [DataBinding, MindMap, HierarchicalTree]
  },
  mounted: function() {
    diagramInstance = this.$refs.diagramObj.ej2Instances;
    diagramInstance.fitToPage();
  }
}

//Function to create ports for nodes
function getPort() {
  let port = [
    {
      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() {
  let obj = {};
  obj.id = randomId();
  obj.data = {};
  (obj.data).Label = "Node";
  return obj;
}
// Function to add a new connector
function addConnector(source, target) {
  let connector = {};
  connector.id = randomId();
  connector.sourceID = source.id;
  connector.targetID = target.id;
  return connector;
}
// Function to get the tool for user handles
function getTool(action) {
  let tool;
  if (action === "leftHandle") {
    tool = new LeftExtendTool(diagramInstance.commandHandler);
  } else if (action === "rightHandle") {
    tool = new RightExtendTool(diagramInstance.commandHandler);
  } else if (action === "delete") {
    tool = new DeleteClick(diagramInstance.commandHandler);
  }
  return tool;
}

// Class definition for handling left extension tool
class LeftExtendTool extends ToolBase {
   mouseDown(args) {
    super.mouseDown(args);
    this.inAction = true;
  }
   mouseUp(args) {
    if (this.inAction) {
      let selectedObject = this.commandHandler.getSelectedObject();
      if (selectedObject[0]) {
        if (selectedObject[0] instanceof Node) {
          let node = addNode();
          if ((selectedObject[0].data).branch === "Root") {
            (node.data).branch = "Right";
          } else if (
            (selectedObject[0].data).branch === "Right" ||
            (selectedObject[0].data).branch === "subRight"
          ) {
            (node.data).branch = "subRight";
          }
          getTextEditValue(selectedObject[0], node);
        }
      }
    }
  }
}
// Class definition for handling right extension tool
class RightExtendTool extends ToolBase {
  //mouseDown event
   mouseDown(args) {
    super.mouseDown(args);
    this.inAction = true;
  }
  //mouseDown event
   mouseUp(args) {
    if (this.inAction) {
      let selectedObject = this.commandHandler.getSelectedObject();
      if (selectedObject[0]) {
        if (selectedObject[0] instanceof Node) {
          let node = addNode();
          if ((selectedObject[0].data).branch === "Root") {
            (node.data ).branch = "Left";
          } else if (
            (selectedObject[0].data).branch === "Left" ||
            (selectedObject[0].data).branch === "subLeft"
          ) {
            (node.data).branch = "subLeft";
          }
          getTextEditValue(selectedObject[0], node);
        }
      }
    }
  }
}
// Class definition for handling delete tool
class DeleteClick extends ToolBase {
  //mouseDown event
   mouseDown(args) {
    super.mouseDown(args);
    this.inAction = true;
  }
  //mouseup event
   mouseUp(args) {
    if (this.inAction) {
      let selectedObject = this.commandHandler.getSelectedObject();
      if (selectedObject[0]) {
        if (selectedObject[0] instanceof Node) {
          let node = selectedObject[0] ;
          this.removeSubChild(node);
        }
        diagramInstance.doLayout();
      }
    }
  }
  //Remove the subchild Elements
   removeSubChild(node) {
    for (let i = node.outEdges.length - 1; i >= 0; i--) {
      let connector = diagramInstance.getObject(
        node.outEdges[i]
      );
      let childNode = diagramInstance.getObject(
        connector.targetID
      );
      if (childNode.outEdges.length > 0) {
        this.removeSubChild(childNode);
      } else {
        diagramInstance.remove(childNode);
      }
    }
    diagramInstance.remove(node);
  }
}
//Function to hide the require userhandle.
function hideUserHandle(name) {
  if (diagramInstance.selectedItems.userHandles) {
    for (let handle of diagramInstance.selectedItems.userHandles) {
      if (handle.name === name) {
        handle.visible = false;
      }
    }
  }
}
// Definitions for user handle icons
let leftarrow =
  "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";
let rightarrow =
  "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";
let deleteicon =
  "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";

let leftuserhandle = setUserHandle(
  //it is in dedicated line here.
  "leftHandle",
  leftarrow,
  "Left",
  1,
  { top: 0, bottom: 0, left: 0, right: 10 },
  "Left",
  "Top"
);
let rightuserhandle = setUserHandle(
  //it is in dedicated line here.
  "rightHandle",
  rightarrow,
  "Right",
  1,
  { top: 0, bottom: 0, left: 10, right: 0 },
  "Right",
  "Top"
);
let deleteuserhandle = setUserHandle(
  //it is in dedicated line here.
  "delete",
  deleteicon,
  "Top",
  0.5,
  { top: 0, bottom: 10, left: 0, right: 0 },
  "Center",
  "Center"
);
let handle = [
  leftuserhandle,
  rightuserhandle,
  deleteuserhandle
];
///Function to set and creation of the Userhandle.
function setUserHandle( //it is in dedicated line here.
  name,
  pathData,
  side,
  offset,
  margin,
  horizontalAlignment,
  verticalAlignment
) {
  let userhandle = {
    name: name,
    pathData: pathData,
    backgroundColor: "black",
    pathColor: "white",
    side: side,
    offset: offset,
    margin: margin,
    horizontalAlignment: horizontalAlignment,
    verticalAlignment: verticalAlignment
  };
  return userhandle;
}
//Function to Change the Position of the UserHandle.
function changeUserHandlePosition(change) {
  if (diagramInstance.selectedItems.userHandles) {
    for (let handle of diagramInstance.selectedItems.userHandles) {
      if (handle.name === "delete" && change === "leftHandle") {
        applyHandle(
          handle,
          "Left",
          1,
          { top: 0, bottom: 0, left: 0, right: 10 },
          "Left",
          "Top"
        );
      } else if (handle.name === "delete" && change === "rightHandle") {
        applyHandle(
          handle,
          "Right",
          1,
          { top: 0, bottom: 0, left: 10, right: 0 },
          "Right",
          "Top"
        );
      }
    }
  }
}
//set the value for UserHandle element.
function applyHandle( //it is in dedicated line here.
  handle,
  side,
  offset,
  margin,
  horizontalAlignment,
  verticalAlignment
) {
  handle.side = side;
  handle.offset = offset;
  handle.margin = margin;
  handle.horizontalAlignment = horizontalAlignment;
  handle.verticalAlignment = verticalAlignment;
}
// Handles node creation, connector addition, selection, layout, view, and text editing in the diagram.
function getTextEditValue(selectObject, node){
    var connector = addConnector(selectObject, node);
   diagramInstance.clearSelection();
   var newNode  = diagramInstance.add(node);
   diagramInstance.add(connector);
   diagramInstance.doLayout();
   diagramInstance.bringIntoView(newNode.wrapper.bounds);
   diagramInstance.select([diagramInstance.nameTable[newNode.id]]);
   diagramInstance.startTextEdit(diagramInstance.selectedItems.nodes[0]);
}
</script>

```

### src/components/diagram-mind-map/DiagramView.vue

```vue
<template>
  <Diagram />
</template>

<script setup>
import Diagram from '../../components/Diagram.vue'
</script>
```
