πͺ 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.
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:800ms;0disables animation)transitionEasing(default:TransitionEasing.CubicInOut)
Lifecycle callbacks:
onTransitionStart()β cycle beganonTransition(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
*ContextMenucallback 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,undefinedclears highlightingoutlinedPointIndicesβ points rendered with an outline ringoutlinedPointRingColor(default:'white')
New config for links:
highlightedLinkIndicesβ same[]vsundefinedsemantics as pointsfocusedLinkIndexβ 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 forcesfalse β true: creates simulation modules and GPU resources, firesonSimulationStart. If a transition is mid-flight it's interrupted first.true β false: stops the simulation, destroys simulation-only resources, firesonSimulationEnd. 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 linksgetSampledLinkPositionsMap()β same data as aMap<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, otherwiseundefinedonPointContextMenu(index, pointPosition, event)β on a pointonLinkContextMenu(linkIndex, event)β on a linkonBackgroundContextMenu(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 setgetConnectedPointIndices(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 transitionNew API Methods
setZoomTransformByPointPositions(positions, duration?, scale?, padding?, enableSimulation?)β fit the viewport to a set of points by passing their positions as a flatFloat32Array.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 whensetPointShapes()is missing or contains invalid values. AcceptsPointShape, 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
rescalePositionscentering β 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 whenrender()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βpointDefaultColorpointSizeβpointDefaultSizelinkColorβlinkDefaultColorlinkWidthβlinkDefaultWidthlinkArrowsβlinkDefaultArrows
Config options removed (no replacement):
useClassicQuadtreesimulationRepulsionQuadtreeLevels
Callbacks renamed:
onSimulationRestartβonSimulationUnpause
Methods renamed:
getPointsInRange()βfindPointsInRect(rect)getPointsInRect(selection)βfindPointsInRect(rect)β now returnsnumber[]instead ofFloat32Array; must be called afterawait graph.readygetPointsInPolygon(polygonPath)βfindPointsInPolygon(polygonPath)β now returnsnumber[]instead ofFloat32Array; must be called afterawait graph.readyselectPointsInRange()βfindPointsInRect(rect)thensetConfigPartial({ highlightedPointIndices })restart()βunpause()getAdjacentIndices(index)βgetNeighboringPointIndices(pointIndices)β now acceptsnumber | number[], returns a deduplicatednumber[]
Selection methods removed (replaced by the highlight/outline config above):
selectPointByIndex()/selectPointsByIndices()βsetConfigPartial({ highlightedPointIndices })selectPointsInRect()βfindPointsInRect()thensetConfigPartial({ highlightedPointIndices })selectPointsInPolygon()βfindPointsInPolygon()thensetConfigPartial({ highlightedPointIndices })unselectPoints()βsetConfigPartial({ highlightedPointIndices: undefined, highlightedLinkIndices: undefined })getSelectedIndices()β track highlighted indices in your own state
Type export removed:
GraphConfigInterfaceis no longer exported. UseGraphConfiginstead.
Changed Defaults
spaceSize:8192β4096β values above4096can 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. UsesetConfigPartial()to update only specific properties without resetting the rest.- Init-only config fields β
initialZoomLevel,randomSeed, andattributioncan only be set during initialization and are preserved acrosssetConfig()andsetConfigPartial()calls. (enableSimulationis no longer init-only β see Runtime Simulation Toggle.) render()no longer controls simulation β usestart(),stop(),pause(), andunpause()instead.start()no longer starts the render loop β it only resets and begins the simulation (alpha, progress, running state). Callrender()separately to start drawing. Callingstart(alpha)while the simulation is already running reheats it (resets alpha and progress) without firingonSimulationStartagain.step()no longer pauses the simulation β it runs exactly one simulation tick and leaves the running state untouched.enableSimulationDuringZoomscope 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 newenableSimulationparameter.- Initialization is now async β the constructor returns immediately; use
ready/isReadyto 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, andhoveredLinkColornow expect[r, g, b, a]values in the0..1range. Passing RGB values in0..255is 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
onPointClickright away. Previously the first tap panned the graph and required a second tap to register a click. onMouseMovefires throughout a touch gesture, not just at the end. If your handler does heavy work, throttle it.