getTheme(), setTheme(), and resetTheme() are the
user-facing entry points to pharos's theme system: a single, named list
of graphical and formatting defaults, consulted by essentially every
plotting function in the package (and by fm() for
numeric/percentage/p-value formatting) whenever the corresponding
function argument is left at its default value.
Arguments
- theme
either a named list of theme components to merge into the active theme (only the given top-level elements are replaced; e.g.
setTheme(list(palette = "Set2"))changes only the palette, leavinggrid,box,twin, etc. untouched), or a single string naming a preset registered in the (currently empty) preset registry.
Value
getTheme() and resetTheme() return the (new)
active theme, a named list; setTheme() returns the new active
theme as well, invisibly.
What the theme is for
Most graphical parameters in pharos's plotting functions (col,
grid, box, pch, ...) default to a sentinel value,
.useTheme, rather than to a hardcoded color or number. At call
time, that sentinel is resolved against getTheme() - so changing
the active theme changes the look of every plot produced
afterwards that didn't explicitly override that argument, without
touching a single plotting function's code.
This gives three independent ways to control a plot's appearance, in order of precedence (highest first):
Explicit argument, e.g.
plotXY(x, y, col = "red")- always wins, for that one call only.Function-specific default - some functions deliberately hardcode a value that differs from the generic theme baseline instead of using
.useTheme(e.g.plotBar()'sboxdefaults toFALSErather than the theme's box setting, andplotDot()'s grid line color/style stays its own orange/grey dashed look regardless of the active theme). This is a conscious per-function design choice, not a bug - see the individual function's documentation for which arguments opt out of theme-following this way.Active theme (
getTheme()) - the package-wide fallback used whenever neither of the above applies.
Structure of the theme
The theme is a named list with the following top-level components.
Nested components (grid, box, points, bar,
sty) are themselves named lists; setTheme() replaces them
wholesale (it does not merge one level deeper), so to change a single
nested value, supply the complete sub-list, e.g.
setTheme(list(grid = list(col = "red", lwd = 1, lty = "dotted"))).
parcol.axis="grey40", las=1, cex=1.1. Globalpar()pass applied by every.applyParFromDots()call (axis label color, axis label orientation, global text scaling).gridcol="grey80", lwd=1, lty="dotted", plusgroup.*variants. Background grid lines, via.drawGrid(). Thegroup.*entries style a subordinate/secondary grid (e.g. group separators) where a function draws one.boxcol="grey50", lwd=1, lty="solid". The frame drawn around a plot region, via.drawBox().pointspch=21, col="grey50", bg=addOpacity("grey"), cex=1.1. Default point styling for scatterplot-like functions (e.g.plotXY(),plotDot()).twinpal("helsana")[c(6, 1)]. A fixed pair of colors for contexts that inherently need exactly two contrasting colors (e.g. a fit line and a smoother inplotXY(), the two poles of a diverging color ramp inplotCor(), a single accent color viatwin[1]inlines.loess()/plotQQ()'s confidence band). Never used as a substitute forpalettewhen more than two colors are needed.palette"helsana". Name of the qualitative (categorical) palette used whenever more than two unordered colors are needed (e.g.plotMosaic()'s fill colors), resolved viapal(). Deliberately not used for sequential or diverging numeric scales – see the next item.- (none – by design)
Sequential/diverging numeric color scales (e.g.
plotDens2D()'s density heatmap,plotHeatmap()'s cell shading) are deliberately not theme-driven; they use a hardcoded, purpose-built palette viapal()instead (e.g.pal("red-black"),pal("Blues")). Neitherpalette(categorical) nortwin(a fixed pair) is the right semantic fit for an ordered, continuous scale – seepal()for the registry of named continuous palettes.barcol="grey80", border=NA. Default bar fill/border inplotBar().styabs="abs.sty", perc="per.sty", num="num.sty", pval="pval.sty". Names offm()format styles (seestyles()) used for absolute counts, percentages, plain numbers, and p-values respectively.stampexpression(...)– unevaluated. The corner stamp text drawn by everystamp()/.withGraphicsState()call. Stored as an unevaluatedexpression()andeval()'d at draw time (not at theme-load or theme-set time), so it always reflects the current user and date rather than freezing whatever they were when the theme was defined or last changed. Default:"<username> / <YYYY-MM-DD>".
The .useTheme sentinel
Internally, a function argument that should follow the active theme
(rather than some fixed value) defaults to a dedicated sentinel object,
.useTheme, not to TRUE, FALSE, NA, or
NULL. Those four are frequently legitimate, explicit values in
their own right (e.g. grid = NULL commonly means "suppress the
grid" for a given function) - using any of them to also mean "no value
was given, defer to the theme" would make that case ambiguous. A
dedicated sentinel avoids the ambiguity entirely and keeps the
resolution logic a single, explicit equality check
(identical(x, .useTheme)) rather than an implicit, error-prone
guess based on data type.
Two small internal helpers resolve it:
.useThemeValue(value, ...)- for a simple value taken from a nested theme key, e.g.col <- .useThemeValue(col, "points", "col")..resolveToggle(spec, themeValue)- for an on/off-style argument (such asgrid/box) that may also be aTRUE/FALSE/NA/NULL/list spec in its own right, used internally by.drawGrid()/.drawBox().
Some theme values require more than a simple key lookup to resolve
(e.g. building a color ramp from twin, or constructing a list of
point parameters from points); those are resolved with a small
inline identical(x, .useTheme) check directly in the consuming
function rather than forcing them through one of the two generic
helpers above. See e.g. plotCor()'s col argument or
plotDot()'s pch argument for worked examples.
How plotting functions consume the theme
.applyParFromDots()Applies
getTheme()$paras the lowest-precedencepar()pass, before the function's own defaults and the user's.....drawGrid(grid, defaults = list())/.drawBox(box, defaults = list())Generic dispatchers wrapping
graphics::grid()/graphics::box(). Resolve the.useThemesentinel, merge the theme's style values with any function-specificdefaults(e.g.plotBar()suppressing the axis-parallel grid direction vianx/ny), and dispatch viabedrock::callIf(). Not used by every function that draws a grid or frame: a few (plotCor(),plotHeatmap(),plotDot()) have grid/box geometry tied to exact data coordinates (e.g. half-integer cell boundaries) that doesn't matchgraphics::grid()'s axis-tick-based geometry: these resolve theme values directly viagetTheme()$grid/getTheme()$boxbut draw with their own clippedrect()/abline()calls instead..withGraphicsState(expr, stamp = .useTheme, ...)Wraps a plotting function's body, restores
par()afterwards, and - afterexprhas run successfully - callsstamp()with the (possibly theme-resolved) corner stamp text/arguments.
Presets
setTheme() also accepts a single preset name (a string) instead
of a list, looked up in an internal preset registry
(.themePresets). No presets are currently registered; the
registry exists so named, complete theme variants (e.g. a monochrome or
high-contrast theme) can be added later without changing the
setTheme() interface.
Examples
# inspect the active theme
getTheme()
#> $par
#> $par$col.axis
#> [1] "grey40"
#>
#> $par$las
#> [1] 1
#>
#> $par$cex
#> [1] 1.1
#>
#>
#> $grid
#> $grid$col
#> [1] "grey80"
#>
#> $grid$lwd
#> [1] 1
#>
#> $grid$lty
#> [1] "dotted"
#>
#> $grid$group.col
#> [1] "grey50"
#>
#> $grid$group.lwd
#> [1] 1
#>
#> $grid$group.lty
#> [1] "dotted"
#>
#>
#> $box
#> $box$col
#> [1] "grey50"
#>
#> $box$lwd
#> [1] 1
#>
#> $box$lty
#> [1] "solid"
#>
#>
#> $points
#> $points$pch
#> [1] 21
#>
#> $points$col
#> [1] "grey50"
#>
#> $points$bg
#> grey
#> "#BEBEBE80"
#>
#> $points$cex
#> [1] 1.1
#>
#>
#> $twin
#> red blue
#> "#9A0941" "#8296C4"
#>
#> $palette
#> [1] "helsana"
#>
#> $bar
#> $bar$col
#> [1] "grey80"
#>
#> $bar$border
#> [1] NA
#>
#>
#> $legend
#> $legend$bg
#> white
#> "#FFFFFF80"
#>
#> $legend$box.col
#> [1] "grey50"
#>
#>
#> $sty
#> $sty$abs
#> Format name:
#> Description:
#> Definition: digits=0, bigMark='', label='Number format for counts'
#> Example: 314159
#>
#> $sty$perc
#> Format name:
#> Description:
#> Definition: digits=1, fmt='%', label='Percentage number format'
#> Example: 3.1e+07%
#>
#> $sty$num
#> Format name:
#> Description:
#> Definition: digits=3, bigMark='', label='Number format for numeric values'
#> Example: 314159.265
#>
#> $sty$pval
#> Format name:
#> Description:
#> Definition: fmt='p', pThreshold=0.001, label='Number format for p-values'
#> Example: NA
#>
#>
#> $stamp
#> expression(gettextf("%s / %s", Sys.getenv("USERNAME"), format(Sys.Date(),
#> "%Y-%m-%d")))
#>
# change only the qualitative palette for the rest of the session
setTheme(list(palette = "Set2"))
# change the accent color pair used for e.g. lm/loess fit lines
setTheme(list(twin = c("firebrick", "navy")))
# turn grid lines off package-wide (any .useTheme-driven grid argument
# everywhere now resolves to "off", unless a function overrides it)
setTheme(list(grid = FALSE))
# back to package defaults
resetTheme()
