tidyverse / tidyverse/ggplot2

Public API for resolving a theme element against a parent

Open
#6,882 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

internals :mag_right: themes :dancer:
Dominant language
R
Stars
7k
Forks
2.1k
Avg merge
59m
Merged PRs (30d)
2

Description

I'm developing a ggplot2 extension that needs to resolve a user-supplied element_*() object as if it were a child of a specific theme element. I'd like to do this without permanently registering the element in the global element tree.

Use case:

My extension, let's call it supertable, adds tables to plots. Users can customize their appearance both via the theme (through custom registered elements like supertable.value) and via inline parameters:

supertable(
  value_style = element_text(size = rel(2)),
  title_style = element_text(margin = margin_part(b = 6),
  line_style = element_line(color = "red")
)

The inline parameter should sit "above" the theme element in the inheritance chain — i.e., rel() and margin_part() should resolve against the fully inherited theme element value, exactly as they would for any normal parent-child relationship in the element tree.

The problem:

The only way to get full resolution (including rel(), margin_part(), and any future deferred-resolution mechanisms) is through calc_element(). merge_element() seems like it should help here, but it simply overwrites fields from the new element onto the old one without resolving deferred values like rel(). So calc_element() appears to be the only option, but it requires the element to be registered in the global tree via register_theme_elements(). So my current workaround is:

calc_element_with_override <- function(override, element, theme, el_class) {
  if (is.null(override)) override <- el_class()
  secret_name <- paste0(element, ".__override__")

  register_theme_elements(
    !!secret_name := el_class(),
    element_tree = rlang::list2(
      !!secret_name := el_def(el_class, inherit = element)
    )
  )

  theme[[secret_name]] <- override
  calc_element(secret_name, theme)
}

This works, but it permanently alters the global element tree as a side effect, which feels wrong.

What would help:

An exported function that resolves an element object against a parent element object. This would apply the same resolution logic as calc_element (resolving rel(), margin_part(), etc.) but without requiring the element to be registered globally. It could be called resolve_element.

I suspect this could also be useful for other extension authors who need to compute derived styles at render time. Thanks for your consideration.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading calc_element(), merge_element(), and register_theme_elements(), which the issue identifies as the relevant entry points for inheritance and deferred resolution. Compare their behavior and determine the API shape needed to resolve an override against a parent without changing the global element tree; done means the requested resolution behavior works without that side effect.

Written by the indexing model from the issue text.

Assessment

Tech stack
r
Domain
design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.