@@ -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+
363409function 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