Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/components/LapChart/LapChart.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.lapChartWrapper {
max-height: 100%;
max-width: 100%;
}
113 changes: 113 additions & 0 deletions frontend/components/LapChart/LapChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from "chart.js";

import isEqual from "lodash.isequal";

import styles from "./LapChart.module.scss";

ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
import { Line } from "react-chartjs-2";
import { useRef, useState } from "react";
import { contextType, LapChartProps } from "./LapChartTypes";
import { compoundStringToColour, createDataObjectFromLapData } from "./LapChartHelpers";


const LapChart: React.FC<LapChartProps> = (props: LapChartProps) => { //TODO: endpoint data types
const handleToolTip = (context: contextType) => {
const tooltipModel = context.tooltip;
if (!chart || !chart.current) return;

if (tooltipModel.opacity === 0) {
if (tooltip.opacity !== 0) setTooltip(prev => ({ ...prev, opacity: 0 }));
return;
}
const position = context.chart.canvas.getBoundingClientRect();
const CUR_DRIVER_SHORT_NAME: string = tooltipModel.dataPoints[0].dataset.label as string;
const newTooltipData = {
opacity: 1,
left: position.left + tooltipModel.caretX,
top: position.top + tooltipModel.caretY,
lap: tooltipModel.dataPoints[0].label,
position: tooltipModel.dataPoints[0].formattedValue,
colour: tooltipModel.dataPoints[0].dataset.borderColor as string,
border: (tooltipModel?.dataPoints[0]?.dataset?.borderDash as number[])[0] == 0 ? "solid" : "dashed",
longname: props.lapData[CUR_DRIVER_SHORT_NAME].Name,
compound: props.lapData[CUR_DRIVER_SHORT_NAME].Compounds[tooltipModel.dataPoints[0].dataIndex],
laptime: props.lapData[CUR_DRIVER_SHORT_NAME].LapTimes[tooltipModel.dataPoints[0].dataIndex],

};
if (!isEqual(tooltip, newTooltipData)) setTooltip(newTooltipData);
};

// TODO: SPINNER
if (!Object.entries(props.lapData)) {
return <div style={{ color: "red" }}>NO LAP DATA</div>;
}
// eslint-disable-next-line react-hooks/rules-of-hooks
const chart = useRef(null); //create reference hook
// eslint-disable-next-line react-hooks/rules-of-hooks
const [tooltip, setTooltip] = useState({
opacity: 0,
top: 0,
left: 0,
lap: "",
position: "",
colour: "#000",
border: "solid",
longname: "tempdrivername",
compound: "tempcompound",
laptime: "templaptime"
});
const lapData = createDataObjectFromLapData(props.lapData);
const title: string = props.name;
return (
<div id='lap-chart-wrapper' className={styles.lapChartWrapper}>
<Line data={lapData} ref={chart}
options={{

scales: { y: { position: "left", reverse: true, ticks: { autoSkip: false, stepSize: 1 } } },
plugins: {
legend: { position: "right" }, title: { text: title, display: true, color: "white", fullSize: true },
tooltip: {
enabled: false,
external: context => {
handleToolTip(context);
},
}
},

}}
>

</Line>

<div className='tooltip' style={{ backgroundColor: "rgb(255,255,255,0.5)", padding: "5px", borderRadius: "5px", position: "absolute", top: tooltip.top, left: tooltip.left, opacity: tooltip.opacity, borderRight: `5px ${tooltip.border} ${tooltip.colour}` }}>
<p>{tooltip.longname}</p>
<p>Lap {tooltip.lap} </p>
<p>Position {tooltip.position} </p>
<p>Laptime: {tooltip.laptime} </p>
<p style={{ border: `3px solid ${compoundStringToColour(tooltip.compound)}` }}>Compound {tooltip.compound} </p>

</div>
</div>
);

};

export default LapChart;
56 changes: 56 additions & 0 deletions frontend/components/LapChart/LapChartHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Driver, Racers } from "./LapChartTypes";


