Skip to content

[scheduler] Dependencies: render the FS arrow #22855

Closed

Description

@rita-codes

Part of #22853 (event-scheduling umbrella) — FS behavior.

Pure rendering: draw the dependency as an arrow connecting predecessor and successor. No logic, no validation, no rescheduling — it only reads dependencies + event positions and draws.

Coordinate system

Timeline events aren't positioned in raw pixels, and not by time-column indices either — the two axes work differently:

  • Horizontal — percentage-based. useElementPositionInCollection gives each occurrence a fractional position / duration relative to the whole collection range, applied as --x-position: ${position * 100}% / --width: ${duration * 100}% (TimelineGridEvent).
  • Vertical — CSS grid rows. useEventOccurrencesWithTimelinePosition assigns each occurrence a lane for stacking when events overlap (position.firstIndex, consumed as grid-row; the timeline calls it with maxSpan: 1, so lastIndex === firstIndex). One row per resource; a row's height is its lane count.

So both arrow anchors must be computed from the model, not measured from the DOM:

  • x = time fraction of the predecessor's end / successor's start × the scrollable width.
  • y = resource row offset + lane index × lane height.

There's an exact precedent: TimelineGridCurrentTimeIndicator is positioned with calc(var(--title-column-width) + var(--unit-count) * var(--unit-width) * var(--x-position)) inside the row container. The SVG overlay should live in the same coordinate space (sibling of the rows inside RowContainer) and derive its anchor coordinates the same way.

DOM measurement (refs + getBoundingClientRect) is not viable: the timeline is virtualized on both axes and off-screen events are unmounted (see below), so an arrow endpoint may simply have no DOM node.

Reactivity

Anchors derive from reactive state (occurrence positions, lanes, dependencies), so re-drawing on move/resize/scroll follows from selector subscriptions — no ResizeObserver or layout reads needed. If a DOM read ever becomes necessary, it must go in useLayoutEffect (not useEffect), but the model-computed approach avoids the problem entirely.

Virtualization

The timeline is virtualized on both axes (@mui/x-virtualizer): resource rows outside the vertical window are unmounted, and events are additionally culled by the visible column range (EventList in EventTimelinePremiumContent). Consequences:

  • Anchors must come from the model (see above) — an endpoint's DOM may not exist.
  • Only draw arrows that intersect the viewport. The "predecessor visible, successor off-screen" case draws the arrow toward the model-computed (off-screen) anchor, clipped by the overlay.

Edge cases

  • Events with no resource aren't rendered in the timeline at all (occurrencesGroupedByResourceListSelector drops them — there's no "unassigned" row). A dependency whose endpoint is such an event has no anchor → don't draw that arrow (the dependency itself still exists in data).
  • Milestones — not supported yet. The scheduler has no milestone / zero-duration concept today (the event model requires start + end, and drag/resize clamps prevent ~0 durations). Nothing to implement in v1 — just avoid baking in assumptions that would break if milestones land later: don't assume the start/end anchors are two distinct points, don't divide by the event's width.

Scope

  • Render an FS arrow from the predecessor's end edge to the successor's start edge.
  • Anchors computed from the model (time fraction + row + lane); the arrow stays attached on move/resize/scroll.
  • Correct behaviour under virtualization (off-screen endpoints).

Out of scope

Prior art (how competitors render the arrow)

Surveyed Bryntum, DHTMLX, Syncfusion, Frappe, MS Project / GanttPRO:

  • Orthogonal routing (right-angle elbows: straight horizontal/vertical segments) is the de-facto standard — Bryntum, DHTMLX, Syncfusion, MS Project, GanttPRO. Corners are usually softened with a small radius (Bryntum). Frappe is the outlier: a single Bézier curve (arrow_curve), which tangles visually once many links cross.
  • All render into an SVG overlay above the grid, with the arrow head (marker-end) at the successor's start edge.
  • Same-row links are a straight horizontal segment; cross-row links add the vertical segment — confirms anchoring by row + lane matters.

→ Default to orthogonal routing with softened corners (Bryntum/DHTMLX style), not a curve.

Accessibility

The SVG arrow is decorative — a screen reader can't infer "A → B" from a path. Most competitors don't expose the relationship programmatically (DHTMLX docs admit "advanced dependency visualizations may not provide complete programmatic relationships for assistive technologies").

  • Mark the arrow aria-hidden.
  • Expose the dependency relationship in the DOM instead — e.g. accessible text / aria-describedby on the successor ("depends on "). Few libraries do this well → differentiation opportunity.

(Creating/editing a dependency by keyboard is #22856's concern, not this issue.)

Activity

  1. added
    type: new featureExpand the scope of the product to solve a new problem.
    plan: PremiumImpact at least one Premium user.
    scope: schedulerChanges related to the scheduler.
    on Jun 17, 2026
  2. self-assigned this
    on Jun 23, 2026
  3. added
    designThis is about UI or UX design, please involve a designer.
    on Jun 23, 2026
  4. github-actions commented on Jul 22, 2026

    @github-actions
    Contributor

    This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue.
    Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

  5. added a commit that references this issue on Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

designThis is about UI or UX design, please involve a designer.plan: PremiumImpact at least one Premium user.scope: schedulerChanges related to the scheduler.type: new featureExpand the scope of the product to solve a new problem.

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions