BPMN text annotation in Vue Diagram component

Text Annotation

  • A BPMN object can be associated with a text annotation which does not affect the flow but gives details about objects within a flow.

  • A TextAnnotation points to or references another BPMN shape, which we call the textAnnotationTarget of the textAnnotation. When a target shape is moved or deleted, any TextAnnotations attached to the shape will be moved or deleted too. Thus, the TextAnnotations remain with their target shapes though you can reposition the TextAnnotation to any offset from its target. The textAnnotationTarget property of the BpmnTextAnnotation is used to connect an annotation element to the BPMN Node.

  • The annotation element can be switched from a BPMN node to another BPMN node simply by dragging the source end of the annotation connector into the other BPMN node.

  • By default, the TextAnnotation shape has a connection.

  • The textAnnotationDirection property is used to set the shape direction of the text annotation.

  • By default, the textAnnotationDirection is set to a Auto.

  • To set the size for text annotation, use the width and height properties of the node.

  • The offsetX and offsetY properties are used to set the distance between the BPMN node and the TextAnnotation.

<template>
    <div id="app">
        <ejs-diagram id="diagram" :width='width' :height='height' :nodes='nodes'></ejs-diagram>
    </div>
</template>
<script setup>
import { provide } from "vue";
import { DiagramComponent as EjsDiagram, BpmnDiagrams } from '@syncfusion/ej2-vue-diagrams';

const nodes = [{
    id: 'event1', style: { strokeWidth: 2 },
    height: 70, width: 70, offsetX: 400, offsetY: 200,
    shape: {
        type: 'Bpmn', shape: 'Event',
        event: { event: 'Start', trigger: 'None' },
    }
},
//node with target
{
    id: 'textNode1', width: 70, height: 70,
    offsetX: 400, offsetY: 400,
    annotations: [{ content: 'textNode1' }],
    shape: {
        type: 'Bpmn', shape: 'TextAnnotation',
        textAnnotation: { textAnnotationDirection: 'Auto', textAnnotationTarget: 'event1' }
    }
},
//Node without target
{
    id: 'textNode2', width: 70, height: 70,
    offsetX: 600, offsetY: 400,
    annotations: [{ content: 'textNode1' }],
    shape: {
        type: 'Bpmn', shape: 'TextAnnotation',
        textAnnotation: { textAnnotationDirection: 'Auto', textAnnotationTarget: '' }
    }
},]

const width = "100%";
const height = "600px";

provide('diagram', [BpmnDiagrams]);
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
</style>
<template>
    <div id="app">
        <ejs-diagram id="diagram" :width='width' :height='height' :nodes='nodes'></ejs-diagram>
    </div>
</template>
<script>
import { DiagramComponent, BpmnDiagrams } from '@syncfusion/ej2-vue-diagrams';

