@synnaxlabs/x 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +30 -30
- package/dist/bounds-8aFLdbEj.cjs +1 -0
- package/dist/{bounds-CFI9wDXn.js → bounds-BtccGJW0.js} +22 -18
- package/dist/bounds.cjs +1 -1
- package/dist/bounds.js +1 -1
- package/dist/{box-Mf8E1Ypp.cjs → box-BpSX4si6.cjs} +1 -1
- package/dist/{box-DVCNGsJG.js → box-CYXc9-qp.js} +2 -2
- package/dist/box.cjs +1 -1
- package/dist/box.js +1 -1
- package/dist/deep.cjs +1 -1
- package/dist/deep.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +169 -151
- package/dist/location-Cn1ByVTZ.js +95 -0
- package/dist/location-DLP2ZS0o.cjs +1 -0
- package/dist/location.cjs +1 -1
- package/dist/location.js +1 -1
- package/dist/path-1tZLZ4AN.cjs +1 -0
- package/dist/path-DD6ytXzr.js +76 -0
- package/dist/{position-CFc9RsSn.js → position-DJXB-pDS.js} +2 -2
- package/dist/{position-DKhPhvPh.cjs → position-JCN6-sJC.cjs} +1 -1
- package/dist/position.cjs +1 -1
- package/dist/position.js +1 -1
- package/dist/record.cjs +1 -1
- package/dist/record.js +5 -4
- package/dist/scale-BI4wJF3b.cjs +1 -0
- package/dist/{scale-DNQE1LMm.js → scale-rZ1YKDFy.js} +70 -67
- package/dist/scale.cjs +1 -1
- package/dist/scale.js +1 -1
- package/dist/series-BN9CILsQ.cjs +11 -0
- package/dist/{series-sjWkW8qe.js → series-CnEQe1dh.js} +473 -370
- package/dist/spatial.cjs +1 -1
- package/dist/spatial.js +6 -6
- package/dist/src/breaker/breaker.d.ts +33 -0
- package/dist/src/breaker/breaker.d.ts.map +1 -0
- package/dist/src/breaker/breaker.spec.d.ts +2 -0
- package/dist/src/breaker/breaker.spec.d.ts.map +1 -0
- package/dist/src/breaker/index.d.ts +2 -0
- package/dist/src/breaker/index.d.ts.map +1 -0
- package/dist/src/change/change.d.ts +5 -5
- package/dist/src/change/change.d.ts.map +1 -1
- package/dist/src/control/control.d.ts +5 -5
- package/dist/src/control/control.d.ts.map +1 -1
- package/dist/src/deep/path.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/mock/MockGLBufferController.d.ts +2 -2
- package/dist/src/mock/MockGLBufferController.d.ts.map +1 -1
- package/dist/src/record.d.ts +1 -0
- package/dist/src/record.d.ts.map +1 -1
- package/dist/src/record.spec.d.ts +2 -0
- package/dist/src/record.spec.d.ts.map +1 -0
- package/dist/src/sleep/index.d.ts +2 -0
- package/dist/src/sleep/index.d.ts.map +1 -0
- package/dist/src/sleep/sleep.d.ts +3 -0
- package/dist/src/sleep/sleep.d.ts.map +1 -0
- package/dist/src/spatial/bounds/bounds.d.ts +12 -0
- package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
- package/dist/src/spatial/location/location.d.ts +1 -0
- package/dist/src/spatial/location/location.d.ts.map +1 -1
- package/dist/src/spatial/scale/scale.d.ts +17 -1
- package/dist/src/spatial/scale/scale.d.ts.map +1 -1
- package/dist/src/spatial/xy/xy.d.ts +36 -1
- package/dist/src/spatial/xy/xy.d.ts.map +1 -1
- package/dist/src/telem/gl.d.ts +2 -2
- package/dist/src/telem/gl.d.ts.map +1 -1
- package/dist/src/telem/series.d.ts +16 -9
- package/dist/src/telem/series.d.ts.map +1 -1
- package/dist/src/telem/telem.d.ts +43 -2
- package/dist/src/telem/telem.d.ts.map +1 -1
- package/dist/src/zodutil/zodutil.d.ts.map +1 -1
- package/dist/telem.cjs +1 -1
- package/dist/telem.js +11 -10
- package/dist/xy-DQdccWlc.js +128 -0
- package/dist/xy-LADI2wVU.cjs +1 -0
- package/dist/xy.cjs +1 -1
- package/dist/xy.js +1 -1
- package/dist/zodutil-BRjUdYAv.cjs +1 -0
- package/dist/{zodutil-Tmuc4CNq.js → zodutil-DI4gVZkT.js} +11 -11
- package/dist/zodutil.cjs +1 -1
- package/dist/zodutil.js +1 -1
- package/package.json +64 -65
- package/src/binary/encoder.ts +3 -3
- package/src/breaker/breaker.spec.ts +74 -0
- package/src/breaker/breaker.ts +32 -0
- package/src/breaker/index.ts +1 -0
- package/src/deep/path.ts +6 -1
- package/src/index.ts +1 -0
- package/src/mock/MockGLBufferController.ts +6 -6
- package/src/record.spec.ts +29 -0
- package/src/record.ts +6 -0
- package/src/sleep/index.ts +1 -0
- package/src/sleep/sleep.ts +6 -0
- package/src/spatial/bounds/bounds.spec.ts +6 -0
- package/src/spatial/bounds/bounds.ts +16 -0
- package/src/spatial/location/location.ts +1 -0
- package/src/spatial/scale/scale.spec.ts +62 -51
- package/src/spatial/scale/scale.ts +21 -12
- package/src/spatial/xy/xy.spec.ts +22 -1
- package/src/spatial/xy/xy.ts +101 -5
- package/src/strings/strings.spec.ts +0 -4
- package/src/telem/gl.ts +6 -2
- package/src/telem/series.spec.ts +3 -3
- package/src/telem/series.ts +45 -16
- package/src/telem/telem.ts +121 -2
- package/src/zodutil/zodutil.ts +4 -1
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/bounds-DzCDHgdE.cjs +0 -1
- package/dist/location-CI9x53qR.js +0 -94
- package/dist/location-DetomF8Z.cjs +0 -1
- package/dist/path-BBCx3K6k.cjs +0 -1
- package/dist/path-CmnoH3RC.js +0 -72
- package/dist/scale-CT61XD_X.cjs +0 -1
- package/dist/series-DWLXo7J6.cjs +0 -11
- package/dist/xy-CrgPnICw.js +0 -89
- package/dist/xy-cP-FXJtR.cjs +0 -1
- package/dist/zodutil-C6RYzvXd.cjs +0 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { breaker } from "@/breaker";
|
|
3
|
+
import { TimeSpan } from "@/telem";
|
|
4
|
+
|
|
5
|
+
describe("breaker", () => {
|
|
6
|
+
it("should allow first attempt without sleeping", async () => {
|
|
7
|
+
const mockSleep = vi.fn();
|
|
8
|
+
const brk = breaker.create({ sleepFn: mockSleep });
|
|
9
|
+
const canRetry = await brk();
|
|
10
|
+
|
|
11
|
+
expect(canRetry).toBe(true);
|
|
12
|
+
expect(mockSleep).toHaveBeenCalled();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should retry specified number of times before failing", async () => {
|
|
16
|
+
const mockSleep = vi.fn();
|
|
17
|
+
const brk = breaker.create({
|
|
18
|
+
maxRetries: 2,
|
|
19
|
+
interval: TimeSpan.milliseconds(1),
|
|
20
|
+
sleepFn: mockSleep,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// First attempt
|
|
24
|
+
expect(await brk()).toBe(true);
|
|
25
|
+
// Second attempt
|
|
26
|
+
expect(await brk()).toBe(true);
|
|
27
|
+
// Third attempt (should fail)
|
|
28
|
+
expect(await brk()).toBe(false);
|
|
29
|
+
|
|
30
|
+
expect(mockSleep).toHaveBeenCalledTimes(2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should increase delay between retries according to scale", async () => {
|
|
34
|
+
const mockSleep = vi.fn();
|
|
35
|
+
const brk = breaker.create({
|
|
36
|
+
interval: TimeSpan.seconds(1),
|
|
37
|
+
maxRetries: 3,
|
|
38
|
+
scale: 2,
|
|
39
|
+
sleepFn: mockSleep,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await brk(); // First attempt - 1s
|
|
43
|
+
await brk(); // Second attempt - 1s * 2 = 2s;
|
|
44
|
+
await brk(); // Third attempt - 2s *2 = 4s;
|
|
45
|
+
|
|
46
|
+
expect(mockSleep).toHaveBeenNthCalledWith(1, TimeSpan.seconds(1));
|
|
47
|
+
expect(mockSleep).toHaveBeenNthCalledWith(2, TimeSpan.seconds(2));
|
|
48
|
+
expect(mockSleep).toHaveBeenNthCalledWith(3, TimeSpan.seconds(4));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should use default values when no options provided", async () => {
|
|
52
|
+
const brk = breaker.create();
|
|
53
|
+
let attempts = 0;
|
|
54
|
+
|
|
55
|
+
while (await brk()) {
|
|
56
|
+
attempts++;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
expect(attempts).toBe(5); // Default maxRetries is 5
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should use custom sleep function when provided", async () => {
|
|
63
|
+
const customSleep = vi.fn();
|
|
64
|
+
const brk = breaker.create({
|
|
65
|
+
interval: TimeSpan.milliseconds(100),
|
|
66
|
+
sleepFn: customSleep,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await brk();
|
|
70
|
+
await brk();
|
|
71
|
+
|
|
72
|
+
expect(customSleep).toHaveBeenCalledTimes(2);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { sleep } from "@/sleep";
|
|
2
|
+
import { CrudeTimeSpan, TimeSpan } from "@/telem";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
export const breakerConfig = z.object({
|
|
6
|
+
interval: TimeSpan.z.optional(),
|
|
7
|
+
maxRetries: z.number().optional(),
|
|
8
|
+
scale: z.number().optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export interface Config extends Omit<z.infer<typeof breakerConfig>, "interval"> {
|
|
12
|
+
interval?: CrudeTimeSpan;
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
scale?: number;
|
|
15
|
+
sleepFn?: (duration: TimeSpan) => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const create = (options: Config = {}): (() => Promise<boolean>) => {
|
|
19
|
+
const sleepFn = options.sleepFn || sleep.sleep;
|
|
20
|
+
const maxRetries = options.maxRetries ?? 5;
|
|
21
|
+
const scale = options.scale ?? 1;
|
|
22
|
+
let retries = 0;
|
|
23
|
+
let interval = new TimeSpan(options.interval ?? TimeSpan.milliseconds(1));
|
|
24
|
+
return async () => {
|
|
25
|
+
// Change from arrow function to regular function to preserve 'this'
|
|
26
|
+
if (retries >= maxRetries) return false;
|
|
27
|
+
await sleepFn(interval);
|
|
28
|
+
interval = interval.mult(scale);
|
|
29
|
+
retries++;
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as breaker from "@/breaker/breaker";
|
package/src/deep/path.ts
CHANGED
|
@@ -163,7 +163,12 @@ export const set = <V>(obj: V, path: string, value: unknown): void => {
|
|
|
163
163
|
result[part] ??= {};
|
|
164
164
|
result = result[part] as UnknownRecord;
|
|
165
165
|
}
|
|
166
|
-
|
|
166
|
+
try {
|
|
167
|
+
result[parts[parts.length - 1]] = value;
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.error("failed to set value", value, "at path", path, "on object", obj);
|
|
170
|
+
throw e;
|
|
171
|
+
}
|
|
167
172
|
};
|
|
168
173
|
|
|
169
174
|
/**
|
package/src/index.ts
CHANGED
|
@@ -41,14 +41,14 @@ export class MockGLBufferController implements GLBufferController {
|
|
|
41
41
|
|
|
42
42
|
bufferData(
|
|
43
43
|
target: number,
|
|
44
|
-
dataOrSize:
|
|
44
|
+
dataOrSize: AllowSharedBufferSource | number,
|
|
45
45
|
usage: number,
|
|
46
46
|
): void {
|
|
47
47
|
if (typeof dataOrSize === "number")
|
|
48
48
|
this.buffers[this.targets[target]] = new ArrayBuffer(dataOrSize);
|
|
49
|
-
else this.buffers[this.targets[target]] = dataOrSize;
|
|
49
|
+
else this.buffers[this.targets[target]] = dataOrSize as ArrayBuffer;
|
|
50
50
|
|
|
51
|
-
this.bufferDataMock(target, dataOrSize, usage);
|
|
51
|
+
this.bufferDataMock(target, dataOrSize as ArrayBuffer, usage);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
bindBuffer(target: number, buffer: WebGLBuffer | null): void {
|
|
@@ -57,14 +57,14 @@ export class MockGLBufferController implements GLBufferController {
|
|
|
57
57
|
this.bindBufferMock(target, buffer);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
bufferSubData(target: number, offset: number, data:
|
|
60
|
+
bufferSubData(target: number, offset: number, data: AllowSharedBufferSource): void {
|
|
61
61
|
let buffer = this.buffers[this.targets[target]];
|
|
62
62
|
if (buffer == null) {
|
|
63
63
|
buffer = new ArrayBuffer(offset + data.byteLength);
|
|
64
64
|
this.buffers[target] = buffer;
|
|
65
65
|
}
|
|
66
66
|
const view = new Uint8Array(buffer);
|
|
67
|
-
view.set(new Uint8Array(data), offset);
|
|
68
|
-
this.bufferSubDataMock(target, offset, data);
|
|
67
|
+
view.set(new Uint8Array(data as ArrayBuffer), offset);
|
|
68
|
+
this.bufferSubDataMock(target, offset, data as ArrayBuffer);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { mapValues } from "@/record";
|
|
4
|
+
|
|
5
|
+
describe("mapValues", () => {
|
|
6
|
+
it("should map values of a record using the provided function", () => {
|
|
7
|
+
const input = { a: 1, b: 2, c: 3 };
|
|
8
|
+
const result = mapValues(input, (x) => x * 2);
|
|
9
|
+
expect(result).toEqual({ a: 2, b: 4, c: 6 });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should handle empty objects", () => {
|
|
13
|
+
const input = {};
|
|
14
|
+
const result = mapValues(input, (x) => x * 2);
|
|
15
|
+
expect(result).toEqual({});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should handle different value types", () => {
|
|
19
|
+
const input = { name: "John", age: 30 };
|
|
20
|
+
const result = mapValues(input, (value) => String(value));
|
|
21
|
+
expect(result).toEqual({ name: "John", age: "30" });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should preserve keys while transforming values", () => {
|
|
25
|
+
const input = { x: "hello", y: "world" };
|
|
26
|
+
const result = mapValues(input, (str) => str.toUpperCase());
|
|
27
|
+
expect(result).toEqual({ x: "HELLO", y: "WORLD" });
|
|
28
|
+
});
|
|
29
|
+
});
|
package/src/record.ts
CHANGED
|
@@ -35,3 +35,9 @@ export type Entries<T> = Array<
|
|
|
35
35
|
|
|
36
36
|
export const getEntries = <T extends Record<Key, unknown>>(obj: T): Entries<T> =>
|
|
37
37
|
Object.entries(obj) as Entries<T>;
|
|
38
|
+
|
|
39
|
+
export const mapValues = <T extends Record<Key, unknown>, U>(
|
|
40
|
+
obj: T,
|
|
41
|
+
fn: (value: T[keyof T], key: Key) => U,
|
|
42
|
+
): Record<Key, U> =>
|
|
43
|
+
Object.fromEntries(getEntries(obj).map(([key, value]) => [key, fn(value, key as Key)]));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as sleep from "@/sleep/sleep";
|
|
@@ -387,4 +387,10 @@ describe("Bounds", () => {
|
|
|
387
387
|
expect(bounds.clamp(b, 4)).toEqual(2);
|
|
388
388
|
});
|
|
389
389
|
});
|
|
390
|
+
describe("mean", () => {
|
|
391
|
+
it("should return the mean of the bounds", () => {
|
|
392
|
+
const b = bounds.construct([1, 3]);
|
|
393
|
+
expect(bounds.mean(b)).toEqual(2);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
390
396
|
});
|
|
@@ -191,6 +191,22 @@ export const isFinite = (a: Crude): boolean => {
|
|
|
191
191
|
return Number.isFinite(_a.lower) && Number.isFinite(_a.upper);
|
|
192
192
|
};
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Returns the mean value between the lower and upper bounds.
|
|
196
|
+
*
|
|
197
|
+
* @param a - The bounds to find the mean of. Can be either a strict bounds object
|
|
198
|
+
* with 'lower' and 'upper' properties or an array of length 2.
|
|
199
|
+
* @returns The mean value between the lower and upper bounds.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* bounds.mean([0, 10]) // => 5
|
|
203
|
+
* bounds.mean({ lower: 0, upper: 10 }) // => 5
|
|
204
|
+
*/
|
|
205
|
+
export const mean = (a: Crude): number => {
|
|
206
|
+
const _a = construct(a);
|
|
207
|
+
return (_a.upper + _a.lower) / 2;
|
|
208
|
+
};
|
|
209
|
+
|
|
194
210
|
/**
|
|
195
211
|
* @returns bounds that have the maximum span of the given bounds i.e. the min of all
|
|
196
212
|
* of the lower bounds and the max of all of the upper bounds.
|
|
@@ -15,60 +15,71 @@ import { Scale, XY } from "@/spatial/scale/scale";
|
|
|
15
15
|
type ScaleSpec = [name: string, scale: Scale<number>, i: number, o: number];
|
|
16
16
|
|
|
17
17
|
describe("Scale", () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
expect(scale.pos(i)).toBe(o);
|
|
43
|
-
});
|
|
18
|
+
const simpleScale = Scale.scale<number>(0, 10).scale(0, 1);
|
|
19
|
+
const translateScale = Scale.scale<number>(0, 10).translate(5).scale(0, 1);
|
|
20
|
+
const translateMagnifyScale = Scale.scale<number>(0, 10)
|
|
21
|
+
.translate(5)
|
|
22
|
+
.magnify(2)
|
|
23
|
+
.scale(0, 1);
|
|
24
|
+
describe("position", () => {
|
|
25
|
+
const positionSpecs: ScaleSpec[] = [
|
|
26
|
+
["basic", simpleScale, 0, 0],
|
|
27
|
+
["basic II", simpleScale, 5, 0.5],
|
|
28
|
+
["reverse basic", simpleScale.reverse(), 0, 0],
|
|
29
|
+
["reverse basic II", simpleScale.reverse(), 0.5, 5],
|
|
30
|
+
["translate", translateScale, 0, 0.5],
|
|
31
|
+
["translate II", translateScale, 5, 1],
|
|
32
|
+
["reverse translate", translateScale.reverse(), 0.5, 0],
|
|
33
|
+
["reverse translate II", translateScale.reverse(), 0, -5],
|
|
34
|
+
["translate magnify", translateMagnifyScale, 0, 1],
|
|
35
|
+
["translate magnify II", translateMagnifyScale, 5, 2],
|
|
36
|
+
["reverse translate magnify", translateMagnifyScale.reverse(), 1, 0],
|
|
37
|
+
["reverse translate magnify II", translateMagnifyScale.reverse(), 0, -5],
|
|
38
|
+
];
|
|
39
|
+
positionSpecs.forEach(([name, scale, i, o]) => {
|
|
40
|
+
it(`should return ${o} for ${i} on ${name}`, () => {
|
|
41
|
+
expect(scale.pos(i)).toBe(o);
|
|
44
42
|
});
|
|
45
43
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
44
|
+
});
|
|
45
|
+
describe("dimension", () => {
|
|
46
|
+
const dimensionSpecs: ScaleSpec[] = [
|
|
47
|
+
["basic", simpleScale, 0, 0],
|
|
48
|
+
["basic II", simpleScale, 5, 0.5],
|
|
49
|
+
["reverse basic", simpleScale.reverse(), 0, 0],
|
|
50
|
+
["reverse basic II", simpleScale.reverse(), 0.5, 5],
|
|
51
|
+
["translate", translateScale, 0, 0],
|
|
52
|
+
["translate II", translateScale, 5, 0.5],
|
|
53
|
+
["reverse translate", translateScale.reverse(), 0.5, 5],
|
|
54
|
+
["translate magnify", translateMagnifyScale, 0, 0],
|
|
55
|
+
["translate magnify II", translateMagnifyScale, 5, 1],
|
|
56
|
+
];
|
|
57
|
+
dimensionSpecs.forEach(([name, scale, i, o]) => {
|
|
58
|
+
it(`should return ${o} for ${i} on ${name}`, () => expect(scale.dim(i)).toBe(o));
|
|
62
59
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
});
|
|
61
|
+
describe("XYScale", () => {
|
|
62
|
+
test("converting a DOM rect to decimal coordinates", () => {
|
|
63
|
+
const s = XY.scale(box.construct(100, 100, 1000, 1000)).scale(box.DECIMAL);
|
|
64
|
+
const b1 = s.box(box.construct(100, 100, 1000, 1000));
|
|
65
|
+
expect(box.bottomLeft(b1)).toEqual({ x: 0, y: 0 });
|
|
66
|
+
const b2 = s.box(box.construct(200, 200, 200, 200));
|
|
67
|
+
expect(box.bottomLeft(b2).x).toBeCloseTo(0.1);
|
|
68
|
+
expect(box.bottomLeft(b2).y).toBeCloseTo(0.7);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("transform", () => {
|
|
72
|
+
it("should return the correct transform", () => {
|
|
73
|
+
const s = Scale.scale<number>(0, 10).translate(5).magnify(2).scale(0, 1);
|
|
74
|
+
const t = s.transform;
|
|
75
|
+
expect(t.scale).toBe(0.2);
|
|
76
|
+
expect(t.offset).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
it("should return the correct transform for an XY scale", () => {
|
|
79
|
+
const s = XY.translate({ x: 5, y: 5 }).magnify({ x: 2, y: 2 });
|
|
80
|
+
const t = s.transform;
|
|
81
|
+
expect(t.scale).toEqual({ x: 2, y: 2 });
|
|
82
|
+
expect(t.offset).toEqual({ x: 10, y: 10 });
|
|
72
83
|
});
|
|
73
84
|
});
|
|
74
85
|
});
|
|
@@ -19,9 +19,14 @@ import * as location from "@/spatial/location/location";
|
|
|
19
19
|
import * as xy from "@/spatial/xy/xy";
|
|
20
20
|
|
|
21
21
|
export const crudeXYTransform = z.object({ offset: xy.crudeZ, scale: xy.crudeZ });
|
|
22
|
-
|
|
23
22
|
export type XYTransformT = z.infer<typeof crudeXYTransform>;
|
|
24
23
|
|
|
24
|
+
export const transform = z.object({ offset: z.number(), scale: z.number() });
|
|
25
|
+
export type TransformT<T extends numeric.Value = number> = {
|
|
26
|
+
offset: T;
|
|
27
|
+
scale: T;
|
|
28
|
+
};
|
|
29
|
+
|
|
25
30
|
export type BoundVariant = "domain" | "range";
|
|
26
31
|
|
|
27
32
|
type ValueType = "position" | "dimension";
|
|
@@ -402,20 +407,13 @@ export class Scale<T extends numeric.Value = number> {
|
|
|
402
407
|
return scale;
|
|
403
408
|
}
|
|
404
409
|
|
|
410
|
+
get transform(): TransformT<T> {
|
|
411
|
+
return { scale: this.dim(1 as T), offset: this.pos(0 as T) };
|
|
412
|
+
}
|
|
413
|
+
|
|
405
414
|
static readonly IDENTITY = new Scale();
|
|
406
415
|
}
|
|
407
416
|
|
|
408
|
-
export const xyScaleToTransform = (scale: XY): XYTransformT => ({
|
|
409
|
-
scale: {
|
|
410
|
-
x: scale.x.dim(1),
|
|
411
|
-
y: scale.y.dim(1),
|
|
412
|
-
},
|
|
413
|
-
offset: {
|
|
414
|
-
x: scale.x.pos(0),
|
|
415
|
-
y: scale.y.pos(0),
|
|
416
|
-
},
|
|
417
|
-
});
|
|
418
|
-
|
|
419
417
|
export class XY {
|
|
420
418
|
x: Scale<number>;
|
|
421
419
|
y: Scale<number>;
|
|
@@ -537,6 +535,10 @@ export class XY {
|
|
|
537
535
|
return { x: this.x.pos(xy.x), y: this.y.pos(xy.y) };
|
|
538
536
|
}
|
|
539
537
|
|
|
538
|
+
dim(xy: xy.XY): xy.XY {
|
|
539
|
+
return { x: this.x.dim(xy.x), y: this.y.dim(xy.y) };
|
|
540
|
+
}
|
|
541
|
+
|
|
540
542
|
box(b: Box): Box {
|
|
541
543
|
return box.construct(
|
|
542
544
|
this.pos(b.one),
|
|
@@ -547,5 +549,12 @@ export class XY {
|
|
|
547
549
|
);
|
|
548
550
|
}
|
|
549
551
|
|
|
552
|
+
get transform(): XYTransformT {
|
|
553
|
+
return {
|
|
554
|
+
scale: this.dim({ x: 1, y: 1 }),
|
|
555
|
+
offset: this.pos({ x: 0, y: 0 }),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
550
559
|
static readonly IDENTITY = new XY();
|
|
551
560
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
|
-
import { describe, expect, it,test } from "vitest";
|
|
10
|
+
import { describe, expect, it, test } from "vitest";
|
|
11
11
|
|
|
12
12
|
import * as xy from "@/spatial/xy/xy";
|
|
13
13
|
|
|
@@ -122,4 +122,25 @@ describe("XY", () => {
|
|
|
122
122
|
expect(xy.scale([1, 2], 2)).toEqual({ x: 2, y: 4 });
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
|
+
describe("sub", () => {
|
|
126
|
+
it("should subtract the second point from the first point", () => {
|
|
127
|
+
expect(xy.sub([1, 2], [2, 1])).toEqual({ x: -1, y: 1 });
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("calculateMiters", () => {
|
|
132
|
+
it("should calculate the miters of the given points", () => {
|
|
133
|
+
const points: xy.XY[] = [
|
|
134
|
+
{ x: 0, y: 0 },
|
|
135
|
+
{ x: 0, y: 1 },
|
|
136
|
+
{ x: 1, y: 1 },
|
|
137
|
+
];
|
|
138
|
+
const miters = xy.calculateMiters(points, 1);
|
|
139
|
+
expect(miters).toEqual([
|
|
140
|
+
{ x: -1, y: 0 },
|
|
141
|
+
{ x: -1, y: 1 },
|
|
142
|
+
{ x: -0, y: 1 },
|
|
143
|
+
]);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
125
146
|
});
|
package/src/spatial/xy/xy.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
xy,
|
|
22
22
|
} from "@/spatial/base";
|
|
23
23
|
|
|
24
|
-
export { type ClientXY as Client, clientXY, type XY,xy };
|
|
24
|
+
export { type ClientXY as Client, clientXY, type XY, xy };
|
|
25
25
|
|
|
26
26
|
/** A crude representation of a {@link XY} coordinate as a zod schema. */
|
|
27
27
|
export const crudeZ = z.union([
|
|
@@ -143,10 +143,10 @@ export const distance = (ca: Crude, cb: Crude): number => {
|
|
|
143
143
|
* @returns the translation that would need to be applied to move the first coordinate
|
|
144
144
|
* to the second coordinate.
|
|
145
145
|
*/
|
|
146
|
-
export const translation = (
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
return { x:
|
|
146
|
+
export const translation = (to: Crude, from: Crude): XY => {
|
|
147
|
+
const to_ = construct(to);
|
|
148
|
+
const from_ = construct(from);
|
|
149
|
+
return { x: from_.x - to_.x, y: from_.y - to_.y };
|
|
150
150
|
};
|
|
151
151
|
|
|
152
152
|
/** @returns true if both the x and y coordinates of the given coordinate are NaN. */
|
|
@@ -183,3 +183,99 @@ export const truncate = (a: Crude, precision: number = 0): XY => {
|
|
|
183
183
|
const xy = construct(a);
|
|
184
184
|
return { x: Number(xy.x.toFixed(precision)), y: Number(xy.y.toFixed(precision)) };
|
|
185
185
|
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Subtracts the second coordinate from the first coordinate.
|
|
189
|
+
* @param a - The first coordinate.
|
|
190
|
+
* @param b - The second coordinate.
|
|
191
|
+
* @returns The difference between the two coordinates.
|
|
192
|
+
*/
|
|
193
|
+
export const sub = (a: Crude, b: Crude): XY => {
|
|
194
|
+
const xy = construct(a);
|
|
195
|
+
const xy_ = construct(b);
|
|
196
|
+
return { x: xy.x - xy_.x, y: xy.y - xy_.y };
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Interprets the given coordinates as a vector and returns the normal of the given
|
|
201
|
+
* vector.
|
|
202
|
+
* @param a - The coordinates to get the normal of.
|
|
203
|
+
* @returns The normal of the given coordinates.
|
|
204
|
+
*/
|
|
205
|
+
export const normal = (a: Crude): XY => {
|
|
206
|
+
const xy = construct(a);
|
|
207
|
+
const length = Math.hypot(xy.x, xy.y);
|
|
208
|
+
if (length === 0) return { x: 0, y: 0 };
|
|
209
|
+
return { x: -xy.y / length, y: xy.x / length };
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Interprets the given coordinates as a vector and returns the unit vector of the given
|
|
214
|
+
* vector.
|
|
215
|
+
* @param a - The coordinates to get the unit vector of.
|
|
216
|
+
* @returns The unit vector of the given coordinates.
|
|
217
|
+
*/
|
|
218
|
+
export const normalize = (a: Crude): XY => {
|
|
219
|
+
const xy = construct(a);
|
|
220
|
+
const length = Math.hypot(xy.x, xy.y);
|
|
221
|
+
if (length === 0) return { x: 0, y: 0 };
|
|
222
|
+
return { x: xy.x / length, y: xy.y / length };
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @returns the average of the given coordinates.
|
|
227
|
+
* @param coordinates - The coordinates to average.
|
|
228
|
+
*/
|
|
229
|
+
export const average = (...coordinates: Crude[]): XY => {
|
|
230
|
+
const sum = coordinates.reduce((p, c) => translate(p, c), ZERO);
|
|
231
|
+
return scale(sum, 1 / coordinates.length);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Calculates the miter vectors for the given path and offset. This function is useful
|
|
236
|
+
* for calculate the translations need to create an offset and parallel path to the
|
|
237
|
+
* given path.
|
|
238
|
+
* @param path - The path to calculate the miters for.
|
|
239
|
+
* @param offset - The magnitude of the miter vectors.
|
|
240
|
+
* @returns The miter vectors for the given path.
|
|
241
|
+
*/
|
|
242
|
+
export const calculateMiters = (path: XY[], offset: number): XY[] => {
|
|
243
|
+
const miters: XY[] = [];
|
|
244
|
+
for (let i = 0; i < path.length; i++) {
|
|
245
|
+
const currPoint = path[i];
|
|
246
|
+
let normalPrev: XY;
|
|
247
|
+
let normalNext: XY;
|
|
248
|
+
let miterNormal: XY;
|
|
249
|
+
let miterLength: number;
|
|
250
|
+
if (i === 0) {
|
|
251
|
+
const nextPoint = path[i + 1];
|
|
252
|
+
const dirNext = sub(nextPoint, currPoint);
|
|
253
|
+
normalNext = normal(dirNext);
|
|
254
|
+
miterNormal = normalNext;
|
|
255
|
+
miterLength = offset;
|
|
256
|
+
} else if (i === path.length - 1) {
|
|
257
|
+
const prevPoint = path[i - 1];
|
|
258
|
+
const dirPrev = sub(currPoint, prevPoint);
|
|
259
|
+
normalPrev = normal(dirPrev);
|
|
260
|
+
miterNormal = normalPrev;
|
|
261
|
+
miterLength = offset;
|
|
262
|
+
} else {
|
|
263
|
+
const prevPoint = path[i - 1];
|
|
264
|
+
const nextPoint = path[i + 1];
|
|
265
|
+
const dirPrev = sub(currPoint, prevPoint);
|
|
266
|
+
const dirNext = sub(nextPoint, currPoint);
|
|
267
|
+
normalPrev = normal(dirPrev);
|
|
268
|
+
normalNext = normal(dirNext);
|
|
269
|
+
const angle = Math.acos(
|
|
270
|
+
(dirPrev.x * dirNext.x + dirPrev.y * dirNext.y) /
|
|
271
|
+
(Math.hypot(dirPrev.x, dirPrev.y) * Math.hypot(dirNext.x, dirNext.y)),
|
|
272
|
+
);
|
|
273
|
+
const sinHalfAngle = Math.sin(angle / 2);
|
|
274
|
+
if (sinHalfAngle === 0) miterLength = offset;
|
|
275
|
+
else miterLength = offset / sinHalfAngle;
|
|
276
|
+
miterNormal = normalize(average(normalPrev, normalNext));
|
|
277
|
+
}
|
|
278
|
+
miters.push(scale(miterNormal, miterLength));
|
|
279
|
+
}
|
|
280
|
+
return miters;
|
|
281
|
+
};
|
|
@@ -62,7 +62,3 @@ describe("generateShortIdentifiers", () => {
|
|
|
62
62
|
expect.arrayContaining(["ab", "a_b", "alibob", "ali_bob"]),
|
|
63
63
|
));
|
|
64
64
|
});
|
|
65
|
-
|
|
66
|
-
console.log(generateShortIdentifiers("John Doe")); // ["jd", "j_d", "johdoe", "joh_doe"]
|
|
67
|
-
console.log(generateShortIdentifiers("Alice 123")); // ["a1", "a_1", "ali123", "ali_123"]
|
|
68
|
-
console.log(generateShortIdentifiers("Bob")); // ["bob"]
|
package/src/telem/gl.ts
CHANGED
|
@@ -11,9 +11,13 @@ import { z } from "zod";
|
|
|
11
11
|
|
|
12
12
|
export interface GLBufferController {
|
|
13
13
|
createBuffer: () => WebGLBuffer | null;
|
|
14
|
-
bufferData: ((target: number, data:
|
|
14
|
+
bufferData: ((target: number, data: AllowSharedBufferSource, usage: number) => void) &
|
|
15
15
|
((target: number, size: number, usage: number) => void);
|
|
16
|
-
bufferSubData: (
|
|
16
|
+
bufferSubData: (
|
|
17
|
+
target: number,
|
|
18
|
+
offset: number,
|
|
19
|
+
data: AllowSharedBufferSource,
|
|
20
|
+
) => void;
|
|
17
21
|
bindBuffer: (target: number, buffer: WebGLBuffer | null) => void;
|
|
18
22
|
deleteBuffer: (buffer: WebGLBuffer | null) => void;
|
|
19
23
|
ARRAY_BUFFER: number;
|
package/src/telem/series.spec.ts
CHANGED
|
@@ -435,7 +435,7 @@ describe("Series", () => {
|
|
|
435
435
|
const buf = controller.buffers[series.glBuffer as number];
|
|
436
436
|
expect(buf).toBeDefined();
|
|
437
437
|
expect(buf.byteLength).toEqual(12);
|
|
438
|
-
expect(buf).toEqual(new Float32Array([1, 2, 3]));
|
|
438
|
+
expect(buf).toEqual(new Float32Array([1, 2, 3]).buffer);
|
|
439
439
|
});
|
|
440
440
|
it("should correctly update a buffer when writing to an allocated array", () => {
|
|
441
441
|
const series = Series.alloc({ capacity: 10, dataType: DataType.FLOAT32 });
|
|
@@ -694,7 +694,8 @@ describe("Series", () => {
|
|
|
694
694
|
data: new Float32Array([1, 2, 3]),
|
|
695
695
|
timeRange: new TimeRange(1, 3),
|
|
696
696
|
}).digest;
|
|
697
|
-
expect(digest.alignment).toEqual({
|
|
697
|
+
expect(digest.alignment.lower).toEqual({ domain: 0n, sample: 0n });
|
|
698
|
+
expect(digest.alignment.upper).toEqual({ domain: 0n, sample: 3n });
|
|
698
699
|
expect(digest.dataType).toEqual("float32");
|
|
699
700
|
expect(digest.length).toEqual(3);
|
|
700
701
|
expect(digest.timeRange).toEqual(new TimeRange(1, 3).toString());
|
|
@@ -885,7 +886,6 @@ describe("MultiSeries", () => {
|
|
|
885
886
|
data: new Float32Array([6, 7, 8, 9, 10]),
|
|
886
887
|
alignment: 8n,
|
|
887
888
|
});
|
|
888
|
-
console.log(a.alignmentBounds.upper);
|
|
889
889
|
const multi = new MultiSeries([a, b]);
|
|
890
890
|
const iter = multi.subAlignmentIterator(7n, 10n);
|
|
891
891
|
expect(iter.next().value).toEqual(6);
|