Skip to content

Commit

Permalink
speedspacechart: setup speed limit layer
Browse files Browse the repository at this point in the history
Signed-off-by: Florian Amsallem <florian.amsallem@gmail.com>
  • Loading branch information
flomonster committed Sep 27, 2024
1 parent e8013af commit c792bf3
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 74 deletions.
82 changes: 48 additions & 34 deletions ui-speedspacechart/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# ui-speedspacechart

The `ui-speedspacechart` package is part of the OSRD project, providing a specialized chart component designed to visualize speed and space data in a dynamic and interactive way. It leverages modern web technologies to offer a rich user experience for data analysis and presentation.
The `ui-speedspacechart` package is part of the OSRD project, providing a specialized chart
component designed to visualize speed and space data in a dynamic and interactive way. It leverages
modern web technologies to offer a rich user experience for data analysis and presentation.

## Features

- **Dynamic Visualization**: Offers a comprehensive view of speed and space data over time.
- **Interactive Controls**: Users can interact with the chart to explore different aspects of the data.
- **Customizable Appearance**: Supports theming and customization to match different UI requirements.
- **Interactive Controls**: Users can interact with the chart to explore different aspects of the
data.
- **Customizable Appearance**: Supports theming and customization to match different UI
requirements.
- **High Performance**: Optimized for performance, even with large datasets.

## Installation
Expand Down Expand Up @@ -41,57 +45,64 @@ export default App;

## Types

| Field Name | Type | Description |
|-|-|-|
| `speeds` | `LayerData<number>[]` | Array with numerical values representing speeds. |
| `ecoSpeeds` | `LayerData<number>[]` | Array with numerical values representing eco speeds. |
| `stops` | `LayerData<string>[]` | Array with string values representing stops. |
| `electrifications` | `LayerData<ElectrificationValues>[]` | Array with electrification values. |
| `slopes` | `LayerData<number>[]` | Array with numerical values representing slopes. |
| `electricalProfiles` | `LayerData<ElectricalPofilelValues>[]` (optional) | Optional array with electrical profile values. |
| `powerRestrictions` | `LayerData<PowerRestrictionValues>[]` (optional) | Optional array with power restriction values. |
| `speedLimitTags` | `LayerData<SpeedLimitTagValues>[]` (optional) | Optional array with speed limit tag values. |

LayerData<T> is a generic type that encapsulates a layer's data, where T is the type of the value contained in the layer. It is defined as follows:

| Field Name | Type | Description |
|-|-|-|
| Field Name | Type | Description |
| -------------------- | ------------------------------------------------- | ------------------------------------------------------------------------- |
| `speeds` | `LayerData<number>[]` | Array with numerical values representing speeds. |
| `ecoSpeeds` | `LayerData<number>[]` | Array with numerical values representing eco speeds. |
| `stops` | `LayerData<string>[]` | Array with string values representing stops. |
| `electrifications` | `LayerData<ElectrificationValues>[]` | Array with electrification values. |
| `slopes` | `LayerData<number>[]` | Array with numerical values representing slopes. |
| `trainLength` | `number` | The train length in meters. |
| `electricalProfiles` | `LayerData<ElectricalPofilelValues>[]` (optional) | Optional array with electrical profile values. |
| `powerRestrictions` | `LayerData<PowerRestrictionValues>[]` (optional) | Optional array with power restriction values. |
| `speedLimitTags` | `LayerData<SpeedLimitTagValues>[]` (optional) | Optional array with speed limit tag values. |
| `mrsp` | `ValuesAlongPath<SpeedLimit>[]` (optional) | Optional struct with most restricted speed profile values along the path. |

LayerData<T> is a generic type that encapsulates a layer's data, where T is the type of the value
contained in the layer. It is defined as follows:

| Field Name | Type | Description |
| ---------- | -------- | ------------------------------------------------------------------------------------- |
| `position` | `Object` | Object containing start and optionally end numbers representing the layer's position. |
| `value` | `T` | The value of the layer, where T can be any of the specific types mentioned above. |
| `value` | `T` | The value of the layer, where T can be any of the specific types mentioned above. |

