Making Iroshizuku Interactive with Observable
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
One tiny dataset, another rabbit hole 🐇🕳️
In the previous post, I turned 24 Pilot Iroshizuku fountain pen inks into a tiny shop using ggplot2.
But while sorting the inks by colour, I started wondering what it would look like if I could rearrange them interactively.
I had also been meaning to try something completely new to me: Observable JS inside a Quarto document.
So this post is partly an ink experiment and partly me figuring out how R, Observable, and Quarto fit together.
The plan is pretty small:
Take the same 24 inks, pass the data from R to Observable, and start moving things around.
And while I’m here, I want to play with two perceptual colour representations:
- HCL, which I used in the previous post
- OKLCH, which I keep seeing pop up in modern web/CSS colour discussions
Let’s see where this goes.
The data
Same 24 inks as before.
Code
iroshizuku_colors <- tibble(
ink_name = c(
"Ajisai", "Asagao", "Konpeki", "Amairo", "Kujaku", "Rikka",
"Tsukiyo", "Shinkai", "Syoro", "Shinryoku", "Suigyoku", "Takesumi",
"Fuyusyogun", "Chikurin", "Hotarubi", "Hanaikada", "Murasakishikibu",
"Yamabudo", "Momiji", "Fuyugaki", "Yuyake", "Toro", "Yamaguri", "Syungyo"
),
ink_name_japanese = c(
"紫陽花", "朝顔", "紺碧", "天色", "孔雀", "立夏",
"月夜", "深海", "松露", "深緑", "翠玉", "竹炭",
"冬将軍", "竹林", "蛍火", "花筏", "紫式部",
"山葡萄", "紅葉", "冬柿", "夕焼け", "灯籠", "山栗", "春暁"
),
hex = c(
"#1255A2", "#04318E", "#0368B4", "#00A0DF", "#028986", "#1A7DA5",
"#016D8C", "#1C3A65", "#077D5E", "#007E4F", "#037261", "#1E1D1E",
"#6A869A", "#94BD4E", "#D9DA26", "#ED7E93", "#765FA8", "#660D5B",
"#E12E2C", "#EA5A10", "#EF881F", "#F0B018", "#5B4532", "#674F4D"
),
description = c(
"Hydrangea",
"Morning Glory",
"Deep Cerulean Blue",
"Sky Blue",
"Peacock",
"Early Summer",
"Moonlit Night",
"Deep Sea",
"Dew on Pine Tree",
"Forest Green",
"Emerald",
"Bamboo Charcoal",
"Winter Commander",
"Bamboo Forest",
"Firefly Glow",
"Floating Cherry Blossoms",
"Murasaki Shikibu",
"Wild Grape Vine",
"Autumn Maple Leaves",
"Winter Persimmon",
"Sunset Glow",
"Lantern Light",
"Wild Chestnut",
"Spring Dawn"
)
)
Two ways of describing colour
In the last post I used HCL to sort the inks.
Then I remembered I had also been curious about OKLCH — mostly because I keep seeing it in CSS. So… why not add that too? 😆
Both give me some version of:
- lightness
- chroma
- hue
They aren’t the same colour space, though, so I don’t expect the numbers — or even the ordering — to match perfectly.
Which actually makes this more interesting. What happens when I give the exact same 24 colours two different perceptual coordinate systems?
Same inks, different order
The 24 inks themselves haven’t changed. The hex values are exactly the same.
But when I sort them by hue, chroma, or lightness, HCL and OKLCH don’t always agree on the order.
Some inks stay close to the same neighbours.
Others suddenly swap places.
That makes sense: HCL and OKLCH are different perceptual colour spaces, so their coordinates aren’t expected to line up perfectly. But seeing the difference as an actual row of ink swatches makes it much more tangible than comparing columns of numbers.
In other words:
same colours, different map of colour space. 🎨
And that is exactly what made me want to turn the sorting into something interactive.
Code
# HCL / polar LUV
hcl_coords <- coords(
as(hex2RGB(iroshizuku_colors$hex), "polarLUV")
) |>
as_tibble() |>
rename(
hcl_h = H,
hcl_c = C,
hcl_l = L
)
# OKLCH
oklch_coords <- farver::decode_colour(
iroshizuku_colors$hex,
to = "oklch"
) |>
as_tibble() |>
rename(
oklch_l = l,
oklch_c = c,
oklch_h = h
)
ink_data <- iroshizuku_colors |>
bind_cols(hcl_coords, oklch_coords) |>
mutate(
original_order = row_number()
) |>
select(
original_order,
ink_name,
ink_name_japanese,
description,
hex,
hcl_h,
hcl_c,
hcl_l,
oklch_h,
oklch_c,
oklch_l
)
ink_data
# A tibble: 24 × 11
original_order ink_name ink_name_japanese description hex hcl_h hcl_c hcl_l
<int> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
1 1 Ajisai 紫陽花 Hydrangea #125… 254. 70.0 36.4
2 2 Asagao 朝顔 Morning Gl… #043… 261. 67.8 24.2
3 3 Konpeki 紺碧 Deep Cerul… #036… 250. 74.5 43.1
4 4 Amairo 天色 Sky Blue #00A… 237. 76.5 62.1
5 5 Kujaku 孔雀 Peacock #028… 189. 40.4 51.4
6 6 Rikka 立夏 Early Summ… #1A7… 233. 52.4 49.0
7 7 Tsukiyo 月夜 Moonlit Ni… #016… 228. 44.5 42.5
8 8 Shinkai 深海 Deep Sea #1C3… 253. 36.9 24.4
9 9 Syoro 松露 Dew on Pin… #077… 156. 41.9 46.3
10 10 Shinryo… 深緑 Forest Gre… #007… 145. 48.9 46.3
# ℹ 14 more rows
# ℹ 3 more variables: oklch_h <dbl>, oklch_c <dbl>, oklch_l <dbl>
Code
ggplot(plot_data, aes(x = x, y = y)) +
geom_tile(
aes(fill = I(hex)),
width = 0.98,
height = 0.96
) +
geom_text(
aes(
label = label_vertical,
colour = I(text_colour)
),
family = "osaka",
lineheight = 0.85,
size = 3.5
) +
facet_wrap(
~ prop_label,
scales = "free",
ncol = 1
) +
theme_void(base_family = "osaka") +
theme(
plot.background = element_rect(
fill = "#F5F1E8",
colour = NA
),
panel.background = element_rect(
fill = "#F5F1E8",
colour = NA
),
strip.background = element_blank(),
strip.text = element_text(
size = 10,
face = "bold",
margin = margin(b = 8)
),
panel.spacing = unit(1.1, "lines"),
plot.title = element_text(
size = 12,
face = "bold",
margin = margin(b = 12)
),
plot.margin = margin(20, 20, 20, 20)
) +
labs(
title = "24 inks, 3 ways of seeing them"
)

