Skip to content

Commit

Permalink
Merge pull request #886 from nextstrain/all-the-zooms
Browse files Browse the repository at this point in the history
All the zooms
  • Loading branch information
jameshadfield authored Feb 5, 2020
2 parents 5e3d3f9 + 8397e40 commit 154f5f5
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 83 deletions.
7 changes: 5 additions & 2 deletions docs-src/docs/advanced-functionality/view-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Each of these can be overridden by the JSON `display_defaults`, and then the vie
* Default phylogeny distance measure is time, if available.
* Default geographic resolution is "country", if available.
* Default colouring is "country", if available.

* Default branch labelling is "clade", if available.

## Dataset (JSON) configurable defaults

Expand All @@ -35,6 +35,7 @@ For instance, if you set `display_defaults.color_by` to `country`, but load the
| `distance_measure` | Phylogeny x-axis measure | "div" or "num_date" |
| `map_triplicate` | Should the map repeat, so that you can pan further in each direction? | Boolean |
| `layout` | Tree layout | "rect", "radial", "clock" or "unrooted |
| `branch_label` | Which set of branch labels are to be displayed | "aa", "lineage" |

Furthermore, a JSON property `meta.panels` lists which panels auspice displays.
If this is not included, then auspice tries to display as many as possible.
Expand Down Expand Up @@ -66,7 +67,9 @@ All URL queries modify the view away from the default settings -- if you change
| `animate` | Animation settings | |
| `n` | Narrative page number | `n=1` goes to the first page |
| `s` | Selected strain | `s=1_0199_PF` |
| `clade` | Labeled clade that tree is zoomed to | `clade=B3` (numeric values are buggy) |
| `branchLabel` | Branch labels to display | `branchLabel=aa` |
| `label` | Labeled branch that tree is zoomed to | `label=clade:B3`, `label=lineage:relapse` |
| `clade` | _DEPRECATED_ Labeled clade that tree is zoomed to | `clade=B3` should now become `label=clade:B3` |


**See this in action:**
Expand Down
5 changes: 3 additions & 2 deletions src/actions/loadData.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ const fetchDataAndDispatch = async (dispatch, url, query, narrativeBlocks) => {
query,
narrativeBlocks,
mainTreeName: secondTreeUrl ? mainDatasetUrl : null,
secondTreeName: secondTreeUrl ? secondTreeUrl : null
secondTreeName: secondTreeUrl ? secondTreeUrl : null,
dispatch
})
});

Expand Down Expand Up @@ -233,7 +234,7 @@ export const loadSecondTree = (secondTreeUrl, firstTreeUrl) => async (dispatch,
return;
}
const oldState = getState();
const newState = createTreeTooState({treeTooJSON: secondJson.tree, oldState, originalTreeUrl: firstTreeUrl, secondTreeUrl: secondTreeUrl});
const newState = createTreeTooState({treeTooJSON: secondJson.tree, oldState, originalTreeUrl: firstTreeUrl, secondTreeUrl: secondTreeUrl, dispatch});
dispatch({type: types.TREE_TOO_DATA, ...newState});
};

