aboutsummaryrefslogtreecommitdiff
path: root/src/layout.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/layout.js')
-rw-r--r--src/layout.js174
1 files changed, 112 insertions, 62 deletions
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){