@@ -33,6 +33,8 @@ class SharpnessChart(ScrollView):
3333 """Horizontally scrollable frame-by-frame sharpness timeline."""
3434
3535 FRAME_STRIDE = 2
36+ BAR_GLYPHS = (" " , "▁" , "▂" , "▃" , "▄" , "▅" , "▆" , "▇" , "█" )
37+ VERTICAL_SUBDIVISIONS = len (BAR_GLYPHS ) - 1
3638 can_focus = True
3739
3840 BINDINGS = [
@@ -53,7 +55,7 @@ class SharpnessChart(ScrollView):
5355
5456 DEFAULT_CSS = """
5557 SharpnessChart {
56- height: 12 ;
58+ height: 13 ;
5759 width: 100%;
5860 border: solid $primary;
5961 margin: 1 0;
@@ -212,16 +214,30 @@ def _render_timeline_column(
212214
213215 frame = self .frames [frame_position ]
214216 normalized = (frame .sharpness_score - self .min_score ) / self .score_range
215- bar_height = 1 + int (normalized * ( chart_height - 1 ) )
216- if ( chart_height - 1 - chart_y ) >= bar_height :
217+ glyph = self . _bar_glyph (normalized , chart_y , chart_height )
218+ if glyph == " " :
217219 return Segment (" " , blank_style )
218220
219221 style = (
220222 selected_style
221223 if frame .index in self .selected_indices
222224 else unselected_style
223225 )
224- return Segment ("█" , style )
226+ return Segment (glyph , style )
227+
228+ @classmethod
229+ def _bar_glyph (
230+ cls , normalized : float , chart_y : int , chart_height : int
231+ ) -> str :
232+ """Render a bar cell with eighth-row vertical precision."""
233+ subdivisions = cls .VERTICAL_SUBDIVISIONS
234+ available_units = max (chart_height * subdivisions , 1 )
235+ clamped = max (0.0 , min (float (normalized ), 1.0 ))
236+ filled_units = max (1 , int ((clamped * available_units ) + 0.5 ))
237+ row_from_bottom = chart_height - 1 - chart_y
238+ units_in_row = filled_units - (row_from_bottom * subdivisions )
239+ units_in_row = max (0 , min (units_in_row , subdivisions ))
240+ return cls .BAR_GLYPHS [units_in_row ]
225241
226242 def _visible_frame_range (self , width : int ) -> tuple [int , int ]:
227243 """Return the frame positions represented by the visible viewport."""
0 commit comments