d3 / d3-selection

Transform the DOM by selecting elements and joining to data.

Home Page:https://d3js.org/d3-selection

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Uncaught Error at Transition.transition_merge when using transition inside join's enter

eleirin opened this issue · comments

commented

While this issue refer to a transition issue, this seems to be directly related to the way join calls its callback.

When using transition inside join, I get an uncaught error.

Minimal code

<!DOCTYPE html>
<html>
    <head>
        <title>D3 Playground</title>
        <script src="https://d3js.org/d3.v5.js"></script>
    </head>

    <body>
        <svg width="500px" height="500px" />
        <script>
            const svg = d3.select("svg")

            d3.interval(t => update(d3.range(t/1000)))

            function update(data) {
                const circles = svg
                  .selectAll("circle")
                  .data(data)
                  .join(
                  enter => enter
                    .append("circle")
                      .attr("fill", "green")
                    .transition().duration(1000)
                      .attr("fill", "red")
                  )
                    .attr("r", 10)
                    .attr("cy", 70)
                    .attr("cx", d => d*20)
            }
        </script>
    </body>
</html>
Uncaught Error
    at Transition.transition_merge [as merge] (d3.v5.js:3654)
    at Selection.selection_join [as join] (d3.v5.js:1080)
    at update (d3.html:24)
    at d3.interval.t (d3.html:16)
    at timerFlush (d3.v5.js:3122)
    at wake (d3.v5.js:3132)

If I remove the call to transition, then it works without any issue.

For reference, the equivalent code that does not use join also works:

<!DOCTYPE html>
<html>
    <head>
        <title>D3 Playground</title>
        <script src="https://d3js.org/d3.v5.js"></script>
    </head>

    <body>
        <svg width="500px" height="500px" />
        <script>
            const svg = d3.select("svg")

            d3.interval(t => update(d3.range(t/1000)))

            function update(data) {
                const circles = svg
                  .selectAll("circle")
                  .data(data)
                  .enter()
                  .append("circle")
                    .attr("fill", "green")
                  .transition().duration(1000)
                    .attr("fill", "red")

               circles
                    .attr("r", 10)
                    .attr("cy", 70)
                    .attr("cx", d => d*20)
            }
        </script>
    </body>
</html>

This create a circle after another every second, and each circle starts green and slowly turn to red individually, which I thought the first code with join would also do.
Is this a problem with the way I use the bundle d3js.v5? Using the suggested order in d3-transition

<script src="https://d3js.org/d3-array.v1.js"></script>
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-ease.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>

Gives the same error.

I appologize if this should be in d3-transition instead, or if this is not the way transition are supposed to be used in the first place.
Thanks for your input :)

The problem is that you’re returning a transition from the enter callback to selection.join. You need to return a selection instead.

                const circles = svg
                  .selectAll("circle")
                  .data(data)
                  .join(
                  enter => enter
                    .append("circle")
                      .attr("fill", "green")
                    .call(enter => enter.transition().duration(1000)
                      .attr("fill", "red"))
                  )
                    .attr("r", 10)
                    .attr("cy", 70)
                    .attr("cx", d => d*20)
commented

Okay, I understand better now how the return type matters, since the result of the selection is then called merge upon
Maybe this should be specified in the documentation? Especially since it seems easy to put a more complex function and forget to return at all.
I can pr a change in the readme if you would like
Although, it was probably my fault for not using typescript this time.

Thank you a lot

This exact problem is described in the notebook that introduced selection.join:

The return value of the enter and update function is important—it specifies the selections to merge and return by selection.join. To avoid breaking the method chain, use selection.call to create transitions. Or, return an undefined enter or update selection to prevent merging.

https://observablehq.com/@d3/selection-join

But yeah, we should update the README and copy some of that explanation back here.