Day charts: notch only between days, first bar flush with the axis
The braille glyphs already carry an intra-cell left margin, so the old leading GAP on the oldest day read as double padding against the axis border while the right edge showed single. Boundaries now pay the n-1 notch dots and the bars split the rest evenly, flush at both borders. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7f1d8e7d10
commit
8d2096767f
2 files changed
+70
-52
No files matched your search
@@ -315,26 +315,37 @@ def _node_sections(
|
||||
|
||||
|
||||
def _day_spans(days: int, chars: int) -> list[tuple[int, int]]:
|
||||
"""Each day's ``(start, width)`` share of the chart's ``2 × chars`` dot columns.
|
||||
"""Each day's lit-bar ``(start, width)`` within the chart's ``2 × chars`` dots.
|
||||
|
||||
The one even split behind both the bars (:func:`_day_columns`) and their axis
|
||||
ticks (:func:`_day_centers`): day ``i`` runs from dot ``⌊i·D/n⌋`` to
|
||||
``⌊(i+1)·D/n⌋`` (``D = 2 × chars``), so no two days differ by more than one
|
||||
dot column and the wider days interleave evenly among the narrower ones
|
||||
instead of pooling at either end. The widths always sum to exactly ``D``,
|
||||
keeping the bars flush with the axis border and caption sized for them.
|
||||
ticks (:func:`_day_centers`). When every bar can keep at least two dots, each
|
||||
of the ``n − 1`` day *boundaries* pays one unclaimed notch dot and the bars
|
||||
split the rest with a floor-edge walk, so no two differ by more than one dot
|
||||
column and the wider days interleave evenly among the narrower ones instead
|
||||
of pooling at either end. The notches sit strictly between days: the first
|
||||
bar starts flush at dot 0 (the axis border is separation enough — a leading
|
||||
blank dot would double the left margin the braille glyphs already carry)
|
||||
and the last ends flush at the right border, so both edges read alike. A
|
||||
denser history drops the notches and the bars simply tile all ``2 × chars``
|
||||
dots.
|
||||
|
||||
Args:
|
||||
days: How many day bars the chart draws.
|
||||
chars: The chart's width in character cells.
|
||||
|
||||
Returns:
|
||||
One ``(start dot, width in dots)`` per day, oldest first.
|
||||
One ``(start dot, width in dots)`` per lit bar, oldest first.
|
||||
"""
|
||||
dots = chars * 2
|
||||
n = max(1, days)
|
||||
edges = [i * dots // n for i in range(n + 1)]
|
||||
return [(edges[i], edges[i + 1] - edges[i]) for i in range(n)]
|
||||
lit = dots - (n - 1) # what the bars keep once each boundary pays its notch dot
|
||||
notch = n > 1 and lit // n >= 2
|
||||
if not notch:
|
||||
lit = dots
|
||||
edges = [i * lit // n for i in range(n + 1)]
|
||||
return [
|
||||
(edges[i] + (i if notch else 0), edges[i + 1] - edges[i]) for i in range(n)
|
||||
]
|
||||
|
||||
|
||||
def _day_centers(days: int, chars: int) -> list[int]:
|
||||
@@ -460,13 +471,12 @@ def _day_columns(values: list[int], chars: int) -> list:
|
||||
leaving the bars short of the axis border and caption sized for the full
|
||||
width — misreading as the whole chart sitting shifted left).
|
||||
|
||||
When every day is at least three dots wide, each opens with a
|
||||
:data:`~meshterm.ui.braillechart.GAP` dot column — its first dot, blank clean
|
||||
down to the axis — so same-height neighbours read as separate bars instead of
|
||||
fusing into one solid block. The oldest day keeps its notch too: it doubles as
|
||||
the bar's one-dot clearance off the axis border, paid for inside the day's own
|
||||
span. A deeper history (days of one or two dots) drops the notch chart-wide —
|
||||
such a bar has no dot to spare — and the days simply abut.
|
||||
The dots the split leaves unclaimed — one per day *boundary*, when the bars
|
||||
are wide enough to afford them (see :func:`_day_spans`) — render as
|
||||
:data:`~meshterm.ui.braillechart.GAP` columns, blank clean down to the axis,
|
||||
so same-height neighbours read as separate bars instead of fusing into one
|
||||
solid block. A deeper history (bars of one dot) has no boundary to spare and
|
||||
the days simply abut.
|
||||
|
||||
Args:
|
||||
values: Per-day counts, oldest first.
|
||||
@@ -476,15 +486,9 @@ def _day_columns(values: list[int], chars: int) -> list:
|
||||
Exactly ``2 × chars`` dot-column readings, oldest first (a
|
||||
:data:`~meshterm.ui.braillechart.GAP` marks a day-boundary notch).
|
||||
"""
|
||||
spans = _day_spans(len(values), chars)
|
||||
notch = min(width for _start, width in spans) >= 3
|
||||
out: list = []
|
||||
for value, (_start, width) in zip(values, spans):
|
||||
if notch:
|
||||
out.append(GAP)
|
||||
out.extend([value] * (width - 1))
|
||||
else:
|
||||
out.extend([value] * width)
|
||||
out: list = [GAP] * (chars * 2)
|
||||
for value, (start, width) in zip(values, _day_spans(len(values), chars)):
|
||||
out[start : start + width] = [value] * width
|
||||
return out
|
||||
|
||||
|
||||
|
||||
+41
-27
@@ -249,19 +249,22 @@ def test_day_ticks_prioritise_the_newest_then_the_oldest_day() -> None:
|
||||
def test_day_columns_widths_differ_by_at_most_one_dot_and_interleave() -> None:
|
||||
"""An uneven split spreads the wider days through the chart, not to one side.
|
||||
|
||||
13 days over 30 chars (60 dots) can't divide evenly: days get 4 or 5 dots.
|
||||
The widths must never differ by more than one dot column, and the wide days
|
||||
must mix with the narrow ones instead of pooling at either end (the old
|
||||
char-unit split put every wide day on the left, every narrow one on the right).
|
||||
13 days over 30 chars (60 dots) pay 12 boundary notches, leaving 48 dots of
|
||||
bar that can't divide evenly: bars get 3 or 4 dots. The widths must never
|
||||
differ by more than one dot column, and the wide days must mix with the
|
||||
narrow ones instead of pooling at either end (the old char-unit split put
|
||||
every wide day on the left, every narrow one on the right).
|
||||
"""
|
||||
cols = _day_columns([1] * 13, 30)
|
||||
assert len(cols) == 60
|
||||
starts = [i for i, c in enumerate(cols) if c is GAP] # one notch opens each day
|
||||
widths = [b - a for a, b in zip(starts, [*starts[1:], len(cols)])]
|
||||
assert len(widths) == 13 and set(widths) == {4, 5}
|
||||
first_wide = widths.index(5)
|
||||
last_wide = len(widths) - 1 - widths[::-1].index(5)
|
||||
assert any(w == 4 for w in widths[first_wide:last_wide]) # narrow days sit between wide ones
|
||||
notches = [i for i, c in enumerate(cols) if c is GAP] # one per day boundary
|
||||
assert len(notches) == 12
|
||||
edges = [-1, *notches, len(cols)] # each bar runs between two notches
|
||||
widths = [b - a - 1 for a, b in zip(edges, edges[1:])]
|
||||
assert len(widths) == 13 and set(widths) == {3, 4}
|
||||
first_wide = widths.index(4)
|
||||
last_wide = len(widths) - 1 - widths[::-1].index(4)
|
||||
assert any(w == 3 for w in widths[first_wide:last_wide]) # narrow days sit between wide ones
|
||||
|
||||
|
||||
def test_day_ticks_pack_the_axis_keeping_both_ends() -> None:
|
||||
@@ -286,13 +289,20 @@ def test_day_ticks_pack_the_axis_keeping_both_ends() -> None:
|
||||
assert all(b_start >= a_end + 2 for (_, a_end), (b_start, _) in zip(spans, spans[1:]))
|
||||
|
||||
|
||||
def test_day_columns_notches_the_first_dot_of_a_multi_character_day() -> None:
|
||||
"""A day wide enough to span several characters opens with a GAP dot column."""
|
||||
cols = _day_columns([2, 8, 4], 12) # 3 days, 12 chars -> 4 chars (8 dots) each
|
||||
assert cols[0] is GAP and cols[8] is GAP and cols[16] is GAP
|
||||
assert cols[1:8] == [2] * 7
|
||||
assert cols[9:16] == [8] * 7
|
||||
assert cols[17:24] == [4] * 7
|
||||
def test_day_columns_notch_the_boundaries_and_keep_the_first_bar_flush() -> None:
|
||||
"""Notches sit strictly between days; the first bar starts at the axis itself.
|
||||
|
||||
The braille glyphs already carry a left margin, so a leading GAP dot would
|
||||
read as double padding against the axis border while the right edge (bar
|
||||
flush against the closing border) shows single — the edges must match.
|
||||
"""
|
||||
cols = _day_columns([2, 8, 4], 12) # 24 dots − 2 notches = 22 of bar: 7+7+8
|
||||
assert cols[0] == 2 # flush against the axis — no leading notch
|
||||
assert cols[:7] == [2] * 7
|
||||
assert cols[7] is GAP
|
||||
assert cols[8:15] == [8] * 7
|
||||
assert cols[15] is GAP
|
||||
assert cols[16:] == [4] * 8 # …and flush against the right border
|
||||
|
||||
|
||||
def test_day_columns_skips_the_notch_for_a_single_character_day() -> None:
|
||||
@@ -312,20 +322,24 @@ def test_day_columns_skips_notches_when_days_outnumber_characters() -> None:
|
||||
def test_day_chart_notch_reaches_the_axis_but_a_zero_day_keeps_its_baseline() -> None:
|
||||
"""The notch column is blank down to the axis; a zero-value day stays a grey line.
|
||||
|
||||
Tall day, empty day, tall day at 4 chars each: the leading cell of each tall day
|
||||
shows its right dot column only (the notch's left column blank all the way down),
|
||||
and the empty day draws the faint zero baseline across its own columns.
|
||||
Tall day, empty day, tall day: the boundary notches (dots 7 and 15) blank
|
||||
their whole dot column so the gap runs clean to the axis border, the first
|
||||
bar's very first dot is lit (flush against the axis), and the empty day
|
||||
draws the faint zero baseline across its own columns.
|
||||
"""
|
||||
from meshterm.ui.braillechart import timeline_rows
|
||||
|
||||
cols = _day_columns([8, 0, 8], 12) # -> 4 chars (8 dot cols) per day
|
||||
cols = _day_columns([8, 0, 8], 12) # 22 dots of bar (7+7+8), notches at 7 and 15
|
||||
bottom = timeline_rows(cols, rows=2)[-1]
|
||||
# Leading cell of the first tall day: right column filled, left (notch) blank —
|
||||
# no bottom-left dot, so the gap runs clean to the axis border below.
|
||||
assert not (ord(bottom.plain[0]) & 0x40) # the notch's bottom-left dot is unlit
|
||||
# The empty middle day (cells 4..7) draws the faint zero baseline, in grey.
|
||||
zero_cells = bottom.plain[4:8]
|
||||
assert all(ord(c) & 0x80 for c in zero_cells) # each keeps its right baseline dot
|
||||
assert ord(bottom.plain[0]) & 0x40 # first bar flush: its bottom-left dot is lit
|
||||
# Cell 3 holds the first boundary: bar dot on its left, the notch blank on its
|
||||
# right — no bottom-right dot, so the gap runs clean to the axis border below.
|
||||
assert not (ord(bottom.plain[3]) & 0x80)
|
||||
# The empty middle day (dots 8..14) draws the faint zero baseline, in grey.
|
||||
zero_cells = bottom.plain[4:7]
|
||||
assert all(ord(c) & (0x40 | 0x80) for c in zero_cells) # baseline dots present
|
||||
assert ord(bottom.plain[7]) & 0x40 # the day's last baseline dot…
|
||||
assert not (ord(bottom.plain[7]) & 0x80) # …then the second notch, blank
|
||||
zero_spans = [s for s in bottom.spans if 4 <= s.start < 8]
|
||||
assert zero_spans and all(s.style == "faint" for s in zero_spans)
|
||||
|
||||
|
||||
Reference in new issue
Block a user