Since d3.js v4 came out I have been trying to figure out how to create a basic brush to explore my scatter plot data. As soon as I thought I figured out how to implement a brush in v3, v4 came out! Today we will be creating the above graph which will give you the basic recipe to create a scatterplot with brush functionality in d3 v4. You can take a look at the full code here on this block. This will be the primitive that we will use to create our interactive smart on fhir blood pressure graph in d3 shown below. ( I will create a separate post on how to create the bp graph.)
Just like how we do it in v3 we will create two parts of our graph, one for our actual graph and a scaled down version for our brush section. The actual graph will be appended to focus while the brush graph will be appended to context. This code is based from one of the main creators of D3, mbostock. His code shows how to create a brush on an area graph.
We will first set up the dimension of our chart area. We will give the area margins so that we can have some white space as well as room for our axis labels. The area of the dictated by the height and width will be the layout of our SVG. You can think of it as our canvas that will include our axis scatter plot and brush graph.
Creating the Axis
To create the X axis we will use a time scale to equally space out our tick marks. The range determines the pixel range of the X axis. So we want to span the x axis from pixel 0 all the way to the width of our chart. The domain for the x axis will be the extent of the date of our inputted data. On line 80 you can see that we pass through the data to our function and return the date from there extent is a d3 function that finds the min and max of the set of data. Similarly, we will span the y axis from 0 to the height of the chart. For the Y axis we will use linear scale to make our tick marks as the data that we have is roughly the same order of magnitude. For the Y domain we will add 200 to our data domain so that it gives us more room within our graph so it does not clip data points that are close to the max of our data range. For example, if the max of our data set was 1000 then points at 1000 or near it will get clipped and the data will look incomplete and cut off. You can think of the range as determining how long in pixels your label will be and the domain as what is the min and max of the values of the tick marks on the axis.
Now for the brush axis we will only have the x axis as the y axis would be too small to display. The brush X axis, x2, will span the same distance as the main x axis. you can now see why we subtracted the margins from our height and width variable to get our scatterplot chart area.
Plotting our Scatter Plot
Now that you have your chart area and axis lets plot our data. For the main scatter plot we will append to the data points to the Focus chart area. The position of a single data point is determined by the x and y coordinates that is returned when we pass the data d through our function. This will return the value of the price for our y value and date for our x value these values are then converted into their respective pixel values. The size of each data point is set at 5px.
Similarly we plot our data points on the mini brush graph by appending the points on context. This will create mini data points on our brush chart. It is the same format but instead of passing the data through x() y() we will use x2() y2() to get our pixel conversions. You can think of this function as taking the actual value of our data and using a ratio to map it proportionally to our pixel range of the chart area that we defined under context. So for the smaller brush chart the Y2 range is only 40 pixels high so we need to be able to scale our data points proportionally that the max value, determined by our domain, is 40 pixels high from our 0 value.
Creating the brush
The brush will all us to select a range of data points and display our selection on the main chart. Let's define the brush variable. We only want the user to be able to traverse along the x axis so they can explore the data points through time. We will limit this by using the d3.brushX function. If you wanted to traverse by only along the y axis use d3.brushY and for both x and y you can use d3.brush We can set the extent to a predetermined range. In this case we want to be able to select from our absolute min to our max which is 0,0 and width hieght2. This will look familiar as these are the dimensions we used to create our context graph.
The brush function will be called each time there is a brush event by the user. D3.event.selection returns the min and max value of the current brush selection. When you change the size of the brush area the min and max dates correspond to the ends of the brush. Then we will update the x domain by inverting our range that we got from the selection. .invert will take the range value from our brush selection and convert it to the corresponding domain that we have defined in x2. This will allow us to redraw our scatter plot to plot only the points within our selection determined by the new x domain.
To redraw the scatter plot just call the select all and pass the data to get the x and y coordinates of your selection. The data d will only include the data of your selection. Similarly, update the x axis and scatter plot to reflect the new domain. This will give the perception that you are traversing through time across the graph.
So now you have learned a primitive way to enable brushing for a scatter plot in d3.js v4. Next time we will use what we have learned to make a blood pressure graph using data from the smart on fhir api.