Skip to content

Release v3

Latest

Choose a tag to compare

@Stukova Stukova released this 17 Jun 16:03
· 2 commits to main since this release
dd1fbf5

πŸͺ Port to luma.gl (WebGL 2)

Rendering has been ported from regl to luma.gl (WebGL 2). All public GPU types (Framebuffer, Texture, Buffer, draw commands) and constructor/device/readPixels usage have changed.

The legacy ForceManyBodyQuadtree module and its related config options (useClassicQuadtree, simulationRepulsionQuadtreeLevels) have been removed.

SCR-20260618-kaya

Custom Device

The constructor now accepts an optional third parameter devicePromise β€” a Promise<Device> for your own luma.gl Device β€” so you can share a device across multiple graphs or configure it yourself: new Graph(div, config?, devicePromise?). When an external device is provided, Graph does not own it and will not destroy it on cleanup.

import { luma } from '@luma.gl/core'
import { webgl2Adapter } from '@luma.gl/webgl'

const devicePromise = luma.createDevice({
  type: 'webgl',
  adapters: [webgl2Adapter],
  createCanvasContext: {
    useDevicePixels: window.devicePixelRatio || 2,
    autoResize: true,
  },
})

const graph = new Graph(div, config, devicePromise)

// When destroying, clean up the external device yourself:
graph.destroy()
devicePromise.then(device => device.destroy())

Async Initialization

The graph now exposes ready (a Promise<void> that resolves when initialization is complete) and isReady (a boolean) so you can wait for or check initialization. Initialization is now fully asynchronous β€” the constructor returns immediately and all public methods (like setConfig, setPointPositions, setLinks, etc.) automatically queue until the device is ready.

const graph = new Graph(div, config)
graph.setPointPositions(pointPositions)
graph.render()

await graph.ready
// Graph is now fully initialized
const positions = graph.getPointPositions()

// Or check synchronously:
if (graph.isReady) {
  const positions = graph.getPointPositions()
}

✨ What's New

GPU Transitions

Updates to point positions, colors, sizes, and link widths/colors now animate instead of snapping. Positions interpolate on the GPU; colors/sizes/widths blend in the draw shader.

New config:

  • transitionDuration (default: 800 ms; 0 disables animation)
  • transitionEasing (default: TransitionEasing.CubicInOut)

Lifecycle callbacks:

  • onTransitionStart() β€” cycle began
  • onTransition(progress) β€” eased progress in [0, 1]
  • onTransitionEnd(interrupted) β€” cycle finished or was cut short
const graph = new Graph(div, {
  transitionDuration: 800,
  transitionEasing: TransitionEasing.CubicInOut,
  onTransitionEnd: (interrupted) => { /* ... */ },
})

// First render β€” establishes the initial layout, snaps into place.
graph.setPointPositions(initialPositions)
graph.render()

// Subsequent updates animate from the current state to the new one.
graph.setPointPositions(nextPositions)
graph.render()

All updates queued between two render() calls animate together as one synchronized cycle.

Auto-pause. When a position transition starts with the simulation running, the simulation pauses for the transition and stays paused afterwards β€” setPointPositions() signals intent to explore a specific layout, not feed the physics. Call graph.unpause() to resume.

TransitionEasing is exported from the package.

Touch and Pen Support

