@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering
724 lines • 23.1 kB
JavaScript
import { __rest } from "tslib";
import { ObjectExt, StringExt } from '../common';
import { Point, Polyline } from '../geometry';
import { Registry } from '../registry/registry';
import { Markup } from '../view/markup';
import { Cell, } from './cell';
import { exist, setEdgeRegistry } from './registry';
const toStringTag = `X6.edge`;
const shape = 'basic.edge';
let counter = 0;
function getClassName(name) {
if (name) {
return StringExt.pascalCase(name);
}
counter += 1;
return `CustomEdge${counter}`;
}
export class Edge extends Cell {
static parseStringLabel(text) {
return {
attrs: { label: { text } },
};
}
static isEdge(instance) {
if (instance == null) {
return false;
}
if (instance instanceof Edge) {
return true;
}
const tag = instance[Symbol.toStringTag];
const edge = instance;
if ((tag == null || tag === toStringTag) &&
typeof edge.isNode === 'function' &&
typeof edge.isEdge === 'function' &&
typeof edge.prop === 'function' &&
typeof edge.attr === 'function' &&
typeof edge.disconnect === 'function' &&
typeof edge.getSource === 'function' &&
typeof edge.getTarget === 'function') {
return true;
}
return false;
}
static equalTerminals(a, b) {
const a1 = a;
const b1 = b;
if (a1.cell === b1.cell) {
return a1.port === b1.port || (a1.port == null && b1.port == null);
}
return false;
}
static 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) {
Edge.registry.register(others.shape, ctor, overwrite);
}
return ctor;
}
static create(options) {
const shape = options.shape || 'edge';
const Ctor = Edge.registry.get(shape);
if (Ctor) {
return new Ctor(options);
}
return Edge.registry.onNotFound(shape);
}
get [Symbol.toStringTag]() {
return Edge.toStringTag;
}
constructor(metadata = {}) {
super(metadata);
}
preprocess(metadata, ignoreIdCheck) {
const { source, sourceCell, sourcePort, sourcePoint, target, targetCell, targetPort, targetPoint } = metadata, others = __rest(metadata, ["source", "sourceCell", "sourcePort", "sourcePoint", "target", "targetCell", "targetPort", "targetPoint"]);
const data = others;
const isValidId = (val) => typeof val === 'string' || typeof val === 'number';
if (source != null) {
if (Cell.isCell(source)) {
data.source = { cell: source.id };
}
else if (isValidId(source)) {
data.source = { cell: source };
}
else if (Point.isPoint(source)) {
data.source = source.toJSON();
}
else if (Array.isArray(source)) {
data.source = { x: source[0], y: source[1] };
}
else {
const cell = source.cell;
if (Cell.isCell(cell)) {
data.source = Object.assign(Object.assign({}, source), { cell: cell.id });
}
else {
data.source = source;
}
}
}
if (sourceCell != null || sourcePort != null) {
let terminal = data.source;
if (sourceCell != null) {
const id = isValidId(sourceCell) ? sourceCell : sourceCell.id;
if (terminal) {
terminal.cell = id;
}
else {
terminal = data.source = { cell: id };
}
}
if (sourcePort != null && terminal) {
terminal.port = sourcePort;
}
}
else if (sourcePoint != null) {
data.source = Point.create(sourcePoint).toJSON();
}
if (target != null) {
if (Cell.isCell(target)) {
data.target = { cell: target.id };
}
else if (isValidId(target)) {
data.target = { cell: target };
}
else if (Point.isPoint(target)) {
data.target = target.toJSON();
}
else if (Array.isArray(target)) {
data.target = { x: target[0], y: target[1] };
}
else {
const cell = target.cell;
if (Cell.isCell(cell)) {
data.target = Object.assign(Object.assign({}, target), { cell: cell.id });
}
else {
data.target = target;
}
}
}
if (targetCell != null || targetPort != null) {
let terminal = data.target;
if (targetCell != null) {
const id = isValidId(targetCell) ? targetCell : targetCell.id;
if (terminal) {
terminal.cell = id;
}
else {
terminal = data.target = { cell: id };
}
}
if (targetPort != null && terminal) {
terminal.port = targetPort;
}
}
else if (targetPoint != null) {
data.target = Point.create(targetPoint).toJSON();
}
return super.preprocess(data, ignoreIdCheck);
}
setup() {
super.setup();
this.on('change:labels', (args) => this.onLabelsChanged(args));
this.on('change:vertices', (args) => this.onVertexsChanged(args));
}
isEdge() {
return true;
}
// #region terminal
disconnect(options = {}) {
this.store.set({
source: { x: 0, y: 0 },
target: { x: 0, y: 0 },
}, options);
return this;
}
get source() {
return this.getSource();
}
set source(data) {
this.setSource(data);
}
getSource() {
return this.getTerminal('source');
}
getSourceCellId() {
return this.source.cell;
}
getSourcePortId() {
return this.source.port;
}
setSource(source, args, options = {}) {
return this.setTerminal('source', source, args, options);
}
get target() {
return this.getTarget();
}
set target(data) {
this.setTarget(data);
}
getTarget() {
return this.getTerminal('target');
}
getTargetCellId() {
return this.target.cell;
}
getTargetPortId() {
return this.target.port;
}
setTarget(target, args, options = {}) {
return this.setTerminal('target', target, args, options);
}
getTerminal(type) {
return Object.assign({}, this.store.get(type));
}
setTerminal(type, terminal, args, options = {}) {
// `terminal` is a cell
if (Cell.isCell(terminal)) {
this.store.set(type, ObjectExt.merge({}, args, { cell: terminal.id }), options);
return this;
}
// `terminal` is a point-like object
const p = terminal;
if (Point.isPoint(terminal) || (p.x != null && p.y != null)) {
this.store.set(type, ObjectExt.merge({}, args, { x: p.x, y: p.y }), options);
return this;
}
// `terminal` is an object
this.store.set(type, ObjectExt.cloneDeep(terminal), options);
return this;
}
getSourcePoint() {
return this.getTerminalPoint('source');
}
getTargetPoint() {
return this.getTerminalPoint('target');
}
getTerminalPoint(type) {
const terminal = this[type];
if (Point.isPointLike(terminal)) {
return Point.create(terminal);
}
const cell = this.getTerminalCell(type);
if (cell) {
return cell.getConnectionPoint(this, type);
}
return new Point();
}
getSourceCell() {
return this.getTerminalCell('source');
}
getTargetCell() {
return this.getTerminalCell('target');
}
getTerminalCell(type) {
if (this.model) {
const cellId = type === 'source' ? this.getSourceCellId() : this.getTargetCellId();
if (cellId) {
return this.model.getCell(cellId);
}
}
return null;
}
getSourceNode() {
return this.getTerminalNode('source');
}
getTargetNode() {
return this.getTerminalNode('target');
}
getTerminalNode(type) {
let cell = this; // eslint-disable-line
const visited = {};
while (cell && cell.isEdge()) {
if (visited[cell.id]) {
return null;
}
visited[cell.id] = true;
cell = cell.getTerminalCell(type);
}
return cell && cell.isNode() ? cell : null;
}
// #endregion
// #region router
get router() {
return this.getRouter();
}
set router(data) {
if (data == null) {
this.removeRouter();
}
else {
this.setRouter(data);
}
}
getRouter() {
return this.store.get('router');
}
setRouter(name, args, options) {
if (typeof name === 'object') {
this.store.set('router', name, args);
}
else {
this.store.set('router', { name, args }, options);
}
return this;
}
removeRouter(options = {}) {
this.store.remove('router', options);
return this;
}
// #endregion
// #region connector
get connector() {
return this.getConnector();
}
set connector(data) {
if (data == null) {
this.removeConnector();
}
else {
this.setConnector(data);
}
}
getConnector() {
return this.store.get('connector');
}
setConnector(name, args, options) {
if (typeof name === 'object') {
this.store.set('connector', name, args);
}
else {
this.store.set('connector', { name, args }, options);
}
return this;
}
removeConnector(options = {}) {
return this.store.remove('connector', options);
}
// #endregion
// #region labels
getDefaultLabel() {
const ctor = this.constructor;
const defaults = this.store.get('defaultLabel') || ctor.defaultLabel || {};
return ObjectExt.cloneDeep(defaults);
}
get labels() {
return this.getLabels();
}
set labels(labels) {
this.setLabels(labels);
}
getLabels() {
return [...this.store.get('labels', [])].map((item) => this.parseLabel(item));
}
setLabels(labels, options = {}) {
this.store.set('labels', Array.isArray(labels) ? labels : [labels], options);
return this;
}
insertLabel(label, index, options = {}) {
const labels = this.getLabels();
const len = labels.length;
let idx = index != null && Number.isFinite(index) ? index : len;
if (idx < 0) {
idx = len + idx + 1;
}
labels.splice(idx, 0, this.parseLabel(label));
return this.setLabels(labels, options);
}
appendLabel(label, options = {}) {
return this.insertLabel(label, -1, options);
}
getLabelAt(index) {
const labels = this.getLabels();
if (index != null && Number.isFinite(index)) {
return this.parseLabel(labels[index]);
}
return null;
}
setLabelAt(index, label, options = {}) {
if (index != null && Number.isFinite(index)) {
const labels = this.getLabels();
labels[index] = this.parseLabel(label);
this.setLabels(labels, options);
}
return this;
}
removeLabelAt(index, options = {}) {
const labels = this.getLabels();
const idx = index != null && Number.isFinite(index) ? index : -1;
const removed = labels.splice(idx, 1);
this.setLabels(labels, options);
return removed.length ? removed[0] : null;
}
parseLabel(label) {
if (typeof label === 'string') {
const ctor = this.constructor;
return ctor.parseStringLabel(label);
}
return label;
}
onLabelsChanged({ previous, current, }) {
const added = previous && current
? current.filter((label1) => {
if (!previous.find((label2) => label1 === label2 || ObjectExt.isEqual(label1, label2))) {
return label1;
}
return null;
})
: current
? [...current]
: [];
const removed = previous && current
? previous.filter((label1) => {
if (!current.find((label2) => label1 === label2 || ObjectExt.isEqual(label1, label2))) {
return label1;
}
return null;
})
: previous
? [...previous]
: [];
if (added.length > 0) {
this.notify('labels:added', { added, cell: this, edge: this });
}
if (removed.length > 0) {
this.notify('labels:removed', { removed, cell: this, edge: this });
}
}
// #endregion
// #region vertices
get vertices() {
return this.getVertices();
}
set vertices(vertices) {
this.setVertices(vertices);
}
getVertices() {
return [...this.store.get('vertices', [])];
}
setVertices(vertices, options = {}) {
const points = Array.isArray(vertices) ? vertices : [vertices];
this.store.set('vertices', points.map((p) => Point.toJSON(p)), options);
return this;
}
insertVertex(vertice, index, options = {}) {
const vertices = this.getVertices();
const len = vertices.length;
let idx = index != null && Number.isFinite(index) ? index : len;
if (idx < 0) {
idx = len + idx + 1;
}
vertices.splice(idx, 0, Point.toJSON(vertice));
return this.setVertices(vertices, options);
}
appendVertex(vertex, options = {}) {
return this.insertVertex(vertex, -1, options);
}
getVertexAt(index) {
if (index != null && Number.isFinite(index)) {
const vertices = this.getVertices();
return vertices[index];
}
return null;
}
setVertexAt(index, vertice, options = {}) {
if (index != null && Number.isFinite(index)) {
const vertices = this.getVertices();
vertices[index] = vertice;
this.setVertices(vertices, options);
}
return this;
}
removeVertexAt(index, options = {}) {
const vertices = this.getVertices();
const idx = index != null && Number.isFinite(index) ? index : -1;
vertices.splice(idx, 1);
return this.setVertices(vertices, options);
}
onVertexsChanged({ previous, current, }) {
const added = previous && current
? current.filter((p1) => {
if (!previous.find((p2) => Point.equals(p1, p2))) {
return p1;
}
return null;
})
: current
? [...current]
: [];
const removed = previous && current
? previous.filter((p1) => {
if (!current.find((p2) => Point.equals(p1, p2))) {
return p1;
}
return null;
})
: previous
? [...previous]
: [];
if (added.length > 0) {
this.notify('vertexs:added', { added, cell: this, edge: this });
}
if (removed.length > 0) {
this.notify('vertexs:removed', { removed, cell: this, edge: this });
}
}
// #endregion
// #region markup
getDefaultMarkup() {
return this.store.get('defaultMarkup') || Markup.getEdgeMarkup();
}
getMarkup() {
return super.getMarkup() || this.getDefaultMarkup();
}
// #endregion
// #region transform
/**
* Translate the edge vertices (and source and target if they are points)
* by `tx` pixels in the x-axis and `ty` pixels in the y-axis.
*/
translate(tx, ty, options = {}) {
options.translateBy = options.translateBy || this.id;
options.tx = tx;
options.ty = ty;
return this.applyToPoints((p) => ({
x: (p.x || 0) + tx,
y: (p.y || 0) + ty,
}), options);
}
/**
* Scales the edge's points (vertices) relative to the given origin.
*/
scale(sx, sy, origin, options = {}) {
return this.applyToPoints((p) => {
return Point.create(p).scale(sx, sy, origin).toJSON();
}, options);
}
applyToPoints(worker, options = {}) {
const attrs = {};
const source = this.getSource();
const target = this.getTarget();
if (Point.isPointLike(source)) {
attrs.source = worker(source);
}
if (Point.isPointLike(target)) {
attrs.target = worker(target);
}
const vertices = this.getVertices();
if (vertices.length > 0) {
attrs.vertices = vertices.map(worker);
}
this.store.set(attrs, options);
return this;
}
// #endregion
// #region common
getBBox() {
return this.getPolyline().bbox();
}
getConnectionPoint() {
return this.getPolyline().pointAt(0.5);
}
getPolyline() {
const points = [
this.getSourcePoint(),
...this.getVertices().map((vertice) => Point.create(vertice)),
this.getTargetPoint(),
];
return new Polyline(points);
}
updateParent(options) {
let newParent = null;
const source = this.getSourceCell();
const target = this.getTargetCell();
const prevParent = this.getParent();
if (source && target) {
if (source === target || source.isDescendantOf(target)) {
newParent = target;
}
else if (target.isDescendantOf(source)) {
newParent = source;
}
else {
newParent = Cell.getCommonAncestor(source, target);
}
}
// Unembeds the edge if source and target has no common
// ancestor or common ancestor changed
if (prevParent && newParent && newParent.id !== prevParent.id) {
prevParent.unembed(this, options);
}
// Embeds the edge if source and target are not same
if (newParent && (!prevParent || prevParent.id !== newParent.id)) {
newParent.embed(this, options);
}
return newParent;
}
hasLoop(options = {}) {
const source = this.getSource();
const target = this.getTarget();
const sourceId = source.cell;
const targetId = target.cell;
if (!sourceId || !targetId) {
return false;
}
let loop = sourceId === targetId;
// Note that there in the deep mode a edge can have a loop,
// even if it connects only a parent and its embed.
// A loop "target equals source" is valid in both shallow and deep mode.
// eslint-disable-next-line
if (!loop && options.deep && this._model) {
const sourceCell = this.getSourceCell();
const targetCell = this.getTargetCell();
if (sourceCell && targetCell) {
loop =
sourceCell.isAncestorOf(targetCell, options) ||
targetCell.isAncestorOf(sourceCell, options);
}
}
return loop;
}
getFragmentAncestor() {
const cells = [this, this.getSourceNode(), this.getTargetNode()].filter((item) => item != null);
return this.getCommonAncestor(...cells);
}
isFragmentDescendantOf(cell) {
const ancestor = this.getFragmentAncestor();
return (!!ancestor && (ancestor.id === cell.id || ancestor.isDescendantOf(cell)));
}
}
Edge.toStringTag = toStringTag;
Edge.defaultLabel = {
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
text: {
fill: '#000',
fontSize: 14,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
pointerEvents: 'none',
},
rect: {
ref: 'label',
fill: '#fff',
rx: 3,
ry: 3,
refWidth: 1,
refHeight: 1,
refX: 0,
refY: 0,
},
},
position: {
distance: 0.5,
},
};
Edge.registry = Registry.create({
type: 'edge',
process(shape, options) {
if (exist(shape, false)) {
throw new Error(`Edge with name '${shape}' was registered by anthor Node`);
}
if (typeof options === 'function') {
options.config({ shape });
return options;
}
let parent = Edge;
// default inherit from 'dege'
const { inherit = 'edge' } = options, others = __rest(options, ["inherit"]);
if (typeof inherit === 'string') {
const base = this.get(inherit || 'edge');
if (base == null && inherit) {
this.onNotFound(inherit, 'inherited');
}
else {
parent = base;
}
}
else {
parent = inherit;
}
if (others.constructorName == null) {
others.constructorName = shape;
}
const ctor = parent.define.call(parent, others);
ctor.config({ shape });
return ctor;
},
});
Edge.defaults = {};
setEdgeRegistry(Edge.registry);
Edge.config({
shape,
propHooks(metadata) {
const { label, vertices } = metadata, others = __rest(metadata, ["label", "vertices"]);
if (label) {
if (others.labels == null) {
others.labels = [];
}
const formated = typeof label === 'string' ? Edge.parseStringLabel(label) : label;
others.labels.push(formated);
}
if (vertices) {
if (Array.isArray(vertices)) {
others.vertices = vertices.map((item) => Point.create(item).toJSON());
}
}
return others;
},
});
Edge.registry.register(shape, Edge);
//# sourceMappingURL=edge.js.map