@steelbreeze/pivot
Version:
Minimal TypeScript / JavaScript n-cube library
176 lines (175 loc) • 9.55 kB
JavaScript
;
/**
* 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;
}