Skip to content

Cosmos.gl renderer support - #400

Open
InfinityMod wants to merge 5 commits into
reaviz:masterfrom
Lumvise:cosmos.gl-renderer-support
Open

Cosmos.gl renderer support#400
InfinityMod wants to merge 5 commits into
reaviz:masterfrom
Lumvise:cosmos.gl-renderer-support

Conversation

@InfinityMod

@InfinityMod InfinityMod commented Apr 28, 2026

Copy link
Copy Markdown

PR Checklist

Please check if your PR fulfills the following requirements:

  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

PR Type

As already mentioned in recent bug reports (#113), the framework's rendering performance for large graph networks is laggy and therefore not usable for graphs with multiple thousand nodes/edges. This stems from the three.js rendering engine, which doesn't use native WebGL rendering. Therefore, this feature introduces the implementation of the cosmos.gl renderer, which tends to be very fast.

[ ] Bugfix
[x] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Documentation content changes
[ ] Other... Please describe:

What is the current behavior?

Slow rendering updates on large graphs cause navigation to become stuck and significant delays when interacting with the graph, making it unmanageable.

What is the new behavior?

Fluent and fast interaction with GPU acceleration.
One video tells more than 1000 words:

Reagraph_Rendering_Fix.-.SD.480p.mp4

Does this PR introduce a breaking change?

[x] Yes
[ ] No

The PR keeps the existing Three.js renderer as the default, so the current GraphCanvas usage remains unchanged.

The breaking consideration applies when consumers opt into the new renderEngine="cosmos" renderer. The cosmos renderer should use its own ref contract, CosmosGraphCanvasRef, instead of the existing GraphCanvasRef. It exposes getControls(), but this returns a cosmos-specific controls adapter, not direct Three.js camera controls.

Migration path:

  • Existing Three.js/default usage requires no changes.
  • Use CosmosGraphCanvasRef when rendering with renderEngine="cosmos".
  • Use the cosmos getControls() adapter for shared actions such as zooming, fitting, centering, freezing, and unfreezing.
  • Be aware that some Three.js-specific features are not supported by the cosmos renderer yet, including edge lasso selection, custom renderers, children, cluster rendering/events, and direct Three.js controls access.
  • Node lasso selection is supported for the cosmos renderer via lassoType="node" and lassoType="all".

Other information

A demo is already included inside the storybook, with a very large graph, so you can try it on your own.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@amcdnl

amcdnl commented Apr 29, 2026

Copy link
Copy Markdown
Member

@InfinityMod - This is awesome! Give me a couple days to review and get this merged. Any chance this also supports 3d?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional cosmos.gl-based renderer to GraphCanvas to improve rendering performance for very large 2D graphs, while keeping the existing Three.js renderer as the default.

Changes:

  • Introduces renderEngine="cosmos" with a new CosmosGraphCanvas implementation, config (cosmosConfig), and dedicated ref contract (CosmosGraphCanvasRef).
  • Refactors node/edge selection styling logic into shared getNodeRenderStyle / getEdgeRenderStyle utilities.
  • Updates documentation (README) and adds a large-graph Storybook demo plus Vitest coverage for cosmos mounting and node-lasso behavior.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
stories/demos/LargeGraph.story.tsx Adds a large graph demo and runtime renderer switch (Three.js vs cosmos).
src/utils/renderStyles.ts Centralizes selection/active color + opacity rules for nodes/edges.
src/utils/index.ts Re-exports the new renderStyles utilities.
src/symbols/Node.tsx Uses shared render style helper instead of inline selection styling logic.
src/symbols/Edge.tsx Uses shared render style helper and forwards computed color to line rendering.
src/GraphCanvas/index.ts Exposes new cosmos-related types and ref contracts from the GraphCanvas entrypoint.
src/GraphCanvas/cosmos.ts Adds cosmos graph preparation, buffer building, and config helpers.
src/GraphCanvas/GraphCanvas.tsx Adds renderEngine + cosmosConfig, splits Three vs cosmos refs/types, and dispatches to the correct renderer.
src/GraphCanvas/GraphCanvas.test.ts Adds Vitest coverage for cosmos renderer ref contract and node lasso selection.
src/GraphCanvas/CosmosLabels.tsx Implements DOM label overlay for cosmos renderer with throttled updates.
src/GraphCanvas/CosmosGraphCanvas.tsx Implements the cosmos renderer, interactions, lasso selection, and ref API.
package.json Adds @cosmos.gl/graph dependency.
package-lock.json Locks new dependency tree and bumps package version metadata.
THIRD_PARTY_NOTICES.md Adds license notice for the new dependency.
README.md Documents the new optional cosmos renderer usage and ref contract.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +350 to +356
onLinkMouseOver: index => {
const edge = getEdgeByIndex(index);
if (!edge) return;

hoveredEdgeIdRef.current = edge.id;
setHoveredEdgeId(edge.id);
onEdgePointerOver?.(edge, undefined as never);

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onLinkMouseOver currently calls onEdgePointerOver with undefined for the event argument. Since onEdgePointerOver is part of the public GraphCanvas API and callers may rely on the event object, this will cause runtime errors when they access event properties. Consider accepting the cosmos event argument (if available) and forwarding it, or adjusting the cosmos renderer callback contract to make the event optional/nullable and documenting that for renderEngine="cosmos".

Suggested change
onLinkMouseOver: index => {
const edge = getEdgeByIndex(index);
if (!edge) return;
hoveredEdgeIdRef.current = edge.id;
setHoveredEdgeId(edge.id);
onEdgePointerOver?.(edge, undefined as never);
onLinkMouseOver: (index, event) => {
const edge = getEdgeByIndex(index);
if (!edge) return;
hoveredEdgeIdRef.current = edge.id;
setHoveredEdgeId(edge.id);
onEdgePointerOver?.(edge, event as never);

Copilot uses AI. Check for mistakes.
Comment on lines +494 to +496
onNodeContextMenu(node, {
...getNodeContextProps(node),
onCollapse: () => undefined

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the cosmos renderer, onNodeContextMenu is given an onCollapse callback that is a no-op. This is misleading because canCollapse/isCollapsed are provided and consumers may call onCollapse() expecting it to update collapsed state. Either implement onCollapse for cosmos (maintain internal collapsed state like the Three.js path) or omit/disable collapse in the context menu props to avoid a broken action.

Suggested change
onNodeContextMenu(node, {
...getNodeContextProps(node),
onCollapse: () => undefined
const nodeContextProps = getNodeContextProps(node);
onNodeContextMenu(node, {
...nodeContextProps,
canCollapse: false,
isCollapsed: false

Copilot uses AI. Check for mistakes.
@amcdnl
amcdnl requested review from ebassity and steppy452 April 29, 2026 11:26
@InfinityMod

Copy link
Copy Markdown
Author

@InfinityMod - This is awesome! Give me a couple days to review and get this merged. Any chance this also supports 3d?

I can look inside, cosmos.gl should also have the capabilities for 3d rendering.

@amcdnl
amcdnl requested review from SerhiiTsybulskyi and removed request for ebassity and steppy452 May 4, 2026 18:01

@SerhiiTsybulskyi SerhiiTsybulskyi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, left a few comments with suggestions 👍

Main points:

  • CosmosGraphControls — instead of seven BaseGraphCanvasRef['…'] lookups, better to use Pick<BaseGraphCanvasRef, …> and add fitView / getCosmosGraph on top. Single source of truth, and it reads as "base minus a few methods plus two new ones."
  • fitViewByPointIndices(indices, duration, 0.1) — the 0.1 is the padding (10%), and it's already the default in cosmos.gl, so the third arg can be dropped. Or, if we want to give consumers control over it — expose it via opts.padding on fitNodesInView / centerGraph.
  • The file is pretty large (~600 lines, 8 useEffects) — would be good to add a one-line comment above each useEffect describing its responsibility, just so it's clear at a glance what each one owns, some of them could be extracted into dedicated hooks (useCosmosGraphData, useCosmosLasso).

The rest are minor notes.

Comment thread src/utils/renderStyles.ts
})
});

export const getEdgeRenderStyle = ({

@SerhiiTsybulskyi SerhiiTsybulskyi May 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add JSDoc for each functions and please cover that utils by tests

/**
* Fit the given node ids in view.
*/
fitNodesInView: BaseGraphCanvasRef['fitNodesInView'];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason to do not use extends?
For example

  export interface CosmosGraphControls                                                                                                                  
    extends Pick<                                                                                                                                       
      BaseGraphCanvasRef,
      | 'centerGraph'                                                                                                                                   
      | 'fitNodesInView'                                      
      | 'freeze'
      | 'unFreeze'
      | 'zoomIn'                                                                                                                                        
      | 'zoomOut'
      | 'resetControls'                                                                                                                                 
    > {                                                       
    fitView: (duration?: number, padding?: number) => void;
    getGraph: () => CosmosGraph | undefined;                                                                                                      
  }

/**
* Get the cosmos.gl graph renderer.
*/
getCosmosGraph: () => CosmosGraph | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To align with existing interfaces

Suggested change
getCosmosGraph: () => CosmosGraph | undefined;
getGraph: () => CosmosGraph | undefined;

/**
* Get the cosmos.gl graph renderer.
*/
getCosmosGraph: () => CosmosGraph | undefined;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
getCosmosGraph: () => CosmosGraph | undefined;
getGraph: () => CosmosGraph | undefined;


const LABEL_MARGIN = 24;

const areLabelsEqual = (a: CosmosLabel[], b: CosmosLabel[]) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to move all utils function into separate util file and here keep only component
please also add JSDoc for utils functions and cover by tests

activeIds: Set<string>;
containerRef: React.RefObject<HTMLDivElement | null>;
defaultNodeSize: number;
graphRef: React.RefObject<CosmosGraph | null>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
graphRef: React.RefObject<CosmosGraph | null>;
graphRef: RefObject<CosmosGraph | null>;

updateInterval
}: {
activeIds: Set<string>;
containerRef: React.RefObject<HTMLDivElement | null>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
containerRef: React.RefObject<HTMLDivElement | null>;
containerRef: RefObject<HTMLDivElement | null>;

const graph = cosmosRef.current;
if (!graph) return;

const duration = opts?.animated === false ? 0 : 250;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to have ability to customise this duration (250ms) via props

} else if (indices.length === 1) {
graph.zoomToPointByIndex(indices[0], duration);
} else {
graph.fitViewByPointIndices(indices, duration, 0.1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to have ability to customise this fractional value: 0.1 via props

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a short // … line above each useEffect describing its responsibility? Just enough that a reader can locate the right one without reading the body.

@amcdnl amcdnl added the enhancement New feature or request label Jun 9, 2026
@amcdnl amcdnl self-assigned this Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants