-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathhelpers.ts
150 lines (126 loc) · 4.21 KB
/
helpers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import type { InteractiveWaypoint, Waypoint } from '@osrd-project/ui-manchette';
import { clamp } from 'lodash';
import {
BASE_WAYPOINT_HEIGHT,
MAX_ZOOM_MS_PER_PX,
MAX_ZOOM_X,
MIN_ZOOM_MS_PER_PX,
MIN_ZOOM_X,
} from './consts';
import { calcTotalDistance, getHeightWithoutLastWaypoint } from './utils';
type WaypointsOptions = { isProportional: boolean; yZoom: number; height: number };
export const filterVisibleElements = (
elements: Waypoint[],
totalDistance: number,
heightWithoutFinalWaypoint: number,
minSpace: number
): Waypoint[] => {
const getPosition = (waypoint: Waypoint) =>
(waypoint.position / totalDistance) * heightWithoutFinalWaypoint;
const firstElement = elements.at(0);
const lastElement = elements.at(-1);
if (!firstElement || !lastElement) return elements;
const sortedElements = [...elements].sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0));
const displayedElements: Waypoint[] = [firstElement, lastElement];
for (const element of sortedElements) {
const hasSpace = !displayedElements.some(
(displayed) => Math.abs(getPosition(element) - getPosition(displayed)) < minSpace
);
if (hasSpace) {
displayedElements.push(element);
}
}
return displayedElements.sort((a, b) => a.position - b.position);
};
export const computeWaypointsToDisplay = (
waypoints: Waypoint[],
{ height, isProportional, yZoom }: WaypointsOptions
): InteractiveWaypoint[] => {
if (waypoints.length < 2) return [];
const totalDistance = calcTotalDistance(waypoints);
const heightWithoutFinalWaypoint = getHeightWithoutLastWaypoint(height);
// display all waypoints in linear mode
if (!isProportional) {
return waypoints.map((waypoint, index) => {
const nextWaypoint = waypoints.at(index + 1);
return {
...waypoint,
styles: { height: `${BASE_WAYPOINT_HEIGHT * (nextWaypoint ? yZoom : 1)}px` },
};
});
}
// in proportional mode, hide some waypoints to avoid collisions
const minSpace = BASE_WAYPOINT_HEIGHT / yZoom;
const filteredWaypoints = filterVisibleElements(
waypoints,
totalDistance,
heightWithoutFinalWaypoint,
minSpace
);
return filteredWaypoints.map((waypoint, index) => {
const nextWaypoint = filteredWaypoints.at(index + 1);
return {
...waypoint,
styles: {
height: !nextWaypoint
? `${BASE_WAYPOINT_HEIGHT}px`
: `${
((nextWaypoint.position - waypoint.position) / totalDistance) *
heightWithoutFinalWaypoint *
yZoom
}px`,
},
};
});
};
export const getScales = (
waypoints: Waypoint[],
{ height, isProportional, yZoom }: WaypointsOptions
) => {
if (waypoints.length < 2) return [];
if (!isProportional) {
return waypoints.slice(0, -1).map((from, index) => {
const to = waypoints[index + 1];
return {
from: from.position,
to: to.position,
size: BASE_WAYPOINT_HEIGHT * yZoom,
};
});
}
const from = waypoints.at(0)!.position;
const to = waypoints.at(-1)!.position;
const totalDistance = calcTotalDistance(waypoints);
const heightWithoutFinalWaypoint = getHeightWithoutLastWaypoint(height);
const scaleCoeff = isProportional
? { coefficient: totalDistance / heightWithoutFinalWaypoint / yZoom }
: { size: BASE_WAYPOINT_HEIGHT * (waypoints.length - 1) * yZoom };
return [
{
from,
to,
...scaleCoeff,
},
];
};
export const zoomValueToTimeScale = (slider: number) =>
MIN_ZOOM_MS_PER_PX * Math.pow(MAX_ZOOM_MS_PER_PX / MIN_ZOOM_MS_PER_PX, slider / 100);
export const timeScaleToZoomValue = (timeScale: number) =>
(100 * Math.log(timeScale / MIN_ZOOM_MS_PER_PX)) /
Math.log(MAX_ZOOM_MS_PER_PX / MIN_ZOOM_MS_PER_PX);
/** Zoom on X axis and center on the mouse position */
export const zoomX = (
currentZoom: number,
currentOffset: number,
newZoom: number,
position: number
) => {
const boundedZoom = clamp(newZoom, MIN_ZOOM_X, MAX_ZOOM_X);
const oldTimeScale = zoomValueToTimeScale(currentZoom);
const newTimeScale = zoomValueToTimeScale(boundedZoom);
const newOffset = position - ((position - currentOffset) * oldTimeScale) / newTimeScale;
return {
xZoom: boundedZoom,
xOffset: newOffset,
};
};