mirror of
https://github.com/XFox111/MuiCharts.git
synced 2026-04-22 06:51:05 +03:00
- Implemented actual API for frontend
- Reorganized and refactored frontend project
This commit is contained in:
@@ -0,0 +1 @@
|
||||
VITE_API_URL=http://localhost:5152
|
||||
@@ -32,7 +32,7 @@ module.exports = {
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": "warn",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"@typescript-eslint/no-unused-vars": "warn"
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Use the official Node.js 20 image as the base image
|
||||
FROM node:20 as builder
|
||||
|
||||
ARG API_URL=http://localhost:5152
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
@@ -13,6 +15,8 @@ RUN yarn install
|
||||
# Copy the app source code to the working directory
|
||||
COPY . .
|
||||
|
||||
RUN echo "VITE_API_URL=${API_URL}" > .env.production
|
||||
|
||||
# Build the app
|
||||
RUN yarn build
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"@types/node": "^20.11.20",
|
||||
"@types/react": "^18.2.55",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
|
||||
+70
-23
@@ -4,9 +4,10 @@ import { useEffect, useState } from "react";
|
||||
import useStyles from "./App.styles";
|
||||
import ChartSkeleton from "./Components/ChartSkeleton";
|
||||
import TrackChart from "./Components/TrackChart";
|
||||
import IPoint from "./Data/IPoint";
|
||||
import ITrack from "./Data/ITrack";
|
||||
import LoadMockData from "./Data/LoadMockData";
|
||||
import ApiEndpoints from "./Data/Api/ApiEndpoints";
|
||||
import IPoint from "./Data/Api/Models/IPoint";
|
||||
import ITrack from "./Data/Api/Models/ITrack";
|
||||
import { GeneratePoints, GenerateTracks } from "./Data/MockDataGenerator";
|
||||
|
||||
const theme: Theme = createTheme();
|
||||
|
||||
@@ -20,25 +21,68 @@ function App(): JSX.Element
|
||||
const [zoom, setZoom] = useState<number[]>([0, 1]);
|
||||
const sx = useStyles();
|
||||
|
||||
const loadData = () =>
|
||||
{
|
||||
setLoading(true);
|
||||
console.log("Loading data...");
|
||||
const newData = LoadMockData(true);
|
||||
setData(newData);
|
||||
setZoom([0, Math.min(newData.tracks.length + 1, 20)]);
|
||||
|
||||
new Promise(resolve => setTimeout(resolve, 1000))
|
||||
.then(() => setLoading(false))
|
||||
.catch(console.error);
|
||||
};
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
loadData();
|
||||
void LoadDataAsync();
|
||||
}, []);
|
||||
|
||||
const handleZoomChange = (_: unknown, newValue: number | number[]) =>
|
||||
async function LoadDataAsync(): Promise<void>
|
||||
{
|
||||
setLoading(true);
|
||||
const { Points, Tracks } = new ApiEndpoints();
|
||||
|
||||
try
|
||||
{
|
||||
const tracks: ITrack[] = await Tracks.GetAllTracksAsync();
|
||||
let points: IPoint[] = [];
|
||||
|
||||
if (tracks.length > 0)
|
||||
points = await Points.GetPointsArrayAsync([
|
||||
...tracks.map(t => t.firstId),
|
||||
tracks[tracks.length - 1].secondId
|
||||
]);
|
||||
|
||||
setData({ tracks, points });
|
||||
setZoom([0, Math.min(tracks.length + 1, 20)]);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error("Failed to load data:", error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
console.log("Data loaded")
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function RecreateDataAsync(): Promise<void>
|
||||
{
|
||||
setLoading(true);
|
||||
const { Points, Tracks } = new ApiEndpoints();
|
||||
|
||||
try
|
||||
{
|
||||
const points: IPoint[] = GeneratePoints(120);
|
||||
const tracks: ITrack[] = GenerateTracks(points);
|
||||
|
||||
await Points.ImportPointsAsync(points);
|
||||
await Tracks.ImportTracksAsync(tracks);
|
||||
|
||||
await LoadDataAsync();
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error("Failed to recreate data:", error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
console.log("Data recreated")
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function OnZoomChange(_: unknown, newValue: number | number[]): void
|
||||
{
|
||||
const value: number[] = newValue as number[];
|
||||
|
||||
@@ -46,10 +90,13 @@ function App(): JSX.Element
|
||||
return;
|
||||
|
||||
if (value[1] - value[0] > 50)
|
||||
{
|
||||
setLoading(true);
|
||||
setTimeout(() => setLoading(false), 500);
|
||||
}
|
||||
|
||||
setZoom(value);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={ theme }>
|
||||
@@ -58,21 +105,21 @@ function App(): JSX.Element
|
||||
<Box sx={ sx.root }>
|
||||
<TrackChart
|
||||
tracks={ data.tracks } points={ data.points }
|
||||
zoom={ zoom } onProcessingComplete={ () => setLoading(false) } />
|
||||
zoom={ zoom } />
|
||||
|
||||
<Container sx={ sx.controls }>
|
||||
{ !isLoading &&
|
||||
<Slider
|
||||
min={ 0 } max={ data.tracks.length + 1 }
|
||||
defaultValue={ zoom } onChangeCommitted={ handleZoomChange }
|
||||
defaultValue={ zoom } onChangeCommitted={ OnZoomChange }
|
||||
valueLabelDisplay="auto" />
|
||||
}
|
||||
|
||||
<Button
|
||||
variant="contained" color="inherit" endIcon={ <RefreshIcon /> }
|
||||
onClick={ loadData } disabled={ isLoading }>
|
||||
onClick={ () => void RecreateDataAsync() } disabled={ isLoading }>
|
||||
|
||||
Refresh
|
||||
Recreate
|
||||
</Button>
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SxProps } from "@mui/system";
|
||||
import * as xc from "@mui/x-charts";
|
||||
import { useEffect, useState } from "react";
|
||||
import IPoint from "../Data/Api/Models/IPoint";
|
||||
import MaxSpeed from "../Data/Api/Models/MaxSpeed";
|
||||
import IChartPoint from "../Data/IChartPoint";
|
||||
import IPoint from "../Data/IPoint";
|
||||
import ITrack from "../Data/ITrack";
|
||||
import MaxSpeed from "../Data/MaxSpeed";
|
||||
import ITrack from "../Data/Api/Models/ITrack";
|
||||
import { TooltipProps, TracklineSeries } from "../Data/TrackChartDataProps";
|
||||
import CartesianGrid from "./CartesianGrid";
|
||||
import TrackLinePlot from "./TrackLinePlot";
|
||||
@@ -18,14 +18,12 @@ interface IProps
|
||||
points: IPoint[];
|
||||
/** The zoom levels (start, end). */
|
||||
zoom: number[];
|
||||
/** A callback for when the processing is complete. */
|
||||
onProcessingComplete?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A chart of the track.
|
||||
*/
|
||||
function TrackChart({ tracks, points, zoom, ...props }: IProps): JSX.Element
|
||||
function TrackChart({ tracks, points, zoom }: IProps): JSX.Element
|
||||
{
|
||||
const [dataset, setDataset] = useState<IChartPoint[]>([]);
|
||||
const [xTicks, setXTicks] = useState<number[]>([]);
|
||||
@@ -84,10 +82,7 @@ function TrackChart({ tracks, points, zoom, ...props }: IProps): JSX.Element
|
||||
setXTicks(data.map(i => i.distance));
|
||||
|
||||
console.warn("Reflow!");
|
||||
|
||||
setTimeout(() => props.onProcessingComplete?.(), 500);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tracks, points, props.onProcessingComplete]);
|
||||
}, [tracks, points]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SxProps } from "@mui/system";
|
||||
import { LinePlot, useDrawingArea, useXScale } from "@mui/x-charts";
|
||||
import { ScaleLinear } from "d3-scale";
|
||||
import { useEffect } from "react";
|
||||
import MaxSpeed from "../Data/MaxSpeed";
|
||||
import MaxSpeed from "../Data/Api/Models/MaxSpeed";
|
||||
import IChartPoint from "../Data/IChartPoint";
|
||||
|
||||
// Remarks:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import Points from "./Points";
|
||||
import Tracks from "./Tracks";
|
||||
|
||||
const apiUrl: string = import.meta.env.VITE_API_URL as string;
|
||||
|
||||
export default class ApiEndpoints
|
||||
{
|
||||
public Points: Points;
|
||||
public Tracks: Tracks;
|
||||
|
||||
constructor()
|
||||
{
|
||||
this.Points = new Points(apiUrl);
|
||||
this.Tracks = new Tracks(apiUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import IPoint from "../../Models/IPoint";
|
||||
|
||||
export default interface IGetPointsResponse
|
||||
{
|
||||
points: IPoint[],
|
||||
totalCount: number,
|
||||
count: number,
|
||||
page: number
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export default class UpsertPointRequest
|
||||
{
|
||||
public name: string;
|
||||
public height: number;
|
||||
|
||||
constructor(name: string, height: number)
|
||||
{
|
||||
if (name.length < 1)
|
||||
throw new Error("Name must be at least 1 character long");
|
||||
|
||||
if (height !== Math.floor(height))
|
||||
throw new Error("Height must be an integer");
|
||||
|
||||
this.name = name;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import MaxSpeed from "../../Models/MaxSpeed";
|
||||
import Surface from "../../Models/Surface";
|
||||
|
||||
export default class UpsertTrackRequest
|
||||
{
|
||||
public firstId: number;
|
||||
public secondId: number;
|
||||
public distance: number;
|
||||
public surface: Surface;
|
||||
public maxSpeed: MaxSpeed;
|
||||
|
||||
constructor(firstId: number, secondId: number, distance: number, surface: number, maxSpeed: number)
|
||||
{
|
||||
if (firstId < 0)
|
||||
throw new Error("First ID must be at least 0");
|
||||
|
||||
if (secondId < 0)
|
||||
throw new Error("Second ID must be at least 0");
|
||||
|
||||
if (distance < 1)
|
||||
throw new Error("Distance must be greater than 0");
|
||||
|
||||
this.firstId = firstId;
|
||||
this.secondId = secondId;
|
||||
this.distance = distance;
|
||||
this.surface = surface;
|
||||
this.maxSpeed = maxSpeed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import IGetPointsResponse from "./Contracts/Point/IGetPointsResponse";
|
||||
import UpsertPointRequest from "./Contracts/Point/UpsertPointRequest";
|
||||
import IPoint from "./Models/IPoint";
|
||||
|
||||
export default class Points
|
||||
{
|
||||
private apiUrl: string;
|
||||
|
||||
constructor(apiUrl: string)
|
||||
{
|
||||
this.apiUrl = apiUrl;
|
||||
}
|
||||
|
||||
public async CreatePointAsync(request: UpsertPointRequest): Promise<IPoint>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + "/points", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
});
|
||||
|
||||
const data: IPoint = await response.json() as IPoint;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async GetPointsArrayAsync(ids: number[]): Promise<IPoint[]>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + "/points/array", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(ids)
|
||||
});
|
||||
|
||||
const data: IPoint[] = await response.json() as IPoint[];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async GetPointsAsync(page?: number, count?: number): Promise<IGetPointsResponse>
|
||||
{
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (page)
|
||||
params.append("page", page.toString());
|
||||
|
||||
if (count)
|
||||
params.append("count", count.toString());
|
||||
|
||||
const response: Response = await fetch(this.apiUrl + `/points?${params.toString()}`);
|
||||
const data: IGetPointsResponse = await response.json() as IGetPointsResponse;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async GetPointAsync(id: number): Promise<IPoint>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + `/points/${id}`);
|
||||
const data: IPoint = await response.json() as IPoint;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async UpsertPointAsync(id: number, request: UpsertPointRequest): Promise<IPoint | null>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + `/points/${id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
});
|
||||
|
||||
if (response.status === 204)
|
||||
return null;
|
||||
|
||||
const data: IPoint = await response.json() as IPoint;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async DeletePointAsync(id: number): Promise<void>
|
||||
{
|
||||
await fetch(this.apiUrl + `/points/${id}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
public async ImportPointsAsync(points: IPoint[]): Promise<IPoint[]>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + "/points/import", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(points)
|
||||
});
|
||||
|
||||
const data: IPoint[] = await response.json() as IPoint[];
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import UpsertTrackRequest from "./Contracts/Track/UpsertTrackRequest";
|
||||
import ITrack from "./Models/ITrack";
|
||||
|
||||
export default class Tracks
|
||||
{
|
||||
private apiUrl: string;
|
||||
|
||||
constructor(apiUrl: string)
|
||||
{
|
||||
this.apiUrl = apiUrl;
|
||||
}
|
||||
|
||||
public async CreateTrackAsync(request: UpsertTrackRequest): Promise<ITrack>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + "/tracks", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
});
|
||||
|
||||
const data: ITrack = await response.json() as ITrack;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async GetTrackAsync(firstId: number, secondId: number): Promise<ITrack>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + `/tracks/${firstId}/${secondId}`);
|
||||
const data: ITrack = await response.json() as ITrack;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async GetAllTracksAsync(): Promise<ITrack[]>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + "/tracks");
|
||||
const data: ITrack[] = await response.json() as ITrack[];
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async UpsertTrackAsync(
|
||||
firstId: number,
|
||||
secondId: number,
|
||||
request: UpsertTrackRequest
|
||||
): Promise<ITrack | null>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + `/tracks/${firstId}/${secondId}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(request)
|
||||
});
|
||||
|
||||
if (response.status === 204)
|
||||
return null;
|
||||
|
||||
const data: ITrack = await response.json() as ITrack;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public async DeleteTrackAsync(firstId: number, secondId: number): Promise<void>
|
||||
{
|
||||
await fetch(this.apiUrl + `/tracks/${firstId}/${secondId}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
}
|
||||
|
||||
public async ImportTracksAsync(tracks: ITrack[]): Promise<ITrack[]>
|
||||
{
|
||||
const response: Response = await fetch(this.apiUrl + "/tracks/import", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(tracks)
|
||||
});
|
||||
|
||||
const data: ITrack[] = await response.json() as ITrack[];
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* A class that generates random numbers from a Gaussian distribution.
|
||||
*/
|
||||
export default class GaussianGenerator
|
||||
{
|
||||
private mean: number;
|
||||
private stdDev: number;
|
||||
|
||||
/**
|
||||
* Constructs a GaussianGenerator object with the specified mean and standard deviation.
|
||||
* @param mean The mean of the Gaussian distribution.
|
||||
* @param stdDev The standard deviation of the Gaussian distribution.
|
||||
*/
|
||||
constructor(mean: number, stdDev: number)
|
||||
{
|
||||
this.mean = mean;
|
||||
this.stdDev = stdDev;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next random number from the Gaussian distribution.
|
||||
* @returns The next random number from the Gaussian distribution.
|
||||
*/
|
||||
NextGaussian(): number
|
||||
{
|
||||
let u1 = 0, u2 = 0;
|
||||
while (u1 === 0) u1 = Math.random(); // Converting [0,1) to (0,1)
|
||||
while (u2 === 0) u2 = Math.random();
|
||||
const randStdNormal = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
|
||||
const randNormal = this.mean + this.stdDev * randStdNormal; // random normal(mean,stdDev^2)
|
||||
return Math.floor(randNormal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of random numbers from the Gaussian distribution.
|
||||
* @param n The number of random numbers to generate.
|
||||
* @returns An array of random numbers from the Gaussian distribution.
|
||||
*/
|
||||
Generate(n: number): number[]
|
||||
{
|
||||
const arr = [];
|
||||
for (let i = 0; i < n; i++)
|
||||
{
|
||||
arr.push(this.NextGaussian());
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import MaxSpeed from "./MaxSpeed";
|
||||
import Surface from "./Surface";
|
||||
import MaxSpeed from "./Api/Models/MaxSpeed";
|
||||
import Surface from "./Api/Models/Surface";
|
||||
|
||||
/** Represents an aggregated point on the chart. */
|
||||
export default interface IChartPoint
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import IPoint from "./IPoint";
|
||||
import ITrack from "./ITrack";
|
||||
|
||||
/**
|
||||
* Returns a random number between min and max
|
||||
* @param min The minimum value (inclusive)
|
||||
* @param max The maximum value (exclusive)
|
||||
* @returns A random number between min and max
|
||||
*/
|
||||
const getRandom = (min: number, max: number): number =>
|
||||
Math.floor(Math.random() * (max - min)) + min;
|
||||
|
||||
const pointNames: string[] =
|
||||
[
|
||||
"Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India",
|
||||
"Juliett", "Kilo", "Lima", "Mike", "November", "Oscar", "Papa", "Quebec", "Romeo",
|
||||
"Sierra", "Tango", "Uniform", "Victor", "Whiskey", "X-ray", "Yankee", "Zulu"
|
||||
];
|
||||
|
||||
function LoadMockData(largeCount?: boolean): { tracks: ITrack[], points: IPoint[]; }
|
||||
{
|
||||
const count: number = getRandom(10, 20) * (largeCount ? 10 : 1);
|
||||
const points: IPoint[] = [];
|
||||
const tracks: ITrack[] = [];
|
||||
|
||||
// Generate random data
|
||||
|
||||
for (let i = 0; i < count; i++)
|
||||
{
|
||||
points.push({
|
||||
id: i,
|
||||
name: `${pointNames[getRandom(0, pointNames.length)]}-${i}`,
|
||||
height: getRandom(50, 200)
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < count - 1; i++)
|
||||
{
|
||||
tracks.push({
|
||||
firstId: points[i].id,
|
||||
secondId: points[i + 1].id,
|
||||
distance: getRandom(1000, 2000),
|
||||
surface: getRandom(0, 3),
|
||||
maxSpeed: getRandom(0, 3)
|
||||
});
|
||||
}
|
||||
|
||||
return { tracks, points };
|
||||
}
|
||||
|
||||
export default LoadMockData;
|
||||
@@ -0,0 +1,49 @@
|
||||
import IPoint from "./Api/Models/IPoint";
|
||||
import ITrack from "./Api/Models/ITrack";
|
||||
import MaxSpeed from "./Api/Models/MaxSpeed";
|
||||
import Surface from "./Api/Models/Surface";
|
||||
import GaussianGenerator from "./GaussianGenerator";
|
||||
|
||||
export function GeneratePoints(count: number): IPoint[]
|
||||
{
|
||||
const generator = new GaussianGenerator(160, 10);
|
||||
|
||||
const points: IPoint[] = generator.Generate(count).map((height, index) => ({
|
||||
id: index + 1,
|
||||
name: `${pointNames[index % pointNames.length]}-${index + 1}`,
|
||||
height
|
||||
}));
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
export function GenerateTracks(points: IPoint[]): ITrack[]
|
||||
{
|
||||
const generator = new GaussianGenerator(2000, 500);
|
||||
|
||||
const tracks: ITrack[] = generator.Generate(points.length - 1).map((distance, index) => ({
|
||||
firstId: points[index].id,
|
||||
secondId: points[index + 1].id,
|
||||
distance,
|
||||
surface: getRandom(0, 3) as Surface,
|
||||
maxSpeed: getRandom(0, 3) as MaxSpeed
|
||||
}));
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random number between min and max
|
||||
* @param min The minimum value (inclusive)
|
||||
* @param max The maximum value (exclusive)
|
||||
* @returns A random number between min and max
|
||||
*/
|
||||
const getRandom = (min: number, max: number): number =>
|
||||
Math.floor(Math.random() * (max - min)) + min;
|
||||
|
||||
const pointNames: string[] =
|
||||
[
|
||||
"Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India",
|
||||
"Juliett", "Kilo", "Lima", "Mike", "November", "Oscar", "Papa", "Quebec", "Romeo",
|
||||
"Sierra", "Tango", "Uniform", "Victor", "Whiskey", "X-ray", "Yankee", "Zulu"
|
||||
];
|
||||
@@ -1,8 +1,8 @@
|
||||
import { green, grey, orange, red, yellow } from "@mui/material/colors";
|
||||
import { AllSeriesType, ChartsAxisContentProps } from "@mui/x-charts";
|
||||
import MaxSpeed from "./Api/Models/MaxSpeed";
|
||||
import Surface from "./Api/Models/Surface";
|
||||
import IChartPoint from "./IChartPoint";
|
||||
import MaxSpeed from "./MaxSpeed";
|
||||
import Surface from "./Surface";
|
||||
|
||||
/** Props for rendering trackline segments. */
|
||||
export const TracklineSeries: AllSeriesType[] = [
|
||||
|
||||
@@ -859,6 +859,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||
|
||||
"@types/node@^20.11.20":
|
||||
version "20.11.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.20.tgz#f0a2aee575215149a62784210ad88b3a34843659"
|
||||
integrity sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
|
||||
@@ -2906,6 +2913,11 @@ unbox-primitive@^1.0.2:
|
||||
has-symbols "^1.0.3"
|
||||
which-boxed-primitive "^1.0.2"
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
|
||||
update-browserslist-db@^1.0.13:
|
||||
version "1.0.13"
|
||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
|
||||
|
||||
Reference in New Issue
Block a user