如何将graphviz生成的SVG元素与DOT源代码中的元素相关联 [英] How do I associate SVG elements generated by graphviz to elements in the DOT source code

查看:69
本文介绍了如何将graphviz生成的SVG元素与DOT源代码中的元素相关联的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我已经使用viz.js从点文件生成了svg图.
现在,使用javascript很容易选择它的元素,但是我看不到与原始点文件的任何关联.我在viz.js库中看不到任何对象结构,该对象结构将生成的svg图表元素与点源元素相关联,因此,如果我用鼠标选择svg元素,我会知道该svg元素是对应的到生成它的点元素.有没有办法得到这样的反馈?我需要这样做,以便在svg中(在浏览器中可视化地)编辑元素时,能够将编辑内容映射回点文件并在源上反映更改.

So, I've generated an svg graph from the dot file, using viz.js.
Now, it's easy to select it's elements, using javascript, but I don't see any association to the original dot file. I don't see any object structure in viz.js library, that ties the generated svg chart elements to the dot source elements, so, that, if I select the svg element with the mouse, I would know that this svg element is correspondent to the dot element, that it was generated from. Is there a way to have a such feedback? I need this, so that, if I edit an element in svg (visually in the browser), I would be able to map the edit back to the dot file and reflect the change on the source.

digraph DB {
rankdir=LR
node [shape=record]

person [
    label="
        Person table|
        <id> Person ID|
        <fn> First Name|
        <mn> Middle Name|
        <ln> Last Name
    "
]

address [
    label="
        Addresses table|
        <id> Address ID|
        <pid> Person ID|
        <index> ZIP Code|
        <street> Street Name|
        <house> House Number|
        <town> City/Town/Village Name|
        <state> State Name|
        <district> County/District Name|
        <country> Country Name
    "
]

phone [
    label="
        Phone Number table|
        <pid> Person ID|
        <cc> Country Code|
        <ac> Area Code|
        <n> Phone Number
    "
]
{phone:pid address:pid} -> person:id
}

这是Viz.js库生成的svg结果(但是,对我来说,我不在乎,如果其他库也可以做到这一点,我将使用该其他库):

