Skip to content

Commit

Permalink
Unify common code for loading a second tree
Browse files Browse the repository at this point in the history
There are two ways of loading a second tree: upon initial load /
narrative slide progression (logic within `createStateFromQueryOrJSONs`)
or via the sidebar "second tree" UI (logic within `createTreeTooState`).
This commit aims to reduce the duplication of code between these functions,
where possible given the current design.

There is one behavioural change - the second method of loading a tree
now no longer skips the `castIncorrectTypes` error correction function.

Tangentially: the code in this file is fragile and the order of function
calls is crucial. I hope the adoption of TS will improve resilience
here.
  • Loading branch information
jameshadfield committed Jun 11, 2024
1 parent c11938d commit 62b6dd2
Showing 1 changed file with 51 additions and 55 deletions.
106 changes: 51 additions & 55 deletions src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,26 +692,6 @@ const modifyTreeStateVisAndBranchThickness = (oldState, zoomSelected, controlsSt
return newState;
};

const removePanelIfPossible = (panels, name) => {
const idx = panels.indexOf(name);
if (idx !== -1) {
panels.splice(idx, 1);
}
};

const modifyControlsViaTreeToo = (controls, name) => {
controls.showTreeToo = name;
controls.showTangle = true;
controls.layout = "rect"; /* must be rectangular for two trees */
controls.panelsToDisplay = controls.panelsToDisplay.slice();
removePanelIfPossible(controls.panelsToDisplay, "map");
removePanelIfPossible(controls.panelsToDisplay, "entropy");
removePanelIfPossible(controls.panelsToDisplay, "frequencies");
controls.canTogglePanelLayout = false;
controls.panelLayout = "full";
return controls;
};

/**
* The v2 JSON spec defines colorings as a list, so that order is guaranteed.
* Prior to this, we used a dict, where key insertion order is (guaranteed? essentially always?)
Expand Down Expand Up @@ -879,14 +859,7 @@ export const createStateFromQueryOrJSONs = ({
tree.name = mainTreeName;
metadata.mainTreeNumTips = calcTotalTipsInTree(tree.nodes);
if (secondTreeDataset) {
treeToo = treeJsonToState(secondTreeDataset.tree);
castIncorrectTypes(metadata, treeToo);
treeToo.debug = "RIGHT";
treeToo.name = secondTreeName;
updateMetadataStateViaSecondTree(metadata, secondTreeDataset, entropy?.genomeMap)

/* TODO: calc & display num tips in 2nd tree */
// metadata.secondTreeNumTips = calcTotalTipsInTree(treeToo.nodes);
({treeToo, metadata} = instantiateSecondTree(secondTreeDataset, metadata, entropy?.genomeMap, secondTreeName));
}

