diff options
Diffstat (limited to 'src/components/TileTree.vue')
| -rw-r--r-- | src/components/TileTree.vue | 172 |
1 files changed, 21 insertions, 151 deletions
diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue index 2e5f101..d0c674e 100644 --- a/src/components/TileTree.vue +++ b/src/components/TileTree.vue @@ -1,18 +1,25 @@ <script> +import {defaultLayout} from './layout.js'; + let lastChangedTile = null; //used to increase TileTree z-index during a transition +function updateZForTransition(tileTree){ + if (lastChangedTile !== null){ + lastChangedTile.zIdx = 0; + } + tileTree.zIdx = 1; + lastChangedTile = tileTree; +} export default { name: 'tile-tree', data(){ return { - TILE_SPACING: 5, - HEADER_SZ: 20, - tree: this.expandTree(this.treeIn, this.isRoot ? 1 : 0), zIdx: 0, + layoutSys: defaultLayout, } }, props: { - treeIn: Object, + tree: Object, x: Number, y: Number, width: Number, @@ -23,144 +30,23 @@ export default { layout(){ if (this.tree.children.length == 0) return {}; - let hOffset = (this.isRoot ? 0 : this.HEADER_SZ); + let hOffset = (this.isRoot ? 0 : this.layoutSys.HEADER_SZ); let x = 0, y = hOffset, w = this.width, h = this.height - hOffset; - //return this.basicSquaresLayout(this.tree.children, 0, hOffset, this.width, this.height - hOffset); - //return this.basicRectsLayout(this.tree.children, 0, hOffset, this.width, this.height - hOffset); - return this.sweepToSideLayout(this.tree.children, 0, hOffset, this.width, this.height - hOffset); + return this.layoutSys.genLayout(this.tree.children, 0, hOffset, this.width, this.height - hOffset); } }, methods: { - expandTree(tree, lvl){ - if (lvl == 0){ - return tree; - } else { - let childTrees = tree.tolNode.children.map(e => - this.expandTree({tolNode: e, children: [], tileCount: 1}, lvl-1)); - return { - tolNode: tree.tolNode, - children: childTrees, - tileCount: (childTrees.length == 0) ? 1 : childTrees.map(e => e.tileCount).reduce((x,y) => x+y) - }; - } - }, - //determines layout for squares in a specified rectangle, with spacing - basicSquaresLayout(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 - }]) - ); - }, - basicRectsLayout(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; - }, - sweepToSideLayout(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 this.basicSquaresLayout(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 = this.basicSquaresLayout(leaves, x0, y0, w*ratio, h); - x0 += w*ratio - this.TILE_SPACING; - w -= (w*ratio - this.TILE_SPACING); - } - //return {...retVal, ...this.basicSquaresLayout(nonLeaves, x0, y0, w, h)}; - return {...retVal, ...this.basicRectsLayout(nonLeaves, x0, y0, w, h)}; - } - }, onImgClick(){ if (!this.isRoot){ this.$emit('tile-clicked', [this.tree]); } else { this.onInnerTileClicked([this.tree]); } - //increase z-index during transition - if (lastChangedTile !== null){ - lastChangedTile.zIdx = 0; - } - this.zIdx = 1; - lastChangedTile = this; + updateZForTransition(this); }, onInnerTileClicked(nodeList){ if (!this.isRoot){ - this.$emit('tile-clicked', nodeList.concat([this.tree])); + this.$emit('tile-clicked', [...nodeList, this.tree]); } else { //nodeList will hold an array of tree-objects, from the clicked-on-tile's tree-object upward let numNewTiles = nodeList[0].tolNode.children.length; if (numNewTiles == 0){ @@ -171,36 +57,20 @@ export default { nodeList[0].children = nodeList[0].tolNode.children.map(e => ({ tolNode: e, children: [], - tileCount: 1 })); - //update tile-counts - nodeList[0].tileCount = numNewTiles; - for (let i = 1; i < nodeList.length; i++){ - nodeList[i].tileCount += numNewTiles; - } + this.layoutSys.updateLayoutInfoOnExpand(nodeList); } }, onHeaderClick(){ this.$emit('header-clicked', [this.tree]); - //increase z-index during transition - if (lastChangedTile !== null){ - lastChangedTile.zIdx = 0; - } - this.zIdx = 1; - lastChangedTile = this; + updateZForTransition(this); }, onInnerHeaderClicked(nodeList){ if (!this.isRoot){ - this.$emit('header-clicked', nodeList.concat([this.tree])); + this.$emit('header-clicked', [...nodeList, this.tree]); } else { //nodeList will hold an array of tree-objects, from the clicked-on-tile's tree-object upward - let tc = nodeList[0].tileCount; - //remove children + this.layoutSys.updateLayoutInfoOnCollapse(nodeList); nodeList[0].children = []; - //update tile-counts - nodeList.tileCount = 1; - for (let i = 1; i < nodeList.length; i++){ - nodeList[i].tileCount -= tc - 1; - } } } } @@ -216,11 +86,11 @@ export default { class="h-full hover:cursor-pointer" @click="onImgClick" /> <div v-else> - <div v-if="!isRoot" :style="{height: HEADER_SZ + 'px'}" + <div v-if="!isRoot" :style="{height: this.layoutSys.HEADER_SZ + 'px'}" class="text-center hover:cursor-pointer bg-stone-300" @click="onHeaderClick"> {{tree.tolNode.name}} </div> - <tile-tree v-for="child in tree.children" :key="child.tolNode.name" :treeIn="child" + <tile-tree v-for="child in tree.children" :key="child.tolNode.name" :tree="child" :x="layout[child.tolNode.name].x" :y="layout[child.tolNode.name].y" :width="layout[child.tolNode.name].w" :height="layout[child.tolNode.name].h" @tile-clicked="onInnerTileClicked" @header-clicked="onInnerHeaderClicked" |
