@synnaxlabs/x 0.11.0 → 0.13.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 +6 -6
- package/dist/deep/difference.d.ts +3 -0
- package/dist/deep/external.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/invert.d.ts +1 -0
- package/dist/optional.d.ts +3 -0
- package/dist/spatial/{bounds.d.ts → bounds/bounds.d.ts} +1 -1
- package/dist/spatial/bounds/index.d.ts +1 -0
- package/dist/spatial/{box.d.ts → box/box.d.ts} +25 -8
- package/dist/spatial/box/index.d.ts +1 -0
- package/dist/spatial/{dimensions.d.ts → dimensions/dimensions.d.ts} +1 -1
- package/dist/spatial/dimensions/index.d.ts +1 -0
- package/dist/spatial/{direction.d.ts → direction/direction.d.ts} +1 -1
- package/dist/spatial/direction/index.d.ts +1 -0
- package/dist/spatial/external.d.ts +8 -7
- package/dist/spatial/location/index.d.ts +1 -0
- package/dist/spatial/{location.d.ts → location/location.d.ts} +12 -9
- package/dist/spatial/position/index.d.ts +1 -0
- package/dist/spatial/position/position.d.ts +20 -0
- package/dist/spatial/scale/index.d.ts +1 -0
- package/dist/spatial/{scale.d.ts → scale/scale.d.ts} +5 -5
- package/dist/spatial/scale/scale.spec.d.ts +1 -0
- package/dist/spatial/xy/index.d.ts +1 -0
- package/dist/spatial/{xy.d.ts → xy/xy.d.ts} +10 -9
- package/dist/spatial/xy/xy.spec.d.ts +1 -0
- package/dist/telem/series.d.ts +18 -8
- package/dist/telem/telem.d.ts +21 -1
- package/dist/{x.cjs.js → x.cjs} +401 -55
- package/dist/x.cjs.map +1 -0
- package/dist/{x.es.js → x.js} +401 -55
- package/dist/x.js.map +1 -0
- package/package.json +7 -6
- package/src/deep/difference.spec.ts +38 -0
- package/src/deep/difference.ts +47 -0
- package/src/deep/external.ts +1 -0
- package/src/deep/memo.ts +10 -1
- package/src/index.ts +1 -0
- package/src/invert.ts +1 -0
- package/src/optional.ts +5 -0
- package/src/shallowCopy.ts +10 -1
- package/src/spatial/{bounds.spec.ts → bounds/bounds.spec.ts} +150 -68
- package/src/spatial/{bounds.ts → bounds/bounds.ts} +0 -1
- package/src/spatial/bounds/index.ts +10 -0
- package/src/spatial/{box.spec.ts → box/box.spec.ts} +106 -3
- package/src/spatial/{box.ts → box/box.ts} +107 -22
- package/src/spatial/box/index.ts +10 -0
- package/src/spatial/{dimensions.spec.ts → dimensions/dimensions.spec.ts} +1 -1
- package/src/spatial/dimensions/index.ts +10 -0
- package/src/spatial/{direction.spec.ts → direction/direction.spec.ts} +1 -1
- package/src/spatial/direction/index.ts +10 -0
- package/src/spatial/external.ts +8 -7
- package/src/spatial/location/index.ts +10 -0
- package/src/spatial/{location.spec.ts → location/location.spec.ts} +1 -1
- package/src/spatial/{location.ts → location/location.ts} +67 -29
- package/src/spatial/position/index.ts +10 -0
- package/src/spatial/position/position.spec.ts +211 -0
- package/src/spatial/position/position.ts +174 -0
- package/src/spatial/scale/index.ts +10 -0
- package/src/spatial/{scale.spec.ts → scale/scale.spec.ts} +2 -2
- package/src/spatial/{scale.ts → scale/scale.ts} +6 -6
- package/src/spatial/xy/index.ts +10 -0
- package/src/spatial/{xy.spec.ts → xy/xy.spec.ts} +1 -1
- package/src/spatial/{xy.ts → xy/xy.ts} +19 -14
- package/src/telem/series.spec.ts +37 -19
- package/src/telem/series.ts +34 -22
- package/src/telem/telem.spec.ts +106 -24
- package/src/telem/telem.ts +73 -2
- package/dist/spatial/position.d.ts +0 -2
- package/dist/x.cjs.js.map +0 -1
- package/dist/x.es.js.map +0 -1
- package/src/spatial/position.ts +0 -26
- /package/dist/{spatial/bounds.spec.d.ts → deep/difference.spec.d.ts} +0 -0
- /package/dist/spatial/{box.spec.d.ts → bounds/bounds.spec.d.ts} +0 -0
- /package/dist/spatial/{dimensions.spec.d.ts → box/box.spec.d.ts} +0 -0
- /package/dist/spatial/{direction.spec.d.ts → dimensions/dimensions.spec.d.ts} +0 -0
- /package/dist/spatial/{location.spec.d.ts → direction/direction.spec.d.ts} +0 -0
- /package/dist/spatial/{scale.spec.d.ts → location/location.spec.d.ts} +0 -0
- /package/dist/spatial/{xy.spec.d.ts → position/position.spec.d.ts} +0 -0
- /package/src/spatial/{dimensions.ts → dimensions/dimensions.ts} +0 -0
- /package/src/spatial/{direction.ts → direction/direction.ts} +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Copyright 2023 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
import { type Alignment, type XLocation, type YLocation } from "@/spatial/base";
|
|
11
|
+
import { box } from "@/spatial/box";
|
|
12
|
+
import { direction } from "@/spatial/direction";
|
|
13
|
+
import { location } from "@/spatial/location";
|
|
14
|
+
import { xy } from "@/spatial/xy";
|
|
15
|
+
|
|
16
|
+
export const posititonSoVisible = (target: HTMLElement, p: xy.XY): [xy.XY, boolean] => {
|
|
17
|
+
const { width, height } = target.getBoundingClientRect();
|
|
18
|
+
const { innerWidth, innerHeight } = window;
|
|
19
|
+
let changed = false;
|
|
20
|
+
let nextXY = xy.construct(p);
|
|
21
|
+
if (p.x + width > innerWidth) {
|
|
22
|
+
nextXY = xy.translateX(nextXY, -width);
|
|
23
|
+
changed = true;
|
|
24
|
+
}
|
|
25
|
+
if (p.y + height > innerHeight) {
|
|
26
|
+
nextXY = xy.translateY(nextXY, -height);
|
|
27
|
+
changed = true;
|
|
28
|
+
}
|
|
29
|
+
return [nextXY, changed];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface DialogProps {
|
|
33
|
+
container: box.Crude;
|
|
34
|
+
target: box.Crude;
|
|
35
|
+
dialog: box.Crude;
|
|
36
|
+
alignments?: Alignment[];
|
|
37
|
+
initial?: location.Outer | Partial<location.XY> | location.XY;
|
|
38
|
+
prefer?: Array<location.Outer | Partial<location.XY> | location.XY>;
|
|
39
|
+
disable?: Array<location.Location | Partial<location.XY>>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const parseLocationOptions = (
|
|
43
|
+
initial?: location.Outer | Partial<location.XY> | location.XY,
|
|
44
|
+
): Partial<location.XY> => {
|
|
45
|
+
if (initial == null) return { x: undefined, y: undefined };
|
|
46
|
+
const parsedXYLoc = location.xy.safeParse(initial);
|
|
47
|
+
if (parsedXYLoc.success) return parsedXYLoc.data;
|
|
48
|
+
const parsedLoc = location.location.safeParse(initial);
|
|
49
|
+
if (parsedLoc.success) {
|
|
50
|
+
const isX = direction.construct(parsedLoc.data) === "x";
|
|
51
|
+
return isX
|
|
52
|
+
? { x: parsedLoc.data as XLocation, y: undefined }
|
|
53
|
+
: { x: undefined, y: parsedLoc.data as YLocation };
|
|
54
|
+
}
|
|
55
|
+
return initial as Partial<location.XY>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export interface DialogReturn {
|
|
59
|
+
location: location.XY;
|
|
60
|
+
adjustedDialog: box.Box;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const dialog = ({
|
|
64
|
+
container: containerCrude,
|
|
65
|
+
target: targetCrude,
|
|
66
|
+
dialog: dialogCrude,
|
|
67
|
+
initial,
|
|
68
|
+
prefer,
|
|
69
|
+
alignments = ["start"],
|
|
70
|
+
disable = [],
|
|
71
|
+
}: DialogProps): DialogReturn => {
|
|
72
|
+
const initialLocs = parseLocationOptions(initial);
|
|
73
|
+
|
|
74
|
+
let options = location.XY_LOCATIONS;
|
|
75
|
+
if (prefer != null) {
|
|
76
|
+
const parsedPrefer = prefer.map((p) => parseLocationOptions(p));
|
|
77
|
+
options = options.slice().sort((a, b) => {
|
|
78
|
+
const hasPreferA = parsedPrefer.findIndex((p) => location.xyMatches(a, p));
|
|
79
|
+
const hasPreferB = parsedPrefer.findIndex((p) => location.xyMatches(b, p));
|
|
80
|
+
if (hasPreferA > -1 && hasPreferB > -1) return hasPreferA - hasPreferB;
|
|
81
|
+
if (hasPreferA > -1) return -1;
|
|
82
|
+
if (hasPreferB > -1) return 1;
|
|
83
|
+
return 0;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const mappedOptions = options
|
|
87
|
+
.filter(
|
|
88
|
+
(l) =>
|
|
89
|
+
!location.xyEquals(l, location.CENTER) &&
|
|
90
|
+
(initialLocs.x == null || l.x === initialLocs.x) &&
|
|
91
|
+
(initialLocs.y == null || l.y === initialLocs.y) &&
|
|
92
|
+
!disable.some((d) => location.xyMatches(l, d)),
|
|
93
|
+
)
|
|
94
|
+
.map((l) => alignments?.map((a) => [l, a]))
|
|
95
|
+
.flat() as Array<[location.XY, Alignment]>;
|
|
96
|
+
|
|
97
|
+
const container = box.construct(containerCrude);
|
|
98
|
+
const target = box.construct(targetCrude);
|
|
99
|
+
const dialog = box.construct(dialogCrude);
|
|
100
|
+
|
|
101
|
+
// maximum value of a number in js
|
|
102
|
+
let bestOptionArea = -Infinity;
|
|
103
|
+
const res: DialogReturn = { location: location.CENTER, adjustedDialog: dialog };
|
|
104
|
+
mappedOptions.forEach(([option, alignment]) => {
|
|
105
|
+
const [adjustedBox, area] = evaluateOption({
|
|
106
|
+
option,
|
|
107
|
+
alignment,
|
|
108
|
+
container,
|
|
109
|
+
target,
|
|
110
|
+
dialog,
|
|
111
|
+
});
|
|
112
|
+
if (area > bestOptionArea) {
|
|
113
|
+
bestOptionArea = area;
|
|
114
|
+
res.location = option;
|
|
115
|
+
res.adjustedDialog = adjustedBox;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return res;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
interface EvaluateOptionProps {
|
|
123
|
+
option: location.XY;
|
|
124
|
+
alignment: Alignment;
|
|
125
|
+
container: box.Box;
|
|
126
|
+
target: box.Box;
|
|
127
|
+
dialog: box.Box;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const evaluateOption = ({
|
|
131
|
+
option,
|
|
132
|
+
alignment,
|
|
133
|
+
container,
|
|
134
|
+
target,
|
|
135
|
+
dialog,
|
|
136
|
+
}: EvaluateOptionProps): [box.Box, number] => {
|
|
137
|
+
const root = getRoot(option, alignment);
|
|
138
|
+
const targetPoint = box.xyLoc(target, option);
|
|
139
|
+
const dialogBox = box.constructWithAlternateRoot(
|
|
140
|
+
targetPoint.x,
|
|
141
|
+
targetPoint.y,
|
|
142
|
+
box.width(dialog),
|
|
143
|
+
box.height(dialog),
|
|
144
|
+
root,
|
|
145
|
+
location.TOP_LEFT,
|
|
146
|
+
);
|
|
147
|
+
const area = box.area(box.intersect(dialogBox, container));
|
|
148
|
+
return [dialogBox, area];
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const X_ALIGNMENT_MAP: Record<Alignment, location.X | location.Center> = {
|
|
152
|
+
start: "left",
|
|
153
|
+
center: "center",
|
|
154
|
+
end: "right",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const Y_ALIGNMENT_MAP: Record<Alignment, location.Y | location.Center> = {
|
|
158
|
+
start: "bottom",
|
|
159
|
+
center: "center",
|
|
160
|
+
end: "top",
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const getRoot = (option: location.XY, alignment: Alignment): location.XY => {
|
|
164
|
+
const out: location.XY = { x: "center", y: "center" };
|
|
165
|
+
if (option.y !== "center") {
|
|
166
|
+
out.y = location.swap(option.y) as location.Y;
|
|
167
|
+
const swapper = option.x === "left" ? location.swap : (v: location.Location) => v;
|
|
168
|
+
out.x = swapper(X_ALIGNMENT_MAP[alignment]) as location.X;
|
|
169
|
+
} else {
|
|
170
|
+
out.x = location.swap(option.x) as location.X;
|
|
171
|
+
out.y = Y_ALIGNMENT_MAP[alignment];
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright 2023 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
export * as scale from "@/spatial/scale/scale";
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, it, expect, test } from "vitest";
|
|
11
11
|
|
|
12
|
-
import * as box from "@/spatial/box";
|
|
13
|
-
import { XY, Scale } from "@/spatial/scale";
|
|
12
|
+
import * as box from "@/spatial/box/box";
|
|
13
|
+
import { XY, Scale } from "@/spatial/scale/scale";
|
|
14
14
|
|
|
15
15
|
type ScaleSpec = [name: string, scale: Scale, i: number, o: number];
|
|
16
16
|
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
12
|
import { clamp } from "@/clamp";
|
|
13
|
-
import * as bounds from "@/spatial/bounds";
|
|
14
|
-
import { type Box, isBox } from "@/spatial/box";
|
|
15
|
-
import * as box from "@/spatial/box";
|
|
16
|
-
import type * as dims from "@/spatial/dimensions";
|
|
17
|
-
import * as location from "@/spatial/location";
|
|
18
|
-
import * as xy from "@/spatial/xy";
|
|
13
|
+
import * as bounds from "@/spatial/bounds/bounds";
|
|
14
|
+
import { type Box, isBox } from "@/spatial/box/box";
|
|
15
|
+
import * as box from "@/spatial/box/box";
|
|
16
|
+
import type * as dims from "@/spatial/dimensions/dimensions";
|
|
17
|
+
import * as location from "@/spatial/location/location";
|
|
18
|
+
import * as xy from "@/spatial/xy/xy";
|
|
19
19
|
|
|
20
20
|
export const crudeXYTransform = z.object({ offset: xy.crudeZ, scale: xy.crudeZ });
|
|
21
21
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright 2023 Synnax Labs, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Use of this software is governed by the Business Source License included in the file
|
|
4
|
+
// licenses/BSL.txt.
|
|
5
|
+
//
|
|
6
|
+
// As of the Change Date specified in that file, in accordance with the Business Source
|
|
7
|
+
// License, use of this software will be governed by the Apache License, Version 2.0,
|
|
8
|
+
// included in the file licenses/APL.txt.
|
|
9
|
+
|
|
10
|
+
export * as xy from "@/spatial/xy/xy";
|
|
@@ -44,7 +44,12 @@ export type Crude = z.infer<typeof crudeZ>;
|
|
|
44
44
|
* @param y - If x is a number, the y coordinate. If x is a number and this argument is
|
|
45
45
|
* not given, the y coordinate is assumed to be the same as the x coordinate.
|
|
46
46
|
*/
|
|
47
|
-
export const construct = (x: Crude, y?: number): XY => {
|
|
47
|
+
export const construct = (x: Crude | Direction, y?: number): XY => {
|
|
48
|
+
if (typeof x === "string") {
|
|
49
|
+
if (y === undefined) throw new Error("The y coordinate must be given.");
|
|
50
|
+
if (x === "x") return { x: y, y: 0 };
|
|
51
|
+
return { x: 0, y };
|
|
52
|
+
}
|
|
48
53
|
// The order in which we execute these checks is very important.
|
|
49
54
|
if (typeof x === "number") return { x, y: y ?? x };
|
|
50
55
|
if (Array.isArray(x)) return { x: x[0], y: x[1] };
|
|
@@ -98,19 +103,14 @@ export const translateY = (c: Crude, y: number): XY => {
|
|
|
98
103
|
return { x: p.x, y: p.y + y };
|
|
99
104
|
};
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
interface Translate {
|
|
107
|
+
/** @returns the sum of the given coordinates. */
|
|
108
|
+
(a: Crude, b: Crude, ...cb: Crude[]): XY;
|
|
109
|
+
/** @returns the coordinates translated in the given direction by the given value. */
|
|
110
|
+
(a: Crude, direction: Direction, value: number): XY;
|
|
111
|
+
}
|
|
103
112
|
|
|
104
|
-
|
|
105
|
-
* @returns the given coordinate translated by an arbitrary number of translation
|
|
106
|
-
* coordinates.
|
|
107
|
-
*/
|
|
108
|
-
export const translate: TranslateOverloadOne & TranslateOverloadTwo = (
|
|
109
|
-
a,
|
|
110
|
-
b,
|
|
111
|
-
v,
|
|
112
|
-
...cb
|
|
113
|
-
): XY => {
|
|
113
|
+
export const translate: Translate = (a, b, v, ...cb): XY => {
|
|
114
114
|
if (typeof b === "string" && typeof v === "number") {
|
|
115
115
|
if (b === "x") return translateX(a, v);
|
|
116
116
|
return translateY(a, v);
|
|
@@ -172,7 +172,7 @@ export const isNan = (a: Crude): boolean => {
|
|
|
172
172
|
export const isFinite = (a: Crude): boolean => {
|
|
173
173
|
const xy = construct(a);
|
|
174
174
|
return Number.isFinite(xy.x) && Number.isFinite(xy.y);
|
|
175
|
-
}
|
|
175
|
+
};
|
|
176
176
|
|
|
177
177
|
/** @returns the coordinate represented as a couple of the form [x, y]. */
|
|
178
178
|
export const couple = (a: Crude): NumberCouple => {
|
|
@@ -185,3 +185,8 @@ export const css = (a: Crude): { left: number; top: number } => {
|
|
|
185
185
|
const xy = construct(a);
|
|
186
186
|
return { left: xy.x, top: xy.y };
|
|
187
187
|
};
|
|
188
|
+
|
|
189
|
+
export const truncate = (a: Crude, precision: number = 0): XY => {
|
|
190
|
+
const xy = construct(a);
|
|
191
|
+
return { x: Number(xy.x.toFixed(precision)), y: Number(xy.y.toFixed(precision)) };
|
|
192
|
+
};
|
package/src/telem/series.spec.ts
CHANGED
|
@@ -21,8 +21,8 @@ describe("Series", () => {
|
|
|
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
|
-
expect(a.
|
|
25
|
-
expect(a.
|
|
24
|
+
expect(a.byteCapacity).toEqual(Size.bytes(12));
|
|
25
|
+
expect(a.capacity).toEqual(3);
|
|
26
26
|
const b = new Series({ data: new BigInt64Array([BigInt(1)]) });
|
|
27
27
|
expect(b.dataType.toString()).toBe(DataType.INT64.toString());
|
|
28
28
|
const c = new Series({
|
|
@@ -55,15 +55,15 @@ describe("Series", () => {
|
|
|
55
55
|
|
|
56
56
|
describe("allocation", () => {
|
|
57
57
|
it("should allocate a lazy array", () => {
|
|
58
|
-
const series = Series.alloc({
|
|
59
|
-
expect(series.
|
|
60
|
-
expect(series.
|
|
58
|
+
const series = Series.alloc({ capacity: 10, dataType: DataType.FLOAT32 });
|
|
59
|
+
expect(series.byteCapacity).toEqual(Size.bytes(40));
|
|
60
|
+
expect(series.capacity).toEqual(10);
|
|
61
61
|
expect(series.length).toEqual(0);
|
|
62
62
|
expect(series.byteLength).toEqual(Size.bytes(0));
|
|
63
63
|
});
|
|
64
64
|
it("should throw an error when attempting to allocate an array of lenght 0", () => {
|
|
65
65
|
expect(() => {
|
|
66
|
-
Series.alloc({
|
|
66
|
+
Series.alloc({ capacity: 0, dataType: DataType.FLOAT32 });
|
|
67
67
|
}).toThrow();
|
|
68
68
|
});
|
|
69
69
|
});
|
|
@@ -87,8 +87,26 @@ describe("Series", () => {
|
|
|
87
87
|
});
|
|
88
88
|
expect(series.at(3)).toBeUndefined();
|
|
89
89
|
});
|
|
90
|
+
it("should allow the index to be negative", () => {
|
|
91
|
+
const series = new Series({
|
|
92
|
+
data: new Float32Array([1, 2, 3]),
|
|
93
|
+
dataType: DataType.FLOAT32,
|
|
94
|
+
});
|
|
95
|
+
expect(series.at(-1)).toEqual(3);
|
|
96
|
+
});
|
|
97
|
+
it("should throw an error when the index is out of bounds and require is set to true", () => {
|
|
98
|
+
const series = new Series({
|
|
99
|
+
data: new Float32Array([1, 2, 3]),
|
|
100
|
+
dataType: DataType.FLOAT32,
|
|
101
|
+
});
|
|
102
|
+
expect(() => {
|
|
103
|
+
series.at(3, true);
|
|
104
|
+
}).toThrow();
|
|
105
|
+
});
|
|
90
106
|
});
|
|
91
107
|
|
|
108
|
+
|
|
109
|
+
|
|
92
110
|
describe("slice", () => {
|
|
93
111
|
it("should slice a lazy array", () => {
|
|
94
112
|
const a = new Series({
|
|
@@ -100,14 +118,14 @@ describe("Series", () => {
|
|
|
100
118
|
expect(b.data).toEqual(new Float32Array([2]));
|
|
101
119
|
expect(b.length).toEqual(1);
|
|
102
120
|
expect(b.byteLength).toEqual(Size.bytes(4));
|
|
103
|
-
expect(b.
|
|
104
|
-
expect(b.
|
|
121
|
+
expect(b.byteCapacity).toEqual(Size.bytes(4));
|
|
122
|
+
expect(b.capacity).toEqual(1);
|
|
105
123
|
});
|
|
106
124
|
});
|
|
107
125
|
|
|
108
126
|
describe("min and max", () => {
|
|
109
127
|
it("should return a min and max of zero on an allocated array", () => {
|
|
110
|
-
const series = Series.alloc({
|
|
128
|
+
const series = Series.alloc({ capacity: 10, dataType: DataType.FLOAT32 });
|
|
111
129
|
expect(series.max).toEqual(-Infinity);
|
|
112
130
|
expect(series.min).toEqual(Infinity);
|
|
113
131
|
});
|
|
@@ -154,8 +172,8 @@ describe("Series", () => {
|
|
|
154
172
|
|
|
155
173
|
describe("writing", () => {
|
|
156
174
|
it("should correctly write to an allocated lazy array", () => {
|
|
157
|
-
const series = Series.alloc({
|
|
158
|
-
expect(series.
|
|
175
|
+
const series = Series.alloc({ capacity: 10, dataType: DataType.FLOAT32 });
|
|
176
|
+
expect(series.byteCapacity).toEqual(Size.bytes(40));
|
|
159
177
|
expect(series.length).toEqual(0);
|
|
160
178
|
const writeOne = new Series({ data: new Float32Array([1]) });
|
|
161
179
|
expect(series.write(writeOne)).toEqual(1);
|
|
@@ -165,7 +183,7 @@ describe("Series", () => {
|
|
|
165
183
|
expect(series.length).toEqual(3);
|
|
166
184
|
});
|
|
167
185
|
it("should recompute cached max and min correctly", () => {
|
|
168
|
-
const series = Series.alloc({
|
|
186
|
+
const series = Series.alloc({ capacity: 10, dataType: DataType.FLOAT32 });
|
|
169
187
|
series.enrich();
|
|
170
188
|
const writeTwo = new Series({ data: new Float32Array([2, 3]) });
|
|
171
189
|
series.write(writeTwo);
|
|
@@ -174,7 +192,7 @@ describe("Series", () => {
|
|
|
174
192
|
});
|
|
175
193
|
it("should correctly adjust the sample offset of a written array", () => {
|
|
176
194
|
const series = Series.alloc({
|
|
177
|
-
|
|
195
|
+
capacity: 2,
|
|
178
196
|
dataType: DataType.FLOAT32,
|
|
179
197
|
timeRange: TimeRange.ZERO,
|
|
180
198
|
sampleOffset: -3,
|
|
@@ -200,7 +218,7 @@ describe("Series", () => {
|
|
|
200
218
|
expect(ts.timeRange).toEqual(
|
|
201
219
|
new TimeRange(TimeStamp.seconds(1), TimeStamp.seconds(6)),
|
|
202
220
|
);
|
|
203
|
-
expect(ts.
|
|
221
|
+
expect(ts.capacity).toEqual(5);
|
|
204
222
|
expect(ts.length).toEqual(5);
|
|
205
223
|
expect(ts.dataType.toString()).toEqual(DataType.TIMESTAMP.toString());
|
|
206
224
|
expect(ts.data).toEqual(
|
|
@@ -233,7 +251,7 @@ describe("Series", () => {
|
|
|
233
251
|
expect(buf).toEqual(new Float32Array([1, 2, 3]));
|
|
234
252
|
});
|
|
235
253
|
it("should correctly update a buffer when writing to an allocated array", () => {
|
|
236
|
-
const series = Series.alloc({
|
|
254
|
+
const series = Series.alloc({ capacity: 10, dataType: DataType.FLOAT32 });
|
|
237
255
|
const controller = new MockGLBufferController();
|
|
238
256
|
series.updateGLBuffer(controller);
|
|
239
257
|
expect(controller.createBufferMock).toHaveBeenCalledTimes(1);
|
|
@@ -249,7 +267,7 @@ describe("Series", () => {
|
|
|
249
267
|
expect(controller.bufferDataMock).toHaveBeenCalledTimes(1);
|
|
250
268
|
expect(controller.bufferSubDataMock).toHaveBeenCalledTimes(1);
|
|
251
269
|
buf = controller.buffers[series.glBuffer as number];
|
|
252
|
-
expect(buf.byteLength).toEqual(series.
|
|
270
|
+
expect(buf.byteLength).toEqual(series.byteCapacity.valueOf());
|
|
253
271
|
expect(new Float32Array(buf)[0]).toEqual(1);
|
|
254
272
|
const writeTwo = new Series({ data: new Float32Array([2, 3]) });
|
|
255
273
|
series.write(writeTwo);
|
|
@@ -257,7 +275,7 @@ describe("Series", () => {
|
|
|
257
275
|
expect(controller.bufferDataMock).not.toHaveBeenCalledTimes(2);
|
|
258
276
|
expect(controller.bufferSubDataMock).toHaveBeenCalledTimes(2);
|
|
259
277
|
buf = controller.buffers[series.glBuffer as number];
|
|
260
|
-
expect(buf.byteLength).toEqual(series.
|
|
278
|
+
expect(buf.byteLength).toEqual(series.byteCapacity.valueOf());
|
|
261
279
|
expect(new Float32Array(buf)[0]).toEqual(1);
|
|
262
280
|
expect(new Float32Array(buf)[1]).toEqual(2);
|
|
263
281
|
expect(new Float32Array(buf)[2]).toEqual(3);
|
|
@@ -286,7 +304,7 @@ describe("Series", () => {
|
|
|
286
304
|
series.acquire(controller);
|
|
287
305
|
expect(controller.createBufferMock).toHaveBeenCalledTimes(2);
|
|
288
306
|
const buf = controller.buffers[series.glBuffer as number];
|
|
289
|
-
expect(buf.byteLength).toEqual(series.
|
|
307
|
+
expect(buf.byteLength).toEqual(series.byteCapacity.valueOf());
|
|
290
308
|
});
|
|
291
309
|
});
|
|
292
310
|
|
|
@@ -341,7 +359,7 @@ describe("Series", () => {
|
|
|
341
359
|
|
|
342
360
|
describe("binarySearch", () => {
|
|
343
361
|
it("should correctly binary search a pre-allocated array", () => {
|
|
344
|
-
const series = Series.alloc({
|
|
362
|
+
const series = Series.alloc({ capacity: 10, dataType: DataType.FLOAT32 });
|
|
345
363
|
const writeOne = new Series({ data: new Float32Array([1, 2, 3, 4, 5]) });
|
|
346
364
|
series.write(writeOne);
|
|
347
365
|
expect(series.binarySearch(3)).toEqual(2);
|
package/src/telem/series.ts
CHANGED
|
@@ -26,12 +26,6 @@ import {
|
|
|
26
26
|
|
|
27
27
|
export type SampleValue = number | bigint;
|
|
28
28
|
|
|
29
|
-
const validateFieldNotNull = (name: string, field: unknown): void => {
|
|
30
|
-
if (field == null) {
|
|
31
|
-
throw new Error(`field ${name} is null`);
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
29
|
interface GL {
|
|
36
30
|
control: GLBufferController | null;
|
|
37
31
|
buffer: WebGLBuffer | null;
|
|
@@ -46,6 +40,7 @@ export interface SeriesDigest {
|
|
|
46
40
|
alignment: bounds.Bounds;
|
|
47
41
|
timeRange?: string;
|
|
48
42
|
length: number;
|
|
43
|
+
capacity: number;
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
interface BaseSeriesProps {
|
|
@@ -62,7 +57,7 @@ export interface SeriesProps extends BaseSeriesProps {
|
|
|
62
57
|
}
|
|
63
58
|
|
|
64
59
|
export interface SeriesAllocProps extends BaseSeriesProps {
|
|
65
|
-
|
|
60
|
+
capacity: number;
|
|
66
61
|
dataType: CrudeDataType;
|
|
67
62
|
}
|
|
68
63
|
|
|
@@ -106,7 +101,7 @@ export class Series {
|
|
|
106
101
|
/** Tracks the number of entities currently using this array. */
|
|
107
102
|
private _refCount: number = 0;
|
|
108
103
|
|
|
109
|
-
static alloc({ length, dataType, ...props }: SeriesAllocProps): Series {
|
|
104
|
+
static alloc({ capacity: length, dataType, ...props }: SeriesAllocProps): Series {
|
|
110
105
|
if (length === 0)
|
|
111
106
|
throw new Error("[Series] - cannot allocate an array of length 0");
|
|
112
107
|
const data = new new DataType(dataType).Array(length);
|
|
@@ -188,13 +183,21 @@ export class Series {
|
|
|
188
183
|
throw new Error("cannot release an array with a negative reference count");
|
|
189
184
|
}
|
|
190
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Writes the given series to this series. If the series being written exceeds the
|
|
188
|
+
* remaining of series being written to, only the portion that fits will be written.
|
|
189
|
+
* @param other the series to write to this series. The data type of the series written
|
|
190
|
+
* must be the same as the data type of the series being written to.
|
|
191
|
+
* @returns the number of samples written. If the entire series fits, this value is
|
|
192
|
+
* equal to the length of the series being written.
|
|
193
|
+
*/
|
|
191
194
|
write(other: Series): number {
|
|
192
195
|
if (!other.dataType.equals(this.dataType))
|
|
193
196
|
throw new Error("buffer must be of the same type as this array");
|
|
194
197
|
|
|
195
198
|
// We've filled the entire underlying buffer
|
|
196
199
|
if (this.writePos === FULL_BUFFER) return 0;
|
|
197
|
-
const available = this.
|
|
200
|
+
const available = this.capacity - this.writePos;
|
|
198
201
|
|
|
199
202
|
const toWrite = available < other.length ? other.slice(0, available) : other;
|
|
200
203
|
this.underlyingData.set(toWrite.data as any, this.writePos);
|
|
@@ -252,23 +255,23 @@ export class Series {
|
|
|
252
255
|
|
|
253
256
|
/** @returns the time range of this array. */
|
|
254
257
|
get timeRange(): TimeRange {
|
|
255
|
-
|
|
256
|
-
return this._timeRange
|
|
258
|
+
if (this._timeRange == null) throw new Error("time range not set on series");
|
|
259
|
+
return this._timeRange;
|
|
257
260
|
}
|
|
258
261
|
|
|
259
|
-
/** @returns the capacity of the
|
|
260
|
-
get
|
|
262
|
+
/** @returns the capacity of the series in bytes. */
|
|
263
|
+
get byteCapacity(): Size {
|
|
261
264
|
return new Size(this.buffer.byteLength);
|
|
262
265
|
}
|
|
263
266
|
|
|
264
|
-
/** @returns the capacity of the
|
|
265
|
-
get
|
|
266
|
-
return this.dataType.density.length(this.
|
|
267
|
+
/** @returns the capacity of the series in samples. */
|
|
268
|
+
get capacity(): number {
|
|
269
|
+
return this.dataType.density.length(this.byteCapacity);
|
|
267
270
|
}
|
|
268
271
|
|
|
269
|
-
/** @returns the length of the
|
|
272
|
+
/** @returns the length of the series in bytes. */
|
|
270
273
|
get byteLength(): Size {
|
|
271
|
-
if (this.writePos === FULL_BUFFER) return this.
|
|
274
|
+
if (this.writePos === FULL_BUFFER) return this.byteCapacity;
|
|
272
275
|
return this.dataType.density.size(this.writePos);
|
|
273
276
|
}
|
|
274
277
|
|
|
@@ -371,9 +374,17 @@ export class Series {
|
|
|
371
374
|
return addSamples(this.max, -this.min);
|
|
372
375
|
}
|
|
373
376
|
|
|
374
|
-
at(index: number): SampleValue
|
|
377
|
+
at(index: number, required: true): SampleValue;
|
|
378
|
+
|
|
379
|
+
at(index: number, required?: false): SampleValue | undefined;
|
|
380
|
+
|
|
381
|
+
at(index: number, required?: boolean): SampleValue | undefined {
|
|
382
|
+
if (index < 0) index = this.length + index;
|
|
375
383
|
const v = this.data[index];
|
|
376
|
-
if (v == null)
|
|
384
|
+
if (v == null) {
|
|
385
|
+
if (required) throw new Error(`[series] - no value at index ${index}`);
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
377
388
|
return addSamples(v, this.sampleOffset);
|
|
378
389
|
}
|
|
379
390
|
|
|
@@ -388,7 +399,7 @@ export class Series {
|
|
|
388
399
|
const cf = compare.newF(value);
|
|
389
400
|
while (left <= right) {
|
|
390
401
|
const mid = Math.floor((left + right) / 2);
|
|
391
|
-
const cmp = cf(this.at(mid), value);
|
|
402
|
+
const cmp = cf(this.at(mid, true), value);
|
|
392
403
|
if (cmp === 0) return mid;
|
|
393
404
|
if (cmp < 0) left = mid + 1;
|
|
394
405
|
else right = mid - 1;
|
|
@@ -414,7 +425,7 @@ export class Series {
|
|
|
414
425
|
// This means we only need to buffer part of the array.
|
|
415
426
|
if (this.writePos !== FULL_BUFFER) {
|
|
416
427
|
if (prevBuffer === 0) {
|
|
417
|
-
gl.bufferData(gl.ARRAY_BUFFER, this.
|
|
428
|
+
gl.bufferData(gl.ARRAY_BUFFER, this.byteCapacity.valueOf(), gl.STATIC_DRAW);
|
|
418
429
|
}
|
|
419
430
|
const byteOffset = this.dataType.density.size(prevBuffer).valueOf();
|
|
420
431
|
const slice = this.underlyingData.slice(this.gl.prevBuffer, this.writePos);
|
|
@@ -439,6 +450,7 @@ export class Series {
|
|
|
439
450
|
alignment: this.alignmentBounds,
|
|
440
451
|
timeRange: this._timeRange?.toString(),
|
|
441
452
|
length: this.length,
|
|
453
|
+
capacity: this.capacity,
|
|
442
454
|
};
|
|
443
455
|
}
|
|
444
456
|
|