@synnaxlabs/x 0.12.0 → 0.14.1

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 (85) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/binary/encoder.d.ts +2 -22
  3. package/dist/deep/difference.d.ts +3 -0
  4. package/dist/deep/external.d.ts +1 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/invert.d.ts +1 -0
  7. package/dist/optional.d.ts +3 -0
  8. package/dist/spatial/{bounds.d.ts → bounds/bounds.d.ts} +1 -1
  9. package/dist/spatial/bounds/index.d.ts +1 -0
  10. package/dist/spatial/{box.d.ts → box/box.d.ts} +25 -8
  11. package/dist/spatial/box/index.d.ts +1 -0
  12. package/dist/spatial/{dimensions.d.ts → dimensions/dimensions.d.ts} +1 -1
  13. package/dist/spatial/dimensions/index.d.ts +1 -0
  14. package/dist/spatial/{direction.d.ts → direction/direction.d.ts} +1 -1
  15. package/dist/spatial/direction/index.d.ts +1 -0
  16. package/dist/spatial/external.d.ts +8 -7
  17. package/dist/spatial/location/index.d.ts +1 -0
  18. package/dist/spatial/{location.d.ts → location/location.d.ts} +12 -9
  19. package/dist/spatial/position/index.d.ts +1 -0
  20. package/dist/spatial/position/position.d.ts +20 -0
  21. package/dist/spatial/scale/index.d.ts +1 -0
  22. package/dist/spatial/{scale.d.ts → scale/scale.d.ts} +5 -5
  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 +1 -0
  27. package/dist/telem/telem.d.ts +91 -15
  28. package/dist/x.cjs +8 -0
  29. package/dist/x.cjs.map +1 -0
  30. package/dist/x.js +6731 -0
  31. package/dist/x.js.map +1 -0
  32. package/package.json +7 -6
  33. package/src/binary/encoder.ts +13 -37
  34. package/src/deep/difference.spec.ts +38 -0
  35. package/src/deep/difference.ts +47 -0
  36. package/src/deep/external.ts +1 -0
  37. package/src/index.ts +1 -0
  38. package/src/invert.ts +1 -0
  39. package/src/optional.ts +5 -0
  40. package/src/primitive.ts +1 -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 +1 -3
  65. package/src/telem/series.ts +6 -10
  66. package/src/telem/telem.spec.ts +127 -24
  67. package/src/telem/telem.ts +162 -41
  68. package/vite.config.ts +1 -1
  69. package/dist/spatial/position.d.ts +0 -2
  70. package/dist/x.cjs.js +0 -9305
  71. package/dist/x.cjs.js.map +0 -1
  72. package/dist/x.es.js +0 -9306
  73. package/dist/x.es.js.map +0 -1
  74. package/src/spatial/position.ts +0 -26
  75. package/src/telem/encode.ts +0 -22
  76. /package/dist/{spatial/bounds.spec.d.ts → deep/difference.spec.d.ts} +0 -0
  77. /package/dist/spatial/{box.spec.d.ts → bounds/bounds.spec.d.ts} +0 -0
  78. /package/dist/spatial/{dimensions.spec.d.ts → box/box.spec.d.ts} +0 -0
  79. /package/dist/spatial/{direction.spec.d.ts → dimensions/dimensions.spec.d.ts} +0 -0
  80. /package/dist/spatial/{location.spec.d.ts → direction/direction.spec.d.ts} +0 -0
  81. /package/dist/spatial/{scale.spec.d.ts → location/location.spec.d.ts} +0 -0
  82. /package/dist/spatial/{xy.spec.d.ts → position/position.spec.d.ts} +0 -0
  83. /package/dist/{telem/encode.d.ts → spatial/scale/scale.spec.d.ts} +0 -0
  84. /package/src/spatial/{dimensions.ts → dimensions/dimensions.ts} +0 -0
  85. /package/src/spatial/{direction.ts → direction/direction.ts} +0 -0
