如何将graphviz生成的SVG元素与DOT源代码

时间:2017-11-06 22:12:26

标签: javascript d3.js svg graphviz

所以,我使用viz.js从点文件生成了一个svg图 现在,使用javascript很容易选择它的元素,但我没有看到与原始点文件的任何关联。我在viz.js库中看不到任何对象结构,它将生成的svg图表元素与点源元素联系起来,所以,如果我用鼠标选择svg元素,我会知道这个svg元素是对应的到点元素,它是由它生成的。有没有办法得到这样的反馈?我需要这个,所以,如果我在svg中编辑一个元素(在浏览器中可视化),我就能将编辑映射回点文件并反映源上的变化。

说明

因此,这是一个可能的源GraphViz点代码的例子:

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结果(但是,对我来说,我不在乎,如果其他库可以完成相同的操作,我将使用其他库):

<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或任何东西,这表明从哪个dot元素生成了特定的svg元素。无法识别编辑哪个元素,以便将编辑后的值传递回正确的点元素,以便在源中进行更改。我可以想到一些方法来解决我的问题:

  • 编辑viz.js库,因此它会在生成的svg
  • 上放置一些ID
  • 繁琐地分析生成的svg,以便在逻辑上识别正确的源元素,编辑svg元素

,但上面的工作太难了,需要很长时间才能完成,所以,我问,如果viz.js中有一些功能,我错过了,这样我就能完成任务或者,也许,我可以使用其他一些库,可以做什么,我需要什么?

2 个答案:

答案 0 :(得分:3)

在比您更简单的情况下,SVG <title>元素可用于引用节点和边缘。对于节点,标题是&#34; node_id&#34; (不要与节点属性id混淆),对于边缘,它是&#34; node_id edgeop node_id&#34;,例如a -> b。从您的SVG代码:

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

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

在一般情况下,Graphviz id属性是您的朋友:

  

id

     

允许图表作者为要包含在输出中的图形对象提供id。正常&#34; \ N&#34;,&#34; \ E&#34;,&#34; \ G&#34;替换适用。如果提供,提供商有责任保持其价值足以满足其预期的下游用途。特别注意,&#34; \ E&#34;不为多边提供唯一ID。如果未提供id属性,则使用唯一的内部标识。但是,图形编写器无法预测此值。外部提供的ID不在内部使用。

     

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

在您的情况下,您不仅要引用节点,还要引用record-based nodes的各个字段。

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

  

fieldId中的第一个字符串为字段分配端口名称,并且可以与节点名称组合以指示将边缘附加到节点的位置。 (见portPos。)

救援来HTML-like labels

  

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

使用它们,您可以创建一个节点,该节点是包含行和列的表,您可以 使用 ID 属性:

  

ID =&#34;值&#34;

     

允许用户为表或单元格指定唯一的ID。有关更多信息,请参阅id属性。注意&#34;值&#34;被视为与id属性类似的escString。

不幸的是,Graphviz中有a bug(更好地描述了here)导致在SVG输出中忽略此属性。幸运的是,有workaround

以下是基于d3-graphviz的解决方案,该解决方案在内部使用viz.js。但是,您不需要使用d3-graphviz。您可以直接使用viz.js实现相同的功能。

如果你保持你的id足够独特并且你可以控制DOT源的格式,你可以使用简单的模式替换,如所提出的解决方案。

如果您无法控制DOT源的格式,您可能最好将信息反馈给生成它的应用程序。另一种方法是避免编写成熟的DOT解析器,使用&#39; dot&#39;来使用viz.js对DOT源进行标准化。作为输出格式并尝试解析它。

&#13;
&#13;
<!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>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

有一个undocumented feature,graphviz接受class属性并将它们输出为svg class="foo"。示例:

$ cat test.dot
digraph G {
  graph [class="cats"];

  subgraph cluster_big {
    graph [class="big_cats"];

    "Lion" [class="yellow social"];
    "Snow Leopard" [class="white solitary"];
  };
}

$ dot -Tsvg ~/test.dot | grep "<g"
<g id="graph0" class="graph cats" ...>
<g id="clust1" class="cluster big_cats">
<g id="node1" class="node yellow social">
<g id="node2" class="node white solitary">