UNPKG

auspice

Version:

Web app for visualizing pathogen evolution

181 lines (171 loc) 6.89 kB
import React from "react"; import { updateVisibleTipsAndBranchThicknesses } from "../../actions/tree"; import Card from "../framework/card"; import Legend from "./legend/legend"; import PhyloTree from "./phyloTree/phyloTree"; import HoverInfoPanel from "./infoPanels/hover"; import TipClickedPanel 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/errorBoundry"; import { untangleTreeToo } from "./tangle/untangling"; export const spaceBetweenTrees = 100; class Tree extends React.Component { constructor(props) { super(props); this.domRefs = { mainTree: undefined, secondTree: undefined }; this.tangleRef = undefined; this.state = { hover: null, selectedBranch: null, selectedTip: null, tree: null, treeToo: null }; /* bind callbacks */ this.clearSelectedTip = callbacks.clearSelectedTip.bind(this); // this.handleIconClickHOF = callbacks.handleIconClickHOF.bind(this); this.redrawTree = () => { this.props.dispatch(updateVisibleTipsAndBranchThicknesses({ root: [0, 0] })); }; } 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"); if (attemptUntangle) { untangleTreeToo(newState.tree, newState.treeToo); } renderTree(this, false, newState.treeToo, props); } componentDidMount() { if (this.props.tree.loaded) { const newState = {}; newState.tree = new PhyloTree(this.props.tree.nodes, "LEFT"); renderTree(this, true, newState.tree, this.props); if (this.props.showTreeToo) { this.setUpAndRenderTreeToo(this.props, newState); /* modifies newState in place */ } this.setState(newState); /* this will trigger an unneccessary CDU :( */ } } componentDidUpdate(prevProps) { let newState = {}; let rightTreeUpdated = false; /* potentially change the (main / left hand) tree */ const [potentialNewState, leftTreeUpdated] = changePhyloTreeViaPropsComparison(true, this.state.tree, prevProps, this.props); if (potentialNewState) newState = potentialNewState; /* has the 2nd (right hand) tree just been turned on, off or swapped? */ if (prevProps.showTreeToo !== this.props.showTreeToo) { if (!this.props.showTreeToo) { /* turned off -> remove the 2nd tree */ newState.treeToo = null; } else { /* turned on -> render the 2nd tree */ if (this.state.treeToo) { /* tree has been swapped -> remove the old tree */ this.state.treeToo.clearSVG(); } newState.tree = this.state.tree; // setUpAndRenderTreeToo needs newState.tree this.setUpAndRenderTreeToo(this.props, newState); /* modifies newState in place */ if (this.tangleRef) this.tangleRef.drawLines(); } } else if (this.state.treeToo) { /* the tree hasn't just been swapped, but it does exist and may need updating */ let unusedNewState; // eslint-disable-line [unusedNewState, rightTreeUpdated] = changePhyloTreeViaPropsComparison(false, this.state.treeToo, prevProps, this.props); /* note, we don't incorporate unusedNewState into the state? why not? */ } /* we may need to (imperitively) tell the tangle to redraw */ if (this.tangleRef && (leftTreeUpdated || rightTreeUpdated)) { this.tangleRef.drawLines(); } if (Object.keys(newState).length) this.setState(newState); } getStyles = () => { const activeResetTreeButton = this.props.tree.idxOfInViewRootNode !== 0 || this.props.treeToo.idxOfInViewRootNode !== 0; return { resetTreeButton: { zIndex: 100, position: "absolute", right: 5, top: 0, cursor: activeResetTreeButton ? "pointer" : "auto", color: activeResetTreeButton ? darkGrey : lightGrey } }; }; renderTreeDiv({width, height, mainTree}) { return ( <svg id={mainTree ? "MainTree" : "SecondTree"} style={{pointerEvents: "auto", cursor: "default"}} width={width} height={height} ref={(c) => {mainTree ? this.domRefs.mainTree = c : this.domRefs.secondTree = c;}} /> ); } render() { const styles = this.getStyles(); const widthPerTree = this.props.showTreeToo ? (this.props.width - spaceBetweenTrees) / 2 : this.props.width; return ( <Card center title={"Phylogeny"}> <ErrorBoundary> <Legend width={this.props.width}/> </ErrorBoundary> <HoverInfoPanel hovered={this.state.hovered} colorBy={this.props.colorBy} colorByConfidence={this.props.colorByConfidence} colorScale={this.props.colorScale} colorings={this.props.metadata.colorings} panelDims={{width: this.props.width, height: this.props.height, spaceBetweenTrees}} /> <TipClickedPanel goAwayCallback={this.clearSelectedTip} tip={this.state.selectedTip} colorings={this.props.metadata.colorings} /> {this.props.showTangle && this.state.tree && this.state.treeToo ? ( <Tangle ref={(r) => {this.tangleRef = r;}} width={this.props.width} height={this.props.height} lookup={this.props.treeToo.tangleTipLookup} leftNodes={this.props.tree.nodes} rightNodes={this.props.treeToo.nodes} colors={this.props.tree.nodeColors} cVersion={this.props.tree.nodeColorsVersion} vVersion={this.props.tree.visibilityVersion} metric={this.props.distanceMeasure} spaceBetweenTrees={spaceBetweenTrees} leftTreeName={this.props.tree.name} rightTreeName={this.props.showTreeToo} /> ) : null } {this.renderTreeDiv({width: widthPerTree, height: this.props.height, mainTree: true})} {this.props.showTreeToo ? <div id="treeSpacer" style={{width: spaceBetweenTrees}}/> : null} {this.props.showTreeToo ? this.renderTreeDiv({width: widthPerTree, height: this.props.height, mainTree: false}) : null } {this.props.narrativeMode ? null : ( <button style={{...tabSingle, ...styles.resetTreeButton}} onClick={this.redrawTree} > reset layout </button> )} </Card> ); } } export default Tree;