@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.
Files changed (118) hide show
  1. package/.turbo/turbo-build.log +30 -30
  2. package/dist/bounds-8aFLdbEj.cjs +1 -0
  3. package/dist/{bounds-CFI9wDXn.js → bounds-BtccGJW0.js} +22 -18
  4. package/dist/bounds.cjs +1 -1
  5. package/dist/bounds.js +1 -1
  6. package/dist/{box-Mf8E1Ypp.cjs → box-BpSX4si6.cjs} +1 -1
  7. package/dist/{box-DVCNGsJG.js → box-CYXc9-qp.js} +2 -2
  8. package/dist/box.cjs +1 -1
  9. package/dist/box.js +1 -1
  10. package/dist/deep.cjs +1 -1
  11. package/dist/deep.js +1 -1
  12. package/dist/index.cjs +2 -2
  13. package/dist/index.js +169 -151
  14. package/dist/location-Cn1ByVTZ.js +95 -0
  15. package/dist/location-DLP2ZS0o.cjs +1 -0
  16. package/dist/location.cjs +1 -1
  17. package/dist/location.js +1 -1
  18. package/dist/path-1tZLZ4AN.cjs +1 -0
  19. package/dist/path-DD6ytXzr.js +76 -0
  20. package/dist/{position-CFc9RsSn.js → position-DJXB-pDS.js} +2 -2
  21. package/dist/{position-DKhPhvPh.cjs → position-JCN6-sJC.cjs} +1 -1
  22. package/dist/position.cjs +1 -1
  23. package/dist/position.js +1 -1
  24. package/dist/record.cjs +1 -1
  25. package/dist/record.js +5 -4
  26. package/dist/scale-BI4wJF3b.cjs +1 -0
  27. package/dist/{scale-DNQE1LMm.js → scale-rZ1YKDFy.js} +70 -67
  28. package/dist/scale.cjs +1 -1
  29. package/dist/scale.js +1 -1
  30. package/dist/series-BN9CILsQ.cjs +11 -0
  31. package/dist/{series-sjWkW8qe.js → series-CnEQe1dh.js} +473 -370
  32. package/dist/spatial.cjs +1 -1
  33. package/dist/spatial.js +6 -6
  34. package/dist/src/breaker/breaker.d.ts +33 -0
  35. package/dist/src/breaker/breaker.d.ts.map +1 -0
  36. package/dist/src/breaker/breaker.spec.d.ts +2 -0
  37. package/dist/src/breaker/breaker.spec.d.ts.map +1 -0
  38. package/dist/src/breaker/index.d.ts +2 -0
  39. package/dist/src/breaker/index.d.ts.map +1 -0
  40. package/dist/src/change/change.d.ts +5 -5
  41. package/dist/src/change/change.d.ts.map +1 -1
  42. package/dist/src/control/control.d.ts +5 -5
  43. package/dist/src/control/control.d.ts.map +1 -1
  44. package/dist/src/deep/path.d.ts.map +1 -1
  45. package/dist/src/index.d.ts +1 -0
  46. package/dist/src/index.d.ts.map +1 -1
  47. package/dist/src/mock/MockGLBufferController.d.ts +2 -2
  48. package/dist/src/mock/MockGLBufferController.d.ts.map +1 -1
  49. package/dist/src/record.d.ts +1 -0
  50. package/dist/src/record.d.ts.map +1 -1
  51. package/dist/src/record.spec.d.ts +2 -0
  52. package/dist/src/record.spec.d.ts.map +1 -0
  53. package/dist/src/sleep/index.d.ts +2 -0
  54. package/dist/src/sleep/index.d.ts.map +1 -0
  55. package/dist/src/sleep/sleep.d.ts +3 -0
  56. package/dist/src/sleep/sleep.d.ts.map +1 -0
  57. package/dist/src/spatial/bounds/bounds.d.ts +12 -0
  58. package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
  59. package/dist/src/spatial/location/location.d.ts +1 -0
  60. package/dist/src/spatial/location/location.d.ts.map +1 -1
  61. package/dist/src/spatial/scale/scale.d.ts +17 -1
  62. package/dist/src/spatial/scale/scale.d.ts.map +1 -1
  63. package/dist/src/spatial/xy/xy.d.ts +36 -1
  64. package/dist/src/spatial/xy/xy.d.ts.map +1 -1
  65. package/dist/src/telem/gl.d.ts +2 -2
  66. package/dist/src/telem/gl.d.ts.map +1 -1
  67. package/dist/src/telem/series.d.ts +16 -9
  68. package/dist/src/telem/series.d.ts.map +1 -1
  69. package/dist/src/telem/telem.d.ts +43 -2
  70. package/dist/src/telem/telem.d.ts.map +1 -1
  71. package/dist/src/zodutil/zodutil.d.ts.map +1 -1
  72. package/dist/telem.cjs +1 -1
  73. package/dist/telem.js +11 -10
  74. package/dist/xy-DQdccWlc.js +128 -0
  75. package/dist/xy-LADI2wVU.cjs +1 -0
  76. package/dist/xy.cjs +1 -1
  77. package/dist/xy.js +1 -1
  78. package/dist/zodutil-BRjUdYAv.cjs +1 -0
  79. package/dist/{zodutil-Tmuc4CNq.js → zodutil-DI4gVZkT.js} +11 -11
  80. package/dist/zodutil.cjs +1 -1
  81. package/dist/zodutil.js +1 -1
  82. package/package.json +64 -65
  83. package/src/binary/encoder.ts +3 -3
  84. package/src/breaker/breaker.spec.ts +74 -0
  85. package/src/breaker/breaker.ts +32 -0
  86. package/src/breaker/index.ts +1 -0
  87. package/src/deep/path.ts +6 -1
  88. package/src/index.ts +1 -0
  89. package/src/mock/MockGLBufferController.ts +6 -6
  90. package/src/record.spec.ts +29 -0
  91. package/src/record.ts +6 -0
  92. package/src/sleep/index.ts +1 -0
  93. package/src/sleep/sleep.ts +6 -0
  94. package/src/spatial/bounds/bounds.spec.ts +6 -0
  95. package/src/spatial/bounds/bounds.ts +16 -0
  96. package/src/spatial/location/location.ts +1 -0
  97. package/src/spatial/scale/scale.spec.ts +62 -51
  98. package/src/spatial/scale/scale.ts +21 -12
  99. package/src/spatial/xy/xy.spec.ts +22 -1
  100. package/src/spatial/xy/xy.ts +101 -5
  101. package/src/strings/strings.spec.ts +0 -4
  102. package/src/telem/gl.ts +6 -2
  103. package/src/telem/series.spec.ts +3 -3
  104. package/src/telem/series.ts +45 -16
  105. package/src/telem/telem.ts +121 -2
  106. package/src/zodutil/zodutil.ts +4 -1
  107. package/tsconfig.json +1 -1
  108. package/tsconfig.tsbuildinfo +1 -1
  109. package/dist/bounds-DzCDHgdE.cjs +0 -1
  110. package/dist/location-CI9x53qR.js +0 -94
  111. package/dist/location-DetomF8Z.cjs +0 -1
  112. package/dist/path-BBCx3K6k.cjs +0 -1
  113. package/dist/path-CmnoH3RC.js +0 -72
  114. package/dist/scale-CT61XD_X.cjs +0 -1
  115. package/dist/series-DWLXo7J6.cjs +0 -11
  116. package/dist/xy-CrgPnICw.js +0 -89
  117. package/dist/xy-cP-FXJtR.cjs +0 -1
  118. 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
