Skip to content

Commit

Permalink
Type fixing
Browse files Browse the repository at this point in the history
  • Loading branch information
mrkthmpsn committed May 7, 2024
1 parent 61a1c7b commit ca0059c
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 260 deletions.
1 change: 0 additions & 1 deletion app/label-unlabelled-statsbomb/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import PageWrapper from "@/components/pageWrapper";
import markdown from "./label-unlabelled-statsbomb.md";
import ProjectPageWrapper from "@/components/projectPageWrapper";
import statsbombLabellingThumb from "public/images/home_page_thumbs/statsbomb_labelling_thumb.png";
Expand Down
140 changes: 91 additions & 49 deletions app/old_page.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,104 @@
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import './pages-styles.css';
import Head from 'next/head';
import CustomNavbar from '@/components/navbar';
import HomePageCard from '@/components/homePageCard';
import analyticsLibraryThumb from '@/public/images/home_page_thumbs/analytics_library_thumb.png';
import xGTimelineThumb from '@/public/images/home_page_thumbs/xg_timeline_design_thumb.png';
import codingTutorialsThumb from '@/public/images/home_page_thumbs/coding_tutorials_thumb.png';
import realMadridD3Thumb from '@/public/images/home_page_thumbs/real_madrid_d3_thumb.png';
import trackingDataProjectThumb from '@/public/images/home_page_thumbs/tracking_data_thumb.png';
import getGoalsideThumb from '@/public/images/home_page_thumbs/get_goalside_thumb.png';
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import "./pages-styles.css";
import Head from "next/head";
import CustomNavbar from "@/components/navbar";
import HomePageCard from "@/components/homePageCard";
import analyticsLibraryThumb from "@/public/images/home_page_thumbs/analytics_library_thumb.png";
import xGTimelineThumb from "@/public/images/home_page_thumbs/xg_timeline_design_thumb.png";
import codingTutorialsThumb from "@/public/images/home_page_thumbs/coding_tutorials_thumb.png";
import realMadridD3Thumb from "@/public/images/home_page_thumbs/real_madrid_d3_thumb.png";
import trackingDataProjectThumb from "@/public/images/home_page_thumbs/tracking_data_thumb.png";
import statsbombLabellingThumb from "public/images/home_page_thumbs/statsbomb_labelling_thumb.png";

