Using d3.forceSimulation does present some additional concerns: the simulation starts a timer that runs for 300 ticks by default (5 seconds), and the simulation mutates the passed-in nodes and links (to assign the x and y positions, and also optionally reassigning the link.source and link.target if specified by id rather than object reference).
This is why the example you linked makes a shallow copy of the nodes and links using Object.copy, and uses the invalidation promise to stop the simulation.
Hence, that example is designed to be “pure” in the sense that if the data changes, it creates a new simulation and discards the old simulation (and all its effects). That is the cleanest approach in Observable. It’s the easiest to reason about because there’s no hidden state that’s affecting the behavior of cells.
If you want to do animated transitions or incremental updates without discarding the old state, you’ll need to opt-in to some additional complexity. There are a variety of ways of doing that, but the one that I recommend is exposing an update function as in these examples:
The skeleton of that approach has two parts. Your chart cell (or whatever cell you want to be responsible for the state…) exposes an update method:
chart = {
const svg = d3.create("svg");
return Object.assign(svg.node(), {
update(data) {
// Animate here! 💃
}
});
}
And then a cell that calls chart.update whenever the data (or whatever other state) changes:
chart.update(data)
If you have more than one thing you want to change, you can have multiple update methods with different names that take different arguments. (The “update” name is just a convention and isn’t required.)
The principle here is that the code that mutates state should live in the same cell that defines the state.
(Also: I would avoid using Observable’s this
! I now consider this
an antipattern because it’s too dangerous. With animated transitions or incremental updates you typically only expect one thing to change, such as data here; with this, the old value is preserved regardless of what changes, making it much easier to write buggy code. For example—and sorry to pick on @bgchen, it’s not your fault, it’s mine for designing this
—if you resize the window in @bgchen’s example the SVG element does not resize as expected because it ignores changes to width.)