diff --git a/narratives/test_simultaneous-tree-updates.md b/narratives/test_simultaneous-tree-updates.md index 5380f08a5..0ff00a892 100644 --- a/narratives/test_simultaneous-tree-updates.md +++ b/narratives/test_simultaneous-tree-updates.md @@ -19,6 +19,10 @@ Check that both the branches and tips update. # [P3: Zoom into clade A1b _and_ change color](http://localhost:4000/flu/seasonal/h3n2/ha/3y?d=tree&label=clade:A1b) Check that the coloring of the branches and tips update as we zoom in. +# [P3b: Zoom in slightly more via "zoom to selected"](http://localhost:4000/flu/seasonal/h3n2/ha/3y?d=tree&f_clade_membership=A1b/186D,A1b/197R,A1b/94N&treeZoom=selected) + +We're now recreating the functionality of filtering to clades A1b/186D, A1b/197R and A1b/94N then clicking "zoom to selected" + # [P4: Lots of simultaneous changes](http://localhost:4000/flu/seasonal/h3n2/ha/3y?c=lbi&d=tree&dmin=2017-01-01&f_region=North%20America&label=clade:3c2.A&m=div) * Zoomed out to near the root (clade 3c2.A) * Changed the horizontal scale to divergence diff --git a/src/actions/recomputeReduxState.js b/src/actions/recomputeReduxState.js index 734646a1e..96f7c54d0 100644 --- a/src/actions/recomputeReduxState.js +++ b/src/actions/recomputeReduxState.js @@ -3,7 +3,7 @@ import { cloneDeep } from 'lodash'; import { numericToCalendar, calendarToNumeric } from "../util/dateHelpers"; import { reallySmallNumber, twoColumnBreakpoint, defaultColorBy, defaultGeoResolution, defaultDateRange, nucleotide_gene, strainSymbol, genotypeSymbol } from "../util/globals"; import { calcBrowserDimensionsInitialState } from "../reducers/browserDimensions"; -import { getIdxMatchingLabel, calculateVisiblityAndBranchThickness } from "../util/treeVisibilityHelpers"; +import { getIdxMatchingLabel, calculateVisiblityAndBranchThickness, getFilteredAndIdxOfFilteredRoot } from "../util/treeVisibilityHelpers"; import { constructVisibleTipLookupBetweenTrees } from "../util/treeTangleHelpers"; import { getDefaultControlsState, shouldDisplayTemporalConfidence } from "../reducers/controls"; import { countTraitsAcrossTree, calcTotalTipsInTree } from "../util/treeCountingHelpers"; @@ -587,15 +587,23 @@ const checkAndCorrectErrorsInState = (state, metadata, query, tree, viewingNarra return state; }; -const modifyTreeStateVisAndBranchThickness = (oldState, zoomSelected, controlsState, dispatch) => { +const modifyTreeStateVisAndBranchThickness = (oldState, query, controlsState, dispatch) => { /* calculate new branch thicknesses & visibility */ let newIdxRoot = oldState.idxOfInViewRootNode; - if (zoomSelected) { + + if (typeof query.label==="string") { // Check and fix old format 'clade=B' - in this case selectionClade is just 'B' - const [labelName, labelValue] = zoomSelected.split(":"); + const [labelName, labelValue] = query.label.split(":"); const cladeSelectedIdx = getIdxMatchingLabel(oldState.nodes, labelName, labelValue, dispatch); - oldState.selectedClade = zoomSelected; + oldState.selectedClade = query.label; newIdxRoot = applyInViewNodesToTree(cladeSelectedIdx, oldState); + delete query.treeZoom; + } else if (query.treeZoom==="selected") { + // zoom to selected requires filters to be applied to tree to calculate the appropriate root + // Note that these are re-calculated by `calculateVisiblityAndBranchThickness` + const {idxOfFilteredRoot} = getFilteredAndIdxOfFilteredRoot(oldState, controlsState, oldState.nodes.map(() => true)); + newIdxRoot = applyInViewNodesToTree(idxOfFilteredRoot, oldState); + if (!idxOfFilteredRoot) delete query.treeZoom; } else { oldState.selectedClade = undefined; newIdxRoot = applyInViewNodesToTree(0, oldState); @@ -831,12 +839,12 @@ export const createStateFromQueryOrJSONs = ({ } /* if query.label is undefined then we intend to zoom to the root */ - tree = modifyTreeStateVisAndBranchThickness(tree, query.label, controls, dispatch); + tree = modifyTreeStateVisAndBranchThickness(tree, query, controls, dispatch); if (treeToo && treeToo.loaded) { treeToo.nodeColorsVersion = tree.nodeColorsVersion; treeToo.nodeColors = calcNodeColor(treeToo, controls.colorScale); - treeToo = modifyTreeStateVisAndBranchThickness(treeToo, undefined, controls, dispatch); + treeToo = modifyTreeStateVisAndBranchThickness(treeToo, {}, controls, dispatch); controls = modifyControlsViaTreeToo(controls, treeToo.name); treeToo.tangleTipLookup = constructVisibleTipLookupBetweenTrees(tree.nodes, treeToo.nodes, tree.visibility, treeToo.visibility); } @@ -912,7 +920,7 @@ export const createTreeTooState = ({ treeToo.debug = "RIGHT"; controls = modifyControlsStateViaTree(controls, tree, treeToo, oldState.metadata.colorings); controls = modifyControlsViaTreeToo(controls, secondTreeUrl); - treeToo = modifyTreeStateVisAndBranchThickness(treeToo, undefined, controls, dispatch); + treeToo = modifyTreeStateVisAndBranchThickness(treeToo, {}, controls, dispatch); /* calculate colours if loading from JSONs or if the query demands change */ const colorScale = calcColorScale(controls.colorBy, controls, tree, treeToo, oldState.metadata); diff --git a/src/actions/tree.js b/src/actions/tree.js index 6518652de..32d0c0c0f 100644 --- a/src/actions/tree.js +++ b/src/actions/tree.js @@ -111,6 +111,15 @@ export const updateVisibleTipsAndBranchThicknesses = ( visibilityToo: dispatchObj.visibilityToo }); + /* We set a flag for the URL middleware here */ + if (root[0]!== undefined) { + if (tree.idxOfFilteredRoot===root[0]) { + dispatchObj.zoomToSelectedQuery = true; + } else { + dispatchObj.zoomToSelectedQuery = false; + } + } + /* D I S P A T C H */ dispatch(dispatchObj); updateEntropyVisibility(dispatch, getState); diff --git a/src/middleware/changeURL.js b/src/middleware/changeURL.js index 53799a603..2d50d4770 100644 --- a/src/middleware/changeURL.js +++ b/src/middleware/changeURL.js @@ -157,7 +157,17 @@ export const changeURLMiddleware = (store) => (next) => (action) => { } case types.UPDATE_VISIBILITY_AND_BRANCH_THICKNESS: { // query.s = action.selectedStrain ? action.selectedStrain : undefined; - query.label = action.cladeName ? action.cladeName : undefined; + if (action.cladeName) { + query.label = action.cladeName; + query.treeZoom = undefined; // we preferentially restore state from clade (branch) label + } else { + query.label = undefined; + if (action.zoomToSelectedQuery===true) { + query.treeZoom="selected"; // setting a string variable allows for future expansion + } else if (action.zoomToSelectedQuery===false) { + query.treeZoom=undefined; + } + } break; } case types.MAP_ANIMATION_PLAY_PAUSE_BUTTON: diff --git a/src/util/treeVisibilityHelpers.js b/src/util/treeVisibilityHelpers.js index d6e0d5570..49f1ec242 100644 --- a/src/util/treeVisibilityHelpers.js +++ b/src/util/treeVisibilityHelpers.js @@ -143,7 +143,7 @@ const getInView = (tree) => { * - controls.filters (redux) is a dict of trait name -> values * - filters (in this code) is a list of filters to apply * e.g. [{trait: "country", values: [...]}, ...] */ -const getFilteredAndIdxOfFilteredRoot = (tree, controls, inView) => { +export const getFilteredAndIdxOfFilteredRoot = (tree, controls, inView) => { if (!tree.nodes) { console.error("getFiltered() ran without tree.nodes"); return null;