Line chart

Lastly, we add a tooltip to our timeline. We talk about how to add a listener rectangle, d3.leastIndex(), and how to add a dot to mark our place.

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 Fullstack D3 Masterclass course and can be unlocked immediately with 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 Fullstack D3 Masterclass with a single-time purchase.

Thumbnail for the \newline course Fullstack D3 Masterclass
  • [00:00 - 00:07] All right, so this is our last interaction example. And we're going to go ahead and do this for our line chart.

    [00:08 - 00:19] And it's again a little bit different. So hopefully this is a good demonstration of how for each chart you really want to think through the interactions instead of just copying what you did before.

    [00:20 - 00:38] All right, so for our timeline, we want to basically whenever you hover over the chart, have a tooltip above the closest data point. And you might see how this is a little bit different, where we're not having a hover event.

    [00:39 - 00:46] There's no element here to mouse over. It's more just whenever you move your mouse over the chart.

    [00:47 - 00:50] So let's see what this looks like. The code's going to be very similar.

    [00:51 - 00:57] Again, we have this tooltip element. We're going to be showing the date and also the maximum temperature, which is on the y-axis.

    [00:58 - 01:09] For our styles, we again have this tooltip. Let's see if we comment out this opacity.

    [01:10 - 01:22] And then maybe if we refresh the page, it'll show up in that top left corner. I know sometimes code sandbox doesn't like to show this tooltip right away.

    [01:23 - 01:29] There we go. So now we can see that tooltip and our rest of our chart should draw in.

    [01:30 - 01:39] In just a second, there we go. So if we comment that out, then we can see the tooltip.

    [01:40 - 01:50] But let's have that hidden right now. And let's open this JavaScript and HTML file again.

    [01:51 - 01:57] So in our JavaScript file, this is the same thing we've seen before. We have our accessor functions.

    [01:58 - 02:02] We're creating our dimensions. We're drawing both the bounds and the wrapper.

    [02:03 - 02:09] We're creating our scales. And then drawing the data with our line generator.

    [02:10 - 02:24] Drawing both axes that we have a y-axis label just showing-- I think this is actually a maximum temperature. So if we're going to label our axes, we're going to want them to be correct.

    [02:25 - 02:36] And then now we're down here on our last step, which is setting up those interactions. So like we said before, there's not really an element to mouse over, so let's create one.

    [02:37 - 02:45] So let's call this listener rect. And basically, we just want to draw a rectangle over our entire bounds.

    [02:46 - 02:56] So bounds.append, we want a rectangle. Let's give it a class.

    [02:57 - 03:02] Let's just call listener rect. Keep our lives nice and simple.

    [03:03 - 03:14] Let's give it a width, which is going to be the width of our dimensions, so the width of our bounds. So we can use dimensions bounded width.

    [03:15 - 03:21] And a similar thing with the height. And instead of bounded width, it's going to be the bounded height.

    [03:22 - 03:34] We can see that this is covering our entire bounds, but it's also covering our entire bounds. So let's go ahead and give that a different fill.

    [03:35 - 03:47] So for our listener rect, let's give it a fill of transparent. It can be tempting to use none here.

    [03:48 - 04:08] A fill of none is also going to make it invisible, but it also doesn't actually draw any SVG elements so that if we try to listen to a mouse enter element, it's not going to be triggered at all. So if we want to trigger mouse events, then we're going to have to go with a fill of transparent if we don't want to see it.

    [04:09 - 04:16] So speaking of mouse events, let's add them. So we want a mouse enter event.

    [04:17 - 04:26] And then we also want a mouse leave event. So on mouse leave.

    [04:27 - 04:36] And then let's create those functions. And they'll have an event and a D parameter here.

    [04:37 - 04:48] Which we probably don't need one on mouse leave. All right.

    [04:49 - 04:54] So the first thing we want to do is grab that tooltip and store it in a D3 selection object. So let's just call it tooltip.

    [04:55 - 05:13] And we want to select the element with an ID of tooltip. So here, let's quickly do the usual tip opacity to 1 and then hide it in our on mouse leave function.

    [05:14 - 05:16] Let's see what that does for us. All right, great.

    [05:17 - 05:24] So when we're hovering the bounds, we get to see our tooltip. And the next thing we want to do is populate that tooltip.

    [05:25 - 05:42] So let's figure out what data point we're hovering. Right now, this D probably isn't going to correspond to anything because we're listening to this listener right, which doesn't have any data bound to it.

    [05:43 - 05:46] It's actually see what this is in the console. Undefined.

    [05:47 - 06:06] So that's not going to be very helpful for us. So this is actually-- it seems simple, but it's going to be a little bit more complicated than the other examples because we don't off the bat have that D variable.

    [06:07 - 06:20] So instead, we're going to grab the mouse position and then work backwards from there. And in order to get the mouse position, we can use something called D3.pointer.

    [06:21 - 06:31] This is actually newer and pointers should work for both desktop and touch events. And then we need to pass our event object.

    [06:32 - 06:46] So D3.pointer is going to use that event object and grab the X and Y position of our pointer. So let's go ahead and log that out to see what it looks like.

    [06:47 - 07:03] So we can see as we move our mouse around and we actually want this to be on a mouse move because it's going to-- we're going to want to trigger it as we move around instead of just right when we enter the bounds. Oh my goodness.

    [07:04 - 07:13] OK. So as we move our mouse around, we can see that this is logging out the position of our mouse.

    [07:14 - 07:19] We can see if we're in the top left corner. It's going to approach 0 and 0 as we move to the right.

    [07:20 - 07:25] The first number gets larger. The second number stays the same.

    [07:26 - 07:29] So the first number is going to be our X value. The second number is going to be our Y value.

    [07:30 - 07:52] And so what we need to do here is go from that mouse position and back into-- we basically need to do the inverse of data to mouse position or data to X or Y position. We need to take the X and Y position and go back to a data point.

    [07:53 - 08:09] In order to do that, we need to figure out, first of all, what is the date that we're hovering? So in order to do this, all our scales have an invert method.

    [08:10 - 08:18] And this is basically the opposite of the scale. So the scale turns data value into pixel value.

    [08:19 - 08:23] And we want to go backwards. So we use our mouse position and we invert it.

    [08:24 - 08:32] So we grab that X value and we turn it into a date. And so let's go ahead and see if that's giving us anything helpful.

    [08:33 - 08:40] So we can see at the beginning of the chart, we're looking at a January date. At the end, we're looking at a December date.

    [08:41 - 08:48] This is great. As you can see, it's a JavaScript date object, which that's what they look like in the code sandbox console.

    [08:49 - 09:07] And we need to take that date and find the closest data point with a date that's similar. So let's create a function called getDistanceFromHoveredDate.

    [09:08 - 09:15] And it's going to take a data point. And it's going to return the X distance.

    [09:16 - 09:36] So if we plotted that date on the chart, how far would a data point be from that date? So we want to grab the date value from that data point and subtract the hover date, which is already a JavaScript date time object.

    [09:37 - 09:52] And this will turn that data point into a JavaScript date time object, because we're using this successor function, which is parsing it. And then we don't care if which one is still after to the right.

    [09:53 - 10:01] So we're just going to use math.abs, which if it's negative, it'll turn it positive. If it's positive, it'll stay positive.

    [10:02 - 10:09] And let's actually just split this to two lines. So we can just this out.

    [10:10 - 10:32] We can log out that first point in our data set and send in that hover date. And we can see as we approach the beginning of our chart, which is where that first data point is, this number gets smaller.

    [10:33 - 10:34] So we approach the end. It gets really large.

    [10:35 - 10:51] All right, so what do we now do with this function? We basically want to go over every item in our data set and figure out which has the smallest distance from the hover date.

    [10:52 - 11:01] And the way we can do this is-- just call it closest index. We can use d3.least index.

    [11:02 - 11:20] d3.least index is going to loop over an array, which is going to be the first parameter. And then it'll run the second parameter, which is a function.

    [11:21 - 11:31] If you've ever used just the plain JavaScript sort function, it's going to be a little bit similar. So we have a first item and a second item.

    [11:32 - 11:50] And we want to return a value of basically which is closer. So we want to run this function, get distance from hover date, for the first element, and then subtract the distance for the second element.

    [11:51 - 12:17] And what this is going to do is map over each item, each pair on the data set, and figure out which one's closer, and then spit out the index of the point that is closest to the hover date. A little bit complicated, but this is usually what you're going to want to do when you have a tooltip for something where you can't just hover over an element.

    [12:18 - 12:20] So now we're logging out the closest index. This is great.

    [12:21 - 12:25] If we're at the beginning, we get a really small index. Say we're hovering over the second element here.

    [12:26 - 12:44] If we go over to the right, it moves down in that data set array, and we get the 320th element in that data set. So we can go from that to closest data point, which is going to be just data set and then that closest index.

    [12:45 - 12:55] And so this is that d element that we've been using up until now. We just had to jump through a few hoops in order to get it.

    [12:56 - 13:00] All right. So let's go ahead and populate our tooltip.

    [13:01 - 13:20] So we have a date ID and a temperature ID. So let's grab our tooltip, select the element with the ID of date, and we want to change the text to show that date for this closest data point.

    [13:21 - 13:46] So the date is on our x-axis so we can use our x-accessor for that data point, which we can see is this date time object, a little bit verbose for what we need here. So let's go ahead and create a format date function, which uses d3.time format.

    [13:47 - 14:10] The string that we used in the last video of what format we want this to be in, it was full month day, full weekday, non-padded day of the month, and then the year. And then all we want to do is grab that format date function and wrap it around this value.

    [14:11 - 14:19] So that we're going from the JavaScript date time object into the formatted date string. So this is a little bit more readable.

    [14:20 - 14:36] The next thing we want to do is select that temperature, the element with an ID of temperature, change the text. We can just use y-accessor for that closest data point.

    [14:37 - 14:42] Let's see where that gets us. OK, great.

    [14:43 - 14:51] So we have that temperature. One thing that would be really nice if it had the degrees Fahrenheit symbol, just to specify what are we looking at here.

    [14:52 - 15:02] We could create a function similar to what we did with the format date function . So let's call it format temperature.

    [15:03 - 15:18] It's going to take the temperature number. And then we're just going to return-- it's also a little bit for both for us.

    [15:19 - 15:29] If we look at this again, maybe we don't want two decimal places here. So we can also use d3.format.

    [15:30 - 15:38] And it's kind of similar to time format. We want to specify we want one decimal place here.

    [15:39 - 15:43] So that's a fixed number of decimals. And we want one of those pass in that temperature.

    [15:44 - 15:54] And then also have it say degrees Fahrenheit, which I'm just copy pasting from over here. All right, so we're going to do the same thing here.

    [15:55 - 16:06] We're going to wrap our temperature value with that format temperature function . And now we're seeing that it's just a little bit more readable.

    [16:07 - 16:12] We can see the temperature values go up as our line goes up. So that's great.

    [16:13 - 16:22] Next thing we want to do is move our tooltip. Like we did before, remember that we have to factor in the left and top margin.

    [16:23 - 16:39] So it's a little bit easier to just get the x and y value before we move our tooltip. So we're going to want to use that scaled accessor for that closest data point.

    [16:40 - 16:51] And then we want to subtract the left margin. And then it's going to be similar for that y value.

    [16:52 - 17:05] And we want to subtract that top margin. Because remember our tooltip is being-- it sits in the DOM outside of the bound.

    [17:06 - 17:15] So it's not automatically moved so that it starts here instead. 0, 0 is up here somewhere.

    [17:16 - 17:26] So like we have done before, we want to position it. So we set a transform, translate.

    [17:27 - 17:39] Remember, we're using calc to take into account the size of the tooltip. So minus 50% plus x for the units on there.

    [17:40 - 17:51] And then a similar thing with the y minus 100% plus that y value. All right.

    [17:52 - 17:56] Let's see if we formatted that correctly. All right.

    [17:57 - 18:05] Something's looking a little bit off here. What could we be doing wrong?

    [18:06 - 18:13] So it looks like our tooltip is in the right spot. Oh, you know what?

    [18:14 - 18:23] Interesting. Let's try to debug this.

    [18:24 - 18:27] So we're transforming it. We're taking away half of the width of our tooltip.

    [18:28 - 18:41] And 100% of the height of the tooltip. Let's double check that in our CSS, we're not doing anything goofy.

    [18:42 - 19:00] This negative 14 pixels should be accounting for that arrow and just scooting it up a little bit. Let's log out this closest data plane to make sure that we're looking at the right thing here.

    [19:01 - 19:19] All right. So for all the way over to the left, this should be a day that's in January and it should have a temperature around 20 degrees.

    [19:20 - 19:29] So if we look at temperature max that looks about right and then the date is January 2nd. So we have the right data point.

    [19:30 - 19:44] And maybe we're doing this part wrong. So let's set it at 0 pixels and 0 pixels.

    [19:45 - 20:02] So that looks right, I think. So our tooltip is sitting in the top left of our wrapper.

    [20:03 - 20:16] You know what's wrong here is we are subtracting a left and top margin. But instead, we want to add that margin because that this x value is within the bounds, our tooltip is outside of the bounds.

    [20:17 - 20:21] So we want to move it over by that left margin. And I bet you this will work.

    [20:22 - 20:28] OK, great. So as we move our mass around, we can see that our tooltip follows it but moves up and down with the line.

    [20:29 - 20:45] So it really solidifies your eye to where the tooltip is on that line. One thing that could help is similar to what we did with the scatter file, which is add a new dot that brings your eye to the right place on the chart.

    [20:46 - 21:07] So up here, let's do this a little bit of a different way just to get a sense of different ways to do it, why you might do it one way or another. So maybe underneath tooltip, let's add a new tooltip circle and then append a circle element circle.

    [21:08 - 21:17] And then add a class so that we can style it. Let's call it tooltip circle.

    [21:18 - 21:29] I like to keep things consistent. And then let's also just set the radius to, I don't know, four pixels.

    [21:30 - 21:44] So we have this tooltip. And now when we hover over our listener element, let's go ahead and move it so it sits on top of that point that we're hovering.

    [21:45 - 21:56] So let's set that Cx value. It's going to be x without the margin offset, similar to like we did in the scatter plot.

    [21:57 - 22:19] We're going to do a similar thing for that y. So we want to set Cy, set the y, we use the y scale, and use the y accessor.

    [22:20 - 22:29] And then we also want it to be visible when you're hovering and invisible when you're not hovering. So similar to this, what we're doing with the tooltip.

    [22:30 - 22:43] We want to make it opacity of 1 when we're hovering and opacity is 0 when we're not hovering. So this is pretty helpful.

    [22:44 - 22:58] Let's change that style a little bit. So if we go to styles.css, maybe right here, let's add some styles for tooltip.circle, I think is what we called it.

    [22:59 - 23:08] By default, let's give it an opacity of 0. Let's set the stroke.

    [23:09 - 23:14] What are we using? We have this x value for the line.

    [23:15 - 23:22] So let's go ahead and grab that. Let's set the fill to white.

    [23:23 - 23:30] And let's see-- maybe that's a little bit too small. Let's set this stroke width to 2.

    [23:31 - 23:39] OK, great. So now let's just make this a little bit wider.

    [23:40 - 23:54] And refresh. So now when we hover around on our chart, we should see this tooltip move to wherever we're hovering it.

    [23:55 - 24:10] And then we also have this circle kind of solidifying where exactly we're hovering. Another thing we want to add here is pointer events of none to make sure it's not stealing that pointer event.

    [24:11 - 24:16] So even when we're hovering over that circle, it doesn't change what we're hovering.