Expand Down
4 changes: 2 additions & 2 deletions src/actions/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const changePage = ({
}

/* the path (dataset) remains the same... but the state may be modulated by the query */
const newState = createStateFromQueryOrJSONs({oldState, query});
const newState = createStateFromQueryOrJSONs({oldState, query, dispatch});
dispatch({
type: URL_QUERY_CHANGE_WITH_COMPUTED_STATE,
...newState,
Expand All @@ -92,7 +92,7 @@ export const goTo404 = (errorMessage) => ({

export const EXPERIMENTAL_showMainDisplayMarkdown = ({query, queryToDisplay}) =>
(dispatch, getState) => {
const newState = createStateFromQueryOrJSONs({oldState: getState(), query});
const newState = createStateFromQueryOrJSONs({oldState: getState(), query, dispatch});
newState.controls.panelsToDisplay = ["EXPERIMENTAL_MainDisplayMarkdown"];
dispatch({
type: URL_QUERY_CHANGE_WITH_COMPUTED_STATE,
Expand Down
74 changes: 57 additions & 17 deletions src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ const modifyStateViaURLQuery = (state, query) => {
} else {
state.animationPlayPauseButton = "Play";
}
if (query.branchLabel) {
state.selectedBranchLabel = query.branchLabel;
// do not modify the default (only the JSON can do this)
}
return state;
};

Expand Down Expand Up @@ -164,8 +168,8 @@ const modifyStateViaMetadata = (state, metadata) => {
console.warn("JSON did not include any filters");
}
if (metadata.displayDefaults) {
const keysToCheckFor = ["geoResolution", "colorBy", "distanceMeasure", "layout", "mapTriplicate"];
const expectedTypes = ["string", "string", "string", "string", "boolean"];
const keysToCheckFor = ["geoResolution", "colorBy", "distanceMeasure", "layout", "mapTriplicate", "selectedBranchLabel"];
const expectedTypes = ["string", "string", "string", "string", "boolean", "string"];

for (let i = 0; i < keysToCheckFor.length; i += 1) {
if (metadata.displayDefaults[keysToCheckFor[i]]) {
Expand Down Expand Up @@ -310,7 +314,13 @@ const modifyControlsStateViaTree = (state, tree, treeToo, colorings) => {
state.distanceMeasure = state.branchLengthsToDisplay === "divOnly" ? "div" :
state.branchLengthsToDisplay === "dateOnly" ? "num_date" : state.distanceMeasure;

state.selectedBranchLabel = tree.availableBranchLabels.indexOf("clade") !== -1 ? "clade" : "none";
/* if clade is available as a branch label, then set this as the "default". This
is largely due to historical reasons. Note that it *can* and *will* be overridden
by JSON display_defaults and URL query */
if (tree.availableBranchLabels.indexOf("clade") !== -1) {
state.defaults.selectedBranchLabel = "clade";
state.selectedBranchLabel = "clade";
}

state.temporalConfidence = getTraitFromNode(tree.nodes[0], "num_date", {confidence: true}) ?
{exists: true, display: true, on: false} :
Expand Down Expand Up @@ -395,6 +405,13 @@ const checkAndCorrectErrorsInState = (state, metadata, query, tree) => {
console.warn("JSONs did not include `geoResolutions`");
}

/* show label */
if (state.selectedBranchLabel && !tree.availableBranchLabels.includes(state.selectedBranchLabel)) {
console.error("Can't set selected branch label to ", state.selectedBranchLabel);
state.selectedBranchLabel = "none";
state.defaults.selectedBranchLabel = "none";
}

/* temporalConfidence */
if (state.temporalConfidence.exists) {
if (state.layout !== "rect") {
Expand Down Expand Up @@ -436,7 +453,7 @@ const checkAndCorrectErrorsInState = (state, metadata, query, tree) => {
return state;
};

const modifyTreeStateVisAndBranchThickness = (oldState, tipSelected, cladeSelected, controlsState) => {
const modifyTreeStateVisAndBranchThickness = (oldState, tipSelected, zoomSelected, controlsState, dispatch) => {
/* calculate new branch thicknesses & visibility */
let tipSelectedIdx = 0;
/* check if the query defines a strain to be selected */
Expand All @@ -445,16 +462,21 @@ const modifyTreeStateVisAndBranchThickness = (oldState, tipSelected, cladeSelect
tipSelectedIdx = strainNameToIdx(oldState.nodes, tipSelected);
oldState.selectedStrain = tipSelected;
}
if (cladeSelected) {
const cladeSelectedIdx = cladeSelected === 'root' ? 0 : getIdxMatchingLabel(oldState.nodes, "clade", cladeSelected);
oldState.selectedClade = cladeSelected;
if (zoomSelected) {
// Check and fix old format 'clade=B' - in this case selectionClade is just 'B'
const [labelName, labelValue] = zoomSelected.split(":");
const cladeSelectedIdx = getIdxMatchingLabel(oldState.nodes, labelName, labelValue, dispatch);
oldState.selectedClade = zoomSelected;
newIdxRoot = applyInViewNodesToTree(cladeSelectedIdx, oldState); // tipSelectedIdx, oldState);
} else {
oldState.selectedClade = undefined;
newIdxRoot = applyInViewNodesToTree(0, oldState); // tipSelectedIdx, oldState);
}
const visAndThicknessData = calculateVisiblityAndBranchThickness(
oldState,
controlsState,
{dateMinNumeric: controlsState.dateMinNumeric, dateMaxNumeric: controlsState.dateMaxNumeric},
{tipSelectedIdx, validIdxRoot: newIdxRoot}
{tipSelectedIdx}
);

const newState = Object.assign({}, oldState, visAndThicknessData);
Expand Down Expand Up @@ -546,6 +568,7 @@ const createMetadataStateFromJSON = (json) => {
color_by: "colorBy",
geo_resolution: "geoResolution",
distance_measure: "distanceMeasure",
branch_label: "selectedBranchLabel",
map_triplicate: "mapTriplicate",
layout: "layout"
};
Expand Down Expand Up @@ -574,7 +597,8 @@ export const createStateFromQueryOrJSONs = ({
narrativeBlocks = false,
mainTreeName = false,
secondTreeName = false,
query
query,
dispatch
}) => {
let tree, treeToo, entropy, controls, metadata, narrative, frequencies;
/* first task is to create metadata, entropy, controls & tree partial state */
Expand Down Expand Up @@ -642,7 +666,7 @@ export const createStateFromQueryOrJSONs = ({


/* calculate colours if loading from JSONs or if the query demands change */
if (json || controls.colorBy !== oldState.colorBy) {
if (json || controls.colorBy !== oldState.controls.colorBy) {
const colorScale = calcColorScale(controls.colorBy, controls, tree, treeToo, metadata);
const nodeColors = calcNodeColor(tree, colorScale);
controls.colorScale = colorScale;
Expand All @@ -651,16 +675,31 @@ export const createStateFromQueryOrJSONs = ({
tree.nodeColors = nodeColors;
}

/* parse the query.label / query.clade */
if (query.clade) {
tree = modifyTreeStateVisAndBranchThickness(tree, undefined, query.clade, controls);
} else { /* if not specifically given in URL, zoom to root */
tree = modifyTreeStateVisAndBranchThickness(tree, undefined, undefined, controls);
if (!query.label && query.clade !== "root") {
query.label = `clade:${query.clade}`;
}
delete query.clade;
}
tree = modifyTreeStateVisAndBranchThickness(tree, query.s, undefined, controls);
if (query.label) {
if (!query.label.includes(":")) {
console.error("Defined a label without ':' separator.");
delete query.label;
}
if (!tree.availableBranchLabels.includes(query.label.split(":")[0])) {
console.error(`Label name ${query.label.split(":")[0]} doesn't exist`);
delete query.label;
}
}

/* if query.label is undefined then we intend to zoom to the root */
tree = modifyTreeStateVisAndBranchThickness(tree, query.s, query.label, controls, dispatch);

if (treeToo && treeToo.loaded) {
treeToo.nodeColorsVersion = tree.nodeColorsVersion;
treeToo.nodeColors = calcNodeColor(treeToo, controls.colorScale);
treeToo = modifyTreeStateVisAndBranchThickness(treeToo, query.s, undefined, controls);
treeToo = modifyTreeStateVisAndBranchThickness(treeToo, query.s, undefined, controls, dispatch);
controls = modifyControlsViaTreeToo(controls, treeToo.name);
treeToo.tangleTipLookup = constructVisibleTipLookupBetweenTrees(tree.nodes, treeToo.nodes, tree.visibility, treeToo.visibility);
}
Expand Down Expand Up @@ -695,7 +734,8 @@ export const createTreeTooState = ({
treeTooJSON, /* raw json data */
oldState,
originalTreeUrl,
secondTreeUrl /* treeToo URL */
secondTreeUrl, /* treeToo URL */
dispatch
}) => {
/* TODO: reconsile choices (filters, colorBys etc) with this new tree */
/* TODO: reconcile query with visibility etc */
Expand All @@ -707,7 +747,7 @@ export const createTreeTooState = ({
treeToo.debug = "RIGHT";
controls = modifyControlsStateViaTree(controls, tree, treeToo, oldState.metadata.colorings);
controls = modifyControlsViaTreeToo(controls, secondTreeUrl);
treeToo = modifyTreeStateVisAndBranchThickness(treeToo, tree.selectedStrain, undefined, controls);
treeToo = modifyTreeStateVisAndBranchThickness(treeToo, tree.selectedStrain, undefined, controls, dispatch);

/* calculate colours if loading from JSONs or if the query demands change */
const colorScale = calcColorScale(controls.colorBy, controls, tree, treeToo, oldState.metadata);
Expand Down
6 changes: 3 additions & 3 deletions src/actions/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const applyInViewNodesToTree = (idx, tree) => {
} else {
applyToChildren(tree.nodes[validIdxRoot].shell, (d) => {d.inView = true;});
}
} else if (idx !== tree.idxOfInViewRootNode) { /* if shell isn't set yet - have set clade in URL */
} else {
tree.nodes.forEach((d) => {
d.inView = false;
});
Expand Down Expand Up @@ -92,7 +92,7 @@ export const updateVisibleTipsAndBranchThicknesses = (
tree,
controls,
{dateMinNumeric: controls.dateMinNumeric, dateMaxNumeric: controls.dateMaxNumeric},
{tipSelectedIdx: tipIdx1, validIdxRoot: rootIdxTree1}
{tipSelectedIdx: tipIdx1}
);
const dispatchObj = {
type: types.UPDATE_VISIBILITY_AND_BRANCH_THICKNESS,
Expand All @@ -113,7 +113,7 @@ export const updateVisibleTipsAndBranchThicknesses = (
treeToo,
controls,
{dateMinNumeric: controls.dateMinNumeric, dateMaxNumeric: controls.dateMaxNumeric},
{tipSelectedIdx: tipIdx2, validIdxRoot: rootIdxTree2}
{tipSelectedIdx: tipIdx2}
);
dispatchObj.tangleTipLookup = constructVisibleTipLookupBetweenTrees(tree.nodes, treeToo.nodes, data.visibility, dataToo.visibility);
dispatchObj.visibilityToo = dataToo.visibility;
Expand Down
2 changes: 0 additions & 2 deletions src/components/main/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ export const calcPanelDims = (grid, panels, narrativeIsDisplayed, availableWidth
}
/* widths */
if (panels.includes("map") && panels.includes("tree") && !grid) {
console.warn("narrative mode specified full display but we have both map & tree");
bigWidthFraction = 0.5;
}
if (grid && (!panels.includes("map") || !panels.includes("tree"))) {
console.warn("narrative mode specified grid display but we are not showing both map & tree");
bigWidthFraction = 1;
}
}
Expand Down
Loading

0 comments on commit 154f5f5

Please sign in to comment.