微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何使 svg 交互式以收集对所描绘元素的评论/注释

如何解决如何使 svg 交互式以收集对所描绘元素的评论/注释

我在 networkxnxv 的帮助下从维基数据创建了如下所示的有向图。结果是一个 svg 文件,它可能会嵌入到某个 html 页面中。

wikidata digraph

现在我希望每个节点和每个边都是“可点击的”,这样用户就可以将他们的评论添加到图表的特定元素中。我认为这可以通过弹出一个模态对话框来完成。这个对话框应该知道它是从哪个元素触发的,它应该通过 post 请求将 textarea 的内容发送到某个 url。

实现这一目标的最佳方法是什么?

解决方法

包裹在 W3C standard Web Component 中(所有现代浏览器都支持),您可以将其设为适用于任何 src="filename.svg"

<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 的样式

,

据我所知,nxv 为每个节点生成一个 g 元素,其类为“node”,所有元素都嵌套在图 g 中。所以基本上你可以遍历主组内的所有 g 元素,并在每个元素上附加一个点击事件侦听器。 (实际上,根据所需的行为,您可能希望将事件侦听器附加到 g 内部的形状,如下所示。要使形状内部可点击,它必须是 filled)>

点击时,它会更新一个 form,以做几件事:更新它的样式以将其显示为模态(提交后,表单应该返回隐藏状态),并使用点击的 textg 内容。

基本上是这样的:

<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>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。