Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

front: fix space time chart when first or last waypoint are hidden #9513

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useMemo, useRef, useState } from 'react';

import { KebabHorizontal } from '@osrd-project/ui-icons';
import { Manchette } from '@osrd-project/ui-manchette';
Expand All @@ -11,12 +11,18 @@ import {
OccupancyBlockLayer,
} from '@osrd-project/ui-spacetimechart';
import type { Conflict } from '@osrd-project/ui-spacetimechart';
import { compact } from 'lodash';

import type { OperationalPoint, TrainSpaceTimeData } from 'applications/operationalStudies/types';
import upward from 'assets/pictures/workSchedules/ScheduledMaintenanceUp.svg';
import type { PostWorkSchedulesProjectPathApiResponse } from 'common/api/osrdEditoastApi';
import cutSpaceTimeRect from 'modules/simulationResult/components/SpaceTimeChart/helpers/utils';
import { ASPECT_LABELS_COLORS } from 'modules/simulationResult/consts';
import type { AspectLabel, WaypointsPanelData } from 'modules/simulationResult/types';
import type {
AspectLabel,
LayerRangeData,
WaypointsPanelData,
} from 'modules/simulationResult/types';

import SettingsPanel from './SettingsPanel';
import ManchetteMenuButton from '../SpaceTimeChart/ManchetteMenuButton';
Expand Down Expand Up @@ -45,9 +51,91 @@ const ManchetteWithSpaceTimeChartWrapper = ({

const [waypointsPanelIsOpen, setWaypointsPanelIsOpen] = useState(false);

// Cut the space time chart curves if the first or last waypoints are hidden
const spaceTimeChartLayersData = useMemo(() => {
let filteredProjectPathTrainResult = projectPathTrainResult;
let filteredConflicts = conflicts;

if (!waypointsPanelData || waypointsPanelData.filteredWaypoints.length < 2)
return { filteredProjectPathTrainResult, filteredConflicts };

const { filteredWaypoints } = waypointsPanelData;
const firstPosition = filteredWaypoints.at(0)!.position;
const lastPosition = filteredWaypoints.at(-1)!.position;

if (firstPosition !== 0 || lastPosition !== operationalPoints.at(-1)!.position) {
filteredProjectPathTrainResult = projectPathTrainResult.map((train) => ({
...train,
space_time_curves: train.space_time_curves.map(({ positions, times }) => {
const cutPositions: number[] = [];
const cutTimes: number[] = [];

for (let i = 1; i < positions.length; i += 1) {
const currentRange: LayerRangeData = {
spaceStart: positions[i - 1],
spaceEnd: positions[i],
timeStart: times[i - 1],
timeEnd: times[i],
};

const interpolatedRange = cutSpaceTimeRect(currentRange, firstPosition, lastPosition);

// TODO : remove reformatting the datas when https://github.com/OpenRailAssociation/osrd-ui/issues/694 is merged
if (!interpolatedRange) continue;

if (i === 1 || cutPositions.length === 0) {
cutPositions.push(interpolatedRange.spaceStart);
cutTimes.push(interpolatedRange.timeStart);
}
cutPositions.push(interpolatedRange.spaceEnd);
cutTimes.push(interpolatedRange.timeEnd);
}

return {
positions: cutPositions,
times: cutTimes,
};
}),
signal_updates: compact(
train.signal_updates.map((signal) => {
const updatedSignalRange = cutSpaceTimeRect(
{
spaceStart: signal.position_start,
spaceEnd: signal.position_end,
timeStart: signal.time_start,
timeEnd: signal.time_end,
},
firstPosition,
lastPosition
);

if (!updatedSignalRange) return null;

// TODO : remove reformatting the datas when https://github.com/OpenRailAssociation/osrd-ui/issues/694 is merged
return {
...signal,
position_start: updatedSignalRange.spaceStart,
position_end: updatedSignalRange.spaceEnd,
time_start: updatedSignalRange.timeStart,
time_end: updatedSignalRange.timeEnd,
};
})
),
}));

filteredConflicts = compact(
conflicts.map((conflict) => cutSpaceTimeRect(conflict, firstPosition, lastPosition))
);

return { filteredProjectPathTrainResult, filteredConflicts };
}

return { filteredProjectPathTrainResult, filteredConflicts };
}, [waypointsPanelData?.filteredWaypoints, projectPathTrainResult, conflicts]);

const { manchetteProps, spaceTimeChartProps, handleScroll } = useManchettesWithSpaceTimeChart(
waypointsPanelData?.filteredWaypoints ?? operationalPoints,
projectPathTrainResult,
spaceTimeChartLayersData.filteredProjectPathTrainResult,
manchetteWithSpaceTimeChartRef,
selectedTrainScheduleId
);
Expand All @@ -58,17 +146,19 @@ const ManchetteWithSpaceTimeChartWrapper = ({
showSignalsStates: false,
});

const occupancyBlocks = projectPathTrainResult.flatMap((train) => {
const departureTime = new Date(train.departure_time).getTime();
const occupancyBlocks = spaceTimeChartLayersData.filteredProjectPathTrainResult.flatMap(
(train) => {
const departureTime = new Date(train.departure_time).getTime();

return train.signal_updates.map((block) => ({
timeStart: departureTime + block.time_start,
timeEnd: departureTime + block.time_end,
spaceStart: block.position_start,
spaceEnd: block.position_end,
color: ASPECT_LABELS_COLORS[block.aspect_label as AspectLabel],
}));
});
return train.signal_updates.map((block) => ({
timeStart: departureTime + block.time_start,
timeEnd: departureTime + block.time_end,
spaceStart: block.position_start,
spaceEnd: block.position_end,
color: ASPECT_LABELS_COLORS[block.aspect_label as AspectLabel],
}));
}
);

return (
<div className="manchette-space-time-chart-wrapper">
Expand Down Expand Up @@ -134,7 +224,9 @@ const ManchetteWithSpaceTimeChartWrapper = ({
imageUrl={upward}
/>
)}
{settings.showConflicts && <ConflictLayer conflicts={conflicts} />}
{settings.showConflicts && (
<ConflictLayer conflicts={spaceTimeChartLayersData.filteredConflicts} />
)}
{settings.showSignalsStates && (
<OccupancyBlockLayer occupancyBlocks={occupancyBlocks} />
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { describe, it, expect } from 'vitest';

import cutSpaceTimeRect from '../utils';

describe('interpolateRange', () => {
it('should return null if the interpolated range ends before the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 200,
};
const interpolatedRange = cutSpaceTimeRect(range, 1, 3);
expect(interpolatedRange).toBeNull();
});

it('should return null if the interpolated range starts after the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 200,
};
const interpolatedRange = cutSpaceTimeRect(range, 5, 7);
expect(interpolatedRange).toBeNull();
});

it('should return the same range if its ranges are inside the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 200,
};
const interpolatedRange = cutSpaceTimeRect(range, 2, 7);
expect(interpolatedRange).toEqual(range);
});

it('should return the interpolated range when the start position is outside the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 200,
};
const interpolatedRange = cutSpaceTimeRect(range, 4, 5);
expect(interpolatedRange).toEqual({
spaceStart: 4,
spaceEnd: 5,
timeStart: 150,
timeEnd: 200,
});
});

