Getting Started

This tutorial demonstrates the main features of qualpal for generating and working with color palettes.

Installation

Qualpal is on PyPi and can be installed via pip:

pip install qualpal

If you want visualization support (requires matplotlib), install with:

pip install qualpal[viz]

Generating Palettes

The main API entry point is the Qualpal class, which generates color palettes through the generate method.

from qualpal import Qualpal

qp = Qualpal()

palette = qp.generate(6)

Here, we have generated a palette with 6 distinct colors. The palette class supports a rich HTML representation in Jupyter notebooks, so printing it will show the colors visually:

palette
#eba5fc
#026808
#af0d27
#035079
#0bf2dc
#ffbb44

Customizing the Color Space

By default, qualpal generates colors across the full HSL color space, but you can restrict the hue, saturation, and lightness ranges to create specific styles of palettes.

qp_pastel = Qualpal(
    colorspace={
        'h': (0, 360),   # Full hue range
        's': (0.3, 0.6), # Low saturation
        'l': (0.7, 0.9)  # High lightness
    }
)

qp_pastel.generate(5)
#e1cd8d
#88e0d5
#8b8adf
#e09187
#dfd7ec

Or create a palette with only warm colors:

qp_warm = Qualpal(
    colorspace={
        'h': (-20, 60),
        's': (0.5, 1.0),
        'l': (0.4, 0.7)
    }
)

qp_warm.generate(5)
#fef93b
#9d3142
#a78929
#f85901
#de879d

As you can see, negative hue values wrap around, so -20 corresponds to 340 degrees.

Alternative Inputs

Qualpal supports two other modes of input: a list of candidate colors to choose from or one of the built-in color palettes that are available. To use candidate colors, provide a list of hex color strings:

candidates = ["#e6194b", "#3cb44b", "#ffe119", "#4363d8",
              "#f58231", "#911eb4", "#46f0f0", "#f032e6",
              "#bcf60c", "#fabebe", "#008080", "#e6beff",
              "#9a6324", "#fffac8", "#800000", "#aaffc3",
              "#808000", "#ffd8b1", "#000075", "#808080"]

qp_candidates = Qualpal(colors=candidates)
qp_candidates.generate(3)
#000075
#e6beff
#bcf60c

You can also use built-in palettes by name:

qp_builtin = Qualpal(palette="Tableau:10")
qp_builtin.generate(5)
#4e79a7
#edc948
#59a14f
#ff9da7
#9c755f

For a list of available built-in palettes, you can call list_palettes(), which returns a dictionary of palette names and their descriptions.

from qualpal import list_palettes, Palette

available_palettes = list_palettes()
available_palettes["Rembrandt"] # Example built-in palette
['AnatomyLesson', 'Staalmeesters']
from qualpal import get_palette

get_palette("Rembrandt:Staalmeesters")
#a13826
#701b06
#4c3114
#7a491e
#d7e1d6
#060a0d
#d39c7b

As you can see from above, built-in palettes are specified using the format Domain:Name, so that Rembrandt:Staalmeesters refers to the “Staalmeesters” palette from the “Rembrandt” collection.

Working with Palettes

Palettes are represented by the Palette class, which supports indexing, iteration, and length retrieval.

from qualpal import Palette

# Create palette from hex colors
pal = Palette(["#ff0000", "#00ff00", "#0000ff", "#ffff00"])

# Access colors by index
first_color = pal[0]
print(f"First color: {first_color.hex()}")

# Iterate over colors
print("\nAll colors:")
for i, color in enumerate(pal):
    print(f"  {i}: {color.hex()}")

# Get length
print(f"\nPalette size: {len(pal)}")
First color: #ff0000

All colors:
  0: #ff0000
  1: #00ff00
  2: #0000ff
  3: #ffff00

Palette size: 4

Indexing a single color returns a Color object, which supports various color operations.

Palette Analysis

Qualpal also provides methods to analyze palettes, such as measuring the perceptual distances between colors.

# Minimum pairwise distance
min_dist = pal.min_distance()
print(f"Minimum distance: {min_dist:.2f}")

# Distance to each nearest neighbor
min_dists = pal.min_distances()
print(f"Distances: {[f'{d:.2f}' for d in min_dists]}")

# Full distance matrix
matrix = pal.distance_matrix()
print(f"\nDistance matrix shape: {len(matrix)}x{len(matrix[0])}")
print(f"Distance from color 0 to color 1: {matrix[0][1]:.2f}")

matrix
Minimum distance: 23.40
Distances: ['52.88', '23.40', '52.88', '23.40']

