aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.vue84
-rw-r--r--src/components/AncestryBar.vue10
-rw-r--r--src/components/SearchModal.vue13
-rw-r--r--src/components/Tile.vue25
-rw-r--r--src/components/TileInfoModal.vue9
-rwxr-xr-xsrc/genTestImgs.sh16
-rw-r--r--src/layout.ts48
-rw-r--r--src/tol.ts49
-rw-r--r--src/tolData.txt388
-rwxr-xr-xsrc/txtTreeToJSON.py76
10 files changed, 114 insertions, 604 deletions
diff --git a/src/App.vue b/src/App.vue
index cf25b18..e00072b 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -13,7 +13,8 @@ import SearchIcon from './components/icon/SearchIcon.vue';
import PlayIcon from './components/icon/PlayIcon.vue';
import SettingsIcon from './components/icon/SettingsIcon.vue';
// Other
-import {TolNode, TolNodeRaw, tolFromRaw, getTolMap} from './tol';
+import type {TolMap} from './tol';
+import {TolNode} from './tol';
import {LayoutNode, initLayoutTree, initLayoutMap, tryLayout} from './layout';
import type {LayoutOptions} from './layout';
import {arraySum, randWeightedChoice} from './util';
@@ -38,9 +39,9 @@ function getReverseAction(action: Action): Action | null {
}
// Get tree-of-life data
-import tolRaw from './tolData.json';
-const tol = tolFromRaw(tolRaw);
-const tolMap = getTolMap(tol);
+import data from './tolData.json';
+let tolMap: TolMap = data;
+const rootName = "[Elaeocarpus williamsianus + Brunellia mexicana]";
// Configurable options
const defaultLytOpts: LayoutOptions = {
@@ -89,14 +90,14 @@ const defaultUiOpts = {
export default defineComponent({
data(){
- let layoutTree = initLayoutTree(tol, 0);
+ let layoutTree = initLayoutTree(tolMap, rootName, 0);
return {
+ tolMap: tolMap,
layoutTree: layoutTree,
activeRoot: layoutTree, // Differs from layoutTree root when expand-to-view is used
layoutMap: initLayoutMap(layoutTree), // Maps names to LayoutNode objects
- tolMap: tolMap, // Maps names to TolNode objects
// Modals and settings related
- infoModalNode: null as TolNode | null, // Node to display info for, or null
+ infoModalNode: null as LayoutNode | null, // Node to display info for, or null
helpOpen: false,
searchOpen: false,
settingsOpen: false,
@@ -170,16 +171,22 @@ export default defineComponent({
methods: {
// For tile expand/collapse events
onLeafClick(layoutNode: LayoutNode){
- let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts,
- {allowCollapse: false, chg: {type: 'expand', node: layoutNode}, layoutMap: this.layoutMap});
+ let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, {
+ allowCollapse: false,
+ chg: {type: 'expand', node: layoutNode, tolMap: this.tolMap},
+ layoutMap: this.layoutMap
+ });
if (!success){
layoutNode.failFlag = !layoutNode.failFlag; // Triggers failure animation
}
return success;
},
onNonleafClick(layoutNode: LayoutNode){
- let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts,
- {allowCollapse: false, chg: {type: 'collapse', node: layoutNode}, layoutMap: this.layoutMap});
+ let success = tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, {
+ allowCollapse: false,
+ chg: {type: 'collapse', node: layoutNode, tolMap: this.tolMap},
+ layoutMap: this.layoutMap
+ });
if (!success){
layoutNode.failFlag = !layoutNode.failFlag; // Triggers failure animation
}
@@ -193,8 +200,11 @@ export default defineComponent({
}
LayoutNode.hideUpward(layoutNode);
this.activeRoot = layoutNode;
- tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts,
- {allowCollapse: true, chg: {type: 'expand', node: layoutNode}, layoutMap: this.layoutMap});
+ tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts, {
+ allowCollapse: true,
+ chg: {type: 'expand', node: layoutNode, tolMap: this.tolMap},
+ layoutMap: this.layoutMap
+ });
},
onNonleafClickHeld(layoutNode: LayoutNode){
if (layoutNode == this.activeRoot){
@@ -204,7 +214,7 @@ export default defineComponent({
LayoutNode.hideUpward(layoutNode);
this.activeRoot = layoutNode;
tryLayout(this.activeRoot, this.tileAreaPos, this.tileAreaDims, this.lytOpts,
- {allowCollapse: true, layoutMap: this.layoutMap, });
+ {allowCollapse: true, layoutMap: this.layoutMap});
},
onDetachedAncestorClick(layoutNode: LayoutNode){
LayoutNode.showDownward(layoutNode);
@@ -215,7 +225,7 @@ export default defineComponent({
// For tile-info events
onInfoIconClick(node: LayoutNode){
this.resetMode();
- this.infoModalNode = node.tolNode;
+ this.infoModalNode = node;
},
// For help events
onHelpIconClick(){
@@ -227,60 +237,58 @@ export default defineComponent({
this.resetMode();
this.searchOpen = true;
},
- onSearchNode(tolNode: TolNode){
+ onSearchNode(name: string){
this.searchOpen = false;
this.modeRunning = true;
- this.expandToTolNode(tolNode);
+ this.expandToNode(name);
},
- expandToTolNode(tolNode: TolNode){
+ expandToNode(name: string){
if (!this.modeRunning){
return;
}
// Check if searched node is displayed
- let layoutNode = this.layoutMap.get(tolNode.name);
- if (layoutNode != null && !layoutNode.hidden){
- this.setLastFocused(layoutNode);
+ let layoutNodeVal = this.layoutMap.get(name);
+ if (layoutNodeVal != null && !layoutNodeVal.hidden){
+ this.setLastFocused(layoutNodeVal);
this.modeRunning = false;
return;
}
// Get nearest in-layout-tree ancestor
- let ancestor = tolNode;
- while (this.layoutMap.get(ancestor.name) == null){
- ancestor = ancestor.parent!;
+ let ancestorName = name;
+ while (this.layoutMap.get(ancestorName) == null){
+ ancestorName = this.tolMap[ancestorName].parent!;
}
- layoutNode = this.layoutMap.get(ancestor.name)!;
+ let layoutNode = this.layoutMap.get(ancestorName)!;
// If hidden, expand self/ancestor in ancestry-bar
if (layoutNode.hidden){
while (!this.detachedAncestors!.includes(layoutNode)){
- ancestor = ancestor.parent!;
- layoutNode = this.layoutMap.get(ancestor.name)!;
+ layoutNode = layoutNode.parent!;
}
this.onDetachedAncestorClick(layoutNode!);
- setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.tileChgDuration);
+ setTimeout(() => this.expandToNode(name), this.uiOpts.tileChgDuration);
return;
}
// Attempt tile-expand
let success = this.onLeafClick(layoutNode);
if (success){
- setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.tileChgDuration);
+ setTimeout(() => this.expandToNode(name), this.uiOpts.tileChgDuration);
return;
}
// Attempt expand-to-view on ancestor just below activeRoot
- if (ancestor.name == this.activeRoot.tolNode.name){
+ if (layoutNode == this.activeRoot){
console.log('Unable to complete search (not enough room to expand active root)');
// Note: Only happens if screen is significantly small or node has significantly many children
this.modeRunning = false;
return;
}
while (true){
- if (ancestor.parent!.name == this.activeRoot.tolNode.name){
+ if (layoutNode.parent! == this.activeRoot){
break;
}
- ancestor = ancestor.parent!;
+ layoutNode = layoutNode.parent!;
}
- layoutNode = this.layoutMap.get(ancestor.name)!;
this.onNonleafClickHeld(layoutNode);
- setTimeout(() => this.expandToTolNode(tolNode), this.uiOpts.tileChgDuration);
+ setTimeout(() => this.expandToNode(name), this.uiOpts.tileChgDuration);
},
// For auto-mode events
onPlayIconClick(){
@@ -317,7 +325,7 @@ export default defineComponent({
if (node == this.activeRoot){
actionWeights['move up'] = 0;
}
- if (node.tolNode.children.length == 0){
+ if (this.tolMap[node.name].children.length == 0){
actionWeights['expand'] = 0;
}
} else {
@@ -463,13 +471,13 @@ export default defineComponent({
<template>
<div class="absolute left-0 top-0 w-screen h-screen overflow-hidden" :style="{backgroundColor: uiOpts.appBgColor}">
<!-- Note: Making the above enclosing div's width/height dynamic seems to cause white flashes when resizing -->
- <tile :layoutNode="layoutTree" :lytOpts="lytOpts" :uiOpts="uiOpts"
+ <tile :layoutNode="layoutTree" :tolMap="tolMap" :lytOpts="lytOpts" :uiOpts="uiOpts"
@leaf-click="onLeafClick" @nonleaf-click="onNonleafClick"
@leaf-click-held="onLeafClickHeld" @nonleaf-click-held="onNonleafClickHeld"
@info-icon-click="onInfoIconClick"/>
<ancestry-bar v-if="detachedAncestors != null"
:pos="[0,0]" :dims="ancestryBarDims" :nodes="detachedAncestors"
- :lytOpts="lytOpts" :uiOpts="uiOpts"
+ :tolMap="tolMap" :lytOpts="lytOpts" :uiOpts="uiOpts"
@detached-ancestor-click="onDetachedAncestorClick" @info-icon-click="onInfoIconClick"/>
<!-- Icons -->
<help-icon @click="onHelpIconClick"
@@ -483,7 +491,7 @@ export default defineComponent({
text-white/40 hover:text-white hover:cursor-pointer"/>
<!-- Modals -->
<transition name="fade">
- <tile-info-modal v-if="infoModalNode != null" :tolNode="infoModalNode" :uiOpts="uiOpts"
+ <tile-info-modal v-if="infoModalNode != null" :node="infoModalNode" :uiOpts="uiOpts"
@info-modal-close="infoModalNode = null"/>
</transition>
<transition name="fade">
diff --git a/src/components/AncestryBar.vue b/src/components/AncestryBar.vue
index 6d6ae3c..a156a96 100644
--- a/src/components/AncestryBar.vue
+++ b/src/components/AncestryBar.vue
@@ -3,6 +3,7 @@ import {defineComponent, PropType} from 'vue';
import Tile from './Tile.vue'
import {LayoutNode} from '../layout';
import type {LayoutOptions} from '../layout';
+import type {TolMap} from '../tol';
// Displays a sequence of nodes, representing ancestors from a tree-of-life root to a currently-active root
export default defineComponent({
@@ -12,7 +13,8 @@ export default defineComponent({
dims: {type: Array as unknown as PropType<[number,number]>, required: true},
// The ancestors to display
nodes: {type: Array as PropType<LayoutNode[]>, required: true},
- // Options
+ // Other
+ tolMap: {type: Object as PropType<TolMap>, required: true},
lytOpts: {type: Object as PropType<LayoutOptions>, required: true},
uiOpts: {type: Object, required: true},
},
@@ -26,7 +28,7 @@ export default defineComponent({
},
usedNodes(){ // Childless versions of 'nodes' used to parameterise <tile>
return this.nodes.map(n => {
- let newNode = new LayoutNode(n.tolNode, []);
+ let newNode = new LayoutNode(n.name, []);
newNode.dims = [this.tileSz, this.tileSz];
return newNode;
});
@@ -80,8 +82,8 @@ export default defineComponent({
<template>
<div :style="styles">
- <tile v-for="(node, idx) in usedNodes" :key="node.tolNode.name" class="shrink-0"
- :layoutNode="node" :nonAbsPos="true" :lytOpts="lytOpts" :uiOpts="uiOpts"
+ <tile v-for="(node, idx) in usedNodes" :key="node.name" class="shrink-0"
+ :layoutNode="node" :tolMap="tolMap" :nonAbsPos="true" :lytOpts="lytOpts" :uiOpts="uiOpts"
@leaf-click="onTileClick(nodes[idx])" @info-icon-click="onInfoIconClick"/>
</div>
</template>
diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue
index 91f06ae..22b6896 100644
--- a/src/components/SearchModal.vue
+++ b/src/components/SearchModal.vue
@@ -1,15 +1,13 @@
<script lang="ts">
import {defineComponent, PropType} from 'vue';
import SearchIcon from './icon/SearchIcon.vue';
-import {TolNode} from '../tol';
import {LayoutNode} from '../layout';
+import type {TolMap} from '../tol';
// Displays a search box, and sends search requests
export default defineComponent({
props: {
- // Map from tree-of-life node names to TolNode objects
- tolMap: {type: Object as PropType<Map<string,TolNode>>, required: true},
- // Options
+ tolMap: {type: Object as PropType<TolMap>, required: true},
uiOpts: {type: Object, required: true},
},
methods: {
@@ -20,15 +18,14 @@ export default defineComponent({
},
onSearchEnter(){
let input = this.$refs.searchInput as HTMLInputElement;
- let tolNode = this.tolMap.get(input.value);
- if (tolNode == null){
+ if (this.tolMap.hasOwnProperty(input.value)){
+ this.$emit('search-node', input.value);
+ } else {
input.value = '';
// Trigger failure animation
input.classList.remove('animate-red-then-fade');
input.offsetWidth; // Triggers reflow
input.classList.add('animate-red-then-fade');
- } else {
- this.$emit('search-node', tolNode);
}
},
focusInput(){
diff --git a/src/components/Tile.vue b/src/components/Tile.vue
index 1a506d6..a0c0f0f 100644
--- a/src/components/Tile.vue
+++ b/src/components/Tile.vue
@@ -3,12 +3,14 @@ import {defineComponent, PropType} from 'vue';
import InfoIcon from './icon/InfoIcon.vue';
import {LayoutNode} from '../layout';
import type {LayoutOptions} from '../layout';
+import type {TolMap} from '../tol';
+import {TolNode} from '../tol';
// Displays one, or a hierarchy of, tree-of-life nodes, as a 'tile'
export default defineComponent({
props: {
- // A LayoutNode representing a laid-out tree-of-life node to display
layoutNode: {type: Object as PropType<LayoutNode>, required: true},
+ tolMap: {type: Object as PropType<TolMap>, required: true},
// Options
lytOpts: {type: Object as PropType<LayoutOptions>, required: true},
uiOpts: {type: Object, required: true},
@@ -26,12 +28,15 @@ export default defineComponent({
};
},
computed: {
+ tolNode(): TolNode{
+ return this.tolMap[this.layoutNode.name];
+ },
// Basic abbreviations
isLeaf(): boolean {
return this.layoutNode.children.length == 0;
},
isExpandableLeaf(): boolean {
- return this.isLeaf && this.layoutNode.tolNode.children.length > 0;
+ return this.isLeaf && this.tolNode.children.length > 0;
},
showNonleafHeader(): boolean {
return (this.layoutNode.showHeader && this.layoutNode.sepSweptArea == null) ||
@@ -83,9 +88,11 @@ export default defineComponent({
leafStyles(): Record<string,string> {
return {
// Image (and scrims)
+ //backgroundImage:
+ // 'linear-gradient(to bottom, rgba(0,0,0,0.4), #0000 40%, #0000 60%, rgba(0,0,0,0.4) 100%),' +
+ // 'url(\'/img/' + this.layoutNode.name.replaceAll('\'', '\\\'') + '.png\')',
backgroundImage:
- 'linear-gradient(to bottom, rgba(0,0,0,0.4), #0000 40%, #0000 60%, rgba(0,0,0,0.4) 100%),' +
- 'url(\'/img/' + this.layoutNode.tolNode.name.replaceAll('\'', '\\\'') + '.png\')',
+ 'linear-gradient(to bottom, rgba(0,0,0,0.4), #0000 40%, #0000 60%, rgba(0,0,0,0.4) 100%)',
backgroundSize: 'cover',
// Other
borderRadius: this.uiOpts.borderRadius + 'px',
@@ -311,7 +318,7 @@ export default defineComponent({
<div v-if="isLeaf" :style="leafStyles"
class="w-full h-full flex flex-col overflow-hidden" :class="{'hover:cursor-pointer': isExpandableLeaf}"
@mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mousedown="onMouseDown" @mouseup="onMouseUp">
- <h1 :style="leafHeaderStyles">{{layoutNode.tolNode.name}}</h1>
+ <h1 :style="leafHeaderStyles">{{layoutNode.name}}</h1>
<info-icon :style="[infoIconStyles, {marginTop: 'auto'}]"
class="self-end text-white/10 hover:text-white hover:cursor-pointer"
@click.stop="onInfoIconClick" @mousedown.stop @mouseup.stop/>
@@ -319,7 +326,7 @@ export default defineComponent({
<div v-else :style="nonleafStyles" class="w-full h-full" ref="nonleaf">
<div v-if="showNonleafHeader" :style="nonleafHeaderStyles" class="flex hover:cursor-pointer"
@mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mousedown="onMouseDown" @mouseup="onMouseUp">
- <h1 :style="nonleafHeaderTextStyles" class="grow">{{layoutNode.tolNode.name}}</h1>
+ <h1 :style="nonleafHeaderTextStyles" class="grow">{{layoutNode.name}}</h1>
<info-icon :style="infoIconStyles" class="text-white/10 hover:text-white hover:cursor-pointer"
@click.stop="onInfoIconClick" @mousedown.stop @mouseup.stop/>
</div>
@@ -328,13 +335,13 @@ export default defineComponent({
<div v-if="layoutNode?.sepSweptArea?.sweptLeft === false"
:style="nonleafHeaderStyles" class="flex hover:cursor-pointer"
@mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @mousedown="onMouseDown" @mouseup="onMouseUp">
- <h1 :style="nonleafHeaderTextStyles" class="grow">{{layoutNode.tolNode.name}}</h1>
+ <h1 :style="nonleafHeaderTextStyles" class="grow">{{layoutNode.name}}</h1>
<info-icon :style="infoIconStyles" class="text-white/10 hover:text-white hover:cursor-pointer"
@click.stop="onInfoIconClick" @mousedown.stop @mouseup.stop/>
</div>
</div>
- <tile v-for="child in layoutNode.children" :key="child.tolNode.name"
- :layoutNode="child" :lytOpts="lytOpts" :uiOpts="uiOpts"
+ <tile v-for="child in layoutNode.children" :key="child.name"
+ :layoutNode="child" :tolMap="tolMap" :lytOpts="lytOpts" :uiOpts="uiOpts"
@leaf-click="onInnerLeafClick" @nonleaf-click="onInnerNonleafClick"
@leaf-click-held="onInnerLeafClickHeld" @nonleaf-click-held="onInnerNonleafClickHeld"
@info-icon-click="onInnerInfoIconClick"/>
diff --git a/src/components/TileInfoModal.vue b/src/components/TileInfoModal.vue
index 7549375..0e2fc94 100644
--- a/src/components/TileInfoModal.vue
+++ b/src/components/TileInfoModal.vue
@@ -1,18 +1,19 @@
<script lang="ts">
import {defineComponent, PropType} from 'vue';
import CloseIcon from './icon/CloseIcon.vue';
-import {TolNode} from '../tol';
+import {LayoutNode} from '../layout';
// Displays information about a tree-of-life node
export default defineComponent({
props: {
- tolNode: {type: Object as PropType<TolNode>, required: true},
+ node: {type: Object as PropType<LayoutNode>, required: true},
uiOpts: {type: Object, required: true},
},
computed: {
imgStyles(): Record<string,string> {
return {
- backgroundImage: 'url(\'/img/' + this.tolNode.name.replaceAll('\'', '\\\'') + '.png\')',
+ //backgroundImage: 'url(\'/img/' + this.node.name.replaceAll('\'', '\\\'') + '.png\')',
+ background: 'black',
width: this.uiOpts.infoModalImgSz + 'px',
height: this.uiOpts.infoModalImgSz + 'px',
backgroundSize: 'cover',
@@ -38,7 +39,7 @@ export default defineComponent({
bg-stone-50 rounded-md shadow shadow-black">
<close-icon @click.stop="onCloseClick" ref="closeIcon"
class="block absolute top-2 right-2 w-6 h-6 hover:cursor-pointer"/>
- <h1 class="text-center text-xl font-bold mb-2">{{tolNode.name}}</h1>
+ <h1 class="text-center text-xl font-bold mb-2">{{node.name}}</h1>
<hr class="mb-4 border-stone-400"/>
<div :style="imgStyles" class="float-left mr-4" alt="an image"></div>
<div>
diff --git a/src/genTestImgs.sh b/src/genTestImgs.sh
deleted file mode 100755
index 21b001b..0000000
--- a/src/genTestImgs.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-set -e
-
-#generate tol.json from tol.txt
-cat tolData.txt | ./txtTreeToJSON.py > tolData.json
-
-#reads through tolData.json, gets names, and generates image for each name
-cat tolData.json | \
- gawk 'match ($0, /"name"\s*:\s*"([^"]*)"/, arr) {print arr[1]}' | \
- while read; do
- convert -size 200x200 xc:khaki +repage \
- -size 150x150 -fill black -background None \
- -font Ubuntu-Mono -gravity center caption:"$REPLY" +repage \
- -gravity Center -composite -strip ../public/img/"$REPLY".png
- done
-
diff --git a/src/layout.ts b/src/layout.ts
index d863fa0..69e84db 100644
--- a/src/layout.ts
+++ b/src/layout.ts
@@ -2,16 +2,18 @@
* Contains classes for representing tile-based layouts of tree-of-life data.
*
* Generally, given a TolNode tree T, initLayoutTree() produces a
- * subtree-analagous LayoutNode tree, for which tryLayout() can attempt to
+ * subtree-analagous LayoutNode tree, for which tryLayout() can attempt to
* find a tile-based layout, filling in node fields to represent placement.
*/
import {TolNode} from './tol';
+import type {TolMap} from './tol';
import {range, arraySum, limitVals, updateAscSeq} from './util';
// Represents a node/tree that holds layout data for a TolNode node/tree
export class LayoutNode {
- tolNode: TolNode;
+ // TolNode name
+ name: string;
// Tree-structure related
children: LayoutNode[];
parent: LayoutNode | null;
@@ -28,8 +30,8 @@ export class LayoutNode {
hasFocus: boolean; // Used by search and auto-mode to highlight a tile
failFlag: boolean; // Used to trigger failure animations
// Constructor ('parent' are 'depth' are generally initialised later, 'dCount' is computed)
- constructor(tolNode: TolNode, children: LayoutNode[]){
- this.tolNode = tolNode;
+ constructor(name: string, children: LayoutNode[]){
+ this.name = name;
this.children = children;
this.parent = null;
this.dCount = children.length == 0 ? 1 : arraySum(children.map(n => n.dCount));
@@ -45,27 +47,27 @@ export class LayoutNode {
this.hasFocus = false;
this.failFlag = false;
}
- // Returns a new tree with the same structure and TolNode linkage
+ // Returns a new tree with the same structure and names
// 'chg' is usable to apply a change to the resultant tree
cloneNodeTree(chg?: LayoutTreeChg | null): LayoutNode {
let newNode: LayoutNode;
if (chg != null && this == chg.node){
switch (chg.type){
case 'expand':
- let children = this.tolNode.children.map((n: TolNode) => new LayoutNode(n, []));
- newNode = new LayoutNode(this.tolNode, children);
+ let children = chg.tolMap[this.name].children.map((name: string) => new LayoutNode(name, []));
+ newNode = new LayoutNode(this.name, children);
newNode.children.forEach(n => {
n.parent = newNode;
n.depth = this.depth + 1;
});
break;
case 'collapse':
- newNode = new LayoutNode(this.tolNode, []);
+ newNode = new LayoutNode(this.name, []);
break;
}
} else {
let children = this.children.map(n => n.cloneNodeTree(chg));
- newNode = new LayoutNode(this.tolNode, children);
+ newNode = new LayoutNode(this.name, children);
children.forEach(n => {n.parent = newNode});
}
newNode.depth = this.depth;
@@ -150,6 +152,7 @@ export type LayoutOptions = {
export type LayoutTreeChg = {
type: 'expand' | 'collapse';
node: LayoutNode;
+ tolMap: TolMap;
}
// Used with layout option 'sweepToParent', and represents, for a LayoutNode, a parent area to place leaf nodes in
export class SepSweptArea {
@@ -171,7 +174,7 @@ export type LayoutMap = Map<string, LayoutNode>;
// Creates a LayoutMap for a given tree
export function initLayoutMap(layoutTree: LayoutNode): LayoutMap {
function helper(node: LayoutNode, map: LayoutMap): void {
- map.set(node.tolNode.name, node);
+ map.set(node.name, node);
node.children.forEach(n => helper(n, map));
}
let map = new Map();
@@ -180,30 +183,31 @@ export function initLayoutMap(layoutTree: LayoutNode): LayoutMap {
}
// Adds a node and it's descendants' names to a LayoutMap
function addToLayoutMap(node: LayoutNode, map: LayoutMap): void {
- map.set(node.tolNode.name, node);
+ map.set(node.name, node);
node.children.forEach(n => addToLayoutMap(n, map));
}
// Removes a node and it's descendants' names from a LayoutMap
function removeFromLayoutMap(node: LayoutNode, map: LayoutMap): void {
- map.delete(node.tolNode.name);
+ map.delete(node.name);
node.children.forEach(n => removeFromLayoutMap(n, map));
}
// 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 {
+export function initLayoutTree(tolMap: TolMap, rootName: string, depth: number): LayoutNode {
+ function initHelper(tolMap: TolMap, nodeName: string, depthLeft: number, atDepth: number = 0): LayoutNode {
if (depthLeft == 0){
- let node = new LayoutNode(tolNode, []);
+ let node = new LayoutNode(nodeName, []);
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);
+ let children = tolMap[nodeName].children.map(
+ (name: string) => initHelper(tolMap, name, depthLeft-1, atDepth+1));
+ let node = new LayoutNode(nodeName, children);
children.forEach(n => n.parent = node);
return node;
}
}
- return initHelper(tol, depth);
+ return initHelper(tolMap, rootName, 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
@@ -568,7 +572,7 @@ let sweepLayout: LayoutFn = function (node, pos, dims, showHeader, allowCollapse
if (parentArea != null){
// Attempt leaves layout
sweptLeft = parentArea.sweptLeft;
- leavesLyt = new LayoutNode(new TolNode('SWEEP_' + node.tolNode.name), leaves);
+ leavesLyt = new LayoutNode('SWEEP_' + node.name, leaves);
// Note: Intentionally neglecting to update child nodes' 'parent' or 'depth' fields here
let leavesSuccess = sqrLayout(leavesLyt, [0,0], parentArea.dims, !sweptLeft, false, opts);
if (leavesSuccess){
@@ -579,7 +583,7 @@ let sweepLayout: LayoutFn = function (node, pos, dims, showHeader, allowCollapse
});
// Attempt non-leaves layout
let newDims: [number,number] = [dims[0], dims[1] - (sweptLeft ? headerSz : 0)];
- nonLeavesLyt = new LayoutNode(new TolNode('SWEEP_REM_' + node.tolNode.name), nonLeaves);
+ nonLeavesLyt = new LayoutNode('SWEEP_REM_' + node.name, nonLeaves);
let tempTree: LayoutNode = nonLeavesLyt.cloneNodeTree();
let sepAreaLen = 0;
let nonLeavesSuccess: boolean;
@@ -670,7 +674,7 @@ let sweepLayout: LayoutFn = function (node, pos, dims, showHeader, allowCollapse
// Attempt leaves layout
let newPos = [0, headerSz];
let newDims: [number,number] = [dims[0], dims[1] - headerSz];
- leavesLyt = new LayoutNode(new TolNode('SWEEP_' + node.tolNode.name), leaves);
+ leavesLyt = new LayoutNode('SWEEP_' + node.name, leaves);
let minSz = opts.minTileSz + opts.tileSpacing*2;
let sweptW = Math.max(minSz, newDims[0] * ratio), sweptH = Math.max(minSz, newDims[1] * ratio);
let leavesSuccess: boolean;
@@ -723,7 +727,7 @@ let sweepLayout: LayoutFn = function (node, pos, dims, showHeader, allowCollapse
newPos[1] += leavesLyt.dims[1] - opts.tileSpacing;
newDims[1] += -leavesLyt.dims[1] + opts.tileSpacing
}
- nonLeavesLyt = new LayoutNode(new TolNode('SWEEP_REM_' + node.tolNode.name), nonLeaves);
+ nonLeavesLyt = new LayoutNode('SWEEP_REM_' + node.name, nonLeaves);
let nonLeavesSuccess: boolean;
if (nonLeaves.length > 1){
nonLeavesSuccess = rectLayout(nonLeavesLyt, [0,0], newDims, false, false, opts, {subLayoutFn:
diff --git a/src/tol.ts b/src/tol.ts
index 42605e5..ba6c6c8 100644
--- a/src/tol.ts
+++ b/src/tol.ts
@@ -2,47 +2,18 @@
* Provides classes for representing and working with tree-of-life data.
*/
-// Represents a tree-of-life node/tree
+// Maps tree-of-life node names to node objects
+export type TolMap = {[key: string]: TolNode};
+// Represents a tree-of-life node
export class TolNode {
- name: string;
- children: TolNode[];
- parent: TolNode | null;
- constructor(name: string, children: TolNode[] = [], parent = null){
- this.name = name;
+ children: string[];
+ parent: string | null;
+ tips: number;
+ pSupport: boolean;
+ constructor(children: string[] = [], parent = null, tips = 0, pSupport = false){
this.children = children;
this.parent = parent;
+ this.tips = tips;
+ this.pSupport = pSupport;
}
}
-// Represents a tree-of-life node obtained from tolData.json
-export class TolNodeRaw {
- name: string;
- children?: TolNodeRaw[];
- constructor(name: string, children: TolNodeRaw[] = []){
- this.name = name;
- this.children = children;
- }
-}
-// Converts a TolNodeRaw tree to a TolNode tree
-export function tolFromRaw(node: TolNodeRaw): TolNode {
- function helper(node: TolNodeRaw, parent: TolNode | null){
- let tolNode = new TolNode(node.name);
- if (node.children == null){
- tolNode.children = [];
- } else {
- tolNode.children = node.children.map(child => helper(child, tolNode));
- }
- tolNode.parent = parent;
- return tolNode;
- }
- return helper(node, null);
-}
-// Returns a map from TolNode names to TolNodes in a given tree
-export function getTolMap(tolTree: TolNode): Map<string, TolNode> {
- function helper(node: TolNode, map: Map<string, TolNode>){
- map.set(node.name, node);
- node.children.forEach(child => helper(child, map));
- }
- let map = new Map();
- helper(tolTree, map);
- return map;
-}
diff --git a/src/tolData.txt b/src/tolData.txt
deleted file mode 100644
index f73a064..0000000
--- a/src/tolData.txt
+++ /dev/null
@@ -1,388 +0,0 @@
-Tree of Life
- Viruses
- Caudovirales
- Herpesvirales
- Ligamenvirales
- Mononegavirales
- Nidovirales
- Picornavirales
- Tymovirales
- Archaea
- Crenarchaeota
- Euryarchaeota
- Bacteria
- Acidobacteria
- Actinobacteria
- Aquificae
- Armatimonadetes
- Bacteroidetes
- Caldiserica
- Chlamydiae
- Chlorobi
- Chloroflexi
- Chrysiogenetes
- Cyanobacteria
- Deferribacteres
- Deinococcus-thermus
- Dictyoglomi
- Elusimicrobia
- Fibrobacteres
- Firmicutes
- Fusobacteria
- Gemmatimonadetes
- Lentisphaerae
- Nitrospira
- Planctomycetes
- Proteobacteria
- Spirochaetae
- Synergistetes
- Tenericutes
- Thermodesulfobacteria
- Thermotogae
- Verrucomicrobia
- Eukaryota
- Diatoms
- Amoebozoa
- Plantae
- Rhodopyhta
- Viridiplantae
- Prasinophytes
- Ulvophyceae
- Streptophyta
- Charales
- Embryophytes
- Marchantiomorpha
- Anthocerotophyta
- Bryophyta
- Lycopodiopsida
- Lycopodiidae
- Selaginellales
- Polypodiopsida
- Polypodiidae
- Polypodiales
- Equisetidae
- Spermatopsida
- Cycads
- Conifers
- Taxaceae
- Cupressaceae
- Pinaceae
- Pinus
- Picea
- Larix
- Cedrus
- Abies
- Ginkgos
- Angiosperms
- Illicium
- magnoliids
- Piperales
- Piperaceae
- Magnoliales
- Annonaceae
- Myristicaceae
- Laurales
- Lauraceae
- Monocotyledons
- Alismatanae
- Aranae
- Liliaceae
- Asparagales
- Amaryllidaceae
- Asparagaceae
- Asphodelaceae
- Iridaceae
- Orchidaceae
- Dioscoreaceae
- Arecanae
- Cocoeae
- Phoeniceae
- Zingiberanae
- Musaceae
- Strelitziaceae
- Zingiberaceae
- Commelinanae
- Bromeliaceae
- Cyperaceae
- Typhaceae
- Poaceae
- Zea mays
- Triticum
- Bambusoideae
- eudicots
- Ranunculales
- Papaveraceae
- Ranunculaceae
- Proteales
- Proteaceae
- Nelumbo
- Core Eudicots
- Saxifragales
- Rosids
- Fabaceae
- Mimosoideae
- IRLC (Inverted Repat-lacking clade)
- Trifolieae
- Fabeae
- Rosales
- Rosaceae
- Rosa
- Malus pumila
- Ulmaceae
- Urticaceae
- Moraceae
- Cannabaceae
- Fagales
- Fagaceae
- Betulaceae
- Juglandaceae
- Cucurbitales
- Cucurbitaceae
- Malpighiales
- Salicaceae
- Violaceae
- Passifloraceae
- Erythroxylaceae
- Rhizophoraceae
- Euphorbiaceae
- Linaceae
- Rafflesiaceae
- Myrtales
- Myrtaceae
- Onagraceae
- Lythraceae
- Brassicales
- Caricaceae
- Brassicaceae
- Malvales
- Core Malvales
- Malvoideae
- Bombacoideae
- Sterculioideae
- Helicteroideae
- Byttnerioideae
- Sapindales
- Anacardiaceae
- Burseraceae
- Meliaceae
- Rutaceae
- Sapindaceae
- Vitaceae
- Caryophyllales
- Polygonaceae
- Droseraceae
- Nepenthaceae
- core Caryophyllales
- Cactaceae
- Amaranthaceae
- Asterids
- Ericales
- Actinidiaceae
- Ericaceae
- Lecythidaceae
- Sapotaceae
- Ebenaceae
- Theaceae
- Solanales
- Solanaceae
- Convolvulaceae
- Lamiales
- Oleaceae
- Fraxinus
- Bignoniaceae
- Pedaliaceae
- Lentibulariaceae
- Lamiaceae
- Gentianales
- Rubiaceae
- Asterales
- Campanulaceae
- Asteraceae
- Carduoideae
- Cardueae
- Cichorioideae
- Cichorieae
- Asteroideae
- Asterodae
- Helianthodae
- Apiales
- Apiaceae
- Araliaceae
- Aquifoliaceae
- Fungi
- Fungi 1
- Dikarya
- Basidiomycota
- Agaricomycotina
- Agaricomycetes
- Agaricomycetes 1
- Agaricomycetidae
- Agaricales
- Strophariaceae strict-sense
- Psathyrellaceae
- Agaricaceae
- Nidulariaceae
- Marasmiaceae
- Physalacriaceae
- Pleurotaceae
- Amanitaceae
- Podoserpula
- Boletales
- Serpulaceae
- Sclerodermataceae
- Boletaceae
- Russulales
- Hymenochaetales
- Phallomycetidae
- Geastrales
- Gomphales
- Phallales
- Cantharellales
- Auriculariales
- Tremellomycetes
- Ustilaginomycotina
- Pucciniomycotina
- Pucciniomycetes
- Septobasidiales
- Pucciniales
- Mixiomycetes
- Tritirachiomycetes
- Entorrhizomycetes
- Wallemiomycetes
- Ascomycota
- Pezizomycotina
- Pezizomycetes
- 'Leotiomyceta'
- Eurotiomycetes
- Geoglossaceae
- Sordariomycetes
- Hypocreomycetidae
- Sordariomycetidae
- Laboulbeniomycetes
- Pleosporomycetidae
- Saccharomycotina
- Taphrinomycotina
- Schizosaccharomycetes
- Pneumocystidiomycetes
- Taphrinomycetes
- Glomeromycota
- Zygomycota
- Endogonales
- Mucorales
- Blastocladiomycota
- Chytridiomycota
- Neocallimastigomycota
- Microsporidia
- Animalia
- Porifera
- Cnidaria
- Tardigrada
- Annelida
- Mollusca
- Bivalvia
- Gastropoda
- Cephalopoda
- Arthropoda
- Arachnida
- Araneae
- Opiliones
- Scorpiones
- Heterostigmata
- Crustacea
- Euphausiacea
- Brachyura
- Isopoda
- Cirripedia
- Insecta
- Anisoptera
- Mantodea
- Cicadoidea
- Siphonaptera
- Cucujoidea
- Phengodidae
- Drosophilidae
- Culicidae
- Lepidoptera
- Apini
- Formicidae
- Deuterostomia
- Echinodermata
- Crinoidea
- Asteroidea
- Echinoidea
- Holothuroidea
- Vertebrata
- Chondrichthyes
- Carcharodon carcharias
- Rhinocodon typus
- Batoidea
- Pristidae
- Actinopterygii
- Clupeomorpha
- Xiphias gladius
- Siluriformes
- Carassius auratus
- Tetraodontidae
- Molidae
- Gymnotiformes
- Lophiiformes
- Exocoetidae
- 'mudskipper'
- Hippocampus
- Psudoliparis swirei
- Sarcopterygii
- Tetrapoda
- Amphibia
- Gymnophiona
- Caudata
- Salamandra
- Cryptobranchidae
- Ambystomatidae
- Anura
- Reptilia
- Testudines
- Plesiosauria
- Chamaeleonidae
- Serpentes
- Crocodilia
- Dinosauria
- Triceratops
- Sauropoda
- Tyrannosauroidea
- Aves
- magpie
- parrot
- eagle
- owl
- swan
- chicken
- penguin
- hummingbird
- Synapsida
- monotreme
- marsupial
- kangaroo
- possum
- wombat
- rodent
- mouse
- beaver
- rabbit
- feline
- canine
- bear
- walrus
- Artiodactyla
- pig
- camel
- deer
- giraffe
- horse
- elephant
- cetacean
- armadillo
- bat
- monkey
- gorilla
- chimpanzee
- homo sapien
diff --git a/src/txtTreeToJSON.py b/src/txtTreeToJSON.py
deleted file mode 100755
index 3b77622..0000000
--- a/src/txtTreeToJSON.py
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/usr/bin/python3
-
-import sys, re
-
-usageInfo = f"usage: {sys.argv[0]}\n"
-usageInfo += "Reads, from stdin, tab-indented lines representing trees, and outputs corresponding JSON.\n"
-
-if len(sys.argv) > 1:
- print(usageInfo, file=sys.stderr)
- sys.exit(1)
-
-lineNum = 0
-trees = [] #each node is a pair holding a name and an array of child nodes
-nodeList = []
-while True:
- #read line
- line = sys.stdin.readline()
- if line == "": break
- line = line.rstrip()
- lineNum += 1
- #create node
- match = re.match(r"^\t*", line)
- indent = len(match.group())
- newNode = [line[indent:], []]
- #add node
- if indent == len(nodeList): #sibling or new tree
- if len(nodeList) == 0:
- nodeList.append(newNode)
- trees.append(newNode)
- else:
- nodeList[-1] = newNode
- if len(nodeList) == 1:
- trees[-1][1].append(newNode)
- else:
- nodeList[-2][1].append(newNode)
- elif indent == len(nodeList) + 1: #direct child
- if len(nodeList) == 0:
- print(f"ERROR: Child without preceding root (line {lineNum})")
- sys.exit(1)
- nodeList.append(newNode)
- nodeList[-2][1].append(newNode)
- elif indent < len(nodeList): #ancestor sibling or new tree
- nodeList = nodeList[:indent]
- if len(nodeList) == 0:
- nodeList.append(newNode)
- trees.append(newNode)
- else:
- nodeList[-1] = newNode
- if len(nodeList) == 1:
- trees[-1][1].append(newNode)
- else:
- nodeList[-2][1].append(newNode)
- else:
- print(f"ERROR: Child with invalid relative indent (line {lineNum})")
- sys.exit(1)
-#print as JSON
-if len(trees) > 1:
- print("[")
-def printNode(node, indent):
- if len(node[1]) == 0:
- print(indent + "{\"name\": \"" + node[0] + "\"}", end="")
- else:
- print(indent + "{\"name\": \"" + node[0] + "\", \"children\": [")
- for i in range(len(node[1])):
- printNode(node[1][i], indent + "\t")
- if i < len(node[1])-1:
- print(",", end="")
- print()
- print(indent + "]}", end="")
-for i in range(len(trees)):
- printNode(trees[i], "")
- if i < len(trees)-1:
- print(",", end="")
- print()
-if len(trees) > 1:
- print("]")