Search code examples
javascriptd3.jsgraphviz

Customizing Graph Attributes / Object Consistency


I have a question about selecting nodes to change certain attributes based on its name/id/title. Even though I spent a good amount of time trying to understand the documentation on the subject (Link) I am still at a loss on how to change the color of a node based on data that is accessible pre-rendering. This is probably only because I am very new to JavaScript and D3 so I'm guessing that the solution will be simple.

In the end I would want to change color of nodes in sequence simulating a token passing through, i.e.

first click: node a -> red

second click: node b -> red, node a -> white again

third click: all nodes white again

JSFiddle

// render initial graph
d3.select("#graph").graphviz()
    .engine("neato")
    .dot(`digraph {a -> b}`)
    .render();

// on click, change color of 
d3.select("#graph").on("click", function () {
        console.log("click");
    /* here I would want to select manipulate a node based on its title (a or b)
       and not the ID of the group, since "id=node1" is not known before 
       rendering */
    d3.selectAll("#node1").select("ellipse").transition().attr("fill", "red");
});

Solution

  • The idea behind d3 is that you assign data to the nodes, which you can access through a function, that then returns the new value of an attribute or style. In every case, the first argument d is the datum object, the second is the index in the selection, and (if I recall correctly) the third is the entire selection.

    In this case, you can see the contents of d by just printing them to the console, since graphviz adds a layer of abstraction around d3. From there, I found that d.parent.key was actually the value I needed, so I could use that to access highlightSequence. I stored that in a list so I could easily cycle through them, if I wanted to.

    // render initial graph
    d3.select("#graph").graphviz()
      .engine("neato")
      .dot(`digraph {a -> b}`)
      .render();
    
    const highlightSequence = [
      { a: 'red', b: 'white' },
      { a: 'white', b: 'red' },
      { a: 'white', b: 'white' }
    ];
    let counter = -1;
    
    // on click, change color of 
    d3.select("#graph").on("click", function() {
      console.log("click");
      counter++;
      d3.selectAll("ellipse")
        .attr("fill", function(d) {
          console.log(d);
          return highlightSequence[counter % highlightSequence.length][d.parent.key];
        });
    });
    <html>
    
    <body>
      <script src="//d3js.org/d3.v4.min.js"></script>
      <script src="https://unpkg.com/@hpcc-js/wasm@0.3.11/dist/index.min.js"></script>
      <script src="https://unpkg.com/d3-graphviz@3.0.5/build/d3-graphviz.js"></script>
      <div id="graph" style="text-align: center;"></div>
    </body>
    
    </html>


    Edit to make it more extensible, only define colors other than white and default to that:

    // render initial graph
    d3.select("#graph").graphviz()
      .engine("neato")
      .dot(`digraph {a -> b}`)
      .render();
    
    const highlightSequence = [
      { a: 'red' },
      { b: 'red' },
      { }
    ];
    let counter = -1;
    
    // on click, change color of 
    d3.select("#graph").on("click", function() {
      console.log("click");
      counter++;
      d3.selectAll("ellipse")
        .attr("fill", function(d) {
          console.log(d);
          return highlightSequence[counter % highlightSequence.length][d.parent.key] || 'white';
        });
    });
    <html>
    
    <body>
      <script src="//d3js.org/d3.v4.min.js"></script>
      <script src="https://unpkg.com/@hpcc-js/wasm@0.3.11/dist/index.min.js"></script>
      <script src="https://unpkg.com/d3-graphviz@3.0.5/build/d3-graphviz.js"></script>
      <div id="graph" style="text-align: center;"></div>
    </body>
    
    </html>