Skip to content

Labels, symbols and arrows

How an analyst chooses how often a track is labelled with the time, marked with a symbol and given a direction arrow; where the map draws them, and how it keeps them legible at any zoom.

Requirements cited

SRD Requirement
CAP-32 Select one or more tracks and set how often their fixes are labelled, marked with a symbol and given a direction arrow, each from 1 second to 24 hours; one change applies to every selected track; the intervals are held per track.
SUB-22 Presentation content, like colour: each change is one recorded, undoable step.
SUB-01 Times are UTC; the analyst's time zone is for display only.
SUB-03 Positions between fixes are along the geodesic.
EXT-14 A legacy plot file's content comes in, and what does not is listed.
NFR-13 Everything works from the keyboard, and by touch.

Model

  1. Three intervals, held by the track. Each track has label_interval, symbol_interval and arrow_interval on its own properties: one value for the track, never one per fix (product owner, 15 Oct 2026). Each is a duration, a whole number of seconds from 1 s to 24 h; or off; for labels and symbols, all, at every recorded fix, legacy Debrief's All (product owner, 15 Oct 2026; arrows have no All); or left out, Debrief's default. The defaults are what the map drew before: no time labels, no symbols, and a few arrows along the track's part in the time window (up to five, at the middle of legs). Arrows set off draw none. Schema 0.0.20.
  2. One step for all the tracks chosen. Select the tracks (Ctrl-click in the outline or on the map), then Frequencies: labels, symbols and arrows… (the outline's menu, the Command Palette) or Frequencies… under Track in the playground. One change is one step for every track chosen, named for what it set: Labels every 10 min, arrows every 5 min on BLUE ONE and RED ONE; Symbols off on 3 tracks. Undo takes it back for all of them. The intervals stay per track, so tracks set together may later differ.
  3. The form. A heading, the tracks it is for, and a chooser each for Labels, Symbols and Arrows, nothing else (the product owner rejects cluttered forms). Each shows what the tracks have now. Where they differ it shows Mixed (unchanged), and a chooser left so changes nothing for any of them. The choices are Off (and, for arrows, A few (default); for labels and symbols, All (every fix)), then every 1 s, 5 s, 10 s, 30 s, 1 min, 2 min, 5 min, 10 min, 15 min, 30 min, 1 h, 2 h, 6 h, 12 h and 24 h, legacy Debrief's choices, and Other…, a box to type an interval in: 90 s, 1h30, 2 hours, 1 day, or all (not for arrows). Beneath it the form says what it read (Means every 1 h 30 min) or what is wrong (Say the unit, such as 30 s, 10 min or 1 h 30 min.; An interval is from 1 s to 24 h.), and sends nothing until it is right. In VS Code the same choices come as three short lists, one after the other, each starting at what the tracks have now; typing an interval offers it at the top. Only what changed is sent: a chooser showing what every track already has changes nothing, and with nothing changed there is no step.
  4. On whole multiples of the interval, as legacy Debrief. Items fall at each whole multiple of the interval of UTC: every 10 min is 0900, 0910, 0920, wherever the track starts (legacy LightweightTrackWrapper.setFixes rounds the track's start up to a multiple of the frequency). UTC has no daylight saving, so every 24 h is midnight Zulu every day. A track too short to hold a multiple gets one, at its start, as legacy Debrief labels it. Only within the time window and the track's span: outside the window a track is context (thin, faint or hidden as the plot says) and carries none, as it carries no arrows or dots.
  5. Where the track was then. Between fixes, the position is along the geodesic in proportion to time, exactly as the square at current time is placed (pointAt in geodesic.ts, the same code as positionAt, held to schemas/vectors/track-positions.json). Legacy Debrief instead marked the first fix at or after each multiple, so with sparse fixes its labels said the fix's own time (0931); Debrief labels the time asked for, on the line. Nothing is drawn inside a gap in the data (fixes more than ten times the track's median time between fixes apart, as smoothing treats a gap): no position is made up where nothing was recorded.
  6. What is drawn.
    • A label is the time, HHmm, legacy Debrief's default fix label; HHmm.ss where the interval is not whole minutes (or, at every fix, where the fix's time is not); and ddHHmm on the first label of each day that is drawn, so the day is never in doubt. It is in the analyst's time zone, Zulu by default, with no zone letter, as on a chart. It sits to port of the track's course, as legacy Debrief places a fix's label, at the end of a short leader in the track's colour, the text in the theme's text colour with a halo, so it is legible on any track colour and any theme. Labels never overlap: all tracks' labels are placed together, earliest first, each to port, or to starboard where port is taken (by another label, a track's name, a fix's own label or a track's end marker), or left out where both are; a fix's label always shown is placed first and never left out. They sit above arrows and markers, below the tracks' names.
    • A symbol is a small dot in the track's colour, with a thin halo in the map's colour.
    • An arrow is the map's chevron, centred on the line and pointing along the course there (the leg leaving a fix, at a fix; the last course it made, where the track stood still). None is drawn within 12 px of a track's first or last fix in the window, where an arrow on the end marker would read as a second arrowhead. A track with an arrow interval set (or off) draws none of the default few.
    • At every fix (all), each fix's time, until fixes would be closer on screen than the spacing; then the smallest preset at least the fixes' median time apart, thinned as any interval is.
  7. Thinned to fit, and said. Zoomed out, items set close together would pile up. The map raises the interval to the smallest clock-friendly multiple of it that leaves them far enough apart on screen (labels 44 px, arrows 28 px, symbols 10 px, on the track's average speed across the screen), so thinned labels still fall on round times: 10 min becomes 20 min, 30 min or 1 h, never 50 min. Where the track is slower than its average, or doubles back, any item closer than that to the one before is left out. No track draws more than 100 labels, 150 arrows or 400 symbols, and only what is in view, or near it, is drawn, again after each pan or zoom. A quiet note above the key says what was thinned, naming the tracks where only some were: Labels every 1 h, arrows every 30 min at this zoom; zoom in for more; Labels every 20 min on RED ONE at this zoom; zoom in for more. It also says some labels left out where more than a quarter of a track's gave way to others.
    • Cheap to pan. Symbols and arrows are painted on one canvas, not drawn as thousands of elements; labels already drawn are kept from one pan to the next; each track is projected once, and positions are worked out once a time (along the straight line on the map's plane on legs under 2 km, where it is the geodesic to well under a millimetre; along the geodesic on longer ones). Budget, measured in the playground with 50 tracks of 2,000 fixes each, labels every minute, symbols every second and arrows every 30 s: redrawing them after a pan takes 5 to 35 ms, and a pan blocks the page once, for 50 to 90 ms, where it blocked it 10 to 14 times for over a second in all before (headless Chromium, software rendering). The budget: after a pan, within 50 ms to redraw and 100 ms blocked; zooming into the densest view, at most 300 ms, once. Zoomed in, every item comes back.
  8. A fix's own label or symbol, always (force on only). A single fix can always show its time label, its symbol or both, whatever the track's intervals and at any zoom: show_label and show_symbol on the fix's mark (product owner, 15 Oct 2026). There is no per-fix off. A forced fix is drawn once where an interval falls on it too, and is never thinned away: it was asked for. Set from the fix's menu (right-click or press and hold the fix on the map; Fixes…): Always show its time label, Always show its symbol and their opposites in VS Code, Time label and Symbol buttons (pressed when on) in the playground's fix list; each change is one step (BLUE ONE fix at 09:31:00Z: time label always shown). The mark follows its fix through moves, splits, merges, smoothing and jump removal, as any mark does.
  9. Shown with the track's details. The outline says each track's intervals: in VS Code, the track's tooltip ends Labels every 10 min · Symbols off · Arrows every 5 min; in the playground, a track with any set has a quiet second line naming those set, and the whole line when pointed at. A fix's details (its tooltip on the map, the fix list) say time label always shown, symbol always shown. Agents read the intervals in the plot debrief/open returns, on each track's properties.
  10. Agents. debrief/set_track_frequencies with features (track ids) and any of label_interval, symbol_interval, arrow_interval: a duration ({value, unit}), seconds, text such as "10 min" or "1h30", "off", "all" (labels and symbols at every fix), or null for the default. One step; refused, saying why, for an interval outside 1 s to 24 h, arrows at every fix, or a feature that is not a track. debrief/mark_fix takes show_label and show_symbol (true, or false to stop).
sequenceDiagram
  actor Analyst
  participant Form as Frequencies form
  participant Host as Host (VS Code or playground)
  participant Python as debrief/set_track_frequencies
  participant Map
  Analyst->>Form: Labels every 10 min, Arrows every 5 min (2 tracks)
  Form->>Host: only what changed
  Host->>Python: features, label_interval, arrow_interval, base
  Python-->>Host: one step: "Labels every 10 min, arrows every 5 min on BLUE ONE and RED ONE"
  Host->>Map: the plot again
  Map->>Map: multiples of 10 min in the window and in view, thinned to fit
  Map-->>Analyst: labels to port, arrows on the line; a note if thinned
  1. Legacy plots. A .dpf file's display rules (CoreFormatter: Label, Symbol or Arrow, every so many milliseconds, for one named track or every track) become those tracks' intervals; legacy All (an interval of 0) becomes all for labels and symbols. Legacy Debrief writes no frequency on a track itself, so where no rule says, the fixes it showed with their label, symbol or arrow give the interval, where they fall exactly as legacy Debrief would have shown them at a preset, or at every fix. Everything else is listed in the import notes: an inactive rule, an interval outside 1 s to 24 h, arrows at every fix, a rule naming no imported track, a symbology match (legacy did not apply it either), a rule not on regular intervals (applied on whole multiples here), and arrows shown at no interval (product owner, 15 Oct 2026). Labels and symbols shown at no interval are kept as those fixes always showing theirs, and the notes say how many (product owner, 16 Oct 2026).

Keyboard and touch

The form is a form: Tab between choosers, arrow keys within one, Enter applies, Escape cancels. In VS Code the lists are VS Code's own, one after another, with Back to the list before (product owner, 16 Oct 2026). A fix's menu opens by right-click, press and hold on a touch screen, or the fix list from Fixes… (keyboard equivalents).

Reference implementation

  • Model and Python: label_interval, symbol_interval, arrow_interval (FixIntervalOffEnum, FixIntervalAllEnum) on TrackProperties and show_label, show_symbol on FixMark in schemas/linkml/features.yaml; the rules in debrief_schemas.documents; reading and naming intervals in debrief_kernel.time (read_interval, describe_interval), held with TypeScript to schemas/vectors/fix-intervals.json; debrief_stac.frequencies and Catalogue.set_track_frequencies; debrief_stac.fixes.marked for a fix's own; the service method debrief/set_track_frequencies; display rules and shown fixes in debrief_io.dpf and debrief_io.dpf_import (_track_frequencies).
  • Map and forms: vscode/webview/frequencies.ts (the form's choices, times, thinning, labels, a track's intervals in words), drawGrid and paintMarks in vscode/webview/map.ts, pointAt in vscode/webview/geodesic.ts; the command debrief.setTrackFrequencies in vscode/src/extension.ts; the playground's form in vscode/play/host.ts.
  • Tests: python/debrief-stac/tests/test_frequencies.py, python/debrief-io/tests/test_dpf_frequencies.py, python/debrief-kernel/tests/test_intervals.py, vscode/test/unit/frequencies.test.ts, vscode/test/play/frequencies.mjs, and a step of vscode/test/integration/suite.ts.