Become a member
to unlock all features

Level Up!

Access all courses & lessons on egghead today and lock-in your price for life.


    Create an Interactive Line Graph with a Hover Line Highlight using VX and D3


    In this lesson we'll take a look at how to render a basic line graph using D3, and VX. We'll move on to analyzing how to add some interactivity that will render a line where ever the user hovers using bisect from D3. Then we'll show how to render another line path using the scales and manipulating our data. Finally we'll add another line split so we're rendering 2 line paths.



    Become a Member to view code

    You must be a Pro Member to view code

    Access all courses and lessons, track your progress, gain confidence and expertise.

    Become a Member
    and unlock code for this lesson
    orLog In




    Instructor: We're going to start with a line graph already set up. The first thing that we need to do is grab bar from @vx/shape. This will allow us render a transparent bar over the entire SVG.

    If we attach our data listeners for mouse moves on top of our line path, it will be a very constricted space that we could then hover over. If we render an invisible bar over everything, then anywhere we move our mouse, we can actually get the mouse coordinates.

    We're going to set up the exact same coordinates that will just cover everything we have here. X=0y=0The width, the height. We're going to fill with transparent. We're going to pass in rx=14, duplicating what our rectangle is, which is the visible portion. Then we'll pass in our data, which is just stock. Then we'll attach our onMouseMove.

    VX will call any functions that it detects and inject the data that it has. If this is some sort of abstraction, you don't have access to the original data, this is what it will pass to it. We're going to say data, and then event, and then we're going to say call a function.

    For us, we'll call this.handleDrag, which we have not created yet. We'll pass in our events. We'll pass in our data. We'll pass in our xSelector. We'll pass in our yScale, and we'll pass in our xScale.

    The reason that we are doing this is just because our function will then receive all of the data in all of the functions that it needs to figure out where exactly our mouse is and how we need to render things. Now let's go created our handleDrag.

    We'll create a handleDrag and we will then destructure our events, our data, our xSelector, our xScale, and then our yScale. The goal of this function is to take an event, figure out where it's at, and then convert that into an index of existing data.

    We're going to first say const x. I'm going to destructure and call local point from our event. This is from VX and it's going to figure out a relative point to the SVG, otherwise it will be relative to the actual document, and we don't want that.

    Then we're going to say const x0=xScale.invert, which will then take a point and invert it into a data point. This takes a coordinate and turns it into x0, which because it's a scale of time, will actually just be a date instance.

    Then we'll say let index=bisectDate, pass in our data, our x0 and a minimum value of 1. What this is going to do is take all of our data and the data, that point that we're looking for, and find the closest value that it can to that. This is usually used for data insertion, but it can also be used to find the closest point.

    This bisectDate comes from our bisector, which we've passed our xSelector, and then said we want the left side. This could also be the right side. If it leaned either way, it would point to either left or right. We've just arbitrarily chosen left.

    Now, because we've set our minimum at 1, we're going to get our 2 data point. Const d0 is equal to our data, which is our index -1. Then d1 will be data index . Now we're going to say let d=d0, and then we're going to check if we even have a data point at the current index that we're at.

    If we have d1 and we have a date, then let's do something. In our case, we're going to compare the x0 to the date and see which one's closer. If x0 minus the xSelector with our d0 datapoint is greater than xSelector d1 minus x0, then this is going to say that we are closer to the datapoint 1. That way, we want to set our datapoint to d1.

    Else, we're going to say our data point is still d0, and then we'll do index=index-1. That way, we set up our datapoint and then the correct index that we are going to use.

    Now, with that all set up, we're going to do a this.setState and we're going to set our position with our index that we've picked, as well as our x position. We could theoretically derive this in the render function but I'm going to set it here for simplicity's sake.

    We're going to have to use our xScale, and then we're going to take our xSelector and pass in our datapoint. This will select the date and then scale it to the actual coordinate of where exactly our mouse is moving and the closest data point.

    Now, we can destructure from this.State where our position is, and then we can render a line. Here, if we have position then we can render a line. This line is from VX but it basically is a helper method to just draw a line.

    We'll do our from. Our from will be an x and y, which will be xPosition.x that we've figured out. Then y will just be the top of the graph. Then if you say 2, it'll still be the position x, because we're drawing a straight line down. Then we'll say it is the bottom of the graph. We'll give it a stroke width of 1.

    Now if we go and take a look, you can see that when we move our mouse across the screen, that we render a line. However, when we move away and out of it, it doesn't disappear. Let's fix that.

    We need to adjust, instead of just an onMouseMove, we need to add onMouseLeave, which with VX, it'll get called with our data, then eventually our event, and then we'll say this.setState and clear out our position and set it to null.

    Now that we have the bar showing and hiding, we need to actually render our second line. We're going to take our line path and just duplicate it, say position and our line path.

    With our line path, we don't need to adjust any of the scales, because they're already set up perfectly to take our current data set and render a line and scale our data up correctly. All we need to do is adjust our data.

    With this, we could do .slice, type position.index, which will take our index all the way to the end of the array. We also need to adjust our coloring. We'll say stroke="rgba(255,255,255,.5). This color can be anything, but it's just a opaque white line.

    Here, we can now see that we are rendering a line, and then also rendering a second line. However, it is right over the top of our other line, and we want it to render as a separate line. To fix this, we need to adjust the data of our normal line.

    We'll say if we have a position, then we'll do stock.slice from 0to our position.index. Otherwise, if we don't have a position, we'll just render the full stock line. We're not messing with any scales. We're just adjusting our data when we need to.

    Now we can see here we're rendering a opaque white line, and then adjusting our other line to render right up to it.