Skill Tracer
Skills › Scientific Agent Skills › scientific-visualization › color_palettes.py

color_palettes.py

263 lines · skills/scientific-visualization/assets/color_palettes.py at 5f2bc03

1"""Curated sRGB palettes for scientific figures.
2
3Sources were checked 2026-10-01. Palette selection alone never establishes
4accessibility: audit the rendered foreground/background contrast and add
5redundant encodings.
6
7Paul Tol values and recommended fixed order:
8https://sronpersonalpages.nl/~pault/data/colourschemes.pdf
9(SRON/EPS/TN/09-002, issue 3.2, 18 August 2021)
10"""
12from __future__ import annotations
14from typing import Final
16# Okabe-Ito colors as reproduced in Wong, Nature Methods 8, 441 (2011).
17OKABE_ITO: Final = {
18 "orange": "#E69F00",
19 "sky_blue": "#56B4E9",
20 "bluish_green": "#009E73",
21 "yellow": "#F0E442",
22 "blue": "#0072B2",
23 "vermillion": "#D55E00",
24 "reddish_purple": "#CC79A7",
25 "black": "#000000",
27OKABE_ITO_LIST: Final = [
28 "#E69F00",
29 "#56B4E9",
30 "#009E73",
31 "#F0E442",
32 "#0072B2",
33 "#D55E00",
34 "#CC79A7",
35 "#000000",
38# These five meet 3:1 against white in sRGB. Still use markers/line styles.
39OKABE_ITO_ON_WHITE: Final = [
40 "#0072B2",
41 "#D55E00",
42 "#009E73",
43 "#CC79A7",
44 "#000000",
47# Compatibility alias: Wong presents the same eight colors in another order.
48WONG: Final = [
49 "#000000",
50 "#E69F00",
51 "#56B4E9",
52 "#009E73",
53 "#F0E442",
54 "#0072B2",
55 "#D55E00",
56 "#CC79A7",
59# Paul Tol qualitative schemes in the fixed sequence recommended in issue 3.2.
60TOL_BRIGHT: Final = [
61 "#4477AA",
62 "#EE6677",
63 "#228833",
64 "#CCBB44",
65 "#66CCEE",
66 "#AA3377",
67 "#BBBBBB",
69TOL_HIGH_CONTRAST: Final = ["#004488", "#DDAA33", "#BB5566"]
70TOL_VIBRANT: Final = [
71 "#EE7733",
72 "#0077BB",
73 "#33BBEE",
74 "#EE3377",
75 "#CC3311",
76 "#009988",
77 "#BBBBBB",
79TOL_MUTED: Final = [
80 "#CC6677",
81 "#332288",
82 "#DDCC77",
83 "#117733",
84 "#88CCEE",
85 "#882255",
86 "#44AA99",
87 "#999933",
88 "#AA4499",
90TOL_MEDIUM_CONTRAST: Final = [
91 "#6699CC",
92 "#004488",
93 "#EECC66",
94 "#994455",
95 "#997700",
96 "#EE99AA",
98TOL_PALE: Final = [
99 "#BBCCEE",
100 "#CCEEFF",
101 "#CCDDAA",
102 "#EEEEBB",
103 "#FFCCCC",
104 "#DDDDDD",
106TOL_DARK: Final = [
107 "#222255",
108 "#225555",
109 "#225522",
110 "#666633",
111 "#663333",
112 "#555555",
114TOL_LIGHT: Final = [
115 "#77AADD",
116 "#EE8866",
117 "#EEDD88",
118 "#FFAABB",
119 "#99DDFF",
120 "#44BB99",
121 "#BBCC33",
122 "#AAAA00",
123 "#DDDDDD",
126PALETTES: Final = {
127 "okabe_ito": OKABE_ITO_LIST,
128 "okabe_ito_on_white": OKABE_ITO_ON_WHITE,
129 "wong": WONG,
130 "tol_bright": TOL_BRIGHT,
131 "tol_high_contrast": TOL_HIGH_CONTRAST,
132 "tol_vibrant": TOL_VIBRANT,
133 "tol_muted": TOL_MUTED,
134 "tol_medium_contrast": TOL_MEDIUM_CONTRAST,
135 "tol_pale": TOL_PALE,
136 "tol_dark": TOL_DARK,
137 "tol_light": TOL_LIGHT,
140PALETTE_METADATA: Final = {
141 "okabe_ito": {
142 "kind": "qualitative",
143 "source": "https://www.nature.com/articles/nmeth.1618",
144 "caveat": "Yellow and other light colors need outlines on white.",
145 },
146 "okabe_ito_on_white": {
147 "kind": "qualitative-subset",
148 "source": "derived from Okabe-Ito by WCAG sRGB contrast calculation",
149 "caveat": "At most five categories; use redundant encodings.",
150 },
151 "tol_bright": {
152 "kind": "qualitative",
153 "source": "https://sronpersonalpages.nl/~pault/data/colourschemes.pdf",
154 "maximum_categories": 7,
155 },
156 "tol_high_contrast": {
157 "kind": "qualitative",
158 "source": "https://sronpersonalpages.nl/~pault/data/colourschemes.pdf",
159 "maximum_categories": 3,
160 "caveat": "Designed to retain separation in grayscale.",
161 },
162 "tol_vibrant": {
163 "kind": "qualitative",
164 "source": "https://sronpersonalpages.nl/~pault/data/colourschemes.pdf",
165 "maximum_categories": 7,
166 },
167 "tol_muted": {
168 "kind": "qualitative",
169 "source": "https://sronpersonalpages.nl/~pault/data/colourschemes.pdf",
170 "maximum_categories": 9,
171 },
172 "tol_medium_contrast": {
173 "kind": "qualitative-pairs",
174 "source": "https://sronpersonalpages.nl/~pault/data/colourschemes.pdf",
175 "maximum_categories": 6,
176 },
177 "tol_pale": {
178 "kind": "text-background",
179 "source": "https://sronpersonalpages.nl/~pault/data/colourschemes.pdf",
180 "caveat": "Not intended for plot lines or categorical maps.",
181 },
182 "tol_dark": {
183 "kind": "text-foreground",
184 "source": "https://sronpersonalpages.nl/~pault/data/colourschemes.pdf",
185 "caveat": "Not intended as a multi-category plot palette.",
186 },
187 "tol_light": {
188 "kind": "labeled-cell-fill",
189 "source": "https://sronpersonalpages.nl/~pault/data/colourschemes.pdf",
190 "maximum_categories": 9,
191 },
194SEQUENTIAL_COLORMAPS: Final = [
195 "viridis",
196 "plasma",
197 "inferno",
198 "magma",
199 "cividis",
202# Candidates, not blanket accessibility certifications. Center and normalization
203# must match the scientific meaning, and rendered contrast still needs review.
204DIVERGING_COLORMAP_CANDIDATES: Final = ["RdBu_r", "PuOr", "BrBG"]
205DIVERGING_COLORMAPS_SAFE = DIVERGING_COLORMAP_CANDIDATES # Compatibility alias.
206DIVERGING_COLORMAPS_AVOID: Final = ["RdYlGn"]
208FLUOROPHORES_TRADITIONAL: Final = {
209 "DAPI": "#0000FF",
210 "GFP": "#00FF00",
211 "RFP": "#FF0000",
212 "Cy5": "#FF00FF",
213 "YFP": "#FFFF00",
215FLUOROPHORES_ACCESSIBLE: Final = {
216 "Channel1": "#0072B2",
217 "Channel2": "#E69F00",
218 "Channel3": "#D55E00",
219 "Channel4": "#CC79A7",
220 "Channel5": "#F0E442",
222DNA_BASES: Final = {
223 "A": "#00CC00",
224 "C": "#0000CC",
225 "G": "#FFB300",
226 "T": "#CC0000",
228DNA_BASES_ACCESSIBLE: Final = {
229 "A": "#009E73",
230 "C": "#0072B2",
231 "G": "#E69F00",
232 "T": "#D55E00",
236def get_palette(palette_name: str = "okabe_ito_on_white") -> list[str]:
237 """Return a copy of a named palette."""
238 try:
239 return list(PALETTES[palette_name])
240 except KeyError as exc:
241 available = ", ".join(sorted(PALETTES))
242 raise ValueError(
243 f"palette {palette_name!r} not found; available: {available}"
244 ) from exc
247def apply_palette(palette_name: str = "okabe_ito_on_white") -> list[str]:
248 """Apply a named palette to Matplotlib, importing it only when requested."""
249 try:
250 import matplotlib as mpl
251 except ImportError as exc:
252 raise RuntimeError(
253 "Matplotlib is required to apply a palette; "
254 "install the pinned snapshot documented in SKILL.md"
255 ) from exc
256 colors = get_palette(palette_name)
257 mpl.rcParams["axes.prop_cycle"] = mpl.cycler(color=colors)
258 return colors
261if __name__ == "__main__":
262 for palette_name in sorted(PALETTES):
263 print(f"{palette_name}: {', '.join(PALETTES[palette_name])}")