Color class

Most plots do not need the Color class: a named token, palette, or colormap is usually enough. When you do need to create, adjust, and export one color, the shortest path is:

Modeled relative CIE Y (relative_y) is calculated from nominal D65 sRGB; it is not a measurement of a particular display, perceived brightness, or OKLab L.

color = dm.oklch(0.7, 0.15, 150)
color.oklch.C *= 1.2
hex_value = color.to_hex()

L is the model’s lightness coordinate, C controls how colorful or muted the color is, and h chooses the hue angle.

The Color class is the programmatic fourth entry point in the design system: use it when a named token, discrete palette, or registered colormap is not the right shape. It is the engine behind color construction, conversion, manipulation, interpolation, and custom colormap registration.

dartwork-mpl uses OKLab and OKLCH — perceptual color spaces designed so numeric distances track visual differences more consistently than RGB. They are useful approximations, not a promise that every equal distance looks exactly equal to every observer. This page covers:

  • Creating Color objects from any format

  • Converting between color spaces

  • Modifying individual color channels

  • Interpolating smooth gradients (and why the color space matters)

  • Building custom colormaps for matplotlib

Why color spaces matter

Traditional RGB mixes colors the way a screen does — by blending light channels. That’s convenient for hardware but terrible for human perception. Two colors that are numerically “equidistant” in RGB can look wildly different to the eye.

Perceptual spaces such as OKLab and OKLCH reduce this mismatch. Operations like blending, lightening, and interpolation are usually more consistent than RGB component arithmetic, while endpoint choice, gamut mapping, viewing conditions, and individual observers still matter.

Note

OKLab and OKLCH are two coordinate views of the same underlying model, not two competing construction rules. Think of the model as a map: OKLab’s a and b are rectangular map axes, while OKLCH converts those axes into distance from the center C and angle h.

Tip

If you’re new to color spaces, don’t worry about the math — dartwork-mpl handles all conversions automatically. Just pick the constructor that matches your input format, and use OKLCH when you need to adjust hue or saturation.

Color object

The Color class is a unified interface for working with colors across multiple color spaces. Internally, colors are stored as OKLab coordinates, which enables efficient and accurate conversions.

import dartwork_mpl as dm

# All Color objects are stored internally as OKLab
color = dm.oklab(0.7, 0.1, 0.2)
print(color)  # Color(oklab=(0.7000, 0.1000, 0.2000))

View objects (color.oklab, color.oklch, color.rgb) provide convenient attribute-based access for reading, writing, unpacking, and indexing color components.

Creating Color objects

You can create Color objects from any supported format. Pick whichever matches your input — all methods produce identical Color instances that can be freely converted.

Live Universal Constructor
Pick a master color to see exactly how to generate it across all Python formats.
#F2711C (Click to change)
OKLab Default
dm.oklab(0.680, 0.140, 0.120)
OKLCH Intuitive
dm.oklch(0.680, 0.184, 40.6)
RGB 0–1
dm.rgb(0.949, 0.443, 0.110)
RGB 0–255
dm.rgb(242, 113, 28)
Hexadecimal
dm.hex('#f2711c')

From OKLab coordinates

import dartwork_mpl as dm

# L: lightness (0–1), a: green↔red, b: blue↔yellow
color = dm.oklab(0.7, 0.1, 0.2)

From OKLCH coordinates

import dartwork_mpl as dm

# L: lightness (0–1), C: chroma (≥0), h: hue in degrees (0–360)
color = dm.oklch(0.7, 0.2, 120)  # Greenish color

OKLCH uses degrees for hue, making it intuitive: 0° = red, 120° = green, 240° = blue. This is the most natural space for adjusting saturation (chroma) and hue independently.

From RGB values

import dartwork_mpl as dm

# Automatically detects range: 0–1 or 0–255
color1 = dm.rgb(0.8, 0.2, 0.3)    # 0–1 range (common in matplotlib)
color2 = dm.rgb(200, 50, 75)      # 0–255 range (auto-detected)

From hex strings

import dartwork_mpl as dm

color1 = dm.hex("#ff5733")
color2 = dm.hex("#f73")  # Short format also supported

From matplotlib color names

import dartwork_mpl as dm

# Works with any matplotlib color name
color1 = dm.color("red")
color2 = dm.color("dc.teal2")      # dartwork Color palette
color3 = dm.color("tw.blue500")    # Tailwind colors

Color space conversion

Once you have a Color object, convert it to any supported space. Pick the method that suits your workflow.

Using conversion methods

import dartwork_mpl as dm

color = dm.hex("#ff5733")

# Returns tuples
L, a, b = color.to_oklab()        # OKLab coordinates
L, C, h = color.to_oklch()        # OKLCH (h in degrees)
r, g, b = color.to_rgb()          # RGB (0–1 range)
hex_str = color.to_hex()          # Hex string

Copying colors

Use copy() to create an independent clone. Without it, modifications would affect the original:

import dartwork_mpl as dm

