Week 7

This week's exercises are all about interactivity. You will update last week's gapminder visualization to support animating over the years and letting the user select which continent is shown.

Exercise 1 (1 pt.)

For this exercise, you have to edit the file src/routes/week_7/exercises/ex_1.svelte. Any changes you make to that file should show up below.

HTML provides several types of input tags as wel as a dedicated button tag that you can use to let people interact with your visualisations. Svelte also makes it easy to program what each input has to do with the on-directive.

Create two buttons, one that keeps track on the number of times you've clicked on it and one to reset the counter. Last week, we used bootstrap to style our buttons, you can do the same here.

Exercise 2 (1 pt.)

For this exercise, you have to edit the file src/routes/week_7/exercises/ex_2.svelte. Any changes you make to that file should show up below.

Interactivity is where Svelte shines; it automates a lot of processes that we want to update when values change. However, it can sometimes be confusing to reason about a svelte component. We do not always see at a glance what code will be updated when values change. It is important to remember that code in the script of a component is evaluated only when the component is constructed. Code in the HTML markup is evaluated any time a variable that is mentioned within the curly braces changes. If you want your script to re-evaluate certain statements when a variable changes, you have to use the $: notation:

<script>
  export let myVariable;
  console.log('This message is printed only once!')
  $: console.log(`This messages prints every time myVariable changes: ${myVariable}`);
</script>

Remember that only variables that are mentioned within the scope of the $: statement are monitored for changes! Also know that two reactive statements do not cause an infinite loop when they change each other's values:

<script>
  export let counter1=0;
  export let counter2=0;

  const increment1 = (_) => counter1+=1;
  const increment2 = (_) => counter2+=1;

  $: increment1(counter2);
  $: increment2(counter1);
</script>

In this example, if you change the value of counter2 both counters will be incremented once. If you change the value of counter1 only counter2 will be incremented.

Sometimes, you also need to schedule the evaluation of a function, so that it will be evaluated some time in the future. JavaScript's setInterval() function can be used for that purpose. You give it a function and a delay-time and it will keep on calling your function with your specified pause in between calls until you either call clearInterval() or close the page.

For this exercise, you have to build a countdown light like they use in F1 racing. There are five lights that are off initially, then they turn red one-by-one from the right to the left. After all the lights are red, they switch to green simultaneously. The necessary data arrays are already created. Use the points arrary as x-coordinates of the circle elements and index as the index of the left-most light that should be on. If a light is on set the opacity to 1, otherwise use 0.3. You should use the second argument of the each-block to capture the index of the current iteration. Write a function that reduces the index every second. Once all lights are on, change color to darkgreen and stop the function from reducing the index further. The animation should only start when the visualisation is visible! Hint: remember how you loaded data when the component was first rendered last week!

Exercise 3 (1 pt.)

For this exercise, you have to edit the files src/routes/week_7/exercises/ex_3.svelte, src/routes/week_7/exercises/_ex_3_scatterplot.svelte, and src/routes/week_7/exercises/_ex_3_controls.svelte. Any changes you make to those file should show up below.

It's time to make the gapminder scatterplot interactive. We have already prepared a static version for you. Note that we load the data in ex_3.svelte as you learned last week. At the end of this exercise, we want to be able to show all the years in the data as an animation. To do that, you have to add a play-pause button, a reset button, and a slider that indicates and controls which year is shown. The label of the slider should contain the currently shown year, so it is easy to read! Finally, you have to add a drop-down menu which can be used to filter which continent's countries are shown.

The visualization uses three components, each with a separate responsibility:

So, perform these steps:

  1. In _ex_3_controls.svelte, add component properties for the currently selected year and continents. The year should be stored as an index into the data array (i.e., the value 0 corresponds to 1800 and the value 10 to 1810). Also add the described buttons, slider, and drop-down menu that manipulate the value of these component properties. Note, the animation should loop-back to the start when it reaches the end. The reset button should stop the animation if it is active and set 1800 as the selected year. Hint: to create the animation, remember how you scheduled a function to be executed in an interval in the last exercise!
  2. In ex_3.svelte, observe the selected year and continent as exposed by _ex_3_controls.svelte using a bind. Then, define a function that extracts the selected data using the selected year and continents variables. Finally, only pass the selected data to the _ex_3_scatterplot.svelte component.

