Skip to content

fix(biochem): use a high-salience mapping palette so rxn00018 groups are visually distinct - #237

Open
VibhavSetlur wants to merge 3 commits into
ModelSEED:stagingfrom
VibhavSetlur:fix/mapping-colour-high-salience-palette
Open

fix(biochem): use a high-salience mapping palette so rxn00018 groups are visually distinct#237
VibhavSetlur wants to merge 3 commits into
ModelSEED:stagingfrom
VibhavSetlur:fix/mapping-colour-high-salience-palette

Conversation

@VibhavSetlur

Copy link
Copy Markdown
Collaborator

Problem

Sam reported that reaction rxn00018 still shows visually similar atom-mapping colours after #236: three carbon groups that differ technically but look alike, oxygen green/blue too close, and light purple / light orange / light brown insufficiently distinguishable.

Two root causes were found:

  1. fix: distinguish atom-mapping colours within a reaction; open compound preview below the cursor #236's max-min subset selection is a no-op for this reaction. rxn00018 has 7 colourable mapping groups against an 8-colour palette, so selectMappingColors returns almost the whole palette and has nothing left to optimise. The prior change improved the numbers for small group counts only.
  2. The old palette was four hue families times two near-twins (two blues, two greens, two purples, two reds). Any reaction that saturates the palette puts every near-twin pair on screen simultaneously.

A third defect nobody reported was also found: mapped colour #D55E00 sat only ΔE76 16.5 from RDKit's unmapped phosphorus colour #FF7F00, which is literally on screen in this bisphosphate reaction. That is almost certainly part of the "light orange / light brown" report.

Change

Replaces MAPPING_PALETTE with a curated, optimiser-verified 8-colour set spanning six hue families, where same-family pairs are separated by ~31 L* so they read as "dark green vs bright green", "deep blue vs sky blue":

#355214 darkGreen   #3FAA18 brightGreen  #00A398 teal     #2994FF skyBlue
#0B26D5 deepBlue    #6F2183 violet       #FF14EF magenta  #E00069 crimson

The palette was chosen by constrained search maximising the minimum normal-vision ΔE76 subject to a hard colour-blind floor, WCAG non-text contrast, and a clearance margin from every RDKit default atom colour. The warm band is deliberately excluded: on a white canvas a >=3:1 contrast requirement forces warm hues dark, and dark warm is brown — which is exactly what Sam was seeing.

No algorithm changed. selectMappingColors, buildAtomMappingColorPlan, component grouping, group ordering, and the index % length cycling are untouched. Mapping computation and atom-label behaviour are unchanged.

Measured result on the colours rxn00018 actually renders

metric before (staging today) after
min ΔE76, normal vision 40.2 48.6
min ΔE76, protan/deutan 19.7 26.2
clearance from on-screen RDKit atom colours 16.5 60.0

Per complaint: light-purple pair #CC79A7#960A82 (40.2) removed — the purple family is now a single violet; the light-orange/brown pair #D55E00#FA3C5A (44.8) removed with the warm family; the two blues #0072B2~#0A5AE6 are replaced by blues split by 31 L*.

Colour-blind safety improves rather than regresses, and no safety threshold in the test suite was lowered.

Verification

Real Chromium render of /biochem/reactions/rxn00018 against this branch, compared to the same region on staging: all 7 rendered mapping colours confirmed painting real ink (min 138 px at 3x DPI), no old-palette hex remaining.

Full CI mirror, all exit 0: npm run lint · npx tsc --noEmit · npm run test:run (364 passed, 4 skipped) · npm run build · npm audit --omit=dev --audit-level=high · git diff --check.

New regression test addresses the rxn00018 high-salience mapping colour report pins the exact selected hexes, asserts every pair clears ΔE76 45 under normal vision, asserts every entry clears ΔE76 25 from each RDKit default (the previously untested phosphorus clash), and asserts over-capacity reuse rather than falsely claiming uniqueness.

One pre-existing assertion was adjusted, not weakened: the blanket "optimal subset strictly beats the naive prefix" check now names the counts where it is strict {2,3,4,5,7} and allows equality at 6 and 8, where the leading palette run is itself an optimal subset. A slice(0, count) stub still fails the strict counts, so the check retains its power.

Release

Patch bump 3.5.1 -> 3.5.2 (fix-only) across VERSION.md, package.json, and CHANGELOG.md.

…m glyphs

Visual experiment requested by Sam Seaver for the atom-mapping display.

Adds two pure SVG post-processing helpers in lib/utils/moleculeHighlights.ts:

- applyMoleculeBackground: recolours RDKit's unclassed background rect from
  #FFFFFF to off-white #F5F5F5, inserting one when absent.
- applyAtomGlyphOutline: gives every atom letter path a thin #333333 stroke
  (width 1.0) with paint-order='stroke', so the outline paints beneath the
  fill and each atom keeps its mapping colour while staying distinct from
  the lighter canvas.

Both are applied once in MoleculeRenderer, after the existing colour passes
and immediately before the SVG is committed to state. Only <path> elements
whose class is anchored atom-N are outlined, so highlight ellipses, bond
paths and unclassed geometry are untouched. Fallback, PNG and error
branches are unchanged.

Stroke width chosen from rendered-pixel measurement: RDKit's viewBox equals
the pixel size, so 1.0 is a constant 0.5px visible rim at both the 134px
inline molecule and the 360px enlarged preview, darkening the outline
without fattening the glyphs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant