Skip to content

Commit e298f4c

Browse files
committed
Fix single-color palette generation
1 parent 3f8a650 commit e298f4c

3 files changed

Lines changed: 73 additions & 4 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
55
## Unreleased
66

77
### Fixed
8+
- Fix single-color requests in `n_colors` and `sample_colorscale` raising `ZeroDivisionError` [[#5805](https://github.com/plotly/plotly.py/issues/5805)].
89
- Fix `mpl_to_plotly` tick marker mirroring, axis positioning, and trace visibility for twinned and overlaid subplots (such as `twinx` and `twiny`) [[#5310](https://github.com/plotly/plotly.py/pull/5310)], with thanks to @robertoffmoura for the contribution!
910
- Fix `mpl_to_plotly` losing matplotlib axis line colors by exporting the color of each axis's displayed spine to the plotly x and y axis `linecolor` [[#5311](https://github.com/plotly/plotly.py/pull/5311)], with thanks to @robertoffmoura for the contribution!
1011
- Fix concurrent first access to lazily initialized graph object properties, which could raise `ValueError("Invalid value")` [[#5691](https://github.com/plotly/plotly.py/pull/5691)], with thanks to @hb1915 for the contribution!

‎_plotly_utils/colors/__init__.py‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -677,18 +677,20 @@ def n_colors(lowcolor, highcolor, n_colors, colortype="tuple"):
677677
which form the intermediate colors between lowcolor and highcolor
678678
from linearly interpolating through RGB space. If colortype is 'rgb'
679679
the function will return a list of colors in the same form.
680+
When one color is requested, the result contains only lowcolor.
680681
"""
681682
if colortype == "rgb":
682683
# convert to tuple
683684
lowcolor = unlabel_rgb(lowcolor)
684685
highcolor = unlabel_rgb(highcolor)
685686

687+
intervals = max(n_colors - 1, 1)
686688
diff_0 = float(highcolor[0] - lowcolor[0])
687-
incr_0 = diff_0 / (n_colors - 1)
689+
incr_0 = diff_0 / intervals
688690
diff_1 = float(highcolor[1] - lowcolor[1])
689-
incr_1 = diff_1 / (n_colors - 1)
691+
incr_1 = diff_1 / intervals
690692
diff_2 = float(highcolor[2] - lowcolor[2])
691-
incr_2 = diff_2 / (n_colors - 1)
693+
incr_2 = diff_2 / intervals
692694
list_of_colors = []
693695

694696
def _constrain_color(c):
@@ -874,6 +876,7 @@ def sample_colorscale(colorscale, samplepoints, low=0.0, high=1.0, colortype="rg
874876
be spaced equally between the low value (default 0.0) and the high value
875877
(default 1.0). The output is a list of colors, formatted according to the
876878
specified colortype.
879+
When one point is requested, the color at low is returned.
877880
"""
878881
from bisect import bisect_left
879882

@@ -891,8 +894,9 @@ def sample_colorscale(colorscale, samplepoints, low=0.0, high=1.0, colortype="rg
891894
colors = validate_colors(colors, colortype="tuple")
892895

893896
if isinstance(samplepoints, int):
897+
intervals = max(samplepoints - 1, 1)
894898
samplepoints = [
895-
low + idx / (samplepoints - 1) * (high - low) for idx in range(samplepoints)
899+
low + idx / intervals * (high - low) for idx in range(samplepoints)
896900
]
897901
elif isinstance(samplepoints, float):
898902
samplepoints = [samplepoints]

‎tests/test_core/test_colors/test_colors.py‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from unittest import TestCase
22

3+
import pytest
4+
35
from plotly.exceptions import PlotlyError
46
import plotly.colors as colors
57

@@ -224,3 +226,65 @@ def test_n_colors(self):
224226
]
225227

226228
self.assertEqual(generated_colorscale, expected_colorscale)
229+
230+
231+
@pytest.mark.parametrize(
232+
"colorscale,low,high,colortype,expected",
233+
[
234+
("Viridis", 0.0, 1.0, "rgb", ["rgb(68, 1, 84)"]),
235+
("Viridis_r", 0.0, 1.0, "rgb", ["rgb(253, 231, 37)"]),
236+
(["#000000", "#ffffff"], 0.0, 1.0, "tuple", [(0.0, 0.0, 0.0)]),
237+
(
238+
[[0.0, "#ff0000"], [1.0, "#0000ff"]],
239+
0.25,
240+
0.75,
241+
"tuple",
242+
[(0.75, 0.0, 0.25)],
243+
),
244+
(["#000000", "#ffffff"], 0.5, 0.5, "tuple", [(0.5, 0.5, 0.5)]),
245+
(["#000000", "#ffffff"], 1.0, 0.0, "rgb", ["rgb(255, 255, 255)"]),
246+
],
247+
)
248+
def test_sample_colorscale_single_color(colorscale, low, high, colortype, expected):
249+
assert (
250+
colors.sample_colorscale(colorscale, 1, low=low, high=high, colortype=colortype)
251+
== expected
252+
)
253+
254+
255+
@pytest.mark.parametrize(
256+
"lowcolor,highcolor,colortype,expected",
257+
[
258+
((0, 0, 0), (1, 1, 1), "tuple", [(0.0, 0.0, 0.0)]),
259+
("rgb(255,0,0)", "rgb(0,255,0)", "rgb", ["rgb(255.0, 0.0, 0.0)"]),
260+
],
261+
)
262+
def test_n_colors_single_color(lowcolor, highcolor, colortype, expected):
263+
assert colors.n_colors(lowcolor, highcolor, 1, colortype=colortype) == expected
264+
265+
266+
@pytest.mark.parametrize(
267+
"count,expected",
268+
[
269+
(0, []),
270+
(2, [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]),
271+
(3, [(0.0, 0.0, 0.0), (0.5, 0.5, 0.5), (1.0, 1.0, 1.0)]),
272+
],
273+
)
274+
def test_sample_colorscale_empty_and_multiple_colors(count, expected):
275+
assert (
276+
colors.sample_colorscale(["#000000", "#ffffff"], count, colortype="tuple")
277+
== expected
278+
)
279+
280+
281+
@pytest.mark.parametrize(
282+
"count,expected",
283+
[
284+
(0, []),
285+
(2, [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]),
286+
(3, [(0.0, 0.0, 0.0), (0.5, 0.5, 0.5), (1.0, 1.0, 1.0)]),
287+
],
288+
)
289+
def test_n_colors_empty_and_multiple_colors(count, expected):
290+
assert colors.n_colors((0, 0, 0), (1, 1, 1), count) == expected

0 commit comments

Comments
 (0)