Skip to content

Commit

Permalink
ui-manchette-with-spacetime-chart: story: zoom on manchette + spacetime
Browse files Browse the repository at this point in the history
Signed-off-by: Valentin Chanas <[email protected]>
  • Loading branch information
anisometropie committed Mar 5, 2025
1 parent b20fa35 commit c9db372
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 66 deletions.
44 changes: 41 additions & 3 deletions ui-manchette-with-spacetimechart/src/__tests__/helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { describe, it, expect } from 'vitest';
import { describe, it, test, expect } from 'vitest';

import { BASE_WAYPOINT_HEIGHT } from '../consts';
import { computeWaypointsToDisplay, getScales } from '../helpers';
import { BASE_WAYPOINT_HEIGHT, MAX_ZOOM_Y, MIN_ZOOM_Y } from '../consts';
import {
computeWaypointsToDisplay,
getScales,
getExtremaScales,
spaceScaleToZoomValue,
zoomValueToSpaceScale,
} from '../helpers';

// Assuming these types from your code

Expand Down Expand Up @@ -105,3 +111,35 @@ describe('getScales', () => {
expect(result[0]).not.toHaveProperty('coefficient');
});
});

describe('space scale functions', () => {
const pathLength = 168056000; // mm
const manchettePxHeight = 528;
const heightBetweenFirstLastWaypoints = 489;

const { minZoomMillimetrePerPx, maxZoomMillimetrePerPx } = getExtremaScales(
manchettePxHeight,
heightBetweenFirstLastWaypoints,
pathLength
);
expect(minZoomMillimetrePerPx).toBeCloseTo(343672.801);
expect(maxZoomMillimetrePerPx).toBeCloseTo(946.97);

test('zoomValueToSpaceScale', () => {
expect(
zoomValueToSpaceScale(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, MIN_ZOOM_Y)
).toBeCloseTo(343672.801);
expect(
zoomValueToSpaceScale(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, MAX_ZOOM_Y)
).toBeCloseTo(946.97);
});

test('spaceScaleToZoomValue', () => {
expect(
spaceScaleToZoomValue(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, 343672.801)
).toBeCloseTo(MIN_ZOOM_Y);
expect(
spaceScaleToZoomValue(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, 946.97)
).toBeCloseTo(MAX_ZOOM_Y);
});
});
9 changes: 3 additions & 6 deletions ui-manchette-with-spacetimechart/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ export const BASE_WAYPOINT_HEIGHT = 32;
export const INITIAL_WAYPOINT_LIST_HEIGHT = 521;
export const INITIAL_SPACE_TIME_CHART_HEIGHT = INITIAL_WAYPOINT_LIST_HEIGHT + 40;

export const MIN_ZOOM_MS_PER_PX = 600000;
export const MIN_ZOOM_MS_PER_PX = 600_000;
export const MAX_ZOOM_MS_PER_PX = 625;
export const DEFAULT_ZOOM_MS_PER_PX = 7500;
export const DEFAULT_ZOOM_MS_PER_PX = 7_500;
export const MIN_ZOOM_X = 0;
export const MAX_ZOOM_X = 100;

export const MIN_ZOOM_METRE_PER_PX = 10000;
export const MAX_ZOOM_METRE_PER_PX = 10;
export const DEFAULT_ZOOM_METRE_PER_PX = 300;

export const MIN_ZOOM_Y = 1;
export const MAX_ZOOM_Y = 10.5;
export const ZOOM_Y_DELTA = 0.5;
export const MAX_ZOOM_MANCHETTE_HEIGHT_MILLIMETER = 500_000;

export const FOOTER_HEIGHT = 40; // height of the manchette footer
export const WAYPOINT_LINE_HEIGHT = 16;
Expand Down
95 changes: 71 additions & 24 deletions ui-manchette-with-spacetimechart/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import { clamp } from 'lodash';

import {
BASE_WAYPOINT_HEIGHT,
MAX_ZOOM_METRE_PER_PX,
MAX_ZOOM_MS_PER_PX,
MAX_ZOOM_X,
MIN_ZOOM_METRE_PER_PX,
MIN_ZOOM_MS_PER_PX,
MIN_ZOOM_X,
MAX_ZOOM_Y,
MIN_ZOOM_Y,
MAX_ZOOM_MANCHETTE_HEIGHT_MILLIMETER,
} from './consts';
import { calcTotalDistance, getHeightWithoutLastWaypoint } from './utils';

type WaypointsOptions = { isProportional: boolean; yZoom: number; height: number };
type WaypointsOptions = {
isProportional: boolean;
yZoom: number;
height: number;
};

