This document is an attempt to reproduce some of the charts shown in Observable Plot documentation notebooks.
As always, there are several ways to do things, in particular data
manipulation could be done either in R before plotting, or in JavaScript
via transforms and JS()
calls. Each example below is one
way to achieve a given result, not necessarily the best one or the most
elegant.
data(anscombe_obs)
obsplot(anscombe_obs, height = 240) |>
mark_frame() |>
mark_dot(x = x, y = y) |>
facet(x = series) |>
opts(grid = TRUE, inset = 10)
data(penguins)
obsplot(penguins) |>
mark_barY(
transform_groupX(y = "count", x = species)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
species_domain <- names(sort(table(penguins$species)))
obsplot(penguins) |>
mark_barY(
transform_groupX(y = "count", x = species)
) |>
mark_ruleY(0) |>
scale_x(domain = species_domain, reverse = TRUE) |>
scale_y(grid = TRUE)
obsplot(penguins) |>
mark_barY(
transform_groupX(y = "proportion", x = species)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE, percent = TRUE)
penguins$body_mass_kg <- penguins$body_mass_g / 1000
obsplot(penguins) |>
mark_barY(
transform_groupX(y = "sum", x = species, y = body_mass_kg)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE, label = "↑ Total mass (kg)")
obsplot(penguins, height = 170) |>
mark_dot(y = species, x = body_mass_g) |>
mark_ruleY(
transform_groupY(
list(x1 = "min", x2 = "max"),
y = species, x = body_mass_g
)
) |>
mark_tickX(
transform_groupY(
list(x = "median"),
y = species, x = body_mass_g, stroke = "red"
)
) |>
scale_x(inset = 6) |>
scale_y(label = NULL) |>
opts(marginLeft = 60)
data(mobydick1)
obsplot(mobydick1) |>
mark_barY(
transform_groupX(y = "count")
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(mobydick1) |>
mark_barY(
transform_groupX(y = "proportion", filter = JS('d => /[AEIOUY]/.test(d)'))
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE, percent = TRUE)
data(penguins)
obsplot(penguins) |>
mark_barY(
transform_groupX(y = "count", x = sex)
) |>
mark_ruleY(0) |>
facet(x = species) |>
scale_x(tickFormat = JS('d => d === null ? "N/A" : d')) |>
scale_y(grid = TRUE)
obsplot(penguins) |>
mark_barY(
transform_groupX(y = "proportion-facet", x = sex)
) |>
mark_ruleY(0) |>
facet(x = species) |>
scale_x(tickFormat = JS('d => d === null ? "N/A" : d')) |>
scale_y(grid = TRUE, percent = TRUE)
obsplot(penguins) |>
mark_barY(
transform_groupX(y = "count", x = species, fill = sex)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(penguins, height = 60) |>
mark_barX(
transform_stackX(
transform_groupZ(x = "proportion", fill = sex)
)
) |>
mark_text(
transform_stackX(
transform_groupZ(
list(x = "proportion", text = "first"),
z = sex, text = sex)
)
) |>
mark_ruleX(c(0, 1)) |>
scale_x(percent = TRUE)
data(athletes)
# Only keep used variables
athletes <- athletes[, c("weight", "height", "sex", "sport")]
obsplot(athletes) |>
mark_rectY(
transform_binX(y = "count", x = weight)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(athletes) |>
mark_rectY(
transform_binX(y = "count", x = weight, inset = 0)
) |>
mark_ruleY(0) |>
scale_x(round = TRUE) |>
scale_y(grid = TRUE)
obsplot(athletes) |>
mark_areaY(
transform_binX(y = "count", x = weight, fill = "#ccc")
) |>
mark_lineY(
transform_binX(y = "count", x = weight)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(athletes) |>
mark_rectY(
transform_binX(y = "count", x = weight, thresholds = "sturges")
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(athletes) |>
mark_rectY(
transform_binX(y = "count", x = weight, cumulative = TRUE)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(athletes) |>
mark_rectY(
transform_binX(
y2 = "count",
x = weight, fill = sex, mixBlendMode = "multiply"
)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(athletes) |>
mark_rectY(
transform_binX(y = "count", x = weight, fill = sex)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(athletes, height = 60) |>
mark_barX(
transform_binX(fill = "count", x = weight)
) |>
scale_x(round = TRUE) |>
scale_color(scheme = "YlGnBu")
obsplot(athletes) |>
mark_rect(
transform_bin(fill = "count", x = weight, y = height, inset = 0)
) |>
scale_color(scheme = "YlGnBu") |>
opts(round = TRUE, grid = TRUE)
obsplot(athletes) |>
mark_rect(
transform_bin(
fillOpacity = "count",
x = weight, y = height, fill = sex, inset = 0
)
) |>
opts(round = TRUE, grid = TRUE)
obsplot(athletes) |>
mark_dot(
transform_bin(r = "count", x = weight, y = height)
) |>
scale_r(range = c(0, 10)) |>
opts(round = TRUE, grid = TRUE)
obsplot(athletes) |>
mark_dot(
transform_bin(r = "count", x = weight, y = height, stroke = sex)
) |>
scale_r(range = c(0, 10)) |>
opts(round = TRUE, grid = TRUE)
obsplot(athletes, height = 60) |>
mark_dot(
transform_binX(r = "count", x = weight)
) |>
scale_r(range = c(0, 14))
sports_by_weight <- levels(reorder(athletes$sport, athletes$weight, median, na.rm = TRUE))
obsplot(athletes, height = 600) |>
mark_barX(
transform_binX(fill = "proportion-facet", x = weight, inset = 0.5)
) |>
facet(marginLeft = 100, y = sport) |>
scale_color(scheme = "YlGnBu") |>
scale_x(round = TRUE, grid = TRUE) |>
scale_fy(domain = sports_by_weight, label = NULL) |>
opts(marginLeft = 100, padding = 0)
data(crimea)
obsplot(crimea) |>
mark_lineY(x = date, y = deaths, stroke = cause) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(crimea) |>
mark_areaY(x = date, y2 = deaths, fill = cause, mixBlendMode = "multiply") |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(crimea) |>
mark_areaY(x = date, y = deaths, fill = cause) |>
mark_ruleY(0) |>
scale_y(grid = TRUE)
obsplot(crimea) |>
mark_barY(x = date, y = deaths, fill = cause) |>
mark_ruleY(0) |>
scale_x(label = NULL, tickFormat = JS('d => d.toLocaleString("en", {month: "narrow"})'))
data(unemployment)
obsplot(unemployment) |>
mark_areaY(
transform_stackY(x = date, y = unemployed, fill = industry, title = industry)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE, label = '↑ Unemployed (thousands)')
obsplot(unemployment) |>
mark_areaY(
transform_stackY(offset = "silhouette", x = date, y = unemployed, fill = industry)
) |>
scale_y(grid = TRUE, label = '↑ Unemployed (thousands)')
obsplot(unemployment) |>
mark_areaY(
transform_stackY(offset = "wiggle", x = date, y = unemployed, fill = industry)
) |>
scale_y(grid = TRUE, label = '↑ Unemployed (thousands)')
obsplot(unemployment) |>
mark_areaY(
transform_stackY(
curve = "catmull-rom",
x = date, y = unemployed, fill = industry, order = value
)
) |>
mark_ruleY(0) |>
scale_y(grid = TRUE, label = '↑ Unemployed (thousands)')
data(riaa)
xy <- list(x = "year", y = "revenue", z = "format", order = "appearance", reverse = TRUE)
obsplot(riaa) |>
mark_areaY(
transform_stackY(
append(
xy,
list(fill = "group", title = JS('d => `${d.format}\n${d.group}`'))
)
)
) |>
mark_lineY(
transform_stackY1(
append(
xy,
list(stroke = "white", strokeWidth = 1)
)
)
) |>
mark_ruleY(0) |>
scale_y(
grid = TRUE, label = "↑ Annual revenue (billions, adj.)",
transform = JS('d => d / 1000')
)
xy <- list(
x = "year", y = "revenue", z = "format",
order = "appearance", reverse = TRUE, offset = "expand"
)
obsplot(riaa) |>
mark_areaY(
transform_stackY(
append(
xy,
list(fill = "group", title = JS('d => `${d.format}\n${d.group}`'))
)
)
) |>
mark_lineY(
transform_stackY1(
append(
xy,
list(stroke = "white", strokeWidth = 1)
)
)
) |>
mark_ruleY(c(0, 1)) |>
scale_y(
grid = TRUE, label = "↑ Annual revenue (billions, adj.)",
transform = JS('d => d / 1000')
)
data(congress)
obsplot(congress, height = 300) |>
mark_dot(
transform_stackY2(
x = JS('d => 2021 - d.birth'),
y = JS('d => d.gender === "M" ? 1 : d.gender === "F" ? -1 : 0'),
fill = gender
)
) |>
mark_ruleY(0) |>
scale_x(label = "Age →", nice = TRUE) |>
scale_y(
grid = TRUE, label = "← Women · Men →",
labelAnchor = "center",
tickFormat = JS('Math.abs')
)
obsplot(congress, height = 280) |>
mark_barX(
transform_stackX(
transform_groupZ(x = "proportion-facet", fill = gender)
)
) |>
mark_text(
transform_stackX(
transform_groupZ(
list(x = "proportion-facet", text = "first"),
z = gender,
text = JS('d => d.gender === "F" ? "Women" : d.gender === "M" ? "Men" : null')
)
)
) |>
mark_ruleX(c(0, 1)) |>
facet(y = JS('d => Math.floor((2021 - d.birth) / 10) * 10')) |>
scale_x(percent = TRUE)
values <- rnorm(500)
obsplot(values, height = 200) |>
mark_lineY(transform_map(list(y = "cumsum"), y = values))
data(sftemp)
obsplot(sftemp) |>
mark_areaY(x = date, y1 = low, y2 = high, curve = "step", fill = "#ccc") |>
mark_line(
transform_windowY(x = date, y = low, k = 7, curve = "step", stroke = "blue")
) |>
mark_line(
transform_windowY(x = date, y = high, k = 7, curve = "step", stroke = "red")
) |>
scale_y(grid = TRUE, label = "↑ Daily temperature range (°F)")
data(stocks)
obsplot(stocks) |>
mark_ruleY(1) |>
mark_line(transform_normalizeY(x = Date, y = Close, stroke = Symbol)) |>
mark_text(
transform_selectLast(
transform_normalizeY(
x = Date, y = Close, stroke = Symbol,
text = Symbol, textAnchor = "start", dx = 3
)
)
) |>
scale_y(
type = "log", grid = TRUE, label = "↑ Change in price (%)",
tickFormat = JS("x => d3.format('+d')((x - 1) * 100)")
) |>
opts(marginRight = 40)
data(stateage)
# Get states order by proportion of >=80
library(dplyr)
state_order <- stateage |>
group_by(name) |>
mutate(
total_pop = sum(population),
prop = population / total_pop
) |>
filter(age == "≥80") |>
arrange(desc(prop))
xy <- transform_normalizeX(basis = "sum", z = name, x = population, y = name)
obsplot(stateage, height = 660) |>
mark_ruleX(x = 0) |>
mark_ruleY(
transform_groupY(list(x1 = "min", x2 = "max"), xy)
) |>
mark_dot(xy, fill = age, title = age) |>
mark_text(
transform_selectMinX(xy), textAnchor = "end", dx = -6, text = name
) |>
scale_x(axis = "top", label = "Percent (%) →", transform = JS("d => d * 100")) |>
scale_y(
domain = state_order$name,
axis = NULL
) |>
scale_color(scheme = "spectral", domain = unique(stateage$age)) |>
opts(grid = TRUE)