Skip to content

Commit 49f3825

Browse files
committed
update ColorValidator for new color processing in plotly.js 4.0
1 parent ed0966c commit 49f3825

3 files changed

Lines changed: 94 additions & 63 deletions

File tree

_plotly_utils/basevalidators.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,11 +1119,13 @@ class ColorValidator(BaseValidator):
11191119
"""
11201120
"color": {
11211121
"description": "A string describing color. Supported formats:
1122-
- hex (e.g. '#d3d3d3')
1123-
- rgb (e.g. 'rgb(255, 0, 0)')
1124-
- rgba (e.g. 'rgb(255, 0, 0, 0.5)')
1125-
- hsl (e.g. 'hsl(0, 100%, 50%)')
1126-
- hsv (e.g. 'hsv(0, 100%, 100%)')
1122+
- hex or short hex (e.g. '#d3d3d3', '#d3d')
1123+
- hex or short hex with alpha (e.g. '#d3d3d380', '#d3d8')
1124+
- rgb (e.g. 'rgb(255, 0, 0)', 'rgb(255 0 0)')
1125+
- rgba (e.g. 'rgba(255, 0, 0, 0.5)', 'rgba(255 0 0 / 0.5)')
1126+
- hsl (e.g. 'hsl(0, 100%, 50%)', 'hsl(0deg 100% 50%)')
1127+
- hsla (e.g. 'hsla(0, 100%, 50%, 0.5)', 'hsla(0deg 100% 50% / 0.5)')
1128+
- hwb (e.g. 'hwb(0, 0%, 100%)', 'hwb(0 0% 100%)')
11271129
- named colors(full list:
11281130
http://www.w3.org/TR/css3-color/#svg-color)",
11291131
"requiredOpts": [],
@@ -1134,8 +1136,14 @@ class ColorValidator(BaseValidator):
11341136
},
11351137
"""
11361138

1137-
re_hex = re.compile(r"#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})")
1138-
re_rgb_etc = re.compile(r"(rgb|hsl|hsv)a?\([\d.]+%?(,[\d.]+%?){2,3}\)")
1139+
re_spaces_to_remove = re.compile(r"(?<!(\d|%|g)) | (?!\d)")
1140+
1141+
re_hex = re.compile(
1142+
r"#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})"
1143+
)
1144+
re_rgb_etc = re.compile(
1145+
r"((rgb|hsl)a?|hwb)\([\d.]+(%|deg)?([ ,] ?[\d.]+%?){2}([ /,] ?[\d.]+%?)?\)"
1146+
)
11391147
re_ddk = re.compile(r"var\(\-\-.*\)")
11401148

11411149
named_colors = [
@@ -1308,12 +1316,15 @@ def numbers_allowed(self):
13081316

13091317
def description(self):
13101318
valid_color_description = """\
1311-
The '{plotly_name}' property is a color and may be specified as:
1312-
- A hex string (e.g. '#ff0000')
1313-
- An rgb/rgba string (e.g. 'rgb(255,0,0)')
1314-
- An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
1315-
- An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
1316-
- A named CSS color: see https://plotly.com/python/css-colors/ for a list""".format(
1319+
The '{plotly_name}' property is a color and may be specified as a string in the following formats:
1320+
- hex or short hex (e.g. '#d3d3d3', '#d3d')
1321+
- hex or short hex with alpha (e.g. '#d3d3d380', '#d3d8')
1322+
- rgb (e.g. 'rgb(255, 0, 0)', 'rgb(255 0 0)')
1323+
- rgba (e.g. 'rgba(255, 0, 0, 0.5)', 'rgba(255 0 0 / 0.5)')
1324+
- hsl (e.g. 'hsl(0, 100%, 50%)', 'hsl(0deg 100% 50%)')
1325+
- hsla (e.g. 'hsla(0, 100%, 50%, 0.5)', 'hsla(0deg 100% 50% / 0.5)')
1326+
- hwb (e.g. 'hwb(0, 0%, 100%)', 'hwb(0 0% 100%)')
1327+
- a named CSS color: see https://plotly.com/python/css-colors/ for a list""".format(
13171328
plotly_name=self.plotly_name
13181329
)
13191330

@@ -1428,7 +1439,11 @@ def perform_validate_coerce(v, allow_number=None):
14281439
return None
14291440
else:
14301441
# Remove spaces so regexes don't need to bother with them.
1431-
v_normalized = v.replace(" ", "").lower()
1442+
# Don't remove spaces between two digits, though.
1443+
v_normalized = v.strip()
1444+
v_normalized = re.sub(" +", " ", v_normalized)
1445+
v_normalized = re.sub(ColorValidator.re_spaces_to_remove, "", v_normalized)
1446+
v_normalized = v_normalized.lower()
14321447

14331448
# if ColorValidator.re_hex.fullmatch(v_normalized):
14341449
if fullmatch(ColorValidator.re_hex, v_normalized):

tests/test_plotly_utils/validators/test_color_validator.py

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,58 @@ def validator_aok_colorscale():
2828
)
2929

