How to Visualize Knowledge Graphs with React Flow in the Understand-Anything Dashboard

The Understand-Anything dashboard renders interactive knowledge graphs by feeding JSON pipeline output into a React Flow canvas, using D3 force-directed layouts for positioning and a Zustand store for state management.

The open-source repository Egonex-AI/Understand-Anything provides a React-based dashboard that transforms static knowledge graph data into explorable node-edge diagrams. This implementation combines a centralized state store, deterministic force-simulation layouts, and React Flow’s component library to handle graphs with hundreds of nodes while maintaining 60fps interactions.

Architecture Overview

The visualization pipeline relies on three coordinated layers. The Zustand store (store.ts) maintains the raw KnowledgeGraph object and derived indexes. The layout engine (utils/layout.ts) computes stable (x, y) coordinates using D3 force simulation. Finally, the React Flow view (KnowledgeGraphView.tsx) maps these positioned entities into interactive React components, handling clicks, search highlighting, and edge styling.

Loading and Validating the Knowledge Graph

When the dashboard initializes in App.tsx, it fetches the knowledge graph JSON produced by the /understand-knowledge pipeline. The data undergoes schema validation before entering the global store.

// App.tsx (lines 35-45)
fetch(dataUrl("knowledge-graph.json", accessToken))
  .then(r => r.json())
  .then((data: unknown) => {
    const result = validateGraph(data);
    if (result.success && result.data) {
      setGraph(result.data);  // Injects validated graph into the store
    }
  });

