@synnaxlabs/x 0.7.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/.eslintrc.cjs +18 -0
- package/.turbo/turbo-build.log +16 -0
- package/.vscode/settings.json +3 -0
- package/LICENSE +4 -0
- package/README.md +44 -0
- package/dist/binary/encoder.d.ts +59 -0
- package/dist/binary/encoder.spec.d.ts +1 -0
- package/dist/binary/index.d.ts +1 -0
- package/dist/case.d.ts +5 -0
- package/dist/change/change.d.ts +32 -0
- package/dist/change/index.d.ts +1 -0
- package/dist/clamp.d.ts +1 -0
- package/dist/compare/compare.d.ts +39 -0
- package/dist/compare/index.d.ts +1 -0
- package/dist/debounce.d.ts +2 -0
- package/dist/deep/copy.d.ts +1 -0
- package/dist/deep/delete.d.ts +2 -0
- package/dist/deep/delete.spec.d.ts +1 -0
- package/dist/deep/equal.d.ts +8 -0
- package/dist/deep/equal.spec.d.ts +1 -0
- package/dist/deep/external.d.ts +7 -0
- package/dist/deep/index.d.ts +1 -0
- package/dist/deep/key.d.ts +30 -0
- package/dist/deep/memo.d.ts +1 -0
- package/dist/deep/merge.d.ts +2 -0
- package/dist/deep/merge.spec.d.ts +1 -0
- package/dist/deep/partial.d.ts +3 -0
- package/dist/destructor.d.ts +1 -0
- package/dist/identity.d.ts +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/join.d.ts +1 -0
- package/dist/kv/index.d.ts +1 -0
- package/dist/kv/types.d.ts +32 -0
- package/dist/mock/MockGLBufferController.d.ts +20 -0
- package/dist/mock/index.d.ts +1 -0
- package/dist/observe/index.d.ts +1 -0
- package/dist/observe/observe.d.ts +11 -0
- package/dist/optional.d.ts +1 -0
- package/dist/primitive.d.ts +8 -0
- package/dist/record.d.ts +14 -0
- package/dist/renderable.d.ts +4 -0
- package/dist/runtime/detect.d.ts +9 -0
- package/dist/runtime/external.d.ts +2 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/os.d.ts +9 -0
- package/dist/search.d.ts +15 -0
- package/dist/spatial/base.d.ts +102 -0
- package/dist/spatial/bounds.d.ts +31 -0
- package/dist/spatial/bounds.spec.d.ts +1 -0
- package/dist/spatial/box.d.ts +265 -0
- package/dist/spatial/box.spec.d.ts +1 -0
- package/dist/spatial/dimensions.d.ts +59 -0
- package/dist/spatial/dimensions.spec.d.ts +1 -0
- package/dist/spatial/direction.d.ts +10 -0
- package/dist/spatial/direction.spec.d.ts +1 -0
- package/dist/spatial/external.d.ts +8 -0
- package/dist/spatial/index.d.ts +1 -0
- package/dist/spatial/location.d.ts +52 -0
- package/dist/spatial/location.spec.d.ts +1 -0
- package/dist/spatial/position.d.ts +2 -0
- package/dist/spatial/scale.d.ts +241 -0
- package/dist/spatial/scale.spec.d.ts +1 -0
- package/dist/spatial/spatial.d.ts +1 -0
- package/dist/spatial/xy.d.ts +116 -0
- package/dist/spatial/xy.spec.d.ts +1 -0
- package/dist/telem/encode.d.ts +1 -0
- package/dist/telem/generate.d.ts +2 -0
- package/dist/telem/gl.d.ts +11 -0
- package/dist/telem/index.d.ts +3 -0
- package/dist/telem/series.d.ts +104 -0
- package/dist/telem/series.spec.d.ts +1 -0
- package/dist/telem/telem.d.ts +633 -0
- package/dist/telem/telem.spec.d.ts +1 -0
- package/dist/toArray.d.ts +1 -0
- package/dist/transform.d.ts +1 -0
- package/dist/unique.d.ts +1 -0
- package/dist/url/index.d.ts +1 -0
- package/dist/url/url.d.ts +46 -0
- package/dist/url/url.spec.d.ts +1 -0
- package/dist/worker/worker.d.ts +32 -0
- package/dist/worker/worker.spec.d.ts +1 -0
- package/dist/x.cjs.js +9046 -0
- package/dist/x.cjs.js.map +1 -0
- package/dist/x.es.js +9047 -0
- package/dist/x.es.js.map +1 -0
- package/package.json +42 -0
- package/src/binary/encoder.spec.ts +31 -0
- package/src/binary/encoder.ts +118 -0
- package/src/binary/index.ts +10 -0
- package/src/case.ts +31 -0
- package/src/change/change.ts +31 -0
- package/src/change/index.ts +10 -0
- package/src/clamp.ts +14 -0
- package/src/compare/compare.ts +116 -0
- package/src/compare/index.ts +10 -0
- package/src/debounce.ts +45 -0
- package/src/deep/copy.ts +13 -0
- package/src/deep/delete.spec.ts +36 -0
- package/src/deep/delete.ts +27 -0
- package/src/deep/equal.spec.ts +82 -0
- package/src/deep/equal.ts +65 -0
- package/src/deep/external.ts +15 -0
- package/src/deep/index.ts +10 -0
- package/src/deep/key.ts +46 -0
- package/src/deep/merge.spec.ts +63 -0
- package/src/deep/merge.ts +41 -0
- package/src/deep/partial.ts +14 -0
- package/src/destructor.ts +10 -0
- package/src/identity.ts +14 -0
- package/src/index.ts +34 -0
- package/src/join.ts +14 -0
- package/src/kv/index.ts +10 -0
- package/src/kv/types.ts +52 -0
- package/src/mock/MockGLBufferController.ts +70 -0
- package/src/mock/index.ts +10 -0
- package/src/observe/index.ts +10 -0
- package/src/observe/observe.ts +33 -0
- package/src/optional.ts +10 -0
- package/src/primitive.ts +46 -0
- package/src/record.ts +45 -0
- package/src/renderable.ts +20 -0
- package/src/runtime/detect.ts +34 -0
- package/src/runtime/external.ts +11 -0
- package/src/runtime/index.ts +10 -0
- package/src/runtime/os.ts +38 -0
- package/src/search.ts +40 -0
- package/src/spatial/base.ts +80 -0
- package/src/spatial/bounds.spec.ts +99 -0
- package/src/spatial/bounds.ts +80 -0
- package/src/spatial/box.spec.ts +137 -0
- package/src/spatial/box.ts +326 -0
- package/src/spatial/dimensions.spec.ts +47 -0
- package/src/spatial/dimensions.ts +64 -0
- package/src/spatial/direction.spec.ts +25 -0
- package/src/spatial/direction.ts +47 -0
- package/src/spatial/external.ts +17 -0
- package/src/spatial/index.ts +10 -0
- package/src/spatial/location.spec.ts +24 -0
- package/src/spatial/location.ts +124 -0
- package/src/spatial/position.ts +26 -0
- package/src/spatial/scale.spec.ts +74 -0
- package/src/spatial/scale.ts +351 -0
- package/src/spatial/spatial.ts +17 -0
- package/src/spatial/xy.spec.ts +68 -0
- package/src/spatial/xy.ts +164 -0
- package/src/telem/encode.ts +22 -0
- package/src/telem/generate.ts +19 -0
- package/src/telem/gl.ts +22 -0
- package/src/telem/index.ts +12 -0
- package/src/telem/series.spec.ts +289 -0
- package/src/telem/series.ts +449 -0
- package/src/telem/telem.spec.ts +302 -0
- package/src/telem/telem.ts +1237 -0
- package/src/toArray.ts +11 -0
- package/src/transform.ts +10 -0
- package/src/unique.ts +10 -0
- package/src/url/index.ts +10 -0
- package/src/url/url.spec.ts +47 -0
- package/src/url/url.ts +113 -0
- package/src/worker/worker.spec.ts +41 -0
- package/src/worker/worker.ts +86 -0
- package/tsconfig.json +7 -0
- package/tsconfig.vite.json +4 -0
- package/vite.config.ts +23 -0
|
@@ -0,0 +1,26 @@
|
|
|
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 * as xy from "@/spatial/xy";
|
|
11
|
+
|
|
12
|
+
export const posititonSoVisible = (target: HTMLElement, p: xy.XY): [xy.XY, boolean] => {
|
|
13
|
+
const { width, height } = target.getBoundingClientRect();
|
|
14
|
+
const { innerWidth, innerHeight } = window;
|
|
15
|
+
let changed = false;
|
|
16
|
+
let nextXY = xy.construct(p);
|
|
17
|
+
if (p.x + width > innerWidth) {
|
|
18
|
+
nextXY = xy.translateX(nextXY, -width);
|
|
19
|
+
changed = true;
|
|
20
|
+
}
|
|
21
|
+
if (p.y + height > innerHeight) {
|
|
22
|
+
nextXY = xy.translateY(nextXY, -height);
|
|
23
|
+
changed = true;
|
|
24
|
+
}
|
|
25
|
+
return [nextXY, changed];
|
|
26
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
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, it, expect, test } from "vitest";
|
|
11
|
+
|
|
12
|
+
import * as box from "@/spatial/box";
|
|
13
|
+
import { XY, Scale } from "@/spatial/scale";
|
|
14
|
+
|
|
15
|
+
type ScaleSpec = [name: string, scale: Scale, i: number, o: number];
|
|
16
|
+
|
|
17
|
+
describe("Scale", () => {
|
|
18
|
+
describe("Scale", () => {
|
|
19
|
+
const simpleScale = Scale.scale(0, 10).scale(0, 1);
|
|
20
|
+
const translateScale = Scale.scale(0, 10).translate(5).scale(0, 1);
|
|
21
|
+
const translateMagnifyScale = Scale.scale(0, 10)
|
|
22
|
+
.translate(5)
|
|
23
|
+
.magnify(2)
|
|
24
|
+
.scale(0, 1);
|
|
25
|
+
describe("position", () => {
|
|
26
|
+
const positionSpecs: ScaleSpec[] = [
|
|
27
|
+
["basic", simpleScale, 0, 0],
|
|
28
|
+
["basic II", simpleScale, 5, 0.5],
|
|
29
|
+
["reverse basic", simpleScale.reverse(), 0, 0],
|
|
30
|
+
["reverse basic II", simpleScale.reverse(), 0.5, 5],
|
|
31
|
+
["translate", translateScale, 0, 0.5],
|
|
32
|
+
["translate II", translateScale, 5, 1],
|
|
33
|
+
["reverse translate", translateScale.reverse(), 0.5, 0],
|
|
34
|
+
["reverse translate II", translateScale.reverse(), 0, -5],
|
|
35
|
+
["translate magnify", translateMagnifyScale, 0, 1],
|
|
36
|
+
["translate magnify II", translateMagnifyScale, 5, 2],
|
|
37
|
+
["reverse translate magnify", translateMagnifyScale.reverse(), 1, 0],
|
|
38
|
+
["reverse translate magnify II", translateMagnifyScale.reverse(), 0, -5],
|
|
39
|
+
];
|
|
40
|
+
positionSpecs.forEach(([name, scale, i, o]) => {
|
|
41
|
+
it(`should return ${o} for ${i} on ${name}`, () => {
|
|
42
|
+
expect(scale.pos(i)).toBe(o);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("dimension", () => {
|
|
47
|
+
const dimensionSpecs: ScaleSpec[] = [
|
|
48
|
+
["basic", simpleScale, 0, 0],
|
|
49
|
+
["basic II", simpleScale, 5, 0.5],
|
|
50
|
+
["reverse basic", simpleScale.reverse(), 0, 0],
|
|
51
|
+
["reverse basic II", simpleScale.reverse(), 0.5, 5],
|
|
52
|
+
["translate", translateScale, 0, 0],
|
|
53
|
+
["translate II", translateScale, 5, 0.5],
|
|
54
|
+
["reverse translate", translateScale.reverse(), 0.5, 5],
|
|
55
|
+
["translate magnify", translateMagnifyScale, 0, 0],
|
|
56
|
+
["translate magnify II", translateMagnifyScale, 5, 1],
|
|
57
|
+
];
|
|
58
|
+
dimensionSpecs.forEach(([name, scale, i, o]) => {
|
|
59
|
+
it(`should return ${o} for ${i} on ${name}`, () =>
|
|
60
|
+
expect(scale.dim(i)).toBe(o));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("XYScale", () => {
|
|
64
|
+
test("converting a DOM rect to decimal coordinates", () => {
|
|
65
|
+
const s = XY.scale(box.construct(100, 100, 1000, 1000)).scale(box.DECIMAL);
|
|
66
|
+
const b1 = s.box(box.construct(100, 100, 1000, 1000));
|
|
67
|
+
expect(box.bottomLeft(b1)).toEqual({ x: 0, y: 0 });
|
|
68
|
+
const b2 = s.box(box.construct(200, 200, 200, 200));
|
|
69
|
+
expect(box.bottomLeft(b2).x).toBeCloseTo(0.1);
|
|
70
|
+
expect(box.bottomLeft(b2).y).toBeCloseTo(0.7);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,351 @@
|
|
|
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 { z } from "zod";
|
|
11
|
+
|
|
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";
|
|
19
|
+
|
|
20
|
+
export const crudeXYTransform = z.object({ offset: xy.crudeZ, scale: xy.crudeZ });
|
|
21
|
+
|
|
22
|
+
export type XYTransformT = z.infer<typeof crudeXYTransform>;
|
|
23
|
+
|
|
24
|
+
export type BoundVariant = "domain" | "range";
|
|
25
|
+
|
|
26
|
+
type ValueType = "position" | "dimension";
|
|
27
|
+
|
|
28
|
+
type Operation = (
|
|
29
|
+
currScale: bounds.Bounds | null,
|
|
30
|
+
type: ValueType,
|
|
31
|
+
number: number,
|
|
32
|
+
reverse: boolean,
|
|
33
|
+
) => OperationReturn;
|
|
34
|
+
|
|
35
|
+
type OperationReturn = [bounds.Bounds | null, number];
|
|
36
|
+
|
|
37
|
+
interface TypedOperation extends Operation {
|
|
38
|
+
type: "translate" | "magnify" | "scale" | "invert" | "clamp" | "re-bound";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const curriedTranslate =
|
|
42
|
+
(translate: number): Operation =>
|
|
43
|
+
(currScale, type, v, reverse) => {
|
|
44
|
+
if (type === "dimension") return [currScale, v];
|
|
45
|
+
return [currScale, reverse ? v - translate : v + translate];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const curriedMagnify =
|
|
49
|
+
(magnify: number): Operation =>
|
|
50
|
+
(currScale, _type, v, reverse) => [currScale, reverse ? v / magnify : v * magnify];
|
|
51
|
+
|
|
52
|
+
const curriedScale =
|
|
53
|
+
(bound: bounds.Bounds): Operation =>
|
|
54
|
+
(currScale, type, v) => {
|
|
55
|
+
if (currScale === null) return [bound, v];
|
|
56
|
+
const { lower: prevLower, upper: prevUpper } = currScale;
|
|
57
|
+
const { lower: nextLower, upper: nextUpper } = bound;
|
|
58
|
+
const prevRange = prevUpper - prevLower;
|
|
59
|
+
const nextRange = nextUpper - nextLower;
|
|
60
|
+
if (type === "dimension") return [bound, v * (nextRange / prevRange)];
|
|
61
|
+
const nextV = (v - prevLower) * (nextRange / prevRange) + nextLower;
|
|
62
|
+
return [bound, nextV];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const curriedReBound =
|
|
66
|
+
(bound: bounds.Bounds): Operation =>
|
|
67
|
+
(currScale, type, v) => [bound, v];
|
|
68
|
+
|
|
69
|
+
const curriedInvert = (): Operation => (currScale, type, v) => {
|
|
70
|
+
if (currScale === null) throw new Error("cannot invert without bounds");
|
|
71
|
+
if (type === "dimension") return [currScale, v];
|
|
72
|
+
const { lower, upper } = currScale;
|
|
73
|
+
return [currScale, upper - (v - lower)];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const curriedClamp =
|
|
77
|
+
(bound: bounds.Bounds): Operation =>
|
|
78
|
+
(currScale, _, v) => {
|
|
79
|
+
const { lower, upper } = bound;
|
|
80
|
+
v = clamp(v, lower, upper);
|
|
81
|
+
return [currScale, v];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export class Scale {
|
|
85
|
+
ops: TypedOperation[] = [];
|
|
86
|
+
currBounds: bounds.Bounds | null = null;
|
|
87
|
+
currType: ValueType | null = null;
|
|
88
|
+
private reversed = false;
|
|
89
|
+
|
|
90
|
+
constructor() {
|
|
91
|
+
this.ops = [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static translate(value: number): Scale {
|
|
95
|
+
return new Scale().translate(value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static magnify(value: number): Scale {
|
|
99
|
+
return new Scale().magnify(value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static scale(lowerOrBound: number | bounds.Bounds, upper?: number): Scale {
|
|
103
|
+
return new Scale().scale(lowerOrBound, upper);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
translate(value: number): Scale {
|
|
107
|
+
const next = this.new();
|
|
108
|
+
const f = curriedTranslate(value) as TypedOperation;
|
|
109
|
+
f.type = "translate";
|
|
110
|
+
next.ops.push(f);
|
|
111
|
+
return next;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
magnify(value: number): Scale {
|
|
115
|
+
const next = this.new();
|
|
116
|
+
const f = curriedMagnify(value) as TypedOperation;
|
|
117
|
+
f.type = "magnify";
|
|
118
|
+
next.ops.push(f);
|
|
119
|
+
return next;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
scale(lowerOrBound: number | bounds.Bounds, upper?: number): Scale {
|
|
123
|
+
const b = bounds.construct(lowerOrBound, upper);
|
|
124
|
+
const next = this.new();
|
|
125
|
+
const f = curriedScale(b) as TypedOperation;
|
|
126
|
+
f.type = "scale";
|
|
127
|
+
next.ops.push(f);
|
|
128
|
+
return next;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
clamp(lowerOrBound: number | bounds.Bounds, upper?: number): Scale {
|
|
132
|
+
const b = bounds.construct(lowerOrBound, upper);
|
|
133
|
+
const next = this.new();
|
|
134
|
+
const f = curriedClamp(b) as TypedOperation;
|
|
135
|
+
f.type = "clamp";
|
|
136
|
+
next.ops.push(f);
|
|
137
|
+
return next;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
reBound(lowerOrBound: number | bounds.Bounds, upper?: number): Scale {
|
|
141
|
+
const b = bounds.construct(lowerOrBound, upper);
|
|
142
|
+
const next = this.new();
|
|
143
|
+
const f = curriedReBound(b) as TypedOperation;
|
|
144
|
+
f.type = "re-bound";
|
|
145
|
+
next.ops.push(f);
|
|
146
|
+
return next;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
invert(): Scale {
|
|
150
|
+
const f = curriedInvert() as TypedOperation;
|
|
151
|
+
f.type = "invert";
|
|
152
|
+
const next = this.new();
|
|
153
|
+
next.ops.push(f);
|
|
154
|
+
return next;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
pos(v: number): number {
|
|
158
|
+
return this.exec("position", v);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
dim(v: number): number {
|
|
162
|
+
return this.exec("dimension", v);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private new(): Scale {
|
|
166
|
+
const scale = new Scale();
|
|
167
|
+
scale.ops = this.ops.slice();
|
|
168
|
+
scale.reversed = this.reversed;
|
|
169
|
+
return scale;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private exec(vt: ValueType, v: number): number {
|
|
173
|
+
this.currBounds = null;
|
|
174
|
+
return this.ops.reduce<OperationReturn>(
|
|
175
|
+
([b, v]: OperationReturn, op: Operation): OperationReturn =>
|
|
176
|
+
op(b, vt, v, this.reversed),
|
|
177
|
+
[null, v],
|
|
178
|
+
)[1];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
reverse(): Scale {
|
|
182
|
+
const scale = this.new();
|
|
183
|
+
scale.ops.reverse();
|
|
184
|
+
// Switch the order of the operations to place scale operation 'BEFORE' the subsequent
|
|
185
|
+
// non - scale operations for example, if we have a reversed [scale A, scale B,
|
|
186
|
+
// translate A, magnify, translate B, scale C] we want to reverse it to [scale C,
|
|
187
|
+
// scale B, translate B, magnify, translate A, scale A]
|
|
188
|
+
const swaps: Array<[number, number]> = [];
|
|
189
|
+
scale.ops.forEach((op, i) => {
|
|
190
|
+
if (op.type === "scale" || swaps.some(([low, high]) => i >= low && i <= high))
|
|
191
|
+
return;
|
|
192
|
+
const nextScale = scale.ops.findIndex((op, j) => op.type === "scale" && j > i);
|
|
193
|
+
if (nextScale === -1) return;
|
|
194
|
+
swaps.push([i, nextScale]);
|
|
195
|
+
});
|
|
196
|
+
swaps.forEach(([low, high]) => {
|
|
197
|
+
const s = scale.ops.slice(low, high);
|
|
198
|
+
s.unshift(scale.ops[high]);
|
|
199
|
+
scale.ops.splice(low, high - low + 1, ...s);
|
|
200
|
+
});
|
|
201
|
+
scale.reversed = !scale.reversed;
|
|
202
|
+
return scale;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static readonly IDENTITY = new Scale();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const xyScaleToTransform = (scale: XY): XYTransformT => ({
|
|
209
|
+
scale: {
|
|
210
|
+
x: scale.x.dim(1),
|
|
211
|
+
y: scale.y.dim(1),
|
|
212
|
+
},
|
|
213
|
+
offset: {
|
|
214
|
+
x: scale.x.pos(0),
|
|
215
|
+
y: scale.y.pos(0),
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export class XY {
|
|
220
|
+
x: Scale;
|
|
221
|
+
y: Scale;
|
|
222
|
+
currRoot: location.CornerXY | null;
|
|
223
|
+
|
|
224
|
+
constructor(
|
|
225
|
+
x: Scale = new Scale(),
|
|
226
|
+
y: Scale = new Scale(),
|
|
227
|
+
root: location.CornerXY | null = null,
|
|
228
|
+
) {
|
|
229
|
+
this.x = x;
|
|
230
|
+
this.y = y;
|
|
231
|
+
this.currRoot = root;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static translate(xy: number | xy.XY, y?: number): XY {
|
|
235
|
+
return new XY().translate(xy, y);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static translateX(x: number): XY {
|
|
239
|
+
return new XY().translateX(x);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static translateY(y: number): XY {
|
|
243
|
+
return new XY().translateY(y);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
static clamp(box: Box): XY {
|
|
247
|
+
return new XY().clamp(box);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
static magnify(xy: xy.XY): XY {
|
|
251
|
+
return new XY().magnify(xy);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
static scale(box: dims.Dimensions | Box): XY {
|
|
255
|
+
return new XY().scale(box);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
static reBound(box: Box): XY {
|
|
259
|
+
return new XY().reBound(box);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
translate(cp: number | xy.Crude, y?: number): XY {
|
|
263
|
+
const _xy = xy.construct(cp, y);
|
|
264
|
+
const next = this.copy();
|
|
265
|
+
next.x = this.x.translate(_xy.x);
|
|
266
|
+
next.y = this.y.translate(_xy.y);
|
|
267
|
+
return next;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
translateX(x: number): XY {
|
|
271
|
+
const next = this.copy();
|
|
272
|
+
next.x = this.x.translate(x);
|
|
273
|
+
return next;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
translateY(y: number): XY {
|
|
277
|
+
const next = this.copy();
|
|
278
|
+
next.y = this.y.translate(y);
|
|
279
|
+
return next;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
magnify(xy: xy.XY): XY {
|
|
283
|
+
const next = this.copy();
|
|
284
|
+
next.x = this.x.magnify(xy.x);
|
|
285
|
+
next.y = this.y.magnify(xy.y);
|
|
286
|
+
return next;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
scale(b: Box | dims.Dimensions): XY {
|
|
290
|
+
const next = this.copy();
|
|
291
|
+
if (isBox(b)) {
|
|
292
|
+
const prevRoot = this.currRoot;
|
|
293
|
+
next.currRoot = b.root;
|
|
294
|
+
if (prevRoot != null && !location.xyEquals(prevRoot, b.root)) {
|
|
295
|
+
if (prevRoot.x !== b.root.x) next.x = next.x.invert();
|
|
296
|
+
if (prevRoot.y !== b.root.y) next.y = next.y.invert();
|
|
297
|
+
}
|
|
298
|
+
next.x = next.x.scale(box.xBounds(b));
|
|
299
|
+
next.y = next.y.scale(box.yBounds(b));
|
|
300
|
+
return next;
|
|
301
|
+
}
|
|
302
|
+
next.x = next.x.scale(b.width);
|
|
303
|
+
next.y = next.y.scale(b.height);
|
|
304
|
+
return next;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
reBound(b: Box): XY {
|
|
308
|
+
const next = this.copy();
|
|
309
|
+
next.x = this.x.reBound(box.xBounds(b));
|
|
310
|
+
next.y = this.y.reBound(box.yBounds(b));
|
|
311
|
+
return next;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
clamp(b: Box): XY {
|
|
315
|
+
const next = this.copy();
|
|
316
|
+
next.x = this.x.clamp(box.xBounds(b));
|
|
317
|
+
next.y = this.y.clamp(box.yBounds(b));
|
|
318
|
+
return next;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
copy(): XY {
|
|
322
|
+
const n = new XY();
|
|
323
|
+
n.currRoot = this.currRoot;
|
|
324
|
+
n.x = this.x;
|
|
325
|
+
n.y = this.y;
|
|
326
|
+
return n;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
reverse(): XY {
|
|
330
|
+
const next = this.copy();
|
|
331
|
+
next.x = this.x.reverse();
|
|
332
|
+
next.y = this.y.reverse();
|
|
333
|
+
return next;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
pos(xy: xy.XY): xy.XY {
|
|
337
|
+
return { x: this.x.pos(xy.x), y: this.y.pos(xy.y) };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
box(b: Box): Box {
|
|
341
|
+
return box.construct(
|
|
342
|
+
this.pos(b.one),
|
|
343
|
+
this.pos(b.two),
|
|
344
|
+
0,
|
|
345
|
+
0,
|
|
346
|
+
this.currRoot ?? b.root,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
static readonly IDENTITY = new XY();
|
|
351
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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 {
|
|
11
|
+
ALIGNMENTS,
|
|
12
|
+
alignment,
|
|
13
|
+
type Alignment,
|
|
14
|
+
ORDERS,
|
|
15
|
+
order,
|
|
16
|
+
type Order,
|
|
17
|
+
} from "@/spatial/base";
|
|
@@ -0,0 +1,68 @@
|
|
|
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, test } from "vitest";
|
|
11
|
+
|
|
12
|
+
import * as xy from "@/spatial/xy";
|
|
13
|
+
|
|
14
|
+
describe("XY", () => {
|
|
15
|
+
describe("construction", () => {
|
|
16
|
+
[
|
|
17
|
+
["from object", { x: 1, y: 2 }],
|
|
18
|
+
["from couple", [1, 2]],
|
|
19
|
+
["from dimensions", { width: 1, height: 2 }],
|
|
20
|
+
["from signed dimensions", { signedWidth: 1, signedHeight: 2 }],
|
|
21
|
+
["from client dimensions", { clientX: 1, clientY: 2 }],
|
|
22
|
+
].forEach(([name, arg]) => {
|
|
23
|
+
test(name as string, () => {
|
|
24
|
+
const p = xy.construct(arg as xy.Crude);
|
|
25
|
+
expect(p.x).toEqual(1);
|
|
26
|
+
expect(p.y).toEqual(2);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
test("translateX", () => {
|
|
32
|
+
let p = xy.construct([1, 2]);
|
|
33
|
+
p = xy.translateX(p, 5);
|
|
34
|
+
expect(p.x).toEqual(6);
|
|
35
|
+
expect(p.y).toEqual(2);
|
|
36
|
+
});
|
|
37
|
+
test("translateY", () => {
|
|
38
|
+
let p = xy.construct([1, 2]);
|
|
39
|
+
p = xy.translateY(p, 5);
|
|
40
|
+
expect(p.x).toEqual(1);
|
|
41
|
+
expect(p.y).toEqual(7);
|
|
42
|
+
});
|
|
43
|
+
test("translate", () => {
|
|
44
|
+
let p = xy.construct([1, 2]);
|
|
45
|
+
p = xy.translate(p, [5, 5]);
|
|
46
|
+
expect(p.x).toEqual(6);
|
|
47
|
+
expect(p.y).toEqual(7);
|
|
48
|
+
});
|
|
49
|
+
describe("equals", () => {
|
|
50
|
+
const TESTS: Array<[xy.Crude, xy.Crude, boolean]> = [
|
|
51
|
+
[[1, 1], { x: 1, y: 1 }, true],
|
|
52
|
+
[[1, 1], [1, 1], true],
|
|
53
|
+
[{ x: 1, y: 12 }, { x: 1, y: 1 }, false],
|
|
54
|
+
[{ x: 1, y: 12 }, { width: 1, height: 12 }, true],
|
|
55
|
+
[{ x: 1, y: 12 }, { width: 12, height: 1 }, false],
|
|
56
|
+
[{ x: 1, y: 12 }, { signedWidth: 1, signedHeight: 12 }, true],
|
|
57
|
+
];
|
|
58
|
+
TESTS.forEach(([one, two, expected], i) => {
|
|
59
|
+
test(`equals ${i}`, () => {
|
|
60
|
+
const p = xy.construct(one);
|
|
61
|
+
expect(xy.equals(p, two)).toEqual(expected);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
test("couple", () => {
|
|
66
|
+
const p = xy.construct([1, 2]);
|
|
67
|
+
expect(xy.couple(p)).toEqual([1, 2]);
|
|
68
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
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 { z } from "zod";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
clientXY,
|
|
14
|
+
dimensions,
|
|
15
|
+
numberCouple,
|
|
16
|
+
xy,
|
|
17
|
+
type NumberCouple,
|
|
18
|
+
type ClientXY,
|
|
19
|
+
type XY,
|
|
20
|
+
signedDimensions,
|
|
21
|
+
type Direction,
|
|
22
|
+
} from "@/spatial/base";
|
|
23
|
+
|
|
24
|
+
export { clientXY, xy, type ClientXY as Client, type XY };
|
|
25
|
+
|
|
26
|
+
/** A crude representation of a {@link XY} coordinate as a zod schema. */
|
|
27
|
+
export const crudeZ = z.union([
|
|
28
|
+
xy,
|
|
29
|
+
numberCouple,
|
|
30
|
+
dimensions,
|
|
31
|
+
signedDimensions,
|
|
32
|
+
clientXY,
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/** A crude representation of a {@link XY} coordinate. */
|
|
36
|
+
export type Crude = z.infer<typeof crudeZ>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @constructs XY
|
|
40
|
+
* @param x - A crude representation of the XY coordinate as a number, number couple,
|
|
41
|
+
* dimensions, signed dimensions, or client XY.
|
|
42
|
+
* @param y - If x is a number, the y coordinate. If x is a number and this argument is
|
|
43
|
+
* not given, the y coordinate is assumed to be the same as the x coordinate.
|
|
44
|
+
*/
|
|
45
|
+
export const construct = (x: Crude | number, y?: number): XY => {
|
|
46
|
+
if (typeof x === "number") return { x, y: y ?? x };
|
|
47
|
+
if (Array.isArray(x)) return { x: x[0], y: x[1] };
|
|
48
|
+
if ("signedWidth" in x) return { x: x.signedWidth, y: x.signedHeight };
|
|
49
|
+
if ("clientX" in x) return { x: x.clientX, y: x.clientY };
|
|
50
|
+
if ("width" in x) return { x: x.width, y: x.height };
|
|
51
|
+
return { ...x };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** An x and y coordinate of zero */
|
|
55
|
+
export const ZERO = { x: 0, y: 0 };
|
|
56
|
+
|
|
57
|
+
/** An x and y coordinate of one */
|
|
58
|
+
export const ONE = { x: 1, y: 1 };
|
|
59
|
+
|
|
60
|
+
/** An x and y coordinate of infinity */
|
|
61
|
+
export const INFINITY = { x: Infinity, y: Infinity };
|
|
62
|
+
|
|
63
|
+
/** An x and y coordinate of NaN */
|
|
64
|
+
export const NAN = { x: NaN, y: NaN };
|
|
65
|
+
|
|
66
|
+
/** @returns true if the two XY coordinates are semntically equal. */
|
|
67
|
+
export const equals = (a: Crude, b: Crude): boolean => {
|
|
68
|
+
const a_ = construct(a);
|
|
69
|
+
const b_ = construct(b);
|
|
70
|
+
return a_.x === b_.x && a_.y === b_.y;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Is zero is true if the XY coordinate has a semantic x and y value of zero. */
|
|
74
|
+
export const isZero = (c: Crude): boolean => equals(c, ZERO);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @returns the given coordinate scaled by the given factors. If only one factor is given,
|
|
78
|
+
* the y factor is assumed to be the same as the x factor.
|
|
79
|
+
*/
|
|
80
|
+
export const scale = (c: Crude, x: number, y: number = x): XY => {
|
|
81
|
+
const p = construct(c);
|
|
82
|
+
return { x: p.x * x, y: p.y * y };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** @returns the given coordinate translated in the X direction by the given amount. */
|
|
86
|
+
export const translateX = (c: Crude, x: number): XY => {
|
|
87
|
+
const p = construct(c);
|
|
88
|
+
return { x: p.x + x, y: p.y };
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** @returns the given coordinate translated in the Y direction by the given amount. */
|
|
92
|
+
export const translateY = (c: Crude, y: number): XY => {
|
|
93
|
+
const p = construct(c);
|
|
94
|
+
return { x: p.x, y: p.y + y };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @returns the given coordinate translated by an arbitrary number of translation
|
|
99
|
+
* coordinates.
|
|
100
|
+
*/
|
|
101
|
+
export const translate = (a: Crude, b: Crude, ...cb: Crude[]): XY =>
|
|
102
|
+
[a, b, ...cb].reduce((p: XY, c) => {
|
|
103
|
+
const xy = construct(c);
|
|
104
|
+
return { x: p.x + xy.x, y: p.y + xy.y };
|
|
105
|
+
}, ZERO);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @returns the given coordinate the given direction set to the given value.
|
|
109
|
+
* @example set({ x: 1, y: 2 }, "x", 3) // { x: 3, y: 2 }
|
|
110
|
+
*/
|
|
111
|
+
export const set = (c: Crude, direction: Direction, value: number): XY => {
|
|
112
|
+
const xy = construct(c);
|
|
113
|
+
if (direction === "x") return { x: value, y: xy.y };
|
|
114
|
+
return { x: xy.x, y: value };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** @returns the magnitude of the distance between the two given coordinates. */
|
|
118
|
+
export const distance = (ca: Crude, cb: Crude): number => {
|
|
119
|
+
const a = construct(ca);
|
|
120
|
+
const b = construct(cb);
|
|
121
|
+
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** @returns the magnitude of the x distance between the two given coordinates. */
|
|
125
|
+
export const xDistance = (ca: Crude, cb: Crude): number => {
|
|
126
|
+
const a = construct(ca);
|
|
127
|
+
const b = construct(cb);
|
|
128
|
+
return Math.abs(a.x - b.x);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** @returns the magnitude of the y distance between the two given coordinates. */
|
|
132
|
+
export const yDistance = (ca: Crude, cb: Crude): number => {
|
|
133
|
+
const a = construct(ca);
|
|
134
|
+
const b = construct(cb);
|
|
135
|
+
return Math.abs(a.y - b.y);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @returns the translation that would need to be applied to move the first coordinate
|
|
140
|
+
* to the second coordinate.
|
|
141
|
+
*/
|
|
142
|
+
export const translation = (ca: Crude, cb: Crude): XY => {
|
|
143
|
+
const a = construct(ca);
|
|
144
|
+
const b = construct(cb);
|
|
145
|
+
return { x: b.x - a.x, y: b.y - a.y };
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** @returns true if both the x and y coordinates of the given coordinate are NaN. */
|
|
149
|
+
export const isNan = (a: Crude): boolean => {
|
|
150
|
+
const xy = construct(a);
|
|
151
|
+
return Number.isNaN(xy.x) || Number.isNaN(xy.y);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** @returns the coordinate represented as a couple of the form [x, y]. */
|
|
155
|
+
export const couple = (a: Crude): NumberCouple => {
|
|
156
|
+
const xy = construct(a);
|
|
157
|
+
return [xy.x, xy.y];
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** @returns the coordinate represented as css properties in the form { left, top }. */
|
|
161
|
+
export const css = (a: Crude): { left: number; top: number } => {
|
|
162
|
+
const xy = construct(a);
|
|
163
|
+
return { left: xy.x, top: xy.y };
|
|
164
|
+
};
|