Skip to content

Commit b8347aa

Browse files
amcdnlclaude
andcommitted
Address PR review feedback for node batching
- Fix cluster dragging bug: use setHoveredNodeId (singular) instead of setHoveredNodeIds for compatibility with Cluster.tsx drag-disable logic - Fix silent data loss: add maxNodeCount prop (default: 10000) with console.warn when node count exceeds limit - Expose maxNodeCount prop through GraphScene.tsx - Remove unused hoveredNodeIds state from store 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 6bca922 commit b8347aa

File tree

4 files changed

+32
-12
lines changed

4 files changed

+32
-12
lines changed

src/GraphScene.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ export interface GraphSceneProps {
191191
*/
192192
webWorkers?: boolean;
193193

194+
/**
195+
* Maximum number of instanced nodes for batched rendering.
196+
* Nodes beyond this limit won't render. A warning will be logged
197+
* if this limit is exceeded. Default: 10000
198+
*/
199+
maxNodeCount?: number;
200+
194201
/**
195202
* When a node was clicked.
196203
*/
@@ -461,6 +468,7 @@ export const GraphScene = forwardRef<GraphSceneRef, GraphSceneProps>(
461468
renderNode={renderNode}
462469
contextMenu={contextMenu}
463470
defaultNodeSize={rest.defaultNodeSize}
471+
maxNodeCount={rest.maxNodeCount}
464472
onClick={onNodeClick}
465473
onDoubleClick={onNodeDoubleClick}
466474
onContextMenu={onNodeContextMenu}
@@ -484,7 +492,8 @@ export const GraphScene = forwardRef<GraphSceneRef, GraphSceneProps>(
484492
onNodePointerOut,
485493
onNodePointerOver,
486494
renderNode,
487-
rest.defaultNodeSize
495+
rest.defaultNodeSize,
496+
rest.maxNodeCount
488497
]
489498
);
490499

src/store.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ export interface GraphState {
3333
selections?: string[];
3434
// The node that is currently hovered, used to disable cluster dragging
3535
hoveredNodeId?: string;
36-
// The nodes that are currently hovered over (for batched node rendering)
37-
hoveredNodeIds?: string[];
3836
// The edges that are currently hovered over, required for cases when animation is disabled
3937
hoveredEdgeIds?: string[];
4038
edgeContextMenus?: Set<string>;
@@ -57,7 +55,6 @@ export interface GraphState {
5755
setActives: (actives: string[]) => void;
5856
setSelections: (selections: string[]) => void;
5957
setHoveredNodeId: (hoveredNodeId: string | null) => void;
60-
setHoveredNodeIds: (hoveredNodeIds: string[] | null) => void;
6158
setHoveredEdgeIds: (hoveredEdgeIds: string[] | null) => void;
6259
setNodes: (nodes: InternalGraphNode[]) => void;
6360
setEdges: (edges: InternalGraphEdge[]) => void;
@@ -92,7 +89,6 @@ export const createStore = ({
9289
draggingIds: [],
9390
actives,
9491
hoveredEdgeIds: [],
95-
hoveredNodeIds: [],
9692
edgeContextMenus: new Set(),
9793
nodeContextMenus: new Set(),
9894
edgeMeshes: [],
@@ -126,8 +122,6 @@ export const createStore = ({
126122
setSelections: selections => set(state => ({ ...state, selections })),
127123
setHoveredNodeId: hoveredNodeId =>
128124
set(state => ({ ...state, hoveredNodeId })),
129-
setHoveredNodeIds: hoveredNodeIds =>
130-
set(state => ({ ...state, hoveredNodeIds })),
131125
setHoveredEdgeIds: hoveredEdgeIds =>
132126
set(state => ({ ...state, hoveredEdgeIds })),
133127
setNodes: nodes =>

src/symbols/nodes/Nodes.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import { createIndexToNodeIdMap, useNodeEvents } from './useNodeEvents';
2929
// LOD: Maximum camera distance at which node labels are visible
3030
const LABEL_VISIBILITY_DISTANCE = 15000;
3131

32+
// Default maximum number of instanced nodes
33+
const DEFAULT_MAX_NODE_COUNT = 10000;
34+
3235
export interface NodesProps {
3336
/** Nodes to render */
3437
nodes: InternalGraphNode[];
@@ -48,6 +51,8 @@ export interface NodesProps {
4851
contextMenu?: (event: ContextMenuEvent) => ReactNode;
4952
/** Default node size */
5053
defaultNodeSize?: number;
54+
/** Maximum number of instanced nodes (default: 10000). Nodes beyond this limit won't render. */
55+
maxNodeCount?: number;
5156
/** Click handler */
5257
onClick?: (
5358
node: InternalGraphNode,
@@ -93,6 +98,7 @@ export const Nodes: FC<NodesProps> = ({
9398
renderNode,
9499
contextMenu,
95100
defaultNodeSize = 7,
101+
maxNodeCount = DEFAULT_MAX_NODE_COUNT,
96102
onClick,
97103
onDoubleClick,
98104
onContextMenu,
@@ -121,7 +127,18 @@ export const Nodes: FC<NodesProps> = ({
121127
);
122128

123129
// Create instanced mesh for sphere nodes
124-
const instancing = useNodeInstancing(5000);
130+
const instancing = useNodeInstancing(maxNodeCount);
131+
132+
// Warn if nodes exceed the maximum count
133+
useEffect(() => {
134+
if (sphereNodes.length > maxNodeCount) {
135+
console.warn(
136+
`[reagraph] Node count (${sphereNodes.length}) exceeds maxNodeCount (${maxNodeCount}). ` +
137+
`${sphereNodes.length - maxNodeCount} nodes will not be rendered. ` +
138+
`Increase maxNodeCount prop to render all nodes.`
139+
);
140+
}
141+
}, [sphereNodes.length, maxNodeCount]);
125142

126143
// Create index-to-nodeId map for raycasting
127144
const indexToNodeId = useMemo(

src/symbols/nodes/useNodeEvents.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ export function useNodeEvents(
100100
const hoveredNodeIdRef = useRef<string | null>(null);
101101
const lastIntersectTimeRef = useRef(0);
102102

103-
// Store for updating hovered state
104-
const setHoveredNodeIds = useStore(state => state.setHoveredNodeIds);
103+
// Store for updating hovered state (use singular for Cluster.tsx compatibility)
104+
const setHoveredNodeId = useStore(state => state.setHoveredNodeId);
105105
const nodeContextMenus = useStore(state => state.nodeContextMenus);
106106
const setNodeContextMenus = useStore(state => state.setNodeContextMenus);
107107

@@ -255,9 +255,9 @@ export function useNodeEvents(
255255
eventsRef.current.onPointerOver(intersectedNode!);
256256
}
257257

258-
// Update hovered state
258+
// Update hovered state (singular for Cluster.tsx compatibility)
259259
hoveredNodeIdRef.current = currentHoveredId;
260-
setHoveredNodeIds?.(currentHoveredId ? [currentHoveredId] : []);
260+
setHoveredNodeId?.(currentHoveredId);
261261
}
262262
});
263263

0 commit comments

Comments
 (0)