Cosmos now works properly on phones and tablets. All existing callbacks fire from touch input.

  • Tap a point β†’ onPointClick
  • Tap a link β†’ onLinkClick
  • Tap empty space β†’ onBackgroundClick
  • Press and hold (~500 ms) on any of the above β†’ the matching *ContextMenu callback fires (touch's equivalent of right-click)
  • Drag a point to move it, drag empty space to pan, pinch to zoom

Read-only embeds (enableDrag: false and enableZoom: false) let the surrounding page scroll over the canvas.

Highlight, Outline, Focus

The selection API has been replaced with three independent config-driven visual states that work on both points and links. Point and link highlighting are independent β€” greying out points no longer auto-greys their links.

New config for points:

  • highlightedPointIndices β€” [] greys all points, undefined clears highlighting
  • outlinedPointIndices β€” points rendered with an outline ring
  • outlinedPointRingColor (default: 'white')

New config for links:

  • highlightedLinkIndices β€” same [] vs undefined semantics as points
  • focusedLinkIndex β€” index of a single focused link (renders wider)
  • focusedLinkWidthIncrease (default: 5)
graph.setConfigPartial({
  highlightedPointIndices: [0, 1, 2],
  highlightedLinkIndices: graph.getConnectedLinkIndices([0, 1, 2]),
})

// Clear
graph.setConfigPartial({
  highlightedPointIndices: undefined,
  highlightedLinkIndices: undefined,
})

Runtime Simulation Toggle

enableSimulation is no longer init-only β€” flip it at any time via setConfig or setConfigPartial:

graph.setConfigPartial({ enableSimulation: false }) // freeze layout, keep rendering
graph.setConfigPartial({ enableSimulation: true })  // re-enable forces
  • false β†’ true: creates simulation modules and GPU resources, fires onSimulationStart. If a transition is mid-flight it's interrupted first.
  • true β†’ false: stops the simulation, destroys simulation-only resources, fires onSimulationEnd. Active transitions keep playing.

Link Sampling

Sample the links currently visible on screen, getting their midpoint positions and angles.

New API:

  • getSampledLinks() β€” returns { indices, positions, angles } arrays for a sample of visible links
  • getSampledLinkPositionsMap() β€” same data as a Map<linkIndex, [x, y, angle]>

New config:

  • linkSamplingDistance (default: 100px) β€” controls density; smaller = more links sampled
graph.setConfig({
  linkSamplingDistance: 50, // denser sampling
  onZoom: () => {
    const { indices, positions, angles } = graph.getSampledLinks()
    // angles are in radians, clockwise from right
    // render your elements at positions[i*2], positions[i*2+1], rotated by angles[i]
  }
})

Context Menu Support

Reachable via right-click on desktop and long-press (~500 ms) on touch and pen.

  • onContextMenu(index, pointPosition, event) β€” anywhere on the canvas; passes hovered point info if a point is under the cursor, otherwise undefined
  • onPointContextMenu(index, pointPosition, event) β€” on a point
  • onLinkContextMenu(linkIndex, event) β€” on a link
  • onBackgroundContextMenu(event) β€” on empty space

Connection Discovery

New methods to traverse the graph structure:

  • getConnectedLinkIndices(pointIndices) β€” returns link indices where both endpoints are in the given point set
  • getConnectedPointIndices(linkIndices) β€” returns point indices at the endpoints of the given links

Useful for selection workflows β€” e.g. highlighting a node and all the links between its selected neighbors.

Simulation Control

Rendering is now separate from simulation. render() only starts the render loop and no longer changes simulation state; use start(), stop(), pause(), and unpause() to control the simulation.

Per-Call Simulation Control for Zoom

enableSimulationDuringZoom now controls whether the simulation stays active during interactive (user-driven) zoom (scroll/pinch). All programmatic zoom/fitView APIs accept an optional enableSimulation parameter (default: true) to override this behavior for that specific transition.

New optional param (programmatic zoom only):

  • zoomToPointByIndex(index, duration?, scale?, canZoomOut?, enableSimulation?)
  • zoom(value, duration?, enableSimulation?)
  • setZoomLevel(value, duration?, enableSimulation?)
  • fitView(duration?, padding?, enableSimulation?)
  • fitViewByPointIndices(indices, duration?, padding?, enableSimulation?)
  • fitViewByPointPositions(positions, duration?, padding?, enableSimulation?)
  • setZoomTransformByPointPositions(positions, duration?, scale?, padding?, enableSimulation?)
graph.fitView(250, 0.1, false) // pause simulation during the transition

New API Methods

  • setZoomTransformByPointPositions(positions, duration?, scale?, padding?, enableSimulation?) β€” fit the viewport to a set of points by passing their positions as a flat Float32Array.
  • setConfigPartial(config) β€” partially updates the graph configuration. Only the provided properties will be changed; all other properties retain their current values.

Default Point Shape

  • pointDefaultShape β€” new config fallback for point shapes when setPointShapes() is missing or contains invalid values. Accepts PointShape, number, or numeric string. Default: PointShape.Circle.

Hover Improvements

Hover now correctly highlights the topmost point when points overlap during selection, and the onPointMouseOver callback now includes isHighlighted and isOutlined flags: onPointMouseOver(index, pointPosition, event, isHighlighted: boolean, isOutlined: boolean).

Exported Default Config Values

Default config values are now exported as part of the public API for easy reference.

import { defaultConfigValues } from '@cosmos.gl/graph'

Link Blending

A new linkBlending config option (default: true) controls whether links are rendered with alpha blending. Alpha blending is required for transparency and antialiased link edges but carries significant GPU overhead on large graphs. Disabling it can dramatically improve rendering performance when you have many links.

const graph = new Graph(div, {
  linkBlending: false, // faster link rendering, no transparency
})

// Or toggle at runtime:
graph.setConfigPartial({ linkBlending: false })

πŸ› Fixes

  • Fixed rescalePositions centering β€” nodes are now placed in the center of the simulation space instead of the bottom-left corner.
  • Fixed hover state lost on render() β€” hover highlight is now preserved when render() is called.
  • Fixed image size in hover detection and selection β€” hover detection, highlight ring, and area selection now use the max of shape size and image size, preventing missed hovers and incorrect outlines on image nodes.

πŸ”§ Optimizations

  • Reduced per-frame GPU work by skipping hover detection when the mouse hasn't moved, while still updating correctly after simulation or zoom ends.

⚠️ Breaking Changes

Removed APIs

Config options renamed:

  • pointColor β†’ pointDefaultColor
  • pointSize β†’ pointDefaultSize
  • linkColor β†’ linkDefaultColor
  • linkWidth β†’ linkDefaultWidth
  • linkArrows β†’ linkDefaultArrows

Config options removed (no replacement):

  • useClassicQuadtree
  • simulationRepulsionQuadtreeLevels

Callbacks renamed:

  • onSimulationRestart β†’ onSimulationUnpause

Methods renamed:

  • getPointsInRange() β†’ findPointsInRect(rect)
  • getPointsInRect(selection) β†’ findPointsInRect(rect) β€” now returns number[] instead of Float32Array; must be called after await graph.ready
  • getPointsInPolygon(polygonPath) β†’ findPointsInPolygon(polygonPath) β€” now returns number[] instead of Float32Array; must be called after await graph.ready
  • selectPointsInRange() β†’ findPointsInRect(rect) then setConfigPartial({ highlightedPointIndices })
  • restart() β†’ unpause()
  • getAdjacentIndices(index) β†’ getNeighboringPointIndices(pointIndices) β€” now accepts number | number[], returns a deduplicated number[]

Selection methods removed (replaced by the highlight/outline config above):

  • selectPointByIndex() / selectPointsByIndices() β†’ setConfigPartial({ highlightedPointIndices })
  • selectPointsInRect() β†’ findPointsInRect() then setConfigPartial({ highlightedPointIndices })
  • selectPointsInPolygon() β†’ findPointsInPolygon() then setConfigPartial({ highlightedPointIndices })
  • unselectPoints() β†’ setConfigPartial({ highlightedPointIndices: undefined, highlightedLinkIndices: undefined })
  • getSelectedIndices() β†’ track highlighted indices in your own state

Type export removed:

  • GraphConfigInterface is no longer exported. Use GraphConfig instead.

Changed Defaults

  • spaceSize: 8192 β†’ 4096 β€” values above 4096 can crash the graph on iOS.
  • pixelRatio: 2 β†’ window.devicePixelRatio || 2 β€” the canvas now matches the display's native pixel ratio by default, which may change rendering quality and GPU memory usage.

Behavioral Changes

  • setConfig() now resets to defaults β€” setConfig() fully resets all configuration to default values before applying the provided properties. Use setConfigPartial() to update only specific properties without resetting the rest.
  • Init-only config fields β€” initialZoomLevel, randomSeed, and attribution can only be set during initialization and are preserved across setConfig() and setConfigPartial() calls. (enableSimulation is no longer init-only β€” see Runtime Simulation Toggle.)
  • render() no longer controls simulation β€” use start(), stop(), pause(), and unpause() instead.
  • start() no longer starts the render loop β€” it only resets and begins the simulation (alpha, progress, running state). Call render() separately to start drawing. Calling start(alpha) while the simulation is already running reheats it (resets alpha and progress) without firing onSimulationStart again.
  • step() no longer pauses the simulation β€” it runs exactly one simulation tick and leaves the running state untouched.
  • enableSimulationDuringZoom scope changed β€” it now applies to interactive (user-driven) zoom only. Programmatic zoom/fitView transitions run simulation by default and can be controlled per call via the new enableSimulation parameter.
  • Initialization is now async β€” the constructor returns immediately; use ready / isReady to know when the graph is usable.
  • RGBA array config values are now normalized β€” tuple-style color config values such as backgroundColor, pointDefaultColor, pointGreyoutColor, hoveredPointRingColor, focusedPointRingColor, outlinedPointRingColor, linkDefaultColor, and hoveredLinkColor now expect [r, g, b, a] values in the 0..1 range. Passing RGB values in 0..255 is a breaking change in v3 and must be converted before use.

Transitions Enabled by Default

transitionDuration defaults to 800 ms in v3. After the first render, setPointPositions(...) + render() animates instead of snapping. To preserve v2 behavior, set transitionDuration: 0:

const graph = new Graph(div, { transitionDuration: 0 })

Or disable it for a single update cycle:

graph.setConfigPartial({ transitionDuration: 0 })
graph.setPointPositions(nextPositions)
graph.render()
graph.setConfigPartial({ transitionDuration: 800 })

Position transitions also auto-pause the simulation and keep it paused afterwards. Call graph.unpause() to resume forces.

Touch Behavior

  • First tap on a point now drags it and fires onPointClick right away. Previously the first tap panned the graph and required a second tap to register a click.
  • onMouseMove fires throughout a touch gesture, not just at the end. If your handler does heavy work, throttle it.