π What's New
deck.gl Integration: @cosmos.gl/deck-layers
cosmos.gl is now a deck.gl layer. The simulation runs on deck's GPU device, and the layer draws straight from its position texture, with no readback.
import { CosmosGraphLayer } from '@cosmos.gl/deck-layers'
new CosmosGraphLayer({ id: 'graph', points: nodes, links: edges, getPointId: (p) => p.id })Points and links are your objects, or cosmos.gl's flat arrays:
// objects, the deck.gl way
points: [{ id: 'a', group: 0 }, β¦]
links: [{ source: 'a', target: 'b' }, β¦]
// flat arrays, no copies: [x0, y0, x1, y1, β¦] and [src0, tgt0, src1, tgt1, β¦]
points: { length, initialPositions }
links: pairsLooks are accessors, as in any deck.gl layer, with colors in 0..255:
getPointColor: (p) => COLORS[p.group],
getPointSize: (p) => p.weight,
getLinkColor: [150, 170, 220, 60],
getLinkWidth: 1,The layer runs the simulation and draws it. Picking and drag are opt-in, as in any deck.gl layer: pickable: true, enablePointDrag: true. Get started β Β· Live examples β under Examples β Integrations.
GraphSimulation: The Layout on Its Own
The simulation is now its own class. Data in, positions out, no canvas and no rendering:
import { GraphSimulation } from '@cosmos.gl/graph'
const simulation = new GraphSimulation(config, devicePromise) // or (config) alone: a hidden device of its own
simulation.setPointPositions(positions)
simulation.setLinks(links)
simulation.applyData()
simulation.start()
// each frame
simulation.step()
simulation.getPointPositionTexture()
// follow a few points, for labels
const tracker = simulation.trackPoints([0, 5, 9])
// or hand it to the deck.gl layer: it steps and draws it, and loads data only if you also pass points.
// The device must be deck's own, from its onDeviceInitialized
new CosmosGraphLayer({ id: 'graph', simulation })Graph is built on it. CosmosGraphLayer creates one, or steps and draws the one you give it.
Run cosmos.gl Inside Your Own Renderer
For applications that own the GPU device and the frame loop. A headless Graph takes no container, schedules no frames, and draws when you tell it to:
const graph = new Graph(null, config, devicePromise) // headless, on your device
graph.setPointPositions(positions)
graph.render() // uploads the data; drawToRenderPass draws nothing until this
// in your frame loop
graph.step() // advance the simulation
graph.setViewTransform({ k, x, y }, [width, height]) // your camera
graph.drawToRenderPass(renderPass) // your render passRead positions the way that suits your renderer:
graph.getPointPositionTexture() // the live texture, sample it by index in your shaders
graph.getPointPositionsArray(out) // a snapshot into your Float32Array
await graph.getPointPositionsAsync(out) // the same, without blocking the main threadMove or pin a few points at a time, the way a drag does:
graph.setPointPosition(index, x, y)
graph.setPinnedPoint(index, true)
graph.isPointPinned(index)To keep cosmos.gl's canvas but drive the frames yourself, set enableRenderLoop: false and call renderOneFrame() when you want a frame. It draws, and steps the simulation while it is running.
Position Reads That Don't Wait for the GPU
graph.getTrackedPointPositionsMap({ nonBlocking: true })
graph.getClusterPositions({ nonBlocking: true })Returns the latest positions the GPU has already handed back instead of waiting for the current tick to finish. Labels that read positions every tick no longer stall the page, most noticeably in Safari. The first read, and the first after the tracked points or the clusters change, still waits: there is nothing earlier to return.
Sizes and Widths As Drawn
For overlays that follow a point or a link while its size animates:
graph.getPointRadiusByIndex(index) // moves with a size transition
graph.getLinkWidthByIndex(index) // new, the same for linksFaster Updates on Large Graphs
Changing colors, sizes or positions no longer rebuilds the per-point link index on every render. That work now happens only when the links or the point count change. On graph.graph, linksBySource and linksByTarget hold the grouping; the sourceIndexToTargetIndices and targetIndexToSourceIndices getters still work but are deprecated.
β οΈ Breaking Change: luma.gl Is Now a Peer Dependency
@luma.gl/* moved from dependencies to peerDependencies at ~9.3.0, so that cosmos.gl and a host such as deck.gl share one luma.gl.
- npm 7+ β peers install themselves. With deck.gl in the app, run the check below: deck's own range admits luma.gl 9.4.
- Yarn, or pnpm without auto-install-peers β install luma.gl next to cosmos.gl, at the range (a bare install gets 9.4):
yarn add @cosmos.gl/graph @luma.gl/core@~9.3.0 @luma.gl/engine@~9.3.0 @luma.gl/shadertools@~9.3.0 @luma.gl/webgl@~9.3.0 # or pnpm add @cosmos.gl/graph @luma.gl/core@~9.3.0 @luma.gl/engine@~9.3.0 @luma.gl/shadertools@~9.3.0 @luma.gl/webgl@~9.3.0 - CDN / UMD β nothing to do, the bundle still includes luma.gl.
Check that one copy resolves, inside ~9.3.0: npm ls @luma.gl/core. Migration guide β
π Fixes
- No more jitter on small graphs β the many-body force is exact on small graphs and samples adaptively on large ones.
- Every edge is anti-aliased at every pixel ratio β a one-device-pixel ramp on every edge of links, rings and point shapes.
- Setters during initialization no longer crash the graph β they queue and run once
readyresolves. - Multiple Graph instances on one page no longer interfere with each other.
Also: invalid input is neutralized instead of corrupting the layout, selection and GPU reads return the right points, config changes that were silently ignored now apply, lifecycle calls made before the graph is ready take effect in order, an async position snapshot never returns stale data, and the package builds with modern bundlers.