From 4953e3c77b8f1656d77a945003573411c023e77f Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 24 Aug 2018 17:28:03 -0700 Subject: [PATCH 1/4] branches are only visible if the tips are in [minDate,maxDate] --- src/util/treeVisibilityHelpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/treeVisibilityHelpers.js b/src/util/treeVisibilityHelpers.js index 357a27b5d..ccaf2b890 100644 --- a/src/util/treeVisibilityHelpers.js +++ b/src/util/treeVisibilityHelpers.js @@ -133,9 +133,9 @@ const calcVisibility = (tree, controls, dates) => { visibility = visibility.map((cv, idx) => (cv && filtered[idx])); } - // TIME FILTERING (internal + terminal nodes) + // TIME FILTERING: only show tips that are within the selected temporal window const timeFiltered = tree.nodes.map((d) => { - return !(d.attr.num_date < dates.dateMinNumeric || d.parent.attr.num_date > dates.dateMaxNumeric); + return d.attr.num_date >= dates.dateMinNumeric && d.attr.num_date <= dates.dateMaxNumeric; }); visibility = visibility.map((cv, idx) => (cv && timeFiltered[idx])); From fafed447c2ca401068010b342566d744c8bb7da0 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 24 Aug 2018 17:31:06 -0700 Subject: [PATCH 2/4] code clean up (should be faster) --- src/util/treeVisibilityHelpers.js | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/util/treeVisibilityHelpers.js b/src/util/treeVisibilityHelpers.js index ccaf2b890..5d9057e26 100644 --- a/src/util/treeVisibilityHelpers.js +++ b/src/util/treeVisibilityHelpers.js @@ -82,32 +82,18 @@ FILTERS: */ const calcVisibility = (tree, controls, dates) => { if (tree.nodes) { - /* reset visibility */ - let visibility = tree.nodes.map(() => true); - - // if we have an analysis slider active, then we must filter on that as well - // note that min date for analyis doesnt apply - // commented out as analysis slider will probably be removed soon! - // if (controls.analysisSlider && controls.analysisSlider.valid) { - // /* extra slider is numerical rounded to 2dp */ - // const valid = tree.nodes.map((d) => - // d.attr[controls.analysisSlider.key] ? Math.round(d.attr[controls.analysisSlider.key] * 100) / 100 <= controls.analysisSlider.value : true - // ); - // visibility = visibility.map((cv, idx) => (cv && valid[idx])); - // } - - // IN VIEW FILTERING (internal + terminal nodes) - /* edge case: this fn may be called before the shell structure of the nodes - has been created (i.e. phyloTree's not run yet). In this case, it's - safe to assume that everything's in view */ + /* inView represents nodes that are within the current view window (i.e. not off the screen) */ let inView; try { inView = tree.nodes.map((d) => d.shell.inView); } catch (e) { + /* edge case: this fn may be called before the shell structure of the nodes + has been created (i.e. phyloTree's not run yet). In this case, it's + safe to assume that everything's in view */ inView = tree.nodes.map(() => true); } - /* intersect visibility and inView */ - visibility = visibility.map((cv, idx) => (cv && inView[idx])); + /* create default visibility to mirror inView (i.e. everything inView starts off visible) */ + let visibility = inView.map((cv) => cv); // FILTERS const filterPairs = []; From 98094142b5a86d2f39caea92b7a493ebb3a3c49e Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Thu, 30 Aug 2018 14:55:11 -0700 Subject: [PATCH 3/4] Visibility array now contains "levels" of visiblity This allows better representation of "visible" nodes across the tree (w.r.t the selected time slice), whilst preserving the desired map behavior. --- src/components/controls/search.js | 2 +- src/components/info/info.js | 2 +- src/components/map/mapHelpersLatLong.js | 8 +-- src/components/tree/phyloTree/change.js | 4 +- src/components/tree/phyloTree/renderers.js | 8 +-- .../tree/reactD3Interface/callbacks.js | 8 +-- src/util/entropy.js | 6 +-- src/util/processFrequencies.js | 2 +- src/util/treeCountingHelpers.js | 4 +- src/util/treeTangleHelpers.js | 4 +- src/util/treeVisibilityHelpers.js | 49 +++++++++++-------- 11 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/components/controls/search.js b/src/components/controls/search.js index 112b839e3..5e0649c0d 100644 --- a/src/components/controls/search.js +++ b/src/components/controls/search.js @@ -81,7 +81,7 @@ class SearchStrains extends React.Component { /* this tells the serch box which strains are visible and therefore are eligible to be searched */ this.state.awesomplete.list = this.props.nodes - .filter((n) => !n.hasChildren && this.props.visibility[n.arrayIdx] === "visible") + .filter((n) => !n.hasChildren && this.props.visibility[n.arrayIdx] === 2) .map((n) => n.strain); this.state.awesomplete.evaluate(); } diff --git a/src/components/info/info.js b/src/components/info/info.js index 4eb69b49f..2eb1020cf 100644 --- a/src/components/info/info.js +++ b/src/components/info/info.js @@ -32,7 +32,7 @@ const styliseDateRange = (dateStr) => { const getNumSelectedTips = (nodes, visibility) => { let count = 0; nodes.forEach((d, idx) => { - if (!d.hasChildren && visibility[idx] === "visible") count += 1; + if (!d.hasChildren && visibility[idx] === 2) count += 1; }); return count; }; diff --git a/src/components/map/mapHelpersLatLong.js b/src/components/map/mapHelpersLatLong.js index f3f755ca0..972734327 100644 --- a/src/components/map/mapHelpersLatLong.js +++ b/src/components/map/mapHelpersLatLong.js @@ -61,7 +61,7 @@ const setupDemeData = (nodes, visibility, geoResolution, nodeColors, triplicate, // second pass to fill vectors nodes.forEach((n, i) => { /* demes only count terminal nodes */ - if (!n.children && visibility[i] === "visible") { + if (!n.children && visibility[i] !== 0) { // if tip and visible, push if (n.attr[geoResolution]) { // check for undefined demeMap[n.attr[geoResolution]].push(nodeColors[i]); @@ -202,7 +202,7 @@ const maybeConstructTransmissionEvent = ( originNumDate: node.attr["num_date"], destinationNumDate: child.attr["num_date"], color: nodeColors[node.arrayIdx], - visible: visibility[child.arrayIdx] === "visible" ? "visible" : "hidden" // transmission visible if child is visible + visible: visibility[child.arrayIdx] !== 0 ? "visible" : "hidden" // transmission visible if child is visible }; } return transmission; @@ -370,7 +370,7 @@ const updateDemeDataColAndVis = (demeData, demeIndices, nodes, visibility, geoRe // second pass to fill vectors nodes.forEach((n, i) => { /* demes only count terminal nodes */ - if (!n.children && visibility[i] === "visible") { + if (!n.children && visibility[i] !== 0) { // if tip and visible, push if (n.attr[geoResolution]) { // check for undefined demeMap[n.attr[geoResolution]].push(nodeColors[i]); @@ -403,7 +403,7 @@ const updateTransmissionDataColAndVis = (transmissionData, transmissionIndices, // this is a transmission event from n to child const id = node.arrayIdx.toString() + "-" + child.arrayIdx.toString(); const col = nodeColors[node.arrayIdx]; - const vis = visibility[child.arrayIdx] === "visible" ? "visible" : "hidden"; // transmission visible if child is visible + const vis = visibility[child.arrayIdx] !== 0 ? "visible" : "hidden"; // transmission visible if child is visible // update transmissionData via index lookup try { diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 6d2e46e58..9f780e16b 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -48,7 +48,7 @@ const svgSetters = { ".tip": { fill: (d) => d.fill, stroke: (d) => d.tipStroke, - visibility: (d) => d["visibility"] + visibility: (d) => d.visibility === 2 ? "visible" : "hidden" }, ".conf": { stroke: (d) => d.branchStroke, @@ -57,7 +57,7 @@ const svgSetters = { ".branch": { stroke: (d) => d.branchStroke, "stroke-width": (d) => d["stroke-width"] + "px", // style - as per drawBranches() - cursor: (d) => d.visibility === "visible" ? "pointer" : "default" + cursor: (d) => d.visibility === 2 ? "pointer" : "default" } } }; diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index 813189a8c..74cf1be5e 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -6,8 +6,8 @@ import { timerStart, timerEnd } from "../../../util/perf"; * @param {string} distance -- the property used as branch length, e.g. div or num_date * @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 shape as tree nodes) - * @param {array} visibility -- array of "visible" or "hidden" (same shape as tree nodes) + * @param {array} branchThickness -- array of branch thicknesses (same ordering as tree nodes) + * @param {array} visibility -- array of valies in {0, 1, 2} (same ordering as tree nodes) * @param {bool} drawConfidence -- should confidence intervals be drawn? * @param {bool} vaccines -- should vaccine crosses (and dotted lines if applicable) be drawn? * @param {array} branchStroke -- branch stroke colour for each node (set onto each node) @@ -110,7 +110,7 @@ export const drawTips = function drawTips() { .on("mouseout", this.callbacks.onTipLeave) .on("click", this.callbacks.onTipClick) .style("pointer-events", "auto") - .style("visibility", (d) => d["visibility"]) + .style("visibility", (d) => d.visibility === 2 ? "visible" : "hidden") .style("fill", (d) => d.fill || params.tipFill) .style("stroke", (d) => d.tipStroke || params.tipStroke) .style("stroke-width", () => params.tipStrokeWidth) /* don't want branch thicknesses applied */ @@ -166,7 +166,7 @@ export const drawBranches = function drawBranches() { .style("stroke-linecap", "round") .style("stroke-width", (d) => d['stroke-width']+"px" || params.branchStrokeWidth) .style("fill", "none") - .style("cursor", (d) => d.visibility === "visible" ? "pointer" : "default") + .style("cursor", (d) => d.visibility === 2 ? "pointer" : "default") .style("pointer-events", "auto") .on("mouseover", this.callbacks.onBranchHover) .on("mouseout", this.callbacks.onBranchLeave) diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index 7e5e8c594..8fc159474 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -6,7 +6,7 @@ import { branchOpacityFunction } from "../../../util/colorHelpers"; /* Callbacks used by the tips / branches when hovered / selected */ export const onTipHover = function onTipHover(d) { - if (d.visibility !== "visible") return; + if (d.visibility !== 2) return; const phylotree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo; @@ -18,7 +18,7 @@ export const onTipHover = function onTipHover(d) { }; export const onTipClick = function onTipClick(d) { - if (d.visibility !== "visible") return; + if (d.visibility !== 2) return; if (this.props.narrativeMode) return; // console.log("tip click", d) this.setState({ @@ -34,7 +34,7 @@ export const onTipClick = function onTipClick(d) { export const onBranchHover = function onBranchHover(d) { - if (d.visibility !== "visible") return; + if (d.visibility !== 2) return; /* emphasize the color of the branch */ for (const id of ["#branch_S_" + d.n.clade, "#branch_T_" + d.n.clade]) { if (this.props.colorByConfidence) { @@ -67,7 +67,7 @@ export const onBranchHover = function onBranchHover(d) { }; export const onBranchClick = function onBranchClick(d) { - if (d.visibility !== "visible") return; + if (d.visibility !== 2) return; if (this.props.narrativeMode) return; const root = [undefined, undefined]; if (d.that.params.orientation[0] === 1) root[0] = d.n.arrayIdx; diff --git a/src/util/entropy.js b/src/util/entropy.js index ed42e9b4d..0593d43fe 100644 --- a/src/util/entropy.js +++ b/src/util/entropy.js @@ -15,7 +15,7 @@ const calcMutationCounts = (nodes, visibility, geneMap, isAA) => { Object.keys(geneMap).forEach((n) => {sparse[n] = {};}); } nodes.forEach((n) => { - if (visibility[n.arrayIdx] !== "visible") {return;} + if (visibility[n.arrayIdx] !== 2) {return;} if (isAA) { if (n.aa_muts) { for (const prot in n.aa_muts) { // eslint-disable-line @@ -126,7 +126,7 @@ const calcEntropy = (nodes, visibility, geneMap, isAA) => { }); recurse(child, newState); } - } else if (visibility[node.arrayIdx] === "visible") { + } else if (visibility[node.arrayIdx] === 2) { visibleTips++; for (const prot of arrayOfProts) { for (const pos of Object.keys(state[prot])) { @@ -193,7 +193,7 @@ const calcEntropy = (nodes, visibility, geneMap, isAA) => { /** * traverse the tree and compile the entropy data for the visibile branches * @param {Array} nodes - list of nodes -* @param {Array} visibility - 1-1 correspondence with nodes. Value: "visibile" or "" +* @param {Array} visibility - 1-1 correspondence with nodes. values: {0, 1, 2} * @param {String} mutType - amino acid | nucleotide mutations - "aa" | "nuc" * @param {obj} geneMap used to NT fill colours. This should be imroved. * @param {bool} showCounts show counts or entropy values? diff --git a/src/util/processFrequencies.js b/src/util/processFrequencies.js index aed4b4173..d222be333 100644 --- a/src/util/processFrequencies.js +++ b/src/util/processFrequencies.js @@ -42,7 +42,7 @@ export const computeMatrixFromRawData = (data, pivots, nodes, visibility, colorS // let debugTipsSeen = 0; const debugPivotTotals = new Array(pivotsLen).fill(0); data.forEach((d) => { - if (visibility[d.idx] === "visible") { + if (visibility[d.idx] === 2) { // debugTipsSeen++; // const colour = tree.nodes[d.idx].attr[colorBy]; const category = assignCategory(colorScale, categories, nodes[d.idx], colorBy, isGenotype) || unassigned_label; diff --git a/src/util/treeCountingHelpers.js b/src/util/treeCountingHelpers.js index a47a6f2bd..7d6a322fc 100644 --- a/src/util/treeCountingHelpers.js +++ b/src/util/treeCountingHelpers.js @@ -22,7 +22,7 @@ export const countTraitsAcrossTree = (nodes, traits, visibility, terminalOnly) = return; } - if (visibility && visibility[node.arrayIdx] !== "visible") { + if (visibility && visibility[node.arrayIdx] !== 2) { return; } @@ -45,6 +45,6 @@ export const calcTipCounts = (node, visibility) => { node.tipCount += node.children[i].tipCount; } } else { - node.tipCount = visibility[node.arrayIdx] === "visible" ? 1 : 0; + node.tipCount = visibility[node.arrayIdx] === 2 ? 1 : 0; } }; diff --git a/src/util/treeTangleHelpers.js b/src/util/treeTangleHelpers.js index b45a146c9..8a242cd60 100644 --- a/src/util/treeTangleHelpers.js +++ b/src/util/treeTangleHelpers.js @@ -13,8 +13,8 @@ export const constructVisibleTipLookupBetweenTrees = (nodesLeft, nodesRight, vis if ( !nodesLeft[i].hasChildren && rightIdx && - visibilityLeft[i] === "visible" && - visibilityRight[rightIdx] === "visible" + visibilityLeft[i] === 2 && + visibilityRight[rightIdx] === 2 ) { lookup.push([i, tree2StrainToIdxMap[nodesLeft[i].strain]]); } diff --git a/src/util/treeVisibilityHelpers.js b/src/util/treeVisibilityHelpers.js index 5d9057e26..e999021f7 100644 --- a/src/util/treeVisibilityHelpers.js +++ b/src/util/treeVisibilityHelpers.js @@ -28,7 +28,7 @@ const calcBranchThickness = (nodes, visibility, rootIdx) => { maxTipCount = 1; } return nodes.map((d, idx) => ( - visibility[idx] === "visible" ? freqScale((d.tipCount + 5) / (maxTipCount + 5)) : 0.5 + visibility[idx] === 2 ? freqScale((d.tipCount + 5) / (maxTipCount + 5)) : 0.5 )); }; @@ -46,13 +46,13 @@ const makeParentVisible = (visArray, node) => { * Create a visibility array to show the path through the tree to the selected tip * @param {array} nodes redux tree nodes * @param {int} tipIdx idx of the selected tip - * @return {array} visibility array (values of "visible" | "hidden") + * @return {array} visibility array (values in {0, 1, 2}) */ const identifyPathToTip = (nodes, tipIdx) => { const visibility = new Array(nodes.length).fill(false); visibility[tipIdx] = true; makeParentVisible(visibility, nodes[tipIdx]); /* recursive */ - return visibility.map((cv) => cv ? "visible" : "hidden"); + return visibility.map((cv) => cv ? 2 : 0); }; @@ -64,12 +64,15 @@ controls.filters use dates NOT controls.dateMin & controls.dateMax RETURNS: -visibility: array of "visible" or "hidden" +visibility: array of integers in {0, 1, 2} + - 0: not displayed by map. Potentially displayed by tree as a thin branch. + - 1: available for display by the map. Displayed by tree as a thin branch. + - 2: Displayed by both the map and the tree. ROUGH DESCRIPTION OF HOW FILTERING IS APPLIED: + - inView filtering (reflects tree zooming): Nodes which are not inView always have visibility=0 - time filtering is simple - all nodes (internal + terminal) not within (tmin, tmax) are excluded. - - inView filtering is similar - nodes out of the view cannot possibly be visible - - filters are a bit more tricky - the visibile tips are calculated, and the parent +- filters are a bit more tricky - the visibile tips are calculated, and the parent branches back to the MRCA are considered visibile. This is then intersected with the time & inView visibile stuff @@ -92,10 +95,9 @@ const calcVisibility = (tree, controls, dates) => { safe to assume that everything's in view */ inView = tree.nodes.map(() => true); } - /* create default visibility to mirror inView (i.e. everything inView starts off visible) */ - let visibility = inView.map((cv) => cv); // FILTERS + let filtered; const filterPairs = []; Object.keys(controls.filters).forEach((key) => { if (controls.filters[key].length) { @@ -104,8 +106,8 @@ const calcVisibility = (tree, controls, dates) => { }); if (filterPairs.length) { /* find the terminal nodes that were (a) already visibile and (b) match the filters */ - const filtered = tree.nodes.map((d, idx) => ( - !d.hasChildren && visibility[idx] && filterPairs.every((x) => x[1].indexOf(d.attr[x[0]]) > -1) + filtered = tree.nodes.map((d, idx) => ( + !d.hasChildren && inView[idx] && filterPairs.every((x) => x[1].indexOf(d.attr[x[0]]) > -1) )); const idxsOfFilteredTips = filtered.reduce((a, e, i) => { if (e) {a.push(i);} @@ -115,20 +117,27 @@ const calcVisibility = (tree, controls, dates) => { for (let i = 0; i < idxsOfFilteredTips.length; i++) { makeParentVisible(filtered, tree.nodes[idxsOfFilteredTips[i]]); } - /* intersect visibility and filtered */ - visibility = visibility.map((cv, idx) => (cv && filtered[idx])); } - // TIME FILTERING: only show tips that are within the selected temporal window - const timeFiltered = tree.nodes.map((d) => { - return d.attr.num_date >= dates.dateMinNumeric && d.attr.num_date <= dates.dateMaxNumeric; + /* intersect the various arrays contributing to visibility */ + const visibility = tree.nodes.map((node, idx) => { + if (inView[idx] && (filtered ? filtered[idx] : true)) { + const nodeDate = node.attr.num_date; + /* is the actual node date (the "end" of the branch) in the time slice? */ + if (nodeDate >= dates.dateMinNumeric && nodeDate <= dates.dateMaxNumeric) { + return 2; + } + /* is any part of the (parent date -> node date) in the time slice? */ + if (!(nodeDate < dates.dateMinNumeric || node.parent.attr.num_date > dates.dateMaxNumeric)) { + return 1; + } + } + return 0; }); - visibility = visibility.map((cv, idx) => (cv && timeFiltered[idx])); - - /* return array of "visible" or "hidden" values */ - return visibility.map((cv) => cv ? "visible" : "hidden"); + return visibility; } - return "visible"; + console.error("calcVisibility ran without tree.nodes"); + return 2; }; From f2873af39d12ffd76aeb155bbc56f27e7fbb411c Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 31 Aug 2018 11:05:14 -0700 Subject: [PATCH 4/4] use named variables to increase code clarity --- src/components/controls/search.js | 3 ++- src/components/info/info.js | 4 ++-- src/components/map/mapHelpersLatLong.js | 10 ++++++---- src/components/tree/phyloTree/change.js | 5 +++-- src/components/tree/phyloTree/renderers.js | 7 ++++--- src/components/tree/reactD3Interface/callbacks.js | 9 +++++---- src/util/entropy.js | 8 ++++---- src/util/globals.js | 4 ++++ src/util/processFrequencies.js | 3 ++- src/util/treeCountingHelpers.js | 6 ++++-- src/util/treeTangleHelpers.js | 5 +++-- src/util/treeVisibilityHelpers.js | 11 +++++------ 12 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/components/controls/search.js b/src/components/controls/search.js index 5e0649c0d..a485edc41 100644 --- a/src/components/controls/search.js +++ b/src/components/controls/search.js @@ -3,6 +3,7 @@ import { connect } from "react-redux"; import Awesomplete from 'awesomplete'; /* https://leaverou.github.io/awesomplete/ */ import { updateVisibleTipsAndBranchThicknesses, updateTipRadii } from "../../actions/tree"; import { dataFont, darkGrey } from "../../globalStyles"; +import { NODE_VISIBLE } from "../../util/globals"; import { SelectLabel } from "../framework/select-label"; import "../../css/awesomplete.css"; @@ -81,7 +82,7 @@ class SearchStrains extends React.Component { /* this tells the serch box which strains are visible and therefore are eligible to be searched */ this.state.awesomplete.list = this.props.nodes - .filter((n) => !n.hasChildren && this.props.visibility[n.arrayIdx] === 2) + .filter((n) => !n.hasChildren && this.props.visibility[n.arrayIdx] === NODE_VISIBLE) .map((n) => n.strain); this.state.awesomplete.evaluate(); } diff --git a/src/components/info/info.js b/src/components/info/info.js index 2eb1020cf..08dda04b6 100644 --- a/src/components/info/info.js +++ b/src/components/info/info.js @@ -4,7 +4,7 @@ import Card from "../framework/card"; import { titleFont, headerFont, medGrey, darkGrey } from "../../globalStyles"; import { applyFilter, changeDateFilter, updateVisibleTipsAndBranchThicknesses } from "../../actions/tree"; import { prettyString } from "../../util/stringHelpers"; -import { months } from "../../util/globals"; +import { months, NODE_VISIBLE } from "../../util/globals"; import { displayFilterValueAsButton } from "../framework/footer"; const plurals = { @@ -32,7 +32,7 @@ const styliseDateRange = (dateStr) => { const getNumSelectedTips = (nodes, visibility) => { let count = 0; nodes.forEach((d, idx) => { - if (!d.hasChildren && visibility[idx] === 2) count += 1; + if (!d.hasChildren && visibility[idx] === NODE_VISIBLE) count += 1; }); return count; }; diff --git a/src/components/map/mapHelpersLatLong.js b/src/components/map/mapHelpersLatLong.js index 972734327..193a93c34 100644 --- a/src/components/map/mapHelpersLatLong.js +++ b/src/components/map/mapHelpersLatLong.js @@ -4,6 +4,8 @@ import _minBy from "lodash/minBy"; import { interpolateNumber } from "d3-interpolate"; import { averageColors } from "../../util/colorHelpers"; import { computeMidpoint, Bezier } from "./transmissionBezier"; +import { NODE_NOT_VISIBLE } from "../../util/globals"; + /* global L */ // L is global in scope and placed by leaflet() @@ -61,7 +63,7 @@ const setupDemeData = (nodes, visibility, geoResolution, nodeColors, triplicate, // second pass to fill vectors nodes.forEach((n, i) => { /* demes only count terminal nodes */ - if (!n.children && visibility[i] !== 0) { + if (!n.children && visibility[i] !== NODE_NOT_VISIBLE) { // if tip and visible, push if (n.attr[geoResolution]) { // check for undefined demeMap[n.attr[geoResolution]].push(nodeColors[i]); @@ -202,7 +204,7 @@ const maybeConstructTransmissionEvent = ( originNumDate: node.attr["num_date"], destinationNumDate: child.attr["num_date"], color: nodeColors[node.arrayIdx], - visible: visibility[child.arrayIdx] !== 0 ? "visible" : "hidden" // transmission visible if child is visible + visible: visibility[child.arrayIdx] !== NODE_NOT_VISIBLE ? "visible" : "hidden" // transmission visible if child is visible }; } return transmission; @@ -370,7 +372,7 @@ const updateDemeDataColAndVis = (demeData, demeIndices, nodes, visibility, geoRe // second pass to fill vectors nodes.forEach((n, i) => { /* demes only count terminal nodes */ - if (!n.children && visibility[i] !== 0) { + if (!n.children && visibility[i] !== NODE_NOT_VISIBLE) { // if tip and visible, push if (n.attr[geoResolution]) { // check for undefined demeMap[n.attr[geoResolution]].push(nodeColors[i]); @@ -403,7 +405,7 @@ const updateTransmissionDataColAndVis = (transmissionData, transmissionIndices, // this is a transmission event from n to child const id = node.arrayIdx.toString() + "-" + child.arrayIdx.toString(); const col = nodeColors[node.arrayIdx]; - const vis = visibility[child.arrayIdx] !== 0 ? "visible" : "hidden"; // transmission visible if child is visible + const vis = visibility[child.arrayIdx] !== NODE_NOT_VISIBLE ? "visible" : "hidden"; // transmission visible if child is visible // update transmissionData via index lookup try { diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 9f780e16b..2ba43ac14 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -2,6 +2,7 @@ import { timerFlush } from "d3-timer"; import { calcConfidenceWidth } from "./confidence"; import { applyToChildren } from "./helpers"; import { timerStart, timerEnd } from "../../../util/perf"; +import { NODE_VISIBLE } from "../../../util/globals"; /* loop through the nodes and update each provided prop with the new value * additionally, set d.update -> whether or not the node props changed @@ -48,7 +49,7 @@ const svgSetters = { ".tip": { fill: (d) => d.fill, stroke: (d) => d.tipStroke, - visibility: (d) => d.visibility === 2 ? "visible" : "hidden" + visibility: (d) => d.visibility === NODE_VISIBLE ? "visible" : "hidden" }, ".conf": { stroke: (d) => d.branchStroke, @@ -57,7 +58,7 @@ const svgSetters = { ".branch": { stroke: (d) => d.branchStroke, "stroke-width": (d) => d["stroke-width"] + "px", // style - as per drawBranches() - cursor: (d) => d.visibility === 2 ? "pointer" : "default" + cursor: (d) => d.visibility === NODE_VISIBLE ? "pointer" : "default" } } }; diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index 74cf1be5e..06b4b758a 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -1,4 +1,5 @@ import { timerStart, timerEnd } from "../../../util/perf"; +import { NODE_VISIBLE } from "../../../util/globals"; /** * @param {d3 selection} svg -- the svg into which the tree is drawn @@ -7,7 +8,7 @@ import { timerStart, timerEnd } from "../../../util/perf"; * @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) - * @param {array} visibility -- array of valies in {0, 1, 2} (same ordering as tree nodes) + * @param {array} visibility -- array of visibility of nodes(same ordering as tree nodes) * @param {bool} drawConfidence -- should confidence intervals be drawn? * @param {bool} vaccines -- should vaccine crosses (and dotted lines if applicable) be drawn? * @param {array} branchStroke -- branch stroke colour for each node (set onto each node) @@ -110,7 +111,7 @@ export const drawTips = function drawTips() { .on("mouseout", this.callbacks.onTipLeave) .on("click", this.callbacks.onTipClick) .style("pointer-events", "auto") - .style("visibility", (d) => d.visibility === 2 ? "visible" : "hidden") + .style("visibility", (d) => d.visibility === NODE_VISIBLE ? "visible" : "hidden") .style("fill", (d) => d.fill || params.tipFill) .style("stroke", (d) => d.tipStroke || params.tipStroke) .style("stroke-width", () => params.tipStrokeWidth) /* don't want branch thicknesses applied */ @@ -166,7 +167,7 @@ export const drawBranches = function drawBranches() { .style("stroke-linecap", "round") .style("stroke-width", (d) => d['stroke-width']+"px" || params.branchStrokeWidth) .style("fill", "none") - .style("cursor", (d) => d.visibility === 2 ? "pointer" : "default") + .style("cursor", (d) => d.visibility === NODE_VISIBLE ? "pointer" : "default") .style("pointer-events", "auto") .on("mouseover", this.callbacks.onBranchHover) .on("mouseout", this.callbacks.onBranchLeave) diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index 8fc159474..59ff89324 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -2,11 +2,12 @@ import { rgb } from "d3-color"; import { interpolateRgb } from "d3-interpolate"; import { updateVisibleTipsAndBranchThicknesses} from "../../../actions/tree"; import { branchOpacityFunction } from "../../../util/colorHelpers"; +import { NODE_VISIBLE } from "../../../util/globals"; /* Callbacks used by the tips / branches when hovered / selected */ export const onTipHover = function onTipHover(d) { - if (d.visibility !== 2) return; + if (d.visibility !== NODE_VISIBLE) return; const phylotree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo; @@ -18,7 +19,7 @@ export const onTipHover = function onTipHover(d) { }; export const onTipClick = function onTipClick(d) { - if (d.visibility !== 2) return; + if (d.visibility !== NODE_VISIBLE) return; if (this.props.narrativeMode) return; // console.log("tip click", d) this.setState({ @@ -34,7 +35,7 @@ export const onTipClick = function onTipClick(d) { export const onBranchHover = function onBranchHover(d) { - if (d.visibility !== 2) return; + if (d.visibility !== NODE_VISIBLE) return; /* emphasize the color of the branch */ for (const id of ["#branch_S_" + d.n.clade, "#branch_T_" + d.n.clade]) { if (this.props.colorByConfidence) { @@ -67,7 +68,7 @@ export const onBranchHover = function onBranchHover(d) { }; export const onBranchClick = function onBranchClick(d) { - if (d.visibility !== 2) return; + if (d.visibility !== NODE_VISIBLE) return; if (this.props.narrativeMode) return; const root = [undefined, undefined]; if (d.that.params.orientation[0] === 1) root[0] = d.n.arrayIdx; diff --git a/src/util/entropy.js b/src/util/entropy.js index 0593d43fe..af251f8b8 100644 --- a/src/util/entropy.js +++ b/src/util/entropy.js @@ -1,4 +1,4 @@ -import { genotypeColors } from "./globals"; +import { genotypeColors, NODE_VISIBLE } from "./globals"; const intersectGenes = function intersectGenes(geneMap, pos) { for (const gene of Object.keys(geneMap)) { @@ -15,7 +15,7 @@ const calcMutationCounts = (nodes, visibility, geneMap, isAA) => { Object.keys(geneMap).forEach((n) => {sparse[n] = {};}); } nodes.forEach((n) => { - if (visibility[n.arrayIdx] !== 2) {return;} + if (visibility[n.arrayIdx] !== NODE_VISIBLE) {return;} if (isAA) { if (n.aa_muts) { for (const prot in n.aa_muts) { // eslint-disable-line @@ -126,7 +126,7 @@ const calcEntropy = (nodes, visibility, geneMap, isAA) => { }); recurse(child, newState); } - } else if (visibility[node.arrayIdx] === 2) { + } else if (visibility[node.arrayIdx] === NODE_VISIBLE) { visibleTips++; for (const prot of arrayOfProts) { for (const pos of Object.keys(state[prot])) { @@ -193,7 +193,7 @@ const calcEntropy = (nodes, visibility, geneMap, isAA) => { /** * traverse the tree and compile the entropy data for the visibile branches * @param {Array} nodes - list of nodes -* @param {Array} visibility - 1-1 correspondence with nodes. values: {0, 1, 2} +* @param {Array} visibility - 1-1 correspondence with nodes. * @param {String} mutType - amino acid | nucleotide mutations - "aa" | "nuc" * @param {obj} geneMap used to NT fill colours. This should be imroved. * @param {bool} showCounts show counts or entropy values? diff --git a/src/util/globals.js b/src/util/globals.js index a61821d3a..ddec0aa24 100644 --- a/src/util/globals.js +++ b/src/util/globals.js @@ -174,3 +174,7 @@ export const months = { export const normalNavBarHeight = 50; export const narrativeNavBarHeight = 55; + +export const NODE_NOT_VISIBLE = 0; +export const NODE_VISIBLE_TO_MAP_ONLY = 1; +export const NODE_VISIBLE = 2; diff --git a/src/util/processFrequencies.js b/src/util/processFrequencies.js index d222be333..cd7e65866 100644 --- a/src/util/processFrequencies.js +++ b/src/util/processFrequencies.js @@ -1,3 +1,4 @@ +import { NODE_VISIBLE } from "./globals"; export const unassigned_label = "unassigned"; @@ -42,7 +43,7 @@ export const computeMatrixFromRawData = (data, pivots, nodes, visibility, colorS // let debugTipsSeen = 0; const debugPivotTotals = new Array(pivotsLen).fill(0); data.forEach((d) => { - if (visibility[d.idx] === 2) { + if (visibility[d.idx] === NODE_VISIBLE) { // debugTipsSeen++; // const colour = tree.nodes[d.idx].attr[colorBy]; const category = assignCategory(colorScale, categories, nodes[d.idx], colorBy, isGenotype) || unassigned_label; diff --git a/src/util/treeCountingHelpers.js b/src/util/treeCountingHelpers.js index 7d6a322fc..db83acc48 100644 --- a/src/util/treeCountingHelpers.js +++ b/src/util/treeCountingHelpers.js @@ -1,3 +1,5 @@ +import { NODE_VISIBLE } from "./globals"; + /** * traverse the tree to get state counts for supplied traits * @param {Array} nodes - list of nodes @@ -22,7 +24,7 @@ export const countTraitsAcrossTree = (nodes, traits, visibility, terminalOnly) = return; } - if (visibility && visibility[node.arrayIdx] !== 2) { + if (visibility && visibility[node.arrayIdx] !== NODE_VISIBLE) { return; } @@ -45,6 +47,6 @@ export const calcTipCounts = (node, visibility) => { node.tipCount += node.children[i].tipCount; } } else { - node.tipCount = visibility[node.arrayIdx] === 2 ? 1 : 0; + node.tipCount = visibility[node.arrayIdx] === NODE_VISIBLE ? 1 : 0; } }; diff --git a/src/util/treeTangleHelpers.js b/src/util/treeTangleHelpers.js index 8a242cd60..89f188acd 100644 --- a/src/util/treeTangleHelpers.js +++ b/src/util/treeTangleHelpers.js @@ -1,3 +1,4 @@ +import { NODE_VISIBLE } from "./globals"; export const constructVisibleTipLookupBetweenTrees = (nodesLeft, nodesRight, visibilityLeft, visibilityRight) => { const tree2StrainToIdxMap = {}; @@ -13,8 +14,8 @@ export const constructVisibleTipLookupBetweenTrees = (nodesLeft, nodesRight, vis if ( !nodesLeft[i].hasChildren && rightIdx && - visibilityLeft[i] === 2 && - visibilityRight[rightIdx] === 2 + visibilityLeft[i] === NODE_VISIBLE && + visibilityRight[rightIdx] === NODE_VISIBLE ) { lookup.push([i, tree2StrainToIdxMap[nodesLeft[i].strain]]); } diff --git a/src/util/treeVisibilityHelpers.js b/src/util/treeVisibilityHelpers.js index e999021f7..64f914f89 100644 --- a/src/util/treeVisibilityHelpers.js +++ b/src/util/treeVisibilityHelpers.js @@ -1,4 +1,4 @@ -import { freqScale } from "./globals"; +import { freqScale, NODE_NOT_VISIBLE, NODE_VISIBLE_TO_MAP_ONLY, NODE_VISIBLE } from "./globals"; import { calcTipCounts } from "./treeCountingHelpers"; export const strainNameToIdx = (nodes, name) => { @@ -125,22 +125,21 @@ const calcVisibility = (tree, controls, dates) => { const nodeDate = node.attr.num_date; /* is the actual node date (the "end" of the branch) in the time slice? */ if (nodeDate >= dates.dateMinNumeric && nodeDate <= dates.dateMaxNumeric) { - return 2; + return NODE_VISIBLE; } /* is any part of the (parent date -> node date) in the time slice? */ if (!(nodeDate < dates.dateMinNumeric || node.parent.attr.num_date > dates.dateMaxNumeric)) { - return 1; + return NODE_VISIBLE_TO_MAP_ONLY; } } - return 0; + return NODE_NOT_VISIBLE; }); return visibility; } console.error("calcVisibility ran without tree.nodes"); - return 2; + return NODE_VISIBLE; }; - export const calculateVisiblityAndBranchThickness = (tree, controls, dates, {idxOfInViewRootNode = 0, tipSelectedIdx = 0} = {}) => { const visibility = tipSelectedIdx ? identifyPathToTip(tree.nodes, tipSelectedIdx) : calcVisibility(tree, controls, dates); /* recalculate tipCounts over the tree - modifies redux tree nodes in place (yeah, I know) */