import React from "react"; import { withTranslation } from "react-i18next"; 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 HoverInfoPanel from "./infoPanels/hover"; import NodeClickedPanel from "./infoPanels/click"; import { changePhyloTreeViaPropsComparison } from "./reactD3Interface/change"; import * as callbacks from "./reactD3Interface/callbacks"; 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"; const rhsTreeId = "RIGHT"; export class TreeComponent extends React.Component { domRefs: { mainTree: SVGGElement | null; secondTree: SVGGElement | null; }; tangleRef?: Tangle; clearSelectedNode: (node: SelectedNode) => void; constructor(props: TreeComponentProps) { super(props); this.domRefs = { mainTree: null, secondTree: null }; this.tangleRef = undefined; this.state = { hoveredNode: null, tree: null, treeToo: null }; /* bind callbacks */ this.clearSelectedNode = callbacks.clearSelectedNode.bind(this); } redrawTree = (): void => { this.props.dispatch(updateVisibleTipsAndBranchThicknesses({ root: [0, 0] })); } setUpAndRenderTreeToo(props: TreeComponentProps, newState: Partial): void { /* this.setState(newState) will be run sometime after this returns */ /* modifies newState in place */ newState.treeToo = new PhyloTree(props.treeToo.nodes, rhsTreeId, props.treeToo.idxOfInViewRootNode); if (attemptUntangle) { untangleTreeToo(newState.tree, newState.treeToo); } renderTree(this, false, newState.treeToo, props); } override componentDidMount(): void { if (this.props.tree.loaded) { const newState: Partial = {}; 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 */ } newState.geneSortFn = sortByGeneOrder(this.props.genomeMap); this.setState(newState); /* this will trigger an unnecessary CDU :( */ } } override componentDidUpdate(prevProps: TreeComponentProps): void { let newState: Partial = {}; let rightTreeUpdated = false; /* potentially change the (main / left hand) tree */ const { newState: potentialNewState, change: 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 */ ({ change: rightTreeUpdated, } = changePhyloTreeViaPropsComparison(false, this.state.treeToo, prevProps, this.props)); /* note, we don't incorporate newState into the state? why not? */ } /* we may need to (imperatively) tell the tangle to redraw */ if (this.tangleRef && (leftTreeUpdated || rightTreeUpdated)) { this.tangleRef.drawLines(); } if (Object.keys(newState).length) this.setState(newState); } renderTreeDiv({ width, height, mainTree, }: { width: number height: number mainTree: boolean }): JSX.Element { return ( {/* TODO: remove intermediate s once the 1Password extension interference is resolved * */} {mainTree ? this.domRefs.mainTree = c : this.domRefs.secondTree = c;}} /> ); } override render(): JSX.Element { const { t } = this.props; const widthPerTree = this.props.showTreeToo ? (this.props.width - spaceBetweenTrees) / 2 : this.props.width; return ( {this.props.showTangle && this.state.tree && this.state.treeToo ? ( {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 ?
: null} {this.props.showTreeToo ? this.renderTreeDiv({width: widthPerTree, height: this.props.height, mainTree: false}) : null } {this.props.showTreeToo && } ); } } export default withTranslation()(TreeComponent);