# CS465 - Lab 10 Scrolly-telling

# Objectives

  • Learn one technique for implementing a scrolling narrative visualization
  • Improve your CSS/HTML/JS integration intuition

# Starting the lab

Go to Github Classroom and grab the starter code for the lab.

# Scrolly-telling

Our goal with this lab is to implement a simple page where the contents of the visualization and accompanying narration change as the user scrolls. We aren't going to intercept scroll events and do our own thing (scrolljacking), we are just going to monitor the scroll behavior and update the view in response. There are a number of ways to accomplish this.

We are going to use graph-scroll.js, which is a D3 plugin. It is fairly basic, and the documentation is minimal, but it works for what we want, and will keep us from doing all the math ourselves. You will find that I have already included a copy in the repository and the starter page already includes a reference to it.

# Preparing the HTML and CSS

I have, in fact, already set up the HTML and CSS for you to save time. The graph-scroll library assumes that you will have a container wrapped around all of your material, a DOM element that holds your graphic element that you want to remain stable, and a collection of "sections" that will scroll. You will see that I have provided you with these elements already. We are going to be working with the Iris data set again, so I added some relevant text to the sections (if you haven't encountered the section tag yet, it works much like a div, the name just provides some better hints as to how it will be used).

There are a couple of things to notice: The sections have large margin-bottom values to maintain distance between the sections. The graph div is a float and sticky. As a result, it will only scroll up to the top of the screen and then remain there until the sections have scrolled by. There is a footer at the bottom, which is large, but empty. I'll admit it -- this is a hack. On my large screens, I can see the whole page, and thus there is no scrolling. The empty footer is just forcing the page to be long enough to require scrolling. This is not ideal, but it works (a slightly less hacky solution would be to compute how much empty space we need and size the footer accordingly).

Go ahead and open the page and watch what happens when you scroll. The text is pale, because we are going to selectively vary the opacity as sections come inot focus. Open up the inspector and use the "Elements" page to select the various elements and see their dimensions. Bear in mind that this is just one way to achieve this particular effect.

# Adding in graph-scroll.js

At the moment, all we have is pure HTML/CSS. We are going to add in the graph-scroll.js functionality now. In truth, this doesn't have an enormous amount of functionality. All it does is watch the scroll events and try to detect when a new section is "in focus". When it is, it gives it the class graph-scroll-active (this is how we change the opacity). It also fires off a new active event, which we can catch and handle.

To start this up, add the following to your code (inside the then block of d3.csv()):

const gs = d3.graphScroll()
    .container(d3.select("#container"))
    .graph(d3.select("#graph"))
    .eventId('sec1_id')
    .sections(d3.selectAll("#container #sections > section"))
    .on("active", function(i){
        console.log(i);
    });

This block of code creates the graphScroll object. Note that we pass it D3 selections so that it knows what the main container, graph and sections are. We also use .on to provide an event handler. The handler will be passed the index of the new section.

Reload the page. The sections should now be full opaque when they are "on deck". You will also see the index number of the section printed on the console.

# Responding to section changes

The best approach is to write a short function that handles configuration for each section. You can then store these functions in an array, and the event handler then can call the correct function using its index parameter.

Write three functions, one for each of the three sections. In each one, print out to the console which function is being called.

Create an array called vis_stepscontaining your function names.

Update the event handler to call the appropriate function (you should be able to do this with vis_steps[i]()).

Now, we need to give these some functionality.

# The visualization

To save time and keep you focused, I wrote the visualization code already. You will find it in "iris_plot.js", which is also already included on the page.

Add this line to "index.html" immediately after the svg is created:

const iris_plot = create_iris_plot(svg, 450, 450, data);

As you can probably guess, this creates a scatterplot using svg as a parent, with dimensions 450x450 and data as the data source. If you reload the page, a familiar plot will appear (complete with tooltips).

Of course, we want to be able to adjust this plot as appropriate for the different sections. Fortunately, iris_plot is designed to be configurable. If you take a look at the function, you will find that I used a design pattern similar to the one advocated by Mike Bostock in Towards Reusable Charts, which I've mentioned to some of you previously, but not pressed since it is a bit different (and incidentally is how much of D3 is designed). Mine is not an exact duplicate, but it is in the same vein.

create_iris_plot actually returns a function that can be called to update the contents of the chart (i.e., invoking iris_plot() as a function will update the contents). In addition, I've added configuration functions to set the x and y metrics (x_metric() and y_metric() respectively), the color (color()), and the opacity (opacity()). So, for example, if I wanted to change the plot to display "petal_length" on the x axis, and "sepal_length" on the y, I could write:

// note that chaining is supported
iris_plot.x_metric("petal_length")
.y_metric("sepal_length");

iris_plot();

Note that all of the updates are done using transitions so we get a nice flow as we scroll.

# Configure the visualization

In the second section, we would like to add color to the visualization, so we can see that the visible cluster is in fact a distinct iris species.

In the function that handles the second section, add code to use color_scale to set the color (color((d)=>color_scale(d.class)) -- the value we pass in here is used directly in a style(fill, color), so this can be constants or functions), and then call iris_plot() to update the view.

When you do that, you will find that the chart gets color when we scroll down to the second section. Hooray! But if we scroll back to the first section, it stays colored. Boo!

One of the reasons to use scrolling is to give the user the feeling of control. It should never feel like scrolling created an irreversible change.

Add code to the function associated with the first section that sets the color to "darkgray". Now you should be able to scroll back and forth between the two sections, and the color will change appropriately when you do.

For the third section, we want to change the axes from the default of "sepal_width"x"sepal_length" to "petal_width"x"petal_length". Use the x_metric and y_metric functions to make this change for the third section.

Again, we want to make sure that we can scroll back up consistently. So, set the x_metric and y_metric to "sepal_width" and "sepal_length" for the first two sections. Why do both sections? It is possible that we have a speedy scroller that skips the second section. In general, it is best to fully specify the configuration at each stage. Go ahead and do this -- you should now have x_metric, y_metric, and color configured for all three stages.

# Completely changing the visualization

What if you want to completely change the chart? The "graph-scroll.js" library will actually allow you to make a second scroll region + chart on the page (see the documentation for an example). However, we are going to put our second chart right in the same SVG object, swapping it out for the scatterplot (sort of). In truth, what we will do is add both of them at the same time and then change the opacity to selectively fade them in and out.

First, we need a new section. Following the structure of the original three, create a new section in the HTML. The text can be whatever you like.

Second, write a fourth configuration function and add it to vis_steps.

Now we need a new visualization. The observant among you will have noticed that there is addition code in "iris_plot.js" for drawing bar charts. Add this line to your code right before the line that declares and defines iris_plot (it is important that it goes before so that the tooltips continue to work):

const bar_charts = create_mini_barcharts(svg, 450, 450,data);

This works just like the create_iris_plot() function, except that only the opacity is configurable.

By default, the opacity is 0, so in your new configuration function, set the opacity to 1.

Of course, you will need to set the opacity to 0 in all of the other functions.

Similarly, you should set the opacity of the scatterplot to 0 in this final function, and explicitly set it to 1 in all of the others. We could be cleverer about this to avoid the redundancy of specifying every single attribute in every section, but this is visible and easy to understand.

Try it out -- make sure you can scroll back and forth and each section shows the correct visualization.

Once it is all working, you are done!

As I said earlier, this is but one way to create this scroll effect. Do not feel that you need to use this one. Nor should you feel obliged to to a scrolling interface. A stepper works in much the same fashion (here is a tutorial from Jim Vallandingham). It is also fine if you don't use either of these techniques.

Last Updated: 11/28/2018, 5:21:40 PM