聚类热图一般情况下,多通过Python或R进行绘制,由于客户需求,需要在图形上进行一些交互操作,于是乎决定通过D3.js进行绘制
- 准备工作
引入d3.js
vue
import * as d3 from "d3";//这里使用的是6.7.0
准备数据
vue
data(){
return {
dataGroup: [
{
"group": "ENSG00000167900",
"variable": "a",
"value": "0.611586075"
},
{
"group": "ENSG00000117399",
"variable": "a",
"value": "0.632157568"
},
...//数据量大,这里省略
{
"group": "ENSG00000276043",
"variable": "f",
"value": "-0.432913538"
},
{
"group": "ENSG00000088325",
"variable": "f",
"value": "-0.449567023"
},
{
"group": "ENSG00000112984",
"variable": "f",
"value": "-0.439659798"
}
],
sampleClusterData: {
"children": [
{
"children": [
{
"name": "e"
},
{
"children": [
{
"name": "d"
},
{
"name": "f"
}
],
"name": "node1"
}
],
"name": "node2"
},
{
"children": [
{
"name": "b"
},
{
"children": [
{
"name": "a"
},
{
"name": "c"
}
],
"name": "node3"
}
],
"name": "node4"
}
],
"name": "node5"
},
geneClusterData: {
"name": "node19",
"children": [
{
"name": "ENSG00000282885"
},
{
"name": "node18",
"children": [
{
"name": "node16",
"children": [
{
"name": "node3",
"children": [
{
"name": "ENSG00000148773"
},
{
"name": "ENSG00000183856"
}
]
},
{
"name": "node13",
"children": [
{
"name": "ENSG00000167900"
},
{
"name": "ENSG00000117399"
}
]
}
]
},
{
"name": "node17",
"children": [
{
"name": "ENSG00000276043"
},
{
"name": "node15",
"children": [
{
"name": "node9",
"children": [
{
"name": "ENSG00000237649"
},
{
"name": "node7",
"children": [
{
"name": "ENSG00000106333"
},
{
"name": "node5",
"children": [
{
"name": "ENSG00000013810"
},
{
"name": "ENSG00000127564"
}
]
}
]
}
]
},
{
"name": "node14",
"children": [
{
"name": "node10",
"children": [
{
"name": "ENSG00000171848"
},
{
"name": "node4",
"children": [
{
"name": "ENSG00000089685"
},
{
"name": "ENSG00000166508"
}
]
}
]
},
{
"name": "node12",
"children": [
{
"name": "node1",
"children": [
{
"name": "ENSG00000088325"
},
{
"name": "ENSG00000112984"
}
]
},
{
"name": "node11",
"children": [
{
"name": "node6",
"children": [
{
"name": "ENSG00000186185"
},
{
"name": "node2",
"children": [
{
"name": "ENSG00000111206"
},
{
"name": "ENSG00000166851"
}
]
}
]
},
{
"name": "node8",
"children": [
{
"name": "ENSG00000011426"
},
{
"name": "ENSG00000131747"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
}
}
- 创建一个容器
html
<div id="my_dataviz"></div>
- 设置图形的尺寸和边距
js
const margin = {
top: 80,
right: 140,
bottom: 30,
left: 100
},
width = 600 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
- 将SVG对象附加到页面主体
js
const svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("id", "content")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
- 将sampleClusterData和geneClusterData分别处理
js
function cluster(dataset, type) {
let xArr = [];
let yArr = [];
let tree = data => {
const root = d3.hierarchy(data)
.sort((a, b) => (a.height - b.height) || a.data.name.localeCompare(b.data.name));
if (type == 'y') {
return d3.cluster().size([height, 80]).separation(() => 2)(root);
} else {
return d3.cluster().size([width, 50]).separation(() => 2)(root);
}
const root = tree(dataset);
let x0 = Infinity;
let x1 = -x0;
root.each(d => {
if (d.x > x1) {
x1 = d.x;
}
if (d.x < x0) {
x0 = d.x
}
if (type == 'x') {
d.x = (d.y ^= d.x ^= d.y) ^ d.x;
}
});
if (type == 'y') {
const g = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("transform", `translate(-80,0)`);
g.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll("path")
.data(root.links())
.join("path")
.attr("d", d => `M${d.target.y},${d.target.x}
L${d.source.y},${d.target.x}
${d.source.y},${d.source.x}
${d.source.y},${d.source.x}`);
g.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll("g")
.data(root.descendants().reverse())
.join("g")
.attr("transform", d => {
if (!d.children) {
yArr.push(d.data.name)
}
return `translate(${d.y},${d.x})`
});
return yArr
} else {
const g = svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("transform", `translate(0,-50)`);
g.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5)
.selectAll("path")
.data(root.links())
.join("path")
.attr("d", d => `M${d.target.y},${d.target.x}
L${d.target.y},${d.source.x}
${d.source.y},${d.source.x}
${d.source.y},${d.source.x}`);
g.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.selectAll("g")
.data(root.descendants().reverse())
.join("g")
.attr("transform", d => {
if (!d.children) {
xArr.push(d.data.name)
}
return `translate(${d.y},${d.x})`
});
return xArr
}
}
- 构建X轴Y轴并渲染sampleClusterData和geneClusterData处理后的数据
js
// 行和列的标签->称为"group"和"variable"列的唯一标识符。
const myGroups = Array.from(new Set(this.dataGroup.map(d => d.group)))
const myVars = Array.from(new Set(this.dataGroup.map(d => d.variable)))
cluster(this.sampleClusterData, "x");
cluster(this.geneClusterData, "y");
// 构建X刻度和轴:
const x = d3.scaleBand()
.range([0, width])
.domain(myVars)
.padding(0.005);
svg.append("g")
.style("font-size", 15)
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x).tickSize(0))
.select(".domain").remove()
// 建立Y刻度和轴:
const y = d3.scaleBand()
.range([height, 0])
.domain(myGroups)
.padding(0.005);
svg.append("g")
.style("font-size", 15)
.attr("transform", `translate(${width}, 0)`)
.call(d3.axisRight(y).tickSize(0))
.select(".domain").remove()
- 建立颜色刻度、创建工具提示
js
// 建立颜色刻度
const myColor = d3.scaleSequential()
.interpolator(d3.interpolateInferno)
.domain([-1, 1])
// 创建工具提示
const tooltip = d3.select("#my_dataviz")
.append("div")
.style("opacity", 0)
.attr("class", "tooltip")
.style("background-color", "white")
.style("border", "solid")
.style("border-width", "2px")
.style("border-radius", "5px")
.style("padding", "5px")
// 当用户悬停/移动/离开单元格时更改工具提示的三个函数
const mouseover = function (event, d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("stroke", "#666")
.style("opacity", 1)
}
const mousemove = function (event, d) {
tooltip
.html("The exact value of<br>this cell is: " + d.value)
.style("left", (event.x) / 2 + "px")
.style("top", (event.y) / 2 + "px")
}
const mouseleave = function (event, d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "none")
.style("opacity", 0.8)
}
- 渲染dataGroup为矩形
js
svg.selectAll()
.data(this.dataGroup)
.join("rect")
.attr("x", function (d) {
return x(d.variable)
})
.attr("y", function (d) {
return y(d.group)
})
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.style("fill", function (d) {
return myColor(d.value)
})
.style("stroke-width", 2)
.style("stroke", "none")
.style("opacity", 0.8)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave)