Skip to content

Commit

Permalink
🚧 merge calcYValues into setDisplayOrder
Browse files Browse the repository at this point in the history
  • Loading branch information
victorlin committed Oct 9, 2024
1 parent 7095407 commit 9a2bdb4
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 52 deletions.
46 changes: 39 additions & 7 deletions src/components/tree/phyloTree/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable no-param-reassign */
import { max } from "d3-array";
import {getTraitFromNode, getDivFromNode, getBranchMutations} from "../../../util/treeMiscHelpers";
import { NODE_VISIBLE } from "../../../util/globals";

/** get a string to be used as the DOM element ID
* Note that this cannot have any "special" characters
Expand Down Expand Up @@ -33,19 +34,20 @@ export const applyToChildren = (phyloNode, func) => {
* of nodes in a rectangular tree.
* If `yCounter` is undefined then we wish to hide the node and all descendants of it
* @param {PhyloNode} node
* @param {function} getDisplayOrder
* @param {int|undefined} yCounter
* @sideeffect modifies node.displayOrder and node.displayOrderRange
* @returns {int|undefined} current yCounter after assignment to the tree originating from `node`
*/
export const setDisplayOrderRecursively = (node, yCounter) => {
export const setDisplayOrderRecursively = (node, getDisplayOrder, yCounter) => {
const children = node.n.children; // (redux) tree node
if (children && children.length) {
for (let i = children.length - 1; i >= 0; i--) {
yCounter = setDisplayOrderRecursively(children[i].shell, yCounter);
yCounter = setDisplayOrderRecursively(children[i].shell, getDisplayOrder, yCounter);
}
} else {
node.displayOrder = (node.n.fullTipCount===0 || yCounter===undefined) ? yCounter : ++yCounter;
node.displayOrderRange = [yCounter, yCounter];
node.displayOrder = (node.n.fullTipCount===0 || yCounter===undefined) ? yCounter : yCounter + getDisplayOrder(node);
node.displayOrderRange = [node.displayOrder, node.displayOrder];
return yCounter;
}
/* if here, then all children have displayOrders, but we don't. */
Expand Down Expand Up @@ -78,19 +80,21 @@ function _getSpaceBetweenSubtrees(numSubtrees, numTips) {
* rectangularLayout, radialLayout, createChildrenAndParents
* side effects: <phyloNode>.displayOrder (i.e. in the redux node) and <phyloNode>.displayOrderRange
* @param {Array<PhyloNode>} nodes
* @param {boolean} focus
* @returns {undefined}
*/
export const setDisplayOrder = (nodes) => {
export const setDisplayOrder = (nodes, focus) => {
const getDisplayOrder = getDisplayOrderCallback(nodes, focus);
const numSubtrees = nodes[0].n.children.filter((n) => n.fullTipCount!==0).length;
const numTips = nodes[0].n.fullTipCount;
const spaceBetweenSubtrees = _getSpaceBetweenSubtrees(numSubtrees, numTips);
let yCounter = 0;
/* iterate through each subtree, and add padding between each */
for (const subtree of nodes[0].n.children) {
if (subtree.fullTipCount===0) { // don't use screen space for this subtree
setDisplayOrderRecursively(nodes[subtree.arrayIdx], undefined);
setDisplayOrderRecursively(nodes[subtree.arrayIdx], getDisplayOrder, undefined);
} else {
yCounter = setDisplayOrderRecursively(nodes[subtree.arrayIdx], yCounter);
yCounter = setDisplayOrderRecursively(nodes[subtree.arrayIdx], getDisplayOrder, yCounter);
yCounter+=spaceBetweenSubtrees;
}
}
Expand All @@ -99,6 +103,34 @@ export const setDisplayOrder = (nodes) => {
nodes[0].displayOrderRange = [undefined, undefined];
};

/**
* @param {Array<PhyloNode>} nodes
* @param {boolean} focus
* @returns fn to return a display order (y position) for a node
*/
function getDisplayOrderCallback(nodes, focus) {
/**
* Start at 0 and increase with each node.
* Note that this value is shared across invocations of the callback.
*/
let displayOrder = 0;

if (focus) {
const nSelected = nodes.filter((d) => !d.hasChildren && d.visibility === NODE_VISIBLE).length;
const yPerSelected = (0.8 * nodes.length) / nSelected;
const yPerUnselected = (0.2 * nodes.length) / (nodes.length - nSelected);
return (node) => {
displayOrder += node.visibility === NODE_VISIBLE ? yPerSelected : yPerUnselected;
return displayOrder;
};
} else {
// No focus: 1 unit per node
return (node) => {
displayOrder += 1;
return displayOrder;
};
}
}

export const formatDivergence = (divergence) => {
return divergence > 1 ?
Expand Down
47 changes: 2 additions & 45 deletions src/components/tree/phyloTree/layouts.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import scaleLinear from "d3-scale/src/linear";
import {point as scalePoint} from "d3-scale/src/band";
import { timerStart, timerEnd } from "../../../util/perf";
import { getTraitFromNode, getDivFromNode } from "../../../util/treeMiscHelpers";
import { stemParent, nodeOrdering } from "./helpers";
import { setDisplayOrder, stemParent, nodeOrdering } from "./helpers";
import { numDate } from "../../../util/colorHelpers";
import { NODE_VISIBLE } from "../../../util/globals";

/**
* assigns the attribute this.layout and calls the function that
Expand Down Expand Up @@ -289,55 +288,13 @@ export const setDistance = function setDistance(distanceAttribute) {
timerEnd("setDistance");
};

/**
* given nodes add y values (node.displayOrder) to every node
* Nodes are the phyloTree nodes (i.e. node.n is the redux node)
* Nodes must have parent child links established (via createChildrenAndParents)
* PhyloTree can subsequently use this information. Accessed by prototypes
* rectangularLayout, radialLayout, createChildrenAndParents
* side effects: node.displayOrder and node.displayOrderRange (i.e. in the phyloTree node)
*/
export const calcYValues = (nodes, focus) => {
let total = 0; /* cumulative counter of y value at tip */
let calcY; /* fn called calcY(node) to return some amount of y value at a tip */
if (focus && 'visibility' in nodes[0]) {
const numberOfTips = nodes.length;
const numTipsVisible = nodes.filter((d) => !d.hasChildren && d.visibility === NODE_VISIBLE).length;
const yPerVisible = (0.8 * numberOfTips) / numTipsVisible;
const yPerNotVisible = (0.2 * numberOfTips) / (numberOfTips - numTipsVisible);
calcY = (node) => {
total += node.visibility === NODE_VISIBLE ? yPerVisible : yPerNotVisible;
return total;
};
} else { /* fall back to no focus */
calcY = () => ++total;
}

const recurse = (node) => {
const children = node.n.children; // (redux) tree node
if (children && children.length) {
for (let i = children.length - 1; i >= 0; i--) {
recurse(children[i].shell);
}
} else {
node.displayOrder = calcY(node);
node.displayOrderRange = [node.displayOrder, node.displayOrder];
return;
}
/* if here, then all children have yvalues, but we dont. */
node.displayOrder = children.reduce((acc, d) => acc + d.shell.displayOrder, 0) / children.length;
node.displayOrderRange = [children[0].shell.displayOrder, children[children.length - 1].shell.displayOrder];
};
recurse(nodes[0]);
};

/**
* Recalculates y values based on focus setting
* @param treeFocus -- whether to focus on filtered nodes
*/
export const setTreeFocus = function setTreeFocus(treeFocus) {
timerStart("setTreeFocus");
calcYValues(this.nodes, treeFocus || false);
setDisplayOrder(this.nodes, treeFocus || false);
timerEnd("setTreeFocus");
};

Expand Down

0 comments on commit 9a2bdb4

Please sign in to comment.