export function createDataObjectFromLapData(lapData: Racers) {

const datasets: { label: string; data: number[]; backgroundColor: string; borderColor: string; borderWidth: number; borderDash: number[]; pointRadius: number; pointBorderColor: string; pointBackgroundColor: string; pointHoverBorderColor: string; pointHoverBackgroundColor: string; }[] = [];
const encounteredTeams: string[] = [];

let maxLaps = [0];
const lapDataArray = Object.entries(lapData);
lapDataArray.forEach((driverEntry: any) => {
const smallLabel: string = driverEntry[0];
const driverLapData: Driver = driverEntry[1];
if (driverLapData.Laps.length > maxLaps.length) {
maxLaps = driverLapData.Laps;
}
let encounteredTeam = false;
if (encounteredTeams.includes(driverLapData.TeamColor)) {
encounteredTeam = true;
} else {
encounteredTeams.push(driverLapData.TeamColor);
}
datasets.push({
label: smallLabel,
data: driverLapData.Positions,
backgroundColor: `#${driverLapData.TeamColor}`,
borderColor: `#${driverLapData.TeamColor}`,
borderWidth: 1,
borderDash: encounteredTeam ? [5, 5] : [0, 0],
pointRadius: 10,
pointBorderColor: "rgba(0, 0, 0, 0)",
pointBackgroundColor: "rgba(0, 0, 0, 0)",
pointHoverBorderColor: `#${driverLapData.TeamColor}`,
pointHoverBackgroundColor: `#${driverLapData.TeamColor}`,

});
});
const data = {
labels: maxLaps,
datasets,
};
return data;
}

export function compoundStringToColour(compoundString: string) {
switch (compoundString) {
case "SOFT":
return "red";
case "HARD":
return "white";
case "MEDIUM":
return "yellow";
default:
return "none";
}
}
29 changes: 29 additions & 0 deletions frontend/components/LapChart/LapChartTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
Chart as ChartJS,
TooltipModel,
ChartTypeRegistry,
BubbleDataPoint,
ScatterDataPoint,
} from "chart.js";

export type LapChartProps = {
name: string,
lapData: Racers
}
export type Driver = {
Name: string;
Positions: number[];
Laps: number[];
TeamColor: string;
Compounds: string[];
LapTimes: any[];
}
export type Racers = {
[key: string]: Driver
}

export type contextType = {
chart: ChartJS<keyof ChartTypeRegistry, (number | ScatterDataPoint | BubbleDataPoint | null)[], unknown>;
tooltip: TooltipModel<"line">;
}

45 changes: 45 additions & 0 deletions frontend/components/Selectors/BaseSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

import { Select } from "@canonical/react-components";
import axios from "axios";
import { useEffect, useState } from "react";


const BaseSelector = (props: any) => {
const callback = props.callback;
const name = props.name;
const ind = props.ind;
const apiPath = props.apiPath;
const [years, setYears] = useState([{ value: 0, label: `Select a ${name}`, disabled: true }]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
axios.get("/api/years/")
.then((res) => {
const temp = [];
for (var year in res.data[ind]) {
temp.push({ value: parseInt(res.data[ind][year]), label: res.data[ind][year], disabled: false });
};
setYears(temp);
setLoading(false);
});
}, []);

const handleChange = (event: any) => {

callback(event.target[event.target.selectedIndex].value);
};

return (
<>
{(loading ? (
<p>Loading...</p>
) : (
<div id={`${name}-selector-wrapper`}>
<Select defaultValue={0} onChange={handleChange} options={years} />
</div>
))}
</>
);
};

export default BaseSelector;
48 changes: 0 additions & 48 deletions frontend/components/YearSelector.tsx

This file was deleted.

14 changes: 14 additions & 0 deletions frontend/components/YearSelector/YearSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

import BaseSelector from "../Selectors/BaseSelector";


const YearSelector = (props: any) => {
const setYear = props.setYear;

return(
<BaseSelector callback={setYear} name="year" ind="years" apiPath="/api/years/"></BaseSelector>

);
};

export default YearSelector;
9 changes: 8 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,19 @@
"@typescript-eslint/eslint-plugin": "^5.38.0",
"@typescript-eslint/parser": "^5.38.0",
"axios": "^0.27.2",
"chart.js": "^3.0.0",
"loadsh": "^0.0.4",
"lodash": "^4.17.21",
"lodash.isequal": "^4.5.0",
"next": "12.2.3",
"prettier": "^2.7.1",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-chartjs-2": "^4.3.1",
"react-dom": "18.2.0",
"sass": "^1.60.0"
},
"devDependencies": {
"@types/lodash.isequal": "^4.5.6",
"@types/node": "18.6.3",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
Expand Down
Loading