Skip to content

Commit

Permalink
ui-spacetimechart: work schedules on the space time chart
Browse files Browse the repository at this point in the history
Signed-off-by: Valentin Chanas <anisometropie@gmail.com>
  • Loading branch information
anisometropie committed Oct 17, 2024
1 parent 9949bbf commit 7022497
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions ui-spacetimechart/src/components/PatternRect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useCallback } from 'react';

import { useDraw } from '../hooks/useCanvas';
import { type DrawingFunction } from '../lib/types';

export type PatternRectProps = {
timeStart: Date;
timeEnd: Date;
spaceStart: number; // mm
spaceEnd: number; // mm
imageElement: HTMLImageElement;
};

/**
* draws a repeating pattern in the space time chart
*/
export const PatternRect = ({
timeStart,
timeEnd,
spaceStart,
spaceEnd,
imageElement,
}: PatternRectProps) => {
const drawRegion = useCallback<DrawingFunction>(
(ctx, { getSpacePixel, getTimePixel, spaceAxis }) => {
const timeStartPixel = getTimePixel(Number(timeStart));
const endTimePixel = getTimePixel(Number(timeEnd));
const spaceStartPixel = getSpacePixel(spaceStart);
const spaceEndPixel = getSpacePixel(spaceEnd);

const areaSpaceSize = spaceEndPixel - spaceStartPixel;
const areaTimeSize = endTimePixel - timeStartPixel;
if (!areaSpaceSize || !areaTimeSize) return;

const pattern = ctx.createPattern(imageElement, 'repeat');
if (!pattern) {
return;
}

ctx.save();
ctx.fillStyle = pattern;
if (spaceAxis === 'x') {
ctx.translate(spaceStartPixel, timeStartPixel);
ctx.fillRect(0, 0, areaSpaceSize, areaTimeSize);
} else {
ctx.translate(timeStartPixel, spaceStartPixel);
ctx.fillRect(0, 0, areaTimeSize, areaSpaceSize);
}
ctx.restore();
},
[timeStart, timeEnd, spaceStart, spaceEnd, imageElement]
);
useDraw('background', drawRegion);

return null;
};
45 changes: 45 additions & 0 deletions ui-spacetimechart/src/components/WorkScheduleLayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useEffect, useState } from 'react';

import { PatternRect } from './PatternRect';
import { type WorkSchedule } from '../types';

type WorkScheduleLayerProps = {
workSchedules: WorkSchedule[];
imageUrl: string;
};

/**
* Displays the workschedule projection on the Space time chart.
* A workschedule has a start time and an end time.
* It can contain several portions of a track occupied during this period.
* Each portion is represented by a rectangle region
*/
export const WorkScheduleLayer = ({ workSchedules, imageUrl }: WorkScheduleLayerProps) => {
const [imageElement, setImageElement] = useState<HTMLImageElement>();
useEffect(() => {
if (imageUrl) {
const newImage = new Image();
newImage.src = imageUrl;
newImage.onload = () => {
setImageElement(newImage);
};
}
}, [imageUrl]);

if (!imageElement) {
return null;
}

return workSchedules.flatMap((ws) =>
ws.spaceRanges.map(([spaceStart, spaceEnd]) => (
<PatternRect
key={`${ws.type}-${ws.timeStart}-${ws.timeEnd}-${spaceStart}-${spaceEnd}`}
timeStart={ws.timeStart}
timeEnd={ws.timeEnd}
spaceStart={spaceStart}
spaceEnd={spaceEnd}
imageElement={imageElement}
/>
))
);
};
4 changes: 4 additions & 0 deletions ui-spacetimechart/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.svg' {
const content: string;
export default content;
}
2 changes: 2 additions & 0 deletions ui-spacetimechart/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export * from './components/SpaceTimeChart';
export * from './components/PathLayer';
export * from './components/ConflictLayer';
export * from './components/OccupancyBlockLayer';
export * from './components/WorkScheduleLayer';
export * from './components/PatternRect';
139 changes: 139 additions & 0 deletions ui-spacetimechart/src/stories/work-schedules.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useState } from 'react';

