Skip to content

Commit 0a3f682

Browse files
authored
Reject all matplotlib shorthand color notations in _is_color_like (#584)
1 parent db82da8 commit 0a3f682

File tree

2 files changed

+59
-6
lines changed

2 files changed

+59
-6
lines changed

src/spatialdata_plot/pl/utils.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,24 +209,50 @@ def _get_coordinate_system_mapping(sdata: SpatialData) -> dict[str, list[str]]:
209209
return mapping
210210

211211

212+
_MPL_SINGLE_LETTER_COLORS = frozenset("bgrcmykw")
213+
214+
212215
def _is_color_like(color: Any) -> bool:
213216
"""Check if a value is a valid color.
214217
215-
For discussion, see: https://github.com/scverse/spatialdata-plot/issues/327.
216-
matplotlib accepts strings in [0, 1] as grey-scale values - therefore,
217-
"0" and "1" are considered valid colors. However, we won't do that
218-
so we're filtering these out.
218+
We reject several matplotlib shorthand notations that are likely to collide
219+
with column or gene names. For discussion, see:
220+
221+
- https://github.com/scverse/spatialdata-plot/issues/211
222+
- https://github.com/scverse/spatialdata-plot/issues/327
223+
224+
Rejected shorthands:
225+
226+
- Greyscale strings: ``"0"``, ``"0.5"``, ``"1"`` (floats in [0, 1])
227+
- Short hex: ``"#RGB"`` / ``"#RGBA"`` (only ``#RRGGBB`` / ``#RRGGBBAA`` accepted)
228+
- Single-letter colors: ``"b"``, ``"g"``, ``"r"``, ``"c"``, ``"m"``, ``"y"``, ``"k"``, ``"w"``
229+
- CN cycle notation: ``"C0"``, ``"C1"``, …
230+
- ``tab:`` prefixed colors: ``"tab:blue"``, ``"tab:orange"``, …
231+
- ``xkcd:`` prefixed colors: ``"xkcd:sky blue"``, …
219232
"""
220233
if isinstance(color, str):
234+
# greyscale strings
221235
try:
222236
num_value = float(color)
223237
if 0 <= num_value <= 1:
224238
return False
225239
except ValueError:
226-
# we're not dealing with what matplotlib considers greyscale
227240
pass
241+
242+
# short hex
228243
if color.startswith("#") and len(color) not in [7, 9]:
229-
# we only accept hex colors in the form #RRGGBB or #RRGGBBAA, not short forms as matplotlib does
244+
return False
245+
246+
# single-letter color shortcuts
247+
if color in _MPL_SINGLE_LETTER_COLORS:
248+
return False
249+
250+
# CN cycle notation (C0, C1, …)
251+
if len(color) >= 2 and color[0] == "C" and color[1:].isdigit():
252+
return False
253+
254+
# tab: and xkcd: prefixed colors
255+
if color.startswith(("tab:", "xkcd:")):
230256
return False
231257

232258
return bool(colors.is_color_like(color))

tests/pl/test_utils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,38 @@ def test_plot_transparent_cmap_shapes_clip_false(self, sdata_blobs: SpatialData)
124124
@pytest.mark.parametrize(
125125
"color_result",
126126
[
127+
# greyscale strings rejected
127128
("0", False),
128129
("0.5", False),
129130
("1", False),
131+
# valid full-form colors accepted
130132
("#00ff00", True),
133+
("#00ff00aa", True),
131134
((0.0, 1.0, 0.0, 1.0), True),
135+
("red", True),
136+
("blue", True),
137+
# short hex rejected
138+
("#f00", False),
139+
("#f00a", False),
140+
# single-letter shortcuts rejected (#211)
141+
("b", False),
142+
("g", False),
143+
("r", False),
144+
("c", False),
145+
("m", False),
146+
("y", False),
147+
("k", False),
148+
("w", False),
149+
# CN cycle notation rejected (#211)
150+
("C0", False),
151+
("C1", False),
152+
("C10", False),
153+
# tab: prefixed rejected (#211)
154+
("tab:blue", False),
155+
("tab:orange", False),
156+
# xkcd: prefixed rejected (#211)
157+
("xkcd:sky blue", False),
158+
("xkcd:red", False),
132159
],
133160
)
134161
def test_is_color_like(color_result: tuple[ColorLike, bool]):

0 commit comments

Comments
 (0)