Created
March 5, 2025 12:50
-
-
Save coxato/910502752d903652dc49d7d86053cc69 to your computer and use it in GitHub Desktop.
Useful class for getting random colors in a color wheel, you can pass the number of steps, also returning as hex or rgba, and more params.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Types for configuration | |
interface ColorGeneratorConfig { | |
initialColor?: string; | |
numberOfSteps?: number; | |
currentIndex?: number; | |
} | |
// State that we can use later | |
export interface ColorGeneratorState { | |
startColor: string; | |
numberOfSteps: number; | |
currentIndex: number; | |
} | |
// Interface for color options | |
interface ColorOptions { | |
alpha?: number; // Value from 0-100 | |
format?: 'hex' | 'rgba'; // Output format | |
} | |
// Convert hex to HSL for easier color manipulation | |
const hexToHSL = (hex: string): [number, number, number] => { | |
// Remove the hash if present | |
hex = hex.replace(/^#/, ""); | |
// Parse the hex values | |
const r = parseInt(hex.slice(0, 2), 16) / 255; | |
const g = parseInt(hex.slice(2, 4), 16) / 255; | |
const b = parseInt(hex.slice(4, 6), 16) / 255; | |
const max = Math.max(r, g, b); | |
const min = Math.min(r, g, b); | |
let h = 0; | |
let s = 0; | |
const l = (max + min) / 2; | |
if (max !== min) { | |
const d = max - min; | |
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | |
switch (max) { | |
case r: | |
h = (g - b) / d + (g < b ? 6 : 0); | |
break; | |
case g: | |
h = (b - r) / d + 2; | |
break; | |
case b: | |
h = (r - g) / d + 4; | |
break; | |
} | |
h /= 6; | |
} | |
return [h * 360, s * 100, l * 100]; | |
}; | |
// Convert HSL to hex or rgba | |
const hslToColor = (h: number, s: number, l: number, options?: { alpha?: number, format?: 'hex' | 'rgba' }): string => { | |
h /= 360; | |
s /= 100; | |
l /= 100; | |
const hue2rgb = (p: number, q: number, t: number): number => { | |
if (t < 0) t += 1; | |
if (t > 1) t -= 1; | |
if (t < 1 / 6) return p + (q - p) * 6 * t; | |
if (t < 1 / 2) return q; | |
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; | |
return p; | |
}; | |
let r, g, b; | |
if (s === 0) { | |
r = g = b = l; | |
} else { | |
const q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
const p = 2 * l - q; | |
r = hue2rgb(p, q, h + 1 / 3); | |
g = hue2rgb(p, q, h); | |
b = hue2rgb(p, q, h - 1 / 3); | |
} | |
const toHex = (x: number): string => { | |
const hex = Math.round(x * 255).toString(16); | |
return hex.length === 1 ? "0" + hex : hex; | |
}; | |
// Default values | |
const alpha = options?.alpha; | |
const format = options?.format || 'hex'; | |
// If we have an alpha value | |
if (alpha !== undefined) { | |
const normalizedAlpha = alpha / 100; // Convert from 0-100 to 0-1 | |
// Return rgba format if requested | |
if (format === 'rgba') { | |
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${normalizedAlpha.toFixed(2)})`; | |
} | |
// Otherwise return hex with alpha | |
const alphaHex = Math.round(normalizedAlpha * 255).toString(16).padStart(2, '0'); | |
return `#${toHex(r)}${toHex(g)}${toHex(b)}${alphaHex}`; | |
} | |
// No alpha value, return standard hex | |
return `#${toHex(r)}${toHex(g)}${toHex(b)}`; | |
}; | |
export class ColorGenerator { | |
private currentIndex: number = 0; | |
private readonly numberOfSteps: number; | |
private readonly hueStep: number; | |
private readonly startHSL: [number, number, number]; | |
public readonly defaultColor: string = "#da4e22"; | |
public readonly startColor: string; | |
constructor(config: ColorGeneratorConfig = {}) { | |
this.startColor = config.initialColor || this.defaultColor; | |
this.numberOfSteps = Math.min(config.numberOfSteps || 10, 360); | |
this.hueStep = 360 / this.numberOfSteps; | |
this.startHSL = hexToHSL(this.startColor); | |
this.currentIndex = config.currentIndex || 0; | |
} | |
getNextColor(options?: ColorOptions): string { | |
// Calculate the new hue by stepping around the color wheel | |
const newHue = (this.startHSL[0] + this.hueStep * this.currentIndex) % 360; | |
// Keep saturation and lightness relatively constant for visibility | |
const saturation = 70; // Fixed saturation for vibrant colors | |
const lightness = 55; // Fixed lightness for good visibility | |
// Increment the index for next time | |
this.currentIndex = (this.currentIndex + 1) % this.numberOfSteps; | |
// Pass options to get color in the requested format | |
return hslToColor(newHue, saturation, lightness, options); | |
} | |
// Get current state | |
getState(): ColorGeneratorState { | |
return { | |
startColor: this.startColor, | |
numberOfSteps: this.numberOfSteps, | |
currentIndex: this.currentIndex, | |
}; | |
} | |
// Get state as JSON string | |
serialize(): string { | |
return JSON.stringify(this.getState()); | |
} | |
// Create a new generator from a serialized state | |
static fromSerialized(serializedJSONString: string): ColorGenerator { | |
// If the serialized string is empty, return a new generator with default values | |
if (serializedJSONString === "") { | |
return new ColorGenerator(); | |
} | |
try { | |
const { startColor, numberOfSteps, currentIndex } = | |
JSON.parse(serializedJSONString); | |
return new ColorGenerator({ | |
initialColor: startColor, | |
numberOfSteps, | |
currentIndex, | |
}); | |
} catch (err) { | |
console.error("Error parsing serialized state", err); | |
console.log("serializedJSONString", serializedJSONString); | |
console.log("Returning default ColorGenerator"); | |
return new ColorGenerator(); | |
} | |
} | |
} | |
// Example usage: | |
// Create initial generator | |
// const generator = new ColorGenerator({ numberOfSteps: 5 }); | |
// // Generate some colors | |
// console.log(generator.getNextColor()); // First color with full opacity (hex) | |
// console.log(generator.getNextColor({ alpha: 50 })); // Second color with 50% opacity (hex with alpha) | |
// console.log(generator.getNextColor({ alpha: 75, format: 'rgba' })); // Color with 75% opacity in rgba format | |
// // Save the state | |
// const serializedState = generator.serialize(); | |
// // This string can be sent to backend/stored | |
// // Later, restore the generator from the saved state | |
// const restoredGenerator = ColorGenerator.fromSerialized(serializedState); | |
// // Will continue from where the previous generator left off |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment