Skip to content

Commit

Permalink
🚧 Address potential null or undefined
Browse files Browse the repository at this point in the history
FIXME: turn on strictNullChecks
  • Loading branch information
victorlin committed Oct 29, 2024
1 parent 066d1e1 commit 260ba00
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 19 deletions.
3 changes: 3 additions & 0 deletions src/actions/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import { AppDispatch, RootState } from "../store";
*/
export const applyInViewNodesToTree = (idx: number | undefined, tree: TreeState) => {
const validIdxRoot = idx !== undefined ? idx : tree.idxOfInViewRootNode;

if (!tree.nodes || !tree.nodes[0] || !tree.nodes[validIdxRoot]) throw "Tree nodes not set!"

if (tree.nodes[0].shell) {
tree.nodes.forEach((d) => {
d.shell.inView = false;
Expand Down
3 changes: 3 additions & 0 deletions src/components/tree/phyloTree/change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ export const change = function change(this: PhyloTree, params: ChangeParams) {
scatterVariables = undefined
}: ChangeParams = params;

// Do nothing if render hasn't happened yet
if (this.timeLastRenderRequested === undefined) return;

// console.log("\n** phylotree.change() (time since last run:", Date.now() - this.timeLastRenderRequested, "ms) **\n\n");
timerStart("phylotree.change()");
const elemsToUpdate = new Set<TreeElement>(); /* what needs updating? E.g. ".branch", ".tip" etc */
Expand Down
16 changes: 14 additions & 2 deletions src/components/tree/phyloTree/layouts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,24 @@ export const scatterplotLayout = function scatterplotLayout(this: PhyloTree) {
* Utility function for the unrooted tree layout. See `unrootedLayout` for details.
*/
const unrootedPlaceSubtree = (node: PhyloNode, totalLeafWeight: number) => {
// FIXME: consider an UnrootedPhyloNode that guarantees presence of these? will need some casting...
if (node.depth === undefined ||
node.pDepth === undefined ||
node.px === undefined ||
node.py === undefined ||
node.tau === undefined ||
node.w === undefined ||
node.n.children === undefined
) {
return;
}
const branchLength = node.depth - node.pDepth;
node.x = node.px + branchLength * Math.cos(node.tau + node.w * 0.5);
node.y = node.py + branchLength * Math.sin(node.tau + node.w * 0.5);
let eta = node.tau; // eta is the cumulative angle for the wedges in the layout
if (node.n.hasChildren) {
for (let i = 0; i < node.n.children.length; i++) {
const ch = node.n.children[i].shell;
for (const child of node.n.children) {
const ch = child.shell;
ch.w = 2 * Math.PI * leafWeight(ch.n) / totalLeafWeight;
ch.tau = eta;
eta += ch.w;
Expand Down Expand Up @@ -175,6 +186,7 @@ export const unrootedLayout = function unrootedLayout(this: PhyloTree) {
for each subtree's root, which will be used by the children of that root
Note 2: Angles will start from `eta` as defined below, and then cover ~2*Pi radians
*/
if (this.nodes[0] === undefined) return;
const totalLeafWeight = leafWeight(this.nodes[0].n);
let eta = 1.5 * Math.PI;
const children = this.nodes[0].n.children; // <Node>
Expand Down
9 changes: 9 additions & 0 deletions src/components/tree/phyloTree/regression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export interface Regression {
* The regression is forced to pass through nodes[0].
*/
function calculateRegressionThroughRoot(nodes: PhyloNode[]): Regression {
if (!nodes[0]) {
throw new Error("`nodes` must contain at least one entry to calculate the regression through the root.");
}
const terminalNodes = nodes.filter((d) => !d.n.hasChildren && d.visibility === NODE_VISIBLE);
const nTips = terminalNodes.length;
if (nTips===0) {
Expand Down Expand Up @@ -66,6 +69,12 @@ export function calculateRegression(this: PhyloTree) {
}

export function makeRegressionText(regression: Regression, layout: string, yScale: any): string {
if (regression.intercept === undefined ||
regression.slope === undefined ||
regression.r2 === undefined) {
return "";
}

if (layout==="clock") {
if (guessAreMutationsPerSite(yScale)) {
return `rate estimate: ${regression.slope.toExponential(2)} subs per site per year`;
Expand Down
20 changes: 11 additions & 9 deletions src/components/tree/phyloTree/renderers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export const render = function render(
export const drawVaccines = function drawVaccines(this: PhyloTree) {
if (!this.vaccines || !this.vaccines.length) return;

if (!("vaccines" in this.groups)) {
if (this.groups.vaccines === undefined) {
this.groups.vaccines = this.svg.append("g").attr("id", "vaccines");
}
this.groups.vaccines
Expand Down Expand Up @@ -154,7 +154,7 @@ export const drawVaccines = function drawVaccines(this: PhyloTree) {
export const drawTips = function drawTips(this: PhyloTree) {
timerStart("drawTips");
const params = this.params;
if (!("tips" in this.groups)) {
if (this.groups.tips === undefined) {
this.groups.tips = this.svg.append("g").attr("id", "tips").attr("clip-path", "url(#treeClip)");
}
this.groups.tips
Expand Down Expand Up @@ -227,7 +227,7 @@ export const drawBranches = function drawBranches(this: PhyloTree) {

/* PART 1: draw the branch Ts (i.e. the bit connecting nodes parent branch ends to child branch beginnings)
Only rectangular & radial trees have this, so we remove it for clock / unrooted layouts */
if (!("branchTee" in this.groups)) {
if (this.groups.branchTee === undefined) {
this.groups.branchTee = this.svg.append("g").attr("id", "branchTee").attr("clip-path", "url(#treeClip)");
}
if (this.layout === "clock" || this.layout === "scatter" || this.layout === "unrooted") {
Expand Down Expand Up @@ -262,7 +262,7 @@ export const drawBranches = function drawBranches(this: PhyloTree) {
// TODO -- explore if duplicate <def> elements (e.g. same colours on each end) slow things down
this.updateColorBy();
/* PART 2b: Draw the stems */
if (!("branchStem" in this.groups)) {
if (this.groups.branchStem === undefined) {
this.groups.branchStem = this.svg.append("g").attr("id", "branchStem").attr("clip-path", "url(#treeClip)");
}
this.groups.branchStem
Expand Down Expand Up @@ -297,7 +297,9 @@ export const drawBranches = function drawBranches(this: PhyloTree) {
// FIXME: check this
export const drawRegression = function drawRegression(this: PhyloTree<"clock">) {
/* check we have computed a sensible regression before attempting to draw */
if (this.regression.slope===undefined) {
if (this.regression === undefined ||
this.regression.intercept === undefined ||
this.regression.slope === undefined) {
return;
}

Expand All @@ -307,7 +309,7 @@ export const drawRegression = function drawRegression(this: PhyloTree<"clock">)
const path = "M " + this.xScale.range()[0].toString() + " " + leftY.toString() +
" L " + this.xScale.range()[1].toString() + " " + rightY.toString();

if (!("regression" in this.groups)) {
if (!this.groups.regression) {
this.groups.regression = this.svg.append("g").attr("id", "regression").attr("clip-path", "url(#treeClip)");
}

Expand Down Expand Up @@ -335,7 +337,7 @@ export const drawRegression = function drawRegression(this: PhyloTree<"clock">)
};

export const removeRegression = function removeRegression(this: PhyloTree) {
if ("regression" in this.groups) {
if (this.groups.regression !== undefined) {
this.groups.regression.selectAll("*").remove();
}
};
Expand Down Expand Up @@ -414,12 +416,12 @@ const handleBranchHoverColor = (d: PhyloNode, c1: string, c2: string) => {
};

export const branchStrokeForLeave = function branchStrokeForLeave(d: PhyloNode) {
if (!d) { return; }
if (!d || !d.n.parent.shell.branchStroke || !d.branchStroke) { return; }
handleBranchHoverColor(d, d.n.parent.shell.branchStroke, d.branchStroke);
};

export const branchStrokeForHover = function branchStrokeForHover(d: PhyloNode) {
if (!d) { return; }
if (!d || !d.n.parent.shell.branchStroke || !d.branchStroke) { return; }
handleBranchHoverColor(d, getEmphasizedColor(d.n.parent.shell.branchStroke), getEmphasizedColor(d.branchStroke));
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/tree/reactD3Interface/initialRender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const renderTree = (
) => {
const ref = main ? that.domRefs.mainTree : that.domRefs.secondTree;
const treeState = main ? props.tree : props.treeToo;
if (!treeState.loaded) {
if (!treeState.loaded || ref === undefined) {
console.warn("can't run renderTree (not loaded)");
return;
}
Expand Down
25 changes: 18 additions & 7 deletions src/components/tree/tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ export interface TreeNode {

// FIXME: is this Partial<TreeComponentProps>?
export interface TreeComponentState {
hoveredNode: TreeNode | null
tree: PhyloTree | null
treeToo: PhyloTree | null
hoveredNode?: TreeNode
tree?: PhyloTree
treeToo?: PhyloTree
geneSortFn?: any
selectedNode?: Record<string, never>
}
Expand All @@ -128,9 +128,9 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
};
this.tangleRef = undefined;
this.state = {
hoveredNode: null,
tree: null,
treeToo: null
hoveredNode: undefined,
tree: undefined,
treeToo: undefined
};

/* bind callbacks */
Expand All @@ -151,6 +151,10 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
}

setUpAndRenderTreeToo(props: TreeComponentProps, newState: Partial<TreeComponentState>) {
if (newState.treeToo === undefined) {
return;
}

/* this.setState(newState) will be run sometime after this returns */
/* modifies newState in place */
newState.treeToo = new PhyloTreeConstructor(props.treeToo.nodes, rhsTreeId, props.treeToo.idxOfInViewRootNode);
Expand All @@ -165,6 +169,9 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
if (this.props.tree.loaded) {
const newState: Partial<TreeComponentState> = {};
newState.tree = new PhyloTreeConstructor(this.props.tree.nodes, lhsTreeId, this.props.tree.idxOfInViewRootNode);
if (newState.tree === undefined) {
return;
}
renderTree(this, true, newState.tree, this.props);
if (this.props.showTreeToo) {
this.setUpAndRenderTreeToo(this.props, newState as TreeComponentState); /* modifies newState in place */
Expand All @@ -175,6 +182,10 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
}

override componentDidUpdate(prevProps: TreeComponentProps) {
if (this.state.tree === undefined) {
return;
}

let newState: Partial<TreeComponentState> = {};
let rightTreeUpdated = false;

Expand All @@ -188,7 +199,7 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
/* has the 2nd (right hand) tree just been turned on, off or swapped? */
if (prevProps.showTreeToo !== this.props.showTreeToo) {
if (!this.props.showTreeToo) { /* turned off -> remove the 2nd tree */
newState.treeToo = null;
newState.treeToo = undefined;
} else { /* turned on -> render the 2nd tree */
if (this.state.treeToo) { /* tree has been swapped -> remove the old tree */
this.state.treeToo.clearSVG();
Expand Down
2 changes: 2 additions & 0 deletions src/reducers/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ const Tree = (state: TreeState = getDefaultTreeState(), action: AnyAction): Tree
case types.TREE_TOO_DATA:
return action.tree;
case types.ADD_EXTRA_METADATA: {
if (state.nodes === undefined || state.nodes === null) return state;

// add data into `nodes` in-place, so no redux update will be triggered if you only listen to `nodes`
addNodeAttrs(state.nodes, action.newNodeAttrs);
// add the new nodeAttrKeys to ensure tip labels get updated
Expand Down

0 comments on commit 260ba00

Please sign in to comment.