a rust tui to view amtrak train status
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Relative speed coloring: brightness normalized across active trains

Speed brightness was absolute (0-90 mph scale), making most trains
look similarly dim since they cluster in the 30-80 mph range. Now
velocity is normalized relative to the fastest active train:

factor = velocity / max_velocity_across_all_active_trains

So the fastest train on the map is always brightest, stopped trains
are always dimmest, and everything in between spreads across the
full brightness range. Makes speed differences visually obvious.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+25 -25
+25 -25
src/ui.rs
··· 49 49 is_selected: bool, 50 50 } 51 51 52 - /// Map velocity to color brightness. Fast trains are vivid, slow/stopped are muted. 53 - fn speed_adjusted_color(base: Color, velocity: f64) -> Color { 54 - // Scale brightness: stopped=40%, 30mph=60%, 60mph=80%, 90+=100% 55 - let factor = if velocity < 1.0 { 56 - 0.4 57 - } else { 58 - (0.4 + (velocity / 90.0).min(1.0) * 0.6).min(1.0) 59 - }; 52 + /// Map a 0.0–1.0 brightness factor to a colored RGB value. 53 + /// 0.0 = dimmest (stopped/slowest), 1.0 = brightest (fastest in current set). 54 + fn speed_adjusted_color(base: Color, factor: f64) -> Color { 55 + // Map factor 0.0–1.0 to brightness 0.35–1.0 56 + let b = 0.35 + factor.clamp(0.0, 1.0) * 0.65; 60 57 match base { 61 - Color::Green => Color::Rgb( 62 - (40.0 * factor) as u8, 63 - (220.0 * factor) as u8, 64 - (40.0 * factor) as u8, 65 - ), 66 - Color::Red => Color::Rgb( 67 - (220.0 * factor) as u8, 68 - (50.0 * factor) as u8, 69 - (50.0 * factor) as u8, 70 - ), 71 - Color::Yellow => Color::Rgb( 72 - (200.0 * factor) as u8, 73 - (200.0 * factor) as u8, 74 - (50.0 * factor) as u8, 75 - ), 58 + Color::Green => Color::Rgb((40.0 * b) as u8, (220.0 * b) as u8, (40.0 * b) as u8), 59 + Color::Red => Color::Rgb((220.0 * b) as u8, (50.0 * b) as u8, (50.0 * b) as u8), 60 + Color::Yellow => Color::Rgb((200.0 * b) as u8, (200.0 * b) as u8, (50.0 * b) as u8), 76 61 _ => base, 77 62 } 78 63 } ··· 108 93 let selected_number = app.selected_train().map(|t| t.number.clone()); 109 94 110 95 // Collect train dots into owned data for the 'static paint closure 111 - let dots: Vec<TrainDot> = app 96 + // Compute velocity range across active trains for relative brightness 97 + let active_trains: Vec<&crate::model::Train> = app 112 98 .trains 113 99 .iter() 114 100 .filter(|t| t.state == TrainState::Active) 101 + .collect(); 102 + let max_velocity = active_trains 103 + .iter() 104 + .map(|t| t.velocity) 105 + .fold(1.0_f64, f64::max); // floor at 1.0 to avoid div by zero 106 + 107 + let dots: Vec<TrainDot> = active_trains 108 + .iter() 115 109 .map(|t| { 116 110 let is_selected = selected_number.as_deref() == Some(&t.number); 117 111 let base_color = delay_color(&t.delay_status()); 112 + // Normalize velocity: stopped=0.0, fastest train=1.0 113 + let speed_factor = if t.velocity < 1.0 { 114 + 0.0 115 + } else { 116 + t.velocity / max_velocity 117 + }; 118 118 TrainDot { 119 119 lon: t.lon, 120 120 lat: t.lat, 121 121 number: t.number.clone(), 122 - color: speed_adjusted_color(base_color, t.velocity), 122 + color: speed_adjusted_color(base_color, speed_factor), 123 123 is_selected, 124 124 } 125 125 })