You should now have an interactive GapMinder visualisation! As in Exercise 1, you can use boostrap to make the input elements look nice. Hint: does your visualisation break when the slider reaches the end? Then you probably made an off-by-one error by allowing the slider's value to reach 1 item passed the end of the data array!

Loading the data, please wait...

Exercise 4 (1 pt.)

For this exercise, you have to edit the files src/routes/week_7/exercises/ex_4.svelte and src/routes/week_7/exercises/_ex_4_scatter.svelte. Any changes you make to those file should show up below.

We have prepared a visualization that shows two scatter plots using the /data/cars-2.csv dataset. Note that we construct two _ex_4_scatter.svelte components using a spread operator in the then-block of ex_4.svelte:

<script>
  import Scatter from './_ex_4_scatter.svelte';
  ...
  const s1 = {
    x: (d) => +d.Horsepower,
    y: (d) => +d.Acceleration,
    xLabel: 'Horsepower',
    yLabel: 'Acceleration'
  };
</script>
...
    <Scatter {data} {...s1} />
...

Here, s1's properties (x, x, xLabel, and xLabel) are passed to the Scatter component as individual component properties. The Scatter component uses them to extract the values it has to use for the x- and y-coordinates, and puts the labels on their respective axes. This way, we can re-use the Scatter component for two different scatter plots.

In this exercise, you will add brushing and linking to these two scatter plots, such that you can draw a rectangle in one plot, and the points that lie within that rectangle get highlighted in both plots. In the case that you draw a rectangle in both plots, only the points that lie within both rectangles should get highlighted.

d3.js provides the functionality that you need to draw these rectangles in the d3-brush module. Like, with the axes, calling d3.js' brush() function gives you a function that takes a selected handle to an svg group element and adds the brushing functionality to that element. You can use Svelte's actions to apply the brush to an element, just as we did for the axes. However, without configuration, the brush will not do anything. You have tell the brush what it has to do when a brush starts, moves, and ends. To do that, you have to call the .on() function on the brush:

brush()
  .on('start', doStart)
  .on('brush', doMove)
  .on('end', doEnd)

So, to implement brushing and linking, peform these steps:

  1. Define two component properties in _ex_4_scatter.svelte. One that stores an array of boolean values indicating which points are selected in both scatter plots and one that stores a similar array for the points selected in just the local component.
  2. In ex_4.svelte use a bind to observe the local selections of both plots. Then reactively define the global selection so that only points that are selected in both plots are set to true. Pass the global selection as a normal component property to the _ex_4_scatter.svelte components.
  3. We already defined the range variable for you that will store the range of the selection rectangle if there is an active selection or null otherwise. Configure a d3-brush so that the selection property of the argument it passes to your callbacks is written to our range variable when the brush starts, moves, and ends. The values the brush gives are in the coordinate system of the element it is applied to. So, apply the brush to the SVG group element that contains our datapoints. This ensures that we do not have to deal with the margins of our svg element when we compute whether a point lies within the selection rectangle.
  4. Since we need to know the x- and y-coordinates of our datapoints to determine whether they are within a selection and to plot them on the screen, it is a good idea to pre-compute these coordinates in the script of _ex_4_scatter.svelte. Define a coords variable that stores an {x, y} object for each data point. Use it to plot the circles instead of re-computing the x- and y-coordinates in the markup section.
  5. Use the range and coords variable to reactively update the values of the local selection such that all data points are set to true if there is no active selection and only the points that lie within the rectangle are set to true when there is an active selection.
  6. Use the global selection to set both the stroke and fill to darkgray, the fill opacity to 0.3, and the stroke opacity to 0.5 when a point is not selected.

Now you should have a working brushing and linking implementation!

Loading the data, please wait...