@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering
1,017 lines • 36.1 kB
JavaScript
import { ArrayExt, Dom, FunctionExt } from '../../common';
import { Config } from '../../config';
import { Point, Rectangle, snapToGrid, } from '../../geometry';
import { Cell } from '../../model/cell';
import { CellView } from '../cell';
import { Markup } from '../markup';
export * from './type';
export class NodeView extends CellView {
constructor() {
super(...arguments);
this.portsCache = {};
// #endregion
}
static isNodeView(instance) {
if (instance == null) {
return false;
}
if (instance instanceof NodeView) {
return true;
}
const tag = instance[Symbol.toStringTag];
const view = instance;
if ((tag == null || tag === NodeViewToStringTag) &&
typeof view.isNodeView === 'function' &&
typeof view.isEdgeView === 'function' &&
typeof view.confirmUpdate === 'function' &&
typeof view.update === 'function' &&
typeof view.findPortElem === 'function' &&
typeof view.resize === 'function' &&
typeof view.rotate === 'function' &&
typeof view.translate === 'function') {
return true;
}
return false;
}
get [Symbol.toStringTag]() {
return NodeViewToStringTag;
}
getContainerClassName() {
const classList = [
super.getContainerClassName(),
this.prefixClassName('node'),
];
if (!this.can('nodeMovable')) {
classList.push(this.prefixClassName('node-immovable'));
}
return classList.join(' ');
}
updateClassName(e) {
const target = e.target;
if (target.hasAttribute('magnet')) {
// port
const className = this.prefixClassName('port-unconnectable');
if (this.can('magnetConnectable')) {
Dom.removeClass(target, className);
}
else {
Dom.addClass(target, className);
}
}
else {
// node
const className = this.prefixClassName('node-immovable');
if (this.can('nodeMovable')) {
this.removeClass(className);
}
else {
this.addClass(className);
}
}
}
isNodeView() {
return true;
}
confirmUpdate(flag, options = {}) {
let ret = flag;
const toolRelatedGeometryChanged = this.hasAction(flag, [
'resize',
'translate',
'rotate',
]);
if (this.hasAction(ret, 'ports')) {
this.removePorts();
this.cleanPortsCache();
}
if (this.hasAction(ret, 'render')) {
this.render();
ret = this.removeAction(ret, [
'render',
'update',
'resize',
'translate',
'rotate',
'ports',
'tools',
]);
}
else {
ret = this.handleAction(ret, 'resize', () => this.resize(), 'update');
ret = this.handleAction(ret, 'update', () => this.update(),
// `update()` will render ports when useCSSSelectors are enabled
Config.useCSSSelector ? 'ports' : null);
ret = this.handleAction(ret, 'translate', () => this.translate());
ret = this.handleAction(ret, 'rotate', () => this.rotate());
ret = this.handleAction(ret, 'ports', () => this.renderPorts());
ret = this.handleAction(ret, 'tools', () => {
const hasModelTools = this.cell.getTools() != null;
if (!toolRelatedGeometryChanged ||
this.tools == null ||
!hasModelTools) {
this.renderTools();
}
else {
this.updateTools(options);
}
});
}
return ret;
}
update(partialAttrs) {
this.cleanCache();
// When CSS selector strings are used, make sure no rule matches port nodes.
if (Config.useCSSSelector) {
this.removePorts();
}
const node = this.cell;
const size = node.getSize();
const attrs = node.getAttrs();
this.updateAttrs(this.container, attrs, {
attrs: partialAttrs === attrs ? null : partialAttrs,
rootBBox: new Rectangle(0, 0, size.width, size.height),
selectors: this.selectors,
});
if (Config.useCSSSelector) {
this.renderPorts();
}
}
renderMarkup() {
const markup = this.cell.markup;
if (markup) {
if (typeof markup === 'string') {
throw new TypeError('Not support string markup.');
}
return this.renderJSONMarkup(markup);
}
throw new TypeError('Invalid node markup.');
}
renderJSONMarkup(markup) {
const ret = this.parseJSONMarkup(markup, this.container);
this.selectors = ret.selectors;
this.container.appendChild(ret.fragment);
}
render() {
this.empty();
this.renderMarkup();
this.resize();
this.updateTransform();
if (!Config.useCSSSelector) {
this.renderPorts();
}
this.renderTools();
this.notify('view:render', { view: this });
return this;
}
resize() {
if (this.cell.getAngle()) {
this.rotate();
}
this.update();
}
translate() {
this.updateTransform();
}
rotate() {
this.updateTransform();
}
getTranslationString() {
const position = this.cell.getPosition();
return `translate(${position.x},${position.y})`;
}
getRotationString() {
const angle = this.cell.getAngle();
if (angle) {
const size = this.cell.getSize();
return `rotate(${angle},${size.width / 2},${size.height / 2})`;
}
}
updateTransform() {
let transform = this.getTranslationString();
const rot = this.getRotationString();
if (rot) {
transform += ` ${rot}`;
}
this.container.setAttribute('transform', transform);
}
// #region ports
findPortElem(portId, selector) {
const cache = portId ? this.portsCache[portId] : null;
if (!cache) {
return null;
}
const portRoot = cache.portContentElement;
const portSelectors = cache.portContentSelectors || {};
return this.findOne(selector, portRoot, portSelectors);
}
cleanPortsCache() {
this.portsCache = {};
}
removePorts() {
Object.values(this.portsCache).forEach((cached) => {
Dom.remove(cached.portElement);
});
}
renderPorts() {
const container = this.container;
// References to rendered elements without z-index
const references = [];
container.childNodes.forEach((child) => {
references.push(child);
});
const parsedPorts = this.cell.getParsedPorts();
const portsGropsByZ = ArrayExt.groupBy(parsedPorts, 'zIndex');
const autoZIndexKey = 'auto';
// render non-z first
if (portsGropsByZ[autoZIndexKey]) {
portsGropsByZ[autoZIndexKey].forEach((port) => {
const portElement = this.getPortElement(port);
container.append(portElement);
references.push(portElement);
});
}
Object.keys(portsGropsByZ).forEach((key) => {
if (key !== autoZIndexKey) {
const zIndex = parseInt(key, 10);
this.appendPorts(portsGropsByZ[key], zIndex, references);
}
});
this.updatePorts();
this.cleanCache();
this.updateConnectedEdges();
}
updateConnectedEdges() {
const graph = this.graph;
const node = this.cell;
const edges = graph.model.getConnectedEdges(node);
for (let i = 0, n = edges.length; i < n; i += 1) {
const edge = edges[i];
const edgeView = edge.findView(graph);
if (!edgeView || !graph.renderer.isViewMounted(edgeView)) {
continue;
}
const actions = ['update'];
if (edge.getSourceCell() === node) {
actions.push('source');
}
if (edge.getTargetCell() === node) {
actions.push('target');
}
graph.renderer.requestViewUpdate(edgeView, edgeView.getFlag(actions));
}
}
appendPorts(ports, zIndex, refs) {
const elems = ports.map((p) => this.getPortElement(p));
if (refs[zIndex] || zIndex < 0) {
Dom.before(refs[Math.max(zIndex, 0)], elems);
}
else {
Dom.append(this.container, elems);
}
}
getPortElement(port) {
const cached = this.portsCache[port.id];
if (cached) {
return cached.portElement;
}
return this.createPortElement(port);
}
createPortElement(port) {
let renderResult = Markup.renderMarkup(this.cell.getPortContainerMarkup());
const portElement = renderResult.elem;
if (portElement == null) {
throw new Error('Invalid port container markup.');
}
renderResult = Markup.renderMarkup(this.getPortMarkup(port));
const portContentElement = renderResult.elem;
const portContentSelectors = renderResult.selectors;
if (portContentElement == null) {
throw new Error('Invalid port markup.');
}
this.setAttrs({
port: port.id,
'port-group': port.group,
}, portContentElement);
let portClass = 'x6-port';
if (port.group) {
portClass += ` x6-port-${port.group}`;
}
Dom.addClass(portElement, portClass);
Dom.addClass(portElement, 'x6-port');
Dom.addClass(portContentElement, 'x6-port-body');
portElement.appendChild(portContentElement);
let portSelectors = portContentSelectors;
let portLabelElement;
let portLabelSelectors;
const existLabel = this.existPortLabel(port);
if (existLabel) {
renderResult = Markup.renderMarkup(this.getPortLabelMarkup(port.label));
portLabelElement = renderResult.elem;
portLabelSelectors = renderResult.selectors;
if (portLabelElement == null) {
throw new Error('Invalid port label markup.');
}
if (portContentSelectors && portLabelSelectors) {
// eslint-disable-next-line
for (const key in portLabelSelectors) {
if (portContentSelectors[key] && key !== this.rootSelector) {
throw new Error('Selectors within port must be unique.');
}
}
portSelectors = Object.assign(Object.assign({}, portContentSelectors), portLabelSelectors);
}
Dom.addClass(portLabelElement, 'x6-port-label');
portElement.appendChild(portLabelElement);
}
this.portsCache[port.id] = {
portElement,
portSelectors,
portLabelElement,
portLabelSelectors,
portContentElement,
portContentSelectors,
};
if (this.graph.options.onPortRendered) {
this.graph.options.onPortRendered({
port,
node: this.cell,
container: portElement,
selectors: portSelectors,
labelContainer: portLabelElement,
labelSelectors: portLabelSelectors,
contentContainer: portContentElement,
contentSelectors: portContentSelectors,
});
}
return portElement;
}
updatePorts() {
const groups = this.cell.getParsedGroups();
const groupList = Object.keys(groups);
if (groupList.length === 0) {
this.updatePortGroup();
}
else {
groupList.forEach((groupName) => {
this.updatePortGroup(groupName);
});
}
}
updatePortGroup(groupName) {
const bbox = Rectangle.fromSize(this.cell.getSize());
const metrics = this.cell.getPortsLayoutByGroup(groupName, bbox);
for (let i = 0, n = metrics.length; i < n; i += 1) {
const metric = metrics[i];
const portId = metric.portId;
const cached = this.portsCache[portId] || {};
const portLayout = metric.portLayout;
// @ts-expect-error
this.applyPortTransform(cached.portElement, portLayout);
if (metric.portAttrs != null) {
const options = {
// @ts-expect-error
selectors: cached.portSelectors || {},
};
if (metric.portSize) {
options.rootBBox = Rectangle.fromSize(metric.portSize);
}
// @ts-expect-error
this.updateAttrs(cached.portElement, metric.portAttrs, options);
}
const labelLayout = metric.labelLayout;
// @ts-expect-error
if (labelLayout && cached.portLabelElement) {
this.applyPortTransform(
// @ts-expect-error
cached.portLabelElement, labelLayout, -(portLayout.angle || 0));
if (labelLayout.attrs) {
const options = {
// @ts-expect-error
selectors: cached.portLabelSelectors || {},
};
if (metric.labelSize) {
options.rootBBox = Rectangle.fromSize(metric.labelSize);
}
// @ts-expect-error
this.updateAttrs(cached.portLabelElement, labelLayout.attrs, options);
}
}
}
}
applyPortTransform(element, layout, initialAngle = 0) {
const angle = layout.angle;
const position = layout.position;
const matrix = Dom.createSVGMatrix()
.rotate(initialAngle)
.translate(position.x || 0, position.y || 0)
.rotate(angle || 0);
Dom.transform(element, matrix, { absolute: true });
}
getPortMarkup(port) {
return port.markup || this.cell.portMarkup;
}
getPortLabelMarkup(label) {
return label.markup || this.cell.portLabelMarkup;
}
existPortLabel(port) {
return port.attrs && port.attrs.text;
}
getEventArgs(e, x, y) {
const view = this; // eslint-disable-line
const node = view.cell;
const cell = node;
if (x == null || y == null) {
return { e, view, node, cell };
}
return { e, x, y, view, node, cell };
}
getPortEventArgs(e, port, pos) {
const view = this; // eslint-disable-line
const node = view.cell;
const cell = node;
if (pos) {
return {
e,
x: pos.x,
y: pos.y,
view,
node,
cell,
port,
};
}
return { e, view, node, cell, port };
}
notifyMouseDown(e, x, y) {
super.onMouseDown(e, x, y);
this.notify('node:mousedown', this.getEventArgs(e, x, y));
}
notifyMouseMove(e, x, y) {
super.onMouseMove(e, x, y);
this.notify('node:mousemove', this.getEventArgs(e, x, y));
}
notifyMouseUp(e, x, y) {
super.onMouseUp(e, x, y);
this.notify('node:mouseup', this.getEventArgs(e, x, y));
}
notifyPortEvent(name, e, pos) {
const port = this.findAttr('port', e.target);
if (port) {
const originType = e.type;
if (name === 'node:port:mouseenter') {
e.type = 'mouseenter';
}
else if (name === 'node:port:mouseleave') {
e.type = 'mouseleave';
}
this.notify(name, this.getPortEventArgs(e, port, pos));
e.type = originType;
}
}
onClick(e, x, y) {
super.onClick(e, x, y);
this.notify('node:click', this.getEventArgs(e, x, y));
this.notifyPortEvent('node:port:click', e, { x, y });
}
onDblClick(e, x, y) {
super.onDblClick(e, x, y);
this.notify('node:dblclick', this.getEventArgs(e, x, y));
this.notifyPortEvent('node:port:dblclick', e, { x, y });
}
onContextMenu(e, x, y) {
super.onContextMenu(e, x, y);
this.notify('node:contextmenu', this.getEventArgs(e, x, y));
this.notifyPortEvent('node:port:contextmenu', e, { x, y });
}
onMouseDown(e, x, y) {
if (this.isPropagationStopped(e)) {
return;
}
this.notifyMouseDown(e, x, y);
this.notifyPortEvent('node:port:mousedown', e, { x, y });
this.startNodeDragging(e, x, y);
}
onMouseMove(e, x, y) {
const data = this.getEventData(e);
const action = data.action;
if (action === 'magnet') {
this.dragMagnet(e, x, y);
}
else {
if (action === 'move') {
const meta = data;
const view = meta.targetView || this;
view.dragNode(e, x, y);
view.notify('node:moving', {
e,
x,
y,
view,
cell: view.cell,
node: view.cell,
});
}
this.notifyMouseMove(e, x, y);
this.notifyPortEvent('node:port:mousemove', e, { x, y });
}
this.setEventData(e, data);
}
onMouseUp(e, x, y) {
const data = this.getEventData(e);
const action = data.action;
if (action === 'magnet') {
this.stopMagnetDragging(e, x, y);
}
else {
this.notifyMouseUp(e, x, y);
this.notifyPortEvent('node:port:mouseup', e, { x, y });
if (action === 'move') {
const meta = data;
const view = meta.targetView || this;
view.stopNodeDragging(e, x, y);
}
}
const magnet = data.targetMagnet;
if (magnet) {
this.onMagnetClick(e, magnet, x, y);
}
this.checkMouseleave(e);
}
onMouseOver(e) {
super.onMouseOver(e);
this.notify('node:mouseover', this.getEventArgs(e));
// mock mouseenter event,so we can get correct trigger time when move mouse from node to port
// wo also need to change e.type for use get correct event args
this.notifyPortEvent('node:port:mouseenter', e);
this.notifyPortEvent('node:port:mouseover', e);
}
onMouseOut(e) {
super.onMouseOut(e);
this.notify('node:mouseout', this.getEventArgs(e));
// mock mouseleave event,so we can get correct trigger time when move mouse from port to node
// wo also need to change e.type for use get correct event args
this.notifyPortEvent('node:port:mouseleave', e);
this.notifyPortEvent('node:port:mouseout', e);
}
onMouseEnter(e) {
this.updateClassName(e);
super.onMouseEnter(e);
this.notify('node:mouseenter', this.getEventArgs(e));
}
onMouseLeave(e) {
super.onMouseLeave(e);
this.notify('node:mouseleave', this.getEventArgs(e));
}
onMouseWheel(e, x, y, delta) {
super.onMouseWheel(e, x, y, delta);
this.notify('node:mousewheel', Object.assign({ delta }, this.getEventArgs(e, x, y)));
}
onMagnetClick(e, magnet, x, y) {
const graph = this.graph;
const count = graph.view.getMouseMovedCount(e);
if (count > graph.options.clickThreshold) {
return;
}
this.notify('node:magnet:click', Object.assign({ magnet }, this.getEventArgs(e, x, y)));
}
onMagnetDblClick(e, magnet, x, y) {
this.notify('node:magnet:dblclick', Object.assign({ magnet }, this.getEventArgs(e, x, y)));
}
onMagnetContextMenu(e, magnet, x, y) {
this.notify('node:magnet:contextmenu', Object.assign({ magnet }, this.getEventArgs(e, x, y)));
}
onMagnetMouseDown(e, magnet, x, y) {
this.startMagnetDragging(e, x, y);
}
onCustomEvent(e, name, x, y) {
this.notify('node:customevent', Object.assign({ name }, this.getEventArgs(e, x, y)));
super.onCustomEvent(e, name, x, y);
}
prepareEmbedding(e) {
const graph = this.graph;
const data = this.getEventData(e);
const node = data.cell || this.cell;
const view = graph.findViewByCell(node);
const localPoint = graph.snapToGrid(e.clientX, e.clientY);
this.notify('node:embed', {
e,
node,
view,
cell: node,
x: localPoint.x,
y: localPoint.y,
currentParent: node.getParent(),
});
}
processEmbedding(e, data) {
const cell = data.cell || this.cell;
const graph = data.graph || this.graph;
const options = graph.options.embedding;
const findParent = options.findParent;
let candidates = typeof findParent === 'function'
? FunctionExt.call(findParent, graph, {
view: this,
node: this.cell,
}).filter((c) => {
return (Cell.isCell(c) &&
this.cell.id !== c.id &&
!c.isDescendantOf(this.cell));
})
: graph.model.getNodesUnderNode(cell, {
by: findParent,
});
// Picks the node with the highest `z` index
if (options.frontOnly) {
if (candidates.length > 0) {
const zIndexMap = ArrayExt.groupBy(candidates, 'zIndex');
const maxZIndex = ArrayExt.max(Object.keys(zIndexMap).map((z) => parseInt(z, 10)));
if (maxZIndex) {
candidates = zIndexMap[maxZIndex];
}
}
}
// Filter the nodes which is invisiable
candidates = candidates.filter((candidate) => candidate.visible);
let newCandidateView = null;
const prevCandidateView = data.candidateEmbedView;
const validateEmbeding = options.validate;
for (let i = candidates.length - 1; i >= 0; i -= 1) {
const candidate = candidates[i];
if (prevCandidateView && prevCandidateView.cell.id === candidate.id) {
// candidate remains the same
newCandidateView = prevCandidateView;
break;
}
else {
const view = candidate.findView(graph);
if (validateEmbeding &&
FunctionExt.call(validateEmbeding, graph, {
child: this.cell,
parent: view.cell,
childView: this,
parentView: view,
})) {
// flip to the new candidate
newCandidateView = view;
break;
}
}
}
this.clearEmbedding(data);
if (newCandidateView) {
newCandidateView.highlight(null, { type: 'embedding' });
}
data.candidateEmbedView = newCandidateView;
const localPoint = graph.snapToGrid(e.clientX, e.clientY);
this.notify('node:embedding', {
e,
cell,
node: cell,
view: graph.findViewByCell(cell),
x: localPoint.x,
y: localPoint.y,
currentParent: cell.getParent(),
candidateParent: newCandidateView ? newCandidateView.cell : null,
});
}
clearEmbedding(data) {
const candidateView = data.candidateEmbedView;
if (candidateView) {
candidateView.unhighlight(null, { type: 'embedding' });
data.candidateEmbedView = null;
}
}
finalizeEmbedding(e, data) {
this.graph.startBatch('embedding');
const cell = data.cell || this.cell;
const graph = data.graph || this.graph;
const view = graph.findViewByCell(cell);
const parent = cell.getParent();
const candidateView = data.candidateEmbedView;
if (candidateView) {
// Candidate view is chosen to become the parent of the node.
candidateView.unhighlight(null, { type: 'embedding' });
data.candidateEmbedView = null;
if (parent == null || parent.id !== candidateView.cell.id) {
candidateView.cell.insertChild(cell, undefined, { ui: true });
}
}
else if (parent) {
parent.unembed(cell, { ui: true });
}
graph.model.getConnectedEdges(cell, { deep: true }).forEach((edge) => {
edge.updateParent({ ui: true });
});
if (view && candidateView) {
const localPoint = graph.snapToGrid(e.clientX, e.clientY);
view.notify('node:embedded', {
e,
cell,
x: localPoint.x,
y: localPoint.y,
node: cell,
view: graph.findViewByCell(cell),
previousParent: parent,
currentParent: cell.getParent(),
});
}
this.graph.stopBatch('embedding');
}
getDelegatedView() {
let cell = this.cell;
let view = this; // eslint-disable-line
while (view) {
if (cell.isEdge()) {
break;
}
if (!cell.hasParent() || view.can('stopDelegateOnDragging')) {
return view;
}
cell = cell.getParent();
view = this.graph.findViewByCell(cell);
}
return null;
}
validateMagnet(cellView, magnet, e) {
if (magnet.getAttribute('magnet') !== 'passive') {
const validate = this.graph.options.connecting.validateMagnet;
if (validate) {
return FunctionExt.call(validate, this.graph, {
e,
magnet,
view: cellView,
cell: cellView.cell,
});
}
return true;
}
return false;
}
startMagnetDragging(e, x, y) {
if (!this.can('magnetConnectable')) {
return;
}
e.stopPropagation();
const magnet = e.currentTarget;
const graph = this.graph;
this.setEventData(e, {
targetMagnet: magnet,
});
if (this.validateMagnet(this, magnet, e)) {
// @ts-expect-error
if (graph.options.magnetThreshold <= 0) {
this.startConnectting(e, magnet, x, y);
}
this.setEventData(e, {
action: 'magnet',
});
this.stopPropagation(e);
}
else {
this.onMouseDown(e, x, y);
}
graph.view.delegateDragEvents(e, this);
}
startConnectting(e, magnet, x, y) {
this.graph.model.startBatch('add-edge');
const edgeView = this.createEdgeFromMagnet(magnet, x, y);
edgeView.setEventData(e, edgeView.prepareArrowheadDragging('target', {
x,
y,
isNewEdge: true,
fallbackAction: 'remove',
}));
this.setEventData(e, { edgeView });
edgeView.notifyMouseDown(e, x, y);
}
getDefaultEdge(sourceView, sourceMagnet) {
let edge;
const create = this.graph.options.connecting.createEdge;
if (create) {
edge = FunctionExt.call(create, this.graph, {
sourceMagnet,
sourceView,
sourceCell: sourceView.cell,
});
}
return edge;
}
createEdgeFromMagnet(magnet, x, y) {
const graph = this.graph;
const model = graph.model;
const edge = this.getDefaultEdge(this, magnet);
edge.setSource(Object.assign(Object.assign({}, edge.getSource()), this.getEdgeTerminal(magnet, x, y, edge, 'source')));
edge.setTarget(Object.assign(Object.assign({}, edge.getTarget()), { x, y }));
edge.addTo(model, { async: false, ui: true });
return edge.findView(graph);
}
dragMagnet(e, x, y) {
const data = this.getEventData(e);
const edgeView = data.edgeView;
if (edgeView) {
edgeView.onMouseMove(e, x, y);
this.autoScrollGraph(e.clientX, e.clientY);
}
else {
const graph = this.graph;
const magnetThreshold = graph.options.magnetThreshold;
const currentTarget = this.getEventTarget(e);
const targetMagnet = data.targetMagnet;
// magnetThreshold when the pointer leaves the magnet
if (magnetThreshold === 'onleave') {
if (targetMagnet === currentTarget ||
targetMagnet.contains(currentTarget)) {
return;
}
// eslint-disable-next-line no-lonely-if
}
else {
// magnetThreshold defined as a number of movements
if (graph.view.getMouseMovedCount(e) <= magnetThreshold) {
return;
}
}
this.startConnectting(e, targetMagnet, x, y);
}
}
stopMagnetDragging(e, x, y) {
const data = this.eventData(e);
const edgeView = data.edgeView;
if (edgeView) {
edgeView.onMouseUp(e, x, y);
this.graph.model.stopBatch('add-edge');
}
}
notifyUnhandledMouseDown(e, x, y) {
this.notify('node:unhandled:mousedown', {
e,
x,
y,
view: this,
cell: this.cell,
node: this.cell,
});
}
notifyNodeMove(name, e, x, y, cell) {
let cells = [cell];
const selection = this.graph.getPlugin('selection');
if (selection && selection.isSelectionMovable()) {
const selectedCells = selection.getSelectedCells();
if (selectedCells.includes(cell)) {
cells = selectedCells.filter((c) => c.isNode());
}
}
cells.forEach((c) => {
this.notify(name, {
e,
x,
y,
cell: c,
node: c,
view: c.findView(this.graph),
});
});
}
getRestrictArea(view) {
const restrict = this.graph.options.translating.restrict;
const area = typeof restrict === 'function'
? FunctionExt.call(restrict, this.graph, view)
: restrict;
if (typeof area === 'number') {
return this.graph.transform.getGraphArea().inflate(area);
}
if (area === true) {
return this.graph.transform.getGraphArea();
}
return area || null;
}
startNodeDragging(e, x, y) {
const targetView = this.getDelegatedView();
if (targetView == null || !targetView.can('nodeMovable')) {
return this.notifyUnhandledMouseDown(e, x, y);
}
this.setEventData(e, {
targetView,
action: 'move',
});
const position = Point.create(targetView.cell.getPosition());
targetView.setEventData(e, {
moving: false,
offset: position.diff(x, y),
restrict: this.getRestrictArea(targetView),
});
}
dragNode(e, x, y) {
const node = this.cell;
const graph = this.graph;
const gridSize = graph.getGridSize();
const data = this.getEventData(e);
const offset = data.offset;
const restrict = data.restrict;
if (!data.moving) {
data.moving = true;
this.addClass('node-moving');
this.notifyNodeMove('node:move', e, x, y, this.cell);
}
this.autoScrollGraph(e.clientX, e.clientY);
const posX = snapToGrid(x + offset.x, gridSize);
const posY = snapToGrid(y + offset.y, gridSize);
node.setPosition(posX, posY, {
restrict,
deep: true,
ui: true,
});
if (graph.options.embedding.enabled) {
if (!data.embedding) {
this.prepareEmbedding(e);
data.embedding = true;
}
this.processEmbedding(e, data);
}
}
autoOffsetNode() {
const node = this.cell;
const graph = this.graph;
const nodePosition = Object.assign({ id: node.id }, node.getPosition());
const allNodes = graph.getNodes();
const restNodePositions = allNodes
.map((node) => {
const pos = node.getPosition();
return { id: node.id, x: pos.x, y: pos.y };
})
.filter((pos) => {
return pos.id !== nodePosition.id;
});
/** offset directions: right bottom, right top, left bottom, left top */
const directions = [
[1, 1], // offset to right bottom
[1, -1], // offset to right top
[-1, 1], // offset to left bottom
[-1, -1], // offset to left top
];
let step = graph.getGridSize();
const hasSamePosition = (position) => restNodePositions.some((pos) => {
return pos.x === position.x && pos.y === position.y;
});
while (hasSamePosition(nodePosition)) {
let found = false;
for (let i = 0; i < directions.length; i += 1) {
const dir = directions[i];
const position = {
x: nodePosition.x + dir[0] * step,
y: nodePosition.y + dir[1] * step,
};
if (!hasSamePosition(position)) {
node.translate(dir[0] * step, dir[1] * step);
found = true;
break;
}
}
if (found) {
break;
}
step += graph.getGridSize();
}
}
stopNodeDragging(e, x, y) {
const data = this.getEventData(e);
const graph = this.graph;
if (data.embedding) {
this.finalizeEmbedding(e, data);
}
if (data.moving) {
const autoOffset = graph.options.translating.autoOffset;
if (autoOffset) {
this.autoOffsetNode();
}
this.removeClass('node-moving');
this.notifyNodeMove('node:moved', e, x, y, this.cell);
}
data.moving = false;
data.embedding = false;
}
// eslint-disable-next-line
autoScrollGraph(x, y) {
const scroller = this.graph.getPlugin('scroller');
if (scroller) {
scroller.autoScroll(x, y);
}
}
}
export const NodeViewToStringTag = `X6.${NodeView.name}`;
NodeView.config({
isSvgElement: true,
priority: 0,
bootstrap: ['render'],
actions: {
view: ['render'],
markup: ['render'],
attrs: ['update'],
size: ['resize', 'ports', 'tools'],
angle: ['rotate', 'tools'],
position: ['translate', 'tools'],
ports: ['ports'],
tools: ['tools'],
},
});
NodeView.registry.register('node', NodeView, true);
//# sourceMappingURL=index.js.map