import '@osrd-project/ui-core/dist/theme.css';

import type { Meta } from '@storybook/react';

import { OPERATIONAL_POINTS, PATHS } from './lib/paths';
import upward from '../assets/images/ScheduledMaintenanceUp.svg';
import { PathLayer } from '../components/PathLayer';
import { SpaceTimeChart } from '../components/SpaceTimeChart';
import { WorkScheduleLayer } from '../components/WorkScheduleLayer';
import { type Point, type PathData, type OperationalPoint } from '../lib/types';
import { type WorkSchedule } from '../types';
import { getDiff } from '../utils/vectors';

const SAMPLE_WORK_SCHEDULES: WorkSchedule[] = [
{
type: 'TRACK',
timeStart: new Date('2024-04-02T00:00:00Z'),
timeEnd: new Date('2024-04-02T00:15:00Z'),
spaceRanges: [
[20000, 35000],
[45000, 60000],
],
},
{
type: 'TRACK',
timeStart: new Date('2024-04-02T00:15:00Z'),
timeEnd: new Date('2024-04-02T01:00:00Z'),
spaceRanges: [
[80000, 100000],
[110000, 140000],
],
},
{
type: 'TRACK',
timeStart: new Date('2024-04-02T01:30:00Z'),
timeEnd: new Date('2024-04-02T02:30:00Z'),
spaceRanges: [[50000, 100000]],
},
];

const DEFAULT_HEIGHT = 550;

type WorkSchedulesWrapperProps = {
operationalPoints: OperationalPoint[];
paths: (PathData & { color: string })[];
workSchedules: WorkSchedule[];
};

const WorkSchedulesWrapper = ({
operationalPoints = [],
paths = [],
workSchedules,
}: WorkSchedulesWrapperProps) => {
const [state, setState] = useState<{
xOffset: number;
yOffset: number;
panning: null | { initialOffset: Point };
}>({
xOffset: -300,
yOffset: 0,
panning: null,
});
const simpleOperationalPoints = operationalPoints.map(({ id, position }) => ({
id,
label: id,
position,
}));
const spaceScale = [
{
from: 0,
to: 75000,
coefficient: 300,
},
];
return (
<div className="manchette-space-time-chart-wrapper" style={{ height: `${DEFAULT_HEIGHT}px` }}>
<SpaceTimeChart
className="inset-0 absolute h-full"
spaceOrigin={0}
xOffset={state.xOffset}
yOffset={state.yOffset}
timeOrigin={+new Date('2024/04/02')}
operationalPoints={simpleOperationalPoints}
timeScale={10000}
spaceScales={spaceScale}
onPan={({ initialPosition, position, isPanning }) => {
const diff = getDiff(initialPosition, position);
setState((s) => {
// Stop panning:
if (!isPanning) {
return { ...s, panning: null };
}
// Start panning:
else if (!s.panning) {
return {
...s,
panning: {
initialOffset: {
x: s.xOffset,
y: s.yOffset,
},
},
};
}
// Keep panning:
else {
const { initialOffset } = s.panning;
return {
...s,
xOffset: initialOffset.x + diff.x,
yOffset: initialOffset.y + diff.y,
};
}
});
}}
>
{paths.map((path) => (
<PathLayer key={path.id} path={path} color={path.color} />
))}
<WorkScheduleLayer workSchedules={workSchedules} imageUrl={upward} />
</SpaceTimeChart>
</div>
);
};

export default {
title: 'SpaceTimeChart/Workschedules',
component: WorkSchedulesWrapper,
} as Meta<typeof WorkSchedulesWrapper>;

export const Default = {
args: {
operationalPoints: OPERATIONAL_POINTS,
paths: PATHS.slice(2, 4),
workSchedules: SAMPLE_WORK_SCHEDULES,
},
};
6 changes: 6 additions & 0 deletions ui-spacetimechart/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type WorkSchedule = {
type: 'TRACK' | 'CATENARY';
timeStart: Date;
timeEnd: Date;
spaceRanges: [number, number][];
};

0 comments on commit 7022497

Please sign in to comment.