Skip to content

Commit

Permalink
front: d3 performances
Browse files Browse the repository at this point in the history
  • Loading branch information
anisometropie committed Dec 12, 2023
1 parent a937195 commit dce87b1
Show file tree
Hide file tree
Showing 20 changed files with 272 additions and 234 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import {
getDisplaySimulation,
getIsUpdating,
getOsrdSimulation,
getPresentSimulation,
getSelectedTrain,
} from 'reducers/osrdsimulation/selectors';
Expand Down Expand Up @@ -49,7 +48,6 @@ export default function SimulationResults({
// TIMELINE DISABLED // const { chart } = useSelector(getOsrdSimulation);
const displaySimulation = useSelector(getDisplaySimulation);
const selectedTrain = useSelector(getSelectedTrain);
const { positionValues, timePosition } = useSelector(getOsrdSimulation);
const simulation = useSelector(getPresentSimulation);
const isUpdating = useSelector(getIsUpdating);

Expand Down Expand Up @@ -138,12 +136,7 @@ export default function SimulationResults({
>
<div className="row">
<div className="col-xl-4">
{selectedTrain && (
<TimeButtons
selectedTrain={selectedTrain as SimulationReport}
timePosition={timePosition}
/>
)}
{selectedTrain && <TimeButtons selectedTrain={selectedTrain as SimulationReport} />}
</div>
<div className="col-xl-8 d-flex justify-content-end mt-2 mt-xl-0">
<TrainDetails />
Expand All @@ -157,7 +150,6 @@ export default function SimulationResults({
chart={chart}
selectedTrainId={selectedTrain?.id || simulation.trains[0].id}
trains={simulation.trains as SimulationReport[]}
timePosition={timePosition}
/>
)} */}

Expand Down Expand Up @@ -192,9 +184,7 @@ export default function SimulationResults({
<SpeedSpaceChart
initialHeight={heightOfSpeedSpaceChart}
onSetChartBaseHeight={setHeightOfSpeedSpaceChart}
positionValues={positionValues}
selectedTrain={selectedTrain}
timePosition={timePosition}
trainRollingStock={selectedTrainRollingStock}
sharedXScaleDomain={positionScaleDomain}
setSharedXScaleDomain={setPositionScaleDomain}
Expand Down Expand Up @@ -230,8 +220,6 @@ export default function SimulationResults({
<SpaceCurvesSlopes
initialHeight={heightOfSpaceCurvesSlopesChart}
selectedTrain={selectedTrain}
timePosition={timePosition}
positionValues={positionValues}
sharedXScaleDomain={positionScaleDomain}
setSharedXScaleDomain={setPositionScaleDomain}
/>
Expand Down
9 changes: 1 addition & 8 deletions front/src/applications/stdcm/views/OSRDStdcmResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,13 @@ import SpaceTimeChart from 'modules/simulationResult/components/SpaceTimeChart/w
import SpeedSpaceChart from 'modules/simulationResult/components/SpeedSpaceChart/SpeedSpaceChart';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import {
getOsrdSimulation,
getPresentSimulation,
getSelectedTrain,
} from 'reducers/osrdsimulation/selectors';
import { getPresentSimulation, getSelectedTrain } from 'reducers/osrdsimulation/selectors';
import { AllowancesSettings } from 'reducers/osrdsimulation/types';
import { SimulationReport } from 'common/api/osrdEditoastApi';

const OSRDStcdmResults = () => {
const { t } = useTranslation(['translation', 'operationalStudies/manageTrainSchedule']);

const { positionValues, timePosition } = useSelector(getOsrdSimulation);
const selectedTrain = useSelector(getSelectedTrain);
const simulation = useSelector(getPresentSimulation);

Expand Down Expand Up @@ -83,8 +78,6 @@ const OSRDStcdmResults = () => {
initialHeight={450}
onSetChartBaseHeight={setSpeedSpaceChartHeight}
selectedTrain={selectedTrain}
positionValues={positionValues}
timePosition={timePosition}
/>
</div>
)}
Expand Down
25 changes: 18 additions & 7 deletions front/src/common/Map/WarpedMap/SimulationWarpedMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ import {
getSelectedProjection,
getSelectedTrain,
} from 'reducers/osrdsimulation/selectors';
import { Train } from 'reducers/osrdsimulation/types';
import type { PositionsSpeedTimes, Train } from 'reducers/osrdsimulation/types';
import { AsyncMemoState, getAsyncMemoData, useAsyncMemo } from 'utils/useAsyncMemo';
import { getSimulationHoverPositions } from 'modules/simulationResult/components/SimulationResultsMap/helpers';
import { clip } from 'utils/mapHelper';

import './SimulationWarpedMap.scss';
import { useChartSynchronizer } from 'modules/simulationResult/components/ChartHelpers/ChartSynchronizer';

const TIME_LABEL = 'Warping OSRD and OSM data';
const WIDTH = 300;
Expand Down Expand Up @@ -82,11 +83,21 @@ const SimulationWarpedMap: FC<{ collapsed?: boolean }> = ({ collapsed }) => {
const [mode, setMode] = useState<'manual' | 'auto'>('auto');
const {
chart,
positionValues,
timePosition,
allowancesSettings,
simulation: { present: simulation },
} = useSelector(getOsrdSimulation);
const [localTimePosition, setLocalTimePosition] = useState<Date>(new Date());
const [localPositionValues, setLocalPositionValues] = useState<PositionsSpeedTimes<Date>>(
{} as PositionsSpeedTimes<Date>
);
useChartSynchronizer(
(timePosition, positionValues) => {
setLocalTimePosition(timePosition);
setLocalPositionValues(positionValues);
},
'warped-map',
[]
);

// Boundaries handling (ie zoom sync):
const syncedBoundingBox: LngLatBoundsLike = useMemo(() => {
Expand Down Expand Up @@ -194,8 +205,8 @@ const SimulationWarpedMap: FC<{ collapsed?: boolean }> = ({ collapsed }) => {
return getSimulationHoverPositions(
path,
simulation,
timePosition,
positionValues,
localTimePosition,
localPositionValues,
selectedTrain?.id,
allowancesSettings
).map((position) => {
Expand All @@ -220,8 +231,8 @@ const SimulationWarpedMap: FC<{ collapsed?: boolean }> = ({ collapsed }) => {
}, [
itineraryState,
simulation,
timePosition,
positionValues,
localTimePosition,
localPositionValues,
selectedTrain,
allowancesSettings,
state,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import * as d3 from 'd3';
import { has, last } from 'lodash';
import { has, last, memoize } from 'lodash';

import { durationInSeconds, sec2time } from 'utils/timeManipulation';
// import/no-cycle is disabled because this func call will be removed by refacto
Expand Down Expand Up @@ -280,6 +280,7 @@ export const interpolateOnPosition = (

// Interpolation of cursor based on time position
// backend is giving only a few number of points. we need to interpolate values between these points.
// eslint-disable-next-line no-underscore-dangle
export const interpolateOnTime = <
SimulationData extends Partial<{ [Key in ListValues[number]]: unknown }>,
Time extends Date | number
Expand Down Expand Up @@ -344,6 +345,18 @@ export const interpolateOnTime = <
return positionInterpolated;
};

// const resolver = <
// SimulationData extends Partial<{ [Key in ListValues[number]]: unknown }>,
// Time extends Date | number
// >(
// dataSimulation: SimulationData | undefined,
// keyValues: ChartAxes,
// listValues: ListValues,
// timePositionLocal: Time
// ) => hash({ dataSimulation, keyValues, listValues, timePositionLocal });

// export const interpolateOnTime = memoize(_interpolateOnTime, resolver);

export const isSpaceTimeChart = (keyValues: ChartAxes) => keyValues[0] === TIME;

export function trainWithDepartureAndArrivalTimes(train: Train, dragOffset = 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useRef, useEffect } from 'react';

import type { PositionsSpeedTimes, SimulationTrain } from 'reducers/osrdsimulation/types';
import { store } from 'Store';
import { getOsrdSimulation } from 'reducers/osrdsimulation/selectors';
import { debounce } from 'lodash';
import { CHART_AXES, LIST_VALUES } from '../simulationResultsConsts';
import { interpolateOnTime } from './ChartHelpers';

/* eslint-disable import/prefer-default-export */
type PositionValues = PositionsSpeedTimes<Date>;
type Subscriber = (timePosition: Date, positionValues: PositionValues) => void;

export class ChartSynchronizer {
private subscribers: Map<string, Subscriber>;

timePosition: Date;

positionValues: PositionValues;

constructor() {
this.subscribers = new Map();
this.timePosition = new Date();
this.positionValues = {} as PositionValues;
}

subscribe(key: string, newSubscriber: Subscriber) {
this.subscribers.set(key, newSubscriber);
}

unsubscribe(key: string) {
this.subscribers.delete(key);
}

computePositionValues = debounce(() => {
const osrdSimulation = getOsrdSimulation(store.getState());
const currentTrainSimulation = osrdSimulation.consolidatedSimulation.find(
(consolidatedSimulation: SimulationTrain) =>
consolidatedSimulation.id === osrdSimulation.selectedTrainId
);
const positionValues = interpolateOnTime(
currentTrainSimulation,
CHART_AXES.SPACE_TIME,
LIST_VALUES.SPACE_TIME,
this.timePosition
);
return positionValues;
}, 10);

notifyAll(timePosition: Date) {
this.timePosition = timePosition;
this.positionValues = this.computePositionValues() as PositionValues;
this.subscribers.forEach((sub) => {
sub(timePosition, this.positionValues);
});
}
}

const hardSynchro = new ChartSynchronizer();

export function useChartSynchronizer(
subscriber?: Subscriber,
key?: string,
dependencies?: unknown[]
) {
const synchronizer = useRef(hardSynchro);
// create or update subscription
useEffect(() => {
console.log('update subscription');
if (subscriber && key && dependencies) {
synchronizer.current.subscribe(key, subscriber);
return () => {
console.log('unsubscribe');
synchronizer.current.unsubscribe(key);
};
}
return undefined;
}, dependencies);
return {
timePosition: synchronizer.current.timePosition,
positionValues: synchronizer.current.positionValues,
updateTimePosition: synchronizer.current.notifyAll.bind(synchronizer.current),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export const traceVerticalLine = (
.attr('y1', chart.y(linePosition))
.attr('y2', chart.y(linePosition));
} else {
// console.log(chart.x.domain(), chart.x.range(), linePosition, chart.x(linePosition));
chart.svg
.selectAll('#vertical-line')
.attr('x1', chart.x(linePosition))
Expand Down Expand Up @@ -259,7 +260,7 @@ export const enableInteractivity = <
rotate: boolean,
setChart: React.Dispatch<React.SetStateAction<T | undefined>>,
simulationIsPlaying: boolean,
dispatchUpdateTimePositionValues: (newTimePositionValues: Date) => void,
updateTimePosition: (newTimePositionValues: Date) => void,
chartDimensions: [Date, Date],
setSharedXScaleDomain?: React.Dispatch<React.SetStateAction<PositionScaleDomain>>,
additionalValues: ChartAxes[] = [] // more values to display on the same chart
Expand Down Expand Up @@ -288,15 +289,6 @@ export const enableInteractivity = <
(event) => (event.button === 0 || event.button === 1) && (event.ctrlKey || event.shiftKey)
);

// TODO: actually a number as we’re not in node.js
let debounceTimeoutId: NodeJS.Timeout;
function debounceUpdateTimePositionValues(timePositionLocal: Date, interval: number) {
clearTimeout(debounceTimeoutId);
debounceTimeoutId = setTimeout(() => {
dispatchUpdateTimePositionValues(timePositionLocal);
}, interval);
}

const mousemove = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!simulationIsPlaying) {
let immediatePositionsValuesForPointer: ReturnType<typeof interpolateOnTime>;
Expand Down Expand Up @@ -350,7 +342,7 @@ export const enableInteractivity = <
});
}

debounceUpdateTimePositionValues(timePositionLocal, 15);
updateTimePosition(timePositionLocal);
if (chart.svg && dateIsInRange(timePositionLocal, chartDimensions)) {
const verticalMark = pointer(event, event.currentTarget)[0];
const horizontalMark = pointer(event, event.currentTarget)[1];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import lineLength from '@turf/length';
import lineSlice from '@turf/line-slice';
import { keyBy } from 'lodash';

import { updateTimePositionValues } from 'reducers/osrdsimulation/actions';
import { getPresentSimulation, getSelectedTrain } from 'reducers/osrdsimulation/selectors';
import { Train } from 'reducers/osrdsimulation/types';
import { updateViewport, Viewport } from 'reducers/map';
Expand Down Expand Up @@ -61,6 +60,7 @@ import { SimulationReport, osrdEditoastApi } from 'common/api/osrdEditoastApi';
import Terrain from 'common/Map/Layers/Terrain';
import { getTerrain3DExaggeration } from 'reducers/map/selectors';
import { getRegimeKey, getSimulationHoverPositions } from './SimulationResultsMap/helpers';
import { useChartSynchronizer } from './ChartHelpers/ChartSynchronizer';

interface MapProps {
setExtViewport: (viewport: Viewport) => void;
Expand All @@ -72,9 +72,7 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {
const { viewport, mapSearchMarker, mapStyle, showOSM } = useSelector(
(state: RootState) => state.map
);
const { isPlaying, positionValues, timePosition, allowancesSettings } = useSelector(
(state: RootState) => state.osrdsimulation
);
const { isPlaying, allowancesSettings } = useSelector((state: RootState) => state.osrdsimulation);
const simulation = useSelector(getPresentSimulation);
const trains = useMemo(() => keyBy(simulation.trains, 'id'), [simulation.trains]);
const selectedTrain = useSelector(getSelectedTrain);
Expand All @@ -87,6 +85,25 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {
const [getPath] = osrdEditoastApi.useLazyGetPathfindingByIdQuery();
const dispatch = useDispatch();

const { updateTimePosition } = useChartSynchronizer(
(timePosition, positionValues) => {
if (timePosition && geojsonPath) {
const positions = getSimulationHoverPositions(
geojsonPath,
simulation,
timePosition,
positionValues,
selectedTrain?.id,
allowancesSettings
);
setTrainHoverPosition(positions.find((train) => train.isSelected));
setOtherTrainsHoverPosition(positions.filter((train) => !train.isSelected));
}
},
'simulation-result-map',
[]
);

const updateViewportChange = useCallback(
(value: Partial<Viewport>) => dispatch(updateViewport(value, undefined)),
[dispatch]
Expand Down Expand Up @@ -152,7 +169,7 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {
const positionLocal = lineLength(sliced, { units: 'kilometers' }) * 1000;
const timePositionLocal = interpolateOnPosition({ speed: train.speeds }, positionLocal);
if (timePositionLocal instanceof Date) {
dispatch(updateTimePositionValues(timePositionLocal));
updateTimePosition(timePositionLocal);
} else {
throw new Error(
'Map onFeatureHover, try to update TimePositionValue with incorrect imput'
Expand Down Expand Up @@ -206,22 +223,6 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedTrain]);

useEffect(() => {
if (timePosition && geojsonPath) {
const positions = getSimulationHoverPositions(
geojsonPath,
simulation,
timePosition,
positionValues,
selectedTrain?.id,
allowancesSettings
);
setTrainHoverPosition(positions.find((train) => train.isSelected));
setOtherTrainsHoverPosition(positions.filter((train) => !train.isSelected));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [timePosition]);

const handleLoadFinished = () => {
setMapLoaded(true);
};
Expand Down
Loading

0 comments on commit dce87b1

Please sign in to comment.