Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add map_values to Gradient #43

Merged
merged 6 commits into from
Jul 17, 2024
Merged
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
43 changes: 43 additions & 0 deletions arcadia_pycolor/gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from arcadia_pycolor.hexcode import HexCode
from arcadia_pycolor.palette import ColorSequence, Palette
from arcadia_pycolor.utils import (
NumericSequence,
distribute_values,
interpolate_x_values,
is_monotonic,
Expand Down Expand Up @@ -94,6 +95,48 @@ def resample_as_palette(self, steps: int = 5) -> Palette:
colors=colors,
)

def map_values(
keithchev marked this conversation as resolved.
Show resolved Hide resolved
self,
values: NumericSequence,
min_value: Union[float, None] = None,
max_value: Union[float, None] = None,
) -> list[HexCode]:
"""Map a sequence of values to their corresponding colors from a gradient

Args:
min_value:
Determines which value corresponds to the first color in the spectrum.
Any values below this minimum are assigned to the first color. If not
provided, min(values) is chosen.
max_value:
Determines which value corresponds to the last color in the spectrum.
Any values greater than this maximum are assigned to the last color. If
not provided, max(values) is chosen.

Returns:
list[HexCode]: A list of hex codes.
"""

if not len(values):
return []

if min_value is None:
min_value = min(values)

if max_value is None:
max_value = max(values)

if min_value >= max_value:
raise ValueError(
f"max_value ({max_value}) must be greater than min_value ({min_value})."
)

cmap = self.to_mpl_cmap()

normalized_values = [(value - min_value) / (max_value - min_value) for value in values]

return [HexCode(f"{value}", mcolors.to_hex(cmap(value))) for value in normalized_values]

def interpolate_lightness(self) -> "Gradient":
"""
Interpolates the gradient to new values based on lightness.
Expand Down
54 changes: 54 additions & 0 deletions arcadia_pycolor/tests/test_gradients.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest

from arcadia_pycolor import Gradient, HexCode
from arcadia_pycolor.colors import black, white

from .test_hexcode import INVALID_HEXCODES

Expand Down Expand Up @@ -116,3 +117,56 @@ def test_gradient_swatch_steps():
)
def test_gradient_swatch_repr(name, colors, swatch):
assert Gradient(name, colors).__repr__() == swatch


@pytest.fixture
def black_to_white_gradient() -> Gradient:
return Gradient("_", [black, white], [0.0, 1.0])


@pytest.mark.parametrize(
"values, expected_colors",
[
([0, 1], ["#000000", "#ffffff"]),
([0, 0.5, 1], ["#000000", "#808080", "#ffffff"]),
([1, 2, 3, 4, 5], ["#000000", "#404040", "#808080", "#c0c0c0", "#ffffff"]),
([-1, 0, 1], ["#000000", "#808080", "#ffffff"]),
([-3, -2, -1], ["#000000", "#808080", "#ffffff"]),
([], []),
],
)
def test_map_values_basic_cases(
keithchev marked this conversation as resolved.
Show resolved Hide resolved
black_to_white_gradient: Gradient,
values: list[float],
expected_colors: list[str],
):
assert black_to_white_gradient.map_values(values) == expected_colors


@pytest.mark.parametrize(
"values, min_value, max_value, expected_colors",
[
([0, 0.5, 1], 0, 1, ["#000000", "#808080", "#ffffff"]),
([0, 0.5, 1], 0.25, 0.75, ["#000000", "#808080", "#ffffff"]),
([-1, 0.5, 2], 0, 1, ["#000000", "#808080", "#ffffff"]),
([0, 10], 0, 20, ["#000000", "#808080"]),
],
)
def test_map_values_custom_ranges(
black_to_white_gradient: Gradient,
values: list[float],
min_value: float,
max_value: float,
expected_colors: list[str],
):
assert black_to_white_gradient.map_values(values, min_value, max_value) == expected_colors


def test_map_values_invalid_cases(black_to_white_gradient: Gradient):
# You can't pass min larger than max
with pytest.raises(ValueError, match="must be greater than"):
black_to_white_gradient.map_values([0, 1], min_value=1, max_value=0)

# Or min equal to max
with pytest.raises(ValueError, match="must be greater than"):
black_to_white_gradient.map_values([0, 1], min_value=1, max_value=1)
Loading