UNPKG

@steelbreeze/pivot

Version:

Minimal TypeScript / JavaScript n-cube library

176 lines (175 loc) 9.55 kB
"use strict"; /** * A minimal library for pivoting data by 1-n dimensions. * * The {@link pivot} function slices and dices data by one or more {@link Dimension dimensions}, returning a {@link Matrix} if one {@link Dimension} is passed, a {@link Cube} if two * {@link Dimension dimensions} are passed, and a {@link Hypercube} if more than two {@link Dimension dimensions} are passed. * * Simple {@link Dimension dimensions} can be created by mapping a set of values using the {@link property} function and a property name from the data set to be pivoted. * * Once a {@link Cube} is created, the {@link query} function can be used to perform query operations on the subset of the source data in each cell. * * @module */ Object.defineProperty(exports, "__esModule", { value: true }); exports.average = exports.sum = exports.query = exports.pivot = exports.slice = exports.property = exports.dimension = void 0; /** * Creates a {@link Dimension} from some source data that will be used to slice and dice. * @typeParam TCriteria The type of the seed data used to creat the dimension. * @param criteria The seed data for the dimension; one entry in the source array will be one point on the dimension. * @param generator A function that creates a {@link Predicate} for each point on the dimension. * The following code creates a {@link Dimension} that will be used to evaluate ```Player``` objects during a {@link pivot} operation based on the value of their ```position``` property: * ```ts * const positions: string[] = ['Goalkeeper', 'Defender', 'Midfielder', 'Forward']; * const x = dimension(positions, property<Player>('position')); * ``` * See {@link https://github.com/steelbreeze/pivot/blob/main/src/example/index.ts GitHub} for a complete example. * @category Cube building */ const dimension = (criteria, generator) => map(criteria, generator); exports.dimension = dimension; /** * Creates a predicate function {@link Predicate} for use in the {@link dimension} function to create a {@link Dimension} matching properties. * @typeParam TValue The type of the source data that will be evaluated by the generated predicate. * @param key The property in the source data to base this {@link Predicate} on. * @example * The following code creates a {@link Dimension} that will be used to evaluate ```Player``` objects during a {@link pivot} operation based on the value of their ```position``` property: * ```ts * const positions: string[] = ['Goalkeeper', 'Defender', 'Midfielder', 'Forward']; * const x = dimension(positions, property<Player>('position')); * ``` * See {@link https://github.com/steelbreeze/pivot/blob/main/src/example/index.ts GitHub} for a complete example. * @category Cube building */ const property = (key) => (criterion) => (value) => value[key] === criterion; exports.property = property; /** * Slices data by one dimension, returning a {@link Matrix}. * @typeParam TValue The type of the source data to be sliced. * @param values The source data, an array of objects. * @param dimension The dimension to slice the data by. * @example * The following code creates a {@link Cube}, slicing and dicing the squad data for a football team by player position and country: * ```ts * const y = dimension(countries, (country: string) => (player: Player) => player.country === country); // using a user-defined generator * * const cube: Matrix<Player> = slice(squad, y); * ``` * @category Cube building * @remarks This is equivalent to {@link pivot} with one dimension. */ const slice = (values, dimension) => map(dimension, (predicate) => filter(values, predicate)); exports.slice = slice; /** * Slices and dices source data by one or more dimensions, returning, {@link Matrix}, {@link Cube} or {@link Hypercube} depending on the number of dimensions passed. * See the overloads for more detail. * @example * The following code creates a {@link Cube}, slicing and dicing the squad data for a football team by player position and country: * ```ts * const x = dimension(positions, property<Player>('position')); // using the built-in dimension generator matching a property * const y = dimension(countries, (country: string) => (player: Player) => player.country === country); // using a user-defined generator * * const cube: Cube<Player> = pivot(squad, y, x); * ``` * @category Cube building */ const pivot = (values, first, ...[second, ...others]) => second ? map((0, exports.slice)(values, first), (sliced) => (0, exports.pivot)(sliced, second, ...others)) : (0, exports.slice)(values, first); exports.pivot = pivot; /** * Queries data from a {@link Matrix} using a selector {@link Function} to transform the objects in each cell of data in the {@link Matrix} into a result. * @typeParam TValue The type of the data within the {@link Matrix}. * @typeParam TResult The type of value returned by the selector. * @param matrix The {@link Matrix} to query data from. * @param selector A callback {@link Function} to create a result from each cell of the {@link Cube}. * @remarks The {@link Matrix} may also be a {@link Cube} or {@link Hypercube}. * @example * The following code queries a {@link Cube}, returning the {@link average} age of players in a squad by country by position: * ```ts * const x = dimension(positions, property<Player>('position')); // using the built-in dimension generator matching a property * const y = dimension(countries, (country: string) => (player: Player) => player.country === country); // using a user-defined generator * * const cube: Cube<Player> = pivot(squad, y, x); * * const result: Matrix<number> = query(cube, average(age())); * * function age(asAt: Date = new Date()): Function<Player, number> { * return player => new Date(asAt.getTime() - player.dateOfBirth.getTime()).getUTCFullYear() - 1970; * } * ``` * See {@link https://github.com/steelbreeze/pivot/blob/main/src/example/index.ts GitHub} for a complete example. * @category Cube query */ const query = (matrix, selector) => map(matrix, (sliced) => map(sliced, selector)); exports.query = query; /** * Create a callback {@link Function} to pass into {@link query} that sums numerical values derived by the selector {@link Function}. * @typeParam TValue The type of the data within the cube that will be passed into the selector. * @param selector A callback {@link Function} to derive a numerical value for each object in the source data. * @example * The following code queries a {@link Cube}, returning the {@link average} age of players in a squad by country by position: * ```ts * const x = dimension(positions, property<Player>('position')); // using the built-in dimension generator matching a property * const y = dimension(countries, (country: string) => (player: Player) => player.country === country); // using a user-defined generator * * const cube: Cube<Player> = pivot(squad, y, x); * * const result: Matrix<number> = query(cube, sum(age())); * * function age(asAt: Date = new Date()): Function<Player, number> { * return player => new Date(asAt.getTime() - player.dateOfBirth.getTime()).getUTCFullYear() - 1970; * } * ``` * See {@link https://github.com/steelbreeze/pivot/blob/main/src/example/index.ts GitHub} for a complete example. * @category Cube query */ const sum = (selector) => (values) => reduce(values, (accumulator, value) => accumulator + selector(value), 0); exports.sum = sum; /** * Create a callback {@link Function} to pass into {@link query} that averages numerical values derived by the selector {@link Function}. * @typeParam TValue The type of the data within the cube that will be passed into the selector. * @param selector A callback {@link Function} to derive a numerical value for each object in the source data. * @example * The following code queries a {@link Cube}, returning the {@link average} age of players in a squad by country by position: * ```ts * const x = dimension(positions, property<Player>('position')); // using the built-in dimension generator matching a property * const y = dimension(countries, (country: string) => (player: Player) => player.country === country); // using a user-defined generator * * const cube: Cube<Player> = pivot(squad, y, x); * * const result: Matrix<number> = query(cube, average(age())); * * function age(asAt: Date = new Date()): Function<Player, number> { * return player => new Date(asAt.getTime() - player.dateOfBirth.getTime()).getUTCFullYear() - 1970; * } * ``` * See {@link https://github.com/steelbreeze/pivot/blob/main/src/example/index.ts GitHub} for a complete example. * @category Cube query */ const average = (selector) => (values) => (0, exports.sum)(selector)(values) / values.length; exports.average = average; // fast alternative to Array.prototype.filter function filter(values, predicate) { const result = []; for (let index = 0; index < values.length; ++index) { if (predicate(values[index])) { result.push(values[index]); } } return result; } // fast alternative to Array.prototype.map function map(values, mapper) { const result = []; for (let index = 0; index < values.length; ++index) { result.push(mapper(values[index])); } return result; } // fast alternative to Array.prototype.reduce function reduce(values, reducer, initialValue) { let accumulator = initialValue; for (let index = 0; index < values.length; ++index) { accumulator = reducer(accumulator, values[index]); } return accumulator; }