c3-colors 2.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- c3_colors-2.0.1/.gitignore +12 -0
- c3_colors-2.0.1/PKG-INFO +87 -0
- c3_colors-2.0.1/README.md +71 -0
- c3_colors-2.0.1/pyproject.toml +45 -0
- c3_colors-2.0.1/src/c3/__init__.py +48 -0
- c3_colors-2.0.1/src/c3/_color.py +284 -0
- c3_colors-2.0.1/src/c3/_hierarchy.py +279 -0
- c3_colors-2.0.1/src/c3/_oklab.py +134 -0
- c3_colors-2.0.1/src/c3/_palette.py +357 -0
- c3_colors-2.0.1/src/c3/_schemes.py +66 -0
- c3_colors-2.0.1/src/c3/_select.py +96 -0
- c3_colors-2.0.1/src/c3/_stops.py +93 -0
- c3_colors-2.0.1/src/c3/_swatches.py +6 -0
- c3_colors-2.0.1/src/c3/_tone.py +236 -0
- c3_colors-2.0.1/src/c3/py.typed +0 -0
c3_colors-2.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: c3-colors
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: Consistent Categorical Colors - Deterministic colors for maps
|
|
5
|
+
Project-URL: Homepage, https://github.com/mapequation/c3
|
|
6
|
+
Project-URL: Repository, https://github.com/mapequation/c3
|
|
7
|
+
Project-URL: Issues, https://github.com/mapequation/c3/issues
|
|
8
|
+
Author: @mapequation
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Keywords: categorical,colors,oklab,oklch,palette
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# C3 for Python - Consistent Categorical Colors
|
|
18
|
+
|
|
19
|
+
A pure Python port of [`@mapequation/c3`](https://www.npmjs.com/package/@mapequation/c3): deterministic, distinct colors for categories and nested modules, spread over hue, lightness and chroma in OKLCH. No dependencies.
|
|
20
|
+
|
|
21
|
+
It gives the same colors as the npm package, checked against shared fixtures (within rounding), and the version numbers match, so `c3-colors` 2.0.0 gives the colors of `@mapequation/c3` 2.0.0.
|
|
22
|
+
|
|
23
|
+
### Install
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install c3-colors
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
or `uv add c3-colors`, and import it as `c3`. Requires Python 3.11 or later.
|
|
30
|
+
|
|
31
|
+
### Usage
|
|
32
|
+
|
|
33
|
+
The API mirrors the JavaScript one, with options as keyword arguments in snake_case.
|
|
34
|
+
The [tutorial notebook](examples/tutorial.ipynb) walks through it with matplotlib, and colors a map with geopandas and network modules with networkx and Infomap.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import c3
|
|
38
|
+
|
|
39
|
+
c3.palette(5)
|
|
40
|
+
# ['#2b9bea', '#cf9f71', '#3b8559', '#b46ba5', '#00b3bb']
|
|
41
|
+
|
|
42
|
+
c3.palette(10, tone="pastel", offset=90)
|
|
43
|
+
c3.palette([3, 1, 1, 2]) # weights: heavier colors get more hue space
|
|
44
|
+
c3.palette(8, seeds=["#ff0000"], method="farthest")
|
|
45
|
+
c3.palette(40, end="darker") # later colors in a darker end zone
|
|
46
|
+
c3.palette(12, tone={"chroma": (0.1, 0.2), "warm_hue": 60, "tilt": 0.3})
|
|
47
|
+
c3.palette(10, tone=c3.swatches["mural"]) # infer the tone from colors
|
|
48
|
+
|
|
49
|
+
colors = c3.hierarchy([
|
|
50
|
+
{"path": [1, 1], "flow": 0.3},
|
|
51
|
+
{"path": [1, 2], "flow": 0.2},
|
|
52
|
+
{"path": [2, 1], "flow": 0.5},
|
|
53
|
+
])
|
|
54
|
+
colors([1, 2]) # color of module 1:2
|
|
55
|
+
colors.modules # all modules with path, flow, color and radius, depth first
|
|
56
|
+
|
|
57
|
+
c3.tones["vivid"] # {'lightness': (0.55, 0.7), 'chroma': (0.15, 0.21), 'warm_hue': 285, 'tilt': 0}
|
|
58
|
+
c3.infer_tone(["#e41a1c", "#377eb8", "#4daf4a"])
|
|
59
|
+
c3.oklab("steelblue"), c3.oklch("#2b9bea")
|
|
60
|
+
c3.distance("#2b9bea", "#2b9beb") # about 0.02 is just noticeable
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Tone and end zone fields are snake_case too: `warm_hue` for `warmHue`, `min_gap` for `minGap`.
|
|
64
|
+
|
|
65
|
+
### Hue sources
|
|
66
|
+
|
|
67
|
+
`hue` is `"Oklch"` (the OKLCH hue circle, the default), one of the d3 color schemes `"Turbo"`, `"Sinebow"` or `"Rainbow"`, or any callable from `t` in [0, 1] to a color. The callable can return a CSS color string like `"#rrggbb"` or `"rgb(r, g, b)"`, or a sequence of floats in [0, 1] (alpha is ignored), the convention of matplotlib colormaps:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import matplotlib
|
|
71
|
+
|
|
72
|
+
c3.palette(10, hue=matplotlib.colormaps["viridis"])
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
matplotlib is not a dependency. The other d3 scheme names the npm package accepts raise a `ValueError`; pass the scheme as a callable instead.
|
|
76
|
+
|
|
77
|
+
### Development
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
cd python
|
|
81
|
+
uv run pytest
|
|
82
|
+
uv run --group notebook python examples/execute.py # re-execute the tutorial in place
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The tests check every case in `fixtures/c3.json` at the repository root, which the TypeScript library generates with `pnpm --filter @mapequation/c3 fixtures`.
|
|
86
|
+
They also check that the committed tutorial notebook has been executed without errors, so re-execute it after changing colors or the notebook.
|
|
87
|
+
The `notebook` dependency group has what the tutorial needs, and the notebook downloads the Census state boundaries when it runs.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# C3 for Python - Consistent Categorical Colors
|
|
2
|
+
|
|
3
|
+
A pure Python port of [`@mapequation/c3`](https://www.npmjs.com/package/@mapequation/c3): deterministic, distinct colors for categories and nested modules, spread over hue, lightness and chroma in OKLCH. No dependencies.
|
|
4
|
+
|
|
5
|
+
It gives the same colors as the npm package, checked against shared fixtures (within rounding), and the version numbers match, so `c3-colors` 2.0.0 gives the colors of `@mapequation/c3` 2.0.0.
|
|
6
|
+
|
|
7
|
+
### Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pip install c3-colors
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
or `uv add c3-colors`, and import it as `c3`. Requires Python 3.11 or later.
|
|
14
|
+
|
|
15
|
+
### Usage
|
|
16
|
+
|
|
17
|
+
The API mirrors the JavaScript one, with options as keyword arguments in snake_case.
|
|
18
|
+
The [tutorial notebook](examples/tutorial.ipynb) walks through it with matplotlib, and colors a map with geopandas and network modules with networkx and Infomap.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import c3
|
|
22
|
+
|
|
23
|
+
c3.palette(5)
|
|
24
|
+
# ['#2b9bea', '#cf9f71', '#3b8559', '#b46ba5', '#00b3bb']
|
|
25
|
+
|
|
26
|
+
c3.palette(10, tone="pastel", offset=90)
|
|
27
|
+
c3.palette([3, 1, 1, 2]) # weights: heavier colors get more hue space
|
|
28
|
+
c3.palette(8, seeds=["#ff0000"], method="farthest")
|
|
29
|
+
c3.palette(40, end="darker") # later colors in a darker end zone
|
|
30
|
+
c3.palette(12, tone={"chroma": (0.1, 0.2), "warm_hue": 60, "tilt": 0.3})
|
|
31
|
+
c3.palette(10, tone=c3.swatches["mural"]) # infer the tone from colors
|
|
32
|
+
|
|
33
|
+
colors = c3.hierarchy([
|
|
34
|
+
{"path": [1, 1], "flow": 0.3},
|
|
35
|
+
{"path": [1, 2], "flow": 0.2},
|
|
36
|
+
{"path": [2, 1], "flow": 0.5},
|
|
37
|
+
])
|
|
38
|
+
colors([1, 2]) # color of module 1:2
|
|
39
|
+
colors.modules # all modules with path, flow, color and radius, depth first
|
|
40
|
+
|
|
41
|
+
c3.tones["vivid"] # {'lightness': (0.55, 0.7), 'chroma': (0.15, 0.21), 'warm_hue': 285, 'tilt': 0}
|
|
42
|
+
c3.infer_tone(["#e41a1c", "#377eb8", "#4daf4a"])
|
|
43
|
+
c3.oklab("steelblue"), c3.oklch("#2b9bea")
|
|
44
|
+
c3.distance("#2b9bea", "#2b9beb") # about 0.02 is just noticeable
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Tone and end zone fields are snake_case too: `warm_hue` for `warmHue`, `min_gap` for `minGap`.
|
|
48
|
+
|
|
49
|
+
### Hue sources
|
|
50
|
+
|
|
51
|
+
`hue` is `"Oklch"` (the OKLCH hue circle, the default), one of the d3 color schemes `"Turbo"`, `"Sinebow"` or `"Rainbow"`, or any callable from `t` in [0, 1] to a color. The callable can return a CSS color string like `"#rrggbb"` or `"rgb(r, g, b)"`, or a sequence of floats in [0, 1] (alpha is ignored), the convention of matplotlib colormaps:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import matplotlib
|
|
55
|
+
|
|
56
|
+
c3.palette(10, hue=matplotlib.colormaps["viridis"])
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
matplotlib is not a dependency. The other d3 scheme names the npm package accepts raise a `ValueError`; pass the scheme as a callable instead.
|
|
60
|
+
|
|
61
|
+
### Development
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
cd python
|
|
65
|
+
uv run pytest
|
|
66
|
+
uv run --group notebook python examples/execute.py # re-execute the tutorial in place
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The tests check every case in `fixtures/c3.json` at the repository root, which the TypeScript library generates with `pnpm --filter @mapequation/c3 fixtures`.
|
|
70
|
+
They also check that the committed tutorial notebook has been executed without errors, so re-execute it after changing colors or the notebook.
|
|
71
|
+
The `notebook` dependency group has what the tutorial needs, and the notebook downloads the Census state boundaries when it runs.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "c3-colors"
|
|
3
|
+
version = "2.0.1"
|
|
4
|
+
description = "Consistent Categorical Colors - Deterministic colors for maps"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
authors = [{ name = "@mapequation" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = []
|
|
10
|
+
keywords = ["colors", "palette", "categorical", "oklab", "oklch"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
14
|
+
"Typing :: Typed",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/mapequation/c3"
|
|
19
|
+
Repository = "https://github.com/mapequation/c3"
|
|
20
|
+
Issues = "https://github.com/mapequation/c3/issues"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["hatchling"]
|
|
24
|
+
build-backend = "hatchling.build"
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.targets.wheel]
|
|
27
|
+
packages = ["src/c3"]
|
|
28
|
+
|
|
29
|
+
# The tests and the tutorial need the rest of the repo
|
|
30
|
+
[tool.hatch.build.targets.sdist]
|
|
31
|
+
include = ["src", "README.md"]
|
|
32
|
+
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = ["pytest>=8"]
|
|
35
|
+
notebook = [
|
|
36
|
+
"geopandas>=1.0",
|
|
37
|
+
"infomap>=2.8",
|
|
38
|
+
"ipykernel>=6.29",
|
|
39
|
+
"matplotlib>=3.9",
|
|
40
|
+
"nbclient>=0.10",
|
|
41
|
+
"networkx>=3.4",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""C3 - Consistent Categorical Colors. Deterministic colors for maps.
|
|
2
|
+
|
|
3
|
+
A Python port of the npm package @mapequation/c3, tested against the same fixtures.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
9
|
+
|
|
10
|
+
from ._hierarchy import Hierarchy, Module, ModuleColor, hierarchy
|
|
11
|
+
from ._oklab import Lab, Lch, distance
|
|
12
|
+
from ._oklab import parse_oklab as oklab
|
|
13
|
+
from ._oklab import parse_oklch as oklch
|
|
14
|
+
from ._palette import EndZone, HueSource, Method, palette
|
|
15
|
+
from ._schemes import Scheme, SchemeName
|
|
16
|
+
from ._swatches import swatches
|
|
17
|
+
from ._tone import PartialTone, Range, Tone, ToneName, ToneOption, infer_tone, tones
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
__version__ = version("c3-colors")
|
|
21
|
+
except PackageNotFoundError: # pragma: no cover
|
|
22
|
+
__version__ = "0.0.0"
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"palette",
|
|
26
|
+
"hierarchy",
|
|
27
|
+
"tones",
|
|
28
|
+
"infer_tone",
|
|
29
|
+
"swatches",
|
|
30
|
+
"oklab",
|
|
31
|
+
"oklch",
|
|
32
|
+
"distance",
|
|
33
|
+
"EndZone",
|
|
34
|
+
"Hierarchy",
|
|
35
|
+
"HueSource",
|
|
36
|
+
"Lab",
|
|
37
|
+
"Lch",
|
|
38
|
+
"Method",
|
|
39
|
+
"Module",
|
|
40
|
+
"ModuleColor",
|
|
41
|
+
"PartialTone",
|
|
42
|
+
"Range",
|
|
43
|
+
"Scheme",
|
|
44
|
+
"SchemeName",
|
|
45
|
+
"Tone",
|
|
46
|
+
"ToneName",
|
|
47
|
+
"ToneOption",
|
|
48
|
+
]
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""CSS color parsing: hex, rgb(), rgba(), hsl(), hsla() and named colors.
|
|
2
|
+
|
|
3
|
+
Follows d3-color's ``rgb()``, which the TypeScript library parses colors with, and its
|
|
4
|
+
``formatRgb()``, which the d3 color schemes return.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import math
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
RGB = tuple[float, float, float]
|
|
13
|
+
|
|
14
|
+
_NUM = r"([+-]?(?:[0-9]*\.)?[0-9]+(?:[eE][+-]?[0-9]+)?)"
|
|
15
|
+
_RE_I = r"\s*([+-]?[0-9]+)\s*"
|
|
16
|
+
_RE_N = rf"\s*{_NUM}\s*"
|
|
17
|
+
_RE_P = rf"\s*{_NUM}%\s*"
|
|
18
|
+
|
|
19
|
+
_HEX = re.compile(r"#([0-9a-f]{3,8})")
|
|
20
|
+
_RGB_INTEGER = re.compile(rf"rgb\({_RE_I},{_RE_I},{_RE_I}\)")
|
|
21
|
+
_RGB_PERCENT = re.compile(rf"rgb\({_RE_P},{_RE_P},{_RE_P}\)")
|
|
22
|
+
_RGBA_INTEGER = re.compile(rf"rgba\({_RE_I},{_RE_I},{_RE_I},{_RE_N}\)")
|
|
23
|
+
_RGBA_PERCENT = re.compile(rf"rgba\({_RE_P},{_RE_P},{_RE_P},{_RE_N}\)")
|
|
24
|
+
_HSL_PERCENT = re.compile(rf"hsl\({_RE_N},{_RE_P},{_RE_P}\)")
|
|
25
|
+
_HSLA_PERCENT = re.compile(rf"hsla\({_RE_N},{_RE_P},{_RE_P},{_RE_N}\)")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def parse_rgb(color: object) -> RGB | None:
|
|
29
|
+
"""A CSS color as (r, g, b) in 0 to 255, unclamped, or None if it can't be parsed or is transparent."""
|
|
30
|
+
fmt = str(color).strip().lower()
|
|
31
|
+
if m := _HEX.fullmatch(fmt):
|
|
32
|
+
digits = m.group(1)
|
|
33
|
+
n = int(digits, 16)
|
|
34
|
+
if len(digits) == 6:
|
|
35
|
+
return _rgbn(n)
|
|
36
|
+
if len(digits) == 3:
|
|
37
|
+
return (
|
|
38
|
+
float((n >> 8 & 0xF) | (n >> 4 & 0xF0)),
|
|
39
|
+
float((n >> 4 & 0xF) | (n & 0xF0)),
|
|
40
|
+
float(((n & 0xF) << 4) | (n & 0xF)),
|
|
41
|
+
)
|
|
42
|
+
if len(digits) == 8:
|
|
43
|
+
return _rgba(n >> 24 & 0xFF, n >> 16 & 0xFF, n >> 8 & 0xFF, (n & 0xFF) / 0xFF)
|
|
44
|
+
if len(digits) == 4:
|
|
45
|
+
return _rgba(
|
|
46
|
+
(n >> 12 & 0xF) | (n >> 8 & 0xF0),
|
|
47
|
+
(n >> 8 & 0xF) | (n >> 4 & 0xF0),
|
|
48
|
+
(n >> 4 & 0xF) | (n & 0xF0),
|
|
49
|
+
(((n & 0xF) << 4) | (n & 0xF)) / 0xFF,
|
|
50
|
+
)
|
|
51
|
+
return None
|
|
52
|
+
if m := _RGB_INTEGER.fullmatch(fmt):
|
|
53
|
+
return (float(m[1]), float(m[2]), float(m[3]))
|
|
54
|
+
if m := _RGB_PERCENT.fullmatch(fmt):
|
|
55
|
+
return (float(m[1]) * 255 / 100, float(m[2]) * 255 / 100, float(m[3]) * 255 / 100)
|
|
56
|
+
if m := _RGBA_INTEGER.fullmatch(fmt):
|
|
57
|
+
return _rgba(float(m[1]), float(m[2]), float(m[3]), float(m[4]))
|
|
58
|
+
if m := _RGBA_PERCENT.fullmatch(fmt):
|
|
59
|
+
return _rgba(float(m[1]) * 255 / 100, float(m[2]) * 255 / 100, float(m[3]) * 255 / 100, float(m[4]))
|
|
60
|
+
if m := _HSL_PERCENT.fullmatch(fmt):
|
|
61
|
+
return _hsla(float(m[1]), float(m[2]) / 100, float(m[3]) / 100, 1.0)
|
|
62
|
+
if m := _HSLA_PERCENT.fullmatch(fmt):
|
|
63
|
+
return _hsla(float(m[1]), float(m[2]) / 100, float(m[3]) / 100, float(m[4]))
|
|
64
|
+
if fmt in NAMED:
|
|
65
|
+
return _rgbn(NAMED[fmt])
|
|
66
|
+
return None # also "transparent"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _rgbn(n: int) -> RGB:
|
|
70
|
+
return (float(n >> 16 & 0xFF), float(n >> 8 & 0xFF), float(n & 0xFF))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _rgba(r: float, g: float, b: float, a: float) -> RGB | None:
|
|
74
|
+
return None if a <= 0 else (float(r), float(g), float(b))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _hsla(h: float, s: float, l: float, a: float) -> RGB | None:
|
|
78
|
+
if a <= 0:
|
|
79
|
+
return None
|
|
80
|
+
if s <= 0 or l <= 0 or l >= 1: # grey, black or white
|
|
81
|
+
return (l * 255, l * 255, l * 255)
|
|
82
|
+
h %= 360
|
|
83
|
+
m2 = l + (l if l < 0.5 else 1 - l) * s
|
|
84
|
+
m1 = 2 * l - m2
|
|
85
|
+
return (
|
|
86
|
+
_hsl2rgb(h - 240 if h >= 240 else h + 120, m1, m2),
|
|
87
|
+
_hsl2rgb(h, m1, m2),
|
|
88
|
+
_hsl2rgb(h + 240 if h < 120 else h - 120, m1, m2),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _hsl2rgb(h: float, m1: float, m2: float) -> float:
|
|
93
|
+
if h < 60:
|
|
94
|
+
v = m1 + (m2 - m1) * h / 60
|
|
95
|
+
elif h < 180:
|
|
96
|
+
v = m2
|
|
97
|
+
elif h < 240:
|
|
98
|
+
v = m1 + (m2 - m1) * (240 - h) / 60
|
|
99
|
+
else:
|
|
100
|
+
v = m1
|
|
101
|
+
return v * 255
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def round_byte(value: float) -> int:
|
|
105
|
+
"""A color channel rounded to an integer in 0 to 255, with halves rounded up."""
|
|
106
|
+
return max(0, min(255, math.floor(value + 0.5)))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def format_rgb(r: float, g: float, b: float) -> str:
|
|
110
|
+
"""An opaque color as "rgb(r, g, b)" with rounded, clamped channels, like d3's rgb.formatRgb()."""
|
|
111
|
+
return f"rgb({round_byte(r)}, {round_byte(g)}, {round_byte(b)})"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# d3-color cubehelix
|
|
115
|
+
_A = -0.14861
|
|
116
|
+
_B = +1.78277
|
|
117
|
+
_C = -0.29227
|
|
118
|
+
_D = -0.90649
|
|
119
|
+
_E = +1.97294
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def cubehelix_rgb(h: float, s: float, l: float) -> RGB:
|
|
123
|
+
"""d3.cubehelix(h, s, l).rgb()"""
|
|
124
|
+
hr = math.radians(h + 120)
|
|
125
|
+
a = s * l * (1 - l)
|
|
126
|
+
cosh = math.cos(hr)
|
|
127
|
+
sinh = math.sin(hr)
|
|
128
|
+
return (
|
|
129
|
+
255 * (l + a * (_A * cosh + _B * sinh)),
|
|
130
|
+
255 * (l + a * (_C * cosh + _D * sinh)),
|
|
131
|
+
255 * (l + a * (_E * cosh)),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
NAMED: dict[str, int] = {
|
|
136
|
+
"aliceblue": 0xf0f8ff,
|
|
137
|
+
"antiquewhite": 0xfaebd7,
|
|
138
|
+
"aqua": 0x00ffff,
|
|
139
|
+
"aquamarine": 0x7fffd4,
|
|
140
|
+
"azure": 0xf0ffff,
|
|
141
|
+
"beige": 0xf5f5dc,
|
|
142
|
+
"bisque": 0xffe4c4,
|
|
143
|
+
"black": 0x000000,
|
|
144
|
+
"blanchedalmond": 0xffebcd,
|
|
145
|
+
"blue": 0x0000ff,
|
|
146
|
+
"blueviolet": 0x8a2be2,
|
|
147
|
+
"brown": 0xa52a2a,
|
|
148
|
+
"burlywood": 0xdeb887,
|
|
149
|
+
"cadetblue": 0x5f9ea0,
|
|
150
|
+
"chartreuse": 0x7fff00,
|
|
151
|
+
"chocolate": 0xd2691e,
|
|
152
|
+
"coral": 0xff7f50,
|
|
153
|
+
"cornflowerblue": 0x6495ed,
|
|
154
|
+
"cornsilk": 0xfff8dc,
|
|
155
|
+
"crimson": 0xdc143c,
|
|
156
|
+
"cyan": 0x00ffff,
|
|
157
|
+
"darkblue": 0x00008b,
|
|
158
|
+
"darkcyan": 0x008b8b,
|
|
159
|
+
"darkgoldenrod": 0xb8860b,
|
|
160
|
+
"darkgray": 0xa9a9a9,
|
|
161
|
+
"darkgreen": 0x006400,
|
|
162
|
+
"darkgrey": 0xa9a9a9,
|
|
163
|
+
"darkkhaki": 0xbdb76b,
|
|
164
|
+
"darkmagenta": 0x8b008b,
|
|
165
|
+
"darkolivegreen": 0x556b2f,
|
|
166
|
+
"darkorange": 0xff8c00,
|
|
167
|
+
"darkorchid": 0x9932cc,
|
|
168
|
+
"darkred": 0x8b0000,
|
|
169
|
+
"darksalmon": 0xe9967a,
|
|
170
|
+
"darkseagreen": 0x8fbc8f,
|
|
171
|
+
"darkslateblue": 0x483d8b,
|
|
172
|
+
"darkslategray": 0x2f4f4f,
|
|
173
|
+
"darkslategrey": 0x2f4f4f,
|
|
174
|
+
"darkturquoise": 0x00ced1,
|
|
175
|
+
"darkviolet": 0x9400d3,
|
|
176
|
+
"deeppink": 0xff1493,
|
|
177
|
+
"deepskyblue": 0x00bfff,
|
|
178
|
+
"dimgray": 0x696969,
|
|
179
|
+
"dimgrey": 0x696969,
|
|
180
|
+
"dodgerblue": 0x1e90ff,
|
|
181
|
+
"firebrick": 0xb22222,
|
|
182
|
+
"floralwhite": 0xfffaf0,
|
|
183
|
+
"forestgreen": 0x228b22,
|
|
184
|
+
"fuchsia": 0xff00ff,
|
|
185
|
+
"gainsboro": 0xdcdcdc,
|
|
186
|
+
"ghostwhite": 0xf8f8ff,
|
|
187
|
+
"gold": 0xffd700,
|
|
188
|
+
"goldenrod": 0xdaa520,
|
|
189
|
+
"gray": 0x808080,
|
|
190
|
+
"green": 0x008000,
|
|
191
|
+
"greenyellow": 0xadff2f,
|
|
192
|
+
"grey": 0x808080,
|
|
193
|
+
"honeydew": 0xf0fff0,
|
|
194
|
+
"hotpink": 0xff69b4,
|
|
195
|
+
"indianred": 0xcd5c5c,
|
|
196
|
+
"indigo": 0x4b0082,
|
|
197
|
+
"ivory": 0xfffff0,
|
|
198
|
+
"khaki": 0xf0e68c,
|
|
199
|
+
"lavender": 0xe6e6fa,
|
|
200
|
+
"lavenderblush": 0xfff0f5,
|
|
201
|
+
"lawngreen": 0x7cfc00,
|
|
202
|
+
"lemonchiffon": 0xfffacd,
|
|
203
|
+
"lightblue": 0xadd8e6,
|
|
204
|
+
"lightcoral": 0xf08080,
|
|
205
|
+
"lightcyan": 0xe0ffff,
|
|
206
|
+
"lightgoldenrodyellow": 0xfafad2,
|
|
207
|
+
"lightgray": 0xd3d3d3,
|
|
208
|
+
"lightgreen": 0x90ee90,
|
|
209
|
+
"lightgrey": 0xd3d3d3,
|
|
210
|
+
"lightpink": 0xffb6c1,
|
|
211
|
+
"lightsalmon": 0xffa07a,
|
|
212
|
+
"lightseagreen": 0x20b2aa,
|
|
213
|
+
"lightskyblue": 0x87cefa,
|
|
214
|
+
"lightslategray": 0x778899,
|
|
215
|
+
"lightslategrey": 0x778899,
|
|
216
|
+
"lightsteelblue": 0xb0c4de,
|
|
217
|
+
"lightyellow": 0xffffe0,
|
|
218
|
+
"lime": 0x00ff00,
|
|
219
|
+
"limegreen": 0x32cd32,
|
|
220
|
+
"linen": 0xfaf0e6,
|
|
221
|
+
"magenta": 0xff00ff,
|
|
222
|
+
"maroon": 0x800000,
|
|
223
|
+
"mediumaquamarine": 0x66cdaa,
|
|
224
|
+
"mediumblue": 0x0000cd,
|
|
225
|
+
"mediumorchid": 0xba55d3,
|
|
226
|
+
"mediumpurple": 0x9370db,
|
|
227
|
+
"mediumseagreen": 0x3cb371,
|
|
228
|
+
"mediumslateblue": 0x7b68ee,
|
|
229
|
+
"mediumspringgreen": 0x00fa9a,
|
|
230
|
+
"mediumturquoise": 0x48d1cc,
|
|
231
|
+
"mediumvioletred": 0xc71585,
|
|
232
|
+
"midnightblue": 0x191970,
|
|
233
|
+
"mintcream": 0xf5fffa,
|
|
234
|
+
"mistyrose": 0xffe4e1,
|
|
235
|
+
"moccasin": 0xffe4b5,
|
|
236
|
+
"navajowhite": 0xffdead,
|
|
237
|
+
"navy": 0x000080,
|
|
238
|
+
"oldlace": 0xfdf5e6,
|
|
239
|
+
"olive": 0x808000,
|
|
240
|
+
"olivedrab": 0x6b8e23,
|
|
241
|
+
"orange": 0xffa500,
|
|
242
|
+
"orangered": 0xff4500,
|
|
243
|
+
"orchid": 0xda70d6,
|
|
244
|
+
"palegoldenrod": 0xeee8aa,
|
|
245
|
+
"palegreen": 0x98fb98,
|
|
246
|
+
"paleturquoise": 0xafeeee,
|
|
247
|
+
"palevioletred": 0xdb7093,
|
|
248
|
+
"papayawhip": 0xffefd5,
|
|
249
|
+
"peachpuff": 0xffdab9,
|
|
250
|
+
"peru": 0xcd853f,
|
|
251
|
+
"pink": 0xffc0cb,
|
|
252
|
+
"plum": 0xdda0dd,
|
|
253
|
+
"powderblue": 0xb0e0e6,
|
|
254
|
+
"purple": 0x800080,
|
|
255
|
+
"rebeccapurple": 0x663399,
|
|
256
|
+
"red": 0xff0000,
|
|
257
|
+
"rosybrown": 0xbc8f8f,
|
|
258
|
+
"royalblue": 0x4169e1,
|
|
259
|
+
"saddlebrown": 0x8b4513,
|
|
260
|
+
"salmon": 0xfa8072,
|
|
261
|
+
"sandybrown": 0xf4a460,
|
|
262
|
+
"seagreen": 0x2e8b57,
|
|
263
|
+
"seashell": 0xfff5ee,
|
|
264
|
+
"sienna": 0xa0522d,
|
|
265
|
+
"silver": 0xc0c0c0,
|
|
266
|
+
"skyblue": 0x87ceeb,
|
|
267
|
+
"slateblue": 0x6a5acd,
|
|
268
|
+
"slategray": 0x708090,
|
|
269
|
+
"slategrey": 0x708090,
|
|
270
|
+
"snow": 0xfffafa,
|
|
271
|
+
"springgreen": 0x00ff7f,
|
|
272
|
+
"steelblue": 0x4682b4,
|
|
273
|
+
"tan": 0xd2b48c,
|
|
274
|
+
"teal": 0x008080,
|
|
275
|
+
"thistle": 0xd8bfd8,
|
|
276
|
+
"tomato": 0xff6347,
|
|
277
|
+
"turquoise": 0x40e0d0,
|
|
278
|
+
"violet": 0xee82ee,
|
|
279
|
+
"wheat": 0xf5deb3,
|
|
280
|
+
"white": 0xffffff,
|
|
281
|
+
"whitesmoke": 0xf5f5f5,
|
|
282
|
+
"yellow": 0xffff00,
|
|
283
|
+
"yellowgreen": 0x9acd32,
|
|
284
|
+
}
|