From 15dd538f86e9bf3d2bb620a7331566e33b4918bc Mon Sep 17 00:00:00 2001 From: Yohh Date: Wed, 20 Nov 2024 16:59:46 +0100 Subject: [PATCH 1/4] ui-trackoccupancydiagram: prepare base html elements and samples Co-authored-by: Uriel-Sautron Signed-off-by: Yohh --- storybook/postcss.config.cjs | 3 + .../TrackOccupancyDiagram.stories.tsx | 59 ++++++++++++++++++- .../occupancyZone.ts | 40 +++++++++++++ .../TrackOccupancyDiagramSamples/tracks.ts | 8 +++ storybook/tailwind.config.js | 6 ++ .../src/components/TrackOccupancyCanvas.tsx | 35 ++++++++++- .../components/TrackOccupancyManchette.tsx | 8 ++- .../src/components/consts.ts | 0 .../src/components/types.ts | 39 ++++++++++++ .../src/hooks/useCanvas.ts | 0 ui-trackoccupancydiagram/src/styles/main.css | 15 +++++ .../src/utils/canvasUtils.ts | 0 .../src/utils/manchetteUtils.ts | 0 13 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 storybook/postcss.config.cjs create mode 100644 storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZone.ts create mode 100644 storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts create mode 100644 storybook/tailwind.config.js create mode 100644 ui-trackoccupancydiagram/src/components/consts.ts create mode 100644 ui-trackoccupancydiagram/src/components/types.ts create mode 100644 ui-trackoccupancydiagram/src/hooks/useCanvas.ts create mode 100644 ui-trackoccupancydiagram/src/utils/canvasUtils.ts create mode 100644 ui-trackoccupancydiagram/src/utils/manchetteUtils.ts diff --git a/storybook/postcss.config.cjs b/storybook/postcss.config.cjs new file mode 100644 index 00000000..16c305a2 --- /dev/null +++ b/storybook/postcss.config.cjs @@ -0,0 +1,3 @@ +const generateBasePostcssConfig = require('../postcss-base.config.cjs'); + +module.exports = generateBasePostcssConfig(); diff --git a/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx b/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx index bf3a5a7f..3925d88f 100644 --- a/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx +++ b/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx @@ -2,15 +2,68 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import '@osrd-project/ui-core/dist/theme.css'; +import '@osrd-project/ui-trackoccupancydiagram/dist/theme.css'; + +import { KebabHorizontal } from '../../../ui-icons/src/index'; import { TrackOccupancyManchette, TrackOccupancyCanvas, } from '../../../ui-trackoccupancydiagram/src/index'; +import zones from '../samples/TrackOccupancyDiagramSamples/occupancyZone'; +import tracks from '../samples/TrackOccupancyDiagramSamples/tracks'; const TrackOccupancyDiagram = () => ( -
- - +
+
+
+ +
+
+
+ +
+
+ +
+
+
); diff --git a/storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZone.ts b/storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZone.ts new file mode 100644 index 00000000..96fe9366 --- /dev/null +++ b/storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZone.ts @@ -0,0 +1,40 @@ +const zones = [ + { + id: '1', + trackId: '1', + arrivalTrainName: 'train 1', + departureTrainName: 'train 2', + color: 'red', + arrivalTime: new Date(), + departureTime: new Date(), + }, + { + id: '2', + trackId: '2', + arrivalTrainName: 'train 2', + departureTrainName: 'train 3', + color: 'blue', + arrivalTime: new Date(), + departureTime: new Date(), + }, + { + id: '3', + trackId: '3', + arrivalTrainName: 'train 3', + departureTrainName: 'train 4', + color: 'green', + arrivalTime: new Date(), + departureTime: new Date(), + }, + { + id: '4', + trackId: '4', + arrivalTrainName: 'train 4', + departureTrainName: 'train 5', + color: 'yellow', + arrivalTime: new Date(), + departureTime: new Date(), + }, +]; + +export default zones; diff --git a/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts b/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts new file mode 100644 index 00000000..a6ed9517 --- /dev/null +++ b/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts @@ -0,0 +1,8 @@ +const tracks = [ + { id: '1', name: 'track 1', line: 'line 1' }, + { id: '2', name: 'track 2', line: 'line 1' }, + { id: '3', name: 'track 3', line: 'line 2' }, + { id: '4', name: 'track 4', line: 'line 2' }, +]; + +export default tracks; diff --git a/storybook/tailwind.config.js b/storybook/tailwind.config.js new file mode 100644 index 00000000..bec740ba --- /dev/null +++ b/storybook/tailwind.config.js @@ -0,0 +1,6 @@ +import osrdUiPreset from '../tailwind-preset.js'; +/** @type {import('tailwindcss').Config} */ +export default { + presets: [osrdUiPreset], + content: ['./stories/**/*.{js,jsx,ts,tsx}'], +}; diff --git a/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx b/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx index 1231e9d9..837dd4a9 100644 --- a/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx +++ b/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx @@ -1,5 +1,38 @@ import React from 'react'; -const TrackOccupancyCanvas = () =>
TrackOccupancyCanvas
; +import type { Store, TrackOccupancyCanvasProps } from './types'; + +const TrackOccupancyCanvas = ({ + zones, + selectedTrain, + timeOrigin, + timeScale, +}: TrackOccupancyCanvasProps) => { + const store: Store = { + zones, + selectedTrain, + timeOrigin, + timeScale, + ratio: 0, + offsetX: 0, + clientX: 0, + clientY: 0, + }; + + console.warn( + 'zones:', + zones, + 'selectedTrain:', + selectedTrain, + 'timeOrigin:', + timeOrigin, + 'timeScale:', + timeScale, + 'store:', + store + ); + + return
; +}; export default TrackOccupancyCanvas; diff --git a/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx b/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx index 5f3fdcb2..08271b53 100644 --- a/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx +++ b/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx @@ -1,5 +1,11 @@ import React from 'react'; -const TrackOccupancyManchette = () =>
TrackOccupancyManchette
; +import { type TrackOccupancyManchetteProps } from './types'; + +const TrackOccupancyManchette = ({ tracks }: TrackOccupancyManchetteProps) => { + console.warn('tracks:', tracks); + + return
; +}; export default TrackOccupancyManchette; diff --git a/ui-trackoccupancydiagram/src/components/consts.ts b/ui-trackoccupancydiagram/src/components/consts.ts new file mode 100644 index 00000000..e69de29b diff --git a/ui-trackoccupancydiagram/src/components/types.ts b/ui-trackoccupancydiagram/src/components/types.ts new file mode 100644 index 00000000..fd5c8611 --- /dev/null +++ b/ui-trackoccupancydiagram/src/components/types.ts @@ -0,0 +1,39 @@ +export type Track = { + id: string; + name: string; + line: string; +}; + +export type OccupancyZone = { + id: string; + trackId: string; + arrivalTrainName: string; + departureTrainName: string; + color: string; + originStation?: string; + destinationStation?: string; + arrivalTime: Date; + departureTime: Date; +}; + +export type TrackOccupancyCanvasProps = { + zones: OccupancyZone[]; + selectedTrain: string | null; + timeOrigin: number; // in ms + timeScale: number; // in ms/px +}; + +export type TrackOccupancyManchetteProps = { + tracks: Track[]; +}; + +export type Store = { + zones: OccupancyZone[]; + selectedTrain: string | null; + timeOrigin: number; // in ms + timeScale: number; // in ms/px + ratio: number; + offsetX: number; + clientX: number; + clientY: number; +}; diff --git a/ui-trackoccupancydiagram/src/hooks/useCanvas.ts b/ui-trackoccupancydiagram/src/hooks/useCanvas.ts new file mode 100644 index 00000000..e69de29b diff --git a/ui-trackoccupancydiagram/src/styles/main.css b/ui-trackoccupancydiagram/src/styles/main.css index a31e4441..6e9587c6 100644 --- a/ui-trackoccupancydiagram/src/styles/main.css +++ b/ui-trackoccupancydiagram/src/styles/main.css @@ -1,3 +1,18 @@ @import 'tailwindcss/base'; @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; + +#trackOccupancyManchette { + height: 100%; + width: 100%; + background-color: rgb(247 246 238); + border-radius: inherit; + box-shadow: inset -1px 0 0 0 rgba(0, 0, 0, 0.25); +} + +#trackOccupancyCanvas { + height: 100%; + width: 100%; + background-color: rgb(255 255 255); + border-radius: inherit; +} diff --git a/ui-trackoccupancydiagram/src/utils/canvasUtils.ts b/ui-trackoccupancydiagram/src/utils/canvasUtils.ts new file mode 100644 index 00000000..e69de29b diff --git a/ui-trackoccupancydiagram/src/utils/manchetteUtils.ts b/ui-trackoccupancydiagram/src/utils/manchetteUtils.ts new file mode 100644 index 00000000..e69de29b From e7e6f632941d9ae83d926f75714fc8d047f03bef Mon Sep 17 00:00:00 2001 From: Yohh Date: Thu, 21 Nov 2024 22:42:08 +0100 Subject: [PATCH 2/4] ui-trackoccupancydiagram: display tracks in TrackOccupancyManchette.tsx Co-authored-by: Uriel-Sautron Signed-off-by: Yohh --- .../TrackOccupancyDiagram.stories.tsx | 18 +++--- .../TrackOccupancyDiagramSamples/tracks.ts | 10 ++-- .../src/components/TrackOccupancyCanvas.tsx | 2 +- .../components/TrackOccupancyManchette.tsx | 18 ++++-- .../src/components/types.ts | 1 + ui-trackoccupancydiagram/src/styles/main.css | 57 +++++++++++++++++-- 6 files changed, 83 insertions(+), 23 deletions(-) diff --git a/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx b/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx index 3925d88f..1892d8db 100644 --- a/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx +++ b/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx @@ -2,9 +2,6 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import '@osrd-project/ui-core/dist/theme.css'; -import '@osrd-project/ui-trackoccupancydiagram/dist/theme.css'; - import { KebabHorizontal } from '../../../ui-icons/src/index'; import { TrackOccupancyManchette, @@ -13,6 +10,8 @@ import { import zones from '../samples/TrackOccupancyDiagramSamples/occupancyZone'; import tracks from '../samples/TrackOccupancyDiagramSamples/tracks'; +const X_ZOOM_LEVEL = 6; + const TrackOccupancyDiagram = () => (
( >
( width: '100%', paddingLeft: 16, borderRadius: '10px 10px 0 0', - boxShadow: 'inset 1px 0 0 0 rgb(255, 255, 255), inset 0 -1px 0 0 rgba(0, 0, 0, 0.25)', + boxShadow: 'inset 0 1px 0 0 rgb(255, 255, 255), inset 0 -1px 0 0 rgba(0, 0, 0, 0.25)', }} > @@ -46,7 +44,6 @@ const TrackOccupancyDiagram = () => (
(
- +
diff --git a/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts b/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts index a6ed9517..aec9cc84 100644 --- a/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts +++ b/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts @@ -1,8 +1,10 @@ const tracks = [ - { id: '1', name: 'track 1', line: 'line 1' }, - { id: '2', name: 'track 2', line: 'line 1' }, - { id: '3', name: 'track 3', line: 'line 2' }, - { id: '4', name: 'track 4', line: 'line 2' }, + { id: '1', name: '14', line: '123456' }, + { id: '2', name: '12', line: '123456' }, + { id: '3', name: '11', line: '123456' }, + { id: '4', name: '10', line: '123456' }, + { id: '5', name: '5', line: '123456' }, + { id: '6', name: '6', line: '123456' }, ]; export default tracks; diff --git a/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx b/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx index 837dd4a9..671853db 100644 --- a/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx +++ b/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx @@ -32,7 +32,7 @@ const TrackOccupancyCanvas = ({ store ); - return
; + return
; }; export default TrackOccupancyCanvas; diff --git a/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx b/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx index 08271b53..7d650a41 100644 --- a/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx +++ b/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx @@ -2,10 +2,18 @@ import React from 'react'; import { type TrackOccupancyManchetteProps } from './types'; -const TrackOccupancyManchette = ({ tracks }: TrackOccupancyManchetteProps) => { - console.warn('tracks:', tracks); - - return
; -}; +const TrackOccupancyManchette = ({ tracks }: TrackOccupancyManchetteProps) => ( +
+ {tracks.map((track) => ( +
+ {track.line} +
+
{track.name}
+
+
+
+ ))} +
+); export default TrackOccupancyManchette; diff --git a/ui-trackoccupancydiagram/src/components/types.ts b/ui-trackoccupancydiagram/src/components/types.ts index fd5c8611..6c46a194 100644 --- a/ui-trackoccupancydiagram/src/components/types.ts +++ b/ui-trackoccupancydiagram/src/components/types.ts @@ -17,6 +17,7 @@ export type OccupancyZone = { }; export type TrackOccupancyCanvasProps = { + tracks: Track[]; zones: OccupancyZone[]; selectedTrain: string | null; timeOrigin: number; // in ms diff --git a/ui-trackoccupancydiagram/src/styles/main.css b/ui-trackoccupancydiagram/src/styles/main.css index 6e9587c6..44315087 100644 --- a/ui-trackoccupancydiagram/src/styles/main.css +++ b/ui-trackoccupancydiagram/src/styles/main.css @@ -2,17 +2,64 @@ @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; -#trackOccupancyManchette { +#track-occupancy-manchette { height: 100%; width: 100%; - background-color: rgb(247 246 238); + padding: 8px 0; + @apply bg-ambientB-10; border-radius: inherit; - box-shadow: inset -1px 0 0 0 rgba(0, 0, 0, 0.25); + box-shadow: + inset 0 1px 0 0 rgb(255 255 255), + inset -1px 0 0 0 rgba(0, 0, 0, 0.25); } -#trackOccupancyCanvas { +#track-occupancy-canvas { height: 100%; width: 100%; - background-color: rgb(255 255 255); + @apply bg-white-100; border-radius: inherit; } + +.track { + height: 73px; + display: flex; + justify-content: flex-end; + align-items: center; + + &-line { + margin-right: 16px; + font-family: 'IBM Plex Sans'; + font-weight: 400; + font-size: 12px; + @apply text-grey-50; + } + + &-name { + height: 24px; + display: flex; + + &-detail { + @apply bg-grey-70; + border-radius: 4px; + box-shadow: + 0 0 0 2px rgb(255, 255, 255), + 0 0 0 2.5px rgb(182, 178, 175); + padding: 2px 6px; + font-weight: 600; + font-size: 14px; + line-height: 20px; + @apply text-white-100; + } + + &-rail { + align-self: center; + height: 9px; + width: 17px; + margin-left: 2.5px; + margin-right: 1px; + @apply bg-white-50; + border-top: solid 1px rgb(211, 209, 207); + border-bottom: solid 1px rgb(211, 209, 207); + } + } +} From cf65ddaa32137587df337ad38c7c420bc186e708 Mon Sep 17 00:00:00 2001 From: Yohh Date: Fri, 22 Nov 2024 13:20:34 +0100 Subject: [PATCH 3/4] ui-trackoccupancydiagram: add graduations layer - import TimeCaption.tsx from ui-spacetimechart - import and set contexts from ui-spacetimechart Co-authored-by: Uriel-Sautron Signed-off-by: Yohh --- .../TrackOccupancyDiagram.stories.tsx | 304 ++++++++++++++---- .../TrackOccupancyDiagramSamples/tracks.ts | 12 +- .../src/components/SpaceTimeChart.tsx | 4 + .../src/components/TimeCaptions.tsx | 25 +- ui-spacetimechart/src/lib/types.ts | 2 + .../src/components/TrackOccupancyCanvas.tsx | 29 +- .../components/TrackOccupancyManchette.tsx | 6 +- .../components/layers/OccupancyZonesLayer.tsx | 5 + .../src/components/layers/TracksLayer.tsx | 5 + .../src/components/types.ts | 1 + ui-trackoccupancydiagram/src/styles/main.css | 64 ++-- 11 files changed, 340 insertions(+), 117 deletions(-) create mode 100644 ui-trackoccupancydiagram/src/components/layers/OccupancyZonesLayer.tsx create mode 100644 ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx diff --git a/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx b/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx index 1892d8db..08ee9123 100644 --- a/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx +++ b/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx @@ -1,8 +1,29 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; 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 { useMouseTracking } from '../../../ui-spacetimechart/src/hooks/useMouseTracking'; +import { useSize } from '../../../ui-spacetimechart/src/hooks/useSize'; +import { DEFAULT_THEME } from '../../../ui-spacetimechart/src/lib/consts'; +import { CanvasContext, SpaceTimeChartContext } from '../../../ui-spacetimechart/src/lib/context'; +import { + type SpaceTimeChartContextType, + type PickingElement, + type SpaceTimeChartTheme, +} from '../../../ui-spacetimechart/src/lib/types'; +import { OPERATIONAL_POINTS } from '../../../ui-spacetimechart/src/stories/lib/paths'; +import { + getTimeToPixel, + getSpaceToPixel, + getDataToPoint, + getPixelToTime, + getPixelToSpace, + getPointToData, + spaceScalesToBinaryTree, +} from '../../../ui-spacetimechart/src/utils/scales'; import { TrackOccupancyManchette, TrackOccupancyCanvas, @@ -10,64 +31,232 @@ import { import zones from '../samples/TrackOccupancyDiagramSamples/occupancyZone'; import tracks from '../samples/TrackOccupancyDiagramSamples/tracks'; +type TrackOccupancyDiagramProps = { + xZoomLevel: number; + yZoomLevel: number; + xOffset: number; + yOffset: number; + spaceScaleType: 'linear' | 'proportional'; + emptyData: boolean; +}; + const X_ZOOM_LEVEL = 6; +const Y_ZOOM_LEVEL = 3; + +const TrackOccupancyDiagram = ({ + xZoomLevel, + yZoomLevel, + xOffset, + yOffset, + spaceScaleType, + emptyData, +}: TrackOccupancyDiagramProps) => { + const spaceOrigin = 0; + const [root, setRoot] = useState(null); + const { width, height } = useSize(root); + const timeOrigin = +new Date('2024/04/02'); + const timeScale = 60000 / xZoomLevel; + const swapAxis = undefined; + const hideGrid = undefined; + const hidePathsLabels = undefined; + const enableSnapping = undefined; + const showTicks = true; + const fullTheme: SpaceTimeChartTheme = { + ...DEFAULT_THEME, + background: 'transparent', + timeGraduationsStyles: { + ...DEFAULT_THEME.timeGraduationsStyles, + 1: { ...DEFAULT_THEME.timeGraduationsStyles[1], color: 'transparent' }, + }, + }; + const operationalPoints = useMemo(() => (emptyData ? [] : OPERATIONAL_POINTS), [emptyData]); + const spaceScales = useMemo(() => { + if (emptyData) { + return []; + } + + return operationalPoints.slice(0, -1).map((point, i) => ({ + from: point.position, + to: operationalPoints[i + 1].position, + ...(spaceScaleType === 'linear' + ? { size: 50 * yZoomLevel } + : { coefficient: 150 / yZoomLevel }), + })); + }, [emptyData, operationalPoints, spaceScaleType, yZoomLevel]); + + const fingerprint = useMemo( + () => + JSON.stringify({ + width, + height, + spaceOrigin, + spaceScales, + timeOrigin, + timeScale, + xOffset, + yOffset, + swapAxis, + hideGrid, + hidePathsLabels, + showTicks, + }), + [ + width, + height, + spaceOrigin, + spaceScales, + timeOrigin, + timeScale, + xOffset, + yOffset, + swapAxis, + hideGrid, + hidePathsLabels, + showTicks, + ] + ); + + const contextState: SpaceTimeChartContextType = useMemo(() => { + const spaceScaleTree = spaceScalesToBinaryTree(spaceOrigin, spaceScales); + const timeAxis = !swapAxis ? 'x' : 'y'; + const spaceAxis = !swapAxis ? 'y' : 'x'; -const TrackOccupancyDiagram = () => ( -
+ // Data translation helpers: + let timePixelOffset; + let spacePixelOffset; + + if (!swapAxis) { + timePixelOffset = xOffset; + spacePixelOffset = yOffset; + } else { + timePixelOffset = yOffset; + spacePixelOffset = xOffset; + } + + const getTimePixel = getTimeToPixel(timeOrigin, timePixelOffset, timeScale); + const getSpacePixel = getSpaceToPixel(spacePixelOffset, spaceScaleTree); + const getPoint = getDataToPoint(getTimePixel, getSpacePixel, timeAxis, spaceAxis); + const getTime = getPixelToTime(timeOrigin, timePixelOffset, timeScale); + const getSpace = getPixelToSpace(spaceOrigin, spacePixelOffset, spaceScaleTree); + const getData = getPointToData(getTime, getSpace, timeAxis, spaceAxis); + + const pickingElements: PickingElement[] = []; + const resetPickingElements = () => { + pickingElements.length = 0; + }; + const registerPickingElement = (element: PickingElement) => { + pickingElements.push(element); + return pickingElements.length - 1; + }; + + return { + fingerprint, + width, + height, + getTimePixel, + getSpacePixel, + getPoint, + getTime, + getSpace, + getData, + pickingElements, + resetPickingElements, + registerPickingElement, + operationalPoints, + spaceOrigin, + spaceScaleTree, + timeOrigin, + timeScale, + timePixelOffset, + spacePixelOffset, + timeAxis, + spaceAxis, + swapAxis: !!swapAxis, + enableSnapping: !!enableSnapping, + hideGrid: !!hideGrid, + hidePathsLabels: !!hidePathsLabels, + showTicks: !!showTicks, + theme: fullTheme, + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fingerprint]); + + const [canvasesRoot, setCanvasesRoot] = useState(null); + const mouseState = useMouseTracking(root); + const { position } = mouseState; + const { canvasContext } = useCanvas(canvasesRoot, contextState, position); + + return (
-
- -
-
-
- -
-
- -
-
+ + +
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
-
-); + ); +}; const meta: Meta = { title: 'TrackOccupancyDiagram/Rendering', @@ -79,9 +268,16 @@ const meta: Meta = { default: 'dark', }, }, - args: {}, + args: { + xZoomLevel: X_ZOOM_LEVEL, + yZoomLevel: Y_ZOOM_LEVEL, + xOffset: 0, + yOffset: 0, + spaceScaleType: 'linear', + emptyData: false, + }, - render: () => , + render: (args) => , tags: ['autodocs'], }; diff --git a/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts b/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts index aec9cc84..f9850050 100644 --- a/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts +++ b/storybook/stories/samples/TrackOccupancyDiagramSamples/tracks.ts @@ -1,10 +1,10 @@ const tracks = [ - { id: '1', name: '14', line: '123456' }, - { id: '2', name: '12', line: '123456' }, - { id: '3', name: '11', line: '123456' }, - { id: '4', name: '10', line: '123456' }, - { id: '5', name: '5', line: '123456' }, - { id: '6', name: '6', line: '123456' }, + { id: '1', name: 'EV', line: '123456' }, + { id: '2', name: '2', line: '456123' }, + { id: '3', name: '2bis', line: '135246' }, + { id: '4', name: 'Z', line: '654321' }, + { id: '5', name: '1bis', line: '615243' }, + { id: '6', name: '1', line: '523416' }, ]; export default tracks; diff --git a/ui-spacetimechart/src/components/SpaceTimeChart.tsx b/ui-spacetimechart/src/components/SpaceTimeChart.tsx index 8804c920..189da8e2 100644 --- a/ui-spacetimechart/src/components/SpaceTimeChart.tsx +++ b/ui-spacetimechart/src/components/SpaceTimeChart.tsx @@ -44,6 +44,7 @@ export const SpaceTimeChart = (props: SpaceTimeChartProps) => { enableSnapping, hideGrid, hidePathsLabels, + showTicks, theme, /* eslint-disable @typescript-eslint/no-unused-vars */ onPan, @@ -73,6 +74,7 @@ export const SpaceTimeChart = (props: SpaceTimeChartProps) => { swapAxis, hideGrid, hidePathsLabels, + showTicks, }), [ width, @@ -86,6 +88,7 @@ export const SpaceTimeChart = (props: SpaceTimeChartProps) => { swapAxis, hideGrid, hidePathsLabels, + showTicks, ] ); @@ -148,6 +151,7 @@ export const SpaceTimeChart = (props: SpaceTimeChartProps) => { enableSnapping: !!enableSnapping, hideGrid: !!hideGrid, hidePathsLabels: !!hidePathsLabels, + showTicks: !!showTicks, theme: fullTheme, }; // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/ui-spacetimechart/src/components/TimeCaptions.tsx b/ui-spacetimechart/src/components/TimeCaptions.tsx index 7ceb2338..6c3dad7c 100644 --- a/ui-spacetimechart/src/components/TimeCaptions.tsx +++ b/ui-spacetimechart/src/components/TimeCaptions.tsx @@ -51,6 +51,7 @@ const TimeCaptions = () => { timeCaptionsStyles, timeGraduationsStyles, }, + showTicks, } ) => { const timeAxisSize = !swapAxis ? width : height; @@ -96,15 +97,17 @@ const TimeCaptions = () => { // Render caption top border: ctx.strokeStyle = timeGraduationsStyles[1].color; ctx.lineWidth = timeGraduationsStyles[1].width; - ctx.beginPath(); - if (!swapAxis) { - ctx.moveTo(0, spaceAxisSize - CAPTION_SIZE); - ctx.lineTo(timeAxisSize, spaceAxisSize - CAPTION_SIZE); - } else { - ctx.moveTo(CAPTION_SIZE, 0); - ctx.lineTo(CAPTION_SIZE, timeAxisSize); + if (!showTicks) { + ctx.beginPath(); + if (!swapAxis) { + ctx.moveTo(0, spaceAxisSize - CAPTION_SIZE); + ctx.lineTo(timeAxisSize, spaceAxisSize - CAPTION_SIZE); + } else { + ctx.moveTo(CAPTION_SIZE, 0); + ctx.lineTo(CAPTION_SIZE, timeAxisSize); + } + ctx.stroke(); } - ctx.stroke(); // Render time captions: for (const t in labelMarks) { @@ -118,6 +121,12 @@ const TimeCaptions = () => { ctx.fillStyle = styles.color; ctx.font = `${styles.fontWeight || 'normal'} ${styles.font}`; if (!swapAxis) { + if (showTicks) { + ctx.strokeStyle = timeCaptionsStyles[1].color; + ctx.moveTo(getTimePixel(+t), spaceAxisSize - CAPTION_SIZE); + ctx.lineTo(getTimePixel(+t), +t % 180000 === 0 ? 8 : 4); + ctx.stroke(); + } ctx.fillText( text, getTimePixel(+t), diff --git a/ui-spacetimechart/src/lib/types.ts b/ui-spacetimechart/src/lib/types.ts index 860ecf73..f0b68d22 100644 --- a/ui-spacetimechart/src/lib/types.ts +++ b/ui-spacetimechart/src/lib/types.ts @@ -193,6 +193,7 @@ export type SpaceTimeChartProps = { // Additional options to show/hide context information: hideGrid?: boolean; hidePathsLabels?: boolean; + showTicks?: boolean; // Custom styles: theme?: Partial; @@ -272,4 +273,5 @@ export type SpaceTimeChartContextType = { enableSnapping: boolean; hideGrid: boolean; hidePathsLabels: boolean; + showTicks: boolean; }; diff --git a/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx b/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx index 671853db..dd131247 100644 --- a/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx +++ b/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx @@ -1,14 +1,19 @@ -import React from 'react'; +import React, { useState } 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) => { - const store: Store = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [store, setStore] = useState({ + tracks, zones, selectedTrain, timeOrigin, @@ -17,22 +22,14 @@ const TrackOccupancyCanvas = ({ offsetX: 0, clientX: 0, clientY: 0, - }; + }); - console.warn( - 'zones:', - zones, - 'selectedTrain:', - selectedTrain, - 'timeOrigin:', - timeOrigin, - 'timeScale:', - timeScale, - 'store:', - store + return ( +
+ + +
); - - return
; }; export default TrackOccupancyCanvas; diff --git a/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx b/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx index 7d650a41..df0e9067 100644 --- a/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx +++ b/ui-trackoccupancydiagram/src/components/TrackOccupancyManchette.tsx @@ -7,10 +7,8 @@ const TrackOccupancyManchette = ({ tracks }: TrackOccupancyManchetteProps) => ( {tracks.map((track) => (
{track.line} -
-
{track.name}
-
-
+
{track.name}
+
))}
diff --git a/ui-trackoccupancydiagram/src/components/layers/OccupancyZonesLayer.tsx b/ui-trackoccupancydiagram/src/components/layers/OccupancyZonesLayer.tsx new file mode 100644 index 00000000..31e6b0c2 --- /dev/null +++ b/ui-trackoccupancydiagram/src/components/layers/OccupancyZonesLayer.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +const OccupancyZonesLayer = () => ; + +export default OccupancyZonesLayer; diff --git a/ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx b/ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx new file mode 100644 index 00000000..416f81b9 --- /dev/null +++ b/ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +const TracksLayer = () => ; + +export default TracksLayer; diff --git a/ui-trackoccupancydiagram/src/components/types.ts b/ui-trackoccupancydiagram/src/components/types.ts index 6c46a194..3dffd822 100644 --- a/ui-trackoccupancydiagram/src/components/types.ts +++ b/ui-trackoccupancydiagram/src/components/types.ts @@ -29,6 +29,7 @@ export type TrackOccupancyManchetteProps = { }; export type Store = { + tracks: Track[]; zones: OccupancyZone[]; selectedTrain: string | null; timeOrigin: number; // in ms diff --git a/ui-trackoccupancydiagram/src/styles/main.css b/ui-trackoccupancydiagram/src/styles/main.css index 44315087..3d6f4957 100644 --- a/ui-trackoccupancydiagram/src/styles/main.css +++ b/ui-trackoccupancydiagram/src/styles/main.css @@ -2,22 +2,24 @@ @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; -#track-occupancy-manchette { +#track-occupancy-manchette, +#track-occupancy-canvas { height: 100%; width: 100%; + border-radius: inherit; +} + +#track-occupancy-manchette { padding: 8px 0; @apply bg-ambientB-10; - border-radius: inherit; box-shadow: inset 0 1px 0 0 rgb(255 255 255), inset -1px 0 0 0 rgba(0, 0, 0, 0.25); } #track-occupancy-canvas { - height: 100%; - width: 100%; @apply bg-white-100; - border-radius: inherit; + position: relative; } .track { @@ -36,30 +38,34 @@ &-name { height: 24px; - display: flex; - - &-detail { - @apply bg-grey-70; - border-radius: 4px; - box-shadow: - 0 0 0 2px rgb(255, 255, 255), - 0 0 0 2.5px rgb(182, 178, 175); - padding: 2px 6px; - font-weight: 600; - font-size: 14px; - line-height: 20px; - @apply text-white-100; - } + @apply bg-grey-70; + border-radius: 4px; + box-shadow: + 0 0 0 2px rgb(255, 255, 255), + 0 0 0 2.5px rgb(182, 178, 175); + padding: 2px 6px; + font-weight: 600; + font-size: 14px; + line-height: 20px; + @apply text-white-100; + } - &-rail { - align-self: center; - height: 9px; - width: 17px; - margin-left: 2.5px; - margin-right: 1px; - @apply bg-white-50; - border-top: solid 1px rgb(211, 209, 207); - border-bottom: solid 1px rgb(211, 209, 207); - } + &-rail { + align-self: center; + height: 9px; + width: 17px; + margin-left: 2.5px; + margin-right: 1px; + @apply bg-white-50; + border-top: solid 1px rgb(211, 209, 207); + border-bottom: solid 1px rgb(211, 209, 207); } } + +#tracks-layer, +#occupancy-zones-layer { + height: 100%; + width: 100%; + position: absolute; + padding: 8px 0; +} From 6c850f13d5cdc4295704d943dff00074642ff93d Mon Sep 17 00:00:00 2001 From: Uriel Sautron Date: Mon, 25 Nov 2024 15:16:04 +0100 Subject: [PATCH 4/4] ui-trackoccupancydiagram: tracks layer - create drawTrack - create drawTracks - create drawTick - add track background - use Space time context Co-authored-by: Yohh Signed-off-by: Uriel Sautron --- .../TrackOccupancyDiagram.stories.tsx | 72 +++--- .../{occupancyZone.ts => occupancyZones.ts} | 4 +- ui-spacetimechart/src/lib/types.ts | 9 + .../src/components/TrackOccupancyCanvas.tsx | 39 +--- .../src/components/consts.ts | 1 + .../helpers/drawElements/drawTrack.ts | 111 +++++++++ .../helpers/drawElements/drawTracks.ts | 64 ++++++ .../src/components/layers/TracksLayer.tsx | 29 ++- .../src/components/types.ts | 24 +- .../src/hooks/useCanvas.ts | 0 .../src/sample/timeScale.ts | 211 ++++++++++++++++++ ui-trackoccupancydiagram/src/styles/main.css | 5 +- 12 files changed, 483 insertions(+), 86 deletions(-) rename storybook/stories/samples/TrackOccupancyDiagramSamples/{occupancyZone.ts => occupancyZones.ts} (93%) create mode 100644 ui-trackoccupancydiagram/src/components/helpers/drawElements/drawTrack.ts create mode 100644 ui-trackoccupancydiagram/src/components/helpers/drawElements/drawTracks.ts delete mode 100644 ui-trackoccupancydiagram/src/hooks/useCanvas.ts create mode 100644 ui-trackoccupancydiagram/src/sample/timeScale.ts diff --git a/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx b/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx index 08ee9123..accfaa8a 100644 --- a/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx +++ b/storybook/stories/TrackOccupancyDiagram/TrackOccupancyDiagram.stories.tsx @@ -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'; @@ -28,7 +28,7 @@ import { TrackOccupancyManchette, TrackOccupancyCanvas, } from '../../../ui-trackoccupancydiagram/src/index'; -import zones from '../samples/TrackOccupancyDiagramSamples/occupancyZone'; +import occupancyZones from '../samples/TrackOccupancyDiagramSamples/occupancyZones'; import tracks from '../samples/TrackOccupancyDiagramSamples/tracks'; type TrackOccupancyDiagramProps = { @@ -54,6 +54,8 @@ const TrackOccupancyDiagram = ({ const spaceOrigin = 0; const [root, setRoot] = useState(null); const { width, height } = useSize(root); + const [canvasesRoot, setCanvasesRoot] = useState(null); + const { width: trackOccupancyWidth, height: trackOccupancyHeight } = useSize(canvasesRoot); const timeOrigin = +new Date('2024/04/02'); const timeScale = 60000 / xZoomLevel; const swapAxis = undefined; @@ -116,6 +118,7 @@ const TrackOccupancyDiagram = ({ ] ); + // TODO: when occupancyZones layer and zoom/pan are implemented, clean all unneeded variables from contextState, variables declared before contextState, and props. If needed, create a new context type. const contextState: SpaceTimeChartContextType = useMemo(() => { const spaceScaleTree = spaceScalesToBinaryTree(spaceOrigin, spaceScales); const timeAxis = !swapAxis ? 'x' : 'y'; @@ -153,6 +156,8 @@ const TrackOccupancyDiagram = ({ fingerprint, width, height, + trackOccupancyHeight, + trackOccupancyWidth, getTimePixel, getSpacePixel, getPoint, @@ -163,6 +168,8 @@ const TrackOccupancyDiagram = ({ resetPickingElements, registerPickingElement, operationalPoints, + tracks, + occupancyZones, spaceOrigin, spaceScaleTree, timeOrigin, @@ -181,10 +188,11 @@ const TrackOccupancyDiagram = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [fingerprint]); - const [canvasesRoot, setCanvasesRoot] = useState(null); + const [spaceTicksRoot, setSpaceTicksRoot] = useState(null); const mouseState = useMouseTracking(root); const { position } = mouseState; const { canvasContext } = useCanvas(canvasesRoot, contextState, position); + const { canvasContext: spaceTicksContext } = useCanvas(spaceTicksRoot, contextState, position); return (
- +
+ +
+
- +
-
-
- -
+
- +
-
+
+
+
-
+
diff --git a/storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZone.ts b/storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZones.ts similarity index 93% rename from storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZone.ts rename to storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZones.ts index 96fe9366..b2aba01f 100644 --- a/storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZone.ts +++ b/storybook/stories/samples/TrackOccupancyDiagramSamples/occupancyZones.ts @@ -1,4 +1,4 @@ -const zones = [ +const OccupancyZones = [ { id: '1', trackId: '1', @@ -37,4 +37,4 @@ const zones = [ }, ]; -export default zones; +export default OccupancyZones; diff --git a/ui-spacetimechart/src/lib/types.ts b/ui-spacetimechart/src/lib/types.ts index f0b68d22..0154e52a 100644 --- a/ui-spacetimechart/src/lib/types.ts +++ b/ui-spacetimechart/src/lib/types.ts @@ -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; @@ -233,6 +238,8 @@ export type SpaceTimeChartProps = { export type SpaceTimeChartContextType = { width: number; height: number; + trackOccupancyWidth?: number; + trackOccupancyHeight?: number; // Axis-swapping related data: timeAxis: Axis; @@ -265,6 +272,8 @@ export type SpaceTimeChartContextType = { // Useful data: operationalPoints: OperationalPoint[]; + tracks?: Track[]; + occupancyZones?: OccupancyZone[]; // Full theme: theme: SpaceTimeChartTheme; diff --git a/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx b/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx index dd131247..9c441afa 100644 --- a/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx +++ b/ui-trackoccupancydiagram/src/components/TrackOccupancyCanvas.tsx @@ -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({ - tracks, - zones, - selectedTrain, - timeOrigin, - timeScale, - ratio: 0, - offsetX: 0, - clientX: 0, - clientY: 0, - }); - - return ( -
- - -
- ); -}; +import { type TrackOccupancyCanvasProps } from './types'; + +const TrackOccupancyCanvas = ({ useDraw, setCanvasesRoot }: TrackOccupancyCanvasProps) => ( +
+ + +
+); export default TrackOccupancyCanvas; diff --git a/ui-trackoccupancydiagram/src/components/consts.ts b/ui-trackoccupancydiagram/src/components/consts.ts index e69de29b..03b88461 100644 --- a/ui-trackoccupancydiagram/src/components/consts.ts +++ b/ui-trackoccupancydiagram/src/components/consts.ts @@ -0,0 +1 @@ +export const TRACK_HEIGHT_CONTAINER = 73; diff --git a/ui-trackoccupancydiagram/src/components/helpers/drawElements/drawTrack.ts b/ui-trackoccupancydiagram/src/components/helpers/drawElements/drawTrack.ts new file mode 100644 index 00000000..c2dc0567 --- /dev/null +++ b/ui-trackoccupancydiagram/src/components/helpers/drawElements/drawTrack.ts @@ -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(); +}; diff --git a/ui-trackoccupancydiagram/src/components/helpers/drawElements/drawTracks.ts b/ui-trackoccupancydiagram/src/components/helpers/drawElements/drawTracks.ts new file mode 100644 index 00000000..eef12430 --- /dev/null +++ b/ui-trackoccupancydiagram/src/components/helpers/drawElements/drawTracks.ts @@ -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 }); + }); +}; diff --git a/ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx b/ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx index 416f81b9..cc6b06fd 100644 --- a/ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx +++ b/ui-trackoccupancydiagram/src/components/layers/TracksLayer.tsx @@ -1,5 +1,30 @@ -import React from 'react'; +import { useCallback } from 'react'; -const TracksLayer = () => ; +import { + type LayerType, + type DrawingFunction, +} from '@osrd-project/ui-spacetimechart/src/lib/types'; + +import { drawTracks } from '../helpers/drawElements/drawTracks'; + +const TracksLayer = ({ useDraw }: { useDraw: (layer: LayerType, fn: DrawingFunction) => void }) => { + const drawingFunction = useCallback( + (ctx, { getTimePixel, tracks, trackOccupancyWidth, trackOccupancyHeight }) => { + if (trackOccupancyHeight && trackOccupancyWidth) + drawTracks({ + ctx, + width: trackOccupancyWidth, + height: trackOccupancyHeight, + tracks, + getTimePixel, + }); + }, + [] + ); + + useDraw('background', drawingFunction); + + return null; +}; export default TracksLayer; diff --git a/ui-trackoccupancydiagram/src/components/types.ts b/ui-trackoccupancydiagram/src/components/types.ts index 3dffd822..00739e50 100644 --- a/ui-trackoccupancydiagram/src/components/types.ts +++ b/ui-trackoccupancydiagram/src/components/types.ts @@ -1,3 +1,8 @@ +import { + type DrawingFunction, + type LayerType, +} from '@osrd-project/ui-spacetimechart/src/lib/types'; + export type Track = { id: string; name: string; @@ -17,25 +22,10 @@ export type OccupancyZone = { }; export type TrackOccupancyCanvasProps = { - tracks: Track[]; - zones: OccupancyZone[]; - selectedTrain: string | null; - timeOrigin: number; // in ms - timeScale: number; // in ms/px + useDraw: (layer: LayerType, fn: DrawingFunction) => void; + setCanvasesRoot: (root: HTMLDivElement | null) => void; }; export type TrackOccupancyManchetteProps = { tracks: Track[]; }; - -export type Store = { - tracks: Track[]; - zones: OccupancyZone[]; - selectedTrain: string | null; - timeOrigin: number; // in ms - timeScale: number; // in ms/px - ratio: number; - offsetX: number; - clientX: number; - clientY: number; -}; diff --git a/ui-trackoccupancydiagram/src/hooks/useCanvas.ts b/ui-trackoccupancydiagram/src/hooks/useCanvas.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/ui-trackoccupancydiagram/src/sample/timeScale.ts b/ui-trackoccupancydiagram/src/sample/timeScale.ts new file mode 100644 index 00000000..c4e0ad0a --- /dev/null +++ b/ui-trackoccupancydiagram/src/sample/timeScale.ts @@ -0,0 +1,211 @@ +export const timeScaleSample = [ + '1712008800000', + '1712008860000', + '1712008920000', + '1712008980000', + '1712009040000', + '1712009100000', + '1712009160000', + '1712009220000', + '1712009280000', + '1712009340000', + '1712009400000', + '1712009460000', + '1712009520000', + '1712009580000', + '1712009640000', + '1712009700000', + '1712009760000', + '1712009820000', + '1712009880000', + '1712009940000', + '1712010000000', + '1712010060000', + '1712010120000', + '1712010180000', + '1712010240000', + '1712010300000', + '1712010360000', + '1712010420000', + '1712010480000', + '1712010540000', + '1712010600000', + '1712010660000', + '1712010720000', + '1712010780000', + '1712010840000', + '1712010900000', + '1712010960000', + '1712011020000', + '1712011080000', + '1712011140000', + '1712011200000', + '1712011260000', + '1712011320000', + '1712011380000', + '1712011440000', + '1712011500000', + '1712011560000', + '1712011620000', + '1712011680000', + '1712011740000', + '1712011800000', + '1712011860000', + '1712011920000', + '1712011980000', + '1712012040000', + '1712012100000', + '1712012160000', + '1712012220000', + '1712012280000', + '1712012340000', + '1712012400000', + '1712012460000', + '1712012520000', + '1712012580000', + '1712012640000', + '1712012700000', + '1712012760000', + '1712012820000', + '1712012880000', + '1712012940000', + '1712013000000', + '1712013060000', + '1712013120000', + '1712013180000', + '1712013240000', + '1712013300000', + '1712013360000', + '1712013420000', + '1712013480000', + '1712013540000', + '1712013600000', + '1712013660000', + '1712013720000', + '1712013780000', + '1712013840000', + '1712013900000', + '1712013960000', + '1712014020000', + '1712014080000', + '1712014140000', + '1712014200000', + '1712014260000', + '1712014320000', + '1712014380000', + '1712014440000', + '1712014500000', + '1712014560000', + '1712014620000', + '1712014680000', + '1712014740000', + '1712014800000', + '1712014860000', + '1712014920000', + '1712014980000', + '1712015040000', + '1712015100000', + '1712015160000', + '1712015220000', + '1712015280000', + '1712015340000', + '1712015400000', + '1712015460000', + '1712015520000', + '1712015580000', + '1712015640000', + '1712015700000', + '1712015760000', + '1712015820000', + '1712015880000', + '1712015940000', + '1712016000000', + '1712016060000', + '1712016120000', + '1712016180000', + '1712016240000', + '1712016300000', + '1712016360000', + '1712016420000', + '1712016480000', + '1712016540000', + '1712016600000', + '1712016660000', + '1712016720000', + '1712016780000', + '1712016840000', + '1712016900000', + '1712016960000', + '1712017020000', + '1712017080000', + '1712017140000', + '1712017200000', + '1712017260000', + '1712017320000', + '1712017380000', + '1712017440000', + '1712017500000', + '1712017560000', + '1712017620000', + '1712017680000', + '1712017740000', + '1712017800000', + '1712017860000', + '1712017920000', + '1712017980000', + '1712018040000', + '1712018100000', + '1712018160000', + '1712018220000', + '1712018280000', + '1712018340000', + '1712018400000', + '1712018460000', + '1712018520000', + '1712018580000', + '1712018640000', + '1712018700000', + '1712018760000', + '1712018820000', + '1712018880000', + '1712018940000', + '1712019000000', + '1712019060000', + '1712019120000', + '1712019180000', + '1712019240000', + '1712019300000', + '1712019360000', + '1712019420000', + '1712019480000', + '1712019540000', + '1712019600000', + '1712019660000', + '1712019720000', + '1712019780000', + '1712019840000', + '1712019900000', + '1712019960000', + '1712020020000', + '1712020080000', + '1712020140000', + '1712020200000', + '1712020260000', + '1712020320000', + '1712020380000', + '1712020440000', + '1712020500000', + '1712020560000', + '1712020620000', + '1712020680000', + '1712020740000', + '1712020800000', + '1712020860000', + '1712020920000', + '1712020980000', + '1712021040000', + '1712005200000', + '1711994400000', + '1711972800000', + '1711929600000', +]; diff --git a/ui-trackoccupancydiagram/src/styles/main.css b/ui-trackoccupancydiagram/src/styles/main.css index 3d6f4957..ba4c0c91 100644 --- a/ui-trackoccupancydiagram/src/styles/main.css +++ b/ui-trackoccupancydiagram/src/styles/main.css @@ -7,6 +7,10 @@ height: 100%; width: 100%; border-radius: inherit; + + > canvas { + border-radius: inherit; + } } #track-occupancy-manchette { @@ -67,5 +71,4 @@ height: 100%; width: 100%; position: absolute; - padding: 8px 0; }