How this was made
In this article I share stylised versions of the select and check box inputs that can be used for more elegant storytelling with data. Sometimes I would like to make inputs feel like they are part of a paragraph. The idea is that this doesn’t take the reader out of the story and can make the inputs more intuitive. For my inputs I use observable with css styling. To illustrate the inputs I use the mpg
dataset included in ggplot2
.
Code
# R
library(stringr)
library(tidyverse)
data('mpg', package='ggplot2')
<- mpg |>
mpg mutate(trans = case_when(
str_detect(trans,'auto')~'automatic',
str_detect(trans,'manual')~'manual',
~ NA_character_
T
))ojs_define(mpg = mpg)
Code
= [
cyl_categories name: "four", value: "4"},
{name: "five", value: "5"},
{name: "six", value: "6"},
{name: "eight", value: "8"}
{
]
= [
trans_categories "automatic",
"manual"
]
= Inputs.select(cyl_categories, {
viewof cyl_select label: "",
format: (t) => t.name
})
= Inputs.select(trans_categories, {
viewof trans_select label: ""
})
Default inputs
Code
= Inputs.select(cyl_categories, {
viewof cyl_select_old label: "Cylinders",
format: (t) => t.name
})
= Inputs.select(trans_categories, {
viewof trans_select_old label: "Transmission"
})
= [...new Set(transpose(mpg).map(x=>x.manufacturer))];
all_manufacturers = Inputs.checkbox(all_manufacturers, {
viewof manufacturer_select_old label: "Manufacturer",
value: all_manufacturers
; })
The default Observable inputs are functional and often appropriate. However, sometimes I would like the inputs to flow with the text.
My inputs
Notice how the following paragraph makes it intuitive how the filtering works, is arguably more elegant, and still has the same functionality.
Code
html`<span style="line-height: 1.5rem;">In the following plot we highlight cars that have <span style="white-space: nowrap;"><span class="dropdown-container" style="width: 5rem;">${viewof cyl_select}</span> cylinders, </span> <span style="white-space: nowrap;"><span class="dropdown-container" style="width: 7rem;">${viewof trans_select}</span> transmission, </span> and were manufactured by <span class="checkbox-styling">${viewof manufacturer_select}</span></span>`
Code
= transpose(mpg).filter(x=>x.cyl==cyl_select.value&&x.trans==trans_select);
mpg2 = mpg2.filter(x=>manufacturer_select.includes(x.manufacturer));
mpg3 = transpose(mpg).map(x => ({
mpg4 ...x,
highlight: x.cyl==cyl_select.value&&x.trans==trans_select&&manufacturer_select.includes(x.manufacturer)
;
}))= [...new Set(mpg2.map(x=>x.manufacturer))];
manufacturers = Inputs.checkbox(manufacturers, {
viewof manufacturer_select label: "",
format: (t) => manufacturers.length==1?
html`<span class="checkbox-option"><span class="checkbox-background">${t}</span>.</span>`
: t==manufacturers[manufacturers.length - 1]?html`<span class="checkbox-option">or <span class="checkbox-background">${t}</span>.</span>`:
html`<span class="checkbox-option"><span class="checkbox-background">${t}</span>${manufacturers.length==2 ? " " : ", "}</span>`,
value: manufacturers
; })
Code
.plot({
Plotmarks: [
.dot(mpg4.filter(x=>!x.highlight), {x: "displ", y: "hwy", stroke: 'lightgrey', strokeOpacity: 0.4}),
Plot.dot(mpg4.filter(x=>x.highlight), {x: "displ", y: "hwy", stroke: 'red', strokeOpacity: 0.4})
Plot,
]x: {label: 'engine displacement, in litres'},
y: {label: 'highway miles per gallon'}
; })
Code
html`
<style>
tr:has(td span.highlight-this-row) {
background-color: yellow;
}
</style>
`
Code
= mpg4.map(({manufacturer, model, year, displ, hwy, cyl, trans, highlight}) => ({manufacturer, model, year, displ, hwy, cyl, trans, highlight}));
mpg5 = {
table let div = d3.create("div").attr("id", "mpg_table");
.append(() => Inputs.table(mpg5, {rows: 18.5,
divformat: {highlight: x=>x?html`<span class='highlight-this-row'>true</span>`:"false"}}
;
))return div.node();
}
Code
html`
<style>
/* The drop downs */
.dropdown-container {
display: inline-block;
position: relative;
line-height: 1;
}
.dropdown-container select {
border: none;
border-bottom: 2px dotted grey;
background: none;
font-size: 1em;
padding: 0 5px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
line-height: 1;
vertical-align: baseline;
text-align: center;
}
.dropdown-container select:focus {
outline: none;
}
.dropdown-container::after {
content: '▼';
font-size: 0.7em;
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
.dropdown-container select {
padding-right: 20px;
}
/* The multi-select */
.checkbox-styling form {
display: inline;
white-space: normal;
}
.checkbox-styling form > div {
display: inline;
}
.checkbox-styling input[type="checkbox"] {
display: none;
}
.checkbox-styling > form > div > label {
margin-right: 10px;
}
.checkbox-styling > form > div > label:last-child {
margin-right: 0px;
}
.checkbox-styling .checkbox-option .checkbox-background {
display: inline-block;
height: 1.5rem;
line-height: 1.5rem;
padding: 0px 2px;
margin: .25rem 0px;
margin-right: 0px;
cursor: pointer;
border: none;
border-radius: 3px;
box-shadow: none;
transition: border 0.3s, box-shadow 0.3s, opacity 0.3s;
}
.checkbox-styling input[type="checkbox"]:not(:checked) + .checkbox-option .checkbox-background {
opacity: 0.5;
text-decoration: line-through;
}
.checkbox-background {
background: lightgrey;
color: black;
}
/* Hide checkboxes from table */
div#mpg_table input {
display: none
}
</style>
`
I hope that this article has made you think about how inputs can be more engaging and better integrated into storytelling with data.