Line data Source code
1 : import type { ProfileSceneEdge, ProfileSceneOperator } from './profile_scene.js';
2 :
3 : export type ProfileNavigationDirection = 'left' | 'right' | 'up' | 'down';
4 :
5 : /// Find the operator in a direction of an operator.
6 : /// The inputs of an operator are below it, or right of it if the plan grows left-to-right. The other two directions
7 : /// step through the operators of a level (a row, or a column if the plan grows left-to-right).
8 1 : export function findProfileOperatorInDirection(
9 1 : operators: readonly ProfileSceneOperator[],
10 1 : edges: readonly Pick<ProfileSceneEdge, 'childOperator' | 'parentOperator'>[],
11 1 : current: ProfileSceneOperator | null,
12 1 : direction: ProfileNavigationDirection,
13 1 : leftToRight = false,
14 1 : ): ProfileSceneOperator | null {
15 1 : if (operators.length === 0) return null;
16 1 : if (current == null) {
17 1 : const sign = direction === 'left' || direction === 'up' ? -1 : 1;
18 1 : const coordinate = direction === 'left' || direction === 'right' ? 'x' : 'y';
19 1 : return [...operators].sort((left, right) =>
20 1 : sign * (left.rect[coordinate] - right.rect[coordinate]) || left.id - right.id,
21 1 : )[0];
22 1 : }
23 :
24 1 : const toChild = leftToRight ? 'right' : 'down';
25 1 : const toParent = leftToRight ? 'left' : 'up';
26 1 : if (direction === toChild) {
27 1 : const child = edges.find(edge => edge.parentOperator === current.id)?.childOperator;
28 1 : return child == null ? null : operators.find(operator => operator.id === child) ?? null;
29 1 : }
30 1 : if (direction === toParent) {
31 1 : const parent = edges.find(edge => edge.childOperator === current.id)?.parentOperator;
32 1 : return parent == null ? null : operators.find(operator => operator.id === parent) ?? null;
33 1 : }
34 :
35 : // The operators of a level share their y, or the left edge of their column if the plan grows left-to-right
36 1 : const level = (operator: ProfileSceneOperator) => leftToRight ? operator.rect.x - operator.rect.width / 2 : operator.rect.y;
37 1 : const position = (operator: ProfileSceneOperator) => leftToRight ? operator.rect.y : operator.rect.x;
38 1 : const backwards = direction === 'left' || direction === 'up';
39 1 : const candidates = operators.flatMap(operator => {
40 1 : if (operator.id === current.id) return [];
41 1 : const delta = position(operator) - position(current);
42 1 : if (level(operator) !== level(current)) return [];
43 1 : if (backwards ? delta >= 0 : delta <= 0) return [];
44 1 : return [{ operator, distance: Math.abs(delta) }];
45 1 : });
46 1 : candidates.sort((left, right) =>
47 1 : left.distance - right.distance
48 0 : || left.operator.id - right.operator.id,
49 1 : );
50 1 : return candidates[0]?.operator ?? null;
51 1 : }
|