Skip to content

Commit bc880d0

Browse files
authored
Refactoring of data layer framework (#893)
1 parent 808d42b commit bc880d0

File tree

155 files changed

+6738
-5915
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+6738
-5915
lines changed

frontend/src/framework/WorkbenchSettings.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class WorkbenchSettings {
6666
};
6767
}
6868

69-
protected getSelectedColorPalette(type: ColorPaletteType): ColorPalette {
69+
getSelectedColorPalette(type: ColorPaletteType): ColorPalette {
7070
const colorPalette = this._colorPalettes[type].find((el) => el.getId() === this._selectedColorPalettes[type]);
7171
if (!colorPalette) {
7272
throw new Error("Could not find selected color palette");

frontend/src/modules/_shared/components/ColorScaleSelector/colorScaleSelector.tsx renamed to frontend/src/framework/components/ColorScaleSelector/colorScaleSelector.tsx

+207-179
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { ColorScaleSelector } from "./colorScaleSelector";
2+
export type { ColorScaleSelectorProps } from "./colorScaleSelector";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { ColorPalette } from "@lib/utils/ColorPalette";
2+
import { ColorScale, ColorScaleGradientType, ColorScaleType } from "@lib/utils/ColorScale";
3+
4+
export type ColorScalePreviewProps = {
5+
colorPalette: ColorPalette;
6+
gradientType: ColorScaleGradientType;
7+
discrete: boolean;
8+
steps: number;
9+
min: number;
10+
max: number;
11+
divMidPoint: number;
12+
id: string;
13+
};
14+
15+
export function ColorScalePreview(props: ColorScalePreviewProps): React.ReactNode {
16+
const colorScale = new ColorScale({
17+
colorPalette: props.colorPalette,
18+
type: props.discrete ? ColorScaleType.Discrete : ColorScaleType.Continuous,
19+
gradientType: props.gradientType,
20+
steps: props.steps,
21+
});
22+
23+
if (props.gradientType === ColorScaleGradientType.Diverging) {
24+
colorScale.setRangeAndMidPoint(props.min, props.max, props.divMidPoint);
25+
}
26+
27+
const colorScaleGradientId = makeGradientId(props.id, props.colorPalette);
28+
29+
return (
30+
<svg className="w-full h-5" version="1.1" xmlns="http://www.w3.org/2000/svg">
31+
<defs>
32+
<GradientDef id={props.id} colorScale={colorScale} />
33+
</defs>
34+
<rect height="1.25rem" width="100%" fill={`url(#${colorScaleGradientId})`} stroke="#555" />
35+
</svg>
36+
);
37+
}
38+
39+
type GradientDefProps = {
40+
id: string;
41+
colorScale: ColorScale;
42+
};
43+
44+
function GradientDef(props: GradientDefProps): React.ReactNode {
45+
const colorStops = props.colorScale.getColorStops();
46+
const gradientId = makeGradientId(props.id, props.colorScale.getColorPalette());
47+
48+
return (
49+
<linearGradient id={gradientId} x1="0" y1="0" x2="1" y2="0">
50+
{colorStops.map((colorStop, index) => (
51+
<stop key={index} offset={`${(colorStop.offset * 100).toFixed(2)}%`} stopColor={colorStop.color} />
52+
))}
53+
</linearGradient>
54+
);
55+
}
56+
57+
function makeGradientId(id: string, colorPalette: ColorPalette): string {
58+
return `${id}-color-scale-gradient-${colorPalette.getId()}`;
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { ColorScalePreview } from "./colorScalePreview";
2+
export type { ColorScalePreviewProps } from "./colorScalePreview";

frontend/src/lib/components/ColorSelect/colorSelect.tsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import { ColorTile } from "@lib/components/ColorTile";
55
import { Dropdown } from "@lib/components/Dropdown";
66
import { ExpandMore } from "@mui/icons-material";
77

8+
import { DenseIconButton } from "../DenseIconButton";
9+
810
export type ColorSelectProps = {
911
value: string;
1012
onChange: (value: string) => void;
1113
colors?: string[];
14+
dense?: boolean;
1215
};
1316

1417
export function ColorSelect(props: ColorSelectProps): JSX.Element {
@@ -41,9 +44,15 @@ export function ColorSelect(props: ColorSelectProps): JSX.Element {
4144
if (props.colors === undefined) {
4245
return (
4346
<>
44-
<Button endIcon={<ExpandMore fontSize="inherit" />} onClick={handleButtonClick}>
45-
<ColorTile color={selectedColor} />
46-
</Button>
47+
{props.dense ? (
48+
<DenseIconButton onClick={handleButtonClick}>
49+
<ColorTile color={selectedColor} />
50+
</DenseIconButton>
51+
) : (
52+
<Button endIcon={<ExpandMore fontSize="inherit" />} onClick={handleButtonClick}>
53+
<ColorTile color={selectedColor} />
54+
</Button>
55+
)}
4756
<input
4857
ref={inputRef}
4958
type="color"

frontend/src/lib/components/DenseIconButton/denseIconButton.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function DenseIconButton(props: DenseIconButtonProps): React.ReactNode {
3434
return (
3535
<button
3636
className={resolveClassNames("p-1 text-sm rounded-sm flex gap-1 items-center", {
37-
[colorScheme + "text-gray-600 focus:outline focus:outline-1 hover:text-gray-900"]: !props.disabled,
37+
[colorScheme + "text-gray-600 focus:outline hover:text-gray-900"]: !props.disabled,
3838
"text-gray-300": props.disabled,
3939
})}
4040
disabled={props.disabled}

frontend/src/lib/components/SortableList/sortableListGroup.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type SortableListGroupProps = {
2020
startAdornment?: React.ReactNode;
2121
endAdornment?: React.ReactNode;
2222
headerStyle?: React.CSSProperties;
23+
content?: React.ReactNode;
2324
contentStyle?: React.CSSProperties;
2425
contentWhenEmpty?: React.ReactNode;
2526
children?: React.ReactElement<SortableListItemProps>[];
@@ -33,6 +34,7 @@ export type SortableListGroupProps = {
3334
* @param {boolean} props.expanded Whether the group should be expanded.
3435
* @param {React.ReactNode} props.startAdornment Start adornment to display to the left of the title.
3536
* @param {React.ReactNode} props.endAdornment End adornment to display to the right of the title.
37+
* @param {React.ReactNode} props.content Optional content to display before actual children.
3638
* @param {React.ReactNode} props.contentWhenEmpty Content to display when the group is empty.
3739
* @param {React.ReactNode} props.children Child components to display as the content of the list item.
3840
*
@@ -117,6 +119,7 @@ export function SortableListGroup(props: SortableListGroupProps): React.ReactNod
117119
)}
118120
style={props.contentStyle}
119121
>
122+
{props.content}
120123
{hasContent ? props.children : props.contentWhenEmpty}
121124
</div>
122125
</div>

frontend/src/lib/utils/bbox.ts

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import * as vec3 from "./vec3";
2+
3+
/**
4+
* A bounding box.
5+
*/
6+
export type BBox = {
7+
min: vec3.Vec3;
8+
max: vec3.Vec3;
9+
};
10+
11+
/**
12+
* Creates a new bounding box.
13+
* @param min The minimum point of the bounding box.
14+
* @param max The maximum point of the bounding box.
15+
* @returns A new bounding box.
16+
*/
17+
export function create(min: vec3.Vec3, max: vec3.Vec3): BBox {
18+
return { min, max };
19+
}
20+
21+
/**
22+
* Returns true if the bounding box contains the given point.
23+
* @param box The bounding box.
24+
* @param point The point.
25+
* @returns True if the bounding box contains the point.
26+
*/
27+
export function containsPoint(box: BBox, point: vec3.Vec3): boolean {
28+
return (
29+
point.x >= box.min.x &&
30+
point.x <= box.max.x &&
31+
point.y >= box.min.y &&
32+
point.y <= box.max.y &&
33+
point.z >= box.min.z &&
34+
point.z <= box.max.z
35+
);
36+
}
37+
38+
/**
39+
* Returns true if the two bounding boxes intersect.
40+
* @param box1 The first bounding box.
41+
* @param box2 The second bounding box.
42+
* @returns True if the two bounding boxes intersect.
43+
*/
44+
export function intersects(box1: BBox, box2: BBox): boolean {
45+
return (
46+
box1.min.x <= box2.max.x &&
47+
box1.max.x >= box2.min.x &&
48+
box1.min.y <= box2.max.y &&
49+
box1.max.y >= box2.min.y &&
50+
box1.min.z <= box2.max.z &&
51+
box1.max.z >= box2.min.z
52+
);
53+
}
54+
55+
/**
56+
* Returns true if outerBox contains innerBox.
57+
* @param outerBox The outer bounding box.
58+
* @param innerBox The inner bounding box.
59+
* @returns True if outerBox contains innerBox.
60+
*/
61+
export function outerBoxcontainsInnerBox(outerBox: BBox, innerBox: BBox): boolean {
62+
return (
63+
outerBox.min.x <= innerBox.min.x &&
64+
outerBox.min.y <= innerBox.min.y &&
65+
outerBox.min.z <= innerBox.min.z &&
66+
outerBox.max.x >= innerBox.max.x &&
67+
outerBox.max.y >= innerBox.max.y &&
68+
outerBox.max.z >= innerBox.max.z
69+
);
70+
}
71+
72+
/**
73+
* Converts a bounding box to an array of numbers.
74+
* The array contains the following numbers in the following order:
75+
* [min.x, min.y, min.z, max.x, max.y, max.z]
76+
*
77+
* @param box The bounding box.
78+
* @returns An array of numbers.
79+
*/
80+
export function toNumArray(box: BBox): [number, number, number, number, number, number] {
81+
return [box.min.x, box.min.y, box.min.z, box.max.x, box.max.y, box.max.z];
82+
}
83+
84+
/**
85+
* Converts an array of numbers to a bounding box.
86+
* The array should contain the following numbers in the following order:
87+
* [min.x, min.y, min.z, max.x, max.y, max.z]
88+
*
89+
* @param array An array of numbers.
90+
* @returns A new bounding box.
91+
*/
92+
export function fromNumArray(array: [number, number, number, number, number, number]): BBox {
93+
return create(vec3.fromArray(array.slice(0, 3)), vec3.fromArray(array.slice(3, 6)));
94+
}
95+
96+
/**
97+
* Clones the given bounding box.
98+
*
99+
* @param box The bounding box to clone.
100+
* @returns A new bounding box.
101+
*/
102+
export function clone(box: BBox): BBox {
103+
return create(vec3.clone(box.min), vec3.clone(box.max));
104+
}
105+
106+
/**
107+
* Combines the two bounding boxes into a new bounding box that contains both.
108+
*
109+
* @param box1 The first bounding box.
110+
* @param box2 The second bounding box.
111+
*
112+
* @returns A new bounding box that holds both bounding boxes.
113+
*/
114+
export function combine(box1: BBox, box2: BBox): BBox {
115+
return create(
116+
vec3.create(
117+
Math.min(box1.min.x, box2.min.x),
118+
Math.min(box1.min.y, box2.min.y),
119+
Math.min(box1.min.z, box2.min.z)
120+
),
121+
vec3.create(
122+
Math.max(box1.max.x, box2.max.x),
123+
Math.max(box1.max.y, box2.max.y),
124+
Math.max(box1.max.z, box2.max.z)
125+
)
126+
);
127+
}

frontend/src/lib/utils/geometry.ts

+8
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,11 @@ export function isDOMRectContained(inner: DOMRect, outer: DOMRect): boolean {
129129
export function domRectsAreEqual(rect1: DOMRect, rect2: DOMRect): boolean {
130130
return rect1.x === rect2.x && rect1.y === rect2.y && rect1.width === rect2.width && rect1.height === rect2.height;
131131
}
132+
133+
export function degreesToRadians(degrees: number): number {
134+
return (degrees * Math.PI) / 180;
135+
}
136+
137+
export function radiansToDegrees(radians: number): number {
138+
return (radians * 180) / Math.PI;
139+
}

frontend/src/lib/utils/vec3.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* A 3D vector.
3+
*/
4+
export type Vec3 = {
5+
x: number;
6+
y: number;
7+
z: number;
8+
};
9+
10+
/**
11+
* Creates a new 3D vector from the given components.
12+
*
13+
* @param x The x component.
14+
* @param y The y component.
15+
* @param z The z component.
16+
* @returns A new 3D vector.
17+
*/
18+
export function create(x: number, y: number, z: number): Vec3 {
19+
return { x, y, z };
20+
}
21+
22+
/**
23+
* Creates a new 3D vector from the given array.
24+
*
25+
* @param array The array containing the components: [x, y, z].
26+
* @returns A new 3D vector.
27+
*/
28+
export function fromArray(array: ArrayLike<number> | [number, number, number]): Vec3 {
29+
if (array.length !== 3) {
30+
throw new Error("The array must contain exactly three elements.");
31+
}
32+
return { x: array[0], y: array[1], z: array[2] };
33+
}
34+
35+
/**
36+
* Converts a 3D vector to an array of numbers.
37+
*
38+
* @param vector A 3D vector.
39+
* @returns A new array of numbers containing the components of the vector: [x, y, z].
40+
*/
41+
export function toArray(vector: Vec3): [number, number, number] {
42+
return [vector.x, vector.y, vector.z];
43+
}
44+
45+
/**
46+
* Clones the given vector.
47+
*
48+
* @param vector The vector to clone.
49+
* @returns A new vector that is a clone of the given vector.
50+
*/
51+
export function clone(vector: Vec3): Vec3 {
52+
return { x: vector.x, y: vector.y, z: vector.z };
53+
}

frontend/src/main.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ client.setConfig({
3232

3333
// --------------------------------------------------------------------
3434

35+
/*
36+
Render the application.
37+
*/
38+
3539
const container = document.getElementById("root");
3640

3741
if (!container) {
@@ -49,5 +53,5 @@ root.render(
4953
</CustomQueryClientProvider>
5054
</AuthProvider>
5155
</GlobalErrorBoundary>
52-
</React.StrictMode>,
56+
</React.StrictMode>
5357
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Setting } from "@modules/_shared/LayerFramework/settings/settingsDefinitions";
2+
import type {
3+
Annotation,
4+
FactoryFunctionArgs,
5+
} from "@modules/_shared/LayerFramework/visualization/VisualizationFactory";
6+
import { ColorScaleWithName } from "@modules/_shared/utils/ColorScaleWithName";
7+
8+
export function makeColorScaleAnnotation({
9+
getSetting,
10+
id,
11+
name,
12+
}: FactoryFunctionArgs<[Setting.COLOR_SCALE], any>): Annotation[] {
13+
const colorScale = getSetting(Setting.COLOR_SCALE)?.colorScale;
14+
15+
if (!colorScale) {
16+
return [];
17+
}
18+
19+
return [{ id, colorScale: ColorScaleWithName.fromColorScale(colorScale, name) }];
20+
}

0 commit comments

Comments
 (0)