Public
Edited
Apr 30, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof dendo_width = Inputs.range([50, 300], {label: "Dendo Width", step: 1, value: 200})//Inputs.range([20, (heat_width*.6)], {label: "Dendo Width", step: 1, value: 150})
Insert cell
Insert cell
viewof heat_width = Inputs.range([50,600], {label: "Heatmap Width", step: 1, value: 300})
Insert cell
plot = dendrogram(hclustTree,{ width:dendo_width , height: dendo_height, fontSize: 8, h: 100, hideLabels: false })
Insert cell
plot2 = dendrogram2(hclustTree,{ width:dendo_width , height: dendo_height,linkColor: "red"})
Insert cell
function dendrogram2 (data,options = {}) {

const {
width: width = width,
height: height = height,
innerHeight = height,
linkColor: linkColor = linkColor,
} = options;

const svg = d3
.create("svg")
.attr("width", width )
.attr("height", height )


const root = d3.hierarchy(data)
const maxHeight = root.data.height;
const clusterLayout = d3.cluster().nodeSize([1, width / root.height])
clusterLayout(root)

const allNodes = root.descendants().reverse()
const leafs = allNodes.filter(d => !d.children)
leafs.sort((a,b) => a.x - b.x)
const leafHeight = height / leafs.length
leafs.forEach((d,i) => d.x = i*leafHeight + leafHeight/2)
allNodes.forEach(node => {
if (node.children) {
node.x = d3.mean(node.children, d => d.x)
}})

function transformX(data) {
const height = width ;
return height - (data.data.height / maxHeight) * height;
}

// Links
root.links().forEach((link,i) => {
svg
.append("path")
.attr("class", "link")
.attr("stroke", link.source.color || linkColor)
.attr("fill", "none")
.attr("d", elbow(link))
})
function elbow(d) {
return ("M" + transformX(d.source) + "," + d.source.x + "V" + d.target.x + "H" +transformX(d.target))
}
// heatmap
const cells = []
for (let i = 0; i < distanceMatrix.length; i++) {
for (let j = 0; j < distanceMatrix.length; j++) {
cells.push({i,j,distance: distanceMatrix[i][j]})
}
}
const heatmap = Plot.plot({
width: heat_width,
height: dendo_height,
margin: 0,
axis: null,
color: { scheme: "Blues"},
marks: [
Plot.cell(cells, {
x: "i",
y: "j",
fill: "distance"
})
]
})
//heatmap.style.flexShrink = 0
const div = document.createElement("div")
div.style.display = "flex"

div.appendChild(svg.node())
div.appendChild(heatmap)
return div
}
Insert cell
function dendrogram (data,options = {}) {
const {
width: width = width,
height: height = height,
hideLabels: hideLabels = false,
paddingBottom: paddingBottom = hideLabels ? 10: 50,
innerHeight = width - paddingBottom,
paddingLeft = 0,
h: cutHeight = undefined,
yLabel: yLabel = "↑ Height",
colors: colors = d3.schemeTableau10,
fontFamily: fontFamily = "Inter, sans-serif",
linkColor: linkColor = "grey",
fontSize: fontSize = fontSize,
strokeWidth: strokeWidth = 2
} = options;

const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height )

const root = d3.hierarchy(data)
const clusterLayout = d3.cluster().size([width - paddingLeft * 2 , innerHeight]);
clusterLayout(root)
const allNodes = root.descendants().reverse()
const leafs = allNodes.filter(d => !d.children)
leafs.sort((a,b) => a.x - b.x)
const leafHeight = height / leafs.length
leafs.forEach((d,i) => d.x = i*leafHeight + leafHeight/2)
allNodes.forEach(node => {
if (node.children) {
node.x = d3.mean(node.children, d => d.x)
}})
const maxHeight = root.data.height;

// line height scale on plot
const xScaleLinear = d3
.scaleLinear()
.domain([0, maxHeight])
.range([hideLabels ? innerHeight : innerHeight ,hideLabels ? -25: -5]);

const xAxisLinear = d3.axisTop(xScaleLinear).tickSize(3);

function transformX(data) {
const height = hideLabels ? innerHeight - 15 : innerHeight ;
return height - (data.data.height / maxHeight) * height;
}
// traverse through first order children and assign colors
if (cutHeight) {
let curIndex = -1;
root.each((child) => {
if (
child.data.height <= cutHeight &&
child.data.height > 0 &&
child.parent &&
!child.parent.color
) {
curIndex++;
child.color = colors[curIndex];
} else if (child.parent && child.parent.color) {
child.color = child.parent.color;
}
});
}

// y-axis
svg
// .append("g")
// .attr("transform", `translate(89, 95`)
.append("g")
.attr("class", "axis")
.attr("transform", `translate(${hideLabels ? 40 : 10} ,15)`)
.call(xAxisLinear)
.call((g) => g.select(".domain").remove())
.call((g) =>
g
.append("text")
.attr("x", -paddingLeft )
.attr("y", -20)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.style("font-family", fontFamily)
.text(yLabel)
)
.selectAll(".tick")
.classed("baseline", (d) => d == 0)
.style("font-size", `${fontSize}px`)
.style("font-family", fontFamily);
// }

// Links
root.links().forEach((link,i) => {
svg
.append("path")
.attr("class", "link")
.attr("stroke", link.source.color || linkColor)
.attr("stroke-width", `${strokeWidth}px`)
.attr("fill", "none")
.attr("transform", `translate(${hideLabels ? 20 : 10},${paddingLeft})`)
.attr("d", elbow(link))
})

// Nodes
root.descendants().forEach((desc) => {
svg
.append("circle")
.classed("node", true)
.attr("fill", desc.color)
.attr("cy", desc.x)
.attr("cx", transformX(desc))
.attr("transform", `translate(${hideLabels ? 20 : 10},${paddingLeft})`)
.attr("r", 4)

// leaf labels
if (desc.data.isLeaf && !hideLabels) {
svg
.append("text")
//.attr("x", desc.x)
.attr("dx", 20)
.attr("dy", 3)
.attr("text-anchor", "end")
.style("font-size", `${fontSize}px`)
.style("font-family", fontFamily)
.attr(
"transform",
`translate(${desc.y },${desc.x+ paddingLeft}) rotate(0)`
)
.text(desc.data.name || desc.data.index);
}
})
function elbow(d) {
return ("M" + transformX(d.source) + "," + d.source.x + "V" + d.target.x + "H" +transformX(d.target))
}

// heatmap
const cells = []
for (let i = 0; i < distanceMatrix.length; i++) {
for (let j = 0; j < distanceMatrix.length; j++) {
cells.push({i,j,distance: distanceMatrix[i][j]})
}
}
const heatmap = Plot.plot({
width: heat_width,
height: dendo_height,
margin: 0,
axis: null,
color: { scheme: "Blues"},
marks: [
Plot.cell(cells, {
x: "i",
y: "j",
fill: "distance"
})
]
})
//heatmap.style.flexShrink = 0
const div = document.createElement("div")
div.style.display = "flex"

div.appendChild(svg.node())
div.appendChild(heatmap)
return div
//eturn svg.node()
}
Insert cell
Insert cell
Insert cell
Insert cell
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