@synnaxlabs/x 0.7.0 → 0.9.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 +8 -7
- package/dist/binary/index.d.ts +1 -1
- package/dist/change/change.d.ts +4 -2
- package/dist/change/index.d.ts +1 -1
- package/dist/compare/index.d.ts +1 -1
- package/dist/deep/delete.d.ts +1 -1
- package/dist/deep/external.d.ts +7 -7
- package/dist/deep/index.d.ts +1 -1
- package/dist/deep/merge.d.ts +1 -1
- package/dist/deep/path.d.ts +37 -0
- package/dist/deep/path.spec.d.ts +1 -0
- package/dist/destructor.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/kv/index.d.ts +1 -1
- package/dist/mock/index.d.ts +1 -1
- package/dist/observe/index.d.ts +1 -1
- package/dist/observe/observe.d.ts +1 -1
- package/dist/runtime/external.d.ts +2 -2
- package/dist/runtime/index.d.ts +1 -1
- package/dist/shallowCopy.d.ts +1 -0
- package/dist/spatial/bounds.d.ts +48 -9
- package/dist/spatial/box.d.ts +9 -35
- package/dist/spatial/dimensions.d.ts +1 -1
- package/dist/spatial/direction.d.ts +1 -1
- package/dist/spatial/external.d.ts +8 -8
- package/dist/spatial/index.d.ts +1 -1
- package/dist/spatial/location.d.ts +1 -1
- package/dist/spatial/position.d.ts +1 -1
- package/dist/spatial/scale.d.ts +5 -5
- package/dist/spatial/spatial.d.ts +1 -1
- package/dist/spatial/xy.d.ts +5 -2
- package/dist/telem/generate.d.ts +1 -1
- package/dist/telem/index.d.ts +3 -3
- package/dist/telem/series.d.ts +31 -5
- package/dist/telem/telem.d.ts +3 -0
- package/dist/toArray.d.ts +1 -0
- package/dist/x.cjs.js +289 -60
- package/dist/x.cjs.js.map +1 -1
- package/dist/x.es.js +289 -60
- package/dist/x.es.js.map +1 -1
- package/package.json +6 -6
- package/src/change/change.ts +13 -11
- package/src/deep/delete.ts +1 -1
- package/src/deep/external.ts +2 -1
- package/src/deep/memo.ts +15 -0
- package/src/deep/path.spec.ts +82 -0
- package/src/deep/path.ts +95 -0
- package/src/destructor.ts +2 -0
- package/src/index.ts +1 -0
- package/src/observe/observe.ts +2 -2
- package/src/shallowCopy.ts +6 -0
- package/src/spatial/bounds.spec.ts +218 -0
- package/src/spatial/bounds.ts +155 -19
- package/src/spatial/box.ts +7 -0
- package/src/spatial/dimensions.ts +5 -0
- package/src/spatial/direction.ts +8 -8
- package/src/spatial/location.ts +11 -0
- package/src/spatial/xy.spec.ts +8 -0
- package/src/spatial/xy.ts +32 -9
- package/src/telem/series.spec.ts +54 -46
- package/src/telem/series.ts +110 -57
- package/src/telem/telem.spec.ts +17 -1
- package/src/telem/telem.ts +33 -0
- package/src/toArray.ts +3 -0
- package/dist/deep/key.d.ts +0 -30
- package/src/deep/key.ts +0 -46
package/src/spatial/bounds.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// included in the file licenses/APL.txt.
|
|
9
9
|
|
|
10
10
|
import { type Bounds, bounds, type CrudeBounds } from "@/spatial/base";
|
|
11
|
+
import { resolveObjectURL } from "buffer";
|
|
11
12
|
|
|
12
13
|
export { type Bounds, bounds };
|
|
13
14
|
|
|
@@ -40,34 +41,60 @@ export const DECIMAL = { lower: 0, upper: 1 };
|
|
|
40
41
|
|
|
41
42
|
export const CLIP = { lower: -1, upper: 1 };
|
|
42
43
|
|
|
43
|
-
export const equals = (
|
|
44
|
-
|
|
44
|
+
export const equals = (_a?: Bounds, _b?: Bounds): boolean => {
|
|
45
|
+
if (_a == null && _b == null) return true;
|
|
46
|
+
if (_a == null || _b == null) return false;
|
|
47
|
+
const a = construct(_a);
|
|
48
|
+
const b = construct(_b);
|
|
49
|
+
return a?.lower === b?.lower && a?.upper === b?.upper;
|
|
50
|
+
}
|
|
45
51
|
|
|
46
52
|
export const makeValid = (a: Bounds): Bounds => {
|
|
47
53
|
if (a.lower > a.upper) return { lower: a.upper, upper: a.lower };
|
|
48
54
|
return a;
|
|
49
55
|
};
|
|
50
56
|
|
|
51
|
-
export const clamp = (bounds:
|
|
52
|
-
|
|
53
|
-
if (target
|
|
57
|
+
export const clamp = (bounds: Crude, target: number): number => {
|
|
58
|
+
const _bounds = construct(bounds);
|
|
59
|
+
if (target < _bounds.lower) return _bounds.lower;
|
|
60
|
+
if (target >= _bounds.upper) return _bounds.upper - 1;
|
|
54
61
|
return target;
|
|
55
62
|
};
|
|
56
63
|
|
|
57
|
-
export const contains = (bounds:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
export const contains = (bounds: Crude, target: number | CrudeBounds): boolean => {
|
|
65
|
+
const _bounds = construct(bounds);
|
|
66
|
+
if (typeof target === "number") return target >= _bounds.lower && target < _bounds.upper;
|
|
67
|
+
const _target = construct(target);
|
|
68
|
+
return _target.lower >= _bounds.lower && _target.upper <= _bounds.upper;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const overlapsWith = (a: Crude, b: Crude): boolean => {
|
|
72
|
+
const _a = construct(a);
|
|
73
|
+
const _b = construct(b);
|
|
74
|
+
if (_a.lower ==_b.lower) return true;
|
|
75
|
+
if (_b.upper == _a.lower || _b.lower == _a.upper) return false;
|
|
76
|
+
return contains(_a, _b.upper)
|
|
77
|
+
|| contains(_a, _b.lower)
|
|
78
|
+
|| contains(_b, _a.upper)
|
|
79
|
+
|| contains(_b, _a.lower);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const span = (a: Crude): number => {
|
|
83
|
+
const _a = construct(a);
|
|
84
|
+
return _a.upper - _a.lower;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const isZero = (a: Crude): boolean => {
|
|
88
|
+
const _a = construct(a);
|
|
89
|
+
return _a.lower === 0 && _a.upper === 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const spanIsZero = (a: Crude): boolean => span(a) === 0;
|
|
93
|
+
|
|
94
|
+
export const isFinite = (a: Crude): boolean => {
|
|
95
|
+
const _a = construct(a);
|
|
96
|
+
return Number.isFinite(_a.lower) && Number.isFinite(_a.upper);
|
|
97
|
+
}
|
|
71
98
|
|
|
72
99
|
export const max = (bounds: Crude[]): Bounds => ({
|
|
73
100
|
lower: Math.min(...bounds.map((b) => construct(b).lower)),
|
|
@@ -78,3 +105,112 @@ export const min = (bounds: Crude[]): Bounds => ({
|
|
|
78
105
|
lower: Math.max(...bounds.map((b) => construct(b).lower)),
|
|
79
106
|
upper: Math.min(...bounds.map((b) => construct(b).upper)),
|
|
80
107
|
});
|
|
108
|
+
|
|
109
|
+
export const constructArray = (bounds: Crude): number[] => {
|
|
110
|
+
const _bounds = construct(bounds);
|
|
111
|
+
return Array.from({ length: span(bounds) }, (_, i) => i + _bounds.lower);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const findInsertPosition = (bounds: Crude[], target: number): { index: number, position: number } => {
|
|
115
|
+
const _bounds = bounds.map(construct);
|
|
116
|
+
const index = _bounds.findIndex((b, i) => contains(b, target) || target < _bounds[i].lower);
|
|
117
|
+
if (index === -1) return { index: bounds.length, position: 0 };
|
|
118
|
+
const b = _bounds[index];
|
|
119
|
+
if (contains(b, target)) return { index, position: target - b.lower };
|
|
120
|
+
return {index: index, position: 0};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A plan for inserting a new bound into an ordered array of bounds.
|
|
126
|
+
*/
|
|
127
|
+
export interface InsertionPlan {
|
|
128
|
+
/** How much to increase the lower bound of the new bound or decrease the upper bound
|
|
129
|
+
* of the previous bound. */
|
|
130
|
+
removeBefore: number;
|
|
131
|
+
/** How much to decrease the upper bound of the new bound or increase the lower bound
|
|
132
|
+
* of the next bound. */
|
|
133
|
+
removeAfter: number;
|
|
134
|
+
/** The index at which to insert the new bound. */
|
|
135
|
+
insertInto: number;
|
|
136
|
+
/** The number of bounds to remove from the array. */
|
|
137
|
+
deleteInBetween: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const ZERO_PLAN: InsertionPlan = {
|
|
141
|
+
removeBefore: 0,
|
|
142
|
+
removeAfter: 0,
|
|
143
|
+
insertInto: 0,
|
|
144
|
+
deleteInBetween: 0,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Build a plan for inserting a new bound into an ordered array of bounds. This function
|
|
149
|
+
* is particularly useful for inserting a new array into a sorted array of array of arrays
|
|
150
|
+
* that may overlap. The plan is used to determine how to splice the new array into the
|
|
151
|
+
* existing array. The following are important constraints:
|
|
152
|
+
*
|
|
153
|
+
*
|
|
154
|
+
* 1. If the new bound is entirely contained within an existing bound, the new bound
|
|
155
|
+
* is not inserted and the plan is null.
|
|
156
|
+
*
|
|
157
|
+
* @param bounds - An ordered array of bounds, where each bound is valid (i.e., lower <= upper)
|
|
158
|
+
* and the lower bound of each bound is less than the upper bound of the next bound.
|
|
159
|
+
* @param value - The new bound to insert.
|
|
160
|
+
* @returns A plan for inserting the new bound into the array of bounds, or null if the
|
|
161
|
+
* new bound is entirely contained within an existing bound. See the {@link InsertionPlan}
|
|
162
|
+
* type for more details.
|
|
163
|
+
*/
|
|
164
|
+
export const buildInsertionPlan = (bounds: Crude[], value: Crude):InsertionPlan | null => {
|
|
165
|
+
const _bounds = bounds.map(construct);
|
|
166
|
+
const _target = construct(value);
|
|
167
|
+
// No bounds to insert into, so just insert the new bound at the beginning of the array.
|
|
168
|
+
if (_bounds.length === 0) return ZERO_PLAN;
|
|
169
|
+
const lower = findInsertPosition(bounds, _target.lower);
|
|
170
|
+
const upper = findInsertPosition(bounds, _target.upper);
|
|
171
|
+
// Greater than all bounds,
|
|
172
|
+
if (lower.index == bounds.length) return { ...ZERO_PLAN, insertInto: bounds.length };
|
|
173
|
+
// Less than all bounds,
|
|
174
|
+
if (upper.index == 0) return {
|
|
175
|
+
...ZERO_PLAN,
|
|
176
|
+
removeAfter: upper.position
|
|
177
|
+
}
|
|
178
|
+
if (lower.index === upper.index) {
|
|
179
|
+
// The case where the bound is entirely contained within an existing bound.
|
|
180
|
+
if (lower.position !== 0 && upper.position !== 0)
|
|
181
|
+
return null;
|
|
182
|
+
return {
|
|
183
|
+
removeAfter: upper.position,
|
|
184
|
+
removeBefore: lower.position,
|
|
185
|
+
insertInto: lower.index,
|
|
186
|
+
deleteInBetween: 0,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
let deleteInBetween = (upper.index - lower.index)
|
|
190
|
+
let insertInto = lower.index;
|
|
191
|
+
let removeBefore = span(_bounds[lower.index]) - lower.position;
|
|
192
|
+
// If we're overlapping with the previous bound, we need to slice out one less
|
|
193
|
+
// and insert one further up.
|
|
194
|
+
if (lower.position != 0) {
|
|
195
|
+
deleteInBetween -= 1;
|
|
196
|
+
insertInto += 1;
|
|
197
|
+
// We're not overlapping with the previous bound, so don't need to remove anything
|
|
198
|
+
} else removeBefore = 0;
|
|
199
|
+
return {
|
|
200
|
+
removeBefore,
|
|
201
|
+
removeAfter: upper.position,
|
|
202
|
+
insertInto,
|
|
203
|
+
deleteInBetween,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
export const insert = (bounds: Crude[], value: Crude): Crude[] => {
|
|
209
|
+
const plan = buildInsertionPlan(bounds, value);
|
|
210
|
+
if (plan == null) return bounds;
|
|
211
|
+
const _target = construct(value);
|
|
212
|
+
_target.lower += plan.removeBefore;
|
|
213
|
+
_target.upper -= plan.removeAfter;
|
|
214
|
+
bounds.splice(plan.insertInto, plan.deleteInBetween, _target);
|
|
215
|
+
return bounds.map(construct);
|
|
216
|
+
}
|
package/src/spatial/box.ts
CHANGED
|
@@ -115,6 +115,11 @@ export const construct = (
|
|
|
115
115
|
return b;
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
export const resize = (
|
|
119
|
+
b: Box,
|
|
120
|
+
dims: dimensions.Dimensions,
|
|
121
|
+
): Box => construct(b.one, dims);
|
|
122
|
+
|
|
118
123
|
/**
|
|
119
124
|
* Checks if a box contains a point or another box.
|
|
120
125
|
*
|
|
@@ -184,6 +189,8 @@ export const dim = (
|
|
|
184
189
|
return signed ? dim : Math.abs(dim);
|
|
185
190
|
};
|
|
186
191
|
|
|
192
|
+
|
|
193
|
+
|
|
187
194
|
/** @returns the pont corresponding to the given corner of the box. */
|
|
188
195
|
export const xyLoc = (b: Crude, l: location.XY): xy.XY => {
|
|
189
196
|
const b_ = construct(b);
|
|
@@ -62,3 +62,8 @@ export const min = (crude: Crude[]): Dimensions => ({
|
|
|
62
62
|
width: Math.min(...crude.map((c) => construct(c).width)),
|
|
63
63
|
height: Math.min(...crude.map((c) => construct(c).height)),
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
export const scale = (ca: Crude, factor: number): Dimensions => {
|
|
67
|
+
const a = construct(ca);
|
|
68
|
+
return { width: a.width * factor, height: a.height * factor };
|
|
69
|
+
}
|
package/src/spatial/direction.ts
CHANGED
|
@@ -32,16 +32,16 @@ export const construct = (c: Crude): Direction => {
|
|
|
32
32
|
else return "x";
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export const swap = (direction:
|
|
36
|
-
direction === "x" ? "y" : "x";
|
|
35
|
+
export const swap = (direction: CrudeDirection): Direction =>
|
|
36
|
+
construct(direction) === "x" ? "y" : "x";
|
|
37
37
|
|
|
38
|
-
export const dimension = (direction:
|
|
39
|
-
direction === "x" ? "width" : "height";
|
|
38
|
+
export const dimension = (direction: CrudeDirection): Dimension =>
|
|
39
|
+
construct(direction) === "x" ? "width" : "height";
|
|
40
40
|
|
|
41
|
-
export const location = (direction:
|
|
42
|
-
direction === "x" ? "left" : "top";
|
|
41
|
+
export const location = (direction: CrudeDirection): Location =>
|
|
42
|
+
construct(direction) === "x" ? "left" : "top";
|
|
43
43
|
|
|
44
44
|
export const isDirection = (c: unknown): c is Direction => crude.safeParse(c).success;
|
|
45
45
|
|
|
46
|
-
export const signedDimension = (direction:
|
|
47
|
-
direction === "x" ? "signedWidth" : "signedHeight";
|
|
46
|
+
export const signedDimension = (direction: CrudeDirection): SignedDimension =>
|
|
47
|
+
construct(direction) === "x" ? "signedWidth" : "signedHeight";
|
package/src/spatial/location.ts
CHANGED
|
@@ -52,6 +52,14 @@ const SWAPPED: Record<Location, Location> = {
|
|
|
52
52
|
center: "center",
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
const ROTATE_90: Record<Location, Location> = {
|
|
56
|
+
top: "left",
|
|
57
|
+
right: "top",
|
|
58
|
+
bottom: "right",
|
|
59
|
+
left: "bottom",
|
|
60
|
+
center: "center",
|
|
61
|
+
};
|
|
62
|
+
|
|
55
63
|
export const crude = crudeLocation;
|
|
56
64
|
|
|
57
65
|
export type Crude = CrudeLocation;
|
|
@@ -65,6 +73,8 @@ export const construct = (cl: Crude): Location => {
|
|
|
65
73
|
|
|
66
74
|
export const swap = (cl: Crude): Location => SWAPPED[construct(cl)];
|
|
67
75
|
|
|
76
|
+
export const rotate90 = (cl: Crude): Location => ROTATE_90[construct(cl)];
|
|
77
|
+
|
|
68
78
|
export const direction = (cl: Crude): Direction => {
|
|
69
79
|
const l = construct(cl);
|
|
70
80
|
if (l === "top" || l === "bottom") return "y";
|
|
@@ -76,6 +86,7 @@ export const corner = z.object({ x: xLocation, y: yLocation });
|
|
|
76
86
|
|
|
77
87
|
export type XY = z.infer<typeof xy>;
|
|
78
88
|
export type CornerXY = z.infer<typeof corner>;
|
|
89
|
+
export type CornerXYString = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
|
|
79
90
|
|
|
80
91
|
export const TOP_LEFT: CornerXY = { x: "left", y: "top" };
|
|
81
92
|
export const TOP_RIGHT: CornerXY = { x: "right", y: "top" };
|
package/src/spatial/xy.spec.ts
CHANGED
|
@@ -46,6 +46,14 @@ test("translate", () => {
|
|
|
46
46
|
expect(p.x).toEqual(6);
|
|
47
47
|
expect(p.y).toEqual(7);
|
|
48
48
|
});
|
|
49
|
+
|
|
50
|
+
test("translate multiple", () => {
|
|
51
|
+
let p = xy.construct([1, 2]);
|
|
52
|
+
p = xy.translate(p, [5, 5], [2, 2]);
|
|
53
|
+
expect(p.x).toEqual(8);
|
|
54
|
+
expect(p.y).toEqual(9);
|
|
55
|
+
});
|
|
56
|
+
|
|
49
57
|
describe("equals", () => {
|
|
50
58
|
const TESTS: Array<[xy.Crude, xy.Crude, boolean]> = [
|
|
51
59
|
[[1, 1], { x: 1, y: 1 }, true],
|
package/src/spatial/xy.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { clientXY, xy, type ClientXY as Client, type XY };
|
|
|
25
25
|
|
|
26
26
|
/** A crude representation of a {@link XY} coordinate as a zod schema. */
|
|
27
27
|
export const crudeZ = z.union([
|
|
28
|
+
z.number(),
|
|
28
29
|
xy,
|
|
29
30
|
numberCouple,
|
|
30
31
|
dimensions,
|
|
@@ -38,17 +39,19 @@ export type Crude = z.infer<typeof crudeZ>;
|
|
|
38
39
|
/**
|
|
39
40
|
* @constructs XY
|
|
40
41
|
* @param x - A crude representation of the XY coordinate as a number, number couple,
|
|
41
|
-
* dimensions, signed dimensions, or
|
|
42
|
+
* dimensions, signed dimensions, or mouse event. If it's a mouse event, the clientX and
|
|
43
|
+
* clientY coordinates are preferred over the x and y coordinates.
|
|
42
44
|
* @param y - If x is a number, the y coordinate. If x is a number and this argument is
|
|
43
45
|
* not given, the y coordinate is assumed to be the same as the x coordinate.
|
|
44
46
|
*/
|
|
45
|
-
export const construct = (x: Crude
|
|
47
|
+
export const construct = (x: Crude, y?: number): XY => {
|
|
48
|
+
// The order in which we execute these checks is very important.
|
|
46
49
|
if (typeof x === "number") return { x, y: y ?? x };
|
|
47
50
|
if (Array.isArray(x)) return { x: x[0], y: x[1] };
|
|
48
51
|
if ("signedWidth" in x) return { x: x.signedWidth, y: x.signedHeight };
|
|
49
52
|
if ("clientX" in x) return { x: x.clientX, y: x.clientY };
|
|
50
53
|
if ("width" in x) return { x: x.width, y: x.height };
|
|
51
|
-
return {
|
|
54
|
+
return { x: x.x, y: x.y };
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
/** An x and y coordinate of zero */
|
|
@@ -63,11 +66,12 @@ export const INFINITY = { x: Infinity, y: Infinity };
|
|
|
63
66
|
/** An x and y coordinate of NaN */
|
|
64
67
|
export const NAN = { x: NaN, y: NaN };
|
|
65
68
|
|
|
66
|
-
/** @returns true if the two XY coordinates are
|
|
67
|
-
export const equals = (a: Crude, b: Crude): boolean => {
|
|
69
|
+
/** @returns true if the two XY coordinates are semantically equal. */
|
|
70
|
+
export const equals = (a: Crude, b: Crude, threshold: number = 0): boolean => {
|
|
68
71
|
const a_ = construct(a);
|
|
69
72
|
const b_ = construct(b);
|
|
70
|
-
return a_.x === b_.x && a_.y === b_.y;
|
|
73
|
+
if (threshold === 0) return a_.x === b_.x && a_.y === b_.y;
|
|
74
|
+
return Math.abs(a_.x - b_.x) <= threshold && Math.abs(a_.y - b_.y) <= threshold;
|
|
71
75
|
};
|
|
72
76
|
|
|
73
77
|
/** Is zero is true if the XY coordinate has a semantic x and y value of zero. */
|
|
@@ -94,15 +98,28 @@ export const translateY = (c: Crude, y: number): XY => {
|
|
|
94
98
|
return { x: p.x, y: p.y + y };
|
|
95
99
|
};
|
|
96
100
|
|
|
101
|
+
type TranslateOverloadOne = (a: Crude, b: Crude, ...cb: Crude[]) => XY;
|
|
102
|
+
type TranslateOverloadTwo = (a: Crude, direction: Direction, value: number) => XY;
|
|
103
|
+
|
|
97
104
|
/**
|
|
98
105
|
* @returns the given coordinate translated by an arbitrary number of translation
|
|
99
106
|
* coordinates.
|
|
100
107
|
*/
|
|
101
|
-
export const translate
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
export const translate: TranslateOverloadOne & TranslateOverloadTwo = (
|
|
109
|
+
a,
|
|
110
|
+
b,
|
|
111
|
+
v,
|
|
112
|
+
...cb
|
|
113
|
+
): XY => {
|
|
114
|
+
if (typeof b === "string" && typeof v === "number") {
|
|
115
|
+
if (b === "x") return translateX(a, v);
|
|
116
|
+
return translateY(a, v);
|
|
117
|
+
}
|
|
118
|
+
return [a, b, v ?? ZERO, ...cb].reduce((p: XY, c) => {
|
|
119
|
+
const xy = construct(c as Crude);
|
|
104
120
|
return { x: p.x + xy.x, y: p.y + xy.y };
|
|
105
121
|
}, ZERO);
|
|
122
|
+
};
|
|
106
123
|
|
|
107
124
|
/**
|
|
108
125
|
* @returns the given coordinate the given direction set to the given value.
|
|
@@ -151,6 +168,12 @@ export const isNan = (a: Crude): boolean => {
|
|
|
151
168
|
return Number.isNaN(xy.x) || Number.isNaN(xy.y);
|
|
152
169
|
};
|
|
153
170
|
|
|
171
|
+
/** @returns true if both the x and y coordinates of the given coordinate are finite. */
|
|
172
|
+
export const isFinite = (a: Crude): boolean => {
|
|
173
|
+
const xy = construct(a);
|
|
174
|
+
return Number.isFinite(xy.x) && Number.isFinite(xy.y);
|
|
175
|
+
}
|
|
176
|
+
|
|
154
177
|
/** @returns the coordinate represented as a couple of the form [x, y]. */
|
|
155
178
|
export const couple = (a: Crude): NumberCouple => {
|
|
156
179
|
const xy = construct(a);
|
package/src/telem/series.spec.ts
CHANGED
|
@@ -17,42 +17,42 @@ import { DataType, Rate, Size, TimeRange, TimeStamp } from "@/telem/telem";
|
|
|
17
17
|
describe("Series", () => {
|
|
18
18
|
describe("construction", () => {
|
|
19
19
|
test("valid from native", () => {
|
|
20
|
-
const a = new Series(new Float32Array([1, 2, 3]));
|
|
20
|
+
const a = new Series({data: new Float32Array([1, 2, 3])});
|
|
21
21
|
expect(a.dataType.toString()).toBe(DataType.FLOAT32.toString());
|
|
22
22
|
expect(a.length).toEqual(3);
|
|
23
23
|
expect(a.byteLength).toEqual(Size.bytes(12));
|
|
24
24
|
expect(a.byteCap).toEqual(Size.bytes(12));
|
|
25
25
|
expect(a.cap).toEqual(3);
|
|
26
|
-
const b = new Series(new BigInt64Array([BigInt(1)]));
|
|
26
|
+
const b = new Series({data: new BigInt64Array([BigInt(1)])});
|
|
27
27
|
expect(b.dataType.toString()).toBe(DataType.INT64.toString());
|
|
28
|
-
const c = new Series(new BigInt64Array([BigInt(1)]), DataType.TIMESTAMP);
|
|
28
|
+
const c = new Series({data: new BigInt64Array([BigInt(1)]), dataType: DataType.TIMESTAMP});
|
|
29
29
|
expect(c.dataType.toString()).toBe(DataType.TIMESTAMP.toString());
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
test("from buffer without data type provided", () => {
|
|
33
33
|
expect(() => {
|
|
34
34
|
// eslint-disable-next-line no-new
|
|
35
|
-
new Series(new ArrayBuffer(4));
|
|
35
|
+
new Series({data: new ArrayBuffer(4)});
|
|
36
36
|
}).toThrow();
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
test("from buffer with data type provided", () => {
|
|
40
|
-
const a = new Series(new ArrayBuffer(4), DataType.FLOAT32);
|
|
40
|
+
const a = new Series({data: new ArrayBuffer(4), dataType: DataType.FLOAT32});
|
|
41
41
|
expect(a.dataType.toString()).toBe(DataType.FLOAT32.toString());
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
test("with time range", () => {
|
|
45
|
-
const a = new Series(
|
|
46
|
-
new Float32Array([1, 2, 3]),
|
|
47
|
-
DataType.FLOAT32,
|
|
48
|
-
new TimeRange(1, 2),
|
|
49
|
-
|
|
45
|
+
const a = new Series({
|
|
46
|
+
data: new Float32Array([1, 2, 3]),
|
|
47
|
+
dataType: DataType.FLOAT32,
|
|
48
|
+
timeRange: new TimeRange(1, 2),
|
|
49
|
+
});
|
|
50
50
|
expect(a.timeRange.span.valueOf()).toBe(1);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
describe("allocation", () => {
|
|
54
54
|
it("should allocate a lazy array", () => {
|
|
55
|
-
const arr = Series.alloc(10, DataType.FLOAT32);
|
|
55
|
+
const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
|
|
56
56
|
expect(arr.byteCap).toEqual(Size.bytes(40));
|
|
57
57
|
expect(arr.cap).toEqual(10);
|
|
58
58
|
expect(arr.length).toEqual(0);
|
|
@@ -60,33 +60,32 @@ describe("Series", () => {
|
|
|
60
60
|
});
|
|
61
61
|
it("should throw an error when attempting to allocate an array of lenght 0", () => {
|
|
62
62
|
expect(() => {
|
|
63
|
-
Series.alloc(0, DataType.FLOAT32);
|
|
63
|
+
Series.alloc({length: 0, dataType: DataType.FLOAT32});
|
|
64
64
|
}).toThrow();
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
describe("at", () => {
|
|
70
70
|
it("should return the value at the given index and add the sample offset", () => {
|
|
71
|
-
const arr = new Series(
|
|
72
|
-
new Float32Array([1, 2, 3]),
|
|
73
|
-
DataType.FLOAT32,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
71
|
+
const arr = new Series({
|
|
72
|
+
data: new Float32Array([1, 2, 3]),
|
|
73
|
+
dataType: DataType.FLOAT32,
|
|
74
|
+
sampleOffset: 2,
|
|
75
|
+
});
|
|
77
76
|
expect(arr.at(0)).toEqual(3);
|
|
78
77
|
expect(arr.at(1)).toEqual(4);
|
|
79
78
|
expect(arr.at(2)).toEqual(5);
|
|
80
79
|
});
|
|
81
80
|
it("should return undefined when the index is out of bounds", () => {
|
|
82
|
-
const arr = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
|
|
81
|
+
const arr = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
|
|
83
82
|
expect(arr.at(3)).toBeUndefined();
|
|
84
83
|
});
|
|
85
84
|
});
|
|
86
85
|
|
|
87
86
|
describe("slice", () => {
|
|
88
87
|
it("should slice a lazy array", () => {
|
|
89
|
-
const a = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
|
|
88
|
+
const a = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
|
|
90
89
|
const b = a.slice(1, 2);
|
|
91
90
|
expect(b.dataType.toString()).toBe(DataType.FLOAT32.toString());
|
|
92
91
|
expect(b.data).toEqual(new Float32Array([2]));
|
|
@@ -99,12 +98,12 @@ describe("Series", () => {
|
|
|
99
98
|
|
|
100
99
|
describe("min and max", () => {
|
|
101
100
|
it("should return a min and max of zero on an allocated array", () => {
|
|
102
|
-
const arr = Series.alloc(10, DataType.FLOAT32);
|
|
101
|
+
const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
|
|
103
102
|
expect(arr.max).toEqual(-Infinity);
|
|
104
103
|
expect(arr.min).toEqual(Infinity);
|
|
105
104
|
});
|
|
106
105
|
it("should correctly calculate the min and max of a lazy array", () => {
|
|
107
|
-
const arr = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
|
|
106
|
+
const arr = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
|
|
108
107
|
expect(arr.max).toEqual(3);
|
|
109
108
|
expect(arr.min).toEqual(1);
|
|
110
109
|
});
|
|
@@ -112,21 +111,21 @@ describe("Series", () => {
|
|
|
112
111
|
|
|
113
112
|
describe("conversion", () => {
|
|
114
113
|
test("from float64 to float32", () => {
|
|
115
|
-
const a = new Series(new Float64Array([1, 2, 3]), DataType.FLOAT64);
|
|
114
|
+
const a = new Series({data: new Float64Array([1, 2, 3]), dataType: DataType.FLOAT64});
|
|
116
115
|
const b = a.convert(DataType.FLOAT32);
|
|
117
116
|
expect(b.dataType.toString()).toBe(DataType.FLOAT32.toString());
|
|
118
117
|
expect(b.data).toEqual(new Float32Array([1, 2, 3]));
|
|
119
118
|
});
|
|
120
119
|
|
|
121
120
|
test("from int64 to int32", () => {
|
|
122
|
-
const a = new Series(new BigInt64Array([BigInt(1), BigInt(2), BigInt(3)]));
|
|
121
|
+
const a = new Series({data: new BigInt64Array([BigInt(1), BigInt(2), BigInt(3)])});
|
|
123
122
|
const b = a.convert(DataType.INT32);
|
|
124
123
|
expect(b.dataType.toString()).toBe(DataType.INT32.toString());
|
|
125
124
|
expect(b.data).toEqual(new Int32Array([1, 2, 3]));
|
|
126
125
|
});
|
|
127
126
|
|
|
128
127
|
test("from float32 to int64", () => {
|
|
129
|
-
const a = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
|
|
128
|
+
const a = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
|
|
130
129
|
const b = a.convert(DataType.INT64);
|
|
131
130
|
expect(b.dataType.toString()).toBe(DataType.INT64.toString());
|
|
132
131
|
expect(b.data).toEqual(new BigInt64Array([BigInt(1), BigInt(2), BigInt(3)]));
|
|
@@ -135,35 +134,35 @@ describe("Series", () => {
|
|
|
135
134
|
|
|
136
135
|
describe("writing", () => {
|
|
137
136
|
it("should correctly write to an allocated lazy array", () => {
|
|
138
|
-
const arr = Series.alloc(10, DataType.FLOAT32);
|
|
137
|
+
const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
|
|
139
138
|
expect(arr.byteCap).toEqual(Size.bytes(40));
|
|
140
139
|
expect(arr.length).toEqual(0);
|
|
141
|
-
const writeOne = new Series(new Float32Array([1]));
|
|
140
|
+
const writeOne = new Series({data:new Float32Array([1])});
|
|
142
141
|
expect(arr.write(writeOne)).toEqual(1);
|
|
143
142
|
expect(arr.length).toEqual(1);
|
|
144
|
-
const writeTwo = new Series(new Float32Array([2, 3]));
|
|
143
|
+
const writeTwo = new Series({data: new Float32Array([2, 3])});
|
|
145
144
|
expect(arr.write(writeTwo)).toEqual(2);
|
|
146
145
|
expect(arr.length).toEqual(3);
|
|
147
146
|
});
|
|
148
147
|
it("should recompute cached max and min correctly", () => {
|
|
149
|
-
const arr = Series.alloc(10, DataType.FLOAT32);
|
|
148
|
+
const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
|
|
150
149
|
arr.enrich();
|
|
151
|
-
const writeTwo = new Series(new Float32Array([2, 3]));
|
|
150
|
+
const writeTwo = new Series({data: new Float32Array([2, 3])});
|
|
152
151
|
arr.write(writeTwo);
|
|
153
152
|
expect(arr.max).toEqual(3);
|
|
154
153
|
expect(arr.min).toEqual(2);
|
|
155
154
|
});
|
|
156
155
|
it("should correctly adjust the sample offset of a written array", () => {
|
|
157
|
-
const arr = Series.alloc(2, DataType.FLOAT32, TimeRange.ZERO, -3);
|
|
158
|
-
const writeOne = new Series(new Float32Array([-2]));
|
|
156
|
+
const arr = Series.alloc({length: 2, dataType: DataType.FLOAT32, timeRange: TimeRange.ZERO, sampleOffset: -3});
|
|
157
|
+
const writeOne = new Series({data: new Float32Array([-2])});
|
|
159
158
|
expect(arr.write(writeOne)).toEqual(1);
|
|
160
159
|
expect(arr.min).toEqual(-5);
|
|
161
|
-
const writeTwo = new Series(
|
|
162
|
-
new Float32Array([1]),
|
|
163
|
-
DataType.FLOAT32,
|
|
164
|
-
TimeRange.ZERO,
|
|
165
|
-
-1,
|
|
166
|
-
|
|
160
|
+
const writeTwo = new Series({
|
|
161
|
+
data: new Float32Array([1]),
|
|
162
|
+
dataType: DataType.FLOAT32,
|
|
163
|
+
timeRange: TimeRange.ZERO,
|
|
164
|
+
sampleOffset: -1,
|
|
165
|
+
});
|
|
167
166
|
expect(arr.write(writeTwo)).toEqual(1);
|
|
168
167
|
expect(arr.min).toEqual(-5);
|
|
169
168
|
expect(arr.max).toEqual(-2);
|
|
@@ -193,7 +192,7 @@ describe("Series", () => {
|
|
|
193
192
|
|
|
194
193
|
describe("webgl buffering", () => {
|
|
195
194
|
it("should correctly buffer a new lazy array", () => {
|
|
196
|
-
const arr = new Series(new Float32Array([1, 2, 3]), DataType.FLOAT32);
|
|
195
|
+
const arr = new Series({data: new Float32Array([1, 2, 3]), dataType: DataType.FLOAT32});
|
|
197
196
|
const controller = new MockGLBufferController();
|
|
198
197
|
arr.updateGLBuffer(controller);
|
|
199
198
|
expect(controller.createBufferMock).toHaveBeenCalledTimes(1);
|
|
@@ -206,7 +205,7 @@ describe("Series", () => {
|
|
|
206
205
|
expect(buf).toEqual(new Float32Array([1, 2, 3]));
|
|
207
206
|
});
|
|
208
207
|
it("should correctly update a buffer when writing to an allocated array", () => {
|
|
209
|
-
const arr = Series.alloc(10, DataType.FLOAT32);
|
|
208
|
+
const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
|
|
210
209
|
const controller = new MockGLBufferController();
|
|
211
210
|
arr.updateGLBuffer(controller);
|
|
212
211
|
expect(controller.createBufferMock).toHaveBeenCalledTimes(1);
|
|
@@ -216,7 +215,7 @@ describe("Series", () => {
|
|
|
216
215
|
let buf = controller.buffers[arr.glBuffer as number];
|
|
217
216
|
expect(buf).toBeDefined();
|
|
218
217
|
expect(buf.byteLength).toEqual(0);
|
|
219
|
-
const writeOne = new Series(new Float32Array([1]));
|
|
218
|
+
const writeOne = new Series({data: new Float32Array([1])});
|
|
220
219
|
arr.write(writeOne);
|
|
221
220
|
arr.updateGLBuffer(controller);
|
|
222
221
|
expect(controller.bufferDataMock).toHaveBeenCalledTimes(1);
|
|
@@ -224,7 +223,7 @@ describe("Series", () => {
|
|
|
224
223
|
buf = controller.buffers[arr.glBuffer as number];
|
|
225
224
|
expect(buf.byteLength).toEqual(arr.byteCap.valueOf());
|
|
226
225
|
expect(new Float32Array(buf)[0]).toEqual(1);
|
|
227
|
-
const writeTwo = new Series(new Float32Array([2, 3]));
|
|
226
|
+
const writeTwo = new Series({data: new Float32Array([2, 3])});
|
|
228
227
|
arr.write(writeTwo);
|
|
229
228
|
arr.updateGLBuffer(controller);
|
|
230
229
|
expect(controller.bufferDataMock).not.toHaveBeenCalledTimes(2);
|
|
@@ -239,7 +238,7 @@ describe("Series", () => {
|
|
|
239
238
|
|
|
240
239
|
describe("acquire", () => {
|
|
241
240
|
it("should increase the reference count and buffer gl data", () => {
|
|
242
|
-
const s = new Series(new Float32Array([1, 2, 3]));
|
|
241
|
+
const s = new Series({data: new Float32Array([1, 2, 3])});
|
|
243
242
|
expect(s.refCount).toEqual(0);
|
|
244
243
|
const control = new MockGLBufferController();
|
|
245
244
|
s.acquire(control);
|
|
@@ -259,7 +258,7 @@ describe("Series", () => {
|
|
|
259
258
|
expect(outStrings).toEqual(["apple", "banana", "carrot"]);
|
|
260
259
|
});
|
|
261
260
|
it("should throw an error if the series is not of type string", () => {
|
|
262
|
-
const s = new Series(new Float32Array([1, 2, 3]));
|
|
261
|
+
const s = new Series({data: new Float32Array([1, 2, 3])});
|
|
263
262
|
expect(() => {
|
|
264
263
|
s.toStrings();
|
|
265
264
|
}).toThrow();
|
|
@@ -278,7 +277,6 @@ describe("Series", () => {
|
|
|
278
277
|
{ a: 3, b: "carrot" },
|
|
279
278
|
]);
|
|
280
279
|
const outJSON = s.parseJSON(schema);
|
|
281
|
-
print(outJSON);
|
|
282
280
|
expect(outJSON).toEqual([
|
|
283
281
|
{ a: 1, b: "apple" },
|
|
284
282
|
{ a: 2, b: "banana" },
|
|
@@ -286,4 +284,14 @@ describe("Series", () => {
|
|
|
286
284
|
]);
|
|
287
285
|
});
|
|
288
286
|
});
|
|
287
|
+
|
|
288
|
+
describe("binarySearch", () => {
|
|
289
|
+
it("should correctly binary search a pre-allocated array", () => {
|
|
290
|
+
const arr = Series.alloc({length: 10, dataType: DataType.FLOAT32});
|
|
291
|
+
const writeOne = new Series({data: new Float32Array([1, 2, 3, 4, 5])});
|
|
292
|
+
arr.write(writeOne);
|
|
293
|
+
expect(arr.binarySearch(3)).toEqual(2);
|
|
294
|
+
expect(arr.binarySearch(6)).toEqual(5);
|
|
295
|
+
})
|
|
296
|
+
})
|
|
289
297
|
});
|