Skip to content

Commit c3e4f06

Browse files
committed
ui-manchette-with-spacetime-chart: story: zoom on manchette + spacetime
Signed-off-by: Valentin Chanas <[email protected]>
1 parent fd7c036 commit c3e4f06

File tree

9 files changed

+485
-55
lines changed

9 files changed

+485
-55
lines changed

ui-charts/src/manchette/useManchetteWithSpaceTimeChart/__tests__/helpers.spec.ts

+41-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { describe, it, test, expect } from 'vitest';
22

3-
import { BASE_WAYPOINT_HEIGHT } from '../consts';
4-
import { computeWaypointsToDisplay, getScales } from '../helpers';
3+
import { BASE_WAYPOINT_HEIGHT, MAX_ZOOM_Y, MIN_ZOOM_Y } from '../consts';
4+
import {
5+
computeWaypointsToDisplay,
6+
getScales,
7+
getExtremaScales,
8+
spaceScaleToZoomValue,
9+
zoomValueToSpaceScale,
10+
} from '../helpers';
511

612
// Assuming these types from your code
713

@@ -105,3 +111,35 @@ describe('getScales', () => {
105111
expect(result[0]).not.toHaveProperty('coefficient');
106112
});
107113
});
114+
115+
describe('space scale functions', () => {
116+
const pathLength = 168056000; // mm
117+
const manchettePxHeight = 528;
118+
const heightBetweenFirstLastWaypoints = 489;
119+
120+
const { minZoomMillimetrePerPx, maxZoomMillimetrePerPx } = getExtremaScales(
121+
manchettePxHeight,
122+
heightBetweenFirstLastWaypoints,
123+
pathLength
124+
);
125+
expect(minZoomMillimetrePerPx).toBeCloseTo(343672.801);
126+
expect(maxZoomMillimetrePerPx).toBeCloseTo(946.97);
127+
128+
test('zoomValueToSpaceScale', () => {
129+
expect(
130+
zoomValueToSpaceScale(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, MIN_ZOOM_Y)
131+
).toBeCloseTo(343672.801);
132+
expect(
133+
zoomValueToSpaceScale(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, MAX_ZOOM_Y)
134+
).toBeCloseTo(946.97);
135+
});
136+
137+
test('spaceScaleToZoomValue', () => {
138+
expect(
139+
spaceScaleToZoomValue(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, 343672.801)
140+
).toBeCloseTo(MIN_ZOOM_Y);
141+
expect(
142+
spaceScaleToZoomValue(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, 946.97)
143+
).toBeCloseTo(MAX_ZOOM_Y);
144+
});
145+
});

ui-charts/src/manchette/useManchetteWithSpaceTimeChart/consts.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ export const BASE_WAYPOINT_HEIGHT = 32;
22
export const INITIAL_WAYPOINT_LIST_HEIGHT = 521;
33
export const INITIAL_SPACE_TIME_CHART_HEIGHT = INITIAL_WAYPOINT_LIST_HEIGHT + 40;
44

5-
export const MIN_ZOOM_MS_PER_PX = 600000;
5+
export const MIN_ZOOM_MS_PER_PX = 600_000;
66
export const MAX_ZOOM_MS_PER_PX = 625;
7-
export const DEFAULT_ZOOM_MS_PER_PX = 7500;
7+
export const DEFAULT_ZOOM_MS_PER_PX = 7_500;
88
export const MIN_ZOOM_X = 0;
99
export const MAX_ZOOM_X = 100;
1010

1111
export const MIN_ZOOM_Y = 1;
1212
export const MAX_ZOOM_Y = 10.5;
1313
export const ZOOM_Y_DELTA = 0.5;
14+
export const MAX_ZOOM_MANCHETTE_HEIGHT_MILLIMETER = 500_000;
1415

1516
export const FOOTER_HEIGHT = 40; // height of the manchette footer
1617
export const WAYPOINT_LINE_HEIGHT = 16;

ui-charts/src/manchette/useManchetteWithSpaceTimeChart/helpers.ts

+73-17
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@ import {
66
MAX_ZOOM_X,
77
MIN_ZOOM_MS_PER_PX,
88
MIN_ZOOM_X,
9+
MAX_ZOOM_Y,
10+
MIN_ZOOM_Y,
11+
MAX_ZOOM_MANCHETTE_HEIGHT_MILLIMETER,
912
} from './consts';
1013
import { calcTotalDistance, getHeightWithoutLastWaypoint } from './utils';
1114
import type { InteractiveWaypoint, Waypoint } from '../Manchette';
1215

13-
type WaypointsOptions = { isProportional: boolean; yZoom: number; height: number };
16+
type WaypointsOptions = {
17+
isProportional: boolean;
18+
yZoom: number;
19+
height: number;
20+
};
1421