Specific types for LayerData values:

- `PowerRestrictionValues`

- `powerRestriction`: string
- `handled`: boolean
- `powerRestriction`: string
- `handled`: boolean

- `ElectricalPofilelValues`

- `electricalProfile`: string
- `color?`: string (optional)
- `heightLevel?`: number (optional)
- `electricalProfile`: string
- `color?`: string (optional)
- `heightLevel?`: number (optional)

- `SpeedLimitTagValues`

- `tag`: string
- `color`: string
- `tag`: string
- `color`: string

- `ElectrificationValues`

- `type`: 'electrification' | 'neutral_section'
- `voltage?`: '1500V' | '25000V' (optional)
- `lowerPantograph?`: boolean (optional)
- `type`: 'electrification' | 'neutral_section'
- `voltage?`: '1500V' | '25000V' (optional)
- `lowerPantograph?`: boolean (optional)

Make sure to replace yourData and yourTranslations with your actual data and translation objects.

## Adding Translations

The `SpeedSpaceChart` component supports internationalization by allowing you to provide custom translations for various UI elements. This feature enables you to tailor the chart to different languages and locales, enhancing the user experience for a global audience.
The `SpeedSpaceChart` component supports internationalization by allowing you to provide custom
translations for various UI elements. This feature enables you to tailor the chart to different
languages and locales, enhancing the user experience for a global audience.

### Define Your Translations

Create an object that contains key-value pairs for each text string you wish to override. The keys should match the expected identifiers used by the `SpeedSpaceChart` component, and the values should be the translated strings.
Create an object that contains key-value pairs for each text string you wish to override. The keys
should match the expected identifiers used by the `SpeedSpaceChart` component, and the values should
be the translated strings.

Example translation object for French:

