Skip to contents

A discrete color palette generator with support for fixed colors, optimized for color vision deficient viewers. Features different optimization algorithms and a multi-objective optimization framework for advanced color palette generation.

Installation

You can install the development version of huerd from GitHub with:

# install.packages("pak")
pak::pak("sims1253/huerd")

Basic Usage

Generate a palette with 8 colors using either the standard or quick method:

library(huerd)

set.seed(42)
# Standard generation with full control
palette <- generate_palette(8, progress = FALSE)
print(palette)
#> 
#> -- huerd Color Palette (8 colors) --
#> Colors:
#> [ 1] #371D00
#> [ 2] #483E00
#> [ 3] #7C00D2
#> [ 4] #757800
#> [ 5] #AF4D88
#> [ 6] #0096C7
#> [ 7] #FF004B
#> [ 8] #00DFC3
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.111
#> * Optimizer Performance Ratio      : 35.9%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.096
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 693
#> * Optimizer Status: NLOPT_XTOL_REACHED: Optimization stopped because xtol_rel or xtol_abs (above) was reached.

# Quick generation for immediate use
quick_palette <- quick_palette(8)
print(quick_palette)
#> 
#> -- huerd Color Palette (8 colors) --
#> Colors:
#> [ 1] #003C00
#> [ 2] #740084
#> [ 3] #5320F5
#> [ 4] #FF0000
#> [ 5] #00CB99
#> [ 6] #FF5EFF
#> [ 7] #00F8FF
#> [ 8] #FFDE51
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.163
#> * Optimizer Performance Ratio      : 52.7%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.144
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 623
#> * Optimizer Status: NLOPT_XTOL_REACHED: Optimization stopped because xtol_rel or xtol_abs (above) was reached.

Visualize your palette:

library(huerd)

set.seed(42)
palette <- generate_palette(8, progress = FALSE)
plot(palette, type = "swatches")

ggplot2 Integration

Use huerd palettes directly in your ggplot2 visualizations:

library(ggplot2)
library(huerd)

# Create a huerd palette
set.seed(42)
huerd_colors <- generate_palette(5, progress = FALSE)

# Example with iris data using scale_color_huerd()
ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) +
  geom_point(size = 3) +
  scale_color_huerd(palette = huerd_colors) +
  theme_minimal() +
  labs(title = "Iris Dataset with huerd Colors")


# Example with mtcars data using scale_fill_huerd()
ggplot(mtcars, aes(x = factor(cyl), fill = factor(cyl))) +
  geom_bar() +
  scale_fill_huerd(palette = huerd_colors) +
  theme_minimal() +
  labs(title = "Car Cylinder Count with huerd Colors",
       x = "Number of Cylinders", y = "Count")

Convenience Functions

Access pre-made palettes and export options for different workflows:

library(huerd)

# Get a quick palette without generation
quick_colors <- quick_palette(6)
print(quick_colors)
#> 
#> -- huerd Color Palette (6 colors) --
#> Colors:
#> [ 1] #4B0000
#> [ 2] #00718B
#> [ 3] #C10000
#> [ 4] #FF00FF
#> [ 5] #FAB800
#> [ 6] #00FFFF
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.270
#> * Optimizer Performance Ratio      : 73.9%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.178
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 596
#> * Optimizer Status: NLOPT_XTOL_REACHED: Optimization stopped because xtol_rel or xtol_abs (above) was reached.

# Access the default brand palette
brand_colors <- brand_palette(c("#003366", "#FF6600"), n_total = 6)
print(brand_colors)
#> 
#> -- huerd Color Palette (6 colors) --
#> Colors:
#> [ 1] #003366
#> [ 2] #854700
#> [ 3] #006D91
#> [ 4] #AE7BFB
#> [ 5] #FF6600
#> [ 6] #A1EB9F
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.182
#> * Optimizer Performance Ratio      : 49.9%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.180
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 276
#> * Optimizer Status: NLOPT_XTOL_REACHED: Optimization stopped because xtol_rel or xtol_abs (above) was reached.

# Export palette in different formats for web development
color_names <- paste0("color_", seq_along(quick_colors))
css_output <- export_palette(quick_colors, format = "css", names = color_names)
cat("CSS Output:\n", css_output, "\n\n")
#> CSS Output:
#>  :root {
#>   --color_1: #4B0000;
#>   --color_2: #00718B;
#>   --color_3: #C10000;
#>   --color_4: #FF00FF;
#>   --color_5: #FAB800;
#>   --color_6: #00FFFF;
#> }

