From 7170ca8e3eb154f9cc43ba084c312466d8978d16 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Sat, 4 Aug 2018 15:04:17 -0700 Subject: [PATCH 1/6] allow variable indentation for method chaining (e.g. D3) --- .eslintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc b/.eslintrc index ca97234ac..1b315ad9a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -46,6 +46,7 @@ rules: no-unneeded-ternary: ["error", { "defaultAssignment": true }] quote-props: ["error", "as-needed"] prefer-const: ["error", {"destructuring": "all"}] + indent: ["error", 2, {"MemberExpression": "off"}] parserOptions: ecmaVersion: 6 sourceType: module From 0d2af5f1eb284039ec9375659d8e8268cfdcfe33 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Sat, 4 Aug 2018 15:08:16 -0700 Subject: [PATCH 2/6] Store & reuse elements for the tree grid By storing references to elements, we don't have to create new ones upon updates. This also preserves the initial ordering of groups, avoiding bugs such as https://github.com/nextstrain/auspice/pull/610#issuecomment-410477963 --- src/components/tree/phyloTree/grid.js | 95 ++++++++++++++-------- src/components/tree/phyloTree/phyloTree.js | 3 + 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/components/tree/phyloTree/grid.js b/src/components/tree/phyloTree/grid.js index 139d2da0d..d2e0f633b 100644 --- a/src/components/tree/phyloTree/grid.js +++ b/src/components/tree/phyloTree/grid.js @@ -3,9 +3,15 @@ import { min, max } from "d3-array"; import { timerStart, timerEnd } from "../../../util/perf"; export const hideGrid = function hideGrid() { - this.svg.selectAll(".majorGrid").style('visibility', 'hidden'); - this.svg.selectAll(".minorGrid").style('visibility', 'hidden'); - this.svg.selectAll(".gridTick").style('visibility', 'hidden'); + if ("majorGrid" in this.groups) { + this.groups.majorGrid.selectAll("*").style('visibility', 'hidden'); + } + if ("minorGrid" in this.groups) { + this.groups.minorGrid.selectAll("*").style('visibility', 'hidden'); + } + if ("gridText" in this.groups) { + this.groups.gridText.selectAll("*").style('visibility', 'hidden'); + } }; const calculateMajorGridSeperation = (range) => { @@ -144,46 +150,63 @@ export const addGrid = function addGrid(layout) { /* D3 commands to add grid + text to the DOM */ // add major grid to svg - const majorGrid = this.svg.selectAll('.majorGrid').data(majorGridPoints); - majorGrid.exit().remove(); // EXIT - majorGrid.enter().append("path") // ENTER - .merge(majorGrid) // ENTER + UPDATE - .attr("d", gridline(this.xScale, this.yScale, layout)) - .attr("class", "majorGrid") - .style("fill", "none") - .style("visibility", (d) => d[1]) - .style("stroke", this.params.majorGridStroke) - .style("stroke-width", this.params.majorGridWidth); + if (!("majorGrid" in this.groups)) { + this.groups.majorGrid = this.svg.append("g").attr("id", "majorGrid"); + } + this.groups.majorGrid.selectAll("*").remove(); + this.groups.majorGrid + .selectAll('.majorGrid') + .data(majorGridPoints) + .enter() + .append("path") + .attr("d", gridline(this.xScale, this.yScale, layout)) + .attr("class", "majorGrid") + .style("fill", "none") + .style("visibility", (d) => d[1]) + .style("stroke", this.params.majorGridStroke) + .style("stroke-width", this.params.majorGridWidth); // add minor grid to SVG - const minorGrid = this.svg.selectAll('.minorGrid').data(minorGridPoints); - minorGrid.exit().remove(); // EXIT - minorGrid.enter().append("path") // ENTER - .merge(minorGrid) // ENTER + UPDATE - .attr("d", gridline(this.xScale, this.yScale, layout)) - .attr("class", "minorGrid") - .style("fill", "none") - .style("visibility", (d) => d[1]) - .style("stroke", this.params.minorGridStroke) - .style("stroke-width", this.params.minorGridWidth); + if (!("minorGrid" in this.groups)) { + this.groups.minorGrid = this.svg.append("g").attr("id", "minorGrid"); + } + this.groups.minorGrid.selectAll("*").remove(); + this.svg.selectAll(".minorGrid").remove(); + this.groups.minorGrid + .selectAll('.minorGrid') + .data(minorGridPoints) + .enter() + .append("path") + .attr("d", gridline(this.xScale, this.yScale, layout)) + .attr("class", "minorGrid") + .style("fill", "none") + .style("visibility", (d) => d[1]) + .style("stroke", this.params.minorGridStroke) + .style("stroke-width", this.params.minorGridWidth); /* draw the text labels for majorGridPoints */ - const gridLabels = this.svg.selectAll('.gridTick').data(majorGridPoints); const precisionX = Math.max(0, -Math.floor(Math.log10(step))); const precisionY = Math.max(0, -Math.floor(Math.log10(yStep))); - gridLabels.exit().remove(); // EXIT - gridLabels.enter().append("text") // ENTER - .merge(gridLabels) // ENTER + UPDATE - .text((d) => d[0].toFixed(d[2]==='y' ? precisionY : precisionX)) - .attr("class", "gridTick") - .style("font-size", this.params.tickLabelSize) - .style("font-family", this.params.fontFamily) - .style("fill", this.params.tickLabelFill) - .style("text-anchor", textAnchor(layout)) - .style("visibility", (d) => d[1]) - .attr("x", xTextPos(this.xScale, layout)) - .attr("y", yTextPos(this.yScale, layout)); + if (!("gridText" in this.groups)) { + this.groups.gridText = this.svg.append("g").attr("id", "gridText"); + } + this.groups.gridText.selectAll("*").remove(); + this.svg.selectAll(".gridText").remove(); + this.groups.gridText + .selectAll('.gridText') + .data(majorGridPoints) + .enter() + .append("text") + .text((d) => d[0].toFixed(d[2]==='y' ? precisionY : precisionX)) + .attr("class", "gridText") + .style("font-size", this.params.tickLabelSize) + .style("font-family", this.params.fontFamily) + .style("fill", this.params.tickLabelFill) + .style("text-anchor", textAnchor(layout)) + .style("visibility", (d) => d[1]) + .attr("x", xTextPos(this.xScale, layout)) + .attr("y", yTextPos(this.yScale, layout)); this.grid=true; timerEnd("addGrid"); diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index b618e4c13..e438ed9c6 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -15,6 +15,9 @@ const PhyloTree = function PhyloTree(reduxNodes, debugId) { this.grid = false; this.attributes = ['r', 'cx', 'cy', 'id', 'class', 'd']; this.params = createDefaultParams(); + this.groups = {}; + /* by storing DOM elements, we can quickly refer to groups here rather than scanning the DOM. + It also helps preserve the initial order of groups in the DOM as we are not creating new ones upon updates */ this.debugId = debugId; /* super useful when one is trying to debug multiple trees! */ /* create this.nodes, which is an array of nodes with properties used by phylotree for drawing. this.nodes is the same length as reduxNodes such that this.nodes[i] is related to reduxNodes[i] From d48f21bf9f05d1ad405983fcdccdb8c49e19e487 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Sat, 4 Aug 2018 15:37:55 -0700 Subject: [PATCH 3/6] Collect remaining tree SVG elements into named groups See previous commit for "why" --- src/components/tree/phyloTree/change.js | 4 +- src/components/tree/phyloTree/confidence.js | 26 ++- src/components/tree/phyloTree/labels.js | 28 ++- src/components/tree/phyloTree/phyloTree.js | 2 + src/components/tree/phyloTree/renderers.js | 168 +++++++++++------- .../tree/reactD3Interface/callbacks.js | 8 +- 6 files changed, 153 insertions(+), 83 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 5e17998b4..3f0ddc331 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -159,7 +159,7 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra else this.hideGrid(); } if (elemsToUpdate.has('.regression')) { - this.svg.selectAll(".regression").remove(); + this.removeRegression(); if (this.layout === "clock" && this.distance === "num_date") this.drawRegression(); } @@ -179,7 +179,7 @@ export const modifySVG = function modifySVG(elemsToUpdate, svgPropsToUpdate, tra /* branch labels */ if (extras.newBranchLabellingKey) { - this.svg.selectAll('.branchLabel').remove(); + this.removeBranchLabels(); if (extras.newBranchLabellingKey !== "none") { this.drawBranchLabels(extras.newBranchLabellingKey); } diff --git a/src/components/tree/phyloTree/confidence.js b/src/components/tree/phyloTree/confidence.js index bb6bfa909..d82730f63 100644 --- a/src/components/tree/phyloTree/confidence.js +++ b/src/components/tree/phyloTree/confidence.js @@ -1,31 +1,39 @@ export const removeConfidence = function removeConfidence(dt) { this.confidencesInSVG = false; + if (!("confidenceIntervals" in this.groups)) return; + if (dt) { - this.svg.selectAll(".conf") + this.groups.confidenceIntervals + .selectAll("*") .transition().duration(dt) - .style("opacity", 0) - .remove(); + .style("opacity", 0) + .remove(); } else { - this.svg.selectAll(".conf").remove(); + this.groups.confidenceIntervals.selectAll("*").remove(); } }; export const drawConfidence = function drawConfidence(dt) { this.confidencesInSVG = true; + if (!("confidenceIntervals" in this.groups)) { + this.groups.confidenceIntervals = this.svg.append("g").attr("id", "confidenceIntervals"); + } if (dt) { - this.confidence = this.svg.append("g").selectAll(".conf") + this.groups.confidenceIntervals + .selectAll(".conf") .data(this.nodes) .enter() .call((sel) => this.drawSingleCI(sel, 0)); - this.svg.selectAll(".conf") + this.groups.confidenceIntervals .transition().duration(dt) - .style("opacity", 0.5); + .style("opacity", 0.5); } else { - this.confidence = this.svg.append("g").selectAll(".conf") + this.groups.confidenceIntervals + .selectAll(".conf") .data(this.nodes) .enter() - .call((sel) => this.drawSingleCI(sel, 0.5)); + .call((sel) => this.drawSingleCI(sel, 0.5)); } }; diff --git a/src/components/tree/phyloTree/labels.js b/src/components/tree/phyloTree/labels.js index 98a3b32d9..32ff196a0 100644 --- a/src/components/tree/phyloTree/labels.js +++ b/src/components/tree/phyloTree/labels.js @@ -1,7 +1,12 @@ import { timerFlush } from "d3-timer"; export const updateTipLabels = function updateTipLabels(dt) { - this.svg.selectAll('.tipLabel').remove(); + if ("tipLabels" in this.groups) { + this.groups.tipLabels.selectAll("*").remove(); + } else { + this.groups.tipLabels = this.svg.append("g").attr("id", "tipLabels"); + } + const tLFunc = this.callbacks.tipLabel; const xPad = this.params.tipLabelPadX; const yPad = this.params.tipLabelPadY; @@ -20,7 +25,8 @@ export const updateTipLabels = function updateTipLabels(dt) { } window.setTimeout(() => { - this.tipLabels = this.svg.append("g").selectAll('.tipLabel') + this.groups.tipLabels + .selectAll('.tipLabel') .data(inViewTerminalNodes) .enter() .append("text") @@ -79,7 +85,8 @@ export const updateBranchLabels = function updateBranchLabels(dt) { const visibility = createBranchLabelVisibility(this.params.branchLabelKey, this.layout, this.zoomNode.n.tipCount); const labelSize = branchLabelSize(this.params.branchLabelKey); const fontWeight = branchLabelFontWeight(this.params.branchLabelKey); - this.svg.selectAll('.branchLabel') + this.groups.branchLabels + .selectAll('.branchLabel') .transition().duration(dt) .attr("x", (d) => d.xTip - 5) .attr("y", (d) => d.yTip - this.params.branchLabelPadY) @@ -89,18 +96,29 @@ export const updateBranchLabels = function updateBranchLabels(dt) { if (!dt) timerFlush(); }; +export const removeBranchLabels = function removeBranchLabels() { + if ("branchLabels" in this.groups) { + this.groups.branchLabels.selectAll("*").remove(); + } +}; + export const drawBranchLabels = function drawBranchLabels(key) { /* salient props: this.zoomNode.n.tipCount, this.zoomNode.n.fullTipCount */ this.params.branchLabelKey = key; const labelSize = branchLabelSize(key); const fontWeight = branchLabelFontWeight(key); const visibility = createBranchLabelVisibility(key, this.layout, this.zoomNode.n.tipCount); - this.svg.append("g").selectAll('.branchLabel') + + if (!("branchLabels" in this.groups)) { + this.groups.branchLabels = this.svg.append("g").attr("id", "branchLabels"); + } + this.groups.branchLabels + .selectAll('.branchLabel') .data(this.nodes.filter((d) => d.n.attr.labels && d.n.attr.labels[key])) .enter() .append("text") .attr("class", "branchLabel") - .attr("x", (d) => d.xTip + ((this.params.orientation[0]>0)?-5:5) ) + .attr("x", (d) => d.xTip + ((this.params.orientation[0]>0)?-5:5)) .attr("y", (d) => d.yTip - this.params.branchLabelPadY) .style("text-anchor", (this.params.orientation[0]>0)?"end":"start") .style("visibility", visibility) diff --git a/src/components/tree/phyloTree/phyloTree.js b/src/components/tree/phyloTree/phyloTree.js index e438ed9c6..dce7b1bc3 100644 --- a/src/components/tree/phyloTree/phyloTree.js +++ b/src/components/tree/phyloTree/phyloTree.js @@ -64,6 +64,7 @@ PhyloTree.prototype.drawTips = renderers.drawTips; PhyloTree.prototype.drawBranches = renderers.drawBranches; PhyloTree.prototype.drawVaccines = renderers.drawVaccines; PhyloTree.prototype.drawRegression = renderers.drawRegression; +PhyloTree.prototype.removeRegression = renderers.removeRegression; /* 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; @@ -83,6 +84,7 @@ PhyloTree.prototype.updateConfidence = confidence.updateConfidence; /* L A B E L S ( T I P , B R A N C H , C O N F I D E N C E ) */ PhyloTree.prototype.drawBranchLabels = labels.drawBranchLabels; +PhyloTree.prototype.removeBranchLabels = labels.removeBranchLabels; PhyloTree.prototype.updateBranchLabels = labels.updateBranchLabels; PhyloTree.prototype.updateTipLabels = labels.updateTipLabels; diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index d2e6ca5eb..ac22cbf97 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -58,32 +58,39 @@ export const render = function render(svg, layout, distance, parameters, callbac * @return {null} */ export const drawVaccines = function drawVaccines() { - this.svg.append("g").selectAll(".vaccineCross") + if (!this.vaccines || !this.vaccines.length) return; + + if (!("vaccines" in this.groups)) { + this.groups.vaccines = this.svg.append("g").attr("id", "vaccines"); + } + this.groups.vaccines + .selectAll(".vaccineCross") .data(this.vaccines) .enter() - .append("path") - .attr("class", "vaccineCross") - .attr("d", (d) => d.vaccineCross) - .style("stroke", "#333") - .style("stroke-width", 2 * this.params.branchStrokeWidth) - .style("fill", "none") - .style("cursor", "pointer") - .style("pointer-events", "auto") - .on("mouseover", this.callbacks.onTipHover) - .on("mouseout", this.callbacks.onTipLeave) - .on("click", this.callbacks.onTipClick); - - this.svg.append("g").selectAll('.vaccineDottedLine') + .append("path") + .attr("class", "vaccineCross") + .attr("d", (d) => d.vaccineCross) + .style("stroke", "#333") + .style("stroke-width", 2 * this.params.branchStrokeWidth) + .style("fill", "none") + .style("cursor", "pointer") + .style("pointer-events", "auto") + .on("mouseover", this.callbacks.onTipHover) + .on("mouseout", this.callbacks.onTipLeave) + .on("click", this.callbacks.onTipClick); + + this.groups.vaccines + .selectAll('.vaccineDottedLine') .data(this.vaccines) .enter() - .append("path") - .attr("class", "vaccineDottedLine") - .attr("d", (d) => d.vaccineLine) - .style("stroke-dasharray", "5, 5") - .style("stroke", "black") - .style("stroke-width", this.params.branchStrokeWidth) - .style("fill", "none") - .style("pointer-events", "none"); + .append("path") + .attr("class", "vaccineDottedLine") + .attr("d", (d) => d.vaccineLine) + .style("stroke-dasharray", "5, 5") + .style("stroke", "black") + .style("stroke-width", this.params.branchStrokeWidth) + .style("fill", "none") + .style("pointer-events", "none"); }; @@ -94,63 +101,81 @@ export const drawVaccines = function drawVaccines() { export const drawTips = function drawTips() { timerStart("drawTips"); const params = this.params; - this.svg.append("g").selectAll(".tip") + + if (!("tips" in this.groups)) { + this.groups.tips = this.svg.append("g").attr("id", "tips"); + } + this.groups.tips + .selectAll(".tip") .data(this.nodes.filter((d) => d.terminal)) .enter() - .append("circle") - .attr("class", "tip") - .attr("id", (d) => "tip_" + d.n.clade) - .attr("cx", (d) => d.xTip) - .attr("cy", (d) => d.yTip) - .attr("r", (d) => d.r) - .on("mouseover", this.callbacks.onTipHover) - .on("mouseout", this.callbacks.onTipLeave) - .on("click", this.callbacks.onTipClick) - .style("pointer-events", "auto") - .style("visibility", (d) => d["visibility"]) - .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 */ - .style("cursor", "pointer"); + .append("circle") + .attr("class", "tip") + .attr("id", (d) => "tip_" + d.n.clade) + .attr("cx", (d) => d.xTip) + .attr("cy", (d) => d.yTip) + .attr("r", (d) => d.r) + .on("mouseover", this.callbacks.onTipHover) + .on("mouseout", this.callbacks.onTipLeave) + .on("click", this.callbacks.onTipClick) + .style("pointer-events", "auto") + .style("visibility", (d) => d["visibility"]) + .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 */ + .style("cursor", "pointer"); + timerEnd("drawTips"); }; /** - * adds all branches to the svg, these are paths with class branch + * adds all branches to the svg, these are paths with class branch, which comprise two groups * @return {null} */ export const drawBranches = function drawBranches() { timerStart("drawBranches"); const params = this.params; - this.Tbranches = this.svg.append("g").selectAll('.branch') + + /* PART 1: draw the branch Ts (i.e. the bit connecting nodes parent branch ends to child branch beginnings) */ + if (!("branchTee" in this.groups)) { + this.groups.branchTee = this.svg.append("g").attr("id", "branchTee"); + } + this.groups.branchTee + .selectAll('.branch') .data(this.nodes.filter((d) => !d.terminal)) .enter() - .append("path") - .attr("class", "branch T") - .attr("id", (d) => "branch_T_" + d.n.clade) - .attr("d", (d) => d.branch[1]) - .style("stroke", (d) => d.branchStroke || params.branchStroke) - .style("stroke-width", (d) => d['stroke-width'] || params.branchStrokeWidth) - .style("fill", "none") - .style("pointer-events", "auto"); - - this.branches = this.svg.append("g").selectAll('.branch') + .append("path") + .attr("class", "branch T") + .attr("id", (d) => "branch_T_" + d.n.clade) + .attr("d", (d) => d.branch[1]) + .style("stroke", (d) => d.branchStroke || params.branchStroke) + .style("stroke-width", (d) => d['stroke-width'] || params.branchStrokeWidth) + .style("fill", "none") + .style("pointer-events", "auto"); + + /* PART 2: draw the branch stems (i.e. the actual branches) */ + if (!("branchStem" in this.groups)) { + this.groups.branchStem = this.svg.append("g").attr("id", "branchStem"); + } + this.groups.branchStem + .selectAll('.branch') .data(this.nodes) .enter() - .append("path") - .attr("class", "branch S") - .attr("id", (d) => "branch_S_" + d.n.clade) - .attr("d", (d) => d.branch[0]) - .style("stroke", (d) => d.branchStroke || params.branchStroke) - .style("stroke-linecap", "round") - .style("stroke-width", (d) => d['stroke-width']+"px" || params.branchStrokeWidth) - .style("fill", "none") - .style("cursor", "pointer") - .style("pointer-events", "auto") - .on("mouseover", this.callbacks.onBranchHover) - .on("mouseout", this.callbacks.onBranchLeave) - .on("click", this.callbacks.onBranchClick); + .append("path") + .attr("class", "branch S") + .attr("id", (d) => "branch_S_" + d.n.clade) + .attr("d", (d) => d.branch[0]) + .style("stroke", (d) => d.branchStroke || params.branchStroke) + .style("stroke-linecap", "round") + .style("stroke-width", (d) => d['stroke-width']+"px" || params.branchStrokeWidth) + .style("fill", "none") + .style("cursor", "pointer") + .style("pointer-events", "auto") + .on("mouseover", this.callbacks.onBranchHover) + .on("mouseout", this.callbacks.onBranchLeave) + .on("click", this.callbacks.onBranchClick); + timerEnd("drawBranches"); }; @@ -165,14 +190,21 @@ export const drawRegression = function drawRegression() { const path = "M " + this.xScale.range()[0].toString() + " " + leftY.toString() + " L " + this.xScale.range()[1].toString() + " " + rightY.toString(); - this.svg.append("path") + + if (!("clockRegression" in this.groups)) { + this.groups.clockRegression = this.svg.append("g").attr("id", "clockRegression"); + } + + this.groups.clockRegression + .append("path") .attr("d", path) .attr("class", "regression") .style("fill", "none") .style("visibility", "visible") .style("stroke", this.params.regressionStroke) .style("stroke-width", this.params.regressionWidth); - this.svg.append("text") + this.groups.clockRegression + .append("text") .text(`rate estimate: ${this.regression.slope.toExponential(2)} subs per site per year`) .attr("class", "regression") .attr("x", this.xScale.range()[1] / 2 - 75) @@ -183,6 +215,12 @@ export const drawRegression = function drawRegression() { .style("font-family", this.params.fontFamily); }; +export const removeRegression = function removeRegression() { + if ("clockRegression" in this.groups) { + this.groups.clockRegression.selectAll("*").remove(); + } +}; + /* * add and remove elements from tree, initial render */ diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index a312dbdf4..221574fa1 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -49,10 +49,14 @@ export const onBranchHover = function onBranchHover(d) { } if (this.props.temporalConfidence.exists && this.props.temporalConfidence.display && !this.props.temporalConfidence.on) { const tree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo; - tree.svg.append("g").selectAll(".conf") + if (!("confidenceIntervals" in tree.groups)) { + tree.groups.confidenceIntervals = tree.svg.append("g").attr("id", "confidenceIntervals"); + } + tree.groups.confidenceIntervals + .selectAll(".conf") .data([d]) .enter() - .call((sel) => this.state.tree.drawSingleCI(sel, 0.5)); + .call((sel) => this.state.tree.drawSingleCI(sel, 0.5)); } this.setState({ hovered: {d, type: ".branch"} From 14f7d25ef1d97d84fbfa8bb9ae9385fedd3c9823 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Sat, 4 Aug 2018 15:48:40 -0700 Subject: [PATCH 4/6] remove (unused) dotted vaccine line from DOM --- src/components/tree/phyloTree/change.js | 6 ------ src/components/tree/phyloTree/layouts.js | 1 - src/components/tree/phyloTree/renderers.js | 13 ------------- 3 files changed, 20 deletions(-) diff --git a/src/components/tree/phyloTree/change.js b/src/components/tree/phyloTree/change.js index 3f0ddc331..80ccc65c6 100644 --- a/src/components/tree/phyloTree/change.js +++ b/src/components/tree/phyloTree/change.js @@ -40,9 +40,6 @@ const svgSetters = { ".vaccineCross": { d: (d) => d.vaccineCross }, - ".vaccineDottedLine": { - d: (d) => d.vaccineLine - }, ".conf": { d: (d) => d.confLine } @@ -53,9 +50,6 @@ const svgSetters = { "stroke": (d) => d.tipStroke, "visibility": (d) => d["visibility"] }, - ".vaccineDottedLine": { - opacity: (d) => d.that.distance === "num_date" ? 1 : 0 - }, ".conf": { "stroke": (d) => d.branchStroke, "stroke-width": calcConfidenceWidth diff --git a/src/components/tree/phyloTree/layouts.js b/src/components/tree/phyloTree/layouts.js index b2a9525a7..9787eb80e 100644 --- a/src/components/tree/phyloTree/layouts.js +++ b/src/components/tree/phyloTree/layouts.js @@ -354,7 +354,6 @@ export const mapToScreen = function mapToScreen() { const xTipCross = this.xScale(d.xCross); /* x position of the center of the cross */ const yTipCross = this.yScale(d.yCross); /* x position of the center of the cross */ d.vaccineCross = ` M ${xTipCross-n},${yTipCross-n} L ${xTipCross+n},${yTipCross+n} M ${xTipCross-n},${yTipCross+n} L ${xTipCross+n},${yTipCross-n}`; - d.vaccineLine = ` M ${d.xTip},${d.yTip} L ${xTipCross},${yTipCross}`; }); } diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index ac22cbf97..911ee5967 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -78,19 +78,6 @@ export const drawVaccines = function drawVaccines() { .on("mouseover", this.callbacks.onTipHover) .on("mouseout", this.callbacks.onTipLeave) .on("click", this.callbacks.onTipClick); - - this.groups.vaccines - .selectAll('.vaccineDottedLine') - .data(this.vaccines) - .enter() - .append("path") - .attr("class", "vaccineDottedLine") - .attr("d", (d) => d.vaccineLine) - .style("stroke-dasharray", "5, 5") - .style("stroke", "black") - .style("stroke-width", this.params.branchStrokeWidth) - .style("fill", "none") - .style("pointer-events", "none"); }; From a706d9a63fc5cdbb87a649bb1a7d44b53defe55f Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Sat, 4 Aug 2018 15:52:45 -0700 Subject: [PATCH 5/6] Don't draw branch-T elements for unrooted / clock tree layouts Reduces the number of elements in the DOM --- src/components/tree/phyloTree/renderers.js | 31 +++++++++++++--------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/components/tree/phyloTree/renderers.js b/src/components/tree/phyloTree/renderers.js index 911ee5967..e2173a316 100644 --- a/src/components/tree/phyloTree/renderers.js +++ b/src/components/tree/phyloTree/renderers.js @@ -124,22 +124,27 @@ export const drawBranches = function drawBranches() { timerStart("drawBranches"); const params = this.params; - /* PART 1: draw the branch Ts (i.e. the bit connecting nodes parent branch ends to child branch beginnings) */ + /* PART 1: draw the branch Ts (i.e. the bit connecting nodes parent branch ends to child branch beginnings) + Only rectangular & radial trees have this, so we remove it for clock / unrooted layouts */ if (!("branchTee" in this.groups)) { this.groups.branchTee = this.svg.append("g").attr("id", "branchTee"); } - this.groups.branchTee - .selectAll('.branch') - .data(this.nodes.filter((d) => !d.terminal)) - .enter() - .append("path") - .attr("class", "branch T") - .attr("id", (d) => "branch_T_" + d.n.clade) - .attr("d", (d) => d.branch[1]) - .style("stroke", (d) => d.branchStroke || params.branchStroke) - .style("stroke-width", (d) => d['stroke-width'] || params.branchStrokeWidth) - .style("fill", "none") - .style("pointer-events", "auto"); + if (this.layout === "clock" || this.layout === "unrooted") { + this.groups.branchTee.selectAll("*").remove(); + } else { + this.groups.branchTee + .selectAll('.branch') + .data(this.nodes.filter((d) => !d.terminal)) + .enter() + .append("path") + .attr("class", "branch T") + .attr("id", (d) => "branch_T_" + d.n.clade) + .attr("d", (d) => d.branch[1]) + .style("stroke", (d) => d.branchStroke || params.branchStroke) + .style("stroke-width", (d) => d['stroke-width'] || params.branchStrokeWidth) + .style("fill", "none") + .style("pointer-events", "auto"); + } /* PART 2: draw the branch stems (i.e. the actual branches) */ if (!("branchStem" in this.groups)) { From 71d4644e016cb129a017ed22b84fe464f100c3b4 Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Sat, 4 Aug 2018 17:21:26 -0700 Subject: [PATCH 6/6] immediately remove CIs onBranchLeave to prevent d3 transition bugs Problem: D3 selection, with transitions, seems to be lazily evaluated such at it would grab the newly displayed CI as well and remove it. Evaluating .selection() prior to setTimeout didn't fix this. This wasn't a problem before as we just added CIs in different DOM groups. Solution: instantaneously remove the CIs on branch leave. --- src/components/tree/reactD3Interface/callbacks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/tree/reactD3Interface/callbacks.js b/src/components/tree/reactD3Interface/callbacks.js index 221574fa1..769836ec8 100644 --- a/src/components/tree/reactD3Interface/callbacks.js +++ b/src/components/tree/reactD3Interface/callbacks.js @@ -56,7 +56,7 @@ export const onBranchHover = function onBranchHover(d) { .selectAll(".conf") .data([d]) .enter() - .call((sel) => this.state.tree.drawSingleCI(sel, 0.5)); + .call((sel) => tree.drawSingleCI(sel, 0.5)); } this.setState({ hovered: {d, type: ".branch"} @@ -78,7 +78,7 @@ export const onBranchLeave = function onBranchLeave(d) { } if (this.props.temporalConfidence.exists && this.props.temporalConfidence.display && !this.props.temporalConfidence.on) { const tree = d.that.params.orientation[0] === 1 ? this.state.tree : this.state.treeToo; - tree.removeConfidence(mediumTransitionDuration); + tree.removeConfidence(); } if (this.state.hovered) { this.setState({hovered: null});