Programmatic Generation

When events come from data it is more practical to generate the divs with code than to write them by hand.

R — htmltools

The htmltools package builds HTML elements as R objects. htmltools::div() maps directly onto the timeline divs, and knitr renders the result inline.

library(htmltools)

phones <- as.data.frame(WorldPhones)

div(
  class = "timeline",
  lapply(rownames(phones), function(year) {
    div(
      class        = "event",
      `data-label` = year,
      paste0("N. America: ", phones[year, "N.Amer"], "k"),
      tags$br(),
      paste0("Europe: ", phones[year, "Europe"], "k")
    )
  })
)
N. America: 45939k
Europe: 21574k
N. America: 60423k
Europe: 29990k
N. America: 64721k
Europe: 32510k
N. America: 68484k
Europe: 35218k
N. America: 71799k
Europe: 37598k
N. America: 76036k
Europe: 40341k
N. America: 79831k
Europe: 43173k

Timeline modifier classes and event attributes work the same way — pass them through class and named arguments:

# Modifier class on the timeline
div(class = "timeline vertical tl-card", ...)

# data-dot on an individual event
div(class = "event", `data-label` = "Launch", `data-dot` = "\u2605", ...)

Empty events are an empty div():

div(class = "event", `data-label` = "2022")

Python

In a Quarto Python chunk, print() with results: asis passes the output through as raw HTML:

events = [
    ("2020", "Project Started", "Initial concept and planning phase."),
    ("2021", "First Release",   "Launched version 1.0 to early users."),
    ("2022", "Major Update",    "Rewrote core engine for performance."),
    ("2023", "Scale Up",        "Reached 1 million users worldwide."),
]

items = "\n".join(
    f'<div class="event" data-label="{year}">'
    f"<strong>{title}</strong> - {desc}"
    f"</div>"
    for year, title, desc in events
)

print(f'<div class="timeline vertical">\n{items}\n</div>')
Project Started - Initial concept and planning phase.
First Release - Launched version 1.0 to early users.
Major Update - Rewrote core engine for performance.
Scale Up - Reached 1 million users worldwide.

In a Jupyter notebook, use IPython.display.HTML(...) instead of print().

An empty event is a self-contained div with no inner content:

f'<div class="event" data-label="2022"></div>'