Data visualization with Observable JavaScript

Learn how to make the most of Observable JavaScript and the Observable Plot library, including a step-by-step guide to eight basic data visualization tasks in Plot.

Data visualization with Observable JavaScript.

Built-in reactivity is one of Observable JavaScript's biggest value adds. In my two previous articles, I've introduced you to using Observable JavaScript with R or Python in Quarto and learning Observable JavaScript with Observable notebooks. In this article, we get to the fun part: creating interactive tables and graphics with Observable JavaScript and the Observable Plot JavaScript library.

Create a basic Observable table

I usually think of a table as an “output”—that is, a helpful way to view and explore data. In Observable, though, a basic table can also be considered an “input." That’s because Observable tables have rows that are clickable and selectable by default, and those selected values can be used to affect plots and other data on your page. This helps explain why the function  Inputs.table(your_dataset) generates a table.

The default table is sortable if you click column headers, and shows several rows along with a scroll bar. 

To specify the number of rows you'd like visible in the scrollable window, add the rows argument. The argument shown here displays the first 20 rows in the scrollable window:


Inputs.table(mydata, {
rows: 20
})

Note that table options are enclosed in curly braces, even if there's only one option.

To choose specific columns to appear in a table, add a columns option to Inputs.table() with the syntax:


Inputs.table(mydata, {
 columns: ["column_name_1", "column_name_2", "column_name3"]
 })

Note that columns: is followed by a JavaScript array, which is created by [] brackets similar to Python and not c() as in R. Column names need to be in quotation marks.

To rename columns, use header: with the following syntax:


header: {
   original_name1: "NewName1",
   original_name2: "NewName2"
}

To combine selecting and renaming columns, use both  columns: and header::


Inputs.table(mydata, {
  columns: [
    "column_name_1",
    "column_name_2"
  ],
header: {
   original_name1: "NewName1",
   original_name2: "NewName2"
}
})

The resulting default will look something like this, using data for US states and their populations:

Table with columns for State, PctChange, Pop2020, and Pop2010. Sharon Machlis, Foundry

Figure 1. An Observable default table using Inputs.table().

 

Several other options are available for Inputs.table(), including sorting and reverse-sorting, which you can do with the syntax: sort: "ColumnName", reverse: true.

If you’re coding in a hosted notebook on ObservableHQ.com, you'll see that there are other built-in table types available for a cell, such as Data Table cells. Some of these types are part of the Observable web platform. They are not standard JavaScript functions and you won't be able to use them in a Quarto document. How can you tell? If you don’t see a line of code pop up after you create a table, it’s probably not available off-platform unless you code it yourself.

Create interactive filters for data in Observable

One of Observable's biggest value adds is its built-in reactivity. To create an interactive filter, the syntax is usually along the lines of


viewof new_variable_name = Inputs.the_filter_type()

where the_filter_type is one of the built-in Observable filter types.

Filter types include checkbox, radio, range (for a slider), select (for a dropdown menu), search, and text (for free-form single-line text). Arguments for the Inputs filter function depend on the filter type.

viewof makes the filter reactive. In his notebook A brief Introduction to viewof, Mike Bostock, CTO and founder of Observable, Inc., writes: "The way to think about viewof foo is that, in addition to the viewof foo that displays the input element, viewof creates a second hidden cell foo that exposes the current value of this input element to the rest of the notebook."

Inputs.search()

Inputs.search() creates both a text search box and a reactive data set that is automatically filtered and subsetted based on what a user types into the box. For R Shiny users, it’s as if a single function created both the text field UI and the server-side code for the reactive data set.

The syntax is:


viewof my_filtered_data = Inputs.search(mydata)

To use the reactive data set elsewhere in your code, simply refer to my_filtered_data instead of mydata, such as:


Inputs.table(my_filtered_data)

The above code generates a table from my_filtered_data. The data set and table will update whenever a user types something into the search box, looking for matches across all the columns.

You can use that filtered data set multiple times on a page in various kinds of plots or in other ways.

Inputs.select()

Inputs.search() is somewhat of a special case because it is designed to filter a data set in a specific way, by looking across all columns for a partial match. That's simple but may not be what you want, since you may need to search only one column or create a numerical filter.

Most other Observable Inputs require two steps:

  • Create the input.
  • Write a function to filter data based on the value of that input.

For example, if you want a dropdown list to display possible values from one column in your data and filter the data by that selection, the code to create that dropdown needs to specify what values to display and which column should be used for subsetting. The code below creates a dropdown list based on sorted, unique values from the my_column column in the mydata data set:


viewof my_filtering_value = 
  Inputs.select(my_initial_data.map(d => d.my_column), {sort: true, unique: true, label: "Label for dropdown:"})

As you might conclude from the variable name my_filtering_value, this both creates the dropdown list and stores whatever value is chosen, thanks to viewof

map() applies a function to each item in an array, in this case getting all the values in my_initial_data’s my_column

The above code uses JavaScript’s newer “arrow” (=>) format for writing functions. You could write the same code with JavaScript's  older function and return syntax, as well:


viewof my_filtering_value2 = 
  Inputs.select(mydata.map(function(d) {return d.my_column}), {sort: true, unique: true, label: "Label for dropdown:"})

Once again, don’t forget the viewof before defining Inputs if you want the code to be reactive.

Unlike Inputs.search(), which automatically acts upon an entire data set, Inputs.select() only returns values from the dropdown. So, you still need to write code to subset the data set based on those selected values and create a new, filtered data set. That code is the same as if you were filtering a static data set.

As explained in Quarto's Observable documentation: "We don't need any special syntax to refer to the dynamic input values, they 'just work', and the filtering code is automatically re-run when the inputs change." You don't need viewof before the statement that defines the data set; use it only before the Inputs.


my_filtered_data2 = 
  mydata.filter(function(d) {return d.my_column === my_filtering_value2})

If you prefer, you can use JavaScript’s older syntax with  function() and return:


my_filtered_data2 = 
  mydata.filter(function(d) {return d.my_column === my_filtering_value2})
  

You can use the filtered data set as you would a static data set, such as:


Inputs.table(my_filtered_data2)

Numerical and date filters work similarly to Input.select().

Check out the Hello Inputs and Observable Inputs notebooks for more about Inputs.

Include a variable’s value in a text string

Including the value of a variable in a text string to generate dynamic text can be useful if you want to create a graph headline or summary paragraph that changes with your data. If a variable is defined in an ojs code chunk, you can include that variable’s value within a Quarto ojs code chunk by enclosing the variable name with ${}, such as:


md`You have selected ${my_filtering_value2}.`

Here, md indicates that what’s contained within the backticks should be evaluated as Markdown text, not JavaScript code. You can also use html before the backticks if you want to write HTML that includes a variable value, such as:


html <p>The value of x is <strong>${x}</strong>.

Note that md and html are only needed in Quarto documents. If you are using a hosted notebook at ObservableHQ.com, you can choose Markdown or HTML for the cell mode so you don’t need to signify md or html.

You should use backticks on ObservableHQ.com to signify that the contents are text and not code if you are storing text in a variable inside a notebook's JavaScript cell.

1 2 Page 1
Page 1 of 2