12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076 |
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
- return c > 3 && r && Object.defineProperty(target, key, r), r;
- };
- import { FunctionExt, Dijkstra, Basecoat } from '@antv/x6-common';
- import { Rectangle } from '@antv/x6-geometry';
- import { Cell } from './cell';
- import { Edge } from './edge';
- import { Node } from './node';
- import { Collection } from './collection';
- export class Model extends Basecoat {
- get [Symbol.toStringTag]() {
- return Model.toStringTag;
- }
- constructor(cells = []) {
- super();
- this.batches = {};
- this.addings = new WeakMap();
- this.nodes = {};
- this.edges = {};
- this.outgoings = {};
- this.incomings = {};
- this.collection = new Collection(cells);
- this.setup();
- }
- notify(name, args) {
- this.trigger(name, args);
- const graph = this.graph;
- if (graph) {
- if (name === 'sorted' || name === 'reseted' || name === 'updated') {
- graph.trigger(`model:${name}`, args);
- }
- else {
- graph.trigger(name, args);
- }
- }
- return this;
- }
- setup() {
- const collection = this.collection;
- collection.on('sorted', () => this.notify('sorted', null));
- collection.on('updated', (args) => this.notify('updated', args));
- collection.on('cell:change:zIndex', () => this.sortOnChangeZ());
- collection.on('added', ({ cell }) => {
- this.onCellAdded(cell);
- });
- collection.on('removed', (args) => {
- const cell = args.cell;
- this.onCellRemoved(cell, args.options);
- // Should trigger remove-event manually after cell was removed.
- this.notify('cell:removed', args);
- if (cell.isNode()) {
- this.notify('node:removed', Object.assign(Object.assign({}, args), { node: cell }));
- }
- else if (cell.isEdge()) {
- this.notify('edge:removed', Object.assign(Object.assign({}, args), { edge: cell }));
- }
- });
- collection.on('reseted', (args) => {
- this.onReset(args.current);
- this.notify('reseted', args);
- });
- collection.on('edge:change:source', ({ edge }) => this.onEdgeTerminalChanged(edge, 'source'));
- collection.on('edge:change:target', ({ edge }) => {
- this.onEdgeTerminalChanged(edge, 'target');
- });
- }
- sortOnChangeZ() {
- this.collection.sort();
- }
- onCellAdded(cell) {
- const cellId = cell.id;
- if (cell.isEdge()) {
- // Auto update edge's parent
- cell.updateParent();
- this.edges[cellId] = true;
- this.onEdgeTerminalChanged(cell, 'source');
- this.onEdgeTerminalChanged(cell, 'target');
- }
- else {
- this.nodes[cellId] = true;
- }
- }
- onCellRemoved(cell, options) {
- const cellId = cell.id;
- if (cell.isEdge()) {
- delete this.edges[cellId];
- const source = cell.getSource();
- const target = cell.getTarget();
- if (source && source.cell) {
- const cache = this.outgoings[source.cell];
- const index = cache ? cache.indexOf(cellId) : -1;
- if (index >= 0) {
- cache.splice(index, 1);
- if (cache.length === 0) {
- delete this.outgoings[source.cell];
- }
- }
- }
- if (target && target.cell) {
- const cache = this.incomings[target.cell];
- const index = cache ? cache.indexOf(cellId) : -1;
- if (index >= 0) {
- cache.splice(index, 1);
- if (cache.length === 0) {
- delete this.incomings[target.cell];
- }
- }
- }
- }
- else {
- delete this.nodes[cellId];
- }
- if (!options.clear) {
- if (options.disconnectEdges) {
- this.disconnectConnectedEdges(cell, options);
- }
- else {
- this.removeConnectedEdges(cell, options);
- }
- }
- if (cell.model === this) {
- cell.model = null;
- }
- }
- onReset(cells) {
- this.nodes = {};
- this.edges = {};
- this.outgoings = {};
- this.incomings = {};
- cells.forEach((cell) => this.onCellAdded(cell));
- }
- onEdgeTerminalChanged(edge, type) {
- const ref = type === 'source' ? this.outgoings : this.incomings;
- const prev = edge.previous(type);
- if (prev && prev.cell) {
- const cellId = Cell.isCell(prev.cell) ? prev.cell.id : prev.cell;
- const cache = ref[cellId];
- const index = cache ? cache.indexOf(edge.id) : -1;
- if (index >= 0) {
- cache.splice(index, 1);
- if (cache.length === 0) {
- delete ref[cellId];
- }
- }
- }
- const terminal = edge.getTerminal(type);
- if (terminal && terminal.cell) {
- const terminalId = Cell.isCell(terminal.cell)
- ? terminal.cell.id
- : terminal.cell;
- const cache = ref[terminalId] || [];
- const index = cache.indexOf(edge.id);
- if (index === -1) {
- cache.push(edge.id);
- }
- ref[terminalId] = cache;
- }
- }
- prepareCell(cell, options) {
- if (!cell.model && (!options || !options.dryrun)) {
- cell.model = this;
- }
- if (cell.zIndex == null) {
- cell.setZIndex(this.getMaxZIndex() + 1, { silent: true });
- }
- return cell;
- }
- resetCells(cells, options = {}) {
- // Do not update model at this time. Because if we just update the graph
- // with the same json-data, the edge will reference to the old nodes.
- cells.map((cell) => this.prepareCell(cell, Object.assign(Object.assign({}, options), { dryrun: true })));
- this.collection.reset(cells, options);
- // Update model and trigger edge update it's references
- cells.map((cell) => this.prepareCell(cell, { options }));
- return this;
- }
- clear(options = {}) {
- const raw = this.getCells();
- if (raw.length === 0) {
- return this;
- }
- const localOptions = Object.assign(Object.assign({}, options), { clear: true });
- this.batchUpdate('clear', () => {
- // The nodes come after the edges.
- const cells = raw.sort((a, b) => {
- const v1 = a.isEdge() ? 1 : 2;
- const v2 = b.isEdge() ? 1 : 2;
- return v1 - v2;
- });
- while (cells.length > 0) {
- // Note that all the edges are removed first, so it's safe to
- // remove the nodes without removing the connected edges first.
- const cell = cells.shift();
- if (cell) {
- cell.remove(localOptions);
- }
- }
- }, localOptions);
- return this;
- }
- addNode(metadata, options = {}) {
- const node = Node.isNode(metadata) ? metadata : this.createNode(metadata);
- this.addCell(node, options);
- return node;
- }
- updateNode(metadata, options = {}) {
- const node = this.createNode(metadata);
- const prop = node.getProp();
- node.dispose();
- return this.updateCell(prop, options);
- }
- createNode(metadata) {
- return Node.create(metadata);
- }
- addEdge(metadata, options = {}) {
- const edge = Edge.isEdge(metadata) ? metadata : this.createEdge(metadata);
- this.addCell(edge, options);
- return edge;
- }
- createEdge(metadata) {
- return Edge.create(metadata);
- }
- updateEdge(metadata, options = {}) {
- const edge = this.createEdge(metadata);
- const prop = edge.getProp();
- edge.dispose();
- return this.updateCell(prop, options);
- }
- addCell(cell, options = {}) {
- if (Array.isArray(cell)) {
- return this.addCells(cell, options);
- }
- if (!this.collection.has(cell) && !this.addings.has(cell)) {
- this.addings.set(cell, true);
- this.collection.add(this.prepareCell(cell, options), options);
- cell.eachChild((child) => this.addCell(child, options));
- this.addings.delete(cell);
- }
- return this;
- }
- addCells(cells, options = {}) {
- const count = cells.length;
- if (count === 0) {
- return this;
- }
- const localOptions = Object.assign(Object.assign({}, options), { position: count - 1, maxPosition: count - 1 });
- this.startBatch('add', Object.assign(Object.assign({}, localOptions), { cells }));
- cells.forEach((cell) => {
- this.addCell(cell, localOptions);
- localOptions.position -= 1;
- });
- this.stopBatch('add', Object.assign(Object.assign({}, localOptions), { cells }));
- return this;
- }
- updateCell(prop, options = {}) {
- const existing = prop.id && this.getCell(prop.id);
- if (existing) {
- return this.batchUpdate('update', () => {
- Object.entries(prop).forEach(([key, val]) => existing.setProp(key, val, options));
- return true;
- }, prop);
- }
- return false;
- }
- removeCell(obj, options = {}) {
- const cell = typeof obj === 'string' ? this.getCell(obj) : obj;
- if (cell && this.has(cell)) {
- return this.collection.remove(cell, options);
- }
- return null;
- }
- updateCellId(cell, newId) {
- if (cell.id === newId)
- return;
- this.startBatch('update', { id: newId });
- cell.prop('id', newId);
- const newCell = cell.clone({ keepId: true });
- this.addCell(newCell);
- // update connected edge terminal
- const edges = this.getConnectedEdges(cell);
- edges.forEach((edge) => {
- const sourceCell = edge.getSourceCell();
- const targetCell = edge.getTargetCell();
- if (sourceCell === cell) {
- edge.setSource(Object.assign(Object.assign({}, edge.getSource()), { cell: newId }));
- }
- if (targetCell === cell) {
- edge.setTarget(Object.assign(Object.assign({}, edge.getTarget()), { cell: newId }));
- }
- });
- this.removeCell(cell);
- this.stopBatch('update', { id: newId });
- return newCell;
- }
- removeCells(cells, options = {}) {
- if (cells.length) {
- return this.batchUpdate('remove', () => {
- return cells.map((cell) => this.removeCell(cell, options));
- });
- }
- return [];
- }
- removeConnectedEdges(cell, options = {}) {
- const edges = this.getConnectedEdges(cell);
- edges.forEach((edge) => {
- edge.remove(options);
- });
- return edges;
- }
- disconnectConnectedEdges(cell, options = {}) {
- const cellId = typeof cell === 'string' ? cell : cell.id;
- this.getConnectedEdges(cell).forEach((edge) => {
- const sourceCellId = edge.getSourceCellId();
- const targetCellId = edge.getTargetCellId();
- if (sourceCellId === cellId) {
- edge.setSource({ x: 0, y: 0 }, options);
- }
- if (targetCellId === cellId) {
- edge.setTarget({ x: 0, y: 0 }, options);
- }
- });
- }
- has(obj) {
- return this.collection.has(obj);
- }
- total() {
- return this.collection.length;
- }
- indexOf(cell) {
- return this.collection.indexOf(cell);
- }
- /**
- * Returns a cell from the graph by its id.
- */
- getCell(id) {
- return this.collection.get(id);
- }
- /**
- * Returns all the nodes and edges in the graph.
- */
- getCells() {
- return this.collection.toArray();
- }
- /**
- * Returns the first cell (node or edge) in the graph. The first cell is
- * defined as the cell with the lowest `zIndex`.
- */
- getFirstCell() {
- return this.collection.first();
- }
- /**
- * Returns the last cell (node or edge) in the graph. The last cell is
- * defined as the cell with the highest `zIndex`.
- */
- getLastCell() {
- return this.collection.last();
- }
- /**
- * Returns the lowest `zIndex` value in the graph.
- */
- getMinZIndex() {
- const first = this.collection.first();
- return first ? first.getZIndex() || 0 : 0;
- }
- /**
- * Returns the highest `zIndex` value in the graph.
- */
- getMaxZIndex() {
- const last = this.collection.last();
- return last ? last.getZIndex() || 0 : 0;
- }
- getCellsFromCache(cache) {
- return cache
- ? Object.keys(cache)
- .map((id) => this.getCell(id))
- .filter((cell) => cell != null)
- : [];
- }
- /**
- * Returns all the nodes in the graph.
- */
- getNodes() {
- return this.getCellsFromCache(this.nodes);
- }
- /**
- * Returns all the edges in the graph.
- */
- getEdges() {
- return this.getCellsFromCache(this.edges);
- }
- /**
- * Returns all outgoing edges for the node.
- */
- getOutgoingEdges(cell) {
- const cellId = typeof cell === 'string' ? cell : cell.id;
- const cellIds = this.outgoings[cellId];
- return cellIds
- ? cellIds
- .map((id) => this.getCell(id))
- .filter((cell) => cell && cell.isEdge())
- : null;
- }
- /**
- * Returns all incoming edges for the node.
- */
- getIncomingEdges(cell) {
- const cellId = typeof cell === 'string' ? cell : cell.id;
- const cellIds = this.incomings[cellId];
- return cellIds
- ? cellIds
- .map((id) => this.getCell(id))
- .filter((cell) => cell && cell.isEdge())
- : null;
- }
- /**
- * Returns edges connected with cell.
- */
- getConnectedEdges(cell, options = {}) {
- const result = [];
- const node = typeof cell === 'string' ? this.getCell(cell) : cell;
- if (node == null) {
- return result;
- }
- const cache = {};
- const indirect = options.indirect;
- let incoming = options.incoming;
- let outgoing = options.outgoing;
- if (incoming == null && outgoing == null) {
- incoming = outgoing = true;
- }
- const collect = (cell, isOutgoing) => {
- const edges = isOutgoing
- ? this.getOutgoingEdges(cell)
- : this.getIncomingEdges(cell);
- if (edges != null) {
- edges.forEach((edge) => {
- if (cache[edge.id]) {
- return;
- }
- result.push(edge);
- cache[edge.id] = true;
- if (indirect) {
- if (incoming) {
- collect(edge, false);
- }
- if (outgoing) {
- collect(edge, true);
- }
- }
- });
- }
- if (indirect && cell.isEdge()) {
- const terminal = isOutgoing
- ? cell.getTargetCell()
- : cell.getSourceCell();
- if (terminal && terminal.isEdge()) {
- if (!cache[terminal.id]) {
- result.push(terminal);
- collect(terminal, isOutgoing);
- }
- }
- }
- };
- if (outgoing) {
- collect(node, true);
- }
- if (incoming) {
- collect(node, false);
- }
- if (options.deep) {
- const descendants = node.getDescendants({ deep: true });
- const embedsCache = {};
- descendants.forEach((cell) => {
- if (cell.isNode()) {
- embedsCache[cell.id] = true;
- }
- });
- const collectSub = (cell, isOutgoing) => {
- const edges = isOutgoing
- ? this.getOutgoingEdges(cell.id)
- : this.getIncomingEdges(cell.id);
- if (edges != null) {
- edges.forEach((edge) => {
- if (!cache[edge.id]) {
- const sourceCell = edge.getSourceCell();
- const targetCell = edge.getTargetCell();
- if (!options.enclosed &&
- sourceCell &&
- embedsCache[sourceCell.id] &&
- targetCell &&
- embedsCache[targetCell.id]) {
- return;
- }
- result.push(edge);
- cache[edge.id] = true;
- }
- });
- }
- };
- descendants.forEach((cell) => {
- if (cell.isEdge()) {
- return;
- }
- if (outgoing) {
- collectSub(cell, true);
- }
- if (incoming) {
- collectSub(cell, false);
- }
- });
- }
- return result;
- }
- isBoundary(cell, isOrigin) {
- const node = typeof cell === 'string' ? this.getCell(cell) : cell;
- const arr = isOrigin
- ? this.getIncomingEdges(node)
- : this.getOutgoingEdges(node);
- return arr == null || arr.length === 0;
- }
- getBoundaryNodes(isOrigin) {
- const result = [];
- Object.keys(this.nodes).forEach((nodeId) => {
- if (this.isBoundary(nodeId, isOrigin)) {
- const node = this.getCell(nodeId);
- if (node) {
- result.push(node);
- }
- }
- });
- return result;
- }
- /**
- * Returns an array of all the roots of the graph.
- */
- getRoots() {
- return this.getBoundaryNodes(true);
- }
- /**
- * Returns an array of all the leafs of the graph.
- */
- getLeafs() {
- return this.getBoundaryNodes(false);
- }
- /**
- * Returns `true` if the node is a root node, i.e. there is no edges
- * coming to the node.
- */
- isRoot(cell) {
- return this.isBoundary(cell, true);
- }
- /**
- * Returns `true` if the node is a leaf node, i.e. there is no edges
- * going out from the node.
- */
- isLeaf(cell) {
- return this.isBoundary(cell, false);
- }
- /**
- * Returns all the neighbors of node in the graph. Neighbors are all
- * the nodes connected to node via either incoming or outgoing edge.
- */
- getNeighbors(cell, options = {}) {
- let incoming = options.incoming;
- let outgoing = options.outgoing;
- if (incoming == null && outgoing == null) {
- incoming = outgoing = true;
- }
- const edges = this.getConnectedEdges(cell, options);
- const map = edges.reduce((memo, edge) => {
- const hasLoop = edge.hasLoop(options);
- const sourceCell = edge.getSourceCell();
- const targetCell = edge.getTargetCell();
- if (incoming &&
- sourceCell &&
- sourceCell.isNode() &&
- !memo[sourceCell.id]) {
- if (hasLoop ||
- (sourceCell !== cell &&
- (!options.deep || !sourceCell.isDescendantOf(cell)))) {
- memo[sourceCell.id] = sourceCell;
- }
- }
- if (outgoing &&
- targetCell &&
- targetCell.isNode() &&
- !memo[targetCell.id]) {
- if (hasLoop ||
- (targetCell !== cell &&
- (!options.deep || !targetCell.isDescendantOf(cell)))) {
- memo[targetCell.id] = targetCell;
- }
- }
- return memo;
- }, {});
- if (cell.isEdge()) {
- if (incoming) {
- const sourceCell = cell.getSourceCell();
- if (sourceCell && sourceCell.isNode() && !map[sourceCell.id]) {
- map[sourceCell.id] = sourceCell;
- }
- }
- if (outgoing) {
- const targetCell = cell.getTargetCell();
- if (targetCell && targetCell.isNode() && !map[targetCell.id]) {
- map[targetCell.id] = targetCell;
- }
- }
- }
- return Object.keys(map).map((id) => map[id]);
- }
- /**
- * Returns `true` if `cell2` is a neighbor of `cell1`.
- */
- isNeighbor(cell1, cell2, options = {}) {
- let incoming = options.incoming;
- let outgoing = options.outgoing;
- if (incoming == null && outgoing == null) {
- incoming = outgoing = true;
- }
- return this.getConnectedEdges(cell1, options).some((edge) => {
- const sourceCell = edge.getSourceCell();
- const targetCell = edge.getTargetCell();
- if (incoming && sourceCell && sourceCell.id === cell2.id) {
- return true;
- }
- if (outgoing && targetCell && targetCell.id === cell2.id) {
- return true;
- }
- return false;
- });
- }
- getSuccessors(cell, options = {}) {
- const successors = [];
- this.search(cell, (curr, distance) => {
- if (curr !== cell && this.matchDistance(distance, options.distance)) {
- successors.push(curr);
- }
- }, Object.assign(Object.assign({}, options), { outgoing: true }));
- return successors;
- }
- /**
- * Returns `true` if `cell2` is a successor of `cell1`.
- */
- isSuccessor(cell1, cell2, options = {}) {
- let result = false;
- this.search(cell1, (curr, distance) => {
- if (curr === cell2 &&
- curr !== cell1 &&
- this.matchDistance(distance, options.distance)) {
- result = true;
- return false;
- }
- }, Object.assign(Object.assign({}, options), { outgoing: true }));
- return result;
- }
- getPredecessors(cell, options = {}) {
- const predecessors = [];
- this.search(cell, (curr, distance) => {
- if (curr !== cell && this.matchDistance(distance, options.distance)) {
- predecessors.push(curr);
- }
- }, Object.assign(Object.assign({}, options), { incoming: true }));
- return predecessors;
- }
- /**
- * Returns `true` if `cell2` is a predecessor of `cell1`.
- */
- isPredecessor(cell1, cell2, options = {}) {
- let result = false;
- this.search(cell1, (curr, distance) => {
- if (curr === cell2 &&
- curr !== cell1 &&
- this.matchDistance(distance, options.distance)) {
- result = true;
- return false;
- }
- }, Object.assign(Object.assign({}, options), { incoming: true }));
- return result;
- }
- matchDistance(distance, preset) {
- if (preset == null) {
- return true;
- }
- if (typeof preset === 'function') {
- return preset(distance);
- }
- if (Array.isArray(preset) && preset.includes(distance)) {
- return true;
- }
- return distance === preset;
- }
- /**
- * Returns the common ancestor of the passed cells.
- */
- getCommonAncestor(...cells) {
- const arr = [];
- cells.forEach((item) => {
- if (item) {
- if (Array.isArray(item)) {
- arr.push(...item);
- }
- else {
- arr.push(item);
- }
- }
- });
- return Cell.getCommonAncestor(...arr);
- }
- /**
- * Returns an array of cells that result from finding nodes/edges that
- * are connected to any of the cells in the cells array. This function
- * loops over cells and if the current cell is a edge, it collects its
- * source/target nodes; if it is an node, it collects its incoming and
- * outgoing edges if both the edge terminal (source/target) are in the
- * cells array.
- */
- getSubGraph(cells, options = {}) {
- const subgraph = [];
- const cache = {};
- const nodes = [];
- const edges = [];
- const collect = (cell) => {
- if (!cache[cell.id]) {
- subgraph.push(cell);
- cache[cell.id] = cell;
- if (cell.isEdge()) {
- edges.push(cell);
- }
- if (cell.isNode()) {
- nodes.push(cell);
- }
- }
- };
- cells.forEach((cell) => {
- collect(cell);
- if (options.deep) {
- const descendants = cell.getDescendants({ deep: true });
- descendants.forEach((descendant) => collect(descendant));
- }
- });
- edges.forEach((edge) => {
- // For edges, include their source & target
- const sourceCell = edge.getSourceCell();
- const targetCell = edge.getTargetCell();
- if (sourceCell && !cache[sourceCell.id]) {
- subgraph.push(sourceCell);
- cache[sourceCell.id] = sourceCell;
- if (sourceCell.isNode()) {
- nodes.push(sourceCell);
- }
- }
- if (targetCell && !cache[targetCell.id]) {
- subgraph.push(targetCell);
- cache[targetCell.id] = targetCell;
- if (targetCell.isNode()) {
- nodes.push(targetCell);
- }
- }
- });
- nodes.forEach((node) => {
- // For nodes, include their connected edges if their source/target
- // is in the subgraph.
- const edges = this.getConnectedEdges(node, options);
- edges.forEach((edge) => {
- const sourceCell = edge.getSourceCell();
- const targetCell = edge.getTargetCell();
- if (!cache[edge.id] &&
- sourceCell &&
- cache[sourceCell.id] &&
- targetCell &&
- cache[targetCell.id]) {
- subgraph.push(edge);
- cache[edge.id] = edge;
- }
- });
- });
- return subgraph;
- }
- /**
- * Clones the whole subgraph (including all the connected links whose
- * source/target is in the subgraph). If `options.deep` is `true`, also
- * take into account all the embedded cells of all the subgraph cells.
- *
- * Returns a map of the form: { [original cell ID]: [clone] }.
- */
- cloneSubGraph(cells, options = {}) {
- const subgraph = this.getSubGraph(cells, options);
- return this.cloneCells(subgraph);
- }
- cloneCells(cells) {
- return Cell.cloneCells(cells);
- }
- getNodesFromPoint(x, y) {
- const p = typeof x === 'number' ? { x, y: y || 0 } : x;
- return this.getNodes().filter((node) => {
- return node.getBBox().containsPoint(p);
- });
- }
- getNodesInArea(x, y, w, h, options) {
- const rect = typeof x === 'number'
- ? new Rectangle(x, y, w, h)
- : Rectangle.create(x);
- const opts = typeof x === 'number' ? options : y;
- const strict = opts && opts.strict;
- return this.getNodes().filter((node) => {
- const bbox = node.getBBox();
- return strict ? rect.containsRect(bbox) : rect.isIntersectWithRect(bbox);
- });
- }
- getEdgesInArea(x, y, w, h, options) {
- const rect = typeof x === 'number'
- ? new Rectangle(x, y, w, h)
- : Rectangle.create(x);
- const opts = typeof x === 'number' ? options : y;
- const strict = opts && opts.strict;
- return this.getEdges().filter((edge) => {
- const bbox = edge.getBBox();
- if (bbox.width === 0) {
- bbox.inflate(1, 0);
- }
- else if (bbox.height === 0) {
- bbox.inflate(0, 1);
- }
- return strict ? rect.containsRect(bbox) : rect.isIntersectWithRect(bbox);
- });
- }
- getNodesUnderNode(node, options = {}) {
- const bbox = node.getBBox();
- const nodes = options.by == null || options.by === 'bbox'
- ? this.getNodesInArea(bbox)
- : this.getNodesFromPoint(bbox[options.by]);
- return nodes.filter((curr) => node.id !== curr.id && !curr.isDescendantOf(node));
- }
- /**
- * Returns the bounding box that surrounds all cells in the graph.
- */
- getAllCellsBBox() {
- return this.getCellsBBox(this.getCells());
- }
- /**
- * Returns the bounding box that surrounds all the given cells.
- */
- getCellsBBox(cells, options = {}) {
- return Cell.getCellsBBox(cells, options);
- }
- // #region search
- search(cell, iterator, options = {}) {
- if (options.breadthFirst) {
- this.breadthFirstSearch(cell, iterator, options);
- }
- else {
- this.depthFirstSearch(cell, iterator, options);
- }
- }
- breadthFirstSearch(cell, iterator, options = {}) {
- const queue = [];
- const visited = {};
- const distance = {};
- queue.push(cell);
- distance[cell.id] = 0;
- while (queue.length > 0) {
- const next = queue.shift();
- if (next == null || visited[next.id]) {
- continue;
- }
- visited[next.id] = true;
- if (FunctionExt.call(iterator, this, next, distance[next.id]) === false) {
- continue;
- }
- const neighbors = this.getNeighbors(next, options);
- neighbors.forEach((neighbor) => {
- distance[neighbor.id] = distance[next.id] + 1;
- queue.push(neighbor);
- });
- }
- }
- depthFirstSearch(cell, iterator, options = {}) {
- const queue = [];
- const visited = {};
- const distance = {};
- queue.push(cell);
- distance[cell.id] = 0;
- while (queue.length > 0) {
- const next = queue.pop();
- if (next == null || visited[next.id]) {
- continue;
- }
- visited[next.id] = true;
- if (FunctionExt.call(iterator, this, next, distance[next.id]) === false) {
- continue;
- }
- const neighbors = this.getNeighbors(next, options);
- const lastIndex = queue.length;
- neighbors.forEach((neighbor) => {
- distance[neighbor.id] = distance[next.id] + 1;
- queue.splice(lastIndex, 0, neighbor);
- });
- }
- }
- // #endregion
- // #region shortest path
- /** *
- * Returns an array of IDs of nodes on the shortest
- * path between source and target.
- */
- getShortestPath(source, target, options = {}) {
- const adjacencyList = {};
- this.getEdges().forEach((edge) => {
- const sourceId = edge.getSourceCellId();
- const targetId = edge.getTargetCellId();
- if (sourceId && targetId) {
- if (!adjacencyList[sourceId]) {
- adjacencyList[sourceId] = [];
- }
- if (!adjacencyList[targetId]) {
- adjacencyList[targetId] = [];
- }
- adjacencyList[sourceId].push(targetId);
- if (!options.directed) {
- adjacencyList[targetId].push(sourceId);
- }
- }
- });
- const sourceId = typeof source === 'string' ? source : source.id;
- const previous = Dijkstra.run(adjacencyList, sourceId, options.weight);
- const path = [];
- let targetId = typeof target === 'string' ? target : target.id;
- if (previous[targetId]) {
- path.push(targetId);
- }
- while ((targetId = previous[targetId])) {
- path.unshift(targetId);
- }
- return path;
- }
- // #endregion
- // #region transform
- /**
- * Translate all cells in the graph by `tx` and `ty` pixels.
- */
- translate(tx, ty, options) {
- this.getCells()
- .filter((cell) => !cell.hasParent())
- .forEach((cell) => cell.translate(tx, ty, options));
- return this;
- }
- resize(width, height, options) {
- return this.resizeCells(width, height, this.getCells(), options);
- }
- resizeCells(width, height, cells, options = {}) {
- const bbox = this.getCellsBBox(cells);
- if (bbox) {
- const sx = Math.max(width / bbox.width, 0);
- const sy = Math.max(height / bbox.height, 0);
- const origin = bbox.getOrigin();
- cells.forEach((cell) => cell.scale(sx, sy, origin, options));
- }
- return this;
- }
- // #endregion
- // #region serialize/deserialize
- toJSON(options = {}) {
- return Model.toJSON(this.getCells(), options);
- }
- parseJSON(data) {
- return Model.fromJSON(data);
- }
- fromJSON(data, options = {}) {
- const cells = this.parseJSON(data);
- this.resetCells(cells, options);
- return this;
- }
- // #endregion
- // #region batch
- startBatch(name, data = {}) {
- this.batches[name] = (this.batches[name] || 0) + 1;
- this.notify('batch:start', { name, data });
- return this;
- }
- stopBatch(name, data = {}) {
- this.batches[name] = (this.batches[name] || 0) - 1;
- this.notify('batch:stop', { name, data });
- return this;
- }
- batchUpdate(name, execute, data = {}) {
- this.startBatch(name, data);
- const result = execute();
- this.stopBatch(name, data);
- return result;
- }
- hasActiveBatch(name = Object.keys(this.batches)) {
- const names = Array.isArray(name) ? name : [name];
- return names.some((batch) => this.batches[batch] > 0);
- }
- // #endregion
- dispose() {
- this.collection.dispose();
- }
- }
- __decorate([
- Model.dispose()
- ], Model.prototype, "dispose", null);
- (function (Model) {
- Model.toStringTag = `X6.${Model.name}`;
- function isModel(instance) {
- if (instance == null) {
- return false;
- }
- if (instance instanceof Model) {
- return true;
- }
- const tag = instance[Symbol.toStringTag];
- const model = instance;
- if ((tag == null || tag === Model.toStringTag) &&
- typeof model.addNode === 'function' &&
- typeof model.addEdge === 'function' &&
- model.collection != null) {
- return true;
- }
- return false;
- }
- Model.isModel = isModel;
- })(Model || (Model = {}));
- (function (Model) {
- function toJSON(cells, options = {}) {
- return {
- cells: cells.map((cell) => cell.toJSON(options)),
- };
- }
- Model.toJSON = toJSON;
- function fromJSON(data) {
- const cells = [];
- if (Array.isArray(data)) {
- cells.push(...data);
- }
- else {
- if (data.cells) {
- cells.push(...data.cells);
- }
- if (data.nodes) {
- data.nodes.forEach((node) => {
- if (node.shape == null) {
- node.shape = 'rect';
- }
- cells.push(node);
- });
- }
- if (data.edges) {
- data.edges.forEach((edge) => {
- if (edge.shape == null) {
- edge.shape = 'edge';
- }
- cells.push(edge);
- });
- }
- }
- return cells.map((cell) => {
- const type = cell.shape;
- if (type) {
- if (Node.registry.exist(type)) {
- return Node.create(cell);
- }
- if (Edge.registry.exist(type)) {
- return Edge.create(cell);
- }
- }
- throw new Error('The `shape` should be specified when creating a node/edge instance');
- });
- }
- Model.fromJSON = fromJSON;
- })(Model || (Model = {}));
- //# sourceMappingURL=model.js.map
|