it('should return the interpolated range when the end position is is outside the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 6,
timeStart: 100,
timeEnd: 160,
};
const interpolatedRange = cutSpaceTimeRect(range, 3, 5);
expect(interpolatedRange).toEqual({
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 140,
});
});

it('should return the interpolated range when both positions are outside the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 6,
timeStart: 100,
timeEnd: 160,
};
const interpolatedRange = cutSpaceTimeRect(range, 4, 5);
expect(interpolatedRange).toEqual({
spaceStart: 4,
spaceEnd: 5,
timeStart: 120,
timeEnd: 140,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { LayerRangeData } from '../../../types';

const cutSpaceTimeRect = (
range: LayerRangeData,
minSpace: number,
maxSpace: number
): LayerRangeData | null => {
let { timeStart, timeEnd, spaceStart, spaceEnd } = range;

if (spaceEnd <= minSpace || spaceStart >= maxSpace) {
return null;
}

if (spaceStart < minSpace) {
const interpolationFactor = (minSpace - spaceStart) / (spaceEnd - spaceStart);
spaceStart = minSpace;
timeStart += (timeEnd - timeStart) * interpolationFactor;
}

if (spaceEnd > maxSpace) {
const interpolationFactor = (spaceEnd - maxSpace) / (spaceEnd - spaceStart);
spaceEnd = maxSpace;
timeEnd -= (timeEnd - timeStart) * interpolationFactor;
}

return {
spaceStart,
spaceEnd,
timeStart,
timeEnd,
};
};

export default cutSpaceTimeRect;
7 changes: 7 additions & 0 deletions front/src/modules/simulationResult/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ export type WaypointsPanelData = {
projectionPath: TrainScheduleBase['path'];
};

export type LayerRangeData = {
spaceStart: number;
spaceEnd: number;
timeStart: number;
timeEnd: number;
};

export type AspectLabel =
| 'VL'
| '300VL'
Expand Down
Loading