UNPKG

cytoscape-tidytree

Version:

Cytoscape.js layout extension for positioning trees

183 lines (182 loc) 6.25 kB
// A linked list of the indexes of left siblings and their lowest vertical coordinate. class IYL { lowY; index; nxt; constructor(lowY, index, nxt) { this.lowY = lowY; this.index = index; this.nxt = nxt; } static updateIYL(minY, i, ih) { // Remove siblings that are hidden by the new subtree. while (ih != undefined && minY >= ih.lowY) ih = ih.nxt; // Prepend the new subtree. return new IYL(minY, i, ih); } } class Tree { isExtra; w; h; x; y; c; prelim = 0; mod = 0; shift = 0; change = 0; tl; tr; // Left and right thread. el; er; // Extreme left and right nodes. msel; mser; // Sum of modifiers at the extreme nodes. constructor(w, h, y, c, isExtra = false) { this.w = w; this.h = h; this.y = y; this.c = c; this.isExtra = isExtra; } layout() { this.firstWalk(); this.secondWalk(0); } firstWalk() { if (this.c.length == 0) { this.setExtremes(); return; } this.c[0].firstWalk(); // Create siblings in contour minimal vertical coordinate and index list. let ih = IYL.updateIYL(this.c[0].el.bottom(), 0, undefined); for (let i = 1; i < this.c.length; i++) { this.c[i].firstWalk(); //Store lowest vertical coordinate while extreme nodes still point in current subtree. const minY = this.c[i].er.bottom(); this.separate(i, ih); ih = IYL.updateIYL(minY, i, ih); } this.positionRoot(); this.setExtremes(); } setExtremes() { if (this.c.length == 0) { this.el = this; this.er = this; this.msel = this.mser = 0; } else { this.el = this.c[0].el; this.msel = this.c[0].msel; this.er = this.c[this.c.length - 1].er; this.mser = this.c[this.c.length - 1].mser; } } separate(i, ih) { // Right contour node of left siblings and its sum of modfiers. let sr = this.c[i - 1]; let mssr = sr.mod; // Left contour node of current subtree and its sum of modfiers. let cl = this.c[i]; let mscl = cl.mod; // Modification to the algorithm: correctly lay out the first child if it has no children let first = true; while (sr !== undefined && cl !== undefined) { if (sr.bottom() > ih.lowY) ih = ih.nxt; // How far to the left of the right side of sr is the left side of cl? const dist = (mssr + sr.prelim + sr.w) - (mscl + cl.prelim); if (dist > 0 || (first && dist <= 0)) { mscl += dist; this.moveSubtree(i, ih.index, dist); } first = false; const sy = sr.bottom(), cy = cl.bottom(); // Advance highest node(s) and sum(s) of modifiers (Coordinate system increases downwards) if (sy <= cy) { sr = sr.nextRightContour(); if (sr !== undefined) mssr += sr.mod; } if (sy >= cy) { cl = cl.nextLeftContour(); if (cl !== undefined) mscl += cl.mod; } } // Set threads and update extreme nodes. // In the first case, the current subtree must be taller than the left siblings. if (sr == undefined && cl != undefined) this.setLeftThread(i, cl, mscl); // In this case, the left siblings must be taller than the current subtree. else if (sr != undefined && cl == undefined) this.setRightThread(i, sr, mssr); } moveSubtree(i, si, dist) { // Move subtree by changing mod. this.c[i].mod += dist; this.c[i].msel += dist; this.c[i].mser += dist; this.distributeExtra(i, si, dist); } nextLeftContour() { return this.c.length == 0 ? this.tl : this.c[0]; } nextRightContour() { return this.c.length == 0 ? this.tr : this.c[this.c.length - 1]; } bottom() { return this.y + this.h; } setLeftThread(i, cl, modsumcl) { const li = this.c[0].el; li.tl = cl; // Change mod so that the sum of modifier after following thread is correct. const diff = (modsumcl - cl.mod) - this.c[0].msel; li.mod += diff; // Change preliminary x coordinate so that the node does not move. li.prelim -= diff; // Update extreme node and its sum of modifiers. this.c[0].el = this.c[i].el; this.c[0].msel = this.c[i].msel; } // Symmetrical to setLeftThread. setRightThread(i, sr, modsumsr) { const ri = this.c[i].er; ri.tr = sr; const diff = (modsumsr - sr.mod) - this.c[i].mser; ri.mod += diff; ri.prelim -= diff; this.c[i].er = this.c[i - 1].er; this.c[i].mser = this.c[i - 1].mser; } positionRoot() { // Position root between children, taking into account their mod. this.prelim = (this.c[0].prelim + this.c[0].mod + this.c[this.c.length - 1].mod + this.c[this.c.length - 1].prelim + this.c[this.c.length - 1].w) / 2 - this.w / 2; } secondWalk(modsum) { modsum += this.mod; // Set absolute (non-relative) horizontal coordinate. this.x = this.prelim + modsum; this.addChildSpacing(); for (const child of this.c) child.secondWalk(modsum); } distributeExtra(i, si, dist) { // Are there intermediate children? if (si != i - 1) { const nr = i - si; this.c[si + 1].shift += dist / nr; this.c[i].shift -= dist / nr; this.c[i].change -= dist - dist / nr; } } // Process change and shift to add intermediate spacing to mod. addChildSpacing() { let d = 0, modsumdelta = 0; for (const child of this.c) { d += child.shift; modsumdelta += d + child.change; child.mod += modsumdelta; } } } export { Tree };