Skip to content

Commit

Permalink
feat: add more getRescaler algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
lpatiny committed Jun 23, 2023
1 parent 37a4f26 commit 6f1b5de
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 15 deletions.
29 changes: 29 additions & 0 deletions src/utils/__tests__/getRescaler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,32 @@ test('getRescale with wrong min / max, no clamp', () => {
});
expect(() => rescaler(2)).toThrowError('Value 2 is out of range [0, 1]');
});

test('getRescale with wrong min / max and logartihmic', () => {
const array = [10, 100, 1000, 10000];

const rescaler = getRescaler({
originalMin: 10,
originalMax: 1000,
algorithm: 'logarithmic',
});

const rescaledArray = array.map(rescaler);
expect(rescaledArray).toStrictEqual([0, 0.5, 1, 1]);
});

test('getRescale with square root', () => {
const array = [25, 36, 49];

const rescaler = getRescaler({
originalMin: 25,
originalMax: 49,
targetMin: 10,
targetMax: 50,
algorithm: 'power',
algorithmOptions: { power: 0.5 },
});

const rescaledArray = array.map(rescaler);
expect(rescaledArray).toStrictEqual([10, 30, 50]);
});
78 changes: 63 additions & 15 deletions src/utils/getRescaler.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
type Algorithms = 'linear' | 'logarithmic' | 'power';

export interface RescalerOptions {
/**
* The minimum value of the original range
Expand All @@ -24,33 +26,79 @@ export interface RescalerOptions {
* @default true
*/
clamp?: boolean;
/**
* The algorithm to use for the rescaling
* @default 'linear'
*/
algorithm?: Algorithms;
/**
* Options for the algorithm
* @default {}
*/
algorithmOptions?: Record<string, number>;
}

export function getRescaler(options: RescalerOptions = {}) {
const {
let {
originalMin = 0,
originalMax = 1,
targetMin = 0,
targetMax = 1,
clamp = true,
algorithmOptions = {},
} = options;

const convert = getDataConverter(
options.algorithm || 'linear',
algorithmOptions,
);
originalMin = convert(originalMin);
originalMax = convert(originalMax);

const originalRange = originalMax - originalMin;
const targetRange = targetMax - targetMin;

return (value: number) => {
if (value < originalMin) {
if (clamp) return targetMin;
throw new RangeError(
`Value ${value} is out of range [${originalMin}, ${originalMax}]`,
);
}
if (value > originalMax) {
if (clamp) return targetMax;
throw new RangeError(
`Value ${value} is out of range [${originalMin}, ${originalMax}]`,
);
}
const originalRange = originalMax - originalMin;
const targetRange = targetMax - targetMin;
value = convert(value);
value = checkRange(value, originalMin, originalMax, clamp);

const valueScaled = (value - originalMin) / originalRange;
return targetMin + valueScaled * targetRange;
};
}

function getDataConverter(
kind: Algorithms = 'linear',
options: { power?: number } = {},
) {
return (value: number) => {
switch (kind) {
case 'linear':
return value;
case 'logarithmic':
return Math.log10(value);
case 'power':
return value ** (options.power || 2);
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unknown kind ${kind}`);
}
};
}

function checkRange(
value: number,
min: number,
max: number,
clamp = true,
): number {
if (value < min) {
if (clamp) return min;
throw new RangeError(`Value ${value} is out of range [${min}, ${max}]`);
}
if (value > max) {
if (clamp) return max;
throw new RangeError(`Value ${value} is out of range [${min}, ${max}]`);
}
return value;
}

0 comments on commit 6f1b5de

Please sign in to comment.