Making Our Circles Reactive and Responsive

Using reactive blocks to fix our force diagram

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To 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.

Table of Contents

This lesson preview is part of the Better Data Visualizations with Svelte course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Better Data Visualizations with Svelte, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Better Data Visualizations with Svelte
  • [00:00 - 00:12] Hey everyone, in this lesson we're going to fix our broken force diagram. To briefly kind of go over what's broken, you'll recall that the chart when on first page load is fully broken.

    [00:13 - 00:30] It appears in the top left corner and these circles seem to be centered around like the end point, 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.

    [00:31 - 00:49] Overall we know that something is broken, right? 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.

    [00:50 - 01:06] And as a refresher, these updates, these reactive labels are what are responsible, right? 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.

    [01:07 - 01:37] 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, online 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.

    [01:38 - 01:50] And to verify that, if I removed all of these other forces and then click save and refresh, 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?

    [01:51 - 02:02] 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.

    [02:03 - 02:19] 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. Felt is not paying attention for some reason and its internals to our force X, force Y, or force collide.

    [02:20 - 02:44] And so what we then know is that there's some dependency complication, there's some conflict between spelt 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 spelt and D three force diagrams is that we can't just expect this to work by making the entire thing reactive.

    [02:45 - 03:02] The other problem with this is that simulation itself is going to be re-instant iated 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 re-instantiate the entire simulation.

    [03:03 - 03:18] 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 spelt is basically recreating this complicated force simulation that has all of these dependencies that's trying to do all of the math and physics behind the scenes.

    [03:19 - 03:38] 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.

    [03:39 - 03:57] 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 re-instantiate the simulation.

    [03:58 - 04:10] We want to update the simulations, dependencies, and therefore its physics in its own separate block. So that sounds a lot more complicated whenever you say it, but it'll be easier once we actually write this.

    [04:11 - 04:21] But first, let's go over kind of why we have to do this from a fundamental level, from a theoretical level, before we get into the code. I think it's important to review what's happening behind the scenes.

    [04:22 - 04:33] And for that, we're going to take a look at what are called ticks in a force simulation. So as you can see in the course lesson here, a force simulation is composed of multiple ticks.

    [04:34 - 04:45] 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:46 - 05:02] Now at the first tick, right, 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.

    [05:03 - 05:14] 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.

    [05:15 - 05:24] They converge on what looks like here, a x axis of zero. There's a force x bringing everything to zero or force y, I apologize.

    [05:25 - 05:34] And so you can see this dynamically how a force simulation actually plays out using physics by playing the simulation. You'll notice that a lot of it is very like front heavy.

    [05:35 - 05:42] Most of the simulation animation actually occurs in these first like 20 frames. And then the rest is kind of easing into play.

    [05:43 - 05:54] And this is all about certain properties that we can specify like decay, power, strength, we'll get into that below. But I would recommend you really research what a tick is and how it works.

    [05:55 - 06:23] You can read more about it in this observable notebook, which kind of shows simulation.tick runs and iterations of a force simulation layout, again, illustrating the exact same thing this time changing between this circular tree map layout to more of a branched layout as we see here, right? And so the important thing is we're not using ticks in our application and we should be because every documentation of D three force shows ticks and its internals.

    [06:24 - 06:34] So 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, right?

    [06:35 - 06:42] 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.

    [06:43 - 07:04] So you can also read about this documentation within the D three force, GitHub repository, basically each simulation has an on listener, an event listener that you can add to your simulation that can either be on tick or on end. And so on tick would run in every 149 of these frames.

    [07:05 - 07:22] And on end would only trigger on the last frame, right? So it's basically an event listener that you can tack on to your simulation that will trigger either on every tick or at the very end of the simulation ticks, all of them, whenever they're complete, it will then run the on end.

    [07:23 - 07:31] So in our case, we want to run on tick, which as you can see here runs after each tick of the simulations internal timer. Okay.

    [07:32 - 07:38] And 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.

    [07:39 - 08:02] But now the simulation is, you know, 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.

    [08:03 - 08:29] 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 nodes, if and when the simulation ticks. So the big difference here is that we're no longer updating nodes anytime simulation itself changes.

    [08:30 - 08:37] And 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.

    [08:38 - 08:50] 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.

    [08:51 - 09:00] Now you might think that we could do something like this, but then you'll notice a bit of an issue with resize. Right, and so the reason for that is because it's a pretty bad issue.

    [09:01 - 09:13] 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.

    [09:14 - 09:21] That's not what we want to do. Essentially we want to let the force simulation itself handle the internals here, right?

    [09:22 - 09:33] 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.

    [09:34 - 09:44] Let's remove this. And now we realize if this has to be, you know, not prefix with the dollar label, so too does the simulation we declare on line eight.

    [09:45 - 09:56] So 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.

    [09:57 - 10:08] Excale is not a function. And then we seem to be in this endless dependency loop, this problem where we can't use excale, but we have to create this with a const rather than a dollar label, so we must be out of luck.

    [10:09 - 10:16] The answer is we're not out of luck, thankfully. What we actually need to do is simplify how simulation is constructed.

    [10:17 - 10:30] So 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 safe.

    [10:31 - 10:40] Now by default you see everything appears in the top left corner, which is precisely what we would expect, right? But now what do we want to do?

    [10:41 - 10:51] What we essentially want to do is move all this complicated force logic into its own reactive block. Now what is a reactive block?

    [10:52 - 11:09] Here's kind of the documentation for reactive statements on a block level from Svelte. So 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.

    [11:10 - 11:16] Okay? So here for example, if and when count then passes 10, we can trigger this entire block.

    [11:17 - 11:26] And we want to do a block like reactive statement for our force simulation. Now the question is why are we doing that?

    [11:27 - 11:55] Basically the simulation itself is very simple, but we want to update the simulation, remember we don't want to reinstantiate 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 on line eight, let's open and close a reactive block, which essentially looks like that.

    [11:56 - 12:08] 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.

    [12:09 - 12:18] And automatically you'll see that our chart is in fact working. There are a few other quirks to work out, which we'll get to, but for the most part, we're basically there.

    [12:19 - 12:34] 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 SELT to recreate simulation if and when anything on the right side of the equal sign changes.

    [12:35 - 12:43] 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.

    [12:44 - 13:04] 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.

    [13:05 - 13:23] 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.

    [13:24 - 13:38] 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.

    [13:39 - 13:52] 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.

    [13:53 - 13:56] So basically, how much movement do we want? How fast do we want it to be?

    [13:57 - 14:08] 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?

    [14:09 - 14:13] And then the final one will be restart. And restart is probably the simplest of them.

    [14:14 - 14:19] This is what tells the application to restart. So by default, obviously this isn't going to work because we need numbers.

    [14:20 - 14:26] Let's go ahead and put some numbers. Let's do 1.01 and restart doesn't take an argument.

    [14:27 - 14:34] 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?

    [14:35 - 14:48] It's a bit choppy, 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.

    [14:49 - 15:02] 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. So you can go ahead and look up some documentation for each of these three.

    [15:03 - 15:14] There's a great notebook here, which basically shows the different parameters. So here you can see all of these arguments, alpha min, alpha decay, velocity decay, alpha target and alpha.

    [15:15 - 15:22] And we could change any of these numbers to test how things change. So this is a good kind of visual illustration.

    [15:23 - 15:31] As you can see, I just broke the application by putting too high of an alpha target. The point being that you probably need to play around with the numbers to see what works.

    [15:32 - 15:43] But I would recommend doing some reading on this. Again, the D3 documentation on GitHub is going to have all the answers you need where alpha is roughly analogous to temperature and simulated annealing.

    [15:44 - 16:06] If you're not a physics expert, you might just want to play around with the numbers rather than try to understand the physics here. But read through these and really understand what is happening behind the scenes whenever we pass certain properties with alpha and alpha decay being the two that I have personally noticed are most important for a well adjusted force diagram.

    [16:07 - 16:12] And spoiler alert, I've been working on this for a little bit. So I know which numbers are going to work well here.

    [16:13 - 16:28] 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.

    [16:29 - 16:37] Pretty good movement, pretty dynamic, feels pretty good. Overall, there are very few issues with the visualization itself.

    [16:38 - 16:42] Again, you could play around with these numbers and make them whatever you want . But you might notice that some work much better than others.

    [16:43 - 16:53] So this is going to be personal preference. And I'm going to go ahead and add some writing that I have put together for what these means that you can review them.

    [16:54 - 17:08] And then the important thing on the last line is restart. And so this restart parameter basically tells the simulation to update if and when any of these operands change and this entire block triggers.

    [17:09 - 17:22] 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 screen would still look pretty good because it's using D3 physics to basically make sure that no bubbles are overlapping.

    [17:23 - 17:36] They're respecting one another's space and they're arranged via physics. So in this lesson, we basically took this broken force diagram that was using reactivity in the wrong way and we reorganized the logic.

    [17:37 - 17:54] 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.

    [17:55 - 18:10] We simply update it and that specific to D3 force, updating a simulation, these parameters being tacked onto 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.

    [18:11 - 18:33] 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.

    [18:34 - 18:48] So we have the basics of a force diagram down and from here on out, we're really just adding some polish to make it look more like a real chart rather than just an example. So in the next lesson, we're going to begin by styling our circles.

    [18:49 - 18:57] We'll add color and different radii for each circle so that it's more clear which country each circle is representing. So I'm really excited and I'll see you there.