knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 4.2, out.width = "100%" ) library(ggkodom) library(ggplot2)
geom_kodom_line() draws one horizontal lane per subject, with time on the x-axis and value encoded as a color gradient along each path. It is a direct ggplot2 layer, so it composes freely with facet_wrap(), scale_*(), theme(), and any other ggplot2 building block.
All examples use a simulated HbA1c dataset: 25 patients each with 6–12
irregular visits over 24 months. Each patient also has a fixed age covariate
used later to demonstrate independent aesthetic mappings.
set.seed(42) n_subjects <- 25 n_obs_per <- sample(6:12, n_subjects, replace = TRUE) df <- do.call(rbind, lapply(seq_len(n_subjects), function(i) { n <- n_obs_per[i] base <- rnorm(1, mean = 7.5, sd = 1.2) trend <- rnorm(1, mean = -0.02, sd = 0.01) time <- sort(runif(n, 0, 24)) value <- base + trend * time + rnorm(n, sd = 0.4) data.frame( subject_id = sprintf("P%03d", i), visit_month = time, relative_month = time - min(time), hba1c = pmax(4, value), arm = ifelse(i <= 12, "Treatment", "Control"), age = sample(30:75, 1), stringsAsFactors = FALSE ) }))
Map x to time, id to the subject identifier, and colour to the measurement value. The stat assigns each unique id a lane position on the y-axis automatically.
ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line() + scale_colour_kodom() + labs(x = "Visit (months)", y = "", colour = "HbA1c (%)") + theme_kodom()
The color along each path is a smooth gradient interpolated between consecutive measurement points, not a step function. Teal = lower HbA1c, red = higher.
sort_byBy default lanes follow first-appearance order of subject IDs. Use sort_by to re-order by a summary of the colour variable.
# Top lanes = highest mean HbA1c, bottom = lowest ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom() + labs(x = "Visit (months)", y = "", title = 'sort_by = "mean"') + theme_kodom(legend_position = "top")
Available sort_by values:
| Value | Lane order |
|---|---|
| "none" | First appearance in data (default) |
| "mean" | Descending mean of colour |
| "mean_asc" | Ascending mean of colour |
| "first" | Descending value at earliest timepoint |
| "last" | Descending value at latest timepoint |
Often visits are counted by relative differences with the initial visit for every patients marked at time $0$. This can be achieved by simple transformation of the data.
ggplot(df, aes(x = relative_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "first") + scale_colour_kodom() + labs(x = "Visit (months)", y = "", title = 'sort_by = "mean"') + theme_kodom(legend_position = "top")
n_maxFor large cohorts, n_max takes a random sample of subjects to keep the plot
readable. Here we show only 12 of the 25 patients.
ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean", n_max = 12) + scale_colour_kodom() + labs(x = "Visit (months)", y = "", title = "12 randomly sampled subjects") + theme_kodom()
show_points = TRUE (the default) places a dot at every observation. This makes irregular visit times visible at a glance — critical for sparse clinical data.
p_base <- ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + scale_colour_kodom() + labs(x = "Visit (months)", y = "") + theme_kodom() p_base + geom_kodom_line(show_points = TRUE) + labs(title = "show_points = TRUE (default)")
p_base + geom_kodom_line(show_points = FALSE) + labs(title = "show_points = FALSE")
You can also suppress points by passing shape = NA or size = 0 as fixed aesthetics — useful when you want to keep the parameter API consistent across geom calls:
p_base + geom_kodom_line(shape = NA) + labs(title = "shape = NA — same effect as show_points = FALSE")
shape, size, stroke, and fill control the observation markers. These follow standard ggplot2 conventions.
# shape 21 = filled circle — fill controls interior, colour controls border ggplot( df, aes(x = visit_month, id = subject_id, colour = hba1c, fill = hba1c) ) + geom_kodom_line(shape = 21, size = 2.5, stroke = 0.6) + scale_colour_kodom() + scale_fill_kodom() + labs(x = "Visit (months)", y = "", title = "shape = 21 with fill + colour") + theme_kodom()
# shape 23 = filled diamond ggplot( df, aes(x = visit_month, id = subject_id, colour = hba1c, fill = hba1c) ) + geom_kodom_line(shape = 23, size = 2.5) + scale_colour_kodom() + scale_fill_kodom() + labs(x = "Visit (months)", y = "", title = "shape = 23 (diamond)") + theme_kodom()
linewidth sets the path width. alpha controls transparency for both line and points.
ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(linewidth = 3, alpha = 0.5) + scale_colour_kodom() + labs(x = "Visit (months)", y = "", title = "linewidth = 3, alpha = 0.5") + theme_kodom()
scale_colour_kodom(discretize = TRUE)Continuous gradients can wash out when values cluster tightly. discretize = TRUE switches to solid color bands, and color_breaks sets the clinical thresholds.
ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom( discretize = TRUE, color_breaks = c(5.7, 6.5, 8), name = "HbA1c (%)" ) + labs(x = "Visit (months)", y = "", title = "Discrete clinical bands") + theme_kodom()
size and linewidth — encoding patient covariatessize controls only the observation markers; linewidth controls only
the connecting path. They are independently routed to GeomPoint and
GeomSegment respectively, so mapping one has no effect on the other.
Use this to encode a fixed patient-level covariate (here, age) as point size
while keeping the path width constant:
ggplot(df, aes( x = visit_month, id = subject_id, colour = hba1c, size = age )) + geom_kodom_line(sort_by = "mean", linewidth = 0.4) + scale_colour_kodom() + scale_size_continuous(range = c(1, 4), name = "Age") + labs( x = "Visit (months)", y = "", title = "Point size = patient age, path width fixed" ) + theme_kodom()
alpha works the same way — here it encodes age as opacity, making older
patients' trajectories more prominent:
ggplot(df, aes( x = visit_month, id = subject_id, colour = hba1c, alpha = age )) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom() + scale_alpha_continuous(range = c(0.2, 1), name = "Age") + labs( x = "Visit (months)", y = "", title = "Opacity = patient age" ) + theme_kodom()
Both can be combined — size and alpha each independently driven by a covariate:
ggplot(df, aes( x = visit_month, id = subject_id, colour = hba1c, size = age, alpha = age )) + geom_kodom_line(sort_by = "mean", linewidth = 0.3) + scale_colour_kodom() + scale_size_continuous(range = c(1, 4), name = "Age") + scale_alpha_continuous(range = c(0.25, 1), guide = "none") + labs( x = "Visit (months)", y = "", title = "Size + opacity both encode age" ) + theme_kodom()
Because geom_kodom_line() is a standard ggplot2 layer, facet_wrap() and facet_grid() work out of the box. Lane ordering is computed independently within each facet panel.
ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom(color_breaks = c(5.7, 6.5, 8)) + facet_wrap(~arm) + labs(x = "Visit (months)", y = "", title = "Treatment vs. Control") + theme_kodom()
The y-axis shows integer lane numbers by default. Use scale_y_continuous() to replace them with subject IDs, or theme() to suppress them entirely.
# Suppress y labels for large cohorts — usually the right choice ggplot(df, aes(x = visit_month, id = subject_id, colour = hba1c)) + geom_kodom_line(sort_by = "mean") + scale_colour_kodom() + labs(x = "Visit (months)", y = "") + theme_kodom() + theme(axis.text.y = element_blank())
# View the three anchor colors and any interpolated expansion scales::show_col(kodom_colors(7))
kodom_colors() returns the three anchors (teal #008D98, gold #FFCC3D, red #D7433B). Passing n > 3 interpolates between them via colorRampPalette.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.