mirror of
https://github.com/nunocoracao/blowfish.git
synced 2026-01-30 15:31:52 +00:00
config redirect
This commit is contained in:
+208
@@ -0,0 +1,208 @@
|
||||
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 }
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
import { layout, Tree } from './algorithm'
|
||||
|
||||
class BoundingBox {
|
||||
/**
|
||||
* @param {number} gap - the gap between sibling nodes
|
||||
* @param {number} bottomPadding - the height reserved for connection drawing
|
||||
*/
|
||||
constructor(gap, bottomPadding) {
|
||||
this.gap = gap
|
||||
this.bottomPadding = bottomPadding
|
||||
}
|
||||
|
||||
addBoundingBox(width, height) {
|
||||
return { width: width + this.gap, height: height + this.bottomPadding }
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the coordinate without the bounding box for a node
|
||||
*/
|
||||
removeBoundingBox(x, y) {
|
||||
return { x: x + this.gap / 2, y }
|
||||
}
|
||||
}
|
||||
|
||||
class Layout {
|
||||
constructor(boundingBox) {
|
||||
this.bb = boundingBox
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout treeData.
|
||||
* Return modified treeData and the bounding box encompassing all the nodes.
|
||||
*
|
||||
* See getSize() for more explanation.
|
||||
*/
|
||||
layout(treeData) {
|
||||
const tree = this.convert(treeData)
|
||||
layout(tree)
|
||||
const { boundingBox, result } = this.assignLayout(tree, treeData)
|
||||
|
||||
return { result, boundingBox }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Tree to layout, with bounding boxes added to each node.
|
||||
*/
|
||||
convert(treeData, y = 0) {
|
||||
if (treeData === null) return null
|
||||
|
||||
const { width, height } = this.bb.addBoundingBox(
|
||||
treeData.width,
|
||||
treeData.height
|
||||
)
|
||||
let children = []
|
||||
if (treeData.children && treeData.children.length) {
|
||||
for (let i = 0; i < treeData.children.length; i++) {
|
||||
children[i] = this.convert(treeData.children[i], y + height)
|
||||
}
|
||||
}
|
||||
|
||||
return new Tree(width, height, y, children)
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign layout tree x, y coordinates back to treeData,
|
||||
* with bounding boxes removed.
|
||||
*/
|
||||
assignCoordinates(tree, treeData) {
|
||||
const { x, y } = this.bb.removeBoundingBox(tree.x, tree.y)
|
||||
treeData.x = x
|
||||
treeData.y = y
|
||||
for (let i = 0; i < tree.c.length; i++) {
|
||||
this.assignCoordinates(tree.c[i], treeData.children[i])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bounding box that encompasses all the nodes.
|
||||
* The result has a structure of
|
||||
* { left: number, right: number, top: number, bottom: nubmer}.
|
||||
* This is not the same bounding box concept as the `BoundingBox` class
|
||||
* used to construct `Layout` class.
|
||||
*/
|
||||
getSize(treeData, box = null) {
|
||||
const { x, y, width, height } = treeData
|
||||
if (box === null) {
|
||||
box = { left: x, right: x + width, top: y, bottom: y + height }
|
||||
}
|
||||
box.left = Math.min(box.left, x)
|
||||
box.right = Math.max(box.right, x + width)
|
||||
box.top = Math.min(box.top, y)
|
||||
box.bottom = Math.max(box.bottom, y + height)
|
||||
|
||||
if (treeData.children) {
|
||||
for (const child of treeData.children) {
|
||||
this.getSize(child, box)
|
||||
}
|
||||
}
|
||||
|
||||
return box
|
||||
}
|
||||
|
||||
/**
|
||||
* This function does assignCoordinates and getSize in one pass.
|
||||
*/
|
||||
assignLayout(tree, treeData, box = null) {
|
||||
const { x, y } = this.bb.removeBoundingBox(tree.x, tree.y)
|
||||
treeData.x = x
|
||||
treeData.y = y
|
||||
|
||||
const { width, height } = treeData
|
||||
if (box === null) {
|
||||
box = { left: x, right: x + width, top: y, bottom: y + height }
|
||||
}
|
||||
box.left = Math.min(box.left, x)
|
||||
box.right = Math.max(box.right, x + width)
|
||||
box.top = Math.min(box.top, y)
|
||||
box.bottom = Math.max(box.bottom, y + height)
|
||||
|
||||
for (let i = 0; i < tree.c.length; i++) {
|
||||
this.assignLayout(tree.c[i], treeData.children[i], box)
|
||||
}
|
||||
|
||||
return { result: treeData, boundingBox: box }
|
||||
}
|
||||
}
|
||||
|
||||
export { Layout, BoundingBox }
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { layout, Tree } from './algorithm'
|
||||
import { BoundingBox, Layout } from './helpers'
|
||||
|
||||
export { layout, Tree, BoundingBox, Layout }
|
||||
Reference in New Issue
Block a user