const HomePage = () => {
return (
<>
<Head>
<title>Mark Thompson.</title>
</Head>
<>
<Head>
<title>Mark Thompson.</title>
</Head>

<div>
<CustomNavbar />
</div>


<div>
<br />
<h3 style={{ textAlign: "center", paddingBottom: "2px" }}>
<i>When your hands are busy holding an idea they can't type.</i>
</h3>
<p style={{ textAlign: 'right' }}>-- Seth Godin</p>
<br />
<p>
Hi, I'm Mark. <b>Mark Thompson</b>. I make stuff, make sense of stuff, or make sense of it for others.
</p>
<p>
So far, that's mostly been in the world of football, but Python, data science, and a good attitude go a long way.
</p>
<p>
You can find some of my projects (on football data insights, spatio-temporal data, data visualisation design, React and Django Rest Framework use) below:
</p>

<HomePageCard emoji='🎨' linkUrl='https://www.twenty3.sport/twenty3-introducing-our-xg-timelines-visualisations/' linkExternal={true} anchorText='Article: Designing a data visualisation for media & professional
football use' descriptionText="A blog for Twenty3 on the design of our take on an xG (expected goals) timeline data visualisation, with considerations for multiple audiences. [External link]" imageUrl={xGTimelineThumb.src} />

<HomePageCard emoji='🧤' linkUrl='/goalkeeper-tracking' linkExternal={false} anchorText="Working with spatio-temporal tracking data to investigate goalkeeper pressure" descriptionText="A project write-up on the processing, ideation, and repurposing of prior work, pivoting from a previous idea to looking at how goalkeepers respond to pressure when on the ball in football." imageUrl={trackingDataProjectThumb.src} />
<div>
<CustomNavbar />
</div>

<HomePageCard emoji='📚' linkUrl='/analytics-library' linkExternal={false} anchorText='Creating a personal football analytics library app with React and Django' descriptionText='Write-up of a project to create an interface for a personal football analytics library database, with files stored in and retrieved from cloud storage.' imageUrl={analyticsLibraryThumb.src} />
<div>
<br />
<h3 style={{ textAlign: "center", paddingBottom: "2px" }}>
<i>When your hands are busy holding an idea they can't type.</i>
</h3>
<p style={{ textAlign: "right" }}>-- Seth Godin</p>
<br />
<p>
Hi, I'm Mark. <b>Mark Thompson</b>. I make stuff, make sense of stuff,
or make sense of it for others.
</p>
<p>
So far, that's mostly been in the world of football, but Python, data
science, and a good attitude go a long way.
</p>
<p>
You can find some of my projects (on football data insights,
spatio-temporal data, data visualisation design, React and Django Rest
Framework use) below:
</p>

<HomePageCard emoji='🏷️' linkUrl='https://www.getgoalsideanalytics.com/high-fat-data-for-low-er-fat-costs/' linkExternal={true} anchorText="Creating labels from unlabelled data - football positions and Statsbomb 360" descriptionText="An edition of the 'Get Goalside' newsletter aiming to take existing analytics research and apply it to a slightly different use-case, creating positional labels from unlabelled data. [External link]" imageUrl={statsbombLabellingThumb.src} />
<HomePageCard
emoji="🎨"
linkUrl="https://www.twenty3.sport/twenty3-introducing-our-xg-timelines-visualisations/"
linkExternal={true}
anchorText="Article: Designing a data visualisation for media & professional
football use"
descriptionText="A blog for Twenty3 on the design of our take on an xG (expected goals) timeline data visualisation, with considerations for multiple audiences. [External link]"
imageUrl={xGTimelineThumb.src}
/>

<HomePageCard emoji='👨‍‍🏫' linkUrl='https://mrkthmpsn-streamlit-coding-tutorial-home-wk3wn4.streamlit.app/' linkExternal={true} anchorText='Learn-to-code with football data tutorials, using Streamlit' descriptionText="A mini-site of tutorials to help people learn to code using football data (from the website FBref), using the Python framework Streamlit. [External link]" imageUrl={codingTutorialsThumb.src} />
<HomePageCard
emoji="🧤"
linkUrl="/goalkeeper-tracking"
linkExternal={false}
anchorText="Working with spatio-temporal tracking data to investigate goalkeeper pressure"
descriptionText="A project write-up on the processing, ideation, and repurposing of prior work, pivoting from a previous idea to looking at how goalkeepers respond to pressure when on the ball in football."
imageUrl={trackingDataProjectThumb.src}
/>

<HomePageCard emoji='📊' linkUrl='/real-madrid' linkExternal={false} anchorText="Article: 'The renewal of Real Madrid' (experimenting with d3)" descriptionText="An article from July 2022 about the turnover of Real Madrid men's squad
featuring some interactive d3 visualisations." imageUrl={realMadridD3Thumb.src} />
<HomePageCard
emoji="📚"
linkUrl="/analytics-library"
linkExternal={false}
anchorText="Creating a personal football analytics library app with React and Django"
descriptionText="Write-up of a project to create an interface for a personal football analytics library database, with files stored in and retrieved from cloud storage."
imageUrl={analyticsLibraryThumb.src}
/>

</div>

<HomePageCard
emoji="🏷️"
linkUrl="https://www.getgoalsideanalytics.com/high-fat-data-for-low-er-fat-costs/"
linkExternal={true}
anchorText="Creating labels from unlabelled data - football positions and Statsbomb 360"
descriptionText="An edition of the 'Get Goalside' newsletter aiming to take existing analytics research and apply it to a slightly different use-case, creating positional labels from unlabelled data. [External link]"
imageUrl={statsbombLabellingThumb.src}
/>

<HomePageCard
emoji="👨‍‍🏫"
linkUrl="https://mrkthmpsn-streamlit-coding-tutorial-home-wk3wn4.streamlit.app/"
linkExternal={true}
anchorText="Learn-to-code with football data tutorials, using Streamlit"
descriptionText="A mini-site of tutorials to help people learn to code using football data (from the website FBref), using the Python framework Streamlit. [External link]"
imageUrl={codingTutorialsThumb.src}
/>

<HomePageCard
emoji="📊"
linkUrl="/real-madrid"
linkExternal={false}
anchorText="Article: 'The renewal of Real Madrid' (experimenting with d3)"
descriptionText="An article from July 2022 about the turnover of Real Madrid men's squad
featuring some interactive d3 visualisations."
imageUrl={realMadridD3Thumb.src}
/>
</div>
</>
);
};
Expand Down
2 changes: 2 additions & 0 deletions app/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import React from "react";

