← All @molecule/* packages · App templates
@molecule/app-feature-graph-view-reactFeature · graph-view · App (browser) · v1.0.1 · Apache-2.0
Force-directed graph view (Obsidian-style) for visualizing note / page linkages
npm install @molecule/app-feature-graph-view-react@molecule/app-feature-graph-view-react is a ready-made graph-view feature for the app (browser) side. It composes the core interfaces it needs, so it works with whichever providers your app has bonded.
import { GraphView, type GraphNode, type GraphEdge } from '@molecule/app-feature-graph-view-react'
function NoteGraph({ notes, links }: { notes: Note[]; links: Link[] }) {
const nodes: GraphNode[] = notes.map((n) => ({ id: n.id, label: n.title, weight: n.linkCount }))
const edges: GraphEdge[] = links.map((l) => ({ id: l.id, source: l.from, target: l.to }))
return <GraphView nodes={nodes} edges={edges} onNodeClick={(n) => open(n.id)} />
}Works with: @molecule/app-react, @molecule/app-ui
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
React force-directed graph view (Obsidian-style).
Exports:
<GraphView> — top-level node-and-edge visualization with built-in
force, circular, and grid layouts, click + selection support,
and pluggable node / edge renderers.GraphNode, GraphEdge, GraphLayout, GraphPoint, PositionedNode,
GraphNodeRenderer, GraphEdgeRenderer types.forceLayout, circularLayout, gridLayout,
layoutNodes, boundingBox, createRng).Used by note-taking and other apps to visualize page / note linkages the same way Obsidian does — a force-directed map of which notes link to which.
import { GraphView, type GraphNode, type GraphEdge } from '@molecule/app-feature-graph-view-react'
function NoteGraph({ notes, links }: { notes: Note[]; links: Link[] }) {
const nodes: GraphNode[] = notes.map((n) => ({ id: n.id, label: n.title, weight: n.linkCount }))
const edges: GraphEdge[] = links.map((l) => ({ id: l.id, source: l.from, target: l.to }))
return <GraphView nodes={nodes} edges={edges} onNodeClick={(n) => open(n.id)} />
}
feature
npm install @molecule/app-feature-graph-view-react @molecule/app-react @molecule/app-ui react
npm install -D @types/react
ForceLayoutOptionsForce-layout tunables.
interface ForceLayoutOptions {
/** Hard cap on iterations. Defaults to {@link DEFAULT_FORCE_ITERATIONS}. */
iterations?: number
/** Repulsive constant between every pair of nodes. */
repulsion?: number
/** Spring (Hooke) constant for edges. */
spring?: number
/** Ideal edge length (rest distance). */
restLength?: number
/** Velocity damping per step (0–1). */
damping?: number
/** Optional deterministic seed (for tests). Defaults to a fixed value. */
seed?: number
}
GraphEdgeEdge connecting two nodes by id.
interface GraphEdge {
/** Unique edge id. */
id: string
/** Source node id. */
source: string
/** Target node id. */
target: string
/** Optional weight; default 1. Stronger edges pull harder under force layout. */
weight?: number
}
GraphNodeSingle node in the graph.
group is an opaque grouping discriminator (e.g. tag, folder) the
caller can use for colouring; weight scales the rendered radius.
interface GraphNode {
/** Unique node id. */
id: string
/** Human-readable label rendered next to the node. */
label: string
/** Optional grouping discriminator (e.g. tag id). */
group?: string
/** Optional weight; default 1. Scales the rendered radius. */
weight?: number
}
GraphPointA 2-D point in graph (world) coordinates.
interface GraphPoint {
/** X coordinate. */
x: number
/** Y coordinate. */
y: number
}
GraphViewProps<GraphView> props.
interface GraphViewProps {
/** Nodes to render. */
nodes: GraphNode[]
/** Edges to render. */
edges: GraphEdge[]
/** Fired when the user clicks (or activates) a node. */
onNodeClick?: (node: GraphNode) => void
/** Currently-selected node id (highlighted in the view). */
selectedNodeId?: string
/** Layout strategy. Defaults to `'force'`. */
layout?: GraphLayout
/** Custom node renderer (overrides the default circle + label). */
nodeRenderer?: GraphNodeRenderer
/** Custom edge renderer (overrides the default `<line>`). */
edgeRenderer?: GraphEdgeRenderer
/** Force-layout tunables; ignored for non-force layouts. */
forceOptions?: ForceLayoutOptions
/** Extra classes merged onto the outer wrapper. */
className?: string
}
PositionedNodeInternal positioned node used by the renderer; the x/y coordinates
are in world (graph) space, with (0, 0) at the canvas centre.
interface PositionedNode extends GraphNode {
/** World-space X coordinate. */
x: number
/** World-space Y coordinate. */
y: number
}
GraphEdgeRendererRenderer for an individual edge. Receives the edge plus both endpoints.
type GraphEdgeRenderer = (
edge: GraphEdge,
source: PositionedNode,
target: PositionedNode,
) => ReactNode
GraphLayoutAvailable layout strategies.
type GraphLayout = 'force' | 'circular' | 'grid'
GraphNodeRendererRenderer for an individual node. Receives the positioned node.
type GraphNodeRenderer = (node: PositionedNode) => ReactNode
boundingBox(nodes, padding)Compute the world-space bounding box of a set of positioned nodes, with a small padding for visual breathing room.
function boundingBox(
nodes: PositionedNode[],
padding?: number,
): { minX: number; minY: number; maxX: number; maxY: number } | null
nodes — Positioned nodes.padding — Padding (world units) added to every side. Default 40.Returns: Bounding box { minX, minY, maxX, maxY } — or null if empty.
circularLayout(nodes, radius)Place nodes evenly around the unit circle (scaled by radius).
function circularLayout(nodes: GraphNode[], radius: number): PositionedNode[]
nodes — Nodes to position.radius — Circle radius in world units.Returns: Positioned nodes, in input order.
createRng(seed)Tiny deterministic PRNG (mulberry32). Allows reproducible initial
positions in tests without depending on Math.random().
function createRng(seed: number): () => number
seed — 32-bit unsigned seed.Returns: A function returning floats in [0, 1).
forceLayout(nodes, edges, options)Run a minimal velocity-Verlet force-directed simulation.
Implements:
restLength.Stops early once the maximum per-node displacement-squared drops below
{@link CONVERGENCE_EPSILON}, or after iterations steps — whichever
comes first.
function forceLayout(
nodes: GraphNode[],
edges: GraphEdge[],
options?: ForceLayoutOptions,
): PositionedNode[]
nodes — Nodes to lay out.edges — Edges (used only for attractive springs).options — Force-layout tunables.Returns: Positioned nodes, in input order.
GraphView(props)Force-directed graph view (Obsidian-style).
Renders nodes + edges into an SVG, computing positions via a built-in
minimal velocity-Verlet simulation. Three layouts are supported —
force (default), circular and grid — and either nodes or edges
can be overridden via custom renderers.
Style is driven by getClassMap(). Inline styles are reserved for
SVG geometry — transform, viewBox computation, stroke widths — which
classes can't express.
function GraphView(
props: GraphViewProps,
): ReactElement<unknown, string | JSXElementConstructor<any>>
props — Component props.Returns: The graph-view element.
gridLayout(nodes, spacing)Place nodes on a square-ish grid, centred on the origin.
function gridLayout(nodes: GraphNode[], spacing: number): PositionedNode[]
nodes — Nodes to position.spacing — Pixel spacing between adjacent grid cells.Returns: Positioned nodes, in input order.
layoutNodes(nodes, edges, layout, options)Compute world-space positions for every node using the chosen layout.
function layoutNodes(
nodes: GraphNode[],
edges: GraphEdge[],
layout: GraphLayout,
options?: ForceLayoutOptions,
): PositionedNode[]
nodes — Nodes to position.edges — Edges (consumed only by force).layout — Layout strategy.options — Optional force-layout tunables (ignored for non-force).Returns: Positioned nodes.
CONVERGENCE_EPSILONConvergence threshold (max per-node displacement squared).
const CONVERGENCE_EPSILON: 0.01
DEFAULT_DAMPINGDefault velocity damping used by forceLayout.
const DEFAULT_DAMPING: 0.85
DEFAULT_FORCE_ITERATIONSDefault cap on force-layout iterations.
const DEFAULT_FORCE_ITERATIONS: 200
DEFAULT_REPULSIONDefault repulsion strength used by forceLayout.
const DEFAULT_REPULSION: 800
DEFAULT_REST_LENGTHDefault ideal edge length used by forceLayout.
const DEFAULT_REST_LENGTH: 80
DEFAULT_SPRINGDefault spring stiffness used by forceLayout.
const DEFAULT_SPRING: 0.05
Peer dependencies:
@molecule/app-react ^1.0.1@molecule/app-ui ^1.0.1react ^18.0.0 || ^19.0.0@molecule/app-react@molecule/app-uireactThe root element fills 100% of its parent — the PARENT must have an explicit height (e.g. a fixed-height panel or a flex/grid track) or the graph renders zero-tall and appears blank.
Interaction surface is click / keyboard-activate + selectedNodeId
highlighting only. Nodes are NOT draggable and there is no built-in
pan / zoom — the SVG viewBox auto-fits the layout bounds. For a
pannable / zoomable surface, compose with
@molecule/app-feature-canvas-react instead.
Layout is deterministic (seeded RNG), computed once per
(nodes, edges, layout, forceOptions) change. forceOptions is
ignored by the circular and grid layouts.
Translation strings are provided by @molecule/app-locales-feature-graph-view.