@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.
Files changed (80) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/deep/difference.d.ts +3 -0
  3. package/dist/deep/external.d.ts +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/invert.d.ts +1 -0
  6. package/dist/optional.d.ts +3 -0
  7. package/dist/spatial/{bounds.d.ts → bounds/bounds.d.ts} +1 -1
  8. package/dist/spatial/bounds/index.d.ts +1 -0
  9. package/dist/spatial/{box.d.ts → box/box.d.ts} +25 -8
  10. package/dist/spatial/box/index.d.ts +1 -0
  11. package/dist/spatial/{dimensions.d.ts → dimensions/dimensions.d.ts} +1 -1
  12. package/dist/spatial/dimensions/index.d.ts +1 -0
  13. package/dist/spatial/{direction.d.ts → direction/direction.d.ts} +1 -1
  14. package/dist/spatial/direction/index.d.ts +1 -0
  15. package/dist/spatial/external.d.ts +8 -7
  16. package/dist/spatial/location/index.d.ts +1 -0
  17. package/dist/spatial/{location.d.ts → location/location.d.ts} +12 -9
  18. package/dist/spatial/position/index.d.ts +1 -0
  19. package/dist/spatial/position/position.d.ts +20 -0
  20. package/dist/spatial/scale/index.d.ts +1 -0
  21. package/dist/spatial/{scale.d.ts → scale/scale.d.ts} +5 -5
  22. package/dist/spatial/scale/scale.spec.d.ts +1 -0
  23. package/dist/spatial/xy/index.d.ts +1 -0
  24. package/dist/spatial/{xy.d.ts → xy/xy.d.ts} +10 -9
  25. package/dist/spatial/xy/xy.spec.d.ts +1 -0
  26. package/dist/telem/series.d.ts +18 -8
  27. package/dist/telem/telem.d.ts +21 -1
  28. package/dist/{x.cjs.js → x.cjs} +401 -55
  29. package/dist/x.cjs.map +1 -0
  30. package/dist/{x.es.js → x.js} +401 -55
  31. package/dist/x.js.map +1 -0
  32. package/package.json +7 -6
  33. package/src/deep/difference.spec.ts +38 -0
  34. package/src/deep/difference.ts +47 -0
  35. package/src/deep/external.ts +1 -0
  36. package/src/deep/memo.ts +10 -1
  37. package/src/index.ts +1 -0
  38. package/src/invert.ts +1 -0
  39. package/src/optional.ts +5 -0
  40. package/src/shallowCopy.ts +10 -1
  41. package/src/spatial/{bounds.spec.ts → bounds/bounds.spec.ts} +150 -68
  42. package/src/spatial/{bounds.ts → bounds/bounds.ts} +0 -1
  43. package/src/spatial/bounds/index.ts +10 -0
  44. package/src/spatial/{box.spec.ts → box/box.spec.ts} +106 -3
  45. package/src/spatial/{box.ts → box/box.ts} +107 -22
  46. package/src/spatial/box/index.ts +10 -0
  47. package/src/spatial/{dimensions.spec.ts → dimensions/dimensions.spec.ts} +1 -1
  48. package/src/spatial/dimensions/index.ts +10 -0
  49. package/src/spatial/{direction.spec.ts → direction/direction.spec.ts} +1 -1
  50. package/src/spatial/direction/index.ts +10 -0
  51. package/src/spatial/external.ts +8 -7
  52. package/src/spatial/location/index.ts +10 -0
  53. package/src/spatial/{location.spec.ts → location/location.spec.ts} +1 -1
  54. package/src/spatial/{location.ts → location/location.ts} +67 -29
  55. package/src/spatial/position/index.ts +10 -0
  56. package/src/spatial/position/position.spec.ts +211 -0
  57. package/src/spatial/position/position.ts +174 -0
  58. package/src/spatial/scale/index.ts +10 -0
  59. package/src/spatial/{scale.spec.ts → scale/scale.spec.ts} +2 -2
  60. package/src/spatial/{scale.ts → scale/scale.ts} +6 -6
  61. package/src/spatial/xy/index.ts +10 -0
  62. package/src/spatial/{xy.spec.ts → xy/xy.spec.ts} +1 -1
  63. package/src/spatial/{xy.ts → xy/xy.ts} +19 -14
  64. package/src/telem/series.spec.ts +37 -19
  65. package/src/telem/series.ts +34 -22
  66. package/src/telem/telem.spec.ts +106 -24
  67. package/src/telem/telem.ts +73 -2
  68. package/dist/spatial/position.d.ts +0 -2
  69. package/dist/x.cjs.js.map +0 -1
  70. package/dist/x.es.js.map +0 -1
  71. package/src/spatial/position.ts +0 -26
  72. /package/dist/{spatial/bounds.spec.d.ts → deep/difference.spec.d.ts} +0 -0
  73. /package/dist/spatial/{box.spec.d.ts → bounds/bounds.spec.d.ts} +0 -0
  74. /package/dist/spatial/{dimensions.spec.d.ts → box/box.spec.d.ts} +0 -0
  75. /package/dist/spatial/{direction.spec.d.ts → dimensions/dimensions.spec.d.ts} +0 -0
  76. /package/dist/spatial/{location.spec.d.ts → direction/direction.spec.d.ts} +0 -0
  77. /package/dist/spatial/{scale.spec.d.ts → location/location.spec.d.ts} +0 -0
  78. /package/dist/spatial/{xy.spec.d.ts → position/position.spec.d.ts} +0 -0
  79. /package/src/spatial/{dimensions.ts → dimensions/dimensions.ts} +0 -0
  80. /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";
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { describe, expect, test } from "vitest";
11
11
 
