Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog

* When multiple trees are displayed ("tangletrees") the tree-buttons (zoom out, zoom to selected, zoom to root) are now present for each tree and only change their respective tree. ([#2026](https://github.com/nextstrain/auspice/pull/2026))
* Downloaded TSV will include associated URL regardless of value type ([#2024](https://github.com/nextstrain/auspice/pull/2024))
* Tip info modal displays link for traits that have a URL regardless of value type ([#2024](https://github.com/nextstrain/auspice/pull/2024))

Expand Down
2 changes: 1 addition & 1 deletion src/components/framework/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Card extends React.Component {
fontWeight: 500,
backgroundColor: "#FFFFFF",
borderTop: "thin solid #BBB",
minHeight: "15px"
minHeight: this.props.tallTitle ? 25 : 15,
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/tree/tangle/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class Tangle extends React.Component {
}
}
render() {
const textStyles = {position: "absolute", top: 5, zIndex: 100, fontSize: 16, color: "#000", fontWeight: 500};
const textStyles = {position: "absolute", top: 25, zIndex: 100, fontSize: 16, color: "#000", fontWeight: 500};
const lefts = [this.props.width/2 - this.props.spaceBetweenTrees/2, this.props.width/2 + this.props.spaceBetweenTrees/2];
return (
<div id="TangleContainer">
Expand Down
111 changes: 10 additions & 101 deletions src/components/tree/tree.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import React from "react";
import { withTranslation } from "react-i18next";
import { FaSearchMinus } from "react-icons/fa";
import { Root, updateVisibleTipsAndBranchThicknesses } from "../../actions/tree";
import { updateVisibleTipsAndBranchThicknesses } from "../../actions/tree";
import { SelectedNode } from "../../reducers/controls";
import Card from "../framework/card";
import Legend from "./legend/legend";
import PhyloTree from "./phyloTree/phyloTree";
import { getParentBeyondPolytomy, getParentStream } from "./phyloTree/helpers";
import HoverInfoPanel from "./infoPanels/hover";
import NodeClickedPanel from "./infoPanels/click";
import { changePhyloTreeViaPropsComparison } from "./reactD3Interface/change";
import * as callbacks from "./reactD3Interface/callbacks";
import { tabSingle, darkGrey, lightGrey } from "../../globalStyles";
import { renderTree } from "./reactD3Interface/initialRender";
import Tangle from "./tangle";
import { attemptUntangle } from "../../util/globals";
import ErrorBoundary from "../../util/errorBoundary";
import { untangleTreeToo } from "./tangle/untangling";
import { sortByGeneOrder } from "../../util/treeMiscHelpers";
import { TreeComponentProps, TreeComponentState } from "./types";
import { TreeButtons } from "./treeButtons";

export const spaceBetweenTrees = 100;
export const lhsTreeId = "LEFT";
Expand Down Expand Up @@ -116,53 +114,6 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
if (Object.keys(newState).length) this.setState<never>(newState);
}

getStyles = (): {
treeButtonsDiv: React.CSSProperties
resetTreeButton: React.CSSProperties
zoomToSelectedButton: React.CSSProperties
zoomOutButton: React.CSSProperties
} => {
const filteredTree = !!this.props.tree.idxOfFilteredRoot &&
this.props.tree.idxOfInViewRootNode !== this.props.tree.idxOfFilteredRoot;
const filteredTreeToo = !!this.props.treeToo.idxOfFilteredRoot &&
this.props.treeToo.idxOfInViewRootNode !== this.props.treeToo.idxOfFilteredRoot;
const activeZoomButton = filteredTree || filteredTreeToo;

const anyTreeZoomed = this.props.tree.idxOfInViewRootNode !== 0 ||
this.props.treeToo.idxOfInViewRootNode !== 0;

return {
treeButtonsDiv: {
zIndex: 100,
position: "absolute",
right: 5,
top: 0
},
resetTreeButton: {
zIndex: 100,
display: "inline-block",
marginLeft: 4,
cursor: anyTreeZoomed ? "pointer" : "auto",
color: anyTreeZoomed ? darkGrey : lightGrey
},
zoomToSelectedButton: {
zIndex: 100,
display: "inline-block",
cursor: activeZoomButton ? "pointer" : "auto",
color: activeZoomButton ? darkGrey : lightGrey,
pointerEvents: activeZoomButton ? "auto" : "none"
},
zoomOutButton: {
zIndex: 100,
display: "inline-block",
cursor: anyTreeZoomed ? "pointer" : "auto",
color: anyTreeZoomed ? darkGrey : lightGrey,
pointerEvents: anyTreeZoomed ? "auto" : "none",
marginRight: "4px"
}
};
};

renderTreeDiv({
width,
height,
Expand Down Expand Up @@ -194,38 +145,11 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
);
}

zoomToSelected = (): void => {
this.props.dispatch(updateVisibleTipsAndBranchThicknesses({
root: [this.props.tree.idxOfFilteredRoot, this.props.treeToo.idxOfFilteredRoot]
}));
};

zoomBack = (): void => {
const root: Root = [undefined, undefined];
// Zoom out of main tree if index of root node is not 0
if (this.props.tree.idxOfInViewRootNode !== 0) {
const rootNode = this.props.tree.nodes[this.props.tree.idxOfInViewRootNode];
if (this.props.showStreamTrees && rootNode.inStream && !!this.props.tree.streams[rootNode.streamName].parentStreamName) {
root[0] = getParentStream(rootNode).arrayIdx;
} else {
root[0] = getParentBeyondPolytomy(rootNode, this.props.distanceMeasure, this.props.tree.observedMutations).arrayIdx;
}
}
// Also zoom out of second tree if index of root node is not 0
// (don't have to consider stream trees as they're not possible for the second tree)
if (this.props.treeToo.idxOfInViewRootNode !== 0) {
const rootNodeToo = this.props.treeToo.nodes[this.props.treeToo.idxOfInViewRootNode];
root[1] = getParentBeyondPolytomy(rootNodeToo, this.props.distanceMeasure, this.props.treeToo.observedMutations).arrayIdx;
}
this.props.dispatch(updateVisibleTipsAndBranchThicknesses({root}));
}

override render(): JSX.Element {
const { t } = this.props;
const styles = this.getStyles();
const widthPerTree = this.props.showTreeToo ? (this.props.width - spaceBetweenTrees) / 2 : this.props.width;
return (
<Card center infocard={this.props.showOnlyPanels} title={t("Phylogeny")}>
<Card center infocard={this.props.showOnlyPanels} title={t("Phylogeny")} tallTitle={!!this.props.showTreeToo}>
<ErrorBoundary>
<Legend width={this.props.width}/>
</ErrorBoundary>
Expand Down Expand Up @@ -275,31 +199,16 @@ export class TreeComponent extends React.Component<TreeComponentProps, TreeCompo
this.renderTreeDiv({width: widthPerTree, height: this.props.height, mainTree: false}) :
null
}
{this.props.narrativeMode ? null : (
<div style={{...styles.treeButtonsDiv}}>
<button
style={{...tabSingle, ...styles.zoomOutButton}}
onClick={this.zoomBack}
>
<FaSearchMinus/>
</button>
<button
style={{...tabSingle, ...styles.zoomToSelectedButton}}
onClick={this.zoomToSelected}
>
{t("Zoom to Selected")}
</button>
<button
style={{...tabSingle, ...styles.resetTreeButton}}
onClick={this.redrawTree}
>
{t("Zoom to Root")}
</button>
</div>
)}

<TreeButtons {...this.props} mainTree={true}
offsetPx={this.props.showTreeToo ? widthPerTree + spaceBetweenTrees + 5 : 5} />

{this.props.showTreeToo &&
<TreeButtons {...this.props} mainTree={false} offsetPx={5} />}
</Card>
);
}
}

export default withTranslation()(TreeComponent);

75 changes: 75 additions & 0 deletions src/components/tree/treeButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, {ReactElement} from "react";
import { FaSearchMinus } from "react-icons/fa";
import { Root, updateVisibleTipsAndBranchThicknesses } from "../../actions/tree";
import { getParentBeyondPolytomy, getParentStream } from "./phyloTree/helpers";
import { tabSingle, darkGrey, lightGrey } from "../../globalStyles";
import { TreeComponentProps } from "./types";

type Props = TreeComponentProps & {
/* RHS offset in pixels */
offsetPx: number;

/* Are the buttons for the main tree or the second (RHS) tree? */
mainTree: boolean;
}

export function TreeButtons(props: Props): ReactElement {
if (props.narrativeMode) {
return null; // hide the buttons when viewing a narrative to prevent tree manipulations
}
const tree = props.mainTree ? props.tree : props.treeToo;
const filtered = !!tree.idxOfFilteredRoot &&
tree.idxOfInViewRootNode !== tree.idxOfFilteredRoot;
const activeZoomButton = filtered;
const treeZoomed = tree.idxOfInViewRootNode !== 0;

const containerStyles: React.CSSProperties = {zIndex: 100, position: "absolute", right: props.offsetPx, top: 0};
const baseButtonStyles: React.CSSProperties = {...tabSingle, zIndex: 100, display: "inline-block", marginLeft: "4px"};
const selectedButtonStyles: React.CSSProperties = {...baseButtonStyles, cursor: "pointer", color: darkGrey, pointerEvents: "auto"};
const unselectedButtonStyles: React.CSSProperties = {...baseButtonStyles, cursor: "auto", color: lightGrey, pointerEvents: "none"};

return (
<div style={containerStyles}>

<button style={treeZoomed ? selectedButtonStyles : unselectedButtonStyles} onClick={zoomBack}>
<FaSearchMinus/>
</button>

<button style={activeZoomButton ? selectedButtonStyles : unselectedButtonStyles} onClick={zoomToSelected}>
{props.t("Zoom to Selected")}
</button>

<button style={treeZoomed ? selectedButtonStyles : unselectedButtonStyles} onClick={redrawTree}>
{props.t("Zoom to Root")}
</button>

</div>
);

function zoomToSelected(): void {
props.dispatch(updateVisibleTipsAndBranchThicknesses({
root: props.mainTree ? [tree.idxOfFilteredRoot, undefined] : [undefined, tree.idxOfFilteredRoot]
}));
}

function zoomBack(): void {
// Zoom out of main tree as long as we're not showing everything
let rootIdx: number;
if (tree.idxOfInViewRootNode !== 0) {
const rootNode = tree.nodes[tree.idxOfInViewRootNode];
if (props.mainTree && props.showStreamTrees && rootNode.inStream && !!tree.streams[rootNode.streamName].parentStreamName) {
rootIdx = getParentStream(rootNode).arrayIdx;
} else {
rootIdx = getParentBeyondPolytomy(rootNode, props.distanceMeasure, tree.observedMutations).arrayIdx;
}
}
props.dispatch(updateVisibleTipsAndBranchThicknesses({
root: props.mainTree ? [rootIdx, undefined] : [undefined, rootIdx]
}));
}

function redrawTree(): void {
const root: Root = props.mainTree ? [0, undefined] : [undefined, 0];
props.dispatch(updateVisibleTipsAndBranchThicknesses({root}));
}
}
Loading