import React, { useRef, useState, ReactNode } from 'react'; import JSZip from 'jszip'; interface ExportableChartProps { children: ReactNode; filename?: string; title?: string; className?: string; controls?: ReactNode; } // Wrapper component that adds PNG export to any chart export function ExportableChart({ children, filename = 'chart', title = '', className = '', controls = null }: ExportableChartProps) { const chartRef = useRef(null); const exportAsPNG = () => { const chartContainer = chartRef.current; if (!chartContainer) return; const canvas = chartContainer.querySelector('canvas'); if (!canvas) return; // Create a new canvas with white background and title const exportCanvas = document.createElement('canvas'); const ctx = exportCanvas.getContext('2d'); if (!ctx) return; // Set dimensions with padding and title space const padding = 24; const titleHeight = title ? 48 : 0; exportCanvas.width = canvas.width + (padding * 2); exportCanvas.height = canvas.height + (padding * 2) + titleHeight; // Fill white background ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, exportCanvas.width, exportCanvas.height); // Draw title if provided (left-aligned, matching on-screen style) if (title) { ctx.fillStyle = '#1e293b'; ctx.font = '600 20px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; ctx.textAlign = 'left'; ctx.fillText(title, padding, padding + 24); } // Draw the chart ctx.drawImage(canvas, padding, padding + titleHeight); // Export const link = document.createElement('a'); link.download = `${filename}-${new Date().toISOString().split('T')[0]}.png`; link.href = exportCanvas.toDataURL('image/png', 1.0); link.click(); }; return (
{/* Download button - positioned absolutely in corner */} {title && (

{title}

{controls &&
{controls}
}
)} {!title && controls &&
{controls}
}
{children}
); } // Utility function to export all charts from a container as a ZIP export async function exportAllCharts(containerSelector: string, zipFilename: string = 'charts'): Promise { const container = document.querySelector(containerSelector); if (!container) return; const zip = new JSZip(); const chartWrappers = container.querySelectorAll('.exportable-chart-wrapper'); for (let i = 0; i < chartWrappers.length; i++) { const wrapper = chartWrappers[i]; const canvas = wrapper.querySelector('canvas'); const titleEl = wrapper.querySelector('.chart-header-with-export h2'); const title = titleEl?.textContent || `chart-${i + 1}`; if (!canvas) continue; // Create export canvas with white background and title const exportCanvas = document.createElement('canvas'); const ctx = exportCanvas.getContext('2d'); if (!ctx) continue; const padding = 32; const titleHeight = 56; exportCanvas.width = canvas.width + (padding * 2); exportCanvas.height = canvas.height + (padding * 2) + titleHeight; // White background ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, exportCanvas.width, exportCanvas.height); // Draw title ctx.fillStyle = '#1e293b'; ctx.font = '600 24px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; ctx.textAlign = 'left'; ctx.fillText(title, padding, padding + 28); // Draw chart ctx.drawImage(canvas, padding, padding + titleHeight); // Convert to blob and add to zip const dataUrl = exportCanvas.toDataURL('image/png', 1.0); const base64Data = dataUrl.split(',')[1]; const safeFilename = title.replace(/[^a-zA-Z0-9\u0600-\u06FF\s-]/g, '').replace(/\s+/g, '-'); zip.file(`${String(i + 1).padStart(2, '0')}-${safeFilename}.png`, base64Data, { base64: true }); } // Generate and download ZIP const blob = await zip.generateAsync({ type: 'blob' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${zipFilename}-${new Date().toISOString().split('T')[0]}.zip`; link.click(); URL.revokeObjectURL(url); } interface ExportAllButtonProps { containerSelector: string; zipFilename?: string; label: string; loadingLabel: string; } // Button component for exporting all charts export function ExportAllButton({ containerSelector, zipFilename = 'charts', label, loadingLabel }: ExportAllButtonProps) { const [exporting, setExporting] = useState(false); const handleExport = async () => { setExporting(true); try { await exportAllCharts(containerSelector, zipFilename); } finally { setExporting(false); } }; return ( ); } export default ExportableChart;