Spaces:
Running
Running
Pulastya B
Add HuggingFace storage integration - users can now persist datasets, models, and plots to their own HuggingFace account
7af9e82 | import React, { useEffect, useRef, useState } from 'react'; | |
| import { Loader2, AlertCircle, Download, Maximize2, Minimize2 } from 'lucide-react'; | |
| interface PlotData { | |
| type: 'plotly' | 'chartjs'; | |
| name: string; | |
| data: any; | |
| created_at: string; | |
| } | |
| interface PlotRendererProps { | |
| plotData?: PlotData; | |
| plotUrl?: string; // Fallback for legacy HTML plots | |
| title: string; | |
| onClose?: () => void; | |
| } | |
| // Lazy load Plotly to reduce bundle size | |
| const loadPlotly = (): Promise<any> => { | |
| return new Promise((resolve, reject) => { | |
| if ((window as any).Plotly) { | |
| resolve((window as any).Plotly); | |
| return; | |
| } | |
| const script = document.createElement('script'); | |
| script.src = 'https://cdn.plot.ly/plotly-2.27.0.min.js'; | |
| script.async = true; | |
| script.onload = () => resolve((window as any).Plotly); | |
| script.onerror = () => reject(new Error('Failed to load Plotly')); | |
| document.head.appendChild(script); | |
| }); | |
| }; | |
| export const PlotRenderer: React.FC<PlotRendererProps> = ({ | |
| plotData, | |
| plotUrl, | |
| title, | |
| onClose | |
| }) => { | |
| const containerRef = useRef<HTMLDivElement>(null); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const [isFullscreen, setIsFullscreen] = useState(false); | |
| useEffect(() => { | |
| if (!plotData && !plotUrl) { | |
| setError('No plot data provided'); | |
| setLoading(false); | |
| return; | |
| } | |
| const renderPlot = async () => { | |
| try { | |
| setLoading(true); | |
| setError(null); | |
| if (plotData && plotData.type === 'plotly') { | |
| // Render Plotly chart from JSON data | |
| const Plotly = await loadPlotly(); | |
| if (containerRef.current) { | |
| // Extract data and layout from the plot data | |
| const { data, layout, config } = plotData.data; | |
| // Apply dark theme | |
| const darkLayout = { | |
| ...layout, | |
| paper_bgcolor: 'rgba(0,0,0,0)', | |
| plot_bgcolor: 'rgba(0,0,0,0)', | |
| font: { color: '#ffffff' }, | |
| xaxis: { | |
| ...layout?.xaxis, | |
| gridcolor: 'rgba(255,255,255,0.1)', | |
| linecolor: 'rgba(255,255,255,0.2)' | |
| }, | |
| yaxis: { | |
| ...layout?.yaxis, | |
| gridcolor: 'rgba(255,255,255,0.1)', | |
| linecolor: 'rgba(255,255,255,0.2)' | |
| }, | |
| margin: { t: 40, r: 20, b: 40, l: 60 } | |
| }; | |
| const darkConfig = { | |
| ...config, | |
| responsive: true, | |
| displayModeBar: true, | |
| displaylogo: false, | |
| modeBarButtonsToRemove: ['lasso2d', 'select2d'] | |
| }; | |
| Plotly.newPlot(containerRef.current, data, darkLayout, darkConfig); | |
| } | |
| } | |
| setLoading(false); | |
| } catch (err) { | |
| console.error('Error rendering plot:', err); | |
| setError(err instanceof Error ? err.message : 'Failed to render plot'); | |
| setLoading(false); | |
| } | |
| }; | |
| renderPlot(); | |
| // Cleanup | |
| return () => { | |
| if (containerRef.current && (window as any).Plotly) { | |
| (window as any).Plotly.purge(containerRef.current); | |
| } | |
| }; | |
| }, [plotData, plotUrl]); | |
| // Handle window resize | |
| useEffect(() => { | |
| const handleResize = () => { | |
| if (containerRef.current && (window as any).Plotly && plotData) { | |
| (window as any).Plotly.Plots.resize(containerRef.current); | |
| } | |
| }; | |
| window.addEventListener('resize', handleResize); | |
| return () => window.removeEventListener('resize', handleResize); | |
| }, [plotData]); | |
| const handleDownload = () => { | |
| if (containerRef.current && (window as any).Plotly) { | |
| (window as any).Plotly.downloadImage(containerRef.current, { | |
| format: 'png', | |
| width: 1200, | |
| height: 800, | |
| filename: title.replace(/\s+/g, '_') | |
| }); | |
| } | |
| }; | |
| const toggleFullscreen = () => { | |
| setIsFullscreen(!isFullscreen); | |
| }; | |
| // If we only have a URL (legacy HTML plot), use iframe | |
| if (!plotData && plotUrl) { | |
| return ( | |
| <div className={`relative ${isFullscreen ? 'fixed inset-0 z-50 bg-black' : 'w-full h-full'}`}> | |
| <div className="absolute top-2 right-2 flex gap-2 z-10"> | |
| <button | |
| onClick={toggleFullscreen} | |
| className="p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors" | |
| > | |
| {isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />} | |
| </button> | |
| </div> | |
| <iframe | |
| src={plotUrl} | |
| className="w-full h-full border-0" | |
| title={title} | |
| sandbox="allow-scripts allow-same-origin" | |
| /> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className={`relative ${isFullscreen ? 'fixed inset-0 z-50 bg-[#0a0a0a]' : 'w-full h-full'}`}> | |
| {/* Controls */} | |
| <div className="absolute top-2 right-2 flex gap-2 z-10"> | |
| <button | |
| onClick={handleDownload} | |
| className="p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors" | |
| title="Download as PNG" | |
| > | |
| <Download className="w-4 h-4" /> | |
| </button> | |
| <button | |
| onClick={toggleFullscreen} | |
| className="p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors" | |
| title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'} | |
| > | |
| {isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />} | |
| </button> | |
| </div> | |
| {/* Loading state */} | |
| {loading && ( | |
| <div className="absolute inset-0 flex items-center justify-center bg-black/50"> | |
| <div className="flex items-center gap-3 text-white/60"> | |
| <Loader2 className="w-6 h-6 animate-spin" /> | |
| <span>Loading visualization...</span> | |
| </div> | |
| </div> | |
| )} | |
| {/* Error state */} | |
| {error && ( | |
| <div className="absolute inset-0 flex items-center justify-center"> | |
| <div className="flex items-center gap-3 text-red-400"> | |
| <AlertCircle className="w-6 h-6" /> | |
| <span>{error}</span> | |
| </div> | |
| </div> | |
| )} | |
| {/* Plot container */} | |
| <div | |
| ref={containerRef} | |
| className="w-full h-full min-h-[400px]" | |
| style={{ visibility: loading ? 'hidden' : 'visible' }} | |
| /> | |
| </div> | |
| ); | |
| }; | |
| export default PlotRenderer; | |