Svelte Reactivity and D3 Force Ticks in Beeswarm Charts
In this lesson, we will learn how ticks work in D3 force simulation layouts, and we will dive into Svelte reactivity (reactivity blocks) for correctly rendering nodes of a force layout in a responsive SVG canvas. A tick represents a “frame” in a force simulation - the state of the force layout at a specific point in a force simulation. With Svelte reactivity, nodes, represented as circles, will be positioned correctly in a beeswarm plot upon initial page load.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
Lesson Transcript
[00:00 - 00:43] In this lesson we're going to fix our broken force diagram. You'll recall that the chart went on first page load is fully broken. It appears in the top left corner and these circles seem to be centered around like the midpoint of zero zero but they're definitely not what we want to see. And then if we resize the window we do start to see the chart that we want but there's this weird resize behavior it's like laggy sometimes it doesn't resize and the culprit for this is most likely reactivity and how we render these circles as it relates to how the page loads, how it's felt works behind the scenes and how it tries to update reactive variables. And as a refresher these updates these reactive labels are what are responsible right.
[00:44 - 04:08] The dollar label is how we tell us felt to basically watch for updates and update if and when something changes on the right side of the equals sign. So the reason that the circles are at first populated in the top left corner and then update when we resize is most likely due to the reactivity in our X scale and in our simulation. You see on first page load we're rendering a series of nodes right here on line 57 that by default are going to be positioned in the default force simulation layout we could assume which is in this top left corner and to verify that if I removed all of these other forces and then click save and refreshed we would see that they appear in the top left corner and they stay there if I resize. Now what is that confirming for me right what that's confirming is that this line eight simulation being a simple force simulation of data with no other forces applied that is what creates all of these dots in the top left corner. Remember that's true on page load and on resize which means that the first time that the page is loading this is essentially what we are rendering. Svelte is not paying attention for some reason and its internals to our force X force Y or force collect and so what we then know is that there's some dependency complication there's some conflict between Svelte internals and how it wants to render reactive variables and how D three force simulations work. So in my investigation and what you'll find in other examples of Svelte and D three force diagrams is that we can't just expect this to work by making the entire thing reactive. The other problem with this is that simulation itself is going to be reinstantiated every time that a dependency on this right side of the data changes. So in our case if and when X scale changes because of the width updating this will reinstant iate the entire simulation and you may be asking what's the problem with that. Well the issue is that's why we have this weird resize behavior because Svelte is basically recreating this complicated force simulation that has all of these dependencies as trying to do all of the math and physics behind the scenes and it's re rendering that simulation on every single tick on every single second that the width is updating that X scale is updating that any of these dependencies are updating. So the TLDR here the big takeaway in summary is that we don't want to create simulation with a dollar label. We instead actually want simulation to be its own simple variable that is bare bones in nature that is basically only equivalent to this and then we want to use what's called a reactive block to update the entire simulations physics itself rather than reinstantiate the simulation we want to update the simulations dependencies and therefore its physics in its own separate block and for that we're going to take a look at what are called ticks in a force simulation. A force simulation is composed of multiple ticks and a tick for this purpose can be thought of as a frame of animation and so this great example from Ben Tannen basically illustrates each frame or each tick of a force simulation.
[04:09 - 14:29] Now at the first tick at frame zero or frame one all of these circles are randomly assorted and you'll remember that in our visualization at frame zero everything is in this top left corner okay so this is tick zero or tick one if you want to index there and what you can see is that as we progress through the animation as each tick progresses these circles get closer and closer to their desired endpoint so it starts at random points they converge on what looks like here a x axis of zero so there's a force y bringing everything to zero and so you can see this dynamically how force simulation actually plays out using physics by playing the simulation you 'll notice that a lot of it is very front heavy most of the simulation animation actually occurs in these first 20 frames and then the rest is kind of easing into play and this is all about certain properties that we can specify like decay power strength we'll get into that below in our code we 're not using ticks and that's a problem and that's why everything is rendering in this top left because it's rendering it on tick number zero so now that we're aligned on that we can answer the question how do we solve the problem and the answer is on tick the question is what do we want to do on tick let's go back to our code and let's review what we're doing now right now the simulation is this entire block and then we instantiate nodes just once here at the bottom to make it more clear we can actually bring it closer to the actual simulation and what we want to do instead of this because of the issue that we've already identified is we want to update nodes anytime that simulation changes and the way that we'll replace this to start off is by making nodes an empty array by default and then listening for any tick by writing exactly the code that we just saw simulation on tick and then resetting nodes to be equal to simulation dot nodes if and when the simulation ticks so the big difference here is that we're no longer updating nodes anytime simulation itself changes remember that that's this entire object we're only changing it whenever the simulation ticks so let 's go ahead and save that and see what happens and we can expect to find some errors particularly because simulation is undefined now the reason for this is because there is a dollar label preceding simulation as we've already identified now you might think that we could do something like this but then you'll notice a bit of an issue with resize and so the reason for that is because it's a pretty bad issue you definitely don't want to do this to be clear the reason again is because we are simply re-instantiating this complicated physics based simulation object over and over again every time we resize the page that's not what we want to do essentially we want to let the force simulation itself handle the internals here we do not want to re-instantiate the simulation we want the nodes to be re-declared based on when the simulation ticks so we don't want to use dollar labels because that recreates elements let's remove this and now we realize if this has to be you know not prefixed with the dollar label so too does the simulation we declare online eight let's start off and go ahead and just call this let simulation rather than dollar label or we could do const now by default we're going to see the same error that we encountered last lesson x scale is not a function what we actually need to do is simplify how simulation is constructed rather than this entire block from line eight until line 21 being our simulation let's make our simulation only line eight for now let's comment out the rest of our simulation and click save now by default you see everything appears in the top left corner which is precisely what we would expect but now what do we want to do what we essentially want to do is move all this complicated force logic into its own reactive block now what is a reactive block as you can see here essentially you can put more complex logic like this entire if block in a dollar label and it will update if and when anything on the right side triggers so here for example if and when count then passes 10 we can trigger this entire block and we want to do a block like reactive statement for our force simulation the question is why are we doing that basically the simulation itself is very simple but we want to update the simulation remember we don't want to re- instantiate the simulation we don't want to recreate it we want to update the simulation if and when any of these dependencies change so let's go ahead and uncomment this and rather than put it directly in the simulation construction online 8 let's open and close a reactive block which essentially looks like that and let's move this entire complicated force logic into the block and then we need to basically just write simulation and then the rest of the code and hit save automatically you'll see that our chart is in fact working so what's different about these two bits of code you'll recall that in the first version we had this dollar label in front of simulation which tells spelt to recreate simulation if and when anything on the right side of the equal sign changes in our new version we are only instantiating simulation one time at page load that's why it is a constant it's never being written over but what we are doing is applying these additional arguments like force x force y and force collide to the simulation if and when it updates and we're doing that with this reactive block which essentially says if anything in this entire block changes then go ahead and run what's inside so whenever we resize our window for example or whenever x scale is updated on first page load that is what triggers this entire simulation to run there are a few other things we're going to need here because as you can see if we resize there's a bit of an issue sometimes it's slow sometimes it doesn't update all the way it's also just not working and the reason for this is because we need a few other arguments that are commonly applied to force simulations to get these bubbles to really ease into their actual positions and there are going to be three parameters that we're going to pay attention to one is going to be alpha which we're going to basically write the amount between zero and one or the rate at which the simulation finishes so basically how much movement do we want how fast do we want it to be the second is going to be alpha decay which is going to be the rate at which the simulation alpha approaches zero so basically between simulation states how long does it take and then the final one will be restart and restart is probably the simplest of them this is what tells the application to restart so by default obviously this isn't going to work because we need numbers let's go ahead and put some numbers let's do one point zero one and restart doesn't take an argument now if I resize the page you'll notice that we don't have any breaking errors you'll notice that the visualization doesn't look perfect right it's a bit cho ppy but it's no longer causing these app breaking errors and really to get it to a production ready standpoint now all we need to do is adjust these numbers to be a bit more reasonable so the question of what our value should be for alpha alpha decay and restart really depends on personal preference and on your own testing you can go ahead and look up some documentation for each of these three there's a great notebook here which basically shows the different parameters here you can see all of these arguments alpha min alpha decay velocity decay alpha target and alpha and we could change any of these numbers to test how things change this is a good kind of visual illustration as you can see I just broke the application by putting too high of an alpha target the point being that you you probably need to play around with the numbers to see what works spoiler alert I've been working on this for a little bit so I know which numbers are going to work well here I found that an alpha of 0.3 and alpha decay of 0.0005 tend to be the numbers that we want now if I refresh this and move you'll notice a lot better of a visualization with very few issues pretty good movement pretty dynamic feels pretty good overall there are very few issues with the visualization itself again you could play around these numbers and make them whatever you want but you might notice that some work much better than others so this is going to be personal preference I'm going to go ahead and add some writing that I have put together for what these means that you can review them and then the important thing on the last line is restart this restart parameter basically tells the simulation to update if and win any of these operands change and this entire block triggers so again I recommend you play around with the numbers but as you can see here we have a smoothly resizing application which even on a really tiny screens would still look pretty good because it's using d3 physics to basically make sure that no bubbles are overlapping they're respecting one another space and they're arranged via physics the high level summary is that we moved all of the complicated force logic out of the initial construction of simulation and we moved it into its own reactive block and the reason for this is that we do not recreate simulation every time we simply update it that specific to d3 force right updating a simulation these parameters being tacked on to simulation at the end is specific to d3 force but we definitely don't want to be recreating simulation over and over and over again we only want to update it based on these forces if and when x scale and y scale change so we have one simulation that's updating its internal physics engine and all of these forces if and when it needs to which led to this much better looking responsive reactive visualization that actually appears correct on initial page load
[00:00 - 00:43] In this lesson we're going to fix our broken force diagram. You'll recall that the chart went on first page load is fully broken. It appears in the top left corner and these circles seem to be centered around like the midpoint of zero zero but they're definitely not what we want to see. And then if we resize the window we do start to see the chart that we want but there's this weird resize behavior it's like laggy sometimes it doesn't resize and the culprit for this is most likely reactivity and how we render these circles as it relates to how the page loads, how it's felt works behind the scenes and how it tries to update reactive variables. And as a refresher these updates these reactive labels are what are responsible right.
[00:44 - 04:08] The dollar label is how we tell us felt to basically watch for updates and update if and when something changes on the right side of the equals sign. So the reason that the circles are at first populated in the top left corner and then update when we resize is most likely due to the reactivity in our X scale and in our simulation. You see on first page load we're rendering a series of nodes right here on line 57 that by default are going to be positioned in the default force simulation layout we could assume which is in this top left corner and to verify that if I removed all of these other forces and then click save and refreshed we would see that they appear in the top left corner and they stay there if I resize. Now what is that confirming for me right what that's confirming is that this line eight simulation being a simple force simulation of data with no other forces applied that is what creates all of these dots in the top left corner. Remember that's true on page load and on resize which means that the first time that the page is loading this is essentially what we are rendering. Svelte is not paying attention for some reason and its internals to our force X force Y or force collect and so what we then know is that there's some dependency complication there's some conflict between Svelte internals and how it wants to render reactive variables and how D three force simulations work. So in my investigation and what you'll find in other examples of Svelte and D three force diagrams is that we can't just expect this to work by making the entire thing reactive. The other problem with this is that simulation itself is going to be reinstantiated every time that a dependency on this right side of the data changes. So in our case if and when X scale changes because of the width updating this will reinstant iate the entire simulation and you may be asking what's the problem with that. Well the issue is that's why we have this weird resize behavior because Svelte is basically recreating this complicated force simulation that has all of these dependencies as trying to do all of the math and physics behind the scenes and it's re rendering that simulation on every single tick on every single second that the width is updating that X scale is updating that any of these dependencies are updating. So the TLDR here the big takeaway in summary is that we don't want to create simulation with a dollar label. We instead actually want simulation to be its own simple variable that is bare bones in nature that is basically only equivalent to this and then we want to use what's called a reactive block to update the entire simulations physics itself rather than reinstantiate the simulation we want to update the simulations dependencies and therefore its physics in its own separate block and for that we're going to take a look at what are called ticks in a force simulation. A force simulation is composed of multiple ticks and a tick for this purpose can be thought of as a frame of animation and so this great example from Ben Tannen basically illustrates each frame or each tick of a force simulation.
[04:09 - 14:29] Now at the first tick at frame zero or frame one all of these circles are randomly assorted and you'll remember that in our visualization at frame zero everything is in this top left corner okay so this is tick zero or tick one if you want to index there and what you can see is that as we progress through the animation as each tick progresses these circles get closer and closer to their desired endpoint so it starts at random points they converge on what looks like here a x axis of zero so there's a force y bringing everything to zero and so you can see this dynamically how force simulation actually plays out using physics by playing the simulation you 'll notice that a lot of it is very front heavy most of the simulation animation actually occurs in these first 20 frames and then the rest is kind of easing into play and this is all about certain properties that we can specify like decay power strength we'll get into that below in our code we 're not using ticks and that's a problem and that's why everything is rendering in this top left because it's rendering it on tick number zero so now that we're aligned on that we can answer the question how do we solve the problem and the answer is on tick the question is what do we want to do on tick let's go back to our code and let's review what we're doing now right now the simulation is this entire block and then we instantiate nodes just once here at the bottom to make it more clear we can actually bring it closer to the actual simulation and what we want to do instead of this because of the issue that we've already identified is we want to update nodes anytime that simulation changes and the way that we'll replace this to start off is by making nodes an empty array by default and then listening for any tick by writing exactly the code that we just saw simulation on tick and then resetting nodes to be equal to simulation dot nodes if and when the simulation ticks so the big difference here is that we're no longer updating nodes anytime simulation itself changes remember that that's this entire object we're only changing it whenever the simulation ticks so let 's go ahead and save that and see what happens and we can expect to find some errors particularly because simulation is undefined now the reason for this is because there is a dollar label preceding simulation as we've already identified now you might think that we could do something like this but then you'll notice a bit of an issue with resize and so the reason for that is because it's a pretty bad issue you definitely don't want to do this to be clear the reason again is because we are simply re-instantiating this complicated physics based simulation object over and over again every time we resize the page that's not what we want to do essentially we want to let the force simulation itself handle the internals here we do not want to re-instantiate the simulation we want the nodes to be re-declared based on when the simulation ticks so we don't want to use dollar labels because that recreates elements let's remove this and now we realize if this has to be you know not prefixed with the dollar label so too does the simulation we declare online eight let's start off and go ahead and just call this let simulation rather than dollar label or we could do const now by default we're going to see the same error that we encountered last lesson x scale is not a function what we actually need to do is simplify how simulation is constructed rather than this entire block from line eight until line 21 being our simulation let's make our simulation only line eight for now let's comment out the rest of our simulation and click save now by default you see everything appears in the top left corner which is precisely what we would expect but now what do we want to do what we essentially want to do is move all this complicated force logic into its own reactive block now what is a reactive block as you can see here essentially you can put more complex logic like this entire if block in a dollar label and it will update if and when anything on the right side triggers so here for example if and when count then passes 10 we can trigger this entire block and we want to do a block like reactive statement for our force simulation the question is why are we doing that basically the simulation itself is very simple but we want to update the simulation remember we don't want to re- instantiate the simulation we don't want to recreate it we want to update the simulation if and when any of these dependencies change so let's go ahead and uncomment this and rather than put it directly in the simulation construction online 8 let's open and close a reactive block which essentially looks like that and let's move this entire complicated force logic into the block and then we need to basically just write simulation and then the rest of the code and hit save automatically you'll see that our chart is in fact working so what's different about these two bits of code you'll recall that in the first version we had this dollar label in front of simulation which tells spelt to recreate simulation if and when anything on the right side of the equal sign changes in our new version we are only instantiating simulation one time at page load that's why it is a constant it's never being written over but what we are doing is applying these additional arguments like force x force y and force collide to the simulation if and when it updates and we're doing that with this reactive block which essentially says if anything in this entire block changes then go ahead and run what's inside so whenever we resize our window for example or whenever x scale is updated on first page load that is what triggers this entire simulation to run there are a few other things we're going to need here because as you can see if we resize there's a bit of an issue sometimes it's slow sometimes it doesn't update all the way it's also just not working and the reason for this is because we need a few other arguments that are commonly applied to force simulations to get these bubbles to really ease into their actual positions and there are going to be three parameters that we're going to pay attention to one is going to be alpha which we're going to basically write the amount between zero and one or the rate at which the simulation finishes so basically how much movement do we want how fast do we want it to be the second is going to be alpha decay which is going to be the rate at which the simulation alpha approaches zero so basically between simulation states how long does it take and then the final one will be restart and restart is probably the simplest of them this is what tells the application to restart so by default obviously this isn't going to work because we need numbers let's go ahead and put some numbers let's do one point zero one and restart doesn't take an argument now if I resize the page you'll notice that we don't have any breaking errors you'll notice that the visualization doesn't look perfect right it's a bit cho ppy but it's no longer causing these app breaking errors and really to get it to a production ready standpoint now all we need to do is adjust these numbers to be a bit more reasonable so the question of what our value should be for alpha alpha decay and restart really depends on personal preference and on your own testing you can go ahead and look up some documentation for each of these three there's a great notebook here which basically shows the different parameters here you can see all of these arguments alpha min alpha decay velocity decay alpha target and alpha and we could change any of these numbers to test how things change this is a good kind of visual illustration as you can see I just broke the application by putting too high of an alpha target the point being that you you probably need to play around with the numbers to see what works spoiler alert I've been working on this for a little bit so I know which numbers are going to work well here I found that an alpha of 0.3 and alpha decay of 0.0005 tend to be the numbers that we want now if I refresh this and move you'll notice a lot better of a visualization with very few issues pretty good movement pretty dynamic feels pretty good overall there are very few issues with the visualization itself again you could play around these numbers and make them whatever you want but you might notice that some work much better than others so this is going to be personal preference I'm going to go ahead and add some writing that I have put together for what these means that you can review them and then the important thing on the last line is restart this restart parameter basically tells the simulation to update if and win any of these operands change and this entire block triggers so again I recommend you play around with the numbers but as you can see here we have a smoothly resizing application which even on a really tiny screens would still look pretty good because it's using d3 physics to basically make sure that no bubbles are overlapping they're respecting one another space and they're arranged via physics the high level summary is that we moved all of the complicated force logic out of the initial construction of simulation and we moved it into its own reactive block and the reason for this is that we do not recreate simulation every time we simply update it that specific to d3 force right updating a simulation these parameters being tacked on to simulation at the end is specific to d3 force but we definitely don't want to be recreating simulation over and over and over again we only want to update it based on these forces if and when x scale and y scale change so we have one simulation that's updating its internal physics engine and all of these forces if and when it needs to which led to this much better looking responsive reactive visualization that actually appears correct on initial page load