diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16ac87231..5263c8af5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,8 @@
# Changelog
-## version 2.52.0 - 2024/02/09
-
+* Bugfix: Restore the intended behaviour when hovering or clicking on terminal branches. ([#1753](https://github.com/nextstrain/auspice/pull/1753))
+## version 2.52.0 - 2024/02/09
* Sidebar filtering now contains all non-continuous metadata defined across the tree (i.e. all data within `node.node_attrs`). The traits listed in `meta.filters` are now only used to determine which filters to list in the footer of the page. ([#1743](https://github.com/nextstrain/auspice/pull/1743))
* The interaction between strain-selected modals and the corresponding strain-filter has been improved. We now preserve the strain filter state present before the node was clicked. ([#1749](https://github.com/nextstrain/auspice/issues/1749))
diff --git a/src/components/tree/infoPanels/click.js b/src/components/tree/infoPanels/click.js
index eefb5148a..dc21c7281 100644
--- a/src/components/tree/infoPanels/click.js
+++ b/src/components/tree/infoPanels/click.js
@@ -247,31 +247,33 @@ const NodeClickedPanel = ({selectedNode, nodes, clearSelectedNode, colorings, ob
const panelStyle = { ...infoPanelStyles.panel};
panelStyle.maxHeight = "70%";
const isTerminal = !node.hasChildren;
- const title = isTerminal ?
+ const isTip = !selectedNode.isBranch;
+
+ const title = isTip ?
node.name :
isTerminal ?
`Branch leading to ${node.name}` :
"Internal branch";
return (
-
clearSelectedNode(selectedNode, isTerminal)}>
+
clearSelectedNode(selectedNode)}>
stopProp(e)}>
{title}
- {!isTerminal && item(t("Number of terminal tips"), node.fullTipCount)}
- {isTerminal && }
+ {!isTip && item(t("Number of terminal tips"), node.fullTipCount)}
+ {isTip && }
- {!isTerminal && item("Node name", node.name)}
- {isTerminal && }
+ {!isTip && item("Node name", node.name)}
+ {isTip && }
{getTraitsToDisplay(node).map((trait) => (
))}
- {isTerminal && }
+ {isTip && }
{item("", "")}
-
+
{t("Click outside this box to go back to the tree")}
diff --git a/src/components/tree/infoPanels/hover.js b/src/components/tree/infoPanels/hover.js
index 393c36bbc..1812bea7c 100644
--- a/src/components/tree/infoPanels/hover.js
+++ b/src/components/tree/infoPanels/hover.js
@@ -402,11 +402,12 @@ const HoverInfoPanel = ({
t
}) => {
if (!selectedNode) return null
- const node = selectedNode.n;
+ const node = selectedNode.node.n; // want the redux node, not the phylo node
const idxOfInViewRootNode = getIdxOfInViewRootNode(node);
+
return (
- {node.hasChildren===false ? (
+ {selectedNode.isBranch===false ? (
<>
diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js
index dbd13f7d0..139bfdadc 100644
--- a/src/components/tree/reactD3Interface/callbacks.js
+++ b/src/components/tree/reactD3Interface/callbacks.js
@@ -13,7 +13,9 @@ export const onTipHover = function onTipHover(d) {
this.state.treeToo;
phylotree.svg.select("#"+getDomId("tip", d.n.name))
.attr("r", (e) => e["r"] + 4);
- this.setState({hoveredNode: d});
+ this.setState({
+ hoveredNode: {node: d, isBranch: false}
+ });
};
export const onTipClick = function onTipClick(d) {
@@ -22,7 +24,7 @@ export const onTipClick = function onTipClick(d) {
/* The order of these two dispatches is important: the reducer handling
`SELECT_NODE` must have access to the filtering state _prior_ to these filters
being applied */
- this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx});
+ this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx, isBranch: false});
this.props.dispatch(applyFilter("add", strainSymbol, [d.n.name]));
};
@@ -46,7 +48,9 @@ export const onBranchHover = function onBranchHover(d) {
}
/* Set the hovered state so that an info box can be displayed */
- this.setState({hoveredNode: d});
+ this.setState({
+ hoveredNode: {node: d, isBranch: true}
+ });
};
export const onBranchClick = function onBranchClick(d) {
@@ -56,7 +60,7 @@ export const onBranchClick = function onBranchClick(d) {
/* if a branch was clicked while holding the shift key, we instead display a node-clicked modal */
if (window.event.shiftKey) {
// no need to dispatch a filter action
- this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx})
+ this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx, isBranch: true})
return;
}
@@ -120,8 +124,8 @@ export const onTipLeave = function onTipLeave(d) {
};
/* clearSelectedNode when clicking to remove the node-selected modal */
-export const clearSelectedNode = function clearSelectedNode(selectedNode, isTerminal) {
- if (isTerminal) {
+export const clearSelectedNode = function clearSelectedNode(selectedNode) {
+ if (!selectedNode.isBranch) {
/* perform the filtering action (if necessary) that will restore the
filtering state of the node prior to the selection */
if (!selectedNode.existingFilterState) {
diff --git a/src/components/tree/tree.js b/src/components/tree/tree.js
index 2020657dd..f91040fa4 100644
--- a/src/components/tree/tree.js
+++ b/src/components/tree/tree.js
@@ -44,10 +44,7 @@ class Tree extends React.Component {
/* pressing the escape key should dismiss an info modal (if one exists) */
this.handlekeydownEvent = (event) => {
if (event.key==="Escape" && this.props.selectedNode) {
- this.clearSelectedNode(
- this.props.selectedNode,
- !this.props.tree.nodes[this.props.selectedNode.idx].hasChildren
- );
+ this.clearSelectedNode(this.props.selectedNode);
}
};
}
diff --git a/src/reducers/controls.ts b/src/reducers/controls.ts
index 503c8ded5..ca4024447 100644
--- a/src/reducers/controls.ts
+++ b/src/reducers/controls.ts
@@ -238,7 +238,7 @@ const Controls = (state: ControlsState = getDefaultControlsState(), action): Con
const existingFilterInfo = (state.filters?.[strainSymbol]||[]).find((info) => info.value===action.name);
const existingFilterState = existingFilterInfo === undefined ? null :
existingFilterInfo.active ? 'active' : 'inactive';
- return {...state, selectedNode: {name: action.name, idx: action.idx, existingFilterState}};
+ return {...state, selectedNode: {name: action.name, idx: action.idx, existingFilterState, isBranch: action.isBranch}};
}
case types.DESELECT_NODE: {
return {...state, selectedNode: null}