From f4dfb30d60669a1e677807fcf8a4a883cd9ff20a Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Fri, 24 Aug 2018 14:38:16 -0700 Subject: [PATCH] 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] ); };