From cd4cdfbd52e267f63f2ce878d43d8c01074f5202 Mon Sep 17 00:00:00 2001 From: SharglutDev Date: Tue, 29 Oct 2024 16:36:21 +0100 Subject: [PATCH] front: fix space time chart when first or last waypoint are hidden Filter data from projected path (path and occupancy blocks) and conflicts when the first or last waypoint is hidden. Allow the space time chart to look as if it started/ended with the first/last displayed waypoint. Signed-off-by: SharglutDev --- .../ManchetteWithSpaceTimeChart.tsx | 120 ++++++++++++++++-- .../helpers/__tests__/utils.spec.ts | 86 +++++++++++++ .../SpaceTimeChart/helpers/utils.ts | 34 +++++ front/src/modules/simulationResult/types.ts | 7 + 4 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 front/src/modules/simulationResult/components/SpaceTimeChart/helpers/__tests__/utils.spec.ts create mode 100644 front/src/modules/simulationResult/components/SpaceTimeChart/helpers/utils.ts diff --git a/front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart.tsx b/front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart.tsx index 71a9489bc6c..770394e2801 100644 --- a/front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart.tsx +++ b/front/src/modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart.tsx @@ -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'; @@ -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'; @@ -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 ); @@ -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 (
@@ -134,7 +224,9 @@ const ManchetteWithSpaceTimeChartWrapper = ({ imageUrl={upward} /> )} - {settings.showConflicts && } + {settings.showConflicts && ( + + )} {settings.showSignalsStates && ( )} diff --git a/front/src/modules/simulationResult/components/SpaceTimeChart/helpers/__tests__/utils.spec.ts b/front/src/modules/simulationResult/components/SpaceTimeChart/helpers/__tests__/utils.spec.ts new file mode 100644 index 00000000000..7d579e523b4 --- /dev/null +++ b/front/src/modules/simulationResult/components/SpaceTimeChart/helpers/__tests__/utils.spec.ts @@ -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, + }); + }); +}); diff --git a/front/src/modules/simulationResult/components/SpaceTimeChart/helpers/utils.ts b/front/src/modules/simulationResult/components/SpaceTimeChart/helpers/utils.ts new file mode 100644 index 00000000000..dc010f8e9cf --- /dev/null +++ b/front/src/modules/simulationResult/components/SpaceTimeChart/helpers/utils.ts @@ -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; diff --git a/front/src/modules/simulationResult/types.ts b/front/src/modules/simulationResult/types.ts index 32654dd8ede..658fd105329 100644 --- a/front/src/modules/simulationResult/types.ts +++ b/front/src/modules/simulationResult/types.ts @@ -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'