Mostly I just want to get this little table out of R and into JavaScript so I can start moving things around in browser!
R, meet Observable 👋
This part felt slightly magical the first time it worked.
Quarto’s ojs_define() lets me create something in R and hand it over to Observable running in the browser.
So R says: Here are my 24 inks.
Observable says: Thanks. Now let me play with them.
flowchart LR
R[R 🐣<br/>prepare data] --> Q[Quarto 📦<br/>hand it over]
Q --> O[Observable ✨<br/>play in the browser]
That handoff is basically the whole experiment.
R still does the data wrangling I’m comfortable with. Quarto acts as the bridge. Observable takes over once I want the page itself to react.
Reference: https://quarto.org/docs/computations/ojs.html
The R data frame needs one small reshaping step on the Observable side.
After that, Observable can work with it like ordinary JavaScript data.
This tiny handoff was one of the things I really wanted to understand from this experiment.
R prepares the data. Observable gets to play with it in the browser.
First Observable experiment: rearrange the inks
I’m starting with something very simple.
Two controls:
Which colour space?
And:
What should I sort by?
Because Observable is reactive, changing either input automatically changes anything that depends on it.
I’ll first map the selected options to the appropriate data column.
And then sort.
For hue I want low → high around the colour wheel. For chroma and lightness, I find high → low slightly easier to read.
The interactive palette
For my first Observable visualization, I’m deliberately keeping the geometry boring.
Each ink is just a coloured tile.
The interesting part is that its position is reactive.
Try changing both controls.
Same 24 inks.
Same hex colours.
Different representation, different ordering.
Put the inks into colour space
Sorting is one way to use the coordinates.
But I can also stop treating the shelf position as meaningful at all and let the colour coordinates determine where each ink goes.
First I’ll create generic hue and chroma values based on whichever colour space is selected.
Now the same plot can switch between HCL and OKLCH.
Now the colour-space control changes the coordinate system itself, not just the order of the tiles.
That is much more fun.
One important caveat: I shouldn’t interpret the numeric scales of HCL chroma and OKLCH chroma as though they were directly comparable. What interests me here is the resulting relative structure of the 24 colours.
What I learned just getting this far
This is my first time putting Observable JS directly inside a Quarto document, and the biggest adjustment so far is that it doesn’t feel like writing another sequence of notebook cells.
There are a few different things happening:
R prepares the dataset.
Quarto passes it into the page.
Observable handles reactive values and dependencies.
Observable Plot draws the browser-side visualization.
Once that clicked, this started to feel much less mysterious.
And I really like the idea that I can keep doing data preparation in R while using JavaScript only for the parts where browser interaction is actually useful.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.