aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/components/Tile.vue2
-rw-r--r--src/components/TileTree.vue26
-rw-r--r--src/lib.ts115
3 files changed, 66 insertions, 77 deletions
diff --git a/src/components/Tile.vue b/src/components/Tile.vue
index 4009068..9b43082 100644
--- a/src/components/Tile.vue
+++ b/src/components/Tile.vue
@@ -90,7 +90,7 @@ export default defineComponent({
position: 'absolute',
left: this.options.leafHeaderX + 'px',
top: this.options.leafHeaderY + 'px',
- maxWidth: this.layoutNode.hidden ? 0 : this.layoutNode.dims[0] - this.options.leafHeaderX * 2 + 'px',
+ maxWidth: (this.layoutNode.hidden ? 0 : this.layoutNode.dims[0] - this.options.leafHeaderX * 2) + 'px',
height: this.options.leafHeaderFontSz + 'px',
lineHeight: this.options.leafHeaderFontSz + 'px',
fontSize: this.options.leafHeaderFontSz + 'px',
diff --git a/src/components/TileTree.vue b/src/components/TileTree.vue
index 2cc2d53..6c63f4f 100644
--- a/src/components/TileTree.vue
+++ b/src/components/TileTree.vue
@@ -1,7 +1,7 @@
<script lang="ts">
import {defineComponent} from 'vue';
import Tile from './Tile.vue';
-import {TolNode, LayoutTree, LayoutNode} from '../lib';
+import {TolNode, LayoutNode, initLayoutTree, tryLayout} from '../lib';
import type {LayoutOptions} from '../lib';
// Import paths lack a .ts or .js extension because .ts makes vue-tsc complain, and .js makes vite complain
@@ -40,10 +40,10 @@ const defaultOtherOptions = {
// Collects events about tile expansion/collapse and window-resize, and initiates relayout of tiles
export default defineComponent({
data(){
- let layoutTree = new LayoutTree(tol, defaultLayoutOptions, 0);
+ let layoutTree = initLayoutTree(tol, 0);
return {
layoutTree: layoutTree,
- activeRoot: layoutTree.root,
+ activeRoot: layoutTree,
layoutOptions: {...defaultLayoutOptions},
otherOptions: {...defaultOtherOptions},
width: document.documentElement.clientWidth - (defaultOtherOptions.rootOffset * 2),
@@ -57,34 +57,30 @@ export default defineComponent({
// Update data and relayout tiles
this.width = document.documentElement.clientWidth - (this.otherOptions.rootOffset * 2);
this.height = document.documentElement.clientHeight - (this.otherOptions.rootOffset * 2);
- if (!this.layoutTree.tryLayout(this.activeRoot, [0,0], [this.width,this.height], true)){
- console.log('Unable to layout tree');
- }
+ tryLayout(this.activeRoot, [0,0], [this.width,this.height], this.layoutOptions, true);
// Prevent re-triggering until after a delay
this.resizeThrottled = true;
setTimeout(() => {this.resizeThrottled = false;}, this.otherOptions.resizeDelay);
}
},
onInnerLeafClicked({layoutNode, domNode}: {layoutNode: LayoutNode, domNode: HTMLElement}){
- let success = this.layoutTree.tryLayout(this.activeRoot, [0,0], [this.width,this.height], false,
+ let success = tryLayout(this.activeRoot, [0,0], [this.width,this.height], this.layoutOptions, false,
{type: 'expand', node: layoutNode});
if (!success){
// Trigger failure animation
domNode.classList.remove('animate-expand-shrink');
domNode.offsetWidth; // Triggers reflow
domNode.classList.add('animate-expand-shrink');
- //console.log('Unable to layout tree');
}
},
onInnerHeaderClicked({layoutNode, domNode}: {layoutNode: LayoutNode, domNode: HTMLElement}){
- let success = this.layoutTree.tryLayout(this.activeRoot, [0,0], [this.width,this.height], false,
+ let success = tryLayout(this.activeRoot, [0,0], [this.width,this.height], this.layoutOptions, false,
{type: 'collapse', node: layoutNode});
if (!success){
// Trigger failure animation
domNode.classList.remove('animate-shrink-expand');
domNode.offsetWidth; // Triggers reflow
domNode.classList.add('animate-shrink-expand');
- //console.log('Unable to layout tree');
}
},
onInnerLeafDblClicked(layoutNode: LayoutNode){
@@ -94,7 +90,7 @@ export default defineComponent({
}
LayoutNode.hideUpward(layoutNode);
this.activeRoot = layoutNode;
- this.layoutTree.tryLayout(layoutNode, [0,0], [this.width,this.height], true,
+ tryLayout(layoutNode, [0,0], [this.width,this.height], this.layoutOptions, true,
{type: 'expand', node: layoutNode});
},
onInnerHeaderDblClicked(layoutNode: LayoutNode){
@@ -104,14 +100,12 @@ export default defineComponent({
}
LayoutNode.hideUpward(layoutNode);
this.activeRoot = layoutNode;
- this.layoutTree.tryLayout(layoutNode, [0,0], [this.width,this.height], true);
+ tryLayout(layoutNode, [0,0], [this.width,this.height], this.layoutOptions, true);
},
},
created(){
window.addEventListener('resize', this.onResize);
- if (!this.layoutTree.tryLayout(this.activeRoot, [0,0], [this.width,this.height], true)){
- console.log('Unable to layout tree');
- }
+ tryLayout(this.activeRoot, [0,0], [this.width,this.height], this.layoutOptions, true);
},
unmounted(){
window.removeEventListener('resize', this.onResize);
@@ -124,7 +118,7 @@ export default defineComponent({
<template>
<div class="h-screen bg-stone-800">
- <tile :layoutNode="layoutTree.root"
+ <tile :layoutNode="layoutTree"
:headerSz="layoutOptions.headerSz" :tileSpacing="layoutOptions.tileSpacing"
:transitionDuration="otherOptions.transitionDuration"
@leaf-clicked="onInnerLeafClicked" @header-clicked="onInnerHeaderClicked"
diff --git a/src/lib.ts b/src/lib.ts
index c4bea0b..d1d0080 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -2,8 +2,9 @@
* Contains classes used for representing tree-of-life data, and tile-based layouts of such data.
*
* Generally, given a TolNode with child TolNodes representing tree-of-life T,
- * a LayoutTree is created for a subtree of T, and represents a tile-based layout of that subtree.
- * The LayoutTree holds LayoutNodes, each of which holds placement info for a linked TolNode.
+ * initLayoutTree() produces a tree structure representing a subtree of T,
+ * which is passed to tryLayout(), which alters data fields to represent a tile-based layout.
+ * The tree structure consists of LayoutNode objects, each of which holds placement info for a linked TolNode.
*/
// Represents a tree-of-life node/tree
@@ -15,63 +16,6 @@ export class TolNode {
this.children = children;
}
}
-// Represents a tree of LayoutNode objects, and has methods for (re)computing layout
-export class LayoutTree {
- root: LayoutNode;
- options: LayoutOptions;
- // Creates an object representing a TolNode tree, up to a given depth (0 means just the root)
- constructor(tol: TolNode, options: LayoutOptions, depth: number){
- this.root = this.initHelper(tol, depth);
- this.options = options;
- }
- // Used by constructor to initialise the LayoutNode tree
- initHelper(tolNode: TolNode, depthLeft: number, atDepth: number = 0): LayoutNode {
- if (depthLeft == 0){
- let node = new LayoutNode(tolNode, []);
- node.depth = atDepth;
- return node;
- } else {
- let children = tolNode.children.map((n: TolNode) => this.initHelper(n, depthLeft-1, atDepth+1));
- let node = new LayoutNode(tolNode, children);
- children.forEach(n => n.parent = node);
- return node;
- }
- }
- // Attempts layout of TolNode tree, for an area with given xy-coordinate and width+height (in pixels)
- // 'allowCollapse' allows the layout algorithm to collapse nodes to avoid layout failure
- // 'chg' allows for performing layout after expanding/collapsing a node
- tryLayout(root: LayoutNode, pos: [number,number], dims: [number,number], allowCollapse: boolean = false,
- chg?: LayoutTreeChg){
- // Create a new LayoutNode tree, keeping the old one in case of layout failure
- let tempTree = root.cloneNodeTree(chg);
- let success: boolean;
- switch (this.options.layoutType){
- case 'sqr': success = sqrLayout(tempTree, pos, dims, true, allowCollapse, this.options); break;
- case 'rect': success = rectLayout(tempTree, pos, dims, true, allowCollapse, this.options); break;
- case 'sweep': success = sweepLayout(tempTree, pos, dims, true, allowCollapse, this.options); break;
- }
- if (success){
- // Center root in layout area
- tempTree.pos[0] = (dims[0] - tempTree.dims[0]) / 2;
- tempTree.pos[1] = (dims[1] - tempTree.dims[1]) / 2;
- // Apply to active LayoutNode tree
- tempTree.copyTreeForRender(root);
- }
- return success;
- }
-}
-// Contains settings that affect how layout is done
-export type LayoutOptions = {
- tileSpacing: number; // Spacing between tiles, in pixels (ignoring borders)
- headerSz: number;
- minTileSz: number; // Minimum size of a tile edge, in pixels (ignoring borders)
- maxTileSz: number;
- layoutType: 'sqr' | 'rect' | 'sweep'; // The LayoutFn function to use
- rectMode: 'horz' | 'vert' | 'linear' | 'auto'; // Layout in 1 row, 1 column, 1 row or column, or multiple rows
- sweepMode: 'left' | 'top' | 'shorter' | 'auto'; // Sweep to left, top, shorter-side, or to minimise empty space
- sweptNodesPrio: 'linear' | 'sqrt' | 'pow-2/3'; // Specifies allocation of space to swept-vs-remaining nodes
- sweepingToParent: boolean; // Allow swept nodes to occupy empty space in a parent's swept-leaves area
-};
// Represents a node/tree, and holds layout data for a TolNode node/tree
export class LayoutNode {
tolNode: TolNode;
@@ -177,6 +121,18 @@ export class LayoutNode {
});
}
}
+// Contains settings that affect how layout is done
+export type LayoutOptions = {
+ tileSpacing: number; // Spacing between tiles, in pixels (ignoring borders)
+ headerSz: number;
+ minTileSz: number; // Minimum size of a tile edge, in pixels (ignoring borders)
+ maxTileSz: number;
+ layoutType: 'sqr' | 'rect' | 'sweep'; // The LayoutFn function to use
+ rectMode: 'horz' | 'vert' | 'linear' | 'auto'; // Layout in 1 row, 1 column, 1 row or column, or multiple rows
+ sweepMode: 'left' | 'top' | 'shorter' | 'auto'; // Sweep to left, top, shorter-side, or to minimise empty space
+ sweptNodesPrio: 'linear' | 'sqrt' | 'pow-2/3'; // Specifies allocation of space to swept-vs-remaining nodes
+ sweepingToParent: boolean; // Allow swept nodes to occupy empty space in a parent's swept-leaves area
+};
export type LayoutTreeChg = {
type: 'expand' | 'collapse';
node: LayoutNode;
@@ -196,7 +152,46 @@ export class SepSweptArea {
}
}
-// Type for functions called by LayoutTree to perform layout
+// Creates a LayoutNode representing a TolNode tree, up to a given depth (0 means just the root)
+export function initLayoutTree(tol: TolNode, depth: number): LayoutNode {
+ function initHelper(tolNode: TolNode, depthLeft: number, atDepth: number = 0): LayoutNode {
+ if (depthLeft == 0){
+ let node = new LayoutNode(tolNode, []);
+ node.depth = atDepth;
+ return node;
+ } else {
+ let children = tolNode.children.map((n: TolNode) => initHelper(n, depthLeft-1, atDepth+1));
+ let node = new LayoutNode(tolNode, children);
+ children.forEach(n => n.parent = node);
+ return node;
+ }
+ }
+ return initHelper(tol, depth);
+}
+// Attempts layout on a LayoutNode's corresponding TolNode tree, for an area with given xy-position and width+height
+// 'allowCollapse' allows the layout algorithm to collapse nodes to avoid layout failure
+// 'chg' allows for performing layout after expanding/collapsing a node
+export function tryLayout(layoutTree: LayoutNode, pos: [number,number], dims: [number,number],
+ options: LayoutOptions, allowCollapse: boolean = false, chg?: LayoutTreeChg){
+ // Create a new LayoutNode tree, in case of layout failure
+ let tempTree = layoutTree.cloneNodeTree(chg);
+ let success: boolean;
+ switch (options.layoutType){
+ case 'sqr': success = sqrLayout(tempTree, pos, dims, true, allowCollapse, options); break;
+ case 'rect': success = rectLayout(tempTree, pos, dims, true, allowCollapse, options); break;
+ case 'sweep': success = sweepLayout(tempTree, pos, dims, true, allowCollapse, options); break;
+ }
+ if (success){
+ // Center in layout area
+ tempTree.pos[0] = (dims[0] - tempTree.dims[0]) / 2;
+ tempTree.pos[1] = (dims[1] - tempTree.dims[1]) / 2;
+ // Apply to active LayoutNode tree
+ tempTree.copyTreeForRender(layoutTree);
+ }
+ return success;
+}
+
+// Type for functions called by tryLayout() to perform layout
// Given a LayoutNode tree, determines and records a new layout by setting fields of nodes in the tree
// Returns a boolean indicating success
type LayoutFn = (