color = dm.oklch(0.7, 0.2, 120)
brighter = color.copy()
brighter.oklab.L += 0.1  # original is unchanged

print(color.oklab.L)      # 0.7
print(brighter.oklab.L)   # 0.8

Color space overview

Space

Type

Components

Best for

OKLab

Cartesian

L (lightness), a, b

Blending, interpolation, color math

OKLCH

Polar

L, C (chroma), h (hue°)

Adjusting saturation/hue independently

RGB

Device

r, g, b (0–1)

Display output, matplotlib compatibility

Hex

String

#RRGGBB

CSS, web, copy-paste

Tip

When should I use which?

  • OKLCH for creative work — it’s the most intuitive for adjusting how vivid (chroma) or what shade (hue) a color is.

  • OKLab for programmatic operations — blending, distance calculations, and interpolation.

  • RGB/Hex mainly for output — passing colors to matplotlib or the web.

Color interpolation with cspace

The cspace() function generates smooth color gradients by interpolating between two colors in a specified color space. Think of it as np.linspace for colors.

Why does the color space matter? Try the demo below with the default purple → yellow pair. Notice how:

  • RGB produces a muddy gray-brown in the middle (the “dead zone”)

  • OKLab stays smooth but can look washed out

  • OKLCH maintains vivid hues throughout — it takes the scenic route around the color wheel

Color interpolation comparison across RGB, OKLab, and OKLCH
import dartwork_mpl as dm

dm.style.use("scientific")

# Interpolate in OKLCH (default perceptual path)
colors_oklch = dm.cspace("#ff5733", "#33ff57", n=10, space="oklch")

# Interpolate in OKLab
colors_oklab = dm.cspace("#ff5733", "#33ff57", n=10, space="oklab")

# Interpolate in RGB (often produces muddy midpoints)
colors_rgb = dm.cspace("#ff5733", "#33ff57", n=10, space="rgb")

The function accepts Color objects or hex strings:

import dartwork_mpl as dm

start = dm.color("dc.teal2")
end = dm.hex("#ff5733")
gradient = dm.cspace(start, end, n=20, space="oklch")

Creating custom colormaps

Use cspace() to build custom matplotlib colormaps with smooth OKLab/OKLCH paths. This often avoids the muddy midpoint of RGB component interpolation.

Technical detail

Unlike the shipped dc.* catalog, arbitrary custom endpoints are not automatically gated for ΔEOK variation, modeled-relative-Y topology, ΔE00, or CVD. See Validation for the shipped checks and validation workflow.

Try it live: Build your own colormap below. Toggle between Sequential and Diverging modes, pick your colors, and copy the generated Python code.

Colormap Builder
#1A237E
#FF6F00
colors = dm.cspace("#1A237E", "#FF6F00", n=256, space="oklch")

Sequential colormaps

A sequential colormap ramps from one color to another — ideal for magnitude, density, or probability maps:

import dartwork_mpl as dm
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

dm.style.use("scientific")

# Create a custom sequential colormap
colors = dm.cspace("#1a237e", "#ff6f00", n=256, space="oklch")
cmap = mcolors.ListedColormap([c.to_rgb() for c in colors],
                               name="custom_blue_orange")

# Use it in a plot
fig, ax = plt.subplots(figsize=dm.figsize("15cm", "9cm"))
data = np.random.randn(100, 100)
im = ax.imshow(data, cmap=cmap)
plt.colorbar(im, ax=ax, label="Value")
dm.simple_layout(fig)

Diverging colormaps

For data with a meaningful midpoint (e.g., positive vs. negative), create a diverging colormap by interpolating through a neutral center:

import dartwork_mpl as dm
import matplotlib.colors as mcolors

# Diverging: blue → white → red
colors1 = dm.cspace("#1a237e", "#ffffff", n=128, space="oklch")
colors2 = dm.cspace("#ffffff", "#c62828", n=128, space="oklch")
colors = colors1[:-1] + colors2  # Remove duplicate white
cmap_div = mcolors.ListedColormap([c.to_rgb() for c in colors],
                                   name="custom_diverging")

Registering colormaps

To make your custom colormap available globally in your session:

import matplotlib as mpl

mpl.colormaps.register(cmap=cmap)
# Now you can use it anywhere: plt.imshow(data, cmap="custom_blue_orange")

Quick reference

Task

Code

Create from OKLab

dm.oklab(L, a, b)

Create from OKLCH

dm.oklch(L, C, h) — h in degrees

Create from RGB

dm.rgb(r, g, b) — auto-detects 0–1 vs 0–255

Create from hex

dm.hex("#ff5733")

Create from name

dm.color("dc.teal2") — also accepts hex / rgb(…) / oklch(…) / oklab(…)

Convert to tuple

color.to_oklab(), .to_oklch(), .to_rgb()

Convert to hex

color.to_hex()

Read/write components

color.oklch.C *= 1.2, color.rgb.r = 0.9

Copy

color.copy()

Interpolate

dm.cspace(start, end, n=10, space="oklch")

See also