Types of Interactivity in D3

UI input elements and SVG interaction events

Code, Recording

  1. Any HTML page can support text, numeric, button, select, and slider inputs, just like in Shiny. In fact, behind the scenes, Shiny inputs are simply R wrappers of plain HTML input elements. For example, the HTML code below creates an example of each of these inputs.
    <input type="text"> Enter Something</input>
    <button type="button">Click Me</button>
    <label for="dropdown">Select an option...</label>
    <select name="Select an Option" id="dropdown">
      <option value="optA">Option A</option>
      <option value="optB">Option B</option>
      <option value="optC">Option C</option>
    </select>
    <input type="range" min="0", max="1" step="0.1">This is a slider</input>
    

    This is what the page looks like,

The default styling for input elements are not that attractive. Fortunately, many people have come up with CSS rules to create more attractive components. For example, by applying classes defined in Bootstrap (the same library behind bslib), almost the same HTML code as above yields the more modern page below.

  1. An aside about select inputs: We can define the selection options using a data bind. Contrast the manual implementation [1, 2] of the gapminder visualization that enters the options in the HTML code with this binding-based alternative [3, 4] that uses the function setup_inputs to create input elements using an array of continent names.
  2. In addition to these separate interface elements, we can “listen” for specific kinds of user interactions. In web programming, objects that register specific types of user interactions are called event listeners. For example, we can use a listener to register hover events when the mouse moves over a specific HTML element. Let’s look at basic versions of these events on a canvas with just a few randomly located circles.
  1. Click events are defined using an .on(“click”, function) added to a D3 selection. For example, this will print to the console every time the second circle is clicked.
function f2() {
  console.log("Clicked!")
}

d3.select("#circle2")
  .on("click", f2)

Note that, if we wanted to print whenever any element from a selection was clicked, we simply add .on("click", function) to the full selection. For example, we could use this to run a function whenever any point within a scatterplot is clicked (the same principle applies to all listeners in these notes).

  1. If we just want to detect whether the user’s mouse has moved over an element, we can use .on("mousemove", function). We can also keep track of the user’s mouse position using d3.pointer(event), where event refers to an event object that is pass into the function by default,
  function f4(event) {
    console.log(d3.pointer(event))
  }

  d3.select("#circle4")
    .on("mousemove", f4)
  1. We can even distinguish between when the mouse has just entered or exited an SVG element using on("mouseover", ...) and .on(“mouseout”, ...).
  2. Brushes are defined using d3.brush(). They can be applied to a <g> element using .call() applied to the group (otherwise, they will not appear).
      let brush = d3.brush().on("brush", f)
      d3.select(".brush").call(brush) // the <g> element was given class "brush"
    
  1. The .on("brush", f) element at the end of the brush definition will make sure to call f every time the brush is moved. Like hover events, we can distinguish between when brushes have been just moved or whether the user has lifted their mouse up. In Shiny, we were only able to update a brush selection after the users’ mouse had moved, so this is going to allow us to create smoother visualizations.
  2. To access the pixel coordinates of the brush’s bounding box, we can use the selection attribute of the event object that is passed into the brush function. For example, the code above is printing the pixel coordinates each time the brush is modified.
  function f(event) {
    console.log(event.selection)
  }
  1. Finally, we can create multiple brushes simultaneously, which had been impossible in Shiny. This is handy for sketching out complex shapes in time series, like in the Time Searcher.