Я хочу обновить DOM, содержащий дочерние элементы, с распространением данных.
В первый раз, когда DOM заполняется, данные должным образом распространяются на их дочерние элементы. Однако, когда я обновляю родительский DOM новыми данными, дочерний DOM не обновляется вместе с ним.
Также удивительно, что старые данные можно было извлечь из дочерних элементов, против чего я категорически против, потому что данные слишком велики, и я не хочу, чтобы старые/лишние данные сохранялись, которые содержат ссылку и сохраняют старые данные. (Утечка памяти в моем понимании)
const DATA = [{
name: "data1",
left: 70,
top: 70,
radius: 20
}, {
name: "data2",
left: 200,
top: 100,
radius: 50
}];
const svg = d3.select("#svg");
// Not working as expected
function loadData(data) {
console.info(data);
const container = svg.selectAll(".container")
.data(data);
container.exit().remove();
const enter = container.enter().append("g")
.attr("class", "container");
enter.append("circle");
enter.append("text");
container.merge(enter)
.attr("transform", entry => `translate(0,${entry.top})`)
svg.selectAll(".container circle")
.attr("r", entry => entry.radius)
.attr("cx", entry => entry.left);
svg.selectAll(".container text")
.text(entry => entry.name);
}
// Working, but not desired
function expected(data) {
console.info(data);
const container = svg.selectAll(".container")
.data(data);
container.exit().remove();
const enter = container.enter().append("g")
.attr("class", "container");
enter.append("circle");
enter.append("text");
container.merge(enter)
.attr("transform", entry => `translate(0,${entry.top})`)
svg.selectAll(".container circle")
.data(data)
.attr("r", entry => entry.radius)
.attr("cx", entry => entry.left);
svg.selectAll(".container text")
.data(data)
.text(entry => entry.name);
}
let i = 0;
loadData(DATA);
function updateData() {
i = (i+1) % 2;
loadData(DATA.slice(i,i+1));
}
function expectedUpdate() {
i = (i+1) % 2;
expected(DATA.slice(i,i+1));
}
button {
display: inline-block;
}
svg {
width: 300px;
height: 300px;
display: block;
}
<script src = "https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button onclick = "updateData();">Update</button>
<button onclick = "expectedUpdate();">Expected</button>
<svg id = "svg"></svg>
Это просто демонстрация, где у меня есть родительский элемент <g>
, и к нему добавляются 2 дочерних элемента <circle>
и <text>
.
В первый раз, когда он отображается, я использовал метод loadData()
для заполнения DOM, соответствующие дочерние DOM (круг и текст) смогли получить данные, вставленные в их родительский DOM (g), что показало правильное распространение данных по DOM. дерево.
Однако, когда я нажимаю кнопку «обновить» несколько раз, обновляется только родительский DOM, а дочерние DOM могут получить доступ к несуществующим данным (старым данным).
Кнопка «ожидаемый» показывает ожидаемое поведение, но я вижу, что данные привязываются несколько раз, чего я, как инженер javascript, так боюсь такого рода привязки данных.
Основная проблема в вашей функции нерабочий (loadData
) заключается в том, что вы используете selectAll
. В отличие от select
, selectAll
не распространяет данные.
Взгляните на эту таблицу, которую я составил, резюмируя различия между ними:
Метод | Выбрать() | выбрать все() |
---|---|---|
Выбор | выбирает первый элемент, который соответствует строке селектора | выбирает все элементы, соответствующие строке селектора |
Группировка | Не влияет на группировку | Влияет на группировку |
Распространение данных | Распространяет данные | Не распространяет данные |
Как видите, основная информация для вас такова:
select
: распространяется данные.selectAll
: не распространяется данные.Учитывая, что в вашем фрагменте всего один дочерних элементов (круг и текст) на группу, самое простое решение — просто изменить selectAll
на select
. Однако, если у вас есть более одного дочернего элемента для каждого родителя, лучшим (и идиоматическим) решением является создание вложенного выбора ввода-обновления-выхода, что немного более трудоемко.
Кроме того, вам не хватает ключевой функции:
const container = svg.selectAll(".container")
.data(data, function(d) {
return d.name;
});
Вот ваш код с этими изменениями:
const DATA = [{
name: "data1",
left: 70,
top: 70,
radius: 20
}, {
name: "data2",
left: 200,
top: 100,
radius: 50
}];
const svg = d3.select("#svg");
// Not working as expected
function loadData(data) {
console.info(data);
const container = svg.selectAll(".container")
.data(data, function(d) {
return d.name;
});
container.exit().remove();
const enter = container.enter().append("g")
.attr("class", "container");
enter.append("circle");
enter.append("text");
container.merge(enter)
.attr("transform", entry => `translate(0,${entry.top})`)
svg.select(".container circle")
.attr("r", entry => entry.radius)
.attr("cx", entry => entry.left);
svg.select(".container text")
.text(entry => entry.name);
}
// Working, but not desired
function expected(data) {
console.info(data);
const container = svg.selectAll(".container")
.data(data);
container.exit().remove();
const enter = container.enter().append("g")
.attr("class", "container");
enter.append("circle");
enter.append("text");
container.merge(enter)
.attr("transform", entry => `translate(0,${entry.top})`)
svg.selectAll(".container circle")
.data(data)
.attr("r", entry => entry.radius)
.attr("cx", entry => entry.left);
svg.selectAll(".container text")
.data(data)
.text(entry => entry.name);
}
let i = 0;
loadData(DATA);
function updateData() {
i = (i + 1) % 2;
loadData(DATA.slice(i, i + 1));
}
function expectedUpdate() {
i = (i + 1) % 2;
expected(DATA.slice(i, i + 1));
}
button {
display: inline-block;
}
svg {
width: 300px;
height: 300px;
display: block;
}
<script src = "https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button onclick = "updateData();">Update</button>
<button onclick = "expectedUpdate();">Expected</button>
<svg id = "svg"></svg>
@ShiraishiMai Это хорошее объяснение функции клавиши: bost.ocks.org/mike/constancy
Привет, Херардо, спасибо за ответ на вопрос! Очень ценю! Мне каким-то образом удалось решить мою настоящую модель, но я совершенно не понимаю, что происходит. Я вижу, что ключевая функция является важным фактором в этом примере, однако, когда я хочу добавить анимацию перехода, я вижу, что весь график перерисовывается, а не обновляется. Я хотел бы знать, как работает ключевая функция, если вы не возражаете, спасибо!