В D3 нет встроенного способа выбора элемента по его текстовому содержимому, что связано с тем, что D3 внутренне использует Element.querySelector()
и Element.querySelectorAll()
выбрать элементы из DOM.Эти методы принимают строку CSS Selector в качестве отдельного параметра, который определяется спецификацией Selector Level 3 .К сожалению, нет способа выбрать элемент на основе его содержимого (это когда-то было возможно с помощью псевдокласса :contains()
, который, однако, отсутствует).
Поэтому, чтобы построить ваш выбор, вы должны прибегнуть к передаче функции в .select()
, которая выбирает и возвращает интересующий вас элемент <text>
. Есть разные способы сделать это, но я бы хотелпредложить не столь очевидный, но элегантный подход.Это использует малоизвестный интерфейс NodeIterator
, который можно использовать для создания итератора по списку узлов из DOM, которые соответствуют критериям вашего фильтра.
NodeIterator
экземпляр создается путем вызова Document.createNodeIterator()
, который принимает три аргумента:
- Корень поддерева DOM, из которого выполняется поиск.
- Aбитовая маска, определяющая, какие типы узлов вас интересуют;для ваших целей это будет
NodeFilter.SHOW_TEXT
. - Объект, реализующий метод
.acceptNode()
интерфейса NodeFilter
.Этот метод представлен каждому узлу указанного типа в порядке документа и должен возвращать NodeFilter.FILTER_ACCEPT
для любого совпадающего узла и NodeFilter.FILTER_REJECT
любых других узлов.На этом этапе ваша реализация будет искать совпадение значения id с текстовым содержимым фактического текстового элемента.
Затем вы можете вызвать .nextNode()
на созданном узлеитератор для обхода списка совпадающих узлов.Для вашей задачи это может быть что-то вроде следующего:
document.createNodeIterator(
this, // The root node of the searched DOM sub-tree.
NodeFilter.SHOW_TEXT, // Look for text nodes only.
{
acceptNode(node) { // The filter method of interface NodeFilter
return new RegExp(value).test(node.textContent) // Check if text contains string
? NodeFilter.FILTER_ACCEPT // Found: accept node
: NodeFilter.FILTER_REJECT; // Not found: reject and continue
}
})
.nextNode() // Get first node from iterator.
.parentElement; // Found node is a "pure" text node, get parent <text> element.
Имея этот узел под рукой, легко применять любые модификации, которые вам нужны для этого элемента - то есть добавлять элементы, устанавливать атрибуты ... Это такжелегко адаптируется для обработки нескольких узлов, если вы искали не уникальное значение, а несколько элементов, соответствующих одной строке.Вам просто нужно будет вернуть массив узлов, найденных итератором, который затем может быть напрямую передан в D3 .selectAll()
для создания выбора из нескольких узлов.
Для рабочей демонстрации взгляните наследующий фрагмент:
function nodeIterator(value) {
return function() {
return document.createNodeIterator(
this, // The root node of the searched DOM sub-tree.
NodeFilter.SHOW_TEXT, // Look for text nodes only.
{
acceptNode(node) { // The filter method of interface NodeFilter
return new RegExp(value).test(node.textContent) // Check if text contains string
? NodeFilter.FILTER_ACCEPT // Found: accept node
: NodeFilter.FILTER_REJECT; // Not found: reject and continue
}
})
.nextNode() // Get first node from iterator.
.parentElement; // Found node is a "pure" text node, get parent <text> element.
}
}
const filter = nodeIterator("id0");
const sel = d3.select("svg").select(filter);
// Manipulate the selection:...
// sel.append("g")
// .attr("transform", "...");
console.log(sel.node());
<script src="https://d3js.org/d3.v5.js"></script>
<svg>
<g>
<g>
<text> id3 </text>
<text> 73% </text>
<svg></svg>
</g>
<g>
<svg></svg>
</g>
<g>
<text> id0 </text>
<text> 11% </text>
<svg></svg>
</g>
<g>
<text> id1 </text>
<text> 66% </text>
<svg></svg>
</g>
<g>
<svg></svg>
</g>
</g>
</svg>