Я использую библиотеку рисования d3 с React.js и столкнулся с проблемой запуска событий onClick из элементов SVG.
Что я хочу сделать, так это вызвать функцию (переданную от родителя в реквизитах), когда я нажимаю на элемент в SVG. Проблема в том, что элементы SVG создаются d3 и не являются реактивными компонентами. Есть ли способ, которым я могу сделать эту работу?
Для тех, кто не знаком с d3, он предоставляет очень удобный синтаксис для создания и обновления svg из набора данных. Код выглядит примерно так:
const existingNodes = d3Select('.nodes')
.selectAll('g.node')
.data(root.descendants());
const newNodes = existingNodes.enter().append('g');
newNodes
.append('circle')
.attr('onclick', (node) => 'onNodeSelected("Test")')
.attr('r', (node) => nodeRadius(node.data));
Проблема с этим кодом заключается в строке с надписью .attr('onclick', (node) => 'onNodeSelected("Test")')
, которая правильно добавляет атрибут onclick
к кругам, но, конечно же, onNodeSelected
является реквизитом компонента реакции, и к нему нельзя получить доступ таким образом.
Чтобы обеспечить некоторый контекст, сокращенный код компонента React выглядит следующим образом:
export const HierarchyDiagram = ({ onNodeSelected, hierarchyData }) => {
useEffect(() => {
function buildSvg(root) {
...abbreviated
const existingNodes = d3Select('.nodes')
.selectAll('g.node')
.data(root.descendants());
const newNodes = existingNodes.enter().append('g');
newNodes
.append('circle')
.attr('onclick', (node) => 'onNodeSelected({node})')
.attr('r', (node) => nodeRadius(node.data));
....abbreviated
}
if (hierarchyData) {
buildSvg(hierarchyData);
}
}, [hierarchyData]);
return (
<svg width = "100%" height = "100%">
<g className = "wrapper">
<g className = "links"></g>
<g className = "nodes"></g>
</g>
</svg>
);
};
Я отредактировал свой вопрос, чтобы предоставить больше контекста для того, как этот код называется
Я придумал ужасное решение, которое действительно работает. Если кто-то еще найдет лучшее решение, я отмечу ваш ответ как правильный.
Мое решение состояло в том, чтобы добавить скрытый ввод и использовать его для передачи идентификатора узла, который был нажат. Это работает, потому что дочерний элемент (круг в SVG) получает щелчок раньше родительского (события всплывают).
Мой сокращенный код выглядит так:
export const HierarchyDiagram = ({ onNodeSelected, hierarchyData }) => {
const hiddenInputId = 'selected-node-id';
useEffect(() => {
function buildSvg(root) {
...abbreviated
const existingNodes = d3Select('.nodes')
.selectAll('g.node')
.data(root.descendants());
const newNodes = existingNodes.enter().append('g');
newNodes
.append('circle')
.attr('onclick', (node) => 'getElementById("' + hiddenInputId + '").value = "' + node.data.id + '"')
.attr('r', (node) => nodeRadius(node.data));
....abbreviated
}
if (hierarchyData) {
buildSvg(hierarchyData);
}
}, [hierarchyData]);
function handleClick() {
const hiddenInput = document.getElementById(hiddenInputId);
const selectedNodeId = hiddenInput.value;
hierarchyData.descendants().forEach((node) => {
if (node.data?.id === selectedNodeId) onNodeSelected(node.data);
});
}
return (
<>
<input type = "hidden" id = {hiddenInputId} />
<svg width = "100%" height = "100%">
<g className = "wrapper">
<g className = "links"></g>
<g className = "nodes"></g>
</g>
</svg>
</>
);
};
Если вы хотите прикрепить событие onclick
, используйте on
. Это должно работать:
.append('circle')
.on('click', (evt, node) => {
onNodeSelected(node);
d3.event.stopPropagation(); // stop the event propagation
});
Это очень близко, но не совсем там. Когда я устанавливаю точку останова, я вижу, что node
имеет тип MouseEvent
, но мне нужен доступ к данным.
Я обнаружил, что this.__data__.data
действительно дает мне доступ к данным и, возможно, лучший хак, чем решение для скрытого ввода. Есть ли менее хакерский способ сделать это?
Обработчик события должен быть (evt, node) =>
вместо (node) =>
. Я обновил ответ, который теперь работает как положено.
Да, спасибо! Я не обращал внимания на часть аргументов, но теперь ответ выглядит намного лучше.
Где вы разместили код выше? в боковом компоненте?