export const filterVisibleElements = (
elements: Waypoint[],
Expand Down Expand Up @@ -45,20 +50,23 @@ export const filterVisibleElements = (

export const computeWaypointsToDisplay = (
waypoints: Waypoint[],
{ height, isProportional, yZoom }: WaypointsOptions
{ height, isProportional, yZoom }: WaypointsOptions,
minZoomMillimetrePerPx: number,
maxZoomMillimetrePerPx: number
): InteractiveWaypoint[] => {
if (waypoints.length < 2) return [];

const totalDistance = calcTotalDistance(waypoints);
const heightWithoutFinalWaypoint = getHeightWithoutLastWaypoint(height);
const manchetteHeight = getHeightWithoutLastWaypoint(height);

// display all waypoints in linear mode
if (!isProportional) {
return waypoints.map((waypoint, index) => {
const nextWaypoint = waypoints.at(index + 1);
const waypointHeight = BASE_WAYPOINT_HEIGHT * (nextWaypoint ? yZoom : 1);
return {
...waypoint,
styles: { height: `${BASE_WAYPOINT_HEIGHT * (nextWaypoint ? yZoom : 1)}px` },
styles: { height: `${waypointHeight}px` },
};
});
}
Expand All @@ -69,30 +77,36 @@ export const computeWaypointsToDisplay = (
const filteredWaypoints = filterVisibleElements(
waypoints,
totalDistance,
heightWithoutFinalWaypoint,
manchetteHeight,
minSpace
);

const spaceScale = zoomValueToSpaceScale(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, yZoom);

return filteredWaypoints.map((waypoint, index) => {
const nextWaypoint = filteredWaypoints.at(index + 1);
const waypointHeight = !nextWaypoint
? BASE_WAYPOINT_HEIGHT
: (nextWaypoint.position - waypoint.position) / spaceScale
return {
...waypoint,
styles: {
height: !nextWaypoint
? `${BASE_WAYPOINT_HEIGHT}px`
: `${
((nextWaypoint.position - waypoint.position) / totalDistance) *
heightWithoutFinalWaypoint *
yZoom
}px`,
height: `${waypointHeight}px`,
},
};
});
};

/**
* 2 modes for space scales
* km: { coefficient: gives a scale in metre/pixel } (isProportional true)
* linear: { size: height in pixel } (each point distributed evenly along the height of manchette.)
*/
export const getScales = (
waypoints: Waypoint[],
{ height, isProportional, yZoom }: WaypointsOptions
{ isProportional, yZoom }: WaypointsOptions,
minZoomMillimetrePerPx: number,
maxZoomMillimetrePerPx: number
) => {
if (waypoints.length < 2) return [];

Expand All @@ -111,11 +125,8 @@ export const getScales = (
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 }
? { coefficient: zoomValueToSpaceScale(minZoomMillimetrePerPx, maxZoomMillimetrePerPx, yZoom) }
: { size: BASE_WAYPOINT_HEIGHT * (waypoints.length - 1) * yZoom };

return [
Expand All @@ -134,12 +145,48 @@ 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);

export const zoomValueToSpaceScale = (slider: number) =>
MIN_ZOOM_METRE_PER_PX * Math.pow(MAX_ZOOM_METRE_PER_PX / MIN_ZOOM_METRE_PER_PX, slider / 100);
/**
* min zoom is computed with manchette px height between first and last waypoint.
* max zoom just the canvas drawing height (without the x-axis scale section)
*/
export const getExtremaScales = (
drawingHeightWithoutTopPadding: number,
drawingHeightWithoutBothPadding: number,
pathLengthMillimeter: number
) => {
return {
minZoomMillimetrePerPx: pathLengthMillimeter / drawingHeightWithoutBothPadding,
maxZoomMillimetrePerPx: MAX_ZOOM_MANCHETTE_HEIGHT_MILLIMETER / drawingHeightWithoutTopPadding,
};
};

// export const getScaleFromRectangle = ()

export const spaceScaleToZoomValue = (spaceScale: number) =>
(100 * Math.log(spaceScale / MIN_ZOOM_METRE_PER_PX)) /
Math.log(MAX_ZOOM_METRE_PER_PX / MIN_ZOOM_METRE_PER_PX);
export const zoomValueToSpaceScale = (
minZoomMillimetrePerPx: number,
maxZoomMillimetrePerPx: number,
slider: number
) => {
return (
minZoomMillimetrePerPx *
Math.pow(
maxZoomMillimetrePerPx / minZoomMillimetrePerPx,
(slider - MIN_ZOOM_Y) / (MAX_ZOOM_Y - MIN_ZOOM_Y)
)
);
};

export const spaceScaleToZoomValue = (
minZoomMillimetrePerPx: number,
maxZoomMillimetrePerPx: number,
spaceScale: number
) => {
return (
((MAX_ZOOM_Y - MIN_ZOOM_Y) * Math.log(spaceScale / minZoomMillimetrePerPx)) /
Math.log(maxZoomMillimetrePerPx / minZoomMillimetrePerPx) +
MIN_ZOOM_Y
);
};

/** Zoom on X axis and center on the mouse position */
export const zoomX = (
Expand Down
Loading

0 comments on commit c9db372

Please sign in to comment.