Home > Mobile >  d3 force directed graph & svelte reactive statement
d3 force directed graph & svelte reactive statement

Time:12-04

I tried to make a simple d3 force-directed graph with the svelte reactive statement, but apparently, it's not working together.

1st example, this is the simplest way I make the graph (working REPL):

<script>
    import * as d3 from 'd3'
    import { onMount } from 'svelte'
    import data from './data.json'

    let svg,
            width = 500,
            height = 400,
            links, nodes
    
    let simulation = d3.forceSimulation(data)

</script>

<svg 
         bind:this={svg}
         width={width}
         height={height}
         viewBox="{-width/2} {-height/2} {width} {height}"
         >
    {#each data as node}
        <g  >
            <circle bind:this={nodes}
                fill="cornflowerblue"
                r="5"
                cx={node.x}
                cy={node.y}
                ></circle>
        </g>
    {/each}
</svg>

2nd example, I want to add some force with reactive statement to it, let's say forceX, and it doesn't work, until I add simulation.tick(100) or any number > 0 inside the .tick() (working REPL):

<script>
    // the before part is still the same
    
    let simulation = d3.forceSimulation(data)

    $: simulation.force("x", d3.forceX(0)) // not working, the circle stay at the initial position
    $: simulation.tick(100) // the force worked, but not animating from the initial position

</script>

<!-- the rest of svg is still the same -->

3rd example, I tried the .on("tick") with function. The function is fired (I only give a console.log to test if the tick working.) but I have no idea how to pass the modified data to the {#each} block. Looking at the console.log(data) inside the update function, the x and y data are changing every tick, but not updating the actual circle position. (working REPL):

<script>
    // the before part is still the same
    
    let simulation = d3.forceSimulation(data)

    function update() {
       console.log(data)
       return data // I don't know if it's the correct way, but still not working tho
    }

    $: simulation.force("x", d3.forceX(0))
    $: simulation.on("tick", update)

</script>

<!-- the rest of svg is still the same -->

data.json

[
    {value: 10},
    {value: 12},
    {value: 15},
    {value: 8},
    {value: 7},
    {value: 12},
    {value: 25},
    {value: 20},
    {value: 16},
    {value: 13},
    {value: 5},
    {value: 7},
    {value: 8},
    {value: 10},
    {value: 12},
    {value: 14},
    {value: 24},
    {value: 23},
    {value: 22},
    {value: 11},
    
]

CodePudding user response:

The simplest way to address your problem in step #3 is to declare a displayData array, to copy over data into displayData inside your update function, and to iterate your #each block over displayData.

data being an import variable, you cannot directly reassign to it, and because you cannot reassign to it, it is not reactive and data updates do not trigger a re-render.

By assigning data to displayData in your update function (which will run on every tick), you will trigger a re-render of your #each block (provided you now iterate over displayData).

Working REPL

  • Related