Designing ggtime:
A grammar of temporal graphics
12th February 2025 @ ggextenders club
Mitchell O’Hara-Wild, Monash University Cynthia Huang, Monash University
This talk is more about designing ggplot2 extensions than temporal visualisation.
The {ggtime}
package has a maturing design, and is in early software development.
Today’s presentation
I’ll share some {ggtime}
design elements, and prompt discussion questions for some design challenges.
👋 Later discussion
There won’t be enough time to discuss everything today.
I’ll share discussion links in the slides, e.g. discussions#79
Please join the conversation afterwards ❤️
github://ggplot2-extenders/ggplot-extension-club/discussions
Tangential to discussions#77
A clear scope for ggplot extensions helps:
The scope of ggtime
It’s all about time (and only about time)!
Several types of data require unique care.
{distributional}
/ {ggdist}
){tidygraph}
/ {ggraph}
){sf}
/ {ggplot2}
){mixtime}
/ {ggtime}
)What makes these dimensions “special”?
An open question! Some ideas:
Time is comparatively simple dimension, it is:
ggplot2 already supports temporal data
Simply map a date (Date
), datetime (POSIXct
), or even time (hms
) to any aesthetic. Scales to adjust temporal labels and breaks exist in ggplot2:
scale_*_date()
for Date
,scale_*_datetime()
for POSIXct
,scale_*_time()
for hms
.Temporal data/patterns align with calendars.
📆 Calendrical time
Calendars complicate temporal visualisation!
While time itself is simple, calendrical patterns are complex!
⏰ ggtime is calendrical
{ggtime}
aligns physical time with calendrical structure (e.g. timezones, granularity, cycles, …).
These ‘visual idioms’ of time series plots can be categorised into several groups:
A grammar of temporal graphics
{ggtime}
aims to re-express these time series plots with common elements of a grammar.
Those elements can be further remixed to create other useful temporal visualisations.
These show time as a continuous dimension.
These are simply time plots.
They just happen to show forecasts, or uncertain values.
⚛️ Notable grammar elements
Most data is long and across many series.
⚛️ Notable grammar elements
Uses calendar layouts (multiple rows) to partially resolve long series.
⚛️ Notable grammar elements
Transforms time to reveal circular patterns.
🎯 Goals
Date
& POSIXct
assumes a Gregorian calendar with day or second precision.
{mixtime}
directly associates time with a calendar to support:
"2 aweeks"
vs "2 weeks"
for absolute/civilgeom_time_line()
A time-aware version of geom_line()
. Shows calendrical jumps with dashed lines and/or open and closed circles.
tz_shift <- as_tibble(tsibbledata::gafa_stock) |>
filter(
(Symbol == "AAPL" & Date <= "2014-01-15") |
(Symbol == "GOOG" & Date <= "2014-01-13")
) |>
mutate(Date = Sys.Date() + hours(c(1:3, 3:9, 1:2, 4:9)), DST = ifelse(Symbol == "AAPL", "DST Ends", "DST Begins")) |>
slice(1:3, 3:12, 12:n()) |>
mutate(
open = duplicated(Open),
closed = c(open[-1], FALSE),
Date = Date + open*3600*((DST=="DST Begins")*2-1)
)
tz_shift |>
ggplot(aes(x = Date, y = Close)) +
geom_path(aes(group = cumsum(open))) +
geom_path(linetype = "dashed", data = filter(tz_shift, open | closed)) +
geom_point(aes(shape = closed), data = filter(tz_shift, open | closed), size = 3) +
facet_wrap(vars(DST), ncol = 2, scales = "free_y") +
scale_shape_manual(values = c("TRUE" = 16, "FALSE" = 1)) +
guides(shape = "none")
geom_time_candle()
Shows calendrical value changes/ranges (e.g. daily, weekly, monthly, annual).
Cheeky discussion: are these ‘layers’ or ‘geometries’?
Much like geom_boxplot()
, these are compositions of geom
, stat
, and position
.
Following discussions#58, I’m tempted to expose and promote layer_*()
aliases for these.
Recall the geom_time_line()
example handling jumps in time from daylight savings.
This is an alignment of timezone changes (also known as civil or local time).
🕝 Timezone alignment in the grammar
Where should this ‘jumping’ behaviour live in the grammar?
geom_time_line()
?position_time_civil()
/position_time_absolute()
?scale_*_mixtime()
?What about other jumps, e.g. working hours?
{ggtime}
will also align granularities.
Imagine Australian births (annual) compared with total births by state (monthly).
📅 Temporal alignment across granularities
When constrained to Date and POSIXct, left alignment is commonly used for less-frequent granularities.
e.g. 2025-01-01
can be 2025, Jan 2025, or Jan 1 2025.
Consequently, plotting is also often left-aligned.
{ggtime}
center aligns granularities.
📅 Aligning temporal granularities
A time_align
option is for left, center, or right alignment.
Where should this option belong? position? scale?
{ggtime}
could also align cycles.
Cycles are repeating patterns with an irregular duration (and shape).
Warping cycles to have the same length as “% of cycle” can help compare cycle shapes.
📅 Temporal alignment across cycles
Two arguments are proposed (like breaks
and date_breaks
):
warp
(stretch time between specific time points)time_warp
(stretch time by calendar, e.g. "1 month"
)Does this fit best in scale_*_mixtime()
?
Perhaps data
pre-processing, a position
or stat
?
Much like ggplot2 temporal scales, {ggtime}
has a unified scale_*_mixtime()
with:
time_labels
(strftime-based for Gregorian)time_breaks
(calendar-based, e.g. "3 months"
)The scales (pending discussions#79) may also control calendrical alignment for:
time_local
boolean)time_align
0-1)warp
and time_warp
)Two calendar-plot options are proposed:
facet_calendar()
coord_calendar()
📅 Calendar layouts for temporal graphics
Calendars are useful for plotting long time series, they:
Calendars are not limited to the standard weekly layout, but are hierarchical in nature over any calendar structure.
Facets separate each day (or calendar period).
Each day (or calendar period) shares the same panel.
Using coordinates offers many benefits:
However creating a new coordinate system for calendars is intimidating… 😅
My current design for coord_calendar()
is to rearrange an inner coordinate system.
This creates a nested or hierarchical coordinate system – interesting!
📈 Inner cartesian coordinates (default)
p + coord_calendar(coord = coord_cartesian())
coord
foreground, background, and axis into calendar layout with gtable.❓ Is this technically feasible in ggplot2?
Sorry @teunbrand - I am being especially… creative… here.
Perhaps there are better design alternatives?
My primary concerns are with data access, and grob cutting.
There are some implementation challenges which could prevent this idea working:
💽 Challenge 1: Data access
The calendar layout requires access to {mixtime}
data.
How should the equivalent classed data be recovered after various plot stats and transformations?
✂️ Challenge 2: Grob cutting
Visually cutting grobs seems the safest and best way to relocate layers into a calendar layout.
Is rendering-style grob manipulation feasible in grid/ggplot2?
For example, intersecting a bounding box with the grobs for each week to produce partial lines to the boundary?
Recall the seasonal plot:
It features an origin-less time axis (e.g. month within year), which could be achieved with data pre-processing in {mixtime}
.
Wrapping continuous time
A better alternative is to retain the origin.
Instead wrap observations over calendar periods.
Wrapping continuous time
A better alternative is to retain the origin.
Instead wrap observations over calendar periods.
Simply + coord_polar()
for polar coordinates.
Where should temporal wrapping exist?
coord_wrapped()
coord_calendar()
, but without the calendar layout.coord_sf()
Leveraging calendar structures, ggtime adds
These elements can be combined to produce familiar plots, or remixed to create more bespoke temporal visualisations.
Example: time plot (and forecast plot)
Example: calendar plots
Example: seasonal plots
Continue the discussions on GitHub!
I’ve created a ggtime discussion thread here: discussions#79
There are reply threads for each discussed topic:
I appreciate your contributions!
Thanks to these Unsplash contributors for their photos