github.com/jbendotnet/noms@v0.0.0-20190904222105-c43e4293ea92/cmd/noms/splore/src/buchheim.js (about) 1 // Copyright 2017 Attic Labs, Inc. All rights reserved. 2 // Licensed under the Apache License, version 2.0: 3 // http://www.apache.org/licenses/LICENSE-2.0 4 5 // @flow 6 7 import {notNull} from './assert.js'; 8 import type {NodeGraph, SploreNode} from './types.js'; 9 10 // JavaScript implementation of Christoph Buchheim, Michael Jünger, Sebastian Leipert's tree layout 11 // algorithm. See: http://dl.acm.org/citation.cfm?id=729576. 12 // 13 // Thanks also to Bill Mill for the explanation and Python sample code: 14 // http://billmill.org/pymag-trees/. 15 16 // TreeNode represents one node of the tree visualization. 17 18 export class TreeNode { 19 x: number; 20 y: number; 21 id: string; 22 node: SploreNode; 23 isOpen: boolean; 24 children: TreeNode[]; 25 parent: ?TreeNode; 26 thread: ?TreeNode; 27 offset: number; 28 ancestor: TreeNode; 29 change: number; 30 shift: number; 31 number: number; 32 mod: number; 33 34 constructor( 35 graph: NodeGraph, 36 id: string, 37 parent: ?TreeNode, 38 depth: number, 39 number: number, 40 seen: {[key: string]: boolean}, 41 ) { 42 seen[id] = true; 43 this.x = -1; 44 this.y = depth; 45 this.id = id; 46 this.node = graph.nodes[id]; 47 this.isOpen = !!graph.open[id]; 48 this.children = (this.isOpen ? graph.links[id] : []) 49 .concat(graph.keyLinks[id]) 50 .filter(cid => !(cid in seen)) 51 .map((cid, i) => new TreeNode(graph, cid, this, depth + 1, i + 1, seen)); 52 this.parent = parent; 53 this.thread = null; 54 this.offset = 0; 55 this.ancestor = this; 56 this.change = 0; 57 this.shift = 0; 58 this.number = number; 59 this.mod = 0; 60 } 61 62 left(): ?TreeNode { 63 if (this.children.length > 0) { 64 return this.children[0]; 65 } 66 return this.thread; 67 } 68 69 right(): ?TreeNode { 70 if (this.children.length > 0) { 71 return this.children[this.children.length - 1]; 72 } 73 return this.thread; 74 } 75 76 leftBrother(): ?TreeNode { 77 let n = null; 78 if (this.parent) { 79 for (let i = 0; i < this.parent.children.length; i++) { 80 const node = this.parent.children[i]; 81 if (node === this) { 82 return n; 83 } else { 84 n = node; 85 } 86 } 87 } 88 return n; 89 } 90 91 getLeftMostSibling(): ?TreeNode { 92 if (this.parent && this !== this.parent.children[0]) { 93 return this.parent.children[0]; 94 } else { 95 return null; 96 } 97 } 98 } 99 100 export function layout(tree: TreeNode): void { 101 firstWalk(tree, 1); 102 secondWalk(tree, 0, 0); 103 } 104 105 function firstWalk(v: TreeNode, distance: number): void { 106 if (v.children.length === 0) { 107 if (v.getLeftMostSibling()) { 108 v.x = notNull(v.leftBrother()).x + distance; 109 } else { 110 v.x = 0; 111 } 112 } else { 113 let defaultAncestor = v.children[0]; 114 for (let i = 0; i < v.children.length; i++) { 115 const w = v.children[i]; 116 firstWalk(w, distance); 117 defaultAncestor = apportion(w, defaultAncestor, distance); 118 } 119 executeShifts(v); 120 121 const midpoint = (v.children[0].x + v.children[v.children.length - 1].x) / 2; 122 123 const w = v.leftBrother(); 124 if (w) { 125 v.x = w.x + distance; 126 v.mod = v.x - midpoint; 127 } else { 128 v.x = midpoint; 129 } 130 } 131 } 132 133 function apportion(v: TreeNode, defaultAncestor: TreeNode, distance: number): TreeNode { 134 const w = v.leftBrother(); 135 if (w !== null) { 136 let vir = v; 137 let vor = v; 138 let vil = notNull(w); 139 let vol = notNull(v.getLeftMostSibling()); 140 let sir = v.mod; 141 let sor = v.mod; 142 let sil = vil.mod; 143 let sol = vol.mod; 144 while (vil.right() && vir.left()) { 145 vil = notNull(vil.right()); 146 vir = notNull(vir.left()); 147 vol = notNull(vol.left()); 148 vor = notNull(vor.right()); 149 vor.ancestor = v; 150 const shift = vil.x + sil - (vir.x + sir) + distance; 151 if (shift > 0) { 152 const a = ancestor(vil, v, defaultAncestor); 153 moveSubtree(a, v, shift); 154 sir = sir + shift; 155 sor = sor + shift; 156 } 157 sil += vil.mod; 158 sir += vir.mod; 159 sol += vol.mod; 160 sor += vor.mod; 161 } 162 if (vil.right() && !vor.right()) { 163 vor.thread = vil.right(); 164 vor.mod += sil - sor; 165 } else { 166 if (vir.left() && !vol.left()) { 167 vol.thread = vir.left(); 168 vol.mod += sir - sol; 169 } 170 defaultAncestor = v; 171 } 172 } 173 return defaultAncestor; 174 } 175 176 function moveSubtree(wl: TreeNode, wr: TreeNode, shift: number): void { 177 const subtrees = wr.number - wl.number; 178 wr.change -= shift / subtrees; 179 wr.shift += shift; 180 wl.change += shift / subtrees; 181 wr.x += shift; 182 wr.mod += shift; 183 } 184 185 function executeShifts(v: TreeNode): void { 186 let shift = 0; 187 let change = 0; 188 for (let i = v.children.length - 1; i >= 0; i--) { 189 const w = v.children[i]; 190 w.x += shift; 191 w.mod += shift; 192 change += w.change; 193 shift += w.shift + change; 194 } 195 } 196 197 function ancestor(vil: TreeNode, v: TreeNode, defaultAncestor: TreeNode): TreeNode { 198 if (v.parent && v.parent.children.indexOf(vil.ancestor) > -1) { 199 return vil.ancestor; 200 } else { 201 return defaultAncestor; 202 } 203 } 204 205 function secondWalk(v: TreeNode, m: number, depth: number): void { 206 v.x += m; 207 v.y = depth; 208 209 for (let i = 0; i < v.children.length; i++) { 210 secondWalk(v.children[i], m + v.mod, depth + 1); 211 } 212 }