- Add new libraries: ui-accessibility, ui-animations, ui-backgrounds, ui-code-display, ui-data-utils, ui-font-manager, hcl-studio - Add extensive layout components: gallery-grid, infinite-scroll-container, kanban-board, masonry, split-view, sticky-layout - Add comprehensive demo components for all new features - Update project configuration and dependencies - Expand component exports and routing structure - Add UI landing pages planning document 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
378 lines
9.9 KiB
TypeScript
378 lines
9.9 KiB
TypeScript
/**
|
|
* Data transformation utilities
|
|
*/
|
|
|
|
import { GroupByResult, AggregationFunction, AggregationResult } from './types';
|
|
|
|
/**
|
|
* Get nested property value from object
|
|
* @param obj - Object to get value from
|
|
* @param path - Property path (supports dot notation)
|
|
*/
|
|
function getNestedValue(obj: any, path: string | keyof any): any {
|
|
if (typeof path === 'string' && path.includes('.')) {
|
|
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
}
|
|
return obj?.[path];
|
|
}
|
|
|
|
/**
|
|
* Group array of objects by a specific property
|
|
* @param data - Array to group
|
|
* @param key - Property to group by
|
|
*/
|
|
export function groupBy<T>(data: T[], key: keyof T): GroupByResult<T>[] {
|
|
if (!Array.isArray(data) || data.length === 0) return [];
|
|
|
|
const groups = new Map<any, T[]>();
|
|
|
|
data.forEach(item => {
|
|
const groupKey = getNestedValue(item, key);
|
|
if (!groups.has(groupKey)) {
|
|
groups.set(groupKey, []);
|
|
}
|
|
groups.get(groupKey)!.push(item);
|
|
});
|
|
|
|
return Array.from(groups.entries()).map(([groupKey, items]) => ({
|
|
key: groupKey,
|
|
items,
|
|
count: items.length
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Group array by multiple properties
|
|
* @param data - Array to group
|
|
* @param keys - Properties to group by
|
|
*/
|
|
export function groupByMultiple<T>(data: T[], keys: (keyof T)[]): GroupByResult<T>[] {
|
|
if (!Array.isArray(data) || data.length === 0) return [];
|
|
if (!keys || keys.length === 0) return [{ key: null, items: data, count: data.length }];
|
|
|
|
const groups = new Map<string, T[]>();
|
|
|
|
data.forEach(item => {
|
|
const groupKey = keys.map(key => String(getNestedValue(item, key))).join('|');
|
|
if (!groups.has(groupKey)) {
|
|
groups.set(groupKey, []);
|
|
}
|
|
groups.get(groupKey)!.push(item);
|
|
});
|
|
|
|
return Array.from(groups.entries()).map(([groupKey, items]) => ({
|
|
key: groupKey,
|
|
items,
|
|
count: items.length
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Aggregate values using specified functions
|
|
* @param data - Array to aggregate
|
|
* @param key - Property to aggregate
|
|
* @param functions - Aggregation functions to apply
|
|
*/
|
|
export function aggregate<T>(
|
|
data: T[],
|
|
key: keyof T,
|
|
functions: AggregationFunction[]
|
|
): AggregationResult {
|
|
if (!Array.isArray(data) || data.length === 0) {
|
|
return functions.reduce((acc, fn) => ({ ...acc, [fn]: 0 }), {});
|
|
}
|
|
|
|
const values = data
|
|
.map(item => getNestedValue(item, key))
|
|
.filter(value => value != null && !isNaN(Number(value)))
|
|
.map(value => Number(value));
|
|
|
|
if (values.length === 0) {
|
|
return functions.reduce((acc, fn) => ({ ...acc, [fn]: 0 }), {});
|
|
}
|
|
|
|
const result: AggregationResult = {};
|
|
|
|
functions.forEach(fn => {
|
|
switch (fn) {
|
|
case 'sum':
|
|
result['sum'] = values.reduce((acc, val) => acc + val, 0);
|
|
break;
|
|
case 'avg':
|
|
result['avg'] = values.reduce((acc, val) => acc + val, 0) / values.length;
|
|
break;
|
|
case 'min':
|
|
result['min'] = Math.min(...values);
|
|
break;
|
|
case 'max':
|
|
result['max'] = Math.max(...values);
|
|
break;
|
|
case 'count':
|
|
result['count'] = values.length;
|
|
break;
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Group by and aggregate in one operation
|
|
* @param data - Array to process
|
|
* @param groupKey - Property to group by
|
|
* @param aggregateKey - Property to aggregate
|
|
* @param functions - Aggregation functions
|
|
*/
|
|
export function groupAndAggregate<T>(
|
|
data: T[],
|
|
groupKey: keyof T,
|
|
aggregateKey: keyof T,
|
|
functions: AggregationFunction[]
|
|
): Array<{ group: any; aggregates: AggregationResult }> {
|
|
const groups = groupBy(data, groupKey);
|
|
|
|
return groups.map(group => ({
|
|
group: group.key,
|
|
aggregates: aggregate(group.items, aggregateKey, functions)
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Extract specific properties from objects (pluck)
|
|
* @param data - Array of objects
|
|
* @param keys - Properties to extract
|
|
*/
|
|
export function pluck<T extends Record<string, any>, K extends keyof T>(data: T[], keys: K[]): Pick<T, K>[] {
|
|
if (!Array.isArray(data) || data.length === 0) return [];
|
|
if (!keys || keys.length === 0) return [];
|
|
|
|
return data.map(item => {
|
|
const result = {} as Pick<T, K>;
|
|
keys.forEach(key => {
|
|
if (key in item) {
|
|
result[key] = item[key];
|
|
}
|
|
});
|
|
return result;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Extract single property values into array
|
|
* @param data - Array of objects
|
|
* @param key - Property to extract
|
|
*/
|
|
export function pluckProperty<T, K extends keyof T>(data: T[], key: K): T[K][] {
|
|
if (!Array.isArray(data) || data.length === 0) return [];
|
|
|
|
return data.map(item => getNestedValue(item, key)).filter(value => value !== undefined);
|
|
}
|
|
|
|
/**
|
|
* Flatten nested arrays
|
|
* @param data - Array that may contain nested arrays
|
|
* @param depth - Maximum depth to flatten (default: 1)
|
|
*/
|
|
export function flatten<T>(data: any[], depth: number = 1): T[] {
|
|
if (!Array.isArray(data)) return [];
|
|
|
|
const flattenRecursive = (arr: any[], currentDepth: number): any[] => {
|
|
return currentDepth > 0
|
|
? arr.reduce((acc, val) =>
|
|
Array.isArray(val)
|
|
? acc.concat(flattenRecursive(val, currentDepth - 1))
|
|
: acc.concat(val), [])
|
|
: arr.slice();
|
|
};
|
|
|
|
return flattenRecursive(data, depth);
|
|
}
|
|
|
|
/**
|
|
* Flatten array completely (all levels)
|
|
* @param data - Array to flatten
|
|
*/
|
|
export function flattenDeep<T>(data: any[]): T[] {
|
|
if (!Array.isArray(data)) return [];
|
|
|
|
return data.reduce((acc, val) =>
|
|
Array.isArray(val)
|
|
? acc.concat(flattenDeep(val))
|
|
: acc.concat(val), []);
|
|
}
|
|
|
|
/**
|
|
* Create pivot table from data
|
|
* @param data - Array to pivot
|
|
* @param rowKey - Property for rows
|
|
* @param colKey - Property for columns
|
|
* @param valueKey - Property for values
|
|
* @param aggregateFunction - How to aggregate values
|
|
*/
|
|
export function pivot<T>(
|
|
data: T[],
|
|
rowKey: keyof T,
|
|
colKey: keyof T,
|
|
valueKey: keyof T,
|
|
aggregateFunction: AggregationFunction = 'sum'
|
|
): { [row: string]: { [col: string]: number } } {
|
|
if (!Array.isArray(data) || data.length === 0) return {};
|
|
|
|
const result: { [row: string]: { [col: string]: number } } = {};
|
|
|
|
data.forEach(item => {
|
|
const row = String(getNestedValue(item, rowKey));
|
|
const col = String(getNestedValue(item, colKey));
|
|
const value = Number(getNestedValue(item, valueKey)) || 0;
|
|
|
|
if (!result[row]) {
|
|
result[row] = {};
|
|
}
|
|
|
|
if (!result[row][col]) {
|
|
result[row][col] = 0;
|
|
}
|
|
|
|
switch (aggregateFunction) {
|
|
case 'sum':
|
|
result[row][col] += value;
|
|
break;
|
|
case 'count':
|
|
result[row][col] += 1;
|
|
break;
|
|
case 'avg':
|
|
// For avg, we need to track sum and count separately
|
|
// This is a simplified version that treats each occurrence as separate
|
|
result[row][col] = (result[row][col] + value) / 2;
|
|
break;
|
|
case 'min':
|
|
result[row][col] = Math.min(result[row][col], value);
|
|
break;
|
|
case 'max':
|
|
result[row][col] = Math.max(result[row][col], value);
|
|
break;
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Transpose 2D array (flip rows and columns)
|
|
* @param data - 2D array to transpose
|
|
*/
|
|
export function transpose<T>(data: T[][]): T[][] {
|
|
if (!Array.isArray(data) || data.length === 0) return [];
|
|
if (!Array.isArray(data[0])) return [];
|
|
|
|
const rows = data.length;
|
|
const cols = data[0].length;
|
|
const result: T[][] = [];
|
|
|
|
for (let col = 0; col < cols; col++) {
|
|
result[col] = [];
|
|
for (let row = 0; row < rows; row++) {
|
|
result[col][row] = data[row][col];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Create frequency map of values
|
|
* @param data - Array of values or objects
|
|
* @param key - Property key (if data contains objects)
|
|
*/
|
|
export function frequency<T>(data: T[], key?: keyof T): Map<any, number> {
|
|
if (!Array.isArray(data)) return new Map();
|
|
|
|
const freqMap = new Map<any, number>();
|
|
|
|
data.forEach(item => {
|
|
const value = key ? getNestedValue(item, key) : item;
|
|
freqMap.set(value, (freqMap.get(value) || 0) + 1);
|
|
});
|
|
|
|
return freqMap;
|
|
}
|
|
|
|
/**
|
|
* Get unique values from array or object property
|
|
* @param data - Array of data
|
|
* @param key - Property key (optional)
|
|
*/
|
|
export function unique<T>(data: T[], key?: keyof T): any[] {
|
|
if (!Array.isArray(data)) return [];
|
|
|
|
const values = key
|
|
? data.map(item => getNestedValue(item, key))
|
|
: data;
|
|
|
|
return [...new Set(values)];
|
|
}
|
|
|
|
/**
|
|
* Chunk array into smaller arrays of specified size
|
|
* @param data - Array to chunk
|
|
* @param size - Size of each chunk
|
|
*/
|
|
export function chunk<T>(data: T[], size: number): T[][] {
|
|
if (!Array.isArray(data) || size <= 0) return [];
|
|
|
|
const chunks: T[][] = [];
|
|
for (let i = 0; i < data.length; i += size) {
|
|
chunks.push(data.slice(i, i + size));
|
|
}
|
|
return chunks;
|
|
}
|
|
|
|
/**
|
|
* Zip multiple arrays together
|
|
* @param arrays - Arrays to zip
|
|
*/
|
|
export function zip<T>(...arrays: T[][]): T[][] {
|
|
if (arrays.length === 0) return [];
|
|
|
|
const length = Math.max(...arrays.map(arr => Array.isArray(arr) ? arr.length : 0));
|
|
const result: T[][] = [];
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
result[i] = arrays.map(arr => arr[i]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Create cross product of multiple arrays
|
|
* @param arrays - Arrays to get cross product from
|
|
*/
|
|
export function cartesianProduct<T>(...arrays: T[][]): T[][] {
|
|
if (arrays.length === 0) return [];
|
|
if (arrays.some(arr => !Array.isArray(arr) || arr.length === 0)) return [];
|
|
|
|
return arrays.reduce(
|
|
(acc, curr) => acc.flatMap(a => curr.map(b => [...a, b])),
|
|
[[]] as T[][]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sample random items from array
|
|
* @param data - Array to sample from
|
|
* @param count - Number of items to sample
|
|
* @param withReplacement - Whether to allow duplicate selections
|
|
*/
|
|
export function sample<T>(data: T[], count: number, withReplacement: boolean = false): T[] {
|
|
if (!Array.isArray(data) || data.length === 0 || count <= 0) return [];
|
|
|
|
if (withReplacement) {
|
|
return Array.from({ length: count }, () =>
|
|
data[Math.floor(Math.random() * data.length)]
|
|
);
|
|
} else {
|
|
const shuffled = [...data].sort(() => Math.random() - 0.5);
|
|
return shuffled.slice(0, Math.min(count, data.length));
|
|
}
|
|
} |