CS-GY 6313 - Information Visualization
New York University
2026-10-16
| User Interface | Graphics Library | Notebook |
|---|---|---|
| observablehq.com | D3 | Week 7 Lab Notebook |
Today we will be a exploring the following topics:
Image souce: GeekForGeeks
Two broad ways to manipulate the DOM:
<button> and <input>.Using HTML and JavaScript, select any HTML elements in the DOM that match a query.
Then, attach an event listener that “listens” for events that happen to those elements.
By this point, you’ll want to be familiar with the following concepts:
<button>, <textarea>).#id or .class.| Input Type | HTML Tag | Description |
|---|---|---|
| Button | <input type="button"> or <button> |
The classic button! |
| Checkbox | <input type="checkbox"> |
Toggle any from a set |
| Radio | <input type="radio"> |
Choose only one from a set |
| Select | <select> and <option> |
Choose one or any from a set (drop-down, usually) |
| Text | <input type="text"> |
Enter freeform single-line text |
Interactions necessitate that you have HTML elements. Usually, you’ll just add these directly into your HTML code. However, more dynamic interfaces will usually generate them via JavaScript or TypeScript. You can also do the same with D3.js.
// Button Element Creation
let button = document.createElement("button");
button.setAttribute("id","js_button");
button.textContent = "Button created with D3.js";
// Event Handling
button.addEventListener("click", () => {
console.log("Hello JS/TS button!");
});
// Unique to Observable/Framework
display(button);Be careful of unique identifiers like id! Above, we distinguished our buttons using different id’s: “js_button” and “d3_button”.
JavaScript natively allows us to access the DOM and create, modify, or delete HTML elements. This ability usually extends to TypeScript and other libraries such as D3.js. You’ll usually encounter them in the form of:
Mouse events are perhaps the most direct way of letting a user interact with data. We’ve encountered a simple example of mouse events long before: the vl.tooltip() element we used in Vega-Lite.
JavaScript gives us access to some core event types associated with the mouse, including:
mousedown / mouseup: These events fire when a mouse button is pressed down or released over an element, respectively.click: Fires after both a mousedown and mouseup event occur on the same element with the main mouse button (usually the left).mousemove: Fires continuously as the mouse pointer moves over an element, providing positional information (coordinates) about the cursor.mouseover: Fires when the pointer moves over an element (or one of its children).mouseout: Fires when the pointer leaves an element.These definitions and more are defined here. There are definitely some nuances you need to remember (e.g. the difference between mouseover v.s. mouseenter).
When we call mouse events such as mousemove, event.clientX and event.clientY are how we can access the mouse’s position. This changes slightly using D3.js, but the overall logic is the same.
However, event.clientX and event.clientY refer to the absolute position of the mouse on the browser window. If we want to get the relative position of the mouse with respect to its parent (or any other element on the webpage), then we need a different procedure.
We created an SVG with a single circle populating it. We also have a
<svg> element.Transitions aren’t so much of an interaction as they are a visual way to communicate a change in state. Transitions are very important for two reasons:
There are many research papers on this topic; I recommend “Animated Transitions in Statistical Data Graphics” by Jeffrey Heer, George Robertson - particularly, their video attached to their report.
The D3.js implementation for transitions is very easy, because a lot of animation is abstracted away using D3.js’s transition() function. We highly recommend that you read up on documentation surrounding D3.js’ transitions!
// Create our circle, append it to `svg`, and define its properties
svg.append("circle")
.attr("cx", 150)
.attr("cy", 100)
.attr("r", 15)
.attr("fill", "steelblue")
// Handle when the mouse enters the circle
.on("mouseenter", function() {
d3.select(this) // D3.js selects `this` (i.e. the circle)
.transition() // Declares the transition
.duration(500) // Sets the duration of the transition
.attr("r", 40); // Makes the transition aim for a radius of 40px
})
// Handle when the mouse leaves the circle
.on("mouseleave", function() {
d3.select(this) // D3.js selects `this` (i.e. the circle)
.transition() // Declares the transition
.duration(500) // Sets the duration of the transition
.attr("r", 15); // Makes the transition aim for a radius of 15px
});We previously covered Pie Charts in Exercise #6. In this case, we’re working with Donut Charts - a variant of Pie Charts that has an inner radius.
We initially have a donut chart depicting ice cream statistics. By default, each arc has an opacity of 75%. We want to animate this pie chart such that:

Indirect manipulations are useful for dashboards where users may expect some kind of controls for modifying visualizations. This requires some forethought about what kind of HTML inputs you think users would like to have access to, such as search bars for filtering or buttons to modify the appearance of a chart.
If you’re struggling, one good practice is to separate between dynamic and static variables - which values, scales, etc. can remain the same, and what needs to be updated whenever we select a button, type a text into a search bar, etc?
.join()So far, we’ve usually used .enter().append() to create new SVG elements for data items that were not mapped to visual elements… at least, you should have been using this.
However, this no longer applies here, because we’ve already rendered our points, bars, lines, etc., so calling .enter().append() will not actually change the bars. We instead need to use .join(), which allows us to modify existing HTML elements that are already mapped to data.
svg.selectAll(".bar") // Search for all elements with the class "bar"
.data(counts, d => d.island) // Set the data to our `counts` Array;
.enter() // Select all data items that do not have a ".bar" element mapped to them
.append("rect") // Create a rect
.attr("class", "bar") // Give it a class name.
.attr("x", d => x_scale(d.island)) // Set the x and width of the rectsvg.selectAll(".bar") // Search for all elements with the class "bar"
.data(counts, d => d.island) // Set the data to our `counts` Array;
.join("rect") // Create a rect for every data item, even for those that were already created
.attr("class", "bar") // Give it a class name.
.attr("x", d => x_scale(d.island)) // Set the x and width of the rectThe Palmer Penguins Dataset is a useful tool for this part because penguin statistics can be grouped in several different ways. Like we did before, we’ll be creating bar charts that shows the counts of penguins per island.
We’ve provided most of the legwork for rendering chart. Specifically, the buttons and SVG elements are already created, and we already have a helper function updateChart(STRING) that is called when we want to update the bars. However, we need to update this operation:
updateChart(STRING) to filter data based on the STRING passed to it, and then update the way the bars are rendered to account for the change.updateChart(STRING) to the buttons that are rendered alongside the chart.There are a multitude of different examples of interactive visualizations out there. Here are some standout examples. Pay attention to how they code variables, track mouse events, and implement any transitions and such.
No more exercises! But we will be transitioning to Mini-Projects!
Mini-Project #1 is due in 2 weeks, on October 29th @ 11:59pm
