这是我的数据。
let data = [
{
"sample_date": "2017-07-04T00:00:00.000Z",
"ubiome": [
{
"count_norm": 1283,
"tax_name": "Bacteroides fragilis",
"tax_rank": "species"
},
{
"count_norm": 3708,
"tax_name": "Bacteroides thetaiotaomicron",
"tax_rank": "species"
},
{
"count_norm": 731,
"tax_name": "Bacteroides uniformis",
"tax_rank": "species"
},
{
"count_norm": 62226,
"tax_name": "Bacteroides vulgatus",
"tax_rank": "species"
},
{
"count_norm": 2139,
"tax_name": "Parabacteroides distasonis",
"tax_rank": "species"
}
]
},
{
"sample_date": "2017-07-04T00:00:00.000Z",
"ubiome": [
{
"count_norm": 1283,
"tax_name": "Bacteroides fragilis",
"tax_rank": "species"
},
{
"count_norm": 3708,
"tax_name": "Bacteroides thetaiotaomicron",
"tax_rank": "species"
},
{
"count_norm": 731,
"tax_name": "Bacteroides uniformis",
"tax_rank": "species"
},
{
"count_norm": 62226,
"tax_name": "Bacteroides vulgatus",
"tax_rank": "species"
},
{
"count_norm": 2139,
"tax_name": "Parabacteroides distasonis",
"tax_rank": "species"
}
]
}
]
我正在尝试使用d3来构建热图。我知道我需要使用访问器函数来使用示例日期,tax_name和count_norm来绘制x,y和rect值。我只是无法获得访问器函数来超越数据的第一级。...
var cells = svg.selectAll('rect')
.data(data)
.enter().append('g').append('rect')
.attr('class', 'cell')
.attr('width', cellSize)
.attr('height', cellSize)
.attr('y', function(d) { return yScale(d.name); })
.attr('x', function(d) { return xScale(d.rank); })
.attr('fill', function(d) { return color(d.value); });
如何构建访问器函数以获取更多嵌套数据?
这是我所处位置的更新。我无法使x&y pos在数据中工作。
let data = [{
"sample_date": "2017-07-04T00:00:00.000Z",
"ubiome": [{
"count_norm": 1283,
"tax_name": "Bacteroides fragilis",
"tax_rank": "species"
},
{
"count_norm": 3708,
"tax_name": "Bacteroides thetaiotaomicron",
"tax_rank": "species"
},
{
"count_norm": 731,
"tax_name": "Bacteroides uniformis",
"tax_rank": "species"
},
{
"count_norm": 62226,
"tax_name": "Bacteroides vulgatus",
"tax_rank": "species"
},
{
"count_norm": 2139,
"tax_name": "Parabacteroides distasonis",
"tax_rank": "species"
}
]
},
{
"sample_date": "2017-07-10T00:00:00.000Z",
"ubiome": [{
"count_norm": 1200,
"tax_name": "Bacteroides Noway",
"tax_rank": "species"
},
{
"count_norm": 3700,
"tax_name": "Bacteroides thetaiotaomicron",
"tax_rank": "species"
},
{
"count_norm": 700,
"tax_name": "Bacteroides uniformis",
"tax_rank": "species"
},
{
"count_norm": 62000,
"tax_name": "Bacteroides vulgatus",
"tax_rank": "species"
},
{
"count_norm": 2100,
"tax_name": "Parabacteroides distasonis",
"tax_rank": "species"
}
]
}
];
var dates = [];
var ubiomeonly = [];
var itemSize = 30,
cellSize = itemSize - 1,
margin = {
top: 120,
right: 20,
bottom: 20,
left: 110
};
var width = 750 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
for (i = 0; i < data.length; i++) {
var adate = moment(data[i].sample_date).format("YYYY-MM-DD")
dates.push(adate);
};
var bacteria = [];
for (i = 0; i < data.length; i++) {
bacteria.push(data[i].ubiome.slice(0, data[i].ubiome.length));
}
var bacteriaList = d3.merge(bacteria).map(function(d) {
return d.tax_name
});
bacteriaList = d3.set(bacteriaList).values();
var x_elements = dates,
y_elements = bacteriaList;
var xScale = d3.scaleOrdinal()
.domain(x_elements)
.range([0, x_elements.length * itemSize]);
var xAxis = d3.axisTop()
.scale(xScale)
.tickFormat(function(d) {
return d;
});
var yScale = d3.scaleOrdinal()
.domain(y_elements)
.range([0, y_elements.length * itemSize]);
var yAxis = d3.axisLeft()
.scale(yScale)
.tickFormat(function(d) {
return d;
});
var colorScale = d3.scaleThreshold()
.domain([0, 10000])
.range(["#2980B9", "#E67E22", "#27AE60", "#27AE60"]);
var svg = d3.select('#heatmap')
.data(data)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var cells = svg.selectAll('rect')
.data(function(d) {
return d.ubiome;
})
.enter().append('g').append('rect')
.attr('class', 'cell')
.attr('width', cellSize)
.attr('height', cellSize)
.attr('y', function(d, i) {
return yScale(d + i);
})
.attr('x', function(d) { return xScale(d.sample_date); })
.attr('fill', function(d) {
return colorScale(d.count_norm);
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll('text')
.attr('font-weight', 'normal');
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.selectAll('text')
.attr('font-weight', 'normal')
.style("text-anchor", "start")
.attr("dx", ".8em")
.attr("dy", ".5em")
.attr("transform", function(d) {
return "rotate(-65)";
});
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta charset="utf-8">
</head>
<body>
<div id="wrapper">
<h1>uBiome Bacterial Counts</h1>
<div id="heatmap"></div>
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
</body>
</html>
答案 0 :(得分:1)
您需要使用嵌套选择。通过两个selectAll().data().enter()
循环,我们可以在父元素中附加嵌套的子元素-每个元素都有自己的基准。仅使用一个selectAll().data().enter()
,我们仅在DOM中为数据数组中的每个项目创建元素。数据数组中的每个项目都可能具有某些属性,该属性本身就是一个数组(或者本身就是一个数据数组)-但是您实际上尚未对子数组进行任何操作。
由于数组中只有两个项目,因此只会创建两个元素。由于这些项目都不具有name
或rank
属性,因此访问这些属性将导致未定义。
这是上面代码的简化示例,我为示例数据数组中的每个项目附加了一个p
。每个p
的文本均设置为该元素的基准。嵌套数据仍然只是每个p
的基准的属性。由于示例数据数组有两个项目,因此仅创建两个元素:
let data = [{"sample_date":"2017-07-04T00:00:00.000Z","ubiome":[{"count_norm":1283,"tax_name":"Bacteroidesfragilis","tax_rank":"species"},{"count_norm":3708,"tax_name":"Bacteroidesthetaiotaomicron","tax_rank":"species"},{"count_norm":731,"tax_name":"Bacteroidesuniformis","tax_rank":"species"},{"count_norm":62226,"tax_name":"Bacteroidesvulgatus","tax_rank":"species"},{"count_norm":2139,"tax_name":"Parabacteroidesdistasonis","tax_rank":"species"}]},{"sample_date":"2017-07-04T00:00:00.000Z","ubiome":[{"count_norm":1283,"tax_name":"Bacteroidesfragilis","tax_rank":"species"},{"count_norm":3708,"tax_name":"Bacteroidesthetaiotaomicron","tax_rank":"species"},{"count_norm":731,"tax_name":"Bacteroidesuniformis","tax_rank":"species"},{"count_norm":62226,"tax_name":"Bacteroidesvulgatus","tax_rank":"species"},{"count_norm":2139,"tax_name":"Parabacteroidesdistasonis","tax_rank":"species"}]}];
var body = d3.select("body");
body.selectAll("p")
.data(data)
.enter()
.append("p")
.text(function(d) { return JSON.stringify(d); })
.style("background-color", function(d,i) { return ["yellow","skyblue"][i]; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
在您的示例中,您在示例中使用了嵌套元素,在rect
中使用了g
,但是您要向每个{{1 }}(否则,您将需要使用另一个输入循环),并且rect
与g
共享相同的基准,这将不会创建具有所需基准的元素。
现在,我们有两个(即将成为父级)元素,其基准面具有嵌套数据作为某些属性,我们可以为每个父级输入新元素。为此,我们现在可以对这两个元素进行一个rect
循环。请记住,下面的g
是每个selectAll().data().enter()
的数据:
d
在这里,我们根据每个父母的特定数据为父母创建一个孩子的选择。现在,我们可以通过访问父基准来使用包含每个孩子信息的属性。下面的代码段再次为每个父级创建了一个父级p
(再次通过颜色区分),子级选择使用父级的基准,并为每个子级创建了var parents = body.selectAll("p")
.data(data)
.enter()
.append("p");
var children = parents.selectAll("span")
.data(function(d) { return d.ubiome; }) // d is the parent datum here : {"sample_date":"time","ubiome":[child,child]}
.enter()
.append("span")
.attr("x", function(d) { }) // d is the child datum here
(通过边框进行了区分)。我已设置每个跨度以显示其基准。这给了我们两个父p
,每个都有五个子span
。
p
spans
let data = [{"sample_date":"2017-07-04T00:00:00.000Z","ubiome":[{"count_norm":9876,"tax_name":"Bacteroidesfragilis","tax_rank":"species"},{"count_norm":3708,"tax_name":"Bacteroidesthetaiotaomicron","tax_rank":"species"},{"count_norm":731,"tax_name":"Bacteroidesuniformis","tax_rank":"species"},{"count_norm":62226,"tax_name":"Bacteroidesvulgatus","tax_rank":"species"},{"count_norm":2139,"tax_name":"Parabacteroidesdistasonis","tax_rank":"species"}]},{"sample_date":"2017-07-04T00:00:00.000Z","ubiome":[{"count_norm":1283,"tax_name":"Bacteroidesfragilis","tax_rank":"species"},{"count_norm":3708,"tax_name":"Bacteroidesthetaiotaomicron","tax_rank":"species"},{"count_norm":731,"tax_name":"Bacteroidesuniformis","tax_rank":"species"},{"count_norm":62226,"tax_name":"Bacteroidesvulgatus","tax_rank":"species"},{"count_norm":2139,"tax_name":"Parabacteroidesdistasonis","tax_rank":"species"}]}];
var body = d3.select("body");
var parents = body.selectAll("p")
.data(data)
.enter()
.append("p")
.style("background-color", function(d,i) { return ["yellow","lightblue"][i]; })
var children = parents.selectAll("span")
.data(function(d) { return d.ubiome; })
.enter()
.append("span")
.text(function(d) { return JSON.stringify(d); })
在这里,我不访问子项span {
display: block;
border: 1px dotted black;
}
的特定属性,但是您可以看到跨度的数据对应于父项的ubiome数组的项。因此,访问子级的特定属性应该非常简单。
以下是适用于您的代码段的逻辑:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
d
我在这里做什么?
首先,但与嵌套数据无关,我在天和物种上都使用了带刻度(这些刻度是为占用特定宽度/高度的东西(如条形图而设计的,但仍然是顺序的)。除了格式化轴标签以简化比例之外,我也不会转换您的日期字符串。 我还使x始终引用水平比例尺/数据/轴,并与y相同(因此为什么轴被翻转,我也不会将数据绑定到svg上,因为这是不必要的)。 / p>
第二,我为每个采样周期(父元素)创建了一个组。我根据日期的缩放值(包含在父基准中)对这些父let data = [{
"sample_date": "2017-07-04T00:00:00.000Z",
"ubiome": [{
"count_norm": 1283,
"tax_name": "Bacteroides fragilis",
"tax_rank": "species"
},
{
"count_norm": 3708,
"tax_name": "Bacteroides thetaiotaomicron",
"tax_rank": "species"
},
{
"count_norm": 731,
"tax_name": "Bacteroides uniformis",
"tax_rank": "species"
},
{
"count_norm": 62226,
"tax_name": "Bacteroides vulgatus",
"tax_rank": "species"
},
{
"count_norm": 2139,
"tax_name": "Parabacteroides distasonis",
"tax_rank": "species"
}
]
},
{
"sample_date": "2017-07-10T00:00:00.000Z",
"ubiome": [{
"count_norm": 1200,
"tax_name": "Bacteroides Noway",
"tax_rank": "species"
},
{
"count_norm": 3700,
"tax_name": "Bacteroides thetaiotaomicron",
"tax_rank": "species"
},
{
"count_norm": 700,
"tax_name": "Bacteroides uniformis",
"tax_rank": "species"
},
{
"count_norm": 62000,
"tax_name": "Bacteroides vulgatus",
"tax_rank": "species"
},
{
"count_norm": 2100,
"tax_name": "Parabacteroides distasonis",
"tax_rank": "species"
}
]
}
];
var dates = [];
var ubiomeonly = [];
var itemSize = 30,
cellSize = itemSize - 1,
margin = {
top: 120,
right: 20,
bottom: 20,
left: 110
};
var width = 750 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
dates = data.map(function(d) {
return d.sample_date;
})
var bacteria = [];
for (i = 0; i < data.length; i++) {
bacteria.push(data[i].ubiome.slice(0, data[i].ubiome.length));
}
var bacteriaList = d3.merge(bacteria).map(function(d) {
return d.tax_name
});
bacteriaList = d3.set(bacteriaList).values();
var y_elements = dates,
x_elements = bacteriaList;
var xScale = d3.scaleBand()
.domain(x_elements)
.range([0, x_elements.length * itemSize]);
var xAxis = d3.axisTop()
.scale(xScale)
.tickFormat(function(d) {
return d;
});
var yScale = d3.scaleBand()
.domain(y_elements)
.range([0, y_elements.length * itemSize]);
var yAxis = d3.axisLeft()
.scale(yScale)
.tickFormat(function(d) {
return moment(d).format("YYYY-MM-DD");
});
var colorScale = d3.scaleThreshold()
.domain([0, 10000])
.range(["#2980B9", "#E67E22", "#27AE60", "#27AE60"]);
var svg = d3.select('#heatmap')
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parents = svg.selectAll(null)
.data(data)
.enter().append('g')
.attr("transform",function(d) { return "translate(0," + yScale(d.sample_date) + ")" });
var children = parents.selectAll('rect')
.data(function(d) { return d.ubiome; })
.enter()
.append('rect')
.attr('class', 'cell')
.attr('width', cellSize)
.attr('height', cellSize)
.attr('x', function(d) { return xScale(d.tax_name); })
.attr('fill', function(d) {
return colorScale(d.count_norm);
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll('text')
.attr('font-weight', 'normal');
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.selectAll('text')
.attr('font-weight', 'normal')
.style("text-anchor", "start")
.attr("dx", ".8em")
.attr("dy", ".5em")
.attr("transform", function(d) {
return "rotate(-65)";
});
应用了转换。通过这样做,我将每个父级都视为一行(我仅转换y值)。 虽然我不必在上面的文本示例中放置任何内容,但我改用了背景色。
第三,我正在为每个父级创建子级元素,这些子级元素是根据种类进行定位的(因为该行已经在父级<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<h1>uBiome Bacterial Counts</h1>
<div id="heatmap"></div>
上进行了平移)。从子(矩形)的基准面访问该物种-原始数据集中第二级数组之一中的一项。要设置每个孩子的基准,我在嵌套的g
循环中使用父级的基准(d.ubiome-而不是整个基准),就像上面带有文本的示例一样。
在此模式中,每个父基准都是原始数组中的一个项目。每个子基准都是该子各自父基准中包含的数组中的一项。因此,为什么我们使用g
就是这样。当行将子级放置在子级上时,我没有访问任何子级父级数据,但是如果您需要访问父级数据,则可以使用几种方法,一种是从访问器函数中选择父级:selectAll().data().enter()
,或使用局部变量。
您可能会问,为什么d3不将嵌套的数据绑定到嵌套的元素,这似乎是您期望的行为。嵌套元素经常与它们的父元素共享相同的数据,因为这样便于进行标注(父g中的圆圈和文字)。具有包含数组的属性的数据可能允许数据的多种表示形式(通过时间进行动画处理,数据的不同分类或表示形式等),因此,数组不是嵌套的数据,它们将永远使用其自己的元素来表示。有时数据集将具有多个包含数组的属性(它们本身包含数组,例如:geojson),这种行为如何知道要为子级使用哪个数组?