From 09bd44c4aacc4d6897b0c963329ff0ae2dd142e5 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 24 Aug 2018 11:56:30 -0700 Subject: [PATCH 1/4] remove hover infoboxes & clicks from non-visible tips & branches --- src/components/tree/reactD3Interface/callbacks.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index 769836ec8..23c393f5f 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -7,6 +7,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; const phylotree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo; @@ -18,6 +19,7 @@ export const onTipHover = function onTipHover(d) { }; export const onTipClick = function onTipClick(d) { + if (d.visibility !== "visible") return; // console.log("tip click", d) this.setState({ hovered: null, @@ -32,6 +34,7 @@ export const onTipClick = function onTipClick(d) { export const onBranchHover = function onBranchHover(d) { + if (d.visibility !== "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) { @@ -64,6 +67,7 @@ export const onBranchHover = function onBranchHover(d) { }; export const onBranchClick = function onBranchClick(d) { + if (d.visibility !== "visible") return; const root = [undefined, undefined]; if (d.that.params.orientation[0] === 1) root[0] = d.n.arrayIdx; else root[1] = d.n.arrayIdx; From 2cef097609b663c660b38af17e982120add8afe6 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 24 Aug 2018 12:08:00 -0700 Subject: [PATCH 2/4] make non-visible tree branches thinner Makes it clearer that they are not "in-view" --- src/util/treeVisibilityHelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/treeVisibilityHelpers.js b/src/util/treeVisibilityHelpers.js index 740cac3a7..357a27b5d 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)) : 1 + visibility[idx] === "visible" ? freqScale((d.tipCount + 5) / (maxTipCount + 5)) : 0.5 )); }; From e789ea5c08f04870e275e7bf1014e981b23a25b4 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 24 Aug 2018 12:31:34 -0700 Subject: [PATCH 3/4] branch cursor is pointer only if branch is visible --- src/components/tree/phyloTree/change.js | 19 ++++++++++--------- src/components/tree/phyloTree/renderers.js | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 80ccc65c6..1a9a5bca4 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -29,7 +29,7 @@ const updateNodesWithNewData = (nodes, newNodeProps) => { * Note that only the relevant functions are called on a transition. */ const svgSetters = { - "attrs": { + attrs: { ".tip": { r: (d) => d.r, cx: (d) => d.xTip, @@ -44,19 +44,20 @@ const svgSetters = { d: (d) => d.confLine } }, - "styles": { + styles: { ".tip": { - "fill": (d) => d.fill, - "stroke": (d) => d.tipStroke, - "visibility": (d) => d["visibility"] + fill: (d) => d.fill, + stroke: (d) => d.tipStroke, + visibility: (d) => d["visibility"] }, ".conf": { - "stroke": (d) => d.branchStroke, + stroke: (d) => d.branchStroke, "stroke-width": calcConfidenceWidth }, ".branch": { - "stroke": (d) => d.branchStroke, - "stroke-width": (d) => d["stroke-width"] + "px" // style - as per drawBranches() + stroke: (d) => d.branchStroke, + "stroke-width": (d) => d["stroke-width"] + "px", // style - as per drawBranches() + cursor: (d) => d.visibility === "visible" ? "pointer" : "default" } } }; @@ -275,7 +276,7 @@ export const change = function change({ /* check that visibility is not undefined */ /* in the future we also change the branch visibility (after skeleton merge) */ elemsToUpdate.add(".tip"); - svgPropsToUpdate.add("visibility"); + svgPropsToUpdate.add("visibility").add("cursor"); nodePropsToModify.visibility = visibility; } if (changeTipRadii) { diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index e2173a316..bf698a89d 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -162,7 +162,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", "pointer") + .style("cursor", (d) => d.visibility === "visible" ? "pointer" : "default") .style("pointer-events", "auto") .on("mouseover", this.callbacks.onBranchHover) .on("mouseout", this.callbacks.onBranchLeave) From f4dfb30d60669a1e677807fcf8a4a883cd9ff20a Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 24 Aug 2018 14:38:16 -0700 Subject: [PATCH 4/4] add temporal time slice to tree (rect layout only) --- src/components/tree/index.js | 2 + src/components/tree/phyloTree/change.js | 10 +++- src/components/tree/phyloTree/grid.js | 54 +++++++++++++++++-- src/components/tree/phyloTree/phyloTree.js | 2 + src/components/tree/phyloTree/renderers.js | 8 ++- .../tree/reactD3Interface/change.js | 4 ++ .../tree/reactD3Interface/initialRender.js | 4 +- 7 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/components/tree/index.js b/src/components/tree/index.js index 174ecd3e5..b972fb73f 100644 --- a/src/components/tree/index.js +++ b/src/components/tree/index.js @@ -4,6 +4,8 @@ import UnconnectedTree from "./tree"; const Tree = connect((state) => ({ tree: state.tree, treeToo: state.treeToo, + dateMinNumeric: state.controls.dateMinNumeric, + dateMaxNumeric: state.controls.dateMaxNumeric, quickdraw: state.controls.quickdraw, colorBy: state.controls.colorBy, colorByConfidence: state.controls.colorByConfidence, diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 1a9a5bca4..6d2e46e58 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -150,7 +150,7 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra this.updateTipLabels(); } if (elemsToUpdate.has('.grid')) { - if (this.grid && this.layout !== "unrooted") this.addGrid(this.layout); + if (this.grid && this.layout !== "unrooted") this.addGrid(); else this.hideGrid(); } if (elemsToUpdate.has('.regression')) { @@ -172,6 +172,11 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra } } + /* background temporal time slice */ + if (extras.timeSliceHasPotentiallyChanged) { + this.addTemporalSlice(); + } + /* branch labels */ if (extras.newBranchLabellingKey) { this.removeBranchLabels(); @@ -197,6 +202,7 @@ export const modifySVGInStages = function modifySVGInStages(elemsToUpdate, svgPr this.svg.selectAll(".tip").remove(); this.drawTips(); if (this.vaccines) this.drawVaccines(); + this.addTemporalSlice(); if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); if (elemsToUpdate.has(".branchLabel")) this.drawBranchLabels(this.params.branchLabelKey); }; @@ -218,6 +224,7 @@ export const modifySVGInStages = function modifySVGInStages(elemsToUpdate, svgPr .remove() .on("start", () => inProgress++) .on("end", step2); + this.removeTemporalSlice(); if (!transitionTimeFadeOut) timerFlush(); }; @@ -336,6 +343,7 @@ export const change = function change({ /* Finally, actually change the SVG elements themselves */ const extras = {removeConfidences, showConfidences, newBranchLabellingKey}; + extras.timeSliceHasPotentiallyChanged = changeVisibility || newDistance; if (useModifySVGInStages) { this.modifySVGInStages(elemsToUpdate, svgPropsToUpdate, transitionTime, 1000); } else { diff --git a/src/components/tree/phyloTree/grid.js b/src/components/tree/phyloTree/grid.js index 5c038d944..a1ad7fdeb 100644 --- a/src/components/tree/phyloTree/grid.js +++ b/src/components/tree/phyloTree/grid.js @@ -15,6 +15,9 @@ export const hideGrid = function hideGrid() { }; const addSVGGroupsIfNeeded = (groups, svg) => { + if (!("temporalWindow" in groups)) { + groups.temporalWindow = svg.append("g").attr("id", "temporalWindow"); + } if (!("majorGrid" in groups)) { groups.majorGrid = svg.append("g").attr("id", "majorGrid"); } @@ -41,8 +44,8 @@ const calculateMajorGridSeperation = (range) => { * add a grid to the svg * @param {layout} */ -export const addGrid = function addGrid(layout) { - if (typeof layout==="undefined") {layout=this.layout;} // eslint-disable-line no-param-reassign +export const addGrid = function addGrid() { + const layout = this.layout; addSVGGroupsIfNeeded(this.groups, this.svg); if (layout==="unrooted") return; timerStart("addGrid"); @@ -161,7 +164,6 @@ export const addGrid = function addGrid(layout) { /* D3 commands to add grid + text to the DOM Note that the groups were created the first time this function was called */ - // add major grid to svg this.groups.majorGrid.selectAll("*").remove(); this.groups.majorGrid @@ -215,3 +217,49 @@ export const addGrid = function addGrid(layout) { this.grid=true; timerEnd("addGrid"); }; + + +export const removeTemporalSlice = function removeTemporalSlice() { + this.groups.temporalWindow.selectAll("*").remove(); +}; + +/** + * add background grey rectangles to demarcate the temporal slice + */ +export const addTemporalSlice = function addTemporalSlice() { + this.removeTemporalSlice(); + if (this.layout !== "rect" || this.distance !== "num_date") return; + + const xWindow = [this.xScale(this.dateRange[0]), this.xScale(this.dateRange[1])]; + const height = this.yScale.range()[1]; + const fill = "#EEE"; // this.params.minorGridStroke + const minPxThreshold = 30; + const rightHandTree = this.params.orientation[0] === -1; + const rootXPos = this.xScale(this.nodes[0].x); + let totalWidth = rightHandTree ? this.xScale.range()[0] : this.xScale.range()[1]; + totalWidth += (this.params.margins.left + this.params.margins.right); + + /* the gray region between the root (ish) and the minimum date */ + if (Math.abs(xWindow[0]-rootXPos) > minPxThreshold) { /* don't render anything less than this num of px */ + this.groups.temporalWindow.append("rect") + .attr("x", rightHandTree ? xWindow[0] : 0) + .attr("width", rightHandTree ? totalWidth-xWindow[0]: xWindow[0]) + .attr("y", 0) + .attr("height", height) + .attr("fill", fill); + } + + /* the gray region between the maximum selected date and the last tip */ + const startingX = rightHandTree ? this.params.margins.right : xWindow[1]; + const rectWidth = rightHandTree ? + xWindow[1]-this.params.margins.right : + totalWidth-this.params.margins.right-xWindow[1]; + if (rectWidth > minPxThreshold) { + this.groups.temporalWindow.append("rect") + .attr("x", startingX) + .attr("width", rectWidth) + .attr("y", 0) + .attr("height", height) + .attr("fill", fill); + } +}; diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index dce7b1bc3..10761fcdc 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -91,5 +91,7 @@ PhyloTree.prototype.updateTipLabels = labels.updateTipLabels; /* G R I D */ PhyloTree.prototype.hideGrid = grid.hideGrid; PhyloTree.prototype.addGrid = grid.addGrid; +PhyloTree.prototype.addTemporalSlice = grid.addTemporalSlice; +PhyloTree.prototype.removeTemporalSlice = grid.removeTemporalSlice; export default PhyloTree; diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index bf698a89d..813189a8c 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -16,12 +16,13 @@ import { timerStart, timerEnd } from "../../../util/perf"; * @param {array|null} tipRadii -- array of tip radius' * @return {null} */ -export const render = function render(svg, layout, distance, parameters, callbacks, branchThickness, visibility, drawConfidence, vaccines, branchStroke, tipStroke, tipFill, tipRadii) { +export const render = function render(svg, layout, distance, parameters, callbacks, branchThickness, visibility, drawConfidence, vaccines, branchStroke, tipStroke, tipFill, tipRadii, dateRange) { timerStart("phyloTree render()"); this.svg = svg; this.params = Object.assign(this.params, parameters); this.callbacks = callbacks; this.vaccines = vaccines ? vaccines.map((d) => d.shell) : undefined; + this.dateRange = dateRange; /* set x, y values & scale them to the screen */ this.setDistance(distance); @@ -39,7 +40,10 @@ export const render = function render(svg, layout, distance, parameters, callbac }); /* draw functions */ - if (this.params.showGrid) this.addGrid(); + if (this.params.showGrid) { + this.addGrid(); + this.addTemporalSlice(); + } this.drawBranches(); this.drawTips(); if (this.params.branchLabelKey) this.drawBranchLabels(this.params.branchLabelKey); diff --git a/src/components/tree/reactD3Interface/change.js b/src/components/tree/reactD3Interface/change.js index ab2b56262..0700243ee 100644 --- a/src/components/tree/reactD3Interface/change.js +++ b/src/components/tree/reactD3Interface/change.js @@ -8,6 +8,10 @@ export const changePhyloTreeViaPropsComparison = (mainTree, phylotree, oldProps, const oldTreeRedux = mainTree ? oldProps.tree : oldProps.treeToo; const newTreeRedux = mainTree ? newProps.tree : newProps.treeToo; + /* do any properties on the tree object need to be updated? + Note that updating properties itself won't trigger any visual changes */ + phylotree.dateRange = [newProps.dateMinNumeric, newProps.dateMaxNumeric]; + /* catch selectedStrain dissapearence seperately to visibility and remove modal */ if (oldTreeRedux.selectedStrain && !newTreeRedux.selectedStrain) { /* TODO change back the tip radius */ diff --git a/src/components/tree/reactD3Interface/initialRender.js b/src/components/tree/reactD3Interface/initialRender.js index 2301c4058..b56455c2d 100644 --- a/src/components/tree/reactD3Interface/initialRender.js +++ b/src/components/tree/reactD3Interface/initialRender.js @@ -10,7 +10,6 @@ export const renderTree = (that, main, phylotree, props) => { console.warn("can't run renderTree (not loaded)"); return; } - /* simply the call to phylotree.render */ phylotree.render( select(ref), @@ -40,6 +39,7 @@ export const renderTree = (that, main, phylotree, props) => { calcBranchStrokeCols(treeState, props.colorByConfidence, props.colorBy), treeState.nodeColors, treeState.nodeColors.map((col) => rgb(col).brighter([0.65]).toString()), - treeState.tipRadii /* might be null */ + treeState.tipRadii, /* might be null */ + [props.dateMinNumeric, props.dateMaxNumeric] ); };