From 0442b3c5c861c2096fb3d7f74fb77073660651f1 Mon Sep 17 00:00:00 2001
From: Terry Truong <terry06890@gmail.com>
Date: Fri, 4 Mar 2022 01:34:30 +1100
Subject: Improve sweep-to-side layout

---
 src/components/TileTree.vue | 86 +++++++++++++++++++++++++++++++++++----------
 1 file changed, 68 insertions(+), 18 deletions(-)

(limited to 'src/components/TileTree.vue')

diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue
index 46da28e..8170ad7 100644
--- a/src/components/TileTree.vue
+++ b/src/components/TileTree.vue
@@ -26,13 +26,27 @@ export default {
 		}
 	},
 	methods: {
+		//determines layout for squares in a specified rectangle, with spacing
 		basicSquaresLayout(nodes, x0, y0, w, h){
-			//determine layout for squares in a specified rectangle, with spacing
-			let numCols = this.pickNumCols(nodes.length, w/h);
-			let numRows = Math.ceil(nodes.length / numCols);
+			//get number-of-columns with highest occupied-fraction of rectangles with aspect-ratio w/h
+				//account for tile-spacing?
+			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.name, {
 					x: x0 + (idx % numCols)*(tileSz + this.TILE_SPACING) + this.TILE_SPACING,
@@ -42,19 +56,54 @@ export default {
 					}])
 				);
 		},
-		pickNumCols(numTiles, aspectRatio){ //account for tile-spacing?
-			//look for number of columns with highest occupied-fraction of rectangles with aspectRatio
-			let bestNum, bestFrac = 0;
-			for (let numCols = 1; numCols <= numTiles; numCols++){
-				let numRows = Math.ceil(numTiles/numCols);
-				let ar = numCols/numRows;
-				let frac = aspectRatio > ar ? ar/aspectRatio : aspectRatio/ar;
-				if (frac > bestFrac){
-					bestFrac = frac;
-					bestNum = numCols;
+		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++){
+						score -= Math.abs(grid[r][c] - (rowProp[r] * colProp[c]));
+					}
+				}
+				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]);
 			}
-			return bestNum;
+			let retVal = Object.fromEntries(
+				nodes.map((el, idx) => [el.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
@@ -68,10 +117,11 @@ export default {
 				if (leaves.length > 0){
 					let ratio = leaves.length / this.tree.tileCount;
 					retVal = this.basicSquaresLayout(leaves, x0, y0, w*ratio, h);
-					x0 += w*ratio;
-					w -= w*ratio;
+					x0 += w*ratio - this.TILE_SPACING;
+					w -= (w*ratio - this.TILE_SPACING);
 				}
-				return {...retVal, ...this.basicSquaresLayout(nonLeaves, x0, y0, w, h)};
+				//return {...retVal, ...this.basicSquaresLayout(nonLeaves, x0, y0, w, h)};
+				return {...retVal, ...this.basicRectsLayout(nonLeaves, x0, y0, w, h)};
 			}
 		}
 	}
@@ -82,7 +132,7 @@ export default {
 <div v-if="tree.children && tree.children.length > 0" class="border border-black"
 	:style="{position: 'absolute', left: x + 'px', top: y + 'px', width: + width + 'px', height: height + 'px'}">
 	<div v-if="!isRoot" :style="{height: HEADER_SZ + 'px'}" class="text-center">{{tree.name}}</div>
-	<tile-tree v-for="child in tree.children" :tree="child"
+	<tile-tree v-for="child in tree.children" :key="child.name" :tree="child"
 		:x="layout[child.name].x" :y="layout[child.name].y" :width="layout[child.name].w" :height="layout[child.name].h"
 		></tile-tree>
 </div>
-- 
cgit v1.2.3

