Map a value to a vertical level (0-4) for Braille rendering
(&self, value: f32, height: u16, cell_y: u16)
| 159 | |
| 160 | /// Map a value to a vertical level (0-4) for Braille rendering |
| 161 | fn value_to_level(&self, value: f32, height: u16, cell_y: u16) -> usize { |
| 162 | let (min, max) = self.y_range; |
| 163 | let range = max - min; |
| 164 | |
| 165 | if !range.is_finite() || range.abs() < 0.0001 || !value.is_finite() { |
| 166 | return 2; |
| 167 | } |
| 168 | |
| 169 | let normalized = ((value - min) / range).clamp(0.0, 1.0); |
| 170 | |
| 171 | // When inverted, flip the cell_y to process from top instead of bottom |
| 172 | // This makes high values fill cells near the top of the area |
| 173 | let effective_cell_y = if self.invert_y { |
| 174 | height.saturating_sub(1) - cell_y |
| 175 | } else { |
| 176 | cell_y |
| 177 | }; |
| 178 | |
| 179 | let pixel_value = normalized * 100.0; |
| 180 | |
| 181 | let cell_top = if height > 1 { |
| 182 | 100.0 * (height - effective_cell_y) as f32 / height as f32 |
| 183 | } else { |
| 184 | 100.0 |
| 185 | }; |
| 186 | let cell_bottom = if height > 1 { |
| 187 | 100.0 * (height - effective_cell_y - 1) as f32 / height as f32 |
| 188 | } else { |
| 189 | 0.0 |
| 190 | }; |
| 191 | |
| 192 | if pixel_value >= cell_top { |
| 193 | 4 |
| 194 | } else if pixel_value <= cell_bottom { |
| 195 | 0 |
| 196 | } else { |
| 197 | let cell_range = cell_top - cell_bottom; |
| 198 | let position = (pixel_value - cell_bottom) / cell_range; |
| 199 | ((position * 4.0).round() as usize).clamp(0, 4) |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | impl Widget for Graph<'_> { |