1522
export const filterVisibleElements = (
1623
elements: Waypoint[],
@@ -43,20 +50,23 @@ export const filterVisibleElements = (
4350

4451
export const computeWaypointsToDisplay = (
4552
waypoints: Waypoint[],
46-
{ height, isProportional, yZoom }: WaypointsOptions
53+
{ height, isProportional, yZoom }: WaypointsOptions,
54+
minZoomMillimetrePerPx: number,
55+
maxZoomMillimetrePerPx: number
4756
): InteractiveWaypoint[] => {
4857
if (waypoints.length < 2) return [];
4958

5059
const totalDistance = calcTotalDistance(waypoints);
51-
const heightWithoutFinalWaypoint = getHeightWithoutLastWaypoint(height);
60+
const manchetteHeight = getHeightWithoutLastWaypoint(height);
5261

5362
// display all waypoints in linear mode
5463
if (!isProportional) {
5564
return waypoints.map((waypoint, index) => {
5665
const nextWaypoint = waypoints.at(index + 1);
66+
const waypointHeight = BASE_WAYPOINT_HEIGHT * (nextWaypoint ? yZoom : 1);
5767
return {
5868
...waypoint,
59-
styles: { height: `${BASE_WAYPOINT_HEIGHT * (nextWaypoint ? yZoom : 1)}px` },
69+
styles: { height: `${waypointHeight}px` },
6070
};
6171
});
6272
}
@@ -67,30 +77,36 @@ export const computeWaypointsToDisplay = (
6777
const filteredWaypoints = filterVisibleElements(
6878
waypoints,
6979
totalDistance,
70-
heightWithoutFinalWaypoint,
80+
manchetteHeight,
7181
minSpace
7282
);
7383

84+
const spaceScale = zoomValueToSpaceScale(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, yZoom);
85+
7486
return filteredWaypoints.map((waypoint, index) => {
7587
const nextWaypoint = filteredWaypoints.at(index + 1);
88+
const waypointHeight = !nextWaypoint
89+
? BASE_WAYPOINT_HEIGHT
90+
: (nextWaypoint.position - waypoint.position) / spaceScale
7691
return {
7792
...waypoint,
7893
styles: {
79-
height: !nextWaypoint
80-
? `${BASE_WAYPOINT_HEIGHT}px`
81-
: `${
82-
((nextWaypoint.position - waypoint.position) / totalDistance) *
83-
heightWithoutFinalWaypoint *
84-
yZoom
85-
}px`,
94+
height: `${waypointHeight}px`,
8695
},
8796
};
8897
});
8998
};
9099

100+
/**
101+
* 2 modes for space scales
102+
* km: { coefficient: gives a scale in metre/pixel } (isProportional true)
103+
* linear: { size: height in pixel } (each point distributed evenly along the height of manchette.)
104+
*/
91105
export const getScales = (
92106
waypoints: Waypoint[],
93-
{ height, isProportional, yZoom }: WaypointsOptions
107+
{ isProportional, yZoom }: WaypointsOptions,
108+
minZoomMillimetrePerPx: number,
109+
maxZoomMillimetrePerPx: number
94110
) => {
95111
if (waypoints.length < 2) return [];
96112

@@ -109,11 +125,8 @@ export const getScales = (
109125
const from = waypoints.at(0)!.position;
110126
const to = waypoints.at(-1)!.position;
111127

112-
const totalDistance = calcTotalDistance(waypoints);
113-
const heightWithoutFinalWaypoint = getHeightWithoutLastWaypoint(height);
114-
115128
const scaleCoeff = isProportional
116-
? { coefficient: totalDistance / heightWithoutFinalWaypoint / yZoom }
129+
? { coefficient: zoomValueToSpaceScale(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, yZoom) }
117130
: { size: BASE_WAYPOINT_HEIGHT * (waypoints.length - 1) * yZoom };
118131

119132
return [
@@ -132,6 +145,49 @@ export const timeScaleToZoomValue = (timeScale: number) =>
132145
(100 * Math.log(timeScale / MIN_ZOOM_MS_PER_PX)) /
133146
Math.log(MAX_ZOOM_MS_PER_PX / MIN_ZOOM_MS_PER_PX);
134147

148+
/**
149+
* min zoom is computed with manchette px height between first and last waypoint.
150+
* max zoom just the canvas drawing height (without the x-axis scale section)
151+
*/
152+
export const getExtremaScales = (
153+
drawingHeightWithoutTopPadding: number,
154+
drawingHeightWithoutBothPadding: number,
155+
pathLengthMillimeter: number
156+
) => {
157+
return {
158+
minZoomMillimetrePerPx: pathLengthMillimeter / drawingHeightWithoutBothPadding,
159+
maxZoomMillimetrePerPx: MAX_ZOOM_MANCHETTE_HEIGHT_MILLIMETER / drawingHeightWithoutTopPadding,
160+
};
161+
};
162+
163+
// export const getScaleFromRectangle = ()
164+
165+
export const zoomValueToSpaceScale = (
166+
minZoomMillimetrePerPx: number,
167+
maxZoomMillimetrePerPx: number,
168+
slider: number
169+
) => {
170+
return (
171+
minZoomMillimetrePerPx *
172+
Math.pow(
173+
maxZoomMillimetrePerPx / minZoomMillimetrePerPx,
174+
(slider - MIN_ZOOM_Y) / (MAX_ZOOM_Y - MIN_ZOOM_Y)
175+
)
176+
);
177+
};
178+
179+
export const spaceScaleToZoomValue = (
180+
minZoomMillimetrePerPx: number,
181+
maxZoomMillimetrePerPx: number,
182+
spaceScale: number
183+
) => {
184+
return (
185+
((MAX_ZOOM_Y - MIN_ZOOM_Y) * Math.log(spaceScale / minZoomMillimetrePerPx)) /
186+
Math.log(maxZoomMillimetrePerPx / minZoomMillimetrePerPx) +
187+
MIN_ZOOM_Y
188+
);
189+
};
190+
135191
/** Zoom on X axis and center on the mouse position */
136192
export const zoomX = (
137193
currentZoom: number,

0 commit comments

Comments
 (0)