Here is the svg result, generated by Viz.js library (but, as for me, I don't care, if the same can be done by other library, I will use that other library):

<svg width="671pt" height="257pt" viewBox="0 0 671 257" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 253)">
<title>DB</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-253 666.8861,-253 666.8861,4 -4,4"></polygon>
<!-- person -->
<g id="node1" class="node">
<title>person</title>
<polygon fill="none" stroke="#000000" points="277.8566,-62.5 277.8566,-186.5 371.2234,-186.5 371.2234,-62.5 277.8566,-62.5"></polygon>
<text text-anchor="middle" x="324.54" y="-169.9" font-family="Times,serif" font-size="14.00" fill="#000000">Person table</text>
<polyline fill="none" stroke="#000000" points="277.8566,-161.7 371.2234,-161.7 "></polyline>
<text text-anchor="middle" x="324.54" y="-145.1" font-family="Times,serif" font-size="14.00" fill="#000000">Person ID</text>
<polyline fill="none" stroke="#000000" points="277.8566,-136.9 371.2234,-136.9 "></polyline>
<text text-anchor="middle" x="324.54" y="-120.3" font-family="Times,serif" font-size="14.00" fill="#000000">First Name</text>
<polyline fill="none" stroke="#000000" points="277.8566,-112.1 371.2234,-112.1 "></polyline>
<text text-anchor="middle" x="324.54" y="-95.5" font-family="Times,serif" font-size="14.00" fill="#000000">Middle Name</text>
<polyline fill="none" stroke="#000000" points="277.8566,-87.3 371.2234,-87.3 "></polyline>
<text text-anchor="middle" x="324.54" y="-70.7" font-family="Times,serif" font-size="14.00" fill="#000000">Last Name</text>
</g>
<!-- address -->
<g id="node2" class="node">
<title>address</title>
<polygon fill="none" stroke="#000000" points="504.1939,-.5 504.1939,-248.5 662.8861,-248.5 662.8861,-.5 504.1939,-.5"></polygon>
<text text-anchor="middle" x="583.54" y="-231.9" font-family="Times,serif" font-size="14.00" fill="#000000">Addresses table</text>
<polyline fill="none" stroke="#000000" points="504.1939,-223.7 662.8861,-223.7 "></polyline>
<text text-anchor="middle" x="583.54" y="-207.1" font-family="Times,serif" font-size="14.00" fill="#000000">Address ID</text>
<polyline fill="none" stroke="#000000" points="504.1939,-198.9 662.8861,-198.9 "></polyline>
<text text-anchor="middle" x="583.54" y="-182.3" font-family="Times,serif" font-size="14.00" fill="#000000">Person ID</text>
<polyline fill="none" stroke="#000000" points="504.1939,-174.1 662.8861,-174.1 "></polyline>
<text text-anchor="middle" x="583.54" y="-157.5" font-family="Times,serif" font-size="14.00" fill="#000000">ZIP Code</text>
<polyline fill="none" stroke="#000000" points="504.1939,-149.3 662.8861,-149.3 "></polyline>
<text text-anchor="middle" x="583.54" y="-132.7" font-family="Times,serif" font-size="14.00" fill="#000000">Street Name</text>
<polyline fill="none" stroke="#000000" points="504.1939,-124.5 662.8861,-124.5 "></polyline>
<text text-anchor="middle" x="583.54" y="-107.9" font-family="Times,serif" font-size="14.00" fill="#000000">House Number</text>
<polyline fill="none" stroke="#000000" points="504.1939,-99.7 662.8861,-99.7 "></polyline>
<text text-anchor="middle" x="583.54" y="-83.1" font-family="Times,serif" font-size="14.00" fill="#000000">City/Town/Village Name</text>
<polyline fill="none" stroke="#000000" points="504.1939,-74.9 662.8861,-74.9 "></polyline>
<text text-anchor="middle" x="583.54" y="-58.3" font-family="Times,serif" font-size="14.00" fill="#000000">State Name</text>
<polyline fill="none" stroke="#000000" points="504.1939,-50.1 662.8861,-50.1 "></polyline>
<text text-anchor="middle" x="583.54" y="-33.5" font-family="Times,serif" font-size="14.00" fill="#000000">County/District Name</text>
<polyline fill="none" stroke="#000000" points="504.1939,-25.3 662.8861,-25.3 "></polyline>
<text text-anchor="middle" x="583.54" y="-8.7" font-family="Times,serif" font-size="14.00" fill="#000000">Country Name</text>
</g>
<!-- address&#45;&gt;person -->
<g id="edge1" class="edge">
<title>address-&gt;person:id</title>
<path fill="none" stroke="#000000" d="M503.9959,-133.8802C457.4691,-139.3669 403.6776,-145.7102 381.6916,-148.3029"></path>
<polygon fill="#000000" stroke="#000000" points="381.0613,-144.8529 371.54,-149.5 381.8811,-151.8047 381.0613,-144.8529"></polygon>
</g>
<!-- phone -->
<g id="node3" class="node">
<title>phone</title>
<polygon fill="none" stroke="#000000" points="0,-62.5 0,-186.5 131.08,-186.5 131.08,-62.5 0,-62.5"></polygon>
<text text-anchor="middle" x="65.54" y="-169.9" font-family="Times,serif" font-size="14.00" fill="#000000">Phone Number table</text>
<polyline fill="none" stroke="#000000" points="0,-161.7 131.08,-161.7 "></polyline>
<text text-anchor="middle" x="65.54" y="-145.1" font-family="Times,serif" font-size="14.00" fill="#000000">Person ID</text>
<polyline fill="none" stroke="#000000" points="0,-136.9 131.08,-136.9 "></polyline>
<text text-anchor="middle" x="65.54" y="-120.3" font-family="Times,serif" font-size="14.00" fill="#000000">Country Code</text>
<polyline fill="none" stroke="#000000" points="0,-112.1 131.08,-112.1 "></polyline>
<text text-anchor="middle" x="65.54" y="-95.5" font-family="Times,serif" font-size="14.00" fill="#000000">Area Code</text>
<polyline fill="none" stroke="#000000" points="0,-87.3 131.08,-87.3 "></polyline>
<text text-anchor="middle" x="65.54" y="-70.7" font-family="Times,serif" font-size="14.00" fill="#000000">Phone Number</text>
</g>
<!-- phone&#45;&gt;person -->
<g id="edge2" class="edge">
<title>phone-&gt;person:id</title>
<path fill="none" stroke="#000000" d="M131.1663,-132.2389C180.2951,-138.0324 243.0276,-145.4301 267.307,-148.2933"></path>
<polygon fill="#000000" stroke="#000000" points="267.1989,-151.8047 277.54,-149.5 268.0187,-144.8529 267.1989,-151.8047"></polygon>
</g>
</g>
</svg>

