diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f21fe2cf..6252e9a2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Changelog -## version 2.54.0 - 2024/06/06 +* Fixed a big bug where clicking on tips (and shift-clicking on branches) on the RHS tree in a tanglegram would bring up a modal detailing a node in the LHS tree. ([#1783](https://github.com/nextstrain/auspice/pull/1783)) +* Fixed a small bug where branch labels prevented you from hovering on the branch itself, a situation that was more common in tangletrees. ([#1783](https://github.com/nextstrain/auspice/pull/1783)) + +## version 2.54.0 - 2024/06/06 * Add a link-out to use datasets in Nextclade (this functionality is only enabled when running within the larger nextstrain.org ecosystem.) This builds on functionality introduced in [Nextclade 3.7.0](https://github.com/nextstrain/nextclade/releases/tag/3.7.0) allowing an Auspice dataset to be used as a nextclade dataset so that users can then add new sequences (via drag-and-drop) and see them placed on the tree. ([#1776](https://github.com/nextstrain/auspice/pull/1776)) diff --git a/src/components/tree/infoPanels/click.js b/src/components/tree/infoPanels/click.js index dc21c7281..7d5b8ef9f 100644 --- a/src/components/tree/infoPanels/click.js +++ b/src/components/tree/infoPanels/click.js @@ -5,6 +5,7 @@ import { numericToCalendar } from "../../../util/dateHelpers"; import { getTraitFromNode, getFullAuthorInfoFromNode, getVaccineFromNode, getAccessionFromNode, getUrlFromNode } from "../../../util/treeMiscHelpers"; import { MutationTable } from "./MutationTable"; +import { lhsTreeId} from "../tree"; export const styles = { container: { @@ -234,16 +235,21 @@ const Trait = ({node, trait, colorings, isTerminal}) => { * A React component to display information about a tree tip in a modal-overlay style * @param {Object} props * @param {Object} props.selectedNode - * @param {Object[]} props.nodes + * @param {Object[]} props.nodesLhsTree + * @param {Object[]|undefined} props.nodesRhsTree * @param {function} props.clearSelectedNode * @param {Object} props.colorings * @param {Object} props.observedMutations * @param {function} props.geneSortFn * @param {function} props.t */ -const NodeClickedPanel = ({selectedNode, nodes, clearSelectedNode, colorings, observedMutations, geneSortFn, t}) => { +const NodeClickedPanel = ({selectedNode, nodesLhsTree, nodesRhsTree, clearSelectedNode, colorings, observedMutations, geneSortFn, t}) => { if (!selectedNode) return null; - const node = nodes[selectedNode.idx]; + const node = (selectedNode.treeId===lhsTreeId ? nodesLhsTree : nodesRhsTree)?.[selectedNode.idx]; + if (!node) { + console.error('Internal error retrieving selected node'); + return null; + } const panelStyle = { ...infoPanelStyles.panel}; panelStyle.maxHeight = "70%"; const isTerminal = !node.hasChildren; diff --git a/src/components/tree/phyloTree/labels.js b/src/components/tree/phyloTree/labels.js index de9a0e1b2..31fe9db88 100644 --- a/src/components/tree/phyloTree/labels.js +++ b/src/components/tree/phyloTree/labels.js @@ -39,6 +39,7 @@ export const updateTipLabels = function updateTipLabels(dt) { .text((d) => tLFunc(d)) .attr("class", "tipLabel") .style("font-size", fontSize.toString() + "px") + .style("pointer-events", "none") .style("visibility", (d) => (d.visibility === NODE_VISIBLE ? "visible" : "hidden")); }, dt); } diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index 139bfdadc..0ecc3fc85 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -24,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, isBranch: false}); + this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx, isBranch: false, treeId: d.that.id}); this.props.dispatch(applyFilter("add", strainSymbol, [d.n.name])); }; @@ -60,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, isBranch: true}) + this.props.dispatch({type: SELECT_NODE, name: d.n.name, idx: d.n.arrayIdx, isBranch: true, treeId: d.that.id}) return; } diff --git a/src/components/tree/tree.js b/src/components/tree/tree.js index f91040fa4..5c358d94f 100644 --- a/src/components/tree/tree.js +++ b/src/components/tree/tree.js @@ -19,6 +19,8 @@ import { untangleTreeToo } from "./tangle/untangling"; import { sortByGeneOrder } from "../../util/treeMiscHelpers"; export const spaceBetweenTrees = 100; +export const lhsTreeId = "LEFT"; +const rhsTreeId = "RIGHT"; class Tree extends React.Component { constructor(props) { @@ -51,7 +53,7 @@ class Tree extends React.Component { setUpAndRenderTreeToo(props, newState) { /* this.setState(newState) will be run sometime after this returns */ /* modifies newState in place */ - newState.treeToo = new PhyloTree(props.treeToo.nodes, "RIGHT", props.treeToo.idxOfInViewRootNode); + newState.treeToo = new PhyloTree(props.treeToo.nodes, rhsTreeId, props.treeToo.idxOfInViewRootNode); if (attemptUntangle) { untangleTreeToo(newState.tree, newState.treeToo); } @@ -61,7 +63,7 @@ class Tree extends React.Component { document.addEventListener('keyup', this.handlekeydownEvent); if (this.props.tree.loaded) { const newState = {}; - newState.tree = new PhyloTree(this.props.tree.nodes, "LEFT", this.props.tree.idxOfInViewRootNode); + newState.tree = new PhyloTree(this.props.tree.nodes, lhsTreeId, this.props.tree.idxOfInViewRootNode); renderTree(this, true, newState.tree, this.props); if (this.props.showTreeToo) { this.setUpAndRenderTreeToo(this.props, newState); /* modifies newState in place */ @@ -209,7 +211,8 @@ class Tree extends React.Component { info.value===action.name); const existingFilterState = existingFilterInfo === undefined ? null : existingFilterInfo.active ? 'active' : 'inactive'; - return {...state, selectedNode: {name: action.name, idx: action.idx, existingFilterState, isBranch: action.isBranch}}; + const selectedNode = {name: action.name, idx: action.idx, existingFilterState, isBranch: action.isBranch, treeId: action.treeId}; + return {...state, selectedNode}; } case types.DESELECT_NODE: { return {...state, selectedNode: null}