3030

31+
VALID_COLORS = [
32+
"red",
33+
"BLUE",
34+
"#8574b2",
35+
"#8574B2",
36+
"#8574b2cc",
37+
"#8574B2CC",
38+
"#87b",
39+
"#87B",
40+
"#87bc",
41+
"#87BC",
42+
"rgb(248, 12, 0)",
43+
"rgb(248 12 0)",
44+
"rgba(248, 12, 0, 0.5)",
45+
"rgba(248 12 0 / 0.5)",
46+
"var(--accent)",
47+
"hsl(0, 100%, 50%)",
48+
"hsl(0 100% 50%)",
49+
"hsl(0deg 100% 50%)",
50+
"hsla(0, 100%, 50%, 100%)",
51+
"hsla(0 100% 50% 100%)",
52+
"hsla(0deg 100% 50% 100%)",
53+
"hwb(0, 100%, 50%)",
54+
"hwb(0 100% 50%)",
55+
"hwb(0, 100%, 50%, 0.5)",
56+
"hwb(0 100% 50% 0.5)",
57+
]
58+
59+
INVALID_COLORS_WRONG_TYPE = [
60+
set(),
61+
{},
62+
["red"],
63+
[12],
64+
]
65+
66+
INVALID_COLORS_WRONG_FORMAT = [
67+
"redd",
68+
"rgbbb(255, 0, 0)",
69+
"hsl(0, 1%0000%, 50%)",
70+
"hsv(0, 100%, 100%)",
71+
"hsva(0, 100%, 100%, 50%)",
72+
]
73+
74+
3175
# Array not ok, numbers not ok
32-
@pytest.mark.parametrize(
33-
"val",
34-
[
35-
"red",
36-
"BLUE",
37-
"rgb(255, 0, 0)",
38-
"var(--accent)",
39-
"hsl(0, 100%, 50%)",
40-
"hsla(0, 100%, 50%, 100%)",
41-
"hsv(0, 100%, 100%)",
42-
"hsva(0, 100%, 100%, 50%)",
43-
],
44-
)
76+
@pytest.mark.parametrize("val", VALID_COLORS)
4577
def test_acceptance(val, validator):
4678
assert validator.validate_coerce(val) == val
4779

4880

4981
# Rejection by type
50-
@pytest.mark.parametrize("val", [set(), 23, 0.5, {}, ["red"], [12]])
82+
@pytest.mark.parametrize("val", INVALID_COLORS_WRONG_TYPE + [23, 0.5])
5183
def test_rejection_1(val, validator):
5284
with pytest.raises(ValueError) as validation_failure:
5385
validator.validate_coerce(val)
@@ -56,7 +88,7 @@ def test_rejection_1(val, validator):
5688

5789

5890
# Rejection by value
59-
@pytest.mark.parametrize("val", ["redd", "rgbbb(255, 0, 0)", "hsl(0, 1%0000%, 50%)"])
91+
@pytest.mark.parametrize("val", INVALID_COLORS_WRONG_FORMAT)
6092
def test_rejection_2(val, validator):
6193
with pytest.raises(ValueError) as validation_failure:
6294
validator.validate_coerce(val)
@@ -68,27 +100,13 @@ def test_rejection_2(val, validator):
68100

69101

70102
# Acceptance
71-
@pytest.mark.parametrize(
72-
"val",
73-
[
74-
"red",
75-
"BLUE",
76-
23,
77-
15,
78-
"rgb(255, 0, 0)",
79-
"var(--accent)",
80-
"hsl(0, 100%, 50%)",
81-
"hsla(0, 100%, 50%, 100%)",
82-
"hsv(0, 100%, 100%)",
83-
"hsva(0, 100%, 100%, 50%)",
84-
],
85-
)
103+
@pytest.mark.parametrize("val", VALID_COLORS + [23, 15])
86104
def test_acceptance_colorscale(val, validator_colorscale):
87105
assert validator_colorscale.validate_coerce(val) == val
88106

89107

90108
# Rejection by type
91-
@pytest.mark.parametrize("val", [set(), {}, ["red"], [12]])
109+
@pytest.mark.parametrize("val", INVALID_COLORS_WRONG_TYPE)
92110
def test_rejection_colorscale_1(val, validator_colorscale):
93111
with pytest.raises(ValueError) as validation_failure:
94112
validator_colorscale.validate_coerce(val)
@@ -97,7 +115,7 @@ def test_rejection_colorscale_1(val, validator_colorscale):
97115

98116

