Datavision / frontend /src /components /simulator /SimulatorChart.tsx
DataVision CI/CD Bot
release: clean production build for HuggingFace Space
09801ca
Raw
History Blame Contribute Delete
5.93 kB
import React from 'react';
import { useUserStore } from '@/store/userStore';
import {
ResponsiveContainer,
LineChart,
Line,
AreaChart,
Area,
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ReferenceLine,
} from 'recharts';
interface SimulatorChartProps {
data: any[];
type?: 'line' | 'area' | 'bar';
height?: number;
lines?: Array<{
dataKey: string;
color: string;
name?: string;
strokeDasharray?: string;
type?: 'monotone' | 'linear';
dot?: boolean;
fill?: boolean;
}>;
xDataKey?: string;
showGrid?: boolean;
showLegend?: boolean;
showTooltip?: boolean;
referenceLineY?: number;
referenceLabel?: string;
formatY?: (v: any) => string;
formatX?: (v: any) => string;
}
const SimulatorChart: React.FC<SimulatorChartProps> = ({
data,
type = 'area',
height = 300,
lines = [],
xDataKey = 'period',
showGrid = true,
showLegend = true,
showTooltip = true,
referenceLineY,
referenceLabel,
formatY,
formatX,
}) => {
const { isDark } = useUserStore();
const gridColor = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(0,0,0,0.04)';
const axisColor = isDark ? '#64748b' : '#94a3b8';
const tooltipBg = isDark ? '#1e1e2e' : '#ffffff';
const tooltipBorder = isDark ? '#2e2e3e' : '#e2e8f0';
const CustomTooltip = ({ active, payload, label }: any) => {
if (!active || !payload?.length) return null;
return (
<div
className="rounded-xl border p-3 shadow-xl backdrop-blur-sm"
style={{
background: tooltipBg,
borderColor: tooltipBorder,
}}
>
<p className="text-xs font-semibold mb-2" style={{ color: isDark ? '#f8fafc' : '#0f172a' }}>
{label}
</p>
{payload.map((entry: any, i: number) => (
<div key={i} className="flex items-center gap-2 text-xs mb-1">
<div className="w-2 h-2 rounded-full" style={{ background: entry.color }} />
<span style={{ color: isDark ? '#94a3b8' : '#64748b' }}>{entry.name}:</span>
<span className="font-semibold" style={{ color: isDark ? '#f8fafc' : '#0f172a' }}>
{formatY ? formatY(entry.value) : entry.value?.toLocaleString()}
</span>
</div>
))}
</div>
);
};
const commonProps = {
data,
margin: { top: 5, right: 10, left: 5, bottom: 5 },
};
const commonAxisProps = {
xAxis: (
<XAxis
dataKey={xDataKey}
tick={{ fill: axisColor, fontSize: 11 }}
axisLine={{ stroke: gridColor }}
tickLine={false}
tickFormatter={formatX}
/>
),
yAxis: (
<YAxis
tick={{ fill: axisColor, fontSize: 11 }}
axisLine={false}
tickLine={false}
tickFormatter={formatY || ((v: number) => v >= 10000000 ? `${(v / 10000000).toFixed(0)} Cr` : v >= 100000 ? `${(v / 100000).toFixed(0)} L` : v.toLocaleString())}
width={55}
/>
),
};
const renderLines = () =>
lines.map((line, i) => {
if (type === 'area' || line.fill) {
return (
<Area
key={i}
type={line.type || 'monotone'}
dataKey={line.dataKey}
name={line.name || line.dataKey}
stroke={line.color}
fill={line.color}
fillOpacity={0.08}
strokeWidth={line.strokeDasharray ? 1.5 : 2.5}
strokeDasharray={line.strokeDasharray}
dot={line.dot !== false ? { r: 3, fill: line.color, strokeWidth: 0 } : false}
activeDot={{ r: 5, fill: line.color, stroke: '#fff', strokeWidth: 2 }}
/>
);
}
return (
<Line
key={i}
type={line.type || 'monotone'}
dataKey={line.dataKey}
name={line.name || line.dataKey}
stroke={line.color}
strokeWidth={line.strokeDasharray ? 1.5 : 2.5}
strokeDasharray={line.strokeDasharray}
dot={line.dot !== false ? { r: 3, fill: line.color, strokeWidth: 0 } : false}
activeDot={{ r: 5, fill: line.color, stroke: '#fff', strokeWidth: 2 }}
/>
);
});
if (type === 'bar') {
return (
<ResponsiveContainer width="100%" height={height}>
<BarChart {...commonProps}>
{showGrid && <CartesianGrid strokeDasharray="3 3" stroke={gridColor} vertical={false} />}
{commonAxisProps.xAxis}
{commonAxisProps.yAxis}
{showTooltip && <Tooltip content={<CustomTooltip />} />}
{showLegend && <Legend iconSize={8} wrapperStyle={{ fontSize: 11, color: axisColor }} />}
{lines.map((line, i) => (
<Bar key={i} dataKey={line.dataKey} name={line.name || line.dataKey} fill={line.color} radius={[4, 4, 0, 0]} />
))}
</BarChart>
</ResponsiveContainer>
);
}
return (
<ResponsiveContainer width="100%" height={height}>
<AreaChart {...commonProps}>
<defs>
{lines.map((line, i) => (
<linearGradient key={i} id={`gradient-${line.dataKey}`} x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={line.color} stopOpacity={0.15} />
<stop offset="95%" stopColor={line.color} stopOpacity={0} />
</linearGradient>
))}
</defs>
{showGrid && <CartesianGrid strokeDasharray="3 3" stroke={gridColor} vertical={false} />}
{commonAxisProps.xAxis}
{commonAxisProps.yAxis}
{showTooltip && <Tooltip content={<CustomTooltip />} />}
{showLegend && <Legend iconSize={8} wrapperStyle={{ fontSize: 11, color: axisColor }} />}
{referenceLineY !== undefined && (
<ReferenceLine y={referenceLineY} stroke={isDark ? '#475569' : '#94a3b8'} strokeDasharray="5 5" label={referenceLabel} />
)}
{renderLines()}
</AreaChart>
</ResponsiveContainer>
);
};
export default SimulatorChart;