@@ -9,6 +9,7 @@ class FlowDiagram
99
1010 def initialize ( _frame_width , _frame_height )
1111 @animation_frame = 0
12+ @render_tick = 0 # Counter for slowing down animation
1213 @width = 0 # Actual width of diagram content
1314 @height = 0 # Actual height of diagram content
1415 end
@@ -82,8 +83,13 @@ def render(terminal, stats_data, x_offset: 0, y_offset: 0)
8283 diagram_stage . render ( terminal , stage_data , x_offset , y_offset )
8384 end
8485
86+ # Update animation (every 2 renders for half speed)
87+ @render_tick += 1
88+ if @render_tick % 2 == 0
89+ @animation_frame = ( @animation_frame + 1 ) % 24
90+ end
8591 # Update animation
86- @animation_frame = ( @animation_frame + 1 ) % 60
92+ # @animation_frame = (@animation_frame + 1) % 24
8793
8894 # Clear cached data for next frame
8995 @cached_layout = nil
@@ -366,14 +372,17 @@ def render_fanout_connection(terminal, from_pos, target_positions, stage_data, x
366372
367373 # Check if connection is active
368374 active = stage_data [ :throughput ] && stage_data [ :throughput ] > 0
369- color = active ? Theme . primary : Theme . muted
370375
371376 # Calculate split point (horizontal spine where fan-out occurs)
372- first_target_y = target_positions . first [ :y ]
373377 split_y = from_y + 1
374378
379+ # Distance counter starts at 0 from source box exit
380+ distance = 0
381+
375382 # Draw vertical line from source to split point
376- terminal . write_at ( x_offset + from_x , y_offset + from_y , "│" , color : color )
383+ char , color = Theme . animated_flow_char ( :vertical , distance , @animation_frame , active )
384+ terminal . write_at ( x_offset + from_x , y_offset + from_y , char , color : color )
385+ distance += 1
377386
378387 # Get X positions of all targets (sorted)
379388 target_xs = target_positions . map { |pos | pos [ :x ] + pos [ :width ] / 2 } . sort
@@ -383,47 +392,43 @@ def render_fanout_connection(terminal, from_pos, target_positions, stage_data, x
383392 # Check if there's a target directly below the source
384393 has_center_target = target_xs . include? ( from_x )
385394
386- # Draw horizontal spine with junctions
387- # Pattern with center target: ┌───────────────┼───────────────┐
388- # Pattern without center: ┌───────────────┴───────────────┐
395+ # Draw horizontal spine with distance radiating from center
396+ # Distance flows outward from center (from_x)
389397 ( leftmost_x ..rightmost_x ) . each do |x |
390- # Determine the proper box-drawing character
391- char = if x == leftmost_x
392- # Left corner
393- "┌"
394- elsif x == rightmost_x
395- # Right corner
396- "┐"
397- elsif x == from_x
398- # Source position: ┼ if target below, ┴ if not
399- has_center_target ? "┼" : "┴"
400- else
401- # Regular horizontal line (spine)
402- if active
403- offset = ( @animation_frame / 4 ) % 4
404- [ "─" , "╌" , "┄" , "┈" ] [ offset ]
405- else
406- "─"
407- end
408- end
409-
398+ # Calculate distance from center (where flow splits)
399+ x_distance_from_center = ( x - from_x ) . abs
400+ spine_distance = distance + x_distance_from_center
401+
402+ # Determine the proper box-drawing character type
403+ char_type = if x == leftmost_x
404+ :corner_tl # Left corner ┌
405+ elsif x == rightmost_x
406+ :corner_tr # Right corner ┐
407+ elsif x == from_x
408+ # Source position: cross if target below, t_up if not
409+ has_center_target ? :cross : :t_up
410+ else
411+ :horizontal # Regular horizontal line
412+ end
413+
414+ char , color = Theme . animated_flow_char ( char_type , spine_distance , @animation_frame , active )
410415 terminal . write_at ( x_offset + x , y_offset + split_y , char , color : color )
411416 end
412417
413418 # Draw vertical lines down to each target
419+ # Distance continues from spine position
414420 target_positions . each do |to_pos |
415421 to_x = to_pos [ :x ] + to_pos [ :width ] / 2
416422 to_y = to_pos [ :y ]
417423
418- ( ( split_y + 1 ) ...to_y ) . each do |y |
419- char = if active
420- offset = ( @animation_frame / 4 ) % Theme ::FLOW_CHARS . length
421- phase = ( y - split_y + offset ) % Theme ::FLOW_CHARS . length
422- Theme ::FLOW_CHARS [ phase ]
423- else
424- "│"
425- end
424+ # Calculate distance at spine for this target
425+ x_distance_from_center = ( to_x - from_x ) . abs
426+ spine_distance_at_target = distance + x_distance_from_center
426427
428+ ( ( split_y + 1 ) ...to_y ) . each do |y |
429+ # Distance increases as we go down
430+ drop_distance = spine_distance_at_target + ( y - split_y )
431+ char , color = Theme . animated_flow_char ( :vertical , drop_distance , @animation_frame , active )
427432 terminal . write_at ( x_offset + to_x , y_offset + y , char , color : color )
428433 end
429434 end
@@ -436,7 +441,6 @@ def render_fanin_connection(terminal, source_positions, to_pos, stage_data, x_of
436441
437442 # Check if connection is active
438443 active = stage_data [ :throughput ] && stage_data [ :throughput ] > 0
439- color = active ? Theme . primary : Theme . muted
440444
441445 # Calculate merge point (where horizontal lines converge)
442446 # Place it 1 line above the target
@@ -450,64 +454,66 @@ def render_fanin_connection(terminal, source_positions, to_pos, stage_data, x_of
450454 }
451455 end . sort_by { |s | s [ :x ] }
452456
453- # Draw vertical lines from each source down to merge level
454- # Then turn inward with corners
457+ # Draw lines from each source down to merge level, then turn inward
455458 source_data . each do |source |
459+ # Distance starts at 0 for each source
460+ distance = 0
461+
456462 # Vertical line from source to turn point
457463 ( source [ :y ] ...merge_y ) . each do |y |
458- char = if active
459- offset = ( @animation_frame / 4 ) % Theme ::FLOW_CHARS . length
460- phase = ( y - source [ :y ] + offset ) % Theme ::FLOW_CHARS . length
461- Theme ::FLOW_CHARS [ phase ]
462- else
463- "│"
464- end
465-
464+ char , color = Theme . animated_flow_char ( :vertical , distance , @animation_frame , active )
466465 terminal . write_at ( x_offset + source [ :x ] , y_offset + y , char , color : color )
466+ distance += 1
467467 end
468468
469- # Corner at turn point
469+ # Corner at turn point and horizontal line to center
470470 if source [ :x ] < to_x
471471 # Left source: turn right with └
472- terminal . write_at ( x_offset + source [ :x ] , y_offset + merge_y , "└" , color : color )
472+ char , color = Theme . animated_flow_char ( :corner_bl , distance , @animation_frame , active )
473+ terminal . write_at ( x_offset + source [ :x ] , y_offset + merge_y , char , color : color )
474+ distance += 1
473475
474- # Horizontal line from corner to center (or near target)
476+ # Horizontal line from corner to center
475477 ( ( source [ :x ] + 1 ) ...to_x ) . each do |x |
476- char = if active
477- offset = ( @animation_frame / 4 ) % 4
478- [ "─" , "╌" , "┄" , "┈" ] [ offset ]
479- else
480- "─"
481- end
482-
478+ char , color = Theme . animated_flow_char ( :horizontal , distance , @animation_frame , active )
483479 terminal . write_at ( x_offset + x , y_offset + merge_y , char , color : color )
480+ distance += 1
484481 end
485482 elsif source [ :x ] > to_x
486483 # Right source: turn left with ┘
487- terminal . write_at ( x_offset + source [ :x ] , y_offset + merge_y , "┘" , color : color )
488-
489- # Horizontal line from corner to center (or near target)
490- ( ( to_x + 1 ) ...source [ :x ] ) . each do |x |
491- char = if active
492- offset = ( @animation_frame / 4 ) % 4
493- [ "─" , "╌" , "┄" , "┈" ] [ offset ]
494- else
495- "─"
496- end
484+ char , color = Theme . animated_flow_char ( :corner_br , distance , @animation_frame , active )
485+ terminal . write_at ( x_offset + source [ :x ] , y_offset + merge_y , char , color : color )
486+ distance += 1
497487
488+ # Horizontal line from corner to center
489+ ( ( to_x + 1 ) ...source [ :x ] ) . reverse_each do |x |
490+ char , color = Theme . animated_flow_char ( :horizontal , distance , @animation_frame , active )
498491 terminal . write_at ( x_offset + x , y_offset + merge_y , char , color : color )
492+ distance += 1
499493 end
500494 else
501- # Source directly above target - just draw vertical line
495+ # Source directly above target - vertical line continues
502496 # (already drawn above)
503497 end
504498 end
505499
506500 # Draw junction at the converge point (center X position)
507- # Use ┼ if there's a source directly above, ┬ if not
501+ # Use cross if there's a source directly above, t_down if not
508502 has_center_source = source_data . any? { |s | s [ :x ] == to_x }
509- junction_char = has_center_source ? "┼" : "┬"
510- terminal . write_at ( x_offset + to_x , y_offset + merge_y , junction_char , color : color )
503+
504+ # Calculate distance for junction (use center source distance if exists)
505+ center_source = source_data . find { |s | s [ :x ] == to_x }
506+ junction_distance = if center_source
507+ merge_y - center_source [ :y ]
508+ else
509+ # Use distance from closest source
510+ closest = source_data . min_by { |s | ( s [ :x ] - to_x ) . abs }
511+ ( merge_y - closest [ :y ] ) + ( closest [ :x ] - to_x ) . abs
512+ end
513+
514+ junction_type = has_center_source ? :cross : :t_down
515+ char , color = Theme . animated_flow_char ( junction_type , junction_distance , @animation_frame , active )
516+ terminal . write_at ( x_offset + to_x , y_offset + merge_y , char , color : color )
511517 end
512518
513519 # Draw animated connection line between two boxes
@@ -521,62 +527,76 @@ def render_connection_line(terminal, from_pos, to_pos, stage_data, x_offset, y_o
521527
522528 # Check if connection is active (has throughput)
523529 active = stage_data [ :throughput ] && stage_data [ :throughput ] > 0
524- color = active ? Theme . primary : Theme . muted
530+
531+ # Distance counter starts at 0 from source
532+ distance = 0
525533
526534 if from_x == to_x
527535 # Straight vertical line
528536 ( from_y ...to_y ) . each do |y |
529- char = if active
530- offset = ( @animation_frame / 4 ) % Theme ::FLOW_CHARS . length
531- phase = ( y - from_y + offset ) % Theme ::FLOW_CHARS . length
532- Theme ::FLOW_CHARS [ phase ]
533- else
534- "│"
535- end
536-
537+ char , color = Theme . animated_flow_char ( :vertical , distance , @animation_frame , active )
537538 terminal . write_at ( x_offset + from_x , y_offset + y , char , color : color )
539+ distance += 1
538540 end
539541 else
540- # L-shaped connection: vertical down, horizontal across, vertical down
542+ # L-shaped connection: vertical down, corner, horizontal across, corner , vertical down
541543 mid_y = from_y + 1
542544
543545 # First vertical segment (short drop from source)
544- terminal . write_at ( x_offset + from_x , y_offset + from_y , "│" , color : color )
545-
546- # Horizontal segment
547- x_start = [ from_x , to_x ] . min
548- x_end = [ from_x , to_x ] . max
549- ( x_start ..x_end ) . each do |x |
550- char = if active
551- offset = ( @animation_frame / 4 ) % 4
552- [ "─" , "╌" , "┄" , "┈" ] [ offset ]
553- else
554- "─"
555- end
556-
557- terminal . write_at ( x_offset + x , y_offset + mid_y , char , color : color )
546+ char , color = Theme . animated_flow_char ( :vertical , distance , @animation_frame , active )
547+ terminal . write_at ( x_offset + from_x , y_offset + from_y , char , color : color )
548+ distance += 1
549+
550+ # Determine corner and horizontal direction
551+ if from_x < to_x
552+ # Going right: use └ and ┐
553+ corner1_type = :corner_bl
554+ corner2_type = :corner_tr
555+
556+ # First corner
557+ char , color = Theme . animated_flow_char ( corner1_type , distance , @animation_frame , active )
558+ terminal . write_at ( x_offset + from_x , y_offset + mid_y , char , color : color )
559+ distance += 1
560+
561+ # Horizontal segment (left to right)
562+ ( ( from_x + 1 ) ...to_x ) . each do |x |
563+ char , color = Theme . animated_flow_char ( :horizontal , distance , @animation_frame , active )
564+ terminal . write_at ( x_offset + x , y_offset + mid_y , char , color : color )
565+ distance += 1
566+ end
567+
568+ # Second corner
569+ char , color = Theme . animated_flow_char ( corner2_type , distance , @animation_frame , active )
570+ terminal . write_at ( x_offset + to_x , y_offset + mid_y , char , color : color )
571+ distance += 1
572+ else
573+ # Going left: use ┘ and ┌
574+ corner1_type = :corner_br
575+ corner2_type = :corner_tl
576+
577+ # First corner
578+ char , color = Theme . animated_flow_char ( corner1_type , distance , @animation_frame , active )
579+ terminal . write_at ( x_offset + from_x , y_offset + mid_y , char , color : color )
580+ distance += 1
581+
582+ # Horizontal segment (right to left)
583+ ( ( to_x + 1 ) ...from_x ) . reverse_each do |x |
584+ char , color = Theme . animated_flow_char ( :horizontal , distance , @animation_frame , active )
585+ terminal . write_at ( x_offset + x , y_offset + mid_y , char , color : color )
586+ distance += 1
587+ end
588+
589+ # Second corner
590+ char , color = Theme . animated_flow_char ( corner2_type , distance , @animation_frame , active )
591+ terminal . write_at ( x_offset + to_x , y_offset + mid_y , char , color : color )
592+ distance += 1
558593 end
559594
560595 # Second vertical segment (drop to target)
561596 ( ( mid_y + 1 ) ...to_y ) . each do |y |
562- char = if active
563- offset = ( @animation_frame / 4 ) % Theme ::FLOW_CHARS . length
564- phase = ( y - mid_y + offset ) % Theme ::FLOW_CHARS . length
565- Theme ::FLOW_CHARS [ phase ]
566- else
567- "│"
568- end
569-
597+ char , color = Theme . animated_flow_char ( :vertical , distance , @animation_frame , active )
570598 terminal . write_at ( x_offset + to_x , y_offset + y , char , color : color )
571- end
572-
573- # Corner characters
574- if from_x < to_x
575- terminal . write_at ( x_offset + from_x , y_offset + mid_y , "└" , color : color )
576- terminal . write_at ( x_offset + to_x , y_offset + mid_y , "┐" , color : color )
577- else
578- terminal . write_at ( x_offset + from_x , y_offset + mid_y , "┘" , color : color )
579- terminal . write_at ( x_offset + to_x , y_offset + mid_y , "┌" , color : color )
599+ distance += 1
580600 end
581601 end
582602 end
0 commit comments