// // SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // "use client" import React, { useState, useEffect, useCallback, useMemo } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Switch } from "@/components/ui/switch" import { Badge } from "@/components/ui/badge" import { Progress } from "@/components/ui/progress" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { PyGraphistryViewer } from "@/components/pygraphistry-viewer" import { ForceGraphWrapper } from "@/components/force-graph-wrapper" import { useToast } from "@/hooks/use-toast" import { Play, Square, Zap, Database, Activity, BarChart3, Settings, AlertTriangle, CheckCircle, Clock, Eye, Download, Upload, Server } from "lucide-react" interface NodeObject { id: string name: string val?: number group?: string pagerank?: number betweenness?: number degree?: number x?: number y?: number z?: number } interface LinkObject { source: string target: string name: string weight?: number } interface GraphData { nodes: NodeObject[] links: LinkObject[] } interface GenerationStats { node_count: number edge_count: number generation_time: number density: number avg_degree: number pattern: string parameters: any } interface GenerationTask { task_id: string status: string progress: number message: string result?: { graph_data: GraphData stats: GenerationStats } error?: string } // Graph generation patterns const GRAPH_PATTERNS = { RANDOM: 'random', SCALE_FREE: 'scale-free', SMALL_WORLD: 'small-world', CLUSTERED: 'clustered', HIERARCHICAL: 'hierarchical', GRID: 'grid' } as const type GraphPattern = typeof GRAPH_PATTERNS[keyof typeof GRAPH_PATTERNS] export default function TestMillionNodesPage() { const [graphData, setGraphData] = useState(null) const [isGenerating, setIsGenerating] = useState(false) const [generationProgress, setGenerationProgress] = useState(0) const [generationStats, setGenerationStats] = useState(null) const [error, setError] = useState(null) const [currentTask, setCurrentTask] = useState(null) // Generation parameters const [numNodes, setNumNodes] = useState(100000) const [graphPattern, setGraphPattern] = useState(GRAPH_PATTERNS.SCALE_FREE) const [avgDegree, setAvgDegree] = useState(5) const [numClusters, setNumClusters] = useState(100) const [smallWorldK, setSmallWorldK] = useState(6) const [smallWorldP, setSmallWorldP] = useState(0.1) const [seed, setSeed] = useState(null) // Visualization mode const [visualizationMode, setVisualizationMode] = useState<'pygraphistry' | 'force-graph'>('pygraphistry') const { toast } = useToast() // Poll for task status // Removed polling useEffect since we're using direct API calls now const generateGraphData = useCallback(async () => { if (numNodes > 1000000) { toast({ title: "Node Limit Exceeded", description: "Maximum 1 million nodes allowed for performance reasons.", variant: "destructive" }) return } setIsGenerating(true) setError(null) setGenerationProgress(0) try { const requestBody = { num_nodes: numNodes, pattern: graphPattern, avg_degree: avgDegree, num_clusters: graphPattern === GRAPH_PATTERNS.CLUSTERED ? numClusters : undefined, small_world_k: graphPattern === GRAPH_PATTERNS.SMALL_WORLD ? smallWorldK : undefined, small_world_p: graphPattern === GRAPH_PATTERNS.SMALL_WORLD ? smallWorldP : undefined, seed: seed || undefined } toast({ title: "Generation Started", description: `Starting generation of ${numNodes.toLocaleString()} nodes using ${graphPattern} pattern...`, }) // First generate the graph data const generateResponse = await fetch('/api/pygraphistry/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }) if (!generateResponse.ok) { const errorData = await generateResponse.json() throw new Error(errorData.error || 'Failed to start graph generation') } const generateResult = await generateResponse.json() // Update progress setGenerationProgress(0.5) // If we have graph data directly, process it with the unified service if (generateResult.graph_data) { const visualizeRequest = { graph_data: generateResult.graph_data, processing_mode: "pygraphistry_cloud", layout_type: "force", gpu_acceleration: true, clustering: true } const visualizeResponse = await fetch('/api/pygraphistry/visualize', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(visualizeRequest) }) if (!visualizeResponse.ok) { const errorData = await visualizeResponse.json() throw new Error(errorData.error || 'Failed to process graph visualization') } const result = await visualizeResponse.json() setGraphData(result.graph_data || generateResult.graph_data) setGenerationStats({ node_count: generateResult.graph_data.nodes.length, edge_count: generateResult.graph_data.links.length, generation_time: 0, density: generateResult.graph_data.links.length / (generateResult.graph_data.nodes.length * (generateResult.graph_data.nodes.length - 1)), avg_degree: avgDegree, pattern: graphPattern, parameters: requestBody }) setGenerationProgress(1.0) setIsGenerating(false) toast({ title: "Graph Generated Successfully", description: `Generated ${generateResult.graph_data.nodes.length.toLocaleString()} nodes and ${generateResult.graph_data.links.length.toLocaleString()} edges`, }) } else { throw new Error('No graph data returned from generation service') } } catch (err: any) { console.error("Error generating graph:", err) setError(`Failed to generate graph: ${err.message}`) setIsGenerating(false) toast({ title: "Generation Failed", description: err.message, variant: "destructive" }) } }, [numNodes, graphPattern, avgDegree, numClusters, smallWorldK, smallWorldP, seed, toast]) const clearGraph = useCallback(() => { setGraphData(null) setGenerationStats(null) setGenerationProgress(0) setError(null) setCurrentTask(null) if (isGenerating) { setIsGenerating(false) } }, [isGenerating]) const exportGraphData = useCallback(() => { if (!graphData) return const dataStr = JSON.stringify(graphData, null, 2) const dataBlob = new Blob([dataStr], { type: 'application/json' }) const url = URL.createObjectURL(dataBlob) const link = document.createElement('a') link.href = url link.download = `graph_${numNodes}_nodes_${Date.now()}.json` document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) }, [graphData, numNodes]) const presetConfigs = [ { name: "Small Test", nodes: 1000, pattern: GRAPH_PATTERNS.RANDOM, degree: 5 }, { name: "Medium Test", nodes: 10000, pattern: GRAPH_PATTERNS.SCALE_FREE, degree: 3 }, { name: "Large Test", nodes: 100000, pattern: GRAPH_PATTERNS.SCALE_FREE, degree: 3 }, { name: "Huge Test", nodes: 500000, pattern: GRAPH_PATTERNS.CLUSTERED, degree: 4 }, { name: "Million Nodes", nodes: 1000000, pattern: GRAPH_PATTERNS.CLUSTERED, degree: 2 }, ] const memoryEstimate = useMemo(() => { // Rough estimate: each node ~100 bytes, each link ~150 bytes const nodeMemory = numNodes * 100 / 1024 / 1024 // MB const linkMemory = (numNodes * avgDegree / 2) * 150 / 1024 / 1024 // MB return nodeMemory + linkMemory }, [numNodes, avgDegree]) return (
{/* Header */}

