@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.
586 lines (585 loc) • 23.4 kB
JavaScript
import { DiagramElement } from '../core/elements/diagram-element';
import { NodeConstraints, DiagramAction } from '../enum/enum';
import { Node } from '../objects/node';
import { cloneObject, randomId } from './../utility/base-util';
import { TextElement } from '../core/elements/text-element';
import { Canvas } from '../core/containers/canvas';
import { Size } from '../primitives/size';
import { getDiagramElement } from './dom-util';
import { PathElement } from '../core/elements/path-element';
/**
* Container wrapper initialization \
*
* @returns {DiagramElement} initContainerWrapper method. \
* @param {DiagramElement} content - Provide the content value.
* @param {NodeModel} node - Provide the node value.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function initContainerWrapper(content, node, diagram) {
content = getContainerElement(content, node, diagram);
return content;
}
/**
* Apply styles to the element \
*
* @returns {void} setStyle method. \
* @param {DiagramElement} child - Provide the child value.
* @param {Node} node - Provide the node value.
* @private
*/
function setStyle(child, node) {
//set style
child.style.fill = node.style.fill;
child.style.strokeColor = node.style.strokeColor;
child.style.strokeWidth = node.style.strokeWidth;
child.style.strokeDashArray = node.style.strokeDashArray;
child.style.opacity = node.style.opacity;
child.style.gradient = node.style.gradient;
//941052: Issue with visible property doesn't hide shadows
if ((node.constraints & NodeConstraints.Shadow) !== 0 && node.visible) {
child.shadow = node.shadow;
}
}
/**
* Gets the container element for a node \
*
* @returns {GroupableView} getContainerElement method. \
* @param {DiagramElement} content - Provide the content value.
* @param {Node} node - Provide the node value.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
function getContainerElement(content, node, diagram) {
var container = new Canvas();
//set style
setStyle(container, node);
container.id = node.id + '_container';
var size = getSize(node, content);
node.width = size.width;
node.height = size.height;
content.id = node.id + '_content';
content.width = size.width;
content.height = size.height;
setStyle(content, node);
container.children = [content];
if (node.shape.header && node.shape.hasHeader) {
var header = initHeader(diagram, node);
container.children.push(header);
}
var containerShapeObj = (node.shape);
if (node.shape.children && node.shape.children.length > 0) {
for (var i = 0; i < containerShapeObj.children.length; i++) {
var child = diagram.nameTable[containerShapeObj.children[parseInt(i.toString(), 10)]];
if (child && (!child.parentId || child.parentId === node.id)) {
child.parentId = node.id;
if (!child.margin.left) {
child.margin.left = 10;
}
if (!child.margin.top) {
child.margin.top = 10;
}
if (node.shape.header && node.shape.hasHeader && !diagram.isLoading
&& !(diagram.diagramActions & DiagramAction.UndoRedo)) {
child.margin.top += node.shape.header.height;
}
var nodeOffsetX = (node.offsetX - node.width * node.pivot.x) +
(child.margin.left + child.width * child.pivot.x);
var nodeOffsetY = (node.offsetY - node.height * node.pivot.y) +
(child.margin.top + child.height * child.pivot.y);
if (child.offsetX !== nodeOffsetX || child.offsetY !== nodeOffsetY) {
child.offsetX = nodeOffsetX;
child.offsetY = nodeOffsetY;
}
container.children.push(child.wrapper);
updateIndex(diagram, child);
}
}
}
container.width = node.width;
container.height = node.height;
return container;
}
/**
* Computes size for a node based on constraints and content \
*
* @returns {Size} getSize method. \
* @param {Node} node - Provide the node value.
* @param {DiagramElement} content - Provide the content value.
* @private
*/
function getSize(node, content) {
var size = new Size(node.width, node.height);
if (!(content instanceof PathElement)) {
size.width = size.width || 50;
size.height = size.height || 50;
}
if (content.actualSize.width && content.actualSize.height) {
return content.actualSize;
}
if (node.maxWidth !== undefined) {
size.width = Math.min(size.width, node.maxWidth);
}
if (node.maxHeight !== undefined) {
size.height = Math.min(size.height, node.maxHeight);
}
if (node.minWidth !== undefined) {
size.width = Math.max(size.width, node.minWidth);
}
if (node.minHeight !== undefined) {
size.height = Math.max(size.height, node.minHeight);
}
return size;
}
/**
* Updates the size for a container node when modified \
*
* @returns {void} setSizeForContainer method. \
* @param {Node} newObject - Provide the newObject value.
* @param {Node} oldObject - Provide the oldObject value.
* @param {Node} object - Provide the object value.
* @param {Canvas} wrapper - Provide the wrapper value.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function setSizeForContainer(newObject, oldObject, object, wrapper, diagram) {
wrapper.children[0].width = newObject.width;
wrapper.children[0].height = newObject.height;
if (object.shape.header && object.shape.hasHeader) {
var header = diagram.nameTable[object.id + object.shape.header.id];
header.wrapper.children[0].width = newObject.width;
header.wrapper.measure(new Size(header.wrapper.bounds.width, header.wrapper.bounds.height));
header.wrapper.arrange(header.wrapper.desiredSize);
}
}
/**
* Initialize header for container nodes \
*
* @returns {GroupableView} initHeader method. \
* @param {Diagram} diagram - Provide the diagram value.
* @param {NodeModel} object - Provide the object value.
* @private
*/
function initHeader(diagram, object) {
var shape = object.shape;
shape.header.id = shape.header.id || randomId();
var node = {
id: object.id + shape.header.id,
annotations: [cloneObject(shape.header.annotation)],
style: shape.header.style,
offsetX: object.offsetX, offsetY: object.offsetY,
height: shape.header.height,
width: object.width
};
var wrapper = getHeaderWrapper(diagram, object, node, true);
return wrapper;
}
/**
* Retrieves the header wrapper for rendering \
*
* @returns {GroupableView} getHeaderWrapper method. \
* @param {Diagram} diagram - Provide the diagram value.
* @param {NodeModel} parent - Provide the parent value.
* @param {NodeModel} object - Provide the object value.
* @param {boolean} isHeader - Provide the isHeader value.
* @private
*/
function getHeaderWrapper(diagram, parent, object, isHeader) {
var node = new Node(diagram, 'nodes', object, true);
node.parentId = parent.id;
node.isHeader = isHeader;
node.constraints &= ~(NodeConstraints.InConnect | NodeConstraints.OutConnect);
node.constraints |= NodeConstraints.HideThumbs;
diagram.initObject(node);
diagram.nodes.push(node);
if (node.wrapper.children.length > 0) {
for (var i = 0; i < node.wrapper.children.length; i++) {
var child = node.wrapper.children[parseInt(i.toString(), 10)];
if (child instanceof DiagramElement) {
child.isCalculateDesiredSize = false;
}
if (child instanceof TextElement) {
child.canConsiderBounds = false;
}
}
node.wrapper.measure(new Size(undefined, undefined));
node.wrapper.arrange(node.wrapper.desiredSize);
}
node.wrapper.measure(new Size(undefined, undefined));
node.wrapper.arrange(node.wrapper.desiredSize);
return node.wrapper;
}
/**
* Updates the z-index and order of nodes within the diagram \
*
* @returns {void} updateIndex method. \
* @param {Diagram} diagram - Provide the diagram value.
* @param {Node | Connector} source - Provide the source value.
* @private
*/
function updateIndex(diagram, source) {
var childNode = source;
var nodeindex = diagram.getIndex(childNode, childNode.id);
var layerIndex = diagram.layers.indexOf(diagram.commandHandler.getObjectLayer(source.id));
var layer = diagram.layers[parseInt(layerIndex.toString(), 10)];
diagram.nodes.splice(Number(nodeindex), 1);
delete layer.zIndexTable[childNode.zIndex];
childNode.zIndex = layer.objectZIndex + 1;
diagram.nodes.push(childNode);
layer.zIndexTable[childNode.zIndex] = childNode.id;
}
/**
* Manages the drop of a child node into a container node \
*
* @returns {void} dropContainerChild method. \
* @param {Node} target - Provide the target value.
* @param {Node} source - Provide the source value.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function dropContainerChild(target, source, diagram) {
var container = diagram.nameTable[target.id].shape;
// 967788 : selection issue when drag and drop child from palette to container
if (diagram.currentSymbol && target.shape.type === 'Container') {
source.parentId = target.id;
return;
}
container.children = container.children || [];
var redoElement = cloneObject(source);
var sources = diagram.nameTable[source.id].wrapper;
var targetWrapper = diagram.nameTable[target.id].wrapper;
sources.margin.top = (sources.offsetY - (sources.actualSize.height / 2))
- (target.offsetY - (target.actualSize.height / 2));
sources.margin.left = (sources.offsetX - (sources.actualSize.width / 2))
- (target.offsetX - (target.actualSize.width / 2));
diagram.nameTable[source.id].parentId = target.id;
container.children.push(source.id);
targetWrapper.children[0].children.push(diagram.nameTable[source.id].wrapper);
var bound = getChildrenBound(target, source.id, diagram);
adjustContainerSize(bound, source, diagram, false);
if (sources.margin.left < 0) {
sources.margin.left = 10;
}
if (sources.margin.top < 0 || (container.hasHeader && (sources.margin.top < container.header.height))) {
sources.margin.top = 10;
if (container.hasHeader) {
sources.margin.top += container.header.height;
}
}
targetWrapper.measure(new Size(undefined, undefined));
targetWrapper.arrange(targetWrapper.desiredSize);
var obj = cloneObject(source);
var entry = {
type: 'PositionChanged', undoObject: { nodes: [redoElement] },
redoObject: { nodes: [obj] }, category: 'Internal'
};
diagram.addHistoryEntry(entry);
if (diagram.mode === 'SVG') {
if (source.zIndex < target.zIndex) {
diagram.moveSvgNode(source.id);
updateIndex(diagram, source);
}
var parent_1 = getDiagramElement(target.id + '_groupElement');
parent_1.appendChild(getDiagramElement(source.id + '_groupElement'));
}
}
/**
* Computes the bounding rectangle of children nodes excluding a specific child \
*
* @returns {Rect} getChildrenBound method. \
* @param {Node} node - Provide the node value.
* @param {string} excludeChild - Provide the excludeChild value.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function getChildrenBound(node, excludeChild, diagram) {
var children = node.shape.children;
var bound;
if (children && children.length) {
for (var _i = 0; _i < children.length; _i++) {
var i = children[parseInt(_i.toString(), 10)];
if (excludeChild !== i) {
if (!bound) {
bound = diagram.nameTable['' + i].wrapper.bounds;
}
else {
bound = diagram.nameTable['' + i].wrapper.bounds.uniteRect(bound);
}
}
}
}
return bound || diagram.nameTable['' + excludeChild].wrapper.bounds;
}
/**
* Adjusts the container size based on child element bounds \
*
* @returns {void} adjustContainerSize method. \
* @param {Rect} bound - Provide the bound value.
* @param {Node} obj - Provide the obj value.
* @param {Diagram} diagram - Provide the diagram value.
* @param {boolean} isDrag - Provide the isDrag value.
* @private
*/
export function adjustContainerSize(bound, obj, diagram, isDrag) {
var diffX;
var diffY;
var node = diagram.nameTable[obj.parentId];
var pivot = { x: 0.5, y: 0.5 };
var actualSize = node.wrapper.actualSize;
var headerHeight = 0;
if (node.shape.hasHeader) {
headerHeight = node.shape.header.height;
}
if ((node.wrapper.bounds.left + obj.margin.left + obj.width) > (node.wrapper.bounds.right)) {
pivot.x = 0;
diffX = (obj.wrapper.margin.left + obj.wrapper.bounds.width) / actualSize.width;
}
else if (obj.margin.left < 0) {
pivot.x = 1;
diffX = (obj.wrapper.bounds.x + obj.wrapper.bounds.width) / node.wrapper.bounds.x;
}
if ((node.wrapper.bounds.top + obj.margin.top + obj.height) > (node.wrapper.bounds.bottom)) {
pivot.y = 0;
diffY = (obj.wrapper.margin.top + obj.wrapper.bounds.height) / actualSize.height;
}
else if (obj.margin.top < headerHeight && !isDrag) {
pivot.y = 1;
diffY = (obj.wrapper.bounds.y + obj.wrapper.bounds.height) / node.wrapper.bounds.y;
}
if ((diffX > 0 || diffY > 0) && (node.constraints & NodeConstraints.Resize)) {
diagram.commandHandler.scale(diagram.nameTable[obj.parentId], diffX || 1, diffY || 1, pivot);
}
}
/**
* Handles drag operation of a child node within its container \
*
* @returns {void} dragContainerChild method. \
* @param {Node} obj - Provide the obj value.
* @param {Diagram} diagram - Provide the diagram value.
* @param {number} tx - Provide the tx value.
* @param {number} ty - Provide the ty value.
* @private
*/
export function dragContainerChild(obj, diagram, tx, ty) {
var node = diagram.nameTable[(obj).parentId];
var headerHeight = 0;
if (node.shape.hasHeader) {
headerHeight = node.shape.header.height;
}
var newMargin = {
top: (obj.margin.top + ty) - headerHeight >= 0 ? obj.margin.top + ty : obj.margin.top,
left: obj.margin.left + tx >= 0 ? obj.margin.left + tx : obj.margin.left
};
if (newMargin.top !== obj.margin.top || newMargin.left !== obj.margin.left) {
diagram.nodePropertyChange(obj, {}, { margin: newMargin });
}
var bound = getChildrenBound(node, obj.id, diagram);
adjustContainerSize(bound, obj, diagram, true);
node.wrapper.measure(new Size(undefined, undefined));
node.wrapper.arrange(node.wrapper.desiredSize);
diagram.updateSelector();
updateContainerDocks(node, diagram);
}
/**
* Change the child wrapper value in container while drop from symbol palette \
*
* @returns {void} updateChildWrapper method. \
* @param {Node} node - Provide the id of node.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function updateChildWrapper(node, diagram) {
var containerChildren = diagram.nameTable[node.parentId].wrapper.children[0].children;
for (var i = 0; i < containerChildren.length; i++) {
if (containerChildren[parseInt(i.toString(), 10)].id === node.wrapper.id &&
(containerChildren[parseInt(i.toString(), 10)].offsetX === node.wrapper.offsetX ||
containerChildren[parseInt(i.toString(), 10)].offsetY === node.wrapper.offsetY)) {
containerChildren[parseInt(i.toString(), 10)] = node.wrapper;
break;
}
}
}
/**
* Updates connections and dock positions for container's child nodes \
*
* @returns {void} updateContainerDocks method. \
* @param {Node} obj - Provide the obj value.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function updateContainerDocks(obj, diagram) {
var childTable = obj.shape.children;
if (childTable) {
for (var _a = 0, childTable_1 = childTable; _a < childTable_1.length; _a++) {
var i = childTable_1[_a];
var actualObject = diagram.nameTable["" + i];
if (actualObject) {
diagram.updateConnectorEdges(actualObject);
actualObject.wrapper.measure(new Size(actualObject.wrapper.width, actualObject.wrapper.height));
actualObject.wrapper.arrange(actualObject.wrapper.desiredSize);
if (actualObject.shape.children
&& actualObject.shape.children.length) {
updateContainerDocks(actualObject, diagram);
}
}
}
}
else {
return;
}
}
/**
* Removes a child node from its container \
*
* @returns {void} removeChildFromContainer method. \
* @param {Node} currentObj - Provide the currentObj value.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function removeChildFromContainer(currentObj, diagram) {
var element = diagram.nameTable[currentObj.parentId];
if (currentObj.shape.type === 'Container') {
if (currentObj.shape.children && currentObj.shape.children.length > 0) {
var children = currentObj.shape.children;
for (var j = children.length - 1; j >= 0; j--) {
diagram.remove(diagram.nameTable[children[parseInt(j.toString(), 10)]]);
}
}
if (currentObj.shape.hasHeader) {
diagram.remove(diagram.nameTable[currentObj.id + currentObj.shape.header.id]);
}
}
if (element) {
diagram.removeDependentConnector(currentObj);
var children = element.shape.children;
removeGElement(element.wrapper, currentObj.id, diagram, true);
if (children) {
var childIndex = children.indexOf(currentObj.id);
children.splice(childIndex, 1);
}
}
}
/**
* Removes a child element from a container's layout \
*
* @returns {void} removeGElement method. \
* @param {GroupableView} wrapper - Provide the wrapper value.
* @param {string} name - Provide the name value.
* @param {Diagram} diagram - Provide the diagram value.
* @param {boolean} isDelete - Provide the isDelete value.
* @private
*/
export function removeGElement(wrapper, name, diagram, isDelete) {
for (var _a = 0, _b = wrapper.children; _a < _b.length; _a++) {
var i = _b[_a];
if (i.id === name) {
wrapper.children.splice(wrapper.children.indexOf(i), 1);
if (!isDelete) {
var element = document.getElementById(i.id + '_groupElement');
var diagramLayer = document.getElementById(diagram.element.id + '_diagramLayer');
var parent_2 = element.parentElement;
parent_2.removeChild(element);
diagramLayer.appendChild(element);
}
}
else if (i.children) {
removeGElement(i, name, diagram, isDelete);
}
}
}
/**
* Adds a child node to a container \
*
* @returns {void} addContainerChild method. \
* @param {NodeModel} child - Provide the child value.
* @param {string} parentId - Provide the parentId value.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function addContainerChild(child, parentId, diagram) {
var id = child.id;
var node = diagram.nameTable["" + id];
var undoElement = cloneObject(node);
// Add the child to the diagram if it's not already present
if (!node) {
diagram.add(child);
node = diagram.nameTable["" + id];
}
child.parentId = parentId;
var parentContainer = diagram.nameTable["" + parentId];
// Ensure the parent has a children structure
var containerShape = parentContainer.shape;
if (!containerShape.children) {
containerShape.children = [];
}
// Check if the container and parent are correctly configured
if (node && parentContainer) {
node.parentId = parentId;
if (node.margin.top <= 0 || (parentContainer.shape.hasHeader &&
node.margin.top < parentContainer.shape.header.height)) {
node.margin.top = 10;
if (parentContainer.shape.hasHeader) {
node.margin.top += parentContainer.shape.header.height;
}
}
if (node.margin.left <= 0) {
node.margin.left = 10;
}
var nodeOffsetX = (parentContainer.offsetX - parentContainer.width * parentContainer.pivot.x) +
(node.margin.left + node.width * node.pivot.x);
var nodeOffsetY = (parentContainer.offsetY - parentContainer.height * parentContainer.pivot.y) +
(node.margin.top + node.height * node.pivot.y);
if (node.offsetX !== nodeOffsetX || node.offsetY !== nodeOffsetY) {
node.offsetX = nodeOffsetX;
node.offsetY = nodeOffsetY;
}
var children = containerShape.children;
// Add the container id to the parent's children list if not already present
if (children.indexOf(id) < 0) {
children.push(id);
}
// Append the container wrapper to the parent's wrapper and measure layout
parentContainer.wrapper.children.push(node.wrapper);
parentContainer.wrapper.measure(new Size());
parentContainer.wrapper.arrange(parentContainer.wrapper.desiredSize);
diagram.refreshDiagramLayer();
// Record the action in history for undo/redo functionality
if (!(diagram.diagramActions & DiagramAction.UndoRedo) && (!diagram.historyManager.currentEntry ||
diagram.historyManager.currentEntry.type !== 'CollectionChanged')) {
var obj = cloneObject(node);
var entry = {
type: 'PositionChanged',
undoObject: { nodes: [undoElement] },
redoObject: { nodes: [obj] },
category: 'Internal'
};
diagram.addHistoryEntry(entry);
}
// If using SVG, configure DOM accordingly
if (diagram.mode === 'SVG') {
var childElement = getDiagramElement(parentId + '_groupElement');
childElement.appendChild(getDiagramElement(child.id + '_groupElement'));
}
}
}
/**
* Removes a child node from its parent container while undo \
*
* @returns {void} removeChild method. \
* @param {Node} node - Provide the id of node.
* @param {Diagram} diagram - Provide the diagram value.
* @private
*/
export function removeChild(node, diagram) {
var id = node.id;
var parent = diagram.nameTable[node.parentId];
if (parent) {
var children = parent.shape.children;
removeGElement(parent.wrapper, id, diagram, true);
var childrenIndex = children.indexOf(id);
children.splice(childrenIndex, 1);
node.parentId = '';
diagram.refreshDiagramLayer();
diagram.updateSelector();
}
}