12
- import * as xy from "@/spatial/xy";
12
+ import * as xy from "@/spatial/xy/xy";
13
13
 
14
14
  describe("XY", () => {
15
15
  describe("construction", () => {
@@ -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
- type TranslateOverloadOne = (a: Crude, b: Crude, ...cb: Crude[]) => XY;
102
- type TranslateOverloadTwo = (a: Crude, direction: Direction, value: number) => XY;
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
+ };
@@ -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.byteCap).toEqual(Size.bytes(12));
25
- expect(a.cap).toEqual(3);
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({ length: 10, dataType: DataType.FLOAT32 });
59
- expect(series.byteCap).toEqual(Size.bytes(40));
60
- expect(series.cap).toEqual(10);
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({ length: 0, dataType: DataType.FLOAT32 });
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.byteCap).toEqual(Size.bytes(4));
104
- expect(b.cap).toEqual(1);
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({ length: 10, dataType: DataType.FLOAT32 });
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({ length: 10, dataType: DataType.FLOAT32 });
158
- expect(series.byteCap).toEqual(Size.bytes(40));
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({ length: 10, dataType: DataType.FLOAT32 });
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
- length: 2,
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.cap).toEqual(5);
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({ length: 10, dataType: DataType.FLOAT32 });
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.byteCap.valueOf());
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.byteCap.valueOf());
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.byteCap.valueOf());
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({ length: 10, dataType: DataType.FLOAT32 });
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);
@@ -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
- length: number;
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.cap - this.writePos;
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
- validateFieldNotNull("timeRange", this._timeRange);
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 underlying buffer in bytes. */
260
- get byteCap(): Size {
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 underlying buffer in samples. */
265
- get cap(): number {
266
- return this.dataType.density.length(this.byteCap);
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 underlying buffer in samples. */
272
+ /** @returns the length of the series in bytes. */
270
273
  get byteLength(): Size {
271
- if (this.writePos === FULL_BUFFER) return this.byteCap;
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) return undefined as any;
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.byteCap.valueOf(), gl.STATIC_DRAW);
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