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
// 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");
});
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>