upd
This commit is contained in:
150
src/components/scoreboard/ScoreChart.tsx
Normal file
150
src/components/scoreboard/ScoreChart.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { useMemo } from 'react';
|
||||
import { ScoreHistory } from '../../api/types';
|
||||
import { cn } from '../../utils/helpers';
|
||||
import { getTeamColor } from '../../utils/colors';
|
||||
|
||||
interface ScoreChartProps {
|
||||
history: ScoreHistory[];
|
||||
height?: number;
|
||||
showLegend?: boolean;
|
||||
showGrid?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ScoreChart({
|
||||
history,
|
||||
height = 300,
|
||||
showLegend = true,
|
||||
showGrid = true,
|
||||
className,
|
||||
}: ScoreChartProps) {
|
||||
// Get all rounds
|
||||
const allRounds = useMemo(() => {
|
||||
const rounds = new Set<number>();
|
||||
history.forEach((h) => {
|
||||
h.history.forEach((point) => rounds.add(point.round));
|
||||
});
|
||||
return Array.from(rounds).sort((a, b) => a - b);
|
||||
}, [history]);
|
||||
|
||||
// Get max score for scaling
|
||||
const maxScore = useMemo(() => {
|
||||
let max = 0;
|
||||
history.forEach((h) => {
|
||||
h.history.forEach((point) => {
|
||||
if (point.score > max) max = point.score;
|
||||
});
|
||||
});
|
||||
return max || 100;
|
||||
}, [history]);
|
||||
|
||||
// Generate path for a team
|
||||
const generatePath = (teamHistory: ScoreHistory) => {
|
||||
if (allRounds.length === 0 || teamHistory.history.length === 0) return '';
|
||||
|
||||
const points = allRounds.map((round) => {
|
||||
const point = teamHistory.history.find((p) => p.round === round);
|
||||
const score = point?.score || 0;
|
||||
const x = (allRounds.indexOf(round) / (allRounds.length - 1 || 1)) * 100;
|
||||
const y = 100 - (score / maxScore) * 100;
|
||||
return `${x},${y}`;
|
||||
});
|
||||
|
||||
return points.map((p, i) => (i === 0 ? `M ${p}` : `L ${p}`)).join(' ');
|
||||
};
|
||||
|
||||
if (history.length === 0) {
|
||||
return (
|
||||
<div className={cn('flex items-center justify-center', className)} style={{ height }}>
|
||||
<p className="text-muted-foreground">Нет данных для отображения</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('space-y-4', className)}>
|
||||
{/* Chart */}
|
||||
<div className="relative overflow-hidden rounded-lg border border-border bg-card">
|
||||
<svg
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="none"
|
||||
className="h-full w-full"
|
||||
style={{ height }}
|
||||
>
|
||||
{/* Grid */}
|
||||
{showGrid && (
|
||||
<>
|
||||
{[0, 25, 50, 75, 100].map((y) => (
|
||||
<line
|
||||
key={y}
|
||||
x1="0"
|
||||
y1={y}
|
||||
x2="100"
|
||||
y2={y}
|
||||
stroke="hsl(var(--border))"
|
||||
strokeWidth="0.2"
|
||||
strokeDasharray="2,2"
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Lines */}
|
||||
{history.map((teamHistory, index) => (
|
||||
<g key={teamHistory.teamId}>
|
||||
<path
|
||||
d={generatePath(teamHistory)}
|
||||
fill="none"
|
||||
stroke={getTeamColor(index)}
|
||||
strokeWidth="0.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
{/* Points */}
|
||||
{teamHistory.history.map((point, i) => {
|
||||
const x = (allRounds.indexOf(point.round) / (allRounds.length - 1 || 1)) * 100;
|
||||
const y = 100 - (point.score / maxScore) * 100;
|
||||
return (
|
||||
<circle
|
||||
key={i}
|
||||
cx={x}
|
||||
cy={y}
|
||||
r="1"
|
||||
fill={getTeamColor(index)}
|
||||
className="opacity-0 hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
|
||||
{/* X-axis labels */}
|
||||
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-2 text-[10px] text-muted-foreground">
|
||||
{allRounds.slice(0, 10).map((round) => (
|
||||
<span key={round}>{round}</span>
|
||||
))}
|
||||
{allRounds.length > 10 && <span>...</span>}
|
||||
{allRounds.length > 1 && <span>{allRounds[allRounds.length - 1]}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Legend */}
|
||||
{showLegend && (
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{history.map((teamHistory, index) => (
|
||||
<div key={teamHistory.teamId} className="flex items-center gap-2">
|
||||
<div
|
||||
className="h-3 w-3 rounded-full"
|
||||
style={{ backgroundColor: getTeamColor(index) }}
|
||||
/>
|
||||
<span className="text-sm">{teamHistory.teamName}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ScoreChart;
|
||||
Reference in New Issue
Block a user