Published
Edited
May 24, 2021
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
rect_time_tree_chart_recomb = {
const root = d3.hierarchy(localTree.root)
.sum(d => d.children ? 0 : 1)
.sort((a, b) => (a.value - b.value) || d3.ascending(a.data._length, b.data._length));

rectCluster(root);

setRadius(root, root.data._length = 0, maxLength(root));
setColor(root);
//recombination event links
var recombLinks = makeAllRecombinationEvents(root);

//to combine all edges (tree + recombination events)
// root.edges = root.links().concat(newLinks);


const svg = d3.create("svg")
.attr("viewBox", [0, -10 , height + 10, width + 10])
.attr("font-family", "sans-serif")
.attr("font-size", 10);

// svg.append("g")
// .call(legend);

// controls mouseover style
svg.append("style").text(`

.link--active {
stroke: #69b3a2 !important;
stroke-width: 8px;
stroke-opacity: 1.0;

}
.recombLink--active {
stroke: #1F51FF !important;
stroke-width: 10px;
stroke-opacity: 1.0;

}

.tips--active {
fill: #226b94 !important;
r: 16;
}

.label--active {
font-weight: bold;
}

`);

// #AAFF00
const link = svg.append("g")
.attr("fill", "none")
.attr("stroke", "#696969")
.attr("stroke-width", "3px")
.selectAll("path")
//.data(root.edges) //if want to include all links in single object structurer
.data(root.links())
.join("path")
.each(function(d) { d.target.linkNode = this; }) //this is used for the highlighted path
.attr("d", linkVariableHorizontal);

//could style recombination links separately
const recombLink = svg.append("g")
.attr("fill", "none")
.attr("stroke", "#696969")
.attr("stroke-width", "3px")
.selectAll("path")
.data(recombLinks)
.join("path")
.each(function(d) { d.target.recombLinkNode = this; }) //to include recombination events in highlighting
.style("stroke-dasharray", ("20, 5"))
.attr("d", linkVariableHorizontal);

// tip shapes
const tips = svg.append("g")
.selectAll("circle")
.data(root.leaves())
.join("circle")
.attr("dy", ".31em")
.attr("fill", "#226b94")
.attr("fill-opacity", 0.8)
.attr("transform", d => `translate(${d.radius},${d.x})`) //time scaled tree
.attr("r", 11);
// .on("mouseover", mouseovered(true))
// .on("mouseout", mouseovered(false));
tips
.on('mouseover', mouseovered(true))
.on('mouseout', mouseovered(false))

// tip labels
// svg.append("g")
// .selectAll("text")
// .data(root.leaves())
// .join("text")
// .attr("dy", ".31em")
// .attr("transform", d => `translate(${d.radius + tipLabelPadding},${d.x})`) //time scaled tree

// .text(d => d.data.name.replace(/_/g, " "))
// .on("mouseover", mouseovered(true))
// .on("mouseout", mouseovered(false));


// make lineage bold on mouseover
function mouseovered(active) {
return function(event, d) {
if (active) {
link.style('stroke', "#B8B8B8").style("opacity", 1.0)
recombLink.style('stroke', "#B8B8B8").style("opacity", 1.0)
tips.style('fill', "#AAC5D5")
}
else {
link.style('stroke', "#696969").style("opacity", 1.0)
recombLink.style('stroke', "#696969").style("opacity", 1.0)
tips.style('fill', "#226b94")
}
d3.select(this).classed("tips--active", active);
d3.select(this).classed("label--active", active);

do {
d3.select(d.linkNode).classed("link--active", active).raise();
d3.select(d.recombLinkNode).classed("recombLink--active", active).raise();
}
while (d = d.parent); // follows child-parent path
};
}

return Object.assign(svg.node());
}

Insert cell
// based on https://stackoverflow.com/questions/9133500/how-to-find-a-node-in-a-tree-with-javascript

function searchTree(node, matchingID){
if(node.data._id == matchingID){
return node;
} else if (node.children != null){
var i;
var result = null;
for(i=0; result == null && i < node.children.length; i++){
result = searchTree(node.children[i], matchingID);
}
return result;
}
return null;
}
Insert cell
function makeAllRecombinationEvents(root){
var results = [];
root.each(d => addRecombinationEvents (d, root, results));
return results
}
Insert cell
addRecombinationEvents = function(node, root, results){
if (node.data._id.includes('#')){
results.push(createRecombinationLink(node, root))
}
}
Insert cell
Insert cell
function createLinkObject(sourceZh, targetZh){
return {source: sourceZh, target: targetZh};
}
Insert cell
//TODO: Decide what to color by and fix these categories, may need to extract attributes from names
color = d3.scaleOrdinal()
.domain(["United States", "China"])
.range(d3.schemeTableau10)
.unknown(null)
Insert cell
rectCluster = d3.cluster()
.size([width, height])
.separation((a, b) => 0.01)
Insert cell
// Compute the maximum cumulative length of any node in the tree.
function maxLength(d) {
return d.data._length + (d.children ? d3.max(d.children, maxLength) : 0);
}
Insert cell
// Set the radius of each node by recursively summing and scaling the distance from the root.
// this could probably also just be used to calculate x value when y is calculated some other way?

function setRadius(d, y0, k) {
d.radius = (y0 += d.data._length) * k;
if (d.children) d.children.forEach(d => setRadius(d, y0, k));
}
Insert cell
// Set the color of each node by recursively inheriting.
function setColor(d) {
var name = d.data._id;
d.color = color.domain().indexOf(name) >= 0 ? color(name) : d.parent ? d.parent.color : null;
if (d.children) d.children.forEach(setColor);
}
Insert cell
function linkVariableHorizontal(d) {
return linkStepHorizontal(d.source.x, d.source.radius, d.target.x, d.target.radius);
}
Insert cell
function linkStepHorizontal(startY, startRadius, endY, endRadius) {

const endYbigger = startY + ((endY-startY)/2)
const startYbigger = startY - ((endY-startY)/2)

const startRadiusNew = startRadius
return "M " + startRadius + " " + startY + " "
+ (endY === startY ? "" : "L " + startRadiusNew + " " + startY + " L " + startRadiusNew + " " + endY)
+ " L " + endRadius + " " + endY;
}
Insert cell
legend = svg => {
const g = svg
.selectAll("g")
.data(color.domain())
.join("g")
.attr("transform", (d, i) => `translate(${-outerRadius},${-outerRadius + i * 20})`);

g.append("rect")
.attr("width", 18)
.attr("height", 18)
.attr("fill", color);

g.append("text")
.attr("x", 24)
.attr("y", 9)
.attr("dy", "0.35em")
.text(d => d);
}
Insert cell
widthRadial = 1000
Insert cell
width = 2000
Insert cell
height = 2000
Insert cell
outerRadius = widthRadial / 2
Insert cell
innerRadius = outerRadius - 200
Insert cell
tipLabelPadding = 10
Insert cell
Insert cell
Insert cell
Insert cell
ft = import("https://cdn.jsdelivr.net/gh/rambaut/figtree.js@d12cb72/dist/figtree.esm.js")
Insert cell
import {swatches} from "@d3/color-legend"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more