import { compoundClass } from '../../lib/live' import '../../styles/stint-timeline.css' export interface StintTimelineStint { compound: string lapStart: number lapEnd: number isNew?: boolean } export interface StintTimelineRow { label: string color: string stints: StintTimelineStint[] /** Lap numbers where the driver pitted; optional — rows without stops render normally. */ pitStops?: number[] } interface TyreStintTimelineProps { rows: StintTimelineRow[] totalLaps: number } const SVG_W = 640 const LEFT = 48 const RIGHT = 12 const ROW_H = 28 const BAR_H = 14 const BAR_Y = 7 const AXIS_H = 20 const BAR_W = SVG_W - LEFT - RIGHT const COMPOUND_ORDER = ['SOFT', 'MEDIUM', 'HARD', 'INTERMEDIATE', 'WET'] as const function compoundLabel(compound: string): string { const upper = compound.toUpperCase() if (upper === 'INTERMEDIATE') return 'Intermediate' return upper.charAt(0) + upper.slice(1).toLowerCase() } function stintLength(stint: StintTimelineStint): number { return stint.lapEnd - stint.lapStart + 1 } function stintTitle(stint: StintTimelineStint): string { const length = stintLength(stint) return `${compoundLabel(stint.compound)} · L${stint.lapStart}–${stint.lapEnd} · ${length} lap${length === 1 ? '' : 's'}` } function lapX(lap: number, totalLaps: number): number { return LEFT + (lap / totalLaps) * BAR_W } function stintBarX(stint: StintTimelineStint, totalLaps: number): number { return LEFT + ((stint.lapStart - 1) / totalLaps) * BAR_W } function stintBarW(stint: StintTimelineStint, totalLaps: number): number { return Math.max(2, (stintLength(stint) / totalLaps) * BAR_W) } function pitMarkerX(lapNumber: number, totalLaps: number): number { return LEFT + ((lapNumber - 1) / totalLaps) * BAR_W } function pitMarkerTitle(driverLabel: string, lapNumber: number): string { return `${driverLabel} pit stop · L${lapNumber}` } function axisTicks(totalLaps: number): number[] { const ticks: number[] = [] for (let lap = 0; lap <= totalLaps; lap += 10) { ticks.push(lap) } return ticks } function collectUsedCompounds(rows: StintTimelineRow[]): string[] { const seen = new Set() for (const row of rows) { for (const stint of row.stints) { seen.add(stint.compound.toUpperCase()) } } const ordered = COMPOUND_ORDER.filter((c) => seen.has(c)) const extras = [...seen] .filter((c) => !COMPOUND_ORDER.includes(c as (typeof COMPOUND_ORDER)[number])) .sort() return [...ordered, ...extras] } export function TyreStintTimeline({ rows, totalLaps }: TyreStintTimelineProps) { const safeTotal = Math.max(totalLaps, 1) const usedCompounds = collectUsedCompounds(rows) const ticks = axisTicks(safeTotal) const chartH = rows.length * ROW_H const svgH = chartH + AXIS_H + 4 if (rows.length === 0) { return (
No stint data to display.
) } return (
{rows.map((row, i) => { const rowY = i * ROW_H return ( {row.label} {row.stints.map((stint, si) => ( {stintTitle(stint)} ))} {(row.pitStops ?? []).map((lapNumber, pi) => ( {pitMarkerTitle(row.label, lapNumber)} ))} ) })} {ticks.map((lap) => ( {lap} ))}
{usedCompounds.map((compound) => ( {compoundLabel(compound)} ))} {safeTotal} laps
) }