Вам нужно использовать вложенные выборки.С помощью двух 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
, но вы добавляетеодин 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;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
У меня нет доступа к определенным свойствам дочернего элемента d
здесь, но вы можете видеть, что данные диапазона соответствуют элементу родительского элементамассив убиомей.Таким образом, доступ к определенным свойствам детей должен быть довольно простым.
Вот приведенная выше логика, примененная к вашему фрагменту:
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)";
});
<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>
Что я здесь сделал?
Во-первых, но я не использую вложенные данные, я использую шкалы для ваших дней и видов.(это шкалы, предназначенные для вещей, которые занимают определенную ширину / высоту, такие как гистограммы, они все еще порядковые).Я также не конвертирую вашу строку даты, кроме как для форматирования метки оси, чтобы упростить масштаб. Я также заставил x постоянно ссылаться на горизонтальные шкалы / данные / оси и то же самое с y (следовательно, почему ваши оси перевернуты, я также не привязываю данные к svg, так как это не нужно) .
Во-вторых, я создал группу для каждого периода выборки (родительский элемент).Я применяю перевод к этим родительским g
s на основе масштабированного значения даты (содержится в исходных данных).Делая это, я рассматриваю каждого родителя как строку (я перевожу только значение y). Хотя мне не нужно было ничего размещать в текстовых примерах выше, я применил цвет фона .
В-третьих, я создаю дочерние элементы для каждого родителя, они расположены в зависимости от вида (так как строка уже размещена с переводом на родителя g
). Доступ к виду осуществляется из исходных данных ребенка (прямоугольник) - элемента в одном из массивов второго уровня в исходном наборе данных. Чтобы установить данные каждого ребенка, я использую данные родителя (d.ubiome - не весь элемент данных) в цикле selectAll().data().enter()
, как в приведенном выше примере с текстом.
В этом шаблоне каждый родительский элемент данных является элементом в исходном массиве. Каждый дочерний элемент данных является элементом в некотором массиве, который содержится в элементах данных соответствующего дочернего элемента. Следовательно, почему мы используем parent.selectAll().data(function(d) { return d.ubiome }).enter()
Вот и все. Я не обращаюсь ни к каким родительским данным для дочерних элементов, так как строки располагают дочерние элементы, но если вам нужно было получить доступ к родительским данным, вы можете использовать несколько подходов, один из них - выбрать родительский элемент из функции доступа: d3.select(this.parentElement).data()
или использовать локальную переменную.
Вы можете спросить, почему d3 не связывает вложенные данные с вложенными элементами, что, по-видимому, соответствует ожидаемому вами поведению. Чаще всего вложенные элементы имеют тот же элемент данных, что и их родительские элементы, поскольку это облегчает такие вещи, как маркировка (круг и текст в родительском элементе g). Данные со свойствами, которые содержат массивы, могут разрешать множественные представления данных (анимация во времени, различные классификации или представления данных и т. Д.), И, таким образом, массивы не являются вложенными данными, которые когда-либо будут представлены своими собственными элементами. И иногда наборы данных будут иметь несколько свойств, которые содержат массивы (которые сами содержат массивы, например, geojson), как это поведение будет знать, какой массив использовать для дочерних элементов?