Fast, dependency-free OKLab categorical color palettes powered by Rust
Project description
okpalette
Fast, deterministic categorical color palettes for Python.
Use okpalette when you need distinct, stable colors for labels, plots, dashboards, or reports.
Install
pip install okpalette
With uv:
uv add okpalette
Quickstart
Create stable colors for categories:
from okpalette import create_palette
colors = create_palette(8)
# ["#080050", "#e00800", "#1078ff", ...]
Get the same kind of palette from a shell:
okpalette create 8
CLI success output is JSON only:
{"colors":["#080050","#e00800","#1078ff"],"format":"hex"}
Extend colors you already have:
from okpalette import extend_palette
colors = extend_palette(["#0057b8", "#ffd700"], 8)
Use position-aware label colors when nearby labels should be easier to tell apart:
from okpalette import create_label_palette
positions = [(0.0, 0.0), (0.2, 0.0), (5.0, 0.0), (5.2, 0.0)]
labels = ["control", "treated", "control", "outlier"]
label_colors = create_label_palette(positions, labels)
Example
Agent Skill
okpalette includes an optional packaged agent skill for simple JSON CLI usage.
Install it into a personal Codex or Claude skill directory:
okpalette install-skill --agent codex
okpalette install-skill --agent claude
Use --dry-run to print the target path without writing, and --overwrite to
replace an existing installed skill. The Codex installer respects $CODEX_HOME
and otherwise writes under ~/.codex; Claude skills are installed under
~/.claude.
Formats
Use RGB tuples when that fits your plotting library better:
rgb = create_palette(5, format="rgb")
# [(8, 0, 80), (224, 8, 0), ...]
rgb01 = create_palette(5, format="rgb01")
# [(0.03137254901960784, 0.0, 0.3137254901960784), ...]
Extend Colors
Use extend_palette() when you already have brand colors or a small palette.
from okpalette import extend_palette
brand = ["#0057b8", "#ffd700"]
colors = extend_palette(brand, 12)
assert colors[:2] == ["#0057b8", "#ffd700"]
assert len(colors) == 12
Use existing colors as anchors without returning them:
new_colors = extend_palette(brand, 10, include_existing=False)
The same basic workflows are available through the CLI:
okpalette create 10
okpalette create 5 --format rgb
okpalette extend 12 --color "#0057b8" --color "#ffd700"
okpalette extend 10 --color "#0057b8" --generated-only
okpalette create and okpalette extend always write JSON on success, with
the stable shape {"colors":[...],"format":"hex"}. For --format rgb and
--format rgb01, tuple colors are serialized as JSON arrays. Validation and
generation errors write a short message to stderr and leave stdout empty.
Map Labels To Colors
Use create_label_palette() when positions should influence which label gets
which color. Nearby or overlapping labels are assigned more distinct colors.
from okpalette import create_label_palette
positions = [(0.0, 0.0), (0.2, 0.0), (5.0, 0.0), (5.2, 0.0)]
labels = ["control", "treated", "control", "outlier"]
colors = create_label_palette(positions, labels)
# {"control": "#080050", "treated": "#e00800", "outlier": ...}
Labels may be strings, integers, tuples, or other hashable Python objects. The returned dict preserves first-seen label order.
Keep specific label colors fixed:
colors = create_label_palette(
positions,
labels,
fixed_colors={"control": "#0057b8"},
)
For dataframe-like objects, read position and label columns by duck typing:
colors = create_label_palette_from_columns(
data,
positions=["x", "y"],
label="cluster",
)
Use With Plotting Libraries
okpalette has no required Matplotlib, Altair, or Plotly dependency. The
default output is a list of lowercase hex strings, which all three libraries
accept directly. Use format="rgb01" for Matplotlib APIs that specifically
expect normalized RGB tuples.
Matplotlib color cycles and colormaps:
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from okpalette import create_palette
colors = create_palette(6)
fig, ax = plt.subplots()
ax.set_prop_cycle(color=colors)
ax.plot(x, y1)
ax.plot(x, y2)
cmap = ListedColormap(create_palette(12), name="okpalette")
ax.scatter(x, y, c=values, cmap=cmap)
Altair categorical scales and raw color columns:
import altair as alt
from okpalette import create_palette
categories = ["control", "treated", "outlier"]
colors = create_palette(len(categories))
chart = alt.Chart(data).mark_point().encode(
x="x:Q",
y="y:Q",
color=alt.Color(
"group:N",
scale=alt.Scale(domain=categories, range=colors),
),
)
# When a dataframe column already contains okpalette hex strings:
chart = alt.Chart(data).mark_point().encode(
x="x:Q",
y="y:Q",
color=alt.Color("color:N", scale=None),
)
Plotly Express discrete sequences and maps:
import plotly.express as px
from okpalette import create_palette
categories = ["control", "treated", "outlier"]
colors = create_palette(len(categories))
color_map = dict(zip(categories, colors))
fig = px.scatter(
data,
x="x",
y="y",
color="group",
color_discrete_map=color_map,
)
Tune Appearance
Background Contrast
By default, palettes are generated without a background constraint. Pass both
background and background_contrast when you want colors separated from a
known plotting background. Use "normal" for the OKLab background-separation
heuristic, or "high" / "wcag" for WCAG 2.2 non-text contrast of at least
3.0:1 against every configured background color.
colors = create_palette(
32,
background="#ffffff",
background_contrast="normal",
)
Avoid other colors:
colors = create_palette(
16,
avoid_colors=["#000000"],
background=["#ffffff", "#f2f2f2"],
background_contrast="high",
)
background accepts one color or a sequence of colors and filters candidates
against those backgrounds. avoid_colors keeps exact colors out of the palette
and uses them as distance anchors.
Colorblind-aware generation
Opt into colorblind-aware generation when pairwise palette separability should be tested under selected color vision deficiency simulations:
colors = create_palette(12, colorblind_mode="all")
CLI:
okpalette create 12 --colorblind-mode red-green
okpalette create 12 --colorblind-mode all
colorblind_mode may be None, "protan", "deutan", "tritan",
"red-green", or "all". The "red-green" mode optimizes against protan and
deutan simulations without including tritan; "daltonism" is accepted as a
compatibility alias, but "red-green" is the preferred spelling. The feature
uses Machado, Oliveira, and Fernandes 2009 severity-1.0 matrices on linear
sRGB and optimizes the worst-case OKLab distance across ordinary vision and the
selected simulations. It does not make a palette universally accessible or
colorblind-safe. If you also set
background_contrast="high" or "wcag", WCAG contrast is still checked against
the ordinary sRGB background, not against simulated colors.
Limit hue ranges
warm = create_palette(10, hue=(330, 100))
cool = create_palette(10, hue=(150, 280))
Common constraints:
muted = create_palette(12, chroma=(0.02, 0.12))
bright = create_palette(12, chroma=(0.10, None))
mid_lightness = create_palette(12, lightness=(0.30, 0.80))
lightness is OKLab L in 0..1. hue is OKLCH degrees in 0..360; ranges can wrap around zero.
Preview And Save
from okpalette import create_palette, save_palette, view_palette
colors = create_palette(12)
view_palette(colors)
save_palette(colors, "palette.svg")
save_palette(colors, "palette.png")
view_palette() works in notebooks through _repr_svg_() and _repr_png_().
For raw preview bytes:
from okpalette import palette_png, palette_svg
svg = palette_svg(colors)
png = palette_png(colors)
Grid Size
grid_size controls how many candidate colors are searched.
quick = create_palette(24, grid_size="coarse") # step 16
default = create_palette(24, grid_size="medium") # step 8
fine = create_palette(24, grid_size="fine") # step 4
custom = create_palette(24, grid_size=12)
If constraints leave too few candidates, okpalette raises ValueError with a hint
to relax lightness, chroma, hue, or grid_size.
API
create_palette(
palette_size,
*,
seed_colors=(),
avoid_colors=None,
background=None,
background_contrast=None,
lightness=(0.20, 0.90),
chroma=(0.04, None),
hue=None,
grid_size="medium",
lightness_weight=1.0,
chroma_weight=1.0,
colorblind_mode=None,
format="hex",
)
extend_palette(
colors,
target_size,
*,
include_existing=True,
**create_palette_options,
)
create_label_palette(
positions,
labels,
*,
fixed_colors=None,
seed_colors=(),
avoid_colors=None,
background=None,
background_contrast=None,
lightness=(0.20, 0.90),
chroma=(0.04, None),
hue=None,
grid_size="medium",
lightness_weight=1.0,
chroma_weight=1.0,
colorblind_mode=None,
neighbors=8,
max_points=50_000,
format="hex",
)
create_label_palette_from_columns(data, *, positions, label, **create_label_palette_options)
view_palette(palette, *, width=1246, height=154)
palette_svg(palette, *, width=1246, height=154)
palette_png(palette, *, width=1246, height=154)
save_palette(palette, path, *, width=1246, height=154)
How It Works
okpalette uses a greedy Glasbey-style algorithm. It starts with anchor colors
such as seeds, avoid colors, and the background, then repeatedly chooses the
candidate color that is farthest from the nearest anchor or selected color.
Distances are measured in OKLab. Lightness, chroma, and hue constraints are applied through OKLab and OKLCH before colors are selected.
When colorblind_mode is enabled, candidate distances are scored as the minimum
of ordinary OKLab distance and the OKLab distance after each selected Machado
2009 severity-1.0 simulation. This is a generation objective for palettes
tested under selected CVD simulations, not an accessibility certification.
For label palettes, okpalette builds a weighted label-neighborhood graph from
the input positions, then uses that graph while choosing and assigning colors.
The default max_points=50_000 bounds graph construction for large datasets;
use max_points=None to opt into all-points preprocessing.
The result is deterministic, fast, and stable when extending a palette. It is not a global optimizer.
Python And Wheels
okpalette supports Python 3.12 and newer. Prebuilt wheels use the Python
cp312-abi3 stable ABI and are built for Linux x86_64 and aarch64
manylinux2014, macOS aarch64, and Windows x64.
Other platforms install from the source distribution when a wheel is not available, which requires Rust and the normal Python build toolchain. Intel macOS, musllinux, Windows ARM64, and other secondary targets are not prebuilt wheel targets.
References
These references describe the methods and standards that inform okpalette.
They do not make generated palettes WCAG-compliant, colorblind-safe, or globally
optimal.
- Glasbey-style categorical palette generation: lmcinnes/glasbey and the Colorcet categorical guide.
- OKLab and OKLCH: A perceptual color space for image processing and MDN OKLCH documentation.
- WCAG 2.2 contrast: WCAG 2.2, Non-text Contrast, and relative luminance.
- Color vision deficiency simulation: Machado, Oliveira, and Fernandes 2009 and the CVD simulation project page.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 okpalette-1.0.0.tar.gz.
File metadata
- Download URL: okpalette-1.0.0.tar.gz
- Upload date:
- Size: 2.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bde9af830ba8dafe9cdc762fc0b54f036f7dbad516a8aa993fca1bb7971c389
|
|
| MD5 |
cca7a621874159dad3733b8ad9d20ff8
|
|
| BLAKE2b-256 |
0248aee99390b9dfe737da250714a13e3999f8394f44819c345dfa51a03ced6e
|
Provenance
The following attestation bundles were made for okpalette-1.0.0.tar.gz:
Publisher:
release.yml on pmbaumgartner/okpalette
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
okpalette-1.0.0.tar.gz -
Subject digest:
1bde9af830ba8dafe9cdc762fc0b54f036f7dbad516a8aa993fca1bb7971c389 - Sigstore transparency entry: 1615430683
- Sigstore integration time:
-
Permalink:
pmbaumgartner/okpalette@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pmbaumgartner
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Trigger Event:
push
-
Statement type:
File details
Details for the file okpalette-1.0.0-cp312-abi3-win_amd64.whl.
File metadata
- Download URL: okpalette-1.0.0-cp312-abi3-win_amd64.whl
- Upload date:
- Size: 341.1 kB
- Tags: CPython 3.12+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6912a80754861d334290067d38315ba6a4ece5e7c9cc6e4b4d1bba66efe9acf
|
|
| MD5 |
f504aff899b499a24c3b9cf17fdf6c2b
|
|
| BLAKE2b-256 |
379811d4128e0b9044780a80e044c41d9e340a33a32d903643a998badd180e31
|
Provenance
The following attestation bundles were made for okpalette-1.0.0-cp312-abi3-win_amd64.whl:
Publisher:
release.yml on pmbaumgartner/okpalette
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
okpalette-1.0.0-cp312-abi3-win_amd64.whl -
Subject digest:
e6912a80754861d334290067d38315ba6a4ece5e7c9cc6e4b4d1bba66efe9acf - Sigstore transparency entry: 1615430709
- Sigstore integration time:
-
Permalink:
pmbaumgartner/okpalette@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pmbaumgartner
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Trigger Event:
push
-
Statement type:
File details
Details for the file okpalette-1.0.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: okpalette-1.0.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 514.1 kB
- Tags: CPython 3.12+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bca1a9029b00c82fa3d08e0a19e4bafef61d49221ac411b5a39f71ef58801078
|
|
| MD5 |
615aac30e0e938f69d55c893544a34af
|
|
| BLAKE2b-256 |
355f8182b2edf3376ec27d42f169195fab4d721ca4a7c491c3856ec6b9574c60
|
Provenance
The following attestation bundles were made for okpalette-1.0.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on pmbaumgartner/okpalette
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
okpalette-1.0.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
bca1a9029b00c82fa3d08e0a19e4bafef61d49221ac411b5a39f71ef58801078 - Sigstore transparency entry: 1615430694
- Sigstore integration time:
-
Permalink:
pmbaumgartner/okpalette@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pmbaumgartner
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Trigger Event:
push
-
Statement type:
File details
Details for the file okpalette-1.0.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: okpalette-1.0.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 498.2 kB
- Tags: CPython 3.12+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aefd456170a9cdf11a5ccaa6393c3f8b44b4f1cadf3fcdc8563bce59bbc0d5d3
|
|
| MD5 |
619ec19d82b3bdc68ffd7a3aae7adbc2
|
|
| BLAKE2b-256 |
d2bfde770dab5b8374ea34d304baf489a998e4ffe02886ca34935e34bccf1a34
|
Provenance
The following attestation bundles were made for okpalette-1.0.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on pmbaumgartner/okpalette
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
okpalette-1.0.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
aefd456170a9cdf11a5ccaa6393c3f8b44b4f1cadf3fcdc8563bce59bbc0d5d3 - Sigstore transparency entry: 1615430704
- Sigstore integration time:
-
Permalink:
pmbaumgartner/okpalette@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pmbaumgartner
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Trigger Event:
push
-
Statement type:
File details
Details for the file okpalette-1.0.0-cp312-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: okpalette-1.0.0-cp312-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 448.2 kB
- Tags: CPython 3.12+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8545f6ba25ddc91b26ea1f943bd44d6a61ce95997ac093f08d8dbd3a8b24e6d7
|
|
| MD5 |
c66cd85058fef292393e539609f0b285
|
|
| BLAKE2b-256 |
25bcb4394a3dd60514201591fcbc2d0637835741928ac454c93d453020ad7d39
|
Provenance
The following attestation bundles were made for okpalette-1.0.0-cp312-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on pmbaumgartner/okpalette
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
okpalette-1.0.0-cp312-abi3-macosx_11_0_arm64.whl -
Subject digest:
8545f6ba25ddc91b26ea1f943bd44d6a61ce95997ac093f08d8dbd3a8b24e6d7 - Sigstore transparency entry: 1615430688
- Sigstore integration time:
-
Permalink:
pmbaumgartner/okpalette@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/pmbaumgartner
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b3895b3dadc0663bde3427d112cc3256e13a2c45 -
Trigger Event:
push
-
Statement type: