Spaces:
Sleeping
Sleeping
| import React from 'react'; | |
| import { TableData, ExtractedDataRow } from '../types'; | |
| import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, ScatterChart, Scatter, BarChart, Bar } from 'recharts'; | |
| interface DataVisualizerProps { | |
| tables: TableData[]; | |
| } | |
| const DataVisualizer: React.FC<DataVisualizerProps> = ({ tables }) => { | |
| // Filter tables that have configuration from LLM | |
| const visualizableTables = tables.filter(t => t.visualization_config && t.rows.length > 0); | |
| if (visualizableTables.length === 0) { | |
| return ( | |
| <div className="flex items-center justify-center h-64 bg-industrial-900/30 rounded-xl border border-dashed border-industrial-800"> | |
| <div className="text-center"> | |
| <p className="text-gray-500 mb-2">No visualizable data found.</p> | |
| <p className="text-xs text-gray-600">The AI determines which tables contain data suitable for charting.</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Helper to validate chart data | |
| const isValidChartData = (rows: ExtractedDataRow[], xKey: string, yKeys: string[]): boolean => { | |
| if (!rows || rows.length === 0) return false; | |
| if (!xKey || !yKeys || yKeys.length === 0) return false; | |
| // Check if at least some rows have valid data for the specified keys | |
| const hasValidData = rows.some(row => { | |
| const hasX = row[xKey] !== undefined && row[xKey] !== null; | |
| const hasY = yKeys.some(yKey => row[yKey] !== undefined && row[yKey] !== null); | |
| return hasX && hasY; | |
| }); | |
| return hasValidData; | |
| }; | |
| // Common chart margins - increased to prevent overlap | |
| const chartMargins = { top: 20, right: 40, left: 70, bottom: 80 }; | |
| // Custom axis tick formatter for long labels | |
| const formatAxisTick = (value: any): string => { | |
| if (typeof value === 'string' && value.length > 12) { | |
| return value.substring(0, 12) + '...'; | |
| } | |
| return String(value); | |
| }; | |
| return ( | |
| <div className="space-y-8"> | |
| {visualizableTables.map((table, idx) => { | |
| const config = table.visualization_config!; | |
| const { type, xAxisKey, yAxisKeys, title } = config; | |
| // Validate chart data before rendering | |
| if (!isValidChartData(table.rows, xAxisKey, yAxisKeys)) { | |
| return ( | |
| <div key={idx} className="bg-industrial-900 border border-industrial-800 rounded-xl p-4"> | |
| <h4 className="text-sm font-bold text-petro-100 mb-4 uppercase tracking-wider"> | |
| {title || table.title} | |
| </h4> | |
| <div className="flex items-center justify-center h-48 bg-industrial-900/30 rounded-lg border border-dashed border-industrial-700"> | |
| <p className="text-gray-500 text-sm">Insufficient data for chart visualization</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Generate chart description | |
| const chartDescription = `Showing ${yAxisKeys.join(', ')} plotted against ${xAxisKey}${table.rows.length > 0 ? ` (${table.rows.length} data points)` : ''}`; | |
| return ( | |
| <div key={idx} className="bg-industrial-900 border border-industrial-800 rounded-xl p-4"> | |
| <h4 className="text-sm font-bold text-petro-100 mb-4 uppercase tracking-wider"> | |
| {title || table.title} | |
| </h4> | |
| <div className="h-[350px] w-full"> | |
| <ResponsiveContainer width="100%" height="100%"> | |
| {(() => { | |
| switch (type) { | |
| case 'line_chart': | |
| return ( | |
| <LineChart data={table.rows} margin={chartMargins}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="#333" /> | |
| <XAxis | |
| dataKey={xAxisKey} | |
| stroke="#888" | |
| tick={{ fill: '#888', fontSize: 11 }} | |
| tickFormatter={formatAxisTick} | |
| angle={-45} | |
| textAnchor="end" | |
| height={60} | |
| interval="preserveStartEnd" | |
| /> | |
| <YAxis | |
| stroke="#888" | |
| tick={{ fill: '#888', fontSize: 11 }} | |
| label={{ | |
| value: yAxisKeys[0], | |
| angle: -90, | |
| position: 'insideLeft', | |
| fill: '#666', | |
| style: { textAnchor: 'middle' } | |
| }} | |
| width={60} | |
| /> | |
| <Tooltip | |
| contentStyle={{ | |
| backgroundColor: '#1a1d21', | |
| borderColor: '#333', | |
| borderRadius: '8px', | |
| fontSize: '12px' | |
| }} | |
| /> | |
| <Legend | |
| verticalAlign="top" | |
| height={36} | |
| wrapperStyle={{ paddingBottom: '10px' }} | |
| /> | |
| {yAxisKeys.map((key, i) => ( | |
| <Line | |
| key={key} | |
| type="monotone" | |
| dataKey={key} | |
| stroke={['#c59178', '#60a5fa', '#34d399', '#f472b6', '#fbbf24'][i % 5]} | |
| strokeWidth={2} | |
| dot={{ r: 3, fill: ['#c59178', '#60a5fa', '#34d399', '#f472b6', '#fbbf24'][i % 5] }} | |
| activeDot={{ r: 5 }} | |
| /> | |
| ))} | |
| </LineChart> | |
| ); | |
| case 'bar_chart': | |
| return ( | |
| <BarChart data={table.rows} margin={chartMargins}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="#333" /> | |
| <XAxis | |
| dataKey={xAxisKey} | |
| stroke="#888" | |
| tick={{ fill: '#888', fontSize: 11 }} | |
| tickFormatter={formatAxisTick} | |
| angle={-45} | |
| textAnchor="end" | |
| height={60} | |
| interval="preserveStartEnd" | |
| /> | |
| <YAxis | |
| stroke="#888" | |
| tick={{ fill: '#888', fontSize: 11 }} | |
| label={{ | |
| value: yAxisKeys[0], | |
| angle: -90, | |
| position: 'insideLeft', | |
| fill: '#666', | |
| style: { textAnchor: 'middle' } | |
| }} | |
| width={60} | |
| /> | |
| <Tooltip | |
| contentStyle={{ | |
| backgroundColor: '#1a1d21', | |
| borderColor: '#333', | |
| borderRadius: '8px', | |
| fontSize: '12px' | |
| }} | |
| /> | |
| <Legend | |
| verticalAlign="top" | |
| height={36} | |
| wrapperStyle={{ paddingBottom: '10px' }} | |
| /> | |
| {yAxisKeys.map((key, i) => ( | |
| <Bar | |
| key={key} | |
| dataKey={key} | |
| fill={['#c59178', '#60a5fa', '#34d399', '#f472b6', '#fbbf24'][i % 5]} | |
| radius={[4, 4, 0, 0]} | |
| /> | |
| ))} | |
| </BarChart> | |
| ); | |
| case 'scatter_chart': | |
| const yKey = yAxisKeys[0]; | |
| return ( | |
| <ScatterChart margin={chartMargins}> | |
| <CartesianGrid strokeDasharray="3 3" stroke="#333" /> | |
| <XAxis | |
| type="number" | |
| dataKey={xAxisKey} | |
| name={xAxisKey} | |
| stroke="#888" | |
| tick={{ fill: '#888', fontSize: 11 }} | |
| label={{ | |
| value: xAxisKey, | |
| position: 'bottom', | |
| fill: '#666', | |
| offset: -5 | |
| }} | |
| /> | |
| <YAxis | |
| type="number" | |
| dataKey={yKey} | |
| name={yKey} | |
| stroke="#888" | |
| tick={{ fill: '#888', fontSize: 11 }} | |
| label={{ | |
| value: yKey, | |
| angle: -90, | |
| position: 'insideLeft', | |
| fill: '#666', | |
| style: { textAnchor: 'middle' } | |
| }} | |
| width={60} | |
| /> | |
| <Tooltip | |
| cursor={{ strokeDasharray: '3 3' }} | |
| contentStyle={{ | |
| backgroundColor: '#1a1d21', | |
| borderColor: '#333', | |
| borderRadius: '8px', | |
| fontSize: '12px' | |
| }} | |
| /> | |
| <Legend | |
| verticalAlign="top" | |
| height={36} | |
| wrapperStyle={{ paddingBottom: '10px' }} | |
| /> | |
| <Scatter | |
| name={title || table.title} | |
| data={table.rows} | |
| fill="#c59178" | |
| /> | |
| </ScatterChart> | |
| ); | |
| default: | |
| return null; | |
| } | |
| })()} | |
| </ResponsiveContainer> | |
| </div> | |
| {/* Chart Description */} | |
| <p className="mt-3 text-xs text-gray-500 text-center border-t border-industrial-800 pt-3"> | |
| {chartDescription} | |
| </p> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| ); | |
| }; | |
| export default DataVisualizer; |