Million Node Graph Test

Generate and visualize large-scale graphs with up to 1 million nodes using GPU acceleration

Backend Generation PyGraphistry Ready {memoryEstimate > 100 && ( High Memory ({memoryEstimate.toFixed(0)}MB) )}
{/* Control Panel */} Graph Generation Settings Parameters Presets Advanced
setNumNodes(Math.min(1000000, Math.max(1, parseInt(e.target.value) || 1)))} min="1" max="1000000" step="1000" disabled={isGenerating} />

Max: 1,000,000

setAvgDegree(Math.min(50, Math.max(1, parseInt(e.target.value) || 1)))} min="1" max="50" disabled={isGenerating} />

Connections per node

setSeed(e.target.value ? parseInt(e.target.value) : null)} placeholder="Random" disabled={isGenerating} />

For reproducible graphs

{/* Pattern-specific parameters */} {graphPattern === GRAPH_PATTERNS.CLUSTERED && (
setNumClusters(Math.max(1, parseInt(e.target.value) || 1))} min="1" max="1000" disabled={isGenerating} />
)} {graphPattern === GRAPH_PATTERNS.SMALL_WORLD && (
setSmallWorldK(Math.max(2, parseInt(e.target.value) || 2))} min="2" max="20" disabled={isGenerating} />
setSmallWorldP(Math.min(1, Math.max(0, parseFloat(e.target.value) || 0)))} min="0" max="1" disabled={isGenerating} />
)}
Estimated Memory: {memoryEstimate.toFixed(1)}MB
{presetConfigs.map((preset, index) => (

{preset.name}

Nodes: {preset.nodes.toLocaleString()}
Pattern: {preset.pattern}
Degree: {preset.degree}
))}

PyGraphistry recommended for large graphs (>50k nodes)

Backend Processing

Graphs are now generated on the backend using optimized NetworkX algorithms with GPU acceleration available through PyGraphistry.

{/* Progress and Stats */} {(isGenerating || generationStats) && ( {isGenerating && (
Generating graph on backend... {currentTask && ( Task: {currentTask} )}

Progress: {generationProgress.toFixed(1)}%

)} {generationStats && (
Generation Complete
{generationStats.node_count.toLocaleString()} Nodes
{generationStats.edge_count.toLocaleString()} Links
{generationStats.generation_time.toFixed(2)}s
{generationStats.pattern}
Density: {(generationStats.density * 100).toFixed(4)}%
Avg Degree: {generationStats.avg_degree.toFixed(2)}
)}
)} {/* Error Display */} {error && (
Error: {error}
)} {/* Visualization */} {graphData && !isGenerating && (
Graph Visualization
{visualizationMode === 'pygraphistry' ? 'GPU Accelerated' : 'WebGL'} {graphData.nodes.length.toLocaleString()} nodes
{visualizationMode === 'pygraphistry' ? ( { console.error("PyGraphistry error:", err) setError(`Visualization error: ${err.message}`) }} /> ) : ( { console.error("Force graph error:", err) setError(`Visualization error: ${err.message}`) }} /> )}
)}
) }