import "@/app/pages-styles.css";
import "bootstrap/dist/css/bootstrap.min.css";

Expand Down
2 changes: 1 addition & 1 deletion components/pageWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import CustomNavbar from "./navbar";

interface PageWrapperProps {
pageTitle: string;
pageMarkdown: any;
pageMarkdown: string;
isCategoryPage: boolean;
}

Expand Down
18 changes: 15 additions & 3 deletions components/pitchAnimation/basicScrubber.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import React from "react";
import React, { ChangeEvent } from "react";

const VideoScrubber = ({ min, max, onChange, currentIndex }) => {
const handleSliderChange = (event) => {
interface VideoScrubberProps {
min: number;
max: number;
onChange: CallableFunction;
currentIndex: number;
}

const VideoScrubber: React.FC<VideoScrubberProps> = ({
min,
max,
onChange,
currentIndex,
}) => {
const handleSliderChange = (event: ChangeEvent<HTMLInputElement>) => {
const newFrame = parseInt(event.target.value, 10);
onChange(newFrame);
};
Expand Down
54 changes: 17 additions & 37 deletions components/pitchAnimation/pitchAnimation.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import React, { useEffect } from "react";
import React, { RefObject, useEffect } from "react";
import * as d3 from "d3";
import {
Coordinate,
DefensiveBlockData,
InBlockOpportunityList,
PlayerAnimationData,
} from "../../types/pitchControl";
import { BallPosition, DefensiveBlock } from "../../types/api";
import { ObjectPosition, DefensiveBlock, teamPlayersType } from "@/data/types";
import * as d3_soccer from "d3-soccer";

// Should the pitch control info all be part of the same frame data, or supplied as separate objects?
interface PitchControlAnimationProps {
teamAData: PlayerAnimationData[];
teamBData: PlayerAnimationData[];
rootRef;
teamAData: teamPlayersType;
teamBData: teamPlayersType;
rootRef: RefObject<HTMLDivElement | null>;
index: number;
ballData: Array<BallPosition | null> | null;
ballData: Array<ObjectPosition | null> | null;
defensiveBlockData: Array<DefensiveBlock | null> | null;
inBlockOpportunitiesData: InBlockOpportunityList[];
possessionPhase: string | null;
}

Expand All @@ -26,13 +19,12 @@ const PitchControlAnimation: React.FC<PitchControlAnimationProps> = ({
teamBData,
ballData,
defensiveBlockData,
inBlockOpportunitiesData,
rootRef,
index,
possessionPhase,
}) => {
useEffect(() => {
if (rootRef.current) {
if (rootRef?.current) {
const svgRef = d3.select(rootRef.current);
svgRef.selectAll("*").remove();

Expand All @@ -43,33 +35,19 @@ const PitchControlAnimation: React.FC<PitchControlAnimationProps> = ({
svgRef.call(pitch);

if (defensiveBlockData && defensiveBlockData[index]) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const defensiveBlock: DefensiveBlock = defensiveBlockData[index]!;
svgRef
.selectAll("#above")
.append("rect")
.attr("x", defensiveBlockData[index].left)
.attr("y", defensiveBlockData[index].top)
.attr(
"width",
defensiveBlockData[index].right - defensiveBlockData[index].left
)
.attr(
"height",
defensiveBlockData[index].bottom - defensiveBlockData[index].top
)
.attr("x", defensiveBlock.left)
.attr("y", defensiveBlock.top)
.attr("width", defensiveBlock.right - defensiveBlock.left)
.attr("height", defensiveBlock.bottom - defensiveBlock.top)
.style("fill", possessionPhase === "FIFATMA" ? "#bfa660" : "#899dcb")
.style("fill-opacity", 0.3);
}

if (inBlockOpportunitiesData[index]) {
inBlockOpportunitiesData[index].forEach((oppData) => {
svgRef
.select("#above")
.append("polygon")
.attr("points", oppData)
.attr("fill", "yellow")
.attr("fill-opacity", 0.5);
});
}
Object.values(teamAData).map((player) => {
svgRef
.select("#above")
Expand All @@ -92,11 +70,13 @@ const PitchControlAnimation: React.FC<PitchControlAnimationProps> = ({
.attr("id", player.id);
});
if (ballData && ballData[index]) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const ball: ObjectPosition = ballData[index]!;
svgRef
.select("#above")
.append("circle")
.attr("cx", ballData[index].x)
.attr("cy", ballData[index].y)
.attr("cx", ball.x)
.attr("cy", ball.y)
.attr("fill", "white")
.attr("stroke", "black")
.attr("stroke-width", 0.5)
Expand Down
28 changes: 13 additions & 15 deletions components/pitchAnimation/pitchScreenWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { useEffect, useState, Dispatch, SetStateAction } from "react";
import { useEffect, useState } from "react";
import PitchWrapper from "./pitchWrapper";
import React from "react";
import VideoScrubber from "./basicScrubber";
import { BallPosition, DefensiveBlock } from "@/data/types";
import {
ObjectPosition,
DefensiveBlock,
teamPlayersType,
FramesDataType,
} from "@/data/types";
import { framesData } from "@/data/framesData";
import "@/app/pages-styles.css";

Expand All @@ -14,7 +19,7 @@ const PitchScreenWrapper = () => {
const totalFrames = 120;

useEffect(() => {
let intervalId;
let intervalId: NodeJS.Timer;
if (isPlaying) {
intervalId = setInterval(() => {
setFrameIndex((currentIndex) => (currentIndex + 1) % totalFrames);
Expand All @@ -31,8 +36,8 @@ const PitchScreenWrapper = () => {
setIsPlaying(!isPlaying);
};

const convertPlayersData = (framesData, teamName) => {
let teamPlayersResult = {};
const convertPlayersData = (framesData: FramesDataType, teamName: string) => {
const teamPlayersResult: teamPlayersType = {};

// Iterate over each key in the main data object
for (const key in framesData) {
Expand Down Expand Up @@ -66,11 +71,10 @@ const PitchScreenWrapper = () => {
const [wrapperTeamA, setWrapperTeamA] = useState({});
const [wrapperTeamB, setWrapperTeamB] = useState({});
const [wrapperBallPosition, setWrapperBallPosition] =
useState<Array<BallPosition | null> | null>(null);
useState<Array<ObjectPosition | null> | null>(null);
const [wrapperDefensiveBlock, setWrapperDefensiveBlock] =
useState<Array<DefensiveBlock | null> | null>(null);
const [wrapperInBlockOpportunities, setWrapperInBlockOpportunities] =
useState([]);

useEffect(() => {
const wrapperTeamA = framesData
? convertPlayersData(framesData, "FIFATMA")
Expand All @@ -88,17 +92,12 @@ const PitchScreenWrapper = () => {
return frame.defensive_block;
})
: null;
const wrapperInBlockOpportunities = framesData
? Object.values(framesData).map((frame) => {
return frame.in_block_opportunities;
})
: [];

setWrapperPitchControl(wrapperPitchControl);
setWrapperTeamA(wrapperTeamA);
setWrapperTeamB(wrapperTeamB);
setWrapperBallPosition(wrapperBallPosition);
setWrapperDefensiveBlock(wrapperDefensiveBlock);
setWrapperInBlockOpportunities(wrapperInBlockOpportunities);
}, [framesData]);

return (
Expand All @@ -122,7 +121,6 @@ const PitchScreenWrapper = () => {
teamBData={wrapperTeamB}
ballData={wrapperBallPosition}
defensiveBlockData={wrapperDefensiveBlock}
inBlockOpportunitiesData={wrapperInBlockOpportunities}
index={frameIndex}
possessionPhase={
framesData
Expand Down
Loading

0 comments on commit ca0059c

Please sign in to comment.