diff --git a/src/actions/tree.js b/src/actions/tree.js
index a5f7de910..d40b51814 100644
--- a/src/actions/tree.js
+++ b/src/actions/tree.js
@@ -294,6 +294,7 @@ export const applyFilter = (mode, trait, values) => {
}
dispatch({type: types.APPLY_FILTER, trait, values: newValues});
dispatch(updateVisibleTipsAndBranchThicknesses());
+ // FIXME: re-focus with one of the above dispatches or new dispatch
};
};
diff --git a/src/actions/types.js b/src/actions/types.js
index 10a71d8db..cf40b7aeb 100644
--- a/src/actions/types.js
+++ b/src/actions/types.js
@@ -7,6 +7,7 @@ export const SEARCH_INPUT_CHANGE = "SEARCH_INPUT_CHANGE";
export const CHANGE_LAYOUT = "CHANGE_LAYOUT";
export const CHANGE_BRANCH_LABEL = "CHANGE_BRANCH_LABEL";
export const CHANGE_DISTANCE_MEASURE = "CHANGE_DISTANCE_MEASURE";
+export const TOGGLE_TREE_FOCUS = "TOGGLE_TREE_FOCUS";
export const CHANGE_DATES_VISIBILITY_THICKNESS = "CHANGE_DATES_VISIBILITY_THICKNESS";
export const CHANGE_ABSOLUTE_DATE_MIN = "CHANGE_ABSOLUTE_DATE_MIN";
export const CHANGE_ABSOLUTE_DATE_MAX = "CHANGE_ABSOLUTE_DATE_MAX";
diff --git a/src/components/tree/index.ts b/src/components/tree/index.ts
index 3bfb1e7ca..db4fef0bb 100644
--- a/src/components/tree/index.ts
+++ b/src/components/tree/index.ts
@@ -16,6 +16,7 @@ const Tree = connect((state: RootState) => ({
temporalConfidence: state.controls.temporalConfidence,
distanceMeasure: state.controls.distanceMeasure,
explodeAttr: state.controls.explodeAttr,
+ treeFocus: state.controls.treeFocus,
colorScale: state.controls.colorScale,
colorings: state.metadata.colorings,
genomeMap: state.entropy.genomeMap,
diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js
index a3a0c4277..6613d56af 100644
--- a/src/components/tree/phyloTree/change.js
+++ b/src/components/tree/phyloTree/change.js
@@ -258,6 +258,7 @@ export const change = function change({
/* change these things to provided value (unless undefined) */
newDistance = undefined,
newLayout = undefined,
+ newTreeFocus = undefined,
updateLayout = undefined, // todo - this seems identical to `newLayout`
newBranchLabellingKey = undefined,
showAllBranchLabels = undefined,
@@ -313,7 +314,7 @@ export const change = function change({
svgPropsToUpdate.add("stroke-width");
nodePropsToModify["stroke-width"] = branchThickness;
}
- if (newDistance || newLayout || updateLayout || zoomIntoClade || svgHasChangedDimensions || changeNodeOrder) {
+ if (newDistance || newLayout || newTreeFocus || updateLayout || zoomIntoClade || svgHasChangedDimensions || changeNodeOrder) {
elemsToUpdate.add(".tip").add(".branch.S").add(".branch.T").add(".branch");
elemsToUpdate.add(".vaccineCross").add(".vaccineDottedLine").add(".conf");
elemsToUpdate.add('.branchLabel').add('.tipLabel');
@@ -359,8 +360,10 @@ export const change = function change({
/* run calculations as needed - these update properties on the phylotreeNodes (similar to updateNodesWithNewData) */
/* distance */
if (newDistance || updateLayout) this.setDistance(newDistance);
- /* layout (must run after distance) */
- if (newDistance || newLayout || updateLayout || changeNodeOrder) {
+ /* treeFocus */
+ if (newTreeFocus || updateLayout) this.setTreeFocus(newTreeFocus);
+ /* layout (must run after distance and treeFocus) */
+ if (newDistance || newLayout || newTreeFocus || updateLayout || changeNodeOrder) {
this.setLayout(newLayout || this.layout, scatterVariables);
}
/* show confidences - set this param which actually adds the svg paths for
@@ -377,6 +380,7 @@ export const change = function change({
newDistance ||
newLayout ||
changeNodeOrder ||
+ newTreeFocus ||
updateLayout ||
zoomIntoClade ||
svgHasChangedDimensions ||
diff --git a/src/components/tree/phyloTree/layouts.js b/src/components/tree/phyloTree/layouts.js
index 226ae2352..aadd61e93 100644
--- a/src/components/tree/phyloTree/layouts.js
+++ b/src/components/tree/phyloTree/layouts.js
@@ -7,6 +7,7 @@ import { timerStart, timerEnd } from "../../../util/perf";
import { getTraitFromNode, getDivFromNode } from "../../../util/treeMiscHelpers";
import { stemParent, nodeOrdering } from "./helpers";
import { numDate } from "../../../util/colorHelpers";
+import { NODE_VISIBLE } from "../../../util/globals";
/**
* assigns the attribute this.layout and calls the function that
@@ -288,6 +289,58 @@ export const setDistance = function setDistance(distanceAttribute) {
timerEnd("setDistance");
};
+/**
+ * given nodes add y values (node.displayOrder) to every node
+ * Nodes are the phyloTree nodes (i.e. node.n is the redux node)
+ * Nodes must have parent child links established (via createChildrenAndParents)
+ * PhyloTree can subsequently use this information. Accessed by prototypes
+ * rectangularLayout, radialLayout, createChildrenAndParents
+ * side effects: node.displayOrder and node.displayOrderRange (i.e. in the phyloTree node)
+ */
+export const calcYValues = (nodes, focus) => {
+ // console.log("calcYValues started with ", spacing);
+ let total = 0; /* cumulative counter of y value at tip */
+ let calcY; /* fn called calcY(node) to return some amount of y value at a tip */
+ if (focus && 'visibility' in nodes[0]) {
+ const numberOfTips = nodes.length;
+ const numTipsVisible = nodes.filter((d) => !d.hasChildren && d.visibility === NODE_VISIBLE).length;
+ const yPerVisible = (0.8 * numberOfTips) / numTipsVisible;
+ const yPerNotVisible = (0.2 * numberOfTips) / (numberOfTips - numTipsVisible);
+ calcY = (node) => {
+ total += node.visibility === NODE_VISIBLE ? yPerVisible : yPerNotVisible;
+ return total;
+ };
+ } else { /* fall back to no focus */
+ calcY = () => ++total;
+ }
+
+ const recurse = (node) => {
+ const children = node.n.children; // (redux) tree node
+ if (children && children.length) {
+ for (let i = children.length - 1; i >= 0; i--) {
+ recurse(children[i].shell);
+ }
+ } else {
+ node.displayOrder = calcY(node);
+ node.displayOrderRange = [node.displayOrder, node.displayOrder];
+ return;
+ }
+ /* if here, then all children have yvalues, but we dont. */
+ node.displayOrder = children.reduce((acc, d) => acc + d.shell.displayOrder, 0) / children.length;
+ node.displayOrderRange = [children[0].shell.displayOrder, children[children.length - 1].shell.displayOrder];
+ };
+ recurse(nodes[0]);
+};
+
+/**
+ * Recalculates y values based on focus setting
+ * @param treeFocus -- whether to focus on filtered nodes
+ */
+export const setTreeFocus = function setTreeFocus(treeFocus) {
+ timerStart("setTreeFocus");
+ calcYValues(this.nodes, treeFocus || false);
+ timerEnd("setTreeFocus");
+};
/**
* Initializes and sets the range of the scales (this.xScale, this.yScale)
diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js
index 9c1129c02..c91224fbd 100644
--- a/src/components/tree/phyloTree/phyloTree.js
+++ b/src/components/tree/phyloTree/phyloTree.js
@@ -63,6 +63,7 @@ PhyloTree.prototype.updateColorBy = renderers.updateColorBy;
/* C A L C U L A T E G E O M E T R I E S E T C ( M O D I F I E S N O D E S , N O T S V G ) */
PhyloTree.prototype.setDistance = layouts.setDistance;
PhyloTree.prototype.setLayout = layouts.setLayout;
+PhyloTree.prototype.setTreeFocus = layouts.setTreeFocus;
PhyloTree.prototype.rectangularLayout = layouts.rectangularLayout;
PhyloTree.prototype.scatterplotLayout = layouts.scatterplotLayout;
PhyloTree.prototype.unrootedLayout = layouts.unrootedLayout;
diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js
index c955cc978..c4d2796ba 100644
--- a/src/components/tree/phyloTree/renderers.js
+++ b/src/components/tree/phyloTree/renderers.js
@@ -7,6 +7,7 @@ import { getEmphasizedColor } from "../../../util/colorHelpers";
* @param {d3 selection} svg -- the svg into which the tree is drawn
* @param {string} layout -- the layout to be used, e.g. "rect"
* @param {string} distance -- the property used as branch length, e.g. div or num_date
+ * @param {string} treeFocus -- whether to focus on filtered nodes
* @param {object} parameters -- an object that contains options that will be added to this.params
* @param {object} callbacks -- an object with call back function defining mouse behavior
* @param {array} branchThickness -- array of branch thicknesses (same ordering as tree nodes)
@@ -21,7 +22,7 @@ import { getEmphasizedColor } from "../../../util/colorHelpers";
* @param {object} scatterVariables -- {x, y} properties to map nodes => scatterplot (only used if layout="scatter")
* @return {null}
*/
-export const render = function render(svg, layout, distance, parameters, callbacks, branchThickness, visibility, drawConfidence, vaccines, branchStroke, tipStroke, tipFill, tipRadii, dateRange, scatterVariables) {
+export const render = function render(svg, layout, distance, treeFocus, parameters, callbacks, branchThickness, visibility, drawConfidence, vaccines, branchStroke, tipStroke, tipFill, tipRadii, dateRange, scatterVariables) {
timerStart("phyloTree render()");
this.svg = svg;
this.params = Object.assign(this.params, parameters);
@@ -42,6 +43,7 @@ export const render = function render(svg, layout, distance, parameters, callbac
/* set x, y values & scale them to the screen */
setDisplayOrder(this.nodes);
this.setDistance(distance);
+ this.setTreeFocus(treeFocus);
this.setLayout(layout, scatterVariables);
this.mapToScreen();
diff --git a/src/components/tree/reactD3Interface/change.js b/src/components/tree/reactD3Interface/change.js
index 07bff8b1b..5fdd7e31c 100644
--- a/src/components/tree/reactD3Interface/change.js
+++ b/src/components/tree/reactD3Interface/change.js
@@ -49,6 +49,15 @@ export const changePhyloTreeViaPropsComparison = (mainTree, phylotree, oldProps,
args.changeNodeOrder = true;
}
+ /* change treeFocus behavior */
+ // FIXME: re-focus when:
+ // 1. filters have changed or been removed
+ // 2. zoom level has changed
+ if (oldProps.treeFocus !== newProps.treeFocus) {
+ args.newTreeFocus = newProps.treeFocus;
+ args.updateLayout = true;
+ }
+
/* change in key used to define branch labels, tip labels */
if (oldProps.canRenderBranchLabels===true && newProps.canRenderBranchLabels===false) {
args.newBranchLabellingKey = "none";
diff --git a/src/components/tree/reactD3Interface/initialRender.js b/src/components/tree/reactD3Interface/initialRender.js
index b85dd9917..87b5b4b95 100644
--- a/src/components/tree/reactD3Interface/initialRender.js
+++ b/src/components/tree/reactD3Interface/initialRender.js
@@ -22,6 +22,7 @@ export const renderTree = (that, main, phylotree, props) => {
select(ref),
props.layout,
props.distanceMeasure,
+ props.treeFocus,
{ /* parameters (modifies PhyloTree's defaults) */
grid: true,
confidence: props.temporalConfidence.display,
diff --git a/src/components/tree/tree.js b/src/components/tree/tree.js
index f53ff72ce..0281102b0 100644
--- a/src/components/tree/tree.js
+++ b/src/components/tree/tree.js
@@ -2,6 +2,7 @@ import React from "react";
import { withTranslation } from "react-i18next";
import { FaSearchMinus } from "react-icons/fa";
import { updateVisibleTipsAndBranchThicknesses } from "../../actions/tree";
+import { TOGGLE_TREE_FOCUS } from "../../actions/types";
import Card from "../framework/card";
import Legend from "./legend/legend";
import PhyloTree from "./phyloTree/phyloTree";
@@ -42,6 +43,7 @@ class Tree extends React.Component {
}
redrawTree = () => {
+ this.props.dispatch({ type: TOGGLE_TREE_FOCUS, focus: false });
this.props.dispatch(updateVisibleTipsAndBranchThicknesses({
root: [0, 0]
}));
@@ -127,6 +129,8 @@ class Tree extends React.Component {
const activeResetTreeButton = anyTreeZoomed;
+ const activeFocusButton = true;
+
return {
treeButtonsDiv: {
zIndex: 100,
@@ -148,6 +152,13 @@ class Tree extends React.Component {
color: activeZoomButton ? darkGrey : lightGrey,
pointerEvents: activeZoomButton ? "auto" : "none"
},
+ focusOnSelectedButton: {
+ zIndex: 100,
+ display: "inline-block",
+ cursor: activeFocusButton ? "pointer" : "auto",
+ color: activeFocusButton ? darkGrey : lightGrey,
+ pointerEvents: activeFocusButton ? "auto" : "none"
+ },
zoomOutButton: {
zIndex: 100,
display: "inline-block",
@@ -171,6 +182,10 @@ class Tree extends React.Component {
);
}
+ toggleFocus = () => {
+ this.props.dispatch({ type: TOGGLE_TREE_FOCUS });
+ };
+
zoomToSelected = () => {
this.props.dispatch(updateVisibleTipsAndBranchThicknesses({
root: [this.props.tree.idxOfFilteredRoot, this.props.treeToo.idxOfFilteredRoot]
@@ -262,6 +277,12 @@ class Tree extends React.Component {
>
{t("Zoom to Selected")}
+