99117
# Rejection by value
100-
@pytest.mark.parametrize("val", ["redd", "rgbbb(255, 0, 0)", "hsl(0, 1%0000%, 50%)"])
118+
@pytest.mark.parametrize("val", INVALID_COLORS_WRONG_FORMAT)
101119
def test_rejection_colorscale_2(val, validator_colorscale):
102120
with pytest.raises(ValueError) as validation_failure:
103121
validator_colorscale.validate_coerce(val)
@@ -115,11 +133,11 @@ def test_rejection_colorscale_2(val, validator_colorscale):
115133
"blue",
116134
["red", "rgb(255, 0, 0)"],
117135
np.array(["red", "rgb(255, 0, 0)"]),
118-
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsv(0, 100%, 100%)"],
136+
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsl(0, 100%, 100%)"],
119137
np.array(
120-
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsv(0, 100%, 100%)"]
138+
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsl(0, 100%, 100%)"]
121139
),
122-
["hsva(0, 100%, 100%, 50%)"],
140+
["hsla(0, 100%, 100%, 50%)"],
123141
],
124142
)
125143
def test_acceptance_aok(val, validator_aok):
@@ -165,8 +183,8 @@ def test_acceptance_aok_2D(val, validator_aok):
165183
[23],
166184
[0, 1, 2],
167185
["redd", "rgb(255, 0, 0)"],
168-
["hsl(0, 100%, 50_00%)", "hsla(0, 100%, 50%, 100%)", "hsv(0, 100%, 100%)"],
169-
["hsva(0, 1%00%, 100%, 50%)"],
186+
["hsl(0, 100%, 50_00%)", "hsla(0, 100%, 50%, 100%)", "hsl(0, 100%, 100%)"],
187+
["hsla(0, 1%00%, 100%, 50%)"],
170188
],
171189
)
172190
def test_rejection_aok(val, validator_aok):
@@ -182,11 +200,11 @@ def test_rejection_aok(val, validator_aok):
182200
[["redd", "rgb(255, 0, 0)"]],
183201
[
184202
["hsl(0, 100%, 50_00%)", "hsla(0, 100%, 50%, 100%)"],
185-
["hsv(0, 100%, 100%)", "purple"],
203+
["hsl(0, 100%, 100%)", "purple"],
186204
],
187205
[
188206
np.array(["hsl(0, 100%, 50_00%)", "hsla(0, 100%, 50%, 100%)"]),
189-
np.array(["hsv(0, 100%, 100%)", "purple"]),
207+
np.array(["hsl(0, 100%, 100%)", "purple"]),
190208
],
191209
[["blue"], [2]],
192210
],
@@ -209,8 +227,8 @@ def test_rejection_aok_2D(val, validator_aok):
209227
23,
210228
[0, 1, 2],
211229
["red", 0.5, "rgb(255, 0, 0)"],
212-
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsv(0, 100%, 100%)"],
213-
["hsva(0, 100%, 100%, 50%)"],
230+
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsl(0, 100%, 100%)"],
231+
["hsla(0, 100%, 100%, 50%)"],
214232
],
215233
)
216234
def test_acceptance_aok_colorscale(val, validator_aok_colorscale):
@@ -226,8 +244,8 @@ def test_acceptance_aok_colorscale(val, validator_aok_colorscale):
226244
"val",
227245
[
228246
["redd", 0.5, "rgb(255, 0, 0)"],
229-
["hsl(0, 100%, 50_00%)", "hsla(0, 100%, 50%, 100%)", "hsv(0, 100%, 100%)"],
230-
["hsva(0, 1%00%, 100%, 50%)"],
247+
["hsl(0, 100%, 50_00%)", "hsla(0, 100%, 50%, 100%)", "hsl(0, 100%, 100%)"],
248+
["hsla(0, 1%00%, 100%, 50%)"],
231249
],
232250
)
233251
def test_rejection_aok_colorscale(val, validator_aok_colorscale):

tests/test_plotly_utils/validators/test_colorlist_validator.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,11 @@ def test_rejection_element(validator, val):
3535
"val",
3636
[
3737
["blue"],
38-
["red", "rgb(255, 0, 0)"],
38+
["red", "rgb(255, 0, 0)", "rgb(255 0 0)"],
3939
np.array(["red", "rgb(255, 0, 0)"]),
40-
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsv(0, 100%, 100%)"],
41-
np.array(
42-
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsv(0, 100%, 100%)"]
43-
),
44-
["hsva(0, 100%, 100%, 50%)"],
40+
["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsl(0 100% 100%)"],
41+
np.array(["hsl(0, 100%, 50%)", "hsla(0, 100%, 50%, 100%)", "hsl(0 100% 100%)"]),
42+
["hsla(0, 100%, 100%, 50%)"],
4543
],
4644
)
4745
def test_acceptance_aok(val, validator):

0 commit comments

Comments
 (0)