sass_output <- export_palette(quick_colors, format = "sass", names = color_names)
cat("Sass Output:\n", sass_output, "\n\n")
#> Sass Output:
#>  $color_1: #4B0000;
#> $color_2: #00718B;
#> $color_3: #C10000;
#> $color_4: #FF00FF;
#> $color_5: #FAB800;
#> $color_6: #00FFFF;

json_output <- export_palette(quick_colors, format = "json", names = color_names)
cat("JSON Output:\n", json_output, "\n")
#> JSON Output:
#>  {
#>     "color_1": "#4B0000",
#>     "color_2": "#00718B",
#>     "color_3": "#C10000",
#>     "color_4": "#FF00FF",
#>     "color_5": "#FAB800",
#>     "color_6": "#00FFFF"
#> }

# Interpret palette quality metrics
quality_info <- interpret_palette_quality(quick_colors)
print(quality_info)
#> 
#> ── Palette Quality Assessment ──
#> 
#> This 6-color palette is highly optimized (74% of theoretical maximum).
#> Excellent - colors are highly distinct and easy to differentiate
#> 
#> ── Distinctness
#> Excellent - colors are highly distinct and easy to differentiate
#> 
#> ── Accessibility
#> Excellent - palette is safe for most color vision deficiencies

Constrained Color Palettes

Include specific colors while optimizing the remaining colors:

library(huerd)

set.seed(123)
palette <- generate_palette(
  n = 8,
  include_colors = c("#4A6B8A", "#E5A04C"),
  progress = FALSE
)
print(palette)
#> 
#> -- huerd Color Palette (8 colors) --
#> Colors:
#> [ 1] #1B1000
#> [ 2] #5F4151
#> [ 3] #4A6B8A
#> [ 4] #EA0000
#> [ 5] #008ED7
#> [ 6] #FF00CB
#> [ 7] #E5A04C
#> [ 8] #FCADFF
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.131
#> * Optimizer Performance Ratio      : 42.4%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.101
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 370
#> * Optimizer Status: NLOPT_XTOL_REACHED: Optimization stopped because xtol_rel or xtol_abs (above) was reached.

Multi-Optimizer Support

Choose from 5 different optimization algorithms based on your needs:

library(huerd)

set.seed(456)
# COBYLA: Default deterministic optimizer for general use
cobyla_palette <- generate_palette(6, optimizer = "nloptr_cobyla", progress = FALSE)

# SANN: Stochastic simulated annealing for higher quality
sann_palette <- generate_palette(6, optimizer = "sann", progress = FALSE)

# DIRECT: Global optimization for reproducibility (may need tuning)
direct_palette <- generate_palette(6, optimizer = "nlopt_direct", progress = FALSE)

# Nelder-Mead: Derivative-free local optimization
# As an alternative deterministic approach
neldermead_palette <- generate_palette(6, optimizer = "nlopt_neldermead", progress = FALSE)

# L-BFGS: Gradient-based optimization for smooth objectives (v0.5.0+)
lbfgs_palette <- generate_palette(6, optimizer = "nlopt_lbfgs", 
                                  weights = c(smooth_repulsion = 1), progress = FALSE)

cat("COBYLA:", paste(cobyla_palette, collapse = ", "), "\n")
#> COBYLA: #100405, #960081, #008700, #FF2BBD, #00C700, #00FFFF
cat("SANN:", paste(sann_palette, collapse = ", "), "\n")
#> SANN: #000C02, #770000, #B20070, #FF0000, #FFB5FF, #A8FF00
cat("DIRECT:", paste(direct_palette, collapse = ", "), "\n")
#> DIRECT: #636363, #636363, #636363, #636363, #636363, #636363
cat("Nelder-Mead:", paste(neldermead_palette, collapse = ", "), "\n")
#> Nelder-Mead: #2F00E4, #0089A1, #FF0000, #FF48FF, #00C99E, #00FCFF
cat("L-BFGS:", paste(lbfgs_palette, collapse = ", "), "\n")
#> L-BFGS: #003700, #2E0079, #000092, #FF0000, #FF00FF, #00FFFF

Multi-Objective Framework

The package includes a multi-objective optimization framework with both discrete and smooth optimization support:

