Skip to content

Commit

Permalink
ui-trackoccupancydiagram: tracks layer
Browse files Browse the repository at this point in the history
  - create drawTrack
  - create drawTracks
  - create drawTick
  - add track background
  - use Space time context

Co-authored-by: Yohh <durandyohan@zaclys.net>
Signed-off-by: Uriel Sautron <uriel.sautron@gmail.com>
  • Loading branch information
Uriel-Sautron and Yohh committed Nov 27, 2024
1 parent 2c1f5c6 commit b5586e5
Show file tree
Hide file tree
Showing 12 changed files with 495 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Meta, StoryObj } from '@storybook/react';

import { KebabHorizontal } from '../../../ui-icons/src/index';
import TimeCaptions from '../../../ui-spacetimechart/src/components/TimeCaptions';
import { useCanvas } from '../../../ui-spacetimechart/src/hooks/useCanvas';
import { useCanvas, useDraw } from '../../../ui-spacetimechart/src/hooks/useCanvas';
import { useMouseTracking } from '../../../ui-spacetimechart/src/hooks/useMouseTracking';
import { useSize } from '../../../ui-spacetimechart/src/hooks/useSize';
import { DEFAULT_THEME } from '../../../ui-spacetimechart/src/lib/consts';
Expand Down Expand Up @@ -54,6 +54,8 @@ const TrackOccupancyDiagram = ({
const spaceOrigin = 0;
const [root, setRoot] = useState<HTMLDivElement | null>(null);
const { width, height } = useSize(root);
const [canvasesRoot, setCanvasesRoot] = useState<HTMLDivElement | null>(null);
const { width: trackOccupancyWidth, height: trackOccupancyHeight } = useSize(canvasesRoot);
const timeOrigin = +new Date('2024/04/02');
const timeScale = 60000 / xZoomLevel;
const swapAxis = undefined;
Expand Down Expand Up @@ -153,6 +155,8 @@ const TrackOccupancyDiagram = ({
fingerprint,
width,
height,
trackOccupancyHeight,
trackOccupancyWidth,
getTimePixel,
getSpacePixel,
getPoint,
Expand All @@ -163,6 +167,8 @@ const TrackOccupancyDiagram = ({
resetPickingElements,
registerPickingElement,
operationalPoints,
tracks,
occupancyZones: zones,
spaceOrigin,
spaceScaleTree,
timeOrigin,
Expand All @@ -181,10 +187,11 @@ const TrackOccupancyDiagram = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fingerprint]);

const [canvasesRoot, setCanvasesRoot] = useState<HTMLDivElement | null>(null);
const [spaceTicksRoot, setSpaceTicksRoot] = useState<HTMLDivElement | null>(null);
const mouseState = useMouseTracking(root);
const { position } = mouseState;
const { canvasContext } = useCanvas(canvasesRoot, contextState, position);
const { canvasContext: spaceTicksContext } = useCanvas(spaceTicksRoot, contextState, position);

return (
<div
Expand Down Expand Up @@ -231,24 +238,21 @@ const TrackOccupancyDiagram = ({
style={{
width: 1224,
borderRadius: '0 0 10px 0',
position: 'relative',
}}
>
<TrackOccupancyCanvas
tracks={tracks}
zones={zones}
selectedTrain={null}
timeOrigin={timeOrigin}
timeScale={timeScale}
/>
<TrackOccupancyCanvas useDraw={useDraw} setCanvasesRoot={setCanvasesRoot} />
</div>
</div>
</div>
</CanvasContext.Provider>
<CanvasContext.Provider value={spaceTicksContext}>
<div
ref={setRoot}
className="relative"
style={{ marginLeft: 200, width: 1224, height: 33 }}
>
<div ref={setCanvasesRoot} className="absolute inset-0">
<div ref={setSpaceTicksRoot} className="absolute inset-0">
<TimeCaptions />
</div>
</div>
Expand Down
9 changes: 9 additions & 0 deletions ui-spacetimechart/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { type HTMLProps, type ReactNode } from 'react';

import type {
Track,
OccupancyZone,
} from '@osrd-project/ui-trackoccupancydiagram/dist/components/types';

// GLOBAL UTILITY TYPES:
export type Point = {
x: number;
Expand Down Expand Up @@ -233,6 +238,8 @@ export type SpaceTimeChartProps = {
export type SpaceTimeChartContextType = {
width: number;
height: number;
trackOccupancyWidth?: number;
trackOccupancyHeight?: number;

// Axis-swapping related data:
timeAxis: Axis;
Expand Down Expand Up @@ -265,6 +272,8 @@ export type SpaceTimeChartContextType = {

// Useful data:
operationalPoints: OperationalPoint[];
tracks?: Track[];
occupancyZones?: OccupancyZone[];

// Full theme:
theme: SpaceTimeChartTheme;
Expand Down
39 changes: 9 additions & 30 deletions ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,14 @@
import React, { useState } from 'react';
import React from 'react';

import OccupancyZonesLayer from './layers/OccupancyZonesLayer';
import TracksLayer from './layers/TracksLayer';
import type { Store, TrackOccupancyCanvasProps } from './types';

const TrackOccupancyCanvas = ({
tracks,
zones,
selectedTrain,
timeOrigin,
timeScale,
}: TrackOccupancyCanvasProps) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [store, setStore] = useState<Store>({
tracks,
zones,
selectedTrain,
timeOrigin,
timeScale,
ratio: 0,
offsetX: 0,
clientX: 0,
clientY: 0,
});

return (
<div id="track-occupancy-canvas" className="bg-white-100">
<TracksLayer />
<OccupancyZonesLayer />
</div>
);
};
import { type TrackOccupancyCanvasProps } from './types';

const TrackOccupancyCanvas = ({ useDraw, setCanvasesRoot }: TrackOccupancyCanvasProps) => (
<div id="track-occupancy-canvas" className="bg-white-100" ref={setCanvasesRoot}>
<TracksLayer useDraw={useDraw} />
<OccupancyZonesLayer />
</div>
);

export default TrackOccupancyCanvas;
1 change: 1 addition & 0 deletions ui-trackoccupancydiagram/src/components/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const TRACK_HEIGHT_CONTAINER = 73;
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { sum } from 'lodash';

import { timeScaleSample } from '../../../sample/timeScale';
import { TRACK_HEIGHT_CONTAINER } from '../../consts';
type DrawTrackProps = {
ctx: CanvasRenderingContext2D;
width: number;
getTimePixel: (time: number) => number;
};

const TICKS_PATTERN = {
MINUTE: [2, 9, 2],
FIVE_MINUTES: [6, 9, 6],
QUARTER_HOUR: [2, 2, 6, 9, 6, 2, 2],
HALF_HOUR: [2, 2, 2, 2, 6, 9, 6, 2, 2, 2, 2],
HOUR: [16, 9, 16],
};

const drawRails = ({
xStart,
yStart,
width,
stroke = '#D3D1CF',
ctx,
}: {
xStart: number;
yStart: number;
width: number;
stroke?: string;
ctx: CanvasRenderingContext2D;
}) => {
ctx.clearRect(xStart, yStart, width, 9);
ctx.beginPath();
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.strokeStyle = stroke;
ctx.lineWidth = 1;
ctx.rect(xStart, yStart, width, 8);
ctx.fill();
ctx.stroke();
};

const drawTick = ({
ctx,
xStart,
yStart,
ticks,
stroke,
}: {
ctx: CanvasRenderingContext2D;
xStart: number;
yStart: number;
ticks: number[];
stroke: string;
}) => {
ctx.beginPath();
ctx.setLineDash(ticks);
ctx.strokeStyle = stroke;
ctx.lineWidth = 1;
ctx.moveTo(xStart, yStart - sum(ticks) / 2);
ctx.lineTo(xStart, yStart + sum(ticks) / 2);
ctx.stroke();
};

export const drawTrack = ({ ctx, width, getTimePixel }: DrawTrackProps) => {
ctx.save();

drawRails({ xStart: -1, yStart: TRACK_HEIGHT_CONTAINER / 2 - 4, width: width + 1, ctx });

timeScaleSample.forEach((time) => {
const date = new Date(+time);
const minutes = date.getMinutes().toString().padStart(2, '0');

type TickPattern = keyof typeof TICKS_PATTERN;
let tickPattern: TickPattern = 'MINUTE';
switch (minutes) {
case '00':
tickPattern = 'HOUR';
break;
case '30':
tickPattern = 'HALF_HOUR';
break;
case '15':
case '45':
tickPattern = 'QUARTER_HOUR';
break;
case '05':
case '10':
case '20':
case '25':
case '35':
case '40':
case '50':
case '55':
tickPattern = 'FIVE_MINUTES';
break;
default:
tickPattern = 'MINUTE';
break;
}

drawTick({
ctx,
xStart: getTimePixel(+time),
yStart: TRACK_HEIGHT_CONTAINER / 2,
ticks: TICKS_PATTERN[tickPattern],
stroke: '#2170B9',
});
});

ctx.restore();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { drawTrack } from './drawTrack';
import { timeScaleSample } from '../../../sample/timeScale';
import { type Track } from '../../types';

export function getTimeToPixel(
timeOrigin: number,
pixelOffset: number,
timeScale: number
): (time: number) => number {
return (time: number) => pixelOffset + (time - timeOrigin) / timeScale;
}

const drawBackground = ({
ctx,
xStart,
width,
height,
switchBackground,
}: {
ctx: CanvasRenderingContext2D;
xStart: number;
width: number;
height: number;
switchBackground: boolean;
}) => {
if (xStart >= 0) {
ctx.clearRect(xStart, 0, width, height);
ctx.fillStyle = switchBackground ? 'rgba(243, 248, 253, 0.5)' : 'rgb(255, 255, 255)';
ctx.fillRect(xStart, 0, width, height);
}
};

type DrawTracksProps = {
ctx: CanvasRenderingContext2D;
width: number;
height: number;
tracks: Track[] | undefined;
getTimePixel: (time: number) => number;
};

export const drawTracks = ({ ctx, width, height, tracks, getTimePixel }: DrawTracksProps) => {
ctx.clearRect(0, 0, width, height);

let switchBackground = false;
timeScaleSample.forEach((time) => {
const date = new Date(+time);
const minutes = date.getMinutes().toString().padStart(2, '0');

switch (minutes) {
case '00':
switchBackground = !switchBackground;
drawBackground({ ctx, xStart: getTimePixel(+time), width, height, switchBackground });
break;
default:
return;
}
});

tracks?.forEach((_, index) => {
const trackTranslate = index === 0 ? 8 : 73;
ctx.translate(0, trackTranslate);
drawTrack({ ctx, width, getTimePixel });
});
};
30 changes: 30 additions & 0 deletions ui-trackoccupancydiagram/src/components/hooks/useCanvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect, useRef } from 'react';

import type { DrawFunctionParams, Store } from '../types';

type DrawFunction = (params: DrawFunctionParams) => void;

type UseCanvasParams = {
width: number;
height: number;
store: Store;
setStore?: React.Dispatch<React.SetStateAction<Store>>;
};

type UseCanvas = (
draw: DrawFunction,
params: UseCanvasParams
) => React.RefObject<HTMLCanvasElement>;

export const useCanvas: UseCanvas = (draw, { width, height, store, setStore }) => {
const canvas = useRef<HTMLCanvasElement>(null);

useEffect(() => {
const currentCanvas = canvas.current as HTMLCanvasElement;
const ctx = currentCanvas.getContext('2d') as CanvasRenderingContext2D;
draw({ ctx, width, height, store, setStore });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [width, height, store]);

return canvas;
};
29 changes: 27 additions & 2 deletions ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
import React from 'react';
import { useCallback } from 'react';

const TracksLayer = () => <canvas id="tracks-layer" />;
import {
type LayerType,
type DrawingFunction,
} from '@osrd-project/ui-spacetimechart/dist/lib/types';

Check warning on line 6 in ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx

View workflow job for this annotation

GitHub Actions / build

Unable to resolve path to module '@osrd-project/ui-spacetimechart/dist/lib/types'

Check warning on line 6 in ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx

View workflow job for this annotation

GitHub Actions / build

Unable to resolve path to module '@osrd-project/ui-spacetimechart/dist/lib/types'

import { drawTracks } from '../helpers/drawElements/drawTracks';

const TracksLayer = ({ useDraw }: { useDraw: (layer: LayerType, fn: DrawingFunction) => void }) => {
const drawingFunction = useCallback<DrawingFunction>(
(ctx, { getTimePixel, tracks, trackOccupancyWidth, trackOccupancyHeight }) => {
if (trackOccupancyHeight && trackOccupancyWidth)
drawTracks({
ctx,
width: trackOccupancyWidth,
height: trackOccupancyHeight,
tracks,
getTimePixel,
});
},
[]
);

useDraw('background', drawingFunction);

return null;
};

export default TracksLayer;
Loading

0 comments on commit b5586e5

Please sign in to comment.