fix(colors): emit exact rgb255() colours instead of snapping to ~8 names - #15
Merged
Conversation
``rgb_to_gle`` mapped every hex string / RGB tuple onto one of about eight named GLE colours by dominant channel. The result was silent, unreported colour substitution: ``#8c8c8c`` and ``#999999`` (greys) both rendered MAGENTA, ``#bbbbbb`` rendered WHITE (invisible on a white page), and ``#9467bd`` (matplotlib's tab purple) rendered MAGENTA -- so two distinct series could collide onto one colour with nothing to warn the user. GLE accepts ``rgb255(r,g,b)`` (and ``rgb(r,g,b)`` with 0-1 floats) anywhere a colour name is accepted, so the snapping bought nothing. Now: - a recognised GLE colour NAME (any case, all 151 GLE knows) passes through as that name; - everything else -- hex ``#RRGGBB``/``#RGB``, RGB tuples, and matplotlib's ``C0``..``C9`` / ``tab:`` cycle references -- becomes an exact ``rgb255(r,g,b)`` expression; - an already-formed colour expression passes through (whitespace-normalised), which is what makes a parser round trip idempotent. Colours reach every emission context (lines, markers, error bars including the bars-only ``color <c>`` path, bar fills, fill-between, contours, text) as an opaque token, so all of them now carry the exact colour. The parser side gains ``_collect_color``: a colour expression lexes into several tokens (``rgb255`` ``(`` ``140`` ``,`` ...), so reading one token recovered only the function name. Applied to dataset attributes, ``bar ... fill``, ``fill dA,dB color`` and ``set color``. The GUI colour swatches now invert both stored forms via the new ``colors.gle_color_to_rgb255``, so a picked colour is stored and redisplayed exactly rather than snapped to the nearest palette entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…round trip Asserts on the generated GLE text: the reported greys/purples emit their exact rgb255 values with no MAGENTA/WHITE substitution anywhere; three near-identical greys stay three distinct colours; matplotlib's default cycle (C0-C9 and the tab: names alike) yields ten DISTINCT emitted colours where snapping collapsed it to five; named colours still emit as names. Covers every emission context -- line, line+marker, marker-only, scatter, errorbar (marker, line, and the bars-only ``color <c>`` path at every capsize), bar fill, fill-between, contour, text, and the *_from_file series -- plus the parser round trip: reopening preserves each colour and re-emitting is byte-identical, including a hand-written script whose ``rgb255( 140 , 140 , 140 )`` spans six lexer tokens. Adds an ``exact_rgb_colors`` builder to the golden battery so the existing fixed-point and to_dict-equivalence machinery exercises rgb255 too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
States the new contract up front (name in -> name out; anything else -> exact rgb255), calls out the pre-1.8.2 snapping as a behaviour change with the workaround for anyone who relied on it, and documents the newly accepted inputs: every GLE colour name, #RGB shorthand, the C0-C9 / tab: cycle, and pass-through of colour expressions. Notes that rgb255 needs no GLE newer than the 4.3+ gleplot already requires (verified against 4.3.10), and points at nearest_gle_color for deliberate snapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The GUI colour swatch never called it, and now stores picked colours exactly as rgb255 anyway. Describe it for what it is: an opt-in helper for callers who deliberately want a named approximation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
rgb_to_glesnapped every hex/RGB colour onto ~8 named GLE colours by dominant channel: greys (#8c8c8c,#999999) rendered magenta,#bbbbbbrendered white (invisible), the matplotlib tab10 cycle collapsed onto 5 colours, andC0–C9strings weren't recognised at all (all black) — distinct series silently colliding with no warning.GLE supports exact colours natively, so the fix emits them: recognised GLE colour names (now the full 151-name table, any case) pass through as names; everything else emits as
rgb255(r,g,b). Also added:#RGBshorthand,C0–C9/tab:*cycle references, clamping, idempotent pass-through of formed colour expressions, andgle_color_to_rgb255()for exact inversion (the GUI swatches now use it instead of a 21-entry approximate dict). The parser gained_collect_color()sorgb255(...)(which lexes into six tokens) round-trips exactly at all four colour-reading sites. The deliberate approximationnearest_gle_color()is untouched and documented as the opt-in path.Behaviour change: previously-snapped figures now render the requested colours, and stored colour values are
rgb255(...)rather than approximate names — documented in COLORS_AND_MARKERS.md.Test plan
tests/unit/test_color_fidelity.py(47 tests) asserting on generated GLE text across every emission context (lines, markers, errorbars incl. the bars-only path at all capsizes, bar fills, region fills, contours, text) plus parser round-trip and a golden-battery builder; three near-identical greys stay distinct; bothC0–C9andtab:*give ten distinct colours.🤖 Generated with Claude Code