UNPKG

@syncfusion/ej2-diagrams

Version:

Feature-rich diagram control to create diagrams like flow charts, organizational charts, mind maps, and BPMN diagrams. Its rich feature set includes built-in shapes, editing, serializing, exporting, printing, overview, data binding, and automatic layouts.

545 lines (544 loc) 24.3 kB
import { Node } from '../objects/node'; import { Connector } from '../objects/connector'; import { DataManager, Query } from '@syncfusion/ej2-data'; import { randomId, getFunction } from '../utility/base-util'; import { cloneBlazorObject } from '../utility/diagram-util'; import { updateDefaultValues } from '../utility/diagram-util'; import { isBlazor } from '@syncfusion/ej2-base'; /** * data source defines the basic unit of diagram */ var DataBinding = /** @class */ (function () { /** * Constructor for the data binding module. * @private */ function DataBinding() { /** @private */ this.dataTable = {}; //constructs the data binding module } /** * To destroy the data binding module * * @returns {void} * @private */ DataBinding.prototype.destroy = function () { /** * Destroy method performed here */ }; /** * Core method to return the component name. * * @returns {string} Core method to return the component name. * @private */ DataBinding.prototype.getModuleName = function () { /** * Returns the module name */ return 'DataBinding'; }; /** * Initialize nodes and connectors when we have a data as JSON * * @param {DataSourceModel} data * @param {Diagram} diagram * @private */ DataBinding.prototype.initData = function (data, diagram) { var dataSource; var dataProp = 'data'; var jsonProp = 'json'; var dataManager = data.dataManager || data.dataSource || {}; dataSource = dataManager["" + dataProp] || dataManager["" + jsonProp] || (dataManager.dataSource ? dataManager.dataSource.json : undefined); if (dataSource && dataSource.length === 0 && dataManager.dataSource.data) { dataSource = dataManager.dataSource.data; } if (dataSource && dataSource.length) { this.applyDataSource(data, dataSource, diagram); diagram.trigger('dataLoaded', { diagram: (isBlazor()) ? null : cloneBlazorObject(diagram) }); } }; /** * Initialize nodes and connector when we have a data as remote url * * @param {DataSourceModel} data * @param {Diagram} diagram * @private */ DataBinding.prototype.initSource = function (data, diagram) { var _this = this; var dataSource = data; var result; var mapper = data; if (dataSource.dataManager instanceof DataManager || dataSource.dataSource instanceof DataManager) { var tempObj = mapper.dataManager || mapper.dataSource; var query = tempObj.defaultQuery || new Query(); var dataManager = data.dataManager || data.dataSource; dataManager.executeQuery(query).then(function (e) { var prop = 'result'; result = e["" + prop]; if (!diagram.isDestroyed) { diagram.protectPropertyChange(true); _this.applyDataSource(data, result, diagram); diagram.refreshDiagram(); diagram.protectPropertyChange(false); diagram.trigger('dataLoaded', { diagram: (isBlazor()) ? null : cloneBlazorObject(diagram) }); } }); } }; DataBinding.prototype.applyDataSource = function (mapper, data, diagram) { this.dataTable = {}; var obj; var firstNode; var node; var rootNodes = []; var firstLevel = []; var item; var nextLevel; if (data !== undefined) { for (var r = 0; r < data.length; r++) { obj = data[parseInt(r.toString(), 10)]; //832886 - Rendering layout without case sensitivity if (obj[mapper.parentId] === undefined || obj[mapper.parentId] === null || typeof obj[mapper.parentId] !== 'object' || obj[mapper.parentId].length === 0) { if (isNaN(obj[mapper.parentId]) && obj[mapper.parentId] !== undefined) { if (rootNodes[obj[mapper.parentId] ? obj[mapper.parentId].toLowerCase() : obj[mapper.parentId]] !== undefined) { rootNodes[obj[mapper.parentId].toLowerCase()].items.push(obj); } else { rootNodes[obj[mapper.parentId] ? obj[mapper.parentId].toLowerCase() : obj[mapper.parentId]] = { items: [obj] }; } } else { if (rootNodes[obj[mapper.parentId]] !== undefined) { rootNodes[obj[mapper.parentId]].items.push(obj); } else { rootNodes[obj[mapper.parentId]] = { items: [obj] }; } } } else { rootNodes = this.updateMultipleRootNodes(obj, rootNodes, mapper, data); } if (mapper.root && isNaN(mapper.root) && obj[mapper.id] && isNaN(obj[mapper.id])) { if ((mapper.root).toLowerCase() === obj[mapper.id].toLowerCase()) { firstNode = { items: [obj] }; } } else { if (mapper.root === obj[mapper.id]) { firstNode = { items: [obj] }; } } } if (firstNode) { firstLevel.push(firstNode); } else { for (var _i = 0, _a = Object.keys(rootNodes); _i < _a.length; _i++) { var n = _a[_i]; if (!n || n === 'undefined' || n === '\'\'' || n === 'null') { firstLevel.push(rootNodes["" + n]); } } } for (var i = 0; i < firstLevel.length; i++) { for (var j = 0; j < firstLevel[parseInt(i.toString(), 10)].items.length; j++) { item = firstLevel[parseInt(i.toString(), 10)].items[parseInt(j.toString(), 10)]; node = this.applyNodeTemplate(mapper, item, diagram); diagram.nodes.push(node); if (isNaN(item[mapper.id]) && item[mapper.id] !== undefined) { this.dataTable[item[mapper.id].toLowerCase()] = node; } else { this.dataTable[item[mapper.id]] = node; } if (isNaN(node.data[mapper.id]) && node.data[mapper.id] !== undefined) { nextLevel = rootNodes[node.data[mapper.id].toLowerCase()]; } else { nextLevel = rootNodes[node.data[mapper.id]]; } if (nextLevel !== undefined) { this.renderChildNodes(mapper, nextLevel, node.id, rootNodes, diagram); } } } } this.dataTable = null; }; /** * updateMultipleRootNodes method is used to update the multiple Root Nodes * * @param {Object} object * @param {Object[]} rootnodes * @param {DataSourceModel} mapper * @param {Object[]} data */ DataBinding.prototype.updateMultipleRootNodes = function (obj, rootNodes, mapper, data) { var parents = obj[mapper.parentId]; var parent; for (var i = 0; i < parents.length; i++) { if (parents[parseInt(i.toString(), 10)]) { if (isNaN(parents[parseInt(i.toString(), 10)])) { parent = (parents[parseInt(i.toString(), 10)]).toLowerCase(); } else { parent = (parents[parseInt(i.toString(), 10)]); } if (rootNodes["" + parent]) { rootNodes["" + parent].items.push(obj); } else { rootNodes["" + parent] = { items: [obj] }; } } else { parent = parents[parseInt(i.toString(), 10)]; if (rootNodes["" + parent]) { rootNodes["" + parent].items.push(obj); } else { rootNodes["" + parent] = { items: [obj] }; } } } return rootNodes; }; /** * Get the node values\ * * @returns { Node } Get the node values.\ * @param {DataSourceModel} mapper - provide the id value. * @param {Object} item - provide the id value. * @param {Diagram} diagram - provide the id value. * * @private */ DataBinding.prototype.applyNodeTemplate = function (mapper, item, diagram) { //const root: Object = item; var id = randomId(); //const blazor: string = 'Blazor'; var nodeModel = { id: id, data: item }; //Task 895538: Flow-chart layout support for EJ2 diagram. //Added below code to set node shape and style based on the data. if (diagram.layout.type === 'Flowchart') { var shape = this.getFlowChartNodeShape(item); var style = { fill: item.color ? item.color : 'white', strokeColor: item.stroke ? item.stroke : 'black', strokeWidth: item.strokeWidth ? item.strokeWidth : 1 }; nodeModel.shape = shape; nodeModel.style = style; nodeModel.annotations = [{ content: item.name ? item.name : '' }]; } // eslint-disable-next-line @typescript-eslint/ban-types var doBinding = getFunction(mapper.doBinding); if (doBinding) { doBinding(nodeModel, item, diagram); } var obj = new Node(diagram, 'nodes', nodeModel, true); updateDefaultValues(obj, nodeModel, diagram.nodeDefaults); if (mapper.dataMapSettings) { var index = void 0; var arrayProperty = []; var innerProperty = []; for (var i = 0; i < mapper.dataMapSettings.length; i++) { if (mapper.dataMapSettings[parseInt(i.toString(), 10)].property.indexOf('.') !== -1) { innerProperty = this.splitString(mapper.dataMapSettings[parseInt(i.toString(), 10)].property); for (var p = 0; p < innerProperty.length; p++) { if (innerProperty[parseInt(p.toString(), 10)].indexOf('[') !== -1) { index = innerProperty[parseInt(p.toString(), 10)].indexOf('['); arrayProperty = innerProperty[parseInt(p.toString(), 10)].split('['); } } if (index) { if (innerProperty[2]) { obj[arrayProperty[0]][innerProperty[0].charAt(index + 1)][innerProperty[1]][innerProperty[2]] = item[mapper.dataMapSettings[parseInt(i.toString(), 10)].field]; } else { var value = item[mapper.dataMapSettings[parseInt(i.toString(), 10)].field]; obj[arrayProperty[0]][innerProperty[0].charAt(index + 1)][innerProperty[1]] = value; } } else { if (innerProperty[2]) { obj[innerProperty[0]][innerProperty[1]][innerProperty[2]] = item[mapper.dataMapSettings[parseInt(i.toString(), 10)].field]; } else { obj[innerProperty[0]][innerProperty[1]] = item[mapper.dataMapSettings[parseInt(i.toString(), 10)].field]; } } } else { var property = mapper.dataMapSettings[parseInt(i.toString(), 10)].property; property = property.charAt(0).toLowerCase() + property.slice(1); obj["" + property] = item[mapper.dataMapSettings[parseInt(i.toString(), 10)].field]; } index = 0; arrayProperty = []; innerProperty = []; } } if (!this.collectionContains(obj, diagram, mapper.id, mapper.parentId)) { return obj; } else { if (item[mapper.id] && isNaN(item[mapper.id])) { return this.dataTable[item[mapper.id].toLowerCase()]; } else { return this.dataTable[item[mapper.id]]; } } }; DataBinding.prototype.getFlowChartNodeShape = function (data) { if (data.shape !== '') { switch (data.shape) { case 'Rectangle': return { type: 'Basic', shape: 'Rectangle' }; case 'Decision': return { type: 'Flow', shape: 'Decision' }; case 'Hexagon': return { type: 'Path', data: 'M 0 0 L 2 -2 L 11 -2 L 13 0 L 11 2 L 2 2 L 0 0' }; case 'Ellipse': return { type: 'Basic', shape: 'Ellipse' }; case 'Terminator': return { type: 'Flow', shape: 'Terminator' }; case 'PredefinedProcess': return { type: 'Flow', shape: 'PreDefinedProcess' }; case 'Parallelogram': return { type: 'Basic', shape: 'Parallelogram' }; case 'ParallelogramAlt': return { type: 'Path', data: 'M 0 0 L 12 0 L 14 2 L 2 2 L 0 0' }; case 'Trapezoid': return { type: 'Path', data: 'M 0 0 L 1 -1 L 5 -1 L 6 0 L 0 0' }; case 'TrapezoidAlt': return { type: 'Path', data: 'M 0 0 L 5 0 L 4 1 L 1 1 L 0 0' }; case 'DataSource': return { type: 'Path', data: 'M 0 1 L 0 6 C 2 7 4 7 6 6 L 6 1 C 5 0 1 0 0 1 C 1 2 5 2 6 1' }; case 'Asymmetric': return { type: 'Path', data: 'M 0 0 L 8 0 L 8 2 L 0 2 L 2 1 L 0 0' }; case 'DoubleCircle': return { type: 'Path', data: 'M 0 0 A 1 1 0 0 0 7 0 A 1 1 0 0 0 0 0 M -1 0 A 1 1 0 0 0 8 0 A 1 1 0 0 0 -1 0' }; case 'Document': return { type: 'Flow', shape: 'Document' }; case 'PaperTap': return { type: 'Flow', shape: 'PaperTap' }; case 'DirectData': return { type: 'Flow', shape: 'DirectData' }; case 'SequentialData': return { type: 'Flow', shape: 'SequentialData' }; case 'Sort': return { type: 'Flow', shape: 'Sort' }; case 'MultiDocument': return { type: 'Flow', shape: 'MultiDocument' }; case 'Collate': return { type: 'Flow', shape: 'Collate' }; case 'SummingJunction': return { type: 'Flow', shape: 'SummingJunction' }; case 'Or': return { type: 'Flow', shape: 'Or' }; case 'InternalStorage': return { type: 'Flow', shape: 'InternalStorage' }; case 'Extract': return { type: 'Flow', shape: 'Extract' }; case 'ManualOperation': return { type: 'Flow', shape: 'ManualOperation' }; case 'Merge': return { type: 'Flow', shape: 'Merge' }; case 'OffPageReference': return { type: 'Flow', shape: 'OffPageReference' }; case 'SequentialAccessStorage': return { type: 'Flow', shape: 'SequentialAccessStorage' }; case 'Data': return { type: 'Flow', shape: 'Data' }; case 'Card': return { type: 'Flow', shape: 'Card' }; case 'Delay': return { type: 'Flow', shape: 'Delay' }; case 'Preparation': return { type: 'Flow', shape: 'Preparation' }; case 'Display': return { type: 'Flow', shape: 'Display' }; case 'ManualInput': return { type: 'Flow', shape: 'ManualInput' }; case 'LoopLimit': return { type: 'Flow', shape: 'LoopLimit' }; case 'StoredData': return { type: 'Flow', shape: 'StoredData' }; case 'Annotation': return { type: 'Flow', shape: 'Annotation' }; case 'Annotation2': return { type: 'Flow', shape: 'Annotation2' }; default: return { type: 'Flow', shape: 'Process' }; } } return { type: 'Flow', shape: 'Process' }; }; DataBinding.prototype.splitString = function (property) { var temp = []; temp = property.split('.'); for (var i = 0; i < temp.length; i++) { temp[parseInt(i.toString(), 10)] = temp[parseInt(i.toString(), 10)].charAt(0).toLowerCase() + temp[parseInt(i.toString(), 10)].slice(1); } return temp; }; DataBinding.prototype.renderChildNodes = function (mapper, parent, value, rtNodes, diagram, connectorLookup) { var child; var nextLevel; var node; if (!connectorLookup) { connectorLookup = new Set(diagram.connectors .filter(function (connector) { return connector.sourceID && connector.targetID; }) .map(function (connector) { return connector.sourceID + "->" + connector.targetID; })); } for (var j = 0; j < parent.items.length; j++) { child = parent.items[parseInt(j.toString(), 10)]; if (!child[mapper.id]) { continue; } node = this.applyNodeTemplate(mapper, child, diagram); var canBreak = false; if (!this.collectionContains(node, diagram, mapper.id, mapper.parentId)) { if (child[mapper.id] && isNaN(child[mapper.id])) { this.dataTable[child[mapper.id].toLowerCase()] = node; } else { this.dataTable[child[mapper.id]] = node; } diagram.nodes.push(node); } else { canBreak = true; } if (!this.containsConnector(diagram, value, node.id, connectorLookup)) { var conObj = this.applyConnectorTemplate(value, node.id, diagram); diagram.connectors.push(conObj); connectorLookup.add(conObj.sourceID + "->" + conObj.targetID); } if (!canBreak) { if (node.data[mapper.id] && isNaN(node.data[mapper.id])) { nextLevel = rtNodes[node.data[mapper.id].toLowerCase()]; } else { nextLevel = rtNodes[node.data[mapper.id]]; } if (nextLevel !== undefined) { this.renderChildNodes(mapper, nextLevel, node.id, rtNodes, diagram, connectorLookup); } } } }; // Bug 832897: Need to improve performance while rendering layout with large number of nodes. // performance improvement- Replaced array.some with Set() to improve performance. DataBinding.prototype.containsConnector = function (diagram, sourceNode, targetNode, connectorLookup) { return sourceNode && targetNode && connectorLookup.has(sourceNode + "->" + targetNode); }; /** * collectionContains method is used to check wthear the node is already present in collection or not * * @param {Node} node * @param {Diagram} diagram * @param {string} id * @param {string} parentId */ DataBinding.prototype.collectionContains = function (node, diagram, id, parentId) { var obj; if (isNaN(node.data["" + id]) && node.data["" + id]) { obj = this.dataTable[node.data["" + id].toLowerCase()]; } else { obj = this.dataTable[node.data["" + id]]; } if (obj !== undefined && obj.data["" + id] === node.data["" + id] && obj.data["" + parentId] === node.data["" + parentId]) { return true; } else { return false; } }; /** * Get the Connector values * * @param {string} sNode * @param {string} tNode * @param {Diagram} diagram */ DataBinding.prototype.applyConnectorTemplate = function (sNode, tNode, diagram) { var connModel = { id: randomId(), sourceID: sNode, targetID: tNode }; var arrowType; //Task 895538: Flow-chart layout support for EJ2 diagram. //Added below code to set connector annotation and style based on the data. if (diagram.layout.type === 'Flowchart') { var targetNode = diagram.nodes.find(function (node) { return node.id === tNode; }); if (typeof targetNode.data.label === 'string') { connModel.annotations = [{ content: targetNode.data.label }]; } else if (Array.isArray(targetNode.data.label)) { var inConnectors = diagram.connectors.filter(function (connector) { return connector.targetID === tNode; }); var index = 0; if (inConnectors.length > 0) { index = inConnectors.length; } connModel.annotations = [{ content: targetNode.data.label[parseInt(index.toString(), 10)] }]; } arrowType = this.getConnectorArrowType(targetNode.data); } var obj = new Connector(diagram, 'connectors', connModel, true); if (arrowType) { obj.style.strokeWidth = arrowType.strokeWidth; obj.targetDecorator.shape = arrowType.targetDecorator; } updateDefaultValues(obj, connModel, diagram.connectorDefaults); return obj; }; DataBinding.prototype.getConnectorArrowType = function (data) { if (data.arrowType !== '') { switch (data.arrowType) { case 'None': return { targetDecorator: 'None', strokeWidth: 1 }; case 'Arrow': return { targetDecorator: 'Arrow', strokeWidth: 1 }; case 'Diamond': return { targetDecorator: 'Diamond', strokeWidth: 1 }; case 'Circle': return { targetDecorator: 'Circle', strokeWidth: 1 }; case 'OpenArrow': return { targetDecorator: 'OpenArrow', strokeWidth: 1 }; case 'Square': return { targetDecorator: 'Square', strokeWidth: 1 }; case 'Fletch': return { targetDecorator: 'Fletch', strokeWidth: 1 }; case 'OpenFetch': return { targetDecorator: 'OpenFetch', strokeWidth: 1 }; case 'IndentedArrow': return { targetDecorator: 'IndentedArrow', strokeWidth: 1 }; case 'OutdentedArrow': return { targetDecorator: 'OutdentedArrow', strokeWidth: 1 }; case 'DoubleArrow': return { targetDecorator: 'DoubleArrow', strokeWidth: 1 }; default: return { targetDecorator: 'Arrow', strokeWidth: 1 }; } } else { return { targetDecorator: 'Arrow', strokeWidth: 1 }; } }; return DataBinding; }()); export { DataBinding };