How to Add Filters and Hover Effects to Style Svelte Visualizations

Adding new hover and click events to filter and style our visualization

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.

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:10] Hey y'all, in this lesson we're going to bring our chart to the next level with some additional interactivity. To recap, we ended last lesson by adding this tooltip to each circle within our chart.

    [00:11 - 00:27] The tooltip looks great, it's nice, it's responsive, it doesn't fall off the edge of the screen, so we did good at that. But I think we can bring this chart to the next level with additional interact ivity that exceeds just this single circle basis and actually adds interactivity to the entire chart.

    [00:28 - 00:30] What do I mean by that? We're going to do two things.

    [00:31 - 00:48] First, we're going to make this legend up at the top of our chart interactive. We're going to make it so that if the user hovers over a given continent, for example Asia, the circles that correspond to that continent will be highlighted and the circles that don't will be de-emphasized will kind of fade into obscurity.

    [00:49 - 01:04] So that's the first thing that we'll tackle in this lesson. The second thing is a click event on the overall chart that basically toggles the position of these circles so that if and when we click on the chart, they all coalesce to the middle to this y equals zero position.

    [01:05 - 01:12] So that's the second thing we're going to do. We begin with the simpler of the two, which is filtering the chart with the legend.

    [01:13 - 01:38] So let's describe in kind of human terms what we want to accomplish before we start coding. Essentially, if and when the user hovers over any of these six continents in our legend, we want to keep track of which one they are hovering over and then tell the circles in the main body chart to update their styling if and when they match the hovered continent.

    [01:39 - 01:46] So if the user hovers over Africa, all of the circles in our chart need to say, okay, does Africa apply to me? Is that my continent?

    [01:47 - 01:49] If so, emphasize me. If not, de-emphasize me.

    [01:50 - 01:55] In human terms, that's what we're trying to accomplish. So encode, what does that mean we need?

    [01:56 - 02:09] That means we definitely need to keep track of which continent is being hovered . Because we're going to need to access this continent in app.s felt in order to update the circles down here, we can create the variable in app.s felt.

    [02:10 - 02:18] We'll call it hovered continent, just like we have hovered on line 93. This will be hovered continent, which by default is going to be undefined.

    [02:19 - 02:29] Now where else do we need hovered continent? We need it in our legend component because that is where we're going to reset h overed continent if and when the user hovers over a legend item.

    [02:30 - 02:44] I'm going to do something at first, which isn't going to work, to illustrate this tricky concept in felt called component binding. So feel free to code along with me or not while I kind of illustrate what you think you might want to do, but actually would not work.

    [02:45 - 02:53] So what you might want to do is pass hovered continent as a prop into legend.s felt. You're probably familiar with this pattern because this is what we do a lot.

    [02:54 - 03:06] And then you'll need to accept it using export let hovered continent in your legend component. Now let's go ahead and verify this works by console.logging hovered continent and saving.

    [03:07 - 03:22] Now by default, it's going to be undefined. And what I'm going to do now is update right here on mouse over, I want to trigger a resetting of hovered continent to equal continent.

    [03:23 - 03:28] And this is going to scream at you for a while. I'm just going to do this and then fix it later.

    [03:29 - 03:31] Now what happens? So hovered continent is undefined.

    [03:32 - 03:37] But if I hover over a continent, it does in fact update. This is what we expect.

    [03:38 - 03:44] Right? And so notice that this console log is occurring in legend on line five because that is where I placed it right here.

    [03:45 - 03:57] So although we, you know, accept hovered continent as a prop and at the point on which it is passed into legend.spelt, it is undefined, we can reset it within the legend just fine. And that works.

    [03:58 - 04:08] But what if I took this console log, I deleted it from legend.spelt and I added it to app.spelt. And then I refreshed.

    [04:09 - 04:19] What you would notice is that it never console logs again. Recall that in the last example, whenever it was in legend.spelt, it was updating every time I hovered over a new legend item.

    [04:20 - 04:31] You can keep trying to refresh it, keep trying to fix it all you want. It's not going to work anymore because hovered continent is actually not updating upstream of the child that it's passed into.

    [04:32 - 04:34] Sounds like a lot of jargon. What does that mean?

    [04:35 - 04:56] What it means is that a prop like this hovered continent, as it is passed in like so, if it changes in a child, in this case legend.spelt is the child, that change is not going to propagate upstream, meaning it's not going to reach app.spelt in this current prop syntax . So what do we need to do instead?

    [04:57 - 05:03] Thankfully, the solution is really easy. We're going to write bind colon hovered continent.

    [05:04 - 05:24] And this is basically shorthand for this bind hovered continent equals hovered continent, where it's saying this property internally is hovered continent is equal to this variable that we're declaring out here. And I know that sounds complex, but just get used to bind and then the name of whatever you want to propagate upstream.

    [05:25 - 05:40] And if you're ever curious about how this works, you can go ahead and look up component bindings spelt. And you'll see there's the tutorial has an example of this you can play around with, this bind and then the name of what you're binding to the variable that you want to bind it to.

    [05:41 - 05:52] This is how you do kind of parent child communication in spelt. So anyway, let's go ahead and see if this worked by saving and then refreshing and hovering over a continent.

    [05:53 - 06:00] Now you're seeing that app.spelt is logging the proper hovered continent. Notice this is an app.spelt online 99.

    [06:01 - 06:06] And if you can't see it because I'm my big head, now you can. So this is an app.spelt.

    [06:07 - 06:12] It's not in legend.spelt. So now this update actually propagates both to the child and to the parent.

    [06:13 - 06:20] You'll see these are console luck twice now, which means we finally fixed our book. So all that to be said, I said you don't have to code this along.

    [06:21 - 06:24] If you want now you should code along. You should make sure that you have the following accounted for.

    [06:25 - 06:32] We want to bind hovered continent to the variable online 98. And so that's why we write this code right here.

    [06:33 - 06:35] Now we have hovered continent accounted for. It exists.

    [06:36 - 06:48] And what do we want to do with it? The first thing we want to do is update the actual styling of our legend if and wins the individual hovers over that continent.

    [06:49 - 06:55] So this is pretty easy. What we're going to do is add a new class and we're going to call it un hovered .

    [06:56 - 07:12] And the way that you can do this kind of dynamic class and spelt is by doing class colon, the name that you want that element or that class to have. So in our case, we want to apply a class of un hovered if the following condition is met.

    [07:13 - 07:24] And that condition is the following. Hovered continent must exist, which is what this little bit of code means, just that it is not falsy, aka undefined or null in our case.

    [07:25 - 07:35] And the other condition is met, which is that hovered continent, which remember is this string is not equal to the current continent. So this might seem confusing, but recall what we're doing in this each block.

    [07:36 - 07:49] We are rendering six continents, Africa, Asia, South America, North America, Europe, and Oceania. What we are then saying is for each of these six, right, these are six elements , even though it just looks like one, does this condition apply?

    [07:50 - 07:56] So hovered continent exists. That will be true if and when a single one of these six is hovered over.

    [07:57 - 08:00] Okay. The second condition, hovered continent does not equal continent.

    [08:01 - 08:14] That will be true if you're hovering over Africa and the quote unquote continent at hand that's evaluating whether it's un hovered is any of the other five, right? Hovered continent would equal continent if I were hovering over Africa.

    [08:15 - 08:24] So one trigger, but on hovered would apply to the other five. Let's go ahead and save and then look inside of our inspector to see exactly what's happening.

    [08:25 - 08:35] Notice how now whenever I hover over Africa, the other five become un hovered. And then if I move to Asia, the second PTAC see that is now not un hovered, but the rest are.

    [08:36 - 08:47] And we see these classes update as I move from one continent to another. So now that this un hovered class is in fact applied, we can go ahead and create a new style down here and call it un hovered.

    [08:48 - 08:49] And we could do anything. You could change the color.

    [08:50 - 08:57] You could change the opacity, whatever else. I'm going to add opacity of 0.3, which will just make it mostly transparent.

    [08:58 - 09:04] Now as I hover over new continents, that style updates in real time. You'll notice that I can't escape this.

    [09:05 - 09:18] So that's a problem. And the easiest way to fix this is on the entire legend container to add a mouse leave event or mouse out event, either one, which resets hovered continent to null.

    [09:19 - 09:24] We'll go ahead and save that. Actually, let's make it mouse leave.

    [09:25 - 09:30] Go ahead and save that and then see what happens whenever I hover in and hover out. Now these update.

    [09:31 - 09:39] The biggest styling thing we could fix now is that these are very abrupt transitions. So down in our p tag, remember that each of these are paragraph tags, right?

    [09:40 - 09:54] Let's add a transition property, which says transition on opacity, make it make , make it take 300 milliseconds and make it ease. And then finally, let's add a cursor of pointer so that these are known to be interactive.

    [09:55 - 10:07] Now as I hover, it looks smooth, it transitions nicely, everything is great. So obviously our legend is now hoverable, but the filter isn't doing much because it's just highlighting the legend and nothing else is happening.

    [10:08 - 10:23] So let's go back into app dots felt and go down into our circle elements to account for this new hovered continent variable in the same way that we were previously accounting for the hovered circle. So as a recap before we do this, what happens whenever I hover over a circle?

    [10:24 - 10:32] It will basically fade out all of the other ones and apply a dark black stroke to the hovered circle. Okay.

    [10:33 - 10:48] And the reason for that is because of lines 135 on my screen, 135 through 140, where we're looking to see if hovered exists, meaning something is being hovered. And if so, whether it matches the current element in our each block.

    [10:49 - 11:19] Now we could do some complicated ternary operator stuff to continue this pattern, but actually the easiest way to account for hovered continent is just to include it with this pipe operator, which says or. So if hovered exists or if hovered continent exists, then move on to the next line where we check to see if hovered equals node, meaning this is the currently hovered circle, or if hovered continent equals the nodes continent.

    [11:20 - 11:34] And then let's save that, now see what happens whenever I hover over a certain continent. Notice how the strokes do in fact update as we would expect, all just because of this single small addition at the end of each condition.

    [11:35 - 11:46] So let's also add this here. So if hovered or hovered continent and hovered equals node or hovered continent equals node continent.

    [11:47 - 11:52] Let's save, go back in, hover over our legend items. Beautiful.

    [11:53 - 12:15] We see basically exactly what was applying to a single circle now applies to an entire continents group of circles because of these small updates we've made to our stroke and opacity style tags. Okay, so we pretty much have exactly what we wanted to achieve in terms of the legend and legend filter ability.

    [12:16 - 12:21] But you might be asking, why do we need hover interactions? Like I can see this is Africa because it's labeled Africa.

    [12:22 - 12:26] I don't need a hover interaction in the legend. That is true.

    [12:27 - 12:42] But it could come in handy if for example, all of these circles were at the exact same Y position, which is what we're going to be introducing in the second half of this lesson. Pretty quickly, we're going to add the option for the user to collapse all of the circles to the same Y position.

    [12:43 - 12:49] So they're all really like a B swarm, right? They're all concentrated around the same position.

    [12:50 - 13:04] And so we want a click event on the overall chart that when the user clicks on the chart, it will toggle this grouping to either group them by continent as is currently the case or group them all in the middle. So let's create a new variable.

    [13:05 - 13:13] Let's delete our old console log and call it group by continent. And by default, let's go ahead and make it false.

    [13:14 - 13:24] Now group by continent doesn't do anything right now. But what do we know we want to do is we want to trigger it if and when the user clicks on the chart.

    [13:25 - 13:35] So in chart container, let's add a new click event listener. We write on click in the name or the internal of what we want the behavior to do.

    [13:36 - 13:48] For us, we want group by continent to equal the inverse of group by continent. And essentially what this bang operator does is it says the opposite of whatever currently that value is.

    [13:49 - 13:58] So if group by continent is false, group by continent will become true whenever we click. Let's verify that by console.logging group by continent.

    [13:59 - 14:14] If we open up the console, you should see this toggle between true and false back and forth and back and forth. So we now have group by continent being accounted for, but very evidently it's not doing anything.

    [14:15 - 14:31] The other thing we want to do is add this small line of code that says hover should be set to null, which will become evident later. But if we're trying to trigger something between all the circles, we don't want to accidentally hover over a circle while that transition is occurring.

    [14:32 - 14:35] It'll become clear kind of why we're doing that soon. But for now, add hover equals null.

    [14:36 - 14:47] Now, let's describe what we want to do with group by continent. Effectively, we want to update the Y positions of all of these circles to not be, you know, group by continent.

    [14:48 - 15:05] If it's, if it's false, we want them all to be in the same Y position. So in order to do that, we're going to have to play around with the physics that we did a lot of in lessons one and two that we haven't really touched since, but now we're going to go back to specifically, we're going to look at this large reactive block.

    [15:06 - 15:10] On line seven through 28 in my code base. And remember what we have here, right?

    [15:11 - 15:19] This is where we declare the X force, Y force and collision force. And then if and when it changes, we restart the simulation.

    [15:20 - 15:34] If I were to remove one of these variables, for example, the X force, you would see that they're all now positioned at the zero position on the X axis. If I were to remove the Y force, you would see that they're all randomly categorized.

    [15:35 - 15:42] And if I were to remove the collide force, they would all overlap. So we know that each of these forces has a discrete purpose.

    [15:43 - 16:00] The question is, which do we want to play around with in this example? And the answer is our Y force, because we effectively want all of our circles to group either together or within their content, continent based on whether group by continent is very able to be created is true.

    [16:01 - 16:16] So if group by continent is true, we want to continue doing what we're already doing, because this is grouped by continent currently. So we're going to use this ternary operator and say, if group by continent is true, then return this, which is what we currently have.

    [16:17 - 16:23] But if it's not true, then what do we want to do? We want to center the Y position of every circle.

    [16:24 - 16:31] So inner height is the entire chart height, which would put everything at the very bottom. We want to do half of that, basically 50% down.

    [16:32 - 16:38] So we'll write inner height divided by two. We'll close this and hit save and then refresh your page.

    [16:39 - 16:48] Now you see they're all positioned exactly at the middle. And if I click, you'll notice they go to their respective continents back and forth and back and forth.

    [16:49 - 16:50] It's fun. It has momentum.

    [16:51 - 16:52] It's fluid. It looks good.

    [16:53 - 16:58] And it was as simple as adding that one small line. So now you know how to group by continent.

    [16:59 - 17:15] And from here, it's quite easy to make some slight small changes that improve the visual appearance of this. So for example, right now, whenever they're all grouped in the middle, there's no reason to have Africa, Asia, South America, all of these continents on the left side, because they're simply meaningless.

    [17:16 - 17:25] They're not positioned according to those continents right now. So let's go ahead and update axis Y dots felt to account for group by continent .

    [17:26 - 17:34] In axis wise declaration within your markup, you're going to want to pass group by continent as a prop. And then you want to open axis Y.

    [17:35 - 17:45] I'm hitting command P to open up this toolbar at the top, find axis Y and open it and then accept this as a prop. Export let group by continent.

    [17:46 - 17:54] Now let's describe what we want. If group by continent is false, meaning they're all in the middle, we don't want to render any ticks.

    [17:55 - 18:02] So why don't we just say this? If you're by continent, then render what's within.

    [18:03 - 18:07] Otherwise do nothing. And now you'll notice that by default, they're all missing.

    [18:08 - 18:18] And if I click, they reappear. So that's precisely what we wanted, but we could add a little bit of transition to make it feel a little bit smoother rather than this abrupt transition.

    [18:19 - 18:24] So let's do the following. Let's add a transition of fade to each tick element.

    [18:25 - 18:39] The way that you can do this is by doing transition colon fade, but then you'll need to import fade from spelt transition. Now if I save that and restart, you'll notice that they now fade nicely into place.

    [18:40 - 18:55] And if you want one final bit of visual flair, why don't we make this transition respect the index of the individual item? So they kind of stagger in one by one by one, according to their continent.

    [18:56 - 19:16] The way that we're going to do that is by accessing the index of the tick in the each block, recall that in each block iterates through an array and names each array item this. And then the second argument you can apply is the index, where you literally would write index or I or number or whatever you want to call it.

    [19:17 - 19:29] For us, I'll do index. Now we can access this within and we can use it for something like a delay where we say that the delay of each should be equal to index times 100.

    [19:30 - 19:46] Now if I save this, you'll notice that they now fade in one by one, they fade out one by one. If you only want that fade in on the entrance of the element, you could do in fade with the delay, out fade with no arguments at all.

    [19:47 - 19:56] Now if we refresh and look, fades in nicely one by one, fades out all at once. And there we have it, a beautiful access transition.

    [19:57 - 20:10] I think the last thing that we need to do is fix this little bug right here. Oh, I already had it.

    [20:11 - 20:22] Okay, ignore that I had this. What we need to do is fix this bug right here, where if I'm hovering over a circle and I click on it, it still triggers the same transition.

    [20:23 - 20:29] And so the problem with this is that the user might think that Zimbabwe is a clickable element. There is a cursor pointer after all.

    [20:30 - 20:52] So they might say, oh, I want to learn more about Zimbabwe and click, which kind of hijacks their user experience and transitions them all to the middle, which is probably not what they were intending to do. So what we want to do is find the circle elements and add a click listener to those elements, which basically says, if these are clicked on, ignore other pointer events that might be triggered, ignore other click events that might be triggered.

    [20:53 - 20:58] So you might notice I just had it and I deleted it. It's because I'm re-recording this lesson because I messed it up last time.

    [20:59 - 21:02] We'll go ahead and write on click. And then what do we want to do on click?

    [21:03 - 21:13] We want to listen to the event, which is this E or you could call it event or whatever else you want, and then we want to do the following. Oops, I had a little typo there.

    [21:14 - 21:29] We want to E.StopPropegation. And what this does is it basically says anything else that would be triggered under this element, for example, so like the pointer event on the chart itself, stop the propagation of that.

    [21:30 - 21:31] Do not do it. Halt here.

    [21:32 - 21:35] This circle is the only thing that you should pay attention to. Don't do anything else.

    [21:36 - 21:46] So that's exactly what happens. So if I hit save and I try to hover or I try to click on Zimbabwe, nothing will be triggered because it is stopping the propagation underneath.

    [21:47 - 22:00] But if I click anything else on the chart itself, it will in fact operate properly. And you might notice some weird physics whenever you're working on something and then you save, then you try to redo the physics.

    [22:01 - 22:03] Then the physics kind of messes up. That will never happen for a user.

    [22:04 - 22:18] It's only whenever you're working in a live development environment. So if you ever do encounter weird half transitions completed or whatever using D3 force, just do a fresh refresh to really see how it would look for your production final deployed version.

    [22:19 - 22:31] So what have we done in this lesson? We added these nice hover event listeners to our legend, which allows us to see the distribution and the averages of countries, different continents across the world.

    [22:32 - 22:45] Then we added the option to toggle between this continent by continent view and this group view. So really we've just done these two simple little things to add additional interactivity to our chart and make it production ready.

    [22:46 - 22:56] So in the next lesson, we'll very briefly go over some final polish that you could implement optionally to bring this chart ready to publish. And I'm really excited to see you there and finish up this module.