如何使 svg 交互以在描绘的元素上收集 comments/annotations
How to make an svg interactive to gather comments/annotations on depicted elements
我在 networkx and nxv 的帮助下从 wikidata 创建了如下有向图。结果是一个 svg 文件,可能会嵌入到某些 html 页面中。
现在我希望每个节点和每条边都是“可点击的”,以便用户可以将他们的评论添加到图形的特定元素。我认为这可以通过弹出一个模式对话框来完成。这个对话框应该知道它是从哪个元素触发的,它应该通过 post 请求将文本区域的内容发送到某个 url。
实现此目标的最佳方法是什么?
据我所知,nxv 为每个节点生成一个 g
元素和 class “节点”,所有节点都嵌套在图 g
中。所以基本上你可以遍历主组内的所有 g
s 元素,并在每个元素上附加一个点击事件侦听器。 (实际上,根据所需的行为,您可能希望将事件侦听器附加到 g 内部的形状,如下所示。要使形状内部可点击,它必须是 filled)
点击后,它会更新 form
,做几件事:更新其样式以将其显示为模式(提交后,表单应返回隐藏状态),并更新隐藏的输入与点击的 g
的 text
内容。
基本上是这样的:
<svg>Your nxv output goes here</svg>
<form style="display: none;">
<input type="hidden" id="node_title">
<textarea></textarea>
<input type="submit" value="Send!">
</form>
<script>
const graph = document.querySelector("svg g");
const form = document.querySelector("form");
[...graph.querySelectorAll("g")].map(g => { //loop over each g element inside graph
if (g.getAttribute("class") == "node") { //filter for nodes
let target = "polygon";
if (g.querySelector("polygon") === null) {
target = "ellipse";
}
g.querySelector(target).addEventListener("click",() => {
const node_title = g.querySelector("text").innerHTML;
form.querySelector("#node_title").setAttribute("value", node_title);
form.setAttribute("style","display: block;");
});
}
});
const submitForm = async (e) => { //function for handling form submission
const endpoint = "path to your POST endpoint";
const body = {
source_node: form.querySelector("#node_title").value,
textarea: form.querySelector("textarea").value
}
e.preventDefault(); //prevent the default form submission behavior
let response = await fetch(endpoint, { method: "POST", body: JSON.stringify(body) });
// you might wanna do something with the server response
// if everything went ok, let's hide this form again & reset it
form.querySelector("#node_title").value = "";
form.querySelector("textarea").value = "";
form.setAttribute("style","display: none;");
}
form.addEventListener("submit",submitForm);
</script>
包装在 W3C standard Web Component 中(所有现代浏览器都支持),您可以使它对任何 src="filename.svg"
通用
简单例子:How to get SVG document data to be inserted into the DOM?
更复杂的例子:
<graphviz-svg-annotator src="https://graphviz.org/Gallery/directed/fsm.svg">
</graphviz-svg-annotator>
SVG 使用异步获取加载
在此 SO 代码段中可以单击节点和边
添加你自己的、更好的模式,window并保存到数据库
尝试以下 SVG:https://graphviz.org/Gallery/directed/Genetic_Programming.html
<graphviz-svg-annotator src="fsm.svg"></graphviz-svg-annotator>
<graphviz-svg-annotator src="Linux_kernel_diagram.svg"></graphviz-svg-annotator>
<style>
svg .annotate { cursor:pointer }
</style>
<script>
customElements.define('graphviz-svg-annotator', class extends HTMLElement {
constructor() {
let loadSVG = async ( src , container = this.shadowRoot ) => {
container.innerHTML = `<style>:host { display:inline-block }
::slotted(svg) { width:100%;height:200px }
</style>
<slot name="svgonly">Loading ${src}</slot>`;
this.innerHTML = await(await fetch(src)).text(); // load full XML in lightDOM
let svg = this.querySelector("svg");
svg.slot = "svgonly"; // show only SVG part in shadowDOM slot
svg.querySelectorAll('g[id*="node"],g[id*="edge"]').forEach(g => {
let label = g.querySelector("text")?.innerHTML || "No label";
let shapes = g.querySelectorAll("*:not(title):not(text)");
let fill = (color = "none") => shapes.forEach(x => x.style.fill = color);
let prompt = "Please annotate: ID: " + g.id + " label: " + label;
g.classList.add("annotate");
g.onmouseenter = evt => fill("lightgreen");
g.onmouseleave = evt => fill();
g.onclick = evt => g.setAttribute("annotation", window.prompt(prompt));
})
}
super().attachShadow({ mode: 'open' });
loadSVG("//graphviz.org/Gallery/directed/"+this.getAttribute("src"));
}});
</script>
详细:
this.innerHTML = ...
在组件 ligthDOM
中注入完整的 XML
(因为元素有 shadowDOM,lightDOM 在浏览器中不可见)
但你只想要 SVG 部分(graphviz XML 有太多数据)......你不想要屏幕闪烁;这就是为什么我把 XML .. invisible.. in lightDOM
ShadowDOM <slot>
仅用于 反映 <svg>
使用此方法,<svg>
仍然可以从 全局 CSS 设置样式(参见 cursor:pointer
)
屏幕上有多个 SVG <g>
ID 值可能会发生冲突。
完整的 SVG 可以移动到 shadowDOM 中:
let svg = container.appendChild( this.querySelector("svg") );
但是你不能再用全局 CSS 设置 SVG 的样式了,因为全局 CSS 不能设置 shadowDOM
的样式
我在 networkx and nxv 的帮助下从 wikidata 创建了如下有向图。结果是一个 svg 文件,可能会嵌入到某些 html 页面中。
现在我希望每个节点和每条边都是“可点击的”,以便用户可以将他们的评论添加到图形的特定元素。我认为这可以通过弹出一个模式对话框来完成。这个对话框应该知道它是从哪个元素触发的,它应该通过 post 请求将文本区域的内容发送到某个 url。
实现此目标的最佳方法是什么?
据我所知,nxv 为每个节点生成一个 g
元素和 class “节点”,所有节点都嵌套在图 g
中。所以基本上你可以遍历主组内的所有 g
s 元素,并在每个元素上附加一个点击事件侦听器。 (实际上,根据所需的行为,您可能希望将事件侦听器附加到 g 内部的形状,如下所示。要使形状内部可点击,它必须是 filled)
点击后,它会更新 form
,做几件事:更新其样式以将其显示为模式(提交后,表单应返回隐藏状态),并更新隐藏的输入与点击的 g
的 text
内容。
基本上是这样的:
<svg>Your nxv output goes here</svg>
<form style="display: none;">
<input type="hidden" id="node_title">
<textarea></textarea>
<input type="submit" value="Send!">
</form>
<script>
const graph = document.querySelector("svg g");
const form = document.querySelector("form");
[...graph.querySelectorAll("g")].map(g => { //loop over each g element inside graph
if (g.getAttribute("class") == "node") { //filter for nodes
let target = "polygon";
if (g.querySelector("polygon") === null) {
target = "ellipse";
}
g.querySelector(target).addEventListener("click",() => {
const node_title = g.querySelector("text").innerHTML;
form.querySelector("#node_title").setAttribute("value", node_title);
form.setAttribute("style","display: block;");
});
}
});
const submitForm = async (e) => { //function for handling form submission
const endpoint = "path to your POST endpoint";
const body = {
source_node: form.querySelector("#node_title").value,
textarea: form.querySelector("textarea").value
}
e.preventDefault(); //prevent the default form submission behavior
let response = await fetch(endpoint, { method: "POST", body: JSON.stringify(body) });
// you might wanna do something with the server response
// if everything went ok, let's hide this form again & reset it
form.querySelector("#node_title").value = "";
form.querySelector("textarea").value = "";
form.setAttribute("style","display: none;");
}
form.addEventListener("submit",submitForm);
</script>
包装在 W3C standard Web Component 中(所有现代浏览器都支持),您可以使它对任何 src="filename.svg"
简单例子:How to get SVG document data to be inserted into the DOM?
更复杂的例子:
<graphviz-svg-annotator src="https://graphviz.org/Gallery/directed/fsm.svg"> </graphviz-svg-annotator>
SVG 使用异步获取加载
在此 SO 代码段中可以单击节点和边
添加你自己的、更好的模式,window并保存到数据库
尝试以下 SVG:https://graphviz.org/Gallery/directed/Genetic_Programming.html
<graphviz-svg-annotator src="fsm.svg"></graphviz-svg-annotator>
<graphviz-svg-annotator src="Linux_kernel_diagram.svg"></graphviz-svg-annotator>
<style>
svg .annotate { cursor:pointer }
</style>
<script>
customElements.define('graphviz-svg-annotator', class extends HTMLElement {
constructor() {
let loadSVG = async ( src , container = this.shadowRoot ) => {
container.innerHTML = `<style>:host { display:inline-block }
::slotted(svg) { width:100%;height:200px }
</style>
<slot name="svgonly">Loading ${src}</slot>`;
this.innerHTML = await(await fetch(src)).text(); // load full XML in lightDOM
let svg = this.querySelector("svg");
svg.slot = "svgonly"; // show only SVG part in shadowDOM slot
svg.querySelectorAll('g[id*="node"],g[id*="edge"]').forEach(g => {
let label = g.querySelector("text")?.innerHTML || "No label";
let shapes = g.querySelectorAll("*:not(title):not(text)");
let fill = (color = "none") => shapes.forEach(x => x.style.fill = color);
let prompt = "Please annotate: ID: " + g.id + " label: " + label;
g.classList.add("annotate");
g.onmouseenter = evt => fill("lightgreen");
g.onmouseleave = evt => fill();
g.onclick = evt => g.setAttribute("annotation", window.prompt(prompt));
})
}
super().attachShadow({ mode: 'open' });
loadSVG("//graphviz.org/Gallery/directed/"+this.getAttribute("src"));
}});
</script>
详细:
this.innerHTML = ...
在组件 ligthDOM
中注入完整的 XML (因为元素有 shadowDOM,lightDOM 在浏览器中不可见)但你只想要 SVG 部分(graphviz XML 有太多数据)......你不想要屏幕闪烁;这就是为什么我把 XML .. invisible.. in lightDOM
ShadowDOM
<slot>
仅用于 反映<svg>
使用此方法,
<svg>
仍然可以从 全局 CSS 设置样式(参见cursor:pointer
)屏幕上有多个 SVG
<g>
ID 值可能会发生冲突。
完整的 SVG 可以移动到 shadowDOM 中:let svg = container.appendChild( this.querySelector("svg") );
但是你不能再用全局 CSS 设置 SVG 的样式了,因为全局 CSS 不能设置 shadowDOM
的样式