LiveFlow.Viewport.Culling (LiveFlow v0.3.2)

Copy Markdown View Source

Server-side viewport culling for LiveFlow.

Filters nodes and edges to only render those visible within the current viewport, dramatically reducing DOM size for large flows.

How It Works

The viewport transform maps flow-space to screen-space:

screen_x = flow_x * zoom + viewport.x
screen_y = flow_y * zoom + viewport.y

Inverting gives us the visible flow-space rectangle:

flow_left   = -viewport.x / zoom
flow_top    = -viewport.y / zoom
flow_right  = (-viewport.x + container_width) / zoom
flow_bottom = (-viewport.y + container_height) / zoom

A node is visible if its bounding box overlaps this rectangle (with a margin). Unmeasured nodes use an estimated size (200x100) for the visibility check, so they render when near the viewport and get measured by JS on first appearance.

When zoomed out far enough that more than max_visible nodes would be in view, nodes are sampled by proximity to the viewport center to cap DOM size.

Summary

Functions

Filters a list of edges to only those with at least one visible endpoint.

Filters a list of nodes to only those visible in the viewport.

Checks if a node is visible within the given flow-space rectangle.

Computes the visible rectangle in flow-space coordinates.

Functions

cull_edges(edges, visible_node_ids)

@spec cull_edges(list(), MapSet.t()) :: list()

Filters a list of edges to only those with at least one visible endpoint.

An edge is kept if its source OR target node is in the visible set. This prevents edges from "popping" in when both endpoints are just outside the viewport.

cull_nodes(nodes, viewport, container_w, container_h, opts \\ [])

@spec cull_nodes(list(), LiveFlow.Viewport.t(), number(), number(), keyword()) ::
  {list(), MapSet.t()}

Filters a list of nodes to only those visible in the viewport.

Returns {visible_nodes, visible_node_ids} where visible_node_ids is a MapSet used for edge culling.

When more than max_visible nodes fall within the viewport (e.g. when zoomed out very far), nodes closest to the viewport center are prioritized to cap DOM size and prevent browser freezes.

node_visible?(node, rect)

@spec node_visible?(map(), map()) :: boolean()

Checks if a node is visible within the given flow-space rectangle.

Unmeasured nodes use their position as a point check (with a generous default size estimate) so they only render when near the viewport. This prevents rendering thousands of off-screen unmeasured nodes at once.

visible_rect(viewport, container_w, container_h, margin \\ 200)

@spec visible_rect(LiveFlow.Viewport.t(), number(), number(), number()) :: map()

Computes the visible rectangle in flow-space coordinates.

Returns %{left: float, top: float, right: float, bottom: float}. The margin (in flow-space pixels) extends the rectangle in all directions to preload nodes just outside the viewport for smooth panning.