diff options
| author | Terry Truong <terry06890@gmail.com> | 2022-04-25 01:33:08 +1000 |
|---|---|---|
| committer | Terry Truong <terry06890@gmail.com> | 2022-04-25 01:33:08 +1000 |
| commit | 2ab48497797441164e7f57fca2660097d93398ca (patch) | |
| tree | a6f22d3edff60d182de454359bc40beda82fb5d8 /src | |
| parent | 23436a9ad4c2a710c7f0d49a07a720e0153d8225 (diff) | |
Adapt to handle open-tree-of-life data
Added data_otol/ with script that converts data from 'Open Tree of Life' release 13.4 into a JSON form.
Moved old tree-of-life data and images into data_tol_old/.
Added TolMap type to tol.ts, changed TolNode, and adapted other code to handle it.
Temporarily disabling tile images until image data is added.
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.vue | 84 | ||||
| -rw-r--r-- | src/components/AncestryBar.vue | 10 | ||||
| -rw-r--r-- | src/components/SearchModal.vue | 13 | ||||
| -rw-r--r-- | src/components/Tile.vue | 25 | ||||
| -rw-r--r-- | src/components/TileInfoModal.vue | 9 | ||||
| -rwxr-xr-x | src/genTestImgs.sh | 16 | ||||
| -rw-r--r-- | src/layout.ts | 48 | ||||
| -rw-r--r-- | src/tol.ts | 49 | ||||
| -rw-r--r-- | src/tolData.txt | 388 | ||||
| -rwxr-xr-x | src/txtTreeToJSON.py | 76 |
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: @@ -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("]") |