let nodes = [{
    id: 'event1', style: { strokeWidth: 2 },
    height: 70, width: 70, offsetX: 400, offsetY: 200,
    shape: {
        type: 'Bpmn', shape: 'Event',
        event: { event: 'Start', trigger: 'None' },
    }
},
//node with target
{
    id: 'textNode1', width: 70, height: 70,
    offsetX: 400, offsetY: 400,
    annotations: [{ content: 'textNode1' }],
    shape: {
        type: 'Bpmn', shape: 'TextAnnotation',
        textAnnotation: { textAnnotationDirection: 'Auto', textAnnotationTarget: 'event1' }
    }
},
//Node without target
{
    id: 'textNode2', width: 70, height: 70,
    offsetX: 600, offsetY: 400,
    annotations: [{ content: 'textNode1' }],
    shape: {
        type: 'Bpmn', shape: 'TextAnnotation',
        textAnnotation: { textAnnotationDirection: 'Auto', textAnnotationTarget: '' }
    }
},]
export default {
    name: "App",
    components: {
        "ejs-diagram": DiagramComponent
    },
    data() {
        return {
            width: "100%",
            height: "600px",
            nodes: nodes,
        }
    },
    provide: {
        diagram: [BpmnDiagrams]
    }
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
</style>

Text annotation in palette.

Text annotation node can be rendered in symbol palette like other bpmn shapes. The following example shows how to render BPMN text annotation node in symbol palette.

<template>
    <div style="width: 100%">
        <div id="palette-space" class="sb-mobile-palette">
            <ejs-symbolpalette id="symbolpalette" :palettes="palettes" :width="palettewidth" :height="paletteheight"
                :getNodeDefaults="palettegetNodeDefaults" :getSymbolInfo="getSymbolInfo" :symbolMargin="symbolMargin"
                :symbolHeight="symbolHeight" :symbolWidth="symbolWidth">
            </ejs-symbolpalette>
        </div>
        <div id="diagram-space" class="sb-mobile-diagram">
            <ejs-diagram style="display: block" ref="diagramObject" id="diagram" :width="width" :height="height">
            </ejs-diagram>
        </div>
    </div>
</template>
<script setup>
import { DiagramComponent as EjsDiagram, SymbolPaletteComponent as EjsSymbolpalette ,  BpmnDiagrams ,
  UndoRedo,SymbolPaletteComponent,  DiagramContextMenu, } from '@syncfusion/ej2-vue-diagrams';
import { provide } from "vue";
const bpmnShapes = [
{
  id: 'Start',
  width: 35,
  height: 35,
  shape: {
    type: 'Bpmn',
    shape: 'Event',
    event: { event: 'Start' },
  },
},
{
  id: 'Gateway',
  width: 35,
  height: 35,
  offsetX: 100,
  offsetY: 100,
  shape: {
    type: 'Bpmn',
    shape: 'Gateway',
    gateway: { type: 'Exclusive' },
  },
},
{
  id: 'DataObject',
  width: 35,
  height: 35,
  offsetX: 500,
  offsetY: 100,
  shape: {
    type: 'Bpmn',
    shape: 'DataObject',
    dataObject: { collection: false, type: 'None' },
  },
},
{
  id: 'textAnnotation',
  width: 35,
  height: 35,
  shape: {
    type: 'Bpmn',
    shape: 'TextAnnotation',
  },
  annotations: [{ content: 'textAnnotation' }],
} 
];

const width = '100%';
const height = '700px';
const palettes = [
    {
        id: 'uml',
        expanded: true,
        symbols: bpmnShapes,
        title: 'BPMN Shapes',
    },
]
const palettewidth = '100%';
const paletteheight = '200px';
const symbolHeight = 100;
const symbolWidth = 100;

const palettegetNodeDefaults = (symbol) => {
    symbol.width = 100;
    symbol.height = 100;
}
const symbolMargin = { left: 15, right: 15, top: 15, bottom: 15 };
const getSymbolInfo = (symbol) => {
    return { fit: true, description: { text: symbol.id } };
}
provide('diagram', [BpmnDiagrams, UndoRedo, DiagramContextMenu]);
provide('SymbolPalette',[BpmnDiagrams]);

</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
</style>
<template>
<div style="width: 100%">
    <div id="palette-space" class="sb-mobile-palette">
        <ejs-symbolpalette id="symbolpalette" :palettes="palettes" :width="palettewidth" :height="paletteheight"
            :getNodeDefaults="palettegetNodeDefaults" :getSymbolInfo="getSymbolInfo" :symbolMargin="symbolMargin"
            :symbolHeight="symbolHeight" :symbolWidth="symbolWidth"></ejs-symbolpalette>
    </div>
    <div id="diagram-space" class="sb-mobile-diagram">
        <ejs-diagram style="display: block" ref="diagramObject" id="diagram" :width="width"
            :height="height"></ejs-diagram>
    </div>
</div>
</template>
<script>

import { DiagramComponent,  BpmnDiagrams,
  UndoRedo,SymbolPaletteComponent,  DiagramContextMenu,
} from '@syncfusion/ej2-vue-diagrams';

let bpmnShapes = [
{
  id: 'Start',
  width: 35,
  height: 35,
  shape: {
    type: 'Bpmn',
    shape: 'Event',
    event: { event: 'Start' },
  },
},
{
  id: 'Gateway',
  width: 35,
  height: 35,
  offsetX: 100,
  offsetY: 100,
  shape: {
    type: 'Bpmn',
    shape: 'Gateway',
    gateway: { type: 'Exclusive' },
  },
},
{
  id: 'DataObject',
  width: 35,
  height: 35,
  offsetX: 500,
  offsetY: 100,
  shape: {
    type: 'Bpmn',
    shape: 'DataObject',
    dataObject: { collection: false, type: 'None' },
  },
},
{
  id: 'textAnnotation',
  width: 35,
  height: 35,
  shape: {
    type: 'Bpmn',
    shape: 'TextAnnotation',
  },
  annotations: [{ content: 'textAnnotation' }],
} 
];

export default {
name: "App",
components: {
    "ejs-symbolpalette": SymbolPaletteComponent,
    "ejs-diagram": DiagramComponent
},
data() {
    return {
        width: '100%',
        height: '700px',
        palettes: [
            {
                id: 'uml',
                expanded: true,
                symbols: bpmnShapes,
                title: 'Bpmn Shapes',
            },
        ],
        palettewidth: '100%',
        paletteheight: '200px', 
        symbolHeight: 100,
        symbolWidth: 100,
        palettegetNodeDefaults: (symbol) => {
            symbol.width = 100;
            symbol.height = 100;
        },
        symbolMargin: { left: 15, right: 15, top: 15, bottom: 15 },
        getSymbolInfo: (symbol) => {
            return { fit: true, description: { text: symbol.id } };
        },
    };
},
provide: {
  diagram: [BpmnDiagrams, UndoRedo, DiagramContextMenu],
  SymbolPalette:[BpmnDiagrams]

},
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
</style>

Connect the TextAnnotation to BPMN node

Drag and drop any bpmn shapes from the palette to diagram and connect the BPMN Node and textAnnotation.

The following image shows how to drag a symbol from the palette and connect the textAnnotation to the BPMNNode with interaction.

Text annotation GIF

Text annotation direction

There are several types of Text annotation directions as follows:

Text annotation direction Image
Auto BPMN text annotation direction auto
Left BPMN text annotation direction left
Right BPMN text annotation direction right
Top BPMN text annotation direction top
Bottom BPMN text annotation direction bottom

Add text annotation at runtime

Text annotations can be added dynamically using either the addTextAnnotation method or the add method of the diagram. The following example shows how to use these methods to add a text annotation node.

<template>
  <div id="app">
    <ejs-button ref="textAnnotation" id="textAnnotation">Add Text Annotation</ejs-button>
    <ejs-button ref="textAnnotationNode" id="textAnnotationNode">Add Text Annotation Node</ejs-button>
    <ejs-button ref="textAnnotationAlone" id="textAnnotationAlone">Add TextAnnotation Alone</ejs-button>
    <ejs-diagram
      id="diagram"
      :width="width"
      :height="height"
      :nodes="nodes"
      ref="diagram"
    ></ejs-diagram>
  </div>
</template>

<script setup>
import { provide } from "vue";
import { ref, onMounted } from "vue";
import {
  DiagramComponent as EjsDiagram,
  randomId,
  BpmnDiagrams,
} from "@syncfusion/ej2-vue-diagrams";
import { ButtonComponent as EjsButton } from "@syncfusion/ej2-vue-buttons";

// Refs for elements and diagram instance
const diagram = ref(null);
const textAnnotation = ref(null);
const textAnnotationNode = ref(null);
const textAnnotationAlone = ref(null);

// Diagram nodes data
const nodes = [
  {
    id: "event",
    offsetX: 200,
    offsetY: 200,
    width: 70,
    height: 70,
    shape: { type: "Bpmn", shape: "Event" },
  },
];

// Diagram dimensions
const width = "100%";
const height = "600px";
// Utility function to add a text annotation to the diagram
const addTextAnnotation = (textAnnotation, parentNode = null) => {
  const diagramInstance = diagram.value.ej2Instances;

  // If there's a parent node (like the 'event' node), add the annotation to it
  if (parentNode) {
    diagramInstance.addTextAnnotation(textAnnotation, parentNode);
  } else {
    // Otherwise, add the annotation to the diagram as an independent element
    diagramInstance.add(textAnnotation);
  }
};
provide("diagram", [BpmnDiagrams]);

// Mount lifecycle hook to handle event bindings after component is mounted
onMounted(() => {
  const diagramInstance = diagram.value.ej2Instances;
  const textAnnotationInstance = textAnnotation.value.ej2Instances;
  const textAnnotationNodeInstance = textAnnotationNode.value.ej2Instances;
  const textAnnotationAloneInstance = textAnnotationAlone.value.ej2Instances;

  // Adding event handlers for button clicks
  textAnnotationInstance.element.onclick = () => {
    const eventNode = diagramInstance.nodes[0]; // 'event' node
    const annotation = {
      name: "newAnnotation" + randomId(),
      angle: 0,
      length: 100,
      width: 100,
      height: 40,
      text: "New Annotation",
    };

    addTextAnnotation(annotation, eventNode); // Add to 'event' node
  };

  textAnnotationNodeInstance.element.onclick = () => {
    const annotation = {
      id: "textAnnotation" + randomId(),
      offsetX: 300,
      offsetY: 100,
      width: 100,
      height: 40,
      annotations: [{ content: "Text Annotation" }],
      shape: {
        type: "Bpmn",
        shape: "TextAnnotation",
        textAnnotation: {
          textAnnotationTarget: "event",
          textAnnotationDirection: "Auto",
        },
      },
    };

    addTextAnnotation(annotation);
  };

  textAnnotationAloneInstance.element.onclick = () => {
    const annotation = {
      id: "textAnnotationAlone" + randomId(),
      offsetX: 300,
      offsetY: 300,
      width: 100,
      height: 70,
      annotations: [{ content: "Text Annotation" }],
      shape: {
        type: "Bpmn",
        shape: "TextAnnotation",
      },
    };
    addTextAnnotation(annotation); // Add to diagram without a parent
  };
});
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
</style>
<template>
  <div id="app">
    <ejs-button v-on:click="text">Add Text Annotation </ejs-button>
    <ejs-button v-on:click="node">Add Text Annotation Node </ejs-button>
    <ejs-button v-on:click="alone">Add Text Annotation Alone </ejs-button>
    <ejs-diagram
      id="diagram"
      ref="diagram"
      :width="width"
      :height="height"
      :nodes="nodes"
    ></ejs-diagram>
  </div>
</template>
<script>
import { DiagramComponent, BpmnDiagrams,randomId } from '@syncfusion/ej2-vue-diagrams';
import { ButtonComponent } from '@syncfusion/ej2-vue-buttons';

let nodes = [{
  id: 'event',
  offsetX: 200,
  offsetY: 200,
  width: 70,
  height: 70,
  shape: { type: 'Bpmn', shape: 'Event' },
},]
export default {
name: "App",
components: {
    "ejs-diagram": DiagramComponent,
    'ejs-button': ButtonComponent,
},
data() {
    return {
        width: "100%",
        height: "600px",
        nodes: nodes,
    }
},
provide: {
    diagram: [BpmnDiagrams]
},
methods: {
  text: function () {
    var diagramInstance = this.$refs.diagram.ej2Instances;

    let event = diagramInstance.nodes[0];
    let textAnnotation = {
      name: 'newAnnotation' + randomId(),
      angle: 0,
      length: 100,
      width: 100,
      height: 40,
      text: 'New Annotation',
    };
    /**
     * parameter 1 - TextAnnotation to be added
     * parameter 2 - The parent node where the text annotation will be added as a child.
     */
     diagramInstance.addTextAnnotation(textAnnotation, event);
  },
  node: function () {
    var diagramInstance = this.$refs.diagram.ej2Instances;
    let textAnnotation = {
      id: 'textAnnotation' + randomId(),
      offsetX: 300,
      offsetY: 100,
      width: 100,
      height: 40,
      annotations: [{ content: 'Text Annotation' }],
      shape: {
        type: 'Bpmn',
        shape: 'TextAnnotation',
        textAnnotation: {
          //Parent node of text annotation
          textAnnotationTarget: 'event',
          textAnnotationDirection: 'Auto',
        },
      },
    };
    /**
     * parameter 1 - TextAnnotation to be added to the event node
     */
     diagramInstance.add(textAnnotation);
  },
  alone: function () {
    var diagramInstance = this.$refs.diagram.ej2Instances;
    let textAnnotation = {
      id: 'textAnnotationAlone' + randomId(),
      offsetX: 300,
      offsetY: 300,
      width: 100,
      height: 70,
      annotations: [{ content: 'Text Annotation' }],
      shape: {
        type: 'Bpmn',
        shape: 'TextAnnotation',
      },
    };
    /**
     * parameter 1 - TextAnnotation to be added to diagram without parent
     */
     diagramInstance.add(textAnnotation);

  },
},
}
</script>
<style>
@import "../node_modules/@syncfusion/ej2-vue-diagrams/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
</style>