InstallationGetting Started
npm install jsongraphs
# or
yarn add jsongraphs
# or
bun add jsongraphsQuick StartGetting Started
The simplest possible integration — create a JsonGraph instance, point it at a container element, and call load().
import { JsonGraph, darkTheme } from class="token-string">'jsongraphs';
const container = document.getElementById(class="token-string">'graph-container')!;
const graph = new JsonGraph({
container,
theme: darkTheme, class="token-comment">// or defaultTheme for light
layout: class="token-string">'tree', class="token-comment">// or class="token-string">'radial'
minimap: true,
toolbar: true,
search: true,
});
class="token-comment">// Load from a plain JS object
await graph.load({
project: class="token-string">"My App",
version: class="token-string">"class="token-number">2.0",
dependencies: {
react: class="token-string">"^class="token-number">19",
typescript: class="token-string">"^class="token-number">5",
},
});React / Next.js IntegrationGetting Started
Use a useRef for the container and initialize inside useEffect. Always call destroy() in the cleanup function.
class="token-string">"use client";
import { useEffect, useRef } from class="token-string">"react";
import { JsonGraph, darkTheme, defaultTheme } from class="token-string">"jsongraphs";
interface Props {
data: object;
dark?: boolean;
}
export function GraphViewer({ data, dark = true }: Props) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const graph = new JsonGraph({
container: containerRef.current,
theme: dark ? darkTheme : defaultTheme,
layout: class="token-string">"tree",
minimap: true,
toolbar: true,
search: true,
onLoad: (nodes, edges) => {
console.log(class="token-string">`Loaded ${nodes} nodes, ${edges} edges`);
},
onError: (err) => {
console.error(class="token-string">"Parse error:", err);
},
});
graph.load(data);
class="token-comment">// Cleanup on unmount
return () => graph.destroy();
}, [data, dark]);
return (
class="token-string">"class="token-number">100%", height: class="token-string">"600px" }}
/>
);
}💡 TipAdd dynamic import with ssr: false in Next.js to avoid window-not-defined errors during SSR, or prefix the component with "use client" in the App Router.Live Demo
Key Features
🚀Canvas-Based Rendering
60 FPS rendering with optimized spatial indexing. Handles hundreds of thousands of nodes without breaking a sweat.
📡Streaming Parser
Load multi-megabyte JSON files without blocking the UI thread. Feed fetch() streams directly as they download.
📐Multiple Layouts
Switch between hierarchical Tree and organic Radial layouts dynamically at runtime with a single method call.
🎨Themeable
Built-in light and dark themes. Customize every color, size, and geometry via the structured Theme interface.
🧩Zero Dependencies
Pure TypeScript, no external runtime deps. Ultra-lightweight bundle size, easy to integrate anywhere.
🛠️Built-in UI
Integrated MiniMap, Toolbar (zoom, fit, layout toggle), and Search overlay — all without any extra configuration.
Node Type Colours
Each JSON primitive type is rendered with a distinct color palette that works in both light and dark themes:
Light theme
objectarraystringnumberbooleannullDark theme
objectarraystringnumberbooleannullJsonGraphAPI Reference
The main class. Initializes the canvas renderer, overlay UI components (toolbar, search bar, minimap), and event model inside the provided container element.
typescriptimport { JsonGraph } from class="token-string">'jsongraphs';
const graph = new JsonGraph(opts: JsonGraphOptions);
Constructor Options
Option Type Default Description container* HTMLElement — The host element for the graph canvas and overlay UI. theme Theme defaultTheme Initial visual theme. Import defaultTheme or darkTheme, or provide a custom Theme object. layout 'tree' | 'radial' 'tree' Initial layout algorithm. Can be changed at runtime via setLayout(). minimap boolean true Show the interactive mini-map overlay in the bottom-right corner. toolbar boolean true Show the toolbar overlay with zoom, fit, layout toggle, expand/collapse, and theme toggle buttons. search boolean true Show the node search bar overlay at the top-center of the graph. maxDepth number 200 Maximum JSON nesting depth before a ParseLimitError is thrown. maxNodes number 500_000 Maximum total graph nodes before a ParseLimitError is thrown. onLoad (nodes: number, edges: number) => void — Callback invoked after parsing completes. Receives total node and edge counts. onError (err: Error) => void — Callback invoked if parsing fails (e.g. invalid JSON or limit exceeded).
📝 NoteThe container element gets position: relative and overflow: hidden applied automatically. Set an explicit width and height on it before constructing JsonGraph.load(source: JsonSource)
Asynchronously parses and renders a JSON source. Accepts five input formats:
objectPlain JS object
Any serialisable JS value. Parsed synchronously.
stringJSON string
A raw JSON string. Parsed synchronously.
FileFile object
From an <input type='file'> element. Streamed.
URLURL object
Fetched via fetch() and streamed.
ReadableStreamReadableStream
Streamed byte-by-byte. Ideal for huge files.
typescriptclass="token-comment">// From a plain object
await graph.load({ name: class="token-string">"Alice", age: class="token-number">30 });
class="token-comment">// From a JSON string
await graph.load(class="token-string">'{"hello": "world"}');
class="token-comment">// From a file input
const file = fileInput.files[class="token-number">0];
await graph.load(file);
class="token-comment">// From a URL (auto-fetches and streams)
await graph.load(new URL(class="token-string">'https:class="token-comment">//api.example.com/data.json'));
class="token-comment">// From a fetch() stream (most memory-efficient for large files)
const res = await fetch(class="token-string">'/huge-data.json');
await graph.load(res.body!);
setLayout(layout: LayoutType)
Switches the layout algorithm at runtime and re-runs the layout engine immediately. The toolbar also calls this method when the user clicks the layout toggle buttons.
typescriptclass="token-comment">// Switch to radial layout
graph.setLayout(class="token-string">'radial');
class="token-comment">// Switch back to tree layout
graph.setLayout(class="token-string">'tree');
💡 TipThe tree layout is best for deep, hierarchical JSON with clear parent-child relationships. The radial layout works great for shallow, wide structures and looks beautiful for network-like data.setTheme(theme: Theme)
Applies a new Theme object to the canvas renderer, toolbar, search bar, and minimap simultaneously.
typescriptimport { darkTheme, defaultTheme } from class="token-string">'jsongraphs';
class="token-comment">// Apply dark theme
graph.setTheme(darkTheme);
class="token-comment">// Apply light theme
graph.setTheme(defaultTheme);
class="token-comment">// Apply a custom theme
graph.setTheme(myCustomTheme);
toggleTheme()
Convenience method that toggles between defaultTheme (light) and darkTheme. Equivalent to checking the current theme name and calling setTheme().
typescriptclass="token-comment">// One-liner to toggle light ↔ dark
graph.toggleTheme();
fitView()
Resets pan and zoom so all visible nodes fit within the container with an 80 px padding. Also marks the minimap dirty.
typescriptgraph.fitView();
zoomIn() / zoomOut()
Smooth programmatic zoom centered on the container midpoint.zoomIn()scales by ×1.15 andzoomOut()scales by ×0.87.
typescriptgraph.zoomIn(); class="token-comment">// scale × class="token-number">1.15
graph.zoomOut(); class="token-comment">// scale × class="token-number">0.87
expandAll() / collapseAll()
expandAll() expands everyobject andarray node.collapseAll() collapses all except the root node. Both re-run the layout engine after toggling visibility.
typescriptgraph.expandAll(); class="token-comment">// Show all nodes
graph.collapseAll(); class="token-comment">// Collapse all (keep root visible)
destroy()
Cancels any pending animation frame, destroys the canvas renderer, and unmounts the search bar, minimap, and toolbar overlays. Always call this when removing the graph from the DOM to avoid memory leaks.
typescriptclass="token-comment">// In a React useEffect cleanup
return () => graph.destroy();
class="token-comment">// Or manually when done
graph.destroy();
⚠️ WarningFailing to call destroy() will leave dangling event listeners and canvas elements in memory.TypesTypes
JsonSource
A union type representing every valid data source for graph.load().
typescripttype JsonSource =
| File class="token-comment">// Browser File API (e.g. from type=class="token-string">"file">)
| ReadableStream class="token-comment">// WHATWG Streams (e.g. fetch().body)
| string class="token-comment">// Raw JSON string
| URL class="token-comment">// URL — fetched and streamed automatically
| object; class="token-comment">// Any plain JS object / array
LayoutType
typescripttype LayoutType = class="token-string">'tree' | class="token-string">'radial';
'tree'Tree Layout
Hierarchical top-down layout. Best for deeply nested, structured JSON.
'radial'Radial Layout
Organic radial layout emanating from the root. Great for wide, shallow structures.
NodeType
Represents each JSON primitive and container type.
typescripttype NodeType = class="token-string">'object' | class="token-string">'array' | class="token-string">'string' | class="token-string">'number' | class="token-string">'boolean' | class="token-string">'null';
GraphNode
Represents a single node in the graph. Each JSON value (object, array, or primitive) produces one GraphNode.
typescriptinterface NodePosition {
x: number;
y: number;
vx: number; class="token-comment">// velocity used by force layout
vy: number;
}
interface GraphNode {
id: string;
type: NodeType;
key?: string;
value?: string;
childCount: number;
depth: number;
parentId?: string;
expanded: boolean;
pos: NodePosition;
width: number;
height: number;
highlighted: boolean;
hovered: boolean;
alpha: number;
transparent?: boolean;
}
Fields
Option Type Default Description id string — Unique node identifier. type NodeType — JSON value type: object | array | string | number | boolean | null. key string? — The JSON key that produced this node. Undefined for the root node. value string? — Serialised display value for primitives (string, number, boolean, null). childCount number — Number of direct children (object keys or array items). depth number — Zero-indexed depth from the root. parentId string? — ID of the parent node. Undefined for the root. expanded boolean — Whether this node is currently expanded in the UI. pos NodePosition — Layout position { x, y, vx, vy }. Mutated by the layout engine. width number — Bounding box width set after layout measurement. height number — Bounding box height set after layout measurement. highlighted boolean — True when this node matches a search query. hovered boolean — True when the pointer is over this node. alpha number — Animation progress 0→1 used for fade-in. transparent boolean? — Anonymous array-item wrapper — hidden from view, edges skipped through.
GraphEdge
Represents a directed edge between two nodes. Edges carry the JSON key label and an optional caption pill.
typescriptinterface GraphEdge {
id: string;
sourceId: string;
targetId: string;
label?: string; class="token-comment">// JSON key that connects these nodes
caption?: string; class="token-comment">// Optional pill label at edge midpoint
alpha: number; class="token-comment">// Fade-in animation progress
}
Option Type Default Description id string — Unique edge identifier. sourceId string — ID of the source (parent) node. targetId string — ID of the target (child) node. label string? — The JSON key label on this edge (same as target.key). caption string? — Optional text displayed in a pill box at the edge midpoint. Set automatically when the target key is __CAPTION__. alpha number — Animation progress 0→1 for fade-in.
ParseOptions
Low-level parser configuration passed via maxDepth and maxNodes on JsonGraphOptions.
typescriptinterface ParseOptions {
maxDepth?: number; class="token-comment">// Max nesting depth. Default: class="token-number">200
maxNodes?: number; class="token-comment">// Max total nodes. Default: class="token-number">500_000
chunkSize?: number; class="token-comment">// Text decoder chunk in bytes. Default: class="token-number">65_536 (class="token-number">64 KB)
}
ParseLimitError
Error class thrown (and forwarded to onError) when the parser exceeds maxDepth or maxNodes.
typescriptimport { ParseLimitError } from class="token-string">'jsongraphs';
class ParseLimitError extends Error {
readonly kind: class="token-string">'depth' | class="token-string">'nodes';
}
class="token-comment">// Usage
const graph = new JsonGraph({
container,
maxNodes: class="token-number">10_000,
onError: (err) => {
if (err instanceof ParseLimitError) {
if (err.kind === class="token-string">'nodes') {
console.warn(class="token-string">'Too many nodes! Increase maxNodes.');
} else if (err.kind === class="token-string">'depth') {
console.warn(class="token-string">'JSON too deeply nested! Increase maxDepth.');
}
}
},
});
Theme
The full theme interface. Every visual property of the graph is configurable through this object.
typescriptinterface NodeColors {
fill: string; class="token-comment">// Node background
stroke: string; class="token-comment">// Node border
text: string; class="token-comment">// Node label text
badge: string; class="token-comment">// Type badge colour
}
interface Theme {
name: string;
background: string; class="token-comment">// Canvas background colour
gridLine: string; class="token-comment">// Subtle grid line colour
nodeColors: Record; class="token-comment">// Per-type colours
edge: {
stroke: string; class="token-comment">// Default edge colour
highlight: string; class="token-comment">// Highlighted edge colour
width: number; class="token-comment">// Edge line width in px
captionBg: string; class="token-comment">// Caption pill background
captionText: string; class="token-comment">// Caption pill text
captionBorder: string; class="token-comment">// Caption pill border
};
font: {
family: string; class="token-comment">// CSS font-family
keySize: number; class="token-comment">// Key label font size
valueSize: number; class="token-comment">// Value label font size
typeSize: number; class="token-comment">// Type badge font size
};
node: {
radius: number; class="token-comment">// Corner radius
paddingX: number; class="token-comment">// Horizontal padding
paddingY: number; class="token-comment">// Vertical padding
minWidth: number; class="token-comment">// Minimum node width
shadowBlur: number; class="token-comment">// Drop shadow blur radius
shadowColor: string; class="token-comment">// Drop shadow colour
shadowOffset: number; class="token-comment">// Drop shadow offset
};
ui: {
panelBg: string; class="token-comment">// Toolbar / search / minimap background
panelBorder: string; class="token-comment">// Panel border colour
panelShadow: string; class="token-comment">// Panel box-shadow CSS value
btnHover: string; class="token-comment">// Button hover background
btnActive: string; class="token-comment">// Button active / selected background
text: string; class="token-comment">// Primary UI text colour
textMuted: string; class="token-comment">// Secondary UI text colour
inputBg: string; class="token-comment">// Search input background
inputText: string; class="token-comment">// Search input text colour
inputPlaceholder: string; class="token-comment">// Search placeholder colour
focusRing: string; class="token-comment">// Focus ring colour
separator: string; class="token-comment">// Separator line colour
minimapViewport: string; class="token-comment">// Minimap viewport rect stroke
};
}
ThemesThemes
JsonGraphs ships with two polished built-in themes. Import and use them directly, or compose a custom theme.
Light Theme (defaultTheme)
Dark Theme (darkTheme)
typescriptimport { defaultTheme, darkTheme } from class="token-string">'jsongraphs';
class="token-comment">// Apply light theme
graph.setTheme(defaultTheme);
class="token-comment">// Apply dark theme
graph.setTheme(darkTheme);
class="token-comment">// Toggle between them
graph.toggleTheme();
Custom Theme
Build a full custom theme by satisfying the Theme interface. The easiest approach is to spread a built-in theme and override only the properties you want to change.
typescriptimport { defaultTheme, type Theme } from class="token-string">'jsongraphs';
const sunsetTheme: Theme = {
...defaultTheme,
name: class="token-string">'sunset',
background: class="token-string">'#1a0a00',
gridLine: class="token-string">'rgba(class="token-number">255,class="token-number">120,class="token-number">50,class="token-number">0.05)',
nodeColors: {
object: { fill: class="token-string">'#2a1000', stroke: class="token-string">'#ff6820', text: class="token-string">'#ffd0a0', badge: class="token-string">'#ff6820' },
array: { fill: class="token-string">'#001a10', stroke: class="token-string">'#20d080', text: class="token-string">'#a0ffc8', badge: class="token-string">'#20d080' },
string: { fill: class="token-string">'#00101a', stroke: class="token-string">'#20a0ff', text: class="token-string">'#a0d8ff', badge: class="token-string">'#20a0ff' },
number: { fill: class="token-string">'#1a1000', stroke: class="token-string">'#c0b020', text: class="token-string">'#ffe890', badge: class="token-string">'#c0b020' },
boolean: { fill: class="token-string">'#1a0020', stroke: class="token-string">'#c040c0', text: class="token-string">'#f0a0f0', badge: class="token-string">'#c040c0' },
null: { fill: class="token-string">'#class="token-number">151510', stroke: class="token-string">'#class="token-number">808070', text: class="token-string">'#c8c8b0', badge: class="token-string">'#class="token-number">808070' },
},
edge: {
...defaultTheme.edge,
stroke: class="token-string">'rgba(class="token-number">255,class="token-number">120,class="token-number">50,class="token-number">0.4)',
highlight: class="token-string">'#ff6820',
},
ui: {
...defaultTheme.ui,
panelBg: class="token-string">'rgba(class="token-number">26,class="token-number">10,class="token-number">0,class="token-number">0.92)',
text: class="token-string">'#ffd0a0',
focusRing: class="token-string">'#ff6820',
},
};
graph.setTheme(sunsetTheme);
💡 TipUse the spread operator (...defaultTheme) to inherit all defaults, then override only what you need. This is much safer than building a theme from scratch.Advanced UsageAdvanced
__CAPTION__ Key — Custom Edge Labels
Add a special __CAPTION__ key inside any JSON object to place a descriptive label in a pill box at the midpoint of the incoming edge. The node itself is hidden — only the caption is shown on the edge.
example.jsonjson{
class="token-string">"user": {
class="token-string">"__CAPTION__": class="token-string">"is assigned to",
class="token-string">"name": class="token-string">"Alice",
class="token-string">"role": class="token-string">"admin"
},
class="token-string">"project": {
class="token-string">"__CAPTION__": class="token-string">"belongs to",
class="token-string">"title": class="token-string">"JsonGraphs Docs",
class="token-string">"status": class="token-string">"active"
}
}
📝 NoteThe __CAPTION__ node is automatically detected during parsing. Its value becomes the caption field on the parent edge, and the node itself is marked transparent.Streaming Large Files
JsonGraphs uses a chunked streaming parser internally. You can feed it a ReadableStream<Uint8Array> directly, which means it starts rendering as it downloads — perfect for very large JSON files.
typescriptclass="token-comment">// Stream directly from fetch — renders as data arrives
const response = await fetch(class="token-string">'/api/huge-dataset.json');
await graph.load(response.body!);
class="token-comment">// From a File input (also streamed)
document.querySelector(class="token-string">'input[type="file"]')
.addEventListener(class="token-string">'change', async (e) => {
const file = e.target.files[class="token-number">0];
await graph.load(file); class="token-comment">// Streams the file, no full read into memory
});
class="token-comment">// With limit guards
const graph = new JsonGraph({
container,
maxNodes: class="token-number">50_000, class="token-comment">// Stop at 50k nodes
maxDepth: class="token-number">100, class="token-comment">// Stop at depth class="token-number">100
onError: (err) => {
console.error(class="token-string">'Limit reached:', err.message);
},
});
💡 TipFor very large JSON files (10MB+), always use the streaming approach via fetch().body or a File object. Passing a pre-parsed string works too, but keeps the entire string in memory.