score:8

Accepted answer

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>

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

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

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.

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.

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

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

To your rescue comes HTML-like labels:

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.

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

ID="value"

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.

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.

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.

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.

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>

score:3

Graphviz accepts class attributes and outputs them as SVG class="foo". Example:

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

Related Query