Skip to content

Commit

Permalink
save
Browse files Browse the repository at this point in the history
  • Loading branch information
anisometropie committed Feb 24, 2025
1 parent 6464720 commit b20fa35
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 25 deletions.
4 changes: 4 additions & 0 deletions ui-manchette-with-spacetimechart/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export const DEFAULT_ZOOM_MS_PER_PX = 7500;
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;
Expand Down
9 changes: 9 additions & 0 deletions ui-manchette-with-spacetimechart/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ 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,
} from './consts';
Expand Down Expand Up @@ -132,6 +134,13 @@ 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);

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);

/** Zoom on X axis and center on the mouse position */
export const zoomX = (
currentZoom: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,45 @@ import type {
} from '@osrd-project/ui-spacetimechart/dist/lib/types';

import usePaths from './usePaths';
import { MAX_ZOOM_Y, MIN_ZOOM_Y, ZOOM_Y_DELTA, DEFAULT_ZOOM_MS_PER_PX } from '../consts';
import {
MAX_ZOOM_Y,
MIN_ZOOM_Y,
ZOOM_Y_DELTA,
DEFAULT_ZOOM_MS_PER_PX,
MAX_ZOOM_MS_PER_PX,
MIN_ZOOM_MS_PER_PX,
MAX_ZOOM_METRE_PER_PX,
MIN_ZOOM_METRE_PER_PX,
} from '../consts';
import {
computeWaypointsToDisplay,
getScales,
zoomX,
zoomValueToTimeScale,
timeScaleToZoomValue,
spaceScaleToZoomValue,
} from '../helpers';
import { getDiff } from '../utils/point';

Check warning on line 28 in ui-manchette-with-spacetimechart/src/hooks/useManchetteWithSpaceTimeChart.ts

View workflow job for this annotation

GitHub Actions / build

There should be at least one empty line between import groups
import { clamp } from 'lodash';

Check warning on line 29 in ui-manchette-with-spacetimechart/src/hooks/useManchetteWithSpaceTimeChart.ts

View workflow job for this annotation

GitHub Actions / build

`lodash` import should occur before import of `./usePaths`

type State = {
xZoom: number;
yZoom: number;
timeOrigin: number;
spaceOrigin: number;
xOffset: number;
/** the current y-scroll of the view. always updates */
yOffset: number;
/** only update after a zoom. used to update back the view scroll value */
scrollTo: number | null;
panning: { initialOffset: { x: number; y: number } } | null;
zoomMode: boolean;
rect: {
timeStart: Date;
timeEnd: Date;
spaceStart: number; // mm
spaceEnd: number; // mm
} | null;
isProportional: boolean;
waypointsChart: Waypoint[];
scales: SpaceScale[];
Expand All @@ -43,17 +63,34 @@ const useManchettesWithSpaceTimeChart = (
const [state, setState] = useState<State>({
xZoom: timeScaleToZoomValue(DEFAULT_ZOOM_MS_PER_PX),
yZoom: 1,
timeOrigin: Math.min(...projectPathTrainResult.map((p) => +p.departureTime)),
spaceOrigin: 0,
xOffset: 0,
yOffset: 0,
scrollTo: null,
panning: null,
zoomMode: false,
rect: null,
isProportional: true,
waypointsChart: [],
scales: [],
});

const { xZoom, yZoom, xOffset, yOffset, scrollTo, panning, isProportional } = state;
const {
xZoom,
yZoom,
timeOrigin,
spaceOrigin,
xOffset,
yOffset,
scrollTo,
panning,
zoomMode,
rect,
isProportional,
} = state;

console.log(yZoom);

Check warning on line 93 in ui-manchette-with-spacetimechart/src/hooks/useManchetteWithSpaceTimeChart.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement
const paths = usePaths(projectPathTrainResult, selectedTrain);

const waypointsToDisplay = useMemo(
Expand All @@ -72,6 +109,42 @@ const useManchettesWithSpaceTimeChart = (
[waypointsToDisplay]
);

const handleRectangleZoom = useCallback(

Check warning on line 112 in ui-manchette-with-spacetimechart/src/hooks/useManchetteWithSpaceTimeChart.ts

View workflow job for this annotation

GitHub Actions / build

'handleRectangleZoom' is assigned a value but never used
({
scales: { chosenTimeScale, chosenSpaceScale },
overrideState,
}: {
scales: { chosenTimeScale: number; chosenSpaceScale: number };
overrideState?: Partial<State>;
}) => {
setState((prev) => {
if (prev.zoomMode || !prev.rect) {
return prev;
}

const newTimeScale = clamp(chosenTimeScale, MAX_ZOOM_MS_PER_PX, MIN_ZOOM_MS_PER_PX);
const newSpaceScale = clamp(chosenSpaceScale, MAX_ZOOM_METRE_PER_PX, MIN_ZOOM_METRE_PER_PX);
const timeZoomValue = timeScaleToZoomValue(newTimeScale);
const spaceZoomValue = spaceScaleToZoomValue(newSpaceScale);

const leftRectSide = Math.min(Number(prev.rect.timeStart), Number(prev.rect.timeEnd));
const topRectSide = Math.min(prev.rect.spaceStart, prev.rect.spaceEnd);
const newXOffset = (timeOrigin - leftRectSide) / newTimeScale;
const newYOffset = (spaceOrigin - topRectSide) / newSpaceScale;

return {
...prev,
timeZoomValue: timeZoomValue,
spaceZoomValue: spaceZoomValue,
xOffset: newXOffset,
yOffset: newYOffset,
...overrideState,
};
});
},
[timeOrigin, spaceOrigin]
);

const zoomYIn = useCallback(() => {
if (yZoom < MAX_ZOOM_Y) {
const newYZoom = yZoom + ZOOM_Y_DELTA;
Expand Down Expand Up @@ -99,6 +172,16 @@ const useManchettesWithSpaceTimeChart = (
}
}, [yZoom, yOffset]);

const handleXZoom = useCallback(
(newXZoom: number, xPosition = (spaceTimeChartRef?.current?.offsetWidth || 0) / 2) => {
setState((prev) => ({
...prev,
...zoomX(prev.xZoom, prev.xOffset, newXZoom, xPosition),
}));
},
[spaceTimeChartRef]
);

useEffect(() => {
if (scrollTo !== null && manchetteWithSpaceTimeChartContainer.current) {
manchetteWithSpaceTimeChartContainer.current.scrollTo({
Expand Down Expand Up @@ -152,6 +235,11 @@ const useManchettesWithSpaceTimeChart = (
[simplifiedWaypoints, height, isProportional, yZoom]
);

const toggleZoomMode = useCallback(() => {
setState((prev) => ({ ...prev, zoomMode: !prev.zoomMode }));
}, []);
// console.log('computedScales', computedScales);

const manchetteProps = useMemo(
() => ({
waypoints: waypointsToDisplay,
Expand All @@ -166,16 +254,6 @@ const useManchettesWithSpaceTimeChart = (
[waypointsToDisplay, zoomYIn, zoomYOut, resetZoom, toggleMode, yZoom, isProportional, yOffset]
);

const handleXZoom = useCallback(
(newXZoom: number, xPosition = (spaceTimeChartRef?.current?.offsetWidth || 0) / 2) => {
setState((prev) => ({
...prev,
...zoomX(prev.xZoom, prev.xOffset, newXZoom, xPosition),
}));
},
[spaceTimeChartRef]
);

const spaceTimeChartProps = useMemo(
() => ({
operationalPoints: simplifiedWaypoints,
Expand All @@ -184,24 +262,47 @@ const useManchettesWithSpaceTimeChart = (
paths,
xOffset,
yOffset: -yOffset + 14,
timeOrigin,
spaceOrigin,
rect,
onZoom: ({ delta, position }: Parameters<NonNullable<SpaceTimeChartProps['onZoom']>>[0]) => {
if (isShiftPressed) {
handleXZoom(xZoom + delta, position.x);
}
},
onPan: (payload: {
initialPosition: { x: number; y: number };
position: { x: number; y: number };
isPanning: boolean;
}) => {
onPan: (payload: Parameters<NonNullable<SpaceTimeChartProps['onPan']>>[0]) => {
const diff = getDiff(payload.initialPosition, payload.position);
const newState = { ...state };
setState((prev) => {
if (!payload.isPanning) {
return {
...prev,
panning: null,
zoomMode: false,
};
}

if (!payload.isPanning) {
newState.panning = null;
} else if (!panning) {
newState.panning = { initialOffset: { x: xOffset, y: yOffset } };
} else {
if (state.zoomMode) {
const newRect = {
timeStart: new Date(payload.initialData.time),
timeEnd: new Date(payload.data.time),
spaceStart: payload.initialData.position,
spaceEnd: payload.data.position,
};

return {
...prev,
rect: newRect,
};
}

if (!panning) {
return {
...prev,
panning: { initialOffset: { x: xOffset, y: yOffset } },
};
}

const newState = { ...prev };
const { initialOffset } = panning;
newState.xOffset = initialOffset.x + diff.x;

Expand All @@ -215,8 +316,8 @@ const useManchettesWithSpaceTimeChart = (
newState.yOffset = newYPos;
manchetteWithSpaceTimeChartContainer.current.scrollTop = newYPos;
}
}
setState(newState);
return newState;
});
},
}),
[

Check warning on line 323 in ui-manchette-with-spacetimechart/src/hooks/useManchetteWithSpaceTimeChart.ts

View workflow job for this annotation

GitHub Actions / build

React Hook useMemo has missing dependencies: 'rect', 'spaceOrigin', and 'timeOrigin'. Either include them or remove the dependency array
Expand All @@ -241,6 +342,8 @@ const useManchettesWithSpaceTimeChart = (
handleScroll,
handleXZoom,
xZoom,
toggleZoomMode,
zoomMode,
}),
[manchetteProps, spaceTimeChartProps, handleScroll, handleXZoom, xZoom]

Check warning on line 348 in ui-manchette-with-spacetimechart/src/hooks/useManchetteWithSpaceTimeChart.ts

View workflow job for this annotation

GitHub Actions / build

React Hook useMemo has missing dependencies: 'toggleZoomMode' and 'zoomMode'. Either include them or remove the dependency array
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useRef } from 'react';

import Manchette, { type ProjectPathTrainResult, type Waypoint } from '@osrd-project/ui-manchette';
import { PathLayer, SpaceTimeChart, RectangleZoom } from '@osrd-project/ui-spacetimechart';
import type { Meta } from '@storybook/react';

import '@osrd-project/ui-core/dist/theme.css';
import '@osrd-project/ui-manchette/dist/theme.css';
import '@osrd-project/ui-manchette-with-spacetimechart/dist/theme.css';
import '../styles/stories/rectangle-zoom.css';

import { SAMPLE_WAYPOINTS, SAMPLE_PATHS_DATA } from '../assets/sampleData';
import useManchettesWithSpaceTimeChart from '../hooks/useManchetteWithSpaceTimeChart';
import { ZoomIn } from '@osrd-project/ui-icons';

type ManchetteWithSpaceTimeWrapperProps = {
waypoints: Waypoint[];
projectPathTrainResult: ProjectPathTrainResult[];
selectedTrain: number;
};

const DEFAULT_HEIGHT = 561;

const ManchetteWithSpaceTimeWrapper = ({
waypoints,
projectPathTrainResult,
selectedTrain,
}: ManchetteWithSpaceTimeWrapperProps) => {
const manchetteWithSpaceTimeChartRef = useRef<HTMLDivElement>(null);

const { manchetteProps, spaceTimeChartProps, handleScroll, toggleZoomMode, zoomMode } =
useManchettesWithSpaceTimeChart(
waypoints,
projectPathTrainResult,
manchetteWithSpaceTimeChartRef,
selectedTrain
);

return (
<div className="manchette-space-time-chart-wrapper">
<div
className="header bg-ambientB-5 w-full border-b border-grey-30"
style={{ height: '40px' }}
></div>
<div
ref={manchetteWithSpaceTimeChartRef}
className="manchette flex"
style={{ height: `${DEFAULT_HEIGHT}px` }}
onScroll={handleScroll}
>
<Manchette {...manchetteProps} />
<div
className="space-time-chart-container w-full sticky"
style={{ bottom: 0, left: 0, top: 2, height: `${DEFAULT_HEIGHT - 6}px` }}
>
<div className="toolbar">
<button type="button" className="menu-button" onClick={toggleZoomMode}>
<ZoomIn />
</button>
</div>
<SpaceTimeChart className="inset-0 absolute h-full" {...spaceTimeChartProps}>
{spaceTimeChartProps.paths.map((path) => (
<PathLayer key={path.id} path={path} color={path.color} level={path.level} />
))}
{spaceTimeChartProps.rect && <RectangleZoom rect={spaceTimeChartProps.rect} />}
</SpaceTimeChart>
</div>
</div>
</div>
);
};

const meta: Meta<typeof ManchetteWithSpaceTimeWrapper> = {
title: 'Manchette with SpaceTimeChart/Zoom rectangle',
component: ManchetteWithSpaceTimeWrapper,
};

export default meta;

export const Default = {
args: {
waypoints: SAMPLE_WAYPOINTS,
projectPathTrainResult: SAMPLE_PATHS_DATA,
selectedTrain: 1,
},
};
Loading

0 comments on commit b20fa35

Please sign in to comment.