var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { Point, Rectangle, Angle } from '@antv/x6-geometry'; import { StringExt, ObjectExt, NumberExt, Interp, } from '@antv/x6-common'; import { Registry } from '../registry/registry'; import { Markup } from '../view/markup'; import { Cell } from './cell'; import { ShareRegistry } from './registry'; import { PortManager } from './port'; export class Node extends Cell { get [Symbol.toStringTag]() { return Node.toStringTag; } constructor(metadata = {}) { super(metadata); this.initPorts(); } preprocess(metadata, ignoreIdCheck) { const { x, y, width, height } = metadata, others = __rest(metadata, ["x", "y", "width", "height"]); if (x != null || y != null) { const position = others.position; others.position = Object.assign(Object.assign({}, position), { x: x != null ? x : position ? position.x : 0, y: y != null ? y : position ? position.y : 0 }); } if (width != null || height != null) { const size = others.size; others.size = Object.assign(Object.assign({}, size), { width: width != null ? width : size ? size.width : 0, height: height != null ? height : size ? size.height : 0 }); } return super.preprocess(others, ignoreIdCheck); } isNode() { return true; } size(width, height, options) { if (width === undefined) { return this.getSize(); } if (typeof width === 'number') { return this.setSize(width, height, options); } return this.setSize(width, height); } getSize() { const size = this.store.get('size'); return size ? Object.assign({}, size) : { width: 1, height: 1 }; } setSize(width, height, options) { if (typeof width === 'object') { this.resize(width.width, width.height, height); } else { this.resize(width, height, options); } return this; } resize(width, height, options = {}) { this.startBatch('resize', options); const direction = options.direction; if (direction) { const currentSize = this.getSize(); switch (direction) { case 'left': case 'right': // Don't change height when resizing horizontally. height = currentSize.height; // eslint-disable-line break; case 'top': case 'bottom': // Don't change width when resizing vertically. width = currentSize.width; // eslint-disable-line break; default: break; } const map = { right: 0, 'top-right': 0, top: 1, 'top-left': 1, left: 2, 'bottom-left': 2, bottom: 3, 'bottom-right': 3, }; let quadrant = map[direction]; const angle = Angle.normalize(this.getAngle() || 0); if (options.absolute) { // We are taking the node's rotation into account quadrant += Math.floor((angle + 45) / 90); quadrant %= 4; } // This is a rectangle in size of the un-rotated node. const bbox = this.getBBox(); // Pick the corner point on the node, which meant to stay on its // place before and after the rotation. let fixedPoint; if (quadrant === 0) { fixedPoint = bbox.getBottomLeft(); } else if (quadrant === 1) { fixedPoint = bbox.getCorner(); } else if (quadrant === 2) { fixedPoint = bbox.getTopRight(); } else { fixedPoint = bbox.getOrigin(); } // Find an image of the previous indent point. This is the position, // where is the point actually located on the screen. const imageFixedPoint = fixedPoint .clone() .rotate(-angle, bbox.getCenter()); // Every point on the element rotates around a circle with the centre of // rotation in the middle of the element while the whole element is being // rotated. That means that the distance from a point in the corner of // the element (supposed its always rect) to the center of the element // doesn't change during the rotation and therefore it equals to a // distance on un-rotated element. // We can find the distance as DISTANCE = (ELEMENTWIDTH/2)^2 + (ELEMENTHEIGHT/2)^2)^0.5. const radius = Math.sqrt(width * width + height * height) / 2; // Now we are looking for an angle between x-axis and the line starting // at image of fixed point and ending at the center of the element. // We call this angle `alpha`. // The image of a fixed point is located in n-th quadrant. For each // quadrant passed going anti-clockwise we have to add 90 degrees. // Note that the first quadrant has index 0. // // 3 | 2 // --c-- Quadrant positions around the element's center `c` // 0 | 1 // let alpha = (quadrant * Math.PI) / 2; // Add an angle between the beginning of the current quadrant (line // parallel with x-axis or y-axis going through the center of the // element) and line crossing the indent of the fixed point and the // center of the element. This is the angle we need but on the // un-rotated element. alpha += Math.atan(quadrant % 2 === 0 ? height / width : width / height); // Lastly we have to deduct the original angle the element was rotated // by and that's it. alpha -= Angle.toRad(angle); // With this angle and distance we can easily calculate the centre of // the un-rotated element. // Note that fromPolar constructor accepts an angle in radians. const center = Point.fromPolar(radius, alpha, imageFixedPoint); // The top left corner on the un-rotated element has to be half a width // on the left and half a height to the top from the center. This will // be the origin of rectangle we were looking for. const origin = center.clone().translate(width / -2, height / -2); this.store.set('size', { width, height }, options); this.setPosition(origin.x, origin.y, options); } else { this.store.set('size', { width, height }, options); } this.stopBatch('resize', options); return this; } scale(sx, sy, origin, options = {}) { const scaledBBox = this.getBBox().scale(sx, sy, origin == null ? undefined : origin); this.startBatch('scale', options); this.setPosition(scaledBBox.x, scaledBBox.y, options); this.resize(scaledBBox.width, scaledBBox.height, options); this.stopBatch('scale'); return this; } position(arg0, arg1, arg2) { if (typeof arg0 === 'number') { return this.setPosition(arg0, arg1, arg2); } return this.getPosition(arg0); } getPosition(options = {}) { if (options.relative) { const parent = this.getParent(); if (parent != null && parent.isNode()) { const currentPosition = this.getPosition(); const parentPosition = parent.getPosition(); return { x: currentPosition.x - parentPosition.x, y: currentPosition.y - parentPosition.y, }; } } const pos = this.store.get('position'); return pos ? Object.assign({}, pos) : { x: 0, y: 0 }; } setPosition(arg0, arg1, arg2 = {}) { let x; let y; let options; if (typeof arg0 === 'object') { x = arg0.x; y = arg0.y; options = arg1 || {}; } else { x = arg0; y = arg1; options = arg2 || {}; } if (options.relative) { const parent = this.getParent(); if (parent != null && parent.isNode()) { const parentPosition = parent.getPosition(); x += parentPosition.x; y += parentPosition.y; } } if (options.deep) { const currentPosition = this.getPosition(); this.translate(x - currentPosition.x, y - currentPosition.y, options); } else { this.store.set('position', { x, y }, options); } return this; } translate(tx = 0, ty = 0, options = {}) { if (tx === 0 && ty === 0) { return this; } // Pass the initiator of the translation. options.translateBy = options.translateBy || this.id; const position = this.getPosition(); if (options.restrict != null && options.translateBy === this.id) { // We are restricting the translation for the element itself only. We get // the bounding box of the element including all its embeds. // All embeds have to be translated the exact same way as the element. const bbox = this.getBBox({ deep: true }); const ra = options.restrict; // - - - - - - - - - - - - -> ra.x + ra.width // - - - -> position.x | // -> bbox.x // ▓▓▓▓▓▓▓ | // ░░░░░░░▓▓▓▓▓▓▓ // ░░░░░░░░░ | // ▓▓▓▓▓▓▓▓░░░░░░░ // ▓▓▓▓▓▓▓▓ | // <-dx-> | restricted area right border // <-width-> | ░ translated element // <- - bbox.width - -> ▓ embedded element const dx = position.x - bbox.x; const dy = position.y - bbox.y; // Find the maximal/minimal coordinates that the element can be translated // while complies the restrictions. const x = Math.max(ra.x + dx, Math.min(ra.x + ra.width + dx - bbox.width, position.x + tx)); const y = Math.max(ra.y + dy, Math.min(ra.y + ra.height + dy - bbox.height, position.y + ty)); // recalculate the translation taking the restrictions into account. tx = x - position.x; // eslint-disable-line ty = y - position.y; // eslint-disable-line } const translatedPosition = { x: position.x + tx, y: position.y + ty, }; // To find out by how much an element was translated in event // 'change:position' handlers. options.tx = tx; options.ty = ty; if (options.transition) { if (typeof options.transition !== 'object') { options.transition = {}; } this.transition('position', translatedPosition, Object.assign(Object.assign({}, options.transition), { interp: Interp.object })); this.eachChild((child) => { var _a; const excluded = (_a = options.exclude) === null || _a === void 0 ? void 0 : _a.includes(child); if (!excluded) { child.translate(tx, ty, options); } }); } else { this.startBatch('translate', options); this.store.set('position', translatedPosition, options); this.eachChild((child) => { var _a; const excluded = (_a = options.exclude) === null || _a === void 0 ? void 0 : _a.includes(child); if (!excluded) { child.translate(tx, ty, options); } }); this.stopBatch('translate', options); } return this; } angle(val, options) { if (val == null) { return this.getAngle(); } return this.rotate(val, options); } getAngle() { return this.store.get('angle', 0); } rotate(angle, options = {}) { const currentAngle = this.getAngle(); if (options.center) { const size = this.getSize(); const position = this.getPosition(); const center = this.getBBox().getCenter(); center.rotate(currentAngle - angle, options.center); const dx = center.x - size.width / 2 - position.x; const dy = center.y - size.height / 2 - position.y; this.startBatch('rotate', { angle, options }); this.setPosition(position.x + dx, position.y + dy, options); this.rotate(angle, Object.assign(Object.assign({}, options), { center: null })); this.stopBatch('rotate'); } else { this.store.set('angle', options.absolute ? angle : (currentAngle + angle) % 360, options); } return this; } // #endregion // #region common getBBox(options = {}) { if (options.deep) { const cells = this.getDescendants({ deep: true, breadthFirst: true }); cells.push(this); return Cell.getCellsBBox(cells); } return Rectangle.fromPositionAndSize(this.getPosition(), this.getSize()); } getConnectionPoint(edge, type) { const bbox = this.getBBox(); const center = bbox.getCenter(); const terminal = edge.getTerminal(type); if (terminal == null) { return center; } const portId = terminal.port; if (!portId || !this.hasPort(portId)) { return center; } const port = this.getPort(portId); if (!port || !port.group) { return center; } const layouts = this.getPortsPosition(port.group); const position = layouts[portId].position; const portCenter = Point.create(position).translate(bbox.getOrigin()); const angle = this.getAngle(); if (angle) { portCenter.rotate(-angle, center); } return portCenter; } /** * Sets cell's size and position based on the children bbox and given padding. */ fit(options = {}) { const children = this.getChildren() || []; const embeds = children.filter((cell) => cell.isNode()); if (embeds.length === 0) { return this; } this.startBatch('fit-embeds', options); if (options.deep) { embeds.forEach((cell) => cell.fit(options)); } let { x, y, width, height } = Cell.getCellsBBox(embeds); const padding = NumberExt.normalizeSides(options.padding); x -= padding.left; y -= padding.top; width += padding.left + padding.right; height += padding.bottom + padding.top; this.store.set({ position: { x, y }, size: { width, height }, }, options); this.stopBatch('fit-embeds'); return this; } // #endregion // #region ports get portContainerMarkup() { return this.getPortContainerMarkup(); } set portContainerMarkup(markup) { this.setPortContainerMarkup(markup); } getDefaultPortContainerMarkup() { return (this.store.get('defaultPortContainerMarkup') || Markup.getPortContainerMarkup()); } getPortContainerMarkup() { return (this.store.get('portContainerMarkup') || this.getDefaultPortContainerMarkup()); } setPortContainerMarkup(markup, options = {}) { this.store.set('portContainerMarkup', Markup.clone(markup), options); return this; } get portMarkup() { return this.getPortMarkup(); } set portMarkup(markup) { this.setPortMarkup(markup); } getDefaultPortMarkup() { return this.store.get('defaultPortMarkup') || Markup.getPortMarkup(); } getPortMarkup() { return this.store.get('portMarkup') || this.getDefaultPortMarkup(); } setPortMarkup(markup, options = {}) { this.store.set('portMarkup', Markup.clone(markup), options); return this; } get portLabelMarkup() { return this.getPortLabelMarkup(); } set portLabelMarkup(markup) { this.setPortLabelMarkup(markup); } getDefaultPortLabelMarkup() { return (this.store.get('defaultPortLabelMarkup') || Markup.getPortLabelMarkup()); } getPortLabelMarkup() { return this.store.get('portLabelMarkup') || this.getDefaultPortLabelMarkup(); } setPortLabelMarkup(markup, options = {}) { this.store.set('portLabelMarkup', Markup.clone(markup), options); return this; } get ports() { const res = this.store.get('ports', { items: [] }); if (res.items == null) { res.items = []; } return res; } getPorts() { return ObjectExt.cloneDeep(this.ports.items); } getPortsByGroup(groupName) { return this.getPorts().filter((port) => port.group === groupName); } getPort(portId) { return ObjectExt.cloneDeep(this.ports.items.find((port) => port.id && port.id === portId)); } getPortAt(index) { return this.ports.items[index] || null; } hasPorts() { return this.ports.items.length > 0; } hasPort(portId) { return this.getPortIndex(portId) !== -1; } getPortIndex(port) { const portId = typeof port === 'string' ? port : port.id; return portId != null ? this.ports.items.findIndex((item) => item.id === portId) : -1; } getPortsPosition(groupName) { const size = this.getSize(); const layouts = this.port.getPortsLayoutByGroup(groupName, new Rectangle(0, 0, size.width, size.height)); return layouts.reduce((memo, item) => { const layout = item.portLayout; memo[item.portId] = { position: Object.assign({}, layout.position), angle: layout.angle || 0, }; return memo; }, {}); } getPortProp(portId, path) { return this.getPropByPath(this.prefixPortPath(portId, path)); } setPortProp(portId, arg1, arg2, arg3) { if (typeof arg1 === 'string' || Array.isArray(arg1)) { const path = this.prefixPortPath(portId, arg1); const value = arg2; return this.setPropByPath(path, value, arg3); } const path = this.prefixPortPath(portId); const value = arg1; return this.setPropByPath(path, value, arg2); } removePortProp(portId, path, options) { if (typeof path === 'string' || Array.isArray(path)) { return this.removePropByPath(this.prefixPortPath(portId, path), options); } return this.removePropByPath(this.prefixPortPath(portId), path); } portProp(portId, path, value, options) { if (path == null) { return this.getPortProp(portId); } if (typeof path === 'string' || Array.isArray(path)) { if (arguments.length === 2) { return this.getPortProp(portId, path); } if (value == null) { return this.removePortProp(portId, path, options); } return this.setPortProp(portId, path, value, options); } return this.setPortProp(portId, path, value); } prefixPortPath(portId, path) { const index = this.getPortIndex(portId); if (index === -1) { throw new Error(`Unable to find port with id: "${portId}"`); } if (path == null || path === '') { return ['ports', 'items', `${index}`]; } if (Array.isArray(path)) { return ['ports', 'items', `${index}`, ...path]; } return `ports/items/${index}/${path}`; } addPort(port, options) { const ports = [...this.ports.items]; ports.push(port); this.setPropByPath('ports/items', ports, options); return this; } addPorts(ports, options) { this.setPropByPath('ports/items', [...this.ports.items, ...ports], options); return this; } insertPort(index, port, options) { const ports = [...this.ports.items]; ports.splice(index, 0, port); this.setPropByPath('ports/items', ports, options); return this; } removePort(port, options = {}) { return this.removePortAt(this.getPortIndex(port), options); } removePortAt(index, options = {}) { if (index >= 0) { const ports = [...this.ports.items]; ports.splice(index, 1); options.rewrite = true; this.setPropByPath('ports/items', ports, options); } return this; } removePorts(portsForRemoval, opt) { let options; if (Array.isArray(portsForRemoval)) { options = opt || {}; if (portsForRemoval.length) { options.rewrite = true; const currentPorts = [...this.ports.items]; const remainingPorts = currentPorts.filter((cp) => !portsForRemoval.some((p) => { const id = typeof p === 'string' ? p : p.id; return cp.id === id; })); this.setPropByPath('ports/items', remainingPorts, options); } } else { options = portsForRemoval || {}; options.rewrite = true; this.setPropByPath('ports/items', [], options); } return this; } getParsedPorts() { return this.port.getPorts(); } getParsedGroups() { return this.port.groups; } getPortsLayoutByGroup(groupName, bbox) { return this.port.getPortsLayoutByGroup(groupName, bbox); } initPorts() { this.updatePortData(); this.on('change:ports', () => { this.processRemovedPort(); this.updatePortData(); }); } processRemovedPort() { const current = this.ports; const currentItemsMap = {}; current.items.forEach((item) => { if (item.id) { currentItemsMap[item.id] = true; } }); const removed = {}; const previous = this.store.getPrevious('ports') || { items: [], }; previous.items.forEach((item) => { if (item.id && !currentItemsMap[item.id]) { removed[item.id] = true; } }); const model = this.model; if (model && !ObjectExt.isEmpty(removed)) { const incomings = model.getConnectedEdges(this, { incoming: true }); incomings.forEach((edge) => { const portId = edge.getTargetPortId(); if (portId && removed[portId]) { edge.remove(); } }); const outgoings = model.getConnectedEdges(this, { outgoing: true }); outgoings.forEach((edge) => { const portId = edge.getSourcePortId(); if (portId && removed[portId]) { edge.remove(); } }); } } validatePorts() { const ids = {}; const errors = []; this.ports.items.forEach((p) => { if (typeof p !== 'object') { errors.push(`Invalid port ${p}.`); } if (p.id == null) { p.id = this.generatePortId(); } if (ids[p.id]) { errors.push('Duplicitied port id.'); } ids[p.id] = true; }); return errors; } generatePortId() { return StringExt.uuid(); } updatePortData() { const err = this.validatePorts(); if (err.length > 0) { this.store.set('ports', this.store.getPrevious('ports')); throw new Error(err.join(' ')); } const prev = this.port ? this.port.getPorts() : null; this.port = new PortManager(this.ports); const curr = this.port.getPorts(); const added = prev ? curr.filter((item) => { if (!prev.find((prevPort) => prevPort.id === item.id)) { return item; } return null; }) : [...curr]; const removed = prev ? prev.filter((item) => { if (!curr.find((curPort) => curPort.id === item.id)) { return item; } return null; }) : []; if (added.length > 0) { this.notify('ports:added', { added, cell: this, node: this }); } if (removed.length > 0) { this.notify('ports:removed', { removed, cell: this, node: this }); } } } Node.defaults = { angle: 0, position: { x: 0, y: 0 }, size: { width: 1, height: 1 }, }; (function (Node) { Node.toStringTag = `X6.${Node.name}`; function isNode(instance) { if (instance == null) { return false; } if (instance instanceof Node) { return true; } const tag = instance[Symbol.toStringTag]; const node = instance; if ((tag == null || tag === Node.toStringTag) && typeof node.isNode === 'function' && typeof node.isEdge === 'function' && typeof node.prop === 'function' && typeof node.attr === 'function' && typeof node.size === 'function' && typeof node.position === 'function') { return true; } return false; } Node.isNode = isNode; })(Node || (Node = {})); (function (Node) { Node.config({ propHooks(_a) { var { ports } = _a, metadata = __rest(_a, ["ports"]); if (ports) { metadata.ports = Array.isArray(ports) ? { items: ports } : ports; } return metadata; }, }); })(Node || (Node = {})); (function (Node) { Node.registry = Registry.create({ type: 'node', process(shape, options) { if (ShareRegistry.exist(shape, true)) { throw new Error(`Node with name '${shape}' was registered by anthor Edge`); } if (typeof options === 'function') { options.config({ shape }); return options; } let parent = Node; const { inherit } = options, config = __rest(options, ["inherit"]); if (inherit) { if (typeof inherit === 'string') { const base = this.get(inherit); if (base == null) { this.onNotFound(inherit, 'inherited'); } else { parent = base; } } else { parent = inherit; } } if (config.constructorName == null) { config.constructorName = shape; } const ctor = parent.define.call(parent, config); ctor.config({ shape }); return ctor; }, }); ShareRegistry.setNodeRegistry(Node.registry); })(Node || (Node = {})); (function (Node) { let counter = 0; function getClassName(name) { if (name) { return StringExt.pascalCase(name); } counter += 1; return `CustomNode${counter}`; } function define(config) { const { constructorName, overwrite } = config, others = __rest(config, ["constructorName", "overwrite"]); const ctor = ObjectExt.createClass(getClassName(constructorName || others.shape), this); ctor.config(others); if (others.shape) { Node.registry.register(others.shape, ctor, overwrite); } return ctor; } Node.define = define; function create(options) { const shape = options.shape || 'rect'; const Ctor = Node.registry.get(shape); if (Ctor) { return new Ctor(options); } return Node.registry.onNotFound(shape); } Node.create = create; })(Node || (Node = {})); //# sourceMappingURL=node.js.map