Skip to content

Commit

Permalink
add inverse linear node
Browse files Browse the repository at this point in the history
  • Loading branch information
mck committed Nov 13, 2024
1 parent 18f41d3 commit be053ca
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/lazy-pets-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tokens-studio/graph-engine": minor
---

Add inverse linear node.
4 changes: 3 additions & 1 deletion packages/graph-engine/src/nodes/series/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import exponentialDecay from './exponentialDecay.js';
import fibonacci from './fibonacci.js';
import geometric from './geometric.js';
import harmonic from './harmonic.js';
import inverseLinear from './inverseLinear.js';
import linear from './linear.js';
import power from './power.js';

Expand All @@ -12,8 +13,9 @@ export const nodes = [
arithmetic,
exponentialDecay,
fibonacci,
harmonic,
geometric,
harmonic,
inverseLinear,
linear,
power
];
103 changes: 103 additions & 0 deletions packages/graph-engine/src/nodes/series/inverseLinear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { BooleanSchema, NumberSchema } from '../../schemas/index.js';
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';
import { setToPrecision } from '../../utils/precision.js';

export default class NodeDefinition extends Node {
static title = 'Inverse Linear Mapping';
static type = 'studio.tokens.series.inverseLinear';
static description =
'Maps values from one range to another with optional clamping';

declare inputs: ToInput<{
value: number;
inMin: number;
inMax: number;
outMin: number;
outMax: number;
clamp: boolean;
precision: number;
}>;

declare outputs: ToOutput<{
value: number;
}>;

constructor(props: INodeDefinition) {
super(props);
this.addInput('value', {
type: {
...NumberSchema,
default: 0.5,
description: 'Value to map'
}
});
this.addInput('inMin', {
type: {
...NumberSchema,
default: 0,
description: 'Input range minimum'
}
});
this.addInput('inMax', {
type: {
...NumberSchema,
default: 1,
description: 'Input range maximum'
}
});
this.addInput('outMin', {
type: {
...NumberSchema,
default: 0,
description: 'Output range minimum'
}
});
this.addInput('outMax', {
type: {
...NumberSchema,
default: 100,
description: 'Output range maximum'
}
});
this.addInput('clamp', {
type: {
...BooleanSchema,
default: true,
description: 'Clamp output to range'
}
});
this.addInput('precision', {
type: {
...NumberSchema,
default: 2,
minimum: 0
}
});
this.addOutput('value', {
type: NumberSchema
});
}

execute(): void | Promise<void> {
const { value, inMin, inMax, outMin, outMax, clamp, precision } =
this.getAllInputs();

let result;
if (inMax === inMin) {
result = outMin;
} else {
const normalizedValue = (value - inMin) / (inMax - inMin);
result = outMin + normalizedValue * (outMax - outMin);

if (clamp) {
result = Math.min(
Math.max(result, Math.min(outMin, outMax)),
Math.max(outMin, outMax)
);
}
}

this.outputs.value.set(setToPrecision(result, precision));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { Graph } from '../../../../src/graph/graph.js';
import { describe, expect, test } from 'vitest';
import Node from '../../../../src/nodes/series/inverseLinear.js';

describe('series/inverseLinear', () => {
test('maps value in range correctly', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(0.5);
node.inputs.inMin.setValue(0);
node.inputs.inMax.setValue(1);
node.inputs.outMin.setValue(0);
node.inputs.outMax.setValue(100);

await node.execute();

expect(node.outputs.value.value).toBe(50);
});

test('handles inverse ranges', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(0.75);
node.inputs.inMin.setValue(0);
node.inputs.inMax.setValue(1);
node.inputs.outMin.setValue(100);
node.inputs.outMax.setValue(0);

await node.execute();

expect(node.outputs.value.value).toBe(25);
});

test('clamps values when enabled', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(2);
node.inputs.inMin.setValue(0);
node.inputs.inMax.setValue(1);
node.inputs.outMin.setValue(0);
node.inputs.outMax.setValue(100);
node.inputs.clamp.setValue(true);

await node.execute();

expect(node.outputs.value.value).toBe(100);
});

test('allows out of range values when clamping disabled', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(2);
node.inputs.inMin.setValue(0);
node.inputs.inMax.setValue(1);
node.inputs.outMin.setValue(0);
node.inputs.outMax.setValue(100);
node.inputs.clamp.setValue(false);

await node.execute();

expect(node.outputs.value.value).toBe(200);
});

test('handles equal input range', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(5);
node.inputs.inMin.setValue(1);
node.inputs.inMax.setValue(1);
node.inputs.outMin.setValue(0);
node.inputs.outMax.setValue(100);

await node.execute();

expect(node.outputs.value.value).toBe(0);
});

test('respects precision setting', async () => {
const graph = new Graph();
const node = new Node({ graph });

node.inputs.value.setValue(0.333);
node.inputs.inMin.setValue(0);
node.inputs.inMax.setValue(1);
node.inputs.outMin.setValue(0);
node.inputs.outMax.setValue(100);
node.inputs.precision.setValue(3);

await node.execute();

expect(node.outputs.value.value).toBe(33.3);
});
});

0 comments on commit be053ca

Please sign in to comment.