Search code examples
javascriptd3.jsgraphviz

Is there a method to highlight the order of edges according to ID in D3?


I am having a GV file (Final_Graph.gv) like this:

digraph Final_Graph {
    graph [center=true rankdir=LR ratio=compress size="15,10"]
    a
    b
    c
    d
    a -> b [label = 1 id=1]
    a -> c [label = 2 id=2]
    a -> d [label = 3 id=3]
    b -> d [label = 4 id=4]
    c -> d [label = 5 id=5]

    subgraph cluster_1{
        color=lightgrey style=filled
        label="A"
        a
        b
    }
    
    subgraph cluster_2{
        color=lightgrey style=filled
        label="B"
        a
        b
    }
    
    subgraph cluster_3{
        color=lightgrey style=filled
        label="C"
        c
        d
    }
}


function renderString(str){
    graphviz.tweenShapes(false)
    .renderDot(str);
    
}

function render() {
fetch('Final_Graph.gv').then(response  => response.text()).then(textAsString => 
     renderString(textAsString));
}

rendered GV

I have written my code in D3.js so that each of the edges would be highlighted (change color to red) according to its ID, so a->b would be highlighted first with id=1, followed up by a->c (id=2), and so on.

Here is my current code in D3 (Please note that my code is in D3 v5, so I am not sure that this code could be compatible with the newer versions):

let graphviz = d3.select(".graph").graphviz()
.transition(function () {
    return d3.transition("main")
        .ease(d3.easeLinear)
        .delay(500)
        .duration(1500);}
)
.logEvents(true)
.on("initEnd", render)
.on("end", function() {
    
    d3.selectAll("g.edge").sort(function(a,b){ // Set up edges.
        return a["id"] - b["id"];
    });
    let link = d3.selectAll("path");
    
    link.each(function(d, i) {
        setTimeout(function() {
            
            d3.select(link.nodes()[i]).transition().style("stroke", "red");
        }, i * 1000);
    })
});

However when I load the website, the edges are not highlighted in ascending order as my idea; instead the highlighting order is 5->1->2->3->4. I have checked in my console and adding ID for nodes, as well as rearranging nodes according to ID; however that also did not solve the problem.

So is there any method to make edges in this graph highlighted with ID from 1 to 5 as I mentioned?


Solution

  • It seems to me that

    div
      .selectAll(".edge")
      .nodes()
      .forEach(...)
    

    should iterate through the edges in the order in which they were laid down so there should be no need to sort. Taking that into account, here's some code that animates the drawing of the edges in response to a button push:

    let source = `digraph Final_Graph {
      graph [center=true rankdir=LR ratio=compress size="15,10"]
      a
      b
      c
      d
      a -> b [label = 1 id=1]
      a -> c [label = 2 id=2]
      a -> d [label = 3 id=3]
      b -> d [label = 4 id=4]
      c -> d [label = 5 id=5]
      subgraph cluster_1{
    color=lightgrey style=filled
    label="A"
    a
    b
      }
      subgraph cluster_2{
    color=lightgrey style=filled
    label="B"
    a
    b
      }
      subgraph cluster_3{
    color=lightgrey style=filled
    label="C"
    c
    d
      }
    }`;
    
    let div = d3.select("#graph")
    div.graphviz().renderDot(source, function() {
      div.selectAll("title").remove();
      div.selectAll("text").style("pointer-events", "none");
    })
    d3.select('#button').on('click', draw_edges)
    
    
    function draw_edges() {
      div
    .selectAll(".edge")
    .nodes()
    .forEach(function (e, i) {
      let ee = d3.select(e);
      let path = ee.select("path");
      let length = path.node().getTotalLength();
      path.attr("stroke-dasharray", [0, length]);
      let head = ee.select("polygon");
      head.attr("opacity", 0);
      setTimeout(() => {
        path
          .attr("stroke-dasharray", [0, length])
          .transition()
          .duration(400)
          .attr("stroke-dasharray", [length, length]);
        setTimeout(() => head.attr("opacity", 1), 400);
      }, 400 * (i + 1));
    });
    }
    <script src="//d3js.org/d3.v5.min.js"></script>
    <script src="https://unpkg.com/@hpcc-js/[email protected]/dist/index.min.js"></script>
    <script src="https://unpkg.com/[email protected]/build/d3-graphviz.js"></script>
    
    <button id="button">Draw edges</button>
    <div id="graph" style="text-align: center;"></div>

    I think the motion captures the users attention much better than the coloring.

    I've also implemented this using Observable and you can see code for animating the process using color there as well.