diff options
Diffstat (limited to 'src/layout.js')
| -rw-r--r-- | src/layout.js | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/layout.js b/src/layout.js new file mode 100644 index 0000000..9cfd210 --- /dev/null +++ b/src/layout.js @@ -0,0 +1,171 @@ +export {defaultLayout, initTree}; + +const DEFAULT_TILE_SPACING = 5; +const DEFAULT_HEADER_SZ = 20; + +const staticSqrLayout = { //determines layout for squares in a specified rectangle, with spacing + TILE_SPACING: DEFAULT_TILE_SPACING, + HEADER_SZ: DEFAULT_HEADER_SZ, + genLayout(nodes, x0, y0, w, h){ + //get number-of-columns with highest occupied-fraction of rectangles with aspect-ratio w/h + //account for tile-spacing?, account for parent-box-border?, + let numTiles = nodes.length, ar = w/h; + let numCols, numRows, bestFrac = 0; + for (let nc = 1; nc <= numTiles; nc++){ + let nr = Math.ceil(numTiles/nc); + let ar2 = nc/nr; + let frac = ar > ar2 ? ar2/ar : ar/ar2; + if (frac > bestFrac){ + bestFrac = frac; + numCols = nc; + numRows = nr; + } + } + //compute other parameters + let tileSz = Math.min( + ((w - this.TILE_SPACING) / numCols) - this.TILE_SPACING, + ((h - this.TILE_SPACING) / numRows) - this.TILE_SPACING); + //determine layout + return Object.fromEntries( + nodes.map((el, idx) => [el.tolNode.name, { + x: x0 + (idx % numCols)*(tileSz + this.TILE_SPACING) + this.TILE_SPACING, + y: y0 + Math.floor(idx / numCols)*(tileSz + this.TILE_SPACING) + this.TILE_SPACING, + w: tileSz, + h: tileSz + }]) + ); + }, + initLayoutInfo(tree){ + return; + }, + updateLayoutInfoOnExpand(nodeList){ + return; + }, + updateLayoutInfoOnCollapse(nodeList){ + return; + } +}; +const staticRectLayout = { + TILE_SPACING: DEFAULT_TILE_SPACING, + HEADER_SZ: DEFAULT_HEADER_SZ, + genLayout(nodes, x0, y0, w, h){ + //get number-of-columns with highest tileCount-proportions-alignment + let numTiles = nodes.length; + let tileCounts = nodes.map(e => e.tileCount); + let tileCountTotal = tileCounts.reduce((x, y) => x+y); + let numCols, bestScore = Number.NEGATIVE_INFINITY, rowProportions, colProportions; + for (let nc = 1; nc <= numTiles; nc++){ + let nr = Math.ceil(numTiles/nc); + //create grid representing each tile's tileCount (0 for no tile) + let grid = Array(nr).fill().map(e => Array(nc).fill(0)); + for (let i = 0; i < tileCounts.length; i++){ + grid[Math.floor(i / nc)][i % nc] = tileCounts[i]; + } + //get totals across each row/column divided by tileCountTotal + let rowProp = grid.map(e => e.reduce((x, y) => x+y) / tileCountTotal); + let colProp = [...Array(nc).keys()].map(c => + [...Array(nr).keys()].map(r => grid[r][c]).reduce((x,y) => x+y) / tileCountTotal); + //get score + let score = 0; + for (let r = 0; r < nr; r++){ + for (let c = 0; c < nc; c++){ + if (grid[r][c] > 0){ + score -= Math.abs(grid[r][c] - (rowProp[r] * colProp[c])); + } + } + } + //also score for w/h occupation? + if (score > bestScore){ + bestScore = score; + numCols = nc; + rowProportions = rowProp; + colProportions = colProp; + } + } + //determine layout + let rowNetProps = [0]; + for (let i = 0; i < rowProportions.length-1; i++){ + rowNetProps.push(rowNetProps[i] + rowProportions[i]); + } + let colNetProps = [0]; + for (let i = 0; i < colProportions.length-1; i++){ + colNetProps.push(colNetProps[i] + colProportions[i]); + } + let retVal = Object.fromEntries( + nodes.map((el, idx) => [el.tolNode.name, { + x: x0 + colNetProps[idx % numCols]*(w - this.TILE_SPACING) + this.TILE_SPACING, + y: y0 + rowNetProps[Math.floor(idx / numCols)]*(h - this.TILE_SPACING) + this.TILE_SPACING, + w: colProportions[idx % numCols]*(w - this.TILE_SPACING) - this.TILE_SPACING, + h: rowProportions[Math.floor(idx / numCols)]*(h - this.TILE_SPACING) - this.TILE_SPACING + }])); + return retVal; + }, + initLayoutInfo(tree){ //initialise node-tree with tile-counts + if (tree.children.length == 0){ + tree.tileCount = 1; + } else { + tree.children.forEach(e => this.initLayoutInfo(e)); + tree.tileCount = tree.children.map(e => e.tileCount).reduce((x,y) => x+y); + } + }, + updateLayoutInfoOnExpand(nodeList){ //given list of tree-nodes from expanded_child-to-parent, update tile-counts + nodeList[0].children.forEach(e => {e.tileCount = 1}); + nodeList[0].tileCount = nodeList[0].children.length; + for (let i = 1; i < nodeList.length; i++){ + nodeList[i].tileCount += nodeList[0].tileCount - 1; + } + }, + updateLayoutInfoOnCollapse(nodeList){ //given list of tree-nodes from child_to_collapse-to-parent, update tile-counts + let tc = nodeList[0].tileCount; + nodeList[0].tileCount = 1; + for (let i = 1; i < nodeList.length; i++){ + nodeList[i].tileCount -= tc - 1; + } + } +}; +const sweepToSideLayout = { + TILE_SPACING: DEFAULT_TILE_SPACING, + HEADER_SZ: DEFAULT_HEADER_SZ, + genLayout(nodes, x0, y0, w, h){ + //separate leaf and non-leaf nodes + let leaves = [], nonLeaves = []; + nodes.forEach(e => (e.children.length == 0 ? leaves : nonLeaves).push(e)); + //determine layout + if (nonLeaves.length == 0){ //if all leaves, use squares-layout + return staticSqrLayout.genLayout(nodes, x0, y0, w, h); + } else { //if some non-leaves, use rect-layout + let retVal = {}; + if (leaves.length > 0){ + let ratio = leaves.length / (leaves.length + nonLeaves.map(e => e.tileCount).reduce((x,y) => x+y)); + retVal = staticSqrLayout.genLayout(leaves, x0, y0, w*ratio, h); + x0 += w*ratio - this.TILE_SPACING; + w -= (w*ratio - this.TILE_SPACING); + } + //return {...retVal, ...staticSqrLayout.genLayout(nonLeaves, x0, y0, w, h)}; + return {...retVal, ...staticRectLayout.genLayout(nonLeaves, x0, y0, w, h)}; + } + }, + initLayoutInfo(tree){ + staticRectLayout.initLayoutInfo(tree); + }, + updateLayoutInfoOnExpand(nodeList){ + staticRectLayout.updateLayoutInfoOnExpand(nodeList); + }, + updateLayoutInfoOnCollapse(nodeList){ + staticRectLayout.updateLayoutInfoOnCollapse(nodeList); + } +}; +let defaultLayout = sweepToSideLayout; + +function initTree(tol, lvl, layout = defaultLayout){ + let tree = {tolNode: tol, children: []}; + initTreeRec(tree, lvl); + layout.initLayoutInfo(tree) + return tree; +} +function initTreeRec(tree, lvl){ + if (lvl > 0){ + tree.children = tree.tolNode.children.map(e => initTreeRec({tolNode: e, children: []}, lvl-1)); + } + return tree; +} |
