Skip to content

Commit

Permalink
front: memoize interpolateOnTime
Browse files Browse the repository at this point in the history
  • Loading branch information
anisometropie committed Dec 13, 2023
1 parent ddfad14 commit d63bc5c
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* 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 { datetime2time, durationInSeconds, sec2time } from 'utils/timeManipulation';
// import/no-cycle is disabled because this func call will be removed by refacto
// eslint-disable-next-line
import {
Expand All @@ -15,9 +15,9 @@ import {
Train,
Stop,
SimulationD3Scale,
PositionsSpeedTimes,
} from 'reducers/osrdsimulation/types';
import {
AllListValues,
ChartAxes,
ListValues,
TIME,
Expand Down Expand Up @@ -280,69 +280,99 @@ 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.
const interpolateCache = new WeakMap();
export const interpolateOnTime = <
SimulationData extends Partial<{ [Key in ListValues[number]]: unknown }>,
Time extends Date | number
>(
dataSimulation: SimulationData | undefined,
keyValues: ChartAxes,
listValues: ListValues,
timePositionLocal: Time
listValues: ListValues
) => {
const xAxis = getAxis(keyValues, 'x', false);
if (dataSimulation) {
if (!interpolateCache.has(dataSimulation)) {
const newInterpolate = memoize(
specificInterpolateOnTime(dataSimulation, keyValues, listValues),
(timePosition: Date | number) => {
if (typeof timePosition === 'number') {
return timePosition;
}
return datetime2time(timePosition);
}
);
interpolateCache.set(dataSimulation, newInterpolate);
}
return interpolateCache.get(dataSimulation) as (time: Time) => PositionsSpeedTimes<Time>;
}
return specificInterpolateOnTime(dataSimulation, keyValues, listValues);
};

type ObjectWithXAxis = {
[K in XAxis]?: K extends 'time' ? Time : number;
} & {
[K in string]?: K extends XAxis ? never : unknown;
};
const bisect = d3.bisector<ObjectWithXAxis, Time>((d) => d[xAxis]! as Time).left;
const positionInterpolated = {} as Record<AllListValues, PositionSpeedTime<Time>>;

listValues.forEach((listValue) => {
let bisection: [ObjectWithXAxis, ObjectWithXAxis] | undefined;
if (dataSimulation && has(dataSimulation, listValue)) {
// not array of array
if (listValue === 'speed' || listValue === 'speeds') {
const currentData = dataSimulation[listValue] as ObjectWithXAxis[];
if (currentData[0]) {
const timeBisect = d3.bisector<ObjectWithXAxis, Time>((d) => d.time as Time).left;
const index = timeBisect(currentData, timePositionLocal, 1);
bisection = [currentData[index - 1], currentData[index]];
export const specificInterpolateOnTime =
<
SimulationData extends Partial<{ [Key in ListValues[number]]: unknown }>,
Time extends Date | number
>(
dataSimulation: SimulationData | undefined,
keyValues: ChartAxes,
listValues: ListValues
) =>
(timePositionLocal: Time) => {
const xAxis = getAxis(keyValues, 'x', false);

type ObjectWithXAxis = {
[K in XAxis]?: K extends 'time' ? Time : number;
} & {
[K in string]?: K extends XAxis ? never : unknown;
};
const bisect = d3.bisector<ObjectWithXAxis, Time>((d) => d[xAxis]! as Time).left;
const positionInterpolated = {} as PositionsSpeedTimes<Time>;

listValues.forEach((listValue) => {
let bisection: [ObjectWithXAxis, ObjectWithXAxis] | undefined;
if (dataSimulation && has(dataSimulation, listValue)) {
// not array of array
if (listValue === 'speed' || listValue === 'speeds') {
const currentData = dataSimulation[listValue] as ObjectWithXAxis[];
if (currentData[0]) {
const timeBisect = d3.bisector<ObjectWithXAxis, Time>((d) => d.time as Time).left;
const index = timeBisect(currentData, timePositionLocal, 1);
bisection = [currentData[index - 1], currentData[index]];
}
} else {
// Array of array
const currentData = dataSimulation[listValue] as ObjectWithXAxis[][];
currentData.forEach((section) => {
const index = bisect(section, timePositionLocal, 1);
const firstXValue = section[0] ? section[0][xAxis] : undefined;
if (index !== section.length && firstXValue && timePositionLocal >= firstXValue) {
bisection = [section[index - 1], section[index]];
}
});
}
} else {
// Array of array
const currentData = dataSimulation[listValue] as ObjectWithXAxis[][];
currentData.forEach((section) => {
const index = bisect(section, timePositionLocal, 1);
const firstXValue = section[0] ? section[0][xAxis] : undefined;
if (index !== section.length && firstXValue && timePositionLocal >= firstXValue) {
bisection = [section[index - 1], section[index]];
if (bisection && bisection[1] && bisection[1].time) {
const bisec0 = bisection[0];
const bisec1 = bisection[1];
if (bisec0.time && bisec0.position && bisec1.time && bisec1.position) {
const duration = Number(bisec1.time) - Number(bisec0.time);
const timePositionFromTime = Number(timePositionLocal) - Number(bisec0.time);
const proportion = timePositionFromTime / duration;
positionInterpolated[listValue] = {
position: d3.interpolateNumber(bisec0.position, bisec1.position)(proportion),
speed:
bisec0.speed && bisec1.speed
? d3.interpolateNumber(
bisec0.speed as number,
bisec1.speed as number
)(proportion) * 3.6
: NaN,
time: timePositionLocal,
};
}
});
}
if (bisection && bisection[1] && bisection[1].time) {
const bisec0 = bisection[0];
const bisec1 = bisection[1];
if (bisec0.time && bisec0.position && bisec1.time && bisec1.position) {
const duration = Number(bisec1.time) - Number(bisec0.time);
const timePositionFromTime = Number(timePositionLocal) - Number(bisec0.time);
const proportion = timePositionFromTime / duration;
positionInterpolated[listValue] = {
position: d3.interpolateNumber(bisec0.position, bisec1.position)(proportion),
speed:
bisec0.speed && bisec1.speed
? d3.interpolateNumber(bisec0.speed as number, bisec1.speed as number)(proportion) *
3.6
: NaN,
time: timePositionLocal,
};
}
}
}
});
return positionInterpolated;
};
});
return positionInterpolated;
};

// const resolver = <
// SimulationData extends Partial<{ [Key in ListValues[number]]: unknown }>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@ export class ChartSynchronizer {
const positionValues = interpolateOnTime(
currentTrainSimulation,
CHART_AXES.SPACE_TIME,
LIST_VALUES.SPACE_TIME,
this.timePosition
);
LIST_VALUES.SPACE_TIME
)(this.timePosition);
return positionValues;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ describe('interpolateOnTime', () => {
const result = interpolateOnTime(
dataSimulation,
CHART_AXES.SPACE_TIME,
LIST_VALUES.REGIME,
time
);
LIST_VALUES.REGIME
)(time);
expect(result).toStrictEqual({
head_positions: { position: 1870.9890488984856, speed: NaN, time },
tail_positions: { position: 1471.3290488984856, speed: NaN, time },
Expand All @@ -48,9 +47,8 @@ describe('interpolateOnTime', () => {
const result = interpolateOnTime(
dataSimulation,
CHART_AXES.SPACE_TIME,
LIST_VALUES.SPACE_TIME,
time
);
LIST_VALUES.SPACE_TIME
)(time);
expect(result).toStrictEqual({
headPosition: {
position: 16662.376939865702,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export const enableInteractivity = <

const mousemove = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!simulationIsPlaying) {
let immediatePositionsValuesForPointer: ReturnType<typeof interpolateOnTime>;
let immediatePositionsValuesForPointer: ReturnType<ReturnType<typeof interpolateOnTime>>;
let timePositionLocal: Date | null;
if (isSpaceTimeChart(keyValues)) {
// SpaceTimeChart
Expand All @@ -304,9 +304,8 @@ export const enableInteractivity = <
immediatePositionsValuesForPointer = interpolateOnTime(
selectedTrainData,
keyValues,
LIST_VALUES.SPACE_TIME,
timePositionLocal
);
LIST_VALUES.SPACE_TIME
)(timePositionLocal);
} else {
// SpeedSpaceChart or SpaceCurvesSlopesChart
const positionLocal = (
Expand All @@ -327,9 +326,8 @@ export const enableInteractivity = <
immediatePositionsValuesForPointer = interpolateOnTime(
selectedTrainData,
keyValues,
LIST_VALUES.SPACE_SPEED,
timePositionLocal
);
LIST_VALUES.SPACE_SPEED
)(timePositionLocal);

// GEV prepareData func multiply speeds by 3.6. We need to normalize that to make a convenitn pointer update
LIST_VALUES.SPACE_SPEED.forEach((name) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,8 @@ export function getSimulationHoverPositions(
const interpolation = interpolateOnTime(
trainRegime,
CHART_AXES.SPACE_TIME,
LIST_VALUES.REGIME,
timePosition
) as Record<string, PositionSpeedTime<Date>>;
LIST_VALUES.REGIME
)(timePosition) as Record<string, PositionSpeedTime<Date>>;
if (interpolation.head_positions && interpolation.speeds) {
concernedTrains.push({
...interpolation,
Expand Down

0 comments on commit d63bc5c

Please sign in to comment.