比方说,我要编辑源点文件中的城市/镇/村名",而不是编辑源文本,而是通过视觉上单击该点源的相关svg表示形式来进行编辑.我可以编写一些JavaScript,例如,这将允许我单击svg图形上的城市/城镇/村庄名称",并且该块变为活动状态.然后,根据需要我就地对其进行编辑.问题在于将更改保存回源. JavaScript应该相应地更改点源,但是问题是,用viz.js生成的svg与源没有任何关系.也就是说,如果您查看生成的svg的来源,则不会添加任何ID或任何ID,这表示从哪个dot元素生成了特定的svg元素.无法识别要编辑哪个元素,以便将编辑后的值传递回正确的点元素,以便在源中进行更改.我可以想到一些方法来解决我的问题:

Let's say, I want to edit the " City/Town/Village Name" in the source dot file not by editing source's text, but by visually clicking on the related generated svg representation of that dot source. I can write some JavaScript, that will allow me to click on "City/Town/Village Name" on the svg graphic, for example, and the block becomes active. Then, I edit it in-place, as I wish. The problem lies with saving the change back to the source. JavaScript should change the dot source accordingly, but the problem is that the svg, generated with viz.js doesn't have any ties to the source. I.e., if you look at the source of the generated svg, it doesn't add any ids or anything, that would indicate, that a particular svg element was generated from which dot element. There is no way to identify, which element was edited in order to pass the edited value back to the correct dot element for the change to be made in the source. There are some ways, that I can think of so solve my issue:

  • 编辑viz.js库,以便在生成的svg上放置一些ID
  • 对已编辑的svg元素进行繁琐的分析,以便从逻辑上识别出正确的源元素

,但是以上操作太困难了,需要很长时间才能完成,所以我问我是否错过了viz.js中的某些功能,可以让我完成自己的任务,或者也许还有其他我可以使用的库,可以满足我的要求?

,but the above are too difficult jobs and would take a long time to accomplish, so, I am asking, if there is some feature in viz.js, that I've missed, that would allow me to accomplish my task, or maybe, there is some other library that I could use, that can do, what I require?

推荐答案

在比您更简单的情况下,可以使用SVG <title>元素来指代节点和边.对于节点,标题为" node_id "(不是与节点属性 id 混淆)它是" node_id a -> b.从您的SVG代码中:

In cases simpler that yours, the SVG <title> element can be used to refer back to nodes and edges. For nodes, the title is the "node_id" (not to be confused with the node attribute id) and for edges it is "node_id edgeop node_id", e.g. a -> b. From your SVG code:

<g id="node1" class="node"> <title>person</title>

<g id="node1" class="node"> <title>person</title>