@@ -0,0 +1,211 @@
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 { describe, expect, it } from "vitest";
11
+
12
+ import { box } from "@/spatial";
13
+ import { type Alignment } from "@/spatial/base";
14
+ import { location } from "@/spatial/location";
15
+ import { position } from "@/spatial/position";
16
+
17
+ type Spec = [position.DialogProps, location.XY];
18
+
19
+ describe("position", () => {
20
+ describe("dialog", () => {
21
+ const SPEC_CASE_1: Spec = [
22
+ {
23
+ container: box.construct(0, 0, 100, 100),
24
+ target: box.construct(45, 55, 10, 10),
25
+ dialog: box.construct(0, 0, 20, 20),
26
+ },
27
+ location.LEFT_CENTER,
28
+ ];
29
+
30
+ const SPEC_CASE_2: Spec = [
31
+ {
32
+ container: box.construct(0, 0, 100, 100),
33
+ target: box.construct(45, 55, 10, 10),
34
+ dialog: box.construct(0, 0, 20, 20),
35
+ initial: "top",
36
+ },
37
+ location.TOP_CENTER,
38
+ ];
39
+
40
+ const SPEC_CASE_3: Spec = [
41
+ {
42
+ container: box.construct(0, 0, 100, 100),
43
+ target: box.construct(45, 55, 10, 10),
44
+ dialog: box.construct(0, 0, 20, 20),
45
+ initial: "bottom",
46
+ },
47
+ location.BOTTOM_CENTER,
48
+ ];
49
+
50
+ const SPEC_CASE_4: Spec = [
51
+ {
52
+ container: box.construct(0, 0, 100, 100),
53
+ target: box.construct(45, 55, 10, 10),
54
+ dialog: box.construct(0, 0, 20, 20),
55
+ initial: "left",
56
+ },
57
+ location.LEFT_CENTER,
58
+ ];
59
+
60
+ const SPEC_CASE_5: Spec = [
61
+ {
62
+ container: box.construct(0, 0, 100, 100),
63
+ target: box.construct(45, 55, 10, 10),
64
+ dialog: box.construct(0, 0, 20, 20),
65
+ initial: "right",
66
+ },
67
+ location.RIGHT_CENTER,
68
+ ];
69
+
70
+ // Target is in bottom right corner
71
+ const SPEC_CASE_6: Spec = [
72
+ {
73
+ container: box.construct(0, 0, 100, 100),
74
+ target: box.construct(90, 90, 10, 10),
75
+ dialog: box.construct(0, 0, 20, 20),
76
+ },
77
+ location.LEFT_CENTER,
78
+ ];
79
+
80
+ // Target is in the top left corner
81
+ const SPEC_CASE_7: Spec = [
82
+ {
83
+ container: box.construct(0, 0, 100, 100),
84
+ target: box.construct(0, 0, 10, 10),
85
+ dialog: box.construct(0, 0, 20, 20),
86
+ },
87
+ location.BOTTOM_CENTER,
88
+ ];
89
+
90
+ const SPEC_CASE_8: Spec = [
91
+ {
92
+ container: {
93
+ one: { x: 0, y: 0 },
94
+ two: { x: 1707, y: 1075 },
95
+ root: { x: "left", y: "top" },
96
+ },
97
+ target: {
98
+ one: { x: 79, y: 965 },
99
+ two: { x: 1647, y: 992 },
100
+ root: { x: "left", y: "top" },
101
+ },
102
+ dialog: {
103
+ one: { x: 79.53125, y: 781 },
104
+ two: { x: 1647.53125, y: 1141 },
105
+ root: { x: "left", y: "top" },
106
+ },
107
+ initial: { x: "center" },
108
+ alignments: ["center"],
109
+ },
110
+ location.TOP_CENTER,
111
+ ];
112
+
113
+ const SPEC_CASE_9: Spec = [
114
+ {
115
+ container: {
116
+ one: { x: 0, y: 0 },
117
+ two: { x: 1707, y: 697 },
118
+ root: { x: "left", y: "top" },
119
+ },
120
+ target: {
121
+ one: { x: 79, y: 587 },
122
+ two: { x: 1647, y: 614 },
123
+ root: { x: "left", y: "top" },
124
+ },
125
+ dialog: {
126
+ one: { x: 79, y: 991 },
127
+ two: { x: 1647, y: 1351 },
128
+ root: { x: "left", y: "top" },
129
+ },
130
+ initial: { x: "center" },
131
+ alignments: ["center"],
132
+ },
133
+ location.TOP_CENTER,
134
+ ];
135
+
136
+ // const SPEC_CASE_10 = [
137
+ // {
138
+ // container: {
139
+ // one: { x: 0, y: 0 },
140
+ // two: { x: 1707, y: 697 },
141
+ // root: { x: "left", y: "top" },
142
+ // },
143
+ // target: {
144
+ // one: { x: 78, y: 4 },
145
+ // two: { x: 278, y: 31 },
146
+ // root: { x: "left", y: "top" },
147
+ // },
148
+ // dialog: {
149
+ // one: { x: 78, y: 4 },
150
+ // two: { x: 78, y: 4 },
151
+ // root: { x: "left", y: "top" },
152
+ // },
153
+ // alignments: ["end"],
154
+ // disable: ["center"],
155
+ // },
156
+ // location.BOTTOM_LEFT,
157
+ // ];
158
+
159
+ const SPECS: Spec[] = [
160
+ SPEC_CASE_1,
161
+ SPEC_CASE_2,
162
+ SPEC_CASE_3,
163
+ SPEC_CASE_4,
164
+ SPEC_CASE_5,
165
+ SPEC_CASE_6,
166
+ SPEC_CASE_7,
167
+ SPEC_CASE_8,
168
+ SPEC_CASE_9,
169
+ // SPEC_CASE_10,
170
+ ];
171
+
172
+ SPECS.forEach(([props, expected]) => {
173
+ it(`should position dialog correctly`, () => {
174
+ expect(position.dialog(props).location).toEqual(expected);
175
+ });
176
+ });
177
+ });
178
+ describe("getRoot", () => {
179
+ const SPECS: Array<[location.XY, Alignment, location.XY]> = [
180
+ [location.TOP_LEFT, "start", location.BOTTOM_RIGHT],
181
+ [location.TOP_LEFT, "center", location.BOTTOM_CENTER],
182
+ [location.TOP_LEFT, "end", location.BOTTOM_LEFT],
183
+ [location.TOP_RIGHT, "start", location.BOTTOM_LEFT],
184
+ [location.TOP_RIGHT, "center", location.BOTTOM_CENTER],
185
+ [location.TOP_RIGHT, "end", location.BOTTOM_RIGHT],
186
+ [location.TOP_CENTER, "start", location.BOTTOM_LEFT],
187
+ [location.TOP_CENTER, "center", location.BOTTOM_CENTER],
188
+ [location.TOP_CENTER, "end", location.BOTTOM_RIGHT],
189
+ [location.LEFT_CENTER, "start", location.BOTTOM_RIGHT],
190
+ [location.LEFT_CENTER, "center", location.RIGHT_CENTER],
191
+ [location.LEFT_CENTER, "end", location.TOP_RIGHT],
192
+ [location.RIGHT_CENTER, "start", location.BOTTOM_LEFT],
193
+ [location.RIGHT_CENTER, "center", location.LEFT_CENTER],
194
+ [location.RIGHT_CENTER, "end", location.TOP_LEFT],
195
+ [location.BOTTOM_LEFT, "start", location.TOP_RIGHT],
196
+ [location.BOTTOM_LEFT, "center", location.TOP_CENTER],
197
+ [location.BOTTOM_LEFT, "end", location.TOP_LEFT],
198
+ [location.BOTTOM_RIGHT, "start", location.TOP_LEFT],
199
+ [location.BOTTOM_RIGHT, "center", location.TOP_CENTER],
200
+ [location.BOTTOM_RIGHT, "end", location.TOP_RIGHT],
201
+ [location.BOTTOM_CENTER, "start", location.TOP_LEFT],
202
+ [location.BOTTOM_CENTER, "center", location.TOP_CENTER],
203
+ [location.BOTTOM_CENTER, "end", location.TOP_RIGHT],
204
+ ];
205
+ SPECS.forEach(([option, order, expected]) => {
206
+ it(`should position get the correct positioning root for ${location.xyToString(option)} and ${order}`, () => {
207
+ expect(position.getRoot(option, order)).toEqual(expected);
208
+ });
209
+ });
210
+ });
211
+ });
@@ -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
+ };
@@ -50,7 +50,7 @@ describe("Series", () => {
50
50
  dataType: DataType.FLOAT32,
51
51
  timeRange: new TimeRange(1, 2),
52
52
  });