library(huerd)

set.seed(789)
# Discrete distance optimization (default)
distance_palette <- generate_palette(
  n = 6,
  weights = c(distance = 1),  # Explicit distance weighting
  optimizer = "nloptr_cobyla",
  progress = FALSE
)

# Smooth optimization for faster convergence (v0.5.0+)
smooth_palette <- generate_palette(
  n = 8,
  weights = c(smooth_repulsion = 1),  # Smooth repulsion objective
  optimizer = "nlopt_lbfgs",          # L-BFGS for gradient-based optimization
  progress = FALSE
)

# Alternative smooth objective using log-sum-exp
logsumexp_palette <- generate_palette(
  n = 6,
  weights = c(smooth_logsumexp = 1),
  optimizer = "nlopt_lbfgs",
  progress = FALSE
)

# Compare optimization results
cat("Distance-based palette:\n")
#> Distance-based palette:
print(distance_palette)
#> 
#> -- huerd Color Palette (6 colors) --
#> Colors:
#> [ 1] #002B00
#> [ 2] #9B0000
#> [ 3] #C80000
#> [ 4] #FF0000
#> [ 5] #0095FF
#> [ 6] #00DDC2
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.097
#> * Optimizer Performance Ratio      : 26.6%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.067
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 407
#> * Optimizer Status: NLOPT_XTOL_REACHED: Optimization stopped because xtol_rel or xtol_abs (above) was reached.
cat("\nSmooth repulsion palette:\n")
#> 
#> Smooth repulsion palette:
print(smooth_palette)
#> 
#> -- huerd Color Palette (8 colors) --
#> Colors:
#> [ 1] #003700
#> [ 2] #2E0079
#> [ 3] #000092
#> [ 4] #2A3700
#> [ 5] #FF0000
#> [ 6] #FF00FF
#> [ 7] #00FF00
#> [ 8] #00FFFF
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.043
#> * Optimizer Performance Ratio      : 13.9%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.012
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 28
#> * Optimizer Status: NLOPT_SUCCESS: Generic success return value.
cat("\nLog-sum-exp palette:\n")
#> 
#> Log-sum-exp palette:
print(logsumexp_palette)
#> 
#> -- huerd Color Palette (6 colors) --
#> Colors:
#> [ 1] #003700
#> [ 2] #000092
#> [ 3] #AD00FF
#> [ 4] #FF0000
#> [ 5] #00FF00
#> [ 6] #00FFFF
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.238
#> * Optimizer Performance Ratio      : 65.1%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.050
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 28
#> * Optimizer Status: NLOPT_SUCCESS: Generic success return value.

Diagnostic Dashboard

Get a quick overview of your palette properties:

library(huerd)

set.seed(2024)
palette <- generate_palette(8, progress = FALSE)
plot_palette_analysis(palette, force_font_scale = 0.6)

Palette Quality Evaluation

Or look at the numerical evaluation results:

library(huerd)

set.seed(314)
palette <- generate_palette(8, progress = FALSE)
evaluation <- evaluate_palette(palette)

# Access raw metrics (no subjective scoring)
cat("Minimum distance:", evaluation$distances$min, "\n")
#> Minimum distance: 0.139767
cat("Performance ratio:", evaluation$distances$performance_ratio * 100, "%\n")
#> Performance ratio: 45.10603 %
cat("CVD worst case:", evaluation$cvd_safety$worst_case_min_distance, "\n")
#> CVD worst case: 0.1112826

Custom Parameters

Fine-tune the generation process with advanced options:

library(huerd)

set.seed(271)
palette <- generate_palette(
  n = 8,
  initialization = "harmony",              # Color harmony-based initialization
  init_lightness_bounds = c(0.3, 0.8),    # Constrain lightness range
  max_iterations = 2000,                   # Increased iterations
  optimizer = "nloptr_cobyla",             # Use COBYLA for optimization
  progress = FALSE
)
print(palette)
#> 
#> -- huerd Color Palette (8 colors) --
#> Colors:
#> [ 1] #AB5445
#> [ 2] #AC7D3B
#> [ 3] #8FA800
#> [ 4] #C0BC00
#> [ 5] #FF93D2
#> [ 6] #B2B8FF
#> [ 7] #4CDF9C
#> [ 8] #59FDE7
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.092
#> * Optimizer Performance Ratio      : 29.6%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.072
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 753
#> * Optimizer Status: NLOPT_XTOL_REACHED: Optimization stopped because xtol_rel or xtol_abs (above) was reached.

Complete Workflow Example

library(huerd)

set.seed(161)
# 1. Generate brand palette with advanced optimization
my_brand_palette <- generate_palette(
  n = 8,
  include_colors = c("#1f77b4", "#ff7f0e"),  # Fixed brand colors
  fixed_aesthetic_influence = 0.9,
  initialization = "harmony",
  optimizer = "sann",
  max_iterations = 5000,
  weights = c(distance = 1),
  return_metrics = TRUE,
  progress = TRUE
)
#> ℹ Preparing for palette generation...
#> ℹ Adapting initialization from fixed colors' aesthetics...
#> Initializing 6 free colors (method: harmony)...
#> Optimizing 6 free colors using sann...
#> ℹ Finalizing palette...
#> 
#> ✔ Done

# 2. Diagnostic analysis
plot_palette_analysis(my_brand_palette, force_font_scale = 0.6)


# 3. Quality evaluation
evaluation <- evaluate_palette(my_brand_palette)
cat("Min distance:", round(evaluation$distances$min, 3), "\n")
#> Min distance: 0.207
cat("Performance:", round(evaluation$distances$performance_ratio * 100, 1), "%\n")
#> Performance: 66.9 %

# 4. CVD accessibility check
cvd_safe <- is_cvd_safe(my_brand_palette)
if (cvd_safe) {
  cat("Palette is CVD-accessible\n")
} else {
  cat("Palette may challenge CVD viewers\n")
}
#> Palette is CVD-accessible

# 5. CVD simulation for verification
cvd_simulation <- simulate_palette_cvd(my_brand_palette, cvd_type = "all")
print(cvd_simulation)
#> 
#> -- huerd CVD Simulation Result (Multiple Types, Severity: 1.00) --
#> Palette for: original
#>   [ 1] #520000
#>   [ 2] #2E008F
#>   [ 3] #005C2A
#>   [ 4] #8900FF
#>   [ 5] #1F77B4
#>   [ 6] #7EA984
#>   [ 7] #FF7F0E
#>   [ 8] #00FFFF
#> Palette for: protan
#>   [ 1] #1E1900
#>   [ 2] #003192
#>   [ 3] #5C5326
#>   [ 4] #0064FF
#>   [ 5] #5A79B7
#>   [ 6] #AAA182
#>   [ 7] #A59100
#>   [ 8] #EDF2FF
#> Palette for: deutan
#>   [ 1] #312A00
#>   [ 2] #00278D
#>   [ 3] #534C2E
#>   [ 4] #0060FB
#>   [ 5] #456CB3
#>   [ 6] #A39D86
#>   [ 7] #C4AE05
#>   [ 8] #D0DDFF
#> Palette for: tritan
#>   [ 1] #5C0001
#>   [ 2] #003752
#>   [ 3] #005A50
#>   [ 4] #676496
#>   [ 5] #00868D
#>   [ 6] #79A79F
#>   [ 7] #FF616D
#>   [ 8] #00FFFE

# 6. Display final palette (colors are brightness-sorted)
print(my_brand_palette)
#> 
#> -- huerd Color Palette (8 colors) --
#> Colors:
#> [ 1] #520000
#> [ 2] #2E008F
#> [ 3] #005C2A
#> [ 4] #8900FF
#> [ 5] #1F77B4
#> [ 6] #7EA984
#> [ 7] #FF7F0E
#> [ 8] #00FFFF
#> 
#> -- Quality Metrics Summary --
#> * Min. Perceptual Distance (OKLAB): 0.207
#> * Optimizer Performance Ratio      : 66.9%
#> * Min. CVD-Safe Distance (OKLAB)  : 0.106
#> 
#> -- Generation Details --
#> * Optimizer Iterations: 5000
#> * Optimizer Status: Optimization converged

Workflow Guides

The huerd package includes comprehensive vignettes for different user needs:

  • Data Scientist Workflow: Create accessible dashboard visualizations with optimized color schemes for color vision deficient viewers.

  • Designer Workflow: Integrate brand colors into cohesive palettes and export them in various formats (CSS, Sass, JSON) for web development.

  • Package Developer Workflow: Use the programmatic API for reproducible palette generation and integrate huerd into your own packages or applications.