Expand All @@ -114,12 +125,15 @@ const yourTranslations = {

## Visualization

The `ui-speedspacechart` component can be observed and manipulated on Storybook at this address: [storybook/speedspacechart](https://openrailassociation.github.io/osrd-ui/?path=/story/speedspacechart-rendering--speed-space-chart-default)
The `ui-speedspacechart` component can be observed and manipulated on Storybook at this address:
[storybook/speedspacechart](https://openrailassociation.github.io/osrd-ui/?path=/story/speedspacechart-rendering--speed-space-chart-default)

## Contributing

Contributions are welcome! Please refer to the repository's main README.md and CODE_OF_CONDUCT.md for more details on how to contribute.
Contributions are welcome! Please refer to the repository's main README.md and CODE_OF_CONDUCT.md
for more details on how to contribute.

## License

This project is licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - see the LICENSE file for details.
This project is licensed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 - see
the LICENSE file for details.
3 changes: 2 additions & 1 deletion ui-speedspacechart/src/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ const store: Store = {
stops: [],
electrifications: [],
slopes: [],
mrsp: undefined,
powerRestrictions: [],
electricalProfiles: [],
speedLimitTags: [],
trainLength: 400,
ratioX: 1,
leftOffset: 0,
cursor: {
Expand All @@ -55,7 +57,6 @@ const store: Store = {
declivities: false,
speedLimitTags: false,
steps: true,
temporarySpeedLimits: false,
},
isSettingsPanelOpened: false,
};
Expand Down
32 changes: 11 additions & 21 deletions ui-speedspacechart/src/components/SpeedSpaceChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import SettingsPanel from './common/SettingsPanel';
import { LINEAR_LAYERS_HEIGHTS, MARGINS } from './const';
import { resetZoom } from './helpers/layersManager';
import {
StepsLayer,
AxisLayerY,
CurveLayer,
DeclivityLayer,
ElectricalProfileLayer,
FrontInteractivityLayer,
PowerRestrictionsLayer,
ReticleLayer,
SpeedLimitsLayer,
SpeedLimitTagsLayer,
StepsLayer,
TickLayerX,
TickLayerYRight,
} from './layers/index';
Expand Down Expand Up @@ -62,9 +63,11 @@ const SpeedSpaceChart = ({
stops: [],
electrifications: [],
slopes: [],
mrsp: undefined,
powerRestrictions: undefined,
electricalProfiles: undefined,
speedLimitTags: undefined,
trainLength: 0,
ratioX: 1,
leftOffset: 0,
cursor: {
Expand All @@ -82,7 +85,6 @@ const SpeedSpaceChart = ({
steps: true,
declivities: false,
speedLimits: false,
temporarySpeedLimits: false,
electricalProfiles: false,
powerRestrictions: false,
speedLimitTags: false,
Expand Down Expand Up @@ -122,25 +124,10 @@ const SpeedSpaceChart = ({
};

useEffect(() => {
const storeData = {
speeds: data.speeds || [],
ecoSpeeds: data.ecoSpeeds || [],
stops: data.stops || [],
electrifications: data.electrifications || [],
slopes: data.slopes || [],
electricalProfiles: data.electricalProfiles,
powerRestrictions: data.powerRestrictions,
speedLimitTags: data.speedLimitTags,
};

const { speeds, ecoSpeeds, stops, electrifications, slopes } = storeData;

if (speeds && ecoSpeeds && stops && electrifications && slopes) {
setStore((prev) => ({
...prev,
...storeData,
}));
}
setStore((prev) => ({
...prev,
...data,
}));
}, [data]);

useEffect(() => {
Expand Down Expand Up @@ -181,6 +168,9 @@ const SpeedSpaceChart = ({
<DeclivityLayer width={WIDTH_OFFSET} height={HEIGHT_OFFSET} store={store} />
)}
<CurveLayer width={WIDTH_OFFSET} height={HEIGHT_OFFSET} store={store} />
{store.layersDisplay.speedLimits && (
<SpeedLimitsLayer width={adjustedWidthRightAxis} height={height} store={store} />
)}
{store.layersDisplay.steps && (
<StepsLayer width={adjustedWidthRightAxis} height={height} store={store} />
)}
Expand Down
4 changes: 2 additions & 2 deletions ui-speedspacechart/src/components/common/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { X } from '@osrd-project/ui-icons';
import type { Store } from '../../types/chartTypes';
import { DETAILS_BOX_SELECTION, LAYERS_SELECTION } from '../const';
import type { SpeedSpaceChartProps } from '../SpeedSpaceChart';
import { checkLayerData, getAdaptiveHeight } from '../utils';
import { isLayerActive, getAdaptiveHeight } from '../utils';

const SETTINGS_PANEL_BASE_HEIGHT = 442;
const SPEEDSPACECHART_BASE_HEIGHT = 521.5;
Expand Down Expand Up @@ -64,7 +64,7 @@ const SettingsPanel = ({
<Checkbox
label={translations?.layersDisplay[selection] || selection}
checked={store.layersDisplay[selection]}
disabled={checkLayerData(store, selection)}
disabled={!isLayerActive(store, selection)}
onChange={() => {
setStore((prev) => ({
...prev,
Expand Down
2 changes: 1 addition & 1 deletion ui-speedspacechart/src/components/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ export const LAYERS_SELECTION: Array<keyof Store['layersDisplay']> = [
'steps',
'declivities',
'speedLimits',
'temporarySpeedLimits',
'electricalProfiles',
'powerRestrictions',
'speedLimitTags',
Expand All @@ -74,6 +73,7 @@ export const WHITE = chroma(255, 255, 255);
export const GREY_50 = chroma(121, 118, 113);
export const GREY_80 = chroma(49, 46, 43);
export const LIGHT_BLUE = chroma(33, 112, 185);
export const ERROR_60 = chroma(217, 28, 28);

/**
* COLOR_DICTIONARY maps specific colors to their corresponding secondary colors used for speed limit tags.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { DrawFunctionParams } from '../../../types/chartTypes';
import { ERROR_60, MARGINS } from '../../const';
import { clearCanvas, positionToPosX, maxPositionValue, maxSpeedValue } from '../../utils';

const { CURVE_MARGIN_TOP } = MARGINS;

export const drawSpeedLimits = ({ ctx, width, height, store }: DrawFunctionParams) => {
const { mrsp, trainLength, ratioX, leftOffset } = store;
const maxSpeed = maxSpeedValue(store);

clearCanvas(ctx, width, height);

ctx.save();
ctx.translate(leftOffset, 0);

const maxPosition = maxPositionValue(store);

// TODO: draw speed limits
ctx.beginPath();
ctx.lineWidth = 1;
ctx.lineCap = 'round';
ctx.strokeStyle = ERROR_60.hex();

const adjustedHeight = height - CURVE_MARGIN_TOP;
const y = height - (200 / maxSpeed) * adjustedHeight;
const xStart = positionToPosX(0, maxPosition, width, ratioX);
ctx.lineTo(xStart, y);
const xEnd = positionToPosX(maxPosition, maxPosition, width, ratioX);
ctx.lineTo(xEnd, y);
ctx.stroke();

ctx.restore();
};
27 changes: 27 additions & 0 deletions ui-speedspacechart/src/components/layers/SpeedLimitsLayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import type { Store } from '../../types/chartTypes';
import { drawSpeedLimits } from '../helpers/drawElements/speedLimits';
import { useCanvas } from '../hooks';

type SpeedLimitsLayerProps = {
width: number;
height: number;
store: Store;
};

const SpeedLimitsLayer = ({ width, height, store }: SpeedLimitsLayerProps) => {
const canvas = useCanvas(drawSpeedLimits, { width, height, store });

return (
<canvas
id="speed-limits-layer"
className="absolute rounded-t-xl"
ref={canvas}
width={width}
height={height}
/>
);
};

export default SpeedLimitsLayer;
1 change: 1 addition & 0 deletions ui-speedspacechart/src/components/layers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as StepsLayer } from './StepsLayer';
export { default as SpeedLimitsLayer } from './SpeedLimitsLayer';
export { default as CurveLayer } from './CurveLayer';
export { default as DeclivityLayer } from './DeclivityLayer';
export { default as ElectricalProfileLayer } from './ElectricalProfileLayer';
Expand Down
19 changes: 10 additions & 9 deletions ui-speedspacechart/src/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,17 @@ export const drawLinearLayerBackground = (
};

/**
* Check if an optional layer data is missing in the store.
* Optional datas : electricalProfiles, powerRestrictions, speedLimitTags
* Return wether a layer should be active or not.
* Depending on the available data, some layers should be disabled.
*/
export const checkLayerData = (store: Store, selection: (typeof LAYERS_SELECTION)[number]) =>
(selection === 'speedLimits' ||
selection === 'temporarySpeedLimits' ||
selection === 'electricalProfiles' ||
selection === 'powerRestrictions' ||
selection === 'speedLimitTags') &&
!store[selection];
export const isLayerActive = (store: Store, selection: (typeof LAYERS_SELECTION)[number]) => {
if (selection === 'speedLimits') return store['mrsp'];
if (selection === 'electricalProfiles') return store['electricalProfiles'];
if (selection === 'powerRestrictions') return store['powerRestrictions'];
if (selection === 'speedLimitTags') return store['speedLimitTags'];
return true;
};

/**
* Given a store including a list of slopes, return the position and value of min and max slopes
* @param store
Expand Down
5 changes: 4 additions & 1 deletion ui-speedspacechart/src/stories/assets/simulation_PMP_LM.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { Simulation } from '../../types/simulationTypes';

export const simulationPmpLm: Simulation = {
status: 'success',
mrsp: {
boundaries: [1000000, 1200000, 1800000, 3550000, 4500000],
values: [16.667, 27.778, 16.667, 27.778, 8.333, 83.333],
},
base: {
positions: [
0, 819, 3276, 7366, 29415, 52245, 98601, 159332, 207711, 357262, 1019000, 1142973, 1228000,
Expand Down
Loading

0 comments on commit c792bf3

Please sign in to comment.