JavaScript library for color conversions and color scale generation with zero dependencies
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Utility functions for analyzing colors including contrast calculation, color difference measurement, validation, and data analysis for scale creation.
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:
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)
);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:
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'})`);
});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:
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 lightnessCalculate 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 spacelch - LCH color spacehsl - HSL color spacecmyk - CMYK color spaceUsage 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;
}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 intervalsquantile - Quantile-based breakslogarithmic - Logarithmic scalingjenks - 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']
);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 };
}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());