Distance matrix shape: 4x4
Distance from color 0 to color 1: 86.61
[[0.0, 86.60823745353736, 52.88136803127366, 64.30086090099867],
 [86.60823745353736, 0.0, 83.18587765548577, 23.404244339728105],
 [52.88136803127366, 83.18587765548577, 0.0, 103.4269788927728],
 [64.30086090099867, 23.404244339728105, 103.4269788927728, 0.0]]

Exporting Palettes

Qualpal supports exporting palettes in various formats. For example, CSS variables:

pal.to_css(prefix="brand")
['--brand-1: #ff0000;',
 '--brand-2: #00ff00;',
 '--brand-3: #0000ff;',
 '--brand-4: #ffff00;']

There is also support for JSON export:

import json
json_str = pal.to_json()
print(f"JSON: {json_str}")

# Can be used in configs
config = {
    "theme": {
        "colors": json.loads(json_str)
    }
}
print(f"\nConfig: {config}")
JSON: ["#ff0000", "#00ff00", "#0000ff", "#ffff00"]

Config: {'theme': {'colors': ['#ff0000', '#00ff00', '#0000ff', '#ffff00']}}

CVD-Aware Palette Generation

You can generate palettes that are optimized for viewers with color vision deficiencies (CVD). To do this, provide a cvd dictionary specifying the type and severity of CVD to account for.

Here, we generate a palette that is safe for deuteranomaly (the most common form of CVD):

qp_cvd = Qualpal(
    cvd={'deutan': 0.7}  # 70% severity deuteranomaly
)

qp_cvd.generate(5)
#f68ec8
#233604
#15045a
#13cbf6
#ebf919

Account for Background Color

When generating palettes, you can also specify a background color to ensure good contrast. This is especially useful for generating palettes for dark backgrounds. To do so, provide a background hex color string when creating the Qualpal instance.

Here, we create a palette optimized for dark backgrounds:

qp_dark = Qualpal(background="#1a1a1a")
qp_dark.generate(4)
#bcfd67
#f8bdef
#4b0252
#003f0e

Compare this to a palette optimized for light backgrounds:

qp_light = Qualpal(background="#ffffff")
qp_light.generate(4)
#bcfd67
#f8bdef
#4b0252
#003f0e

Complete Example: Accessible Data Visualization Palette

Let’s create a complete palette suitable for accessible data visualization:

# Requirements:
# - 8 distinct colors
# - Work on white background
# - Safe for deuteranomaly (most common CVD)
# - Avoid very light or very dark colors

qp_accessible = Qualpal(
    colorspace={
        'h': (0, 360),
        's': (0.4, 0.9),
        'l': (0.3, 0.7)
    },
    background="#ffffff",
    cvd={'deutan': 0.5}
)

accessible_palette = qp_accessible.generate(8)

print("Accessible visualization palette:")
for i, color in enumerate(accessible_palette, 1):
    rgb = color.rgb255()
    print(f"  {i}. {color.hex()}  RGB{rgb}")

print(f"\nMinimum distance: {accessible_palette.min_distance():.2f}")
print("(Higher is better - minimum recommended: 30)")

accessible_palette
Accessible visualization palette:
  1. #4a732c  RGB(74, 115, 44)
  2. #f69263  RGB(246, 146, 99)
  3. #93190a  RGB(147, 25, 10)
  4. #318af2  RGB(49, 138, 242)
  5. #32f2ea  RGB(50, 242, 234)
  6. #e9ea16  RGB(233, 234, 22)
  7. #0e0c93  RGB(14, 12, 147)
  8. #f319b6  RGB(243, 25, 182)

Minimum distance: 38.51
(Higher is better - minimum recommended: 30)
#4a732c
#f69263
#93190a
#318af2
#32f2ea
#e9ea16
#0e0c93
#f319b6

Visualization

If you have matplotlib installed, you can visualize palettes using the show method, which returns a figure with color swatches.

# Display color swatches
fig = pal.show()
_images/07d2d43ca3953011a1329d297bb7121eb83a63a2a17a782ab7ae258d4d0f26b8.png

If you like, you can add labels of the hex codes to the swatches:

fig = pal.show(labels=True)
_images/8b033772d300b20e7f34e17c08cb6d2a7f7543292e682aef27fe5b819e94fb87.png

You can also provide custom labels:

# With custom labels
fig = pal.show(labels=["Primary", "Success", "Info", "Warning"])
_images/c8cfd58bdfe9bde2105b9ea311c3637c991464963210b3ecc833580613b133ce.png

The figures that are returned are matplotlib Figure objects and can be further customized or saved:

fig.savefig("palette.png", bbox_inches="tight")

For more details, see the API documentation.