@cosmos.gl/graph Documentation

repository·main·Indexed 22 days ago

https://github.com/cosmosgl/graph

A high-performance, GPU-accelerated WebGL force graph engine designed for real-time simulation of massive networks with hundreds of thousands of points and links. It features a P3M (particle–particle / particle–mesh) repulsion algorithm using a Grid Pyramid and Monte-Carlo near-field sampling to achieve efficient, natural layouts without manual tuning. The engine includes GPU-rendered ID buffers for constant-time hover picking of points and links, as well as support for collision forces and animated GPU transitions.

Tokens
38.8K
Snippets
46
Records
213
Agent score
76%

What's inside @cosmos.gl/graph

  1. How the spatial-hash collision force works on the GPU

    main

    The collision force uses a spatial-hash grid to avoid $O(n^2)$ complexity, allowing it to scale to hundreds of thousands of points. The GPU pipeline operates in two phases per tick, repeated over 4 half-cell offsets to handle collisions straddling cell boundaries:

    1. Build Phase: For each offset, a point-list draw bins every point into a grid cell using additive blending. Each cell accumulates (sumX, sumY, sumSize, count).
    2. Resolve Phase: A fullscreen pass reads the cell averages in a point's 3×3 neighborhood and computes a push-apart velocity. This is accumulated additively across the 4 offsets.

    Key Technical Details:

    • Grid Sizing: cellSize = max(effectiveRadius, 8). The gridTextureSize is clamped between 32 and 512.
    • Stability: To prevent 'ping-ponging' (overshooting) in dense regions, the force is clamped to ~10% of the collision radius per pass (roughly 40% per frame across 4 passes).
    • Density Damping: The force is scaled down when a point has many neighbors to reduce jitter.
  2. How the Many-Body Repulsion force works

    main

    The Many-Body (repulsion) force uses a hybrid approach to approximate all-pairs repulsion efficiently. It replaces the old theta-banded quadtree with a grid pyramid combined with a Monte-Carlo near field.

    • Grid Pyramid: A series of grids with increasing resolutions (from 4² up to an adaptive finest resolution of ~2·√n cells per axis, bounded between 8² and 512²). Each level of the pyramid handles centroid repulsion for its specific spatial coverage.
    • Monte-Carlo Near Field: For the finest 3×3 neighborhood, the algorithm uses a depth-peeling technique to select a random K-subset (where K=8) of points from each cell. By weighting each sampled pairwise force by count / sampled (using the Horvitz–Thompson estimator), the expected force is an unbiased estimate of the exact all-pairs sum. This allows dense clumps to spread tangentially rather than collapsing into flat disks.
    • Accuracy: For small or sparse graphs where cells contain ≤ 1 point, the approximation is effectively exact.
  3. Use async vs synchronous picking for different interactions

    main

    The picking system provides two modes of operation depending on the required latency:

    Async Readback (Hovering)

    For hover effects, the system uses PickingReadback to perform non-blocking reads via fenceSync/clientWaitSync. This prevents GPU→CPU pipeline stalls. The result typically arrives one or two frames after the event, which is imperceptible for hover states.

    Synchronous Readback (Clicks/Drags)

    For interactions that require immediate feedback (like click, drag-start, or long-press), the system uses synchronous methods to ensure the hovered item is populated before the event handler executes:

    • pickPointSync
    • pickLinkSync

    These methods ensure that store.hoveredPoint (or the relevant link state) is updated immediately within the same event loop iteration.

  4. Understand the picking mechanism for points and links

    main

    The library uses a high-performance picking system designed to decouple the cost of detecting element hovers from the total number of elements. It employs two distinct strategies:

    1. Points: Uses a half-resolution screen-space ID buffer (rgba32float) containing [index, x, y]. When hovering, the system reads a 9×9 window around the cursor and uses resolveNearestPickedPoint to find the closest valid candidate. This makes point picking independent of the total point count.
    2. Links: Uses a full-resolution link index buffer (linkIndexFbo) containing [index, 0, 0, validity]. The system reads the 1×1 pixel directly under the cursor using resolvePickedLinkIndex.

    Priority: Points always take precedence over links. If a point is detected within the 9×9 window, the link result is discarded. Note that due to the 9×9 window, points have a slight 'forgiveness' area where they will win over links even if the cursor is technically over the link near a node endpoint.

  5. Replace selection methods with config-driven highlighting

    main

    The method-based selection API (e.g., selectPointByIndex) has been removed in v3.0. Visual states for points and links are now controlled via configuration properties using setConfigPartial().

    Key Concepts:

    • Highlighting: Use highlightedPointIndices and highlightedLinkIndices.
    • Clearing Highlighting: To clear all highlights, set the indices to undefined. Setting them to [] (an empty array) activates highlighting but greys out everything.
    • Independence: Point and link highlighting are independent; greying out points does not automatically grey out links.

    New Config Properties:

    • highlightedPointIndices: Array of indices to highlight ([] = all greyed, undefined = no highlighting).
    • outlinedPointIndices: Array of indices to render with an outline ring.
    • outlinedPointRingColor: Color of the outline ring (default: 'white').
    • highlightedLinkIndices: Array of indices to highlight.
    • focusedLinkIndex: Index of a single focused link (renders wider).
    • focusedLinkWidthIncrease: Extra pixels added to focused link width (default: 5).

    New Helper Methods:

    • getConnectedLinkIndices(pointIndices): Returns link indices where both endpoints are in the provided set.
    • getConnectedPointIndices(linkIndices): Returns point indices at the endpoints of the given links.
    // After (v3)
    graph.setConfigPartial({
      highlightedPointIndices: [0, 1, 2],
      highlightedLinkIndices: graph.getConnectedLinkIndices([0, 1, 2]),
    })
    
    // Clear highlighting
    graph.setConfigPartial({
      highlightedPointIndices: undefined,
      highlightedLinkIndices: undefined,
    })
  6. Behavior of absent points in simulation and camera

    main

    Points with NaN positions are treated as 'absent' across the entire engine to ensure stability:

    • Physics/Simulation: Absent points are excluded from force calculations (centroid, grid, spring, collision, etc.) to prevent NaN values from poisoning the layout.
    • Picking/Selection: Absent points are ignored by hover, rect selection, and polygon selection.
    • Camera/Zoom: zoomToPointByIndex is a no-op for absent points. The camera math (zoom/fitView) skips non-finite positions to prevent the view from collapsing to NaN when all points are removed.
    • Read-backs:
      • getPointPositions and getTrackedPointPositionsArray return NaN for absent slots to maintain index alignment.
      • getTrackedPointPositionsMap omits the key for absent points.
  7. Understand link pattern zoom behavior

    main

    The behavior of dashed and dotted patterns during zooming is determined by the scaleLinksOnZoom configuration flag. This determines whether the pattern is fixed to the screen or locked to the graph geometry.

    scaleLinksOnZoomPattern spaceOn zoom
    false (default)screen pixelsconstant on-screen dash size, but the pattern shifts along the link as its on-screen length changes
    truegraph (world) spacepattern is locked to the link and scales with zoom — no crawling

    Note: Constant on-screen size and no-crawl are mutually exclusive. On curved links, the pattern is an approximation based on the non-arc-length curve parameter.

  8. Animate GPU transitions

    main

    Point positions, colors, sizes, link colors, and widths animate by default. You can control this behavior using:

    • transitionDuration: The duration of the animation (e.g., 800). Set to 0 to disable animations and snap updates immediately.
    • transitionEasing: The easing function (e.g., TransitionEasing.CubicInOut).

    You can track the animation lifecycle using the following callbacks:

    • onTransitionStart
    • onTransition
    • onTransitionEnd
  9. Understand On-demand rendering in cosmos.gl

    main

    As of version 3.4.0, cosmos.gl uses an on-demand rendering model to save GPU resources and battery life. Instead of rendering on every requestAnimationFrame indefinitely, the engine only schedules frames when visual changes are possible. When the scene is static (e.g., simulation has decayed, no user interaction, no transitions), the rendering loop idles, costing zero work per frame.

    When the rendering loop stays active

    The loop continues to run automatically if any of the following conditions are met:

    • Simulation/Transitions: store.isSimulationRunning is true, or a transition.isActive is in progress.
    • User Interaction: A drag is active (dragInstance.isActive), a zoom is in flight (zoomInstance.isRunning), or a mouse/touch event is being processed.
    • Visual Feedback: fpsMonitor is enabled (showFPSMonitor: true), or right-click repulsion is active.
    • Hover Detection: There is pending hover work (hasPendingHoverWork()).
    • Environment Constraints: If ResizeObserver is unavailable in the environment (e.g., jsdom or legacy embeds), the loop continues to run to poll for size changes manually.

    Triggering manual renders

    If you are integrating cosmos.gl with external devices or custom logic that requires a redraw, you must explicitly trigger a render. The engine no longer redraws the shared context every frame by default. Use the render() method to wake the loop and ensure visual updates are applied.

  10. How point fade in/out transitions work

    main

    When a point is marked absent via NaN positions, the engine handles the visual transition automatically:

    • Fade Out (Removal): The point freezes at its last real position. The engine interpolates its size and opacity from its current values toward EXIT_DEFAULT_SIZE and EXIT_DEFAULT_COLOR_CHANNEL (transparent/size 0). No manual size/color transition is required.
    • Fade In (Addition): A point transitions from NaN to a real position. It appears at its target position and fades in from the exit defaults to its current size/color.

    Key Behaviors:

    • Hover/Picking: Hover picking remains active during a bare-removal fade.
    • Custom Exits: If you manually call setPointSizes or setPointColors during a removal, you are providing a custom exit look, which will pause hover interaction as per standard behavior.
    • Links: Links are considered absent if either of their endpoints is absent. Links will fade in/out in sync with their endpoints using the same animated ramp.
  11. How hover picking works with screen-space ID buffers

    main

    Hover detection in @cosmos.gl/graph uses GPU-rendered ID buffers instead of scanning elements on every mouse move. This allows for constant-time hover detection regardless of the number of elements.

    Point Picking

    Points are rasterized into a half-resolution buffer (capped at 1536px, rgba32float). Each point is rendered as a circular sprite containing [point index, x, y] per pixel.

    • Detection Mechanism: A hover reads a 9×9 pixel window under the cursor and selects the valid candidate nearest to the cursor.
    • Performance: Constant cost relative to point count due to the fixed window size and resolution cap.

    Links use a full-resolution link index buffer containing [link index, 0, 0, validity].

    • Detection Mechanism: The buffer is sampled at the single pixel directly under the cursor. Full resolution is required to ensure 1px links are not missed between texels.
    • Features:
      • Alpha gates determine pickability.
      • Dash gaps remain pickable (the dash mask is only applied to the visible pass).
      • Hover Hysteresis: The hovered link is drawn wider in this buffer to make selection easier.

    Interaction Rules and Lifecycle

    • Priority: A picked point always takes precedence over a picked link.
    • Invalidation: Buffers re-render only when marked stale (movement, zoom, resize, data, or config updates). A hover change specifically invalidates only the link buffer to update the wider hover-state drawing.
    • Asynchronicity: Hover detection is performed asynchronously using PBO + fence to prevent stalling the GPU pipeline. However, clicks, drag starts, and long-presses use synchronous reads to ensure immediate response within the event loop.
  12. Understand the Many-Body Repulsion Algorithm

    main

    The many-body repulsion algorithm in @cosmos.gl/graph implements a P3M (particle–particle / particle–mesh) scheme to calculate repulsion forces between $n$ points efficiently. It solves the $O(n^2)$ complexity problem by approximating distant mass while maintaining high fidelity for nearby points.

    Core Components

    1. Grid Pyramid (Far Field): A hierarchy of grids (resolutions like $4^2, 8^2, 16^2, ext{etc.}$) aggregates mass into cells using [Σx, Σy, count]. Each cell represents a centroid (center of mass) used to calculate repulsion for distant points.
    2. Monte-Carlo Near Field (Close Range): Instead of using a single centroid for nearby points (which causes radial-only force artifacts), the algorithm uses an unbiased random sample of real pairwise forces. This allows points to move tangentially and spread into natural 'clouds' rather than flat disks.
    3. Seamless Tiling: The algorithm uses a fixed, resolution-independent rule where each level of the pyramid tiles space exactly once, eliminating the need for a simulationRepulsionTheta tuning parameter.

    Key Benefits

    • No Tuning Required: The old simulationRepulsionTheta parameter is deprecated and ignored; the new algorithm is seam-free.
    • Natural Layouts: Near-field sampling allows for tangential forces, preventing dense hubs from collapsing into 'petal' shapes.
    • Efficiency: It is approximately 1.2–4× faster than the old theta-banded quadtree approach due to a fixed loop structure and coherent texel fetches.
    • Effectively Exact for Sparse Graphs: In cells with $\le 8$ points, the sampling is exhaustive, making the force calculation exact.