- result[parts[parts.length - 1]] = value;
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,3 +41,4 @@ export * from "@/unique";
41
41
  export * from "@/url";
42
42
  export * from "@/worker";
43
43
  export * from "@/zodutil";
44
+ export * from "@/breaker";
@@ -41,14 +41,14 @@ export class MockGLBufferController implements GLBufferController {
41
41
 
42
42
  bufferData(
43
43
  target: number,
44
- dataOrSize: ArrayBufferLike | number,
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: ArrayBufferLike): void {
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";
@@ -0,0 +1,6 @@
1
+ import { CrudeTimeSpan, TimeSpan } from "@/telem";
2
+
3
+ export const sleep = async (span: CrudeTimeSpan): Promise<void> =>
4
+ await new Promise((resolve) =>
5
+ setTimeout(resolve, TimeSpan.fromMilliseconds(span).milliseconds),
6
+ );
@@ -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.
@@ -41,6 +41,7 @@ export {
41
41
 
42
42
  export const x = xLocation;
43
43
  export const y = yLocation;
44
+ export const center = centerLocation;
44
45
 
45
46
  export type X = XLocation;
46
47
  export type Y = YLocation;
@@ -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
- describe("Scale", () => {
19
- const simpleScale = Scale.scale<number>(0, 10).scale(0, 1);
20
- const translateScale = Scale.scale<number>(0, 10).translate(5).scale(0, 1);
21
- const translateMagnifyScale = Scale.scale<number>(0, 10)
22
- .translate(5)
23
- .magnify(2)
24
- .scale(0, 1);
25
- describe("position", () => {
26
- const positionSpecs: ScaleSpec[] = [
27
- ["basic", simpleScale, 0, 0],
28
- ["basic II", simpleScale, 5, 0.5],
29
- ["reverse basic", simpleScale.reverse(), 0, 0],
30
- ["reverse basic II", simpleScale.reverse(), 0.5, 5],
31
- ["translate", translateScale, 0, 0.5],
32
- ["translate II", translateScale, 5, 1],
33
- ["reverse translate", translateScale.reverse(), 0.5, 0],
34
- ["reverse translate II", translateScale.reverse(), 0, -5],
35
- ["translate magnify", translateMagnifyScale, 0, 1],
36
- ["translate magnify II", translateMagnifyScale, 5, 2],
37
- ["reverse translate magnify", translateMagnifyScale.reverse(), 1, 0],
38
- ["reverse translate magnify II", translateMagnifyScale.reverse(), 0, -5],
39
- ];
40
- positionSpecs.forEach(([name, scale, i, o]) => {
41
- it(`should return ${o} for ${i} on ${name}`, () => {
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
- describe("dimension", () => {
47
- const dimensionSpecs: ScaleSpec[] = [
48
- ["basic", simpleScale, 0, 0],
49
- ["basic II", simpleScale, 5, 0.5],
50
- ["reverse basic", simpleScale.reverse(), 0, 0],
51
- ["reverse basic II", simpleScale.reverse(), 0.5, 5],
52
- ["translate", translateScale, 0, 0],
53
- ["translate II", translateScale, 5, 0.5],
54
- ["reverse translate", translateScale.reverse(), 0.5, 5],
55
- ["translate magnify", translateMagnifyScale, 0, 0],
56
- ["translate magnify II", translateMagnifyScale, 5, 1],
57
- ];
58
- dimensionSpecs.forEach(([name, scale, i, o]) => {
59
- it(`should return ${o} for ${i} on ${name}`, () =>
60
- expect(scale.dim(i)).toBe(o));
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
- describe("XYScale", () => {
64
- test("converting a DOM rect to decimal coordinates", () => {
65
- const s = XY.scale(box.construct(100, 100, 1000, 1000)).scale(box.DECIMAL);
66
- const b1 = s.box(box.construct(100, 100, 1000, 1000));
67
- expect(box.bottomLeft(b1)).toEqual({ x: 0, y: 0 });
68
- const b2 = s.box(box.construct(200, 200, 200, 200));
69
- expect(box.bottomLeft(b2).x).toBeCloseTo(0.1);
70
- expect(box.bottomLeft(b2).y).toBeCloseTo(0.7);
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
  });
@@ -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 = (ca: Crude, cb: Crude): XY => {
147
- const a = construct(ca);
148
- const b = construct(cb);
149
- return { x: b.x - a.x, y: b.y - a.y };
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: ArrayBufferLike, usage: number) => void) &
14
+ bufferData: ((target: number, data: AllowSharedBufferSource, usage: number) => void) &
15
15
  ((target: number, size: number, usage: number) => void);
16
- bufferSubData: (target: number, offset: number, data: ArrayBufferLike) => void;
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;
@@ -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({ lower: 0n, upper: 3n });
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);