mirror of
https://github.com/nunocoracao/blowfish.git
synced 2026-01-30 15:31:52 +00:00
config redirect
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
extends: "eslint:recommended",
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module'
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Michael Wong
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
# non-layered-tidy-tree-layout
|
||||
|
||||
Draw non-layered tidy trees in linear time.
|
||||
|
||||
> This a JavaScript port from the project [cwi-swat/non-layered-tidy-trees](https://github.com/cwi-swat/non-layered-tidy-trees), which is written in Java. The algorithm used in that project is from the paper by _A.J. van der Ploeg_, [Drawing Non-layered Tidy Trees in Linear Time](http://oai.cwi.nl/oai/asset/21856/21856B.pdf). There is another JavaScript port from that project [d3-flextree](https://github.com/Klortho/d3-flextree), which depends on _d3-hierarchy_. This project is dependency free.
|
||||
|
||||
## Getting started
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
npm install non-layered-tidy-tree-layout
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```
|
||||
yarn add non-layered-tidy-tree-layout
|
||||
```
|
||||
|
||||
There's also a built verison: `dist/non-layered-tidy-tree-layout.js` for use with browser `<script>` tag, or as a Javascript module.
|
||||
|
||||
### Usage
|
||||
|
||||
```js
|
||||
import { BoundingBox, Layout } from 'non-layered-tidy-tree-layout'
|
||||
|
||||
// BoundingBox(gap, bottomPadding)
|
||||
const bb = new BoundingBox(10, 20)
|
||||
const layout = new Layout(bb)
|
||||
const treeData = {
|
||||
id: 0,
|
||||
width: 40,
|
||||
height: 40,
|
||||
children: [
|
||||
{
|
||||
id: 1,
|
||||
width: 40,
|
||||
height: 40,
|
||||
children: [{ id: 6, width: 400, height: 40 }]
|
||||
},
|
||||
{ id: 2, width: 40, height: 40 },
|
||||
{ id: 3, width: 40, height: 40 },
|
||||
{ id: 4, width: 40, height: 40 },
|
||||
{ id: 5, width: 40, height: 80 }
|
||||
]
|
||||
}
|
||||
const { result, boundingBox } = layout.layout(treeData)
|
||||
|
||||
// result:
|
||||
// {
|
||||
// id: 0,
|
||||
// x: 300,
|
||||
// y: 0,
|
||||
// width: 40,
|
||||
// height: 40,
|
||||
// children: [
|
||||
// {
|
||||
// id: 1,
|
||||
// x: 185,
|
||||
// y: 60,
|
||||
// width: 40,
|
||||
// height: 40,
|
||||
// children: [
|
||||
// { id: 6, x: 5, y: 120, width: 400, height: 40 }
|
||||
// ]
|
||||
// },
|
||||
// { id: 2, x: 242.5, y: 60, width: 40, height: 40 },
|
||||
// { id: 3, x: 300, y: 60, width: 40, height: 40 },
|
||||
// { id: 4, x: 357.5, y: 60, width: 40, height: 40 },
|
||||
// { id: 5, x: 415, y: 60, width: 40, height: 80 }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// boundingBox:
|
||||
// {
|
||||
// left: 5,
|
||||
// right: 455,
|
||||
// top: 0,
|
||||
// bottom: 160
|
||||
// }
|
||||
```
|
||||
|
||||
The method `Layout.layout` modifies `treeData` inplace. It returns an object like `{ result: treeData, boundingBox: {left: num, right: num, top: num, bottom: num} }`. `result` is the same object `treeData` with calculated coordinates, `boundingBox` are the coordinates for the whole tree:
|
||||
|
||||

|
||||
|
||||
The red dashed lines are the bounding boxes for each node. `Layout.layout()` produces coordinates to draw nodes, which are the grey boxes with black border.
|
||||
|
||||
The library also provides a class `Tree` and a method `layout`.
|
||||
|
||||
```js
|
||||
/**
|
||||
* Constructor for Tree.
|
||||
* @param {number} width - width of bounding box
|
||||
* @param {number} height - height of bounding box
|
||||
* @param {number} y - veritcal coordinate of bounding box
|
||||
* @param {array} children - a list of Tree instances
|
||||
*/
|
||||
new Tree(width, height, y, children)
|
||||
|
||||
/**
|
||||
* Calculate x, y coordindates and assign them to tree.
|
||||
* @param {Object} tree - a Tree object
|
||||
*/
|
||||
layout(tree)
|
||||
```
|
||||
|
||||
In case your data structure are not the same as provided by the example above, you can refer to `src/helpers.js` to implement a `Layout` class that converts your data to a `Tree`, then call `layout` to calculate the coordinates for drawing.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
## Changelog
|
||||
|
||||
### [2.0.1]
|
||||
- Fixed bounding box calculation in `Layout.getSize` and `Layout.assignLayout` and `Layout.layout`
|
||||
### [2.0.0]
|
||||
- Added `Layout.layout`
|
||||
- Removed `Layout.layoutTreeData`
|
||||
### [1.0.0]
|
||||
- Added `Layout`, `BoundingBox`, `layout`, `Tree`
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
|
||||
}
|
||||
+1
File diff suppressed because one or more lines are too long
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "non-layered-tidy-tree-layout",
|
||||
"version": "2.0.2",
|
||||
"description": "Draw non-layered tidy trees in linear time",
|
||||
"main": "dist/non-layered-tidy-tree-layout.js",
|
||||
"module": "src/index.js",
|
||||
"repository": "https://github.com/stetrevor/non-layered-tidy-tree-layout.git",
|
||||
"author": "Michael Wong",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.6.3",
|
||||
"@babel/preset-env": "^7.6.3",
|
||||
"@webpack-cli/init": "^0.2.2",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-jest": "^24.9.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"eslint": "^6.5.1",
|
||||
"eslint-config-recommended": "^4.0.0",
|
||||
"eslint-loader": "^3.0.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "^24.9.0",
|
||||
"webpack": "^4.41.0",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"webpack-dev-server": "^3.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"test": "jest test"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
+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 }
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import { BoundingBox, Layout } from '../src/helpers'
|
||||
|
||||
test('bounding box', () => {
|
||||
const bb = new BoundingBox(10, 20)
|
||||
expect(bb.addBoundingBox(100, 200)).toEqual(
|
||||
expect.objectContaining({
|
||||
width: 110,
|
||||
height: 220
|
||||
})
|
||||
)
|
||||
expect(bb.removeBoundingBox(10, 120)).toEqual(
|
||||
expect.objectContaining({
|
||||
x: 15,
|
||||
y: 120
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('Layout class', () => {
|
||||
const data = {
|
||||
width: 10,
|
||||
height: 10,
|
||||
children: [
|
||||
{
|
||||
width: 10,
|
||||
height: 10,
|
||||
children: [{ width: 150, height: 10, children: [] }]
|
||||
},
|
||||
{ width: 10, height: 10, children: [] },
|
||||
{ width: 10, height: 10, children: [] },
|
||||
{ width: 10, height: 10, children: [] },
|
||||
{ width: 10, height: 20, children: [] }
|
||||
]
|
||||
}
|
||||
const bb = new BoundingBox(10, 10)
|
||||
const layout = new Layout(bb)
|
||||
const { boundingBox } = layout.layout(data)
|
||||
|
||||
expect(data).toEqual(expect.objectContaining({ x: 120, y: 0 }))
|
||||
expect(data.children[0]).toEqual(expect.objectContaining({ x: 75, y: 20 }))
|
||||
expect(data.children[1]).toEqual(expect.objectContaining({ x: 97.5, y: 20 }))
|
||||
expect(data.children[2]).toEqual(expect.objectContaining({ x: 120, y: 20 }))
|
||||
expect(data.children[3]).toEqual(expect.objectContaining({ x: 142.5, y: 20 }))
|
||||
expect(data.children[4]).toEqual(expect.objectContaining({ x: 165, y: 20 }))
|
||||
expect(data.children[0].children[0]).toEqual(
|
||||
expect.objectContaining({ x: 5, y: 40 })
|
||||
)
|
||||
|
||||
expect(boundingBox).toEqual(
|
||||
expect.objectContaining({ left: 5, right: 175, top: 0, bottom: 50 })
|
||||
)
|
||||
})
|
||||
|
||||
test('Big root, small child', () => {
|
||||
const t = {
|
||||
id: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
children: [{ id: 1, width: 50, height: 50 }]
|
||||
}
|
||||
const l = new Layout(new BoundingBox(0, 0))
|
||||
const { result, boundingBox } = l.layout(t)
|
||||
expect(result).toEqual(expect.objectContaining({ x: -25, y: 0 }))
|
||||
expect(result.children[0]).toEqual(expect.objectContaining({ x: 0, y: 50 }))
|
||||
expect(boundingBox).toEqual(
|
||||
expect.objectContaining({ left: -25, right: 75, top: 0, bottom: 100 })
|
||||
)
|
||||
})
|
||||
|
||||
describe('Layout.getSize', () => {
|
||||
test('big root, small child', () => {
|
||||
const t = {
|
||||
id: 0,
|
||||
width: 100,
|
||||
height: 50,
|
||||
children: [{ id: 1, width: 50, height: 50 }]
|
||||
}
|
||||
const l = new Layout(new BoundingBox(0, 0))
|
||||
l.layout(t)
|
||||
const bb = l.getSize(t)
|
||||
expect(bb).toEqual(
|
||||
expect.objectContaining({ left: -25, right: 75, top: 0, bottom: 100 })
|
||||
)
|
||||
})
|
||||
|
||||
test('small root, big child', () => {
|
||||
const t = {
|
||||
id: 0,
|
||||
width: 50,
|
||||
height: 50,
|
||||
children: [{ id: 1, width: 100, height: 50 }]
|
||||
}
|
||||
const l = new Layout(new BoundingBox(20, 20))
|
||||
l.layout(t)
|
||||
const bb = l.getSize(t)
|
||||
expect(bb).toEqual(
|
||||
expect.objectContaining({ left: 10, right: 110, top: 0, bottom: 120 })
|
||||
)
|
||||
})
|
||||
})
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { Tree, layout } from '../src/algorithm'
|
||||
|
||||
export default {
|
||||
convert(treeNode) {
|
||||
if (treeNode === null) return null
|
||||
|
||||
let children = []
|
||||
for (let i = 0; i < treeNode.children.length; i++) {
|
||||
children[i] = this.convert(treeNode.children[i])
|
||||
}
|
||||
|
||||
return new Tree(treeNode.width, treeNode.height, treeNode.y, children)
|
||||
},
|
||||
|
||||
convertBack(converted, root) {
|
||||
root.x = converted.x
|
||||
for (let i = 0; i < converted.c.length; i++) {
|
||||
this.convertBack(converted.c[i], root.children[i])
|
||||
}
|
||||
},
|
||||
|
||||
runOnConverted(root) {
|
||||
layout(root)
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Test module in >script< tag</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="../dist/non-layered-tidy-tree-layout.js"></script>
|
||||
<script>
|
||||
console.log(nonLayeredTidyTreeLayout);
|
||||
console.log(nonLayeredTidyTreeLayout.Tree);
|
||||
console.log(nonLayeredTidyTreeLayout.layout);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
test('nonLayeredTidyTreeLayout exists', () => {
|
||||
const { layout, Tree } = require('../dist/non-layered-tidy-tree-layout')
|
||||
expect(layout).toBeTruthy()
|
||||
expect(Tree).toBeTruthy()
|
||||
})
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { layout, Tree } from '../src/algorithm'
|
||||
import TreeNode from './tree-node'
|
||||
import Marshall from './marshall'
|
||||
|
||||
test('layout tree with one node', () => {
|
||||
const tree = new Tree(10, 5, 0, [])
|
||||
layout(tree)
|
||||
|
||||
expect(tree).toEqual(expect.objectContaining({ x: 0, y: 0 }))
|
||||
})
|
||||
|
||||
test('layout tree with 2 nodes', () => {
|
||||
const child = new TreeNode(20, 10)
|
||||
const root = new TreeNode(10, 4)
|
||||
root.addChild(child)
|
||||
const tree = Marshall.convert(root)
|
||||
layout(tree)
|
||||
Marshall.convertBack(tree, root)
|
||||
expect(root).toEqual(expect.objectContaining({ x: 5, y: 0 }))
|
||||
expect(child).toEqual(expect.objectContaining({ x: 0, y: 4 }))
|
||||
})
|
||||
|
||||
test('layout tree with 3 nodes', () => {
|
||||
const c1 = new TreeNode(10, 30)
|
||||
const c2 = new TreeNode(20, 10)
|
||||
const root = new TreeNode(40, 10)
|
||||
root.addChild(c1)
|
||||
root.addChild(c2)
|
||||
const tree = Marshall.convert(root)
|
||||
layout(tree)
|
||||
|
||||
Marshall.convertBack(tree, root)
|
||||
expect(root).toEqual(expect.objectContaining({ x: -5, y: 0 }))
|
||||
expect(c1).toEqual(expect.objectContaining({ x: 0, y: 10 }))
|
||||
expect(c2).toEqual(expect.objectContaining({ x: 10, y: 10 }))
|
||||
})
|
||||
|
||||
test('reflection of the tree is the mirror image of the original tree', () => {
|
||||
const n1 = new TreeNode(10, 10)
|
||||
const n2 = new TreeNode(10, 10)
|
||||
const n3 = new TreeNode(10, 10)
|
||||
const n4 = new TreeNode(10, 10)
|
||||
const n5 = new TreeNode(10, 10)
|
||||
const n6 = new TreeNode(10, 20)
|
||||
const n7 = new TreeNode(150, 10)
|
||||
n1.addChild(n2)
|
||||
n1.addChild(n3)
|
||||
n1.addChild(n4)
|
||||
n1.addChild(n5)
|
||||
n1.addChild(n6)
|
||||
n2.addChild(n7)
|
||||
const tree = Marshall.convert(n1)
|
||||
layout(tree)
|
||||
|
||||
Marshall.convertBack(tree, n1)
|
||||
expect(n1).toEqual(expect.objectContaining({ x: 110, y: 0 }))
|
||||
expect(n2).toEqual(expect.objectContaining({ x: 70, y: 10 }))
|
||||
expect(n3).toEqual(expect.objectContaining({ x: 90, y: 10 }))
|
||||
expect(n4).toEqual(expect.objectContaining({ x: 110, y: 10 }))
|
||||
expect(n5).toEqual(expect.objectContaining({ x: 130, y: 10 }))
|
||||
expect(n6).toEqual(expect.objectContaining({ x: 150, y: 10 }))
|
||||
expect(n7).toEqual(expect.objectContaining({ x: 0, y: 20 }))
|
||||
})
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
export default class TreeNode {
|
||||
constructor(width, height) {
|
||||
this.width = width
|
||||
this.height = height
|
||||
this.x = 0
|
||||
this.y = 0
|
||||
this.children = []
|
||||
}
|
||||
|
||||
addChild(child) {
|
||||
child.y = this.y + this.height
|
||||
this.children.push(child)
|
||||
}
|
||||
|
||||
randExpand(tree) {
|
||||
tree.y += this.height
|
||||
const i = Math.floor(Math.random() * (this.children.length + 1))
|
||||
if (i === this.children.length) {
|
||||
this.children.push(tree)
|
||||
} else {
|
||||
this.children[i].randExpand(tree)
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'non-layered-tidy-tree-layout.js',
|
||||
library: 'nonLayeredTidyTreeLayout',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user