Dike logoDocs

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

On this page