"""Default styles for plots."""
from typing import Final, cast
import matplotlib.cm as mplcm # type: ignore
import matplotlib.pyplot as plt # type: ignore
import numpy as np
from matplotlib import colors # type: ignore
from pycommons.types import check_int_range, type_error
#: The internal color black.
COLOR_BLACK: Final[tuple[float, float, float]] = (0.0, 0.0, 0.0)
#: The internal color white.
COLOR_WHITE: Final[tuple[float, float, float]] = (1.0, 1.0, 1.0)
[docs]
def str_to_palette(palette: str) \
-> tuple[tuple[float, float, float], ...]:
"""
Obtain a palette from a string.
:param palette: the string with all the color data.
:returns: the palette
"""
if isinstance(colors, str):
raise type_error(colors, "colors", str)
result: list[tuple[float, float, float]] = []
end: int = -1
length: Final[int] = len(palette)
while end < length:
start: int = end + 1
end = length
for ch in "\n\r\t ,;":
end_new: int = palette.find(ch, start)
if start < end_new < end:
end = end_new
color: str = palette[start:end].strip()
if color.startswith("#"):
color = color[1:].strip()
if len(color) > 0:
if len(color) != 6:
raise ValueError(f"invalid color: {color!r} in {palette!r}")
result.append((int(color[0:2], 16) / 255,
int(color[2:4], 16) / 255,
int(color[4:6], 16) / 255))
if len(result) <= 0:
raise ValueError(f"empty colors: {palette!r}")
return tuple(result)
#: A palette with 11 distinct colors.
__PALETTE_11: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"ff0000 0000ff 00d900 a32626 ffa500 008833 7788ff cf00ff 770066 cccc00 "
"a0a9a0")
#: A palette with 21 distinct colors.
__PALETTE_21: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"e6194b 3cb44b ffe119 0082c8 f58231 911eb4 46f0f0 f032e6 d2f53c fabebe "
"008080 e6beff aa6e28 cfca08 800000 aaffc3 808000 ffd8b1 000080 505000 "
"90a0a0")
#: A palette with 25 distinct colors.
__PALETTE_25: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"20b2aa f0e68c b8860b ff00ff 00fa9a ee82ee 9acd32 dcdcdc 556b2f db7093 "
"00bfff 1e90ff 8a2be2 ff1493 008000 ffd700 000080 800000 7fff00 8b008b "
"0000ff 483d8b ff0000 696969 ff7f50")
#: A palette with 28 distinct colors.
__PALETTE_28: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"545454 550000 b0b0ff bababa 878787 005500 550055 b000b0 e4e400 00b0b0 "
"00ffff 00ff00 878700 870087 005555 ff0000 0000ff baba00 00b000 ff00ff "
"870000 4949ff 008700 8484ff 008787 e4e4e4 545400 b00000")
#: A palette with 30 distinct colors.
__PALETTE_30: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"00bfff ff1493 7f007f 8a2be2 1e90ff adff2f ffb6c1 fa8072 ffff54 90ee90 "
"ff4500 00ff00 483d8b ffa500 696969 add8e6 ff00ff 7f0000 8fbc8f da70d6 "
"006400 dc143c 00ff7f 008b8b 808000 b03060 00ffff 0000ff 000080 deb887 ")
#: A palette with 35 distinct colors.
__PALETTE_35: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"228b22 808080 9acd32 008080 483d8b 7f0000 d2b48c 00ff00 00008b 808000 "
"00ced1 8fbc8f 556b2f ffc0cb 1e90ff ffff54 800080 00bfff ff00ff 7b68ee "
"ff6347 9400d3 ffa500 7fffd4 b03060 ff0000 f08080 f4a460 00ff7f 98fb98 "
"f0e68c ff1493 0000ff b0e0e6 dda0dd")
#: A palette with 40 distinct colors.
__PALETTE_40: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"ff00ff 7f007f a9a9a9 0000ff daa520 2f4f4f da70d6 ff8c00 ffc0cb afeeee "
"00008b 20b2aa 00ff00 f4a460 8b0000 008000 f08080 a0522d ff6347 808000 "
"bdb76b 98fb98 191970 ffe4c4 ffff00 4682b4 32cd32 1e90ff 3cb371 ff1493 "
"b03060 7fffd4 556b2f dda0dd adff2f 7b68ee 9acd32 ff0000 00bfff 9400d3 ")
#: A palette with 45 distinct colors.
__PALETTE_45: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"ffb60c 00008b 8b4513 e9967a a020f0 ff0000 6a5acd 808080 008b8b 00ff00 "
"90ee90 4682b4 daa520 7f007f ffd700 00bfff 00ced1 556b2f c0c0c0 008000 "
"ff69b4 dda0dd adff2f 3cb371 8b0000 0000ff f5deb3 32cd32 1e90ff b03060 "
"ff7f50 cd5c5c ff00ff 7fffd4 6b8e23 dc143c 9acd32 afeeee 2f4f4f 00fa9a "
"191970 8fbc8f ba55d3 ff8c00 bdb76b")
#: A palette with 50 distinct colors.
__PALETTE_50: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"7fffd4 000080 00ff7f 483d8b 008000 afeeee ffc0cb daa520 ffd700 ff8c00 "
"87cefa ff00ff c0c0c0 cd5c5c 00ff00 adff2f e9967a ffff00 dc143c cd853f "
"9932cc 98fb98 708090 800000 ee82ee 800080 8fbc8f 4682b4 dda0dd 32cd32 "
"a020f0 48d1cc a0522d ff1493 ff69b4 f0e68c 9370db 2f4f4f ff0000 6b8e23 "
"556b2f ff6347 f5deb3 008b8b db7093 0000cd 9acd32 3cb371 0000ff 6495ed")
#: A palette with 60 distinct colors.
__PALETTE_60: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"d2b48c 191970 000080 20b2aa bc8f8f 2f4f4f 8b4513 ff4500 dc143c afeeee "
"db7093 c71585 bdb76b b03060 708090 00ffff 6495ed 808000 f08080 b8860b "
"cd853f 556b2f ffa07a 008080 eee8aa dda0dd ee82ee 7fffd4 9370db 7f007f "
"d8bfd8 9acd32 4682b4 98fb98 00ff00 9932cc 0000cd d2691e 00fa9a ff8c00 "
"663399 a9a9a9 ffff00 32cd32 8b0000 4169e1 8fbc8f 0000ff ff69b4 008000 "
"ffb6c1 00bfff ffd700 ff1493 ff00ff 3cb371 a020f0 87ceeb ff6347 adff2f")
#: A palette with 70 distinct colors.
__PALETTE_70: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"b8860b a52a2a ffe4c4 663399 da70d6 a020f0 eee8aa 9acd32 ba55d3 ff8c00 "
"00008b 2e8b57 66cdaa 2f4f4f 0000cd 32cd32 556b2f adff2f 7fffd4 00ff00 "
"008b8b 4b0082 ffd700 00bfff 6b8e23 48d1cc b03060 778899 ffff54 ff0000 "
"00ff7f d2b48c 3cb371 bc8f8f b0e0e6 ffc0cb dcdcdc ff00ff cd5c5c 6495ed "
"0000ff 9370db dc143c a9a9a9 bdb76b f4a460 c71585 e9967a 4682b4 db7093 "
"483d8b 8b008b 87cefa 7fff00 dda0dd 006400 9932cc 808000 ff1493 191970 "
"4169e1 ff6347 b0c4de ff69b4 d2691e 00ffff 98fb98 ffff00 a0522d 8fbc8f")
#: A palette with 80 distinct colors.
__PALETTE_80: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"eee8aa bdb76b 2e8b57 0000ff 1e90ff cd5c5c ffd700 90ee90 00ffff ba55d3 "
"d3d3d3 40e0d0 ff0000 b0c4de 008000 0000cd 00bfff ffa07a ff00ff 778899 "
"dc143c cd853f 556b2f 9acd32 9400d3 663399 b03060 ffdab9 b22222 ffa500 "
"7fffd4 4169e1 dda0dd 8fbc8f ffff00 ff1493 191970 00008b 9932cc 006400 "
"483d8b 808000 696969 00ff7f d8bfd8 d2691e 4682b4 a9a9a9 c71585 5f9ea0 "
"ffc0cb 7f0000 800080 ee82ee 008080 4b0082 ffff54 9370db 8b4513 ff6347 "
"daa520 87cefa adff2f ff8c00 6b8e23 deb887 00fa9a 66cdaa 7b68ee fa8072 "
"ff69b4 db7093 b0e0e6 00ff00 2f4f4f bc8f8f 32cd32 3cb371 20b2aa 7cfc00")
#: A palette with 90 distinct colors.
__PALETTE_90: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"fa8072 ffff54 0000cd 32cd32 dc143c 40e0d0 ff8c00 9932cc 7b68ee 800000 "
"4b0082 008000 afeeee ee82ee ffd700 6a5acd 2e8b57 eee8aa a9a9a9 ff69b4 "
"b8860b c71585 ff4500 7fff00 2f4f4f 00ff7f ffa500 add8e6 008b8b bdb76b "
"ff00ff cd853f f0e68c 808080 db7093 800080 a0522d 87ceeb 3cb371 9400d3 "
"b0c4de daa520 b03060 dda0dd 66cdaa 6b8e23 d3d3d3 4682b4 9acd32 4169e1 "
"ff7f50 5f9ea0 f08080 00fa9a 00ced1 808000 adff2f 0000ff d2691e ff1493 "
"ff6347 ffdead 20b2aa 006400 7fffd4 483d8b cd5c5c f4a460 8fbc8f 663399 "
"d8bfd8 9370db b22222 d2b48c ffff00 ba55d3 bc8f8f ffb6c1 6495ed e9967a "
"ff0000 000080 00ff00 191970 90ee90 00ffff 778899 1e90ff 00bfff 556b2f")
#: A palette with 100 distinct colors.
__PALETTE_100: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"bdb76b 8a2be2 bc8f8f eee8aa 8fbc8f a020f0 ff4500 2f4f4f 000080 ff6347 "
"6b8e23 808080 663399 afeeee dcdcdc ff8c00 3cb371 191970 4682b4 7cfc00 "
"ee82ee 32cd32 9370db ff1493 00ff00 6a5acd adff2f 006400 00fa9a ffd700 "
"d2691e c0c0c0 00ff7f ba55d3 add8e6 ffff00 6495ed 8b008b 48d1cc 87cefa "
"fa8072 deb887 b22222 7b68ee b8860b 90ee90 8b4513 9acd32 cd853f f08080 "
"ff00ff da70d6 9400d3 ff69b4 ffe4b5 556b2f 8b0000 1e90ff e9967a 696969 "
"c71585 ffa07a f0e68c a9a9a9 20b2aa a0522d 87ceeb 0000ff 008000 daa520 "
"00ffff b03060 483d8b ffb6c1 dda0dd ff0000 9932cc 708090 cd5c5c 4b0082 "
"ffa500 0000cd dc143c a52a2a 008080 7fffd4 5f9ea0 b0c4de d8bfd8 808000 "
"db7093 f4a460 2e8b57 ffff54 40e0d0 66cdaa ffe4c4 ff7f50 4169e1 00bfff")
#: A palette with 110 distinct colors.
__PALETTE_110: Final[tuple[tuple[float, float, float], ...]] = str_to_palette(
"0000ff 556b2f ff4500 000080 f5deb3 ff1493 00008b 0000cd ff8c00 ffc0cb "
"fa8072 696969 da70d6 8b4513 d2691e 00fa9a 008080 c71585 bdb76b 40e0d0 "
"6495ed daa520 5f9ea0 ffe4c4 f4a460 ba55d3 228b22 00ced1 00ffff 66cdaa "
"b03060 9400d3 f08080 dc143c adff2f 00ff00 9932cc ee82ee 8a2be2 32cd32 "
"afeeee 191970 808080 9acd32 90ee90 b22222 a0522d ff00ff 006400 c0c0c0 "
"2f4f4f d3d3d3 cd853f 98fb98 9370db 4169e1 4b0082 00ff7f ff7f50 8fbc8f "
"8b0000 eee8aa dda0dd 008000 ffa500 ff6347 ff69b4 d8bfd8 ffa07a 4682b4 "
"b0c4de 2e8b57 ff0000 00bfff f0e68c 7f0000 b8860b d2b48c ffff54 1e90ff "
"e9967a 3cb371 87cefa 87ceeb 6a5acd ffd700 708090 bc8f8f 7b68ee 6b8e23 "
"a52a2a 7fffd4 ffdead ffdab9 808000 483d8b 20b2aa a020f0 48d1cc db7093 "
"a9a9a9 663399 b0e0e6 deb887 7f007f 7fff00 8b008b ffff00 cd5c5c add8e6")
#: A set of predefined uniquely-looking colors.
__FIXED_COLORS: Final[tuple[tuple[tuple[float, float, float], ...], ...]] = (
__PALETTE_11, __PALETTE_21, __PALETTE_25, __PALETTE_28, __PALETTE_30,
__PALETTE_35, __PALETTE_40, __PALETTE_45, __PALETTE_50, __PALETTE_60,
__PALETTE_70, __PALETTE_80, __PALETTE_90, __PALETTE_100, __PALETTE_110)
[docs]
def distinct_colors(n: int) -> tuple[tuple[float, float, float], ...]:
"""
Obtain a set of `n` distinct colors.
:param n: the number of colors required
:return: a tuple of colors
"""
check_int_range(n, "n", 1, 1000)
# First, let us see if we can cover the range with hand-picked colors.
for k in __FIXED_COLORS:
lk = len(k)
if lk >= n:
if lk == n:
return k
return tuple(k[0:n])
# Second, let's see whether the method from
# https://stackoverflow.com/questions/8389636
# works.
# This method does not seem to make good use of the available color space.
# Since we use it only for cases with more than 110 colors, that's OK.
cm = plt.get_cmap("gist_rainbow")
c_norm = colors.Normalize(vmin=0, vmax=n - 1)
scalar_map = mplcm.ScalarMappable(norm=c_norm, cmap=cm)
qq = cast(list[tuple[float, float, float]],
[tuple(scalar_map.to_rgba(i)[0:3]) for i in np.arange(n)])
ss = set(qq)
if len(ss) == n:
return tuple(qq)
raise ValueError(f"Could not obtain {n} distinct colors.")
#: The solid line dash
LINE_DASH_SOLID: Final[str] = "solid"
#: An internal array of fixed line styles.
__FIXED_LINE_DASHES: \
Final[tuple[str | tuple[float, tuple[float, ...]], ...]] = \
(LINE_DASH_SOLID,
"dashed",
"dashdot",
"dotted",
(0.0, (3.0, 5.0, 1.0, 5.0, 1.0, 5.0)), # dashdotdotted
(0.0, (3.0, 1.0, 1.0, 1.0)), # densely dashdotted
(0.0, (5.0, 1.0)), # densely dashed
(0.0, (1.0, 1.0)), # densely dotted
(0.0, (3.0, 1.0, 1.0, 1.0, 1.0, 1.0)), # densely dashdotdotted
(0.0, (1.0, 10.0)), # loosely dotted
(0.0, (5.0, 10.0)), # loosely dashed
(0.0, (3.0, 10.0, 1.0, 10.0)), # loosely dashdotted
(0.0, (3.0, 10.0, 1.0, 10.0, 1.0, 10.0))) # loosely dashdotdotted
[docs]
def distinct_line_dashes(n: int) -> \
tuple[str | tuple[float, tuple[float, ...]], ...]:
"""
Create a sequence of distinct line dashes.
:param n: the number of styles
:return: the styles
"""
check_int_range(n, "n", 1, len(__FIXED_LINE_DASHES) - 1)
if n == __FIXED_LINE_DASHES:
return __FIXED_LINE_DASHES
return tuple(__FIXED_LINE_DASHES[0:n])
#: The fixed predefined distinct markers
__FIXED_MARKERS: tuple[str, ...] = ("o", "^", "s", "P", "X", "D", "*", "p")
[docs]
def distinct_markers(n: int) -> tuple[str, ...]:
"""
Create a sequence of distinct markers.
:param n: the number of markers
:return: the markers
"""
lfm: Final[int] = len(__FIXED_MARKERS)
check_int_range(n, "n", 1, lfm)
if n == lfm:
return __FIXED_MARKERS
return tuple(__FIXED_MARKERS[0:n])
[docs]
def importance_to_line_width(importance: int) -> float:
"""
Transform an importance value to a line width.
Basically, an importance of `0` indicates a normal line in a normal
plot that does not need to be emphasized.
A positive importance means that the line should be emphasized.
A negative importance means that the line should be de-emphasized.
:param importance: a value between -9 and 9
:return: the line width
"""
check_int_range(importance, "importance", -9, 9)
if importance >= 0:
return 2.0 * (0.5 + importance)
if importance == -1:
return 2.0 / 3.0
if importance == -2:
return 0.5
return 0.7 ** (-importance)
[docs]
def importance_to_alpha(importance: int) -> float:
"""
Transform an importance value to an alpha value.
Basically, an importance of `0` indicates a normal line in a normal
plot that does not need to be emphasized.
A positive importance means that the line should be emphasized.
A negative importance means that the line should be de-emphasized.
:param importance: a value between -9 and 9
:return: the alpha
"""
check_int_range(importance, "importance", -9, 9)
if importance >= 0:
return 1.0
if importance == -1:
return 2.0 / 3.0
if importance == -2:
return 0.5
return 1.0 / 3.0
#: The internal default basic style
__BASE_LINE_STYLE: Final[dict[str, object]] = {
"alpha": 1.0,
"antialiased": True,
"color": COLOR_BLACK,
"dash_capstyle": "butt",
"dash_joinstyle": "round",
"linestyle": LINE_DASH_SOLID,
"linewidth": 1.0,
"solid_capstyle": "round",
"solid_joinstyle": "round",
}
[docs]
def create_line_style(**kwargs) -> dict[str, object]:
"""
Obtain the basic style for lines in diagrams.
:param kwargs: any additional overrides
:return: a dictionary with the style elements
"""
res = dict(__BASE_LINE_STYLE)
res.update(kwargs)
return res
[docs]
def importance_to_font_size(importance: float) -> float:
"""
Transform an importance value to a font size.
:param importance: the importance value
:return: the font size
"""
check_int_range(importance, "importance", -9, 9)
if importance < 0:
return 7.5
if importance <= 0:
return 8.0
if importance == 1:
return 8.5
if importance == 2:
return 9.0
if importance == 3:
return 10.0
return 11.0
#: The default grid color
GRID_COLOR: Final[tuple[float, float, float]] = \
(7.0 / 11.0, 7.0 / 11.0, 7.0 / 11.0)
[docs]
def rgb_to_gray(r: float, g: float, b: float) -> float:
"""
Convert RGB values to gray scale.
:param r: the red value
:param g: the green value
:param b: the blue value
:return: the gray value
"""
return (0.2989 * r) + (0.5870 * g) + (0.1140 * b)
[docs]
def text_color_for_background(background: tuple[float, float, float]) \
-> tuple[float, float, float]:
"""
Get a reasonable text color for a given background color.
:param background: the background color
:return: the text color
"""
br: Final[float] = background[0]
bg: Final[float] = background[1]
bb: Final[float] = background[2]
bgg: Final[float] = rgb_to_gray(br, bg, bb)
return COLOR_WHITE if bgg < 0.3 else COLOR_BLACK