import React, { useState, useEffect } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { Loader2, Calendar, TrendingUp, Database, Clock } from 'lucide-react'; import api from '../services/api'; interface TimelineData { year: number; [key: string]: number; } interface AlgorithmTimeline { algorithm: string; name: string; category: string; data: { year: number; count: number }[]; } interface CacheStats { cached: number; fetched: number; } interface ProgressState { current: number; total: number; currentAlgorithm: string; } const Timeline: React.FC = () => { const [timelineData, setTimelineData] = useState([]); const [algorithms, setAlgorithms] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedAlgorithms, setSelectedAlgorithms] = useState([]); const [yearRange, setYearRange] = useState({ start: 2015, end: 2024 }); const [cacheStats, setCacheStats] = useState(null); const [progress, setProgress] = useState(null); useEffect(() => { fetchTimelineData(); }, [yearRange]); const fetchTimelineData = async () => { try { setLoading(true); setError(null); setProgress(null); setCacheStats(null); // Use Server-Sent Events for real-time progress const eventSource = new EventSource(`/api/search/timeline-stream?startYear=${yearRange.start}&endYear=${yearRange.end}`); eventSource.onmessage = (event) => { const data = JSON.parse(event.data); switch (data.type) { case 'init': setProgress({ current: 0, total: data.totalOperations, currentAlgorithm: data.message }); setCacheStats({ cached: data.cachedResults, fetched: data.fetchedResults }); break; case 'algorithm_start': setProgress({ current: data.completed, total: data.total, currentAlgorithm: `Processing ${data.algorithm}...` }); break; case 'year_complete': setProgress({ current: data.completed, total: data.total, currentAlgorithm: `${data.algorithm} (${data.year}): ${data.count} papers` }); break; case 'algorithm_complete': setProgress({ current: data.completed, total: data.total, currentAlgorithm: `Completed ${data.algorithm}` }); break; case 'cache_saved': setProgress(prev => prev ? { ...prev, currentAlgorithm: data.message } : null); break; case 'complete': setTimelineData(data.timelineData); setAlgorithms(data.algorithms); setCacheStats(data.cacheStats); // Auto-select top 5 algorithms if none selected if (selectedAlgorithms.length === 0) { const topAlgorithms = data.algorithms .sort((a: AlgorithmTimeline, b: AlgorithmTimeline) => { const aTotal = a.data.reduce((sum, item) => sum + item.count, 0); const bTotal = b.data.reduce((sum, item) => sum + item.count, 0); return bTotal - aTotal; }) .slice(0, 5) .map((algo: AlgorithmTimeline) => algo.algorithm); setSelectedAlgorithms(topAlgorithms); } setLoading(false); setProgress(null); eventSource.close(); break; case 'error': setError(`Failed to load timeline data: ${data.error}`); setLoading(false); setProgress(null); eventSource.close(); break; } }; eventSource.onerror = () => { setError('Connection lost while loading timeline data'); setLoading(false); setProgress(null); eventSource.close(); }; } catch (err) { setError('Failed to start timeline data loading'); setLoading(false); setProgress(null); } }; const handleAlgorithmToggle = (algorithmKey: string) => { setSelectedAlgorithms(prev => prev.includes(algorithmKey) ? prev.filter(key => key !== algorithmKey) : [...prev, algorithmKey] ); }; const getAlgorithmColor = (algorithmKey: string, index: number): string => { // Define a palette of distinct colors for better visualization const colors = [ '#3B82F6', // Blue '#EF4444', // Red '#10B981', // Green '#F59E0B', // Yellow '#8B5CF6', // Purple '#EC4899', // Pink '#06B6D4', // Cyan '#84CC16', // Lime '#F97316', // Orange '#6366F1', // Indigo '#14B8A6', // Teal '#F43F5E', // Rose '#8B5A3C', // Brown '#6B7280', // Gray '#DC2626', // Dark Red '#059669', // Dark Green '#7C3AED', // Dark Purple '#DB2777', // Dark Pink '#0891B2', // Dark Cyan '#65A30D' // Dark Lime ]; // Use algorithm key to get consistent color assignment const colorIndex = selectedAlgorithms.indexOf(algorithmKey) % colors.length; return colors[colorIndex]; }; if (loading) { return (

Algorithm Usage Timeline

Track how the usage of AI algorithms in medical research has evolved over time

Loading timeline data...
{progress && (
{progress.currentAlgorithm} {Math.round((progress.current / progress.total) * 100)}%
{progress.current} / {progress.total} requests {progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0}% complete
)}
Cache Information

Historical data is cached on disk to speed up future requests. Only the current year data needs to be fetched from PubMed each time.

); } if (error) { return (

{error}

); } const filteredTimelineData = timelineData.map(yearData => { const filtered: TimelineData = { year: yearData.year }; selectedAlgorithms.forEach(algoKey => { if (yearData[algoKey] !== undefined) { filtered[algoKey] = yearData[algoKey]; } }); return filtered; }); return (

Algorithm Usage Timeline

Track how the usage of AI algorithms in medical research has evolved over time

Year Range: setYearRange(prev => ({ ...prev, start: parseInt(e.target.value) }))} className="ml-2 px-2 py-1 border border-gray-300 rounded text-sm w-20" /> to setYearRange(prev => ({ ...prev, end: parseInt(e.target.value) }))} className="px-2 py-1 border border-gray-300 rounded text-sm w-20" />
{selectedAlgorithms.length} algorithm{selectedAlgorithms.length !== 1 ? 's' : ''} selected
{cacheStats && (
{cacheStats.cached} cached
{cacheStats.fetched} fetched
)}
{selectedAlgorithms.map((algoKey, index) => { const algo = algorithms.find(a => a.algorithm === algoKey); if (!algo) return null; return ( ); })}

Classical ML

{algorithms .filter(algo => algo.category === 'classical_ml') .slice(0, 8) .map(algo => ( ))}

Deep Learning

{algorithms .filter(algo => algo.category === 'deep_learning') .slice(0, 8) .map(algo => ( ))}

Large Language Models

{algorithms .filter(algo => algo.category === 'llms') .slice(0, 8) .map(algo => ( ))}
); }; export default Timeline;