File size: 4,993 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
class Tree {
constructor(width, height, y, children) {
this.w = width
this.h = height
this.y = y
this.c = children
this.cs = children.length
this.x = 0
this.prelim = 0
this.mod = 0
this.shift = 0
this.change = 0
this.tl = null // Left thread
this.tr = null // Right thread
this.el = null // extreme left nodes
this.er = null // extreme right nodes
//sum of modifiers at the extreme nodes
this.msel = 0
this.mser = 0
}
}
function setExtremes(tree) {
if (tree.cs === 0) {
tree.el = tree
tree.er = tree
tree.msel = tree.mser = 0
} else {
tree.el = tree.c[0].el
tree.msel = tree.c[0].msel
tree.er = tree.c[tree.cs - 1].er
tree.mser = tree.c[tree.cs - 1].mser
}
}
function bottom(tree) {
return tree.y + tree.h
}
/* A linked list of the indexes of left siblings and their lowest vertical coordinate.
*/
class IYL {
constructor(lowY, index, next) {
this.lowY = lowY
this.index = index
this.next = next
}
}
function updateIYL(minY, i, ih) {
// Remove siblings that are hidden by the new subtree.
while (ih !== null && minY >= ih.lowY) {
// Prepend the new subtree
ih = ih.next
}
return new IYL(minY, i, ih)
}
function distributeExtra(tree, i, si, distance) {
// Are there intermediate children?
if (si !== i - 1) {
const nr = i - si
tree.c[si + 1].shift += distance / nr
tree.c[i].shift -= distance / nr
tree.c[i].change -= distance - distance / nr
}
}
function moveSubtree(tree, i, si, distance) {
// Move subtree by changing mod.
tree.c[i].mod += distance
tree.c[i].msel += distance
tree.c[i].mser += distance
distributeExtra(tree, i, si, distance)
}
function nextLeftContour(tree) {
return tree.cs === 0 ? tree.tl : tree.c[0]
}
function nextRightContour(tree) {
return tree.cs === 0 ? tree.tr : tree.c[tree.cs - 1]
}
function setLeftThread(tree, i, cl, modsumcl) {
const li = tree.c[0].el
li.tl = cl
// Change mod so that the sum of modifier after following thread is correct.
const diff = (modsumcl - cl.mod) - tree.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.
tree.c[0].el = tree.c[i].el
tree.c[0].msel = tree.c[i].msel
}
// Symmetrical to setLeftThread
function setRightThread(tree, i, sr, modsumsr) {
const ri = tree.c[i].er
ri.tr = sr
const diff = (modsumsr - sr.mod) - tree.c[i].mser
ri.mod += diff
ri.prelim -= diff
tree.c[i].er = tree.c[i - 1].er
tree.c[i].mser = tree.c[i - 1].mser
}
function seperate(tree, i, ih) {
// Right contour node of left siblings and its sum of modifiers.
let sr = tree.c[i - 1]
let mssr = sr.mod
// Left contour node of right siblings and its sum of modifiers.
let cl = tree.c[i]
let mscl = cl.mod
while (sr !== null && cl !== null) {
if (bottom(sr) > ih.lowY) {
ih = ih.next
}
// How far to the left of the right side of sr is the left side of cl.
const distance = mssr + sr.prelim + sr.w - (mscl + cl.prelim)
if (distance > 0) {
mscl += distance
moveSubtree(tree, i, ih.index, distance)
}
const sy = bottom(sr)
const cy = bottom(cl)
if (sy <= cy) {
sr = nextRightContour(sr)
if (sr !== null) {
mssr += sr.mod
}
}
if (sy >= cy) {
cl = nextLeftContour(cl)
if (cl !== null) {
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 === null && cl !== null) {
setLeftThread(tree, i, cl, mscl)
} else if (sr !== null && cl === null) {
setRightThread(tree, i, sr, mssr)
}
}
function positionRoot(tree) {
// Position root between children, taking into account their mod.
tree.prelim =
(tree.c[0].prelim +
tree.c[0].mod +
tree.c[tree.cs - 1].mod +
tree.c[tree.cs - 1].prelim +
tree.c[tree.cs - 1].w) /
2 -
tree.w / 2
}
function firstWalk(tree) {
if (tree.cs === 0) {
setExtremes(tree)
return
}
firstWalk(tree.c[0])
let ih = updateIYL(bottom(tree.c[0].el), 0, null)
for (let i = 1; i < tree.cs; i++) {
firstWalk(tree.c[i])
const minY = bottom(tree.c[i].er)
seperate(tree, i, ih)
ih = updateIYL(minY, i, ih)
}
positionRoot(tree)
setExtremes(tree)
}
function addChildSpacing(tree) {
let d = 0
let modsumdelta = 0
for (let i = 0; i < tree.cs; i++) {
d += tree.c[i].shift
modsumdelta += d + tree.c[i].change
tree.c[i].mod += modsumdelta
}
}
function secondWalk(tree, modsum) {
modsum += tree.mod
// Set absolute (no-relative) horizontal coordinates.
tree.x = tree.prelim + modsum
addChildSpacing(tree)
for (let i = 0; i < tree.cs; i++) {
secondWalk(tree.c[i], modsum)
}
}
function layout(tree) {
firstWalk(tree)
secondWalk(tree, 0)
}
export { Tree, layout }
|