Files
Neon-Desk/components/charts/renderers/combination-chart-view.tsx

113 lines
3.3 KiB
TypeScript

import {
ComposedChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
Legend
} from 'recharts';
import type { DataSeries } from '@/lib/types';
import { getChartColors } from '../utils/chart-colors';
import { ChartTooltip } from '../primitives/chart-tooltip';
import { mergeDataSeries } from '../utils/chart-data-transformers';
type CombinationChartViewProps = {
dataSeries: DataSeries[];
formatters?: {
price?: (value: number) => string;
date?: (value: string) => string;
volume?: (value: number) => string;
};
};
export function CombinationChartView({
dataSeries,
formatters
}: CombinationChartViewProps) {
const colors = getChartColors();
if (!dataSeries || dataSeries.length === 0) {
return (
<div className="flex h-full items-center justify-center text-sm text-[color:var(--terminal-muted)]">
No data series provided
</div>
);
}
const mergedData = mergeDataSeries(dataSeries);
const visibleSeries = dataSeries.filter(series => series.visible !== false);
const baseValues = Object.fromEntries(visibleSeries.map((series) => {
const initialPoint = mergedData.find((entry) => typeof entry[series.id] === 'number');
const baseValue = typeof initialPoint?.[series.id] === 'number' ? Number(initialPoint[series.id]) : null;
return [series.id, baseValue];
}));
const normalizedData = mergedData.map((point) => {
const normalizedPoint: Record<string, string | number | null> = { date: point.date };
visibleSeries.forEach((series) => {
const baseValue = baseValues[series.id];
const currentValue = typeof point[series.id] === 'number' ? Number(point[series.id]) : null;
normalizedPoint[series.id] = baseValue && currentValue
? ((currentValue / baseValue) - 1) * 100
: null;
});
return normalizedPoint;
});
return (
<ResponsiveContainer width="100%" height="100%">
<ComposedChart data={normalizedData}>
<CartesianGrid strokeDasharray="2 2" stroke={colors.grid} />
<XAxis
dataKey="date"
stroke={colors.muted}
fontSize={11}
tickFormatter={formatters?.date}
minTickGap={32}
/>
<YAxis
stroke={colors.muted}
fontSize={11}
tickFormatter={(value) => `${Number(value).toFixed(0)}%`}
width={60}
domain={['auto', 'auto']}
/>
<Tooltip
content={(tooltipProps) => (
<ChartTooltip
{...tooltipProps}
formatters={{
...formatters,
price: (value: number) => `${value.toFixed(2)}%`
}}
/>
)}
cursor={{ stroke: colors.muted, strokeDasharray: '3 3' }}
/>
<Legend />
{visibleSeries.map(series => {
const seriesColor = series.color || colors.primary;
return (
<Line
key={series.id}
type="monotone"
dataKey={series.id}
name={series.label}
stroke={seriesColor}
strokeWidth={2}
dot={false}
connectNulls={false}
isAnimationActive={normalizedData.length <= 500}
/>
);
})}
</ComposedChart>
</ResponsiveContainer>
);
}