luma.gl 是 peer 依赖,用 Yarn 或跳过自动安装 peer 的 pnpm 时需手动安装四个 @luma.gl 包并锁定 ~9.3.0,否则会装到更新版本;setConfig() 会先把所有配置重置为默认值,只想改单项要用 setConfigPartial();initialZoomLevel、randomSeed、attribution 只能在初始化时设置。
npm install @cosmos.gl/graphREADME 未说明具体显存或 GPU 型号要求,仅说明需支持 WebGL 2;Android 设备需支持 OES_texture_float 扩展,否则无法运行。
基于 WebGL 的 GPU 加速力导向图引擎,可在浏览器实时模拟数十万节点和连线的网络图
用 WebGL 在浏览器里跑力导向图布局和渲染,把大规模网络图的计算交给 GPU,适合做高节点数的交互式图可视化。
核心能力
- 用 WebGL 顶点与片元着色器在 GPU 上完成布局计算和绘制
- 支持数十万点与连线的实时力导向模拟
- 提供斥力、重力、摩擦、碰撞力等可配置模拟参数
- 支持点与连线的独立高亮、框选和邻接查询
- 内置点位置、颜色、尺寸及连线样式的 GPU 过渡动画
容易踩的坑
- v3.6.0Release v3.6最新正式版2026-11-04 · 2026-11-04 17:16 UTC
🎉 What's New
The Layout Decides Its Own Size
Every position used to be clamped into the
spaceSizesquare on every tick. A layout whose natural size was larger than the space could not grow into it: it piled up along the border and took the square's shape, andfitViewframed the square rather than the graph. Nothing bounds a position now. A layout grows as far as its forces take it, the repulsion grid follows the points wherever they go, andfitViewframes the points.spaceSize: 4096, // deprecated: it places the world origin at spaceSize / 2 and bounds nothing — leave it at the default graph.fitView() // frames the points, wherever they went layer.getBounds() // deck.gl: the box of the live layout, measured without a stallExisting graphs that filled the space will now spread past it, with gravity setting the size: at the default gravity a layout settles near
2·√(40n)units across, which 100,000 points fit inside the old square and 300,000 did not. A non-defaultspaceSizelogs a deprecation warning; a layout saved around another origin will need shifting when the key goes in the next major. One known limit: a single point very far from the rest coarsens the repulsion grid for everyone until gravity brings it back — the Far Point example shows it. Try it →Repulsion That Fits the Layout
Above 4,096 points the many-body force approximates: far away, a cell's centroid stands in for its points; up close, each point is repelled through a sample of its cell's points. The far-field grid now follows the layout, and the near-field sample count can follow measured cell occupancy.
simulationRepulsionQualitysets how close a centroid may come. One cell away shimmers on clustered data while the simulation is hot; two cells away is calm and costs about a third more per step above 25,000 points.'precise'— every centroid at least two cells away (a 5×5 shell on every level but the finest)'fast'— a centroid may be one cell away (3×3)'auto'— precise up to 25,000 points, fast above
simulationRepulsionSamplesis how many points are sampled from each nearby grid cell. It used to depend on the point count alone, so crowded clusters shimmered while sparse layouts did extra work. Now, when you leave it unset, the graph checks how crowded the layout is and picks the number for you; set a number to fix it. This close-range step is also about a quarter faster.Automatic measurement runs only when
Graphowns both its device and render loop. Host-driven graphs—including deck.gl,GraphSimulation, headless or manual rendering,enableRenderLoop: false, and external-device graphs—keep the point-count fallback (32 / 16 / 8). On a busy GPU each measurement can cause a short pause, so checks are spaced to cost about 1 % of the time. See it under load →simulationRepulsionQuality: 'auto', // new: 'precise' | 'fast' | 'auto' simulationRepulsionSamples: 32, // new: fixes the sample count, 2 to 64; unset (the default) measures itA change of either key applies on the next tick. Try it →
<img width="760" alt="Before and now, far field and near field. Far field: before, lumps stand in for the cells from one cell away, coarse next door and shimmering on clusters; now, from two cells away, still, a third more work above 25,000 p
- v3.5.1Release v3.52026-10-05 · 2026-10-05 12:18 UTC
查看更新说明
A small oops. We published
@cosmos.gl/deck-layers3.5.0 a little too eagerly, and its layer API changed right after. 3.5.0 is deprecated: please use 3.5.1. If you were quick enough to try 3.5.0, thank you, and sorry. The migration notes have the mapping.🎉 What's New
deck.gl Integration:
@cosmos.gl/deck-layerscosmos.gl is now a deck.gl layer. The graph runs on deck's GPU device, and cosmos.gl's own renderer draws it into deck's render pass under deck's camera. Positions never leave the GPU, and everything cosmos.gl draws (point shapes, curved links, arrows, dashes) works in the layer.
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,An accessor you leave out sends nothing, and the matching config key draws instead:
pointDefaultColor,pointDefaultSize,linkDefaultColor,linkDefaultWidth.Everything else is cosmos.gl's configuration, through
config:config: { simulationGravity: 0.25, curvedLinks: true, linkDefaultArrows: true, pointDefaultShape: PointShape.Hexagon }The view must be flat and y-up, like cosmos.gl's space:
OrthographicView({ flipY: false }), or a map at pitch 0 and bearing 0.The layer runs the simulation and draws it. Picking and drag are opt-in, as in any deck.gl layer:
pickable: true,enablePointDrag: true.autoHighlight: truelights the hovered point or link with cosmos.gl's hover ring. Get started → · Live examples → under Examples → Integrations.GraphSimulation: The Layout on Its OwnThe 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 draw it with deck.gl layers of your own, which read its position texture: // the Custom deck layers story shows how. The device must be deck's own, from its onDeviceInitializedGraphis built on it.CosmosGraphLayercreates a headlessGraph, or steps and draws the one you give it throughgraph.Run cosmos.gl Inside Your Own Renderer
For applications that own the GPU device and the frame loop. A headless
Graphtakes 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 - v3.4.0Release v3.42026-07-27 · 2026-07-27 10:40 UTC
查看更新说明
🎉 What's New
Better-Looking Dense Graphs
The repulsion force was rebuilt around one idea: handle far and near neighbors differently.
- Far away — a stack of grids, coarse for distant points and finer closer in. A distant group counts as one blob at its average position, and every part of space is counted exactly once: no gaps, no double counting, nothing to tune.
- Up close — real point-to-point forces instead of an average. From each nearby grid cell a point takes up to 8 actual neighbors, picked at random and re-picked every tick, weighted so it averages out to the exact answer as the layout settles. Cells holding 8 points or fewer use all of them, so most graphs get exact close-range forces.
- Stacked points — points sitting at exactly the same spot have no direction to separate along, so each is pushed along its own random direction and the pile comes apart instead of staying stuck.
Dense hubs spread into readable clouds and tight clusters open up. It's also faster per simulation step. How it works in detail →
simulationRepulsionThetano longer does anything — the new algorithm has no distance bands to tune. It's still accepted so existing configs keep working, but you can safely delete it.Smoother Hover on Large Graphs
The GPU now keeps extra, off-screen images of the graph in which every pixel records which point — or which link — covers it. Hovering is then a quick lookup: read the pixels right under the cursor, instead of checking every point in the graph.
The point image is drawn smaller than the screen — half resolution, and capped on very large displays. Hover doesn't need pixel-perfect precision, and at full size the image would cost tens of megabytes of GPU memory. Links keep full resolution, because a thin link would otherwise fall between pixels and become hard to hover. Both images are redrawn only when the scene changes, and hover reads them without making anything wait.
Hover does nothing at all while the scene and the cursor are both still, and picking is forgiving: you don't have to hit a point dead-center.
Faster Rendering When Points Overlap
When opaque points overlap, only the one on top is visible. The opaque middles are now drawn first, starting with the topmost point, so the GPU can skip whatever is buried underneath instead of drawing it and painting over it. Soft edges and see-through points follow in a second pass, so the picture looks exactly the same — while heavily overlapping scenes render faster.
It's on by default, and applies while points are fully opaque and highlighting isn't in use — otherwise rendering falls back to the old single pass on its own. Set
pointOcclusionCulling: falseto switch it off entirely. Live demo: Misc → Point Occlusion Culling.On-Demand Rendering
Frames are drawn only when something changes, and rendering stops completely when your graph is idle — no GPU, CPU, or battery spent on a static scene. Interactions, config changes, and data updates all wake rendering automatically.
- v3.3.0Release v3.32026-07-12 · 2026-07-12 09:05 UTC
查看更新说明
🎉 What's New
Link Styles: Dashed, Dotted, and Gradient Links
https://github.com/user-attachments/assets/2e26990b-1df6-42b7-956f-7e4e050a258f
Links can now be dashed or dotted, and colored as a gradient between their endpoint colors. Both combine freely — per link.
import { Graph, LinkStyle } from '@cosmos.gl/graph' // one value per link, like setPointShapes graph.setLinkStyles(new Float32Array([LinkStyle.Solid, LinkStyle.Dashed, LinkStyle.Dotted])) // gradient from source to target point color graph.setConfigPartial({ linkColorInterpolateFromEndpoints: true })const config = { linkDefaultStyle: LinkStyle.Solid, // fallback for links without a style linkDashLength: 8, // dash length, in screen px linkDashGap: 4, // gap between dashes (or dots) linkColorInterpolateFromEndpoints: false, // endpoint-color gradient }All four can be changed in real time.
luma.gl 9.3
The rendering stack moved to luma.gl 9.3. Internal only — no API changes.
- v3.2.0Release v3.22026-07-09 · 2026-07-09 11:28 UTC
查看更新说明
🎉 What's New
Add & Remove Points via
NaNPositions (with fade in/out)https://github.com/user-attachments/assets/bbb30e95-99ab-44bc-8392-959848cb110e
Removing a point used to mean shrinking the positions array — renumbering every later point and making the whole graph jump. Now set a point's position to
NaNand it fades out in place; every other point keeps its index and spot. Giving the slot a real position fades it back in.// remove point i — it fades out, everything else stays put positions[i * 2] = NaN positions[i * 2 + 1] = NaN graph.setPointPositions(positions)- The fade is automatic — or pass per-point sizes/colors in the same update to shrink, fade, or recolor a point as it leaves. Hover picking keeps working while a bare removal fades.
- Links follow their points — they fade out with a removed endpoint and stop being hoverable; drop them whenever you compact.
- Absent points are really gone — excluded from all forces (safe with the simulation on or off), from hover, selection, sampling, and zoom/
fitView. - Your arrays stay yours — size/color arrays are read as-is and never modified (
NaNchannels resolve at render time), and read-backs are honest: absent points come back asNaN(getTrackedPointPositionsMapomits them),getPointColors/getPointSizesreturn as-rendered snapshots. - Opt-in and non-breaking — shrinking the array still works exactly as before. cosmos.gl does not auto-compact the gaps; reuse freed slots or compact when convenient.
Per-render transition duration:
render(simulationAlpha?, transitionDuration?)render()takes an optional duration (ms) for the transition it starts — this call only.0snaps,undefinedusesconfig.transitionDuration. Made for compaction: drop the tombstones, renumber, and snap so nothing visibly moves.graph.setPointPositions(compactedPositions) graph.render(undefined, 0) // snap this update onlyA transition already playing keeps its own duration — changing
transitionDurationvia config no longer affects it. To end one immediately, apply an update withrender(undefined, 0).
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
cosmos.gl is a high-performance WebGL Force Graph algorithm and rendering engine. All the computations and drawing occur on the GPU in fragment and vertex shaders, avoiding expensive memory operations. It enables the real-time simulation of network graphs consisting of hundreds of thousands of points and links on modern hardware.
This engine powers 🪐 Cosmograph — a toolset for exploring complex networks and AI embeddings.
📺 Comparison with other libraries
🎮 Check out our storybook for examples
Quick Start
Install the package:
npm install @cosmos.gl/graph
Note: luma.gl (
@luma.gl/core,@luma.gl/engine,@luma.gl/shadertools,@luma.gl/webgl, compatible range~9.3.0) is a peer dependency. npm 7+ installs it automatically; with Yarn, or pnpm configurations that skip auto-installing peers, add the four packages explicitly at~9.3.0— a bare install gets a newer luma.gl. Keeping luma.gl as a peer lets cosmos.gl share one luma.gl installation — and one GPUDevice— with hosts like deck.gl.
Get the data, configure the graph and run the simulation:
import { Graph } from '@cosmos.gl/graph'
const div = document.querySelector('div')
const config = {
simulationFriction: 0.1, // keeps the graph inert
simulationGravity: 0, // disables the gravity force
simulationRepulsion: 0.5, // increases repulsion between points
curvedLinks: true, // curved links
fitViewOnInit: true, // fit the view to the graph after initialization
fitViewDelay: 1000, // wait 1 second before fitting the view
fitViewPadding: 0.3, // centers the graph with a padding of ~30% of screen
rescalePositions: false, // rescale positions, useful when coordinates are too small
enableDrag: true, // enable dragging points
onClick: (pointIndex) => { console.log('Clicked point index: ', pointIndex) },
/* ... */
}
const graph = new Graph(div, config)
// Points: [x1, y1, x2, y2, x3, y3]
const pointPositions = new Float32Array([
0.0, 0.0, // Point 1 at (0,0)
1.0, 0.0, // Point 2 at (1,0)
0.5, 1.0, // Point 3 at (0.5,1)
]);
graph.setPointPositions(pointPositions)
// Links: [sourceIndex1, targetIndex1, sourceIndex2, targetIndex2]
const links = new Float32Array([
0, 1, // Link from point 0 to point 1
1, 2, // Link from point 1 to point 2
2, 0, // Link from point 2 to point 0
]);
graph.setLinks(links)
graph.render()
What's New in v3.0?
cosmos.gl v3.0 brings a new rendering engine, async initialization, and several new features:
- luma.gl (WebGL 2) — rendering ported from regl to luma.gl, with support for sharing a custom
Deviceacross multiple graphs vianew Graph(div, config, devicePromise). - Async initialization — the constructor returns immediately; all public methods queue until the device is ready. Use
graph.ready/graph.isReadyto know when the graph is usable. - Simulation control — rendering is now separate from simulation. The simulation starts automatically by default; use
start(),stop(),pause(), andunpause()to control it independently. - Link sampling — sample visible links on screen with
getSampledLinks()andgetSampledLinkPositionsMap()for rendering labels or overlays along links. - Touch and pen support — tap, drag, and pinch-to-zoom on phones and tablets. Long-press (~500 ms) triggers context menu callbacks.
onMouseMoveand all click callbacks fire from touch input. - Context menu support — new callbacks for right-click on desktop and long-press on touch/pen:
onContextMenu,onPointContextMenu,onLinkContextMenu,onBackgroundContextMenu. - Fit viewport to points —
setZoomTransformByPointPositions()zooms and pans to fit a set of points into view. - Config-driven highlighting — imperative selection methods replaced by
highlightedPointIndices,highlightedLinkIndices, andoutlinedPointIndicesconfig properties. Points and links are highlighted independently. Focused links are rendered wider viafocusedLinkIndex/focusedLinkWidthIncrease. NewfindPointsInRect(),findPointsInPolygon(),getNeighboringPointIndices(),getConnectedLinkIndices(), andgetConnectedPointIndices()methods. - Link blending control —
linkBlending: falsedisables alpha blending for significantly faster link rendering on dense graphs. - Hover improvements —
onPointMouseOvernow includesisHighlightedandisOutlinedparameters; hover correctly highlights the topmost point when points overlap. - Config API changes —
setConfig()now resets all values to defaults before applying; use the newsetConfigPartial()to update individual properties without resetting the rest. - Init-only config fields —
initialZoomLevel,randomSeed, andattributioncan only be set during initialization and are preserved across config updates. - Runtime simulation toggle —
enableSimulationcan now be changed at runtime viasetConfig()orsetConfigPartial(). - GPU transitions — point positions, point colors/sizes, and link colors/widths now animate by default (
transitionDuration: 800,transitionEasing: TransitionEasing.CubicInOut). UsetransitionDuration: 0to keep snap updates. - Transition callbacks — use
onTransitionStart,onTransition, andonTransitionEndto track transition lifecycle and progress. - Default point shape — new
pointDefaultShapeconfig property lets you set the fallback shape for all points when no per-point shapes are provided. Accepts aPointShapeenum value (e.g.,PointShape.Star), a plain number (e.g.,6), or a numeric string (e.g.,"6"). - Collision force — new GPU-accelerated collision force keeps points from overlapping, using a spatial-hash grid that scales to large graphs. Enable it with
simulationCollision, and tune the spacing withsimulationCollisionRadius(fixed radius vs. size-derived) andsimulationCollisionPadding(extra gap between points). See the Collision example. - Exported defaults —
defaultConfigValuesis now part of the public API. - Optimized hover detection — skips GPU work when the mouse hasn't moved.
Check the Migration Guide for upgrading from v2.
What's New in v2.0?
cosmos.gl v2.0 introduces significant improvements in performance and data handling:
- Enhanced data structures with WebGL-compatible formats.
- Methods like
setPointPositionsandsetLinksreplacesetDatafor improved efficiency. - Direct control over point and link attributes via Float32Array (e.g., colors, sizes, widths).
- Updated event handling based on indices instead of objects.
- New Point Clustering force (
setPointClusters,setClusterPositionsandsetPointClusterStrength). - Ability to drag points.
Check the Migration Guide for upgrading from v1.
Examples
Showcase (via cosmograph.app)
- Silk Road Case: Bitcoin Transactions (📄 Read more)
- ABACUS Shell (source)
- The MathWorks, Inc: symmetric positive definite matrix (source)
Known Issues
Starting from version 15.4, iOS has stopped supporting the key WebGL extension powering our Many-Body force implementation (The latest iOS works again!EXT_float_blend). We're investigating this issue and exploring solutions.- cosmos.gl doesn't work on Android devices that don't support the
OES_texture_floatWebGL extension.
Documentation
- 🧑💻 Quick Start
- 🛠 Configuration
- ⚙️ API Reference
- 🚀 Migration Guide
License
MIT