Skip to content

Commit

Permalink
Merge pull request #498 from nextstrain/vaccine-cross-date
Browse files Browse the repository at this point in the history
Vaccine cross date
  • Loading branch information
jameshadfield authored Feb 14, 2018
2 parents 7a8fd3a + 91fb4d3 commit 4d432a6
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 125 deletions.
3 changes: 3 additions & 0 deletions src/actions/colors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import getColorScale from "../util/getColorScale";
import { setGenotype } from "../util/setGenotype";
import { calcNodeColor } from "../components/tree/treeHelpers";
import { determineColorByGenotypeType } from "../util/colorHelpers";
import { timerStart, timerEnd } from "../util/perf";
import { updateEntropyVisibility } from "./entropy";
import * as types from "./types";

/* providedColorBy: undefined | string */
export const changeColorBy = (providedColorBy = undefined) => { // eslint-disable-line import/prefer-default-export
return (dispatch, getState) => {
timerStart("changeColorBy calculations");
const { controls, tree, metadata } = getState();
/* step 0: bail if all required params aren't (yet) available! */
/* note this *can* run before the tree is loaded - we only need the nodes */
Expand Down Expand Up @@ -41,6 +43,7 @@ export const changeColorBy = (providedColorBy = undefined) => { // eslint-disabl

/* step 3: change in mutType? */
const newMutType = determineColorByGenotypeType(colorBy) !== controls.mutType ? determineColorByGenotypeType(colorBy) : false;
timerEnd("changeColorBy calculations"); /* end timer before dispatch */
if (newMutType) {
updateEntropyVisibility(dispatch, getState);
}
Expand Down
1 change: 0 additions & 1 deletion src/components/tree/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ class Tree extends React.Component {
{ /* options */
grid: true,
confidence: nextProps.temporalConfidence.display,
showVaccines: !!nextProps.tree.vaccines,
branchLabels: true,
showBranchLabels: false,
tipLabels: true,
Expand Down
1 change: 0 additions & 1 deletion src/components/tree/phyloTree/defaultParams.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,5 @@ export const defaultParams = {
tipLabelFill: "#555",
tipLabelPadX: 8,
tipLabelPadY: 2,
showVaccines: false,
mapToScreenDebounceTime: 500
};
156 changes: 101 additions & 55 deletions src/components/tree/phyloTree/generalUpdates.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,30 @@ export const updateLayout = function updateLayout(layout, dt) {
* @return {[type]}
*/
export const updateGeometry = function updateGeometry(dt) {
timerStart("updateGeometry");
this.svg.selectAll(".tip")
.filter((d) => d.update)
.transition()
.duration(dt)
.attr("cx", (d) => d.xTip)
.attr("cy", (d) => d.yTip);

this.svg.selectAll(".vaccine")
.filter((d) => d.update)
.transition()
.duration(dt)
.attr("x", (d) => d.xTip)
.attr("y", (d) => d.yTip);
if (this.vaccines) {
this.svg.selectAll(".vaccineCross")
.transition().duration(dt)
.attr("d", (dd) => dd.vaccineCross);
if (this.distance === "num_date") {
this.svg.selectAll(".vaccineDottedLine")
.transition().duration(dt)
.style("opacity", 1)
.attr("d", (dd) => dd.vaccineLine);
} else {
this.svg.selectAll(".vaccineDottedLine")
.transition().duration(dt)
.style("opacity", 0)
.attr("d", (dd) => dd.vaccineLine);
}
}

const branchEls = [".S", ".T"];
for (let i = 0; i < 2; i++) {
Expand All @@ -119,69 +130,104 @@ export const updateGeometry = function updateGeometry(dt) {

this.updateBranchLabels(dt);
this.updateTipLabels(dt);
timerEnd("updateGeometry");
};


/*
* redraw the tree based on the current xTip, yTip, branch attributes
* this function will remove branches, move the tips continuously
* and add the new branches again after the tips arrived at their destination
* @params dt -- time of transition in milliseconds
* step 1: fade out everything except tips.
* step 2: when step 1 has finished, move tips across the screen.
* step 3: when step 2 has finished, move everything else (whilst hidden) and fasde back in.
* @params dt -- time of tip move (ms). Note that this function will take a lot longer than this time!
*/
export const updateGeometryFade = function updateGeometryFade(dt) {
this.removeConfidence(dt);
const fadeDt = dt * 0.5;
const moveDt = dt;

// fade out branches
let inProgress = 0; /* counter of transitions currently in progress */
const moveElementsAndFadeBackIn = () => {
if (!--inProgress) { /* decrement counter. When hits 0 run block */
this.svg.selectAll('.branch').filter('.S')
.filter((d) => d.update)
.attr("d", (d) => d.branch[0])
.transition()
.duration(fadeDt)
.style("opacity", 1.0);
this.svg.selectAll('.branch').filter('.T')
.filter((d) => d.update)
.attr("d", (d) => d.branch[1])
.transition()
.duration(fadeDt)
.style("opacity", 1.0);
if (this.vaccines) {
this.svg.selectAll('.vaccineCross')
.attr("d", (dd) => dd.vaccineCross)
.transition()
.duration(fadeDt)
.style("opacity", 1.0);
if (this.distance === "num_date") {
this.svg.selectAll('.vaccineDottedLine')
.attr("d", (dd) => dd.vaccineLine)
.transition()
.duration(fadeDt)
.style("opacity", 1.0);
} else {
this.svg.selectAll('.vaccineDottedLine')
.attr("d", (dd) => dd.vaccineLine);
/* opacity is already 0 */
}
}
this.updateBranchLabels(fadeDt);
this.updateTipLabels(fadeDt);
}
};
const moveTipsWhenFadedOut = () => {
if (!--inProgress) { /* decrement counter. When hits 0 run block */
this.svg.selectAll('.tip')
.filter((d) => d.update)
.transition()
.duration(moveDt)
.attr("cx", (d) => d.xTip)
.attr("cy", (d) => d.yTip)
.on("start", () => inProgress++)
.on("end", moveElementsAndFadeBackIn);
}
};

/* fade out branches, tip & branch labels, vaccine crosses & dotted lines.
When these fade outs are complete, the function moveTipsWhenFadedOut will fire */
this.removeConfidence();
this.svg.selectAll('.branch')
.filter((d) => d.update)
.transition().duration(dt * 0.5)
.style("opacity", 0.0);
.transition().duration(fadeDt)
.style("opacity", 0.0)
.on("start", () => inProgress++)
.on("end", moveTipsWhenFadedOut);
this.svg.selectAll('.branchLabels')
.filter((d) => d.update)
.transition().duration(dt * 0.5)
.style("opacity", 0.0);
.transition().duration(fadeDt)
.style("opacity", 0.0)
.on("start", () => inProgress++)
.on("end", moveTipsWhenFadedOut);
this.svg.selectAll('.tipLabels')
.filter((d) => d.update)
.transition().duration(dt * 0.5)
.style("opacity", 0.0);

// closure to move the tips, called via the time out below
const tipTransHOF = (svgShadow, dtShadow) => () => {
svgShadow.selectAll('.tip')
.filter((d) => d.update)
.transition().duration(dtShadow)
.attr("cx", (d) => d.xTip)
.attr("cy", (d) => d.yTip);
svgShadow.selectAll(".vaccine")
.filter((d) => d.update)
.transition()
.duration(dtShadow)
.attr("x", (d) => d.xTip)
.attr("y", (d) => d.yTip);
};
setTimeout(tipTransHOF(this.svg, dt), 0.5 * dt);

// closure to change the branches, called via time out after the tipTrans is done
const flipBranchesHOF = (svgShadow) => () => {
svgShadow.selectAll('.branch').filter('.S')
.filter((d) => d.update)
.attr("d", (d) => d.branch[0]);
svgShadow.selectAll('.branch').filter('.T')
.filter((d) => d.update)
.attr("d", (d) => d.branch[1]);
};
setTimeout(flipBranchesHOF(this.svg), 0.5 * dt);

// closure to add the new branches after the tipTrans
const fadeBackHOF = (svgShadow, dtShadow) => () => {
svgShadow.selectAll('.branch')
.filter((dd) => dd.update)
.transition().duration(0.5 * dtShadow)
.style("opacity", 1.0);
};
setTimeout(fadeBackHOF(this.svg, 0.2 * dt), 1.5 * dt);
this.updateBranchLabels(dt);
this.updateTipLabels(dt);
.transition().duration(fadeDt)
.style("opacity", 0.0)
.on("start", () => inProgress++)
.on("end", moveTipsWhenFadedOut);
if (this.vaccines) {
this.svg.selectAll('.vaccineCross')
.transition().duration(fadeDt)
.style("opacity", 0.0)
.on("start", () => inProgress++)
.on("end", moveTipsWhenFadedOut);
this.svg.selectAll('.vaccineDottedLine')
.transition().duration(fadeDt)
.style("opacity", 0.0)
.on("start", () => inProgress++)
.on("end", moveTipsWhenFadedOut);
}
};


Expand Down
3 changes: 3 additions & 0 deletions src/components/tree/phyloTree/grid.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable space-infix-ops */
import { max } from "d3-array";
import { timerStart, timerEnd } from "../../../util/perf";

export const removeGrid = function removeGrid() {
this.svg.selectAll(".majorGrid").remove();
Expand All @@ -19,6 +20,7 @@ export const hideGrid = function hideGrid() {
* @param {layout}
*/
export const addGrid = function addGrid(layout, yMinView, yMaxView) {
timerStart("addGrid");
if (typeof layout==="undefined") {layout=this.layout;} // eslint-disable-line no-param-reassign

const xmin = (this.xScale.domain()[0]>0)?this.xScale.domain()[0]:0.0;
Expand Down Expand Up @@ -164,4 +166,5 @@ export const addGrid = function addGrid(layout, yMinView, yMaxView) {
.attr("y", yTextPos(this.yScale, layout));

this.grid=true;
timerEnd("addGrid");
};
40 changes: 40 additions & 0 deletions src/components/tree/phyloTree/layouts.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable no-multi-spaces */
import { min, sum } from "d3-array";
import { addLeafCount } from "./helpers";
import { timerStart, timerEnd } from "../../../util/perf";

/**
* assigns the attribute this.layout and calls the function that
Expand All @@ -9,6 +10,7 @@ import { addLeafCount } from "./helpers";
* ["rect", "radial", "unrooted", "clock"]
*/
export const setLayout = function setLayout(layout) {
timerStart("setLayout");
if (typeof layout === "undefined" || layout !== this.layout) {
this.nodes.forEach((d) => {d.update = true;});
}
Expand All @@ -26,6 +28,7 @@ export const setLayout = function setLayout(layout) {
} else if (this.layout === "unrooted") {
this.unrootedLayout();
}
timerEnd("setLayout");
};


Expand All @@ -41,6 +44,12 @@ export const rectangularLayout = function rectangularLayout() {
d.py = d.y;
d.x_conf = d.conf; // assign confidence intervals
});
if (this.vaccines) {
this.vaccines.forEach((d) => {
d.xCross = d.crossDepth;
d.yCross = d.y;
});
}
};

/**
Expand All @@ -57,6 +66,12 @@ export const timeVsRootToTip = function timeVsRootToTip() {
d.px = d.n.parent.attr["num_date"];
d.py = d.n.parent.attr["div"];
});
if (this.vaccines) { /* overlay vaccine cross on tip */
this.vaccines.forEach((d) => {
d.xCross = d.x;
d.yCross = d.y;
});
}
const nTips = this.numberOfTips;
// REGRESSION WITH FREE INTERCEPT
// const meanDiv = d3.sum(this.nodes.filter((d)=>d.terminal).map((d)=>d.y))/nTips;
Expand Down Expand Up @@ -132,6 +147,13 @@ export const unrootedLayout = function unrootedLayout() {
eta += this.nodes[0].children[i].w;
unrootedPlaceSubtree(this.nodes[0].children[i], nTips);
}
if (this.vaccines) {
this.vaccines.forEach((d) => {
const bL = d.crossDepth - d.depth;
d.xCross = d.px + bL * Math.cos(d.tau + d.w * 0.5);
d.yCross = d.py + bL * Math.sin(d.tau + d.w * 0.5);
});
}
};

/**
Expand All @@ -157,6 +179,17 @@ export const radialLayout = function radialLayout() {
d.xCBarEnd = (d.depth - offset) * Math.sin(angleCBar2);
d.smallBigArc = Math.abs(angleCBar2 - angleCBar1) > Math.PI * 1.0;
});
if (this.vaccines) {
this.vaccines.forEach((d) => {
if (this.distance === "div") {
d.xCross = d.x;
d.yCross = d.y;
} else {
d.xCross = (d.crossDepth - offset) * Math.sin(d.angle);
d.yCross = (d.crossDepth - offset) * Math.cos(d.angle);
}
});
}
};

/*
Expand All @@ -165,6 +198,7 @@ export const radialLayout = function radialLayout() {
* calculate coordinates. Parent depth is assigned as well.
*/
export const setDistance = function setDistance(distanceAttribute) {
timerStart("setDistance");
this.nodes.forEach((d) => {d.update = true;});
if (typeof distanceAttribute === "undefined") {
this.distance = "div"; // default is "div" for divergence
Expand All @@ -182,6 +216,12 @@ export const setDistance = function setDistance(distanceAttribute) {
d.conf = [d.depth, d.depth];
}
});
if (this.vaccines) {
this.vaccines.forEach((d) => {
d.crossDepth = tmp_dist === "div" ? d.depth : d.n.vaccineDateNumeric;
});
}
timerEnd("setDistance");
};


Expand Down
Loading

0 comments on commit 4d432a6

Please sign in to comment.