Timeline Diagrams
A timeline is a sequence of events grouped into named sections, drawn as one horizontal lane per section along a shared axis.
Overview
The model lives in Atelier\Diagram\Timeline\: TimelineDiagram (sections plus an optional Title), TimelineSection (title, a non-empty list of events), and TimelineEvent (label, date). All model classes are immutable.
Reach for it to show ordered events grouped into lanes: product plans, release narratives, incident reviews, project phases. Use it when the point is "what happened in which lane" rather than a charted date scale. In v0 the date is a plain label: it is rendered next to the event, not parsed or scaled onto a calendar axis.
Format
timeline
title Product launch
section Discovery
Research complete : 2026-01
Prototype review : 2026-02
section Build
Private beta : 2026-04
Public launch : 2026-06
title is optional. Every event must belong to an explicit section, and each event line is Label : date. The date is a free-text label, not a parsed value. Unsupported Mermaid timeline features are rejected, never skipped. Full grammar: Mermaid support.
Builder API
TimelineDiagramBuilder (or Diagram::timeline(), which returns it) is the one way to build the model:
use Atelier\Diagram\Diagram;
$diagram = Diagram::timeline()
->title('Product launch')
->section('Discovery')
->event('Research complete', '2026-01')
->event('Prototype review', '2026-02')
->section('Build')
->event('Private beta', '2026-04')
->event('Public launch', '2026-06')
->build();
Diagram::of($diagram)->saveSvg('out.svg');
title($text)- sets a diagram title (optional).section($title)- starts a new lane; throwsInvalidArgumentExceptionon an empty title.event($label, $date)- adds an event to the current section; throwsInvalidArgumentExceptionwhen called before any section. The date is stored as a label.build()- validates that at least one section exists and that every section has at least one event; throwsInvalidArgumentExceptionotherwise.
Options
| Setting | Default | Effect |
|---|---|---|
title(string) |
none | centered heading above the lanes |
Theme::minNodeWidth |
null |
floor for event cards, alongside 17 * spacingUnit |
Theme::spacingUnit |
per preset | card width floor, lane spacing, padding |
- Determinism: sections and events are drawn in source order with equal spacing; the same model always yields the same SVG.
Layout\Timeline\TimelineLayoutEngine stacks one horizontal lane per section against a shared axis. Each section keeps its own color, drawn as a filled connector dot on the axis with a stem to the event card. Dates are rendered as labels only; there is no calendar scale, duration math, or range bars.
Themes
Timelines use accentColors per section: section index n takes accentColors[n % count], coloring that lane's connector lines and axis dots, so palette size sets how many sections cycle before colors repeat. The axis uses mutedTextColor, lanes and event cards use nodeFillColor (lanes at reduced opacity) with nodeStrokeColor, and section and event labels use textColor. The date chip text is white.
All presets apply (Theme::default(), dark(), blueprint(), mono(), neutral()); a small accentColors set (as in mono()) collapses the lanes toward one hue. See Theming.
Theme::blueprint() on the same events. Section colours come from accentColors, which the preset supplies.
Parse
Header keyword: timeline.
$diagram = Diagram::fromMermaid($source); // throws ParseException
$result = Diagram::tryFromMermaid($source); // non-throwing ParseResult
toMermaid() / toMarkdown() serialize a built model back. Full grammar and round-trip rules: Mermaid support.
Debug
On a rejected source, inspect ParseResult::getDiagnostic() (a ParserDiagnostic with a message, a stable code, and a source span) or catch ParseException (getLineNumber(), getSourceLine()). Render a code frame with ParserDiagnosticFormatter:
$result = Diagram::tryFromMermaid($source);
if ($result->isFailure()) {
echo \Atelier\Diagram\Parser\Support\ParserDiagnosticFormatter::format($result->getDiagnostic());
}
See parser diagnostics.