Skip to content

Commit

Permalink
🚧 Remove non-null assertions
Browse files Browse the repository at this point in the history
Handle these cases better.
  • Loading branch information
victorlin committed Oct 25, 2024
1 parent 19bd740 commit 864ed12
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 38 deletions.
17 changes: 10 additions & 7 deletions src/actions/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@ import { PhyloNode } from "../components/tree/phyloTree/types";
*/
export const applyInViewNodesToTree = (idx: number | undefined, tree: TreeState) => {
const validIdxRoot = idx !== undefined ? idx : tree.idxOfInViewRootNode;
if (tree.nodes[0]!.shell) {

if (!tree.nodes[0] || !tree.nodes[validIdxRoot]) return;

if (tree.nodes[0].shell) {
tree.nodes.forEach((d) => {
d.shell.inView = false;
d.shell.update = true;
});
if (tree.nodes[validIdxRoot]!.hasChildren) {
applyToChildren(tree.nodes[validIdxRoot]!.shell, (d: PhyloNode) => {d.inView = true;});
} else if (tree.nodes[validIdxRoot]!.parent.arrayIdx===0) {
if (tree.nodes[validIdxRoot].hasChildren) {

Check failure on line 36 in src/actions/tree.ts

View workflow job for this annotation

GitHub Actions / type-check

Object is possibly 'undefined'.
applyToChildren(tree.nodes[validIdxRoot].shell, (d: PhyloNode) => {d.inView = true;});

Check failure on line 37 in src/actions/tree.ts

View workflow job for this annotation

GitHub Actions / type-check

Object is possibly 'undefined'.
} else if (tree.nodes[validIdxRoot].parent.arrayIdx===0) {

Check failure on line 38 in src/actions/tree.ts

View workflow job for this annotation

GitHub Actions / type-check

Object is possibly 'undefined'.
// subtree with n=1 tips => don't make the parent in-view as this will cover the entire tree!
tree.nodes[validIdxRoot]!.shell.inView = true;
tree.nodes[validIdxRoot].shell.inView = true;

Check failure on line 40 in src/actions/tree.ts

View workflow job for this annotation

GitHub Actions / type-check

Object is possibly 'undefined'.
} else {
applyToChildren(tree.nodes[validIdxRoot]!.parent.shell, (d: PhyloNode) => {d.inView = true;});
applyToChildren(tree.nodes[validIdxRoot].parent.shell, (d: PhyloNode) => {d.inView = true;});

Check failure on line 42 in src/actions/tree.ts

View workflow job for this annotation

GitHub Actions / type-check

Object is possibly 'undefined'.
}
} else {
/* FYI applyInViewNodesToTree is now setting inView on the redux nodes */
Expand All @@ -50,7 +53,7 @@ export const applyInViewNodesToTree = (idx: number | undefined, tree: TreeState)
for (const child of node.children) _markChildrenInView(child);
}
};
const startingNode = tree.nodes[validIdxRoot]!.hasChildren ? tree.nodes[validIdxRoot] : tree.nodes[validIdxRoot]!.parent;
const startingNode = tree.nodes[validIdxRoot].hasChildren ? tree.nodes[validIdxRoot] : tree.nodes[validIdxRoot].parent;

Check failure on line 56 in src/actions/tree.ts

View workflow job for this annotation

GitHub Actions / type-check

Object is possibly 'undefined'.

Check failure on line 56 in src/actions/tree.ts

View workflow job for this annotation

GitHub Actions / type-check

Object is possibly 'undefined'.
_markChildrenInView(startingNode);
}

Expand Down
6 changes: 5 additions & 1 deletion src/components/tree/phyloTree/change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ export const change = function change(this: PhyloTree, params: ChangeParams) {
scatterVariables = undefined
}: ChangeParams = params;

// Return if render hasn't happened yet
if (!this.timeLastRenderRequested) return;

// console.log("\n** phylotree.change() (time since last run:", Date.now() - this.timeLastRenderRequested, "ms) **\n\n");
timerStart("phylotree.change()");
const elemsToUpdate = new Set(); /* what needs updating? E.g. ".branch", ".tip" etc */
Expand All @@ -341,7 +344,8 @@ export const change = function change(this: PhyloTree, params: ChangeParams) {
/* calculate dt */
const idealTransitionTime = 500;
let transitionTime = idealTransitionTime;
if ((Date.now() - this.timeLastRenderRequested!) < idealTransitionTime * 2) {

if ((Date.now() - this.timeLastRenderRequested) < idealTransitionTime * 2) {
transitionTime = 0;
}

Expand Down
23 changes: 17 additions & 6 deletions src/components/tree/phyloTree/layouts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,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) => {
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
// 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
15 changes: 12 additions & 3 deletions src/components/tree/phyloTree/regression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ 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) {
return {slope: undefined, intercept: undefined, r2: undefined};
}
const offset = nodes[0]!.x;
const offset = nodes[0].x;
const XY = sum(
terminalNodes.map((d) => (d.y) * (d.x - offset))
) / nTips;
Expand Down Expand Up @@ -66,11 +69,17 @@ 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`;
return `rate estimate: ${regression.slope.toExponential(2)} subs per site per year`;
}
return `rate estimate: ${formatDivergence(regression.slope)} subs per year`;
}
return `intercept = ${regression.intercept!.toPrecision(3)}, slope = ${regression.slope!.toPrecision(3)}, R^2 = ${regression.r2!.toPrecision(3)}`;
return `intercept = ${regression.intercept.toPrecision(3)}, slope = ${regression.slope.toPrecision(3)}, R^2 = ${regression.r2.toPrecision(3)}`;
}
33 changes: 21 additions & 12 deletions src/components/tree/phyloTree/renderers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ export const drawTips = function drawTips(this: PhyloTree) {
if (!("tips" in this.groups)) {
this.groups.tips = this.svg.append("g").attr("id", "tips").attr("clip-path", "url(#treeClip)");
}
this.groups.tips!
if (!this.groups.tips) throw "tips should be set at this point!"

this.groups.tips
.selectAll(".tip")
.data(this.nodes.filter((d) => !d.n.hasChildren))
.enter()
Expand Down Expand Up @@ -230,10 +232,12 @@ export const drawBranches = function drawBranches(this: PhyloTree) {
if (!("branchTee" in this.groups)) {
this.groups.branchTee = this.svg.append("g").attr("id", "branchTee").attr("clip-path", "url(#treeClip)");
}
if (!this.groups.branchTee) throw "branchTee should be set at this point!"

if (this.layout === "clock" || this.layout === "scatter" || this.layout === "unrooted") {
this.groups.branchTee!.selectAll("*").remove();
this.groups.branchTee.selectAll("*").remove();
} else {
this.groups.branchTee!
this.groups.branchTee
.selectAll('.branch')
.data(this.nodes.filter((d) => d.n.hasChildren && d.displayOrder !== undefined))
.enter()
Expand Down Expand Up @@ -265,7 +269,9 @@ export const drawBranches = function drawBranches(this: PhyloTree) {
if (!("branchStem" in this.groups)) {
this.groups.branchStem = this.svg.append("g").attr("id", "branchStem").attr("clip-path", "url(#treeClip)");
}
this.groups.branchStem!
if (!this.groups.branchStem) throw "branchStem should be set at this point!"

this.groups.branchStem
.selectAll('.branch')
.data(this.nodes.filter((d) => d.displayOrder !== undefined))
.enter()
Expand Down Expand Up @@ -296,21 +302,24 @@ export const drawBranches = function drawBranches(this: PhyloTree) {
*/
export const drawRegression = function drawRegression(this: PhyloTree) {
/* 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;
}

const leftY = this.yScale(this.regression!.intercept! + this.xScale.domain()[0] * this.regression!.slope);
const rightY = this.yScale(this.regression!.intercept! + this.xScale.domain()[1] * this.regression!.slope);
const leftY = this.yScale(this.regression.intercept + this.xScale.domain()[0] * this.regression.slope);
const rightY = this.yScale(this.regression.intercept + this.xScale.domain()[1] * this.regression.slope);

const path = "M " + this.xScale.range()[0].toString() + " " + leftY.toString() +
" L " + this.xScale.range()[1].toString() + " " + rightY.toString();

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

this.groups.regression!
this.groups.regression
.append("path")
.attr("d", path)
.attr("class", "regression")
Expand All @@ -321,9 +330,9 @@ export const drawRegression = function drawRegression(this: PhyloTree) {

/* Compute & draw regression text. Note that the text hasn't been created until now,
as we need to wait until rendering time when the scales have been calculated */
this.groups.regression!
this.groups.regression
.append("text")
.text(makeRegressionText(this.regression!, this.layout, this.yScale))
.text(makeRegressionText(this.regression, this.layout, this.yScale))
.attr("class", "regression")
.attr("x", this.xScale.range()[1] / 2 - 75)
.attr("y", this.yScale.range()[0] + 50)
Expand Down Expand Up @@ -413,8 +422,8 @@ const handleBranchHoverColor = (d: PhyloNode, c1: string, c2: string) => {
};

export const branchStrokeForLeave = function branchStrokeForLeave(d: PhyloNode) {
if (!d) { return; }
handleBranchHoverColor(d, d.n.parent.shell.branchStroke!, d.branchStroke!);
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) {
Expand Down
4 changes: 2 additions & 2 deletions 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 All @@ -26,7 +26,7 @@ export const renderTree = (
const tipStrokeColors = calculateStrokeColors(treeState, false, props.colorByConfidence, props.colorBy);

phylotree.render({
svg: select(ref!),
svg: select(ref),
layout: props.layout,
distance: props.distanceMeasure,
focus: props.focus,
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 @@ -91,8 +91,8 @@ export interface TreeComponentPropsFromState {
// FIXME: is this Partial<TreeComponentProps>?
export interface TreeComponentState {
hoveredNode: PhyloNode | null
tree: PhyloTree | null
treeToo: PhyloTree | null
tree?: PhyloTree
treeToo?: PhyloTree
geneSortFn?: any
selectedNode?: {}
}
Expand All @@ -117,8 +117,8 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
this.tangleRef = undefined;
this.state = {
hoveredNode: null,
tree: null,
treeToo: null
tree: undefined,
treeToo: undefined
};

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

setUpAndRenderTreeToo(props: TreeComponentProps, newState: 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 @@ -153,7 +157,10 @@ 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);
renderTree(this, true, newState.tree!, this.props);
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 @@ -163,20 +170,24 @@ 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;

/* potentially change the (main / left hand) tree */
const {
newState: potentialNewState,
change: leftTreeUpdated,
} = changePhyloTreeViaPropsComparison(true, this.state.tree!, prevProps, this.props);
} = changePhyloTreeViaPropsComparison(true, this.state.tree, prevProps, this.props);
if (potentialNewState) newState = potentialNewState;

/* 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

0 comments on commit 864ed12

Please sign in to comment.