Gho*_*rus 5 javascript dom d3.js
我尝试向某些节点添加新的 SVG 元素。为此,要添加元素的节点必须通过包含在文本内容中的字符串找到,例如,找到"id0"在<text>标签内具有的任何节点。
这是我的 HTML 层次结构的示例:
<pre>
<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>
</pre>
Run Code Online (Sandbox Code Playgroud)
我绝对不知道正确的解决方案,但我认为它是这样的:
d3.select('svg').select('g').selectAll('g').each(function (d, i) {})
.select('g').select('text').filter(function () {
return (d3.select(this).text() === 'id0')
})
.select(function () {
return this.parentElement;
})
.append('svg')
.attr('width', 400)
.attr('height', 400)
Run Code Online (Sandbox Code Playgroud)
如果标签<text>包含"id0",则返回父节点并向其添加 SVG 元素。但在线上return this.parentElement;出现错误:
类型“Window”上不存在属性“parentElement”。
当我使用parentElement或时会发生类似的错误parent。
另一种选择是 xpath,它允许通过文本进行搜索:
// if you know there's only one...
const singleResult = document.evaluate(`//*[name()="text" and contains(text(), "id0")]`, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
console.log(singleResult.nodeName, singleResult.textContent)
// if there might be multiple results
const multipleResults = document.evaluate(`//*[name()="text" and contains(text(), "id_multiple")]`, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
for (let i=0; i < multipleResults.snapshotLength; i++) {
console.log(multipleResults.snapshotItem(i).nodeName, multipleResults.snapshotItem(i).textContent)
}Run Code Online (Sandbox Code Playgroud)
<svg>
<g>
<g>
<text> id_multiple </text>
<text> 73% </text>
<svg></svg>
</g>
<g>
<svg></svg>
</g>
<g>
<text> id0 </text>
<text> 11% </text>
<svg></svg>
</g>
<g>
<text> id_multiple </text>
<text> 66% </text>
<svg></svg>
</g>
<g>
<svg></svg>
</g>
</g>
</svg>Run Code Online (Sandbox Code Playgroud)
返回的迭代器(/snaphots)对我来说是出乎意料的——肯定读过这个优秀的答案:/sf/answers/2272782431/,以及文档:MDN: document.evaluate。
请注意,由于“普通 HTML 节点和 svg 节点属于不同的命名空间”,因此您需要选择 SVG 节点,例如*[name()="svg"].
关于查找文本,我建议使用contains(text(),'needle')而不是更明确的text()='needle',因为周围的任何空格needle都会导致选择器返回null.
有趣的 xpath 与 CSS 评论: cssSelector 和 Xpath 之间有什么区别,在跨浏览器测试的性能方面哪个更好?
请注意,IE 不支持document.evaluate
D3 中没有内置方法可以通过文本内容选择元素,这是因为 D3 内部使用Element.querySelector()和Element.querySelectorAll()从 DOM 中选择元素。这些方法将CSS 选择器字符串作为单个参数,该参数由选择器级别 3规范定义。不幸的是,没有办法根据元素的内容来选择元素(这曾经可以通过:contains()伪类实现,但现在已经消失了)。
因此,要构建您的选择,您必须传递一个函数来.select()选择并返回<text>您感兴趣的元素。有多种方法可以做到这一点,但我想建议一种不太明显但优雅的方法。这利用了鲜为人知的NodeIterator接口,该接口可用于在 DOM 中满足过滤条件的节点列表上创建迭代器。
该NodeIterator实例是通过调用创建的,Document.createNodeIterator()
它接受三个参数:
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.
Run Code Online (Sandbox Code Playgroud)
一旦有了这个节点,就可以轻松应用该元素所需的任何修改,即附加元素、设置属性……如果您不是在寻找唯一值而是在寻找匹配的多个元素,这也可以轻松适应处理多个节点同一个字符串。您只需返回迭代器找到的节点数组,然后可以将其直接传递到 D3.selectAll()以创建多个节点的选择。
对于一个有效的演示,请查看以下代码片段:
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.
Run Code Online (Sandbox Code Playgroud)
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());Run Code Online (Sandbox Code Playgroud)