No project description provided
Project description
Polychromos
πολύχρωμος
Greek; adjective
From πολύ- ("many", "much") and -χρωμος (from χρῶμα, "color").
Multicolored.
Color and palette utility library.
This library provides a set of utilites to handle colors and palettes from an artistic approach, using color theory and the more human-friendly hue-saturation-lightness components.
Modules
The library provides three modules:
color, containing theHSLColorclass, encapsulating a single color in HSL(A) format.web, a submodule containing the standard web colors.
palette, with theHSLColorSequencetype (an alias for a list ofHSLColors) and thePaletteclass with utility functions to generate and handle sets of colors.easing, with the easing functions and their IDs, useful when generating color scales.
Color fundamentals
Colors in polychromos are defined through the HSLA components: hue, saturation, lightness and alpha/opacity. This is an approach more intuitive to humans, specially to artists and designers. This is why this library favors this approach.
Colors using these components exist in a cylindrical coordinates space:
- The hue is the angle of a semiplane containing both the color and the cylinder axis, and the semiplane representing the hue (0deg angle) containing the cylinder axis. This component is cyclical, meaning once its value passes its maximum value (360deg), it "starts" again at 0deg.
- The hue is the distance between the color and the cylinder axis. Colors at the cylinder axis (0% saturation) have no chromacity and look gray.
- The lightness is the position of the circle containing the color across the cylinder axis. A lightness of 50% is the pure color based on its chromacity and intensity. Values above 50% are brighter, and below 50% darker, being 100% and 0% pure white and pure black respectively.
- Alpha, the last component, represents the opacity of the color and is defined outside the reference cylindrical coordinate system.
For an artist it is more intuitive to manipulate each of these components separately, instead of changing the red, green, blue and alpha components to get the exact color they look for.
To define a color in polychromos, the following options are implemented:
from polychromos.color import HSLColor
color: HSLColor
# Default construction format: each component in the range [0,.0, 1.0]
color = HSLColor(0.25, 0.85, 0.45) # hsl(90deg 85% 45%)
color = HSLColor(0.25, 0.85, 0.45, 0.6) # hsla(90deg 85% 45% / 60%)
# From absolute HSLA components: hue [0, 360], sat [0, 100], light [0, 100], alpha [0, 100]
color = HSLColor.from_abs_hsla(90, 85, 45)
color = HSLColor.from_abs_hsla(90, 85, 45, 60)
# From RGBA components: each component in the range [0.0, 1.0]
color = HSLColor.from_rgba(0.51, 0.75, 0.1)
color = HSLColor.from_rgba(0.51, 0.75, 0.1, 1.0)
# From absolute RGBA components: each component in the range [0, 255]
color = HSLColor.from_abs_rgba(0x78, 0xac, 0x19)
color = HSLColor.from_abs_rgba(0x78, 0xac, 0x19, 0xff)
# From CSS hexadecimal notation: #RGB, #RGBA, #RRGGBB, #RRGGBBAA
color = HSLColor.from_hex('#78ac19')
color = HSLColor.from_hex('#78ac19ff')
HSLA components can be read and written directly, and their values are in the [0.0, 1.0] range.
color.hue = 0.7
color.saturation = 0.95
color.lightness = 0.5
color.opacity = 0.35
color.alpha = 0.35 # Alias of opacity as a convenience for those used to name it alpha instead.
A new color can be obtained relative to another applying an absolute shift:
new_color = color.delta(0.02, -0.1, 0.05) # Shift hue 7.2deg, muted by 10%, and brightened 5%.
To use them for the web, a convenience method exports the color to CSS3 HSL and RGB format:
color = HSLColor(0.25, 0.85, 0.45)
color.to_css_hsl() # hsl(90deg 85% 45%)
color.to_css_hsl(legacy=True) # hsl(90, 85%, 45%)
color.to_css_rgb()
(note the legacy format is discouraged, but some software like Plotly still uses it, so it is included for that reason).
Web colors can be used by their name (case insensitive):
from polychromos.color.web import get_web_color
color: HSLColor = get_web_color('Crimson')
Color harmony
Color harmony schemes set a framework to obtain colors related to other colors in a chromatically appealing and balanced way. All of these schemes refer to the hue component explicitly.
The first and most basic schemes (primary, secondary and tertiary) refer to the basic pieces to split a chromatic space into.
There are other more elaborate schemes. Polychromos provides the following three:
- Complementary color: A color chromatically opposite (shifted 180deg) to a given color.
- Triadic colors: Two colors that, along the reference color, form an equilateral triangle. That is, each color hue is shifted 120deg from the other two.
- Split complementary colors: Two colors halfway between the complementary color and the triadic colors of the reference color (i.e., shifted 150deg clocwise and counter-clockwise from the reference color in the chromatic wheel).
These methods allow to optionally change the saturation and lightness by a given delta, if desired, for convenience when building color schemes using these harmonies.
from polychromos.color import HSLColor
from polychromos.palette import Palette
ref_color: HSLColor = HSLColor(0.25, 1.0, 0.5) # hsl(90, 100%, 50%)
complementary_color: HSLColor
complementary_color = Palette.complementary(ref_color)
complementary_color = Palette.complementary(ref_color, mute_saturation=0.3) # 30% less saturated
complementary_color = Palette.complementary(ref_color, mute_lightness=0.2) # 20% darker
complementary_color = Palette.complementary(ref_color, mute_lightness=-0.15) # 15% brighter
triadic_color_1: HSLColor
triadic_color_2: HSLColor
(triadic_color_1, triadic_color_2) = Palette.triadic(ref_color)
(triadic_color_1, triadic_color_2) = Palette.triadic(ref_color, mute_saturation=0.3) # 30% less sat
(triadic_color_1, triadic_color_2) = Palette.triadic(ref_color, mute_lightness=0.2) # 20% darker
(triadic_color_1, triadic_color_2) = Palette.triadic(ref_color, mute_lightness=-0.15) # 15% brighter
split_comp_color_1: HSLColor
split_comp_color_2: HSLColor
(triadic_color_1, triadic_color_2) = Palette.triadic(ref_color)
(triadic_color_1, triadic_color_2) = Palette.triadic(ref_color, mute_saturation=0.3) # 30% less sat
(triadic_color_1, triadic_color_2) = Palette.triadic(ref_color, mute_lightness=0.2) # 20% darker
(triadic_color_1, triadic_color_2) = Palette.triadic(ref_color, mute_lightness=-0.15) # 15% brighter
There are other color harmony schemes like the tetradic or rectangular, that are not provided with this software. The analogous colors scheme is not implemented as such, but can be obtained easily by constructing a color sequence from a reference color as shown in the next section.
Color sequences
Polychromos Palette contains a series of facilites to build color sequences: lists of
colors in HSL format. These facilities can be divided in two subtypes: those that build from a
single reference color and those that build from two or more colors.
Based on a reference color
The first kind use a reference color and deltas for each component, obtaining a shifted color for
each of the steps based on the previous color (starting with the reference color). This is done
with the method sequence_from_deltas:
from polychromos.color import HSLColor
from polychromos.palette import Palette, HSLColorSequence
ref_color: HSLColor = HSLColor(0.25, 1.0, 0.5) # hsl(90, 100%, 50%)
new_sequence: HSLColorSequence = Palette.sequence_from_deltas(
ref_color,
3, # Three steps before
3, # Three steps after
0.025, # 9deg hue shift per step (backwards in the steps before, forwards in the steps after)
0.0, # No saturation shift
-0.05, # -5% lightness shift per step (brighter before, darker after)
)
In the example above, a sequence of seven colors (three before, the reference color, and three after) is built, calculated as a gradient in both the hue and lightness components, with a constant saturation.
As a special case, the analogous color harmony scheme can be obtained with this function:
analogous_colors: HSLColorSequence = Palette.sequence_from_deltas(
ref_color,
2,
2,
15.0 / 360.0,
0.0,
0.0,
)
Based on two or more colors
The second kind of color sequences are defined by interpolating between colors.
There are two kinds of color interpolations implemented in polychromos:
-
Linear interpolation (lerp): Colors from the cylindrical coordinate system are projected onto a cartesian coordinate system, tracing a linear segment between both colors, and obtaining a color of that segment in a relative distance from start (0.0) to end (1.0). When the starting and final colors are opposing in hue, the middle colors are desaturated or even gray (e.g., red-gray-cyan).
-
Cylindrical "spherical" interpolation (cylindrical slerp): Colors are interpolated linearly in the cylindrical coordinate space, meaning each HSLA component is interpolated separately. In this case, intermediate color saturations will not be outside the minimum and maximum saturations of the starting and final colors (e.g., red-yellow-green-cyan).
In this interpolation, as there are two paths to go from the starting hue to the final hue, one of four interpolation path strategies must be chosen: always forward in the wheel(e.g. red-yellow-green, red-yellow-green-cyan-blue), always backwards (e.g., red-magenta-blue-cyan-green, red-magenta-blue), shortes path (e.g., red-yellow-green, red-magenta-blue), or longest path (e.g., red-magenta-blue-cyan-green, red-yellow-green-cyan-blue).
The library provides two interpolation functions:
from polychromos.color import HSLColor
from polychromos.palette import Palette, HSLColorSequence
red: HSLColor = HSLColor.from_abs_hsla(0, 100, 50)
yellow: HSLColor = HSLColor.from_abs_hsla(60, 100, 50)
green: HSLColor = HSLColor.from_abs_hsla(120, 100, 50)
cyan: HSLColor = HSLColor.from_abs_hsla(180, 100, 50)
blue: HSLColor = HSLColor.from_abs_hsla(240, 100, 50)
magenta: HSLColor = HSLColor.from_abs_hsla(300, 100, 50)
Palette.lerp(red, cyan, 0.4) # Reddish gray
Palette.lerp(red, cyan, 0.6) # Cyanish gray
Palette.cylindrical_slerp(red, green, 0.5) # Yellow; shortest path by default
Palette.cylindrical_slerp(red, blue, 0.5) # Magenta; shortest path by default
For convenience, methods to construct the whole sequenece using interpolation are implemented as well, given the colors and a number of steps in the sequence:
# Red, reddish gray, gray, cyanish gray, cyan
Palette.sequence_from_linear_interpolation(red, cyan, 5)
# Red, yellow, green
Palette.sequence_from_cylindrical_interpolation(
red, green, 3,
path_strategy=Palette.CylindricalInterpolationPath.FORWARD,
)
# Red, yellow, green, cyan, blue
Palette.sequence_from_cylindrical_interpolation(
red, green, 5,
path_strategy=Palette.CylindricalInterpolationPath.FORWARD,
)
# Red, magenta, blue, cyan, green
Palette.sequence_from_cylindrical_interpolation(
red, green, 5,
path_strategy=Palette.CylindricalInterpolationPath.BACKWARD,
)
# Red, magenta, blue
Palette.sequence_from_cylindrical_interpolation(
red, green, 3,
path_strategy=Palette.CylindricalInterpolationPath.BACKWARD,
)
# Red, yellow, green
Palette.sequence_from_cylindrical_interpolation(
red, green, 3,
path_strategy=Palette.CylindricalInterpolationPath.SHORTEST,
)
# Red, magenta, blue
Palette.sequence_from_cylindrical_interpolation(
red, green, 3,
path_strategy=Palette.CylindricalInterpolationPath.SHORTEST,
)
# Red, magenta, blue, cyan, green
Palette.sequence_from_cylindrical_interpolation(
red, green, 5,
path_strategy=Palette.CylindricalInterpolationPath.LONGEST,
)
# Red, yellow, green, cyan, blue
Palette.sequence_from_cylindrical_interpolation(
red, green, 5,
path_strategy=Palette.CylindricalInterpolationPath.LONGEST,
)
There is an hybrid approach to build these sequences as well: the elliptical interpolation. It is
an intermediate point between the linear and cylindrical interpolations, with each color being
interpolated between the linear and the cylindrical interpolations by a straightening factor,
where 0.0 means purely cylindrical interpolation, and 1.0 a purely linear interpolation. This
is recommended when the linear interpolation is too harsly muted and the cylindrical interpolation
is too vivid.
These sequence factory methods accept another parameter, open_ended, boolean and False by
default, to prevent the factory to include the final color in the sequence. This is useful to
concatenate sequences where the final color of the current is the starting color of the next.
For this reason a variation of each of the previous sequence factories is provided as well:
sequence_from_multiple_linear_interpolation, sequence_from_multiple_cylindrical_interpolation
and sequence_from_multiple_elliptical_interpolation. These methods, instead of a starting color,
a final color and a number of steps, receive a list of N colors and a list of N-1 steps to
construct a sequence comprised of N-1 subsequences. For each subsequence a color and the next color
are selected as the start and end of a subsequence of the number of steps given in the second list.
# Red, orange, yellow
# yellow, green, cyan, blue
# --------------------------------------
# Red, orange, yellow, green, cyan, blue
Palette.sequence_from_multiple_cylindrical_interpolation(
colors=[red, yellow, blue],
steps=[3, 4],
)
# Red, muted yellow, green
# green, muted cyan, blue
# blue, muted magenta, red
# --------------------------------------------------------------
# Red, muted yellow, green, muted cyan, blue, muted magenta, red
Palette.sequence_from_multiple_linear_interpolation(
colors=[red, green, blue, red],
steps=[3, 3, 3],
)
Shuffling for unordered categorical data
As the colors in the sequences generated by this library are sometimes too similar to their neighbors, they may be not suitable to place them side-by-side in a plot, for instance.
A method alternate_colors is provided by the Palette class for this purpose. It alternates
the colors in the sequence, "shuffling" it so no color is too similar to its neighbors (relatively)
This splits the sequence in two halves, picking one color from each in alternating order. For
instance, for a sequence of N colors, the resulting sequence is: 1, N/2+1, 2, N/2+2 ... N/2, N
Mixing sequences
Sometimes is useful to generate a sequence by picking colors from two different color sequences. For instance, when plotting a set of categorical data where two different sets of categories are to be colored differently.
The method Palette.mix_color_sequences does exactly this. It takes two sequences to pick the
colors from (sequence A and sequence B), and a list of "selectors" that determine which of the
sequences to pick the colors from, depending on the selector value.
The resulting sequence will have the same size as the list of selectors. For each of the entries
in the sequence, it will pick a color from sequence A if the selector is False, or from sequence
B if the selector is True.
The color from each sequence can be picked in one of these ways:
- By position: the color picked matches the position of the element in the resulting sequence.
- By use: colors are picked iteratively from each sequence separately (e.g., it will pick the third color if two colors were previously picked from that sequence).
If the color to pick is outside the bounds of the length of the source sequence, it will start over again.
col_seq_a: HSLColorSequence = [
get_web_color('silver'),
get_web_color('gray'),
]
col_seq_b: HSLColorSequence = [
colors['crimson'],
colors['gold'],
colors['seagreen'],
]
# Selector: False , True , True , False , True , False , True , False
# Index A: 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1
# Index B: 0 , 1 , 2 , 0 , 1 , 2 , 0 , 1
# Color: silver , gold , seagreen, gray , gold , gray , crimson , gray
Palette.mix_color_sequences(
col_seq_a,
col_seq_b,
[False, True, True, False, True, False, True, False],
indexing=Palette.MixIndexing.BY_POSITION,
)
# Selector: False , True , True , False , True , False , True , False
# Index A: 0 , 1 , 1 , 1 , 0 , 0 , 1 , 1
# Index B: 0 , 0 , 1 , 2 , 2 , 0 , 0 , 1
# Color: silver , crimson , gold , gray , seagreen, silver , crimson , gray
Palette.mix_color_sequences(
col_seq_a,
col_seq_b,
[False, True, True, False, True, False, True, False],
indexing=Palette.MixIndexing.BY_USE,
)
Color scales
For some uses, discrete color steps are not enough and continuous color scales are required.
The class Palette provides a method to_color_scale, that maps a color sequence to a color
scale, a list of tuples with the first element of each being a relative position in the scale
(rank [0.0, 1.0]), and the second element the corresponding color in the sequence.
This method supports easing, when colors should be more concentrated at the start, at the end, both, or evenly distributed (i.e., no easing; the default behavior).
from polychromos.color import HSLColor
from polychromos.palette import Palette, HSLColorSequence, HSLColorGradient
from polychromos.easing import EasingFunctionId, get_easing_function
danger: HSLColor = HSColor.from_abs_hsla(335, 70, 50)
warning: HSLColor = HSColor.from_abs_hsla(45, 100, 50)
fine: HSLColor = HSColor.from_abs_hsla(150, 70, 30)
semaphore: HSLColorSequence = Palette.sequence_from_multiple_cylindrical_interpolation(
colors=[danger, warning, fine],
steps=[4, 4],
)
# Semaphore color scale, with colors more concentrated at the ends than the middle area.
semaphore_scale: HSLColorScale = Palette.to_color_scale(
semaphore,
get_easing_function(EasingFunctionId.EASE_IN_OUT_QUAD),
)
Color scales concatenation
Color scales can be concatenated together into a new color scale.
To do so, the concatenation method uses a list of color scales (two at least) and the position of each stitching point in the new scale (as many as the number of scales minus one).
# Creates a new color scale by concatenating three scales:
# - color_scale_1 in the range [0.0, 0.25]
# - color scale_2 in the range (0.25, 0.5]
# - color_scale_3 in the range (0.5, 1.0]
combined_scale: HSLColorScale = Palette.concatenate_scales(
[color_scale_1, color_scale_2, color_scale_3],
[0.25, 0.5]
)
Picking colors from color scales
Colors can be picked from color scales using the method Palette.scale_lerp. This method takes a
color scale, a range (a minimum and a maximum values mapping the start and end of the color scale),
and an absolute position in the given range.
The reason to map the scale to a different range is to make it easier to use for data visualization.
This color is picked by applying a linear interpolation inside of the appropriate scale segment.
picked_color: HSLColor = Palette.scale_lerp(
color_scale,
(10.0, 45.5),
32.6,
)
Converting color scales to other formats
Some helper functions allow to convert color scales to formats suitable for other applications and uses.
SVG
A color scale can be converted to SVG linear gradients using the following methods:
from xml.etree import ElementTree as ET
color_scale_as_string: str = Palette.color_scale_to_svg_linear_gradient(
color_scale=color_scale,
gradient_id='color-scale-id', # XML id attribute for the gradient
start_pos=(0.0, 0.0), # top-left
end_pos=(1.0, 0.0), # top-right
)
color_scale_as_elements_tree: ET.Element = Palette.color_scale_to_svg_linear_gradient_etree(
color_scale=color_scale,
gradient_id='color-scale-id', # XML id attribute for the gradient
start_pos=(0.0, 0.0), # top-left
end_pos=(1.0, 0.0), # top-right
)
Plotly
To use the color scales for Plotly (e.g., for bar charts colored by value), use the method below:
color_scale_for_plotly: List[Tuple[float, str]] = Palette.color_scale_to_plotly(color_scale)
Color sequences from color scales
Once color scales are built, color sequences can be extracted from them by picking colors in regular intervals.
sequence_4: HSLColorSequence = Palette.color_scale_to_color_sequence(color_scale, 4)
sequence_15: HSLColorSequence = Palette.color_scale_to_color_sequence(color_scale, 15)
This method is recommended when a new sequence needs to be constructed from an existing one, but varying the number of total steps, specially when the original was built from several subsequences using different interpolation methods.
License
This project is licensed under the MIT license.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polychromos-1.3.1.tar.gz.
File metadata
- Download URL: polychromos-1.3.1.tar.gz
- Upload date:
- Size: 20.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2be300eb1d9d12559fd64ffcaa27c80ccd4dbd36720d0b64e8fc770db25090b3
|
|
| MD5 |
363f9313b16df6a6cdd30a03fc9d0aa6
|
|
| BLAKE2b-256 |
10f8f2ace7f2a84f8262848c052838e6ce9804d1ae39f4f4b8fbc6ae2b77f6aa
|
File details
Details for the file polychromos-1.3.1-py3-none-any.whl.
File metadata
- Download URL: polychromos-1.3.1-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
234de207aaa74f8b429d33614348474526cb7aa2af18de0d55b3e77a5c5d4c9e
|
|
| MD5 |
0080e2c41858435000210de46d1679b9
|
|
| BLAKE2b-256 |
6e0ef8bff099f60d0aa2125b5277715c337e93e26a79e05150ccc3dbe7142dcf
|