depictr: One visual language from first look to final figure
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
The figures in an analysis are rarely made all at once. An exploratory density plot may survive into a talk, a model adds a forest plot and a reviewer asks for a diagnostic or a Bayesian comparison. When each stage uses different defaults, one analysis acquires several palettes, type sizes and conventions. Accessibility is then checked, if at all, after the visual decisions have already spread through the project.
I wrote depictr (Bernabeu, 2026) to give those stages a shared visual language. It provides plotting functions for common statistical objects, and each function returns an ordinary ggplot2 object that remains open to further editing. It also measures the finished figure against explicit checks for contrast, text size and reliance on colour. The same conventions are available in the R package and the Python package.
The example data, bundled with the package, are simulated from a lexical-decision design in which participants respond to words preceded by semantically related or unrelated primes, presented visually or auditorily.
Development
The analysis below develops one visual argument in three stages. It describes the observations, estimates the planned effects and finally audits the figure that will be shared.
library(depictr)
library(ggplot2)
# Slightly larger axis and legend text for reading on screen, with titles
# centred on the whole figure instead of the panel
web_theme <- theme(
plot.title.position = 'plot',
axis.text = element_text(size = rel(0.9)),
legend.text = element_text(size = rel(0.9))
)
packageVersion('depictr')
#> [1] '0.3.0'
data('lexical_decision', package = 'depictr')
correct <- subset(lexical_decision, accuracy == 1)
c(participants = length(unique(lexical_decision$participant)),
items = length(unique(lexical_decision$item)),
trials = nrow(lexical_decision))
#> participants items trials
#> 24 40 960
Begin With the Observed Distribution
Reaction times are right-skewed, so a mean and a standard deviation do not show the whole pattern. An empirical cumulative distribution avoids a choice of bins and makes the quartiles directly visible, as Figure 1 shows.
ecdf_plot( lexical_decision, RT, group = condition, reference_quantiles = c(0.25, 0.5, 0.75), legend_inside = TRUE, title = 'Related primes are faster across the distribution' ) + labs(x = 'Reaction time (ms)', colour = NULL) + web_theme
Figure 1: Observed Reaction-Time Distributions After Related and Unrelated Primes, With the Quartiles Marked.
The separation extends well beyond a handful of slow responses. Related trials remain shifted towards shorter reaction times through much of the distribution, which gives a visible empirical context to the model estimates that follow.
Keep the Same Language for the Model
The model, fitted to correct trials, includes priming condition, presentation modality and word frequency, with crossed random intercepts for participants and items. coefficient_plot() reads the fitted object directly and puts the fixed effects on a common scale in Figure 2, expressing each as the change in reaction time per standard deviation of its predictor.
fit <- lmerTest::lmer( RT ~ condition + modality + word_frequency + (1 | participant) + (1 | item), data = correct ) term_labels <- c( conditionunrelated = 'Unrelated priming', modalityauditory = 'Auditory modality', word_frequency = 'Word frequency (Zipf)' ) tidy_estimates(fit) #> term estimate std.error conf.low conf.high #> 1 (Intercept) 708.01334 27.253962 654.596553 761.43012 #> 2 conditionunrelated 31.95745 8.119043 16.044418 47.87048 #> 3 modalityauditory 25.34713 8.123180 9.425993 41.26827 #> 4 word_frequency -21.89472 5.860301 -33.380696 -10.40874 coefficient_plot( fit, standardise = TRUE, order = 'ascending', labels = term_labels, point_size = 3.4, line_size = 1.1, title = 'Estimated effects on reaction time', x_lab = 'Change in reaction time (ms) per SD of predictor' ) + scale_x_continuous(breaks = scales::breaks_width(10)) + web_theme
Figure 2: Fixed Effects on Reaction Time, Each per Standard Deviation of Its Predictor, With 95% Confidence Intervals.
The forest plot keeps the colours and typography of the distribution plot, so a reader moving from observations to estimates can concentrate on the result. That continuity is the point of a shared visual language, since nothing in Figure 2 has to be learned again.
Audit the Figure That Will Be Published
A colourblind-safe palette does not guarantee an accessible figure. Contrast against the background and the use of a second visual channel depend on the particular plot. The package therefore audits the whole built plot, including its palette.
first_draft <- explore_distribution( lexical_decision, RT, group = condition, type = 'density', legend_inside = TRUE, title = 'Lexical-decision time by priming condition' ) + web_theme check_figure(first_draft) #> check measured threshold verdict #> 1 colour_separability 119.21 5.0 pass #> 2 colour_separability_protan 108.92 5.0 pass #> 3 colour_separability_deutan 121.29 5.0 pass #> 4 colour_separability_tritan 77.90 5.0 pass #> 5 greyscale_separability 33.34 5.0 pass #> 6 text_size 9.90 6.0 pass #> 7 text_contrast 7.15 4.5 pass #> 8 geometry_contrast 2.25 3.0 fail #> 9 redundant_encoding 0.00 1.0 fail #> detail #> 1 Closest pair #005b96 and #e69f00 of 2 encoding colours. #> 2 Closest pair #005b96 and #e69f00 of 2 encoding colours. #> 3 Closest pair #005b96 and #e69f00 of 2 encoding colours. #> 4 Closest pair #005b96 and #e69f00 of 2 encoding colours. #> 5 Closest pair #005b96 and #e69f00 in CIE lightness. #> 6 Smallest text 9.90 pt, drawn at 17.78 cm and printed at 17.78 cm. #> 7 Lowest-contrast text #005b96 on #ffffff. #> 8 Lowest-contrast colour #e69f00 on #ffffff. #> 9 Colour alone distinguishes the groups.
The default density plot fails two checks. Its orange falls below the package’s contrast threshold for graphical elements on a white panel, and colour is the only channel separating the conditions. The corrected plot in Figure 3 replaces the orange with a darker vermilion and adds line type as a second channel. Both checks then pass, and neither change touches the data or the statistical display.
final_figure <- explore_distribution(
lexical_decision,
RT,
group = condition,
type = 'density',
palette = c('#005B96', '#B44600'),
alpha = 0.35,
legend_inside = TRUE,
title = 'Priming conditions separated by colour and line type'
) +
aes(linetype = condition) +
labs(x = 'Reaction time (ms)', colour = NULL, fill = NULL, linetype = NULL) +
web_theme
check_figure(final_figure)[, c('check', 'measured', 'threshold', 'verdict')]
#> check measured threshold verdict
#> 1 colour_separability 102.76 5.0 pass
#> 2 colour_separability_protan 81.30 5.0 pass
#> 3 colour_separability_deutan 96.85 5.0 pass
#> 4 colour_separability_tritan 93.25 5.0 pass
#> 5 greyscale_separability 7.09 5.0 pass
#> 6 text_size 9.90 6.0 pass
#> 7 text_contrast 7.15 4.5 pass
#> 8 geometry_contrast 5.50 3.0 pass
#> 9 redundant_encoding 1.00 1.0 pass
final_figure
Figure 3: The Corrected Figure, in Which Colour and Line Type Both Separate the Priming Conditions.
The default palette starts from the colours proposed by Okabe and Ito (2008), and the audit simulates colour-vision deficiency with the model of Machado et al. (2009). Each measurement is reported beside its threshold, so a verdict can be revisited when a journal, display size or printing process calls for a different standard.
Discussion
The example supports a modest but useful claim: accessibility belongs in figure development from the first draft onwards. A stable visual grammar helps readers compare plots, while the audit identifies concrete properties to repair. The practical habit is to use one set of conventions throughout an analysis and to audit the exact figure that will leave the project. The result is a reproducible hand-off from analysis code to an editable, interpretable and checked figure.
The R reference and Python reference cover the rest of the package, including diagnostics, posterior plots, power curves, model comparisons and multi-panel composition.
Limits
depictr draws and checks figures, leaving decisions about the model, estimand, contrasts, prior, uncertainty and audience to the analyst. A polished plot can faithfully present a poor analysis. The checks also cover only measurable properties, so alt text, a meaningful caption and a sensible figure order remain editorial work.
References
Bernabeu, P. (2026). depictr: A unified toolkit for visualising statistical models and data (Version 0.3.0) [Computer software]. CRAN. https://doi.org/10.32614/CRAN.package.depictr
Machado, G. M., Oliveira, M. M., & Fernandes, L. A. F. (2009). A physiologically-based model for simulation of color vision deficiency. IEEE Transactions on Visualization and Computer Graphics, 15(6), 1291–1298. https://doi.org/10.1109/TVCG.2009.113
Okabe, M., & Ito, K. (2008). Color universal design (CUD): How to make figures and presentations that are friendly to colorblind people. https://jfly.uni-koeln.de/color/ (Original work published 2002)
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.