annie.pages.timeline¶
The Event-annotation timeline — an inline-SVG track/event surface (UI).
A video-editor-style timeline: a time ruler, a vertical playhead across every lane, and one
lane per track holding its event boxes. The surface is always the full width of its
container; zooming does not widen it — it narrows the time window shown, so the ruler
grows denser and boundaries are easier to place. The visible window is
[view_start_frame, view_end_frame] (the task keeps it centred on the playhead); frames
map to x within that window, and the gesture x-fraction maps back through the same window.
Within one track, events that overlap in time are stacked into sub-rows rather than drawn
on top of each other, so every event stays clickable; a lane’s height grows with how many
events are concurrent at once (see pack_lanes()).
Horizontal geometry is in percent. The SVG has no viewBox, so its user units are real
pixels, while every x position and width is written as a percentage of the element’s width
(x="12.5%"). The server therefore never needs to know the browser’s pixel width, nothing is
scaled, and text is never stretched — the failure mode of a fixed viewBox squeezed to the
container with preserveAspectRatio="none". Vertical measurements (lane heights, font size)
are plain pixels.
Where the logic lives. Python owns the model and renders the SVG markup — this keeps
the geometry testable (build_svg(), pack_lanes(), both pure) and mirrors how the
segment-review layout keeps its logic server-side. JavaScript does two things only: it reports
pointer gestures back (carrying the track and an x fraction of the window) so Python can
convert them to frames, and it moves the single playhead line during playback without a full
re-render (see annie.pages.videoclock.set_playhead_x()).
Attributes¶
Classes¶
One event to draw on a lane. |
|
One lane: a track name and the events on it. |
Functions¶
|
Assign each event a sub-row so overlapping events never share one. |
|
Render the timeline as an inline-SVG string for the visible frame window. |
|
Return JS that reports a timeline click to the server via |
|
Return the pan scrollbar markup: a track holding a draggable thumb. |
|
Return JS that makes the scrollbar draggable and click-to-centre. |
Module Contents¶
- annie.pages.timeline.ROW_HEIGHT = 22¶
- annie.pages.timeline.LANE_PAD = 6¶
- annie.pages.timeline.RULER_HEIGHT = 22¶
- annie.pages.timeline.LANE_GAP = 8¶
- annie.pages.timeline.LANE_LABEL_HEIGHT = 16¶
- annie.pages.timeline.REFERENCE_WIDTH = 1300¶
- annie.pages.timeline.MIN_BOX_PERCENT = 0.25¶
- annie.pages.timeline.FONT_SIZE = 12¶
- annie.pages.timeline.EVENT_RADIUS = 4¶
- class annie.pages.timeline.TimelineEvent[source]¶
One event to draw on a lane.
- event_id¶
The event’s stable id (echoed back on click, to select it).
- start_frame¶
Inclusive start frame.
- end_frame¶
Inclusive end frame.
- label¶
Text drawn inside the box (may be empty).
- selected¶
Whether to draw this box in the selected style.
- color¶
Per-event fill colour override (hex), or
Noneto use the lane’s colour.
- class annie.pages.timeline.TimelineTrack[source]¶
One lane: a track name and the events on it.
- name¶
The track (category) name, shown as the lane label.
- events¶
The events to draw, in any order.
- color¶
Optional lane colour override (hex);
Noneuses the palette by lane order.
- annie.pages.timeline.pack_lanes(events: collections.abc.Sequence[TimelineEvent]) list[int][source]¶
Assign each event a sub-row so overlapping events never share one.
A greedy interval-partitioning pass: events are considered in start-frame order and each is placed on the lowest sub-row whose last event has already ended. Two events overlap when one starts at or before the other ends (inclusive frames), so touching intervals are treated as overlapping and separated — clearer for the reviewer than a 1px seam.
- Parameters:
events – The events on one track, in any order.
- Returns:
A list of sub-row indices, parallel to
eventsin their original order.
- annie.pages.timeline.build_svg(tracks: collections.abc.Sequence[TimelineTrack], *, view_start: int, view_end: int, fps: float, svg_id: str, pending_start: int | None = None, pending_end: int | None = None) str[source]¶
Render the timeline as an inline-SVG string for the visible frame window.
Pure and deterministic — no NiceGUI, no DOM — so the geometry (tick placement, event box extents, lane stacking) is unit-testable.
- Parameters:
tracks – The lanes to draw, top to bottom.
view_start – First frame shown at the left edge.
view_end – Last frame shown at the right edge (
> view_start).fps – Frames per second, for converting the window to ruler seconds.
svg_id – DOM id for the
<svg>(the playhead updater and gesture bridge use it).pending_start – While the reviewer is mid-capture (start marked, end not yet), the start frame of the growing amber band;
Nonewhen not capturing.pending_end – The band’s right edge — the live playhead frame; defaults to
pending_start(a minimal band) when omitted. Ignored ifpending_startisNone.
- Returns:
A complete
<svg>…</svg>markup string that scales to its container’s width.
- annie.pages.timeline.gesture_script(svg_id: str, event_name: str, *, pannable: bool = False) str[source]¶
Return JS that reports a timeline click to the server via
emitEvent.Attached once after the SVG is embedded. It listens for pointer down/up on the SVG and emits
event_namewith adetailof{kind, x, ...}wherexis a[0, 1]fraction of the visible window:kind = "select"when an existing event box is clicked (carriesevent_id);kind = "seek"for a click anywhere else, or any drag.
The timeline is navigation-only — it never creates events — so there is no click/drag authoring gesture; boundaries come from the START/END EVENT buttons and the i/o keys.
- Parameters:
svg_id – DOM id of the timeline
<svg>.event_name – The custom event name the NiceGUI handler listens for.
pannable – Whether the timeline is zoomed in. When true, a horizontal wheel/trackpad scroll (or Shift+wheel) pans the window and emits
kind = "wheel"withdx(a fraction of the window width); the page’s own scroll is left alone otherwise.
- Returns:
The JavaScript to run once via
ui.run_javascript.
- annie.pages.timeline.scrollbar_html(scroll_id: str, left: float, width: float) str[source]¶
Return the pan scrollbar markup: a track holding a draggable thumb.
- Parameters:
scroll_id – DOM id for the track element.
left – The thumb’s left edge as a
[0, 1]fraction of the track.width – The thumb’s width as a
[0, 1]fraction of the track.
- Returns:
An HTML string; positions are percentages so it fills any container width.
- annie.pages.timeline.scrollbar_script(scroll_id: str, event_name: str) str[source]¶
Return JS that makes the scrollbar draggable and click-to-centre.
Emits
event_namewith{kind: "pan", x}wherexis the thumb’s position as a fraction of the pannable range (0= window at the start,1= at the end), throttled to ~20 Hz while dragging.- Parameters:
scroll_id – DOM id of the scrollbar track.
event_name – The custom event name the NiceGUI handler listens for.
- Returns:
The JavaScript to run once via
ui.run_javascript.