Source: [understand-anything-plugin/packages/dashboard/src/App.tsx](https://github.com/Egonex-AI/Understand-Anything/blob/main/understand-anything-plugin/packages/dashboard/src/App.tsx#L35-L45)

State Management with Zustand

The useDashboardStore creates a SearchEngine for fuzzy lookup and builds fast-access indexes (nodesById, nodeIdToLayerId) to avoid O(n) scans during rendering.

// store.ts (lines 66-74)
const searchEngine = new SearchEngine(graph.nodes);
const { nodesById, nodeIdToLayerId, nodeIdToLayerIds } = buildGraphIndexes(graph);
set({ 
  graph, 
  nodesById, 
  nodeIdToLayerId, 
  nodeIdToLayerIds, 
  searchEngine 
});

Source: [understand-anything-plugin/packages/dashboard/src/store.ts](https://github.com/Egonex-AI/Understand-Anything/blob/main/understand-anything-plugin/packages/dashboard/src/store.ts#L66-L74)

Computing Force-Directed Layouts

The KnowledgeGraphView component triggers layout computation via computeLayout, which internally calls applyForceLayout from utils/layout.ts. The engine runs a deterministic number of D3 simulation ticks based on graph density to ensure stable positioning without blocking the UI thread.

// KnowledgeGraphView.tsx (lines 40-44)
const { positionMap, edgeCounts } = useMemo(() => {
  if (!filteredGraph) return { positionMap: new Map(), edgeCounts: new Map() };
  return computeLayout(filteredGraph);
}, [filteredGraph]);

The underlying simulation configures link distance, charge strength, and centering forces, then executes Math.min(300, Math.max(100, nodes.length)) ticks to balance quality and performance.

// utils/layout.ts (lines 31-70)
const sim = forceSimulation<ForceNode>(simNodes)
  .force("link", forceLink(...).distance(linkDistance))
  .force("charge", forceManyBody().strength(chargeStrength))
  .force("center", forceCenter(0, 0).strength(0.03));

const ticks = Math.min(300, Math.max(100, nodes.length));
sim.tick(ticks);

Source: [understand-anything-plugin/packages/dashboard/src/utils/layout.ts](https://github.com/Egonex-AI/Understand-Anything/blob/main/understand-anything-plugin/packages/dashboard/src/utils/layout.ts#L31-L70)

Rendering Nodes and Edges in React Flow

With positions calculated, the component maps graph entities to React Flow’s Node and Edge types. Node data includes UI-specific flags (isSelected, isHighlighted, incomingCount) to enable visual feedback without recomputing layout.

// KnowledgeGraphView.tsx (lines 58-95)
const rfNodes: Node[] = filteredGraph.nodes.map(node => ({
  id: node.id,
  type: "custom",
  position: positionMap.get(node.id) ?? { x: 0, y: 0 },
  data: {
    label: node.name,
    nodeType: node.type,
    isSelected: selectedNodeId === node.id,
    isHighlighted: highlightedNodeIds.has(node.id),
    incomingCount: edgeCounts.get(node.id) ?? 0,
    onNodeClick: handleNodeClick,
  },
}));

Edges receive style presets from the EDGE_STYLES record, which maps knowledge relationship types (e.g., cites, related) to CSS properties like strokeDasharray and opacity.

The final render wraps the mapped arrays in a ReactFlow provider with configured zoom limits and UI controls:

// KnowledgeGraphView.tsx (lines 46-58)
<ReactFlow
  nodes={nodes}
  edges={edges}
  nodeTypes={nodeTypes}
  fitView
  minZoom={0.05}
  maxZoom={2}
>
  <Background variant={BackgroundVariant.Dots} />
  <Controls />
  <MiniMap />
</ReactFlow>

Source: [understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx](https://github.com/Egonex-AI/Understand-Anything/blob/main/understand-anything-plugin/packages/dashboard/src/components/KnowledgeGraphView.tsx#L46-L58)

Handling User Interactions

User actions mutate the Zustand store rather than local React state, ensuring consistent data across the dashboard. The selectNode and setFocusNode methods update selectedNodeId and highlightedNodeIds, triggering React Flow to re-render styles via memoized selector dependencies. This pattern prevents expensive layout recalculation during hover, search, or filter operations.

To highlight a node programmatically:

import { useDashboardStore } from "./store";

function highlightNode(nodeId: string) {
  const selectNode = useDashboardStore.getState().selectNode;
  selectNode(nodeId);  // Updates selectedNodeId → UI re-renders with highlighted styles
}

Summary

  • Data ingestion: The dashboard loads JSON knowledge graphs via fetch in App.tsx, validating against @understand-anything/core/schema before storage.
  • State architecture: useDashboardStore maintains graph data, search indexes, and selection state using Zustand for predictable updates.
  • Positioning: applyForceLayout in utils/layout.ts runs D3 force simulation with adaptive tick counts (100–300) to generate stable node coordinates.
  • Rendering: KnowledgeGraphView.tsx converts positioned nodes into React Flow components, enriching data props with UI state and applying EDGE_STYLES for relationship visuals.
  • Performance: Memoized selectors ensure that interactions (search, selection, filtering) update visual styles without recomputing the force-directed layout.

Frequently Asked Questions

How does the layout engine handle large knowledge graphs?

The force simulation adapts its iteration count based on node density, running Math.min(300, Math.max(100, nodes.length)) ticks. This prevents excessive CPU usage on small graphs while providing sufficient convergence for larger datasets. Results are cached in a positionMap via useMemo, ensuring React Flow only recalculates positions when the filtered graph changes.

Can I customize the appearance of nodes and edges?

Yes. Node rendering is delegated to the CustomNode component referenced in the nodeTypes map, allowing you to modify labels, icons, and click handlers. Edge styles are configured via the EDGE_STYLES record in KnowledgeGraphView.tsx, where you can define CSS properties like stroke, strokeWidth, and strokeDasharray for each relationship type (e.g., cites, inspired_by).

What data format does the dashboard expect for the knowledge graph?

The dashboard expects a JSON object matching the KnowledgeGraph schema defined in @understand-anything/core/schema. This includes arrays of nodes (with id, name, and type fields) and edges (with source, target, and relationship type fields). The validateGraph function in App.tsx ensures type safety before the data enters the store.

How are user interactions like search and selection implemented?

Interactions mutate the global Zustand store (selectNode, setSearchQuery) rather than local component state. The KnowledgeGraphView subscribes to these store slices via selectors, passing derived boolean flags (isSelected, isHighlighted) into each node’s data prop. Because React Flow re-renders nodes when the nodes array reference changes, and because the layout computation is memoized, visual updates occur without triggering expensive D3 recalculations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →