1// The stroke events: each contact as a stem at its time and velocity, colored
2// by which radius it lands on. For a single crash this is one stem; for a
3// repeated figure the jitter and velocity spread are what keep it from
4// sounding like a machine.
6import { useCanvas, drawFrame, PLOT_COLORS } from './canvasUtils';
8interface Props {
9 strikeT: Float32Array;
10 strikeV: Float32Array;
11 strikeS: Float32Array;
12 durationS: number;
13}
15export default function StrikePlot({ strikeT, strikeV, strikeS, durationS }: Props) {
16 const ref = useCanvas(
17 (ctx, w, h) => {
18 drawFrame(ctx, w, h);
19 const padL = 40;
20 const padB = 16;
21 const pw = w - padL - 8;
22 const ph = h - padB - 12;
23 const top = 10;
25 let vMax = 0;
26 for (let i = 0; i < strikeV.length; i++) vMax = Math.max(vMax, strikeV[i]);
27 if (vMax <= 0) vMax = 1;
29 const tx = (t: number) => padL + (t / durationS) * pw;
31 ctx.font = '10px system-ui';
32 ctx.textAlign = 'center';
33 const tStep = durationS > 4 ? 1.0 : durationS > 1.5 ? 0.5 : 0.1;
34 for (let t = 0; t <= durationS + 1e-9; t += tStep) {
35 const x = tx(t);
36 ctx.strokeStyle = PLOT_COLORS.grid;
37 ctx.beginPath();
38 ctx.moveTo(x, top);
39 ctx.lineTo(x, top + ph);
40 ctx.stroke();
41 ctx.fillStyle = PLOT_COLORS.axis;
42 ctx.fillText(`${t.toFixed(1)}s`, x, h - 4);
43 }
44 ctx.textAlign = 'right';
45 ctx.fillStyle = PLOT_COLORS.axis;
46 ctx.fillText(`${vMax.toFixed(1)} m/s`, padL - 4, top + 4);
47 ctx.fillText('0', padL - 4, top + ph);
49 const s0 = strikeS.length > 0 ? strikeS[0] : 0;
50 for (let i = 0; i < strikeT.length; i++) {
51 const x = tx(strikeT[i]);
52 const y = top + ph - (strikeV[i] / vMax) * ph * 0.92;
53 const alt = Math.abs(strikeS[i] - s0) > 1e-9;
54 ctx.strokeStyle = alt ? 'rgba(232, 163, 61, 0.85)' : 'rgba(77, 208, 225, 0.85)';
55 ctx.beginPath();
56 ctx.moveTo(x, top + ph);
57 ctx.lineTo(x, y);
58 ctx.stroke();
59 ctx.fillStyle = alt ? PLOT_COLORS.accent : PLOT_COLORS.trace;
60 ctx.beginPath();
61 ctx.arc(x, y, 2.5, 0, 2 * Math.PI);
62 ctx.fill();
63 }
65 ctx.fillStyle = PLOT_COLORS.axis;
66 ctx.textAlign = 'left';
67 const note =
68 strikeT.length > 1
69 ? `${strikeT.length} strokes · cyan = contact radius, amber = second radius`
70 : `single stroke at t = 0`;
71 ctx.fillText(note, padL + 6, top + 4);
72 },
73 [strikeT, strikeV, strikeS, durationS],
74 150,
75 );
76 return <canvas ref={ref} />;
77}