diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/Tile.vue | 4 | ||||
| -rw-r--r-- | src/components/TileTree.vue | 2 | ||||
| -rw-r--r-- | src/layout.js | 174 |
3 files changed, 114 insertions, 66 deletions
diff --git a/src/components/Tile.vue b/src/components/Tile.vue index 753922d..1693349 100644 --- a/src/components/Tile.vue +++ b/src/components/Tile.vue @@ -20,9 +20,7 @@ export default { layout(){ if (this.tree.children.length == 0) return {}; - let hOffset = (this.hideHeader ? 0 : this.layoutSys.HEADER_SZ); - let x = 0, y = hOffset, w = this.width, h = this.height - hOffset; - return this.layoutSys.genLayout(this.tree.children, 0, hOffset, this.width, this.height - hOffset); + return this.layoutSys.genLayout(this.tree.children, 0, 0, this.width, this.height, this.hideHeader); } }, methods: { diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue index 3f3f0f5..b1fb3fd 100644 --- a/src/components/TileTree.vue +++ b/src/components/TileTree.vue @@ -41,8 +41,8 @@ export default { }, onInnerHeaderClicked(nodeList){ //nodeList will hold an array of tree-objects, from the clicked-on-tile's tree-object upward - this.layoutSys.updateLayoutInfoOnCollapse(nodeList); nodeList[0].children = []; + this.layoutSys.updateLayoutInfoOnCollapse(nodeList); }, }, created(){ diff --git a/src/layout.js b/src/layout.js index 9cfd210..302906f 100644 --- a/src/layout.js +++ b/src/layout.js @@ -6,10 +6,11 @@ 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){ + genLayout(nodes, x0, y0, w, h, hideHeader){ //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 hOffset = (hideHeader ? 0 : this.HEADER_SZ); + let numTiles = nodes.length, ar = w/(h - hOffset); let numCols, numRows, bestFrac = 0; for (let nc = 1; nc <= numTiles; nc++){ let nr = Math.ceil(numTiles/nc); @@ -24,12 +25,12 @@ const staticSqrLayout = { //determines layout for squares in a specified rectang //compute other parameters let tileSz = Math.min( ((w - this.TILE_SPACING) / numCols) - this.TILE_SPACING, - ((h - this.TILE_SPACING) / numRows) - this.TILE_SPACING); + ((h - this.TILE_SPACING - hOffset) / 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, + y: y0 + Math.floor(idx / numCols)*(tileSz + this.TILE_SPACING) + this.TILE_SPACING + hOffset, w: tileSz, h: tileSz }]) @@ -48,101 +49,150 @@ const staticSqrLayout = { //determines layout for squares in a specified rectang 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) + genLayout(nodes, x0, y0, w, h, hideHeader){ + if (nodes.every(e => e.children.length == 0)){ + return staticSqrLayout.genLayout(nodes, x0, y0, w, h, hideHeader); + } + //if a node has children, find 'best' number-of-columns to use + let hOffset = (hideHeader ? 0 : this.HEADER_SZ); + let numChildren = nodes.length; + let numCols, bestScore = Number.NEGATIVE_INFINITY, numRows, rowProps, colProps; + for (let nc = 1; nc <= numChildren; nc++){ + let nr = Math.ceil(numChildren/nc); + //create grid representing each node'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]; + for (let i = 0; i < numChildren; i++){ + grid[Math.floor(i / nc)][i % nc] = nodes[i].tileCount; } - //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 totals across each row/column divided by tileCount total + let totalTileCount = nodes.map(e => e.tileCount).reduce((x,y) => x+y); + let rProps = grid.map(row => row.reduce((x, y) => x+y) / totalTileCount); + let cProps = [...Array(nc).keys()].map(c => + [...Array(nr).keys()].map(r => grid[r][c]).reduce((x,y) => x+y) / totalTileCount); //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])); - } - } + for (let i = 0; i < numChildren; i++){ //get occupied-fraction //account for tile-spacing? + let cellW = (w - this.TILE_SPACING) * cProps[i % nc]; + let cellH = (h - this.TILE_SPACING - hOffset) * rProps[Math.floor(i / nc)]; + let ar = cellW / cellH; + let ar2 = nodes[i].arFromArea(cellW, cellH); + let frac = ar > ar2 ? ar2/ar : ar/ar2; + score += frac * (cellW * cellH); } - //also score for w/h occupation? + ////alternative score-metric + //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] - rProps[r]*cProps[c]); + // } + // } + //} if (score > bestScore){ bestScore = score; numCols = nc; - rowProportions = rowProp; - colProportions = colProp; + numRows = nr; + rowProps = rProps; + colProps = cProps; } } //determine layout let rowNetProps = [0]; - for (let i = 0; i < rowProportions.length-1; i++){ - rowNetProps.push(rowNetProps[i] + rowProportions[i]); + for (let i = 0; i < rowProps.length-1; i++){ + rowNetProps.push(rowNetProps[i] + rowProps[i]); } let colNetProps = [0]; - for (let i = 0; i < colProportions.length-1; i++){ - colNetProps.push(colNetProps[i] + colProportions[i]); + for (let i = 0; i < colProps.length-1; i++){ + colNetProps.push(colNetProps[i] + colProps[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; + return Object.fromEntries( + nodes.map((el, idx) => { + let cellW = colProps[idx % numCols]*(w - this.TILE_SPACING); + let cellH = rowProps[Math.floor(idx / numCols)]*(h - hOffset - this.TILE_SPACING); + let cellAR = cellW / cellH; + return [el.tolNode.name, { + x: x0 + colNetProps[idx % numCols]*(w - this.TILE_SPACING) + this.TILE_SPACING, + y: y0 + rowNetProps[Math.floor(idx / numCols)]*(h - hOffset - this.TILE_SPACING) + + this.TILE_SPACING + hOffset, + w: (el.children.length == 0 ? (cellAR > 1 ? cellW * 1/cellAR : cellW) : cellW) - this.TILE_SPACING, + h: (el.children.length == 0 ? (cellAR > 1 ? cellH : cellH * cellAR) : cellH) - this.TILE_SPACING + }]; + }) + ); }, - initLayoutInfo(tree){ //initialise node-tree with tile-counts - if (tree.children.length == 0){ - tree.tileCount = 1; - } else { + initLayoutInfo(tree){ + if (tree.children.length > 0){ tree.children.forEach(e => this.initLayoutInfo(e)); - tree.tileCount = tree.children.map(e => e.tileCount).reduce((x,y) => x+y); + } + this.updateLayoutInfo(tree); + }, + updateLayoutInfoOnExpand(nodeList){ //given list of tree-nodes from expanded_child-to-parent, update layout-info + nodeList[0].children.forEach(this.updateLayoutInfo); + for (let node of nodeList){ + this.updateLayoutInfo(node); } }, - 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 layout-info + for (let node of nodeList){ + this.updateLayoutInfo(node); } }, - 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; + updateLayoutInfo(tree){ + if (tree.children.length == 0){ + tree.tileCount = 1; + tree.arFromArea = (w, h) => 1; + } else { + let hOffset = (tree.hideHeader ? 0 : this.HEADER_SZ); + tree.tileCount = tree.children.map(e => e.tileCount).reduce((x,y) => x+y); + //determine tree.arFromArea + if (tree.children.every(e => e.children.length == 0)){ + tree.arFromArea = (w, h) => { //cache result? + let numChildren = tree.children.length, ar = w/(h - hOffset); + let bestAR, bestFrac = 0; + for (let nc = 1; nc <= numChildren; nc++){ + let nr = Math.ceil(numChildren/nc); + let ar2 = nc/nr; + let frac = ar > ar2 ? ar2/ar : ar/ar2; + if (frac > bestFrac){ + bestFrac = frac; + bestAR = ar > ar2 ? (ar2/ar * w)/(h + hOffset) : w/(ar/ar2 * h + hOffset) + } + } + return bestAR; + } + } else { + tree.arFromArea = (w, h) => w/h; + } } } }; const sweepToSideLayout = { TILE_SPACING: DEFAULT_TILE_SPACING, HEADER_SZ: DEFAULT_HEADER_SZ, - genLayout(nodes, x0, y0, w, h){ + genLayout(nodes, x0, y0, w, h, hideHeader){ //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); + return staticSqrLayout.genLayout(nodes, x0, y0, w, h, hideHeader); } 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); + let tentativeLayout = staticSqrLayout.genLayout(leaves, x0, y0, w*ratio, h, hideHeader); + //shrink swept-area space to right edge + let rightmostPoints = leaves.map(e => e.tolNode.name).map(name => { + let coords = tentativeLayout[name]; + return coords.x + coords.w + this.TILE_SPACING; + }); + let rightMostPoint = Math.max(...rightmostPoints); + retVal = staticSqrLayout.genLayout(leaves, x0, y0, rightMostPoint-x0, h, hideHeader); + //update coords + w -= (rightMostPoint - x0) - this.TILE_SPACING; + x0 = rightMostPoint - this.TILE_SPACING; } - //return {...retVal, ...staticSqrLayout.genLayout(nonLeaves, x0, y0, w, h)}; - return {...retVal, ...staticRectLayout.genLayout(nonLeaves, x0, y0, w, h)}; + //return {...retVal, ...staticSqrLayout.genLayout(nonLeaves, x0, y0, w, h, hideHeader)}; + return {...retVal, ...staticRectLayout.genLayout(nonLeaves, x0, y0, w, h, hideHeader)}; } }, initLayoutInfo(tree){ |