53
- expect(a.timeRange.span.valueOf()).toBe(1);
53
+ expect(a.timeRange.span.valueOf()).toBe(1n);
54
54
  });
55
55
 
56
56
  describe("allocation", () => {
@@ -105,8 +105,6 @@ describe("Series", () => {
105
105
  });
106
106
  });
107
107
 
108
-
109
-
110
108
  describe("slice", () => {
111
109
  it("should slice a lazy array", () => {
112
110
  const a = new Series({
@@ -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 {
@@ -189,8 +184,8 @@ export class Series {
189
184
  }
190
185
 
191
186
  /**
192
- * Writes the given series to this series. If the series being written exceeds the
193
- * remaining of series being written to, only the portion that fits will be written.
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.
194
189
  * @param other the series to write to this series. The data type of the series written
195
190
  * must be the same as the data type of the series being written to.
196
191
  * @returns the number of samples written. If the entire series fits, this value is
@@ -260,8 +255,8 @@ export class Series {
260
255
 
261
256
  /** @returns the time range of this array. */
262
257
  get timeRange(): TimeRange {
263
- validateFieldNotNull("timeRange", this._timeRange);
264
- return this._timeRange!;
258
+ if (this._timeRange == null) throw new Error("time range not set on series");
259
+ return this._timeRange;
265
260
  }
266
261
 
267
262
  /** @returns the capacity of the series in bytes. */
@@ -455,6 +450,7 @@ export class Series {
455
450
  alignment: this.alignmentBounds,
456
451
  timeRange: this._timeRange?.toString(),
457
452
  length: this.length,
453
+ capacity: this.capacity,
458
454
  };
459
455
  }
460
456