Дублированные/вложенные узлы на d3 для любого нового графа принудительной директивы обновления данных

Вот демо

Когда новые данные попадают в мой сервис d3, он загружает новый набор данных, но старый не удаляется. Поэтому у меня есть дубликаты узлов внутри элемента родительского узла 'g'. Новичок в d3, однако я много читал о selection.join() вместо enter().append(). Я также читал о том, как добавлять node.exit().remove(); и node.merge(node); в определенные моменты.

Как видно из dom, все новые свойства узла находятся в элементе <g class = "node">, продублированы, не заменяя исходные данные. Поэтому я получаю перекрытие контента.

Вот как устроены мои узлы...

const zoomContainer = d3.select('svg g');

const node = zoomContainer.selectAll('g').data(nodes, function (d) {
  return d.id;
});

//zoomContainer.selectAll('.node').data(node).exit().remove();

const nodeEnter = node
  .join('g')
  .attr('class', 'node')
  .call(
    d3
      .drag()
      .on('start', (d) => this.dragended(d3, d, simulation))
      .on('drag', function dragged(d) {
        d.fx = d3.event.x;
        d.fy = d3.event.y;
      })
      .on('end', (d) => this.dragended(d3, d, simulation))
  );

nodeEnter
  .append('circle')
  .style('fill', '#fff')
  .style('cursor', 'pointer')
  .style('fill-opacity', '1')
  .style('stroke-opacity', '0.5')
  .attr('id', (d, i) => d.id)
  .attr('r', 28);

nodeEnter
  .append('image')
  .attr('xlink:href', 'https://github.com/favicon.ico')
  .attr('x', -15)
  .attr('y', -60)
  .attr('width', 16)
  .attr('class', 'image')
  .style('cursor', 'pointer')
  .attr('height', 16);

const nodeText = nodeEnter
  .data(nodes)
  .append('text')
  .style('text-anchor', 'middle')
  .style('cursor', 'pointer')
  .attr('dy', -3)
  .attr('y', -25)
  .attr('class', 'nodeText')
  .attr('id', 'nodeText');

nodeText
  .selectAll('tspan')
  .data((d, i) => d.label)
  .join('tspan')
  .attr('class', 'nodeTextTspan')
  .text((d) => d)
  .style('font-size', '12px')
  .attr('x', -10)
  .attr('dx', 10)
  .attr('dy', 15);

Я, наверное, мог бы очистить график принудительно, но мне нравится и нужен способ, которым .join() можно сравнить, что изменилось, и какие опции использовать enter().append().exit(). Если кто-нибудь может понять, почему дубликаты не удаляются/не объединяются, я был бы признателен.

ОБНОВЛЯТЬ:

Если я использую enter().append('g') вместо join('g'), я получаю лучший результат. Я могу использовать zoomContainer.selectAll('.node').data(node).exit().remove(); заранее, и мои узлы обновляются, но только после двойного нажатия кнопки «Обновить». Если я использую join('g'), они дублируются, и я не могу использовать zoomContainer.selectAll('.node').data(node).exit().remove();

Вот демо

Я изменил код в демо... они больше не дублируются, но новые элементы вложены в элемент 'g'.

Tom Rudge 02.02.2023 12:49

У вас опечатка, должно быть .selectAll('.node') для выбора класса.

Gerardo Furtado 02.02.2023 13:16

Итак, да, я играл с .node и node. С .node я получаю дубликаты внутри g, и только node я получаю дубликаты снаружи

Tom Rudge 02.02.2023 13:27

Я добавил изображение для демонстрации дублирования внутри <g>. Если я .selectAll('node') старые узлы <g> остаются вместе с новыми

Tom Rudge 02.02.2023 13:32

Массивы _exit кажутся пустыми. Хотя они правильно представляют, сколько узлов существует.

Tom Rudge 02.02.2023 15:12
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
5
106
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Нацеливание на элемент <g>, а не на класс, а затем использование exit().remove(), похоже, помогло... Я добавлял атрибут класса на уровне .enter() в .join(), а затем выполнял выход на этом. Демо здесь

const node = zoomContainer
  .selectAll('.node')
  .data(this.nodes, function (d) {
    return d.id;
  });
zoomContainer.selectAll('g').data(node).exit().remove();

Другие вопросы по теме