magjac / d3-graphviz

Graphviz DOT rendering and animated transitions using D3
BSD 3-Clause "New" or "Revised" License
1.67k stars 103 forks source link

Cluster's box shadows nodes inside it after animated transition #308

Closed yihozhang closed 2 months ago

yihozhang commented 3 months ago

Hello! I encountered a weird bug when trying to use d3-graphviz.

I want to render the following graph with d3-graphviz, which works fine.

image

dot code

However, when I tried to do transition from a simpler viz to it. d3-graphviz renders something like this (the result varies slightly from run to run).

image

Note some of the nodes are shadowed by the surrounding yellow cluster boxes.

Here's a minimal self-contained example that reproduces this behavior code

This could be something as simple as I missed a silly graphviz parameter, or a potential bug on the wasm side. I would really appreciate it if you have any leads on where things could go wrong. Thank you!

magjac commented 3 months ago

Thanks for the excellent and detailed report. I can confirm that I see the same problem and that it still exists in the latest version, v5.3.0.

It seems to be a problem with the ordering of nodes and clusters. The nodes must be rendered after the cluster they are located in in order to be visible (See SVG Rendering Order), but this doesn't happen when transitioning from a simpler graph.

I changed the ordering of the graphs, i.e., I started by rendering the more complex graph and then transitioned into the simpler. In this case the problem doesn't seem to occur, regardless how many times the graph is transitioned back and forth. This indicated to me that the simpler graph, when rendered first, establishes an ordering of clusters and nodes that are maintained when transitioning into the more complex one and that this ordering is incorrect for that case. Indeed, the order of cluster cluster_i64-0 and cluster_i64-703266523 in the simpler graph is different in the two cases.

I then changed the cluster fill color from 2 to "#ffffb380" (the same color, but semi-transparent) and then it's clear that the all nodes are placed in the correct positions, just that some of them are covered by the cluster they belong to:

issue-308-modified

Right now, I'm not sure if there's an easy way to fix this. I have to give it some thought.

In the meantime, you may want to try to work around the problem by using keyMode('id'). In this case, the problem doesn't occur, but the animated transition of the nodes is not helpful. You can fix this by giving all nodes an id yourself. See Maintaining Object Constancy for details.

yihozhang commented 3 months ago

keyMode('id') does work for me. Thanks for the quick reply! I'm curious how this fixes the problem. Can I just give each node an id that is the same as its title (which is the default keyMode)?

magjac commented 3 months ago

Now that I think about it, I don't understand why it fixes the problem. Perhaps it's just by chance? Yes, you can give each node an id that is the same as its title. Also, I guessed that if I did the same with the clusters, I would get back the original problem. But I tried that and I didn't, so now I'm really confused.

magjac commented 2 months ago

This problem is now fixed. I will do a release shortly.

For details about the fix, see https://github.com/magjac/d3-graphviz/pull/309/commits/a1efc5bbed1a2d372781ba08f09c7437d16d0d42.

Here's my even smaller self-contained example that reproduced the problem locally:

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="../node_modules/d3/dist/d3.js"></script>
<script src="../node_modules/@hpcc-js/wasm/dist/graphviz.umd.js" type="javascript/worker"></script>
<script src="../build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>

var dotIndex = 0;
var graphviz = d3.select("#graph").graphviz()
    .transition(function () {
        return d3.transition("main")
            .ease(d3.easeLinear);
    })
    .on("initEnd", render)
    .logEvents(true);

function render() {
    var dot = dots[dotIndex];
    graphviz
        .renderDot(dot)
        .on("end", function () {
            if (dotIndex < dots.length - 1) {
              dotIndex = (dotIndex + 1) % dots.length;
              render();
            }
        });
}

var dots = [
  `
digraph  {
  graph[style=filled]
  subgraph "cluster_a" {
    fillcolor=purple
    a
  }
}
`,
`
digraph  {
  graph[style=filled]
  subgraph "cluster_b" {
    fillcolor=orange
    b
  }
  subgraph "cluster_c" {
    fillcolor=limegreen
    c
  }
  subgraph "cluster_d" {
    fillcolor=cyan
    d
  }
  subgraph "cluster_a" {
    fillcolor=purple
    a
  }
}
  `,
];

</script>
yihozhang commented 2 months ago

This makes sense. Thank you! @magjac