PetroMind_AI / frontend /components /DataVisualizer.tsx
gauthamnairy's picture
Upload 41 files
609c821 verified
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;