// The strike events: each hit as a stem at its time and velocity, colored by // which contact point it lands on. For a single strike this is one stem; for // a roll the jitter and velocity spread are what keep it from sounding like // a machine. import { useCanvas, drawFrame, PLOT_COLORS } from './canvasUtils'; interface Props { strikeT: Float32Array; strikeV: Float32Array; strikeS: Float32Array; durationS: number; } export default function StrikePlot({ strikeT, strikeV, strikeS, durationS }: Props) { const ref = useCanvas( (ctx, w, h) => { drawFrame(ctx, w, h); const padL = 40; const padB = 16; const pw = w - padL - 8; const ph = h - padB - 12; const top = 10; let vMax = 0; for (let i = 0; i < strikeV.length; i++) vMax = Math.max(vMax, strikeV[i]); if (vMax <= 0) vMax = 1; const tx = (t: number) => padL + (t / durationS) * pw; ctx.font = '10px system-ui'; ctx.textAlign = 'center'; const tStep = durationS > 4 ? 1.0 : durationS > 1.5 ? 0.5 : 0.1; for (let t = 0; t <= durationS + 1e-9; t += tStep) { const x = tx(t); ctx.strokeStyle = PLOT_COLORS.grid; ctx.beginPath(); ctx.moveTo(x, top); ctx.lineTo(x, top + ph); ctx.stroke(); ctx.fillStyle = PLOT_COLORS.axis; ctx.fillText(`${t.toFixed(1)}s`, x, h - 4); } ctx.textAlign = 'right'; ctx.fillStyle = PLOT_COLORS.axis; ctx.fillText(`${vMax.toFixed(1)} m/s`, padL - 4, top + 4); ctx.fillText('0', padL - 4, top + ph); const s0 = strikeS.length > 0 ? strikeS[0] : 0; for (let i = 0; i < strikeT.length; i++) { const x = tx(strikeT[i]); const y = top + ph - (strikeV[i] / vMax) * ph * 0.92; const alt = Math.abs(strikeS[i] - s0) > 1e-9; ctx.strokeStyle = alt ? 'rgba(232, 163, 61, 0.85)' : 'rgba(77, 208, 225, 0.85)'; ctx.beginPath(); ctx.moveTo(x, top + ph); ctx.lineTo(x, y); ctx.stroke(); ctx.fillStyle = alt ? PLOT_COLORS.accent : PLOT_COLORS.trace; ctx.beginPath(); ctx.arc(x, y, 2.5, 0, 2 * Math.PI); ctx.fill(); } ctx.fillStyle = PLOT_COLORS.axis; ctx.textAlign = 'left'; const note = strikeT.length > 1 ? `${strikeT.length} strikes ยท cyan = strike position, amber = second contact` : `single strike at t = 0`; ctx.fillText(note, padL + 6, top + 4); }, [strikeT, strikeV, strikeS, durationS], 150, ); return ; }