/* new controls state - don't apply query yet (or error check!) */
Expand Down Expand Up @@ -967,11 +940,7 @@ export const createStateFromQueryOrJSONs = ({
tree = modifyTreeStateVisAndBranchThickness(tree, query.label, controls, dispatch);

if (treeToo && treeToo.loaded) {
treeToo.nodeColorsVersion = tree.nodeColorsVersion;
treeToo.nodeColors = calcNodeColor(treeToo, controls.colorScale);
treeToo = modifyTreeStateVisAndBranchThickness(treeToo, undefined, controls, dispatch);
controls = modifyControlsViaTreeToo(controls, treeToo.name);
treeToo.tangleTipLookup = constructVisibleTipLookupBetweenTrees(tree.nodes, treeToo.nodes, tree.visibility, treeToo.visibility);
treeToo = updateSecondTree(tree, treeToo, controls, dispatch)
}

/* we can only calculate which legend items we wish to display _after_ the visibility has been calculated */
Expand Down Expand Up @@ -1046,33 +1015,60 @@ export const createTreeTooState = ({
/* TODO: reconcile choices (filters, colorBys etc) with this new tree */
/* TODO: reconcile query with visibility etc */

const metadata = {...oldState.metadata};
updateMetadataStateViaSecondTree(metadata, json, oldState.entropy?.genomeMap);

let controls = {...oldState.controls};
const tree = Object.assign({}, oldState.tree);
tree.name = originalTreeUrl;
let treeToo = treeJsonToState(json.tree);
treeToo.name = secondTreeUrl;
treeToo.debug = "RIGHT";
controls = modifyControlsStateViaTree(controls, tree, treeToo, oldState.metadata.colorings);
controls = modifyControlsViaTreeToo(controls, secondTreeUrl);
treeToo = modifyTreeStateVisAndBranchThickness(treeToo, undefined, controls, dispatch);
let {treeToo, metadata} = instantiateSecondTree(json, oldState.metadata, oldState.entropy?.genomeMap, secondTreeUrl)

/* calculate colours if loading from JSONs or if the query demands change */
const colorScale = calcColorScale(controls.colorBy, controls, tree, treeToo, metadata);
const nodeColors = calcNodeColor(treeToo, colorScale);
tree.nodeColors = calcNodeColor(tree, colorScale); // also update main tree's colours
tree.nodeColorsVersion++;

controls.colorScale = colorScale;
/* recompute the controls state now that we have new data */
const controls = modifyControlsStateViaTree({...oldState.controls}, tree, treeToo, oldState.metadata.colorings);
/* recalculate the color scale with updated tree data */
controls.colorScale = calcColorScale(controls.colorBy, controls, tree, treeToo, metadata);
controls.colorByConfidence = doesColorByHaveConfidence(controls, controls.colorBy);
treeToo.nodeColorsVersion = colorScale.version;
treeToo.nodeColors = nodeColors;
/* and update the color scale as applied to the LHS tree */
tree.nodeColors = calcNodeColor(tree, controls.colorScale);
tree.nodeColorsVersion++;

treeToo.tangleTipLookup = constructVisibleTipLookupBetweenTrees(
tree.nodes, treeToo.nodes, tree.visibility, treeToo.visibility
);
treeToo = updateSecondTree(tree, treeToo, controls, dispatch);

return {tree, treeToo, controls, metadata};
};


/**
* Code which is common to both loading a second tree within `createStateFromQueryOrJSONs` and
* `createTreeTooState`.
*/
function instantiateSecondTree(secondTreeDataset, metadata, genomeMap, secondTreeName) {
const treeToo = treeJsonToState(secondTreeDataset.tree);
castIncorrectTypes(metadata, treeToo);
treeToo.debug = "RIGHT";
treeToo.name = secondTreeName;
updateMetadataStateViaSecondTree({...metadata}, secondTreeDataset, genomeMap)

/* TODO: calc & display num tips in 2nd tree */
// metadata.secondTreeNumTips = calcTotalTipsInTree(treeToo.nodes);

return {treeToo, metadata}
}

/**
* Update colours and control state options. This function requires that the controls state
* has been instantiated (e.g. the colorScale has been computed)
*/
function updateSecondTree(tree, treeToo, controls, dispatch) {
treeToo.nodeColorsVersion = tree.nodeColorsVersion;
treeToo.nodeColors = calcNodeColor(treeToo, controls.colorScale);
treeToo = modifyTreeStateVisAndBranchThickness(treeToo, undefined, controls, dispatch);
treeToo.tangleTipLookup = constructVisibleTipLookupBetweenTrees(tree.nodes, treeToo.nodes, tree.visibility, treeToo.visibility);

/* modify controls */
controls.showTreeToo = treeToo.name;
controls.showTangle = true;
controls.layout = "rect"; /* must be rectangular for two trees */
controls.panelsToDisplay = controls.panelsToDisplay
.filter((name) => !["map", "entropy", "frequencies"].includes(name));
controls.canTogglePanelLayout = false;
controls.panelLayout = "full";

return treeToo;
}

0 comments on commit 62b6dd2

Please sign in to comment.