Legal documents do not exist in isolation. A modern legal intelligence interface should allow attorneys to trace the statutory lineage and judicial interpretation of any law.
In this guide, we'll build a Legal Graph Explorer that fetches connected nodes using POST /v1/graph/traverse and renders an interactive relationship graph using Cytoscape.js and React.
Rendering diagram...
1. Graph Data Model
The response from POST /v1/graph/traverse provides fully hydrated nodes and edge metadata:
// types/graph.ts
export interface LegalNode {
canonical_id: string;
title: string;
doc_type: 'legislation' | 'case_law' | 'fatwa' | 'decree' | 'regulation';
year: number;
status: 'in_force' | 'amended' | 'repealed' | 'unknown';
}
export interface LegalEdge {
from_id: string;
to_id: string;
edge_type: 'cites' | 'amends' | 'repeals' | 'implements' | 'interprets';
article_ref?: string;
confidence: number;
}2. Recursive Multi-Hop Fetcher (TypeScript)
// services/graph-service.ts
import type { LegalNode, LegalEdge } from '../types/graph';
export async function fetchLegalSubGraph(
rootId: string,
depth: number = 2
): Promise<{ nodes: LegalNode[]; edges: LegalEdge[] }> {
const res = await fetch('https://api.dike.it.com/v1/graph/traverse', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_DIKE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
canonical_id: rootId,
direction: 'both',
depth,
limit: 75,
}),
});
if (!res.ok) {
throw new Error(`Graph traversal failed: ${res.statusText}`);
}
return await res.json();
}3. Cytoscape.js React Component
Convert Dike nodes and edges into Cytoscape visual elements with color-coded edge types:
// components/LegalGraphView.tsx
import React, { useEffect, useRef } from 'react';
import cytoscape from 'cytoscape';
import type { LegalNode, LegalEdge } from '../types/graph';
const EDGE_COLORS = {
amends: '#f59e0b', // Amber
repeals: '#ef4444', // Red
interprets: '#3b82f6', // Blue
implements: '#10b981', // Emerald
cites: '#8b5cf6', // Purple
};
export function LegalGraphView({
nodes,
edges,
onNodeClick,
}: {
nodes: LegalNode[];
edges: LegalEdge[];
onNodeClick?: (node: LegalNode) => void;
}) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
const elements = [
...nodes.map((n) => ({
data: {
id: n.canonical_id,
label: `${n.title}\n(${n.year})`,
status: n.status,
},
})),
...edges.map((e, idx) => ({
data: {
id: `edge-${idx}`,
source: e.from_id,
target: e.to_id,
label: e.edge_type,
color: EDGE_COLORS[e.edge_type] || '#64748b',
},
})),
];
const cy = cytoscape({
container: containerRef.current,
elements,
style: [
{
selector: 'node',
style: {
'background-color': '#1e293b',
'label': 'data(label)',
'color': '#f8fafc',
'text-valign': 'center',
'text-halign': 'center',
'text-wrap': 'wrap',
'font-size': '11px',
'width': '80px',
'height': '80px',
},
},
{
selector: 'edge',
style: {
'width': 2,
'line-color': 'data(color)',
'target-arrow-color': 'data(color)',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier',
'label': 'data(label)',
'font-size': '9px',
'text-rotation': 'autorotate',
'color': '#94a3b8',
},
},
],
layout: {
name: 'breadthfirst',
directed: true,
padding: 20,
},
});
cy.on('tap', 'node', (evt) => {
const clickedId = evt.target.id();
const nodeData = nodes.find((n) => n.canonical_id === clickedId);
if (nodeData && onNodeClick) onNodeClick(nodeData);
});
return () => cy.destroy();
}, [nodes, edges]);
return <div ref={containerRef} className="h-[600px] w-full rounded-xl border border-slate-200 bg-slate-900" />;
}Next Steps
- Explore resilience best practices in Production Best Practices.
- Review Graph Traverse API Reference.