JavaScript/jQuery Diagram - How To
This section contains code samples that show how to use the Diagram UI component:
- Customize the Diagram component
- Restrict operations
Change the Component's Background Color
Override the following CSS rule to change the Diagram component's background color:
jQuery
#diagram .dxdi-canvas .grid { fill: lightGray !important; }
Angular
::ng-deep #diagram .dxdi-canvas .grid { fill: lightGray !important; }
Vue
<style scoped> #diagram .dxdi-canvas .grid { fill: lightGray !important; } </style>
React
#diagram .dxdi-canvas .grid { fill: lightGray !important; }
Change the Color of Connector Points
Add the following CSS rules to change the color of connector points:
jQuery
.dx-diagram .dxdi-control .dxdi-canvas .connection-point:not(.selector) { stroke: red; } .dx-diagram .dxdi-control .dxdi-canvas .connection-point:not(.selector).active { fill: red; }
Angular
::ng-deep .dx-diagram .dxdi-control .dxdi-canvas .connection-point:not(.selector) { stroke: red; } ::ng-deep .dx-diagram .dxdi-control .dxdi-canvas .connection-point:not(.selector).active { fill: red; }
Vue
<style scoped> .dx-diagram .dxdi-control .dxdi-canvas .connection-point:not(.selector) { stroke: red; } .dx-diagram .dxdi-control .dxdi-canvas .connection-point:not(.selector).active { fill: red; } </style>
React
.dx-diagram .dxdi-control .dxdi-canvas .connection-point:not(.selector) { stroke: red; } .dx-diagram .dxdi-control .dxdi-canvas .connection-point:not(.selector).active { fill: red; }
Customize the Context Menu's Items
The example below demonstrates how to show default and custom commands in the context menu depending on the selected item:
jQuery
$(function () { var diagram = $("#diagram").dxDiagram({ onSelectionChanged: function(e) { // Displays the "showGrid" and "snapToGrid" commands when a user selects no items if (e.items.length === 0) e.component.option("contextMenu.commands", ["showGrid", "snapToGrid"]); else // Displays the "fontName", "fontSize", and "selectShapes" commands when a user selects a shape if (e.items[0].itemType === "shape") e.component.option("contextMenu.commands", ["fontName", "fontSize", {name: "selectShapes", text: "Select All Shapes"}]); // Displays the "connectorLineStart", "connectorLineEnd", and "selectConnectors" commands when a user selects a connector else e.component.option("contextMenu.commands", ["connectorLineStart", "connectorLineEnd", {name: "selectConnectors", text: "Select All Connectors"}]); }, onCustomCommand: function(e) { if (e.name == "selectShapes") { var shapes = e.component.getItems().filter(function(item) { return (item.itemType === "shape") }); e.component.setSelectedItems(shapes); } if (e.name == "selectConnectors") { var connectors = e.component.getItems().filter(function(item) { return (item.itemType === "connector") }); e.component.setSelectedItems(connectors); } }, }).dxDiagram("instance"); });
Angular
<dx-diagram #diagram id="diagram" (onSelectionChanged)="selectionChanged($event)" (onCustomCommand)="customCommand($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core'; import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { @ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent; selectionChanged(e) { // Displays the "showGrid" and "snapToGrid" commands if a user selects no items if (e.items.length === 0) e.component.option("contextMenu.commands", ["showGrid", "snapToGrid"]); else // Displays the "fontName", "fontSize", and "selectShapes" commands when a user selects a shape if (e.items[0].itemType === "shape") e.component.option("contextMenu.commands", ["fontName", "fontSize", {name: "selectShapes", text: "Select All Shapes"}]); else // Displays the "connectorLineStart", "connectorLineEnd", and "selectConnectors" commands when a user selects a connector e.component.option("contextMenu.commands", ["connectorLineStart", "connectorLineEnd", {name: "selectConnectors", text: "Select All Connectors"}]); } customCommand(e) { if (e.name == "selectShapes") { var shapes = e.component.getItems().filter(function(item) { return (item.itemType === "shape") }); e.component.setSelectedItems(shapes); } if (e.name == "selectConnectors") { var connectors = e.component.getItems().filter(function(item) { return (item.itemType === "connector") }); e.component.setSelectedItems(connectors); } } }
Vue
<template> <DxDiagram id="diagram" ref="diagram" @selection-changed="onSelectionChanged" @custom-command="onCustomCommand"> </DxDiagram> </template> <script> import DxDiagram from 'devextreme-vue/diagram'; export default { components: { DxDiagram }, methods: { onSelectionChanged(e) { // Displays the "showGrid" and "snapToGrid" commands if a user selects no items if (e.items.length === 0) e.component.option("contextMenu.commands", ["showGrid", "snapToGrid"]); else // Displays the "fontName", "fontSize", and "selectShapes" commands when a user selects a shape if (e.items[0].itemType === "shape") e.component.option("contextMenu.commands", ["fontName", "fontSize", {name: "selectShapes", text: "Select All Shapes"}]); else // Displays the "connectorLineStart", "connectorLineEnd", and "selectConnectors" commands when a user selects a connector e.component.option("contextMenu.commands", ["connectorLineStart", "connectorLineEnd", {name: "selectConnectors", text: "Select All Connectors"}]); }, onCustomCommand(e) { if (e.name == "selectShapes") { var shapes = e.component.getItems().filter(function(item) { return (item.itemType === "shape") }); e.component.setSelectedItems(shapes); } if (e.name == "selectConnectors") { var connectors = e.component.getItems().filter(function(item) { return (item.itemType === "connector") }); e.component.setSelectedItems(connectors); } }, }; </script>
React
import React from 'react'; import Diagram from 'devextreme-react/diagram'; class App extends React.Component { constructor(props) { super(props); this.diagramRef = React.createRef(); this.onSelectionChanged = this.onSelectionChanged.bind(this); this.onCustomCommand = this.onCustomCommand.bind(this); } onSelectionChanged(e) { // Displays the "showGrid" and "snapToGrid" commands when a user selects no items if (e.items.length === 0) e.component.option("contextMenu.commands", ["showGrid", "snapToGrid"]); else // Displays the "fontName", "fontSize", and "selectShapes" commands when a user selects a shape if (e.items[0].itemType === "shape") e.component.option("contextMenu.commands", ["fontName", "fontSize", {name: "selectShapes", text: "Select All Shapes"}]); else // Displays the "connectorLineStart", "connectorLineEnd", and "selectConnectors" commands when a user selects a connector e.component.option("contextMenu.commands", ["connectorLineStart", "connectorLineEnd", {name: "selectConnectors", text: "Select All Connectors"}]); } onCustomCommand(e) { if (e.name == "selectShapes") { var shapes = e.component.getItems().filter(function(item) { return (item.itemType === "shape") }); e.component.setSelectedItems(shapes); } if (e.name == "selectConnectors") { var connectors = e.component.getItems().filter(function(item) { return (item.itemType === "connector") }); e.component.setSelectedItems(connectors); } } render() { return ( <Diagram id="diagram" ref={this.diagramRef} onSelectionChanged={this.onSelectionChanged} onCustomCommand={this.onCustomCommand}> </Diagram> ); } } export default App;
Specify a Command's Location on the Main Toolbar
Use the location property to set the location of a command or separator on the main toolbar. This property accepts one of the following values:
center
Places the command in the center of the main toolbar.before
Places the command before the central element(s).after
Places the command after the central element(s).
The Diagram UI component displays commands with the same location property value in the order in which you listed them. When the main toolbar cannot accommodate all commands, the component places excess commands in the overflow menu.
The example below demonstrates how to customize the main toolbar:
jQuery
$(function() { var diagram = $("#diagram").dxDiagram({ mainToolbar: { visible: true, commands: [ { name: "undo", location: "before" }, { name: "redo", location: "before" }, { name: "separator", location: "before" }, { name: "copy", location: "center" }, { name: "paste", location: "center" }, { name: "cut", location: "center" }, { name: "separator", location: "center" }, { name: "clear", icon: "clearsquare", text: "Clear Diagram", location: "after" } ] }, toolbox: { visibility: 'hidden', }, historyToolbar: { visible: false }, viewToolbar: { visible: false }, onCustomCommand: function(e) { if(e.name === "clear") { var result = DevExpress.ui.dialog.confirm("Are you sure you want to clear the diagram? This action cannot be undone.", "Warning"); result.done( function(dialogResult) { if(dialogResult) { e.component.import(""); } } ); } } }).dxDiagram("instance"); $.ajax({ url: "data/diagram-flow.json", dataType: "text", success: function(data) { diagram.import(data); } }); });
Angular
<dx-diagram #diagram id="diagram" (onCustomCommand)="onCustomCommand($event)"> <dxo-main-toolbar [visible]="true"> <dxi-command name="undo" location="before"> </dxi-command> <dxi-command name="redo" location="before"> </dxi-command> <dxi-command name="separator" location="before"> </dxi-command> <dxi-command name="copy" location="center"> </dxi-command> <dxi-command name="paste" location="center"> </dxi-command> <dxi-command name="cut" location="center"> </dxi-command> <dxi-command name="separator" location="after"> </dxi-command> <dxi-command name="clear" icon="clearsquare" text="Clear Diagram" location="after"> </dxi-command> </dxo-main-toolbar> <dxo-toolbox visibility="hidden"> </dxo-toolbox> <dxo-history-toolbar [visible]="false"> </dxo-history-toolbar> <dxo-view-toolbar [visible]="false"> </dxo-view-toolbar> </dx-diagram>
import { NgModule, Component, ViewChild, enableProdMode, } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { HttpClient, HttpClientModule } from '@angular/common/http'; import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular'; import dialog from 'devextreme/ui/dialog'; @Component({ selector: 'demo-app', templateUrl: 'app/app.component.html', styleUrls: ['app/app.component.css'], preserveWhitespaces: true, }) export class AppComponent { @ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent; constructor(http: HttpClient) { http.get('data/diagram-flow.json').subscribe((data) => { this.diagram.instance.import(JSON.stringify(data)); }, (err) => { throw 'Data Loading Error'; }); } onCustomCommand(e) { if (e.name === 'clear') { const result = dialog.confirm('Are you sure you want to clear the diagram? This action cannot be undone.', 'Warning'); result.then( (dialogResult) => { if (dialogResult) { e.component.import(''); } }, ); } } } @NgModule({ imports: [ BrowserModule, HttpClientModule, DxDiagramModule, ], declarations: [AppComponent], bootstrap: [AppComponent], }) export class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule);
Vue
<template> <DxDiagram id="diagram" ref="diagram" @custom-command="onCustomCommand" > <DxMainToolbar :visible="true"> <DxCommand :name="'undo'" :location="'before'" /> <DxCommand :name="'redo'" :location="'before'" /> <DxCommand :name="'separator'" :location="'before'" /> <DxCommand :name="'copy'" :location="'center'" /> <DxCommand :name="'paste'" :location="'center'" /> <DxCommand :name="'cut'" :location="'center'" /> <DxCommand :name="'separator'" :location="'after'" /> <DxCommand :name="'clear'" :icon="'clearsquare'" :text="'Clear Diagram'" :location="'after'" /> </DxMainToolbar> <DxToolbox :visibility="'hidden'" /> <DxHistoryToolbar :visible="false" /> <DxViewToolbar :visible="false" /> </DxDiagram> </template> <script> import { DxDiagram, DxHistoryToolbar, DxViewToolbar, DxMainToolbar, DxCommand, DxToolbox } from 'devextreme-vue/diagram'; import { confirm } from 'devextreme/ui/dialog'; import 'whatwg-fetch'; export default { components: { DxDiagram, DxHistoryToolbar, DxViewToolbar, DxMainToolbar, DxCommand, DxToolbox }, mounted() { var diagram = this.$refs['diagram'].instance; fetch('data/diagram-flow.json') .then(function(response) { return response.json(); }) .then(function(json) { diagram.import(JSON.stringify(json)); }) .catch(function() { throw 'Data Loading Error'; }); }, methods: { onCustomCommand(e) { if(e.name === 'clear') { var result = confirm('Are you sure you want to clear the diagram? This action cannot be undone.', 'Warning'); result.then( function(dialogResult) { if(dialogResult) { e.component.import(''); } } ); } } } }; </script>
React
import React from 'react'; import Diagram, { HistoryToolbar, ViewToolbar, MainToolbar, Command, Toolbox } from 'devextreme-react/diagram'; import { confirm } from 'devextreme/ui/dialog'; import 'whatwg-fetch'; class App extends React.Component { constructor(props) { super(props); this.diagramRef = React.createRef(); } onCustomCommand(e) { if(e.name === 'clear') { var result = confirm('Are you sure you want to clear the diagram? This action cannot be undone.', 'Warning'); result.then( function(dialogResult) { if(dialogResult) { e.component.import(''); } } ); } } componentDidMount() { var diagram = this.diagramRef.current.instance; fetch('data/diagram-flow.json') .then(function(response) { return response.json(); }) .then(function(json) { diagram.import(JSON.stringify(json)); }) .catch(function() { throw 'Data Loading Error'; }); } render() { return ( <Diagram id="diagram" ref={this.diagramRef} onCustomCommand={this.onCustomCommand}> <MainToolbar visible={true}> <Command name="undo" location="before" /> <Command name="redo" location="before" /> <Command name="separator" location="before" /> <Command name="copy" location="center" /> <Command name="paste" location="center" /> <Command name="cut" location="center" /> <Command name="separator" location="after" /> <Command name="clear" icon="clearsquare" text="Clear Diagram" location="after" /> </MainToolbar> <Toolbox visibility="hidden" /> <HistoryToolbar visible={false} /> <ViewToolbar visible={false} /> </Diagram> ); } } export default App;
Restrict Operations
The Diagram UI component raises the requestEditOperation event every time a user attempts an edit operation. This article contains code samples that demonstrate how to use this event's parameters to prohibit individual edit operations and customize a shape collection in the toolbox and context toolbox.
Refer to the following section for more information on the requestEditOperation event's parameters: Prohibit Individual Operations.
Prohibit Creating Loops
The example below demonstrates how to prevent users from connecting a shape to itself:
jQuery
$(function() { var diagram = $("#diagram").dxDiagram({ onRequestEditOperation: function(e) { if (e.operation === "changeConnection") if (e.args.connector && e.args.connector.fromId === e.args.connector.toId) e.allowed = false; }, }).dxDiagram("instance"); });
Angular
<dx-diagram #diagram id="diagram" (onRequestEditOperation)="requestEditOperationHandler($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core'; import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { @ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent; requestEditOperationHandler(e) { if (e.operation === "changeConnection") if (e.args.connector && e.args.connector.fromId === e.args.connector.toId) e.allowed = false; } }
Vue
<template> <DxDiagram id="diagram" ref="diagram" @request-edit-operation="onRequestEditOperation"> </DxDiagram> </template> <script> import DxDiagram from 'devextreme-vue/diagram'; export default { components: { DxDiagram, }, methods: { onRequestEditOperation(e) { if (e.operation === "changeConnection") if (e.args.connector && e.args.connector.fromId === e.args.connector.toId) e.allowed = false; }, } }; </script>
React
import React from 'react'; import Diagram from 'devextreme-react/diagram'; class App extends React.Component { constructor(props) { super(props); this.diagramRef = React.createRef(); this.onRequestEditOperation = this.onRequestEditOperation.bind(this); } onRequestEditOperation(e) { if (e.operation === 'changeConnection') if (e.args.connector && e.args.connector.fromId === e.args.connector.toId) e.allowed = false; } render() { return ( <Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation}> </Diagram> ); } } export default App;
Prohibit Adding Shapes Twice
The example below demonstrates how to prevent users from adding more than one shape of each type to a chart:
jQuery
$(function() { var diagram = $("#diagram").dxDiagram({ onRequestEditOperation(e) { if (e.operation === 'addShape') { // Gets types of shapes the chart contains var itemsTypes = e.component.getItems().filter(function(item) { return (item.itemType === "shape") && (item.id !== e.args.shape.id); }).map(a => a.type); // Cancels the operation if the chart contains a shape with the same type as the shape that is about to be added if (itemsTypes.indexOf(e.args.shape.type) !== -1) { e.allowed = false; return; } } }, }).dxDiagram("instance"); });
Angular
<dx-diagram #diagram id="diagram" (onRequestEditOperation)="requestEditOperation($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core'; import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { @ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent; requestEditOperation(e) { if (e.operation === 'addShape') { // Gets types of shapes the chart contains var itemsTypes = e.component.getItems().filter(function(item) { return (item.itemType === "shape") && (item.id !== e.args.shape.id); }).map(a => a.type); // Cancels the operation if the chart contains a shape with the same type as the shape that is about to be added if (itemsTypes.indexOf(e.args.shape.type) !== -1) { e.allowed = false; return; } } } }
Vue
<template> <DxDiagram id="diagram" ref="diagram" @request-edit-operation="onRequestEditOperation"> </DxDiagram> </template> <script> import DxDiagram from 'devextreme-vue/diagram'; export default { components: { DxDiagram }, methods: { onRequestEditOperation(e) { if (e.operation === 'addShape') { // Gets types of shapes the chart contains var itemsTypes = e.component.getItems().filter(function(item) { return (item.itemType === "shape") && (item.id !== e.args.shape.id); }).map(a => a.type); // Cancels the operation if the chart contains a shape with the same type as the shape that is about to be added if (itemsTypes.indexOf(e.args.shape.type) !== -1) { e.allowed = false; return; } } }, } }; </script>
React
import React from 'react'; import Diagram, { ContextToolbox,} from 'devextreme-react/diagram'; var currentShapeId; class App extends React.Component { constructor(props) { super(props); this.diagramRef = React.createRef(); this.onRequestEditOperation = this.onRequestEditOperation.bind(this); } onRequestEditOperation(e) { if (e.operation === 'addShape') { // Gets types of shapes the chart contains var itemsTypes = e.component.getItems().filter(function(item) { return (item.itemType === "shape") && (item.id !== e.args.shape.id); }).map(a => a.type); // Cancels the operation if the chart contains a shape with the same type as the shape that is about to be added if (itemsTypes.indexOf(e.args.shape.type) !== -1) { e.allowed = false; return; } } } render() { return ( <Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation} > </Diagram> ); } } export default App;
See Also
Remove Shapes from Toolboxes
In the example below, the Diagram component updates the shape collection in the toolbox and context toolbox as follows:
- Removes a shape from these toolboxes after a user adds it to a chart
- Returns a shape to these toolboxes after a user deletes it from a chart
jQuery
$(function () { var shapeCount = 0; var diagram = $("#diagram").dxDiagram({ onOptionChanged: function (e) { // Detects changes of the Diagram model if (e.name === "hasChanges" && e.value) { e.component.option("hasChanges", false); var currentShapeCount = e.component.getItems().filter(function(item) { return (item.itemType ==="shape") }).length; // Updates the toolbox and context toolbox if a shape was added or deleted if (shapeCount !== currentShapeCount) { shapeCount = currentShapeCount; window.setTimeout(function() { e.component.updateToolbox(); }, 0); } } }, onRequestEditOperation(e) { if (e.operation === "addShapeFromToolbox") { e.component.getItems().forEach(function(item) { // Removes a shape from the toolboxes if the chart contains a shape of this type if (item.itemType === "shape" && item.type === e.args.shapeType) e.allowed = false; }); } }, }) .dxDiagram("instance"); });
Angular
<dx-diagram #diagram id="diagram" (onOptionChanged)="optionChanged($event)" (onRequestEditOperation)="requestEditOperation($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core'; import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { @ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent; shapeCount: any; optionChanged(e) { // Detects changes of the Diagram model if (e.name === "hasChanges" && e.value) { e.component.option("hasChanges", false); var currentShapeCount = e.component.getItems().filter(function(item) { return (item.itemType ==="shape") }).length; // Updates the toolbox and context toolbox if a shape was added or deleted if (this.shapeCount !== currentShapeCount) { this.shapeCount = currentShapeCount; window.setTimeout(function() { e.component.updateToolbox(); }, 0); } } } requestEditOperation(e) { if (e.operation === "addShapeFromToolbox") { e.component.getItems().forEach(function(item) { // Removes a shape from the toolboxes if the chart contains a shape of this type if (item.itemType === "shape" && item.type === e.args.shapeType) e.allowed = false; }); } } }
Vue
<template> <DxDiagram id="diagram" ref="diagram" @request-edit-operation="onRequestEditOperation" @option-changed="onOptionChanged"> </DxDiagram> </template> <script> import DxDiagram from 'devextreme-vue/diagram'; var shapeCount = 0; export default { components: { DxDiagram }, methods: { onOptionChanged(e) { // Detects changes of the Diagram model if (e.name === "hasChanges" && e.value) { e.component.option("hasChanges", false); var currentShapeCount = e.component.getItems().filter(function(item) { return (item.itemType ==="shape") }).length; // Updates the toolbox and context toolbox if a shape was added or deleted if (this.shapeCount !== currentShapeCount) { this.shapeCount = currentShapeCount; window.setTimeout(function() { e.component.updateToolbox(); }, 0); } } }, onRequestEditOperation(e) { if (e.operation === "addShapeFromToolbox") { e.component.getItems().forEach(function(item) { // Removes a shape from the toolboxes if the chart contains a shape of this type if (item.itemType === "shape" && item.type === e.args.shapeType) e.allowed = false; }); } }, } }; </script>
React
import React from 'react'; import Diagram from 'devextreme-react/diagram'; var shapeCount; class App extends React.Component { constructor(props) { super(props); this.diagramRef = React.createRef(); this.onRequestEditOperation = this.onRequestEditOperation.bind(this); this.onOptionChanged = this.onOptionChanged.bind(this); } onOptionChanged(e) { // Detects changes of the Diagram model if (e.name === "hasChanges" && e.value) { e.component.option("hasChanges", false); var currentShapeCount = e.component.getItems().filter(function(item) { return (item.itemType ==="shape") }).length; // Updates the toolbox and context toolbox if a shape was added or deleted if (this.shapeCount !== currentShapeCount) { this.shapeCount = currentShapeCount; window.setTimeout(function() { e.component.updateToolbox(); }, 0); } } } onRequestEditOperation(e) { if (e.operation === "addShapeFromToolbox") { e.component.getItems().forEach(function(item) { // Removes a shape from the toolboxes if the chart contains a shape of this type if (item.itemType === "shape" && item.type === e.args.shapeType) e.allowed = false; }); } } render() { return ( <Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation} onOptionChanged={this.onOptionChanged}> </Diagram> ); } } export default App;
See Also
Prohibit Moving Shapes Between Containers
The example below demonstrates how to prevent users from moving a shape from one container to another:
jQuery
$(function() { var containerIds = {}; var diagram = $("#diagram").dxDiagram({ onRequestEditOperation: function(e) { if (e.operation === "moveShape") // Cancels the operation if a user moves a shape outside its container. if (containerIds[e.args.shape.id] !== e.args.shape.containerId) e.allowed = false; }, onSelectionChanged: function(e) { e.component.getItems().forEach(item => {containerIds[item.id] = item.containerId;}); }, }).dxDiagram("instance"); });
Angular
<dx-diagram #diagram id="diagram" (onSelectionChanged)="selectionChanged($event)" (onRequestEditOperation)="requestEditOperation($event)"> </dx-diagram>
import { Component, ViewChild} from '@angular/core'; import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { @ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent; containerIds: any = {}; requestEditOperation(e) { if (e.operation === "moveShape") // Cancels the operation if a user moves a shape outside its container. if (this.containerIds[e.args.shape.id] !== e.args.shape.containerId) e.allowed = false; } selectionChanged(e) { e.component.getItems().forEach(item => { this.containerIds[item.id] = item.containerId; }); } }
Vue
<template> <DxDiagram id="diagram" ref="diagram" @request-edit-operation="onRequestEditOperation" @selection-changed="onSelectionChanged"> </DxDiagram> </template> <script> import DxDiagram from 'devextreme-vue/diagram'; var containerIds = {}; export default { components: { DxDiagram }, methods: { onRequestEditOperation(e) { if (e.operation === "moveShape") // Cancels the operation if a user moves a shape outside its container. if (containerIds[e.args.shape.id] !== e.args.shape.containerId) e.allowed = false; }, onSelectionChanged(e) { e.component.getItems().forEach(item => {containerIds[item.id] = item.containerId;}); } }, }; </script>
React
import React from 'react'; import Diagram, from 'devextreme-react/diagram'; var containerIds = {}; class App extends React.Component { constructor(props) { super(props); this.diagramRef = React.createRef(); this.onRequestEditOperation = this.onRequestEditOperation.bind(this); this.onSelectionChanged = this.onSelectionChanged.bind(this); } onRequestEditOperation(e) { if (e.operation === "moveShape") // Cancels the operation if a user moves a shape outside its container. if (containerIds[e.args.shape.id] !== e.args.shape.containerId) e.allowed = false; } onSelectionChanged(e) { e.component.getItems().forEach(item => {containerIds[item.id] = item.containerId;}); } render() { return ( <Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation} onSelectionChanged={this.onSelectionChanged}> </Diagram> ); } } export default App;
Customize Shape Collection in the Context Toolbox
The following example demonstrates how to hide shapes in the context toolbox depending on the connector's start node type:
jQuery
$(function() { var currentShapeId; var diagram = $("#diagram").dxDiagram({ onRequestEditOperation: function(e) { if (e.operation === "changeConnection" && e.args.connector) // Gets the connector's start node identifier this.currentShapeId = e.args.connector.fromId; if (e.operation === "addShapeFromToolbox") { // Gets the connector's start node type var currentShape = e.component.getItemById(this.currentShapeId); if (e.args.shapeType === "terminator") // If the connector's start node type is "decision" if (currentShape && currentShape.type === "decision") // Hides the "terminator" shape in the context toolbox e.allowed = false; } }, contextToolbox: { enabled: true, shapeIconsPerRow: 3, shapes: [ "process", "decision", "terminator" ], }, }) .dxDiagram("instance"); });
Angular
<dx-diagram #diagram id="diagram" (onRequestEditOperation)="requestEditOperation($event)"> <dxo-context-toolbox [enabled]="true" [shapes]='["process", "decision", "terminator"]' [shapeIconsPerRow]="3"> </dxo-context-toolbox> </dx-diagram>
import { Component, ViewChild} from '@angular/core'; import { DxDiagramModule, DxDiagramComponent } from 'devextreme-angular'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { @ViewChild(DxDiagramComponent, { static: false }) diagram: DxDiagramComponent; currentShapeId : number; requestEditOperation(e) { if (e.operation === "changeConnection" && e.args.connector) // Gets the connector's start node identifier this.currentShapeId = e.args.connector.fromId; if (e.operation === "addShapeFromToolbox") { // Gets the connector's start node type var currentShape = e.component.getItemById(this.currentShapeId); if (e.args.shapeType === "terminator") // If the connector's start node type is "decision" if (currentShape && currentShape.type === "decision") // Hides the "terminator" shape in the context toolbox e.allowed = false; } } }
Vue
<template> <DxDiagram id="diagram" ref="diagram" @request-edit-operation="onRequestEditOperation"> <DxContextToolbox :shape-icons-per-row="3" :width="100" :shapes="['process', 'decision', 'terminator']" /> </DxDiagram> </template> <script> import { DxDiagram, DxContextToolbox} from 'devextreme-vue/diagram'; var currentShapeId; export default { components: { DxDiagram, DxContextToolbox }, methods: { onRequestEditOperation(e) { if (e.operation === "changeConnection" && e.args.connector) // Gets the connector's start node identifier this.currentShapeId = e.args.connector.fromId; if (e.operation === "addShapeFromToolbox") { // Gets the connector's start node type var currentShape = e.component.getItemById(this.currentShapeId); if (e.args.shapeType === "terminator") // If the connector's start node type is "decision" if (currentShape && currentShape.type === "decision") // Hides the "terminator" shape in the context toolbox e.allowed = false; } } } }; </script>
React
import React from 'react'; import Diagram, { ContextToolbox,} from 'devextreme-react/diagram'; var currentShapeId; class App extends React.Component { constructor(props) { super(props); this.diagramRef = React.createRef(); this.onRequestEditOperation = this.onRequestEditOperation.bind(this); } onRequestEditOperation(e) { if (e.operation === 'changeConnection' && e.args.connector) // Gets the connector's start node identifier this.currentShapeId = e.args.connector.fromId; if (e.operation === 'addShapeFromToolbox') { // Gets the connector's start node type var currentShape = e.component.getItemById(this.currentShapeId); if (e.args.shapeType === 'terminator') // If the connector's start node type is "decision" if (currentShape && currentShape.type === 'decision') // Hides the "terminator" shape in the context toolbox e.allowed = false; } } render() { return ( <Diagram id="diagram" ref={this.diagramRef} onRequestEditOperation={this.onRequestEditOperation} > <ContextToolbox shapeIconsPerRow={3} width={100} shapes={['process', 'decision', 'terminator']}> </ContextToolbox> </Diagram> ); } } export default App;
If you have technical questions, please create a support ticket in the DevExpress Support Center.