Angular Diagram - Data Binding
The Diagram can load external tree-like and graph structures. The supported data structures are listed below.
- Node and Edge Arrays: the information about nodes is maintained separately from the information about edges.
- Linear Array: nodes are maintained in a linear data structure and are connected by Key - Parent Key reference.
- Hierarchical Array: nodes are maintained in hierarchical data structure.
When you bind a Diagram UI component to a data source, you should specify properties that allow the UI component to create the diagram structure. The supported data structures differ in required properties.
Required Properties | Node and Edge Arrays | Linear Array | Hierarchical Array |
---|---|---|---|
nodes.keyExpr | |||
edges.keyExpr | |||
nodes.parentKeyExpr | |||
nodes.itemsExpr |
Two-Way Data Binding
The Diagram component updates the bound data source and reloads the diagram content each time a user changes diagram data by the UI. If you modify the data source directly (beyond the Diagram), use push services to notify the component about data modifications. In this case, the Diagram gets notifications about data source changes and reloads diagram content.
Handle the requestLayoutUpdate event to make the Diagram recalculate and update the diagram layout each time data is reloaded.
If you use push services to modify data source data and specify item key values (IDs in a data source) manually, subscribe to the onInserting data source event to assign these values. Otherwise, the Diagram generates GUID key values for items that are added from the toolbox, pasted, cloned, or returned after being removed (by undo and redo operations).
We recommend that you do not set the reshapeOnPush property to true
in the Diagram data source as it interferes with many of the diagram functions: Selection, History, Document Focus, etc.
Node and Edge Arrays
Use the nodes.dataSource and edges.dataSource properties to bind the Diagram UI component to plain lists of nodes and edges.
Specify the following required properties: nodes.keyExpr, edges.keyExpr.
Do not specify the parentKeyExpr and itemsExpr properties, because they are a part of another binding mode.
During the binding process, the UI component creates a shape for every bound node and a connector for every bound edge. To attach a start or end of a connector to a shape, use the edges.fromExpr and edges.toExpr properties. These expressions should return keys of attached nodes.
jQuery
$(function() { $("#diagram").dxDiagram({ nodes: { dataSource: new DevExpress.data.ArrayStore({ key: "this", data: orgItems }), keyExpr: "id", textExpr: "text", }, edges: { dataSource: new DevExpress.data.ArrayStore({ key: "this", data: orgLinks }), keyExpr: "id", fromExpr: "from", toExpr: "to" }, }); });
var orgItems = [{ "id":"101", "text":"Development", },{ "id":"102", "text":"Javascript\nTeam", },{ "id":"103", "text":"ASP.NET\nTeam", }]; var orgLinks = [{ "id":"121", "from":"101", "to":"102", },{ "id":"122", "from":"101", "to":"103", }];
Angular
<dx-diagram> <dxo-nodes [dataSource]="flowNodesDataSource" keyExpr="id" textExpr="text" ></dxo-nodes> <dxo-edges [dataSource]="flowEdgesDataSource" keyExpr="id" fromExpr="from" toExpr="to" ></dxo-edges> </dx-diagram>
import { Component } from '@angular/core'; import ArrayStore from "devextreme/data/array_store"; import { Service } from "./app.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [Service] }) export class AppComponent { flowNodesDataSource: ArrayStore; flowEdgesDataSource: ArrayStore; constructor(service: Service) { this.flowNodesDataSource = new ArrayStore({ key: "id", data: service.getFlowNodes(), }); this.flowEdgesDataSource = new ArrayStore({ key: "id", data: service.getFlowEdges(), }); } }
import { Injectable } from "@angular/core"; export class FlowNode { id: number; text: string; } export class FlowEdge { id: number; from: number; to: number; } const flowNodes: FlowNode[] = [ { id: 101, text: "Development", }, { id: 102, text: "Javascript\nTeam", }, { id: 103, text: "ASP.NET\nTeam", } ]; const flowEdges: FlowEdge[] = [ { id: 121, from: 101, to: 102, }, { id: 122, from: 101, to: 103, } ]; @Injectable() export class Service { getFlowNodes() { return flowNodes; } getFlowEdges() { return flowEdges; } }
Vue
<template> <DxDiagram> <DxNodes :data-source="flowNodesDataSource" :key-expr="'id'" :text-expr="'text'" > </DxNodes> <DxEdges :data-source="flowEdgesDataSource" :key-expr="'id'" :from-expr="'from'" :to-expr="'to'" /> </DxDiagram> </template> <script> import { DxDiagram, DxNodes, DxEdges } from 'devextreme-vue/diagram'; import ArrayStore from 'devextreme/data/array_store'; import service from './data.js'; export default { components: { DxDiagram, DxNodes, DxEdges }, data() { return { flowNodesDataSource: new ArrayStore({ key: 'id', data: service.getFlowNodes(), }), flowEdgesDataSource: new ArrayStore({ key: 'id', data: service.getFlowEdges(), }), }; }, }; </script>
const flowNodes = [ { id: 101, text: "Development", }, { id: 102, text: "Javascript\nTeam", }, { id: 103, text: "ASP.NET\nTeam", }, ]; const flowEdges = [ { id: 121, from: 101, to: 102, }, { id: 122, from: 101, to: 103, }, ]; export default { getFlowNodes() { return flowNodes; }, getFlowEdges() { return flowEdges; }, };
React
import React, { useState, useEffect } from "react"; import Diagram, { Nodes, Edges } from "devextreme-react/diagram"; import ArrayStore from "devextreme/data/array_store"; import service from "./data.js"; const flowNodesDataSource = new ArrayStore({ key: "id", data: service.getFlowNodes(), }); const flowEdgesDataSource = new ArrayStore({ key: "id", data: service.getFlowEdges(), }); const App = () => { return ( <Diagram> <Nodes dataSource={flowNodesDataSource} keyExpr="id" textExpr="text" /> <Edges dataSource={flowEdgesDataSource} keyExpr="id" fromExpr="from" toExpr="to" /> </Diagram> ); }; export default App;
const flowNodes = [ { id: 101, text: "Development", }, { id: 102, text: "Javascript\nTeam", }, { id: 103, text: "ASP.NET\nTeam", }, ]; const flowEdges = [ { id: 121, from: 101, to: 102, }, { id: 122, from: 101, to: 103, }, ]; export default { getFlowNodes() { return flowNodes; }, getFlowEdges() { return flowEdges; }, };
Linear Array
Use the nodes.dataSource property to bind the UI component to a list of nodes where each record specifies a node's key value and includes a parent node's key value reference.
Specify the following required properties: nodes.keyExpr, nodes.parentKeyExpr.
Do not specify the nodes.itemsExpr and edges properties because they are a part of another binding mode.
During the binding process, the UI component creates a shape for every bound node and a connector between every pair of nodes linked by the Key - Parent Key reference. Note that edges are not maintained as entities in a data source, thus a detached connector disappears after rebinding.
jQuery
$(function() { $("#diagram").dxDiagram({ nodes: { dataSource: new DevExpress.data.ArrayStore({ key: "this", data: employees }), keyExpr: "ID", parentKeyExpr: "Head_ID", textExpr: "Title" }, }); });
const employees = [{ "ID": 3, "Full_Name": "Arthur Miller", "Title": "CTO", }, { "ID": 6, "Head_ID": 3, "Full_Name": "Brett Wade", "Title": "IT Manager", }, { "ID": 9, "Head_ID": 3, "Full_Name": "Barb Banks", "Title": "Support Manager", }, { "ID": 18, "Head_ID": 9, "Full_Name": "James Anderson", "Title": "Support Assistant", }, { "ID": 21, "Head_ID": 6, "Full_Name": "Taylor Riley", "Title": "Network Admin", }, { "ID": 23, "Head_ID": 6, "Full_Name": "Wally Hobbs", "Title": "Programmer", }, { "ID": 24, "Head_ID": 6, "Full_Name": "Brad Jameson", "Title": "Programmer", }];
Angular
<dx-diagram> <dxo-nodes [dataSource]="dataSource" keyExpr="ID" textExpr="Title" parentKeyExpr="Head_ID" > </dxo-nodes> </dx-diagram>
import { Component } from '@angular/core'; import ArrayStore from "devextreme/data/array_store"; import { Service } from "./app.service"; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [Service] }) export class AppComponent { dataSource: ArrayStore; constructor(service: Service) { this.dataSource = new ArrayStore({ key: "ID", data: service.getEmployees(), }); } }
import { Injectable } from "@angular/core"; export class Employee { ID: number; Head_ID?: number; Full_Name: string; Title: string; } const employees: Employee[] = [ { ID: 3, Full_Name: "Arthur Miller", Title: "CTO", }, { ID: 6, Head_ID: 3, Full_Name: "Brett Wade", Title: "IT Manager", }, { ID: 9, Head_ID: 3, Full_Name: "Barb Banks", Title: "Support Manager", }, { ID: 18, Head_ID: 9, Full_Name: "James Anderson", Title: "Support Assistant", }, { ID: 21, Head_ID: 6, Full_Name: "Taylor Riley", Title: "Network Admin", }, { ID: 23, Head_ID: 6, Full_Name: "Wally Hobbs", Title: "Programmer", }, { ID: 24, Head_ID: 6, Full_Name: "Brad Jameson", Title: "Programmer", }, ]; @Injectable() export class Service { getEmployees() { return employees; } }
Vue
<template> <DxDiagram> <DxNodes :data-source="dataSource" :key-expr="'ID'" :text-expr="'Title'" :parent-key-expr="'Head_ID'" /> </DxDiagram> </template> <script> import { DxDiagram, DxNodes } from 'devextreme-vue/diagram'; import ArrayStore from 'devextreme/data/array_store'; import service from './data.js'; export default { components: { DxDiagram, DxNodes, DxEdges }, data() { return { dataSource: new ArrayStore({ key: 'id', data: service.getEmployees(), }) }; } }; </script>
const employees = [ { ID: 3, Full_Name: "Arthur Miller", Title: "CTO", }, { ID: 6, Head_ID: 3, Full_Name: "Brett Wade", Title: "IT Manager", }, { ID: 9, Head_ID: 3, Full_Name: "Barb Banks", Title: "Support Manager", }, { ID: 18, Head_ID: 9, Full_Name: "James Anderson", Title: "Support Assistant", }, { ID: 21, Head_ID: 6, Full_Name: "Taylor Riley", Title: "Network Admin", }, { ID: 23, Head_ID: 6, Full_Name: "Wally Hobbs", Title: "Programmer", }, { ID: 24, Head_ID: 6, Full_Name: "Brad Jameson", Title: "Programmer", }, ]; export default { getEmployees() { return employees; } }
React
import React from "react"; import Diagram, { Nodes } from "devextreme-react/diagram"; import ArrayStore from "devextreme/data/array_store"; import service from "./data.js"; const dataSource = new ArrayStore({ key: 'ID', data: service.getEmployees(), }); const App = () => { return ( <Diagram> <Nodes dataSource={dataSource} keyExpr="ID" textExpr="Title" parentKeyExpr="Head_ID" /> </Diagram> ); }; export default App;
const employees = [ { ID: 3, Full_Name: "Arthur Miller", Title: "CTO", }, { ID: 6, Head_ID: 3, Full_Name: "Brett Wade", Title: "IT Manager", }, { ID: 9, Head_ID: 3, Full_Name: "Barb Banks", Title: "Support Manager", }, { ID: 18, Head_ID: 9, Full_Name: "James Anderson", Title: "Support Assistant", }, { ID: 21, Head_ID: 6, Full_Name: "Taylor Riley", Title: "Network Admin", }, { ID: 23, Head_ID: 6, Full_Name: "Wally Hobbs", Title: "Programmer", }, { ID: 24, Head_ID: 6, Full_Name: "Brad Jameson", Title: "Programmer", }, ]; export default { getEmployees() { return employees; } }
Hierarchical Array
Use the nodes.dataSource property to bind the UI component to a hierarchical object.
Specify the following required properties: nodes.keyExpr, nodes.itemsExpr.
Do not specify the nodes.parentKeyExpr and edges properties because they are a part of another binding mode.
During the binding process, the UI component creates a shape for every bound node and all connectors that are between a node and its children. Note that the edges are not maintained as entities in a data source, thus the detached connector disappears after it is rebound.
jQuery
$(function() { $("#diagram").dxDiagram({ nodes: { dataSource: new DevExpress.data.ArrayStore({ key: "this", data: employees }), keyExpr: "ID", textExpr: "Title", itemsExpr: "Items", }, }); });
var employees = [{ "ID": 3, "Full_Name": "Arthur Miller", "Title": "CTO", "Items": [{ "ID": 6, "Full_Name": "Brett Wade", "Title": "IT Manager", "Items": [{ "ID": 21, "Full_Name": "Taylor Riley", "Title": "Network Admin", }, { "ID": 23, "Full_Name": "Wally Hobbs", "Title": "Programmer", }, { "ID": 24, "Full_Name": "Brad Jameson", "Title": "Programmer", }] }, { "ID": 9, "Full_Name": "Barb Banks", "Title": "Support Manager", "Items": [{ "ID": 18, "Full_Name": "James Anderson", "Title": "Support Assistant", }] }] }];
Diagram Layout
The UI component creates a diagram layout based on the algorithm specified by the autoLayout.orientation and autoLayout.type properties.
jQuery
$(function() { $("#diagram").dxDiagram({ nodes: { autoLayout: { orientation: "horizontal" type: "tree", }, ... }, }); });
You can create a diagram layout based on shape coordinates maintained in a data source. Set the leftExpr and topExpr properties to names of data source fields that provide shape coordinates.
If you bind a Diagram to an array of edges, you can specify a shape's connection point where an edge begins (fromPointIndexExpr) and ends (toPointIndexExpr) and provide additional points for connectors with the pointsExpr property.
Shape and edge point coordinates are specified in units.
If the autoLayout.type property is set to auto (default value), and both the leftExpr and topExpr properties are specified, the autolayout feature is disabled. The UI component creates a diagram layout based on the provided coordinates. If the position properties are not specified, the auto type denotes the layered layout.
If the autoLayout.type property is set to layered or tree, predefined shape coordinates (leftExpr and topExpr) and edge points (pointsExpr) are ignored.
jQuery
$(function() { $("#diagram").dxDiagram({ nodes: { dataSource: new DevExpress.data.ArrayStore({ key: "this", data: orgItems }), autoLayout: { type: "off" }, keyExpr: "key", leftExpr: "left", textExpr: "text", topExpr: "top", }, edges: { dataSource: new DevExpress.data.ArrayStore({ key: "this", data: orgLinks }), keyExpr: "key", fromExpr: "from", toExpr: "to", fromPointIndexExpr: "fromPoint", toPointIndexExpr: "toPoint", pointsExpr: "points", }, units: "in", }); });
var orgItems = [ { key: "101", left: 0.5, text: "Product Manager", top: 0.875, }, { key: "102", left: 2.5, text: "Team", top: 0.5, }, ]; var orgLinks = [ { key: "1", from: "101", to: "102", fromPoint: 1, toPoint: 3, points: [{x:1.5,y:1.125},{x:1.75,y:0.875},{x:2.5,y:0.875}], }, ];
The Diagram UI component reloads the diagram every time the data source changes. The onRequestLayoutUpdate function is executed after diagram data is reloaded and allows you to specify whether the UI component should update the diagram layout.
Optional Binding Expressions
The Diagram allows you to bind a number of shape and connector visual properties, like type, size, and style.
Node property | Value the property should return | Sample return value |
---|---|---|
containerChildrenExpr | A container's nested items. This property is in effect for verticalContainer or horizontalContainer nodes. Excludes containerKeyExpr. | [{"id":"112","text":"Ana\nTrujillo"}, {"id":"113","text":"Antonio\nMoreno"}] |
containerKeyExpr | A parent container node key. The parent container node must be of the verticalContainer or horizontalContainer type. Excludes containerChildrenExpr. | "102" |
customDataExpr | A node's custom data. | - |
heightExpr | A node's height, in units. | 0.625 |
imageUrlExpr | A node's image URL or Base64 encoded image. This property is in effect for nodes of the cardWithImageOnLeft, cardWithImageOnTop, or cardWithImageOnRight type. | "images/employees/30.png" |
leftExpr | The x-coordinate of a node's left border, in units. | 0.5 |
lockedExpr | A value that indicates whether a node is locked.Should return true or false. | true |
styleExpr | A node's style. | { "stroke": "red" } | textExpr | A node's text. | "ASP.NET" |
textStyleExpr | A node's text style. | { "font-weight": "bold", "text-decoration": "underline" } |
topExpr | The y-coordinate of a node's top border, in units. | 0.875 |
typeExpr | A node's shape type. Built-in shape types are shown in the Shape Types section. | "horizontalContainer" |
widthExpr | A node's width, in units. | 2 |
zIndexExpr | A node's z-index. | 1 |
Edge property | Value the property should return | Sample return value |
---|---|---|
customDataExpr | An edge's custom data. | - |
fromExpr | An edge's start node key. | "101", |
fromLineEndExpr | An edge's line start tip.Should return "arrow", "filledTriangle", "outlinedTriangle", or "none". | "none" |
fromPointIndexExpr | A shape's connection point index where an edge begins. | 1 |
lineTypeExpr | An edge's line type.Should return "orthogonal" or "straight". | "straight" |
lockedExpr | A value that indicates whether a node is locked.Should return true or false. | false |
pointsExpr | An edge's key points. | [{x:1.5,y:1.125},{x:1.75,y:0.875},{x:2.5,y:0.875}] |
styleExpr | An edge's style. | {"stroke-dasharray":"4"} |
textExpr | An edge's text. | "text" or { 0.3: "text1", 0.8: "text2" } |
textStyleExpr | An edge's text style. | { "font-weight": "bold"} |
toExpr | An edge's end node key. | "102", |
toLineEndExpr | An edge's line end tip.Should return "arrow", "filledTriangle", "outlinedTriangle", or "none". | "filledTriangle" | toPointIndexExpr | A shape's connection point index where an edge ends. | 11 |
zIndexExpr | An edge's z-index. | 0 |
You can set a binding property to the name of a data source field that supplies item values, or to an expression that returns a constant value or calculates a value in runtime based on conditions.
If your diagram includes container shapes, define the containerKeyExpr property to store information about a parent container in the data source. Otherwise, this information will be lost.
jQuery
$(function() { $("#diagram").dxDiagram({ nodes: { dataSource: new DevExpress.data.ArrayStore({ key: "this", data: orgItems }), autoLayout: { type: "off" }, containerKeyExpr: "containerKey", heightExpr: "height", imageUrlExpr: "imageUrl", keyExpr: "key", leftExpr: "left", lockedExpr: "locked", styleExpr: function(obj) {if (obj.type.includes("Container")) return {"stroke": "red"}}, textExpr: "text", textStyleExpr: "textStyle", topExpr: "top", typeExpr: "type", widthExpr: "width", zIndexExpr: "zIndex", }, edges: { dataSource: new DevExpress.data.ArrayStore({ key: "this", data: orgLinks }), fromExpr: "from", fromLineEndExpr: function() {return "none"}, fromPointIndexExpr: "fromPointIndex", keyExpr: "key", lineTypeExpr: function() {return "straight"}, lockedExpr: "locked", pointsExpr: "points", styleExpr: function() {return ({"stroke-dasharray":"4"})}, textExpr: "text", textStyleExpr: "textStyle", toExpr: "to", toLineEndExpr: function() {return "arrow"}, toPointIndexExpr: "toPointIndex", }, }); });
var orgItems = [ { height: 0.625, key: "101", left: 0.5, locked: true, text: "Product Manager", textStyle: { "font-weight": "bold", "text-decoration": "underline" }, top: 0.875, type: "rectangle", width: 1, zIndex: 2, }, { height: 1.375, key: "102", left: 2.5, locked: false, text: "Team", textStyle: { "font-weight": "bold", "text-decoration": "underline" }, top: 0.5, type: "horizontalContainer", width: 2, zIndex: 1, },{ height: 0.5, imageUrl: "images/employees/30.png", key: "103", left: 2.875, text: "Team Leader", top: 0.625, type: "cardWithImageOnLeft", width: 1.5, containerKey: "102", },{ height: 0.5, key: "104", left: 2.875, text: "Developers", top: 1.25, type: "rectangle", width: 1.5, containerKey: "102", } ]; var orgLinks = [ { from: "101", fromPointIndex: 1, key: "1", locked: false, points: [{x:1.5,y:1.125},{x:1.75,y:0.875},{x:2.5,y:0.875}], text: "Task", textStyle: { "font-weight": "bold"}, to: "102", toPointIndex: 11, }, ];
If you have technical questions, please create a support ticket in the DevExpress Support Center.