Functional Weave
Code in Rust

charts.contrast@1.0.0

README.md

1,789 bytes · view raw

# charts.contrast

`contrastRatio` is the WCAG 2.x contrast ratio between two colours, from 1
(identical) to 21 (black on white). `readableTextColor` picks, from the
candidates you offer (usually black and white), the one that contrasts most
with a background: the label colour for a bar, a pie slice or a heatmap cell.

## The formula

Relative luminance L = 0.2126 R + 0.7152 G + 0.0722 B, where R, G and B are
the channels converted to linear light by the sRGB transfer function
(`charts.color`'s `srgbToLinear`). The ratio is (L1 + 0.05) / (L2 + 0.05) with
L1 the lighter of the two, so the order of the arguments does not matter.

WCAG 2.0's text gives the linearisation threshold as 0.03928; the sRGB
standard, and WCAG 2.2's note, say 0.04045. No 8-bit channel lies between them
(10/255 = 0.0392 and 11/255 = 0.0431), so for hex colours the two agree
exactly.

Because luminance is linear light, intuition from the channel numbers misleads:
pure red on white is only 4.0:1, and mid grey `#808080` contrasts more with
black (5.3:1) than with white (3.9:1), so a "lightness below 50% means white
text" rule picks the wrong colour.

## Rounding and thresholds

`contrastRatio` is rounded to 6 decimal places. WCAG says the ratio must not be
rounded up to meet a threshold: `#777777` on white is 4.478089, which fails AA
(4.5 for body text, 3 for large text; AAA needs 7 and 4.5). Compare the result
directly; do not round it to one place first. `readableTextColor` compares
unrounded ratios, and a tie goes to the earlier candidate, which is returned
exactly as written.

Sources: W3C, Web Content Accessibility Guidelines (WCAG) 2.2, definitions of
"contrast ratio" and "relative luminance", and Success Criteria 1.4.3 and
1.4.6 (https://www.w3.org/TR/WCAG22/); IEC 61966-2-1 (sRGB).