Skip to content

Commit eab0f91

Browse files
committed
Refine mouse visuals and HMM diagram
1 parent a097664 commit eab0f91

1 file changed

Lines changed: 126 additions & 49 deletions

File tree

src/main.jsx

Lines changed: 126 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -211,50 +211,43 @@ function drawMouse(context, x, y, angleDegrees, scale, alpha = 1) {
211211
context.rotate((-angleDegrees * Math.PI) / 180);
212212
context.strokeStyle = `rgba(255,255,255,${alpha})`;
213213
context.fillStyle = '#050505';
214-
context.lineWidth = Math.max(1.3, scale * 0.05);
214+
context.lineWidth = Math.max(1.4, scale * 0.065);
215215
context.lineCap = 'round';
216216
context.lineJoin = 'round';
217217

218-
context.beginPath();
219-
context.moveTo(0, scale * 0.5);
220-
context.lineTo(0, scale * 1.2);
221-
context.stroke();
222-
223218
context.beginPath();
224219
context.ellipse(0, 0, scale * 0.35, scale * 0.56, 0, 0, Math.PI * 2);
225-
context.fill();
226220
context.stroke();
227221

228222
context.beginPath();
229-
context.arc(0, -scale * 0.6, scale * 0.36, 0, Math.PI * 2);
230-
context.fill();
223+
context.arc(0, -scale * 0.58, scale * 0.28, 0, Math.PI * 2);
231224
context.stroke();
232225

233226
context.beginPath();
234-
context.arc(-scale * 0.2, -scale * 0.88, scale * 0.18, 0, Math.PI * 2);
235-
context.fill();
227+
context.arc(-scale * 0.19, -scale * 0.78, scale * 0.12, 0, Math.PI * 2);
236228
context.stroke();
237229
context.beginPath();
238-
context.arc(scale * 0.2, -scale * 0.88, scale * 0.18, 0, Math.PI * 2);
239-
context.fill();
230+
context.arc(scale * 0.19, -scale * 0.78, scale * 0.12, 0, Math.PI * 2);
240231
context.stroke();
241232

242233
context.beginPath();
243-
context.moveTo(0, -scale * 1.1);
244-
context.lineTo(scale * 0.08, -scale * 0.78);
245-
context.lineTo(-scale * 0.08, -scale * 0.78);
246-
context.closePath();
247-
context.fill();
234+
context.moveTo(-scale * 0.08, -scale * 0.84);
235+
context.lineTo(0, -scale * 1.02);
236+
context.lineTo(scale * 0.08, -scale * 0.84);
248237
context.stroke();
249238

250-
context.fillStyle = `rgba(255,255,255,${alpha})`;
251239
context.beginPath();
252-
context.arc(-scale * 0.1, -scale * 0.72, scale * 0.035, 0, Math.PI * 2);
240+
context.arc(-scale * 0.09, -scale * 0.62, scale * 0.018, 0, Math.PI * 2);
241+
context.fillStyle = `rgba(255,255,255,${alpha})`;
253242
context.fill();
254243
context.beginPath();
255-
context.arc(scale * 0.1, -scale * 0.72, scale * 0.035, 0, Math.PI * 2);
244+
context.arc(scale * 0.09, -scale * 0.62, scale * 0.018, 0, Math.PI * 2);
256245
context.fill();
257246

247+
context.beginPath();
248+
context.moveTo(0, scale * 0.52);
249+
context.bezierCurveTo(-scale * 0.18, scale * 0.82, -scale * 0.42, scale * 0.9, -scale * 0.56, scale * 1.12);
250+
context.stroke();
258251
context.restore();
259252
}
260253

@@ -360,54 +353,138 @@ function drawArrow(context, x1, y1, x2, y2, alpha) {
360353
context.restore();
361354
}
362355

356+
function drawCurvedArrow(context, x1, y1, controlX, controlY, x2, y2, alpha) {
357+
const angle = Math.atan2(y2 - controlY, x2 - controlX);
358+
context.save();
359+
context.strokeStyle = `rgba(255,255,255,${alpha})`;
360+
context.fillStyle = `rgba(255,255,255,${alpha})`;
361+
context.lineWidth = 1.4;
362+
context.beginPath();
363+
context.moveTo(x1, y1);
364+
context.quadraticCurveTo(controlX, controlY, x2, y2);
365+
context.stroke();
366+
context.beginPath();
367+
context.moveTo(x2, y2);
368+
context.lineTo(x2 - 9 * Math.cos(angle - 0.45), y2 - 9 * Math.sin(angle - 0.45));
369+
context.lineTo(x2 - 9 * Math.cos(angle + 0.45), y2 - 9 * Math.sin(angle + 0.45));
370+
context.closePath();
371+
context.fill();
372+
context.restore();
373+
}
374+
375+
function drawRoundedRect(context, x, y, width, height, radius) {
376+
const safeRadius = Math.min(radius, width / 2, height / 2);
377+
context.beginPath();
378+
context.moveTo(x + safeRadius, y);
379+
context.lineTo(x + width - safeRadius, y);
380+
context.quadraticCurveTo(x + width, y, x + width, y + safeRadius);
381+
context.lineTo(x + width, y + height - safeRadius);
382+
context.quadraticCurveTo(x + width, y + height, x + width - safeRadius, y + height);
383+
context.lineTo(x + safeRadius, y + height);
384+
context.quadraticCurveTo(x, y + height, x, y + height - safeRadius);
385+
context.lineTo(x, y + safeRadius);
386+
context.quadraticCurveTo(x, y, x + safeRadius, y);
387+
context.closePath();
388+
}
389+
390+
function drawMathLabel(context, x, y, base, subscript, alpha) {
391+
const font = 'Georgia, Times New Roman, serif';
392+
const baseSize = 18;
393+
const subscriptSize = 11;
394+
context.save();
395+
context.fillStyle = `rgba(255,255,255,${alpha})`;
396+
context.textBaseline = 'alphabetic';
397+
context.font = `italic ${baseSize}px ${font}`;
398+
const baseWidth = context.measureText(base).width;
399+
context.font = `${subscriptSize}px ${font}`;
400+
const subscriptWidth = context.measureText(subscript).width;
401+
const startX = x - (baseWidth + subscriptWidth) / 2;
402+
context.font = `italic ${baseSize}px ${font}`;
403+
context.fillText(base, startX, y + baseSize * 0.34);
404+
context.font = `${subscriptSize}px ${font}`;
405+
context.fillText(subscript, startX + baseWidth + 1, y + baseSize * 0.55);
406+
context.restore();
407+
}
408+
363409
function HMMCanvas() {
364-
const canvasRef = useCanvas((context, width, height, frame) => {
410+
const canvasRef = useCanvas((context, width, height, frame, elapsedMs) => {
365411
clearCanvas(context, width, height);
366-
const paddingX = width * 0.1;
412+
const bounds = { xMin: 0.8, xMax: 10.2, yMin: -0.1, yMax: 5.7 };
413+
const padding = 20;
414+
const scale = Math.min((width - padding * 2) / (bounds.xMax - bounds.xMin), (height - padding * 2) / (bounds.yMax - bounds.yMin));
415+
const plotWidth = (bounds.xMax - bounds.xMin) * scale;
416+
const plotHeight = (bounds.yMax - bounds.yMin) * scale;
417+
const originX = (width - plotWidth) / 2;
418+
const originY = (height - plotHeight) / 2;
367419
const columns = 4;
368-
const stepWidth = (width - paddingX * 2) / (columns - 1);
369-
const yInput = height * 0.22;
370-
const yHidden = height * 0.5;
371-
const yObs = height * 0.78;
372-
const phase = Math.floor((frame / 38) % 50);
420+
const inputY = 5;
421+
const hiddenY = 3;
422+
const obsY = 0.6;
423+
const phase = Math.floor((elapsedMs / 100) % 50);
373424
const visibleSteps = Math.min(Math.floor(phase / 10) + 1, columns);
374-
const currentStep = Math.min(Math.floor(phase / 10), columns - 1);
425+
const currentStep = Math.floor(phase / 10);
426+
427+
const mapPoint = (x, y) => ({
428+
x: originX + (x - bounds.xMin) * scale,
429+
y: originY + (bounds.yMax - y) * scale
430+
});
375431

376432
const getAlpha = (step, offset) => {
377-
if (phase >= step * 10 + offset) return step === currentStep ? 1 : 0.34;
378-
return 0.14;
433+
if (phase >= step * 10 + offset) return step === currentStep ? 1 : 0.4;
434+
return 0.3;
379435
};
380436

381-
const drawNode = (x, y, label, shape, alpha) => {
437+
const drawNode = (x, y, base, subscript, shape, alpha) => {
438+
const point = mapPoint(x, y);
382439
context.save();
383440
context.strokeStyle = `rgba(255,255,255,${alpha})`;
384-
context.fillStyle = `rgba(255,255,255,${alpha})`;
441+
context.fillStyle = '#050505';
385442
context.lineWidth = 1.7;
386443
if (shape === 'square') {
387-
context.strokeRect(x - 24, y - 24, 48, 48);
444+
const half = 0.5 * scale;
445+
drawRoundedRect(context, point.x - half, point.y - half, half * 2, half * 2, 0.1 * scale);
446+
context.fill();
447+
context.stroke();
388448
} else {
389449
context.beginPath();
390-
context.arc(x, y, 26, 0, Math.PI * 2);
450+
context.arc(point.x, point.y, 0.6 * scale, 0, Math.PI * 2);
451+
context.fill();
391452
context.stroke();
392453
}
393-
context.font = `14px ${MONO_FONT}`;
394-
context.textAlign = 'center';
395-
context.textBaseline = 'middle';
396-
context.fillText(label, x, y);
397454
context.restore();
455+
drawMathLabel(context, point.x, point.y, base, subscript, alpha);
398456
};
399457

400458
for (let step = 0; step < visibleSteps; step += 1) {
401-
const x = paddingX + step * stepWidth;
402-
drawNode(x, yInput, `u_${step + 1}`, 'circle', getAlpha(step, 0));
403-
drawNode(x, yHidden, `z_${step + 1}`, 'circle', getAlpha(step, 3));
404-
drawNode(x, yObs, `x_${step + 1}`, 'square', getAlpha(step, 6));
405-
drawArrow(context, x, yInput + 30, x, yHidden - 30, getAlpha(step, 2));
406-
drawArrow(context, x, yHidden + 30, x, yObs - 30, getAlpha(step, 5));
407-
drawArrow(context, x - 8, yInput + 32, x - 16, yObs - 32, getAlpha(step, 7));
408-
if (step < visibleSteps - 1) {
409-
drawArrow(context, x + 31, yHidden, x + stepWidth - 31, yHidden, getAlpha(step + 1, 1));
410-
}
459+
const x = 2 + step * 2.5;
460+
const inputStart = mapPoint(x, inputY - 0.6);
461+
const hiddenEnd = mapPoint(x, hiddenY + 0.6);
462+
const hiddenStart = mapPoint(x, hiddenY - 0.6);
463+
const obsEnd = mapPoint(x, obsY + 0.6);
464+
drawArrow(context, inputStart.x, inputStart.y, hiddenEnd.x, hiddenEnd.y, getAlpha(step, 2));
465+
drawArrow(context, hiddenStart.x, hiddenStart.y, obsEnd.x, obsEnd.y, getAlpha(step, 5));
466+
467+
const curveStart = mapPoint(x - 0.2, inputY - 0.6);
468+
const curveEnd = mapPoint(x - 0.4, obsY + 0.6);
469+
const curveControl = mapPoint(x - 1.1, (inputY + obsY) / 2);
470+
drawCurvedArrow(context, curveStart.x, curveStart.y, curveControl.x, curveControl.y, curveEnd.x, curveEnd.y, getAlpha(step, 7));
471+
}
472+
473+
for (let step = 0; step < visibleSteps - 1; step += 1) {
474+
const x1 = 2 + step * 2.5;
475+
const x2 = 2 + (step + 1) * 2.5;
476+
const start = mapPoint(x1 + 0.6, hiddenY);
477+
const end = mapPoint(x2 - 0.6, hiddenY);
478+
const alpha = phase >= (step + 1) * 10 + 1 ? (step === currentStep || step + 1 === currentStep ? 1 : 0.4) : 0.3;
479+
drawArrow(context, start.x, start.y, end.x, end.y, alpha);
480+
}
481+
482+
for (let step = 0; step < visibleSteps; step += 1) {
483+
const x = 2 + step * 2.5;
484+
const subscript = String(step + 1);
485+
drawNode(x, inputY, 'u', subscript, 'circle', getAlpha(step, 0));
486+
drawNode(x, hiddenY, 'z', subscript, 'circle', getAlpha(step, 3));
487+
drawNode(x, obsY, 'x', subscript, 'square', getAlpha(step, 6));
411488
}
412489
}, []);
413490

0 commit comments

Comments
 (0)