dagre.js 100 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271
  1. var dagre = dagre || {};
  2. // Dagre graph layout
  3. // https://github.com/dagrejs/dagre
  4. // https://github.com/dagrejs/graphlib
  5. dagre.layout = (graph, options) => {
  6. options = options || {};
  7. // options.time = true;
  8. const time = (name, callback) => {
  9. const start = Date.now();
  10. const result = callback();
  11. const duration = Date.now() - start;
  12. if (options.time) {
  13. /* eslint-disable */
  14. console.log(name + ': ' + duration + 'ms');
  15. /* eslint-enable */
  16. }
  17. return result;
  18. };
  19. // Constructs a new graph from the input graph, which can be used for layout.
  20. // This process copies only whitelisted attributes from the input graph to the
  21. // layout graph. Thus this function serves as a good place to determine what
  22. // attributes can influence layout.
  23. const buildLayoutGraph = (graph) => {
  24. const g = new dagre.Graph({ compound: true });
  25. g.options = Object.assign({}, { ranksep: 50, edgesep: 20, nodesep: 50, rankdir: 'tb' }, graph.options);
  26. for (const node of graph.nodes.values()) {
  27. const v = node.v;
  28. const label = node.label;
  29. g.setNode(v, {
  30. width: label.width || 0,
  31. height: label.height || 0
  32. });
  33. g.setParent(v, graph.parent(v));
  34. }
  35. for (const e of graph.edges.values()) {
  36. const edge = e.label;
  37. g.setEdge(e.v, e.w, {
  38. minlen: edge.minlen || 1,
  39. weight: edge.weight || 1,
  40. width: edge.width || 0,
  41. height: edge.height || 0,
  42. labeloffset: edge.labeloffset || 10,
  43. labelpos: edge.labelpos || 'r'
  44. });
  45. }
  46. return g;
  47. };
  48. const runLayout = (g, time) => {
  49. let uniqueIdCounter = 0;
  50. const uniqueId = (prefix) => {
  51. const id = ++uniqueIdCounter;
  52. return prefix + id;
  53. };
  54. const flat = (list) => {
  55. if (Array.isArray(list) && list.every((item) => !Array.isArray(item))) {
  56. return list;
  57. }
  58. const target = [];
  59. for (const item of list) {
  60. if (!Array.isArray(item)) {
  61. target.push(item);
  62. continue;
  63. }
  64. for (const entry of item) {
  65. target.push(entry);
  66. }
  67. }
  68. return target;
  69. };
  70. // Adds a dummy node to the graph and return v.
  71. const addDummyNode = (g, type, label, name) => {
  72. let v;
  73. do {
  74. v = uniqueId(name);
  75. } while (g.hasNode(v));
  76. label.dummy = type;
  77. g.setNode(v, label);
  78. return v;
  79. };
  80. const asNonCompoundGraph = (g) => {
  81. const graph = new dagre.Graph({});
  82. graph.options = g.options;
  83. for (const node of g.nodes.values()) {
  84. const v = node.v;
  85. if (g.children(v).length === 0) {
  86. graph.setNode(v, node.label);
  87. }
  88. }
  89. for (const e of g.edges.values()) {
  90. graph.setEdge(e.v, e.w, e.label);
  91. }
  92. return graph;
  93. };
  94. const maxRank = (g) => {
  95. let rank = Number.NEGATIVE_INFINITY;
  96. for (const node of g.nodes.values()) {
  97. const x = node.label.rank;
  98. if (x !== undefined && x > rank) {
  99. rank = x;
  100. }
  101. }
  102. return rank === Number.NEGATIVE_INFINITY ? undefined : rank;
  103. };
  104. // Given a DAG with each node assigned 'rank' and 'order' properties, this function will produce a matrix with the ids of each node.
  105. const buildLayerMatrix = (g) => {
  106. const rank = maxRank(g);
  107. const length = rank === undefined ? 0 : rank + 1;
  108. const layering = Array.from(new Array(length), () => []);
  109. for (const node of g.nodes.values()) {
  110. const label = node.label;
  111. const rank = label.rank;
  112. if (rank !== undefined) {
  113. layering[rank][label.order] = node.v;
  114. }
  115. }
  116. return layering;
  117. };
  118. // This idea comes from the Gansner paper: to account for edge labels in our layout we split each rank in half by doubling minlen and halving ranksep.
  119. // Then we can place labels at these mid-points between nodes.
  120. // We also add some minimal padding to the width to push the label for the edge away from the edge itself a bit.
  121. const makeSpaceForEdgeLabels = (g) => {
  122. const graph = g.options;
  123. graph.ranksep /= 2;
  124. for (const e of g.edges.values()) {
  125. const edge = e.label;
  126. edge.minlen *= 2;
  127. if (edge.labelpos.toLowerCase() !== 'c') {
  128. if (graph.rankdir === 'TB' || graph.rankdir === 'BT') {
  129. edge.width += edge.labeloffset;
  130. }
  131. else {
  132. edge.height += edge.labeloffset;
  133. }
  134. }
  135. }
  136. };
  137. // A helper that preforms a pre- or post-order traversal on the input graph and returns the nodes in the order they were visited.
  138. // If the graph is undirected then this algorithm will navigate using neighbors.
  139. // If the graph is directed then this algorithm will navigate using successors.
  140. // Order must be one of 'pre' or 'post'.
  141. const dfs = (g, vs, postorder) => {
  142. const doDfs = (g, v, postorder, visited, navigation, acc) => {
  143. if (!visited.has(v)) {
  144. visited.add(v);
  145. if (!postorder) {
  146. acc.push(v);
  147. }
  148. for (const w of navigation(v)) {
  149. doDfs(g, w, postorder, visited, navigation, acc);
  150. }
  151. if (postorder) {
  152. acc.push(v);
  153. }
  154. }
  155. };
  156. if (!Array.isArray(vs)) {
  157. vs = [ vs ];
  158. }
  159. const navigation = (g.isDirected() ? g.successors : g.neighbors).bind(g);
  160. const acc = [];
  161. const visited = new Set();
  162. for (const v of vs) {
  163. if (!g.hasNode(v)) {
  164. throw new Error('Graph does not have node: ' + v);
  165. }
  166. doDfs(g, v, postorder, visited, navigation, acc);
  167. }
  168. return acc;
  169. };
  170. const postorder = (g, vs) => {
  171. return dfs(g, vs, true);
  172. };
  173. const preorder = (g, vs) => {
  174. return dfs(g, vs, false);
  175. };
  176. const removeSelfEdges = (g) => {
  177. for (const e of g.edges.values()) {
  178. if (e.v === e.w) {
  179. const label = g.node(e.v).label;
  180. if (!label.selfEdges) {
  181. label.selfEdges = [];
  182. }
  183. label.selfEdges.push({ e: e, label: e.label });
  184. g.removeEdge(e);
  185. }
  186. }
  187. };
  188. const acyclic_run = (g) => {
  189. const dfsFAS = (g) => {
  190. const fas = [];
  191. const stack = new Set();
  192. const visited = new Set();
  193. const dfs = (v) => {
  194. if (!visited.has(v)) {
  195. visited.add(v);
  196. stack.add(v);
  197. for (const e of g.node(v).out) {
  198. if (stack.has(e.w)) {
  199. fas.push(e);
  200. }
  201. else {
  202. dfs(e.w);
  203. }
  204. }
  205. stack.delete(v);
  206. }
  207. };
  208. for (const v of g.nodes.keys()) {
  209. dfs(v);
  210. }
  211. return fas;
  212. };
  213. for (const e of dfsFAS(g)) {
  214. const label = e.label;
  215. g.removeEdge(e);
  216. label.forwardName = e.name;
  217. label.reversed = true;
  218. g.setEdge(e.w, e.v, label, uniqueId('rev'));
  219. }
  220. };
  221. const acyclic_undo = (g) => {
  222. for (const e of g.edges.values()) {
  223. const edge = e.label;
  224. if (edge.reversed) {
  225. edge.points.reverse();
  226. g.removeEdge(e);
  227. const forwardName = edge.forwardName;
  228. delete edge.reversed;
  229. delete edge.forwardName;
  230. g.setEdge(e.w, e.v, edge, forwardName);
  231. }
  232. }
  233. };
  234. // Returns the amount of slack for the given edge.
  235. // The slack is defined as the difference between the length of the edge and its minimum length.
  236. const slack = (g, e) => {
  237. return g.node(e.w).label.rank - g.node(e.v).label.rank - e.label.minlen;
  238. };
  239. // Assigns a rank to each node in the input graph that respects the 'minlen' constraint specified on edges between nodes.
  240. // This basic structure is derived from Gansner, et al., 'A Technique for Drawing Directed Graphs.'
  241. //
  242. // Pre-conditions:
  243. // 1. Graph must be a connected DAG
  244. // 2. Graph nodes must be objects
  245. // 3. Graph edges must have 'weight' and 'minlen' attributes
  246. //
  247. // Post-conditions:
  248. // 1. Graph nodes will have a 'rank' attribute based on the results of the
  249. // algorithm. Ranks can start at any index (including negative), we'll
  250. // fix them up later.
  251. const rank = (g) => {
  252. // Constructs a spanning tree with tight edges and adjusted the input node's ranks to achieve this.
  253. // A tight edge is one that is has a length that matches its 'minlen' attribute.
  254. // The basic structure for this function is derived from Gansner, et al., 'A Technique for Drawing Directed Graphs.'
  255. //
  256. // Pre-conditions:
  257. // 1. Graph must be a DAG.
  258. // 2. Graph must be connected.
  259. // 3. Graph must have at least one node.
  260. // 5. Graph nodes must have been previously assigned a 'rank' property that respects the 'minlen' property of incident edges.
  261. // 6. Graph edges must have a 'minlen' property.
  262. //
  263. // Post-conditions:
  264. // - Graph nodes will have their rank adjusted to ensure that all edges are tight.
  265. //
  266. // Returns a tree (undirected graph) that is constructed using only 'tight' edges.
  267. const feasibleTree = (g) => {
  268. const t = new dagre.Graph({ directed: false });
  269. // Choose arbitrary node from which to start our tree
  270. const start = g.nodes.keys().next().value;
  271. const size = g.nodes.size;
  272. t.setNode(start, {});
  273. // Finds the edge with the smallest slack that is incident on tree and returns it.
  274. const findMinSlackEdge = (t, g) => {
  275. let minKey = Number.MAX_SAFE_INTEGER;
  276. let minValue = undefined;
  277. for (const e of g.edges.values()) {
  278. if (t.hasNode(e.v) !== t.hasNode(e.w)) {
  279. const key = slack(g, e);
  280. if (key < minKey) {
  281. minKey = key;
  282. minValue = e;
  283. }
  284. }
  285. }
  286. return minValue;
  287. };
  288. // Finds a maximal tree of tight edges and returns the number of nodes in the tree.
  289. const tightTree = (t, g) => {
  290. const stack = Array.from(t.nodes.keys()).reverse();
  291. while (stack.length > 0) {
  292. const v = stack.pop();
  293. const node = g.node(v);
  294. for (const e of node.in.concat(node.out)) {
  295. const edgeV = e.v;
  296. const w = (v === edgeV) ? e.w : edgeV;
  297. if (!t.hasNode(w) && !slack(g, e)) {
  298. t.setNode(w, {});
  299. t.setEdge(v, w, {});
  300. stack.push(w);
  301. }
  302. }
  303. }
  304. return t.nodes.size;
  305. };
  306. while (tightTree(t, g) < size) {
  307. const edge = findMinSlackEdge(t, g);
  308. const delta = t.hasNode(edge.v) ? slack(g, edge) : -slack(g, edge);
  309. for (const v of t.nodes.keys()) {
  310. g.node(v).label.rank += delta;
  311. }
  312. }
  313. return t;
  314. };
  315. // Initializes ranks for the input graph using the longest path algorithm. This
  316. // algorithm scales well and is fast in practice, it yields rather poor
  317. // solutions. Nodes are pushed to the lowest layer possible, leaving the bottom
  318. // ranks wide and leaving edges longer than necessary. However, due to its
  319. // speed, this algorithm is good for getting an initial ranking that can be fed
  320. // into other algorithms.
  321. //
  322. // This algorithm does not normalize layers because it will be used by other
  323. // algorithms in most cases. If using this algorithm directly, be sure to
  324. // run normalize at the end.
  325. //
  326. // Pre-conditions:
  327. // 1. Input graph is a DAG.
  328. // 2. Input graph node labels can be assigned properties.
  329. //
  330. // Post-conditions:
  331. // 1. Each node will be assign an (unnormalized) 'rank' property.
  332. const longestPath = (g) => {
  333. const visited = new Set();
  334. const dfs = (v) => {
  335. const node = g.node(v);
  336. if (visited.has(v)) {
  337. return node.label.rank;
  338. }
  339. visited.add(v);
  340. let rank = Number.MAX_SAFE_INTEGER;
  341. for (const e of node.out) {
  342. rank = Math.min(rank, dfs(e.w) - e.label.minlen);
  343. }
  344. if (rank === Number.MAX_SAFE_INTEGER) {
  345. rank = 0;
  346. }
  347. node.label.rank = rank;
  348. return rank;
  349. };
  350. for (const node of g.nodes.values()) {
  351. if (node.in.length === 0) {
  352. dfs(node.v);
  353. }
  354. }
  355. };
  356. // The network simplex algorithm assigns ranks to each node in the input graph
  357. // and iteratively improves the ranking to reduce the length of edges.
  358. //
  359. // Preconditions:
  360. // 1. The input graph must be a DAG.
  361. // 2. All nodes in the graph must have an object value.
  362. // 3. All edges in the graph must have 'minlen' and 'weight' attributes.
  363. //
  364. // Postconditions:
  365. // 1. All nodes in the graph will have an assigned 'rank' attribute that has
  366. // been optimized by the network simplex algorithm. Ranks start at 0.
  367. //
  368. // A rough sketch of the algorithm is as follows:
  369. // 1. Assign initial ranks to each node. We use the longest path algorithm,
  370. // which assigns ranks to the lowest position possible. In general this
  371. // leads to very wide bottom ranks and unnecessarily long edges.
  372. // 2. Construct a feasible tight tree. A tight tree is one such that all
  373. // edges in the tree have no slack (difference between length of edge
  374. // and minlen for the edge). This by itself greatly improves the assigned
  375. // rankings by shorting edges.
  376. // 3. Iteratively find edges that have negative cut values. Generally a
  377. // negative cut value indicates that the edge could be removed and a new
  378. // tree edge could be added to produce a more compact graph.
  379. //
  380. // Much of the algorithms here are derived from Gansner, et al., 'A Technique
  381. // for Drawing Directed Graphs.' The structure of the file roughly follows the
  382. // structure of the overall algorithm.
  383. const networkSimplex = (g) => {
  384. // Returns a new graph with only simple edges. Handles aggregation of data associated with multi-edges.
  385. const simplify = (g) => {
  386. const graph = new dagre.Graph();
  387. graph.options = g.options;
  388. for (const node of g.nodes.values()) {
  389. graph.setNode(node.v, node.label);
  390. }
  391. for (const e of g.edges.values()) {
  392. const simpleEdge = graph.edge(e.v, e.w);
  393. const simpleLabel = simpleEdge ? simpleEdge.label : { weight: 0, minlen: 1 };
  394. const label = e.label;
  395. graph.setEdge(e.v, e.w, {
  396. weight: simpleLabel.weight + label.weight,
  397. minlen: Math.max(simpleLabel.minlen, label.minlen)
  398. });
  399. }
  400. return graph;
  401. };
  402. const initLowLimValues = (tree, root) => {
  403. const dfs = (tree, visited, nextLim, v, parent) => {
  404. const low = nextLim;
  405. const label = tree.node(v).label;
  406. visited.add(v);
  407. for (const w of tree.neighbors(v)) {
  408. if (!visited.has(w)) {
  409. nextLim = dfs(tree, visited, nextLim, w, v);
  410. }
  411. }
  412. label.low = low;
  413. label.lim = nextLim++;
  414. if (parent) {
  415. label.parent = parent;
  416. }
  417. else {
  418. // TODO should be able to remove this when we incrementally update low lim
  419. delete label.parent;
  420. }
  421. return nextLim;
  422. };
  423. root = tree.nodes.keys().next().value;
  424. const visited = new Set();
  425. dfs(tree, visited, 1, root);
  426. };
  427. // Initializes cut values for all edges in the tree.
  428. const initCutValues = (t, g) => {
  429. // Given the tight tree, its graph, and a child in the graph calculate and
  430. // return the cut value for the edge between the child and its parent.
  431. const calcCutValue = (t, g, child) => {
  432. const childLabel = t.node(child).label;
  433. const parent = childLabel.parent;
  434. // The graph's view of the tree edge we're inspecting
  435. const edge = g.edge(child, parent);
  436. // True if the child is on the tail end of the edge in the directed graph
  437. const childIsTail = edge ? true : false;
  438. // The accumulated cut value for the edge between this node and its parent
  439. const graphEdge = edge ? edge.label : g.edge(parent, child).label;
  440. let cutValue = graphEdge.weight;
  441. const node = g.node(child);
  442. for (const e of node.in.concat(node.out)) {
  443. const isOutEdge = e.v === child;
  444. const other = isOutEdge ? e.w : e.v;
  445. if (other !== parent) {
  446. const pointsToHead = isOutEdge === childIsTail;
  447. const otherWeight = e.label.weight;
  448. cutValue += pointsToHead ? otherWeight : -otherWeight;
  449. const edge = t.edge(child, other);
  450. if (edge) {
  451. const otherCutValue = edge.label.cutvalue;
  452. cutValue += pointsToHead ? -otherCutValue : otherCutValue;
  453. }
  454. }
  455. }
  456. return cutValue;
  457. };
  458. const assignCutValue = (t, g, child) => {
  459. const childLabel = t.node(child).label;
  460. const parent = childLabel.parent;
  461. t.edge(child, parent).label.cutvalue = calcCutValue(t, g, child);
  462. };
  463. let vs = postorder(t, Array.from(t.nodes.keys()));
  464. vs = vs.slice(0, vs.length - 1);
  465. for (const v of vs) {
  466. assignCutValue(t, g, v);
  467. }
  468. };
  469. const leaveEdge = (tree) => {
  470. return Array.from(tree.edges.values()).find((e) => e.label.cutvalue < 0);
  471. };
  472. const enterEdge = (t, g, edge) => {
  473. let v = edge.v;
  474. let w = edge.w;
  475. // For the rest of this function we assume that v is the tail and w is the
  476. // head, so if we don't have this edge in the graph we should flip it to
  477. // match the correct orientation.
  478. if (!g.edge(v, w)) {
  479. v = edge.w;
  480. w = edge.v;
  481. }
  482. const vLabel = t.node(v).label;
  483. const wLabel = t.node(w).label;
  484. let tailLabel = vLabel;
  485. let flip = false;
  486. // If the root is in the tail of the edge then we need to flip the logic that
  487. // checks for the head and tail nodes in the candidates function below.
  488. if (vLabel.lim > wLabel.lim) {
  489. tailLabel = wLabel;
  490. flip = true;
  491. }
  492. // Returns true if the specified node is descendant of the root node per the assigned low and lim attributes in the tree.
  493. const isDescendant = (vLabel, rootLabel) => {
  494. return rootLabel.low <= vLabel.lim && vLabel.lim <= rootLabel.lim;
  495. };
  496. let minKey = Number.POSITIVE_INFINITY;
  497. let minValue = undefined;
  498. for (const edge of g.edges.values()) {
  499. if (flip === isDescendant(t.node(edge.v).label, tailLabel) &&
  500. flip !== isDescendant(t.node(edge.w).label, tailLabel)) {
  501. const key = slack(g, edge);
  502. if (key < minKey) {
  503. minKey = key;
  504. minValue = edge;
  505. }
  506. }
  507. }
  508. return minValue;
  509. };
  510. const exchangeEdges = (t, g, e, f) => {
  511. t.removeEdge(e);
  512. t.setEdge(f.v, f.w, {});
  513. initLowLimValues(t);
  514. initCutValues(t, g);
  515. // update ranks
  516. const root = Array.from(t.nodes.keys()).find((v) => !g.node(v).label.parent);
  517. let vs = preorder(t, root);
  518. vs = vs.slice(1);
  519. for (const v of vs) {
  520. const parent = t.node(v).label.parent;
  521. let edge = g.edge(v, parent);
  522. let flipped = false;
  523. if (!edge) {
  524. edge = g.edge(parent, v);
  525. flipped = true;
  526. }
  527. g.node(v).label.rank = g.node(parent).label.rank + (flipped ? edge.label.minlen : -edge.label.minlen);
  528. }
  529. };
  530. g = simplify(g);
  531. longestPath(g);
  532. const t = feasibleTree(g);
  533. initLowLimValues(t);
  534. initCutValues(t, g);
  535. let e;
  536. let f;
  537. while ((e = leaveEdge(t))) {
  538. f = enterEdge(t, g, e);
  539. exchangeEdges(t, g, e, f);
  540. }
  541. };
  542. switch(g.options.ranker) {
  543. case 'tight-tree': {
  544. longestPath(g);
  545. feasibleTree(g);
  546. break;
  547. }
  548. case 'longest-path': {
  549. longestPath(g);
  550. break;
  551. }
  552. default: {
  553. networkSimplex(g);
  554. break;
  555. }
  556. }
  557. };
  558. // Creates temporary dummy nodes that capture the rank in which each edge's label is going to, if it has one of non-zero width and height.
  559. // We do this so that we can safely remove empty ranks while preserving balance for the label's position.
  560. const injectEdgeLabelProxies = (g) => {
  561. for (const e of g.edges.values()) {
  562. const edge = e.label;
  563. if (edge.width && edge.height) {
  564. const v = g.node(e.v).label;
  565. const w = g.node(e.w).label;
  566. addDummyNode(g, 'edge-proxy', { rank: (w.rank - v.rank) / 2 + v.rank, e: e }, '_ep');
  567. }
  568. }
  569. };
  570. const removeEmptyRanks = (g) => {
  571. // Ranks may not start at 0, so we need to offset them
  572. if (g.nodes.size > 0) {
  573. let minRank = Number.MAX_SAFE_INTEGER;
  574. let maxRank = Number.MIN_SAFE_INTEGER;
  575. const nodes = Array.from(g.nodes.values());
  576. for (const node of nodes) {
  577. const label = node.label;
  578. if (label.rank !== undefined) {
  579. minRank = Math.min(minRank, label.rank);
  580. maxRank = Math.max(maxRank, label.rank);
  581. }
  582. }
  583. const size = maxRank - minRank;
  584. if (size > 0) {
  585. const layers = new Array(size);
  586. for (const node of nodes) {
  587. const label = node.label;
  588. if (label.rank !== undefined) {
  589. const rank = label.rank - minRank;
  590. if (!layers[rank]) {
  591. layers[rank] = [];
  592. }
  593. layers[rank].push(node.v);
  594. }
  595. }
  596. let delta = 0;
  597. const nodeRankFactor = g.options.nodeRankFactor;
  598. for (let i = 0; i < layers.length; i++) {
  599. const vs = layers[i];
  600. if (vs === undefined && i % nodeRankFactor !== 0) {
  601. delta--;
  602. }
  603. else if (delta && vs) {
  604. for (const v of vs) {
  605. g.node(v).label.rank += delta;
  606. }
  607. }
  608. }
  609. }
  610. }
  611. };
  612. // A nesting graph creates dummy nodes for the tops and bottoms of subgraphs,
  613. // adds appropriate edges to ensure that all cluster nodes are placed between
  614. // these boundries, and ensures that the graph is connected.
  615. // In addition we ensure, through the use of the minlen property, that nodes
  616. // and subgraph border nodes do not end up on the same rank.
  617. //
  618. // Preconditions:
  619. // 1. Input graph is a DAG
  620. // 2. Nodes in the input graph has a minlen attribute
  621. //
  622. // Postconditions:
  623. // 1. Input graph is connected.
  624. // 2. Dummy nodes are added for the tops and bottoms of subgraphs.
  625. // 3. The minlen attribute for nodes is adjusted to ensure nodes do not
  626. // get placed on the same rank as subgraph border nodes.
  627. //
  628. // The nesting graph idea comes from Sander, 'Layout of Compound Directed Graphs.'
  629. const nestingGraph_run = (g) => {
  630. const root = addDummyNode(g, 'root', {}, '_root');
  631. const treeDepths = (g) => {
  632. const depths = {};
  633. const dfs = (v, depth) => {
  634. const children = g.children(v);
  635. if (children && children.length > 0) {
  636. for (const child of children) {
  637. dfs(child, depth + 1);
  638. }
  639. }
  640. depths[v] = depth;
  641. };
  642. for (const v of g.children()) {
  643. dfs(v, 1);
  644. }
  645. return depths;
  646. };
  647. const dfs = (g, root, nodeSep, weight, height, depths, v) => {
  648. const children = g.children(v);
  649. if (!children.length) {
  650. if (v !== root) {
  651. g.setEdge(root, v, { weight: 0, minlen: nodeSep });
  652. }
  653. return;
  654. }
  655. const top = addDummyNode(g, 'border', { width: 0, height: 0 }, '_bt');
  656. const bottom = addDummyNode(g, 'border', { width: 0, height: 0 }, '_bb');
  657. const label = g.node(v).label;
  658. g.setParent(top, v);
  659. label.borderTop = top;
  660. g.setParent(bottom, v);
  661. label.borderBottom = bottom;
  662. for (const child of children) {
  663. dfs(g, root, nodeSep, weight, height, depths, child);
  664. const childNode = g.node(child).label;
  665. const childTop = childNode.borderTop ? childNode.borderTop : child;
  666. const childBottom = childNode.borderBottom ? childNode.borderBottom : child;
  667. const thisWeight = childNode.borderTop ? weight : 2 * weight;
  668. const minlen = childTop !== childBottom ? 1 : height - depths[v] + 1;
  669. g.setEdge(top, childTop, { weight: thisWeight, minlen: minlen, nestingEdge: true });
  670. g.setEdge(childBottom, bottom, { weight: thisWeight, minlen: minlen, nestingEdge: true });
  671. }
  672. if (!g.parent(v)) {
  673. g.setEdge(root, top, { weight: 0, minlen: height + depths[v] });
  674. }
  675. };
  676. const depths = treeDepths(g);
  677. const height = Math.max(...Object.values(depths)) - 1; // Note: depths is an Object not an array
  678. const nodeSep = 2 * height + 1;
  679. g.options.nestingRoot = root;
  680. // Multiply minlen by nodeSep to align nodes on non-border ranks.
  681. for (const e of g.edges.values()) {
  682. e.label.minlen *= nodeSep;
  683. }
  684. // Calculate a weight that is sufficient to keep subgraphs vertically compact
  685. const weight = Array.from(g.edges.values()).reduce((acc, e) => acc + e.label.weight, 0) + 1;
  686. // Create border nodes and link them up
  687. for (const child of g.children()) {
  688. dfs(g, root, nodeSep, weight, height, depths, child);
  689. }
  690. // Save the multiplier for node layers for later removal of empty border layers.
  691. g.options.nodeRankFactor = nodeSep;
  692. };
  693. const nestingGraph_cleanup = (g) => {
  694. const graphLabel = g.options;
  695. g.removeNode(graphLabel.nestingRoot);
  696. delete graphLabel.nestingRoot;
  697. for (const e of g.edges.values()) {
  698. if (e.label.nestingEdge) {
  699. g.removeEdge(e);
  700. }
  701. }
  702. };
  703. const assignRankMinMax = (g) => {
  704. // Adjusts the ranks for all nodes in the graph such that all nodes v have rank(v) >= 0 and at least one node w has rank(w) = 0.
  705. let min = Number.POSITIVE_INFINITY;
  706. for (const node of g.nodes.values()) {
  707. const rank = node.label.rank;
  708. if (rank !== undefined && rank < min) {
  709. min = rank;
  710. }
  711. }
  712. for (const node of g.nodes.values()) {
  713. const label = node.label;
  714. if (label.rank !== undefined) {
  715. label.rank -= min;
  716. }
  717. }
  718. let maxRank = 0;
  719. for (const node of g.nodes.values()) {
  720. const label = node.label;
  721. if (label.borderTop) {
  722. label.minRank = g.node(label.borderTop).label.rank;
  723. label.maxRank = g.node(label.borderBottom).label.rank;
  724. maxRank = Math.max(maxRank, label.maxRank);
  725. }
  726. }
  727. g.options.maxRank = maxRank;
  728. };
  729. // Breaks any long edges in the graph into short segments that span 1 layer each.
  730. // This operation is undoable with the denormalize function.
  731. //
  732. // Pre-conditions:
  733. // 1. The input graph is a DAG.
  734. // 2. Each node in the graph has a 'rank' property.
  735. //
  736. // Post-condition:
  737. // 1. All edges in the graph have a length of 1.
  738. // 2. Dummy nodes are added where edges have been split into segments.
  739. // 3. The graph is augmented with a 'dummyChains' attribute which contains
  740. // the first dummy in each chain of dummy nodes produced.
  741. const normalize = (g) => {
  742. g.options.dummyChains = [];
  743. for (const e of g.edges.values()) {
  744. let v = e.v;
  745. const w = e.w;
  746. const name = e.name;
  747. const edgeLabel = e.label;
  748. const labelRank = edgeLabel.labelRank;
  749. let vRank = g.node(v).label.rank;
  750. const wRank = g.node(w).label.rank;
  751. if (wRank !== vRank + 1) {
  752. g.removeEdge(e);
  753. let first = true;
  754. vRank++;
  755. while (vRank < wRank) {
  756. edgeLabel.points = [];
  757. delete e.key;
  758. const attrs = {
  759. width: 0, height: 0,
  760. edgeLabel: edgeLabel,
  761. edgeObj: e,
  762. rank: vRank
  763. };
  764. const dummy = addDummyNode(g, 'edge', attrs, '_d');
  765. if (vRank === labelRank) {
  766. attrs.width = edgeLabel.width;
  767. attrs.height = edgeLabel.height;
  768. attrs.dummy = 'edge-label';
  769. attrs.labelpos = edgeLabel.labelpos;
  770. }
  771. g.setEdge(v, dummy, { weight: edgeLabel.weight }, name);
  772. if (first) {
  773. g.options.dummyChains.push(dummy);
  774. first = false;
  775. }
  776. v = dummy;
  777. vRank++;
  778. }
  779. g.setEdge(v, w, { weight: edgeLabel.weight }, name);
  780. }
  781. }
  782. };
  783. const denormalize = (g) => {
  784. for (let v of g.options.dummyChains) {
  785. let label = g.node(v).label;
  786. const edgeLabel = label.edgeLabel;
  787. const e = label.edgeObj;
  788. g.setEdge(e.v, e.w, edgeLabel, e.name);
  789. while (label.dummy) {
  790. const w = g.successors(v)[0];
  791. g.removeNode(v);
  792. edgeLabel.points.push({ x: label.x, y: label.y });
  793. if (label.dummy === 'edge-label') {
  794. edgeLabel.x = label.x;
  795. edgeLabel.y = label.y;
  796. edgeLabel.width = label.width;
  797. edgeLabel.height = label.height;
  798. }
  799. v = w;
  800. label = g.node(v).label;
  801. }
  802. }
  803. };
  804. const removeEdgeLabelProxies = (g) => {
  805. for (const node of g.nodes.values()) {
  806. const label = node.label;
  807. if (label.dummy === 'edge-proxy') {
  808. label.e.label.labelRank = label.rank;
  809. g.removeNode(node.v);
  810. }
  811. }
  812. };
  813. const parentDummyChains = (g) => {
  814. // Find a path from v to w through the lowest common ancestor (LCA). Return the full path and the LCA.
  815. const findPath = (g, postorderNums, v, w) => {
  816. const vPath = [];
  817. const wPath = [];
  818. const low = Math.min(postorderNums[v].low, postorderNums[w].low);
  819. const lim = Math.max(postorderNums[v].lim, postorderNums[w].lim);
  820. // Traverse up from v to find the LCA
  821. let parent = v;
  822. do {
  823. parent = g.parent(parent);
  824. vPath.push(parent);
  825. }
  826. while (parent && (postorderNums[parent].low > low || lim > postorderNums[parent].lim));
  827. const lca = parent;
  828. // Traverse from w to LCA
  829. parent = w;
  830. while ((parent = g.parent(parent)) !== lca) {
  831. wPath.push(parent);
  832. }
  833. return { path: vPath.concat(wPath.reverse()), lca: lca };
  834. };
  835. const postorder = (g) => {
  836. const result = {};
  837. let lim = 0;
  838. const dfs = (v) => {
  839. const low = lim;
  840. for (const u of g.children(v)) {
  841. dfs(u);
  842. }
  843. result[v] = { low: low, lim: lim++ };
  844. };
  845. for (const v of g.children()) {
  846. dfs(v);
  847. }
  848. return result;
  849. };
  850. const postorderNums = postorder(g);
  851. for (let v of g.options.dummyChains || []) {
  852. const node = g.node(v).label;
  853. const edgeObj = node.edgeObj;
  854. const pathData = findPath(g, postorderNums, edgeObj.v, edgeObj.w);
  855. const path = pathData.path;
  856. const lca = pathData.lca;
  857. let pathIdx = 0;
  858. let pathV = path[pathIdx];
  859. let ascending = true;
  860. while (v !== edgeObj.w) {
  861. const node = g.node(v).label;
  862. if (ascending) {
  863. while ((pathV = path[pathIdx]) !== lca && g.node(pathV).label.maxRank < node.rank) {
  864. pathIdx++;
  865. }
  866. if (pathV === lca) {
  867. ascending = false;
  868. }
  869. }
  870. if (!ascending) {
  871. while (pathIdx < path.length - 1 && g.node(pathV = path[pathIdx + 1]).label.minRank <= node.rank) {
  872. pathIdx++;
  873. }
  874. pathV = path[pathIdx];
  875. }
  876. g.setParent(v, pathV);
  877. v = g.successors(v)[0];
  878. }
  879. }
  880. };
  881. const addBorderSegments = (g) => {
  882. const addBorderNode = (g, prop, prefix, sg, sgNode, rank) => {
  883. const label = { width: 0, height: 0, rank: rank, borderType: prop };
  884. const prev = sgNode[prop][rank - 1];
  885. const curr = addDummyNode(g, 'border', label, prefix);
  886. sgNode[prop][rank] = curr;
  887. g.setParent(curr, sg);
  888. if (prev) {
  889. g.setEdge(prev, curr, { weight: 1 });
  890. }
  891. };
  892. const dfs = (v) => {
  893. const children = g.children(v);
  894. const node = g.node(v).label;
  895. if (children.length) {
  896. for (const v of children) {
  897. dfs(v);
  898. }
  899. }
  900. if ('minRank' in node) {
  901. node.borderLeft = [];
  902. node.borderRight = [];
  903. const maxRank = node.maxRank + 1;
  904. for (let rank = node.minRank; rank < maxRank; rank++) {
  905. addBorderNode(g, 'borderLeft', '_bl', v, node, rank);
  906. addBorderNode(g, 'borderRight', '_br', v, node, rank);
  907. }
  908. }
  909. };
  910. for (const v of g.children()) {
  911. dfs(v);
  912. }
  913. };
  914. // Applies heuristics to minimize edge crossings in the graph and sets the best order solution as an order attribute on each node.
  915. //
  916. // Pre-conditions:
  917. // 1. Graph must be DAG
  918. // 2. Graph nodes must have the 'rank' attribute
  919. // 3. Graph edges must have the 'weight' attribute
  920. //
  921. // Post-conditions:
  922. // 1. Graph nodes will have an 'order' attribute based on the results of the algorithm.
  923. const order = (g) => {
  924. const sortSubgraph = (g, v, cg, biasRight) => {
  925. // Given a list of entries of the form {v, barycenter, weight} and a constraint graph this function will resolve any conflicts between the constraint graph and the barycenters for the entries.
  926. // If the barycenters for an entry would violate a constraint in the constraint graph then we coalesce the nodes in the conflict into a new node that respects the contraint and aggregates barycenter and weight information.
  927. // This implementation is based on the description in Forster, 'A Fast and Simple Hueristic for Constrained Two-Level Crossing Reduction,' thought it differs in some specific details.
  928. //
  929. // Pre-conditions:
  930. // 1. Each entry has the form {v, barycenter, weight}, or if the node has no barycenter, then {v}.
  931. //
  932. // Returns:
  933. // A new list of entries of the form {vs, i, barycenter, weight}.
  934. // The list `vs` may either be a singleton or it may be an aggregation of nodes ordered such that they do not violate constraints from the constraint graph.
  935. // The property `i` is the lowest original index of any of the elements in `vs`.
  936. const resolveConflicts = (entries, cg) => {
  937. const mappedEntries = new Map();
  938. for (let i = 0; i < entries.length; i++) {
  939. const entry = entries[i];
  940. const tmp = { indegree: 0, 'in': [], out: [], vs: [ entry.v ], i: i };
  941. if (entry.barycenter !== undefined) {
  942. tmp.barycenter = entry.barycenter;
  943. tmp.weight = entry.weight;
  944. }
  945. mappedEntries.set(entry.v, tmp);
  946. }
  947. for (const e of cg.edges.values()) {
  948. const entryV = mappedEntries.get(e.v);
  949. const entryW = mappedEntries.get(e.w);
  950. if (entryV && entryW) {
  951. entryW.indegree++;
  952. entryV.out.push(entryW);
  953. }
  954. }
  955. const sourceSet = Array.from(mappedEntries.values()).filter((entry) => !entry.indegree);
  956. const results = [];
  957. function handleIn(vEntry) {
  958. return function(uEntry) {
  959. if (uEntry.merged) {
  960. return;
  961. }
  962. if (uEntry.barycenter === undefined || vEntry.barycenter === undefined || uEntry.barycenter >= vEntry.barycenter) {
  963. let sum = 0;
  964. let weight = 0;
  965. if (vEntry.weight) {
  966. sum += vEntry.barycenter * vEntry.weight;
  967. weight += vEntry.weight;
  968. }
  969. if (uEntry.weight) {
  970. sum += uEntry.barycenter * uEntry.weight;
  971. weight += uEntry.weight;
  972. }
  973. vEntry.vs = uEntry.vs.concat(vEntry.vs);
  974. vEntry.barycenter = sum / weight;
  975. vEntry.weight = weight;
  976. vEntry.i = Math.min(uEntry.i, vEntry.i);
  977. uEntry.merged = true;
  978. }
  979. };
  980. }
  981. function handleOut(vEntry) {
  982. return function(wEntry) {
  983. wEntry.in.push(vEntry);
  984. if (--wEntry.indegree === 0) {
  985. sourceSet.push(wEntry);
  986. }
  987. };
  988. }
  989. while (sourceSet.length) {
  990. const entry = sourceSet.pop();
  991. results.push(entry);
  992. entry.in.reverse().forEach(handleIn(entry));
  993. entry.out.forEach(handleOut(entry));
  994. }
  995. return results.filter((entry) => !entry.merged).map((entry) => {
  996. const value = {
  997. vs: entry.vs,
  998. i: entry.i
  999. };
  1000. if (entry.barycenter !== undefined) {
  1001. value.barycenter = entry.barycenter;
  1002. }
  1003. if (entry.weight !== undefined) {
  1004. value.weight = entry.weight;
  1005. }
  1006. return value;
  1007. });
  1008. };
  1009. const barycenter = (g, movable) => {
  1010. return (movable || []).map((v) => {
  1011. const inV = g.node(v).in;
  1012. if (!inV.length) {
  1013. return { v: v };
  1014. }
  1015. else {
  1016. const result = inV.reduce((acc, e) => {
  1017. const edge = e.label;
  1018. const nodeU = g.node(e.v).label;
  1019. return {
  1020. sum: acc.sum + (edge.weight * nodeU.order),
  1021. weight: acc.weight + edge.weight
  1022. };
  1023. }, { sum: 0, weight: 0 });
  1024. return {
  1025. v: v,
  1026. barycenter: result.sum / result.weight,
  1027. weight: result.weight
  1028. };
  1029. }
  1030. });
  1031. };
  1032. const sort = (entries, biasRight) => {
  1033. const consumeUnsortable = (vs, unsortable, index) => {
  1034. let last;
  1035. while (unsortable.length && (last = unsortable[unsortable.length - 1]).i <= index) {
  1036. unsortable.pop();
  1037. vs.push(last.vs);
  1038. index++;
  1039. }
  1040. return index;
  1041. };
  1042. const compareWithBias = (bias) => {
  1043. return function(entryV, entryW) {
  1044. if (entryV.barycenter < entryW.barycenter) {
  1045. return -1;
  1046. }
  1047. else if (entryV.barycenter > entryW.barycenter) {
  1048. return 1;
  1049. }
  1050. return !bias ? entryV.i - entryW.i : entryW.i - entryV.i;
  1051. };
  1052. };
  1053. // partition
  1054. const parts = { lhs: [], rhs: [] };
  1055. for (const value of entries) {
  1056. if ('barycenter' in value) {
  1057. parts.lhs.push(value);
  1058. }
  1059. else {
  1060. parts.rhs.push(value);
  1061. }
  1062. }
  1063. const sortable = parts.lhs;
  1064. const unsortable = parts.rhs.sort((a, b) => -a.i + b.i);
  1065. const vs = [];
  1066. let sum = 0;
  1067. let weight = 0;
  1068. let vsIndex = 0;
  1069. sortable.sort(compareWithBias(!!biasRight));
  1070. vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
  1071. for (const entry of sortable) {
  1072. vsIndex += entry.vs.length;
  1073. vs.push(entry.vs);
  1074. sum += entry.barycenter * entry.weight;
  1075. weight += entry.weight;
  1076. vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
  1077. }
  1078. const result = { vs: flat(vs) };
  1079. if (weight) {
  1080. result.barycenter = sum / weight;
  1081. result.weight = weight;
  1082. }
  1083. return result;
  1084. };
  1085. const node = g.node(v);
  1086. const bl = node && node.label ? node.label.borderLeft : undefined;
  1087. const br = node && node.label ? node.label.borderRight: undefined;
  1088. const subgraphs = {};
  1089. const movable = bl ? g.children(v).filter((w) => w !== bl && w !== br) : g.children(v);
  1090. const barycenters = barycenter(g, movable);
  1091. for (const entry of barycenters) {
  1092. if (g.children(entry.v).length) {
  1093. const result = sortSubgraph(g, entry.v, cg, biasRight);
  1094. subgraphs[entry.v] = result;
  1095. if ('barycenter' in result) {
  1096. if (entry.barycenter !== undefined) {
  1097. entry.barycenter = (entry.barycenter * entry.weight + result.barycenter * result.weight) / (entry.weight + result.weight);
  1098. entry.weight += result.weight;
  1099. }
  1100. else {
  1101. entry.barycenter = result.barycenter;
  1102. entry.weight = result.weight;
  1103. }
  1104. }
  1105. }
  1106. }
  1107. const entries = resolveConflicts(barycenters, cg);
  1108. // expand subgraphs
  1109. for (const entry of entries) {
  1110. entry.vs = flat(entry.vs.map((v) => subgraphs[v] ? subgraphs[v].vs : v));
  1111. }
  1112. const result = sort(entries, biasRight);
  1113. if (bl) {
  1114. result.vs = flat([bl, result.vs, br]);
  1115. if (g.predecessors(bl).length) {
  1116. const blPred = g.node(g.predecessors(bl)[0]).label;
  1117. const brPred = g.node(g.predecessors(br)[0]).label;
  1118. if (!('barycenter' in result)) {
  1119. result.barycenter = 0;
  1120. result.weight = 0;
  1121. }
  1122. result.barycenter = (result.barycenter * result.weight + blPred.order + brPred.order) / (result.weight + 2);
  1123. result.weight += 2;
  1124. }
  1125. }
  1126. return result;
  1127. };
  1128. const sweepLayerGraphs = (layerGraphs, biasRight) => {
  1129. const cg = new dagre.Graph();
  1130. for (const lg of layerGraphs) {
  1131. const root = lg.options.root;
  1132. const sorted = sortSubgraph(lg, root, cg, biasRight);
  1133. const vs = sorted.vs;
  1134. const length = vs.length;
  1135. for (let i = 0; i < length; i++) {
  1136. lg.node(vs[i]).label.order = i;
  1137. }
  1138. // add subgraph constraints
  1139. const prev = {};
  1140. let rootPrev;
  1141. let exit = false;
  1142. for (const v of vs) {
  1143. let child = lg.parent(v);
  1144. let prevChild;
  1145. while (child) {
  1146. const parent = lg.parent(child);
  1147. if (parent) {
  1148. prevChild = prev[parent];
  1149. prev[parent] = child;
  1150. }
  1151. else {
  1152. prevChild = rootPrev;
  1153. rootPrev = child;
  1154. }
  1155. if (prevChild && prevChild !== child) {
  1156. cg.setEdge(prevChild, child, null);
  1157. exit = true;
  1158. break;
  1159. }
  1160. child = parent;
  1161. }
  1162. if (exit) {
  1163. break;
  1164. }
  1165. }
  1166. }
  1167. };
  1168. // A function that takes a layering (an array of layers, each with an array of
  1169. // ordererd nodes) and a graph and returns a weighted crossing count.
  1170. //
  1171. // Pre-conditions:
  1172. // 1. Input graph must be simple (not a multigraph), directed, and include
  1173. // only simple edges.
  1174. // 2. Edges in the input graph must have assigned weights.
  1175. //
  1176. // Post-conditions:
  1177. // 1. The graph and layering matrix are left unchanged.
  1178. //
  1179. // This algorithm is derived from Barth, et al., 'Bilayer Cross Counting.'
  1180. const crossCount = (g, layering) => {
  1181. let count = 0;
  1182. for (let i = 1; i < layering.length; i++) {
  1183. const northLayer = layering[i - 1];
  1184. const southLayer = layering[i];
  1185. // Sort all of the edges between the north and south layers by their position in the north layer and then the south.
  1186. // Map these edges to the position of their head in the south layer.
  1187. const southPos = {};
  1188. for (let i = 0; i < southLayer.length; i++) {
  1189. southPos[southLayer[i]] = i;
  1190. }
  1191. const southEntries = [];
  1192. for (const v of northLayer) {
  1193. const entries = [];
  1194. for (const e of g.node(v).out) {
  1195. entries.push({
  1196. pos: southPos[e.w],
  1197. weight: e.label.weight
  1198. });
  1199. }
  1200. entries.sort((a, b) => a.pos - b.pos);
  1201. for (const entry of entries) {
  1202. southEntries.push(entry);
  1203. }
  1204. }
  1205. // Build the accumulator tree
  1206. let firstIndex = 1;
  1207. while (firstIndex < southLayer.length) {
  1208. firstIndex <<= 1;
  1209. }
  1210. const treeSize = 2 * firstIndex - 1;
  1211. firstIndex -= 1;
  1212. const tree = Array.from(new Array(treeSize), () => 0);
  1213. // Calculate the weighted crossings
  1214. for (const entry of southEntries) {
  1215. let index = entry.pos + firstIndex;
  1216. tree[index] += entry.weight;
  1217. let weightSum = 0;
  1218. while (index > 0) {
  1219. if (index % 2) {
  1220. weightSum += tree[index + 1];
  1221. }
  1222. index = (index - 1) >> 1;
  1223. tree[index] += entry.weight;
  1224. }
  1225. count += entry.weight * weightSum;
  1226. }
  1227. }
  1228. return count;
  1229. };
  1230. // Assigns an initial order value for each node by performing a DFS search
  1231. // starting from nodes in the first rank. Nodes are assigned an order in their
  1232. // rank as they are first visited.
  1233. //
  1234. // This approach comes from Gansner, et al., 'A Technique for Drawing Directed
  1235. // Graphs.'
  1236. //
  1237. // Returns a layering matrix with an array per layer and each layer sorted by
  1238. // the order of its nodes.
  1239. const initOrder = (g) => {
  1240. const visited = new Set();
  1241. const nodes = Array.from(g.nodes.keys()).filter((v) => !g.children(v).length);
  1242. let maxRank = undefined;
  1243. for (const v of nodes) {
  1244. if (!g.children(v).length > 0) {
  1245. const rank = g.node(v).label.rank;
  1246. if (maxRank === undefined || (rank !== undefined && rank > maxRank)) {
  1247. maxRank = rank;
  1248. }
  1249. }
  1250. }
  1251. if (maxRank !== undefined) {
  1252. const layers = Array.from(new Array(maxRank + 1), () => []);
  1253. for (const v of nodes.map((v) => [ g.node(v).label.rank, v ]).sort((a, b) => a[0] - b[0]).map((item) => item[1])) {
  1254. const queue = [ v ];
  1255. while (queue.length > 0) {
  1256. const v = queue.shift();
  1257. if (!visited.has(v)) {
  1258. visited.add(v);
  1259. const rank = g.node(v).label.rank;
  1260. layers[rank].push(v);
  1261. queue.push(...g.successors(v));
  1262. }
  1263. }
  1264. }
  1265. return layers;
  1266. }
  1267. return [];
  1268. };
  1269. // Constructs a graph that can be used to sort a layer of nodes.
  1270. // The graph will contain all base and subgraph nodes from the request layer in their original
  1271. // hierarchy and any edges that are incident on these nodes and are of the type requested by the 'relationship' parameter.
  1272. //
  1273. // Nodes from the requested rank that do not have parents are assigned a root node in the output graph,
  1274. // which is set in the root graph attribute.
  1275. // This makes it easy to walk the hierarchy of movable nodes during ordering.
  1276. //
  1277. // Pre-conditions:
  1278. // 1. Input graph is a DAG
  1279. // 2. Base nodes in the input graph have a rank attribute
  1280. // 3. Subgraph nodes in the input graph has minRank and maxRank attributes
  1281. // 4. Edges have an assigned weight
  1282. //
  1283. // Post-conditions:
  1284. // 1. Output graph has all nodes in the movable rank with preserved hierarchy.
  1285. // 2. Root nodes in the movable layer are made children of the node
  1286. // indicated by the root attribute of the graph.
  1287. // 3. Non-movable nodes incident on movable nodes, selected by the
  1288. // relationship parameter, are included in the graph (without hierarchy).
  1289. // 4. Edges incident on movable nodes, selected by the relationship parameter, are added to the output graph.
  1290. // 5. The weights for copied edges are aggregated as need, since the output graph is not a multi-graph.
  1291. const buildLayerGraph = (g, nodes, rank, relationship) => {
  1292. let root;
  1293. while (g.hasNode((root = uniqueId('_root'))));
  1294. const graph = new dagre.Graph({ compound: true });
  1295. graph.options = { root: root };
  1296. graph.setDefaultNodeLabel((v) => { const node = g.node(v); return node ? node.label : undefined; });
  1297. const length = nodes.length;
  1298. let i = 0;
  1299. while (i < length) {
  1300. const node = nodes[i++];
  1301. const label = node.label;
  1302. if (label.rank === rank || 'minRank' in label && 'maxRank' in label && label.minRank <= rank && rank <= label.maxRank) {
  1303. const v = node.v;
  1304. graph.setNode(v);
  1305. const parent = g.parent(v);
  1306. graph.setParent(v, parent || root);
  1307. // This assumes we have only short edges!
  1308. if (relationship) {
  1309. for (const e of node.in) {
  1310. graph.setEdge(e.v, v, { weight: e.label.weight });
  1311. }
  1312. }
  1313. else {
  1314. for (const e of node.out) {
  1315. graph.setEdge(e.w, v, { weight: e.label.weight });
  1316. }
  1317. }
  1318. if ('minRank' in label) {
  1319. graph.setNode(v, {
  1320. borderLeft: label.borderLeft[rank],
  1321. borderRight: label.borderRight[rank]
  1322. });
  1323. }
  1324. }
  1325. }
  1326. return graph;
  1327. };
  1328. let layering = initOrder(g);
  1329. const assignOrder = (g, layering) => {
  1330. for (const layer of layering) {
  1331. for (let i = 0; i < layer.length; i++) {
  1332. g.node(layer[i]).label.order = i;
  1333. }
  1334. }
  1335. };
  1336. assignOrder(g, layering);
  1337. const rank = maxRank(g) || 0;
  1338. const downLayerGraphs = new Array(rank);
  1339. const upLayerGraphs = new Array(rank);
  1340. const nodes = Array.from(g.nodes.values());
  1341. for (let i = 0; i < rank; i++) {
  1342. downLayerGraphs[i] = buildLayerGraph(g, nodes, i + 1, true);
  1343. upLayerGraphs[i] = buildLayerGraph(g, nodes, rank - i - 1, false);
  1344. }
  1345. let bestCC = Number.POSITIVE_INFINITY;
  1346. let best;
  1347. for (let i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
  1348. sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2);
  1349. layering = buildLayerMatrix(g);
  1350. const cc = crossCount(g, layering);
  1351. if (cc < bestCC) {
  1352. lastBest = 0;
  1353. const length = layering.length;
  1354. best = new Array(length);
  1355. for (let j = 0; j < length; j++) {
  1356. best[j] = layering[j].slice();
  1357. }
  1358. bestCC = cc;
  1359. }
  1360. }
  1361. assignOrder(g, best);
  1362. };
  1363. const insertSelfEdges = (g) => {
  1364. const layers = buildLayerMatrix(g);
  1365. for (const layer of layers) {
  1366. let orderShift = 0;
  1367. layer.forEach(function(v, i) {
  1368. const label = g.node(v).label;
  1369. label.order = i + orderShift;
  1370. if (label.selfEdges) {
  1371. for (const selfEdge of label.selfEdges) {
  1372. addDummyNode(g, 'selfedge', {
  1373. width: selfEdge.label.width,
  1374. height: selfEdge.label.height,
  1375. rank: label.rank,
  1376. order: i + (++orderShift),
  1377. e: selfEdge.e,
  1378. label: selfEdge.label
  1379. }, '_se');
  1380. }
  1381. delete label.selfEdges;
  1382. }
  1383. });
  1384. }
  1385. };
  1386. const coordinateSystem_swapWidthHeight = (g) => {
  1387. for (const node of g.nodes.values()) {
  1388. const label = node.label;
  1389. const w = label.width;
  1390. label.width = label.height;
  1391. label.height = w;
  1392. }
  1393. for (const e of g.edges.values()) {
  1394. const label = e.label;
  1395. const w = label.width;
  1396. label.width = label.height;
  1397. label.height = w;
  1398. }
  1399. };
  1400. const coordinateSystem_adjust = (g) => {
  1401. const rankDir = g.options.rankdir.toLowerCase();
  1402. if (rankDir === 'lr' || rankDir === 'rl') {
  1403. coordinateSystem_swapWidthHeight(g);
  1404. }
  1405. };
  1406. const coordinateSystem_undo = (g) => {
  1407. const rankDir = g.options.rankdir.toLowerCase();
  1408. if (rankDir === 'bt' || rankDir === 'rl') {
  1409. for (const node of g.nodes.values()) {
  1410. node.label.y = -node.label.y;
  1411. }
  1412. for (const e of g.edges.values()) {
  1413. const edge = e.label;
  1414. for (const attr of edge.points) {
  1415. attr.y = -attr.y;
  1416. }
  1417. if ('y' in edge) {
  1418. edge.y = -edge.y;
  1419. }
  1420. }
  1421. }
  1422. if (rankDir === 'lr' || rankDir === 'rl') {
  1423. const swapXYOne = (attrs) => {
  1424. const x = attrs.x;
  1425. attrs.x = attrs.y;
  1426. attrs.y = x;
  1427. };
  1428. for (const node of g.nodes.values()) {
  1429. swapXYOne(node.label);
  1430. }
  1431. for (const e of g.edges.values()) {
  1432. const edge = e.label;
  1433. for (const e of edge.points) {
  1434. swapXYOne(e);
  1435. }
  1436. if (edge.x !== undefined) {
  1437. swapXYOne(edge);
  1438. }
  1439. }
  1440. coordinateSystem_swapWidthHeight(g);
  1441. }
  1442. };
  1443. const position = (g) => {
  1444. const addConflict = (conflicts, v, w) => {
  1445. if (v > w) {
  1446. const tmp = v;
  1447. v = w;
  1448. w = tmp;
  1449. }
  1450. let conflictsV = conflicts[v];
  1451. if (!conflictsV) {
  1452. conflicts[v] = conflictsV = {};
  1453. }
  1454. conflictsV[w] = true;
  1455. };
  1456. const hasConflict = (conflicts, v, w) => {
  1457. if (v > w) {
  1458. const tmp = v;
  1459. v = w;
  1460. w = tmp;
  1461. }
  1462. return conflicts[v] && w in conflicts[v];
  1463. };
  1464. const buildBlockGraph = (g, layering, root, reverseSep) => {
  1465. const nodeSep = g.options.nodesep;
  1466. const edgeSep = g.options.edgesep;
  1467. const blockGraph = new dagre.Graph();
  1468. for (const layer of layering) {
  1469. let u;
  1470. for (const v of layer) {
  1471. const vRoot = root[v];
  1472. blockGraph.setNode(vRoot, {});
  1473. if (u) {
  1474. const uRoot = root[u];
  1475. const vLabel = g.node(v).label;
  1476. const wLabel = g.node(u).label;
  1477. let sum = 0;
  1478. let delta;
  1479. sum += vLabel.width / 2;
  1480. if ('labelpos' in vLabel) {
  1481. switch (vLabel.labelpos) {
  1482. case 'l': delta = -vLabel.width / 2; break;
  1483. case 'r': delta = vLabel.width / 2; break;
  1484. }
  1485. }
  1486. if (delta) {
  1487. sum += reverseSep ? delta : -delta;
  1488. }
  1489. delta = 0;
  1490. sum += (vLabel.dummy ? edgeSep : nodeSep) / 2;
  1491. sum += (wLabel.dummy ? edgeSep : nodeSep) / 2;
  1492. sum += wLabel.width / 2;
  1493. if ('labelpos' in wLabel) {
  1494. switch (wLabel.labelpos) {
  1495. case 'l': delta = wLabel.width / 2; break;
  1496. case 'r': delta = -wLabel.width / 2; break;
  1497. }
  1498. }
  1499. if (delta) {
  1500. sum += reverseSep ? delta : -delta;
  1501. }
  1502. const edge = blockGraph.edge(uRoot, vRoot);
  1503. const max = Math.max(sum, edge ? edge.label : 0);
  1504. if (edge) {
  1505. edge.label = max;
  1506. }
  1507. else {
  1508. blockGraph.setEdge(uRoot, vRoot, max);
  1509. }
  1510. }
  1511. u = v;
  1512. }
  1513. }
  1514. return blockGraph;
  1515. };
  1516. // Try to align nodes into vertical 'blocks' where possible.
  1517. // This algorithm attempts to align a node with one of its median neighbors.
  1518. // If the edge connecting a neighbor is a type-1 conflict then we ignore that possibility.
  1519. // If a previous node has already formed a block with a node after the node we're trying to form a block with,
  1520. // we also ignore that possibility - our blocks would be split in that scenario.
  1521. const verticalAlignment = (layering, conflicts, neighborFn) => {
  1522. const root = {};
  1523. const align = {};
  1524. const pos = {};
  1525. // We cache the position here based on the layering because the graph and layering may be out of sync.
  1526. // The layering matrix is manipulated to generate different extreme alignments.
  1527. for (const layer of layering) {
  1528. let order = 0;
  1529. for (const v of layer) {
  1530. root[v] = v;
  1531. align[v] = v;
  1532. pos[v] = order;
  1533. order++;
  1534. }
  1535. }
  1536. for (const layer of layering) {
  1537. let prevIdx = -1;
  1538. for (const v of layer) {
  1539. let ws = neighborFn(v);
  1540. if (ws.length > 0) {
  1541. ws = ws.sort((a, b) => pos[a] - pos[b]);
  1542. const mp = (ws.length - 1) / 2.0;
  1543. const il = Math.ceil(mp);
  1544. for (let i = Math.floor(mp); i <= il; i++) {
  1545. const w = ws[i];
  1546. if (align[v] === v && prevIdx < pos[w] && !hasConflict(conflicts, v, w)) {
  1547. align[w] = v;
  1548. align[v] = root[v] = root[w];
  1549. prevIdx = pos[w];
  1550. }
  1551. }
  1552. }
  1553. }
  1554. }
  1555. return { root: root, align: align };
  1556. };
  1557. const horizontalCompaction = (g, layering, root, align, reverseSep) => {
  1558. // This portion of the algorithm differs from BK due to a number of problems.
  1559. // Instead of their algorithm we construct a new block graph and do two sweeps.
  1560. const xs = {};
  1561. const blockG = buildBlockGraph(g, layering, root, reverseSep);
  1562. const borderType = reverseSep ? 'borderLeft' : 'borderRight';
  1563. const iterate = (setXsFunc, nextNodesFunc) => {
  1564. let stack = Array.from(blockG.nodes.keys());
  1565. const visited = new Set();
  1566. while (stack.length > 0) {
  1567. const v = stack.pop();
  1568. if (visited.has(v)) {
  1569. setXsFunc(v);
  1570. }
  1571. else {
  1572. visited.add(v);
  1573. stack.push(v);
  1574. stack = stack.concat(nextNodesFunc(v));
  1575. }
  1576. }
  1577. };
  1578. // First pass, places blocks with the smallest possible coordinates.
  1579. const pass1 = (v) => {
  1580. let max = 0;
  1581. for (const e of blockG.node(v).in) {
  1582. max = Math.max(max, xs[e.v] + e.label);
  1583. }
  1584. xs[v] = max;
  1585. };
  1586. // Second pass, removes unused space by moving blocks to the greatest coordinates without violating separation.
  1587. const pass2 = (v) => {
  1588. let min = Number.POSITIVE_INFINITY;
  1589. for (const e of blockG.node(v).out) {
  1590. min = Math.min(min, xs[e.w] - e.label);
  1591. }
  1592. const label = g.node(v).label;
  1593. if (min !== Number.POSITIVE_INFINITY && label.borderType !== borderType) {
  1594. xs[v] = Math.max(xs[v], min);
  1595. }
  1596. };
  1597. iterate(pass1, blockG.predecessors.bind(blockG));
  1598. iterate(pass2, blockG.successors.bind(blockG));
  1599. // Assign x coordinates to all nodes
  1600. for (const v of Object.values(align)) {
  1601. xs[v] = xs[root[v]];
  1602. }
  1603. return xs;
  1604. };
  1605. // Marks all edges in the graph with a type-1 conflict with the 'type1Conflict' property.
  1606. // A type-1 conflict is one where a non-inner segment crosses an inner segment.
  1607. // An inner segment is an edge with both incident nodes marked with the 'dummy' property.
  1608. //
  1609. // This algorithm scans layer by layer, starting with the second, for type-1
  1610. // conflicts between the current layer and the previous layer. For each layer
  1611. // it scans the nodes from left to right until it reaches one that is incident
  1612. // on an inner segment. It then scans predecessors to determine if they have
  1613. // edges that cross that inner segment. At the end a final scan is done for all
  1614. // nodes on the current rank to see if they cross the last visited inner segment.
  1615. //
  1616. // This algorithm (safely) assumes that a dummy node will only be incident on a
  1617. // single node in the layers being scanned.
  1618. const findType1Conflicts = (g, layering) => {
  1619. const conflicts = {};
  1620. if (layering.length > 0) {
  1621. let prev = layering[0];
  1622. for (let k = 1; k < layering.length; k++) {
  1623. const layer = layering[k];
  1624. // last visited node in the previous layer that is incident on an inner segment.
  1625. let k0 = 0;
  1626. // Tracks the last node in this layer scanned for crossings with a type-1 segment.
  1627. let scanPos = 0;
  1628. const prevLayerLength = prev.length;
  1629. const lastNode = layer[layer.length - 1];
  1630. for (let i = 0; i < layer.length; i++) {
  1631. const v = layer[i];
  1632. const w = g.node(v).label.dummy ? g.predecessors(v).find((u) => g.node(u).label.dummy) : null;
  1633. if (w || v === lastNode) {
  1634. const k1 = w ? g.node(w).label.order : prevLayerLength;
  1635. for (const scanNode of layer.slice(scanPos, i + 1)) {
  1636. // for (const scanNode of layer.slice(scanPos, scanPos + 1)) {
  1637. for (const u of g.predecessors(scanNode)) {
  1638. const uLabel = g.node(u).label;
  1639. const uPos = uLabel.order;
  1640. if ((uPos < k0 || k1 < uPos) && !(uLabel.dummy && g.node(scanNode).label.dummy)) {
  1641. addConflict(conflicts, u, scanNode);
  1642. }
  1643. }
  1644. }
  1645. // scanPos += 1;
  1646. scanPos = i + 1;
  1647. k0 = k1;
  1648. }
  1649. }
  1650. prev = layer;
  1651. }
  1652. }
  1653. return conflicts;
  1654. };
  1655. const findType2Conflicts = (g, layering) => {
  1656. const conflicts = {};
  1657. const scan = (south, southPos, southEnd, prevNorthBorder, nextNorthBorder) => {
  1658. let v;
  1659. for (let i = southPos; i < southEnd; i++) {
  1660. v = south[i];
  1661. if (g.node(v).labeldummy) {
  1662. for (const u of g.predecessors(v)) {
  1663. const uNode = g.node(u).label;
  1664. if (uNode.dummy && (uNode.order < prevNorthBorder || uNode.order > nextNorthBorder)) {
  1665. addConflict(conflicts, u, v);
  1666. }
  1667. }
  1668. }
  1669. }
  1670. };
  1671. if (layering.length > 0) {
  1672. let north = layering[0];
  1673. for (let i = 1; i < layering.length; i++) {
  1674. const south = layering[i];
  1675. let prevNorthPos = -1;
  1676. let nextNorthPos;
  1677. let southPos = 0;
  1678. south.forEach(function(v, southLookahead) {
  1679. if (g.node(v).label.dummy === 'border') {
  1680. const predecessors = g.predecessors(v);
  1681. if (predecessors.length) {
  1682. nextNorthPos = g.node(predecessors[0]).label.order;
  1683. scan(south, southPos, southLookahead, prevNorthPos, nextNorthPos);
  1684. southPos = southLookahead;
  1685. prevNorthPos = nextNorthPos;
  1686. }
  1687. }
  1688. scan(south, southPos, south.length, nextNorthPos, north.length);
  1689. });
  1690. north = south;
  1691. }
  1692. }
  1693. return conflicts;
  1694. };
  1695. g = asNonCompoundGraph(g);
  1696. const layering = buildLayerMatrix(g);
  1697. const ranksep = g.options.ranksep;
  1698. // Assign y-coordinate based on rank
  1699. let y = 0;
  1700. for (const layer of layering) {
  1701. const maxHeight = layer.reduce((a, v) => Math.max(a, g.node(v).label.height), 0);
  1702. for (const v of layer) {
  1703. g.node(v).label.y = y + maxHeight / 2;
  1704. }
  1705. y += maxHeight + ranksep;
  1706. }
  1707. // Coordinate assignment based on Brandes and Köpf, 'Fast and Simple Horizontal Coordinate Assignment.'
  1708. const conflicts = Object.assign(findType1Conflicts(g, layering), findType2Conflicts(g, layering));
  1709. const xss = {};
  1710. for (const vertical of ['u', 'd']) {
  1711. let adjustedLayering = vertical === 'u' ? layering : Object.values(layering).reverse();
  1712. for (const horizontal of ['l', 'r']) {
  1713. if (horizontal === 'r') {
  1714. adjustedLayering = adjustedLayering.map((layer) => Object.values(layer).reverse());
  1715. }
  1716. const neighborFn = (vertical === 'u' ? g.predecessors : g.successors).bind(g);
  1717. const align = verticalAlignment(adjustedLayering, conflicts, neighborFn);
  1718. const xs = horizontalCompaction(g, adjustedLayering, align.root, align.align, horizontal === 'r');
  1719. if (horizontal === 'r') {
  1720. for (const entry of Object.entries(xs)) {
  1721. xs[entry[0]] = -entry[1];
  1722. }
  1723. }
  1724. xss[vertical + horizontal] = xs;
  1725. }
  1726. }
  1727. // Find smallest width alignment: Returns the alignment that has the smallest width of the given alignments.
  1728. let minWidth = Number.POSITIVE_INFINITY;
  1729. let minValue = undefined;
  1730. for (const xs of Object.values(xss)) {
  1731. let max = Number.NEGATIVE_INFINITY;
  1732. let min = Number.POSITIVE_INFINITY;
  1733. for (const entry of Object.entries(xs)) {
  1734. const v = entry[0];
  1735. const x = entry[1];
  1736. const halfWidth = g.node(v).label.width / 2;
  1737. max = Math.max(x + halfWidth, max);
  1738. min = Math.min(x - halfWidth, min);
  1739. }
  1740. const width = max - min;
  1741. if (width < minWidth) {
  1742. minWidth = width;
  1743. minValue = xs;
  1744. }
  1745. }
  1746. // Align the coordinates of each of the layout alignments such that
  1747. // left-biased alignments have their minimum coordinate at the same point as
  1748. // the minimum coordinate of the smallest width alignment and right-biased
  1749. // alignments have their maximum coordinate at the same point as the maximum
  1750. // coordinate of the smallest width alignment.
  1751. const alignTo = minValue;
  1752. const range = (values) => {
  1753. let min = Number.POSITIVE_INFINITY;
  1754. let max = Number.NEGATIVE_INFINITY;
  1755. for (const value of values) {
  1756. if (value < min) {
  1757. min = value;
  1758. }
  1759. if (value > max) {
  1760. max = value;
  1761. }
  1762. }
  1763. return [ min, max ];
  1764. };
  1765. const alignToRange = range(Object.values(alignTo));
  1766. for (const vertical of ['u', 'd']) {
  1767. for (const horizontal of ['l', 'r']) {
  1768. const alignment = vertical + horizontal;
  1769. const xs = xss[alignment];
  1770. let delta;
  1771. if (xs !== alignTo) {
  1772. const vsValsRange = range(Object.values(xs));
  1773. delta = horizontal === 'l' ? alignToRange[0] - vsValsRange[0] : alignToRange[1] - vsValsRange[1];
  1774. if (delta) {
  1775. const list = {};
  1776. for (const key of Object.keys(xs)) {
  1777. list[key] = xs[key] + delta;
  1778. }
  1779. xss[alignment] = list;
  1780. }
  1781. }
  1782. }
  1783. }
  1784. // balance
  1785. const align = g.options.align;
  1786. if (align) {
  1787. const xs = xss[align.toLowerCase()];
  1788. for (const v of Object.keys(xss.ul)) {
  1789. g.node(v).label.x = xs[v];
  1790. }
  1791. }
  1792. else {
  1793. for (const v of Object.keys(xss.ul)) {
  1794. const xs = [ xss.ul[v], xss.ur[v], xss.dl[v], xss.dr[v] ].sort((a, b) => a - b);
  1795. g.node(v).label.x = (xs[1] + xs[2]) / 2;
  1796. }
  1797. }
  1798. };
  1799. const positionSelfEdges = (g) => {
  1800. for (const node of g.nodes.values()) {
  1801. const label = node.label;
  1802. if (label.dummy === 'selfedge') {
  1803. const v = node.v;
  1804. const selfNode = g.node(label.e.v).label;
  1805. const x = selfNode.x + selfNode.width / 2;
  1806. const y = selfNode.y;
  1807. const dx = label.x - x;
  1808. const dy = selfNode.height / 2;
  1809. g.setEdge(label.e.v, label.e.w, label.label);
  1810. g.removeNode(v);
  1811. label.label.points = [
  1812. { x: x + 2 * dx / 3, y: y - dy },
  1813. { x: x + 5 * dx / 6, y: y - dy },
  1814. { x: x + dx , y: y },
  1815. { x: x + 5 * dx / 6, y: y + dy },
  1816. { x: x + 2 * dx / 3, y: y + dy }
  1817. ];
  1818. label.label.x = label.x;
  1819. label.label.y = label.y;
  1820. }
  1821. }
  1822. };
  1823. const removeBorderNodes = (g) => {
  1824. for (const node of g.nodes.values()) {
  1825. const v = node.v;
  1826. if (g.children(v).length) {
  1827. const label = node.label;
  1828. const t = g.node(label.borderTop).label;
  1829. const b = g.node(label.borderBottom).label;
  1830. const l = g.node(label.borderLeft[label.borderLeft.length - 1]).label;
  1831. const r = g.node(label.borderRight[label.borderRight.length - 1]).label;
  1832. label.width = Math.abs(r.x - l.x);
  1833. label.height = Math.abs(b.y - t.y);
  1834. label.x = l.x + label.width / 2;
  1835. label.y = t.y + label.height / 2;
  1836. }
  1837. }
  1838. for (const node of g.nodes.values()) {
  1839. if (node.label.dummy === 'border') {
  1840. g.removeNode(node.v);
  1841. }
  1842. }
  1843. };
  1844. const fixupEdgeLabelCoords = (g) => {
  1845. for (const e of g.edges.values()) {
  1846. const edge = e.label;
  1847. if ('x' in edge) {
  1848. if (edge.labelpos === 'l' || edge.labelpos === 'r') {
  1849. edge.width -= edge.labeloffset;
  1850. }
  1851. switch (edge.labelpos) {
  1852. case 'l': edge.x -= edge.width / 2 + edge.labeloffset; break;
  1853. case 'r': edge.x += edge.width / 2 + edge.labeloffset; break;
  1854. }
  1855. }
  1856. }
  1857. };
  1858. const translateGraph = (g) => {
  1859. let minX = Number.POSITIVE_INFINITY;
  1860. let maxX = 0;
  1861. let minY = Number.POSITIVE_INFINITY;
  1862. let maxY = 0;
  1863. const getExtremes = (attrs) => {
  1864. const x = attrs.x;
  1865. const y = attrs.y;
  1866. const w = attrs.width;
  1867. const h = attrs.height;
  1868. minX = Math.min(minX, x - w / 2);
  1869. maxX = Math.max(maxX, x + w / 2);
  1870. minY = Math.min(minY, y - h / 2);
  1871. maxY = Math.max(maxY, y + h / 2);
  1872. };
  1873. for (const node of g.nodes.values()) {
  1874. getExtremes(node.label);
  1875. }
  1876. for (const e of g.edges.values()) {
  1877. const edge = e.label;
  1878. if ('x' in edge) {
  1879. getExtremes(edge);
  1880. }
  1881. }
  1882. for (const node of g.nodes.values()) {
  1883. node.label.x -= minX;
  1884. node.label.y -= minY;
  1885. }
  1886. for (const e of g.edges.values()) {
  1887. const edge = e.label;
  1888. for (const p of edge.points) {
  1889. p.x -= minX;
  1890. p.y -= minY;
  1891. }
  1892. if ('x' in edge) {
  1893. edge.x -= minX;
  1894. }
  1895. if ('y' in edge) {
  1896. edge.y -= minY;
  1897. }
  1898. }
  1899. const graphLabel = g.options;
  1900. graphLabel.width = maxX - minX;
  1901. graphLabel.height = maxY - minY;
  1902. };
  1903. const assignNodeIntersects = (g) => {
  1904. // Finds where a line starting at point ({x, y}) would intersect a rectangle
  1905. // ({x, y, width, height}) if it were pointing at the rectangle's center.
  1906. const intersectRect = (rect, point) => {
  1907. const x = rect.x;
  1908. const y = rect.y;
  1909. // Rectangle intersection algorithm from: http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
  1910. const dx = point.x - x;
  1911. const dy = point.y - y;
  1912. let w = rect.width / 2;
  1913. let h = rect.height / 2;
  1914. if (!dx && !dy) {
  1915. throw new Error('Not possible to find intersection inside of the rectangle');
  1916. }
  1917. let sx;
  1918. let sy;
  1919. if (Math.abs(dy) * w > Math.abs(dx) * h) {
  1920. // Intersection is top or bottom of rect.
  1921. if (dy < 0) {
  1922. h = -h;
  1923. }
  1924. sx = h * dx / dy;
  1925. sy = h;
  1926. }
  1927. else {
  1928. // Intersection is left or right of rect.
  1929. if (dx < 0) {
  1930. w = -w;
  1931. }
  1932. sx = w;
  1933. sy = w * dy / dx;
  1934. }
  1935. return { x: x + sx, y: y + sy };
  1936. };
  1937. for (const e of g.edges.values()) {
  1938. const edge = e.label;
  1939. const nodeV = g.node(e.v).label;
  1940. const nodeW = g.node(e.w).label;
  1941. let p1;
  1942. let p2;
  1943. if (!edge.points) {
  1944. edge.points = [];
  1945. p1 = nodeW;
  1946. p2 = nodeV;
  1947. }
  1948. else {
  1949. p1 = edge.points[0];
  1950. p2 = edge.points[edge.points.length - 1];
  1951. }
  1952. edge.points.unshift(intersectRect(nodeV, p1));
  1953. edge.points.push(intersectRect(nodeW, p2));
  1954. }
  1955. };
  1956. time(' makeSpaceForEdgeLabels', () => { makeSpaceForEdgeLabels(g); });
  1957. time(' removeSelfEdges', () => { removeSelfEdges(g); });
  1958. time(' acyclic_run', () => { acyclic_run(g); });
  1959. time(' nestingGraph_run', () => { nestingGraph_run(g); });
  1960. time(' rank', () => { rank(asNonCompoundGraph(g)); });
  1961. time(' injectEdgeLabelProxies', () => { injectEdgeLabelProxies(g); });
  1962. time(' removeEmptyRanks', () => { removeEmptyRanks(g); });
  1963. time(' nestingGraph_cleanup', () => { nestingGraph_cleanup(g); });
  1964. time(' assignRankMinMax', () => { assignRankMinMax(g); });
  1965. time(' removeEdgeLabelProxies', () => { removeEdgeLabelProxies(g); });
  1966. time(' normalize', () => { normalize(g); });
  1967. time(' parentDummyChains', () => { parentDummyChains(g); });
  1968. time(' addBorderSegments', () => { addBorderSegments(g); });
  1969. time(' order', () => { order(g); });
  1970. time(' insertSelfEdges', () => { insertSelfEdges(g); });
  1971. time(' coordinateSystem_adjust', () => { coordinateSystem_adjust(g); });
  1972. time(' position', () => { position(g); });
  1973. time(' positionSelfEdges', () => { positionSelfEdges(g); });
  1974. time(' removeBorderNodes', () => { removeBorderNodes(g); });
  1975. time(' denormalize', () => { denormalize(g); });
  1976. time(' fixupEdgeLabelCoords', () => { fixupEdgeLabelCoords(g); });
  1977. time(' coordinateSystem_undo', () => { coordinateSystem_undo(g); });
  1978. time(' translateGraph', () => { translateGraph(g); });
  1979. time(' assignNodeIntersects', () => { assignNodeIntersects(g); });
  1980. time(' acyclic_undo', () => { acyclic_undo(g); });
  1981. };
  1982. // Copies final layout information from the layout graph back to the input graph.
  1983. // This process only copies whitelisted attributes from the layout graph to the input graph,
  1984. // so it serves as a good place to determine what attributes can influence layout.
  1985. const updateSourceGraph = (graph, g) => {
  1986. for (const node of graph.nodes.values()) {
  1987. const label = node.label;
  1988. if (label) {
  1989. const v = node.v;
  1990. const layoutLabel = g.node(v).label;
  1991. label.x = layoutLabel.x;
  1992. label.y = layoutLabel.y;
  1993. if (g.children(v).length) {
  1994. label.width = layoutLabel.width;
  1995. label.height = layoutLabel.height;
  1996. }
  1997. }
  1998. }
  1999. for (const e of graph.edges.values()) {
  2000. const layoutLabel = g.edge(e.v, e.w).label;
  2001. e.label.points = layoutLabel.points;
  2002. if ('x' in layoutLabel) {
  2003. e.label.x = layoutLabel.x;
  2004. e.label.y = layoutLabel.y;
  2005. }
  2006. }
  2007. graph.options.width = g.options.width;
  2008. graph.options.height = g.options.height;
  2009. };
  2010. time('layout', () => {
  2011. const layoutGraph =
  2012. time(' buildLayoutGraph', () => { return buildLayoutGraph(graph); });
  2013. time(' runLayout', () => { runLayout(layoutGraph, time); });
  2014. time(' updateSourceGraph', () => { updateSourceGraph(graph, layoutGraph); });
  2015. });
  2016. };
  2017. dagre.Graph = class {
  2018. constructor(options) {
  2019. options = options || {};
  2020. this._directed = 'directed' in options ? options.directed : true;
  2021. this._compound = 'compound' in options ? options.compound : false;
  2022. this._label = undefined;
  2023. this._defaultNodeLabelFn = () => {
  2024. return undefined;
  2025. };
  2026. this.nodes = new Map();
  2027. this.edges = new Map();
  2028. if (this._compound) {
  2029. this._parent = {};
  2030. this._children = {};
  2031. this._children['\x00'] = {};
  2032. }
  2033. }
  2034. set options(value) {
  2035. this._label = value;
  2036. }
  2037. get options() {
  2038. return this._label;
  2039. }
  2040. isDirected() {
  2041. return this._directed;
  2042. }
  2043. isCompound() {
  2044. return this._compound;
  2045. }
  2046. setDefaultNodeLabel(newDefault) {
  2047. this._defaultNodeLabelFn = newDefault;
  2048. }
  2049. setNode(v, label) {
  2050. const node = this.nodes.get(v);
  2051. if (node) {
  2052. if (label) {
  2053. node.label = label;
  2054. }
  2055. }
  2056. else {
  2057. const node = { label: label ? label : this._defaultNodeLabelFn(v), in: [], out: [], predecessors: {}, successors: {}, v: v };
  2058. this.nodes.set(v, node);
  2059. if (this._compound) {
  2060. this._parent[v] = '\x00';
  2061. this._children[v] = {};
  2062. this._children['\x00'][v] = true;
  2063. }
  2064. }
  2065. }
  2066. node(v) {
  2067. return this.nodes.get(v);
  2068. }
  2069. hasNode(v) {
  2070. return this.nodes.has(v);
  2071. }
  2072. removeNode(v) {
  2073. const node = this.nodes.get(v);
  2074. if (node) {
  2075. if (this._compound) {
  2076. delete this._children[this._parent[v]][v];
  2077. delete this._parent[v];
  2078. for (const child of this.children(v)) {
  2079. this.setParent(child);
  2080. }
  2081. delete this._children[v];
  2082. }
  2083. for (const edge of node.in) {
  2084. this.removeEdge(edge);
  2085. }
  2086. for (const edge of node.out) {
  2087. this.removeEdge(edge);
  2088. }
  2089. this.nodes.delete(v);
  2090. }
  2091. }
  2092. setParent(v, parent) {
  2093. if (!this._compound) {
  2094. throw new Error('Cannot set parent in a non-compound graph');
  2095. }
  2096. if (parent) {
  2097. for (let ancestor = parent; ancestor !== undefined; ancestor = this.parent(ancestor)) {
  2098. if (ancestor === v) {
  2099. throw new Error('Setting ' + parent + ' as parent of ' + v + ' would create a cycle.');
  2100. }
  2101. }
  2102. this.setNode(parent);
  2103. }
  2104. else {
  2105. parent = '\x00';
  2106. }
  2107. delete this._children[this._parent[v]][v];
  2108. this._parent[v] = parent;
  2109. this._children[parent][v] = true;
  2110. }
  2111. parent(v) {
  2112. if (this._compound) {
  2113. const parent = this._parent[v];
  2114. if (parent !== '\x00') {
  2115. return parent;
  2116. }
  2117. }
  2118. }
  2119. children(v) {
  2120. if (this._compound) {
  2121. return Object.keys(this._children[v === undefined ? '\x00' : v]);
  2122. }
  2123. else if (v === undefined) {
  2124. return this.nodes.keys();
  2125. }
  2126. else if (this.hasNode(v)) {
  2127. return [];
  2128. }
  2129. }
  2130. predecessors(v) {
  2131. return Object.keys(this.nodes.get(v).predecessors);
  2132. }
  2133. successors(v) {
  2134. return Object.keys(this.nodes.get(v).successors);
  2135. }
  2136. neighbors(v) {
  2137. return Array.from(new Set(this.predecessors(v).concat(this.successors(v))));
  2138. }
  2139. edge(v, w) {
  2140. return this.edges.get(this._edgeKey(this._directed, v, w));
  2141. }
  2142. setEdge(v, w, label, name) {
  2143. const key = this._edgeKey(this._directed, v, w, name);
  2144. const edge = this.edges.get(key);
  2145. if (edge) {
  2146. edge.label = label;
  2147. }
  2148. else {
  2149. if (!this._directed && v > w) {
  2150. const tmp = v;
  2151. v = w;
  2152. w = tmp;
  2153. }
  2154. const edge = { label: label, v: v, w: w, name: name, key: key, vNode: null, wNode: null };
  2155. this.edges.set(key, edge);
  2156. this.setNode(v);
  2157. this.setNode(w);
  2158. const wNode = this.nodes.get(w);
  2159. const vNode = this.nodes.get(v);
  2160. edge.wNode = wNode;
  2161. edge.vNode = vNode;
  2162. const incrementOrInitEntry = (map, k) => {
  2163. if (map[k]) {
  2164. map[k]++;
  2165. }
  2166. else {
  2167. map[k] = 1;
  2168. }
  2169. };
  2170. incrementOrInitEntry(wNode.predecessors, v);
  2171. incrementOrInitEntry(vNode.successors, w);
  2172. wNode.in.push(edge);
  2173. vNode.out.push(edge);
  2174. }
  2175. }
  2176. removeEdge(edge) {
  2177. const key = edge.key;
  2178. const v = edge.v;
  2179. const w = edge.w;
  2180. const decrementOrRemoveEntry = (map, k) => {
  2181. if (!--map[k]) {
  2182. delete map[k];
  2183. }
  2184. };
  2185. const wNode = edge.wNode;
  2186. const vNode = edge.vNode;
  2187. decrementOrRemoveEntry(wNode.predecessors, v);
  2188. decrementOrRemoveEntry(vNode.successors, w);
  2189. wNode.in = wNode.in.filter((edge) => edge.key !== key);
  2190. vNode.out = vNode.out.filter((edge) => edge.key !== key);
  2191. this.edges.delete(key);
  2192. }
  2193. _edgeKey(isDirected, v, w, name) {
  2194. if (!isDirected && v > w) {
  2195. return name ? w + ':' + v + ':' + name : w + ':' + v + ':';
  2196. }
  2197. return name ? v + ':' + w + ':' + name : v + ':' + w + ':';
  2198. }
  2199. toString() {
  2200. return [
  2201. '[nodes]', Array.from(this.nodes.values()).map(n => JSON.stringify(n.label)).join('\n'),
  2202. '[edges]', Array.from(this.edges.values()).map(e => JSON.stringify(e.label)).join('\n'),
  2203. '[parents]', JSON.stringify(this._parent, null, 2),
  2204. '[children]', JSON.stringify(this._children, null, 2)
  2205. ].join('\n');
  2206. }
  2207. };
  2208. if (typeof module !== 'undefined' && typeof module.exports === 'object') {
  2209. module.exports = dagre;
  2210. }