Skip to content

Commit

Permalink
Merge pull request #1246 from nextstrain/tip-label-source
Browse files Browse the repository at this point in the history
Allow tip labels to be user selected
  • Loading branch information
jameshadfield committed Jan 5, 2021
2 parents 0423d6f + 0e55b30 commit 309cd06
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 5 deletions.
12 changes: 11 additions & 1 deletion src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { computeMatrixFromRawData } from "../util/processFrequencies";
import { applyInViewNodesToTree } from "../actions/tree";
import { isColorByGenotype, decodeColorByGenotype } from "../util/getGenotype";
import { getTraitFromNode, getDivFromNode } from "../util/treeMiscHelpers";

import { collectAvailableTipLabelOptions } from "../components/controls/choose-tip-label";

export const doesColorByHaveConfidence = (controlsState, colorBy) =>
controlsState.coloringsPresentOnTreeWithConfidence.has(colorBy);
Expand Down Expand Up @@ -71,6 +71,9 @@ const modifyStateViaURLQuery = (state, query) => {
if (query.p && state.canTogglePanelLayout && (query.p === "full" || query.p === "grid")) {
state["panelLayout"] = query.p;
}
if (query.tl) {
state["tipLabelKey"] = query.tl;
}
if (query.d) {
const proposed = query.d.split(",");
state.panelsToDisplay = state.panelsAvailable.filter((n) => proposed.indexOf(n) !== -1);
Expand Down Expand Up @@ -169,6 +172,7 @@ const restoreQueryableStateToDefaults = (state) => {

state["panelLayout"] = calcBrowserDimensionsInitialState().width > twoColumnBreakpoint ? "grid" : "full";
state.panelsToDisplay = state.panelsAvailable.slice();
state.tipLabelKey = strainSymbol;
// console.log("state now", state);
return state;
};
Expand Down Expand Up @@ -478,6 +482,12 @@ const checkAndCorrectErrorsInState = (state, metadata, query, tree, viewingNarra
state.defaults.selectedBranchLabel = "none";
}

/* check tip label is valid. We use the function which generates the options for the dropdown here */
if (!collectAvailableTipLabelOptions(metadata.colorings).map((o) => o.value).includes(state.tipLabelKey)) {
console.error("Can't set selected tip label to ", state.tipLabelKey);
state.tipLabelKey = strainSymbol;
}

/* temporalConfidence */
if (shouldDisplayTemporalConfidence(state.temporalConfidence.exists, state.distanceMeasure, state.layout)) {
state.temporalConfidence.display = true;
Expand Down
1 change: 1 addition & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ export const TOGGLE_LEGEND = "TOGGLE_LEGEND";
export const TOGGLE_TRANSMISSION_LINES = "TOGGLE_TRANSMISSION_LINES";
export const CACHE_JSONS = "CACHE_JSONS";
export const SET_ROOT_SEQUENCE = "SET_ROOT_SEQUENCE";
export const CHANGE_TIP_LABEL_KEY = "CHANGE_TIP_LABEL_KEY";
66 changes: 66 additions & 0 deletions src/components/controls/choose-tip-label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from "react";
import { connect } from "react-redux";
import Select from "react-select/lib/Select";
import { withTranslation } from 'react-i18next';
import { CHANGE_TIP_LABEL_KEY } from "../../actions/types";
import { SidebarSubtitle } from "./styles";
import { controlsWidth, strainSymbol } from "../../util/globals";

@connect((state) => ({
selected: state.controls.tipLabelKey,
options: collectAvailableTipLabelOptions(state.metadata.colorings)
}))
class ChooseTipLabel extends React.Component {
constructor(props) {
super(props);
this.change = (value) => {this.props.dispatch({type: CHANGE_TIP_LABEL_KEY, key: value.value});};
}
render() {
const { t } = this.props;
return (
<div style={{paddingTop: 5}}>
<SidebarSubtitle>
{t("sidebar:Tip Labels")}
</SidebarSubtitle>
<div style={{width: controlsWidth, fontSize: 14}}>
<Select
value={findSelectedValue(this.props.selected, this.props.options)}
options={this.props.options}
clearable={false}
searchable={false}
valueKey="label"
multi={false}
onChange={this.change}
/>
</div>
</div>
);
}
}

const WithTranslation = withTranslation()(ChooseTipLabel);
export default WithTranslation;

/**
* collect available tip labellings -- currently this is based on the available
* colorings but we ignore genotype (this could be implemented in the future,
* but it's not straightforward)
*/
export function collectAvailableTipLabelOptions(colorings) {
return [
{value: strainSymbol, label: "Sample Name"},
...Object.entries(colorings)
.filter((keyValue) => keyValue[0] !== 'gt')
.map(([key, value]) => {
return {value: key, label: value.title};
})
];
}

/**
* Find the currently selected option.
* <Select> can't handle Symbols so we need to write our own algorithm.
*/
function findSelectedValue(selected, options) {
return options.filter((o) => o.value===selected)[0];
}
2 changes: 2 additions & 0 deletions src/components/controls/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ChooseBranchLabelling from "./choose-branch-labelling";
import ChooseLayout from "./choose-layout";
import ChooseDataset from "./choose-dataset";
import ChooseSecondTree from "./choose-second-tree";
import ChooseTipLabel from "./choose-tip-label";
import ChooseMetric from "./choose-metric";
import PanelLayout from "./panel-layout";
import GeoResolution from "./geo-resolution";
Expand Down Expand Up @@ -42,6 +43,7 @@ function Controls({mapOn, frequenciesOn, mobileDisplay}) {
<ChooseLayout />
<ChooseMetric />
<ChooseBranchLabelling />
<ChooseTipLabel />
<ChooseSecondTree />
<ToggleTangle />

Expand Down
1 change: 1 addition & 0 deletions src/components/tree/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const Tree = connect((state) => ({
showTangle: state.controls.showTangle,
panelsToDisplay: state.controls.panelsToDisplay,
selectedBranchLabel: state.controls.selectedBranchLabel,
tipLabelKey: state.controls.tipLabelKey,
narrativeMode: state.narrative.display,
animationPlayPauseButton: state.controls.animationPlayPauseButton
}))(UnconnectedTree);
Expand Down
9 changes: 8 additions & 1 deletion src/components/tree/phyloTree/change.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { timerStart, timerEnd } from "../../../util/perf";
import { NODE_VISIBLE } from "../../../util/globals";
import { getBranchVisibility, strokeForBranch } from "./renderers";
import { shouldDisplayTemporalConfidence } from "../../../reducers/controls";
import { makeTipLabelFunc } from "./labels";

/* loop through the nodes and update each provided prop with the new value
* additionally, set d.update -> whether or not the node props changed
Expand Down Expand Up @@ -253,11 +254,12 @@ export const change = function change({
zoomIntoClade = false,
svgHasChangedDimensions = false,
animationInProgress = false,
/* change these things to provided value */
/* change these things to provided value (unless undefined) */
newDistance = undefined,
newLayout = undefined,
updateLayout = undefined,
newBranchLabellingKey = undefined,
newTipLabelKey = undefined,
/* arrays of data (the same length as nodes) */
branchStroke = undefined,
tipStroke = undefined,
Expand Down Expand Up @@ -358,6 +360,11 @@ export const change = function change({
) {
this.mapToScreen();
}
/* tip label key change -> update callback used */
if (newTipLabelKey) {
this.callbacks.tipLabel = makeTipLabelFunc(newTipLabelKey);
elemsToUpdate.add('.tipLabel'); /* will trigger d3 commands as required */
}

/* Finally, actually change the SVG elements themselves */
const extras = { removeConfidences, showConfidences, newBranchLabellingKey };
Expand Down
17 changes: 17 additions & 0 deletions src/components/tree/phyloTree/labels.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { timerFlush } from "d3-timer";
import { NODE_VISIBLE } from "../../../util/globals";
import { numericToDateObject, prettifyDate } from "../../../util/dateHelpers";
import { getTraitFromNode } from "../../../util/treeMiscHelpers";

export const updateTipLabels = function updateTipLabels(dt) {
if ("tipLabels" in this.groups) {
Expand Down Expand Up @@ -148,3 +150,18 @@ export const drawBranchLabels = function drawBranchLabels(key) {
.style("font-size", labelSize)
.text((d) => d.n.branch_attrs.labels[key]);
};

/**
* A helper factory to create the tip label function.
* This (returned function) is typically set elsewhere
* and stored on `this.callbacks.tipLabel` which is used
* in the `updateTipLabels` function.
*/
export const makeTipLabelFunc = (tipLabelKey) => {
/* special-case `num_date`. In the future we may wish to examine
`metadata.colorings` and special case other scale types */
if (tipLabelKey === "num_date") {
return (d) => prettifyDate("DAY", numericToDateObject(getTraitFromNode(d.n, "num_date")));
}
return (d) => getTraitFromNode(d.n, tipLabelKey);
};
6 changes: 4 additions & 2 deletions src/components/tree/reactD3Interface/change.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ export const changePhyloTreeViaPropsComparison = (mainTree, phylotree, oldProps,
args.newDistance = newProps.distanceMeasure;
}

/* change in key used to define branch labels (e.g. aa, clade...) */
/* change in key used to define branch labels, tip labels */
if (oldProps.selectedBranchLabel !== newProps.selectedBranchLabel) {
args.newBranchLabellingKey = newProps.selectedBranchLabel;
}

if (oldProps.tipLabelKey !== newProps.tipLabelKey) {
args.newTipLabelKey = newProps.tipLabelKey;
}

/* show / remove confidence intervals across the tree */
if (
Expand Down
3 changes: 2 additions & 1 deletion src/components/tree/reactD3Interface/initialRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'd3-transition';
import { rgb } from "d3-color";
import { calcBranchStrokeCols } from "../../../util/colorHelpers";
import * as callbacks from "./callbacks";
import { makeTipLabelFunc } from "../phyloTree/labels";

export const renderTree = (that, main, phylotree, props) => {
const ref = main ? that.domRefs.mainTree : that.domRefs.secondTree;
Expand Down Expand Up @@ -31,7 +32,7 @@ export const renderTree = (that, main, phylotree, props) => {
onBranchClick: callbacks.onBranchClick.bind(that),
onBranchLeave: callbacks.onBranchLeave.bind(that),
onTipLeave: callbacks.onTipLeave.bind(that),
tipLabel: (d) => d.n.name
tipLabel: makeTipLabelFunc(props.tipLabelKey)
},
treeState.branchThickness, /* guarenteed to be in redux by now */
treeState.visibility,
Expand Down
4 changes: 4 additions & 0 deletions src/middleware/changeURL.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ export const changeURLMiddleware = (store) => (next) => (action) => {
query.p = action.panelLayout;
break;
}
case types.CHANGE_TIP_LABEL_KEY: {
query.tl = action.key===strainSymbol ? undefined : action.key;
break;
}
case types.CHANGE_DATES_VISIBILITY_THICKNESS: {
if (state.controls.animationPlayPauseButton === "Pause") { // animation in progress - no dates in URL
query.dmin = undefined;
Expand Down
4 changes: 4 additions & 0 deletions src/reducers/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { defaultGeoResolution,
defaultLayout,
defaultMutType,
controlsHiddenWidth,
strainSymbol,
twoColumnBreakpoint } from "../util/globals";
import * as types from "../actions/types";
import { calcBrowserDimensionsInitialState } from "./browserDimensions";
Expand Down Expand Up @@ -74,6 +75,7 @@ export const getDefaultControlsState = () => {
panelsAvailable: [],
panelsToDisplay: [],
panelLayout: calcBrowserDimensionsInitialState().width > twoColumnBreakpoint ? "grid" : "full",
tipLabelKey: strainSymbol,
showTreeToo: undefined,
showTangle: false,
zoomMin: undefined,
Expand Down Expand Up @@ -194,6 +196,8 @@ const Controls = (state = getDefaultControlsState(), action) => {
return Object.assign({}, state, {
panelLayout: action.data
});
case types.CHANGE_TIP_LABEL_KEY:
return {...state, tipLabelKey: action.key};
case types.TREE_TOO_DATA:
return action.controls;
case types.TOGGLE_PANEL_DISPLAY:
Expand Down

0 comments on commit 309cd06

Please sign in to comment.