dbl-utils
Version:
Utilities for dbl, adba and others projects
313 lines (312 loc) • 10.2 kB
JavaScript
import chroma from 'chroma-js';
import moment from 'moment';
//----------------------------------Arrays
/**
* Splits an array into chunks of a given size.
* @param arr - Array to split into chunks.
* @param chunkSize - Size of each chunk.
* @returns Array of chunked arrays.
*
* @example
* ```ts
* sliceIntoChunks([1,2,3,4], 2); // [[1,2],[3,4]]
* ```
*/
export function sliceIntoChunks(arr, chunkSize) {
const res = [];
for (let i = 0; i < arr.length; i += chunkSize) {
const chunk = arr.slice(i, i + chunkSize);
res.push(chunk);
}
return res;
}
/**
* Splits and flattens a list of strings based on a separator.
* @param {string[]} arrayStrings - Array of strings to split and flatten.
* @param {string} separator - Separator to use for splitting.
* @returns {string[]} Flattened and splitted array of strings.
*
* @example
* ```ts
* splitAndFlat(['tag1 tag2', 'tag2'], ' ');
* // => ['tag1', 'tag2']
* ```
*/
export function splitAndFlat(arrayStrings, separator = ' ') {
const r = arrayStrings.flatMap(s => (typeof s === 'string' && s.split(separator)));
const filtered = r.filter(Boolean);
return Array.from(new Set(filtered));
}
//-----------------------------------colors
/**
* Generates a random set of colors with an optional format.
* @param count - Number of colors to generate.
* @param options - Object containing optional parameters.
* @param options.format - The format of the output colors ('hex', 'rgb', 'nrgb').
* @param options.bounds - The bounds for color transformation.
* @param options.distribute - Whether the colors should be evenly distributed.
* @returns An array of colors in the specified format.
*
* @example
* ```ts
* generateRandomColors(2, { format: 'rgb' });
* // => [[r,g,b], [r,g,b]]
* ```
*/
export function generateRandomColors(count, { format = 'hex', bounds = 1, distribute = true } = {}) {
const uniqueColors = new Set();
if (distribute) {
const step = Math.floor(0xFFFFFF / count);
for (let i = 0; i < count; i++) {
const colorValue = i * step;
const colorHex = '#' + colorValue.toString(16).padStart(6, '0');
const color = chroma(colorHex);
uniqueColors.add(color);
}
}
else {
while (uniqueColors.size < count) {
uniqueColors.add(chroma.random());
}
}
const formattedColors = Array.from(uniqueColors).map((color) => {
const [r, g, b] = color.rgb();
const adjustedColor = (typeof bounds === 'number' || Array.isArray(bounds))
? color.rgb().map((c) => transform(c, bounds))
: [
transform(r, bounds.r),
transform(g, bounds.g),
transform(b, bounds.b)
];
return chroma(...adjustedColor);
});
return formattedColors.map(color => {
if (format === 'hex') {
return color.hex();
}
else if (format === 'rgb') {
return color.rgb();
}
else if (format === 'nrgb') {
return color.rgb().map((c) => c / 255);
}
});
}
/**
* Transforms a color value based on the specified limit.
* @param value - The color component value.
* @param limit - The limit for transformation.
* @returns The transformed color component.
*/
function transform(value, limit) {
const [min, max] = Array.isArray(limit) ? limit : typeof limit === 'number' ? [0, limit] : [0, 1];
return ((value / 255) * (max - min) + min) * 255;
}
/**
* Evaluates the similarity of a set of colors.
* @param cls - Array of colors in hex format.
* @returns A similarity score between 0 and 1, where 0 indicates all colors are distinct and 1 indicates all are similar.
*
* @example
* ```ts
* evaluateColorSimilarity(['#ffffff', '#fffffe']);
* // => value close to 1
* ```
*/
export function evaluateColorSimilarity(colors) {
let totalDistance = 0;
let pairsCount = 0;
const cls = colors.sort();
for (let i = 0; i < cls.length; i++) {
for (let j = i + 1; j < cls.length; j++) {
totalDistance += chroma.distance(cls[i], cls[j]);
pairsCount++;
}
}
const averageDistance = totalDistance / pairsCount;
const maxDistance = Math.sqrt(Math.pow(255, 2) * 3); // Maximum possible distance in RGB
return 1 - (averageDistance / maxDistance);
}
//-------------------------------------strings
/**
* Normalizes a string by converting it to lowercase and removing diacritical marks.
* @param {string} str - The string to normalize.
* @returns {string} The normalized string.
*
* @example
* ```ts
* normalize('Árbol');
* // => 'arbol'
* ```
*/
export function normalize(str = '') {
return str.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '');
}
/**
* Converts a string to a slug.
* @param {string} str - The string to slugify.
* @returns {string} The slugified string.
*
* @example
* ```ts
* slugify('Hello World!');
* // => 'hello-world'
* ```
*/
export function slugify(str = '') {
return normalize(str)
.replace(/\s/g, '-')
.replace(/[^a-zA-Z\d\-]+/g, '')
.replace(/-+/g, '-');
}
/**
* Generates a random four-character string consisting of hexadecimal digits.
* @returns {string} The random four-character string.
*
* @example
* ```ts
* randomS4();
* // => '9f3b'
* ```
*/
export function randomS4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
/**
* Generates a random string of the specified length using the provided characters.
*
* @param {number} [length=16] - The length of the generated string. Default is 16.
* @param {string} [characters='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'] - A string representing the characters to be used. Default includes uppercase, lowercase letters, and digits.
* @returns {string} A random string composed of the specified characters.
*
* @example
* ```ts
* randomString(5);
* // => e.g., 'aB3dE'
* ```
*/
export function randomString(length = 16, characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') {
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charactersLength);
result += characters.charAt(randomIndex);
}
return result;
}
//------------------------------------------time
/**
* Generates an array of time intervals between two dates.
* @param {Object} options - Options for generating time intervals.
* @param {string|number} options.from - Start date in string format or as a UNIX timestamp.
* @param {string|number} options.to - End date in string format or as a UNIX timestamp.
* @param {number} options.step - The step size in hours for each time interval.
* @param {string} [options.boundary] - Restrict chunks to 'day', 'month', or 'day,month' boundaries.
* @returns An array of objects representing each time interval.
*
* @example
* ```ts
* timeChunks({ from: '2020-01-01', to: '2020-01-02', step: 6 });
* ```
*/
export function timeChunks(options) {
const { from, to, step, boundary } = options;
if (!(step > 0)) {
throw new Error('Invalid step ' + step);
}
if (!moment(from).isValid()) {
throw new Error('Invalid date ' + from);
}
if (!moment(to).isValid()) {
throw new Error('Invalid date ' + to);
}
const hoursArray = [];
const fromMoment = Number.isInteger(from) ? moment.unix(from) : moment(from);
const toMoment = Number.isInteger(to) ? moment.unix(to) : moment(to);
const diffHours = toMoment.diff(fromMoment, 'hours');
for (let i = 0; i < diffHours; i += step) {
let hourMoment = moment(from).add(i, 'hours');
let nextHourMoment = moment(from).add(i + step, 'hours');
if (boundary) {
const boundaries = boundary.split(',');
if (boundaries.includes('day')) {
const endOfDay = moment(hourMoment).endOf('day');
nextHourMoment = nextHourMoment.isAfter(endOfDay) ? endOfDay : nextHourMoment;
}
if (boundaries.includes('month')) {
const endOfMonth = moment(hourMoment).endOf('month');
nextHourMoment = nextHourMoment.isAfter(endOfMonth) ? endOfMonth : nextHourMoment;
}
}
hoursArray.push({
from: hourMoment.format(),
to: nextHourMoment.isAfter(toMoment) ? moment(to).format() : nextHourMoment.format(),
step
});
}
return hoursArray;
}
/**
* Resolves the promise after a given timeout.
* @param {number} timeout - Milliseconds to wait before resolving.
* @returns {Promise<void>} The promise that resolves after the timeout.
*
* @example
* ```ts
* await delay(1000);
* // waits for one second
* ```
*/
export function delay(timeout) {
return new Promise(resolve => setTimeout(resolve, timeout));
}
//-------------------------------------------------------- numbers
/**
* Computes the hash value of a string.
* @param {string} string - The string to hash.
* @returns {number} The computed hash value.
*
* @example
* ```ts
* hash('abc');
* // => deterministic numeric hash
* ```
*/
export function hash(string) {
let hash = 0, i, chr;
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32-bit integer
}
return hash;
}
/**
* Linear Congruential Generator (LCG) class for pseudo-random number generation.
* @class LCG
*
* @example
* ```ts
* const gen = new LCG(123);
* gen.random();
* // => 0.596735... (deterministic)
* ```
*/
export class LCG {
constructor(seed) {
this.a = 1664525;
this.c = 1013904223;
this.m = Math.pow(2, 32);
this.seed = seed;
}
/**
* Generates a pseudo-random number between 0 and 1.
* @returns {number} The generated pseudo-random number.
*/
random() {
this.seed = (this.a * this.seed + this.c) % this.m;
return this.seed / this.m;
}
}