Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased

### Fixed
- Fix single-color requests in `n_colors` and `sample_colorscale` raising `ZeroDivisionError` [[#5805](https://github.com/plotly/plotly.py/issues/5805)].
- 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!
- 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!
- 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!
Expand Down
12 changes: 8 additions & 4 deletions _plotly_utils/colors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,18 +677,20 @@ def n_colors(lowcolor, highcolor, n_colors, colortype="tuple"):
which form the intermediate colors between lowcolor and highcolor
from linearly interpolating through RGB space. If colortype is 'rgb'
the function will return a list of colors in the same form.
When one color is requested, the result contains only lowcolor.
"""
if colortype == "rgb":
# convert to tuple
lowcolor = unlabel_rgb(lowcolor)
highcolor = unlabel_rgb(highcolor)

intervals = max(n_colors - 1, 1)
diff_0 = float(highcolor[0] - lowcolor[0])
incr_0 = diff_0 / (n_colors - 1)
incr_0 = diff_0 / intervals
diff_1 = float(highcolor[1] - lowcolor[1])
incr_1 = diff_1 / (n_colors - 1)
incr_1 = diff_1 / intervals
diff_2 = float(highcolor[2] - lowcolor[2])
incr_2 = diff_2 / (n_colors - 1)
incr_2 = diff_2 / intervals
list_of_colors = []

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

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

if isinstance(samplepoints, int):
intervals = max(samplepoints - 1, 1)
samplepoints = [
low + idx / (samplepoints - 1) * (high - low) for idx in range(samplepoints)
low + idx / intervals * (high - low) for idx in range(samplepoints)
]
elif isinstance(samplepoints, float):
samplepoints = [samplepoints]
Expand Down
64 changes: 64 additions & 0 deletions tests/test_core/test_colors/test_colors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from unittest import TestCase

import pytest

from plotly.exceptions import PlotlyError
import plotly.colors as colors

Expand Down Expand Up @@ -224,3 +226,65 @@ def test_n_colors(self):
]

self.assertEqual(generated_colorscale, expected_colorscale)


@pytest.mark.parametrize(
"colorscale,low,high,colortype,expected",
[
("Viridis", 0.0, 1.0, "rgb", ["rgb(68, 1, 84)"]),
("Viridis_r", 0.0, 1.0, "rgb", ["rgb(253, 231, 37)"]),
(["#000000", "#ffffff"], 0.0, 1.0, "tuple", [(0.0, 0.0, 0.0)]),
(
[[0.0, "#ff0000"], [1.0, "#0000ff"]],
0.25,
0.75,
"tuple",
[(0.75, 0.0, 0.25)],
),
(["#000000", "#ffffff"], 0.5, 0.5, "tuple", [(0.5, 0.5, 0.5)]),
(["#000000", "#ffffff"], 1.0, 0.0, "rgb", ["rgb(255, 255, 255)"]),
],
)
def test_sample_colorscale_single_color(colorscale, low, high, colortype, expected):
assert (
colors.sample_colorscale(colorscale, 1, low=low, high=high, colortype=colortype)
== expected
)


@pytest.mark.parametrize(
"lowcolor,highcolor,colortype,expected",
[
((0, 0, 0), (1, 1, 1), "tuple", [(0.0, 0.0, 0.0)]),
("rgb(255,0,0)", "rgb(0,255,0)", "rgb", ["rgb(255.0, 0.0, 0.0)"]),
],
)
def test_n_colors_single_color(lowcolor, highcolor, colortype, expected):
assert colors.n_colors(lowcolor, highcolor, 1, colortype=colortype) == expected


@pytest.mark.parametrize(
"count,expected",
[
(0, []),
(2, [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]),
(3, [(0.0, 0.0, 0.0), (0.5, 0.5, 0.5), (1.0, 1.0, 1.0)]),
],
)
def test_sample_colorscale_empty_and_multiple_colors(count, expected):
assert (
colors.sample_colorscale(["#000000", "#ffffff"], count, colortype="tuple")
== expected
)


@pytest.mark.parametrize(
"count,expected",
[
(0, []),
(2, [(0.0, 0.0, 0.0), (1.0, 1.0, 1.0)]),
(3, [(0.0, 0.0, 0.0), (0.5, 0.5, 0.5), (1.0, 1.0, 1.0)]),
],
)
def test_n_colors_empty_and_multiple_colors(count, expected):
assert colors.n_colors((0, 0, 0), (1, 1, 1), count) == expected