model.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  1. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  2. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  3. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  4. 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;
  5. return c > 3 && r && Object.defineProperty(target, key, r), r;
  6. };
  7. import { FunctionExt, Dijkstra, Basecoat } from '@antv/x6-common';
  8. import { Rectangle } from '@antv/x6-geometry';
  9. import { Cell } from './cell';
  10. import { Edge } from './edge';
  11. import { Node } from './node';
  12. import { Collection } from './collection';
  13. export class Model extends Basecoat {
  14. get [Symbol.toStringTag]() {
  15. return Model.toStringTag;
  16. }
  17. constructor(cells = []) {
  18. super();
  19. this.batches = {};
  20. this.addings = new WeakMap();
  21. this.nodes = {};
  22. this.edges = {};
  23. this.outgoings = {};
  24. this.incomings = {};
  25. this.collection = new Collection(cells);
  26. this.setup();
  27. }
  28. notify(name, args) {
  29. this.trigger(name, args);
  30. const graph = this.graph;
  31. if (graph) {
  32. if (name === 'sorted' || name === 'reseted' || name === 'updated') {
  33. graph.trigger(`model:${name}`, args);
  34. }
  35. else {
  36. graph.trigger(name, args);
  37. }
  38. }
  39. return this;
  40. }
  41. setup() {
  42. const collection = this.collection;
  43. collection.on('sorted', () => this.notify('sorted', null));
  44. collection.on('updated', (args) => this.notify('updated', args));
  45. collection.on('cell:change:zIndex', () => this.sortOnChangeZ());
  46. collection.on('added', ({ cell }) => {
  47. this.onCellAdded(cell);
  48. });
  49. collection.on('removed', (args) => {
  50. const cell = args.cell;
  51. this.onCellRemoved(cell, args.options);
  52. // Should trigger remove-event manually after cell was removed.
  53. this.notify('cell:removed', args);
  54. if (cell.isNode()) {
  55. this.notify('node:removed', Object.assign(Object.assign({}, args), { node: cell }));
  56. }
  57. else if (cell.isEdge()) {
  58. this.notify('edge:removed', Object.assign(Object.assign({}, args), { edge: cell }));
  59. }
  60. });
  61. collection.on('reseted', (args) => {
  62. this.onReset(args.current);
  63. this.notify('reseted', args);
  64. });
  65. collection.on('edge:change:source', ({ edge }) => this.onEdgeTerminalChanged(edge, 'source'));
  66. collection.on('edge:change:target', ({ edge }) => {
  67. this.onEdgeTerminalChanged(edge, 'target');
  68. });
  69. }
  70. sortOnChangeZ() {
  71. this.collection.sort();
  72. }
  73. onCellAdded(cell) {
  74. const cellId = cell.id;
  75. if (cell.isEdge()) {
  76. // Auto update edge's parent
  77. cell.updateParent();
  78. this.edges[cellId] = true;
  79. this.onEdgeTerminalChanged(cell, 'source');
  80. this.onEdgeTerminalChanged(cell, 'target');
  81. }
  82. else {
  83. this.nodes[cellId] = true;
  84. }
  85. }
  86. onCellRemoved(cell, options) {
  87. const cellId = cell.id;
  88. if (cell.isEdge()) {
  89. delete this.edges[cellId];
  90. const source = cell.getSource();
  91. const target = cell.getTarget();
  92. if (source && source.cell) {
  93. const cache = this.outgoings[source.cell];
  94. const index = cache ? cache.indexOf(cellId) : -1;
  95. if (index >= 0) {
  96. cache.splice(index, 1);
  97. if (cache.length === 0) {
  98. delete this.outgoings[source.cell];
  99. }
  100. }
  101. }
  102. if (target && target.cell) {
  103. const cache = this.incomings[target.cell];
  104. const index = cache ? cache.indexOf(cellId) : -1;
  105. if (index >= 0) {
  106. cache.splice(index, 1);
  107. if (cache.length === 0) {
  108. delete this.incomings[target.cell];
  109. }
  110. }
  111. }
  112. }
  113. else {
  114. delete this.nodes[cellId];
  115. }
  116. if (!options.clear) {
  117. if (options.disconnectEdges) {
  118. this.disconnectConnectedEdges(cell, options);
  119. }
  120. else {
  121. this.removeConnectedEdges(cell, options);
  122. }
  123. }
  124. if (cell.model === this) {
  125. cell.model = null;
  126. }
  127. }
  128. onReset(cells) {
  129. this.nodes = {};
  130. this.edges = {};
  131. this.outgoings = {};
  132. this.incomings = {};
  133. cells.forEach((cell) => this.onCellAdded(cell));
  134. }
  135. onEdgeTerminalChanged(edge, type) {
  136. const ref = type === 'source' ? this.outgoings : this.incomings;
  137. const prev = edge.previous(type);
  138. if (prev && prev.cell) {
  139. const cellId = Cell.isCell(prev.cell) ? prev.cell.id : prev.cell;
  140. const cache = ref[cellId];
  141. const index = cache ? cache.indexOf(edge.id) : -1;
  142. if (index >= 0) {
  143. cache.splice(index, 1);
  144. if (cache.length === 0) {
  145. delete ref[cellId];
  146. }
  147. }
  148. }
  149. const terminal = edge.getTerminal(type);
  150. if (terminal && terminal.cell) {
  151. const terminalId = Cell.isCell(terminal.cell)
  152. ? terminal.cell.id
  153. : terminal.cell;
  154. const cache = ref[terminalId] || [];
  155. const index = cache.indexOf(edge.id);
  156. if (index === -1) {
  157. cache.push(edge.id);
  158. }
  159. ref[terminalId] = cache;
  160. }
  161. }
  162. prepareCell(cell, options) {
  163. if (!cell.model && (!options || !options.dryrun)) {
  164. cell.model = this;
  165. }
  166. if (cell.zIndex == null) {
  167. cell.setZIndex(this.getMaxZIndex() + 1, { silent: true });
  168. }
  169. return cell;
  170. }
  171. resetCells(cells, options = {}) {
  172. // Do not update model at this time. Because if we just update the graph
  173. // with the same json-data, the edge will reference to the old nodes.
  174. cells.map((cell) => this.prepareCell(cell, Object.assign(Object.assign({}, options), { dryrun: true })));
  175. this.collection.reset(cells, options);
  176. // Update model and trigger edge update it's references
  177. cells.map((cell) => this.prepareCell(cell, { options }));
  178. return this;
  179. }
  180. clear(options = {}) {
  181. const raw = this.getCells();
  182. if (raw.length === 0) {
  183. return this;
  184. }
  185. const localOptions = Object.assign(Object.assign({}, options), { clear: true });
  186. this.batchUpdate('clear', () => {
  187. // The nodes come after the edges.
  188. const cells = raw.sort((a, b) => {
  189. const v1 = a.isEdge() ? 1 : 2;
  190. const v2 = b.isEdge() ? 1 : 2;
  191. return v1 - v2;
  192. });
  193. while (cells.length > 0) {
  194. // Note that all the edges are removed first, so it's safe to
  195. // remove the nodes without removing the connected edges first.
  196. const cell = cells.shift();
  197. if (cell) {
  198. cell.remove(localOptions);
  199. }
  200. }
  201. }, localOptions);
  202. return this;
  203. }
  204. addNode(metadata, options = {}) {
  205. const node = Node.isNode(metadata) ? metadata : this.createNode(metadata);
  206. this.addCell(node, options);
  207. return node;
  208. }
  209. updateNode(metadata, options = {}) {
  210. const node = this.createNode(metadata);
  211. const prop = node.getProp();
  212. node.dispose();
  213. return this.updateCell(prop, options);
  214. }
  215. createNode(metadata) {
  216. return Node.create(metadata);
  217. }
  218. addEdge(metadata, options = {}) {
  219. const edge = Edge.isEdge(metadata) ? metadata : this.createEdge(metadata);
  220. this.addCell(edge, options);
  221. return edge;
  222. }
  223. createEdge(metadata) {
  224. return Edge.create(metadata);
  225. }
  226. updateEdge(metadata, options = {}) {
  227. const edge = this.createEdge(metadata);
  228. const prop = edge.getProp();
  229. edge.dispose();
  230. return this.updateCell(prop, options);
  231. }
  232. addCell(cell, options = {}) {
  233. if (Array.isArray(cell)) {
  234. return this.addCells(cell, options);
  235. }
  236. if (!this.collection.has(cell) && !this.addings.has(cell)) {
  237. this.addings.set(cell, true);
  238. this.collection.add(this.prepareCell(cell, options), options);
  239. cell.eachChild((child) => this.addCell(child, options));
  240. this.addings.delete(cell);
  241. }
  242. return this;
  243. }
  244. addCells(cells, options = {}) {
  245. const count = cells.length;
  246. if (count === 0) {
  247. return this;
  248. }
  249. const localOptions = Object.assign(Object.assign({}, options), { position: count - 1, maxPosition: count - 1 });
  250. this.startBatch('add', Object.assign(Object.assign({}, localOptions), { cells }));
  251. cells.forEach((cell) => {
  252. this.addCell(cell, localOptions);
  253. localOptions.position -= 1;
  254. });
  255. this.stopBatch('add', Object.assign(Object.assign({}, localOptions), { cells }));
  256. return this;
  257. }
  258. updateCell(prop, options = {}) {
  259. const existing = prop.id && this.getCell(prop.id);
  260. if (existing) {
  261. return this.batchUpdate('update', () => {
  262. Object.entries(prop).forEach(([key, val]) => existing.setProp(key, val, options));
  263. return true;
  264. }, prop);
  265. }
  266. return false;
  267. }
  268. removeCell(obj, options = {}) {
  269. const cell = typeof obj === 'string' ? this.getCell(obj) : obj;
  270. if (cell && this.has(cell)) {
  271. return this.collection.remove(cell, options);
  272. }
  273. return null;
  274. }
  275. updateCellId(cell, newId) {
  276. if (cell.id === newId)
  277. return;
  278. this.startBatch('update', { id: newId });
  279. cell.prop('id', newId);
  280. const newCell = cell.clone({ keepId: true });
  281. this.addCell(newCell);
  282. // update connected edge terminal
  283. const edges = this.getConnectedEdges(cell);
  284. edges.forEach((edge) => {
  285. const sourceCell = edge.getSourceCell();
  286. const targetCell = edge.getTargetCell();
  287. if (sourceCell === cell) {
  288. edge.setSource(Object.assign(Object.assign({}, edge.getSource()), { cell: newId }));
  289. }
  290. if (targetCell === cell) {
  291. edge.setTarget(Object.assign(Object.assign({}, edge.getTarget()), { cell: newId }));
  292. }
  293. });
  294. this.removeCell(cell);
  295. this.stopBatch('update', { id: newId });
  296. return newCell;
  297. }
  298. removeCells(cells, options = {}) {
  299. if (cells.length) {
  300. return this.batchUpdate('remove', () => {
  301. return cells.map((cell) => this.removeCell(cell, options));
  302. });
  303. }
  304. return [];
  305. }
  306. removeConnectedEdges(cell, options = {}) {
  307. const edges = this.getConnectedEdges(cell);
  308. edges.forEach((edge) => {
  309. edge.remove(options);
  310. });
  311. return edges;
  312. }
  313. disconnectConnectedEdges(cell, options = {}) {
  314. const cellId = typeof cell === 'string' ? cell : cell.id;
  315. this.getConnectedEdges(cell).forEach((edge) => {
  316. const sourceCellId = edge.getSourceCellId();
  317. const targetCellId = edge.getTargetCellId();
  318. if (sourceCellId === cellId) {
  319. edge.setSource({ x: 0, y: 0 }, options);
  320. }
  321. if (targetCellId === cellId) {
  322. edge.setTarget({ x: 0, y: 0 }, options);
  323. }
  324. });
  325. }
  326. has(obj) {
  327. return this.collection.has(obj);
  328. }
  329. total() {
  330. return this.collection.length;
  331. }
  332. indexOf(cell) {
  333. return this.collection.indexOf(cell);
  334. }
  335. /**
  336. * Returns a cell from the graph by its id.
  337. */
  338. getCell(id) {
  339. return this.collection.get(id);
  340. }
  341. /**
  342. * Returns all the nodes and edges in the graph.
  343. */
  344. getCells() {
  345. return this.collection.toArray();
  346. }
  347. /**
  348. * Returns the first cell (node or edge) in the graph. The first cell is
  349. * defined as the cell with the lowest `zIndex`.
  350. */
  351. getFirstCell() {
  352. return this.collection.first();
  353. }
  354. /**
  355. * Returns the last cell (node or edge) in the graph. The last cell is
  356. * defined as the cell with the highest `zIndex`.
  357. */
  358. getLastCell() {
  359. return this.collection.last();
  360. }
  361. /**
  362. * Returns the lowest `zIndex` value in the graph.
  363. */
  364. getMinZIndex() {
  365. const first = this.collection.first();
  366. return first ? first.getZIndex() || 0 : 0;
  367. }
  368. /**
  369. * Returns the highest `zIndex` value in the graph.
  370. */
  371. getMaxZIndex() {
  372. const last = this.collection.last();
  373. return last ? last.getZIndex() || 0 : 0;
  374. }
  375. getCellsFromCache(cache) {
  376. return cache
  377. ? Object.keys(cache)
  378. .map((id) => this.getCell(id))
  379. .filter((cell) => cell != null)
  380. : [];
  381. }
  382. /**
  383. * Returns all the nodes in the graph.
  384. */
  385. getNodes() {
  386. return this.getCellsFromCache(this.nodes);
  387. }
  388. /**
  389. * Returns all the edges in the graph.
  390. */
  391. getEdges() {
  392. return this.getCellsFromCache(this.edges);
  393. }
  394. /**
  395. * Returns all outgoing edges for the node.
  396. */
  397. getOutgoingEdges(cell) {
  398. const cellId = typeof cell === 'string' ? cell : cell.id;
  399. const cellIds = this.outgoings[cellId];
  400. return cellIds
  401. ? cellIds
  402. .map((id) => this.getCell(id))
  403. .filter((cell) => cell && cell.isEdge())
  404. : null;
  405. }
  406. /**
  407. * Returns all incoming edges for the node.
  408. */
  409. getIncomingEdges(cell) {
  410. const cellId = typeof cell === 'string' ? cell : cell.id;
  411. const cellIds = this.incomings[cellId];
  412. return cellIds
  413. ? cellIds
  414. .map((id) => this.getCell(id))
  415. .filter((cell) => cell && cell.isEdge())
  416. : null;
  417. }
  418. /**
  419. * Returns edges connected with cell.
  420. */
  421. getConnectedEdges(cell, options = {}) {
  422. const result = [];
  423. const node = typeof cell === 'string' ? this.getCell(cell) : cell;
  424. if (node == null) {
  425. return result;
  426. }
  427. const cache = {};
  428. const indirect = options.indirect;
  429. let incoming = options.incoming;
  430. let outgoing = options.outgoing;
  431. if (incoming == null && outgoing == null) {
  432. incoming = outgoing = true;
  433. }
  434. const collect = (cell, isOutgoing) => {
  435. const edges = isOutgoing
  436. ? this.getOutgoingEdges(cell)
  437. : this.getIncomingEdges(cell);
  438. if (edges != null) {
  439. edges.forEach((edge) => {
  440. if (cache[edge.id]) {
  441. return;
  442. }
  443. result.push(edge);
  444. cache[edge.id] = true;
  445. if (indirect) {
  446. if (incoming) {
  447. collect(edge, false);
  448. }
  449. if (outgoing) {
  450. collect(edge, true);
  451. }
  452. }
  453. });
  454. }
  455. if (indirect && cell.isEdge()) {
  456. const terminal = isOutgoing
  457. ? cell.getTargetCell()
  458. : cell.getSourceCell();
  459. if (terminal && terminal.isEdge()) {
  460. if (!cache[terminal.id]) {
  461. result.push(terminal);
  462. collect(terminal, isOutgoing);
  463. }
  464. }
  465. }
  466. };
  467. if (outgoing) {
  468. collect(node, true);
  469. }
  470. if (incoming) {
  471. collect(node, false);
  472. }
  473. if (options.deep) {
  474. const descendants = node.getDescendants({ deep: true });
  475. const embedsCache = {};
  476. descendants.forEach((cell) => {
  477. if (cell.isNode()) {
  478. embedsCache[cell.id] = true;
  479. }
  480. });
  481. const collectSub = (cell, isOutgoing) => {
  482. const edges = isOutgoing
  483. ? this.getOutgoingEdges(cell.id)
  484. : this.getIncomingEdges(cell.id);
  485. if (edges != null) {
  486. edges.forEach((edge) => {
  487. if (!cache[edge.id]) {
  488. const sourceCell = edge.getSourceCell();
  489. const targetCell = edge.getTargetCell();
  490. if (!options.enclosed &&
  491. sourceCell &&
  492. embedsCache[sourceCell.id] &&
  493. targetCell &&
  494. embedsCache[targetCell.id]) {
  495. return;
  496. }
  497. result.push(edge);
  498. cache[edge.id] = true;
  499. }
  500. });
  501. }
  502. };
  503. descendants.forEach((cell) => {
  504. if (cell.isEdge()) {
  505. return;
  506. }
  507. if (outgoing) {
  508. collectSub(cell, true);
  509. }
  510. if (incoming) {
  511. collectSub(cell, false);
  512. }
  513. });
  514. }
  515. return result;
  516. }
  517. isBoundary(cell, isOrigin) {
  518. const node = typeof cell === 'string' ? this.getCell(cell) : cell;
  519. const arr = isOrigin
  520. ? this.getIncomingEdges(node)
  521. : this.getOutgoingEdges(node);
  522. return arr == null || arr.length === 0;
  523. }
  524. getBoundaryNodes(isOrigin) {
  525. const result = [];
  526. Object.keys(this.nodes).forEach((nodeId) => {
  527. if (this.isBoundary(nodeId, isOrigin)) {
  528. const node = this.getCell(nodeId);
  529. if (node) {
  530. result.push(node);
  531. }
  532. }
  533. });
  534. return result;
  535. }
  536. /**
  537. * Returns an array of all the roots of the graph.
  538. */
  539. getRoots() {
  540. return this.getBoundaryNodes(true);
  541. }
  542. /**
  543. * Returns an array of all the leafs of the graph.
  544. */
  545. getLeafs() {
  546. return this.getBoundaryNodes(false);
  547. }
  548. /**
  549. * Returns `true` if the node is a root node, i.e. there is no edges
  550. * coming to the node.
  551. */
  552. isRoot(cell) {
  553. return this.isBoundary(cell, true);
  554. }
  555. /**
  556. * Returns `true` if the node is a leaf node, i.e. there is no edges
  557. * going out from the node.
  558. */
  559. isLeaf(cell) {
  560. return this.isBoundary(cell, false);
  561. }
  562. /**
  563. * Returns all the neighbors of node in the graph. Neighbors are all
  564. * the nodes connected to node via either incoming or outgoing edge.
  565. */
  566. getNeighbors(cell, options = {}) {
  567. let incoming = options.incoming;
  568. let outgoing = options.outgoing;
  569. if (incoming == null && outgoing == null) {
  570. incoming = outgoing = true;
  571. }
  572. const edges = this.getConnectedEdges(cell, options);
  573. const map = edges.reduce((memo, edge) => {
  574. const hasLoop = edge.hasLoop(options);
  575. const sourceCell = edge.getSourceCell();
  576. const targetCell = edge.getTargetCell();
  577. if (incoming &&
  578. sourceCell &&
  579. sourceCell.isNode() &&
  580. !memo[sourceCell.id]) {
  581. if (hasLoop ||
  582. (sourceCell !== cell &&
  583. (!options.deep || !sourceCell.isDescendantOf(cell)))) {
  584. memo[sourceCell.id] = sourceCell;
  585. }
  586. }
  587. if (outgoing &&
  588. targetCell &&
  589. targetCell.isNode() &&
  590. !memo[targetCell.id]) {
  591. if (hasLoop ||
  592. (targetCell !== cell &&
  593. (!options.deep || !targetCell.isDescendantOf(cell)))) {
  594. memo[targetCell.id] = targetCell;
  595. }
  596. }
  597. return memo;
  598. }, {});
  599. if (cell.isEdge()) {
  600. if (incoming) {
  601. const sourceCell = cell.getSourceCell();
  602. if (sourceCell && sourceCell.isNode() && !map[sourceCell.id]) {
  603. map[sourceCell.id] = sourceCell;
  604. }
  605. }
  606. if (outgoing) {
  607. const targetCell = cell.getTargetCell();
  608. if (targetCell && targetCell.isNode() && !map[targetCell.id]) {
  609. map[targetCell.id] = targetCell;
  610. }
  611. }
  612. }
  613. return Object.keys(map).map((id) => map[id]);
  614. }
  615. /**
  616. * Returns `true` if `cell2` is a neighbor of `cell1`.
  617. */
  618. isNeighbor(cell1, cell2, options = {}) {
  619. let incoming = options.incoming;
  620. let outgoing = options.outgoing;
  621. if (incoming == null && outgoing == null) {
  622. incoming = outgoing = true;
  623. }
  624. return this.getConnectedEdges(cell1, options).some((edge) => {
  625. const sourceCell = edge.getSourceCell();
  626. const targetCell = edge.getTargetCell();
  627. if (incoming && sourceCell && sourceCell.id === cell2.id) {
  628. return true;
  629. }
  630. if (outgoing && targetCell && targetCell.id === cell2.id) {
  631. return true;
  632. }
  633. return false;
  634. });
  635. }
  636. getSuccessors(cell, options = {}) {
  637. const successors = [];
  638. this.search(cell, (curr, distance) => {
  639. if (curr !== cell && this.matchDistance(distance, options.distance)) {
  640. successors.push(curr);
  641. }
  642. }, Object.assign(Object.assign({}, options), { outgoing: true }));
  643. return successors;
  644. }
  645. /**
  646. * Returns `true` if `cell2` is a successor of `cell1`.
  647. */
  648. isSuccessor(cell1, cell2, options = {}) {
  649. let result = false;
  650. this.search(cell1, (curr, distance) => {
  651. if (curr === cell2 &&
  652. curr !== cell1 &&
  653. this.matchDistance(distance, options.distance)) {
  654. result = true;
  655. return false;
  656. }
  657. }, Object.assign(Object.assign({}, options), { outgoing: true }));
  658. return result;
  659. }
  660. getPredecessors(cell, options = {}) {
  661. const predecessors = [];
  662. this.search(cell, (curr, distance) => {
  663. if (curr !== cell && this.matchDistance(distance, options.distance)) {
  664. predecessors.push(curr);
  665. }
  666. }, Object.assign(Object.assign({}, options), { incoming: true }));
  667. return predecessors;
  668. }
  669. /**
  670. * Returns `true` if `cell2` is a predecessor of `cell1`.
  671. */
  672. isPredecessor(cell1, cell2, options = {}) {
  673. let result = false;
  674. this.search(cell1, (curr, distance) => {
  675. if (curr === cell2 &&
  676. curr !== cell1 &&
  677. this.matchDistance(distance, options.distance)) {
  678. result = true;
  679. return false;
  680. }
  681. }, Object.assign(Object.assign({}, options), { incoming: true }));
  682. return result;
  683. }
  684. matchDistance(distance, preset) {
  685. if (preset == null) {
  686. return true;
  687. }
  688. if (typeof preset === 'function') {
  689. return preset(distance);
  690. }
  691. if (Array.isArray(preset) && preset.includes(distance)) {
  692. return true;
  693. }
  694. return distance === preset;
  695. }
  696. /**
  697. * Returns the common ancestor of the passed cells.
  698. */
  699. getCommonAncestor(...cells) {
  700. const arr = [];
  701. cells.forEach((item) => {
  702. if (item) {
  703. if (Array.isArray(item)) {
  704. arr.push(...item);
  705. }
  706. else {
  707. arr.push(item);
  708. }
  709. }
  710. });
  711. return Cell.getCommonAncestor(...arr);
  712. }
  713. /**
  714. * Returns an array of cells that result from finding nodes/edges that
  715. * are connected to any of the cells in the cells array. This function
  716. * loops over cells and if the current cell is a edge, it collects its
  717. * source/target nodes; if it is an node, it collects its incoming and
  718. * outgoing edges if both the edge terminal (source/target) are in the
  719. * cells array.
  720. */
  721. getSubGraph(cells, options = {}) {
  722. const subgraph = [];
  723. const cache = {};
  724. const nodes = [];
  725. const edges = [];
  726. const collect = (cell) => {
  727. if (!cache[cell.id]) {
  728. subgraph.push(cell);
  729. cache[cell.id] = cell;
  730. if (cell.isEdge()) {
  731. edges.push(cell);
  732. }
  733. if (cell.isNode()) {
  734. nodes.push(cell);
  735. }
  736. }
  737. };
  738. cells.forEach((cell) => {
  739. collect(cell);
  740. if (options.deep) {
  741. const descendants = cell.getDescendants({ deep: true });
  742. descendants.forEach((descendant) => collect(descendant));
  743. }
  744. });
  745. edges.forEach((edge) => {
  746. // For edges, include their source & target
  747. const sourceCell = edge.getSourceCell();
  748. const targetCell = edge.getTargetCell();
  749. if (sourceCell && !cache[sourceCell.id]) {
  750. subgraph.push(sourceCell);
  751. cache[sourceCell.id] = sourceCell;
  752. if (sourceCell.isNode()) {
  753. nodes.push(sourceCell);
  754. }
  755. }
  756. if (targetCell && !cache[targetCell.id]) {
  757. subgraph.push(targetCell);
  758. cache[targetCell.id] = targetCell;
  759. if (targetCell.isNode()) {
  760. nodes.push(targetCell);
  761. }
  762. }
  763. });
  764. nodes.forEach((node) => {
  765. // For nodes, include their connected edges if their source/target
  766. // is in the subgraph.
  767. const edges = this.getConnectedEdges(node, options);
  768. edges.forEach((edge) => {
  769. const sourceCell = edge.getSourceCell();
  770. const targetCell = edge.getTargetCell();
  771. if (!cache[edge.id] &&
  772. sourceCell &&
  773. cache[sourceCell.id] &&
  774. targetCell &&
  775. cache[targetCell.id]) {
  776. subgraph.push(edge);
  777. cache[edge.id] = edge;
  778. }
  779. });
  780. });
  781. return subgraph;
  782. }
  783. /**
  784. * Clones the whole subgraph (including all the connected links whose
  785. * source/target is in the subgraph). If `options.deep` is `true`, also
  786. * take into account all the embedded cells of all the subgraph cells.
  787. *
  788. * Returns a map of the form: { [original cell ID]: [clone] }.
  789. */
  790. cloneSubGraph(cells, options = {}) {
  791. const subgraph = this.getSubGraph(cells, options);
  792. return this.cloneCells(subgraph);
  793. }
  794. cloneCells(cells) {
  795. return Cell.cloneCells(cells);
  796. }
  797. getNodesFromPoint(x, y) {
  798. const p = typeof x === 'number' ? { x, y: y || 0 } : x;
  799. return this.getNodes().filter((node) => {
  800. return node.getBBox().containsPoint(p);
  801. });
  802. }
  803. getNodesInArea(x, y, w, h, options) {
  804. const rect = typeof x === 'number'
  805. ? new Rectangle(x, y, w, h)
  806. : Rectangle.create(x);
  807. const opts = typeof x === 'number' ? options : y;
  808. const strict = opts && opts.strict;
  809. return this.getNodes().filter((node) => {
  810. const bbox = node.getBBox();
  811. return strict ? rect.containsRect(bbox) : rect.isIntersectWithRect(bbox);
  812. });
  813. }
  814. getEdgesInArea(x, y, w, h, options) {
  815. const rect = typeof x === 'number'
  816. ? new Rectangle(x, y, w, h)
  817. : Rectangle.create(x);
  818. const opts = typeof x === 'number' ? options : y;
  819. const strict = opts && opts.strict;
  820. return this.getEdges().filter((edge) => {
  821. const bbox = edge.getBBox();
  822. if (bbox.width === 0) {
  823. bbox.inflate(1, 0);
  824. }
  825. else if (bbox.height === 0) {
  826. bbox.inflate(0, 1);
  827. }
  828. return strict ? rect.containsRect(bbox) : rect.isIntersectWithRect(bbox);
  829. });
  830. }
  831. getNodesUnderNode(node, options = {}) {
  832. const bbox = node.getBBox();
  833. const nodes = options.by == null || options.by === 'bbox'
  834. ? this.getNodesInArea(bbox)
  835. : this.getNodesFromPoint(bbox[options.by]);
  836. return nodes.filter((curr) => node.id !== curr.id && !curr.isDescendantOf(node));
  837. }
  838. /**
  839. * Returns the bounding box that surrounds all cells in the graph.
  840. */
  841. getAllCellsBBox() {
  842. return this.getCellsBBox(this.getCells());
  843. }
  844. /**
  845. * Returns the bounding box that surrounds all the given cells.
  846. */
  847. getCellsBBox(cells, options = {}) {
  848. return Cell.getCellsBBox(cells, options);
  849. }
  850. // #region search
  851. search(cell, iterator, options = {}) {
  852. if (options.breadthFirst) {
  853. this.breadthFirstSearch(cell, iterator, options);
  854. }
  855. else {
  856. this.depthFirstSearch(cell, iterator, options);
  857. }
  858. }
  859. breadthFirstSearch(cell, iterator, options = {}) {
  860. const queue = [];
  861. const visited = {};
  862. const distance = {};
  863. queue.push(cell);
  864. distance[cell.id] = 0;
  865. while (queue.length > 0) {
  866. const next = queue.shift();
  867. if (next == null || visited[next.id]) {
  868. continue;
  869. }
  870. visited[next.id] = true;
  871. if (FunctionExt.call(iterator, this, next, distance[next.id]) === false) {
  872. continue;
  873. }
  874. const neighbors = this.getNeighbors(next, options);
  875. neighbors.forEach((neighbor) => {
  876. distance[neighbor.id] = distance[next.id] + 1;
  877. queue.push(neighbor);
  878. });
  879. }
  880. }
  881. depthFirstSearch(cell, iterator, options = {}) {
  882. const queue = [];
  883. const visited = {};
  884. const distance = {};
  885. queue.push(cell);
  886. distance[cell.id] = 0;
  887. while (queue.length > 0) {
  888. const next = queue.pop();
  889. if (next == null || visited[next.id]) {
  890. continue;
  891. }
  892. visited[next.id] = true;
  893. if (FunctionExt.call(iterator, this, next, distance[next.id]) === false) {
  894. continue;
  895. }
  896. const neighbors = this.getNeighbors(next, options);
  897. const lastIndex = queue.length;
  898. neighbors.forEach((neighbor) => {
  899. distance[neighbor.id] = distance[next.id] + 1;
  900. queue.splice(lastIndex, 0, neighbor);
  901. });
  902. }
  903. }
  904. // #endregion
  905. // #region shortest path
  906. /** *
  907. * Returns an array of IDs of nodes on the shortest
  908. * path between source and target.
  909. */
  910. getShortestPath(source, target, options = {}) {
  911. const adjacencyList = {};
  912. this.getEdges().forEach((edge) => {
  913. const sourceId = edge.getSourceCellId();
  914. const targetId = edge.getTargetCellId();
  915. if (sourceId && targetId) {
  916. if (!adjacencyList[sourceId]) {
  917. adjacencyList[sourceId] = [];
  918. }
  919. if (!adjacencyList[targetId]) {
  920. adjacencyList[targetId] = [];
  921. }
  922. adjacencyList[sourceId].push(targetId);
  923. if (!options.directed) {
  924. adjacencyList[targetId].push(sourceId);
  925. }
  926. }
  927. });
  928. const sourceId = typeof source === 'string' ? source : source.id;
  929. const previous = Dijkstra.run(adjacencyList, sourceId, options.weight);
  930. const path = [];
  931. let targetId = typeof target === 'string' ? target : target.id;
  932. if (previous[targetId]) {
  933. path.push(targetId);
  934. }
  935. while ((targetId = previous[targetId])) {
  936. path.unshift(targetId);
  937. }
  938. return path;
  939. }
  940. // #endregion
  941. // #region transform
  942. /**
  943. * Translate all cells in the graph by `tx` and `ty` pixels.
  944. */
  945. translate(tx, ty, options) {
  946. this.getCells()
  947. .filter((cell) => !cell.hasParent())
  948. .forEach((cell) => cell.translate(tx, ty, options));
  949. return this;
  950. }
  951. resize(width, height, options) {
  952. return this.resizeCells(width, height, this.getCells(), options);
  953. }
  954. resizeCells(width, height, cells, options = {}) {
  955. const bbox = this.getCellsBBox(cells);
  956. if (bbox) {
  957. const sx = Math.max(width / bbox.width, 0);
  958. const sy = Math.max(height / bbox.height, 0);
  959. const origin = bbox.getOrigin();
  960. cells.forEach((cell) => cell.scale(sx, sy, origin, options));
  961. }
  962. return this;
  963. }
  964. // #endregion
  965. // #region serialize/deserialize
  966. toJSON(options = {}) {
  967. return Model.toJSON(this.getCells(), options);
  968. }
  969. parseJSON(data) {
  970. return Model.fromJSON(data);
  971. }
  972. fromJSON(data, options = {}) {
  973. const cells = this.parseJSON(data);
  974. this.resetCells(cells, options);
  975. return this;
  976. }
  977. // #endregion
  978. // #region batch
  979. startBatch(name, data = {}) {
  980. this.batches[name] = (this.batches[name] || 0) + 1;
  981. this.notify('batch:start', { name, data });
  982. return this;
  983. }
  984. stopBatch(name, data = {}) {
  985. this.batches[name] = (this.batches[name] || 0) - 1;
  986. this.notify('batch:stop', { name, data });
  987. return this;
  988. }
  989. batchUpdate(name, execute, data = {}) {
  990. this.startBatch(name, data);
  991. const result = execute();
  992. this.stopBatch(name, data);
  993. return result;
  994. }
  995. hasActiveBatch(name = Object.keys(this.batches)) {
  996. const names = Array.isArray(name) ? name : [name];
  997. return names.some((batch) => this.batches[batch] > 0);
  998. }
  999. // #endregion
  1000. dispose() {
  1001. this.collection.dispose();
  1002. }
  1003. }
  1004. __decorate([
  1005. Model.dispose()
  1006. ], Model.prototype, "dispose", null);
  1007. (function (Model) {
  1008. Model.toStringTag = `X6.${Model.name}`;
  1009. function isModel(instance) {
  1010. if (instance == null) {
  1011. return false;
  1012. }
  1013. if (instance instanceof Model) {
  1014. return true;
  1015. }
  1016. const tag = instance[Symbol.toStringTag];
  1017. const model = instance;
  1018. if ((tag == null || tag === Model.toStringTag) &&
  1019. typeof model.addNode === 'function' &&
  1020. typeof model.addEdge === 'function' &&
  1021. model.collection != null) {
  1022. return true;
  1023. }
  1024. return false;
  1025. }
  1026. Model.isModel = isModel;
  1027. })(Model || (Model = {}));
  1028. (function (Model) {
  1029. function toJSON(cells, options = {}) {
  1030. return {
  1031. cells: cells.map((cell) => cell.toJSON(options)),
  1032. };
  1033. }
  1034. Model.toJSON = toJSON;
  1035. function fromJSON(data) {
  1036. const cells = [];
  1037. if (Array.isArray(data)) {
  1038. cells.push(...data);
  1039. }
  1040. else {
  1041. if (data.cells) {
  1042. cells.push(...data.cells);
  1043. }
  1044. if (data.nodes) {
  1045. data.nodes.forEach((node) => {
  1046. if (node.shape == null) {
  1047. node.shape = 'rect';
  1048. }
  1049. cells.push(node);
  1050. });
  1051. }
  1052. if (data.edges) {
  1053. data.edges.forEach((edge) => {
  1054. if (edge.shape == null) {
  1055. edge.shape = 'edge';
  1056. }
  1057. cells.push(edge);
  1058. });
  1059. }
  1060. }
  1061. return cells.map((cell) => {
  1062. const type = cell.shape;
  1063. if (type) {
  1064. if (Node.registry.exist(type)) {
  1065. return Node.create(cell);
  1066. }
  1067. if (Edge.registry.exist(type)) {
  1068. return Edge.create(cell);
  1069. }
  1070. }
  1071. throw new Error('The `shape` should be specified when creating a node/edge instance');
  1072. });
  1073. }
  1074. Model.fromJSON = fromJSON;
  1075. })(Model || (Model = {}));
  1076. //# sourceMappingURL=model.js.map