person可用于引用DOT源代码行:person [....

person can be used to refer back to the DOT source line: person [....

通常情况下,Graphviz的 id 属性为您的朋友:

In the general case, the Graphviz id attribute is your friend:

id

id

允许图形作者为要包含在输出中的图形对象提供ID.将应用常规的"\ N","\ E","\ G"替换.如果提供的话,提供者有责任使其值对于其预期的下游用途足够独特.请特别注意,"\ E"不会为多边缘提供唯一的ID.如果未提供id属性,则使用唯一的内部ID.但是,该值是图形编写器无法预测的.外部提供的ID不在内部使用.

Allows the graph author to provide an id for graph objects which is to be included in the output. Normal "\N", "\E", "\G" substitutions are applied. If provided, it is the responsibility of the provider to keep its values sufficiently unique for its intended downstream use. Note, in particular, that "\E" does not provide a unique id for multi-edges. If no id attribute is provided, then a unique internal id is used. However, this value is unpredictable by the graph writer. An externally provided id is not used internally.

如果图形提供了id属性,它将用作内部生成的属性的前缀.通过使这些图像与众不同,用户可以在同一文档中包含多个图像映射.

If the graph provides an id attribute, this will be used as a prefix for internally generated attributes. By making these distinct, the user can include multiple image maps in the same document.

在您的情况下,您不仅要引用节点,而且要引用基于记录的节点.

In your case, you want to reference not only nodes, but also individual fields of record-based nodes.

尽管记录标签的字段是用 fieldId 定义的,但似乎并不打算传播到生成的SVG:

Although the fields of record labels are defined with fieldId's, they do not seem to be intended to propagate to the generated SVG:

fieldId中的第一个字符串为该字段分配端口名称,并且可以与节点名称组合以指示将边缘连接到节点的位置. (请参阅portPos.)

The first string in fieldId assigns a portname to the field and can be combined with the node name to indicate where to attach an edge to the node. (See portPos.)

类似HTML的标签:

基于记录的形状在很大程度上已被类似HTML的标签所取代并得到了广泛的推广.也就是说,可以考虑使用shape = none,margin = 0和类似HTML的标签,而不是使用shape = record.

The record-based shape has largely been superseded and greatly generalized by HTML-like labels. That is, instead of using shape=record, one might consider using shape=none, margin=0 and an HTML-like label.

通过它们,您可以创建一个包含行和列的表的节点,您可以在其中使用 ID 属性:

With them you can create a node that is a table with rows and columns where you can use the ID attribute:

ID =值"

ID="value"

允许用户为表或单元格指定唯一的ID.有关更多信息,请参见id属性.请注意,类似于"id"属性,"value"被视为escString.

allows the user to specify a unique ID for a table or cell. See the id attribute for more information. Note that the "value" is treated as an escString similarly to the id attribute.

不幸的是,Graphviz中存在一个错误(更好地描述了解决方法.

Unfortunately there is a bug in Graphviz (better described here) that causes this attribute to be ignored in the SVG output. Fortunately, there's a workaround.

以下是基于 d3-graphviz (使用 viz.js .但是,您不需要使用d3-graphviz.您可以直接使用viz.js实现相同的目的.

Below is a solution which is based on d3-graphviz, which uses viz.js internally. You don't need to use d3-graphviz, though. You can achieve the same thing with viz.js directly.

如果您的ID保持足够的唯一性,并且可以控制DOT源的格式,则可以像本解决方案中那样使用简单的模式替换.

If you keep your id's sufficiently unique and you have control of the formatting of the DOT source, you can use simple pattern replacement as in the presented solution.

如果您无法控制DOT源的格式,最好将信息反馈给生成它的应用程序.为了避免编写成熟的DOT解析器,一种替代方法是使用点"作为输出格式并使用viz.js标准化DOT源,并尝试对其进行解析.

If you don't have control over the formatting of the DOT source, you are probably better off feeding back the information to the application that generates it. An alternative, to avoid writing a full-fledged DOT parser, is to normalize the DOT source with viz.js by using 'dot' as output format and try to parse that.

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/viz.js@1.8.0/viz.js"></script>
<script src="https://unpkg.com/d3-graphviz@0.1.2/build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>

var dotSrc = `
digraph DB {
graph [label="Click on a cell to convert to upper/lower case" labelloc="t", fontsize="20.0" tooltip=" "]
rankdir=LR
node [shape=plain]

person [

    // NOTE: The use of HREF is a workaround for '[Dot] ID="value" fails to produce id string in svg:svg output for html nodes'
    //       See https://gitlab.com/graphviz/graphviz/issues/207
    //       For the workaorund and more info, see http://ftp.graphviz.org/mantisbt/view.php?id=2197

    label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
              <TR><TD>Person table</TD></TR>
              <TR><TD ID="p.id" PORT="id" HREF=" ">Person ID</TD></TR>
              <TR><TD ID="p.fn" PORT="fn" HREF=" ">First Name</TD></TR>
              <TR><TD ID="p.mn" PORT="mn" HREF=" ">Middle Name</TD></TR>
              <TR><TD ID="p.ln" PORT="ln" HREF=" ">Last Name</TD></TR>
            </TABLE> >
]

address [
    label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
        <TR><TD>Addresses table</TD></TR>
        <TR><TD ID="a.id" PORT="id" HREF=" ">Address ID</TD></TR>
        <TR><TD ID="a.pid" PORT="pid" HREF=" ">Person ID</TD></TR>
        <TR><TD ID="a.index" PORT="index" HREF=" ">ZIP Code</TD></TR>
        <TR><TD ID="a.street" PORT="street" HREF=" ">Street Name</TD></TR>
        <TR><TD ID="a.house" PORT="house" HREF=" ">House Number</TD></TR>
        <TR><TD ID="a.town" PORT="town" HREF=" ">City/Town/Village Name</TD></TR>
        <TR><TD ID="a.state" PORT="state" HREF=" ">State Name</TD></TR>
        <TR><TD ID="a.district" PORT="district" HREF=" ">County/District Name</TD></TR>
        <TR><TD ID="a.country" PORT="country" HREF=" ">Country Name</TD></TR>
      </TABLE> >
]

phone [
    label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
        <TR><TD>Phone Number table</TD></TR>
        <TR><TD ID="n.pid" PORT="pid" HREF=" ">Person ID</TD></TR>
        <TR><TD ID="n.cc" PORT="cc" HREF=" ">Country Code</TD></TR>
        <TR><TD ID="n.ac" PORT="ac" HREF=" ">Area Code</TD></TR>
        <TR><TD ID="n.n" PORT="n" HREF=" ">Phone Number</TD></TR>
      </TABLE> >
]
{phone:pid address:pid} -> person:id

}
`;

var graphviz = d3.select("#graph").graphviz();
var dotSrcLines;

function render(dotSrc) {
//    console.log('DOT source =', dotSrc);
    dotSrcLines = dotSrc.split('\n');

    transition1 = d3.transition()
        .delay(100)
        .duration(1000);

    graphviz
        .transition(transition1)
        .renderDot(dotSrc);

    transition1
      .transition()
        .duration(0)
        .on("end", function () {
            nodes = d3.selectAll('.node,.edge');
            nodes
              .selectAll("g")
                .on("click", fieldClickHandler)
              .selectAll("a")
                // Remove the workaround attributes to avoid consuming the click events
                .attr("href", null)
                .attr("title", null);
        });
}

function fieldClickHandler () {
    var node = d3.select(this);
    var text = node.selectAll('text').text();
    var id = node.attr('id');
    var class1 = node.attr('class');
    dotElement = id.replace(/^a_/, '');
    console.log('Element id="%s" class="%s" text="%s" dotElement="%s"', id, class1, text, dotElement);
    console.log('Finding and deleting references to %s "%s" from the DOT source', class1, dotElement);
    for (i = 0; i < dotSrcLines.length; i++) {
        if (dotSrcLines[i].indexOf(dotElement) >= 0) {
            ucText = text.toUpperCase();
            lcText = text.toLowerCase();
            if (text != ucText) {
                newText = ucText;
            } else {
                newText = lcText;
            }
            console.log('Converting "%s" to "%s" on line %d: %s', text, newText, i, dotSrcLines[i]);
            dotSrcLines[i] = dotSrcLines[i].replace(text, newText);
        }
    }
    dotSrc = dotSrcLines.join('\n');
    render(dotSrc);
}

render(dotSrc);

</script>

这篇关于如何将graphviz生成的SVG元素与DOT源代码中的元素相关联的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