Observable Framework 1.6.0 GitHub️ 1.8k

Observable Inputs

Observable Inputs provides “lightweight interface components — buttons, sliders, dropdowns, tables, and the like — to help you explore data and build interactive displays.” Observable Inputs is available by default as Inputs in Markdown, but you can import it explicitly like so:

import * as Inputs from "npm:@observablehq/inputs";

Or, just import the specific inputs you want:

import {button, color} from "npm:@observablehq/inputs";

Inputs are typically passed to the view function for display, while exposing the input’s value generator as a reactive variable. Options differ between inputs. For example, the checkbox input accepts options to disable all or certain values, sort displayed values, and only display repeated values once (among others):

const checkout = view(
  Inputs.checkbox(["B", "A", "Z", "Z", "⚠️F", "D", "G", "G", "G", "⚠️Q"], {
    disabled: ["⚠️F", "⚠️Q"],
    sort: true,
    unique: true,
    value: "B",
    label: "Choose categories:"
  })
);
checkout

To demonstrate Observable Inputs, let’s look at a sample dataset of athletes from the 2016 Rio olympics via Matt Riggott. Here’s a table input — always a good starting point for an agnostic view of the data:

Inputs.table(olympians)
Tables can be inputs, too! The value of the table is the subset of rows that you select using the checkboxes in the first column.

Now let’s wire up the table to a search input. Type anything into the box and the search input will find the matching rows in the data. The value of the search input is the subset of rows that match the query.

A few examples to try: [mal] will match sex = male, but also names that start with “mal”, such as Anna Malova; [1986] will match anyone born in 1986 (and a few other results); [USA gym] will match USA’s gymnastics team. Each space-separated term in your query is prefix-matched against all columns in the data.

const searchResults = view(Inputs.search(olympians, {
  datalist: ["mal", "1986", "USA gym"],
  placeholder: "Search athletes"
}))
Inputs.table(searchResults)

You can sort columns by clicking on the column name: click once to sort ascending, and click again to sort descending. Note that the sort order is temporary: it’ll go away if you reload the page. Specify the column name as the sort option above if you want this order to persist.

For a more structured approach, we can use a select input to choose a sport, then array.filter to determine which rows are shown in the table. The sort and unique options tell the input to show only distinct values and to sort them alphabetically. Try comparing the [gymnastics] and [basketball] sports.

const sport = view(
  Inputs.select(
    olympians.filter((d) => d.weight && d.height).map((d) => d.sport),
    {sort: true, unique: true, label: "sport"}
  )
);
Plot.plot({
  title: `How ${sport} athletes compare`,
  marks: [
    Plot.dot(olympians, {x: "weight", y: "height"}),
    Plot.dot(olympians.filter((d) => d.sport === sport), {x: "weight", y: "height", stroke: "red"})
  ]
})

You can pass grouped data to a select input as a Map from key to array of values, say using d3.group. The value of the select input in this mode is the data in the selected group. Note that unique is no longer required, and that sort works here, too, sorting the keys of the map returned by d3.group.

const sportAthletes = view(
  Inputs.select(
    d3.group(olympians, (d) => d.sport),
    {sort: true, label: "sport"}
  )
);
Inputs.table(sportAthletes)

The select input works well for categorical data, such as sports or nationalities, but how about quantitative dimensions such as height or weight? Here’s a range input that lets you pick a target weight; we then filter the table rows for any athlete within 10% of the target weight. Notice that some columns, such as sport, are strongly correlated with weight.

const weight = view(
  Inputs.range(
    d3.extent(olympians, (d) => d.weight),
    {step: 1, label: "weight (kg)"}
  )
);
Inputs.table(
  olympians.filter((d) => d.weight < weight * 1.1 && weight * 0.9 < d.weight),
  {sort: "weight"}
)

For more, see the individual input pages: