or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

color-analysis.mdcolor-construction.mdcolor-conversion.mdcolor-generation.mdcolor-operations.mdcolor-palettes.mdcolor-scales.mdindex.md
tile.json

color-analysis.mddocs/

Color Analysis

Utility functions for analyzing colors including contrast calculation, color difference measurement, validation, and data analysis for scale creation.

Capabilities

Contrast Analysis

Calculate contrast ratios between colors for accessibility and readability assessment.

/**
 * Calculate WCAG contrast ratio between two colors
 * @param color1 - First color (foreground)
 * @param color2 - Second color (background)
 * @returns Contrast ratio (1-21, higher is better contrast)
 */
function contrast(color1: Color | string, color2: Color | string): number;

WCAG Guidelines:

  • AA Normal text: minimum 4.5:1
  • AA Large text: minimum 3:1
  • AAA Normal text: minimum 7:1
  • AAA Large text: minimum 4.5:1

Usage Examples:

import chroma, { contrast } from "chroma-js";

// Basic contrast calculation
const textColor = '#000000';
const bgColor = '#FFFFFF';
console.log(contrast(textColor, bgColor)); // 21 (maximum contrast)

// Test different combinations
const darkGray = '#333333';
const lightGray = '#CCCCCC';
console.log(contrast(darkGray, lightGray)); // ~9.74

// Accessibility testing
function isAccessible(fg, bg, level = 'AA', size = 'normal') {
  const ratio = contrast(fg, bg);
  const requirements = {
    'AA': { normal: 4.5, large: 3.0 },
    'AAA': { normal: 7.0, large: 4.5 }
  };
  return ratio >= requirements[level][size];
}

console.log(isAccessible('#333', '#fff')); // true (AA normal)
console.log(isAccessible('#666', '#999')); // false (AA normal)

// Find accessible color combinations
const background = '#2E86AB';
const colors = ['#ffffff', '#f5f5f5', '#333333', '#000000'];
const accessible = colors.filter(color => 
  isAccessible(color, background)
);

APCA Contrast

Advanced Perceptual Contrast Algorithm for more accurate contrast prediction.

/**
 * Calculate APCA contrast prediction
 * @param text - Text color (foreground)
 * @param background - Background color
 * @returns APCA contrast value (-108 to 106)
 */
function contrastAPCA(text: Color | string, background: Color | string): number;

APCA Guidelines:

  • Values > 75: Preferred for body text
  • Values > 60: Minimum for body text
  • Values > 45: Minimum for large text
  • Values > 30: Minimum for non-text elements

Usage Examples:

import chroma, { contrastAPCA } from "chroma-js";

// APCA contrast calculation
const textColor = '#000000';
const bgColor = '#FFFFFF';
console.log(contrastAPCA(textColor, bgColor)); // ~106 (maximum)

// Compare with WCAG contrast
const fg = '#666666';
const bg = '#FFFFFF';
console.log(`WCAG: ${contrast(fg, bg)}`);         // ~5.74
console.log(`APCA: ${contrastAPCA(fg, bg)}`);     // ~68.54

// APCA accessibility testing
function isAPCAAccessible(text, bg, minContrast = 60) {
  return Math.abs(contrastAPCA(text, bg)) >= minContrast;
}

// Test color combinations
const combinations = [
  ['#000000', '#FFFFFF'], // Black on white
  ['#333333', '#FFFFFF'], // Dark gray on white
  ['#666666', '#FFFFFF'], // Medium gray on white
  ['#999999', '#FFFFFF']  // Light gray on white
];

combinations.forEach(([text, bg]) => {
  const apcaValue = contrastAPCA(text, bg);
  const accessible = isAPCAAccessible(text, bg);
  console.log(`${text} on ${bg}: ${apcaValue.toFixed(1)} (${accessible ? 'Pass' : 'Fail'})`);
});

Color Difference

Measure perceptual color differences using Delta E algorithms.

/**
 * Calculate Delta E color difference (CIE 2000 formula)
 * @param color1 - First color
 * @param color2 - Second color
 * @param Kl - Lightness weighting factor (default: 1)
 * @param Kc - Chroma weighting factor (default: 1)
 * @param Kh - Hue weighting factor (default: 1)
 * @returns Delta E value (0 = identical, higher = more different)
 */
function deltaE(color1: Color | string, color2: Color | string, Kl?: number, Kc?: number, Kh?: number): number;

Delta E Interpretation:

  • 0-1: Not perceptible to human eye
  • 1-2: Perceptible through close observation
  • 2-10: Perceptible at a glance
  • 11-49: Colors are more similar than opposite
  • 50-100: Colors are completely different

Usage Examples:

import chroma, { deltaE } from "chroma-js";

// Basic color difference
const red1 = '#FF0000';
const red2 = '#FE0000';
console.log(deltaE(red1, red2)); // Very small difference

const red = '#FF0000';
const blue = '#0000FF';  
console.log(deltaE(red, blue)); // Large difference (~76)

// Compare similar colors
const colors = ['#FF0000', '#FF1010', '#FF2020', '#FF3030'];
for (let i = 1; i < colors.length; i++) {
  const diff = deltaE(colors[0], colors[i]);
  console.log(`${colors[0]} vs ${colors[i]}: ΔE = ${diff.toFixed(2)}`);
}

// Quality control for color matching
function isColorMatch(target, sample, tolerance = 2.0) {
  return deltaE(target, sample) <= tolerance;
}

// Test color batch
const targetColor = '#FF4444';
const batch = ['#FF4445', '#FF4440', '#FF4450', '#FF4400'];
const matches = batch.filter(color => isColorMatch(targetColor, color));

// Weighted Delta E for specific applications
const printColor = deltaE('#FF0000', '#FE0505', 2, 1, 1); // Weight lightness

Euclidean Distance

Calculate Euclidean distance between colors in various color spaces.

/**
 * Calculate Euclidean distance between colors
 * @param color1 - First color
 * @param color2 - Second color
 * @param mode - Color space for calculation (default: 'lab')
 * @returns Distance value (0 = identical)
 */
function distance(color1: Color | string, color2: Color | string, mode?: string): number;

Available modes:

  • lab - Lab color space (default, perceptually uniform)
  • rgb - RGB color space
  • lch - LCH color space
  • hsl - HSL color space
  • cmyk - CMYK color space

Usage Examples:

import chroma, { distance } from "chroma-js";

const red = '#FF0000';
const blue = '#0000FF';

// Distance in different color spaces
console.log(distance(red, blue, 'lab')); // Lab distance
console.log(distance(red, blue, 'rgb')); // RGB distance
console.log(distance(red, blue, 'lch')); // LCH distance

// Find closest color in palette
function findClosestColor(target, palette, mode = 'lab') {
  let minDistance = Infinity;
  let closest = null;
  
  for (const color of palette) {
    const dist = distance(target, color, mode);
    if (dist < minDistance) {
      minDistance = dist;
      closest = color;
    }
  }
  
  return { color: closest, distance: minDistance };
}

// Usage
const targetColor = '#FF6B35';
const palette = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF'];
const result = findClosestColor(targetColor, palette);
console.log(`Closest color: ${result.color} (distance: ${result.distance.toFixed(2)})`);

// Color clustering
function clusterColors(colors, maxDistance = 10, mode = 'lab') {
  const clusters = [];
  const used = new Set();
  
  for (const color of colors) {
    if (used.has(color)) continue;
    
    const cluster = [color];
    used.add(color);
    
    for (const otherColor of colors) {
      if (used.has(otherColor)) continue;
      if (distance(color, otherColor, mode) <= maxDistance) {
        cluster.push(otherColor);
        used.add(otherColor);
      }
    }
    
    clusters.push(cluster);
  }
  
  return clusters;
}

Data Analysis

Analyze numeric data to determine optimal scale parameters and class breaks.

/**
 * Analyze numeric data for scale creation
 * @param data - Array of numbers or array of objects
 * @param key - Property key if data contains objects
 * @returns Analysis result with statistical information
 */
function analyze(data: number[] | object[], key?: string): AnalysisResult;

interface AnalysisResult {
  min: number;
  max: number;
  sum: number;
  count: number;
  domain: [number, number];
}

/**
 * Generate class break values from analysis
 * @param data - Analysis result from analyze()
 * @param mode - Classification mode (default: 'equal')
 * @param num - Number of classes (default: 7)
 * @returns Array of break values
 */
function limits(data: AnalysisResult, mode?: string, num?: number): number[];

Classification modes:

  • equal - Equal intervals
  • quantile - Quantile-based breaks
  • logarithmic - Logarithmic scaling
  • jenks - Natural breaks (Jenks optimization)

Usage Examples:

import chroma, { analyze, limits } from "chroma-js";

// Analyze simple numeric data
const populationData = [1200, 3400, 5600, 12100, 18900, 25300, 45200];
const analysis = analyze(populationData);

console.log(analysis);
// {
//   min: 1200,
//   max: 45200,
//   sum: 111800,
//   count: 7,
//   domain: [1200, 45200]
// }

// Generate class breaks
const equalBreaks = limits(analysis, 'equal', 5);
const quantileBreaks = limits(analysis, 'quantile', 5);
const logBreaks = limits(analysis, 'logarithmic', 5);

console.log('Equal intervals:', equalBreaks);
console.log('Quantile breaks:', quantileBreaks);
console.log('Log breaks:', logBreaks);

// Analyze object data
const cityData = [
  { name: 'NYC', population: 8419000, area: 783 },
  { name: 'LA', population: 3980000, area: 1302 },
  { name: 'Chicago', population: 2716000, area: 606 }
];

const popAnalysis = analyze(cityData, 'population');
const areaAnalysis = analyze(cityData, 'area');

// Create scales based on analysis
const popScale = chroma.scale(['lightblue', 'darkblue'])
  .domain([popAnalysis.min, popAnalysis.max]);

const areaScale = chroma.scale(['lightgreen', 'darkgreen'])
  .domain([areaAnalysis.min, areaAnalysis.max]);

// Use with choropleth mapping
function createChoroplethScale(data, property, colors, classes = 5) {
  const analysis = analyze(data, property);
  const breaks = limits(analysis, 'quantile', classes);
  return chroma.scale(colors).classes(breaks);
}

const choropleth = createChoroplethScale(
  cityData, 
  'population', 
  ['#ffffcc', '#c7e9b4', '#7fcdbb', '#41b6c4', '#225ea8']
);

Color Validation

Validate color inputs and check for valid color representations.

/**
 * Check if arguments can create a valid color
 * @param args - Arguments to test
 * @returns True if arguments represent a valid color
 */
function valid(...args: any[]): boolean;

Usage Examples:

import chroma, { valid } from "chroma-js";

// Test various color formats
console.log(valid('#FF0000'));        // true
console.log(valid('red'));            // true
console.log(valid(255, 0, 0));        // true
console.log(valid([255, 0, 0]));      // true
console.log(valid('#GGG'));           // false
console.log(valid('invalid'));        // false
console.log(valid(300, 0, 0));        // false (out of range)

// Validate user input
function parseUserColor(input) {
  if (valid(input)) {
    return chroma(input);
  } else {
    throw new Error(`Invalid color: ${input}`);
  }
}

// Batch validation
const colorInputs = ['#FF0000', 'blue', '#invalid', 'rgb(255,0,0)', 'badcolor'];
const validColors = colorInputs.filter(valid);
console.log('Valid colors:', validColors);

// Safe color creation
function safeColor(input, fallback = '#000000') {
  return valid(input) ? chroma(input) : chroma(fallback);
}

// Form validation
function validateColorInput(input) {
  if (!input) return { valid: false, error: 'Color is required' };
  if (!valid(input)) return { valid: false, error: 'Invalid color format' };
  return { valid: true, color: chroma(input) };
}

// API validation
function processColorData(data) {
  const errors = [];
  const colors = [];
  
  data.forEach((item, index) => {
    if (!valid(item.color)) {
      errors.push(`Invalid color at index ${index}: ${item.color}`);
    } else {
      colors.push({
        ...item,
        chromaColor: chroma(item.color)
      });
    }
  });
  
  return { colors, errors };
}

Statistical Color Analysis

Advanced statistical analysis of color sets.

// Advanced analysis functions (built using core functions)

/**
 * Calculate color diversity in a palette
 */
function calculateColorDiversity(colors: (Color | string)[], mode?: string): number;

/**
 * Find color outliers in a dataset
 */
function findColorOutliers(colors: (Color | string)[], threshold?: number): (Color | string)[];

/**
 * Generate color statistics
 */
function getColorStatistics(colors: (Color | string)[]): ColorStatistics;

interface ColorStatistics {
  averageColor: Color;
  medianLightness: number;
  saturationRange: [number, number];
  hueDistribution: number[];
  totalColors: number;
}

Usage Examples:

// Color diversity calculation
function calculateColorDiversity(colors, mode = 'lab') {
  let totalDistance = 0;
  let comparisons = 0;
  
  for (let i = 0; i < colors.length; i++) {
    for (let j = i + 1; j < colors.length; j++) {
      totalDistance += distance(colors[i], colors[j], mode);
      comparisons++;
    }
  }
  
  return comparisons > 0 ? totalDistance / comparisons : 0;
}

// Find outlier colors
function findColorOutliers(colors, threshold = 50) {
  const outliers = [];
  
  for (const color of colors) {
    const distances = colors
      .filter(c => c !== color)
      .map(c => distance(color, c, 'lab'));
    
    const avgDistance = distances.reduce((a, b) => a + b, 0) / distances.length;
    
    if (avgDistance > threshold) {
      outliers.push(color);
    }
  }
  
  return outliers;
}

// Color statistics
function getColorStatistics(colors) {
  const chromaColors = colors.map(c => chroma(c));
  
  // Average color
  const avgColor = average(chromaColors);
  
  // Lightness statistics
  const lightnesses = chromaColors.map(c => c.get('hsl.l'));
  const medianLightness = lightnesses.sort()[Math.floor(lightnesses.length / 2)];
  
  // Saturation range
  const saturations = chromaColors.map(c => c.get('hsl.s'));
  const saturationRange = [Math.min(...saturations), Math.max(...saturations)];
  
  // Hue distribution (simplified)
  const hues = chromaColors.map(c => c.get('hsl.h')).filter(h => !isNaN(h));
  const hueDistribution = new Array(12).fill(0);
  hues.forEach(h => {
    const bucket = Math.floor(h / 30) % 12;
    hueDistribution[bucket]++;
  });
  
  return {
    averageColor: avgColor,
    medianLightness,
    saturationRange,
    hueDistribution,
    totalColors: colors.length
  };
}

// Usage
const palette = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF'];
const diversity = calculateColorDiversity(palette);
const outliers = findColorOutliers(palette);
const stats = getColorStatistics(palette);

console.log(`Diversity: ${diversity.toFixed(2)}`);
console.log(`Outliers:`, outliers);
console.log(`Average color:`, stats.averageColor.hex());